Add Select component (#37702)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
Chromatic / Check for relevant changes (push) Waiting to run
Chromatic / Run Chromatic (push) Blocked by required conditions
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Crowdin / Upload translations / upload-translations (push) Waiting to run
Check formatting / lint (push) Waiting to run
CSS Linting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Bundler Audit / security (push) Has been cancelled
Haml Linting / lint (push) Has been cancelled

This commit is contained in:
diondiondion 2026-02-02 18:31:34 +01:00 committed by GitHub
parent 6188de3efc
commit ceb4a878fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 127 additions and 38 deletions

View File

@ -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';

View File

@ -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,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='black'/></svg>");
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);
}
}

View File

@ -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 (
<div className='simple_form'>
<SelectField {...args}>
<option>Apple</option>
<option>Banana</option>
<option>Kiwi</option>
<option>Lemon</option>
<option>Mango</option>
<option>Orange</option>
<option>Pomelo</option>
<option>Strawberries</option>
<option>Something else</option>
</SelectField>
</div>
);
children: (
<>
<option>Apple</option>
<option>Banana</option>
<option>Kiwi</option>
<option>Lemon</option>
<option>Mango</option>
<option>Orange</option>
<option>Pomelo</option>
<option>Strawberries</option>
<option>Something else</option>
</>
),
},
} satisfies Meta<typeof SelectField>;
@ -59,3 +54,16 @@ export const WithError: Story = {
hasError: true,
},
};
export const Plain: Story = {
render(args) {
return <Select {...args} />;
},
};
export const Disabled: Story = {
...Plain,
args: {
disabled: true,
},
};

View File

@ -1,8 +1,11 @@
import type { ComponentPropsWithoutRef } from 'react';
import { forwardRef } from 'react';
import classNames from 'classnames';
import { FormFieldWrapper } from './form_field_wrapper';
import type { CommonFieldWrapperProps } from './form_field_wrapper';
import classes from './select.module.scss';
interface Props
extends ComponentPropsWithoutRef<'select'>, CommonFieldWrapperProps {}
@ -25,14 +28,27 @@ export const SelectField = forwardRef<HTMLSelectElement, Props>(
inputId={id}
>
{(inputProps) => (
<div className='select-wrapper'>
<select {...otherProps} {...inputProps} ref={ref}>
{children}
</select>
</div>
<Select {...otherProps} {...inputProps} ref={ref}>
{children}
</Select>
)}
</FormFieldWrapper>
),
);
SelectField.displayName = 'SelectField';
export const Select = forwardRef<
HTMLSelectElement,
ComponentPropsWithoutRef<'select'>
>(({ className, size, ...otherProps }, ref) => (
<div className={classes.wrapper}>
<select
{...otherProps}
className={classNames(className, classes.select)}
ref={ref}
/>
</div>
));
Select.displayName = 'Select';

View File

@ -8,6 +8,7 @@ import { createSelector } from '@reduxjs/toolkit';
import type { List as ImmutableList } from 'immutable';
import type { SelectItem } from '@/mastodon/components/dropdown_selector';
import { Select } from '@/mastodon/components/form_fields';
import type { RootState } from '@/mastodon/store';
import { useAppSelector } from '@/mastodon/store';
@ -104,19 +105,17 @@ export const RulesSection: FC<RulesSectionProps> = ({ isLoading = false }) => {
defaultMessage='Language'
/>
</label>
<div className='select-wrapper'>
<select onChange={handleLocaleChange} id='language-select'>
{localeOptions.map((option) => (
<option
key={option.value}
value={option.value}
selected={option.value === selectedLocale}
>
{option.text}
</option>
))}
</select>
</div>
<Select onChange={handleLocaleChange} id='language-select'>
{localeOptions.map((option) => (
<option
key={option.value}
value={option.value}
selected={option.value === selectedLocale}
>
{option.text}
</option>
))}
</Select>
</div>
)}
</Section>