mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 11:22:31 +00:00
feat(polls) Redesign (#12838)
Convert files to TS Move styles from SCSS to JSS Implement redesign
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import Checkbox from '../../../base/ui/components/web/Checkbox';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.web';
|
||||
@@ -10,8 +10,40 @@ import AbstractPollAnswer, { AbstractProps } from '../AbstractPollAnswer';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
margin: '24px',
|
||||
padding: '16px',
|
||||
backgroundColor: theme.palette.ui02,
|
||||
borderRadius: '8px'
|
||||
},
|
||||
header: {
|
||||
marginBottom: '24px'
|
||||
},
|
||||
question: {
|
||||
...withPixelLineHeight(theme.typography.heading6),
|
||||
color: theme.palette.text01,
|
||||
marginBottom: '8px'
|
||||
},
|
||||
creator: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
color: theme.palette.text02
|
||||
},
|
||||
answerList: {
|
||||
listStyleType: 'none',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
marginBottom: '24px'
|
||||
},
|
||||
answer: {
|
||||
display: 'flex',
|
||||
marginBottom: '16px'
|
||||
},
|
||||
footer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
buttonMargin: {
|
||||
marginRight: theme.spacing(2)
|
||||
marginRight: theme.spacing(3)
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -27,23 +59,23 @@ const PollAnswer = ({
|
||||
t
|
||||
}: AbstractProps) => {
|
||||
const { changingVote } = poll;
|
||||
const { classes: styles } = useStyles();
|
||||
const { classes } = useStyles();
|
||||
|
||||
return (
|
||||
<div className = 'poll-answer'>
|
||||
<div className = 'poll-header'>
|
||||
<div className = 'poll-question'>
|
||||
<span>{ poll.question }</span>
|
||||
<div className = { classes.container }>
|
||||
<div className = { classes.header }>
|
||||
<div className = { classes.question }>
|
||||
{ poll.question }
|
||||
</div>
|
||||
<div className = 'poll-creator'>
|
||||
<div className = { classes.creator }>
|
||||
{ t('polls.by', { name: creatorName }) }
|
||||
</div>
|
||||
</div>
|
||||
<ol className = 'poll-answer-list'>
|
||||
<ul className = { classes.answerList }>
|
||||
{
|
||||
poll.answers.map((answer: any, index: number) => (
|
||||
<li
|
||||
className = 'poll-answer-container'
|
||||
className = { classes.answer }
|
||||
key = { index }>
|
||||
<Checkbox
|
||||
checked = { checkBoxStates[index] }
|
||||
@@ -54,19 +86,17 @@ const PollAnswer = ({
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
<div className = 'poll-footer poll-answer-footer' >
|
||||
</ul>
|
||||
<div className = { classes.footer } >
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.answer.skip') }
|
||||
className = { styles.buttonMargin }
|
||||
fullWidth = { true }
|
||||
className = { classes.buttonMargin }
|
||||
labelKey = { 'polls.answer.skip' }
|
||||
onClick = { changingVote ? skipChangeVote : skipAnswer }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.answer.submit') }
|
||||
disabled = { isSubmitAnswerDisabled(checkBoxStates) }
|
||||
fullWidth = { true }
|
||||
labelKey = { 'polls.answer.submit' }
|
||||
onClick = { submitAnswer } />
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,62 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
// @ts-ignore
|
||||
import { Tooltip } from '../../../base/tooltip';
|
||||
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';
|
||||
// @ts-ignore
|
||||
import AbstractPollCreate from '../AbstractPollCreate';
|
||||
// @ts-ignore
|
||||
import type { AbstractProps } from '../AbstractPollCreate';
|
||||
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(2)
|
||||
marginRight: theme.spacing(3)
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -32,7 +73,7 @@ const PollCreate = ({
|
||||
setQuestion,
|
||||
t
|
||||
}: AbstractProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const { classes } = useStyles();
|
||||
|
||||
/*
|
||||
* This ref stores the Array of answer input fields, allowing us to focus on them.
|
||||
@@ -139,76 +180,54 @@ const PollCreate = ({
|
||||
}
|
||||
}, [ answers, addAnswer, removeAnswer, requestFocus ]);
|
||||
|
||||
const autogrow = (ev: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const el = ev.target;
|
||||
|
||||
el.style.height = '1px';
|
||||
el.style.height = `${el.scrollHeight + 2}px`;
|
||||
};
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
return (<form
|
||||
className = 'polls-pane-content'
|
||||
className = { classes.container }
|
||||
onSubmit = { onSubmit }>
|
||||
<div className = 'poll-create-container poll-container'>
|
||||
<div className = 'poll-create-header'>
|
||||
<div className = { classes.createContainer }>
|
||||
<div className = { classes.header }>
|
||||
{ t('polls.create.create') }
|
||||
</div>
|
||||
<div className = 'poll-question-field'>
|
||||
<span className = 'poll-create-label'>
|
||||
{ t('polls.create.pollQuestion') }
|
||||
</span>
|
||||
<textarea
|
||||
<div className = { classes.questionContainer }>
|
||||
<Input
|
||||
autoFocus = { true }
|
||||
className = 'expandable-input'
|
||||
label = { t('polls.create.pollQuestion') }
|
||||
maxLength = { CHAR_LIMIT }
|
||||
onChange = { ev => setQuestion(ev.target.value) }
|
||||
onInput = { autogrow }
|
||||
onKeyDown = { onQuestionKeyDown }
|
||||
onChange = { setQuestion }
|
||||
onKeyPress = { onQuestionKeyDown }
|
||||
placeholder = { t('polls.create.questionPlaceholder') }
|
||||
required = { true }
|
||||
rows = { 1 }
|
||||
textarea = { true }
|
||||
value = { question } />
|
||||
</div>
|
||||
<ol className = 'poll-answer-field-list'>
|
||||
<ol className = { classes.answerList }>
|
||||
{answers.map((answer: any, i: number) =>
|
||||
(<li
|
||||
className = 'poll-answer-field'
|
||||
className = { classes.answer }
|
||||
key = { i }>
|
||||
<span className = 'poll-create-label'>
|
||||
{ t('polls.create.pollOption', { index: i + 1 })}
|
||||
</span>
|
||||
<div className = 'poll-create-option-row'>
|
||||
<textarea
|
||||
className = 'expandable-input'
|
||||
maxLength = { CHAR_LIMIT }
|
||||
onChange = { ev => setAnswer(i, ev.target.value) }
|
||||
onInput = { autogrow }
|
||||
onKeyDown = { ev => onAnswerKeyDown(i, ev) }
|
||||
placeholder = { t('polls.create.answerPlaceholder', { index: i + 1 }) }
|
||||
ref = { r => registerFieldRef(i, r) }
|
||||
required = { true }
|
||||
rows = { 1 }
|
||||
value = { answer } />
|
||||
</div>
|
||||
<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
|
||||
&& <Tooltip content = { t('polls.create.removeOption') }>
|
||||
<button
|
||||
className = 'poll-remove-option-button'
|
||||
onClick = { () => removeAnswer(i) }
|
||||
type = 'button'>
|
||||
{ t('polls.create.removeOption') }
|
||||
</button>
|
||||
</Tooltip>}
|
||||
&& <button
|
||||
className = { classes.removeOption }
|
||||
onClick = { () => removeAnswer(i) }
|
||||
type = 'button'>
|
||||
{ t('polls.create.removeOption') }
|
||||
</button>}
|
||||
</li>)
|
||||
)}
|
||||
</ol>
|
||||
<div className = 'poll-add-button'>
|
||||
<div className = { classes.addButtonContainer }>
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.create.addOption') }
|
||||
disabled = { answers.length >= ANSWERS_LIMIT }
|
||||
fullWidth = { true }
|
||||
labelKey = { 'polls.create.addOption' }
|
||||
onClick = { () => {
|
||||
addAnswer();
|
||||
@@ -217,23 +236,20 @@ const PollCreate = ({
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
</div>
|
||||
</div>
|
||||
<div className = 'poll-footer poll-create-footer'>
|
||||
<div className = { classes.footer }>
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.create.cancel') }
|
||||
className = { styles.buttonMargin }
|
||||
fullWidth = { true }
|
||||
className = { classes.buttonMargin }
|
||||
labelKey = { 'polls.create.cancel' }
|
||||
onClick = { () => setCreateMode(false) }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.create.send') }
|
||||
disabled = { isSubmitDisabled }
|
||||
fullWidth = { true }
|
||||
isSubmit = { true }
|
||||
labelKey = { 'polls.create.send' } />
|
||||
</div>
|
||||
</form>);
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { PollAnswer, PollResults } from '..';
|
||||
import { shouldShowResults } from '../../functions';
|
||||
|
||||
import PollAnswer from './PollAnswer';
|
||||
import PollResults from './PollResults';
|
||||
|
||||
type Props = {
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Id of the poll.
|
||||
*/
|
||||
pollId: string,
|
||||
pollId: string;
|
||||
|
||||
}
|
||||
|
||||
const PollItem = React.forwardRef<Props, HTMLElement>(({ pollId }: Props, ref) => {
|
||||
const PollItem = React.forwardRef<HTMLDivElement, IProps>(({ pollId }: IProps, ref) => {
|
||||
const showResults = useSelector(shouldShowResults(pollId));
|
||||
|
||||
return (
|
||||
@@ -1,84 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import AbstractPollResults from '../AbstractPollResults';
|
||||
import type { AbstractProps } from '../AbstractPollResults';
|
||||
|
||||
|
||||
/**
|
||||
* Component that renders the poll results.
|
||||
*
|
||||
* @param {Props} props - The passed props.
|
||||
* @returns {React.Node}
|
||||
*/
|
||||
const PollResults = (props: AbstractProps) => {
|
||||
const {
|
||||
answers,
|
||||
changeVote,
|
||||
creatorName,
|
||||
haveVoted,
|
||||
showDetails,
|
||||
question,
|
||||
t,
|
||||
toggleIsDetailed
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className = 'poll-results'>
|
||||
<div className = 'poll-header'>
|
||||
<div className = 'poll-question'>
|
||||
<strong>{ question }</strong>
|
||||
</div>
|
||||
<div className = 'poll-creator'>
|
||||
{ t('polls.by', { name: creatorName }) }
|
||||
</div>
|
||||
</div>
|
||||
<ol className = 'poll-result-list'>
|
||||
{answers.map(({ name, percentage, voters, voterCount }, index) =>
|
||||
(<li key = { index }>
|
||||
<div className = 'poll-answer-header'>
|
||||
<span className = 'poll-answer-vote-name' >{name}</span>
|
||||
</div>
|
||||
<div className = 'poll-answer-short-results'>
|
||||
<span className = 'poll-bar-container'>
|
||||
<div
|
||||
className = 'poll-bar'
|
||||
style = {{ width: `${percentage}%` }} />
|
||||
</span>
|
||||
<div className = 'poll-answer-vote-count-container'>
|
||||
<span className = 'poll-answer-vote-count'>({voterCount}) {percentage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
{ showDetails && voters && voterCount > 0
|
||||
&& <ul className = 'poll-answer-voters'>
|
||||
{voters.map(voter =>
|
||||
<li key = { voter.id }>{voter.name}</li>
|
||||
)}
|
||||
</ul>}
|
||||
</li>)
|
||||
)}
|
||||
</ol>
|
||||
<div className = { 'poll-result-links' }>
|
||||
<a
|
||||
className = { 'poll-detail-link' }
|
||||
onClick = { toggleIsDetailed }>
|
||||
{showDetails ? t('polls.results.hideDetailedResults') : t('polls.results.showDetailedResults')}
|
||||
</a>
|
||||
<a
|
||||
className = { 'poll-change-vote-link' }
|
||||
onClick = { changeVote }>
|
||||
{haveVoted ? t('polls.results.changeVote') : t('polls.results.vote')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* We apply AbstractPollResults to fill in the AbstractProps common
|
||||
* to both the web and native implementations.
|
||||
*/
|
||||
// eslint-disable-next-line new-cap
|
||||
export default AbstractPollResults(PollResults);
|
||||
177
react/features/polls/components/web/PollResults.tsx
Normal file
177
react/features/polls/components/web/PollResults.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import AbstractPollResults, { AbstractProps } from '../AbstractPollResults';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
margin: '24px',
|
||||
padding: '16px',
|
||||
backgroundColor: theme.palette.ui02,
|
||||
borderRadius: '8px'
|
||||
},
|
||||
header: {
|
||||
marginBottom: '16px'
|
||||
},
|
||||
question: {
|
||||
...withPixelLineHeight(theme.typography.heading6),
|
||||
color: theme.palette.text01,
|
||||
marginBottom: '8px'
|
||||
},
|
||||
creator: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
color: theme.palette.text02
|
||||
},
|
||||
resultList: {
|
||||
listStyleType: 'none',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
|
||||
'& li': {
|
||||
marginBottom: '16px'
|
||||
}
|
||||
},
|
||||
answerName: {
|
||||
display: 'flex',
|
||||
flexShrink: 1,
|
||||
overflowWrap: 'anywhere',
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
color: theme.palette.text01,
|
||||
marginBottom: '4px'
|
||||
},
|
||||
answerResultContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
minWidth: '10em'
|
||||
},
|
||||
barContainer: {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
borderRadius: '4px',
|
||||
height: '6px',
|
||||
maxWidth: '160px',
|
||||
width: '158px',
|
||||
flexGrow: 1,
|
||||
marginTop: '2px'
|
||||
},
|
||||
bar: {
|
||||
height: '6px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: theme.palette.action01
|
||||
},
|
||||
voteCount: {
|
||||
flex: 1,
|
||||
textAlign: 'right',
|
||||
...withPixelLineHeight(theme.typography.bodyShortBold),
|
||||
color: theme.palette.text01
|
||||
},
|
||||
voters: {
|
||||
margin: 0,
|
||||
marginTop: '4px',
|
||||
listStyleType: 'none',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.palette.ui03,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
padding: '8px 16px',
|
||||
|
||||
'& li': {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
color: theme.palette.text01,
|
||||
margin: 0,
|
||||
marginBottom: '2px',
|
||||
|
||||
'&:last-of-type': {
|
||||
marginBottom: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
|
||||
'& button': {
|
||||
border: 0,
|
||||
backgroundColor: 'transparent',
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
color: theme.palette.link01
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Component that renders the poll results.
|
||||
*
|
||||
* @param {Props} props - The passed props.
|
||||
* @returns {React.Node}
|
||||
*/
|
||||
const PollResults = ({
|
||||
answers,
|
||||
changeVote,
|
||||
creatorName,
|
||||
haveVoted,
|
||||
showDetails,
|
||||
question,
|
||||
t,
|
||||
toggleIsDetailed
|
||||
}: AbstractProps) => {
|
||||
const { classes } = useStyles();
|
||||
|
||||
return (
|
||||
<div className = { classes.container }>
|
||||
<div className = { classes.header }>
|
||||
<div className = { classes.question }>
|
||||
{question}
|
||||
</div>
|
||||
<div className = { classes.creator }>
|
||||
{t('polls.by', { name: creatorName })}
|
||||
</div>
|
||||
</div>
|
||||
<ul className = { classes.resultList }>
|
||||
{answers.map(({ name, percentage, voters, voterCount }, index) =>
|
||||
(<li key = { index }>
|
||||
<div className = { classes.answerName }>
|
||||
{name}
|
||||
</div>
|
||||
<div className = { classes.answerResultContainer }>
|
||||
<span className = { classes.barContainer }>
|
||||
<div
|
||||
className = { classes.bar }
|
||||
style = {{ width: `${percentage}%` }} />
|
||||
</span>
|
||||
<div className = { classes.voteCount }>
|
||||
{voterCount}({percentage}%)
|
||||
</div>
|
||||
</div>
|
||||
{showDetails && voters && voterCount > 0
|
||||
&& <ul className = { classes.voters }>
|
||||
{voters.map(voter =>
|
||||
<li key = { voter?.id }>{voter?.name}</li>
|
||||
)}
|
||||
</ul>}
|
||||
</li>)
|
||||
)}
|
||||
</ul>
|
||||
<div className = { classes.buttonsContainer }>
|
||||
<button
|
||||
onClick = { toggleIsDetailed }>
|
||||
{showDetails ? t('polls.results.hideDetailedResults') : t('polls.results.showDetailedResults')}
|
||||
</button>
|
||||
<button
|
||||
onClick = { changeVote }>
|
||||
{haveVoted ? t('polls.results.changeVote') : t('polls.results.vote')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* We apply AbstractPollResults to fill in the AbstractProps common
|
||||
* to both the web and native implementations.
|
||||
*/
|
||||
// eslint-disable-next-line new-cap
|
||||
export default AbstractPollResults(PollResults);
|
||||
@@ -1,58 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
|
||||
import { Icon, IconMessage } from '../../../base/icons';
|
||||
import { browser } from '../../../base/lib-jitsi-meet';
|
||||
|
||||
import PollItem from './PollItem';
|
||||
|
||||
const PollsList = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const polls = useSelector(state => state['features/polls'].polls);
|
||||
const pollListEndRef = useRef(null);
|
||||
|
||||
const scrollToBottom = useCallback(() => {
|
||||
if (pollListEndRef.current) {
|
||||
// Safari does not support options
|
||||
const param = browser.isSafari()
|
||||
? false : {
|
||||
behavior: 'smooth',
|
||||
block: 'end',
|
||||
inline: 'nearest'
|
||||
};
|
||||
|
||||
pollListEndRef.current.scrollIntoView(param);
|
||||
}
|
||||
}, [ pollListEndRef.current ]);
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [ polls ]);
|
||||
|
||||
const listPolls = Object.keys(polls);
|
||||
|
||||
return (
|
||||
<>
|
||||
{listPolls.length === 0
|
||||
? <div className = 'pane-content'>
|
||||
<Icon
|
||||
className = 'empty-pane-icon'
|
||||
src = { IconMessage } />
|
||||
<span className = 'empty-pane-message'>{t('polls.results.empty')}</span>
|
||||
</div>
|
||||
: listPolls.map((id, index) => (
|
||||
<PollItem
|
||||
key = { id }
|
||||
pollId = { id }
|
||||
ref = { listPolls.length - 1 === index ? pollListEndRef : null } />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PollsList;
|
||||
89
react/features/polls/components/web/PollsList.tsx
Normal file
89
react/features/polls/components/web/PollsList.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconMessage } from '../../../base/icons/svg';
|
||||
import { browser } from '../../../base/lib-jitsi-meet';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
|
||||
import PollItem from './PollItem';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
emptyIcon: {
|
||||
width: '100px',
|
||||
padding: '16px',
|
||||
|
||||
'& svg': {
|
||||
width: '100%',
|
||||
height: 'auto'
|
||||
}
|
||||
},
|
||||
emptyMessage: {
|
||||
...withPixelLineHeight(theme.typography.bodyLongBold),
|
||||
color: theme.palette.text02,
|
||||
padding: '0 24px',
|
||||
textAlign: 'center'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const PollsList = () => {
|
||||
const { t } = useTranslation();
|
||||
const { classes, theme } = useStyles();
|
||||
|
||||
const polls = useSelector((state: IReduxState) => state['features/polls'].polls);
|
||||
const pollListEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollToBottom = useCallback(() => {
|
||||
if (pollListEndRef.current) {
|
||||
// Safari does not support options
|
||||
const param = browser.isSafari()
|
||||
? false : {
|
||||
behavior: 'smooth' as const,
|
||||
block: 'end' as const,
|
||||
inline: 'nearest' as const
|
||||
};
|
||||
|
||||
pollListEndRef.current.scrollIntoView(param);
|
||||
}
|
||||
}, [ pollListEndRef.current ]);
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [ polls ]);
|
||||
|
||||
const listPolls = Object.keys(polls);
|
||||
|
||||
return (
|
||||
<>
|
||||
{listPolls.length === 0
|
||||
? <div className = { classes.container }>
|
||||
<Icon
|
||||
className = { classes.emptyIcon }
|
||||
color = { theme.palette.icon03 }
|
||||
src = { IconMessage } />
|
||||
<span className = { classes.emptyMessage }>{t('polls.results.empty')}</span>
|
||||
</div>
|
||||
: listPolls.map((id, index) => (
|
||||
<PollItem
|
||||
key = { id }
|
||||
pollId = { id }
|
||||
ref = { listPolls.length - 1 === index ? pollListEndRef : null } />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PollsList;
|
||||
@@ -1,27 +1,42 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
// @ts-ignore
|
||||
import AbstractPollsPane from '../AbstractPollsPane';
|
||||
// @ts-ignore
|
||||
import type { AbstractProps } from '../AbstractPollsPane';
|
||||
import AbstractPollsPane, { AbstractProps } from '../AbstractPollsPane';
|
||||
|
||||
import PollCreate from './PollCreate';
|
||||
// @ts-ignore
|
||||
import PollsList from './PollsList';
|
||||
|
||||
const useStyles = makeStyles()(() => {
|
||||
return {
|
||||
container: {
|
||||
height: '100%',
|
||||
position: 'relative'
|
||||
},
|
||||
listContainer: {
|
||||
height: 'calc(100% - 88px)',
|
||||
overflowY: 'auto'
|
||||
},
|
||||
footer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
padding: '24px',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const PollsPane = (props: AbstractProps) => {
|
||||
const { createMode, onCreate, setCreateMode, t } = props;
|
||||
const PollsPane = ({ createMode, onCreate, setCreateMode, t }: AbstractProps) => {
|
||||
const { classes } = useStyles();
|
||||
|
||||
return createMode
|
||||
? <PollCreate setCreateMode = { setCreateMode } />
|
||||
: <div className = 'polls-pane-content'>
|
||||
<div className = { 'poll-container' } >
|
||||
: <div className = { classes.container }>
|
||||
<div className = { classes.listContainer } >
|
||||
<PollsList />
|
||||
</div>
|
||||
<div className = 'poll-footer poll-create-footer'>
|
||||
<div className = { classes.footer }>
|
||||
<Button
|
||||
accessibilityLabel = { t('polls.create.create') }
|
||||
autoFocus = { true }
|
||||
|
||||
Reference in New Issue
Block a user