@pantoia/ui 0.3.0 → 0.4.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/index.module.css +4 -4
- package/accordion/index.tsx +21 -51
- package/alert-dialog/index.tsx +36 -42
- package/badge/index.tsx +1 -1
- package/button/index.tsx +31 -30
- package/callout/index.tsx +4 -4
- package/checkbox/index.tsx +20 -85
- package/combobox/index.tsx +122 -316
- package/command-palette/index.tsx +51 -32
- package/dialog/index.tsx +28 -24
- package/dist/index.js +5 -5
- package/drawer/index.module.css +2 -2
- package/drawer/index.tsx +52 -130
- package/dropdown-menu/index.tsx +87 -95
- package/field/index.tsx +27 -86
- package/input/index.tsx +7 -3
- package/kbd/index.tsx +2 -2
- package/label/index.tsx +6 -8
- package/number-field/index.tsx +122 -173
- package/package.json +2 -6
- package/popover/index.tsx +61 -32
- package/radio-group/index.tsx +35 -38
- package/registry.json +64 -154
- package/select/index.tsx +141 -70
- package/switch/index.tsx +13 -6
- package/table/index.tsx +1 -2
- package/tabs/index.module.css +3 -3
- package/tabs/index.tsx +22 -76
- package/textarea/index.tsx +19 -7
- package/toast/index.module.css +187 -0
- package/toast/index.tsx +141 -46
- package/toggle/index.tsx +4 -3
- package/tokens.css +7 -1
- package/tooltip/index.tsx +61 -30
- package/utilities.css +11 -6
- package/accordion/AGENTS.md +0 -60
- package/alert-dialog/AGENTS.md +0 -78
- package/badge/AGENTS.md +0 -35
- package/button/AGENTS.md +0 -39
- package/callout/AGENTS.md +0 -37
- package/checkbox/AGENTS.md +0 -54
- package/combobox/AGENTS.md +0 -150
- package/command-palette/AGENTS.md +0 -41
- package/dialog/AGENTS.md +0 -81
- package/drawer/AGENTS.md +0 -66
- package/dropdown-menu/AGENTS.md +0 -133
- package/field/AGENTS.md +0 -92
- package/input/AGENTS.md +0 -35
- package/kbd/AGENTS.md +0 -17
- package/label/AGENTS.md +0 -21
- package/number-field/AGENTS.md +0 -49
- package/popover/AGENTS.md +0 -64
- package/radio-group/AGENTS.md +0 -81
- package/select/AGENTS.md +0 -90
- package/switch/AGENTS.md +0 -37
- package/table/AGENTS.md +0 -82
- package/tabs/AGENTS.md +0 -61
- package/test-utils.ts +0 -10
- package/textarea/AGENTS.md +0 -35
- package/toast/AGENTS.md +0 -34
- package/toggle/AGENTS.md +0 -37
- package/tooltip/AGENTS.md +0 -50
|
@@ -4,13 +4,14 @@ import { useState } from 'react'
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
Combobox,
|
|
7
|
+
ComboboxCollection,
|
|
7
8
|
ComboboxEmpty,
|
|
9
|
+
ComboboxGroup,
|
|
8
10
|
ComboboxInput,
|
|
9
11
|
ComboboxItem,
|
|
10
12
|
ComboboxItemDescription,
|
|
11
13
|
ComboboxItemText,
|
|
12
14
|
ComboboxViewport,
|
|
13
|
-
contains,
|
|
14
15
|
} from '../combobox'
|
|
15
16
|
import { Dialog, DialogContent, DialogDescription, DialogTitle } from '../dialog'
|
|
16
17
|
import { Kbd } from '../kbd'
|
|
@@ -18,52 +19,68 @@ import { Kbd } from '../kbd'
|
|
|
18
19
|
export interface CommandPaletteItem {
|
|
19
20
|
value: string
|
|
20
21
|
label: string
|
|
21
|
-
|
|
22
|
+
description?: string
|
|
23
|
+
textValue?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CommandPaletteGroup {
|
|
27
|
+
label: string
|
|
28
|
+
items: CommandPaletteItem[]
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
export interface CommandPaletteProps {
|
|
25
32
|
open?: boolean
|
|
26
33
|
defaultOpen?: boolean
|
|
27
34
|
onOpenChange?: (open: boolean) => void
|
|
28
|
-
items: CommandPaletteItem[]
|
|
35
|
+
items: CommandPaletteItem[] | CommandPaletteGroup[]
|
|
29
36
|
title: string
|
|
37
|
+
description?: string
|
|
30
38
|
placeholder: string
|
|
31
39
|
emptyText: string
|
|
32
40
|
onSelect?: (value: string) => void
|
|
33
41
|
className?: string
|
|
34
42
|
}
|
|
35
43
|
|
|
44
|
+
const searchText = (item: CommandPaletteItem) => item.textValue ?? item.label
|
|
45
|
+
|
|
46
|
+
const isGrouped = (items: CommandPaletteProps['items']): items is CommandPaletteGroup[] =>
|
|
47
|
+
items.length > 0 && 'items' in items[0]!
|
|
48
|
+
|
|
49
|
+
function renderItem(item: CommandPaletteItem) {
|
|
50
|
+
return (
|
|
51
|
+
<ComboboxItem key={item.value} value={item}>
|
|
52
|
+
<ComboboxItemText>{item.label}</ComboboxItemText>
|
|
53
|
+
{item.description && (
|
|
54
|
+
<ComboboxItemDescription>{item.description}</ComboboxItemDescription>
|
|
55
|
+
)}
|
|
56
|
+
</ComboboxItem>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
36
60
|
export function CommandPalette({
|
|
37
61
|
open,
|
|
38
62
|
defaultOpen = false,
|
|
39
63
|
onOpenChange,
|
|
40
64
|
items,
|
|
41
65
|
title,
|
|
66
|
+
description,
|
|
42
67
|
placeholder,
|
|
43
68
|
emptyText,
|
|
44
69
|
onSelect,
|
|
45
70
|
className,
|
|
46
71
|
}: CommandPaletteProps) {
|
|
47
72
|
const [own, setOwn] = useState(defaultOpen)
|
|
48
|
-
const [search, setSearch] = useState('')
|
|
49
|
-
const [seen, setSeen] = useState(defaultOpen)
|
|
50
73
|
|
|
51
74
|
const isOpen = open ?? own
|
|
52
75
|
|
|
53
|
-
if (seen !== isOpen) {
|
|
54
|
-
setSeen(isOpen)
|
|
55
|
-
if (!isOpen) setSearch('')
|
|
56
|
-
}
|
|
57
|
-
|
|
58
76
|
const setOpen = (next: boolean) => {
|
|
59
77
|
if (open === undefined) setOwn(next)
|
|
60
78
|
onOpenChange?.(next)
|
|
61
79
|
}
|
|
62
80
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
onSelect?.(value)
|
|
81
|
+
const pick = (item: CommandPaletteItem | null) => {
|
|
82
|
+
if (!item) return
|
|
83
|
+
onSelect?.(item.value)
|
|
67
84
|
setOpen(false)
|
|
68
85
|
}
|
|
69
86
|
|
|
@@ -71,19 +88,22 @@ export function CommandPalette({
|
|
|
71
88
|
<Dialog open={isOpen} onOpenChange={setOpen}>
|
|
72
89
|
<DialogContent size="top" className={className}>
|
|
73
90
|
<DialogTitle className="sr-only">{title}</DialogTitle>
|
|
74
|
-
<DialogDescription className="sr-only">
|
|
75
|
-
{placeholder}
|
|
76
|
-
</DialogDescription>
|
|
91
|
+
{description && <DialogDescription className="sr-only">{description}</DialogDescription>}
|
|
77
92
|
|
|
78
|
-
<Combobox
|
|
93
|
+
<Combobox<CommandPaletteItem>
|
|
94
|
+
inline
|
|
95
|
+
open={isOpen}
|
|
96
|
+
items={items}
|
|
97
|
+
itemToStringLabel={searchText}
|
|
98
|
+
value={null}
|
|
99
|
+
onValueChange={pick}
|
|
100
|
+
>
|
|
79
101
|
<div className="border-line flex h-14 items-center gap-stack border-b px-4">
|
|
80
102
|
<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
103
|
<path d="m21 21-4.34-4.34" />
|
|
82
104
|
<circle cx="11" cy="11" r="8" />
|
|
83
105
|
</svg>
|
|
84
106
|
<ComboboxInput
|
|
85
|
-
value={search}
|
|
86
|
-
onValueChange={setSearch}
|
|
87
107
|
placeholder={placeholder}
|
|
88
108
|
className="text-t16"
|
|
89
109
|
autoFocus
|
|
@@ -91,19 +111,18 @@ export function CommandPalette({
|
|
|
91
111
|
<Kbd>Esc</Kbd>
|
|
92
112
|
</div>
|
|
93
113
|
|
|
114
|
+
<ComboboxEmpty className="m-2 py-6">
|
|
115
|
+
{emptyText}
|
|
116
|
+
</ComboboxEmpty>
|
|
117
|
+
|
|
94
118
|
<ComboboxViewport className="max-h-80 gap-1 p-2">
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
{item.group && (
|
|
103
|
-
<ComboboxItemDescription>{item.group}</ComboboxItemDescription>
|
|
104
|
-
)}
|
|
105
|
-
</ComboboxItem>
|
|
106
|
-
))}
|
|
119
|
+
{isGrouped(items)
|
|
120
|
+
? (group: CommandPaletteGroup) => (
|
|
121
|
+
<ComboboxGroup key={group.label} label={group.label} items={group.items}>
|
|
122
|
+
<ComboboxCollection>{renderItem}</ComboboxCollection>
|
|
123
|
+
</ComboboxGroup>
|
|
124
|
+
)
|
|
125
|
+
: renderItem}
|
|
107
126
|
</ComboboxViewport>
|
|
108
127
|
</Combobox>
|
|
109
128
|
</DialogContent>
|
package/dialog/index.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { Dialog as DialogPrimitive } from '
|
|
3
|
+
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
|
|
4
4
|
import type { ComponentProps, ReactNode } from 'react'
|
|
5
5
|
|
|
6
6
|
import { tv } from '../tv'
|
|
@@ -22,54 +22,58 @@ export const dialog = tv({
|
|
|
22
22
|
defaultVariants: { size: 'md' },
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
-
export
|
|
26
|
-
open?: boolean
|
|
27
|
-
defaultOpen?: boolean
|
|
28
|
-
onOpenChange?: (open: boolean) => void
|
|
29
|
-
children?: ReactNode
|
|
30
|
-
}
|
|
25
|
+
export type DialogProps = ComponentProps<typeof DialogPrimitive.Root>
|
|
31
26
|
|
|
32
|
-
export function Dialog(
|
|
33
|
-
return
|
|
34
|
-
<DialogPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
|
|
35
|
-
{children}
|
|
36
|
-
</DialogPrimitive.Root>
|
|
37
|
-
)
|
|
27
|
+
export function Dialog(props: DialogProps) {
|
|
28
|
+
return <DialogPrimitive.Root {...props} />
|
|
38
29
|
}
|
|
39
30
|
|
|
40
|
-
export
|
|
41
|
-
|
|
31
|
+
export type DialogTriggerProps = ComponentProps<typeof DialogPrimitive.Trigger>
|
|
32
|
+
|
|
33
|
+
export function DialogTrigger(props: DialogTriggerProps) {
|
|
34
|
+
return <DialogPrimitive.Trigger {...props} />
|
|
42
35
|
}
|
|
43
36
|
|
|
44
|
-
export
|
|
45
|
-
|
|
37
|
+
export type DialogCloseProps = ComponentProps<typeof DialogPrimitive.Close>
|
|
38
|
+
|
|
39
|
+
export function DialogClose(props: DialogCloseProps) {
|
|
40
|
+
return <DialogPrimitive.Close {...props} />
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
export
|
|
43
|
+
export type DialogContentProps = Omit<ComponentProps<typeof DialogPrimitive.Popup>, 'className'> & {
|
|
49
44
|
size?: 'md' | 'full' | 'top'
|
|
45
|
+
className?: string
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
export function DialogContent({ size, className, children, ...rest }: DialogContentProps) {
|
|
53
49
|
return (
|
|
54
50
|
<DialogPrimitive.Portal>
|
|
55
|
-
<DialogPrimitive.
|
|
51
|
+
<DialogPrimitive.Backdrop className="bg-bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
|
|
56
52
|
|
|
57
|
-
<DialogPrimitive.
|
|
53
|
+
<DialogPrimitive.Popup {...rest} className={dialog({ size, className })}>
|
|
58
54
|
{children}
|
|
59
|
-
</DialogPrimitive.
|
|
55
|
+
</DialogPrimitive.Popup>
|
|
60
56
|
</DialogPrimitive.Portal>
|
|
61
57
|
)
|
|
62
58
|
}
|
|
63
59
|
|
|
64
60
|
const dialogTitle = tv({ base: 'text-section min-w-0' })
|
|
65
61
|
|
|
66
|
-
export
|
|
62
|
+
export type DialogTitleProps = Omit<ComponentProps<typeof DialogPrimitive.Title>, 'className'> & {
|
|
63
|
+
className?: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function DialogTitle({ className, ...rest }: DialogTitleProps) {
|
|
67
67
|
return <DialogPrimitive.Title {...rest} className={dialogTitle({ className })} />
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
const dialogDescription = tv({ base: 'text-prose text-fg-muted' })
|
|
71
71
|
|
|
72
|
-
export
|
|
72
|
+
export type DialogDescriptionProps = Omit<ComponentProps<typeof DialogPrimitive.Description>, 'className'> & {
|
|
73
|
+
className?: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function DialogDescription({ className, ...rest }: DialogDescriptionProps) {
|
|
73
77
|
return (
|
|
74
78
|
<DialogPrimitive.Description {...rest} className={dialogDescription({ className })} />
|
|
75
79
|
)
|
|
@@ -103,7 +107,7 @@ export function DialogHeader({ lead, trail, className, children }: DialogHeaderP
|
|
|
103
107
|
)
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
const dialogFooter = tv({ base: 'flex justify-end gap-2' })
|
|
110
|
+
const dialogFooter = tv({ base: 'mt-auto flex justify-end gap-2' })
|
|
107
111
|
|
|
108
112
|
export function DialogFooter({ className, children }: { className?: string, children?: ReactNode }) {
|
|
109
113
|
return (
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ const USAGE = `npx @pantoia/ui <명령어>
|
|
|
39
39
|
init [--path <경로>] [--css <경로>] 컴포넌트를 저장할 위치와 전역 CSS 파일을 정해요
|
|
40
40
|
add <이름...> [--force] 컴포넌트를 추가해요
|
|
41
41
|
list 추가할 수 있는 컴포넌트를 확인해요
|
|
42
|
-
diff [이름] 추가한
|
|
42
|
+
diff [이름] 추가한 파일을 설치된 패키지의 파일과 비교해요`;
|
|
43
43
|
function fail(message) {
|
|
44
44
|
console.error(message);
|
|
45
45
|
exit(1);
|
|
@@ -139,7 +139,7 @@ async function installPackages() {
|
|
|
139
139
|
let fonts = FONTS.every(spec => have.has(packageName(spec)));
|
|
140
140
|
if (!fonts) {
|
|
141
141
|
console.log('');
|
|
142
|
-
fonts = await confirm(`글꼴
|
|
142
|
+
fonts = await confirm(`글꼴 패키지 ${FONTS.map(packageName).join(', ')}를 설치할까요?`, true);
|
|
143
143
|
if (fonts)
|
|
144
144
|
specs.push(...FONTS);
|
|
145
145
|
}
|
|
@@ -199,7 +199,7 @@ async function importStyles(css, head, tail) {
|
|
|
199
199
|
}
|
|
200
200
|
async function commandInit(args) {
|
|
201
201
|
if (readConfig())
|
|
202
|
-
fail(`${CONFIG}이 이미
|
|
202
|
+
fail(`${CONFIG}이 이미 있어요. 컴포넌트는 npx @pantoia/ui add로 추가하세요.`);
|
|
203
203
|
const detected = detect();
|
|
204
204
|
const path = option(args, '--path') ?? await ask('컴포넌트를 저장할 위치를 입력하세요.', detected.path);
|
|
205
205
|
const css = option(args, '--css') ?? await ask('전역 CSS 파일 위치를 입력하세요.', detected.css);
|
|
@@ -270,7 +270,7 @@ async function commandAdd(args) {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
function commandList() {
|
|
273
|
-
console.log(`${'이름'.padEnd(16)}${'종류'.padEnd(7)}
|
|
273
|
+
console.log(`${'이름'.padEnd(16)}${'종류'.padEnd(7)}함께 추가되는 컴포넌트`);
|
|
274
274
|
for (const item of registry()) {
|
|
275
275
|
const layer = item.meta?.layer ?? (item.type === 'registry:lib' ? 'lib' : '');
|
|
276
276
|
const together = (item.registryDependencies ?? []).join(', ');
|
|
@@ -323,7 +323,7 @@ async function commandDiff(args) {
|
|
|
323
323
|
if (mine === theirs)
|
|
324
324
|
continue;
|
|
325
325
|
if (changed === 0)
|
|
326
|
-
console.log('-
|
|
326
|
+
console.log('- 줄은 내 파일, + 줄은 설치된 패키지의 파일이에요.');
|
|
327
327
|
changed++;
|
|
328
328
|
console.log(`\n${join(config.path, file.target ?? file.path)}`);
|
|
329
329
|
for (const line of difference(mine.split('\n'), theirs.split('\n')))
|
package/drawer/index.module.css
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
transition: transform var(--dur-enter) var(--ease-enter);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
.panel[data-
|
|
11
|
-
.panel[data-
|
|
10
|
+
.panel[data-open] { animation: slide-in var(--dur-enter) var(--ease-enter); }
|
|
11
|
+
.panel[data-closed] { animation: slide-out var(--dur-exit) var(--ease-exit); }
|
|
12
12
|
.panel[data-swiping] { transition-duration: 0ms; }
|
|
13
13
|
.panel-right { --drawer-slide: 100%; }
|
|
14
14
|
|
package/drawer/index.tsx
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
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'
|
|
3
|
+
import { Drawer as DrawerPrimitive } from '@base-ui/react/drawer'
|
|
4
|
+
import { createContext, use, type ComponentProps, type ReactNode } from 'react'
|
|
14
5
|
|
|
15
6
|
import { tv } from '../tv'
|
|
16
7
|
|
|
@@ -18,102 +9,30 @@ import s from './index.module.css'
|
|
|
18
9
|
|
|
19
10
|
type Side = 'left' | 'right'
|
|
20
11
|
|
|
21
|
-
const
|
|
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
|
-
}
|
|
12
|
+
const DrawerSide = createContext<Side>('left')
|
|
81
13
|
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface DrawerProps {
|
|
14
|
+
export type DrawerProps = Omit<DrawerPrimitive.Root.Props, 'swipeDirection'> & {
|
|
86
15
|
side?: Side
|
|
87
|
-
open?: boolean
|
|
88
|
-
defaultOpen?: boolean
|
|
89
|
-
onOpenChange?: (open: boolean) => void
|
|
90
|
-
children: ReactNode
|
|
91
16
|
}
|
|
92
17
|
|
|
93
|
-
export function Drawer({ side = 'left',
|
|
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
|
-
|
|
18
|
+
export function Drawer({ side = 'left', ...rest }: DrawerProps) {
|
|
102
19
|
return (
|
|
103
|
-
<
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
</Dialog.Root>
|
|
107
|
-
</DrawerContext>
|
|
20
|
+
<DrawerSide value={side}>
|
|
21
|
+
<DrawerPrimitive.Root {...rest} swipeDirection={side} />
|
|
22
|
+
</DrawerSide>
|
|
108
23
|
)
|
|
109
24
|
}
|
|
110
25
|
|
|
111
|
-
export
|
|
112
|
-
|
|
26
|
+
export type DrawerTriggerProps = ComponentProps<typeof DrawerPrimitive.Trigger>
|
|
27
|
+
|
|
28
|
+
export function DrawerTrigger(props: DrawerTriggerProps) {
|
|
29
|
+
return <DrawerPrimitive.Trigger {...props} />
|
|
113
30
|
}
|
|
114
31
|
|
|
115
|
-
export
|
|
116
|
-
|
|
32
|
+
export type DrawerCloseProps = ComponentProps<typeof DrawerPrimitive.Close>
|
|
33
|
+
|
|
34
|
+
export function DrawerClose(props: DrawerCloseProps) {
|
|
35
|
+
return <DrawerPrimitive.Close {...props} />
|
|
117
36
|
}
|
|
118
37
|
|
|
119
38
|
export const drawerContent = tv({
|
|
@@ -125,37 +44,32 @@ export const drawerContent = tv({
|
|
|
125
44
|
},
|
|
126
45
|
})
|
|
127
46
|
|
|
128
|
-
export
|
|
129
|
-
className
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const { side, close } = use(DrawerContext)
|
|
135
|
-
const el = useRef<HTMLDivElement>(null)
|
|
136
|
-
const swipe = useSwipeDismiss(el, side, close)
|
|
47
|
+
export type DrawerContentProps = Omit<ComponentProps<typeof DrawerPrimitive.Popup>, 'className'> & {
|
|
48
|
+
className?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function DrawerContent({ className, style, ...rest }: DrawerContentProps) {
|
|
52
|
+
const side = use(DrawerSide)
|
|
137
53
|
|
|
138
54
|
return (
|
|
139
|
-
<
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
>
|
|
156
|
-
|
|
157
|
-
</Dialog.Content>
|
|
158
|
-
</Dialog.Portal>
|
|
55
|
+
<DrawerPrimitive.Portal>
|
|
56
|
+
<DrawerPrimitive.Backdrop className="bg-bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
|
|
57
|
+
|
|
58
|
+
<DrawerPrimitive.Viewport>
|
|
59
|
+
<DrawerPrimitive.Popup
|
|
60
|
+
{...rest}
|
|
61
|
+
style={state => ({
|
|
62
|
+
touchAction: 'pan-y',
|
|
63
|
+
...(typeof style === 'function' ? style(state) : style),
|
|
64
|
+
})}
|
|
65
|
+
className={[
|
|
66
|
+
s.panel,
|
|
67
|
+
side === 'right' ? s['panel-right'] : '',
|
|
68
|
+
drawerContent({ side, className }),
|
|
69
|
+
].filter(Boolean).join(' ')}
|
|
70
|
+
/>
|
|
71
|
+
</DrawerPrimitive.Viewport>
|
|
72
|
+
</DrawerPrimitive.Portal>
|
|
159
73
|
)
|
|
160
74
|
}
|
|
161
75
|
|
|
@@ -189,14 +103,22 @@ export function DrawerHeader({ lead, trail, className, children }: DrawerHeaderP
|
|
|
189
103
|
|
|
190
104
|
const drawerTitle = tv({ base: 'text-section min-w-0' })
|
|
191
105
|
|
|
192
|
-
export
|
|
193
|
-
|
|
106
|
+
export type DrawerTitleProps = Omit<ComponentProps<typeof DrawerPrimitive.Title>, 'className'> & {
|
|
107
|
+
className?: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function DrawerTitle({ className, ...rest }: DrawerTitleProps) {
|
|
111
|
+
return <DrawerPrimitive.Title {...rest} className={drawerTitle({ className })} />
|
|
194
112
|
}
|
|
195
113
|
|
|
196
114
|
const drawerDescription = tv({ base: 'text-prose text-fg-muted' })
|
|
197
115
|
|
|
198
|
-
export
|
|
116
|
+
export type DrawerDescriptionProps = Omit<ComponentProps<typeof DrawerPrimitive.Description>, 'className'> & {
|
|
117
|
+
className?: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function DrawerDescription({ className, ...rest }: DrawerDescriptionProps) {
|
|
199
121
|
return (
|
|
200
|
-
<
|
|
122
|
+
<DrawerPrimitive.Description {...rest} className={drawerDescription({ className })} />
|
|
201
123
|
)
|
|
202
124
|
}
|