@pantoia/ui 0.3.0 → 0.5.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 (63) hide show
  1. package/README.md +46 -0
  2. package/accordion/index.module.css +4 -4
  3. package/accordion/index.tsx +21 -51
  4. package/alert-dialog/index.tsx +36 -42
  5. package/badge/index.tsx +1 -1
  6. package/button/index.tsx +31 -30
  7. package/callout/index.tsx +4 -4
  8. package/checkbox/index.tsx +20 -85
  9. package/combobox/index.tsx +122 -316
  10. package/command-palette/index.tsx +51 -32
  11. package/dialog/index.tsx +28 -24
  12. package/dist/index.js +5 -5
  13. package/drawer/index.module.css +2 -2
  14. package/drawer/index.tsx +52 -130
  15. package/dropdown-menu/index.tsx +87 -95
  16. package/field/index.tsx +27 -86
  17. package/input/index.tsx +7 -3
  18. package/kbd/index.tsx +2 -2
  19. package/label/index.tsx +6 -8
  20. package/number-field/index.tsx +122 -173
  21. package/package.json +19 -11
  22. package/popover/index.tsx +61 -32
  23. package/radio-group/index.tsx +35 -38
  24. package/registry.json +64 -154
  25. package/select/index.tsx +141 -70
  26. package/switch/index.tsx +13 -6
  27. package/table/index.tsx +1 -2
  28. package/tabs/index.module.css +3 -3
  29. package/tabs/index.tsx +22 -76
  30. package/textarea/index.tsx +19 -7
  31. package/toast/index.module.css +187 -0
  32. package/toast/index.tsx +141 -46
  33. package/toggle/index.tsx +4 -3
  34. package/tokens.css +7 -1
  35. package/tooltip/index.tsx +61 -30
  36. package/utilities.css +11 -6
  37. package/accordion/AGENTS.md +0 -60
  38. package/alert-dialog/AGENTS.md +0 -78
  39. package/badge/AGENTS.md +0 -35
  40. package/button/AGENTS.md +0 -39
  41. package/callout/AGENTS.md +0 -37
  42. package/checkbox/AGENTS.md +0 -54
  43. package/combobox/AGENTS.md +0 -150
  44. package/command-palette/AGENTS.md +0 -41
  45. package/dialog/AGENTS.md +0 -81
  46. package/drawer/AGENTS.md +0 -66
  47. package/dropdown-menu/AGENTS.md +0 -133
  48. package/field/AGENTS.md +0 -92
  49. package/input/AGENTS.md +0 -35
  50. package/kbd/AGENTS.md +0 -17
  51. package/label/AGENTS.md +0 -21
  52. package/number-field/AGENTS.md +0 -49
  53. package/popover/AGENTS.md +0 -64
  54. package/radio-group/AGENTS.md +0 -81
  55. package/select/AGENTS.md +0 -90
  56. package/switch/AGENTS.md +0 -37
  57. package/table/AGENTS.md +0 -82
  58. package/tabs/AGENTS.md +0 -61
  59. package/test-utils.ts +0 -10
  60. package/textarea/AGENTS.md +0 -35
  61. package/toast/AGENTS.md +0 -34
  62. package/toggle/AGENTS.md +0 -37
  63. 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
- group?: string
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 filtered = search ? items.filter(i => contains(i.label, search)) : items
64
-
65
- const pick = (value: string) => {
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 open onValueChange={pick}>
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
- <ComboboxEmpty className="py-6">
96
- {emptyText}
97
- </ComboboxEmpty>
98
-
99
- {filtered.map(item => (
100
- <ComboboxItem key={item.value} value={item.value}>
101
- <ComboboxItemText>{item.label}</ComboboxItemText>
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 'radix-ui'
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 interface DialogProps {
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({ open, defaultOpen, onOpenChange, children }: DialogProps) {
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 function DialogTrigger({ children }: { children: ReactNode }) {
41
- return <DialogPrimitive.Trigger asChild>{children}</DialogPrimitive.Trigger>
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 function DialogClose({ children }: { children: ReactNode }) {
45
- return <DialogPrimitive.Close asChild>{children}</DialogPrimitive.Close>
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 interface DialogContentProps extends ComponentProps<typeof DialogPrimitive.Content> {
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.Overlay className="bg-bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
51
+ <DialogPrimitive.Backdrop className="bg-bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
56
52
 
57
- <DialogPrimitive.Content {...rest} className={dialog({ size, className })}>
53
+ <DialogPrimitive.Popup {...rest} className={dialog({ size, className })}>
58
54
  {children}
59
- </DialogPrimitive.Content>
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 function DialogTitle({ className, ...rest }: ComponentProps<typeof DialogPrimitive.Title>) {
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 function DialogDescription({ className, ...rest }: ComponentProps<typeof DialogPrimitive.Description>) {
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(`글꼴 패키지를 설치할까요? ${FONTS.map(packageName).join(', ')}`, true);
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')))
@@ -7,8 +7,8 @@
7
7
  transition: transform var(--dur-enter) var(--ease-enter);
8
8
  }
9
9
 
10
- .panel[data-state="open"] { animation: slide-in var(--dur-enter) var(--ease-enter); }
11
- .panel[data-state="closed"]{ animation: slide-out var(--dur-exit) var(--ease-exit); }
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 { 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'
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 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
- }
12
+ const DrawerSide = createContext<Side>('left')
81
13
 
82
- return { onPointerDown, onPointerMove, onPointerUp, onPointerCancel: onPointerUp }
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', 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
-
18
+ export function Drawer({ side = 'left', ...rest }: DrawerProps) {
102
19
  return (
103
- <DrawerContext value={{ side, close: () => set(false) }}>
104
- <Dialog.Root open={isOpen} onOpenChange={set} modal>
105
- {children}
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 function DrawerTrigger({ children }: { children: ReactNode }) {
112
- return <Dialog.Trigger asChild>{children}</Dialog.Trigger>
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 function DrawerClose({ children }: { children: ReactNode }) {
116
- return <Dialog.Close asChild>{children}</Dialog.Close>
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 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)
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
- <Dialog.Portal>
140
- <Dialog.Overlay className="bg-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>
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 function DrawerTitle({ className, ...rest }: ComponentProps<typeof Dialog.Title>) {
193
- return <Dialog.Title {...rest} className={drawerTitle({ className })} />
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 function DrawerDescription({ className, ...rest }: ComponentProps<typeof Dialog.Description>) {
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
- <Dialog.Description {...rest} className={drawerDescription({ className })} />
122
+ <DrawerPrimitive.Description {...rest} className={drawerDescription({ className })} />
201
123
  )
202
124
  }