Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

PollCreate.js 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. // @flow
  2. import React, { useCallback, useEffect, useRef, useState } from 'react';
  3. import { FlatList, Platform, Text, TextInput, View } from 'react-native';
  4. import { Divider, TouchableRipple } from 'react-native-paper';
  5. import BaseTheme from '../../../base/ui/components/BaseTheme.native';
  6. import Button from '../../../base/ui/components/native/Button';
  7. import { BUTTON_TYPES } from '../../../base/ui/constants';
  8. import styles
  9. from '../../../settings/components/native/styles';
  10. import { ANSWERS_LIMIT, CHAR_LIMIT } from '../../constants';
  11. import AbstractPollCreate from '../AbstractPollCreate';
  12. import type { AbstractProps } from '../AbstractPollCreate';
  13. import { chatStyles, dialogStyles } from './styles';
  14. const PollCreate = (props: AbstractProps) => {
  15. const {
  16. addAnswer,
  17. answers,
  18. isSubmitDisabled,
  19. onSubmit,
  20. question,
  21. removeAnswer,
  22. setAnswer,
  23. setCreateMode,
  24. setQuestion,
  25. t
  26. } = props;
  27. const answerListRef = useRef(null);
  28. /*
  29. * This ref stores the Array of answer input fields, allowing us to focus on them.
  30. * This array is maintained by registerFieldRef and the useEffect below.
  31. */
  32. const answerInputs = useRef([]);
  33. const registerFieldRef = useCallback((i, input) => {
  34. if (input === null) {
  35. return;
  36. }
  37. answerInputs.current[i] = input;
  38. },
  39. [ answerInputs ]
  40. );
  41. useEffect(() => {
  42. answerInputs.current = answerInputs.current.slice(0, answers.length);
  43. }, [ answers ]);
  44. /*
  45. * This state allows us to requestFocus asynchronously, without having to worry
  46. * about whether a newly created input field has been rendered yet or not.
  47. */
  48. const [ lastFocus, requestFocus ] = useState(null);
  49. const { PRIMARY, SECONDARY } = BUTTON_TYPES;
  50. useEffect(() => {
  51. if (lastFocus === null) {
  52. return;
  53. }
  54. const input = answerInputs.current[lastFocus];
  55. if (input === undefined) {
  56. return;
  57. }
  58. input.focus();
  59. }, [ answerInputs, lastFocus ]);
  60. const onQuestionKeyDown = useCallback(() => {
  61. answerInputs.current[0].focus();
  62. });
  63. // Called on keypress in answer fields
  64. const onAnswerKeyDown = useCallback((index: number, ev) => {
  65. const { key } = ev.nativeEvent;
  66. const currentText = answers[index];
  67. if (key === 'Backspace' && currentText === '' && answers.length > 1) {
  68. removeAnswer(index);
  69. requestFocus(index > 0 ? index - 1 : 0);
  70. }
  71. }, [ answers, addAnswer, removeAnswer, requestFocus ]);
  72. /* eslint-disable react/no-multi-comp */
  73. const createRemoveOptionButton = onPress => (
  74. <TouchableRipple
  75. onPress = { onPress }
  76. rippleColor = { 'transparent' } >
  77. <Text style = { dialogStyles.optionRemoveButtonText }>
  78. { t('polls.create.removeOption') }
  79. </Text>
  80. </TouchableRipple>
  81. );
  82. /* eslint-disable react/jsx-no-bind */
  83. const renderListItem = ({ index }: { index: number }) =>
  84. // padding to take into account the two default options
  85. (
  86. <View
  87. style = { dialogStyles.optionContainer }>
  88. <Text style = { dialogStyles.optionFieldLabel }>
  89. { t('polls.create.pollOption', { index: index + 1 }) }
  90. </Text>
  91. <TextInput
  92. blurOnSubmit = { false }
  93. maxLength = { CHAR_LIMIT }
  94. multiline = { true }
  95. onChangeText = { text => setAnswer(index, text) }
  96. onKeyPress = { ev => onAnswerKeyDown(index, ev) }
  97. placeholder = { t('polls.create.answerPlaceholder', { index: index + 1 }) }
  98. placeholderTextColor = { BaseTheme.palette.text03 }
  99. ref = { input => registerFieldRef(index, input) }
  100. selectionColor = { BaseTheme.palette.action01 }
  101. style = { dialogStyles.field }
  102. value = { answers[index] } />
  103. {
  104. answers.length > 2
  105. && createRemoveOptionButton(() => removeAnswer(index))
  106. }
  107. </View>
  108. );
  109. const buttonRowStyles = Platform.OS === 'android'
  110. ? chatStyles.buttonRowAndroid : chatStyles.buttonRowIos;
  111. return (
  112. <View style = { chatStyles.pollCreateContainer }>
  113. <View style = { chatStyles.pollCreateSubContainer }>
  114. <Text style = { chatStyles.questionFieldLabel }>
  115. { t('polls.create.pollQuestion') }
  116. </Text>
  117. <TextInput
  118. autoFocus = { true }
  119. blurOnSubmit = { false }
  120. maxLength = { CHAR_LIMIT }
  121. multiline = { true }
  122. onChangeText = { setQuestion }
  123. onSubmitEditing = { onQuestionKeyDown }
  124. placeholder = { t('polls.create.questionPlaceholder') }
  125. placeholderTextColor = { BaseTheme.palette.text03 }
  126. selectionColor = { BaseTheme.palette.action01 }
  127. style = { dialogStyles.questionField }
  128. value = { question } />
  129. <Divider style = { styles.fieldSeparator } />
  130. <FlatList
  131. blurOnSubmit = { true }
  132. data = { answers }
  133. extraData = { answers }
  134. keyExtractor = { (item, index) => index.toString() }
  135. ref = { answerListRef }
  136. renderItem = { renderListItem } />
  137. <View style = { chatStyles.pollCreateButtonsContainer }>
  138. <Button
  139. accessibilityLabel = 'polls.create.addOption'
  140. disabled = { answers.length >= ANSWERS_LIMIT }
  141. labelKey = 'polls.create.addOption'
  142. onClick = { () => {
  143. // adding and answer
  144. addAnswer();
  145. requestFocus(answers.length);
  146. } }
  147. style = { chatStyles.pollCreateAddButton }
  148. type = { SECONDARY } />
  149. <View
  150. style = { buttonRowStyles }>
  151. <Button
  152. accessibilityLabel = 'polls.create.cancel'
  153. labelKey = 'polls.create.cancel'
  154. onClick = { () => setCreateMode(false) }
  155. style = { chatStyles.pollCreateButton }
  156. type = { SECONDARY } />
  157. <Button
  158. accessibilityLabel = 'polls.create.send'
  159. disabled = { isSubmitDisabled }
  160. labelKey = 'polls.create.send'
  161. onClick = { onSubmit }
  162. style = { chatStyles.pollCreateButton }
  163. type = { PRIMARY } />
  164. </View>
  165. </View>
  166. </View>
  167. </View>
  168. );
  169. };
  170. /*
  171. * We apply AbstractPollCreate to fill in the AbstractProps common
  172. * to both the web and native implementations.
  173. */
  174. // eslint-disable-next-line new-cap
  175. export default AbstractPollCreate(PollCreate);