@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.
- package/accordion/AGENTS.md +5 -5
- package/accordion/index.tsx +21 -11
- package/alert-dialog/AGENTS.md +7 -7
- package/alert-dialog/index.tsx +20 -10
- package/badge/AGENTS.md +5 -5
- package/badge/index.tsx +8 -8
- package/button/AGENTS.md +7 -7
- package/button/index.tsx +34 -34
- package/callout/AGENTS.md +4 -9
- package/callout/index.tsx +42 -22
- package/checkbox/AGENTS.md +5 -5
- package/checkbox/index.tsx +19 -16
- package/combobox/AGENTS.md +13 -13
- package/combobox/index.tsx +90 -42
- package/command-palette/AGENTS.md +3 -6
- package/command-palette/index.tsx +7 -3
- package/dialog/AGENTS.md +6 -6
- package/dialog/index.tsx +11 -5
- package/dist/index.js +200 -43
- package/drawer/AGENTS.md +5 -5
- package/drawer/index.tsx +8 -4
- package/dropdown-menu/AGENTS.md +12 -12
- package/dropdown-menu/index.tsx +27 -12
- package/field/AGENTS.md +8 -12
- package/field/index.tsx +39 -13
- package/input/AGENTS.md +4 -4
- package/input/index.tsx +5 -5
- package/kbd/AGENTS.md +1 -1
- package/kbd/index.tsx +6 -2
- package/label/AGENTS.md +1 -1
- package/label/index.tsx +3 -1
- package/number-field/AGENTS.md +6 -10
- package/number-field/index.tsx +26 -16
- package/package.json +13 -15
- package/palette.css +114 -0
- package/popover/AGENTS.md +4 -4
- package/popover/index.tsx +14 -4
- package/radio-group/AGENTS.md +8 -8
- package/radio-group/index.tsx +20 -16
- package/registry.json +9 -71
- package/select/AGENTS.md +10 -10
- package/select/index.tsx +46 -21
- package/switch/AGENTS.md +4 -4
- package/switch/index.tsx +13 -13
- package/table/AGENTS.md +9 -9
- package/table/index.tsx +16 -6
- package/tabs/AGENTS.md +5 -4
- package/tabs/index.module.css +2 -0
- package/tabs/index.tsx +22 -11
- package/tailwind.css +3 -0
- package/textarea/AGENTS.md +4 -4
- package/textarea/index.tsx +5 -5
- package/toast/AGENTS.md +5 -13
- package/toast/index.tsx +27 -33
- package/toggle/AGENTS.md +4 -4
- package/toggle/index.tsx +8 -7
- package/tokens.css +272 -0
- package/tooltip/AGENTS.md +2 -2
- package/tooltip/index.tsx +14 -4
- package/tv.ts +0 -1
- package/utilities.css +119 -0
- package/code-block/AGENTS.md +0 -60
- package/code-block/index.module.css +0 -46
- package/code-block/index.tsx +0 -127
- package/copy-button/AGENTS.md +0 -43
- package/copy-button/index.tsx +0 -119
- package/press.css +0 -37
- package/styles.css +0 -347
package/checkbox/index.tsx
CHANGED
|
@@ -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-
|
|
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-
|
|
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
|
-
|
|
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: '
|
|
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={
|
|
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?: '
|
|
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-
|
|
147
|
-
|
|
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
|
)
|
package/combobox/AGENTS.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
```
|
|
4
4
|
받기 npx @pantoia/ui add combobox
|
|
5
5
|
층 ui
|
|
6
|
-
파일 combobox/index.tsx
|
|
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
|
-
## 변형
|
|
137
|
+
## 변형 - `comboboxAnchor`
|
|
138
138
|
|
|
139
139
|
| 축 | 값 | 기본 |
|
|
140
140
|
|---|---|---|
|
|
141
|
-
| `surface` | `bare
|
|
142
|
-
| `size` | `sm
|
|
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
|
-
`
|
|
150
|
+
`radix-ui`
|
package/combobox/index.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
|
203
|
+
const { setNotify } = ctx
|
|
186
204
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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={
|
|
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={
|
|
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
|
-
<
|
|
257
|
-
|
|
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={
|
|
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
|
-
<
|
|
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={
|
|
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={
|
|
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={
|
|
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
|
-
<
|
|
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={
|
|
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={
|
|
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={
|
|
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={
|
|
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={
|
|
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
|
|
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
|
|
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
|
-
<
|
|
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
|
|
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
|
-
## 변형
|
|
69
|
+
## 변형 - `dialog`
|
|
70
70
|
|
|
71
71
|
| 축 | 값 | 기본 |
|
|
72
72
|
|---|---|---|
|
|
73
|
-
| `size` | `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: '
|
|
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={
|
|
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={
|
|
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={
|
|
110
|
+
<div className={dialogFooter({ className })}>
|
|
105
111
|
{children}
|
|
106
112
|
</div>
|
|
107
113
|
)
|