diff --git a/packages/components/dropdown/src/Dropdown.doc.mdx b/packages/components/dropdown/src/Dropdown.doc.mdx index 684a39839..859b58d3d 100644 --- a/packages/components/dropdown/src/Dropdown.doc.mdx +++ b/packages/components/dropdown/src/Dropdown.doc.mdx @@ -41,7 +41,7 @@ import { Dropdown } from '@spark-ui/dropdown' }, 'Dropdown.LeadingIcon': { of: Dropdown.LeadingIcon, - description: "Prepend a decorative icon inside the input (to the left).", + description: 'Prepend a decorative icon inside the input (to the left).', }, 'Dropdown.Popover': { of: Dropdown.Popover, @@ -62,6 +62,11 @@ import { Dropdown } from '@spark-ui/dropdown' description: 'The textual part of the item. It should only contain the text you want to see in the trigger when that item is selected. It should not be styled to ensure correct positioning.', }, + 'Dropdown.ItemIndicator': { + of: Dropdown.ItemIndicator, + description: + 'Renders when the item is selected. You can style this element directly, or you can use it as a wrapper to put an icon into, or both.', + }, 'Dropdown.Divider': { of: Dropdown.Divider, description: 'Used to visually separate items in the select.', @@ -101,7 +106,7 @@ TODO ### Item indicator -TODO + ### Trigger leading icon diff --git a/packages/components/dropdown/src/Dropdown.stories.tsx b/packages/components/dropdown/src/Dropdown.stories.tsx index 5f3c0263c..b9c5eaef2 100644 --- a/packages/components/dropdown/src/Dropdown.stories.tsx +++ b/packages/components/dropdown/src/Dropdown.stories.tsx @@ -198,6 +198,51 @@ export const Grouped: StoryFn = _args => { ) } +export const ItemIndicator: StoryFn = _args => { + return ( +
+ + + + + + + + + + To Kill a Mockingbird + New + + + + War and Peace + New + + + + The Idiot + New + + + A Picture of Dorian Gray + New + + + + 1984 + New + + + Pride and Prejudice + New + + + + +
+ ) +} + export const FormFieldLabel: StoryFn = _args => { return (
diff --git a/packages/components/dropdown/src/Dropdown.test.tsx b/packages/components/dropdown/src/Dropdown.test.tsx index f4ca1c806..ecdff62a0 100644 --- a/packages/components/dropdown/src/Dropdown.test.tsx +++ b/packages/components/dropdown/src/Dropdown.test.tsx @@ -302,6 +302,36 @@ describe('Dropdown', () => { expect(getItem('1984')).toHaveAttribute('aria-selected', 'true') }) + it('should render default selected option with proper indicator when including it', () => { + // Given a dropdown with a default selected value + render( + + + + + + + + + War and Peace + + + + 1984 + + + + Pride and Prejudice + + + + + ) + + // Then the corresponding item is selected + expect(screen.getByLabelText('selected')).toBeVisible() + }) + it('should control value', async () => { const user = userEvent.setup() diff --git a/packages/components/dropdown/src/DropdownItem.tsx b/packages/components/dropdown/src/DropdownItem.tsx index e0e2f78a6..d576c824a 100644 --- a/packages/components/dropdown/src/DropdownItem.tsx +++ b/packages/components/dropdown/src/DropdownItem.tsx @@ -3,8 +3,6 @@ import { ReactNode } from 'react' import { useDropdownContext } from './DropdownContext' import { DropdownItemProvider, useDropdownItemContext } from './DropdownItemContext' -import { DropdownItem } from './types' -import { getIndexByKey, getItemText } from './utils' export interface ItemProps { disabled?: boolean @@ -14,25 +12,19 @@ export interface ItemProps { } export const Item = ({ children, ...props }: ItemProps) => { + const { value, disabled } = props + return ( - + {children} ) } const ItemContent = ({ className, disabled = false, value, children }: ItemProps) => { - const { multiple, computedItems, selectedItem, selectedItems, getItemProps, highlightedItem } = - useDropdownContext() - - const { textId } = useDropdownItemContext() - - const index = getIndexByKey(computedItems, value) - const itemData: DropdownItem = { disabled, value, text: getItemText(children) } + const { getItemProps, highlightedItem } = useDropdownContext() - const isSelected = multiple - ? selectedItems.some(selectedItem => selectedItem.value === value) - : selectedItem?.value === value + const { textId, index, itemData, isSelected } = useDropdownItemContext() return (
  • > + isSelected: boolean + itemData: DropdownItem + index: number + disabled: boolean } const DropdownItemContext = createContext(null) -export const DropdownItemProvider = ({ children }: PropsWithChildren) => { +export const DropdownItemProvider = ({ + value, + disabled = false, + children, +}: PropsWithChildren<{ value: string; disabled?: boolean }>) => { + const { multiple, computedItems, selectedItem, selectedItems } = useDropdownContext() + const [textId, setTextId] = useState(undefined) + const index = getIndexByKey(computedItems, value) + const itemData: DropdownItem = { disabled, value, text: getItemText(children) } + + const isSelected = multiple + ? selectedItems.some(selectedItem => selectedItem.value === value) + : selectedItem?.value === value + return ( - + {children} ) diff --git a/packages/components/dropdown/src/DropdownItemIndicator.tsx b/packages/components/dropdown/src/DropdownItemIndicator.tsx new file mode 100644 index 000000000..44d0cb507 --- /dev/null +++ b/packages/components/dropdown/src/DropdownItemIndicator.tsx @@ -0,0 +1,28 @@ +import { Check } from '@spark-ui/icons/dist/icons/Check' +import { cx } from 'class-variance-authority' +import { ReactNode } from 'react' + +import { useDropdownContext } from './DropdownContext' +import { useDropdownItemContext } from './DropdownItemContext' + +export interface ItemIndicatorProps { + children?: ReactNode + className?: string + label?: string +} + +export const ItemIndicator = ({ className, children, label }: ItemIndicatorProps) => { + const { disabled, isSelected } = useDropdownItemContext() + const { multiple } = useDropdownContext() + const childElement = + children || (multiple ? : ) + + return ( + + {isSelected && childElement} + + ) +} + +ItemIndicator.id = 'ItemIndicator' +ItemIndicator.displayName = 'Dropdown.ItemIndicator' diff --git a/packages/components/dropdown/src/index.ts b/packages/components/dropdown/src/index.ts index 4f071f298..b569a6d28 100644 --- a/packages/components/dropdown/src/index.ts +++ b/packages/components/dropdown/src/index.ts @@ -5,6 +5,7 @@ import { DropdownProvider, useDropdownContext } from './DropdownContext' import { Divider } from './DropdownDivider' import { Group } from './DropdownGroup' import { Item } from './DropdownItem' +import { ItemIndicator } from './DropdownItemIndicator' import { Items } from './DropdownItems' import { ItemText } from './DropdownItemText' import { Label } from './DropdownLabel' @@ -20,6 +21,7 @@ export const Dropdown: FC & { Item: typeof Item Items: typeof Items ItemText: typeof ItemText + ItemIndicator: typeof ItemIndicator Label: typeof Label Popover: typeof Popover Divider: typeof Divider @@ -31,6 +33,7 @@ export const Dropdown: FC & { Item, Items, ItemText, + ItemIndicator, Label, Popover, Divider, @@ -41,9 +44,10 @@ export const Dropdown: FC & { Dropdown.displayName = 'Dropdown' Group.displayName = 'Dropdown.Group' -Item.displayName = 'Dropdown.Item' Items.displayName = 'Dropdown.Items' +Item.displayName = 'Dropdown.Item' ItemText.displayName = 'Dropdown.ItemText' +ItemIndicator.displayName = 'Dropdown.ItemIndicator' Label.displayName = 'Dropdown.Label' Popover.displayName = 'Dropdown.Popover' Divider.displayName = 'Dropdown.Divider'