@pantoia/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/accordion/AGENTS.md +60 -0
  2. package/accordion/index.module.css +10 -0
  3. package/accordion/index.tsx +91 -0
  4. package/alert-dialog/AGENTS.md +78 -0
  5. package/alert-dialog/index.tsx +119 -0
  6. package/badge/AGENTS.md +35 -0
  7. package/badge/index.tsx +34 -0
  8. package/button/AGENTS.md +39 -0
  9. package/button/index.tsx +98 -0
  10. package/callout/AGENTS.md +42 -0
  11. package/callout/index.tsx +70 -0
  12. package/checkbox/AGENTS.md +54 -0
  13. package/checkbox/index.tsx +151 -0
  14. package/code-block/AGENTS.md +60 -0
  15. package/code-block/index.module.css +46 -0
  16. package/code-block/index.tsx +127 -0
  17. package/combobox/AGENTS.md +150 -0
  18. package/combobox/index.tsx +420 -0
  19. package/command-palette/AGENTS.md +44 -0
  20. package/command-palette/index.tsx +108 -0
  21. package/copy-button/AGENTS.md +43 -0
  22. package/copy-button/index.tsx +119 -0
  23. package/dialog/AGENTS.md +81 -0
  24. package/dialog/index.tsx +108 -0
  25. package/dist/index.js +189 -0
  26. package/drawer/AGENTS.md +66 -0
  27. package/drawer/index.module.css +17 -0
  28. package/drawer/index.tsx +198 -0
  29. package/dropdown-menu/AGENTS.md +133 -0
  30. package/dropdown-menu/index.tsx +223 -0
  31. package/field/AGENTS.md +96 -0
  32. package/field/index.tsx +149 -0
  33. package/input/AGENTS.md +35 -0
  34. package/input/index.tsx +36 -0
  35. package/kbd/AGENTS.md +17 -0
  36. package/kbd/index.tsx +13 -0
  37. package/label/AGENTS.md +21 -0
  38. package/label/index.tsx +13 -0
  39. package/number-field/AGENTS.md +53 -0
  40. package/number-field/index.tsx +227 -0
  41. package/package.json +73 -0
  42. package/popover/AGENTS.md +64 -0
  43. package/popover/index.tsx +66 -0
  44. package/press.css +37 -0
  45. package/radio-group/AGENTS.md +81 -0
  46. package/radio-group/index.tsx +139 -0
  47. package/registry.json +726 -0
  48. package/select/AGENTS.md +90 -0
  49. package/select/index.tsx +169 -0
  50. package/styles.css +347 -0
  51. package/switch/AGENTS.md +37 -0
  52. package/switch/index.tsx +42 -0
  53. package/table/AGENTS.md +82 -0
  54. package/table/index.tsx +120 -0
  55. package/tabs/AGENTS.md +60 -0
  56. package/tabs/index.module.css +14 -0
  57. package/tabs/index.tsx +105 -0
  58. package/test-utils.ts +10 -0
  59. package/textarea/AGENTS.md +35 -0
  60. package/textarea/index.tsx +37 -0
  61. package/toast/AGENTS.md +42 -0
  62. package/toast/index.tsx +71 -0
  63. package/toggle/AGENTS.md +37 -0
  64. package/toggle/index.tsx +36 -0
  65. package/tooltip/AGENTS.md +50 -0
  66. package/tooltip/index.tsx +57 -0
  67. package/tv.ts +23 -0
@@ -0,0 +1,54 @@
1
+ # Checkbox
2
+
3
+ ```
4
+ 받기 npx @pantoia/ui add checkbox
5
+ 층 ui
6
+ 파일 checkbox/index.tsx · checkbox/AGENTS.md
7
+ ```
8
+
9
+ ## 부위
10
+
11
+ | 이름 | props |
12
+ |---|---|
13
+ | `CheckboxGroup` | `CheckboxGroupProps` |
14
+ | `Checkbox` | `CheckboxProps` |
15
+
16
+ ## props
17
+
18
+ ### `CheckboxGroupProps`
19
+
20
+ | 이름 | 타입 | 필수 |
21
+ |---|---|---|
22
+ | `value` | `string[]` | |
23
+ | `defaultValue` | `string[]` | |
24
+ | `onValueChange` | `(value: string[]) => void` | |
25
+ | `disabled` | `boolean` | |
26
+ | `className` | `string` | |
27
+ | `children` | `ReactNode` | |
28
+
29
+ ### `CheckboxProps`
30
+
31
+ `Omit<ComponentProps<typeof CheckboxPrimitive.Root>, 'checked' | 'onCheckedChange' | 'value'>` 를 확장한다.
32
+
33
+ | 이름 | 타입 | 필수 |
34
+ |---|---|---|
35
+ | `value` | `string` | |
36
+ | `checked` | `boolean` | |
37
+ | `onCheckedChange` | `(checked: boolean) => void` | |
38
+ | `indeterminate` | `boolean` | |
39
+ | `onIndeterminateChange` | `(indeterminate: boolean) => void` | |
40
+ | `color` | `'control' \| 'info' \| 'success' \| 'warning' \| 'danger'` | |
41
+
42
+ ## 변형 — `checkbox`
43
+
44
+ | 축 | 값 | 기본 |
45
+ |---|---|---|
46
+ | `color` | `control` · `info` · `success` · `warning` · `danger` | `control` |
47
+
48
+ ## 함께 받는 것
49
+
50
+ `tv`
51
+
52
+ ## npm 의존
53
+
54
+ `lucide-react` · `radix-ui`
@@ -0,0 +1,151 @@
1
+ 'use client'
2
+
3
+ import { Check, Minus } from 'lucide-react'
4
+ import { Checkbox as CheckboxPrimitive } from 'radix-ui'
5
+ import {
6
+ createContext,
7
+ use,
8
+ useRef,
9
+ useState,
10
+ type ComponentProps,
11
+ type KeyboardEvent as ReactKeyboardEvent,
12
+ type ReactNode,
13
+ } from 'react'
14
+
15
+ import { tv } from '../tv'
16
+
17
+ export const checkbox = tv({
18
+ base: 'size-5 shrink-0 rounded-mark inline-flex items-center justify-center'
19
+ + ' bg-transparent ring-1 ring-inset ring-line-strong'
20
+ + ' transition-state press press-shrink'
21
+ + ' checked:ring-2'
22
+ + ' invalid:ring-2 invalid:ring-danger'
23
+ + ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
24
+ + ' disabled:ring-line disabled:bg-disabled disabled:cursor-not-allowed'
25
+ + ' checked:disabled:bg-control-on-disabled checked:disabled:ring-control-on-disabled',
26
+
27
+ variants: {
28
+ color: {
29
+ control: 'checked:bg-control-on checked:ring-control-on',
30
+ info: 'checked:bg-info checked:ring-info',
31
+ success: 'checked:bg-success checked:ring-success',
32
+ warning: 'checked:bg-warning checked:ring-warning',
33
+ danger: 'checked:bg-danger checked:ring-danger',
34
+ },
35
+ },
36
+
37
+ defaultVariants: { color: 'control' },
38
+ })
39
+
40
+ interface GroupContext {
41
+ value: string[]
42
+ toggle: (item: string, next: boolean) => void
43
+ disabled?: boolean
44
+ }
45
+
46
+ const Group = createContext<GroupContext | null>(null)
47
+
48
+ const ITEMS = '[role="checkbox"]:not([disabled])'
49
+
50
+ export interface CheckboxGroupProps {
51
+ value?: string[]
52
+ defaultValue?: string[]
53
+ onValueChange?: (value: string[]) => void
54
+ disabled?: boolean
55
+ className?: string
56
+ children?: ReactNode
57
+ }
58
+
59
+ export function CheckboxGroup({
60
+ value,
61
+ defaultValue,
62
+ onValueChange,
63
+ disabled,
64
+ className,
65
+ children,
66
+ }: CheckboxGroupProps) {
67
+ const [own, setOwn] = useState<string[]>(defaultValue ?? [])
68
+ const list = value ?? own
69
+ const root = useRef<HTMLDivElement>(null)
70
+
71
+ const toggle = (item: string, next: boolean) => {
72
+ const after = next ? [...list, item] : list.filter(one => one !== item)
73
+ if (value === undefined) setOwn(after)
74
+ onValueChange?.(after)
75
+ }
76
+
77
+ const onKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>) => {
78
+ const step = e.key === 'ArrowDown' || e.key === 'ArrowRight'
79
+ ? 1
80
+ : e.key === 'ArrowUp' || e.key === 'ArrowLeft' ? -1 : 0
81
+ if (step === 0 || !root.current) return
82
+
83
+ const items = [...root.current.querySelectorAll<HTMLElement>(ITEMS)]
84
+ const at = items.indexOf(document.activeElement as HTMLElement)
85
+ if (at === -1) return
86
+
87
+ e.preventDefault()
88
+ items[(at + step + items.length) % items.length]!.focus()
89
+ }
90
+
91
+ return (
92
+ <Group value={{ value: list, toggle, disabled }}>
93
+ <div
94
+ ref={root}
95
+ role="group"
96
+ onKeyDown={onKeyDown}
97
+ className={tv({ base: 'flex flex-col gap-3' })({ className })}
98
+ >
99
+ {children}
100
+ </div>
101
+ </Group>
102
+ )
103
+ }
104
+
105
+ export interface CheckboxProps extends Omit<ComponentProps<typeof CheckboxPrimitive.Root>, 'checked' | 'onCheckedChange' | 'value'> {
106
+ value?: string
107
+ checked?: boolean
108
+ onCheckedChange?: (checked: boolean) => void
109
+ indeterminate?: boolean
110
+ onIndeterminateChange?: (indeterminate: boolean) => void
111
+ color?: 'control' | 'info' | 'success' | 'warning' | 'danger'
112
+ }
113
+
114
+ export function Checkbox({
115
+ value,
116
+ checked,
117
+ onCheckedChange,
118
+ indeterminate,
119
+ onIndeterminateChange,
120
+ color,
121
+ className,
122
+ disabled,
123
+ ...rest
124
+ }: CheckboxProps) {
125
+ const group = use(Group)
126
+ const item = value ?? 'on'
127
+
128
+ const on = group ? group.value.includes(item) : checked ?? false
129
+ const state = indeterminate ? 'indeterminate' as const : on
130
+
131
+ const change = (next: boolean | 'indeterminate') => {
132
+ if (typeof next !== 'boolean') return
133
+ if (group) group.toggle(item, next)
134
+ else onCheckedChange?.(next)
135
+ if (indeterminate) onIndeterminateChange?.(false)
136
+ }
137
+
138
+ return (
139
+ <CheckboxPrimitive.Root
140
+ {...rest}
141
+ checked={state}
142
+ disabled={disabled ?? group?.disabled}
143
+ onCheckedChange={change}
144
+ className={checkbox({ color, className })}
145
+ >
146
+ <CheckboxPrimitive.Indicator className="text-fg-on-control flex">
147
+ {indeterminate ? <Minus className="size-4" /> : <Check className="size-4" />}
148
+ </CheckboxPrimitive.Indicator>
149
+ </CheckboxPrimitive.Root>
150
+ )
151
+ }
@@ -0,0 +1,60 @@
1
+ # CodeBlock
2
+
3
+ ```
4
+ 받기 npx @pantoia/ui add code-block
5
+ 층 ui
6
+ 파일 code-block/index.tsx · code-block/index.module.css · code-block/AGENTS.md
7
+ ```
8
+
9
+ ## 부위
10
+
11
+ | 이름 | props |
12
+ |---|---|
13
+ | `CodeBlock` | `CodeBlockProps` |
14
+ | `CodeBlockHeader` | — |
15
+
16
+ ### `CodeBlockHeader`
17
+
18
+ | 이름 | 타입 | 필수 |
19
+ |---|---|---|
20
+ | `className` | `string` | |
21
+ | `children` | `ReactNode` | |
22
+
23
+ ## props
24
+
25
+ ### `CodeBlockFold`
26
+
27
+ | 이름 | 타입 | 필수 |
28
+ |---|---|---|
29
+ | `maxLines` | `number` | 예 |
30
+ | `more` | `string` | 예 |
31
+ | `moreCount` | `string` | 예 |
32
+ | `less` | `string` | 예 |
33
+
34
+ ### `CodeBlockProps`
35
+
36
+ | 이름 | 타입 | 필수 |
37
+ |---|---|---|
38
+ | `code` | `string` | 예 |
39
+ | `wrap` | `boolean` | |
40
+ | `numbers` | `boolean` | |
41
+ | `fold` | `CodeBlockFold` | |
42
+ | `className` | `string` | |
43
+ | `header` | `ReactNode` | |
44
+ | `line` | `(line: string, index: number) => ReactNode` | |
45
+
46
+ ## 변형 — `codeBlock`
47
+
48
+ 슬롯 `root` · `viewport` · `pre` · `chevron`
49
+
50
+ | 축 | 값 | 기본 |
51
+ |---|---|---|
52
+ | `folded` | `false` | — |
53
+
54
+ ## 함께 받는 것
55
+
56
+ `tv`
57
+
58
+ ## npm 의존
59
+
60
+ `lucide-react`
@@ -0,0 +1,46 @@
1
+ .code {
2
+ margin: 0;
3
+ padding: 1rem;
4
+ counter-reset: ln;
5
+
6
+ --code-block-keyword: var(--purple-700);
7
+ --code-block-name: var(--orange-800);
8
+ --code-block-string: var(--green-800);
9
+ --code-block-comment: var(--gray-500);
10
+ --code-block-value: var(--blue-700);
11
+ }
12
+
13
+ .code-keyword { color: var(--code-block-keyword); }
14
+ .code-name { color: var(--code-block-name); }
15
+ .code-string { color: var(--code-block-string); }
16
+ .code-comment { color: var(--code-block-comment); }
17
+ .code-value { color: var(--code-block-value); }
18
+ .line { display: block; white-space: pre; min-height: 1lh; }
19
+ .wrap .line { white-space: pre-wrap; word-break: break-all; }
20
+
21
+ .numbers { padding-left: 0; }
22
+ .numbers .line::before {
23
+ counter-increment: ln;
24
+ content: counter(ln);
25
+ display: inline-block;
26
+ width: 3.4em;
27
+ padding-right: 0.9em;
28
+ text-align: right;
29
+ color: var(--color-fg-subtle);
30
+ user-select: none;
31
+ pointer-events: none;
32
+ }
33
+
34
+ .numbers:not(.wrap) .line::before {
35
+ position: sticky;
36
+ left: 0;
37
+ background: var(--color-well);
38
+ }
39
+
40
+ .numbers.wrap .line { padding-left: 3.4em; text-indent: -3.4em; }
41
+
42
+ .clip {
43
+ max-height: calc(var(--code-block-max-lines) * 1lh + 2rem);
44
+ overflow-y: hidden;
45
+ mask-image: linear-gradient(to bottom, #000 calc(100% - 2.5rem), transparent);
46
+ }
@@ -0,0 +1,127 @@
1
+ 'use client'
2
+
3
+ import { ChevronDown } from 'lucide-react'
4
+ import { useCallback, useState, useSyncExternalStore, type CSSProperties, type ReactNode } from 'react'
5
+
6
+ import { tv } from '../tv'
7
+
8
+ import s from './index.module.css'
9
+
10
+ export const codeToken = {
11
+ keyword: s['code-keyword'],
12
+ name: s['code-name'],
13
+ string: s['code-string'],
14
+ comment: s['code-comment'],
15
+ value: s['code-value'],
16
+ none: '',
17
+ } as const
18
+
19
+ export interface CodeBlockFold {
20
+ maxLines: number
21
+ more: string
22
+ moreCount: string
23
+ less: string
24
+ }
25
+
26
+ export const codeBlock = tv({
27
+ slots: {
28
+ root: 'bg-well rounded-well text-value font-mono overflow-hidden',
29
+ viewport: 'scroll-slim overflow-auto'
30
+ + ' focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring',
31
+ pre: '',
32
+ chevron: 'size-4 transition-transform motion-reduce:transition-none',
33
+ },
34
+
35
+ variants: {
36
+ folded: { false: { chevron: 'rotate-180' } },
37
+ },
38
+ })
39
+
40
+ export interface CodeBlockProps {
41
+ code: string
42
+ wrap?: boolean
43
+ numbers?: boolean
44
+ fold?: CodeBlockFold
45
+ className?: string
46
+ header?: ReactNode
47
+ line?: (line: string, index: number) => ReactNode
48
+ }
49
+
50
+ export function CodeBlock({
51
+ code,
52
+ wrap = false,
53
+ numbers = false,
54
+ fold,
55
+ className,
56
+ header,
57
+ line,
58
+ }: CodeBlockProps) {
59
+ const [folded, setFolded] = useState(true)
60
+ const [box, setBox] = useState<HTMLDivElement | null>(null)
61
+
62
+ const subscribe = useCallback((onChange: () => void) => {
63
+ if (!box) return () => {}
64
+ const observer = new ResizeObserver(onChange)
65
+ observer.observe(box)
66
+ if (box.firstElementChild) observer.observe(box.firstElementChild)
67
+ return () => observer.disconnect()
68
+ }, [box])
69
+
70
+ const overflows = useSyncExternalStore(
71
+ subscribe,
72
+ () => box !== null && (box.scrollWidth > box.clientWidth || box.scrollHeight > box.clientHeight),
73
+ () => false,
74
+ )
75
+
76
+ const lines = code.split('\n')
77
+ const foldable = !!fold && lines.length > fold.maxLines
78
+ const rest = lines.length - (fold?.maxLines ?? 0)
79
+
80
+ const ui = codeBlock({ folded })
81
+
82
+ const pre = [s.code, wrap ? s.wrap : '', numbers ? s.numbers : '', ui.pre()]
83
+ .filter(Boolean).join(' ')
84
+
85
+ const viewport = [ui.viewport(), foldable && folded ? s.clip : '']
86
+ .filter(Boolean).join(' ')
87
+
88
+ return (
89
+ <div className={ui.root({ className })}>
90
+ {header}
91
+
92
+ <div
93
+ ref={setBox}
94
+ tabIndex={overflows ? 0 : undefined}
95
+ className={viewport}
96
+ style={{ '--code-block-max-lines': fold?.maxLines } as CSSProperties}
97
+ ><pre className={pre}>{lines.map((text, i) => (
98
+ <span key={i} className={s.line}>{line ? line(text, i) : text}</span>
99
+ ))}</pre></div>
100
+
101
+ {foldable && (
102
+ <button
103
+ type="button"
104
+ className="border-line text-caption text-fg-muted flex w-full items-center justify-center
105
+ gap-tight border-t py-2 transition-state press
106
+ enabled:hover:bg-hover
107
+ focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring"
108
+ onClick={() => setFolded(!folded)}
109
+ >
110
+ <ChevronDown className={ui.chevron()} />
111
+ {folded ? (wrap ? fold.more : fold.moreCount.replace('{count}', String(rest))) : fold.less}
112
+ </button>
113
+ )}
114
+ </div>
115
+ )
116
+ }
117
+
118
+ export function CodeBlockHeader({ className, children }: { className?: string, children?: ReactNode }) {
119
+ return (
120
+ <div
121
+ className={tv({ base: 'border-line text-caption text-fg-muted'
122
+ + ' flex items-center gap-stack border-b px-4 py-2' })({ className })}
123
+ >
124
+ {children}
125
+ </div>
126
+ )
127
+ }
@@ -0,0 +1,150 @@
1
+ # Combobox
2
+
3
+ ```
4
+ 받기 npx @pantoia/ui add combobox
5
+ 층 ui
6
+ 파일 combobox/index.tsx · combobox/AGENTS.md
7
+ ```
8
+
9
+ ## 부위
10
+
11
+ | 이름 | props |
12
+ |---|---|
13
+ | `Combobox` | `ComboboxProps` |
14
+ | `ComboboxAnchor` | `ComboboxAnchorProps` |
15
+ | `ComboboxInput` | `ComboboxInputProps` |
16
+ | `ComboboxTrigger` | — |
17
+ | `ComboboxCancel` | — |
18
+ | `ComboboxContent` | `ComboboxContentProps` |
19
+ | `ComboboxViewport` | — |
20
+ | `ComboboxItem` | `ComboboxItemProps` |
21
+ | `ComboboxItemText` | — |
22
+ | `ComboboxItemDescription` | — |
23
+ | `ComboboxGroup` | — |
24
+ | `ComboboxEmpty` | — |
25
+ | `ComboboxSeparator` | — |
26
+
27
+ ### `ComboboxTrigger`
28
+
29
+ | 이름 | 타입 | 필수 |
30
+ |---|---|---|
31
+ | `label` | `string` | |
32
+ | `disabled` | `boolean` | |
33
+ | `className` | `string` | |
34
+
35
+ ### `ComboboxCancel`
36
+
37
+ | 이름 | 타입 | 필수 |
38
+ |---|---|---|
39
+ | `label` | `string` | |
40
+ | `className` | `string` | |
41
+
42
+ ### `ComboboxViewport`
43
+
44
+ | 이름 | 타입 | 필수 |
45
+ |---|---|---|
46
+ | `label` | `string` | |
47
+ | `className` | `string` | |
48
+ | `children` | `ReactNode` | |
49
+
50
+ ### `ComboboxItemText`
51
+
52
+ | 이름 | 타입 | 필수 |
53
+ |---|---|---|
54
+ | `className` | `string` | |
55
+ | `children` | `ReactNode` | |
56
+
57
+ ### `ComboboxItemDescription`
58
+
59
+ | 이름 | 타입 | 필수 |
60
+ |---|---|---|
61
+ | `className` | `string` | |
62
+ | `children` | `ReactNode` | |
63
+
64
+ ### `ComboboxGroup`
65
+
66
+ | 이름 | 타입 | 필수 |
67
+ |---|---|---|
68
+ | `label` | `string` | |
69
+ | `className` | `string` | |
70
+ | `children` | `ReactNode` | |
71
+
72
+ ### `ComboboxEmpty`
73
+
74
+ | 이름 | 타입 | 필수 |
75
+ |---|---|---|
76
+ | `className` | `string` | |
77
+ | `children` | `ReactNode` | |
78
+
79
+ ### `ComboboxSeparator`
80
+
81
+ | 이름 | 타입 | 필수 |
82
+ |---|---|---|
83
+ | `className` | `string` | |
84
+
85
+ ## 도우미
86
+
87
+ `contains(text: string, search: string)`
88
+
89
+ ## props
90
+
91
+ ### `ComboboxProps`
92
+
93
+ | 이름 | 타입 | 필수 |
94
+ |---|---|---|
95
+ | `value` | `string` | |
96
+ | `defaultValue` | `string` | |
97
+ | `onValueChange` | `(value: string) => void` | |
98
+ | `open` | `boolean` | |
99
+ | `defaultOpen` | `boolean` | |
100
+ | `onOpenChange` | `(open: boolean) => void` | |
101
+ | `ignoreFilter` | `boolean` | |
102
+ | `className` | `string` | |
103
+ | `children` | `ReactNode` | |
104
+
105
+ ### `ComboboxAnchorProps`
106
+
107
+ | 이름 | 타입 | 필수 |
108
+ |---|---|---|
109
+ | `surface` | `'bare' \| 'well'` | |
110
+ | `size` | `'sm' \| 'md' \| 'lg'` | |
111
+ | `className` | `string` | |
112
+ | `children` | `ReactNode` | |
113
+
114
+ ### `ComboboxInputProps`
115
+
116
+ `Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange'>` 를 확장한다.
117
+
118
+ | 이름 | 타입 | 필수 |
119
+ |---|---|---|
120
+ | `value` | `string` | |
121
+ | `onValueChange` | `(value: string) => void` | |
122
+
123
+ ### `ComboboxContentProps`
124
+
125
+ `Omit<ComponentProps<typeof PopoverPrimitive.Content>, 'align' | 'sideOffset' | 'onOpenAutoFocus'>`
126
+
127
+ ### `ComboboxItemProps`
128
+
129
+ | 이름 | 타입 | 필수 |
130
+ |---|---|---|
131
+ | `value` | `string` | 예 |
132
+ | `textValue` | `string` | |
133
+ | `disabled` | `boolean` | |
134
+ | `className` | `string` | |
135
+ | `children` | `ReactNode` | |
136
+
137
+ ## 변형 — `comboboxAnchor`
138
+
139
+ | 축 | 값 | 기본 |
140
+ |---|---|---|
141
+ | `surface` | `bare` · `well` | `bare` |
142
+ | `size` | `sm` · `md` · `lg` | `md` |
143
+
144
+ ## 함께 받는 것
145
+
146
+ `tv`
147
+
148
+ ## npm 의존
149
+
150
+ `lucide-react` · `radix-ui`