jiti-meet/react/features/polls/components/web/PollCreate.tsx

261 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useCallback, useEffect, useRef, useState } from 'react';
import { makeStyles } from 'tss-react/mui';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
import Button from '../../../base/ui/components/web/Button';
import Input from '../../../base/ui/components/web/Input';
import { BUTTON_TYPES } from '../../../base/ui/constants.web';
import { ANSWERS_LIMIT, CHAR_LIMIT } from '../../constants';
import AbstractPollCreate, { AbstractProps } from '../AbstractPollCreate';
const useStyles = makeStyles()(theme => {
return {
container: {
height: '100%',
position: 'relative'
},
createContainer: {
padding: '0 24px',
height: 'calc(100% - 88px)',
overflowY: 'auto'
},
header: {
...withPixelLineHeight(theme.typography.heading6),
color: theme.palette.text01,
margin: '24px 0 16px'
},
questionContainer: {
paddingBottom: '24px',
borderBottom: `1px solid ${theme.palette.ui03}`
},
answerList: {
listStyleType: 'none',
margin: 0,
padding: 0
},
answer: {
marginBottom: '24px'
},
removeOption: {
...withPixelLineHeight(theme.typography.bodyShortRegular),
color: theme.palette.link01,
marginTop: '8px',
border: 0,
background: 'transparent'
},
addButtonContainer: {
display: 'flex'
},
footer: {
position: 'absolute',
bottom: 0,
display: 'flex',
justifyContent: 'flex-end',
padding: '24px',
width: '100%',
boxSizing: 'border-box'
},
buttonMargin: {
marginRight: theme.spacing(3)
}
};
});
const PollCreate = ({
addAnswer,
answers,
isSubmitDisabled,
onSubmit,
question,
removeAnswer,
setAnswer,
setCreateMode,
setQuestion,
t
}: AbstractProps) => {
const { classes } = useStyles();
/*
* This ref stores the Array of answer input fields, allowing us to focus on them.
* This array is maintained by registerfieldRef and the useEffect below.
*/
const answerInputs = useRef<Array<HTMLInputElement>>([]);
const registerFieldRef = useCallback((i, r) => {
if (r === null) {
return;
}
answerInputs.current[i] = r;
}, [ answerInputs ]);
useEffect(() => {
answerInputs.current = answerInputs.current.slice(0, answers.length);
}, [ answers ]);
/*
* This state allows us to requestFocus asynchronously, without having to worry
* about whether a newly created input field has been rendered yet or not.
*/
const [ lastFocus, requestFocus ] = useState<number | null>(null);
useEffect(() => {
if (lastFocus === null) {
return;
}
const input = answerInputs.current[lastFocus];
if (input === undefined) {
return;
}
input.focus();
}, [ lastFocus ]);
const checkModifiers = useCallback(ev => {
// Composition events used to add accents to characters
// despite their absence from standard US keyboards,
// to build up logograms of many Asian languages
// from their base components or categories and so on.
if (ev.isComposing || ev.keyCode === 229) {
// keyCode 229 means that user pressed some button,
// but input method is still processing that.
// This is a standard behavior for some input methods
// like entering japanese or сhinese hieroglyphs.
return true;
}
// Because this isn't done automatically on MacOS
if (ev.key === 'Enter' && ev.metaKey) {
ev.preventDefault();
onSubmit();
return;
}
if (ev.ctrlKey || ev.metaKey || ev.altKey || ev.shiftKey) {
return;
}
}, []);
const onQuestionKeyDown = useCallback(ev => {
if (checkModifiers(ev)) {
return;
}
if (ev.key === 'Enter') {
requestFocus(0);
ev.preventDefault();
}
}, []);
// Called on keypress in answer fields
const onAnswerKeyDown = useCallback((i, ev) => {
if (checkModifiers(ev)) {
return;
}
if (ev.key === 'Enter') {
// We add a new option input
// only if we are on the last option input
if (i === answers.length - 1) {
addAnswer(i + 1);
}
requestFocus(i + 1);
ev.preventDefault();
} else if (ev.key === 'Backspace' && ev.target.value === '' && answers.length > 1) {
removeAnswer(i);
requestFocus(i > 0 ? i - 1 : 0);
ev.preventDefault();
} else if (ev.key === 'ArrowDown') {
if (i === answers.length - 1) {
addAnswer();
}
requestFocus(i + 1);
ev.preventDefault();
} else if (ev.key === 'ArrowUp') {
if (i === 0) {
addAnswer(0);
requestFocus(0);
} else {
requestFocus(i - 1);
}
ev.preventDefault();
}
}, [ answers, addAnswer, removeAnswer, requestFocus ]);
/* eslint-disable react/jsx-no-bind */
return (<form
className = { classes.container }
onSubmit = { onSubmit }>
<div className = { classes.createContainer }>
<div className = { classes.header }>
{ t('polls.create.create') }
</div>
<div className = { classes.questionContainer }>
<Input
autoFocus = { true }
label = { t('polls.create.pollQuestion') }
maxLength = { CHAR_LIMIT }
onChange = { setQuestion }
onKeyPress = { onQuestionKeyDown }
placeholder = { t('polls.create.questionPlaceholder') }
textarea = { true }
value = { question } />
</div>
<ol className = { classes.answerList }>
{answers.map((answer: any, i: number) =>
(<li
className = { classes.answer }
key = { i }>
<Input
label = { t('polls.create.pollOption', { index: i + 1 }) }
maxLength = { CHAR_LIMIT }
onChange = { val => setAnswer(i, val) }
onKeyPress = { ev => onAnswerKeyDown(i, ev) }
placeholder = { t('polls.create.answerPlaceholder', { index: i + 1 }) }
ref = { r => registerFieldRef(i, r) }
textarea = { true }
value = { answer } />
{ answers.length > 2
&& <button
className = { classes.removeOption }
onClick = { () => removeAnswer(i) }
type = 'button'>
{ t('polls.create.removeOption') }
</button>}
</li>)
)}
</ol>
<div className = { classes.addButtonContainer }>
<Button
accessibilityLabel = { t('polls.create.addOption') }
disabled = { answers.length >= ANSWERS_LIMIT }
labelKey = { 'polls.create.addOption' }
onClick = { () => {
addAnswer();
requestFocus(answers.length);
} }
type = { BUTTON_TYPES.SECONDARY } />
</div>
</div>
<div className = { classes.footer }>
<Button
accessibilityLabel = { t('polls.create.cancel') }
className = { classes.buttonMargin }
labelKey = { 'polls.create.cancel' }
onClick = { () => setCreateMode(false) }
type = { BUTTON_TYPES.SECONDARY } />
<Button
accessibilityLabel = { t('polls.create.send') }
disabled = { isSubmitDisabled }
isSubmit = { true }
labelKey = { 'polls.create.send' } />
</div>
</form>);
};
/*
* We apply AbstractPollCreate to fill in the AbstractProps common
* to both the web and native implementations.
*/
// eslint-disable-next-line new-cap
export default AbstractPollCreate(PollCreate);