Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

PollCreate.js 5.9KB

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