Skip to content

Commit

Permalink
feat(dropdown): enable custom jsx in DropdownItem
Browse files Browse the repository at this point in the history
  • Loading branch information
Powerplex committed Nov 23, 2023
1 parent a7c8421 commit 9712277
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 15 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.

11 changes: 10 additions & 1 deletion packages/components/dropdown/src/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Switch } from '@spark-ui/switch'
import { Tag } from '@spark-ui/tag'
import { Meta, StoryFn } from '@storybook/react'
import { useState } from 'react'

Expand Down Expand Up @@ -33,9 +34,17 @@ export const Default: StoryFn = _args => {
<Dropdown.Item value="book-7">Meditations</Dropdown.Item>
<Dropdown.Item value="book-8">The Brothers Karamazov</Dropdown.Item>
<Dropdown.Item value="book-9">Anna Karenina</Dropdown.Item>
<Dropdown.Item value="book-10">Crime and Punishment</Dropdown.Item>
<Dropdown.Item value="book-10" className="gap-md">
<Dropdown.ItemText>Crime and Punishment</Dropdown.ItemText>
<Tag>New</Tag>
</Dropdown.Item>
</Dropdown.Items>
</Dropdown>
<p>some content, etc...</p>
<p>some content, etc...</p>
<p>some content, etc...</p>
<p>some content, etc...</p>
<p>some content, etc...</p>
</div>
)
}
8 changes: 2 additions & 6 deletions packages/components/dropdown/src/DropdownContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,8 @@ export const DropdownProvider = ({ children }: DropdownContextProps) => {
const syncItems = () => {
const newMap: ItemsMap = new Map()

getOrderedItems(children).forEach(({ value, disabled, children }) => {
newMap.set(value, {
value,
disabled: !!disabled,
text: children,
})
getOrderedItems(children).forEach(itemData => {
newMap.set(itemData.value, itemData)
})

setComputedItems(newMap)
Expand Down
15 changes: 10 additions & 5 deletions packages/components/dropdown/src/DropdownItem.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,41 @@
import { cx } from 'class-variance-authority'
import { ReactNode } from 'react'

import { useDropdown } from './DropdownContext'
import { getIndexByKey } from './utils'
import { DropdownItem } from './types'
import { getIndexByKey, getItemText } from './utils'

export interface ItemProps {
disabled?: boolean
value: string
children: string
children: ReactNode
className?: string
}

export const Item = ({
className,
disabled = false,
value,
children, // TODO: allow more than string and implement Dropdown.ItemText
}: ItemProps) => {
const { computedItems, selectedItem, getItemProps, higlightedItem } = useDropdown()

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

return (
<li
className={cx(
higlightedItem?.value === value && 'bg-basic-container',
selectedItem?.value === value && 'font-bold',
disabled && 'opacity-dim-3',
'flex flex-col px-sm py-sm'
'flex px-sm py-sm',
className
)}
key={value}
{...getItemProps({ item: itemData, index })}
>
<span>{children}</span>
{children}
</li>
)
}
Expand Down
12 changes: 12 additions & 0 deletions packages/components/dropdown/src/DropdownItemText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { cx } from 'class-variance-authority'

export interface ItemProps {
children: string
}

export const ItemText = ({ children }: ItemProps) => {
return <span className={cx('inline')}>{children}</span>

Check warning on line 8 in packages/components/dropdown/src/DropdownItemText.tsx

View check run for this annotation

Codecov / codecov/patch

packages/components/dropdown/src/DropdownItemText.tsx#L8

Added line #L8 was not covered by tests
}

ItemText.id = 'ItemText'
ItemText.displayName = 'Dropdown.ItemText'
4 changes: 4 additions & 0 deletions packages/components/dropdown/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Dropdown as Root, type DropdownProps } from './Dropdown'
import { DropdownProvider, useDropdown } from './DropdownContext'
import { Item } from './DropdownItem'
import { Items } from './DropdownItems'
import { ItemText } from './DropdownItemText'
import { Trigger } from './DropdownTrigger'

export { useDropdown, DropdownProvider }
Expand All @@ -12,13 +13,16 @@ export const Dropdown: FC<DropdownProps> & {
Trigger: typeof Trigger
Items: typeof Items
Item: typeof Item
ItemText: typeof ItemText
} = Object.assign(Root, {
Trigger,
Items,
Item,
ItemText,
})

Dropdown.displayName = 'Dropdown'
Trigger.displayName = 'Dropdown.Trigger'
Items.displayName = 'Dropdown.Items'
Item.displayName = 'Dropdown.Item'
ItemText.displayName = 'Dropdown.ItemText'
39 changes: 36 additions & 3 deletions packages/components/dropdown/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { type FC, isValidElement, type ReactElement, type ReactNode } from 'react'

import { type ItemProps } from './DropdownItem'
import { type ItemsMap } from './types'
import { type DropdownItem, type ItemsMap } from './types'

export function getIndexByKey(map: ItemsMap, targetKey: string) {
let index = 0
Expand Down Expand Up @@ -35,12 +35,20 @@ const getElementId = (element?: ReactElement) => {
return element ? (element.type as FC & { id?: string }).id : ''
}

export const getOrderedItems = (children: ReactNode, result: ItemProps[] = []): ItemProps[] => {
export const getOrderedItems = (
children: ReactNode,
result: DropdownItem[] = []
): DropdownItem[] => {
React.Children.forEach(children, child => {
if (!isValidElement(child)) return

if (getElementId(child) === 'Item') {
result.push(child.props as ItemProps)
const childProps = child.props as ItemProps
result.push({
value: childProps.value,
disabled: !!childProps.disabled,
text: getItemText(childProps.children),
})
}

if (child.props.children) {
Expand All @@ -50,3 +58,28 @@ export const getOrderedItems = (children: ReactNode, result: ItemProps[] = []):

return result
}

/**
* If Dropdown.Item children:
* - is a string, then the string is used.
* - is JSX markup, then we look for Dropdown.ItemText to get its string value.
*/
export const getItemText = (children: ReactNode, itemText = ''): string => {
if (typeof children === 'string') {
return children
}

React.Children.forEach(children, child => {

Check warning on line 72 in packages/components/dropdown/src/utils.ts

View check run for this annotation

Codecov / codecov/patch

packages/components/dropdown/src/utils.ts#L72

Added line #L72 was not covered by tests
if (!isValidElement(child)) return

if (getElementId(child) === 'ItemText') {
itemText = child.props.children

Check warning on line 76 in packages/components/dropdown/src/utils.ts

View check run for this annotation

Codecov / codecov/patch

packages/components/dropdown/src/utils.ts#L76

Added line #L76 was not covered by tests
}

if (child.props.children) {
getItemText(child.props.children, itemText)

Check warning on line 80 in packages/components/dropdown/src/utils.ts

View check run for this annotation

Codecov / codecov/patch

packages/components/dropdown/src/utils.ts#L80

Added line #L80 was not covered by tests
}
})

return itemText

Check warning on line 84 in packages/components/dropdown/src/utils.ts

View check run for this annotation

Codecov / codecov/patch

packages/components/dropdown/src/utils.ts#L84

Added line #L84 was not covered by tests
}

0 comments on commit 9712277

Please sign in to comment.