Skip to content

Commit

Permalink
docs(dropdown): stories description for dropdown
Browse files Browse the repository at this point in the history
  • Loading branch information
Powerplex committed Dec 7, 2023
1 parent 0f22a4d commit 4630ae6
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 35 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 40 additions & 8 deletions packages/components/dropdown/src/Dropdown.doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -84,50 +84,82 @@ import { Dropdown } from '@spark-ui/dropdown'

<Canvas of={stories.Default} />

### Controlled value
### Controlled

Use `value` and `onValueChange` props to control the value of the dropdown.

<Canvas of={stories.Controlled} />

### Controlled open state

Use `open` and `onOpenChange` props to control when the dropdown is opened or closed.

<Canvas of={stories.ControlledOpenState} />

### Disabled

Use `disabled` on the root component to disable the dropdown entirely.

TODO

### Disabled Item

Use `disabled` on individual `Dropdown.Item` to disable them.

<Canvas of={stories.DisabledItem} />

### Grouped items
### Groups

Similar to `optgroup` HTML tag, you can gather your items in groups.

It is important to use `Dropdown.Label` inside each `Dropdown.Group` to give it an accessible name.

<Canvas of={stories.Grouped} />

### Item indicator

Renders when the parent `DropdownMenu.Item` is selected.

You can style this element directly, or you can use it as a wrapper to put an icon into, or both.

<Canvas of={stories.ItemIndicator} />

### Trigger leading icon

Use `Dropdown.LeadingIcon` inside `Dropdown.Trigger` to prefix your trigger with an icon.

<Canvas of={stories.LeadingIcon} />

### Read only

TODO

### Multiple selection
## Multiple selection

<Canvas of={stories.MultipleSelection} />
When using `multiple` mode, the component manages an array of values and no longer a single value.

### Multiple selection (controlled)
It means you must adapt `value`, `onValueChange` and `defaultValue` accordingly.

<Canvas of={stories.MultipleSelectionControlled} />
In `multiple` mode, the dropdown won't close when the user selects an item, and it is possible to unselect every item.

### Read only
### Default

TODO
<Canvas of={stories.MultipleSelection} />

### Controlled - multiple selection

Use `value` and `onValueChange` props to control the value of the dropdown.

<Canvas of={stories.MultipleSelectionControlled} />

## Advanced usage

### Custom item

If your `Dropdown.Item` contains anything else than raw text, you may use any JSX markup to customize it.

**If you do so, you MUST use `Dropdown.ItemText` inside of your item to give it a proper accessible name.**

<Canvas of={stories.CustomItem} />

### With form field label
Expand Down
41 changes: 30 additions & 11 deletions packages/components/dropdown/src/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ export const Default: StoryFn = _args => {
<div className="pb-[300px]">
<Dropdown>
<Dropdown.Trigger aria-label="Book">
<Dropdown.LeadingIcon>
<Book />
</Dropdown.LeadingIcon>
<Dropdown.Value placeholder="Pick a book" />
</Dropdown.Trigger>

Expand Down Expand Up @@ -198,10 +195,36 @@ export const Grouped: StoryFn = _args => {
)
}

export const LeadingIcon: StoryFn = _args => {
return (
<div className="pb-[300px]">
<Dropdown>
<Dropdown.Trigger aria-label="Book">
<Dropdown.LeadingIcon>
<Book />
</Dropdown.LeadingIcon>
<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>
)
}

export const ItemIndicator: StoryFn = _args => {
return (
<div className="w-sz-480 pb-[300px]">
<Dropdown>
<Dropdown multiple defaultValue={['book-1', 'book-2']}>
<Dropdown.Trigger aria-label="Book">
<Dropdown.Value placeholder="Pick a book" />
</Dropdown.Trigger>
Expand All @@ -211,30 +234,26 @@ export const ItemIndicator: StoryFn = _args => {
<Dropdown.Item value="book-1" className="flex items-center gap-md">
<Dropdown.ItemIndicator />
<Dropdown.ItemText>To Kill a Mockingbird</Dropdown.ItemText>
<Tag>New</Tag>
</Dropdown.Item>
<Dropdown.Item value="book-2" className="flex items-center gap-md">
<Dropdown.ItemIndicator />
<Dropdown.ItemText>War and Peace</Dropdown.ItemText>
<Tag>New</Tag>
</Dropdown.Item>
<Dropdown.Item value="book-3" className="flex items-center gap-md">
<Dropdown.ItemIndicator />
<Dropdown.ItemText>The Idiot</Dropdown.ItemText>
<Tag>New</Tag>
</Dropdown.Item>
<Dropdown.Item value="book-4" className="flex items-center gap-md">
<Dropdown.ItemText>A Picture of Dorian Gray</Dropdown.ItemText>
<Tag>New</Tag>
<Dropdown.ItemIndicator />
<Dropdown.ItemText>A Picture of Dorian Gray</Dropdown.ItemText>
</Dropdown.Item>
<Dropdown.Item value="book-5" className="flex items-center gap-md">
<Dropdown.ItemIndicator />
<Dropdown.ItemText>1984</Dropdown.ItemText>
<Tag>New</Tag>
</Dropdown.Item>
<Dropdown.Item value="book-6" className="flex items-center gap-md">
<Dropdown.ItemIndicator />
<Dropdown.ItemText>Pride and Prejudice</Dropdown.ItemText>
<Tag>New</Tag>
</Dropdown.Item>
</Dropdown.Items>
</Dropdown.Popover>
Expand Down
20 changes: 15 additions & 5 deletions packages/components/dropdown/src/DropdownContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export const DropdownProvider = ({
const [hasPopover, setHasPopover] = useState<boolean>(false)

const field = useFormFieldControl()
const items = Array.from(itemsMap.values())
const items = [...itemsMap.values()]

const id = useId(field.id)
const labelId = useId(field.labelId)

Expand Down Expand Up @@ -180,9 +181,9 @@ export const DropdownProvider = ({
/**
* Indices in a Map are set when an element is added to the Map.
* If for some reason, in the Dropdown:
* - children order changes
* - children are added
* - children are removed
* - items order changes
* - items are added
* - items are removed
*
* The Map must be rebuilt from the new children in order to preserve logical indices.
*
Expand All @@ -191,7 +192,16 @@ export const DropdownProvider = ({
useEffect(() => {
const newMap = getItemsFromChildren(children)

setItemsMap(newMap)
const previousItems = [...itemsMap.values()]
const newItems = [...newMap.values()]

const hasItemsChanges =
previousItems.length !== newItems.length ||
previousItems.some((item, index) => item.value !== newItems[index]?.value)

if (hasItemsChanges) {
setItemsMap(newMap)
}
}, [children])

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/components/dropdown/src/DropdownItemContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ export const DropdownItemProvider = ({
disabled = false,
children,
}: PropsWithChildren<{ value: string; disabled?: boolean }>) => {
const { multiple, computedItems, selectedItem, selectedItems } = useDropdownContext()
const { multiple, itemsMap, selectedItem, selectedItems } = useDropdownContext()

const [textId, setTextId] = useState<ItemTextId>(undefined)

const index = getIndexByKey(computedItems, value)
const index = getIndexByKey(itemsMap, value)
const itemData: DropdownItem = { disabled, value, text: getItemText(children) }

const isSelected = multiple
Expand Down
10 changes: 1 addition & 9 deletions packages/components/dropdown/src/DropdownTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { cx } from 'class-variance-authority'
import { Fragment, ReactNode } from 'react'

import { useDropdownContext } from './DropdownContext'
import { findElement } from './utils'

interface TriggerProps {
'aria-label'?: string
Expand All @@ -20,10 +19,6 @@ export const Trigger = ({ 'aria-label': ariaLabel, children, className }: Trigge
? [Popover.Trigger, { asChild: true }]
: [Fragment, {}]

const finder = findElement(children)
const leadingIcon = finder('LeadingIcon')
const value = finder('Value')

return (
<>
{ariaLabel && (
Expand All @@ -40,10 +35,7 @@ export const Trigger = ({ 'aria-label': ariaLabel, children, className }: Trigge
)}
{...getToggleButtonProps(getDropdownProps())}
>
<span className="flex items-center justify-start gap-sm">
{leadingIcon}
{value}
</span>
<span className="flex items-center justify-start gap-sm">{children}</span>
<span className="px-sm">{isOpen ? <>&#8593;</> : <>&#8595;</>}</span>
</button>
</WrapperComponent>
Expand Down

0 comments on commit 4630ae6

Please sign in to comment.