mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 05:39:05 +08:00
feat(select): introduce new Select component and related features
- Added a new Select component based on Radix UI, including SelectTrigger, SelectContent, SelectItem, and SelectValue. - Implemented support for groups and separators within the Select component. - Updated package.json to include @radix-ui/react-select as a dependency. - Removed deprecated Selector and SearchableSelector components to streamline the codebase. - Added stories for the Select component to showcase various use cases and configurations.
This commit is contained in:
parent
8246f46e7d
commit
b382b06c57
@ -51,6 +51,7 @@
|
|||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
"@radix-ui/react-radio-group": "^1.3.8",
|
"@radix-ui/react-radio-group": "^1.3.8",
|
||||||
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-use-controllable-state": "^1.2.2",
|
"@radix-ui/react-use-controllable-state": "^1.2.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
|||||||
@ -41,19 +41,19 @@ export {
|
|||||||
export { default as SvgSpinners180Ring } from './icons/SvgSpinners180Ring'
|
export { default as SvgSpinners180Ring } from './icons/SvgSpinners180Ring'
|
||||||
export { default as ToolsCallingIcon } from './icons/ToolsCallingIcon'
|
export { default as ToolsCallingIcon } from './icons/ToolsCallingIcon'
|
||||||
|
|
||||||
/* Selector Components */
|
// /* Selector Components */
|
||||||
export { default as Selector } from './primitives/Selector'
|
// export { default as Selector } from './primitives/select'
|
||||||
export { default as SearchableSelector } from './primitives/Selector/SearchableSelector'
|
// export { default as SearchableSelector } from './primitives/Selector/SearchableSelector'
|
||||||
export type {
|
// export type {
|
||||||
MultipleSearchableSelectorProps,
|
// MultipleSearchableSelectorProps,
|
||||||
MultipleSelectorProps,
|
// MultipleSelectorProps,
|
||||||
SearchableSelectorItem,
|
// SearchableSelectorItem,
|
||||||
SearchableSelectorProps,
|
// SearchableSelectorProps,
|
||||||
SelectorItem,
|
// SelectorItem,
|
||||||
SelectorProps,
|
// SelectorProps,
|
||||||
SingleSearchableSelectorProps,
|
// SingleSearchableSelectorProps,
|
||||||
SingleSelectorProps
|
// SingleSelectorProps
|
||||||
} from './primitives/Selector/types'
|
// } from './primitives/Selector/types'
|
||||||
|
|
||||||
/* Additional Composite Components */
|
/* Additional Composite Components */
|
||||||
// CodeEditor
|
// CodeEditor
|
||||||
@ -85,4 +85,5 @@ export * from './primitives/command'
|
|||||||
export * from './primitives/dialog'
|
export * from './primitives/dialog'
|
||||||
export * from './primitives/popover'
|
export * from './primitives/popover'
|
||||||
export * from './primitives/radioGroup'
|
export * from './primitives/radioGroup'
|
||||||
|
export * from './primitives/select'
|
||||||
export * from './primitives/shadcn-io/dropzone'
|
export * from './primitives/shadcn-io/dropzone'
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
/**
|
|
||||||
* @deprecated 此组件使用频率为 0 次,不符合 UI 库提取标准(需 ≥3 次)
|
|
||||||
* 计划在未来版本中移除。如需可搜索选择器,请直接使用 HeroUI 的 Autocomplete 组件。
|
|
||||||
*
|
|
||||||
* This component has 0 usages and does not meet the UI library extraction criteria (requires ≥3 usages).
|
|
||||||
* Planned for removal in future versions. Consider using HeroUI's Autocomplete component directly.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Autocomplete, AutocompleteItem } from '@heroui/react'
|
|
||||||
import type { Key } from '@react-types/shared'
|
|
||||||
import { useMemo } from 'react'
|
|
||||||
|
|
||||||
import type { SearchableSelectorItem, SearchableSelectorProps } from './types'
|
|
||||||
|
|
||||||
const SearchableSelector = <T extends SearchableSelectorItem>(props: SearchableSelectorProps<T>) => {
|
|
||||||
const { items, onSelectionChange, selectedKeys, selectionMode = 'single', children, ...rest } = props
|
|
||||||
|
|
||||||
// 转换 selectedKeys: V | V[] → Key | undefined (Autocomplete 只支持单选)
|
|
||||||
const autocompleteSelectedKey = useMemo(() => {
|
|
||||||
if (selectedKeys === undefined) return undefined
|
|
||||||
|
|
||||||
if (selectionMode === 'multiple') {
|
|
||||||
// Autocomplete 不支持多选,取第一个
|
|
||||||
const keys = selectedKeys as T['value'][]
|
|
||||||
return keys.length > 0 ? String(keys[0]) : undefined
|
|
||||||
} else {
|
|
||||||
return String(selectedKeys)
|
|
||||||
}
|
|
||||||
}, [selectedKeys, selectionMode])
|
|
||||||
|
|
||||||
// 处理选择变化
|
|
||||||
const handleSelectionChange = (key: Key | null) => {
|
|
||||||
if (!onSelectionChange || key === null) return
|
|
||||||
|
|
||||||
const strKey = String(key)
|
|
||||||
// 尝试转换回数字类型
|
|
||||||
const num = Number(strKey)
|
|
||||||
const value = !isNaN(num) && items.some((item) => item.value === num) ? (num as T['value']) : (strKey as T['value'])
|
|
||||||
|
|
||||||
if (selectionMode === 'multiple') {
|
|
||||||
// 多选模式: 返回数组 (Autocomplete 只支持单选,这里简化处理)
|
|
||||||
;(onSelectionChange as (keys: T['value'][]) => void)([value])
|
|
||||||
} else {
|
|
||||||
// 单选模式: 返回单个值
|
|
||||||
;(onSelectionChange as (key: T['value']) => void)(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认渲染函数
|
|
||||||
const defaultRenderItem = (item: T) => (
|
|
||||||
<AutocompleteItem key={String(item.value)} textValue={item.label ? String(item.label) : String(item.value)}>
|
|
||||||
{item.label ?? item.value}
|
|
||||||
</AutocompleteItem>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Autocomplete
|
|
||||||
{...rest}
|
|
||||||
items={items}
|
|
||||||
selectedKey={autocompleteSelectedKey}
|
|
||||||
onSelectionChange={handleSelectionChange}
|
|
||||||
allowsCustomValue={false}>
|
|
||||||
{children ?? defaultRenderItem}
|
|
||||||
</Autocomplete>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SearchableSelector
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import type { Selection } from '@heroui/react'
|
|
||||||
import { Select, SelectItem } from '@heroui/react'
|
|
||||||
import type { Key } from '@react-types/shared'
|
|
||||||
import { useMemo } from 'react'
|
|
||||||
|
|
||||||
import type { SelectorItem, SelectorProps } from './types'
|
|
||||||
|
|
||||||
const Selector = <T extends SelectorItem>(props: SelectorProps<T>) => {
|
|
||||||
const { items, onSelectionChange, selectedKeys, selectionMode = 'single', children, ...rest } = props
|
|
||||||
|
|
||||||
// 转换 selectedKeys: V | V[] | undefined → Set<Key> | undefined
|
|
||||||
const heroUISelectedKeys = useMemo(() => {
|
|
||||||
if (selectedKeys === undefined) return undefined
|
|
||||||
|
|
||||||
if (selectionMode === 'multiple') {
|
|
||||||
// 多选模式: V[] → Set<Key>
|
|
||||||
return new Set((selectedKeys as T['value'][]).map((key) => String(key) as Key))
|
|
||||||
} else {
|
|
||||||
// 单选模式: V → Set<Key>
|
|
||||||
return new Set([String(selectedKeys) as Key])
|
|
||||||
}
|
|
||||||
}, [selectedKeys, selectionMode])
|
|
||||||
|
|
||||||
// 处理选择变化,转换 Selection → V | V[]
|
|
||||||
const handleSelectionChange = (keys: Selection) => {
|
|
||||||
if (!onSelectionChange) return
|
|
||||||
|
|
||||||
if (keys === 'all') {
|
|
||||||
// 如果是全选,返回所有非禁用项的值
|
|
||||||
const allValues = items.filter((item) => !item.disabled).map((item) => item.value)
|
|
||||||
if (selectionMode === 'multiple') {
|
|
||||||
;(onSelectionChange as (keys: T['value'][]) => void)(allValues)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转换 Set<Key> 为原始类型
|
|
||||||
const keysArray = Array.from(keys).map((key) => {
|
|
||||||
const strKey = String(key)
|
|
||||||
// 尝试转换回数字类型(如果原始值是数字)
|
|
||||||
const num = Number(strKey)
|
|
||||||
return !isNaN(num) && items.some((item) => item.value === num) ? (num as T['value']) : (strKey as T['value'])
|
|
||||||
})
|
|
||||||
|
|
||||||
if (selectionMode === 'multiple') {
|
|
||||||
// 多选模式: 返回数组
|
|
||||||
;(onSelectionChange as (keys: T['value'][]) => void)(keysArray)
|
|
||||||
} else {
|
|
||||||
// 单选模式: 返回单个值
|
|
||||||
if (keysArray.length > 0) {
|
|
||||||
;(onSelectionChange as (key: T['value']) => void)(keysArray[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认渲染函数
|
|
||||||
const defaultRenderItem = (item: T) => (
|
|
||||||
<SelectItem key={String(item.value)} textValue={item.label ? String(item.label) : String(item.value)}>
|
|
||||||
{item.label ?? item.value}
|
|
||||||
</SelectItem>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
{...rest}
|
|
||||||
items={items}
|
|
||||||
selectionMode={selectionMode}
|
|
||||||
selectedKeys={heroUISelectedKeys as 'all' | Iterable<Key> | undefined}
|
|
||||||
onSelectionChange={handleSelectionChange}>
|
|
||||||
{children ?? defaultRenderItem}
|
|
||||||
</Select>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Selector
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
// 统一导出 Selector 相关组件和类型
|
|
||||||
export { default as SearchableSelector } from './SearchableSelector'
|
|
||||||
export { default } from './Selector'
|
|
||||||
export type {
|
|
||||||
MultipleSearchableSelectorProps,
|
|
||||||
MultipleSelectorProps,
|
|
||||||
SearchableSelectorItem,
|
|
||||||
SearchableSelectorProps,
|
|
||||||
SelectorItem,
|
|
||||||
SelectorProps,
|
|
||||||
SingleSearchableSelectorProps,
|
|
||||||
SingleSelectorProps
|
|
||||||
} from './types'
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
import type { AutocompleteProps, SelectProps } from '@heroui/react'
|
|
||||||
import type { ReactElement, ReactNode } from 'react'
|
|
||||||
|
|
||||||
interface SelectorItem<V = string | number> {
|
|
||||||
label?: string | ReactNode
|
|
||||||
value: V
|
|
||||||
disabled?: boolean
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义渲染函数类型
|
|
||||||
type SelectorRenderItem<T> = (item: T) => ReactElement
|
|
||||||
|
|
||||||
// 单选模式的 Props
|
|
||||||
interface SingleSelectorProps<T extends SelectorItem = SelectorItem>
|
|
||||||
extends Omit<SelectProps<T>, 'children' | 'onSelectionChange' | 'selectedKeys' | 'selectionMode'> {
|
|
||||||
items: T[]
|
|
||||||
selectionMode?: 'single'
|
|
||||||
selectedKeys?: T['value']
|
|
||||||
onSelectionChange?: (key: T['value']) => void
|
|
||||||
children?: SelectorRenderItem<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
// 多选模式的 Props
|
|
||||||
interface MultipleSelectorProps<T extends SelectorItem = SelectorItem>
|
|
||||||
extends Omit<SelectProps<T>, 'children' | 'onSelectionChange' | 'selectedKeys' | 'selectionMode'> {
|
|
||||||
items: T[]
|
|
||||||
selectionMode: 'multiple'
|
|
||||||
selectedKeys?: T['value'][]
|
|
||||||
onSelectionChange?: (keys: T['value'][]) => void
|
|
||||||
children?: SelectorRenderItem<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
type SelectorProps<T extends SelectorItem = SelectorItem> = SingleSelectorProps<T> | MultipleSelectorProps<T>
|
|
||||||
|
|
||||||
interface SearchableSelectorItem<V = string | number> {
|
|
||||||
label?: string | ReactNode
|
|
||||||
value: V
|
|
||||||
disabled?: boolean
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义渲染函数类型
|
|
||||||
type SearchableRenderItem<T> = (item: T) => ReactElement
|
|
||||||
|
|
||||||
// 单选模式的 Props
|
|
||||||
interface SingleSearchableSelectorProps<T extends SearchableSelectorItem = SearchableSelectorItem>
|
|
||||||
extends Omit<AutocompleteProps<T>, 'children' | 'onSelectionChange' | 'selectedKey' | 'selectionMode'> {
|
|
||||||
items: T[]
|
|
||||||
selectionMode?: 'single'
|
|
||||||
selectedKeys?: T['value']
|
|
||||||
onSelectionChange?: (key: T['value']) => void
|
|
||||||
children?: SearchableRenderItem<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
// 多选模式的 Props
|
|
||||||
interface MultipleSearchableSelectorProps<T extends SearchableSelectorItem = SearchableSelectorItem>
|
|
||||||
extends Omit<AutocompleteProps<T>, 'children' | 'onSelectionChange' | 'selectedKey' | 'selectionMode'> {
|
|
||||||
items: T[]
|
|
||||||
selectionMode: 'multiple'
|
|
||||||
selectedKeys?: T['value'][]
|
|
||||||
onSelectionChange?: (keys: T['value'][]) => void
|
|
||||||
children?: SearchableRenderItem<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
type SearchableSelectorProps<T extends SearchableSelectorItem = SearchableSelectorItem> =
|
|
||||||
| SingleSearchableSelectorProps<T>
|
|
||||||
| MultipleSearchableSelectorProps<T>
|
|
||||||
|
|
||||||
export type {
|
|
||||||
MultipleSearchableSelectorProps,
|
|
||||||
MultipleSelectorProps,
|
|
||||||
SearchableSelectorItem,
|
|
||||||
SearchableSelectorProps,
|
|
||||||
SelectorItem,
|
|
||||||
SelectorProps,
|
|
||||||
SingleSearchableSelectorProps,
|
|
||||||
SingleSelectorProps
|
|
||||||
}
|
|
||||||
@ -18,7 +18,7 @@ import * as React from 'react'
|
|||||||
// ==================== Variants ====================
|
// ==================== Variants ====================
|
||||||
|
|
||||||
const comboboxTriggerVariants = cva(
|
const comboboxTriggerVariants = cva(
|
||||||
'inline-flex items-center justify-between rounded-2xs border-1 text-sm transition-colors outline-none font-normal',
|
'inline-flex items-center justify-between rounded-2xs border-1 text-sm transition-colors outline-none font-normal bg-zinc-50 dark:bg-zinc-900',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
state: {
|
state: {
|
||||||
|
|||||||
179
packages/ui/src/components/primitives/select.tsx
Normal file
179
packages/ui/src/components/primitives/select.tsx
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import { cn } from '@cherrystudio/ui/utils/index'
|
||||||
|
import * as SelectPrimitive from '@radix-ui/react-select'
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
const selectTriggerVariants = cva(
|
||||||
|
'inline-flex items-center justify-between rounded-2xs border-1 text-sm transition-colors outline-none font-normal',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
state: {
|
||||||
|
default: 'bg-zinc-50 dark:bg-zinc-900 border-border aria-expanded:border-primary aria-expanded:ring-3 aria-expanded:ring-primary/20',
|
||||||
|
error: 'bg-zinc-50 dark:bg-zinc-900 border border-destructive! aria-expanded:ring-3 aria-expanded:ring-red-600/20',
|
||||||
|
disabled: 'opacity-50 cursor-not-allowed pointer-events-none bg-zinc-50 dark:bg-zinc-900'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
sm: 'px-3 gap-2 h-8',
|
||||||
|
default: 'px-3 gap-2 h-9'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
state: 'default',
|
||||||
|
size: 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||||
|
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||||
|
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||||
|
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectTrigger({
|
||||||
|
className,
|
||||||
|
size = 'default',
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Trigger> &
|
||||||
|
Omit<VariantProps<typeof selectTriggerVariants>, 'state'> & {
|
||||||
|
size?: 'sm' | 'default'
|
||||||
|
}) {
|
||||||
|
const state = props.disabled ? 'disabled' : props['aria-invalid'] ? 'error' : 'default'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
data-slot="select-trigger"
|
||||||
|
data-size={size}
|
||||||
|
className={cn(
|
||||||
|
selectTriggerVariants({ state, size }),
|
||||||
|
"data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground w-fit whitespace-nowrap *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDownIcon className="size-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
position = 'popper',
|
||||||
|
align = 'center',
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
data-slot="select-content"
|
||||||
|
className={cn(
|
||||||
|
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
|
||||||
|
position === 'popper' &&
|
||||||
|
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
align={align}
|
||||||
|
{...props}>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
'p-1',
|
||||||
|
position === 'popper' &&
|
||||||
|
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1'
|
||||||
|
)}>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
data-slot="select-label"
|
||||||
|
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
data-slot="select-item"
|
||||||
|
className={cn(
|
||||||
|
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}>
|
||||||
|
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="size-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
data-slot="select-separator"
|
||||||
|
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
data-slot="select-scroll-up-button"
|
||||||
|
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
||||||
|
{...props}>
|
||||||
|
<ChevronUpIcon className="size-4" />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectScrollDownButton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
data-slot="select-scroll-down-button"
|
||||||
|
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
||||||
|
{...props}>
|
||||||
|
<ChevronDownIcon className="size-4" />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
}
|
||||||
439
packages/ui/stories/components/primitives/Select.stories.tsx
Normal file
439
packages/ui/stories/components/primitives/Select.stories.tsx
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
import { Globe, Palette, User } from 'lucide-react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
} from '../../../src/components/primitives/select'
|
||||||
|
|
||||||
|
const meta: Meta<typeof Select> = {
|
||||||
|
title: 'Components/Primitives/Select',
|
||||||
|
component: Select,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: 'A dropdown select component based on Radix UI, with support for groups, separators, and custom content.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
disabled: {
|
||||||
|
control: { type: 'boolean' },
|
||||||
|
description: 'Whether the select is disabled'
|
||||||
|
},
|
||||||
|
defaultValue: {
|
||||||
|
control: { type: 'text' },
|
||||||
|
description: 'Default selected value'
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
control: { type: 'text' },
|
||||||
|
description: 'Value in controlled mode'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
// Default
|
||||||
|
export const Default: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select an option" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
<SelectItem value="option4">Option 4</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With Default Value
|
||||||
|
export const WithDefaultValue: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Select defaultValue="option2">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select an option" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
<SelectItem value="option4">Option 4</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With Icons
|
||||||
|
export const WithIcons: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Select defaultValue="user">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a feature" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="user">
|
||||||
|
<User className="size-4" />
|
||||||
|
User Management
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="theme">
|
||||||
|
<Palette className="size-4" />
|
||||||
|
Theme Settings
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="language">
|
||||||
|
<Globe className="size-4" />
|
||||||
|
Language
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With Groups
|
||||||
|
export const WithGroups: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="w-[280px]">
|
||||||
|
<SelectValue placeholder="Select a fruit or vegetable" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Fruits</SelectLabel>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
<SelectItem value="orange">Orange</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
<SelectSeparator />
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Vegetables</SelectLabel>
|
||||||
|
<SelectItem value="carrot">Carrot</SelectItem>
|
||||||
|
<SelectItem value="potato">Potato</SelectItem>
|
||||||
|
<SelectItem value="tomato">Tomato</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sizes
|
||||||
|
export const Sizes: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="mb-2 text-sm text-muted-foreground">Small</p>
|
||||||
|
<Select defaultValue="option1">
|
||||||
|
<SelectTrigger size="sm">
|
||||||
|
<SelectValue placeholder="Small size" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-2 text-sm text-muted-foreground">Default</p>
|
||||||
|
<Select defaultValue="option1">
|
||||||
|
<SelectTrigger size="default">
|
||||||
|
<SelectValue placeholder="Default size" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled
|
||||||
|
export const Disabled: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Select disabled defaultValue="option1">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Disabled select" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled Items
|
||||||
|
export const DisabledItems: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Some options disabled" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2" disabled>
|
||||||
|
Option 2 (Disabled)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
<SelectItem value="option4" disabled>
|
||||||
|
Option 4 (Disabled)
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controlled
|
||||||
|
export const Controlled: Story = {
|
||||||
|
render: function ControlledExample() {
|
||||||
|
const [value, setValue] = useState('option1')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Select value={value} onValueChange={setValue}>
|
||||||
|
<SelectTrigger className="w-[280px]">
|
||||||
|
<SelectValue placeholder="Select an option" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
<SelectItem value="option4">Option 4</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<div className="text-sm text-muted-foreground">Current value: {value}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All States
|
||||||
|
export const AllStates: Story = {
|
||||||
|
render: function AllStatesExample() {
|
||||||
|
const [normalValue, setNormalValue] = useState('')
|
||||||
|
const [selectedValue, setSelectedValue] = useState('option2')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
{/* Normal State */}
|
||||||
|
<div>
|
||||||
|
<p className="mb-2 text-sm text-muted-foreground">Normal State</p>
|
||||||
|
<Select value={normalValue} onValueChange={setNormalValue}>
|
||||||
|
<SelectTrigger className="w-[280px]">
|
||||||
|
<SelectValue placeholder="Please select" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Selected State */}
|
||||||
|
<div>
|
||||||
|
<p className="mb-2 text-sm text-muted-foreground">Selected State</p>
|
||||||
|
<Select value={selectedValue} onValueChange={setSelectedValue}>
|
||||||
|
<SelectTrigger className="w-[280px]">
|
||||||
|
<SelectValue placeholder="Please select" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Disabled State */}
|
||||||
|
<div>
|
||||||
|
<p className="mb-2 text-sm text-muted-foreground">Disabled State</p>
|
||||||
|
<Select disabled value={selectedValue}>
|
||||||
|
<SelectTrigger className="w-[280px]">
|
||||||
|
<SelectValue placeholder="Please select" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error State */}
|
||||||
|
<div>
|
||||||
|
<p className="mb-2 text-sm text-muted-foreground">Error State</p>
|
||||||
|
<Select value="">
|
||||||
|
<SelectTrigger className="w-[280px]" aria-invalid>
|
||||||
|
<SelectValue placeholder="This field is required" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="mt-1 text-xs text-destructive">Please select an option</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real World Examples
|
||||||
|
export const RealWorldExamples: Story = {
|
||||||
|
render: function RealWorldExample() {
|
||||||
|
const [language, setLanguage] = useState('zh-CN')
|
||||||
|
const [theme, setTheme] = useState('system')
|
||||||
|
const [timezone, setTimezone] = useState('')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-8">
|
||||||
|
{/* Language Selection */}
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 text-sm font-semibold">Language Settings</h3>
|
||||||
|
<Select value={language} onValueChange={setLanguage}>
|
||||||
|
<SelectTrigger className="w-[280px]">
|
||||||
|
<SelectValue placeholder="Select language" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="zh-CN">
|
||||||
|
<Globe className="size-4" />
|
||||||
|
Simplified Chinese
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="zh-TW">
|
||||||
|
<Globe className="size-4" />
|
||||||
|
Traditional Chinese
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="en-US">
|
||||||
|
<Globe className="size-4" />
|
||||||
|
English
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="ja-JP">
|
||||||
|
<Globe className="size-4" />
|
||||||
|
Japanese
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Theme Selection */}
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 text-sm font-semibold">Theme Settings</h3>
|
||||||
|
<Select value={theme} onValueChange={setTheme}>
|
||||||
|
<SelectTrigger className="w-[280px]">
|
||||||
|
<SelectValue placeholder="Select theme" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="light">
|
||||||
|
<Palette className="size-4" />
|
||||||
|
Light
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="dark">
|
||||||
|
<Palette className="size-4" />
|
||||||
|
Dark
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="system">
|
||||||
|
<Palette className="size-4" />
|
||||||
|
System
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Timezone Selection (with groups) */}
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 text-sm font-semibold">Timezone Settings</h3>
|
||||||
|
<Select value={timezone} onValueChange={setTimezone}>
|
||||||
|
<SelectTrigger className="w-[280px]">
|
||||||
|
<SelectValue placeholder="Select timezone" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Asia</SelectLabel>
|
||||||
|
<SelectItem value="Asia/Shanghai">Shanghai (UTC+8)</SelectItem>
|
||||||
|
<SelectItem value="Asia/Tokyo">Tokyo (UTC+9)</SelectItem>
|
||||||
|
<SelectItem value="Asia/Seoul">Seoul (UTC+9)</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
<SelectSeparator />
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>America</SelectLabel>
|
||||||
|
<SelectItem value="America/New_York">New York (UTC-5)</SelectItem>
|
||||||
|
<SelectItem value="America/Los_Angeles">Los Angeles (UTC-8)</SelectItem>
|
||||||
|
<SelectItem value="America/Chicago">Chicago (UTC-6)</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
<SelectSeparator />
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Europe</SelectLabel>
|
||||||
|
<SelectItem value="Europe/London">London (UTC+0)</SelectItem>
|
||||||
|
<SelectItem value="Europe/Paris">Paris (UTC+1)</SelectItem>
|
||||||
|
<SelectItem value="Europe/Berlin">Berlin (UTC+1)</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Required Field Example */}
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 text-sm font-semibold">User Role (Required)</h3>
|
||||||
|
<Select value="">
|
||||||
|
<SelectTrigger className="w-[280px]" aria-invalid>
|
||||||
|
<SelectValue placeholder="Select user role" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="admin">
|
||||||
|
<User className="size-4" />
|
||||||
|
Administrator
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="editor">
|
||||||
|
<User className="size-4" />
|
||||||
|
Editor
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="viewer">
|
||||||
|
<User className="size-4" />
|
||||||
|
Viewer
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="mt-1 text-xs text-destructive">Please select a user role</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long List
|
||||||
|
export const LongList: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="w-[280px]">
|
||||||
|
<SelectValue placeholder="Select a number" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Array.from({ length: 50 }, (_, i) => (
|
||||||
|
<SelectItem key={i + 1} value={`item-${i + 1}`}>
|
||||||
|
Option {i + 1}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
66
yarn.lock
66
yarn.lock
@ -2018,6 +2018,7 @@ __metadata:
|
|||||||
"@radix-ui/react-dialog": "npm:^1.1.15"
|
"@radix-ui/react-dialog": "npm:^1.1.15"
|
||||||
"@radix-ui/react-popover": "npm:^1.1.15"
|
"@radix-ui/react-popover": "npm:^1.1.15"
|
||||||
"@radix-ui/react-radio-group": "npm:^1.3.8"
|
"@radix-ui/react-radio-group": "npm:^1.3.8"
|
||||||
|
"@radix-ui/react-select": "npm:^2.2.6"
|
||||||
"@radix-ui/react-slot": "npm:^1.2.3"
|
"@radix-ui/react-slot": "npm:^1.2.3"
|
||||||
"@radix-ui/react-use-controllable-state": "npm:^1.2.2"
|
"@radix-ui/react-use-controllable-state": "npm:^1.2.2"
|
||||||
"@storybook/addon-docs": "npm:^10.0.5"
|
"@storybook/addon-docs": "npm:^10.0.5"
|
||||||
@ -6908,6 +6909,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/number@npm:1.1.1":
|
||||||
|
version: 1.1.1
|
||||||
|
resolution: "@radix-ui/number@npm:1.1.1"
|
||||||
|
checksum: 10c0/0570ad92287398e8a7910786d7cee0a998174cdd6637ba61571992897c13204adf70b9ed02d0da2af554119411128e701d9c6b893420612897b438dc91db712b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/primitive@npm:1.1.3":
|
"@radix-ui/primitive@npm:1.1.3":
|
||||||
version: 1.1.3
|
version: 1.1.3
|
||||||
resolution: "@radix-ui/primitive@npm:1.1.3"
|
resolution: "@radix-ui/primitive@npm:1.1.3"
|
||||||
@ -7334,6 +7342,45 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-select@npm:^2.2.6":
|
||||||
|
version: 2.2.6
|
||||||
|
resolution: "@radix-ui/react-select@npm:2.2.6"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/number": "npm:1.1.1"
|
||||||
|
"@radix-ui/primitive": "npm:1.1.3"
|
||||||
|
"@radix-ui/react-collection": "npm:1.1.7"
|
||||||
|
"@radix-ui/react-compose-refs": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-context": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-direction": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-dismissable-layer": "npm:1.1.11"
|
||||||
|
"@radix-ui/react-focus-guards": "npm:1.1.3"
|
||||||
|
"@radix-ui/react-focus-scope": "npm:1.1.7"
|
||||||
|
"@radix-ui/react-id": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-popper": "npm:1.2.8"
|
||||||
|
"@radix-ui/react-portal": "npm:1.1.9"
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.3"
|
||||||
|
"@radix-ui/react-slot": "npm:1.2.3"
|
||||||
|
"@radix-ui/react-use-callback-ref": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
|
||||||
|
"@radix-ui/react-use-layout-effect": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-use-previous": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-visually-hidden": "npm:1.2.3"
|
||||||
|
aria-hidden: "npm:^1.2.4"
|
||||||
|
react-remove-scroll: "npm:^2.6.3"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/34b2492589c3a4b118a03900d622640033630f30ac93c4a69b3701513117607f4ac3a0d9dd3cad39caa8b6495660f71f3aa9d0074d4eb4dac6804dc0b8408deb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-slot@npm:1.2.3, @radix-ui/react-slot@npm:^1.2.3":
|
"@radix-ui/react-slot@npm:1.2.3, @radix-ui/react-slot@npm:^1.2.3":
|
||||||
version: 1.2.3
|
version: 1.2.3
|
||||||
resolution: "@radix-ui/react-slot@npm:1.2.3"
|
resolution: "@radix-ui/react-slot@npm:1.2.3"
|
||||||
@ -7464,6 +7511,25 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-visually-hidden@npm:1.2.3":
|
||||||
|
version: 1.2.3
|
||||||
|
resolution: "@radix-ui/react-visually-hidden@npm:1.2.3"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.3"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/cf86a37f1cbee50a964056f3dc4f6bb1ee79c76daa321f913aa20ff3e1ccdfafbf2b114d7bb616aeefc7c4b895e6ca898523fdb67710d89bd5d8edb739a0d9b6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/rect@npm:1.1.1":
|
"@radix-ui/rect@npm:1.1.1":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "@radix-ui/rect@npm:1.1.1"
|
resolution: "@radix-ui/rect@npm:1.1.1"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user