Improve design of the Poll Form
This commit is contained in:
parent
354159e1fa
commit
7782c96ba4
7 changed files with 244 additions and 129 deletions
|
@ -20,7 +20,7 @@ export type AutoSuggestion = string | Emoji;
|
||||||
const textAtCursorMatchesToken = (str: string, caretPosition: number, searchTokens: string[]): CursorMatch => {
|
const textAtCursorMatchesToken = (str: string, caretPosition: number, searchTokens: string[]): CursorMatch => {
|
||||||
let word: string;
|
let word: string;
|
||||||
|
|
||||||
const left: number = str.slice(0, caretPosition).search(/\S+$/);
|
const left: number = str.slice(0, caretPosition).search(/\S+$/);
|
||||||
const right: number = str.slice(caretPosition).search(/\s/);
|
const right: number = str.slice(caretPosition).search(/\s/);
|
||||||
|
|
||||||
if (right < 0) {
|
if (right < 0) {
|
||||||
|
@ -201,13 +201,13 @@ export default class AutosuggestInput extends ImmutablePureComponent<IAutosugges
|
||||||
|
|
||||||
if (typeof suggestion === 'object') {
|
if (typeof suggestion === 'object') {
|
||||||
inner = <AutosuggestEmoji emoji={suggestion} />;
|
inner = <AutosuggestEmoji emoji={suggestion} />;
|
||||||
key = suggestion.id;
|
key = suggestion.id;
|
||||||
} else if (suggestion[0] === '#') {
|
} else if (suggestion[0] === '#') {
|
||||||
inner = suggestion;
|
inner = suggestion;
|
||||||
key = suggestion;
|
key = suggestion;
|
||||||
} else {
|
} else {
|
||||||
inner = <AutosuggestAccount id={suggestion} />;
|
inner = <AutosuggestAccount id={suggestion} />;
|
||||||
key = suggestion;
|
key = suggestion;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -279,13 +279,13 @@ export default class AutosuggestInput extends ImmutablePureComponent<IAutosugges
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<div className='relative w-full'>
|
||||||
<label className='sr-only'>{placeholder}</label>
|
<label className='sr-only'>{placeholder}</label>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
className={classNames({
|
className={classNames({
|
||||||
'block w-full sm:text-sm dark:bg-slate-800 dark:text-white dark:placeholder:text-gray-500 focus:ring-indigo-500 focus:border-indigo-500': true,
|
'block w-full rounded-md sm:text-sm border-gray-300 dark:border-gray-600 dark:bg-slate-800 dark:text-white dark:placeholder:text-gray-500 focus:ring-primary-500 focus:border-primary-500': true,
|
||||||
}, className)}
|
}, className)}
|
||||||
ref={this.setInput}
|
ref={this.setInput}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -64,7 +64,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
|
||||||
type={revealed ? 'text' : type}
|
type={revealed ? 'text' : type}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={classNames({
|
className={classNames({
|
||||||
'dark:bg-slate-800 dark:text-white block w-full sm:text-sm border-gray-300 dark:border-gray-600 rounded-md focus:ring-indigo-500 focus:border-indigo-500':
|
'dark:bg-slate-800 dark:text-white block w-full sm:text-sm border-gray-300 dark:border-gray-600 rounded-md focus:ring-primary-500 focus:border-primary-500':
|
||||||
true,
|
true,
|
||||||
'pr-7': isPassword,
|
'pr-7': isPassword,
|
||||||
'text-red-600 border-red-600': hasError,
|
'text-red-600 border-red-600': hasError,
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import AutosuggestInput from 'soapbox/components/autosuggest_input';
|
import AutosuggestInput from 'soapbox/components/autosuggest_input';
|
||||||
import Icon from 'soapbox/components/icon';
|
import { Button, HStack, Stack, Text } from 'soapbox/components/ui';
|
||||||
import IconButton from 'soapbox/components/icon_button';
|
|
||||||
import { HStack } from 'soapbox/components/ui';
|
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
import DurationSelector from './polls/duration-selector';
|
||||||
|
|
||||||
import type { AutoSuggestion } from 'soapbox/components/autosuggest_input';
|
import type { AutoSuggestion } from 'soapbox/components/autosuggest_input';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Choice {number}' },
|
option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Answer #{number}' },
|
||||||
add_option: { id: 'compose_form.poll.add_option', defaultMessage: 'Add a choice' },
|
add_option: { id: 'compose_form.poll.add_option', defaultMessage: 'Add an answer' },
|
||||||
remove_option: { id: 'compose_form.poll.remove_option', defaultMessage: 'Remove this choice' },
|
remove_option: { id: 'compose_form.poll.remove_option', defaultMessage: 'Remove this answer' },
|
||||||
poll_duration: { id: 'compose_form.poll.duration', defaultMessage: 'Poll duration' },
|
poll_duration: { id: 'compose_form.poll.duration', defaultMessage: 'Poll duration' },
|
||||||
switchToMultiple: { id: 'compose_form.poll.switch_to_multiple', defaultMessage: 'Change poll to allow multiple choices' },
|
switchToMultiple: { id: 'compose_form.poll.switch_to_multiple', defaultMessage: 'Change poll to allow multiple answers' },
|
||||||
switchToSingle: { id: 'compose_form.poll.switch_to_single', defaultMessage: 'Change poll to allow for a single choice' },
|
switchToSingle: { id: 'compose_form.poll.switch_to_single', defaultMessage: 'Change poll to allow for a single answer' },
|
||||||
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
|
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
|
||||||
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
|
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
|
||||||
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
|
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
|
||||||
|
@ -26,7 +25,6 @@ const messages = defineMessages({
|
||||||
|
|
||||||
interface IOption {
|
interface IOption {
|
||||||
index: number
|
index: number
|
||||||
isPollMultiple?: boolean
|
|
||||||
maxChars: number
|
maxChars: number
|
||||||
numOptions: number
|
numOptions: number
|
||||||
onChange(index: number, value: string): void
|
onChange(index: number, value: string): void
|
||||||
|
@ -35,7 +33,6 @@ interface IOption {
|
||||||
onRemove(index: number): void
|
onRemove(index: number): void
|
||||||
onRemovePoll(): void
|
onRemovePoll(): void
|
||||||
onSuggestionSelected(tokenStart: number, token: string, value: string, key: (string | number)[]): void
|
onSuggestionSelected(tokenStart: number, token: string, value: string, key: (string | number)[]): void
|
||||||
onToggleMultiple(): void
|
|
||||||
suggestions?: any // list
|
suggestions?: any // list
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
@ -43,7 +40,6 @@ interface IOption {
|
||||||
const Option = (props: IOption) => {
|
const Option = (props: IOption) => {
|
||||||
const {
|
const {
|
||||||
index,
|
index,
|
||||||
isPollMultiple,
|
|
||||||
maxChars,
|
maxChars,
|
||||||
numOptions,
|
numOptions,
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -51,7 +47,6 @@ const Option = (props: IOption) => {
|
||||||
onFetchSuggestions,
|
onFetchSuggestions,
|
||||||
onRemove,
|
onRemove,
|
||||||
onRemovePoll,
|
onRemovePoll,
|
||||||
onToggleMultiple,
|
|
||||||
suggestions,
|
suggestions,
|
||||||
title,
|
title,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -68,20 +63,20 @@ const Option = (props: IOption) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleMultiple = (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => {
|
// const handleToggleMultiple = (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => {
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
event.stopPropagation();
|
// event.stopPropagation();
|
||||||
|
|
||||||
onToggleMultiple();
|
// onToggleMultiple();
|
||||||
};
|
// };
|
||||||
|
|
||||||
const onSuggestionsClearRequested = () => onClearSuggestions();
|
const onSuggestionsClearRequested = () => onClearSuggestions();
|
||||||
|
|
||||||
const handleCheckboxKeypress = (event: React.KeyboardEvent<HTMLElement>) => {
|
// const handleCheckboxKeypress = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||||
if (event.key === 'Enter' || event.key === ' ') {
|
// if (event.key === 'Enter' || event.key === ' ') {
|
||||||
handleToggleMultiple(event);
|
// handleToggleMultiple(event);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const onSuggestionsFetchRequested = (token: string) => onFetchSuggestions(token);
|
const onSuggestionsFetchRequested = (token: string) => onFetchSuggestions(token);
|
||||||
|
|
||||||
|
@ -92,17 +87,11 @@ const Option = (props: IOption) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<HStack alignItems='center' justifyContent='between' space={4}>
|
||||||
<label className='poll__text editable'>
|
<HStack alignItems='center' space={2} grow>
|
||||||
<span
|
<div className='w-6'>
|
||||||
className={classNames('poll__input', { checkbox: isPollMultiple })}
|
<Text weight='bold'>{index + 1}</Text>
|
||||||
onClick={handleToggleMultiple}
|
</div>
|
||||||
onKeyPress={handleCheckboxKeypress}
|
|
||||||
role='button'
|
|
||||||
tabIndex={0}
|
|
||||||
title={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)}
|
|
||||||
aria-label={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AutosuggestInput
|
<AutosuggestInput
|
||||||
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
|
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
|
||||||
|
@ -116,16 +105,14 @@ const Option = (props: IOption) => {
|
||||||
searchTokens={[':']}
|
searchTokens={[':']}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</label>
|
</HStack>
|
||||||
|
|
||||||
<div className='poll__cancel'>
|
{index > 1 && (
|
||||||
<IconButton
|
<div>
|
||||||
title={intl.formatMessage(messages.remove_option)}
|
<Button theme='danger' size='sm' onClick={handleOptionRemove}>Delete</Button>
|
||||||
src={require('@tabler/icons/icons/x.svg')}
|
</div>
|
||||||
onClick={handleOptionRemove}
|
)}
|
||||||
/>
|
</HStack>
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -156,27 +143,21 @@ const PollForm = (props: IPollForm) => {
|
||||||
...filteredProps
|
...filteredProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
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 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 handleAddOption = () => onAddOption('');
|
||||||
|
const handleSelectDuration = (value: number) => onChangeSettings(value, isMultiple);
|
||||||
const handleSelectDuration = (event: React.ChangeEvent<HTMLSelectElement>) =>
|
// const handleToggleMultiple = () => onChangeSettings(expiresIn, !isMultiple);
|
||||||
onChangeSettings(event.target.value, isMultiple);
|
|
||||||
|
|
||||||
const handleToggleMultiple = () => onChangeSettings(expiresIn, !isMultiple);
|
|
||||||
|
|
||||||
|
|
||||||
if (!options) {
|
if (!options) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='compose-form__poll-wrapper'>
|
<Stack space={4}>
|
||||||
<ul>
|
<Stack space={2}>
|
||||||
{options.map((title: string, i: number) => (
|
{options.map((title: string, i: number) => (
|
||||||
<Option
|
<Option
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -184,34 +165,41 @@ const PollForm = (props: IPollForm) => {
|
||||||
index={i}
|
index={i}
|
||||||
onChange={onChangeOption}
|
onChange={onChangeOption}
|
||||||
onRemove={onRemoveOption}
|
onRemove={onRemoveOption}
|
||||||
isPollMultiple={isMultiple}
|
|
||||||
onToggleMultiple={handleToggleMultiple}
|
|
||||||
maxChars={maxOptionChars}
|
maxChars={maxOptionChars}
|
||||||
numOptions={options.size}
|
numOptions={options.size}
|
||||||
{...filteredProps}
|
{...filteredProps}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
|
||||||
|
|
||||||
<HStack className='text-black dark:text-white' space={2}>
|
<HStack space={2}>
|
||||||
{options.size < maxOptions && (
|
<div className='w-6' />
|
||||||
<button className='button button-secondary' onClick={handleAddOption}>
|
|
||||||
<Icon src={require('@tabler/icons/icons/plus.svg')} />
|
|
||||||
<FormattedMessage {...messages.add_option} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<select value={expiresIn} onChange={handleSelectDuration}>
|
{options.size < maxOptions && (
|
||||||
<option value={300}>{intl.formatMessage(messages.minutes, { number: 5 })}</option>
|
<Button
|
||||||
<option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
|
theme='secondary'
|
||||||
<option value={3600}>{intl.formatMessage(messages.hours, { number: 1 })}</option>
|
icon={require('@tabler/icons/icons/plus.svg')}
|
||||||
<option value={21600}>{intl.formatMessage(messages.hours, { number: 6 })}</option>
|
onClick={handleAddOption}
|
||||||
<option value={86400}>{intl.formatMessage(messages.days, { number: 1 })}</option>
|
size='sm'
|
||||||
<option value={259200}>{intl.formatMessage(messages.days, { number: 3 })}</option>
|
>
|
||||||
<option value={604800}>{intl.formatMessage(messages.days, { number: 7 })}</option>
|
<FormattedMessage {...messages.add_option} />
|
||||||
</select>
|
</Button>
|
||||||
</HStack>
|
)}
|
||||||
</div>
|
</HStack>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Duration */}
|
||||||
|
<Stack space={2}>
|
||||||
|
<Text size='lg' weight='medium'>Duration</Text>
|
||||||
|
|
||||||
|
<DurationSelector onDurationChange={handleSelectDuration} />
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Remove Poll */}
|
||||||
|
<div className='text-center'>
|
||||||
|
<Button theme='danger' onClick={props.onRemovePoll}>Remove Poll</Button>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { render, screen } from '../../../../../jest/test-helpers';
|
||||||
|
import DurationSelector from '../duration-selector';
|
||||||
|
|
||||||
|
describe('<DurationSelector />', () => {
|
||||||
|
it('defaults to 2 days', () => {
|
||||||
|
const handler = jest.fn();
|
||||||
|
render(<DurationSelector onDurationChange={handler} />);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('duration-selector-days')).toHaveValue('2');
|
||||||
|
expect(screen.getByTestId('duration-selector-hours')).toHaveValue('0');
|
||||||
|
expect(screen.getByTestId('duration-selector-minutes')).toHaveValue('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when changing the day', () => {
|
||||||
|
it('calls the "onDurationChange" callback', async() => {
|
||||||
|
const handler = jest.fn();
|
||||||
|
render(<DurationSelector onDurationChange={handler} />);
|
||||||
|
|
||||||
|
await userEvent.selectOptions(
|
||||||
|
screen.getByTestId('duration-selector-days'),
|
||||||
|
screen.getByRole('option', { name: '1 day' }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(handler.mock.calls[0][0]).toEqual(172800); // 2 days
|
||||||
|
expect(handler.mock.calls[1][0]).toEqual(86400); // 1 day
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable the hour/minute select if 7 days selected', async() => {
|
||||||
|
const handler = jest.fn();
|
||||||
|
render(<DurationSelector onDurationChange={handler} />);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('duration-selector-hours')).not.toBeDisabled();
|
||||||
|
expect(screen.getByTestId('duration-selector-minutes')).not.toBeDisabled();
|
||||||
|
|
||||||
|
await userEvent.selectOptions(
|
||||||
|
screen.getByTestId('duration-selector-days'),
|
||||||
|
screen.getByRole('option', { name: '7 days' }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('duration-selector-hours')).toBeDisabled();
|
||||||
|
expect(screen.getByTestId('duration-selector-minutes')).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when changing the hour', () => {
|
||||||
|
it('calls the "onDurationChange" callback', async() => {
|
||||||
|
const handler = jest.fn();
|
||||||
|
render(<DurationSelector onDurationChange={handler} />);
|
||||||
|
|
||||||
|
await userEvent.selectOptions(
|
||||||
|
screen.getByTestId('duration-selector-hours'),
|
||||||
|
screen.getByRole('option', { name: '1 hour' }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(handler.mock.calls[0][0]).toEqual(172800); // 2 days
|
||||||
|
expect(handler.mock.calls[1][0]).toEqual(176400); // 2 days, 1 hour
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when changing the minute', () => {
|
||||||
|
it('calls the "onDurationChange" callback', async() => {
|
||||||
|
const handler = jest.fn();
|
||||||
|
render(<DurationSelector onDurationChange={handler} />);
|
||||||
|
|
||||||
|
await userEvent.selectOptions(
|
||||||
|
screen.getByTestId('duration-selector-minutes'),
|
||||||
|
screen.getByRole('option', { name: '15 minutes' }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(handler.mock.calls[0][0]).toEqual(172800); // 2 days
|
||||||
|
expect(handler.mock.calls[1][0]).toEqual(173700); // 2 days, 1 minute
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,93 @@
|
||||||
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { Select } from 'soapbox/components/ui';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
|
||||||
|
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
|
||||||
|
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IDurationSelector {
|
||||||
|
onDurationChange(expiresIn: number): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const DurationSelector = ({ onDurationChange }: IDurationSelector) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [days, setDays] = useState<number>(2);
|
||||||
|
const [hours, setHours] = useState<number>(0);
|
||||||
|
const [minutes, setMinutes] = useState<number>(0);
|
||||||
|
|
||||||
|
const value = useMemo(() => {
|
||||||
|
const now: any = new Date();
|
||||||
|
const future: any = new Date();
|
||||||
|
now.setDate(now.getDate() + days);
|
||||||
|
now.setMinutes(now.getMinutes() + minutes);
|
||||||
|
now.setHours(now.getHours() + hours);
|
||||||
|
|
||||||
|
return (now - future) / 1000;
|
||||||
|
}, [days, hours, minutes]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (days === 7) {
|
||||||
|
setHours(0);
|
||||||
|
setMinutes(0);
|
||||||
|
}
|
||||||
|
}, [days]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onDurationChange(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='grid grid-cols-1 gap-y-2 gap-x-2 sm:grid-cols-3'>
|
||||||
|
<div className='sm:col-span-1'>
|
||||||
|
<Select
|
||||||
|
value={days}
|
||||||
|
onChange={(event) => setDays(Number(event.target.value))}
|
||||||
|
data-testid='duration-selector-days'
|
||||||
|
>
|
||||||
|
{[...Array(8).fill(undefined)].map((_, number) => (
|
||||||
|
<option value={number} key={number}>
|
||||||
|
{intl.formatMessage(messages.days, { number })}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='sm:col-span-1'>
|
||||||
|
<Select
|
||||||
|
value={hours}
|
||||||
|
onChange={(event) => setHours(Number(event.target.value))}
|
||||||
|
disabled={days === 7}
|
||||||
|
data-testid='duration-selector-hours'
|
||||||
|
>
|
||||||
|
{[...Array(24).fill(undefined)].map((_, number) => (
|
||||||
|
<option value={number} key={number}>
|
||||||
|
{intl.formatMessage(messages.hours, { number })}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='sm:col-span-1'>
|
||||||
|
<Select
|
||||||
|
value={minutes}
|
||||||
|
onChange={(event) => setMinutes(Number(event.target.value))}
|
||||||
|
disabled={days === 7}
|
||||||
|
data-testid='duration-selector-minutes'
|
||||||
|
>
|
||||||
|
{[0, 15, 30, 45].map((number) => (
|
||||||
|
<option value={number} key={number}>
|
||||||
|
{intl.formatMessage(messages.minutes, { number })}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DurationSelector;
|
|
@ -271,12 +271,12 @@
|
||||||
"compose_form.markdown.unmarked": "Post markdown disabled",
|
"compose_form.markdown.unmarked": "Post markdown disabled",
|
||||||
"compose_form.message": "Message",
|
"compose_form.message": "Message",
|
||||||
"compose_form.placeholder": "What's on your mind?",
|
"compose_form.placeholder": "What's on your mind?",
|
||||||
"compose_form.poll.add_option": "Add a choice",
|
"compose_form.poll.add_option": "Add an answer",
|
||||||
"compose_form.poll.duration": "Poll duration",
|
"compose_form.poll.duration": "Poll duration",
|
||||||
"compose_form.poll.option_placeholder": "Choice {number}",
|
"compose_form.poll.option_placeholder": "Answer #{number}",
|
||||||
"compose_form.poll.remove_option": "Remove this choice",
|
"compose_form.poll.remove_option": "Remove this answer",
|
||||||
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
|
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple answers",
|
||||||
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
|
"compose_form.poll.switch_to_single": "Change poll to allow for a single answer",
|
||||||
"compose_form.publish": "Post",
|
"compose_form.publish": "Post",
|
||||||
"compose_form.publish_loud": "{publish}!",
|
"compose_form.publish_loud": "{publish}!",
|
||||||
"compose_form.schedule": "Schedule",
|
"compose_form.schedule": "Schedule",
|
||||||
|
|
|
@ -118,49 +118,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose-form__poll-wrapper {
|
|
||||||
border-top: 1px solid var(--foreground-color);
|
|
||||||
|
|
||||||
ul {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.button-secondary {
|
|
||||||
@apply h-auto py-1.5 px-2.5 text-primary-600 dark:text-primary-400 border-primary-600;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.poll__text {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
width: calc(100% - (23px + 6px));
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
@apply border border-solid border-primary-600 bg-white dark:bg-slate-800;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 14px;
|
|
||||||
display: inline-block;
|
|
||||||
width: auto;
|
|
||||||
outline: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: right 8px center;
|
|
||||||
background-size: auto 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 6px 10px;
|
|
||||||
padding-right: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button.disabled {
|
|
||||||
color: var(--brand-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.muted .poll {
|
.muted .poll {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue