@pantoia/ui 0.10.4 → 0.11.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/index.module.css +4 -0
- package/avatar/index.tsx +104 -0
- package/badge/index.tsx +30 -9
- package/bottom-sheet/index.module.css +16 -0
- package/bottom-sheet/index.tsx +101 -0
- package/context-menu/index.tsx +106 -0
- package/dropdown-menu/index.tsx +4 -1
- package/package.json +1 -1
- package/registry.json +74 -0
- package/skills/pantoia-design/SKILL.md +16 -9
- package/tokens.css +6 -0
package/avatar/index.tsx
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Avatar as AvatarPrimitive } from '@base-ui/react/avatar'
|
|
4
|
+
import type { ComponentProps, CSSProperties } from 'react'
|
|
5
|
+
|
|
6
|
+
import { tv } from '../tv'
|
|
7
|
+
|
|
8
|
+
import s from './index.module.css'
|
|
9
|
+
|
|
10
|
+
const HANGUL = /\p{Script=Hangul}/u
|
|
11
|
+
const LATIN = /\p{Script=Latin}+/gu
|
|
12
|
+
const LETTER = /[\p{Script=Hangul}\p{Script=Latin}]/u
|
|
13
|
+
|
|
14
|
+
export function initials(name: string): string | null {
|
|
15
|
+
const first = LETTER.exec(name)?.[0]
|
|
16
|
+
if (!first) return null
|
|
17
|
+
if (HANGUL.test(first)) return first
|
|
18
|
+
|
|
19
|
+
const words = name.match(LATIN) ?? []
|
|
20
|
+
const text = words.length > 1 ? words[0]![0]! + words[1]![0]! : words[0]!.slice(0, 2)
|
|
21
|
+
|
|
22
|
+
return text.toUpperCase()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const FNV_OFFSET = 0x811c9dc5
|
|
26
|
+
const FNV_PRIME = 0x01000193
|
|
27
|
+
|
|
28
|
+
export function fnv1a(seed: string): number {
|
|
29
|
+
let hash = FNV_OFFSET
|
|
30
|
+
|
|
31
|
+
for (const byte of new TextEncoder().encode(seed.toLowerCase())) {
|
|
32
|
+
hash ^= byte
|
|
33
|
+
hash = Math.imul(hash, FNV_PRIME) >>> 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return hash >>> 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function hueOf(seed: string): number {
|
|
40
|
+
const hue = fnv1a(seed) % 295
|
|
41
|
+
return hue >= 75 ? hue + 65 : hue
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const avatar = tv({
|
|
45
|
+
base: 'inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full font-medium',
|
|
46
|
+
|
|
47
|
+
variants: {
|
|
48
|
+
color: {
|
|
49
|
+
neutral: 'bg-bg-neutral-solid/10 text-fg',
|
|
50
|
+
brand: 'bg-bg-brand-weak text-fg-brand-contrast',
|
|
51
|
+
info: 'bg-bg-info-weak text-fg-info-contrast',
|
|
52
|
+
success: 'bg-bg-success-weak text-fg-success-contrast',
|
|
53
|
+
warning: 'bg-bg-warning-weak text-fg-warning-contrast',
|
|
54
|
+
danger: 'bg-bg-danger-weak text-fg-danger-contrast',
|
|
55
|
+
seed: '',
|
|
56
|
+
},
|
|
57
|
+
size: {
|
|
58
|
+
xs: 'size-5 text-t10',
|
|
59
|
+
sm: 'size-6 text-t10',
|
|
60
|
+
md: 'size-8 text-t13',
|
|
61
|
+
lg: 'size-10 text-t16',
|
|
62
|
+
xl: 'size-14 text-t20',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
defaultVariants: { color: 'neutral', size: 'md' },
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
type AvatarTone =
|
|
70
|
+
| { color?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'danger', seed?: never }
|
|
71
|
+
| { seed: string, color?: never }
|
|
72
|
+
|
|
73
|
+
export type AvatarProps = Omit<ComponentProps<typeof AvatarPrimitive.Root>, 'className' | 'color' | 'children'> & AvatarTone & {
|
|
74
|
+
src?: string
|
|
75
|
+
name?: string
|
|
76
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
77
|
+
className?: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function Avatar({ src, name, color, seed, size, className, style, ...rest }: AvatarProps) {
|
|
81
|
+
const text = name ? initials(name) : null
|
|
82
|
+
const seeded = seed !== undefined
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<AvatarPrimitive.Root
|
|
86
|
+
role={name ? 'img' : undefined}
|
|
87
|
+
aria-label={name}
|
|
88
|
+
{...rest}
|
|
89
|
+
style={seeded ? { ...style, '--avatar-hue': hueOf(seed) } as CSSProperties : style}
|
|
90
|
+
className={avatar({ color: seeded ? 'seed' : color, size, className: seeded ? [s.seed, className] : className })}
|
|
91
|
+
>
|
|
92
|
+
{src && <AvatarPrimitive.Image src={src} alt="" className="size-full object-cover" />}
|
|
93
|
+
|
|
94
|
+
<AvatarPrimitive.Fallback aria-hidden="true" className="flex size-full items-center justify-center">
|
|
95
|
+
{text ?? (
|
|
96
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-3/5">
|
|
97
|
+
<circle cx="12" cy="8" r="4" />
|
|
98
|
+
<path d="M4 21a8 8 0 0 1 16 0" />
|
|
99
|
+
</svg>
|
|
100
|
+
)}
|
|
101
|
+
</AvatarPrimitive.Fallback>
|
|
102
|
+
</AvatarPrimitive.Root>
|
|
103
|
+
)
|
|
104
|
+
}
|
package/badge/index.tsx
CHANGED
|
@@ -8,12 +8,16 @@ export const badge = tv({
|
|
|
8
8
|
|
|
9
9
|
variants: {
|
|
10
10
|
color: {
|
|
11
|
-
neutral: '
|
|
12
|
-
brand: '
|
|
13
|
-
info: '
|
|
14
|
-
success: '
|
|
15
|
-
warning: '
|
|
16
|
-
danger: '
|
|
11
|
+
neutral: '',
|
|
12
|
+
brand: '',
|
|
13
|
+
info: '',
|
|
14
|
+
success: '',
|
|
15
|
+
warning: '',
|
|
16
|
+
danger: '',
|
|
17
|
+
},
|
|
18
|
+
emphasis: {
|
|
19
|
+
weak: '',
|
|
20
|
+
solid: '',
|
|
17
21
|
},
|
|
18
22
|
shape: {
|
|
19
23
|
square: 'rounded-r4',
|
|
@@ -21,14 +25,31 @@ export const badge = tv({
|
|
|
21
25
|
},
|
|
22
26
|
},
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
compoundVariants: [
|
|
29
|
+
{ color: 'neutral', emphasis: 'weak', class: 'bg-bg-neutral-solid/10 text-fg' },
|
|
30
|
+
{ color: 'brand', emphasis: 'weak', class: 'bg-bg-brand-weak text-fg-brand-contrast' },
|
|
31
|
+
{ color: 'info', emphasis: 'weak', class: 'bg-bg-info-weak text-fg-info-contrast' },
|
|
32
|
+
{ color: 'success', emphasis: 'weak', class: 'bg-bg-success-weak text-fg-success-contrast' },
|
|
33
|
+
{ color: 'warning', emphasis: 'weak', class: 'bg-bg-warning-weak text-fg-warning-contrast' },
|
|
34
|
+
{ color: 'danger', emphasis: 'weak', class: 'bg-bg-danger-weak text-fg-danger-contrast' },
|
|
35
|
+
|
|
36
|
+
{ color: 'neutral', emphasis: 'solid', class: 'bg-bg-neutral-solid text-fg-neutral-solid-contrast' },
|
|
37
|
+
{ color: 'brand', emphasis: 'solid', class: 'bg-bg-brand-solid text-fg-brand-solid-contrast' },
|
|
38
|
+
{ color: 'info', emphasis: 'solid', class: 'bg-bg-info-solid text-fg-info-solid-contrast' },
|
|
39
|
+
{ color: 'success', emphasis: 'solid', class: 'bg-bg-success-solid text-white' },
|
|
40
|
+
{ color: 'warning', emphasis: 'solid', class: 'bg-bg-warning-solid text-white' },
|
|
41
|
+
{ color: 'danger', emphasis: 'solid', class: 'bg-bg-danger-solid text-white' },
|
|
42
|
+
],
|
|
43
|
+
|
|
44
|
+
defaultVariants: { color: 'neutral', emphasis: 'weak', shape: 'square' },
|
|
25
45
|
})
|
|
26
46
|
|
|
27
47
|
export interface BadgeProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'color'> {
|
|
28
48
|
color?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'danger'
|
|
49
|
+
emphasis?: 'weak' | 'solid'
|
|
29
50
|
shape?: 'square' | 'pill'
|
|
30
51
|
}
|
|
31
52
|
|
|
32
|
-
export function Badge({ color, shape, className, ...rest }: BadgeProps) {
|
|
33
|
-
return <span {...rest} className={badge({ color, shape, className })} />
|
|
53
|
+
export function Badge({ color, emphasis, shape, className, ...rest }: BadgeProps) {
|
|
54
|
+
return <span {...rest} className={badge({ color, emphasis, shape, className })} />
|
|
34
55
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@keyframes slide-in { from { translate: 0 var(--bottom-sheet-slide); } }
|
|
2
|
+
@keyframes slide-out { to { translate: 0 var(--bottom-sheet-slide); } }
|
|
3
|
+
|
|
4
|
+
.panel {
|
|
5
|
+
--bottom-sheet-slide: 100%;
|
|
6
|
+
transform: translateY(var(--drawer-swipe-movement-y, 0px));
|
|
7
|
+
transition: transform var(--dur-enter) var(--ease-enter);
|
|
8
|
+
}
|
|
9
|
+
|
|
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
|
+
.panel[data-swiping] { transition-duration: 0ms; }
|
|
13
|
+
|
|
14
|
+
@media (prefers-reduced-motion: reduce) {
|
|
15
|
+
.panel { --bottom-sheet-slide: 0%; }
|
|
16
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Drawer as DrawerPrimitive } from '@base-ui/react/drawer'
|
|
4
|
+
import type { ComponentProps, ReactNode } from 'react'
|
|
5
|
+
|
|
6
|
+
import { tv } from '../tv'
|
|
7
|
+
|
|
8
|
+
import s from './index.module.css'
|
|
9
|
+
|
|
10
|
+
export type BottomSheetProps = Omit<DrawerPrimitive.Root.Props, 'swipeDirection'>
|
|
11
|
+
|
|
12
|
+
export function BottomSheet(props: BottomSheetProps) {
|
|
13
|
+
return <DrawerPrimitive.Root {...props} swipeDirection="down" />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type BottomSheetTriggerProps = ComponentProps<typeof DrawerPrimitive.Trigger>
|
|
17
|
+
|
|
18
|
+
export function BottomSheetTrigger(props: BottomSheetTriggerProps) {
|
|
19
|
+
return <DrawerPrimitive.Trigger {...props} />
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type BottomSheetCloseProps = ComponentProps<typeof DrawerPrimitive.Close>
|
|
23
|
+
|
|
24
|
+
export function BottomSheetClose(props: BottomSheetCloseProps) {
|
|
25
|
+
return <DrawerPrimitive.Close {...props} />
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const bottomSheetContent = tv({
|
|
29
|
+
base: 'sheet text-fg z-overlay fixed inset-x-0 bottom-0 mx-auto rounded-t-r20'
|
|
30
|
+
+ ' flex max-h-11/12 w-full flex-col gap-stack overflow-y-auto px-4 pb-4 sm:max-w-120'
|
|
31
|
+
+ ' focus-visible:outline-none',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export type BottomSheetContentProps = Omit<ComponentProps<typeof DrawerPrimitive.Popup>, 'className'> & {
|
|
35
|
+
className?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function BottomSheetContent({ className, children, ...rest }: BottomSheetContentProps) {
|
|
39
|
+
return (
|
|
40
|
+
<DrawerPrimitive.Portal>
|
|
41
|
+
<DrawerPrimitive.Backdrop className="bg-bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
|
|
42
|
+
|
|
43
|
+
<DrawerPrimitive.Viewport>
|
|
44
|
+
<DrawerPrimitive.Popup {...rest} className={`${s.panel} ${bottomSheetContent({ className })}`}>
|
|
45
|
+
<div aria-hidden="true" className="bg-bg-neutral-solid/20 mx-auto mt-2 h-1 w-10 shrink-0 rounded-full" />
|
|
46
|
+
{children}
|
|
47
|
+
</DrawerPrimitive.Popup>
|
|
48
|
+
</DrawerPrimitive.Viewport>
|
|
49
|
+
</DrawerPrimitive.Portal>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const bottomSheetHeader = tv({
|
|
54
|
+
slots: {
|
|
55
|
+
root: 'flex items-start gap-stack',
|
|
56
|
+
lead: 'shrink-0',
|
|
57
|
+
body: 'flex min-w-0 grow flex-col gap-tight',
|
|
58
|
+
trail: 'shrink-0',
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
export interface BottomSheetHeaderProps {
|
|
63
|
+
lead?: ReactNode
|
|
64
|
+
trail?: ReactNode
|
|
65
|
+
className?: string
|
|
66
|
+
children?: ReactNode
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function BottomSheetHeader({ lead, trail, className, children }: BottomSheetHeaderProps) {
|
|
70
|
+
const ui = bottomSheetHeader()
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className={ui.root({ className })}>
|
|
74
|
+
{lead && <div className={ui.lead()}>{lead}</div>}
|
|
75
|
+
<div className={ui.body()}>{children}</div>
|
|
76
|
+
{trail && <div className={ui.trail()}>{trail}</div>}
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const bottomSheetTitle = tv({ base: 'text-section min-w-0' })
|
|
82
|
+
|
|
83
|
+
export type BottomSheetTitleProps = Omit<ComponentProps<typeof DrawerPrimitive.Title>, 'className'> & {
|
|
84
|
+
className?: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function BottomSheetTitle({ className, ...rest }: BottomSheetTitleProps) {
|
|
88
|
+
return <DrawerPrimitive.Title {...rest} className={bottomSheetTitle({ className })} />
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const bottomSheetDescription = tv({ base: 'text-prose text-fg-muted' })
|
|
92
|
+
|
|
93
|
+
export type BottomSheetDescriptionProps = Omit<ComponentProps<typeof DrawerPrimitive.Description>, 'className'> & {
|
|
94
|
+
className?: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function BottomSheetDescription({ className, ...rest }: BottomSheetDescriptionProps) {
|
|
98
|
+
return (
|
|
99
|
+
<DrawerPrimitive.Description {...rest} className={bottomSheetDescription({ className })} />
|
|
100
|
+
)
|
|
101
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ContextMenu as ContextMenuPrimitive } from '@base-ui/react/context-menu'
|
|
4
|
+
import type { ComponentProps } from 'react'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
DropdownMenuCheckboxItem,
|
|
8
|
+
DropdownMenuGroup,
|
|
9
|
+
DropdownMenuItem,
|
|
10
|
+
DropdownMenuRadioGroup,
|
|
11
|
+
DropdownMenuRadioItem,
|
|
12
|
+
DropdownMenuSeparator,
|
|
13
|
+
DropdownMenuSub,
|
|
14
|
+
DropdownMenuSubContent,
|
|
15
|
+
DropdownMenuSubTrigger,
|
|
16
|
+
menuPanel,
|
|
17
|
+
type DropdownMenuCheckboxItemProps,
|
|
18
|
+
type DropdownMenuGroupProps,
|
|
19
|
+
type DropdownMenuItemProps,
|
|
20
|
+
type DropdownMenuRadioGroupProps,
|
|
21
|
+
type DropdownMenuRadioItemProps,
|
|
22
|
+
type DropdownMenuSeparatorProps,
|
|
23
|
+
type DropdownMenuSubContentProps,
|
|
24
|
+
type DropdownMenuSubProps,
|
|
25
|
+
type DropdownMenuSubTriggerProps,
|
|
26
|
+
} from '../dropdown-menu'
|
|
27
|
+
|
|
28
|
+
export type ContextMenuProps = ComponentProps<typeof ContextMenuPrimitive.Root>
|
|
29
|
+
|
|
30
|
+
export function ContextMenu(props: ContextMenuProps) {
|
|
31
|
+
return <ContextMenuPrimitive.Root {...props} />
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type ContextMenuTriggerProps = ComponentProps<typeof ContextMenuPrimitive.Trigger>
|
|
35
|
+
|
|
36
|
+
export function ContextMenuTrigger(props: ContextMenuTriggerProps) {
|
|
37
|
+
return <ContextMenuPrimitive.Trigger {...props} />
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type ContextMenuContentProps = Omit<ComponentProps<typeof ContextMenuPrimitive.Popup>, 'className'> & {
|
|
41
|
+
className?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function ContextMenuContent({ className, ...rest }: ContextMenuContentProps) {
|
|
45
|
+
return (
|
|
46
|
+
<ContextMenuPrimitive.Portal>
|
|
47
|
+
<ContextMenuPrimitive.Positioner className="z-overlay min-w-max will-change-transform">
|
|
48
|
+
<ContextMenuPrimitive.Popup {...rest} className={menuPanel({ className })} />
|
|
49
|
+
</ContextMenuPrimitive.Positioner>
|
|
50
|
+
</ContextMenuPrimitive.Portal>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type ContextMenuGroupProps = DropdownMenuGroupProps
|
|
55
|
+
|
|
56
|
+
export function ContextMenuGroup(props: ContextMenuGroupProps) {
|
|
57
|
+
return <DropdownMenuGroup {...props} />
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type ContextMenuItemProps = DropdownMenuItemProps
|
|
61
|
+
|
|
62
|
+
export function ContextMenuItem(props: ContextMenuItemProps) {
|
|
63
|
+
return <DropdownMenuItem {...props} />
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type ContextMenuCheckboxItemProps = DropdownMenuCheckboxItemProps
|
|
67
|
+
|
|
68
|
+
export function ContextMenuCheckboxItem(props: ContextMenuCheckboxItemProps) {
|
|
69
|
+
return <DropdownMenuCheckboxItem {...props} />
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type ContextMenuRadioGroupProps = DropdownMenuRadioGroupProps
|
|
73
|
+
|
|
74
|
+
export function ContextMenuRadioGroup(props: ContextMenuRadioGroupProps) {
|
|
75
|
+
return <DropdownMenuRadioGroup {...props} />
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type ContextMenuRadioItemProps = DropdownMenuRadioItemProps
|
|
79
|
+
|
|
80
|
+
export function ContextMenuRadioItem(props: ContextMenuRadioItemProps) {
|
|
81
|
+
return <DropdownMenuRadioItem {...props} />
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type ContextMenuSeparatorProps = DropdownMenuSeparatorProps
|
|
85
|
+
|
|
86
|
+
export function ContextMenuSeparator(props: ContextMenuSeparatorProps) {
|
|
87
|
+
return <DropdownMenuSeparator {...props} />
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type ContextMenuSubProps = DropdownMenuSubProps
|
|
91
|
+
|
|
92
|
+
export function ContextMenuSub(props: ContextMenuSubProps) {
|
|
93
|
+
return <DropdownMenuSub {...props} />
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type ContextMenuSubTriggerProps = DropdownMenuSubTriggerProps
|
|
97
|
+
|
|
98
|
+
export function ContextMenuSubTrigger(props: ContextMenuSubTriggerProps) {
|
|
99
|
+
return <DropdownMenuSubTrigger {...props} />
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type ContextMenuSubContentProps = DropdownMenuSubContentProps
|
|
103
|
+
|
|
104
|
+
export function ContextMenuSubContent(props: ContextMenuSubContentProps) {
|
|
105
|
+
return <DropdownMenuSubContent {...props} />
|
|
106
|
+
}
|
package/dropdown-menu/index.tsx
CHANGED
|
@@ -39,12 +39,13 @@ export function DropdownMenuTrigger(props: DropdownMenuTriggerProps) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export type DropdownMenuContentProps = Omit<ComponentProps<typeof MenuPrimitive.Popup>, 'className'> & {
|
|
42
|
+
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
42
43
|
align?: 'start' | 'center' | 'end'
|
|
43
44
|
arrow?: boolean
|
|
44
45
|
className?: string
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
const menuPanel = tv({ base: PANEL })
|
|
48
|
+
export const menuPanel = tv({ base: PANEL })
|
|
48
49
|
|
|
49
50
|
const menuArrow = tv({
|
|
50
51
|
base: 'data-uncentered:invisible'
|
|
@@ -55,6 +56,7 @@ const menuArrow = tv({
|
|
|
55
56
|
})
|
|
56
57
|
|
|
57
58
|
export function DropdownMenuContent({
|
|
59
|
+
side = 'bottom',
|
|
58
60
|
align = 'start',
|
|
59
61
|
arrow = true,
|
|
60
62
|
className,
|
|
@@ -64,6 +66,7 @@ export function DropdownMenuContent({
|
|
|
64
66
|
return (
|
|
65
67
|
<MenuPrimitive.Portal>
|
|
66
68
|
<MenuPrimitive.Positioner
|
|
69
|
+
side={side}
|
|
67
70
|
align={align}
|
|
68
71
|
sideOffset={arrow ? 14 : 8}
|
|
69
72
|
arrowPadding={16}
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -63,6 +63,30 @@
|
|
|
63
63
|
"radius": "rounded-r20"
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
+
{
|
|
67
|
+
"name": "avatar",
|
|
68
|
+
"type": "registry:ui",
|
|
69
|
+
"title": "Avatar",
|
|
70
|
+
"files": [
|
|
71
|
+
{
|
|
72
|
+
"path": "avatar/index.tsx",
|
|
73
|
+
"type": "registry:ui"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"path": "avatar/index.module.css",
|
|
77
|
+
"type": "registry:ui"
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"dependencies": [
|
|
81
|
+
"@base-ui/react"
|
|
82
|
+
],
|
|
83
|
+
"registryDependencies": [
|
|
84
|
+
"tv"
|
|
85
|
+
],
|
|
86
|
+
"meta": {
|
|
87
|
+
"layer": "ui"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
66
90
|
{
|
|
67
91
|
"name": "badge",
|
|
68
92
|
"type": "registry:ui",
|
|
@@ -83,6 +107,30 @@
|
|
|
83
107
|
]
|
|
84
108
|
}
|
|
85
109
|
},
|
|
110
|
+
{
|
|
111
|
+
"name": "bottom-sheet",
|
|
112
|
+
"type": "registry:ui",
|
|
113
|
+
"title": "BottomSheet",
|
|
114
|
+
"files": [
|
|
115
|
+
{
|
|
116
|
+
"path": "bottom-sheet/index.tsx",
|
|
117
|
+
"type": "registry:ui"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"path": "bottom-sheet/index.module.css",
|
|
121
|
+
"type": "registry:ui"
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
"dependencies": [
|
|
125
|
+
"@base-ui/react"
|
|
126
|
+
],
|
|
127
|
+
"registryDependencies": [
|
|
128
|
+
"tv"
|
|
129
|
+
],
|
|
130
|
+
"meta": {
|
|
131
|
+
"layer": "ui"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
86
134
|
{
|
|
87
135
|
"name": "button",
|
|
88
136
|
"type": "registry:ui",
|
|
@@ -186,6 +234,32 @@
|
|
|
186
234
|
"layer": "derived"
|
|
187
235
|
}
|
|
188
236
|
},
|
|
237
|
+
{
|
|
238
|
+
"name": "context-menu",
|
|
239
|
+
"type": "registry:ui",
|
|
240
|
+
"title": "ContextMenu",
|
|
241
|
+
"files": [
|
|
242
|
+
{
|
|
243
|
+
"path": "context-menu/index.tsx",
|
|
244
|
+
"type": "registry:ui"
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
"dependencies": [
|
|
248
|
+
"@base-ui/react"
|
|
249
|
+
],
|
|
250
|
+
"registryDependencies": [
|
|
251
|
+
"dropdown-menu"
|
|
252
|
+
],
|
|
253
|
+
"meta": {
|
|
254
|
+
"layer": "derived",
|
|
255
|
+
"sizes": [
|
|
256
|
+
"ContextMenuItem",
|
|
257
|
+
"ContextMenuCheckboxItem",
|
|
258
|
+
"ContextMenuRadioItem",
|
|
259
|
+
"ContextMenuSubTrigger"
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
},
|
|
189
263
|
{
|
|
190
264
|
"name": "dialog",
|
|
191
265
|
"type": "registry:ui",
|
|
@@ -148,12 +148,15 @@ className 그 한 곳만 바뀐다
|
|
|
148
148
|
|---|---|---|---|
|
|
149
149
|
| Accordion | accordion | Accordion, AccordionItem, AccordionTrigger, AccordionContent | - |
|
|
150
150
|
| AlertDialog | alert-dialog | AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogTitle, AlertDialogDescription, AlertDialogHeader, AlertDialogFooter, AlertDialogAction, AlertDialogCancel | - |
|
|
151
|
-
|
|
|
151
|
+
| Avatar | avatar | Avatar | color, size |
|
|
152
|
+
| Badge | badge | Badge | color, emphasis, shape |
|
|
153
|
+
| BottomSheet | bottom-sheet | BottomSheet, BottomSheetTrigger, BottomSheetClose, BottomSheetContent, BottomSheetHeader, BottomSheetTitle, BottomSheetDescription | - |
|
|
152
154
|
| Button | button | Button | color, emphasis, size, shape |
|
|
153
155
|
| Callout | callout | Callout | color |
|
|
154
156
|
| Checkbox | checkbox | CheckboxGroup, Checkbox | color |
|
|
155
157
|
| Combobox | combobox | Combobox, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxCancel, ComboboxContent, ComboboxViewport, ComboboxCollection, ComboboxItem, ComboboxItemText, ComboboxItemDescription, ComboboxGroup, ComboboxEmpty, ComboboxSeparator | surface, size |
|
|
156
158
|
| CommandPalette | command-palette | CommandPalette | - |
|
|
159
|
+
| ContextMenu | context-menu | ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent | color |
|
|
157
160
|
| Dialog | dialog | Dialog, DialogTrigger, DialogClose, DialogContent, DialogTitle, DialogDescription, DialogHeader, DialogFooter | size |
|
|
158
161
|
| Drawer | drawer | Drawer, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription | side |
|
|
159
162
|
| DropdownMenu | dropdown-menu | DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent | color |
|
|
@@ -176,16 +179,20 @@ className 그 한 곳만 바뀐다
|
|
|
176
179
|
컴포넌트를 고를 때 헷갈리는 짝은 이렇다.
|
|
177
180
|
|
|
178
181
|
```
|
|
179
|
-
Dialog와 AlertDialog
|
|
180
|
-
Dialog와 Drawer
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
Dialog와 AlertDialog 내용을 보여 주면 Dialog, 되돌릴 수 없는 일을 한 번 더 물으면 AlertDialog
|
|
183
|
+
Dialog와 Drawer 화면 가운데면 Dialog, 가장자리에서 열리면 Drawer
|
|
184
|
+
Drawer와 BottomSheet 왼쪽이나 오른쪽에서 열리면 Drawer, 아래에서 올라오면 BottomSheet
|
|
185
|
+
DropdownMenu와 ContextMenu 트리거를 누르면 DropdownMenu, 영역을 오른쪽 클릭하거나 길게 누르면 ContextMenu
|
|
186
|
+
Select와 Combobox 목록에서 고르면 Select, 입력해서 찾으면 Combobox
|
|
187
|
+
Checkbox와 Switch 여러 개를 고르면 Checkbox, 설정을 바로 켜고 끄면 Switch
|
|
188
|
+
Toggle과 Switch 도구 모음에서 상태를 누르면 Toggle, 설정이면 Switch
|
|
189
|
+
Badge와 Button 누를 수 없으면 Badge, 누르면 Button
|
|
190
|
+
Tabs와 Accordion 한 번에 하나만 보이면 Tabs, 여러 개를 펴 두면 Accordion
|
|
191
|
+
Tooltip과 Popover 짧은 설명이면 Tooltip, 안에서 조작하면 Popover
|
|
187
192
|
```
|
|
188
193
|
|
|
194
|
+
Avatar의 배경색은 color나 seed 중 하나로 정한다. seed에는 사용자 id처럼 바뀌지 않는 값을 넘기고, 같은 seed는 어느 앱에서나 같은 색이다.
|
|
195
|
+
|
|
189
196
|
Field는 라벨, 도움말, 오류 메시지를 입력 컨트롤에 묶는다. Input, Textarea, NumberField, Select, Combobox와 함께 쓴다.
|
|
190
197
|
|
|
191
198
|
Select는 고른 항목의 `SelectItemText`나 글자를 트리거에 보인다. `SelectItem`을 다른 컴포넌트로 감싸면 `items`로 라벨을 넘긴다.
|
package/tokens.css
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
--pds-fg-danger-contrast: var(--pds-red-700);
|
|
34
34
|
--pds-fg-info: var(--pds-blue-500);
|
|
35
35
|
--pds-fg-info-contrast: var(--pds-blue-700);
|
|
36
|
+
--pds-fg-info-solid-contrast: var(--pds-gray-50);
|
|
36
37
|
--pds-fg-neutral-solid-contrast: var(--pds-gray-50);
|
|
37
38
|
--pds-fg-success: var(--pds-green-500);
|
|
38
39
|
--pds-fg-success-contrast: var(--pds-green-700);
|
|
@@ -97,6 +98,7 @@
|
|
|
97
98
|
--pds-fg-danger-contrast: var(--pds-red-700);
|
|
98
99
|
--pds-fg-info: var(--pds-blue-500);
|
|
99
100
|
--pds-fg-info-contrast: var(--pds-blue-700);
|
|
101
|
+
--pds-fg-info-solid-contrast: var(--pds-gray-900);
|
|
100
102
|
--pds-fg-neutral-solid-contrast: var(--pds-gray-50);
|
|
101
103
|
--pds-fg-success: var(--pds-green-500);
|
|
102
104
|
--pds-fg-success-contrast: var(--pds-green-700);
|
|
@@ -159,6 +161,7 @@
|
|
|
159
161
|
--color-fg-danger-contrast: var(--pds-fg-danger-contrast);
|
|
160
162
|
--color-fg-info: var(--pds-fg-info);
|
|
161
163
|
--color-fg-info-contrast: var(--pds-fg-info-contrast);
|
|
164
|
+
--color-fg-info-solid-contrast: var(--pds-fg-info-solid-contrast);
|
|
162
165
|
--color-fg-neutral-solid-contrast: var(--pds-fg-neutral-solid-contrast);
|
|
163
166
|
--color-fg-success: var(--pds-fg-success);
|
|
164
167
|
--color-fg-success-contrast: var(--pds-fg-success-contrast);
|
|
@@ -276,3 +279,6 @@
|
|
|
276
279
|
|
|
277
280
|
:root, .light { --pds-pressed-dim: rgba(0,23,51,0.12); }
|
|
278
281
|
.dark { --pds-pressed-dim: rgba(209,209,253,0.14); }
|
|
282
|
+
|
|
283
|
+
:root, .light { --pds-avatar-seed-bg: 0.92 0.06; --pds-avatar-seed-fg: 0.42 0.13; }
|
|
284
|
+
.dark { --pds-avatar-seed-bg: 0.38 0.08; --pds-avatar-seed-fg: 0.9 0.07; }
|