@pantoia/ui 0.12.1 → 0.14.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/avatar-group/index.module.css +3 -0
- package/avatar-group/index.tsx +52 -0
- package/bottom-sheet/index.tsx +15 -1
- package/combobox/index.tsx +7 -3
- package/context-menu/index.tsx +16 -4
- package/dialog/index.tsx +15 -1
- package/drawer/index.tsx +15 -1
- package/dropdown-menu/index.tsx +106 -47
- package/modal/index.tsx +7 -2
- package/package.json +1 -1
- package/popover/index.tsx +12 -4
- package/registry.json +73 -3
- package/select/index.tsx +7 -1
- package/skills/pantoia-design/SKILL.md +7 -3
- package/swatch-picker/index.module.css +8 -0
- package/swatch-picker/index.tsx +176 -0
- package/toast/index.module.css +11 -8
- package/toast/index.tsx +64 -37
- package/toggle/index.tsx +49 -11
- package/toggle-group/index.tsx +76 -0
- package/tokens.css +9 -0
- package/tooltip/index.tsx +9 -2
- package/utilities.css +1 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Children, cloneElement, isValidElement, type ComponentProps, type ReactElement, type ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
import { avatar, type AvatarProps } from '../avatar'
|
|
6
|
+
import { tv } from '../tv'
|
|
7
|
+
|
|
8
|
+
import s from './index.module.css'
|
|
9
|
+
|
|
10
|
+
type Size = NonNullable<AvatarProps['size']>
|
|
11
|
+
|
|
12
|
+
export const avatarGroup = tv({
|
|
13
|
+
base: 'inline-flex items-center',
|
|
14
|
+
|
|
15
|
+
variants: {
|
|
16
|
+
size: {
|
|
17
|
+
xs: '-space-x-1',
|
|
18
|
+
sm: '-space-x-1',
|
|
19
|
+
md: '-space-x-1.5',
|
|
20
|
+
lg: '-space-x-2',
|
|
21
|
+
xl: '-space-x-3',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
defaultVariants: { size: 'md' },
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export type AvatarGroupProps = Omit<ComponentProps<'div'>, 'className' | 'children'> & {
|
|
29
|
+
max?: number
|
|
30
|
+
size?: Size
|
|
31
|
+
renderOverflow?: (count: number) => ReactNode
|
|
32
|
+
className?: string
|
|
33
|
+
children?: ReactNode
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function AvatarGroup({ max, size = 'md', renderOverflow, className, children, ...rest }: AvatarGroupProps) {
|
|
37
|
+
const people = Children.toArray(children).filter(isValidElement) as ReactElement<AvatarProps>[]
|
|
38
|
+
const shown = max !== undefined && people.length > max ? people.slice(0, max) : people
|
|
39
|
+
const hidden = people.length - shown.length
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div role="group" {...rest} className={avatarGroup({ size, className: [s.group, className] })}>
|
|
43
|
+
{shown.map(person => cloneElement(person, { size }))}
|
|
44
|
+
|
|
45
|
+
{hidden > 0 && (
|
|
46
|
+
<span role="img" aria-label={`외 ${hidden}명`} className={avatar({ color: 'neutral', size })}>
|
|
47
|
+
{renderOverflow ? renderOverflow(hidden) : `+${hidden}`}
|
|
48
|
+
</span>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
}
|
package/bottom-sheet/index.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Drawer as DrawerPrimitive } from '@base-ui/react/drawer'
|
|
4
4
|
import type { ComponentProps, ReactNode } from 'react'
|
|
5
5
|
|
|
6
|
+
import { Button } from '../button'
|
|
6
7
|
import { tv } from '../tv'
|
|
7
8
|
|
|
8
9
|
import s from './index.module.css'
|
|
@@ -62,11 +63,23 @@ export const bottomSheetHeader = tv({
|
|
|
62
63
|
export interface BottomSheetHeaderProps {
|
|
63
64
|
lead?: ReactNode
|
|
64
65
|
trail?: ReactNode
|
|
66
|
+
closable?: boolean
|
|
67
|
+
closeLabel?: string
|
|
65
68
|
className?: string
|
|
66
69
|
children?: ReactNode
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
function HeaderClose({ label }: { label: string }) {
|
|
73
|
+
return (
|
|
74
|
+
<DrawerPrimitive.Close render={<Button color="neutral" emphasis="ghost" shape="square" aria-label={label} />}>
|
|
75
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
76
|
+
<path d="M18 6 6 18M6 6l12 12" />
|
|
77
|
+
</svg>
|
|
78
|
+
</DrawerPrimitive.Close>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function BottomSheetHeader({ lead, trail, closable = false, closeLabel = '닫기', className, children }: BottomSheetHeaderProps) {
|
|
70
83
|
const ui = bottomSheetHeader()
|
|
71
84
|
|
|
72
85
|
return (
|
|
@@ -74,6 +87,7 @@ export function BottomSheetHeader({ lead, trail, className, children }: BottomSh
|
|
|
74
87
|
{lead && <div className={ui.lead()}>{lead}</div>}
|
|
75
88
|
<div className={ui.body()}>{children}</div>
|
|
76
89
|
{trail && <div className={ui.trail()}>{trail}</div>}
|
|
90
|
+
{closable && <div className={ui.trail()}><HeaderClose label={closeLabel} /></div>}
|
|
77
91
|
</div>
|
|
78
92
|
)
|
|
79
93
|
}
|
package/combobox/index.tsx
CHANGED
|
@@ -135,7 +135,9 @@ export function ComboboxCancel({ label = '지우기', keepMounted = true, classN
|
|
|
135
135
|
)
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
type ComboboxPlacement = Pick<ComponentProps<typeof ComboboxPrimitive.Positioner>, 'sideOffset' | 'alignOffset' | 'collisionAvoidance'>
|
|
139
|
+
|
|
140
|
+
export type ComboboxContentProps = Omit<ComponentProps<typeof ComboboxPrimitive.Popup>, 'className' | 'children'> & ComboboxPlacement & {
|
|
139
141
|
className?: string
|
|
140
142
|
children?: ComboboxPrimitive.List.Props['children'] | ComboboxPrimitive.List.Props['children'][]
|
|
141
143
|
}
|
|
@@ -148,7 +150,7 @@ const comboboxContent = tv({
|
|
|
148
150
|
|
|
149
151
|
const isEmpty = (child: unknown) => isValidElement(child) && child.type === ComboboxEmpty
|
|
150
152
|
|
|
151
|
-
export function ComboboxContent({ 'aria-label': label, className, children, ...rest }: ComboboxContentProps) {
|
|
153
|
+
export function ComboboxContent({ 'aria-label': label, sideOffset = 4, alignOffset, collisionAvoidance, className, children, ...rest }: ComboboxContentProps) {
|
|
152
154
|
const nodes = Array.isArray(children) ? children : [children]
|
|
153
155
|
const empty = nodes.filter(isEmpty) as ReactNode[]
|
|
154
156
|
const list = nodes.filter(node => !isEmpty(node))
|
|
@@ -157,7 +159,9 @@ export function ComboboxContent({ 'aria-label': label, className, children, ...r
|
|
|
157
159
|
<ComboboxPrimitive.Portal>
|
|
158
160
|
<ComboboxPrimitive.Positioner
|
|
159
161
|
align="start"
|
|
160
|
-
sideOffset={
|
|
162
|
+
sideOffset={sideOffset}
|
|
163
|
+
alignOffset={alignOffset}
|
|
164
|
+
collisionAvoidance={collisionAvoidance}
|
|
161
165
|
className="z-overlay min-w-max will-change-transform"
|
|
162
166
|
>
|
|
163
167
|
<ComboboxPrimitive.Popup {...rest} className={comboboxContent({ className })}>
|
package/context-menu/index.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { ContextMenu as ContextMenuPrimitive } from '@base-ui/react/context-menu'
|
|
4
|
-
import type { ComponentProps } from 'react'
|
|
4
|
+
import type { ComponentProps, ReactNode } from 'react'
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
DropdownMenuCheckboxItem,
|
|
8
8
|
DropdownMenuGroup,
|
|
9
9
|
DropdownMenuItem,
|
|
10
|
+
DropdownMenuItemDescription,
|
|
10
11
|
DropdownMenuRadioGroup,
|
|
11
12
|
DropdownMenuRadioItem,
|
|
12
13
|
DropdownMenuSeparator,
|
|
@@ -37,14 +38,21 @@ export function ContextMenuTrigger(props: ContextMenuTriggerProps) {
|
|
|
37
38
|
return <ContextMenuPrimitive.Trigger {...props} />
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
type ContextMenuPlacement = Pick<ComponentProps<typeof ContextMenuPrimitive.Positioner>, 'sideOffset' | 'alignOffset' | 'collisionAvoidance'>
|
|
42
|
+
|
|
43
|
+
export type ContextMenuContentProps = Omit<ComponentProps<typeof ContextMenuPrimitive.Popup>, 'className'> & ContextMenuPlacement & {
|
|
41
44
|
className?: string
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
export function ContextMenuContent({ className, ...rest }: ContextMenuContentProps) {
|
|
47
|
+
export function ContextMenuContent({ sideOffset, alignOffset, collisionAvoidance, className, ...rest }: ContextMenuContentProps) {
|
|
45
48
|
return (
|
|
46
49
|
<ContextMenuPrimitive.Portal>
|
|
47
|
-
<ContextMenuPrimitive.Positioner
|
|
50
|
+
<ContextMenuPrimitive.Positioner
|
|
51
|
+
sideOffset={sideOffset}
|
|
52
|
+
alignOffset={alignOffset}
|
|
53
|
+
collisionAvoidance={collisionAvoidance}
|
|
54
|
+
className="z-overlay min-w-max will-change-transform"
|
|
55
|
+
>
|
|
48
56
|
<ContextMenuPrimitive.Popup {...rest} className={menuPanel({ className })} />
|
|
49
57
|
</ContextMenuPrimitive.Positioner>
|
|
50
58
|
</ContextMenuPrimitive.Portal>
|
|
@@ -63,6 +71,10 @@ export function ContextMenuItem(props: ContextMenuItemProps) {
|
|
|
63
71
|
return <DropdownMenuItem {...props} />
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
export function ContextMenuItemDescription(props: { className?: string, children?: ReactNode }) {
|
|
75
|
+
return <DropdownMenuItemDescription {...props} />
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
export type ContextMenuCheckboxItemProps = DropdownMenuCheckboxItemProps
|
|
67
79
|
|
|
68
80
|
export function ContextMenuCheckboxItem(props: ContextMenuCheckboxItemProps) {
|
package/dialog/index.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
|
|
4
4
|
import type { ComponentProps, ReactNode } from 'react'
|
|
5
5
|
|
|
6
|
+
import { Button } from '../button'
|
|
6
7
|
import { tv } from '../tv'
|
|
7
8
|
|
|
8
9
|
export const dialog = tv({
|
|
@@ -92,11 +93,23 @@ export const dialogHeader = tv({
|
|
|
92
93
|
export interface DialogHeaderProps {
|
|
93
94
|
lead?: ReactNode
|
|
94
95
|
trail?: ReactNode
|
|
96
|
+
closable?: boolean
|
|
97
|
+
closeLabel?: string
|
|
95
98
|
className?: string
|
|
96
99
|
children?: ReactNode
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
|
|
102
|
+
function HeaderClose({ label }: { label: string }) {
|
|
103
|
+
return (
|
|
104
|
+
<DialogPrimitive.Close render={<Button color="neutral" emphasis="ghost" shape="square" aria-label={label} />}>
|
|
105
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
106
|
+
<path d="M18 6 6 18M6 6l12 12" />
|
|
107
|
+
</svg>
|
|
108
|
+
</DialogPrimitive.Close>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function DialogHeader({ lead, trail, closable = false, closeLabel = '닫기', className, children }: DialogHeaderProps) {
|
|
100
113
|
const ui = dialogHeader()
|
|
101
114
|
|
|
102
115
|
return (
|
|
@@ -104,6 +117,7 @@ export function DialogHeader({ lead, trail, className, children }: DialogHeaderP
|
|
|
104
117
|
{lead && <div className={ui.lead()}>{lead}</div>}
|
|
105
118
|
<div className={ui.body()}>{children}</div>
|
|
106
119
|
{trail && <div className={ui.trail()}>{trail}</div>}
|
|
120
|
+
{closable && <div className={ui.trail()}><HeaderClose label={closeLabel} /></div>}
|
|
107
121
|
</div>
|
|
108
122
|
)
|
|
109
123
|
}
|
package/drawer/index.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Drawer as DrawerPrimitive } from '@base-ui/react/drawer'
|
|
4
4
|
import { createContext, use, type ComponentProps, type ReactNode } from 'react'
|
|
5
5
|
|
|
6
|
+
import { Button } from '../button'
|
|
6
7
|
import { tv } from '../tv'
|
|
7
8
|
|
|
8
9
|
import s from './index.module.css'
|
|
@@ -85,11 +86,23 @@ export const drawerHeader = tv({
|
|
|
85
86
|
export interface DrawerHeaderProps {
|
|
86
87
|
lead?: ReactNode
|
|
87
88
|
trail?: ReactNode
|
|
89
|
+
closable?: boolean
|
|
90
|
+
closeLabel?: string
|
|
88
91
|
className?: string
|
|
89
92
|
children?: ReactNode
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
|
|
95
|
+
function HeaderClose({ label }: { label: string }) {
|
|
96
|
+
return (
|
|
97
|
+
<DrawerPrimitive.Close render={<Button color="neutral" emphasis="ghost" shape="square" aria-label={label} />}>
|
|
98
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
99
|
+
<path d="M18 6 6 18M6 6l12 12" />
|
|
100
|
+
</svg>
|
|
101
|
+
</DrawerPrimitive.Close>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function DrawerHeader({ lead, trail, closable = false, closeLabel = '닫기', className, children }: DrawerHeaderProps) {
|
|
93
106
|
const ui = drawerHeader()
|
|
94
107
|
|
|
95
108
|
return (
|
|
@@ -97,6 +110,7 @@ export function DrawerHeader({ lead, trail, className, children }: DrawerHeaderP
|
|
|
97
110
|
{lead && <div className={ui.lead()}>{lead}</div>}
|
|
98
111
|
<div className={ui.body()}>{children}</div>
|
|
99
112
|
{trail && <div className={ui.trail()}>{trail}</div>}
|
|
113
|
+
{closable && <div className={ui.trail()}><HeaderClose label={closeLabel} /></div>}
|
|
100
114
|
</div>
|
|
101
115
|
)
|
|
102
116
|
}
|
package/dropdown-menu/index.tsx
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { Menu as MenuPrimitive } from '@base-ui/react/menu'
|
|
4
|
-
import type
|
|
4
|
+
import { createContext, use, useEffect, useId, useState, type ComponentProps, type ReactNode } from 'react'
|
|
5
5
|
|
|
6
6
|
import { tv } from '../tv'
|
|
7
7
|
|
|
8
8
|
export const menuItem = tv({
|
|
9
|
-
base: 'flex h-9 cursor-default items-center gap-2 rounded-r8 px-2 menu-gutter:ps-8'
|
|
9
|
+
base: 'flex min-h-9 cursor-default items-center gap-2 rounded-r8 px-2 py-1.5 menu-gutter:ps-8'
|
|
10
10
|
+ ' [&_svg]:size-4 [&_svg]:shrink-0'
|
|
11
|
+
+ ' described:items-start described:[&>.menu-lead]:mt-0.5 described:[&>svg]:mt-0.5'
|
|
11
12
|
+ ' text-label transition-state'
|
|
12
13
|
+ ' data-highlighted:outline-none data-disabled:pointer-events-none',
|
|
13
14
|
|
|
@@ -21,6 +22,20 @@ export const menuItem = tv({
|
|
|
21
22
|
defaultVariants: { color: 'neutral' },
|
|
22
23
|
})
|
|
23
24
|
|
|
25
|
+
interface DescriptionContext {
|
|
26
|
+
id: string
|
|
27
|
+
setDescribed: (described: boolean) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ItemDescription = createContext<DescriptionContext | null>(null)
|
|
31
|
+
|
|
32
|
+
function useDescription() {
|
|
33
|
+
const id = useId()
|
|
34
|
+
const [described, setDescribed] = useState(false)
|
|
35
|
+
|
|
36
|
+
return { context: { id, setDescribed }, describedBy: described ? id : undefined }
|
|
37
|
+
}
|
|
38
|
+
|
|
24
39
|
const PANEL = 'menu float text-fg rounded-r12 z-overlay'
|
|
25
40
|
+ ' flex min-w-44 flex-col gap-px p-1'
|
|
26
41
|
+ ' open:animate-enter closed:animate-exit'
|
|
@@ -38,7 +53,9 @@ export function DropdownMenuTrigger(props: DropdownMenuTriggerProps) {
|
|
|
38
53
|
return <MenuPrimitive.Trigger {...props} />
|
|
39
54
|
}
|
|
40
55
|
|
|
41
|
-
|
|
56
|
+
type MenuPlacement = Pick<ComponentProps<typeof MenuPrimitive.Positioner>, 'anchor' | 'sideOffset' | 'alignOffset' | 'collisionAvoidance'>
|
|
57
|
+
|
|
58
|
+
export type DropdownMenuContentProps = Omit<ComponentProps<typeof MenuPrimitive.Popup>, 'className'> & MenuPlacement & {
|
|
42
59
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
43
60
|
align?: 'start' | 'center' | 'end'
|
|
44
61
|
arrow?: boolean
|
|
@@ -59,6 +76,10 @@ export function DropdownMenuContent({
|
|
|
59
76
|
side = 'bottom',
|
|
60
77
|
align = 'start',
|
|
61
78
|
arrow = true,
|
|
79
|
+
anchor,
|
|
80
|
+
sideOffset,
|
|
81
|
+
alignOffset,
|
|
82
|
+
collisionAvoidance,
|
|
62
83
|
className,
|
|
63
84
|
children,
|
|
64
85
|
...rest
|
|
@@ -66,9 +87,12 @@ export function DropdownMenuContent({
|
|
|
66
87
|
return (
|
|
67
88
|
<MenuPrimitive.Portal>
|
|
68
89
|
<MenuPrimitive.Positioner
|
|
90
|
+
anchor={anchor}
|
|
69
91
|
side={side}
|
|
70
92
|
align={align}
|
|
71
|
-
sideOffset={arrow ? 14 : 8}
|
|
93
|
+
sideOffset={sideOffset ?? (arrow ? 14 : 8)}
|
|
94
|
+
alignOffset={alignOffset}
|
|
95
|
+
collisionAvoidance={collisionAvoidance}
|
|
72
96
|
arrowPadding={16}
|
|
73
97
|
className="z-overlay min-w-max will-change-transform"
|
|
74
98
|
>
|
|
@@ -116,17 +140,21 @@ export type DropdownMenuItemProps = Omit<ComponentProps<typeof MenuPrimitive.Ite
|
|
|
116
140
|
}
|
|
117
141
|
|
|
118
142
|
export function DropdownMenuItem({ color, lead, trail, className, children, ...rest }: DropdownMenuItemProps) {
|
|
119
|
-
|
|
120
|
-
<MenuPrimitive.Item {...rest} className={menuItem({ color, className })}>
|
|
121
|
-
{lead && (
|
|
122
|
-
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
123
|
-
{lead}
|
|
124
|
-
</span>
|
|
125
|
-
)}
|
|
143
|
+
const { context, describedBy } = useDescription()
|
|
126
144
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
return (
|
|
146
|
+
<ItemDescription value={context}>
|
|
147
|
+
<MenuPrimitive.Item aria-describedby={describedBy} {...rest} className={menuItem({ color, className })}>
|
|
148
|
+
{lead && (
|
|
149
|
+
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
150
|
+
{lead}
|
|
151
|
+
</span>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
<span className="min-w-0 grow">{children}</span>
|
|
155
|
+
{trail}
|
|
156
|
+
</MenuPrimitive.Item>
|
|
157
|
+
</ItemDescription>
|
|
130
158
|
)
|
|
131
159
|
}
|
|
132
160
|
|
|
@@ -135,18 +163,22 @@ export type DropdownMenuCheckboxItemProps = Omit<ComponentProps<typeof MenuPrimi
|
|
|
135
163
|
}
|
|
136
164
|
|
|
137
165
|
export function DropdownMenuCheckboxItem({ className, children, ...rest }: DropdownMenuCheckboxItemProps) {
|
|
166
|
+
const { context, describedBy } = useDescription()
|
|
167
|
+
|
|
138
168
|
return (
|
|
139
|
-
<
|
|
140
|
-
<
|
|
141
|
-
<
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
169
|
+
<ItemDescription value={context}>
|
|
170
|
+
<MenuPrimitive.CheckboxItem aria-describedby={describedBy} {...rest} className={menuItem({ className })}>
|
|
171
|
+
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
172
|
+
<MenuPrimitive.CheckboxItemIndicator>
|
|
173
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
|
|
174
|
+
<path d="M20 6 9 17l-5-5" />
|
|
175
|
+
</svg>
|
|
176
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
177
|
+
</span>
|
|
178
|
+
|
|
179
|
+
<span className="min-w-0 grow">{children}</span>
|
|
180
|
+
</MenuPrimitive.CheckboxItem>
|
|
181
|
+
</ItemDescription>
|
|
150
182
|
)
|
|
151
183
|
}
|
|
152
184
|
|
|
@@ -161,16 +193,39 @@ export type DropdownMenuRadioItemProps = Omit<ComponentProps<typeof MenuPrimitiv
|
|
|
161
193
|
}
|
|
162
194
|
|
|
163
195
|
export function DropdownMenuRadioItem({ className, children, ...rest }: DropdownMenuRadioItemProps) {
|
|
196
|
+
const { context, describedBy } = useDescription()
|
|
197
|
+
|
|
164
198
|
return (
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
<
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
199
|
+
<ItemDescription value={context}>
|
|
200
|
+
<MenuPrimitive.RadioItem aria-describedby={describedBy} {...rest} className={menuItem({ className })}>
|
|
201
|
+
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
202
|
+
<MenuPrimitive.RadioItemIndicator>
|
|
203
|
+
<span className="bg-bg-neutral-solid in-disabled:bg-bg-neutral-solid-checked-disabled block size-2 rounded-full" />
|
|
204
|
+
</MenuPrimitive.RadioItemIndicator>
|
|
205
|
+
</span>
|
|
206
|
+
|
|
207
|
+
<span className="min-w-0 grow">{children}</span>
|
|
208
|
+
</MenuPrimitive.RadioItem>
|
|
209
|
+
</ItemDescription>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const menuItemDescription = tv({ base: 'text-caption text-fg-subtle block' })
|
|
214
|
+
|
|
215
|
+
export function DropdownMenuItemDescription({ className, children }: { className?: string, children?: ReactNode }) {
|
|
216
|
+
const ctx = use(ItemDescription)
|
|
217
|
+
const setDescribed = ctx?.setDescribed
|
|
218
|
+
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
if (!setDescribed) return
|
|
221
|
+
setDescribed(true)
|
|
222
|
+
return () => setDescribed(false)
|
|
223
|
+
}, [setDescribed])
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<span id={ctx?.id} data-description aria-hidden="true" className={menuItemDescription({ className })}>
|
|
227
|
+
{children}
|
|
228
|
+
</span>
|
|
174
229
|
)
|
|
175
230
|
}
|
|
176
231
|
|
|
@@ -196,20 +251,24 @@ export type DropdownMenuSubTriggerProps = Omit<ComponentProps<typeof MenuPrimiti
|
|
|
196
251
|
}
|
|
197
252
|
|
|
198
253
|
export function DropdownMenuSubTrigger({ lead, className, children, ...rest }: DropdownMenuSubTriggerProps) {
|
|
199
|
-
|
|
200
|
-
<MenuPrimitive.SubmenuTrigger {...rest} className={menuItem({ className })}>
|
|
201
|
-
{lead && (
|
|
202
|
-
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
203
|
-
{lead}
|
|
204
|
-
</span>
|
|
205
|
-
)}
|
|
254
|
+
const { context, describedBy } = useDescription()
|
|
206
255
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
256
|
+
return (
|
|
257
|
+
<ItemDescription value={context}>
|
|
258
|
+
<MenuPrimitive.SubmenuTrigger aria-describedby={describedBy} {...rest} className={menuItem({ className })}>
|
|
259
|
+
{lead && (
|
|
260
|
+
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
261
|
+
{lead}
|
|
262
|
+
</span>
|
|
263
|
+
)}
|
|
264
|
+
|
|
265
|
+
<span className="min-w-0 grow">{children}</span>
|
|
266
|
+
|
|
267
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="text-fg-subtle size-4 shrink-0">
|
|
268
|
+
<path d="m9 18 6-6-6-6" />
|
|
269
|
+
</svg>
|
|
270
|
+
</MenuPrimitive.SubmenuTrigger>
|
|
271
|
+
</ItemDescription>
|
|
213
272
|
)
|
|
214
273
|
}
|
|
215
274
|
|
package/modal/index.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
BottomSheetClose,
|
|
10
10
|
BottomSheetContent,
|
|
11
11
|
BottomSheetDescription,
|
|
12
|
+
BottomSheetHeader,
|
|
12
13
|
BottomSheetTitle,
|
|
13
14
|
BottomSheetTrigger,
|
|
14
15
|
} from '../bottom-sheet'
|
|
@@ -38,7 +39,11 @@ export const modalQuery: Record<ModalBreakpoint, string> = {
|
|
|
38
39
|
md: '(min-width: 48rem)',
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
const ModalShape = createContext<Shape>(
|
|
42
|
+
const ModalShape = createContext<Shape | null>(null)
|
|
43
|
+
|
|
44
|
+
export function useModalShape(): Shape | null {
|
|
45
|
+
return use(ModalShape)
|
|
46
|
+
}
|
|
42
47
|
|
|
43
48
|
const media = (query: string) => (typeof window.matchMedia === 'function' ? window.matchMedia(query) : null)
|
|
44
49
|
|
|
@@ -109,7 +114,7 @@ export function ModalContent({ size, ...rest }: ModalContentProps) {
|
|
|
109
114
|
export type ModalHeaderProps = DialogHeaderProps
|
|
110
115
|
|
|
111
116
|
export function ModalHeader(props: ModalHeaderProps) {
|
|
112
|
-
return <DialogHeader {...props} />
|
|
117
|
+
return use(ModalShape) === 'dialog' ? <DialogHeader {...props} /> : <BottomSheetHeader {...props} />
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
export interface ModalTitleProps {
|
package/package.json
CHANGED
package/popover/index.tsx
CHANGED
|
@@ -48,7 +48,9 @@ export function PopoverClose(props: PopoverCloseProps) {
|
|
|
48
48
|
return <PopoverPrimitive.Close {...props} />
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
type PopoverPlacement = Pick<ComponentProps<typeof PopoverPrimitive.Positioner>, 'anchor' | 'sideOffset' | 'alignOffset' | 'collisionAvoidance'>
|
|
52
|
+
|
|
53
|
+
export type PopoverContentProps = Omit<ComponentProps<typeof PopoverPrimitive.Popup>, 'className'> & PopoverPlacement & {
|
|
52
54
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
53
55
|
align?: 'start' | 'center' | 'end'
|
|
54
56
|
arrow?: boolean
|
|
@@ -73,19 +75,25 @@ export function PopoverContent({
|
|
|
73
75
|
side = 'bottom',
|
|
74
76
|
align = 'center',
|
|
75
77
|
arrow = true,
|
|
78
|
+
anchor,
|
|
79
|
+
sideOffset,
|
|
80
|
+
alignOffset,
|
|
81
|
+
collisionAvoidance,
|
|
76
82
|
className,
|
|
77
83
|
children,
|
|
78
84
|
...rest
|
|
79
85
|
}: PopoverContentProps) {
|
|
80
|
-
const [
|
|
86
|
+
const [anchorPart] = useContext(AnchorContext)
|
|
81
87
|
|
|
82
88
|
return (
|
|
83
89
|
<PopoverPrimitive.Portal>
|
|
84
90
|
<PopoverPrimitive.Positioner
|
|
85
|
-
anchor={anchor ?? undefined}
|
|
91
|
+
anchor={anchor ?? anchorPart ?? undefined}
|
|
86
92
|
side={side}
|
|
87
93
|
align={align}
|
|
88
|
-
sideOffset={arrow ? 14 : 8}
|
|
94
|
+
sideOffset={sideOffset ?? (arrow ? 14 : 8)}
|
|
95
|
+
alignOffset={alignOffset}
|
|
96
|
+
collisionAvoidance={collisionAvoidance}
|
|
89
97
|
arrowPadding={16}
|
|
90
98
|
className="z-overlay min-w-max will-change-transform"
|
|
91
99
|
>
|