@pantoia/ui 0.1.0 → 0.3.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 (68) hide show
  1. package/accordion/AGENTS.md +5 -5
  2. package/accordion/index.tsx +21 -11
  3. package/alert-dialog/AGENTS.md +7 -7
  4. package/alert-dialog/index.tsx +20 -10
  5. package/badge/AGENTS.md +5 -5
  6. package/badge/index.tsx +8 -8
  7. package/button/AGENTS.md +7 -7
  8. package/button/index.tsx +34 -34
  9. package/callout/AGENTS.md +4 -9
  10. package/callout/index.tsx +42 -22
  11. package/checkbox/AGENTS.md +5 -5
  12. package/checkbox/index.tsx +19 -16
  13. package/combobox/AGENTS.md +13 -13
  14. package/combobox/index.tsx +90 -42
  15. package/command-palette/AGENTS.md +3 -6
  16. package/command-palette/index.tsx +7 -3
  17. package/dialog/AGENTS.md +6 -6
  18. package/dialog/index.tsx +11 -5
  19. package/dist/index.js +200 -43
  20. package/drawer/AGENTS.md +5 -5
  21. package/drawer/index.tsx +8 -4
  22. package/dropdown-menu/AGENTS.md +12 -12
  23. package/dropdown-menu/index.tsx +27 -12
  24. package/field/AGENTS.md +8 -12
  25. package/field/index.tsx +39 -13
  26. package/input/AGENTS.md +4 -4
  27. package/input/index.tsx +5 -5
  28. package/kbd/AGENTS.md +1 -1
  29. package/kbd/index.tsx +6 -2
  30. package/label/AGENTS.md +1 -1
  31. package/label/index.tsx +3 -1
  32. package/number-field/AGENTS.md +6 -10
  33. package/number-field/index.tsx +26 -16
  34. package/package.json +13 -15
  35. package/palette.css +114 -0
  36. package/popover/AGENTS.md +4 -4
  37. package/popover/index.tsx +14 -4
  38. package/radio-group/AGENTS.md +8 -8
  39. package/radio-group/index.tsx +20 -16
  40. package/registry.json +9 -71
  41. package/select/AGENTS.md +10 -10
  42. package/select/index.tsx +46 -21
  43. package/switch/AGENTS.md +4 -4
  44. package/switch/index.tsx +13 -13
  45. package/table/AGENTS.md +9 -9
  46. package/table/index.tsx +16 -6
  47. package/tabs/AGENTS.md +5 -4
  48. package/tabs/index.module.css +2 -0
  49. package/tabs/index.tsx +22 -11
  50. package/tailwind.css +3 -0
  51. package/textarea/AGENTS.md +4 -4
  52. package/textarea/index.tsx +5 -5
  53. package/toast/AGENTS.md +5 -13
  54. package/toast/index.tsx +27 -33
  55. package/toggle/AGENTS.md +4 -4
  56. package/toggle/index.tsx +8 -7
  57. package/tokens.css +272 -0
  58. package/tooltip/AGENTS.md +2 -2
  59. package/tooltip/index.tsx +14 -4
  60. package/tv.ts +0 -1
  61. package/utilities.css +119 -0
  62. package/code-block/AGENTS.md +0 -60
  63. package/code-block/index.module.css +0 -46
  64. package/code-block/index.tsx +0 -127
  65. package/copy-button/AGENTS.md +0 -43
  66. package/copy-button/index.tsx +0 -119
  67. package/press.css +0 -37
  68. package/styles.css +0 -347
@@ -1,6 +1,5 @@
1
1
  'use client'
2
2
 
3
- import { Check, Minus } from 'lucide-react'
4
3
  import { Checkbox as CheckboxPrimitive } from 'radix-ui'
5
4
  import {
6
5
  createContext,
@@ -15,26 +14,26 @@ import {
15
14
  import { tv } from '../tv'
16
15
 
17
16
  export const checkbox = tv({
18
- base: 'size-5 shrink-0 rounded-mark inline-flex items-center justify-center'
17
+ base: 'size-5 shrink-0 rounded-r4 inline-flex items-center justify-center'
19
18
  + ' bg-transparent ring-1 ring-inset ring-line-strong'
20
19
  + ' transition-state press press-shrink'
21
20
  + ' 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',
21
+ + ' invalid:ring-2 invalid:ring-line-danger'
22
+ + ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-line-focus-ring'
23
+ + ' disabled:ring-line disabled:bg-bg-neutral-disabled disabled:cursor-not-allowed'
24
+ + ' checked:disabled:bg-bg-neutral-solid-checked-disabled checked:disabled:ring-transparent',
26
25
 
27
26
  variants: {
28
27
  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',
28
+ neutral: 'checked:bg-bg-neutral-solid checked:ring-line-neutral-solid',
29
+ info: 'checked:bg-bg-info-solid checked:ring-line-info',
30
+ success: 'checked:bg-bg-success-solid checked:ring-line-success',
31
+ warning: 'checked:bg-bg-warning-solid checked:ring-line-warning',
32
+ danger: 'checked:bg-bg-danger-solid checked:ring-line-danger',
34
33
  },
35
34
  },
36
35
 
37
- defaultVariants: { color: 'control' },
36
+ defaultVariants: { color: 'neutral' },
38
37
  })
39
38
 
40
39
  interface GroupContext {
@@ -56,6 +55,8 @@ export interface CheckboxGroupProps {
56
55
  children?: ReactNode
57
56
  }
58
57
 
58
+ const checkboxGroup = tv({ base: 'flex flex-col gap-3' })
59
+
59
60
  export function CheckboxGroup({
60
61
  value,
61
62
  defaultValue,
@@ -94,7 +95,7 @@ export function CheckboxGroup({
94
95
  ref={root}
95
96
  role="group"
96
97
  onKeyDown={onKeyDown}
97
- className={tv({ base: 'flex flex-col gap-3' })({ className })}
98
+ className={checkboxGroup({ className })}
98
99
  >
99
100
  {children}
100
101
  </div>
@@ -108,7 +109,7 @@ export interface CheckboxProps extends Omit<ComponentProps<typeof CheckboxPrimit
108
109
  onCheckedChange?: (checked: boolean) => void
109
110
  indeterminate?: boolean
110
111
  onIndeterminateChange?: (indeterminate: boolean) => void
111
- color?: 'control' | 'info' | 'success' | 'warning' | 'danger'
112
+ color?: 'neutral' | 'info' | 'success' | 'warning' | 'danger'
112
113
  }
113
114
 
114
115
  export function Checkbox({
@@ -143,8 +144,10 @@ export function Checkbox({
143
144
  onCheckedChange={change}
144
145
  className={checkbox({ color, className })}
145
146
  >
146
- <CheckboxPrimitive.Indicator className="text-fg-on-control flex">
147
- {indeterminate ? <Minus className="size-4" /> : <Check className="size-4" />}
147
+ <CheckboxPrimitive.Indicator className="text-fg-neutral-solid-contrast flex">
148
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
149
+ {indeterminate ? <path d="M5 12h14" /> : <path d="M20 6 9 17l-5-5" />}
150
+ </svg>
148
151
  </CheckboxPrimitive.Indicator>
149
152
  </CheckboxPrimitive.Root>
150
153
  )
@@ -3,7 +3,7 @@
3
3
  ```
4
4
  받기 npx @pantoia/ui add combobox
5
5
  층 ui
6
- 파일 combobox/index.tsx · combobox/AGENTS.md
6
+ 파일 combobox/index.tsx, combobox/AGENTS.md
7
7
  ```
8
8
 
9
9
  ## 부위
@@ -13,16 +13,16 @@
13
13
  | `Combobox` | `ComboboxProps` |
14
14
  | `ComboboxAnchor` | `ComboboxAnchorProps` |
15
15
  | `ComboboxInput` | `ComboboxInputProps` |
16
- | `ComboboxTrigger` | — |
17
- | `ComboboxCancel` | — |
16
+ | `ComboboxTrigger` | - |
17
+ | `ComboboxCancel` | - |
18
18
  | `ComboboxContent` | `ComboboxContentProps` |
19
- | `ComboboxViewport` | — |
19
+ | `ComboboxViewport` | - |
20
20
  | `ComboboxItem` | `ComboboxItemProps` |
21
- | `ComboboxItemText` | — |
22
- | `ComboboxItemDescription` | — |
23
- | `ComboboxGroup` | — |
24
- | `ComboboxEmpty` | — |
25
- | `ComboboxSeparator` | — |
21
+ | `ComboboxItemText` | - |
22
+ | `ComboboxItemDescription` | - |
23
+ | `ComboboxGroup` | - |
24
+ | `ComboboxEmpty` | - |
25
+ | `ComboboxSeparator` | - |
26
26
 
27
27
  ### `ComboboxTrigger`
28
28
 
@@ -134,12 +134,12 @@
134
134
  | `className` | `string` | |
135
135
  | `children` | `ReactNode` | |
136
136
 
137
- ## 변형 — `comboboxAnchor`
137
+ ## 변형 - `comboboxAnchor`
138
138
 
139
139
  | 축 | 값 | 기본 |
140
140
  |---|---|---|
141
- | `surface` | `bare` · `well` | `bare` |
142
- | `size` | `sm` · `md` · `lg` | `md` |
141
+ | `surface` | `bare`, `well` | `bare` |
142
+ | `size` | `sm`, `md`, `lg` | `md` |
143
143
 
144
144
  ## 함께 받는 것
145
145
 
@@ -147,4 +147,4 @@
147
147
 
148
148
  ## npm 의존
149
149
 
150
- `lucide-react` · `radix-ui`
150
+ `radix-ui`
@@ -1,12 +1,12 @@
1
1
  'use client'
2
2
 
3
- import { Check, ChevronDown, X } from 'lucide-react'
4
3
  import { Popover as PopoverPrimitive } from 'radix-ui'
5
4
  import {
6
5
  createContext,
7
6
  use,
8
7
  useEffect,
9
8
  useId,
9
+ useRef,
10
10
  useState,
11
11
  type ComponentProps,
12
12
  type InputHTMLAttributes,
@@ -23,6 +23,7 @@ interface ComboboxContext {
23
23
  active: string | undefined
24
24
  search: string
25
25
  setSearch: (search: string) => void
26
+ setNotify: (notify: ((search: string) => void) | undefined) => void
26
27
  setOpen: (open: boolean) => void
27
28
  pick: (value: string) => void
28
29
  move: (step: number) => void
@@ -73,6 +74,7 @@ export function Combobox({
73
74
  const [search, setSearch] = useState('')
74
75
  const [count, setCount] = useState(0)
75
76
  const [seen, setSeen] = useState(false)
77
+ const notifyRef = useRef<((search: string) => void) | undefined>(undefined)
76
78
 
77
79
  const isOpen = open ?? ownOpen
78
80
  const current = value ?? ownValue
@@ -87,10 +89,19 @@ export function Combobox({
87
89
  onOpenChange?.(next)
88
90
  }
89
91
 
92
+ const setNotify = (next: ((search: string) => void) | undefined) => {
93
+ notifyRef.current = next
94
+ }
95
+
96
+ const changeSearch = (next: string) => {
97
+ setSearch(next)
98
+ notifyRef.current?.(next)
99
+ }
100
+
90
101
  const pick = (next: string) => {
91
102
  if (value === undefined) setOwnValue(next)
92
103
  onValueChange?.(next)
93
- setSearch(next)
104
+ changeSearch(next)
94
105
  setOpen(false)
95
106
  }
96
107
 
@@ -122,7 +133,8 @@ export function Combobox({
122
133
  open: isOpen,
123
134
  active,
124
135
  search,
125
- setSearch,
136
+ setSearch: changeSearch,
137
+ setNotify,
126
138
  setOpen,
127
139
  pick,
128
140
  see,
@@ -143,15 +155,15 @@ export function Combobox({
143
155
  export const comboboxAnchor = tv({
144
156
  base: 'flex items-center gap-tight rounded-r8 px-3'
145
157
  + ' transition-state'
146
- + ' invalid-within:ring-2 invalid-within:ring-danger'
147
- + ' has-disabled:bg-disabled has-disabled:cursor-not-allowed'
158
+ + ' invalid-within:ring-2 invalid-within:ring-line-danger'
159
+ + ' has-disabled:bg-bg-neutral-disabled has-disabled:cursor-not-allowed'
148
160
  + ' has-focus-visible:outline-2 has-focus-visible:outline-offset-2'
149
- + ' has-focus-visible:outline-ring',
161
+ + ' has-focus-visible:outline-line-focus-ring',
150
162
 
151
163
  variants: {
152
164
  surface: {
153
165
  bare: 'ring-1 ring-inset ring-line-strong',
154
- well: 'bg-well',
166
+ well: 'bg-bg-well',
155
167
  },
156
168
  size: { sm: 'h-8', md: 'h-9', lg: 'h-10 [&_input]:text-t16' },
157
169
  },
@@ -179,19 +191,24 @@ export interface ComboboxInputProps extends Omit<InputHTMLAttributes<HTMLInputEl
179
191
  onValueChange?: (value: string) => void
180
192
  }
181
193
 
194
+ const comboboxInput = tv({
195
+ base: 'text-value text-fg placeholder:text-fg-placeholder'
196
+ + ' w-full min-w-0 bg-transparent outline-none'
197
+ + ' disabled:text-fg-disabled disabled:cursor-not-allowed',
198
+ })
199
+
182
200
  export function ComboboxInput({ value, onValueChange, className, ...rest }: ComboboxInputProps) {
183
201
  const ctx = useCombobox()
184
202
  const text = value ?? ctx.search
185
- const [seen, setSeen] = useState(ctx.search)
203
+ const { setNotify } = ctx
186
204
 
187
- if (seen !== ctx.search) {
188
- setSeen(ctx.search)
189
- if (ctx.search !== text) onValueChange?.(ctx.search)
190
- }
205
+ useEffect(() => {
206
+ setNotify(onValueChange)
207
+ return () => setNotify(undefined)
208
+ })
191
209
 
192
210
  const change = (next: string) => {
193
211
  ctx.setSearch(next)
194
- onValueChange?.(next)
195
212
  if (!ctx.open) ctx.setOpen(true)
196
213
  }
197
214
 
@@ -226,13 +243,18 @@ export function ComboboxInput({ value, onValueChange, className, ...rest }: Comb
226
243
  value={text}
227
244
  onChange={e => change(e.target.value)}
228
245
  onKeyDown={onKeyDown}
229
- className={tv({ base: 'text-value text-fg placeholder:text-fg-placeholder'
230
- + ' w-full min-w-0 bg-transparent outline-none'
231
- + ' disabled:text-fg-disabled disabled:cursor-not-allowed' })({ className })}
246
+ className={comboboxInput({ className })}
232
247
  />
233
248
  )
234
249
  }
235
250
 
251
+ const comboboxTrigger = tv({
252
+ base: 'text-fg-subtle grid size-5 shrink-0 place-items-center rounded-r8'
253
+ + ' transition-state press-shrink hover:text-fg'
254
+ + ' focus-visible:outline-2 focus-visible:outline-offset-2'
255
+ + ' focus-visible:outline-line-focus-ring',
256
+ })
257
+
236
258
  export function ComboboxTrigger({ label = '목록 열기', disabled, className }: {
237
259
  label?: string
238
260
  disabled?: boolean
@@ -248,18 +270,22 @@ export function ComboboxTrigger({ label = '목록 열기', disabled, className }
248
270
  disabled={disabled}
249
271
  data-state={ctx.open ? 'open' : 'closed'}
250
272
  onClick={() => ctx.setOpen(!ctx.open)}
251
- className={tv({ base: 'text-fg-subtle grid size-5 shrink-0 place-items-center rounded-r8'
252
- + ' transition-state press-shrink hover:text-fg'
253
- + ' focus-visible:outline-2 focus-visible:outline-offset-2'
254
- + ' focus-visible:outline-ring' })({ className })}
273
+ className={comboboxTrigger({ className })}
255
274
  >
256
- <ChevronDown
257
- className="size-4 transition-transform open:rotate-180 motion-reduce:transition-none"
258
- />
275
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4 transition-transform open:rotate-180 motion-reduce:transition-none">
276
+ <path d="m6 9 6 6 6-6" />
277
+ </svg>
259
278
  </button>
260
279
  )
261
280
  }
262
281
 
282
+ const comboboxCancel = tv({
283
+ base: 'text-fg-subtle grid size-5 shrink-0 place-items-center rounded-full'
284
+ + ' transition-state press-shrink hover:text-fg'
285
+ + ' focus-visible:outline-2 focus-visible:outline-offset-2'
286
+ + ' focus-visible:outline-line-focus-ring',
287
+ })
288
+
263
289
  export function ComboboxCancel({ label = '지우기', className }: { label?: string, className?: string }) {
264
290
  const ctx = useCombobox()
265
291
 
@@ -269,12 +295,12 @@ export function ComboboxCancel({ label = '지우기', className }: { label?: str
269
295
  tabIndex={-1}
270
296
  aria-label={label}
271
297
  onClick={() => ctx.setSearch('')}
272
- className={tv({ base: 'text-fg-subtle grid size-5 shrink-0 place-items-center rounded-full'
273
- + ' transition-state press-shrink hover:text-fg'
274
- + ' focus-visible:outline-2 focus-visible:outline-offset-2'
275
- + ' focus-visible:outline-ring' })({ className })}
298
+ className={comboboxCancel({ className })}
276
299
  >
277
- <X className="size-4" />
300
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
301
+ <path d="M18 6 6 18" />
302
+ <path d="m6 6 12 12" />
303
+ </svg>
278
304
  </button>
279
305
  )
280
306
  }
@@ -282,6 +308,12 @@ export function ComboboxCancel({ label = '지우기', className }: { label?: str
282
308
  export type ComboboxContentProps =
283
309
  Omit<ComponentProps<typeof PopoverPrimitive.Content>, 'align' | 'sideOffset' | 'onOpenAutoFocus'>
284
310
 
311
+ const comboboxContent = tv({
312
+ base: 'float text-fg rounded-r12 z-overlay'
313
+ + ' w-(--radix-popover-trigger-width)'
314
+ + ' open:animate-enter closed:animate-exit',
315
+ })
316
+
285
317
  export function ComboboxContent({ className, children, ...rest }: ComboboxContentProps) {
286
318
  const label = rest['aria-label']
287
319
 
@@ -292,9 +324,7 @@ export function ComboboxContent({ className, children, ...rest }: ComboboxConten
292
324
  align="start"
293
325
  sideOffset={4}
294
326
  onOpenAutoFocus={e => e.preventDefault()}
295
- className={tv({ base: 'bg-float text-fg rounded-float shadow-s3 z-overlay'
296
- + ' w-(--radix-popover-trigger-width) p-1'
297
- + ' open:animate-enter closed:animate-exit' })({ className })}
327
+ className={comboboxContent({ className })}
298
328
  >
299
329
  <ComboboxViewport label={label} className="max-h-72">
300
330
  {children}
@@ -304,6 +334,8 @@ export function ComboboxContent({ className, children, ...rest }: ComboboxConten
304
334
  )
305
335
  }
306
336
 
337
+ const comboboxViewport = tv({ base: 'flex flex-col gap-px overflow-y-auto p-1' })
338
+
307
339
  export function ComboboxViewport({ label, className, children }: {
308
340
  label?: string
309
341
  className?: string
@@ -316,7 +348,7 @@ export function ComboboxViewport({ label, className, children }: {
316
348
  id={`${ctx.id}-list`}
317
349
  role={ctx.count > 0 ? 'listbox' : undefined}
318
350
  aria-label={ctx.count > 0 ? label : undefined}
319
- className={tv({ base: 'flex flex-col gap-px overflow-y-auto' })({ className })}
351
+ className={comboboxViewport({ className })}
320
352
  >
321
353
  {children}
322
354
  </div>
@@ -331,6 +363,13 @@ export interface ComboboxItemProps {
331
363
  children?: ReactNode
332
364
  }
333
365
 
366
+ const comboboxItem = tv({
367
+ base: 'flex min-h-9 cursor-default items-center justify-between gap-stack'
368
+ + ' shrink-0 rounded-r8 px-2 py-1 transition-state'
369
+ + ' data-highlighted:bg-bg-neutral-hover data-highlighted:outline-none'
370
+ + ' data-disabled:text-fg-disabled data-disabled:pointer-events-none',
371
+ })
372
+
334
373
  export function ComboboxItem({ value, textValue, disabled = false, className, children }: ComboboxItemProps) {
335
374
  const ctx = useCombobox()
336
375
  const shown = ctx.ignoreFilter || contains(textValue ?? value, ctx.search)
@@ -349,10 +388,7 @@ export function ComboboxItem({ value, textValue, disabled = false, className, ch
349
388
  data-disabled={disabled ? '' : undefined}
350
389
  onPointerMove={() => !disabled && ctx.setActive(value)}
351
390
  onClick={() => !disabled && ctx.pick(value)}
352
- className={tv({ base: 'flex min-h-9 cursor-default items-center justify-between gap-stack'
353
- + ' shrink-0 rounded-r8 px-2 py-1 transition-state'
354
- + ' data-highlighted:bg-hover data-highlighted:outline-none'
355
- + ' data-disabled:text-fg-disabled data-disabled:pointer-events-none' })({ className })}
391
+ className={comboboxItem({ className })}
356
392
  >
357
393
  <span className="flex min-w-0 flex-col gap-0.5">
358
394
  {children}
@@ -360,36 +396,44 @@ export function ComboboxItem({ value, textValue, disabled = false, className, ch
360
396
 
361
397
  {ctx.value === value && (
362
398
  <span className="text-fg-muted flex shrink-0">
363
- <Check className="size-4" />
399
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
400
+ <path d="M20 6 9 17l-5-5" />
401
+ </svg>
364
402
  </span>
365
403
  )}
366
404
  </div>
367
405
  )
368
406
  }
369
407
 
408
+ const comboboxItemText = tv({ base: 'text-label text-fg' })
409
+
370
410
  export function ComboboxItemText({ className, children }: { className?: string, children?: ReactNode }) {
371
411
  return (
372
- <span className={tv({ base: 'text-label text-fg' })({ className })}>
412
+ <span className={comboboxItemText({ className })}>
373
413
  {children}
374
414
  </span>
375
415
  )
376
416
  }
377
417
 
418
+ const comboboxItemDescription = tv({ base: 'text-caption text-fg-subtle' })
419
+
378
420
  export function ComboboxItemDescription({ className, children }: { className?: string, children?: ReactNode }) {
379
421
  return (
380
- <span className={tv({ base: 'text-caption text-fg-subtle' })({ className })}>
422
+ <span className={comboboxItemDescription({ className })}>
381
423
  {children}
382
424
  </span>
383
425
  )
384
426
  }
385
427
 
428
+ const comboboxGroup = tv({ base: 'flex flex-col gap-px' })
429
+
386
430
  export function ComboboxGroup({ label, className, children }: {
387
431
  label?: string
388
432
  className?: string
389
433
  children?: ReactNode
390
434
  }) {
391
435
  return (
392
- <div role="group" className={tv({ base: 'flex flex-col gap-px' })({ className })}>
436
+ <div role="group" className={comboboxGroup({ className })}>
393
437
  {label && (
394
438
  <span className="text-caption text-fg-subtle px-2 pb-1 pt-2">
395
439
  {label}
@@ -400,19 +444,23 @@ export function ComboboxGroup({ label, className, children }: {
400
444
  )
401
445
  }
402
446
 
447
+ const comboboxEmpty = tv({ base: 'text-caption text-fg-subtle px-2 py-3 text-center' })
448
+
403
449
  export function ComboboxEmpty({ className, children }: { className?: string, children?: ReactNode }) {
404
450
  const ctx = useCombobox()
405
451
  if (ctx.count > 0) return null
406
452
 
407
453
  return (
408
- <div role="presentation" className={tv({ base: 'text-caption text-fg-subtle px-2 py-3 text-center' })({ className })}>
454
+ <div role="presentation" className={comboboxEmpty({ className })}>
409
455
  {children}
410
456
  </div>
411
457
  )
412
458
  }
413
459
 
460
+ const comboboxSeparator = tv({ base: 'bg-line -mx-1 my-1 h-px' })
461
+
414
462
  export function ComboboxSeparator({ className }: { className?: string }) {
415
- return <div className={tv({ base: 'bg-line -mx-1 my-1 h-px' })({ className })} />
463
+ return <div className={comboboxSeparator({ className })} />
416
464
  }
417
465
 
418
466
  export function contains(text: string, search: string) {
@@ -3,7 +3,7 @@
3
3
  ```
4
4
  받기 npx @pantoia/ui add command-palette
5
5
  층 derived
6
- 파일 command-palette/index.tsx · command-palette/AGENTS.md
6
+ 파일 command-palette/index.tsx, command-palette/AGENTS.md
7
7
  ```
8
8
 
9
9
  ## 부위
@@ -34,11 +34,8 @@
34
34
  | `placeholder` | `string` | 예 |
35
35
  | `emptyText` | `string` | 예 |
36
36
  | `onSelect` | `(value: string) => void` | |
37
+ | `className` | `string` | |
37
38
 
38
39
  ## 함께 받는 것
39
40
 
40
- `combobox` · `dialog` · `kbd`
41
-
42
- ## npm 의존
43
-
44
- `lucide-react`
41
+ `combobox`, `dialog`, `kbd`
@@ -1,6 +1,5 @@
1
1
  'use client'
2
2
 
3
- import { Search } from 'lucide-react'
4
3
  import { useState } from 'react'
5
4
 
6
5
  import {
@@ -31,6 +30,7 @@ export interface CommandPaletteProps {
31
30
  placeholder: string
32
31
  emptyText: string
33
32
  onSelect?: (value: string) => void
33
+ className?: string
34
34
  }
35
35
 
36
36
  export function CommandPalette({
@@ -42,6 +42,7 @@ export function CommandPalette({
42
42
  placeholder,
43
43
  emptyText,
44
44
  onSelect,
45
+ className,
45
46
  }: CommandPaletteProps) {
46
47
  const [own, setOwn] = useState(defaultOpen)
47
48
  const [search, setSearch] = useState('')
@@ -68,7 +69,7 @@ export function CommandPalette({
68
69
 
69
70
  return (
70
71
  <Dialog open={isOpen} onOpenChange={setOpen}>
71
- <DialogContent size="top">
72
+ <DialogContent size="top" className={className}>
72
73
  <DialogTitle className="sr-only">{title}</DialogTitle>
73
74
  <DialogDescription className="sr-only">
74
75
  {placeholder}
@@ -76,7 +77,10 @@ export function CommandPalette({
76
77
 
77
78
  <Combobox open onValueChange={pick}>
78
79
  <div className="border-line flex h-14 items-center gap-stack border-b px-4">
79
- <Search className="text-fg-subtle size-5 shrink-0" />
80
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="text-fg-subtle size-5 shrink-0">
81
+ <path d="m21 21-4.34-4.34" />
82
+ <circle cx="11" cy="11" r="8" />
83
+ </svg>
80
84
  <ComboboxInput
81
85
  value={search}
82
86
  onValueChange={setSearch}
package/dialog/AGENTS.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ```
4
4
  받기 npx @pantoia/ui add dialog
5
5
  층 ui
6
- 파일 dialog/index.tsx · dialog/AGENTS.md
6
+ 파일 dialog/index.tsx, dialog/AGENTS.md
7
7
  ```
8
8
 
9
9
  ## 부위
@@ -11,13 +11,13 @@
11
11
  | 이름 | props |
12
12
  |---|---|
13
13
  | `Dialog` | `DialogProps` |
14
- | `DialogTrigger` | — |
15
- | `DialogClose` | — |
14
+ | `DialogTrigger` | - |
15
+ | `DialogClose` | - |
16
16
  | `DialogContent` | `DialogContentProps` |
17
17
  | `DialogTitle` | `ComponentProps<typeof DialogPrimitive.Title>` |
18
18
  | `DialogDescription` | `ComponentProps<typeof DialogPrimitive.Description>` |
19
19
  | `DialogHeader` | `DialogHeaderProps` |
20
- | `DialogFooter` | — |
20
+ | `DialogFooter` | - |
21
21
 
22
22
  ### `DialogTrigger`
23
23
 
@@ -66,11 +66,11 @@
66
66
  | `className` | `string` | |
67
67
  | `children` | `ReactNode` | |
68
68
 
69
- ## 변형 — `dialog`
69
+ ## 변형 - `dialog`
70
70
 
71
71
  | 축 | 값 | 기본 |
72
72
  |---|---|---|
73
- | `size` | `md` · `full` · `top` | `md` |
73
+ | `size` | `md`, `full`, `top` | `md` |
74
74
 
75
75
  ## 함께 받는 것
76
76
 
package/dialog/index.tsx CHANGED
@@ -6,7 +6,7 @@ import type { ComponentProps, ReactNode } from 'react'
6
6
  import { tv } from '../tv'
7
7
 
8
8
  export const dialog = tv({
9
- base: 'bg-float text-fg z-overlay fixed flex flex-col gap-stack overflow-y-auto'
9
+ base: 'sheet text-fg z-overlay fixed flex flex-col gap-stack overflow-y-auto'
10
10
  + ' open:animate-fade-in closed:animate-fade-out focus-visible:outline-none',
11
11
 
12
12
  variants: {
@@ -52,7 +52,7 @@ export interface DialogContentProps extends ComponentProps<typeof DialogPrimitiv
52
52
  export function DialogContent({ size, className, children, ...rest }: DialogContentProps) {
53
53
  return (
54
54
  <DialogPrimitive.Portal>
55
- <DialogPrimitive.Overlay className="bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
55
+ <DialogPrimitive.Overlay className="bg-bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
56
56
 
57
57
  <DialogPrimitive.Content {...rest} className={dialog({ size, className })}>
58
58
  {children}
@@ -61,13 +61,17 @@ export function DialogContent({ size, className, children, ...rest }: DialogCont
61
61
  )
62
62
  }
63
63
 
64
+ const dialogTitle = tv({ base: 'text-section min-w-0' })
65
+
64
66
  export function DialogTitle({ className, ...rest }: ComponentProps<typeof DialogPrimitive.Title>) {
65
- return <DialogPrimitive.Title {...rest} className={tv({ base: 'text-section min-w-0' })({ className })} />
67
+ return <DialogPrimitive.Title {...rest} className={dialogTitle({ className })} />
66
68
  }
67
69
 
70
+ const dialogDescription = tv({ base: 'text-prose text-fg-muted' })
71
+
68
72
  export function DialogDescription({ className, ...rest }: ComponentProps<typeof DialogPrimitive.Description>) {
69
73
  return (
70
- <DialogPrimitive.Description {...rest} className={tv({ base: 'text-prose text-fg-muted' })({ className })} />
74
+ <DialogPrimitive.Description {...rest} className={dialogDescription({ className })} />
71
75
  )
72
76
  }
73
77
 
@@ -99,9 +103,11 @@ export function DialogHeader({ lead, trail, className, children }: DialogHeaderP
99
103
  )
100
104
  }
101
105
 
106
+ const dialogFooter = tv({ base: 'flex justify-end gap-2' })
107
+
102
108
  export function DialogFooter({ className, children }: { className?: string, children?: ReactNode }) {
103
109
  return (
104
- <div className={tv({ base: 'flex justify-end gap-2' })({ className })}>
110
+ <div className={dialogFooter({ className })}>
105
111
  {children}
106
112
  </div>
107
113
  )