From 0e442f3e9721fad13b11dc0cd25dff873851e5f1 Mon Sep 17 00:00:00 2001 From: Powerplex Date: Tue, 28 Nov 2023 16:31:14 +0100 Subject: [PATCH] feat(dropdown): controlled open state --- .../components/dropdown/src/Dropdown.doc.mdx | 6 ++- .../dropdown/src/Dropdown.stories.tsx | 37 +++++++++++++++++++ .../dropdown/src/DropdownContext.tsx | 36 ++++++++++++++++-- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/packages/components/dropdown/src/Dropdown.doc.mdx b/packages/components/dropdown/src/Dropdown.doc.mdx index aefa3faab..4978a3fac 100644 --- a/packages/components/dropdown/src/Dropdown.doc.mdx +++ b/packages/components/dropdown/src/Dropdown.doc.mdx @@ -33,10 +33,14 @@ import { Dropdown } from '@spark-ui/dropdown' -### Controlled +### Controlled value +### Controlled open state + + + ### Custom item diff --git a/packages/components/dropdown/src/Dropdown.stories.tsx b/packages/components/dropdown/src/Dropdown.stories.tsx index 2003ab3b4..ae38bc7b7 100644 --- a/packages/components/dropdown/src/Dropdown.stories.tsx +++ b/packages/components/dropdown/src/Dropdown.stories.tsx @@ -1,3 +1,4 @@ +import { Button } from '@spark-ui/button' import { FormField } from '@spark-ui/form-field' import { Tag } from '@spark-ui/tag' import { Meta, StoryFn } from '@storybook/react' @@ -60,6 +61,42 @@ export const Controlled: StoryFn = () => { ) } +export const ControlledOpenState: StoryFn = () => { + const [open, setOpen] = useState(false) + + return ( +
+
+ + +
+ +
+ + + + + + + + To Kill a Mockingbird + War and Peace + The Idiot + A Picture of Dorian Gray + 1984 + Pride and Prejudice + + + +
+
+ ) +} + export const CustomItem: StoryFn = _args => { return (
diff --git a/packages/components/dropdown/src/DropdownContext.tsx b/packages/components/dropdown/src/DropdownContext.tsx index 45c15d5f6..fb3b2da16 100644 --- a/packages/components/dropdown/src/DropdownContext.tsx +++ b/packages/components/dropdown/src/DropdownContext.tsx @@ -23,9 +23,30 @@ export interface DropdownContextState extends DownshiftState { } export type DropdownContextProps = PropsWithChildren<{ + /** + * The value of the select when initially rendered. Use when you do not need to control the state of the select. + */ defaultValue?: string + /** + * The controlled value of the select. Should be used in conjunction with `onValueChange`. + */ value?: string + /** + * Event handler called when the value changes. + */ onValueChange?: (value: string) => void + /** + * The controlled open state of the select. Must be used in conjunction with `onOpenChange`. + */ + open?: boolean + /** + * Event handler called when the open state of the select changes. + */ + onIsOpenChange?: (isOpen: boolean) => void + /** + * The open state of the select when it is initially rendered. Use when you do not need to control its open state. + */ + defaultOpen?: boolean }> const DropdownContext = createContext(null) @@ -35,6 +56,9 @@ export const DropdownProvider = ({ defaultValue, value, onValueChange, + open, + onIsOpenChange, + defaultOpen, }: DropdownContextProps) => { const [computedItems, setComputedItems] = useState(getItemsFromChildren(children)) const [hasPopover, setHasPopover] = useState(false) @@ -46,6 +70,7 @@ export const DropdownProvider = ({ const controlledSelectedItem = value ? computedItems.get(value) : undefined const controlledDefaultSelectedItem = defaultValue ? computedItems.get(defaultValue) : undefined + const controlledDefaultOpen = defaultOpen != null ? defaultOpen : false const downshift = useSelect({ items: Array.from(computedItems.values()), @@ -62,10 +87,13 @@ export const DropdownProvider = ({ onValueChange?.(selectedItem?.value) } }, - // onIsOpenChange?: (changes: UseSelectStateChange) => void - // onHighlightedIndexChange?: (changes: UseSelectStateChange) => void - // onStateChange?: (changes: UseSelectStateChange) => void - // environment?: Environment + isOpen: open, + onIsOpenChange: ({ isOpen }) => { + if (isOpen != null) { + onIsOpenChange?.(isOpen) + } + }, + initialIsOpen: controlledDefaultOpen, }) /**