Composer redesign: Poll duration (#40148)

This commit is contained in:
Echo
2026-08-13 12:22:05 +00:00
committed by GitHub
parent 0109568470
commit 348a66890b
4 changed files with 87 additions and 34 deletions

View File

@@ -12,6 +12,8 @@ import { Icon } from '../icon';
import classes from './redesign.module.scss';
export const buttonClasses = classes;
interface ButtonPropsBase<As extends 'a' | 'button'> {
size?: 'lg' | 'md' | 'sm' | 'xs';
variant?: 'solid' | 'ghost';

View File

@@ -64,7 +64,7 @@ input.input {
}
&:focus {
outline: 2px solid var(--color-border-strong);
outline: 2px solid var(--color-border-brand);
}
&:disabled {

View File

@@ -3,6 +3,8 @@ import { useCallback, useId } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import classNames from 'classnames';
import { PlusIcon } from '@phosphor-icons/react';
import { changePollSettings, removePoll } from '@/mastodon/actions/compose';
@@ -11,12 +13,13 @@ import {
deletePollOption,
updatePollOption,
} from '@/mastodon/actions/compose_typed';
import { Button } from '@/mastodon/components/button/redesign';
import { Button, buttonClasses } from '@/mastodon/components/button/redesign';
import {
ToggleField,
TextInput,
} from '@/mastodon/components/form_fields/redesign';
import { useAppSelector, useAppDispatch } from '@/mastodon/store';
import { DAY, HOUR, MINUTE } from '@/mastodon/utils/time';
import { selectComposePoll } from './selectors';
import classes from './styles.module.scss';
@@ -40,26 +43,31 @@ const messages = defineMessages({
},
});
const pollDurationOptions = [
5 * MINUTE,
30 * MINUTE,
HOUR,
12 * HOUR,
DAY,
3 * DAY,
7 * DAY,
];
function durationToMessage(durationMs: number) {
if (durationMs < HOUR) {
return { message: messages.minutes, multiplier: MINUTE };
} else if (durationMs < DAY) {
return { message: messages.hours, multiplier: HOUR };
}
return { message: messages.days, multiplier: DAY };
}
export const ComposePoll: React.FC = () => {
const { options, maxOptions, expiresIn, multiple } =
useAppSelector(selectComposePoll);
const listId = useId();
const intl = useIntl();
const dispatch = useAppDispatch();
const handleAdd = useCallback(() => {
dispatch(addPollOption());
}, [dispatch]);
const handlePollChangeMultiple: React.ChangeEventHandler<HTMLInputElement> =
useCallback(
(event) => {
dispatch(changePollSettings(expiresIn, event.target.checked));
},
[dispatch, expiresIn],
);
const handleDelete = useCallback(() => {
dispatch(removePoll());
}, [dispatch]);
const handleKeyDown = useCallback(
(index: number, event: React.KeyboardEvent<HTMLInputElement>) => {
const value = event.currentTarget.value;
@@ -91,6 +99,28 @@ export const ComposePoll: React.FC = () => {
},
[dispatch, listId, maxOptions],
);
const handleAdd = useCallback(() => {
dispatch(addPollOption());
}, [dispatch]);
const handlePollChangeMultiple: React.ChangeEventHandler<HTMLInputElement> =
useCallback(
(event) => {
dispatch(changePollSettings(expiresIn, event.target.checked));
},
[dispatch, expiresIn],
);
const handleDurationChange: React.ChangeEventHandler<HTMLSelectElement> =
useCallback(
(event) => {
dispatch(
changePollSettings(Number.parseInt(event.target.value), multiple),
);
},
[dispatch, multiple],
);
const handleDelete = useCallback(() => {
dispatch(removePoll());
}, [dispatch]);
const firstItemEmpty = !options.at(0);
@@ -138,22 +168,36 @@ export const ComposePoll: React.FC = () => {
/>
<div className={classes.pollControls}>
<FormattedMessage
id='compose.poll.duration'
defaultMessage='Duration: {button}'
values={{
button: (
<Button color='tonal' size='xs'>
<FormattedMessage
id='intervals.full.days'
defaultMessage='{number, plural, one {# day} other {# days}}'
values={{ number: 1 }}
/>
</Button>
),
}}
tagName='span'
/>
<span>
<FormattedMessage
id='compose.poll.duration'
defaultMessage='Duration:'
description='Followed by current poll duration'
/>
&nbsp;
<select
value={expiresIn}
onChange={handleDurationChange}
className={classNames(
classes.pollDurationSelect,
buttonClasses.base,
buttonClasses.solid,
buttonClasses.tonal,
buttonClasses.xs,
)}
>
{pollDurationOptions.map((duration) => {
const { message, multiplier } = durationToMessage(duration);
return (
<option key={duration} value={duration}>
{intl.formatMessage(message, {
number: duration / multiplier,
})}
</option>
);
})}
</select>
</span>
<Button
variant='ghost'

View File

@@ -272,8 +272,11 @@
@include mixins.type-micro;
content: counter(poll, upper-alpha);
width: 8px;
padding: var(--space-2xs);
width: var(--space-lg);
height: var(--space-lg);
flex-shrink: 0;
line-height: var(--space-lg);
text-align: center;
border-radius: var(--radius-xs);
background: var(--color-bg-highlight);
}
@@ -292,6 +295,10 @@
justify-content: space-between;
}
.pollDurationSelect {
text-align: center;
}
// Emoji picker
div.emojiRoot {