Skip to content

Commit

Permalink
feat(dropdown): controlled open state
Browse files Browse the repository at this point in the history
  • Loading branch information
Powerplex committed Nov 28, 2023
1 parent b2b4812 commit 0e442f3
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 5 deletions.
6 changes: 5 additions & 1 deletion packages/components/dropdown/src/Dropdown.doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ import { Dropdown } from '@spark-ui/dropdown'

<Canvas of={stories.Default} />

### Controlled
### Controlled value

<Canvas of={stories.Controlled} />

### Controlled open state

<Canvas of={stories.ControlledOpenState} />

### Custom item

<Canvas of={stories.CustomItem} />
Expand Down
37 changes: 37 additions & 0 deletions packages/components/dropdown/src/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -60,6 +61,42 @@ export const Controlled: StoryFn = () => {
)
}

export const ControlledOpenState: StoryFn = () => {
const [open, setOpen] = useState(false)

return (
<div className="flex flex-col gap-lg">
<div className="flex gap-md">
<Button design="outlined" intent="success" disabled={open} onClick={() => setOpen(true)}>
Open
</Button>
<Button design="outlined" intent="danger" disabled={!open} onClick={() => setOpen(false)}>
Close
</Button>
</div>

<div className="w-sz-480 pb-[300px]">
<Dropdown open={open} onIsOpenChange={setOpen}>
<Dropdown.Trigger aria-label="Book">
<Dropdown.Value placeholder="Pick a book" />
</Dropdown.Trigger>

<Dropdown.Popover>
<Dropdown.Items>
<Dropdown.Item value="book-1">To Kill a Mockingbird</Dropdown.Item>
<Dropdown.Item value="book-2">War and Peace</Dropdown.Item>
<Dropdown.Item value="book-3">The Idiot</Dropdown.Item>
<Dropdown.Item value="book-4">A Picture of Dorian Gray</Dropdown.Item>
<Dropdown.Item value="book-5">1984</Dropdown.Item>
<Dropdown.Item value="book-6">Pride and Prejudice</Dropdown.Item>
</Dropdown.Items>
</Dropdown.Popover>
</Dropdown>
</div>
</div>
)
}

export const CustomItem: StoryFn = _args => {
return (
<div className="w-sz-480 pb-[300px]">
Expand Down
36 changes: 32 additions & 4 deletions packages/components/dropdown/src/DropdownContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<DropdownContextState | null>(null)
Expand All @@ -35,6 +56,9 @@ export const DropdownProvider = ({
defaultValue,
value,
onValueChange,
open,
onIsOpenChange,
defaultOpen,
}: DropdownContextProps) => {
const [computedItems, setComputedItems] = useState<ItemsMap>(getItemsFromChildren(children))
const [hasPopover, setHasPopover] = useState<boolean>(false)
Expand All @@ -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()),
Expand All @@ -62,10 +87,13 @@ export const DropdownProvider = ({
onValueChange?.(selectedItem?.value)

Check warning on line 87 in packages/components/dropdown/src/DropdownContext.tsx

View check run for this annotation

Codecov / codecov/patch

packages/components/dropdown/src/DropdownContext.tsx#L87

Added line #L87 was not covered by tests
}
},
// onIsOpenChange?: (changes: UseSelectStateChange<Item>) => void
// onHighlightedIndexChange?: (changes: UseSelectStateChange<Item>) => void
// onStateChange?: (changes: UseSelectStateChange<Item>) => void
// environment?: Environment
isOpen: open,
onIsOpenChange: ({ isOpen }) => {

Check warning on line 91 in packages/components/dropdown/src/DropdownContext.tsx

View check run for this annotation

Codecov / codecov/patch

packages/components/dropdown/src/DropdownContext.tsx#L91

Added line #L91 was not covered by tests
if (isOpen != null) {
onIsOpenChange?.(isOpen)

Check warning on line 93 in packages/components/dropdown/src/DropdownContext.tsx

View check run for this annotation

Codecov / codecov/patch

packages/components/dropdown/src/DropdownContext.tsx#L93

Added line #L93 was not covered by tests
}
},
initialIsOpen: controlledDefaultOpen,
})

/**
Expand Down

0 comments on commit 0e442f3

Please sign in to comment.