import { type ComponentPropsWithoutRef } from 'react'; import { type VirtualizerOptions } from '@tanstack/react-virtual'; import { type SelectBoxSlotRecipeVariant } from '../../styled-system/recipes'; import { type SearchBoxProps } from '../SearchBox'; import { type PopoverProps } from '../Popover'; import { type InfiniteScrollDirection } from '../utilities/dom/useInfiniteScroll'; type AllowedValueTypes = string | number | undefined; export type VirtualOptionTypes = Pick, 'estimateSize' | 'overscan'>; export type InfiniteScrollConfig = { /** * Enable infinite scroll functionality. * When enabled, additional options can be loaded dynamically when scrolling. * * @default false */ enabled?: boolean; /** * Callback executed when more options need to be loaded. * Called when user scrolls near the top or bottom of the options list. * * @param direction - The direction of loading ('forward' for bottom, 'backward' for top) */ onLoadMore?: (direction: InfiniteScrollDirection) => Promise; /** * Whether there are more options available to load in the forward direction (bottom). * Used to determine if infinite scroll should trigger when scrolling down. * * @default true */ hasNextPage?: boolean; /** * Whether there are more options available to load in the backward direction (top). * Used to determine if infinite scroll should trigger when scrolling up. * * @default true */ hasPreviousPage?: boolean; /** * The scroll threshold in pixels for triggering infinite scroll. * When the scroll position is within this distance from the top or bottom, * the onLoadMore callback will be triggered. * * @default 100 */ threshold?: number; /** * The error message to display when loading fails. * This message supports internationalization. * * @default "Failed to load data" */ errorMessage?: string; /** * The text for the retry button when loading fails. * This message supports internationalization. * * @default "Retry" */ retryButtonText?: string; }; export type BasedAdditionalProps = Record; export type SelectBoxOption = ({ /** * The label to be displayed in the list. * * This would be the group label of the nested list when options prop is provided. */ label: string; /** * This must be omitted when the options prop is provided. */ value?: never; /** * The options to be nested under the group label. */ options?: SelectBoxOption[]; } & AdditionalProps) | ({ /** * The label to be displayed in the list. */ label: string; /** * The value to be returned when the option is selected. * * This must be unique for each option since it's used to identify the option in the list. */ value?: T; /** * This must be omitted when the value prop is provided. */ options?: never; /** * Whether this option is disabled. */ disabled?: boolean; } & AdditionalProps); /** * A flattened option type representing an individual selectable option (not a group). * Used for keyboard navigation and virtualization. */ export type FlatOption = Extract, { value?: T; }>; export type SelectBoxProps = { /** * The id of this component. * This prop is used to relate the component to the corresponding label. * * @example * ```tsx * * * ``` */ id?: string; /** * Configuration for automatically scrolling to the selected option when opening the dropdown. * When set to false or undefined, no auto-scrolling occurs. * When set to true, uses default settings (smooth behavior, center alignment). * When an object is provided, allows customizing the scroll behavior. * * Please note that enabling this feature will make force the whole page to be scrolled to the selected option. * * @example * ```tsx * // Default smooth scroll to center * * * // Custom scroll behavior * * ``` */ enableAutoScrollToSelectedOption?: boolean | ScrollIntoViewOptions; /** * The properties for the popover content panel. * * @property className - The class name of the popover content panel. * * @property minWidth - The minimum width of the popover content panel. * * @property allowedPlacements - Allowed placements for the popover relative to the trigger element. */ popoverContentProps?: Pick, 'className'> & { minWidth?: PopoverProps['minWidth']; allowedPlacements?: PopoverProps['allowedPlacements']; }; /** * The properties for the popover wrapper element. * * @property maxHeight - The maximum height of the popover wrapper element. */ popoverWrapperProps?: { maxHeight?: PopoverProps['maxHeight']; }; /** * The properties for the trigger element. * * @property aria-label - The alternative text for the trigger element. * * @property className - The class name of the trigger element. */ triggerProps?: Pick, 'aria-label' | 'className'>; /** * Whether you let user to clear the content or not. * This will add the Clear button if current SelectBox has value. * * @default false */ enableClearButton?: boolean; /** * The properties for the clear button. * If this prop is not passed, the default values for each property will be applied. * * @property aria-label - The alternative text for the clear button. Default is '値をクリアする'. * * @default undefined */ clearButtonProps?: Pick, 'aria-label'>; /** * The size of the SelectBox component. */ size?: SelectBoxSlotRecipeVariant['size']; /** * The selectable options for the list * * It should be an array of objects with `label` and `value` properties. * * **Tip**: Use `satisfies` for automatic type inference when using additional properties: * ```tsx * const options = [...] satisfies SelectBoxOption[]; * // Type inference works automatically! * ``` */ options?: SelectBoxOption[]; /** * Pass the initial selected option value. * Use this prop when you want an uncontrolled component. * * Pass a SelectBoxOption object with both label and value. * This is especially useful for async scenarios where the option may not be in the current options list. */ defaultValue?: SelectBoxOption; /** * Pass the currently selected option value. * Use this prop when you want a controlled component. * Should use this prop with "onChange" prop to control the selected option. * * Pass a SelectBoxOption object with both label and value. * This is especially useful for async scenarios where the option may not be in the current options list. */ value?: SelectBoxOption; /** * Text to be displayed when options are not selected. * * @deprecated Placeholder is discouraged by the design guidelines. It will be removed in a future version. */ placeholder?: string; /** * The message to be displayed in the listbox when the options are empty. */ emptyMessage?: string; /** * Whether the trigger is disabled. */ disabled?: boolean; /** * Whether the trigger is invalid. */ invalid?: boolean; /** * Whether the popover width should be constrained to match the trigger element width. * * When enabled, the popover will have the same width as the trigger element, * preventing it from expanding beyond the trigger boundaries even if option * labels are longer. This is useful for maintaining consistent visual alignment * in forms and handling very long option text. * * @default false */ enableConstrainedPopoverWidth?: boolean; /** * The DOM node where the dropdown menu should be rendered. * * If provided, the menu is rendered inside this node. Otherwise, a suitable * DOM node is chosen automatically. */ targetDOMNode?: HTMLElement | null; /** * The name of the input element. * * It's used to identify the input element in the form. */ name?: string; /** * The callback function that is called when the selected option is changed. * * @param option - The full option object that was selected (includes additional properties if defined) * * @example * ```tsx * { * console.log('Selected value:', value); * console.log('Full option:', option); // Access additional option properties * }} * /> * ``` */ onChange?: (option: SelectBoxOption | null) => void; /** * The callback function that is called when the SelectBox loses focus. * This will only fire when the user truly leaves the component (not when the popover opens). * * @param event - The blur event from the trigger element * * @example * ```tsx * { * console.log('SelectBox lost focus'); * // Perform validation or other actions * }} * /> * ``` */ onBlur?: (event: React.FocusEvent) => void; /** * The function to customize what displays inside the trigger button. * - Return only elements that are allowed to be contained inside the `