import React, { forwardRef, RefAttributes, useContext, type JSX } from "react"; import { useListState, Node, ListState, SectionProps } from "react-stately"; import { useListBox, useListBoxSection, useOption } from "@react-aria/listbox"; import { mergeProps } from "@react-aria/utils"; import { BaseCollection, Collection, CollectionBuilder, } from "@react-aria/collections"; import { RenderStyleProps } from "../../../types"; import { useRenderProps } from "@hooks"; import { useFocusRing } from "@hooks/useFocusRing"; import { useForwardedRef } from "@hooks/useForwardedRef"; import { useCollectionRenderer } from "@hooks/useCollectionRenderer"; import { Label } from "@components/Fields"; import { Divider } from "@components/Content/Divider"; import { Provider } from "@components/Internal/Provider"; import { ItemContext, ItemProps, SectionContext } from "@components/Collection"; import { useContextProps } from "@hooks/useContextProps"; import { List, ListItem, SectionTitle, SubList } from "./ListBox.styles"; import { ListBoxProps } from "./ListBox.types"; import { ListBoxContext, ListStateContext } from "./ListBox.context"; type ForwardedListBox = { (props: ListBoxProps & RefAttributes): JSX.Element; displayName: string; }; /** A listbox displays a list of options and allows a user to select one or more of them. * Used as the dropdown menu for `ComboBox` and `CustomSelect` */ export const ListBox = forwardRef(function ListBox( props: ListBoxProps, ref: React.Ref ) { const state = useContext(ListStateContext); [props, ref] = useContextProps(ListBoxContext, props, ref); // When used within a SelectField or similar component, // the state will be provided by the parent component if (state) { return ; } // When rendered standalone, we need to build the collection and // manage the state ourselves return ( }> {(collection: BaseCollection) => { return ( {...props} collection={collection} listBoxRef={ref} /> ); }} ); }) as ForwardedListBox; ListBox.displayName = "ListBox"; interface ManagedListBoxProps extends ListBoxProps { collection: BaseCollection; listBoxRef: React.RefObject; } function ManagedListBox(props: ManagedListBoxProps) { const state = useListState(props); return ; } export interface InternalListBoxProps extends Omit, "children"> { state: ListState; listBoxRef: React.RefObject; } export function InternalListBox(props: InternalListBoxProps) { const { state, className, size = "medium", listBoxRef } = props; const { listBoxProps, labelProps } = useListBox(props, state, listBoxRef); const { CollectionRenderer } = useCollectionRenderer(); const renderProps = useRenderProps({ componentClassName: "aje-listbox", className, size, }); return ( <> {props.label && } } /> ); } interface ListBoxSectionProps extends SectionProps, RenderStyleProps {} function ListBoxSection( props: ListBoxSectionProps, ref: React.ForwardedRef, section: Node ) { const state = useContext(ListStateContext)!; const { itemProps, headingProps, groupProps } = useListBoxSection({ heading: section.rendered, "aria-label": section["aria-label"], }); const renderProps = useRenderProps({ componentClassName: "aje-listbox__section", ...props, children: props.title, }); const { CollectionBranchRenderer } = useCollectionRenderer(); return ( <> {section.key !== state.collection.getFirstKey() && }
  • {renderProps.children && ( {renderProps.children} )} } parent={section} />
  • ); } function ListBoxItem( props: ItemProps, ref: React.ForwardedRef, item: Node ) { const state = useContext(ListStateContext)!; const internalRef = useForwardedRef(ref); const { optionProps, isSelected } = useOption( { key: item.key }, state, internalRef ); // Determine whether we should show a keyboard // focus ring for accessibility const { focusProps, isFocused, isFocusVisible } = useFocusRing(); const renderProps = useRenderProps({ componentClassName: "aje-listbox__item", ...props, children: item.rendered, values: { isSelected, isFocused, isFocusVisible, }, selectors: { "data-selected": isSelected, }, }); return ( {renderProps.children} ); }