@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.
- package/accordion/AGENTS.md +60 -0
- package/accordion/index.module.css +10 -0
- package/accordion/index.tsx +91 -0
- package/alert-dialog/AGENTS.md +78 -0
- package/alert-dialog/index.tsx +119 -0
- package/badge/AGENTS.md +35 -0
- package/badge/index.tsx +34 -0
- package/button/AGENTS.md +39 -0
- package/button/index.tsx +98 -0
- package/callout/AGENTS.md +42 -0
- package/callout/index.tsx +70 -0
- package/checkbox/AGENTS.md +54 -0
- package/checkbox/index.tsx +151 -0
- package/code-block/AGENTS.md +60 -0
- package/code-block/index.module.css +46 -0
- package/code-block/index.tsx +127 -0
- package/combobox/AGENTS.md +150 -0
- package/combobox/index.tsx +420 -0
- package/command-palette/AGENTS.md +44 -0
- package/command-palette/index.tsx +108 -0
- package/copy-button/AGENTS.md +43 -0
- package/copy-button/index.tsx +119 -0
- package/dialog/AGENTS.md +81 -0
- package/dialog/index.tsx +108 -0
- package/dist/index.js +189 -0
- package/drawer/AGENTS.md +66 -0
- package/drawer/index.module.css +17 -0
- package/drawer/index.tsx +198 -0
- package/dropdown-menu/AGENTS.md +133 -0
- package/dropdown-menu/index.tsx +223 -0
- package/field/AGENTS.md +96 -0
- package/field/index.tsx +149 -0
- package/input/AGENTS.md +35 -0
- package/input/index.tsx +36 -0
- package/kbd/AGENTS.md +17 -0
- package/kbd/index.tsx +13 -0
- package/label/AGENTS.md +21 -0
- package/label/index.tsx +13 -0
- package/number-field/AGENTS.md +53 -0
- package/number-field/index.tsx +227 -0
- package/package.json +73 -0
- package/popover/AGENTS.md +64 -0
- package/popover/index.tsx +66 -0
- package/press.css +37 -0
- package/radio-group/AGENTS.md +81 -0
- package/radio-group/index.tsx +139 -0
- package/registry.json +726 -0
- package/select/AGENTS.md +90 -0
- package/select/index.tsx +169 -0
- package/styles.css +347 -0
- package/switch/AGENTS.md +37 -0
- package/switch/index.tsx +42 -0
- package/table/AGENTS.md +82 -0
- package/table/index.tsx +120 -0
- package/tabs/AGENTS.md +60 -0
- package/tabs/index.module.css +14 -0
- package/tabs/index.tsx +105 -0
- package/test-utils.ts +10 -0
- package/textarea/AGENTS.md +35 -0
- package/textarea/index.tsx +37 -0
- package/toast/AGENTS.md +42 -0
- package/toast/index.tsx +71 -0
- package/toggle/AGENTS.md +37 -0
- package/toggle/index.tsx +36 -0
- package/tooltip/AGENTS.md +50 -0
- package/tooltip/index.tsx +57 -0
- package/tv.ts +23 -0
package/drawer/index.tsx
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Dialog } from 'radix-ui'
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
use,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type ComponentProps,
|
|
10
|
+
type PointerEvent as ReactPointerEvent,
|
|
11
|
+
type ReactNode,
|
|
12
|
+
type RefObject,
|
|
13
|
+
} from 'react'
|
|
14
|
+
|
|
15
|
+
import { tv } from '../tv'
|
|
16
|
+
|
|
17
|
+
import s from './index.module.css'
|
|
18
|
+
|
|
19
|
+
type Side = 'left' | 'right'
|
|
20
|
+
|
|
21
|
+
const DrawerContext = createContext<{ side: Side, close: () => void }>({
|
|
22
|
+
side: 'left',
|
|
23
|
+
close: () => {},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const SWIPE_THRESHOLD = 40
|
|
27
|
+
const SWIPE_VELOCITY = 0.3
|
|
28
|
+
const MIN_DRAG = 1
|
|
29
|
+
const IGNORE = 'button,a,input,select,textarea,label,[role="button"]'
|
|
30
|
+
|
|
31
|
+
const MOVEMENT = '--drawer-swipe-movement-x'
|
|
32
|
+
|
|
33
|
+
type Drag = { id: number, from: number, at: number, dx: number, v: number, on: boolean }
|
|
34
|
+
|
|
35
|
+
function useSwipeDismiss(el: RefObject<HTMLDivElement | null>, side: Side, close: () => void) {
|
|
36
|
+
const drag = useRef<Drag | null>(null)
|
|
37
|
+
|
|
38
|
+
const onPointerDown = (e: ReactPointerEvent<HTMLDivElement>) => {
|
|
39
|
+
if (e.button !== 0) return
|
|
40
|
+
if ((e.target as Element).closest(IGNORE)) return
|
|
41
|
+
|
|
42
|
+
drag.current = { id: e.pointerId, from: e.clientX, at: performance.now(), dx: 0, v: 0, on: false }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const onPointerMove = (e: ReactPointerEvent<HTMLDivElement>) => {
|
|
46
|
+
const d = drag.current
|
|
47
|
+
if (!d || d.id !== e.pointerId || !el.current) return
|
|
48
|
+
|
|
49
|
+
const raw = e.clientX - d.from
|
|
50
|
+
const dx = side === 'left' ? Math.min(0, raw) : Math.max(0, raw)
|
|
51
|
+
const now = performance.now()
|
|
52
|
+
const span = now - d.at
|
|
53
|
+
|
|
54
|
+
if (span > 0) d.v = (dx - d.dx) / span
|
|
55
|
+
d.dx = dx
|
|
56
|
+
d.at = now
|
|
57
|
+
|
|
58
|
+
if (!d.on && Math.abs(dx) >= MIN_DRAG) {
|
|
59
|
+
d.on = true
|
|
60
|
+
el.current.setAttribute('data-swiping', '')
|
|
61
|
+
el.current.setPointerCapture(e.pointerId)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (d.on) el.current.style.setProperty(MOVEMENT, `${dx}px`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const onPointerUp = (e: ReactPointerEvent<HTMLDivElement>) => {
|
|
68
|
+
const d = drag.current
|
|
69
|
+
if (!d || d.id !== e.pointerId) return
|
|
70
|
+
drag.current = null
|
|
71
|
+
|
|
72
|
+
if (!d.on) return
|
|
73
|
+
el.current?.removeAttribute('data-swiping')
|
|
74
|
+
|
|
75
|
+
const gone = side === 'left' ? -d.dx : d.dx
|
|
76
|
+
const speed = side === 'left' ? -d.v : d.v
|
|
77
|
+
|
|
78
|
+
if (gone >= SWIPE_THRESHOLD || speed > SWIPE_VELOCITY) close()
|
|
79
|
+
else el.current?.style.removeProperty(MOVEMENT)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { onPointerDown, onPointerMove, onPointerUp, onPointerCancel: onPointerUp }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DrawerProps {
|
|
86
|
+
side?: Side
|
|
87
|
+
open?: boolean
|
|
88
|
+
defaultOpen?: boolean
|
|
89
|
+
onOpenChange?: (open: boolean) => void
|
|
90
|
+
children: ReactNode
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function Drawer({ side = 'left', open, defaultOpen = false, onOpenChange, children }: DrawerProps) {
|
|
94
|
+
const [own, setOwn] = useState(defaultOpen)
|
|
95
|
+
const isOpen = open ?? own
|
|
96
|
+
|
|
97
|
+
const set = (next: boolean) => {
|
|
98
|
+
if (open === undefined) setOwn(next)
|
|
99
|
+
onOpenChange?.(next)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<DrawerContext value={{ side, close: () => set(false) }}>
|
|
104
|
+
<Dialog.Root open={isOpen} onOpenChange={set} modal>
|
|
105
|
+
{children}
|
|
106
|
+
</Dialog.Root>
|
|
107
|
+
</DrawerContext>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function DrawerTrigger({ children }: { children: ReactNode }) {
|
|
112
|
+
return <Dialog.Trigger asChild>{children}</Dialog.Trigger>
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function DrawerClose({ children }: { children: ReactNode }) {
|
|
116
|
+
return <Dialog.Close asChild>{children}</Dialog.Close>
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const drawerContent = tv({
|
|
120
|
+
base: 'bg-float text-fg z-overlay fixed inset-y-0'
|
|
121
|
+
+ ' flex w-full flex-col gap-stack overflow-y-auto p-4 sm:w-120'
|
|
122
|
+
+ ' focus-visible:outline-none',
|
|
123
|
+
variants: {
|
|
124
|
+
side: { left: 'left-0', right: 'right-0' },
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
export function DrawerContent({
|
|
129
|
+
className,
|
|
130
|
+
style,
|
|
131
|
+
children,
|
|
132
|
+
...rest
|
|
133
|
+
}: ComponentProps<typeof Dialog.Content>) {
|
|
134
|
+
const { side, close } = use(DrawerContext)
|
|
135
|
+
const el = useRef<HTMLDivElement>(null)
|
|
136
|
+
const swipe = useSwipeDismiss(el, side, close)
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<Dialog.Portal>
|
|
140
|
+
<Dialog.Overlay className="bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
|
|
141
|
+
|
|
142
|
+
<Dialog.Content
|
|
143
|
+
{...rest}
|
|
144
|
+
ref={el}
|
|
145
|
+
style={{ touchAction: 'pan-y', ...style }}
|
|
146
|
+
onPointerDown={swipe.onPointerDown}
|
|
147
|
+
onPointerMove={swipe.onPointerMove}
|
|
148
|
+
onPointerUp={swipe.onPointerUp}
|
|
149
|
+
onPointerCancel={swipe.onPointerCancel}
|
|
150
|
+
className={[
|
|
151
|
+
s.panel,
|
|
152
|
+
side === 'right' ? s['panel-right'] : '',
|
|
153
|
+
drawerContent({ side, className }),
|
|
154
|
+
].filter(Boolean).join(' ')}
|
|
155
|
+
>
|
|
156
|
+
{children}
|
|
157
|
+
</Dialog.Content>
|
|
158
|
+
</Dialog.Portal>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const drawerHeader = tv({
|
|
163
|
+
slots: {
|
|
164
|
+
root: 'flex items-start gap-stack',
|
|
165
|
+
lead: 'shrink-0',
|
|
166
|
+
body: 'flex min-w-0 grow flex-col gap-tight',
|
|
167
|
+
trail: 'shrink-0',
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
export interface DrawerHeaderProps {
|
|
172
|
+
lead?: ReactNode
|
|
173
|
+
trail?: ReactNode
|
|
174
|
+
className?: string
|
|
175
|
+
children?: ReactNode
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function DrawerHeader({ lead, trail, className, children }: DrawerHeaderProps) {
|
|
179
|
+
const ui = drawerHeader()
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className={ui.root({ className })}>
|
|
183
|
+
{lead && <div className={ui.lead()}>{lead}</div>}
|
|
184
|
+
<div className={ui.body()}>{children}</div>
|
|
185
|
+
{trail && <div className={ui.trail()}>{trail}</div>}
|
|
186
|
+
</div>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function DrawerTitle({ className, ...rest }: ComponentProps<typeof Dialog.Title>) {
|
|
191
|
+
return <Dialog.Title {...rest} className={tv({ base: 'text-section min-w-0' })({ className })} />
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function DrawerDescription({ className, ...rest }: ComponentProps<typeof Dialog.Description>) {
|
|
195
|
+
return (
|
|
196
|
+
<Dialog.Description {...rest} className={tv({ base: 'text-prose text-fg-muted' })({ className })} />
|
|
197
|
+
)
|
|
198
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# DropdownMenu
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add dropdown-menu
|
|
5
|
+
층 ui
|
|
6
|
+
파일 dropdown-menu/index.tsx · dropdown-menu/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `DropdownMenu` | `DropdownMenuProps` |
|
|
14
|
+
| `DropdownMenuTrigger` | — |
|
|
15
|
+
| `DropdownMenuContent` | `DropdownMenuContentProps` |
|
|
16
|
+
| `DropdownMenuGroup` | — |
|
|
17
|
+
| `DropdownMenuItem` | `DropdownMenuItemProps` |
|
|
18
|
+
| `DropdownMenuCheckboxItem` | — |
|
|
19
|
+
| `DropdownMenuRadioGroup` | — |
|
|
20
|
+
| `DropdownMenuRadioItem` | — |
|
|
21
|
+
| `DropdownMenuSeparator` | — |
|
|
22
|
+
| `DropdownMenuSub` | — |
|
|
23
|
+
| `DropdownMenuSubTrigger` | — |
|
|
24
|
+
| `DropdownMenuSubContent` | `ComponentProps<typeof MenuPrimitive.SubContent>` |
|
|
25
|
+
|
|
26
|
+
### `DropdownMenuTrigger`
|
|
27
|
+
|
|
28
|
+
| 이름 | 타입 | 필수 |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `children` | `ReactNode` | 예 |
|
|
31
|
+
|
|
32
|
+
### `DropdownMenuGroup`
|
|
33
|
+
|
|
34
|
+
| 이름 | 타입 | 필수 |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `label` | `string` | |
|
|
37
|
+
| `className` | `string` | |
|
|
38
|
+
| `children` | `ReactNode` | |
|
|
39
|
+
|
|
40
|
+
### `DropdownMenuCheckboxItem`
|
|
41
|
+
|
|
42
|
+
| 이름 | 타입 | 필수 |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `checked` | `boolean` | |
|
|
45
|
+
| `onCheckedChange` | `(checked: boolean) => void` | |
|
|
46
|
+
| `disabled` | `boolean` | |
|
|
47
|
+
| `className` | `string` | |
|
|
48
|
+
| `children` | `ReactNode` | |
|
|
49
|
+
|
|
50
|
+
### `DropdownMenuRadioGroup`
|
|
51
|
+
|
|
52
|
+
| 이름 | 타입 | 필수 |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `value` | `string` | |
|
|
55
|
+
| `onValueChange` | `(value: string) => void` | |
|
|
56
|
+
| `children` | `ReactNode` | |
|
|
57
|
+
|
|
58
|
+
### `DropdownMenuRadioItem`
|
|
59
|
+
|
|
60
|
+
| 이름 | 타입 | 필수 |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `value` | `string` | 예 |
|
|
63
|
+
| `disabled` | `boolean` | |
|
|
64
|
+
| `className` | `string` | |
|
|
65
|
+
| `children` | `ReactNode` | |
|
|
66
|
+
|
|
67
|
+
### `DropdownMenuSeparator`
|
|
68
|
+
|
|
69
|
+
| 이름 | 타입 | 필수 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `className` | `string` | |
|
|
72
|
+
|
|
73
|
+
### `DropdownMenuSub`
|
|
74
|
+
|
|
75
|
+
| 이름 | 타입 | 필수 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `open` | `boolean` | |
|
|
78
|
+
| `defaultOpen` | `boolean` | |
|
|
79
|
+
| `onOpenChange` | `(open: boolean) => void` | |
|
|
80
|
+
| `children` | `ReactNode` | |
|
|
81
|
+
|
|
82
|
+
### `DropdownMenuSubTrigger`
|
|
83
|
+
|
|
84
|
+
| 이름 | 타입 | 필수 |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `disabled` | `boolean` | |
|
|
87
|
+
| `lead` | `ReactNode` | |
|
|
88
|
+
| `className` | `string` | |
|
|
89
|
+
| `children` | `ReactNode` | |
|
|
90
|
+
|
|
91
|
+
## props
|
|
92
|
+
|
|
93
|
+
### `DropdownMenuProps`
|
|
94
|
+
|
|
95
|
+
| 이름 | 타입 | 필수 |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `open` | `boolean` | |
|
|
98
|
+
| `defaultOpen` | `boolean` | |
|
|
99
|
+
| `onOpenChange` | `(open: boolean) => void` | |
|
|
100
|
+
| `children` | `ReactNode` | |
|
|
101
|
+
|
|
102
|
+
### `DropdownMenuContentProps`
|
|
103
|
+
|
|
104
|
+
`Omit<ComponentProps<typeof MenuPrimitive.Content>, 'align'>` 를 확장한다.
|
|
105
|
+
|
|
106
|
+
| 이름 | 타입 | 필수 |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `align` | `'start' \| 'center' \| 'end'` | |
|
|
109
|
+
| `arrow` | `boolean` | |
|
|
110
|
+
|
|
111
|
+
### `DropdownMenuItemProps`
|
|
112
|
+
|
|
113
|
+
`Omit<ComponentProps<typeof MenuPrimitive.Item>, 'color'>` 를 확장한다.
|
|
114
|
+
|
|
115
|
+
| 이름 | 타입 | 필수 |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `color` | `'neutral' \| 'danger'` | |
|
|
118
|
+
| `lead` | `ReactNode` | |
|
|
119
|
+
| `trail` | `ReactNode` | |
|
|
120
|
+
|
|
121
|
+
## 변형 — `menuItem`
|
|
122
|
+
|
|
123
|
+
| 축 | 값 | 기본 |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `color` | `neutral` · `danger` | `neutral` |
|
|
126
|
+
|
|
127
|
+
## 함께 받는 것
|
|
128
|
+
|
|
129
|
+
`tv`
|
|
130
|
+
|
|
131
|
+
## npm 의존
|
|
132
|
+
|
|
133
|
+
`lucide-react` · `radix-ui`
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Check, ChevronRight } from 'lucide-react'
|
|
4
|
+
import { DropdownMenu as MenuPrimitive } from 'radix-ui'
|
|
5
|
+
import type { ComponentProps, ReactNode } from 'react'
|
|
6
|
+
|
|
7
|
+
import { tv } from '../tv'
|
|
8
|
+
|
|
9
|
+
export const menuItem = tv({
|
|
10
|
+
base: 'flex h-9 cursor-default items-center gap-2 rounded-r8 px-2 menu-gutter:ps-8'
|
|
11
|
+
+ ' [&_svg]:size-4 [&_svg]:shrink-0'
|
|
12
|
+
+ ' text-label transition-state'
|
|
13
|
+
+ ' data-highlighted:outline-none data-disabled:pointer-events-none',
|
|
14
|
+
|
|
15
|
+
variants: {
|
|
16
|
+
color: {
|
|
17
|
+
neutral: 'text-fg data-highlighted:bg-hover data-disabled:text-fg-disabled',
|
|
18
|
+
danger: 'text-danger data-highlighted:bg-danger-weak data-disabled:text-danger/40',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
defaultVariants: { color: 'neutral' },
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const PANEL = 'menu bg-float text-fg rounded-float shadow-s3 z-overlay'
|
|
26
|
+
+ ' flex min-w-44 flex-col gap-px p-1'
|
|
27
|
+
+ ' open:animate-enter closed:animate-exit'
|
|
28
|
+
+ ' focus-visible:outline-none'
|
|
29
|
+
|
|
30
|
+
export interface DropdownMenuProps {
|
|
31
|
+
open?: boolean
|
|
32
|
+
defaultOpen?: boolean
|
|
33
|
+
onOpenChange?: (open: boolean) => void
|
|
34
|
+
children?: ReactNode
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function DropdownMenu({ open, defaultOpen, onOpenChange, children }: DropdownMenuProps) {
|
|
38
|
+
return (
|
|
39
|
+
<MenuPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
|
|
40
|
+
{children}
|
|
41
|
+
</MenuPrimitive.Root>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function DropdownMenuTrigger({ children }: { children: ReactNode }) {
|
|
46
|
+
return <MenuPrimitive.Trigger asChild>{children}</MenuPrimitive.Trigger>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface DropdownMenuContentProps extends Omit<ComponentProps<typeof MenuPrimitive.Content>, 'align'> {
|
|
50
|
+
align?: 'start' | 'center' | 'end'
|
|
51
|
+
arrow?: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function DropdownMenuContent({
|
|
55
|
+
align = 'start',
|
|
56
|
+
arrow = true,
|
|
57
|
+
className,
|
|
58
|
+
children,
|
|
59
|
+
...rest
|
|
60
|
+
}: DropdownMenuContentProps) {
|
|
61
|
+
return (
|
|
62
|
+
<MenuPrimitive.Portal>
|
|
63
|
+
<MenuPrimitive.Content
|
|
64
|
+
{...rest}
|
|
65
|
+
align={align}
|
|
66
|
+
sideOffset={8}
|
|
67
|
+
arrowPadding={16}
|
|
68
|
+
className={tv({ base: PANEL })({ className })}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
|
|
72
|
+
{arrow && <MenuPrimitive.Arrow className="fill-float" width={12} height={6} />}
|
|
73
|
+
</MenuPrimitive.Content>
|
|
74
|
+
</MenuPrimitive.Portal>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function DropdownMenuGroup({ label, className, children }: {
|
|
79
|
+
label?: string
|
|
80
|
+
className?: string
|
|
81
|
+
children?: ReactNode
|
|
82
|
+
}) {
|
|
83
|
+
return (
|
|
84
|
+
<MenuPrimitive.Group className={tv({ base: 'flex flex-col gap-px' })({ className })}>
|
|
85
|
+
{label && (
|
|
86
|
+
<MenuPrimitive.Label className="text-caption text-fg-subtle px-2 pb-1 pt-2">
|
|
87
|
+
{label}
|
|
88
|
+
</MenuPrimitive.Label>
|
|
89
|
+
)}
|
|
90
|
+
{children}
|
|
91
|
+
</MenuPrimitive.Group>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface DropdownMenuItemProps extends Omit<ComponentProps<typeof MenuPrimitive.Item>, 'color'> {
|
|
96
|
+
color?: 'neutral' | 'danger'
|
|
97
|
+
lead?: ReactNode
|
|
98
|
+
trail?: ReactNode
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function DropdownMenuItem({ color, lead, trail, className, children, ...rest }: DropdownMenuItemProps) {
|
|
102
|
+
return (
|
|
103
|
+
<MenuPrimitive.Item {...rest} className={menuItem({ color, className })}>
|
|
104
|
+
{lead && (
|
|
105
|
+
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
106
|
+
{lead}
|
|
107
|
+
</span>
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
<span className="min-w-0 grow">{children}</span>
|
|
111
|
+
{trail}
|
|
112
|
+
</MenuPrimitive.Item>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function DropdownMenuCheckboxItem({ checked, onCheckedChange, disabled, className, children }: {
|
|
117
|
+
checked?: boolean
|
|
118
|
+
onCheckedChange?: (checked: boolean) => void
|
|
119
|
+
disabled?: boolean
|
|
120
|
+
className?: string
|
|
121
|
+
children?: ReactNode
|
|
122
|
+
}) {
|
|
123
|
+
return (
|
|
124
|
+
<MenuPrimitive.CheckboxItem
|
|
125
|
+
checked={checked}
|
|
126
|
+
onCheckedChange={onCheckedChange}
|
|
127
|
+
disabled={disabled}
|
|
128
|
+
className={menuItem({ className })}
|
|
129
|
+
onSelect={e => e.preventDefault()}
|
|
130
|
+
>
|
|
131
|
+
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
132
|
+
<MenuPrimitive.ItemIndicator>
|
|
133
|
+
<Check className="size-4" />
|
|
134
|
+
</MenuPrimitive.ItemIndicator>
|
|
135
|
+
</span>
|
|
136
|
+
|
|
137
|
+
<span className="min-w-0 grow">{children}</span>
|
|
138
|
+
</MenuPrimitive.CheckboxItem>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function DropdownMenuRadioGroup({ value, onValueChange, children }: {
|
|
143
|
+
value?: string
|
|
144
|
+
onValueChange?: (value: string) => void
|
|
145
|
+
children?: ReactNode
|
|
146
|
+
}) {
|
|
147
|
+
return (
|
|
148
|
+
<MenuPrimitive.RadioGroup value={value} onValueChange={onValueChange}>
|
|
149
|
+
{children}
|
|
150
|
+
</MenuPrimitive.RadioGroup>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function DropdownMenuRadioItem({ value, disabled, className, children }: {
|
|
155
|
+
value: string
|
|
156
|
+
disabled?: boolean
|
|
157
|
+
className?: string
|
|
158
|
+
children?: ReactNode
|
|
159
|
+
}) {
|
|
160
|
+
return (
|
|
161
|
+
<MenuPrimitive.RadioItem value={value} disabled={disabled} className={menuItem({ className })}>
|
|
162
|
+
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
163
|
+
<MenuPrimitive.ItemIndicator>
|
|
164
|
+
<span className="bg-control-on in-disabled:bg-control-on-disabled size-2 rounded-full" />
|
|
165
|
+
</MenuPrimitive.ItemIndicator>
|
|
166
|
+
</span>
|
|
167
|
+
|
|
168
|
+
<span className="min-w-0 grow">{children}</span>
|
|
169
|
+
</MenuPrimitive.RadioItem>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function DropdownMenuSeparator({ className }: { className?: string }) {
|
|
174
|
+
return <MenuPrimitive.Separator className={tv({ base: 'bg-line -mx-1 my-1 h-px' })({ className })} />
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function DropdownMenuSub({ open, defaultOpen, onOpenChange, children }: {
|
|
178
|
+
open?: boolean
|
|
179
|
+
defaultOpen?: boolean
|
|
180
|
+
onOpenChange?: (open: boolean) => void
|
|
181
|
+
children?: ReactNode
|
|
182
|
+
}) {
|
|
183
|
+
return (
|
|
184
|
+
<MenuPrimitive.Sub open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
|
|
185
|
+
{children}
|
|
186
|
+
</MenuPrimitive.Sub>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function DropdownMenuSubTrigger({ disabled, lead, className, children }: {
|
|
191
|
+
disabled?: boolean
|
|
192
|
+
lead?: ReactNode
|
|
193
|
+
className?: string
|
|
194
|
+
children?: ReactNode
|
|
195
|
+
}) {
|
|
196
|
+
return (
|
|
197
|
+
<MenuPrimitive.SubTrigger disabled={disabled} className={menuItem({ className })}>
|
|
198
|
+
{lead && (
|
|
199
|
+
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
200
|
+
{lead}
|
|
201
|
+
</span>
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
<span className="min-w-0 grow">{children}</span>
|
|
205
|
+
|
|
206
|
+
<ChevronRight className="text-fg-subtle size-4 shrink-0" />
|
|
207
|
+
</MenuPrimitive.SubTrigger>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function DropdownMenuSubContent({ className, children, ...rest }: ComponentProps<typeof MenuPrimitive.SubContent>) {
|
|
212
|
+
return (
|
|
213
|
+
<MenuPrimitive.Portal>
|
|
214
|
+
<MenuPrimitive.SubContent
|
|
215
|
+
{...rest}
|
|
216
|
+
sideOffset={4}
|
|
217
|
+
className={tv({ base: PANEL })({ className })}
|
|
218
|
+
>
|
|
219
|
+
{children}
|
|
220
|
+
</MenuPrimitive.SubContent>
|
|
221
|
+
</MenuPrimitive.Portal>
|
|
222
|
+
)
|
|
223
|
+
}
|
package/field/AGENTS.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Field
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add field
|
|
5
|
+
층 ui
|
|
6
|
+
파일 field/index.tsx · field/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `Field` | `FieldProps` |
|
|
14
|
+
| `FieldLabel` | — |
|
|
15
|
+
| `FieldHelp` | — |
|
|
16
|
+
| `FieldError` | `FieldErrorProps` |
|
|
17
|
+
| `FieldPending` | — |
|
|
18
|
+
| `FieldSuccess` | — |
|
|
19
|
+
|
|
20
|
+
### `FieldLabel`
|
|
21
|
+
|
|
22
|
+
| 이름 | 타입 | 필수 |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `className` | `string` | |
|
|
25
|
+
| `children` | `ReactNode` | |
|
|
26
|
+
|
|
27
|
+
### `FieldHelp`
|
|
28
|
+
|
|
29
|
+
| 이름 | 타입 | 필수 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `className` | `string` | |
|
|
32
|
+
| `children` | `ReactNode` | |
|
|
33
|
+
|
|
34
|
+
### `FieldPending`
|
|
35
|
+
|
|
36
|
+
| 이름 | 타입 | 필수 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `className` | `string` | |
|
|
39
|
+
| `children` | `ReactNode` | |
|
|
40
|
+
|
|
41
|
+
### `FieldSuccess`
|
|
42
|
+
|
|
43
|
+
| 이름 | 타입 | 필수 |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `className` | `string` | |
|
|
46
|
+
| `children` | `ReactNode` | |
|
|
47
|
+
|
|
48
|
+
## props
|
|
49
|
+
|
|
50
|
+
### `FieldContext`
|
|
51
|
+
|
|
52
|
+
| 이름 | 타입 | 필수 |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `id` | `string` | 예 |
|
|
55
|
+
| `labelId` | `string` | 예 |
|
|
56
|
+
| `messageId` | `string` | 예 |
|
|
57
|
+
|
|
58
|
+
### `FieldControl`
|
|
59
|
+
|
|
60
|
+
| 이름 | 타입 | 필수 |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `id` | `string` | 예 |
|
|
63
|
+
| `aria-labelledby` | `string` | 예 |
|
|
64
|
+
| `aria-invalid` | `'true' \| undefined` | 예 |
|
|
65
|
+
| `aria-describedby` | `string` | 예 |
|
|
66
|
+
|
|
67
|
+
### `FieldProps`
|
|
68
|
+
|
|
69
|
+
| 이름 | 타입 | 필수 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `invalid` | `boolean` | |
|
|
72
|
+
| `layout` | `'stack' \| 'inline' \| 'split'` | |
|
|
73
|
+
| `className` | `string` | |
|
|
74
|
+
| `children` | `ReactNode \| ((args: { control: FieldControl }) => ReactNode)` | |
|
|
75
|
+
|
|
76
|
+
### `FieldErrorProps`
|
|
77
|
+
|
|
78
|
+
| 이름 | 타입 | 필수 |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `items` | `string \| string[]` | |
|
|
81
|
+
| `className` | `string` | |
|
|
82
|
+
| `children` | `ReactNode` | |
|
|
83
|
+
|
|
84
|
+
## 변형 — `field`
|
|
85
|
+
|
|
86
|
+
| 축 | 값 | 기본 |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `layout` | `stack` · `inline` · `split` | `stack` |
|
|
89
|
+
|
|
90
|
+
## 함께 받는 것
|
|
91
|
+
|
|
92
|
+
`label` · `tv`
|
|
93
|
+
|
|
94
|
+
## npm 의존
|
|
95
|
+
|
|
96
|
+
`lucide-react`
|