diff --git a/app/javascript/mastodon/components/button/index.tsx b/app/javascript/mastodon/components/button/index.tsx index f940c07d056..c40d2b246d3 100644 --- a/app/javascript/mastodon/components/button/index.tsx +++ b/app/javascript/mastodon/components/button/index.tsx @@ -24,13 +24,13 @@ interface PropsWithText extends BaseProps { children?: undefined; } -type Props = PropsWithText | PropsChildren; +export type ButtonProps = PropsWithText | PropsChildren; /** * Primary UI component for user interaction that doesn't result in navigation. */ -export const Button: React.FC = ({ +export const Button: React.FC = ({ type = 'button', onClick, disabled, diff --git a/app/javascript/mastodon/components/dropdown/index.tsx b/app/javascript/mastodon/components/dropdown/index.tsx index b6a04b9027f..fb4e9014927 100644 --- a/app/javascript/mastodon/components/dropdown/index.tsx +++ b/app/javascript/mastodon/components/dropdown/index.tsx @@ -21,7 +21,7 @@ interface DropdownProps { items: SelectItem[]; onChange: (value: string) => void; current: string; - labelId: string; + labelId?: string; descriptionId?: string; emptyText?: MessageDescriptor; classPrefix: string; @@ -79,7 +79,7 @@ export const Dropdown: FC< type='button' {...buttonProps} id={buttonId} - aria-labelledby={`${labelId} ${buttonId}`} + aria-labelledby={classNames(labelId, buttonId)} aria-describedby={descriptionId} aria-expanded={open} aria-controls={listboxId} diff --git a/app/javascript/mastodon/features/ui/components/donate_modal.tsx b/app/javascript/mastodon/features/ui/components/donate_modal.tsx new file mode 100644 index 00000000000..b51574b3e53 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/donate_modal.tsx @@ -0,0 +1,163 @@ +import type { FC, SyntheticEvent } from 'react'; +import { forwardRef, useCallback, useState } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import type { ButtonProps } from '@/mastodon/components/button'; +import { Button } from '@/mastodon/components/button'; +import { Dropdown } from '@/mastodon/components/dropdown'; +import type { SelectItem } from '@/mastodon/components/dropdown_selector'; +import { IconButton } from '@/mastodon/components/icon_button'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; + +import type { BaseConfirmationModalProps } from './confirmation_modals/confirmation_modal'; + +import './donate_model.scss'; + +type DonateModalProps = BaseConfirmationModalProps; + +type Frequency = 'one_time' | 'monthly'; + +const messages = defineMessages({ + close: { id: 'lightbox.close', defaultMessage: 'Close' }, +}); + +const currencyOptions = [ + { + value: 'usd', + text: 'USD', + }, + { + value: 'eur', + text: 'EUR', + }, +] as const satisfies SelectItem[]; +type Currency = (typeof currencyOptions)[number]['value']; +function isCurrency(value: string): value is Currency { + return currencyOptions.some((option) => option.value === value); +} + +const amountOptions = [ + { value: '2000', text: '€20' }, + { value: '1000', text: '€10' }, + { value: '500', text: '€5' }, + { value: '300', text: '€3' }, +] as const satisfies SelectItem[]; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars -- React throws a warning if not set. +const DonateModal: FC = forwardRef(({ onClose }, ref) => { + const intl = useIntl(); + + const [frequency, setFrequency] = useState('one_time'); + const handleFrequencyToggle = useCallback((value: Frequency) => { + return () => { + setFrequency(value); + }; + }, []); + + const [currency, setCurrency] = useState('usd'); + const handleCurrencyChange = useCallback((value: string) => { + if (isCurrency(value)) { + setCurrency(value); + } + }, []); + + const [amount, setAmount] = useState(() => + Number.parseInt(amountOptions[0].value), + ); + const handleAmountChange = useCallback((event: SyntheticEvent) => { + if ( + event.target instanceof HTMLButtonElement || + event.target instanceof HTMLInputElement + ) { + setAmount(Number.parseInt(event.target.value)); + } + }, []); + + return ( +
+
+ + + By supporting Mastodon, you help sustain a global network that values + people over profit. Will you join us today? + +
+
+
+
+ + One Time + + + Monthly + +
+ +
+ + +
+ +
+ {amountOptions.map((option) => ( + + ))} +
+ + +
+
+
+ ); +}); +DonateModal.displayName = 'DonateModal'; + +const ToggleButton: FC = ({ + active, + ...props +}) => { + return ( +