From ceb4a878fb3ea9a4c8cafaf5bfedfab8dc2622e1 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Mon, 2 Feb 2026 18:31:34 +0100 Subject: [PATCH] Add `Select` component (#37702) --- .../mastodon/components/form_fields/index.ts | 2 +- .../components/form_fields/select.module.scss | 66 +++++++++++++++++++ .../form_fields/select_field.stories.tsx | 46 +++++++------ .../components/form_fields/select_field.tsx | 26 ++++++-- .../features/about/components/rules.tsx | 25 ++++--- 5 files changed, 127 insertions(+), 38 deletions(-) create mode 100644 app/javascript/mastodon/components/form_fields/select.module.scss diff --git a/app/javascript/mastodon/components/form_fields/index.ts b/app/javascript/mastodon/components/form_fields/index.ts index 8dd693d51e1..76137dd37a0 100644 --- a/app/javascript/mastodon/components/form_fields/index.ts +++ b/app/javascript/mastodon/components/form_fields/index.ts @@ -2,4 +2,4 @@ export { TextInputField } from './text_input_field'; export { TextAreaField } from './text_area_field'; export { CheckboxField, Checkbox } from './checkbox_field'; export { ToggleField, Toggle } from './toggle_field'; -export { SelectField } from './select_field'; +export { SelectField, Select } from './select_field'; diff --git a/app/javascript/mastodon/components/form_fields/select.module.scss b/app/javascript/mastodon/components/form_fields/select.module.scss new file mode 100644 index 00000000000..e68e248fec6 --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/select.module.scss @@ -0,0 +1,66 @@ +.wrapper { + position: relative; + width: 100%; + + /* Dropdown indicator icon */ + &::after { + --icon-size: 11px; + + content: ''; + position: absolute; + top: 0; + bottom: 0; + inset-inline-end: 9px; + width: var(--icon-size); + background-color: var(--color-text-tertiary); + pointer-events: none; + mask-image: url("data:image/svg+xml;utf8,"); + mask-position: right center; + mask-size: var(--icon-size); + mask-repeat: no-repeat; + } + + &:has(.select:focus-visible)::after { + background-color: var(--color-text-secondary); + } + + &:has(.select:disabled)::after { + background-color: var(--color-text-disabled); + } +} + +.select { + appearance: none; + box-sizing: border-box; + display: block; + width: 100%; + height: 41px; + padding-inline-start: 10px; + padding-inline-end: 30px; + font-family: inherit; + font-size: 14px; + color: var(--color-text-primary); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border-primary); + border-radius: 4px; + outline: 0; + + @media screen and (width <= 600px) { + font-size: 16px; + } + + &:focus-visible { + outline: var(--outline-focus-default); + outline-offset: -1px; + } + + &:disabled { + color: var(--color-text-disabled); + border-color: transparent; + cursor: not-allowed; + } + + [data-has-error='true'] & { + border-color: var(--color-text-error); + } +} diff --git a/app/javascript/mastodon/components/form_fields/select_field.stories.tsx b/app/javascript/mastodon/components/form_fields/select_field.stories.tsx index 762436fe287..469238dd44d 100644 --- a/app/javascript/mastodon/components/form_fields/select_field.stories.tsx +++ b/app/javascript/mastodon/components/form_fields/select_field.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { SelectField } from './select_field'; +import { SelectField, Select } from './select_field'; const meta = { title: 'Components/Form Fields/SelectField', @@ -8,24 +8,19 @@ const meta = { args: { label: 'Fruit preference', hint: 'Select your favourite fruit or not. Up to you.', - }, - render(args) { - // Component styles require a wrapper class at the moment - return ( -
- - - - - - - - - - - -
- ); + children: ( + <> + + + + + + + + + + + ), }, } satisfies Meta; @@ -59,3 +54,16 @@ export const WithError: Story = { hasError: true, }, }; + +export const Plain: Story = { + render(args) { + return - {children} - - + )} ), ); SelectField.displayName = 'SelectField'; + +export const Select = forwardRef< + HTMLSelectElement, + ComponentPropsWithoutRef<'select'> +>(({ className, size, ...otherProps }, ref) => ( +
+ - {localeOptions.map((option) => ( - - ))} - -
+ )}