import React, { useId, useState } from 'react' import classnames from 'classnames' import { useButton } from 'react-aria' import ReactSelect, { components, type NoticeProps, type Props as ReactSelectProps, } from 'react-select' import Async, { type AsyncProps as ReactAsyncSelectProps } from 'react-select/async' import { ClearButton } from '~components/ClearButton' import { FieldMessage } from '~components/FieldMessage' import { Icon } from '~components/Icon' import { Label } from '~components/Label' import { Tag } from '~components/Tag' import styles from './Select.module.scss' export type SelectProps = { /** * The secondary variant is a more subdued variant that takes up as little space as possible * `variant="secondary" reversed="false" is not implemented and will throw a "not implemented" error * @default "default" */ variant?: 'default' | 'secondary' | 'secondary-small' status?: 'default' | 'error' label?: React.ReactNode validationMessage?: React.ReactNode description?: React.ReactNode /** * Use a reversed colour scheme * @default false */ reversed?: boolean /** * Whether the "select control" (the button you click to open the menu) width fills the * full width of the container or is as wide as the selected option text. * Note that the control text will ellipsize if it is wider than the parent container. * @default false */ fullWidth?: boolean } & Omit, 'placeholder'> // react-select defaults to showing "Select..." placeholder text, which goes against our a11y // standards — use `label` for the field name and `description` for help text instead. // `noPlaceholderText` overrides the default string; `NullPlaceholder` removes the empty DOM node // that react-select still renders even when the text is empty. const noPlaceholderText = '' const NullPlaceholder = (): null => null /** * {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3081896474/Select Guidance} | * {@link https://cultureamp.design/?path=/docs/components-select--docs Storybook} */ export const Select = React.forwardRef( ( { variant = 'default', status = 'default', reversed = false, label, validationMessage, description, fullWidth: propsFullWidth, className: propsClassName, ...props }, ref, ) => { const reactId = useId() // the default for fullWidth depends on the variant const fullWidth = propsFullWidth ?? !['secondary', 'secondary-small'].includes(variant) const classes = classnames( propsClassName, styles.specificityIncreaser, (!reversed || variant === 'default') && styles.default, reversed && styles.reversed, variant === 'secondary' && styles.secondary, variant === 'secondary-small' && styles.secondarySmall, !fullWidth && styles.notFullWidth, props.isDisabled && styles.disabled, status === 'error' && styles.error, ) const [labelId] = useState(label ? reactId : undefined) return ( <> {label ? ( ) : null} {validationMessage ? : null} {description ? : null} ) }, ) Select.displayName = 'Select' interface AsyncProps extends Omit, 'placeholder'>, Omit, 'placeholder'> {} export const AsyncSelect = React.forwardRef( ({ className: propsClassName, ...props }: AsyncProps, ref: React.Ref) => ( ), ) AsyncSelect.displayName = 'AsyncSelect' const Control: typeof components.Control = (props) => (
) const DropdownIndicator: typeof components.DropdownIndicator = (props) => ( ) const LoadingMessage = (props: NoticeProps): JSX.Element => ( ) const Menu: typeof components.Menu = (props) => ( ) const GroupHeading: typeof components.GroupHeading = (props) => ( ) const Option: typeof components.Option = (props) => (
) const NoOptionsMessage = (props: NoticeProps): JSX.Element => ( {props.children} ) const SingleValue: typeof components.SingleValue = (props) => ( {props.children} ) const MultiValue: typeof components.MultiValue = (props) => (
{props.children}
) const IndicatorsContainer: typeof components.IndicatorsContainer = (props) => ( ) const Input: typeof components.Input = (props) => ( ) const ValueContainer: typeof components.ValueContainer = (props) => ( ) const ClearIndicator: typeof components.ClearIndicator = (props) => { const buttonRef = React.useRef(null) const { buttonProps } = useButton({ ...props, onClick: props.clearValue }, buttonRef) return }