Merge branch 'poll-improvements' into 'develop'

UX Improvements on Polls

See merge request soapbox-pub/soapbox-fe!1528
environments/review-develop-3zknud/deployments/265
Justin 2 years ago
commit 0bb2818b71

@ -1,6 +1,6 @@
import classNames from 'classnames'; import classNames from 'classnames';
type ButtonThemes = 'primary' | 'secondary' | 'ghost' | 'accent' | 'danger' | 'transparent' | 'link' type ButtonThemes = 'primary' | 'secondary' | 'ghost' | 'accent' | 'danger' | 'transparent' | 'link' | 'danger-link'
type ButtonSizes = 'sm' | 'md' | 'lg' type ButtonSizes = 'sm' | 'md' | 'lg'
type IButtonStyles = { type IButtonStyles = {
@ -25,6 +25,7 @@ const useButtonStyles = ({
ghost: 'shadow-none border-gray-200 text-gray-700 bg-white dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2', ghost: 'shadow-none border-gray-200 text-gray-700 bg-white dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2',
accent: 'border-transparent text-white bg-accent-500 hover:bg-accent-300 focus:ring-pink-500 focus:ring-2 focus:ring-offset-2', accent: 'border-transparent text-white bg-accent-500 hover:bg-accent-300 focus:ring-pink-500 focus:ring-2 focus:ring-offset-2',
danger: 'border-transparent text-danger-700 bg-danger-100 hover:bg-danger-200 focus:ring-danger-500 focus:ring-2 focus:ring-offset-2', danger: 'border-transparent text-danger-700 bg-danger-100 hover:bg-danger-200 focus:ring-danger-500 focus:ring-2 focus:ring-offset-2',
'danger-link': 'border-transparent text-accent-500 hover:bg-accent-500 hover:bg-opacity-10',
transparent: 'border-transparent text-gray-800 backdrop-blur-sm bg-white/75 hover:bg-white/80', transparent: 'border-transparent text-gray-800 backdrop-blur-sm bg-white/75 hover:bg-white/80',
link: 'border-transparent text-primary-600 dark:text-primary-400 hover:bg-primary-100 hover:text-primary-700 dark:hover:bg-slate-900/50', link: 'border-transparent text-primary-600 dark:text-primary-400 hover:bg-primary-100 hover:text-primary-700 dark:hover:bg-slate-900/50',
}; };

@ -14,13 +14,13 @@ import Icon from 'soapbox/components/icon';
import { Button } from 'soapbox/components/ui'; import { Button } from 'soapbox/components/ui';
import { isMobile } from 'soapbox/is_mobile'; import { isMobile } from 'soapbox/is_mobile';
import PollForm from '../components/polls/poll-form';
import ReplyMentions from '../components/reply_mentions'; import ReplyMentions from '../components/reply_mentions';
import UploadForm from '../components/upload_form'; import UploadForm from '../components/upload_form';
import Warning from '../components/warning'; import Warning from '../components/warning';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import MarkdownButtonContainer from '../containers/markdown_button_container'; import MarkdownButtonContainer from '../containers/markdown_button_container';
import PollButtonContainer from '../containers/poll_button_container'; import PollButtonContainer from '../containers/poll_button_container';
import PollFormContainer from '../containers/poll_form_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import QuotedStatusContainer from '../containers/quoted_status_container'; import QuotedStatusContainer from '../containers/quoted_status_container';
import ReplyIndicatorContainer from '../containers/reply_indicator_container'; import ReplyIndicatorContainer from '../containers/reply_indicator_container';
@ -361,7 +361,7 @@ class ComposeForm extends ImmutablePureComponent {
!condensed && !condensed &&
<div className='compose-form__modifiers'> <div className='compose-form__modifiers'>
<UploadForm /> <UploadForm />
<PollFormContainer /> <PollForm />
<ScheduleFormContainer /> <ScheduleFormContainer />
</div> </div>
} }

@ -1,13 +1,12 @@
'use strict';
import React from 'react'; import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { addPollOption, changePollOption, changePollSettings, clearComposeSuggestions, fetchComposeSuggestions, removePoll, removePollOption, selectComposeSuggestion } from 'soapbox/actions/compose';
import AutosuggestInput from 'soapbox/components/autosuggest_input'; import AutosuggestInput from 'soapbox/components/autosuggest_input';
import { Button, Divider, HStack, Stack, Text, Toggle } from 'soapbox/components/ui'; import { Button, Divider, HStack, Stack, Text, Toggle } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import DurationSelector from './polls/duration-selector'; import DurationSelector from './duration-selector';
import type { AutoSuggestion } from 'soapbox/components/autosuggest_input'; import type { AutoSuggestion } from 'soapbox/components/autosuggest_input';
@ -31,12 +30,8 @@ interface IOption {
maxChars: number maxChars: number
numOptions: number numOptions: number
onChange(index: number, value: string): void onChange(index: number, value: string): void
onClearSuggestions(): void
onFetchSuggestions(token: string): void
onRemove(index: number): void onRemove(index: number): void
onRemovePoll(): void onRemovePoll(): void
onSuggestionSelected(tokenStart: number, token: string, value: string, key: (string | number)[]): void
suggestions?: any // list
title: string title: string
} }
@ -46,16 +41,16 @@ const Option = (props: IOption) => {
maxChars, maxChars,
numOptions, numOptions,
onChange, onChange,
onClearSuggestions,
onFetchSuggestions,
onRemove, onRemove,
onRemovePoll, onRemovePoll,
suggestions,
title, title,
} = props; } = props;
const dispatch = useAppDispatch();
const intl = useIntl(); const intl = useIntl();
const suggestions = useAppSelector((state) => state.compose.get('suggestions'));
const handleOptionTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => onChange(index, event.target.value); const handleOptionTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => onChange(index, event.target.value);
const handleOptionRemove = () => { const handleOptionRemove = () => {
@ -66,13 +61,13 @@ const Option = (props: IOption) => {
} }
}; };
const onSuggestionsClearRequested = () => onClearSuggestions(); const onSuggestionsClearRequested = () => dispatch(clearComposeSuggestions());
const onSuggestionsFetchRequested = (token: string) => onFetchSuggestions(token); const onSuggestionsFetchRequested = (token: string) => dispatch(fetchComposeSuggestions(token));
const onSuggestionSelected = (tokenStart: number, token: string | null, value: AutoSuggestion) => { const onSuggestionSelected = (tokenStart: number, token: string | null, value: AutoSuggestion) => {
if (token && typeof value === 'string') { if (token && typeof token === 'string') {
props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', index]); dispatch(selectComposeSuggestion(tokenStart, token, value, ['poll', 'options', index]));
} }
}; };
@ -94,7 +89,7 @@ const Option = (props: IOption) => {
onSuggestionsClearRequested={onSuggestionsClearRequested} onSuggestionsClearRequested={onSuggestionsClearRequested}
onSuggestionSelected={onSuggestionSelected} onSuggestionSelected={onSuggestionSelected}
searchTokens={[':']} searchTokens={[':']}
autoFocus={index === 0} autoFocus={index === 0 || index >= 2}
/> />
</HStack> </HStack>
@ -107,42 +102,26 @@ const Option = (props: IOption) => {
); );
}; };
interface IPollForm { const PollForm = () => {
expiresIn?: number const dispatch = useAppDispatch();
isMultiple?: boolean
onAddOption(value: string): void
onChangeOption(): void
onChangeSettings(value: string | number | undefined, isMultiple?: boolean): void
onClearSuggestions(): void
onFetchSuggestions(token: string): void
onRemoveOption(): void
onRemovePoll(): void
onSuggestionSelected(tokenStart: number, token: string, value: string, key: (string | number)[]): void
options?: any
suggestions?: any // list
}
const PollForm = (props: IPollForm) => {
const {
expiresIn,
isMultiple,
onAddOption,
onChangeOption,
onChangeSettings,
onRemoveOption,
options,
...filteredProps
} = props;
const intl = useIntl(); const intl = useIntl();
const pollLimits = useAppSelector((state) => state.instance.getIn(['configuration', 'polls']) as any); const pollLimits = useAppSelector((state) => state.instance.getIn(['configuration', 'polls']) as any);
const options = useAppSelector((state) => state.compose.getIn(['poll', 'options']));
const expiresIn = useAppSelector((state) => state.compose.getIn(['poll', 'expires_in']));
const isMultiple = useAppSelector((state) => state.compose.getIn(['poll', 'multiple']));
const maxOptions = pollLimits.get('max_options'); const maxOptions = pollLimits.get('max_options');
const maxOptionChars = pollLimits.get('max_characters_per_option'); const maxOptionChars = pollLimits.get('max_characters_per_option');
const handleAddOption = () => onAddOption(''); const onRemoveOption = (index: number) => dispatch(removePollOption(index));
const onChangeOption = (index: number, title: string) => dispatch(changePollOption(index, title));
const handleAddOption = () => dispatch(addPollOption(''));
const onChangeSettings = (expiresIn: string | number | undefined, isMultiple?: boolean) =>
dispatch(changePollSettings(expiresIn, isMultiple));
const handleSelectDuration = (value: number) => onChangeSettings(value, isMultiple); const handleSelectDuration = (value: number) => onChangeSettings(value, isMultiple);
const handleToggleMultiple = () => onChangeSettings(expiresIn, !isMultiple); const handleToggleMultiple = () => onChangeSettings(expiresIn, !isMultiple);
const onRemovePoll = () => dispatch(removePoll());
if (!options) { if (!options) {
return null; return null;
@ -160,7 +139,7 @@ const PollForm = (props: IPollForm) => {
onRemove={onRemoveOption} onRemove={onRemoveOption}
maxChars={maxOptionChars} maxChars={maxOptionChars}
numOptions={options.size} numOptions={options.size}
{...filteredProps} onRemovePoll={onRemovePoll}
/> />
))} ))}
@ -211,7 +190,7 @@ const PollForm = (props: IPollForm) => {
{/* Remove Poll */} {/* Remove Poll */}
<div className='text-center'> <div className='text-center'>
<Button theme='danger' size='sm' onClick={props.onRemovePoll}> <Button theme='danger-link' onClick={onRemovePoll}>
{intl.formatMessage(messages.removePoll)} {intl.formatMessage(messages.removePoll)}
</Button> </Button>
</div> </div>

@ -1,57 +0,0 @@
import { connect } from 'react-redux';
import {
addPollOption,
removePollOption,
changePollOption,
changePollSettings,
removePoll,
clearComposeSuggestions,
fetchComposeSuggestions,
selectComposeSuggestion,
} from '../../../actions/compose';
import PollForm from '../components/poll-form';
const mapStateToProps = state => ({
suggestions: state.getIn(['compose', 'suggestions']),
options: state.getIn(['compose', 'poll', 'options']),
expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
isMultiple: state.getIn(['compose', 'poll', 'multiple']),
});
const mapDispatchToProps = dispatch => ({
onAddOption(title) {
dispatch(addPollOption(title));
},
onRemoveOption(index) {
dispatch(removePollOption(index));
},
onChangeOption(index, title) {
dispatch(changePollOption(index, title));
},
onChangeSettings(expiresIn, isMultiple) {
dispatch(changePollSettings(expiresIn, isMultiple));
},
onClearSuggestions() {
dispatch(clearComposeSuggestions());
},
onFetchSuggestions(token) {
dispatch(fetchComposeSuggestions(token));
},
onSuggestionSelected(position, token, accountId, path) {
dispatch(selectComposeSuggestion(position, token, accountId, path));
},
onRemovePoll() {
dispatch(removePoll());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(PollForm);
Loading…
Cancel
Save