Skip to content

Commit

Permalink
feat(select): select api
Browse files Browse the repository at this point in the history
  • Loading branch information
Powerplex committed Nov 21, 2023
1 parent f02f802 commit 0de75ec
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 9 deletions.
12 changes: 6 additions & 6 deletions package-lock.json

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

19 changes: 18 additions & 1 deletion packages/components/select/src/Select.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Job } from '@spark-ui/icons/dist/icons/Job'
import { Meta, StoryFn } from '@storybook/react'

import { Select } from '.'
Expand All @@ -9,4 +10,20 @@ const meta: Meta<typeof Select> = {

export default meta

export const Default: StoryFn = _args => <Select />
export const Default: StoryFn = _args => (
<div className="w-sz-480 bg-success p-xl">
<Select value="Full time job" placeholder="--Pick a job type--">
<Select.Trigger>
<Select.LeadingIcon>
<Job />
</Select.LeadingIcon>
</Select.Trigger>

<Select.Items>
<Select.Item>Full time job</Select.Item>
<Select.Item>Part time job</Select.Item>
<Select.Item>Internship</Select.Item>
</Select.Items>
</Select>
</div>
)
28 changes: 27 additions & 1 deletion packages/components/select/src/Select.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
export const Select = () => <>select</>
import { ReactElement } from 'react'

import { SelectProvider } from './SelectContext'
import { findElement } from './utils'

export interface SelectProps {
children: ReactElement[]
value?: string
placeholder?: string
}

export const Select = ({ children, placeholder }: SelectProps) => {
const trigger = findElement('Trigger', children)

const items = findElement('Items', children)

return (
<SelectProvider
data-spark-component="select"
// value={value}
placeholder={placeholder}
items={items}
>
{trigger}
</SelectProvider>
)
}
39 changes: 39 additions & 0 deletions packages/components/select/src/SelectContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createContext, ReactElement, type ReactNode, useContext } from 'react'

export interface SelectContextState {
items: ReactElement | undefined
placeholder?: string | undefined
}

const SelectContext = createContext<SelectContextState | null>(null)

export const SelectProvider = ({
children,
items,
placeholder,
}: {
children: ReactNode
items: ReactElement | undefined
placeholder: string | undefined
}) => {
return (
<SelectContext.Provider
value={{
items,
placeholder,
}}
>
{children}
</SelectContext.Provider>
)
}

export const useSelect = () => {
const context = useContext(SelectContext)

if (!context) {
throw Error('useSelect must be used within a Select provider')
}

return context
}
6 changes: 6 additions & 0 deletions packages/components/select/src/SelectItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const Item = ({ children }: { children: string }) => {
return <option value={children}>{children}</option>
}

Item.id = 'Item'
Item.displayName = 'Select.Item'
17 changes: 17 additions & 0 deletions packages/components/select/src/SelectItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { PropsWithChildren } from 'react'

import { useSelect } from './SelectContext'

export const Items = ({ children }: PropsWithChildren) => {
const { placeholder } = useSelect()

return (
<select className="absolute left-none top-none h-full w-full opacity-0">
{placeholder && <option value="">{placeholder}</option>}
{children}
</select>
)
}

Items.id = 'Items'
Items.displayName = 'Select.Items'
4 changes: 4 additions & 0 deletions packages/components/select/src/SelectItemsGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const ItemsGroup = () => null

ItemsGroup.id = 'ItemsGroup'
ItemsGroup.displayName = 'Select.ItemsGroup'
9 changes: 9 additions & 0 deletions packages/components/select/src/SelectLeadingIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Icon } from '@spark-ui/icon'
import { ReactElement } from 'react'

export const LeadingIcon = ({ children }: { children: ReactElement }) => {
return <Icon>{children}</Icon>
}

LeadingIcon.id = 'LeadingIcon'
LeadingIcon.displayName = 'Select.LeadingIcon'
23 changes: 23 additions & 0 deletions packages/components/select/src/SelectTrigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Icon } from '@spark-ui/icon'
import { ArrowHorizontalDown } from '@spark-ui/icons/dist/icons/ArrowHorizontalDown'
import { ReactNode } from 'react'

import { useSelect } from './SelectContext'

export const Trigger = ({ children }: { children?: ReactNode }) => {
const { items, placeholder } = useSelect()

return (
<div className="relative flex h-sz-44 items-center gap-md rounded-md border-sm border-outline bg-surface px-lg focus-within:u-ring">
{children}
{placeholder && <p className="grow">{placeholder}</p>}
<Icon>
<ArrowHorizontalDown />
</Icon>
{items && items}
</div>
)
}

Trigger.id = 'Trigger'
Trigger.displayName = 'Select.Trigger'
30 changes: 29 additions & 1 deletion packages/components/select/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,29 @@
export { Select } from './Select'
import type { FC } from 'react'

import { Select as Root, type SelectProps } from './Select'
import { Item } from './SelectItem'
import { Items } from './SelectItems'
import { ItemsGroup } from './SelectItemsGroup'
import { LeadingIcon } from './SelectLeadingIcon'
import { Trigger } from './SelectTrigger'

export const Select: FC<SelectProps> & {
Trigger: typeof Trigger
LeadingIcon: typeof LeadingIcon
Items: typeof Items
Item: typeof Item
ItemsGroup: typeof ItemsGroup
} = Object.assign(Root, {
Trigger,
LeadingIcon,
Items,
Item,
ItemsGroup,
})

Select.displayName = 'Select'
Trigger.displayName = 'Select.Trigger'
LeadingIcon.displayName = 'Select.LeadingIcon'
Items.displayName = 'Select.Items'
Item.displayName = 'Select.Item'
ItemsGroup.displayName = 'Select.ItemsGroup'
9 changes: 9 additions & 0 deletions packages/components/select/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type FC, type ReactElement } from 'react'

const getElementId = (element?: ReactElement) => {
return element ? (element.type as FC & { id?: string }).id : ''
}

export const findElement = (value: string, children: ReactElement[]) => {
return children.find(child => value === getElementId(child) || '')
}

0 comments on commit 0de75ec

Please sign in to comment.