import type { CSSProperties, Key, Ref, RefObject } from "react"; import type { AriaInputProps, FocusEvents, HTMLInputBaseProps, RebuiltInputCommonProps } from "../sharedHelpers/types"; import type { FormFieldProps } from "../FormField"; import type { InputTextRebuiltProps, InputTextRef } from "../InputText"; /** * ARIA attributes for Autocomplete with managed/orchestrated behavior. * Certain ARIA attributes are controlled internally by Autocomplete's * state management and floating-ui integration and shouldn't be overridden. */ interface AriaInputPropsManaged extends Omit { } export type ExtraProps = Record; type OptionValue = string | number; export interface BaseOption { label: string; } export interface Option extends BaseOption { value?: OptionValue; description?: string; details?: string; } export interface GroupOption extends BaseOption { options: Option[]; } export type OptionCollection = AnyOption[]; export type AnyOption = GenericOption; export type OptionInGroup = T extends GroupOption ? T["options"][number] : T; export interface AutocompleteBaseProps extends Pick { /** * @deprecated * Use `ref` instead. */ readonly inputRef?: FormFieldProps["inputRef"]; readonly initialOptions?: GenericOption[]; /** * Set Autocomplete value. */ readonly value: GenericOptionValue | undefined; /** * Allow the autocomplete to use values not from the drop down menu. * * @default true */ readonly allowFreeForm?: boolean; /** * Debounce in milliseconds for getOptions * * @default 300 */ readonly debounce?: number; /** * Simplified onChange handler that only provides the new value. * @param newValue */ onChange(newValue?: GenericOptionValue): void; /** * Called as the user types in the input. The autocomplete will display what * is returned from this method to the user as available options. * @param newInputText */ getOptions(newInputText: string): Array | Promise>; /** * Hint text that goes above the value once the form is filled out. */ readonly placeholder: string; /** * Override the content rendered in the menu. */ readonly customRenderMenu?: (props: CustomOptionsMenuProp) => React.ReactElement; } export interface AutocompleteLegacyProps extends AutocompleteBaseProps { /** * Version of the component to use. * @default 1 */ readonly version?: 1; /** * @deprecated * Use `ref` instead. */ readonly inputRef?: FormFieldProps["inputRef"]; /** * Initial options to display in the autocomplete. */ readonly initialOptions?: GenericOption[]; /** * Called as the user types in the input. The autocomplete will display what * is returned from this method to the user as available options. * @param newInputText */ readonly getOptions: (newInputText: string) => Array | Promise>; /** * Validations to run on the input. */ readonly validations?: FormFieldProps["validations"]; } export type AutocompleteProps = AutocompleteLegacyProps; export type CustomOptionsMenuType = (props: CustomOptionsMenuProp) => React.ReactElement; export interface MenuProps { readonly options: GenericOption[]; readonly selectedOption?: GenericOptionValue; readonly inputFocused: boolean; /** * Element that the menu is attached to when the menu opens. */ readonly attachTo: HTMLDivElement | null; /** * Ref to the TextInput element. */ readonly inputRef: RefObject; onOptionSelect(chosenOption?: GenericOptionValue): void; readonly customRenderMenu?: (props: CustomOptionsMenuProp) => React.ReactElement; } export interface CustomOptionsMenuProp { /** * The options to display in the menu */ options: Array | GenericOption>; /** * The currently selected option */ selectedOption?: GenericOptionValue; /** * The HTML element that wraps the menu content. Used for handling keyboard scroll behavior. */ readonly menuRef: HTMLElement | null | undefined; /** * Callback to select an option */ readonly onOptionSelect: (chosenOption?: GenericOptionValue) => void; /** * Determine if the input is focused. Can be used to conditionally render the menu. */ readonly inputFocused: boolean; /** * Ref to the TextInput element. * v1 provides InputTextRef; v2 provides a DOM element ref. */ readonly inputRef: RefObject; /** * Component that wraps the menu content. Used for handling keyboard scroll behavior. */ readonly MenuWrapper: (props: { children: React.ReactNode; visible: boolean; }) => React.ReactElement; } export interface OptionLike { label: string; /** * "label" will be used as the key by default * If labels are not unique, a unique key must be provided */ key?: Key; } interface MenuActionBase { type: "action"; label: string; /** * "label" will be used as the key by default * If labels are not unique, a unique key must be provided */ key?: Key; /** * Determines if the menu should close when the action is used. * * @default true */ shouldClose?: boolean; onClick: () => void; } export type MenuAction = MenuActionBase & Extra; export interface ActionConfig { run: () => void; closeOnRun?: boolean; } export type MenuSection = SectionExtra & { type: "section"; label: string; /** * "label" will be used as the key by default * If labels are not unique, a unique key must be provided */ key?: Key; options: T[]; actions?: MenuAction[]; }; export interface MenuOptions { type: "options"; options: T[]; actions?: MenuAction[]; } export type MenuHeader = Extra & { type: "header"; label: string; /** * "label" will be used as the key by default * If labels are not unique, a unique key must be provided */ key?: Key; /** * If provided, the header item is interactive and participates in * arrow-key navigation. Activated with Enter. */ onClick?: () => void; /** * Determines if the menu should close when the header is activated. * @default true */ shouldClose?: boolean; }; export type MenuFooter = Extra & { type: "footer"; label: string; /** * "label" will be used as the key by default * If labels are not unique, a unique key must be provided */ key?: Key; /** * If provided, the footer item is interactive and participates in * arrow-key navigation. Activated with Enter. */ onClick?: () => void; /** * Determines if the menu should close when the footer is activated. * @default true */ shouldClose?: boolean; }; export type MenuItem = MenuSection | MenuOptions | MenuHeader | MenuFooter; export type AutocompleteValue = Multiple extends true ? Value[] : Value | undefined; interface AutocompleteRebuiltBaseProps extends AriaInputPropsManaged, Pick, Pick, FocusEvents { /** * Whether the autocomplete allows multiple selections. * When true, selected values are displayed as dismissible chips above the input. * The menu stays open after each selection so the user can pick additional options. * Pressing Backspace on an empty input removes the most recently added selection. */ readonly multiple?: Multiple; /** * The currently selected value of the Autocomplete. * Single-select: undefined indicates no selection */ readonly value: AutocompleteValue; /** * The current input value of the Autocomplete. */ readonly inputValue: string; /** * Callback invoked when the input value changes. */ readonly onInputChange: (value: string) => void; /** * Custom equality for input text to option mapping. * Defaults to case-sensitive label equality via getOptionLabel. */ readonly inputEqualsOption?: (input: string, option: Value) => boolean; /** * Data structure for the menu. * Observes a data hierarchy to determine elements, order, and grouping. * Accepts Sections, Options as top level objects in the array. * Actions may appear in both sections and options. */ readonly menu: MenuItem[]; /** * Controls how options are filtered in response to the current input value. * - Omit to use the default case-insensitive substring match against labels using getOptionLabel * - Provide a function to implement custom filtering logic * - Set to `false` to opt out of filtering entirely (useful for async options) */ readonly filterOptions?: ((options: Value[], inputValue: string) => Value[]) | false; /** * Used to determine the label for a given option, useful for custom data for options. * Defaults to option.label. */ readonly getOptionLabel?: (option: Value) => string; /** * Debounce in milliseconds for input-driven filtering and search render. * Set to 0 to disable debouncing. * * @default 300 */ readonly debounce?: number; /** * Render prop to customize the rendering of an option. * @param args.value - The option value including all extra keys from the menu item * @param args.isActive - Whether the option is currently highlighted/active * @param args.isSelected - Whether the option is currently selected */ readonly customRenderOption?: (args: { value: Value; isActive: boolean; isSelected: boolean; }) => React.ReactNode; /** * Render prop to customize the rendering of a section. * @param args.section - The section value including all extra keys from the menu item */ readonly customRenderSection?: (section: MenuSection) => React.ReactNode; /** * Render prop to customize the rendering of an action. * @param args.value - The action value including all extra keys from the menu item * @param args.isActive - Whether the action is currently highlighted/active * @param args.origin - The origin of the action ("menu" or "empty") */ readonly customRenderAction?: (args: { value: MenuAction; isActive: boolean; origin?: ActionOrigin; }) => React.ReactNode; /** * Render prop to customize the rendering of header items. */ readonly customRenderHeader?: (args: { value: MenuHeader; isActive?: boolean; }) => React.ReactNode; /** * Render prop to customize the rendering of footer items. */ readonly customRenderFooter?: (args: { value: MenuFooter; isActive?: boolean; }) => React.ReactNode; /** * Render prop to customize the content inside each selection chip. * Only applicable in `multiple` mode. The Autocomplete handles the chip container, * padding, dismiss button, hover/focus states, and disabled/readOnly behavior. * You only provide the content that appears to the left of the dismiss button. * * When not provided, the chip content defaults to `getOptionLabel(option)`. * * @param args.value - The selected option this chip represents * @param args.getOptionLabel - Function to get the display text for an option */ readonly customRenderValue?: (args: { value: Value; getOptionLabel: (option: Value) => string; }) => React.ReactNode; /** * Render prop to customize the rendering of the input. * @param props.inputRef - The ref to the input element * @param props.inputProps - The props to pass to the input element * Note that you must pass the inputRef to the input */ readonly customRenderInput?: (props: { inputRef: Ref; inputProps: InputTextRebuiltProps; }) => React.ReactNode; /** * **Use at your own risk:** Custom class names for specific elements. This should only be used as a * **last resort**. Using this may result in unexpected side effects. * More information in the [Customizing components Guide](https://atlantis.getjobber.com/guides/customizing-components). */ readonly UNSAFE_className?: { menu?: string; option?: string; section?: string; action?: string; input?: string; header?: string; footer?: string; selection?: string; }; /** * **Use at your own risk:** Custom style for specific elements. This should only be used as a * **last resort**. Using this may result in unexpected side effects. * More information in the [Customizing components Guide](https://atlantis.getjobber.com/guides/customizing-components). */ readonly UNSAFE_styles?: { menu?: CSSProperties; option?: CSSProperties; section?: CSSProperties; action?: CSSProperties; input?: CSSProperties; header?: CSSProperties; footer?: CSSProperties; selection?: CSSProperties; }; /** * Render a custom empty state when the menu is empty. * NOTE: Do not put interactive elements in the empty state, it will break accessibility. * If you require interactive elements in the empty state, use the `emptyActions` prop. * To opt out of the default empty state message entirely use "false". * * @default string "No options" */ readonly emptyStateMessage?: React.ReactNode; /** * Actions to display when there are no options to render after filtering. * Can be a static list or a function that derives actions from the current input value. * When provided and options are empty, these are rendered as navigable actions. Compatible with or without `emptyStateMessage`. */ readonly emptyActions?: MenuAction[] | ((args: { inputValue: string; }) => MenuAction[]); /** * Maximum number of selection chips visible when the input is not focused. * When the input gains focus, all selections are shown regardless of this value. * Set to `-1` to always show all selections. * * Only applicable when `multiple` is `true`. Ignored for single-select. * * @default 6 */ readonly limitVisibleSelections?: number; /** * Function to generate the label displayed when selections are truncated * by `limitVisibleSelections`. Receives the number of hidden selections. * The final returned content must be Phrasing Content eg. string, span, etc. * * Only applicable when `multiple` is `true`. Ignored for single-select. * * @default (count) => `+${count}` */ readonly limitSelectionText?: (truncatedCount: number) => React.ReactNode; /** * Whether the menu should open when the input gains focus. * Note: Clicking on the input will always open the menu. * openOnFocus only determines the behavior of focus events such as tabs or programmatic focus. * * @default true */ readonly openOnFocus?: boolean; /** * Callback invoked when the menu opens. * */ readonly onOpen?: () => void; /** * Callback invoked when the menu closes. * */ readonly onClose?: () => void; /** * Custom render prop for content to render when `loading` is true. */ readonly customRenderLoading?: React.ReactNode; /** * Custom equality for option to value mapping. */ readonly isOptionEqualToValue?: (option: Value, value: Value) => boolean; } interface FreeFormOff { /** * Whether the autocomplete allows free-form input. * When true, the input value is not restricted to the options in the menu. Input can be used to create a new value. * When false, the input value must match an option in the menu. * Input value will be cleared if no selection is made and focus is lost. */ readonly allowFreeForm?: false; /** * Callback invoked when the selection value changes. */ readonly onChange: (value: AutocompleteValue) => void; } interface FreeFormOn { /** * Whether the autocomplete allows free-form input. * When true, the input value is not restricted to the options * in the menu. Input can be used to create a new value. * When false, the input value must match an option in the menu. * Input value will be cleared if no selection is made and * Whether the autocomplete allows free-form input. * When true, the input value is not restricted to the options in the menu. Input can be used to create a new value. * When false, the input value must match an option in the menu. * Input value will be cleared if no selection is made and focus is lost. * */ readonly allowFreeForm: true; /** * Factory used to create a Value from free-form input when committing. Necessary with complex option values. The only value the input can produce is a string. * @param input - The input value */ readonly createFreeFormValue: (input: string) => Value; /** * Callback invoked when the selection value changes. * This is called when we consider a selection "committed" * - The user presses enter * - The user clicks outside the menu with a selection typed * - The user clicks the clear button * The user clears a previous selection by deleting the input value * - The user selects an option with click or enter * - The user types a value that matches an option * - The user types a value that does not match an option and allowFreeForm is true */ readonly onChange: (value: AutocompleteValue) => void; } export type ActionOrigin = "menu" | "empty"; export type AutocompleteRebuiltProps = AutocompleteRebuiltBaseProps & (FreeFormOn | FreeFormOff); export declare const menuOptions: (options: T[], actions?: MenuAction[]) => MenuOptions; export declare const menuSection: (label: string, options: T[], actions?: MenuAction[], extra?: SectionExtra) => MenuSection; export declare function defineMenu(menu: MenuItem[]): MenuItem[]; export {};