You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PollCreate.js 6.4KB

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