@preset.nz/ux-kit 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +90 -2
- package/package.json +51 -3
- package/src/components/avatar.tsx +35 -0
- package/src/components/badge.tsx +42 -0
- package/src/components/breadcrumb.tsx +102 -0
- package/src/components/button.tsx +56 -0
- package/src/components/card.tsx +92 -0
- package/src/components/checkbox.tsx +27 -0
- package/src/components/color-field.tsx +248 -0
- package/src/components/color-swatch.tsx +52 -0
- package/src/components/context-menu.tsx +249 -0
- package/src/components/dialog.tsx +137 -0
- package/src/components/dropdown-menu.tsx +249 -0
- package/src/components/empty-state.tsx +32 -0
- package/src/components/input.tsx +20 -0
- package/src/components/label.tsx +18 -0
- package/src/components/navigation-menu.tsx +144 -0
- package/src/components/number-field.tsx +428 -0
- package/src/components/popover.tsx +82 -0
- package/src/components/select.tsx +201 -0
- package/src/components/separator.tsx +25 -0
- package/src/components/sheet.tsx +124 -0
- package/src/components/side-panel.tsx +166 -0
- package/src/components/sidebar.tsx +735 -0
- package/src/components/skeleton.tsx +14 -0
- package/src/components/slider.tsx +75 -0
- package/src/components/snackbar.tsx +141 -0
- package/src/components/status-bar.tsx +38 -0
- package/src/components/tabs.tsx +78 -0
- package/src/components/textarea.tsx +18 -0
- package/src/components/toggle-group.tsx +86 -0
- package/src/components/toggle.tsx +43 -0
- package/src/components/toolbar.tsx +307 -0
- package/src/components/tooltip.tsx +54 -0
- package/src/graphics/adsr.ts +70 -0
- package/src/graphics/curve-editor.tsx +525 -0
- package/src/graphics/curve-edits.ts +129 -0
- package/src/graphics/index.ts +16 -0
- package/src/graphics/scale.ts +22 -0
- package/src/graphics/use-canvas.ts +56 -0
- package/src/graphics/use-tokens.ts +32 -0
- package/src/icons.ts +3 -0
- package/src/index.css +199 -0
- package/src/index.ts +37 -0
- package/src/lib/use-mobile.ts +18 -0
- package/src/lib/utils.ts +6 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Menu as MenuPrimitive } from "@base-ui/react/menu"
|
|
3
|
+
import { CaretRightIcon, CheckIcon } from "@phosphor-icons/react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
|
|
7
|
+
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
|
|
8
|
+
return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
|
|
12
|
+
return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function DropdownMenuTrigger({ className, ...props }: MenuPrimitive.Trigger.Props) {
|
|
16
|
+
return (
|
|
17
|
+
<MenuPrimitive.Trigger
|
|
18
|
+
data-slot="dropdown-menu-trigger"
|
|
19
|
+
className={cn("select-none", className)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function DropdownMenuContent({
|
|
26
|
+
align = "start",
|
|
27
|
+
alignOffset = 0,
|
|
28
|
+
side = "bottom",
|
|
29
|
+
sideOffset = 4,
|
|
30
|
+
className,
|
|
31
|
+
...props
|
|
32
|
+
}: MenuPrimitive.Popup.Props &
|
|
33
|
+
Pick<MenuPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">) {
|
|
34
|
+
return (
|
|
35
|
+
<MenuPrimitive.Portal>
|
|
36
|
+
<MenuPrimitive.Positioner
|
|
37
|
+
className="isolate z-50 outline-none"
|
|
38
|
+
align={align}
|
|
39
|
+
alignOffset={alignOffset}
|
|
40
|
+
side={side}
|
|
41
|
+
sideOffset={sideOffset}
|
|
42
|
+
>
|
|
43
|
+
<MenuPrimitive.Popup
|
|
44
|
+
data-slot="dropdown-menu-content"
|
|
45
|
+
className={cn(
|
|
46
|
+
"z-50 max-h-(--available-height) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
</MenuPrimitive.Positioner>
|
|
52
|
+
</MenuPrimitive.Portal>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
|
|
57
|
+
return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function DropdownMenuLabel({
|
|
61
|
+
className,
|
|
62
|
+
inset,
|
|
63
|
+
...props
|
|
64
|
+
}: MenuPrimitive.GroupLabel.Props & { inset?: boolean }) {
|
|
65
|
+
return (
|
|
66
|
+
<MenuPrimitive.GroupLabel
|
|
67
|
+
data-slot="dropdown-menu-label"
|
|
68
|
+
data-inset={inset}
|
|
69
|
+
className={cn(
|
|
70
|
+
"px-2 py-1.5 text-xs font-medium text-muted-foreground data-inset:pl-8",
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function DropdownMenuItem({
|
|
79
|
+
className,
|
|
80
|
+
inset,
|
|
81
|
+
variant = "default",
|
|
82
|
+
...props
|
|
83
|
+
}: MenuPrimitive.Item.Props & {
|
|
84
|
+
inset?: boolean
|
|
85
|
+
variant?: "default" | "destructive"
|
|
86
|
+
}) {
|
|
87
|
+
return (
|
|
88
|
+
<MenuPrimitive.Item
|
|
89
|
+
data-slot="dropdown-menu-item"
|
|
90
|
+
data-inset={inset}
|
|
91
|
+
data-variant={variant}
|
|
92
|
+
className={cn(
|
|
93
|
+
"group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground data-inset:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
|
|
94
|
+
className
|
|
95
|
+
)}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
|
|
102
|
+
return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function DropdownMenuSubTrigger({
|
|
106
|
+
className,
|
|
107
|
+
inset,
|
|
108
|
+
children,
|
|
109
|
+
...props
|
|
110
|
+
}: MenuPrimitive.SubmenuTrigger.Props & { inset?: boolean }) {
|
|
111
|
+
return (
|
|
112
|
+
<MenuPrimitive.SubmenuTrigger
|
|
113
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
114
|
+
data-inset={inset}
|
|
115
|
+
className={cn(
|
|
116
|
+
"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground data-inset:pl-8 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
117
|
+
className
|
|
118
|
+
)}
|
|
119
|
+
{...props}
|
|
120
|
+
>
|
|
121
|
+
{children}
|
|
122
|
+
<CaretRightIcon className="ml-auto" />
|
|
123
|
+
</MenuPrimitive.SubmenuTrigger>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function DropdownMenuSubContent({
|
|
128
|
+
align = "start",
|
|
129
|
+
alignOffset = -3,
|
|
130
|
+
side = "right",
|
|
131
|
+
sideOffset = 0,
|
|
132
|
+
className,
|
|
133
|
+
...props
|
|
134
|
+
}: React.ComponentProps<typeof DropdownMenuContent>) {
|
|
135
|
+
return (
|
|
136
|
+
<DropdownMenuContent
|
|
137
|
+
data-slot="dropdown-menu-sub-content"
|
|
138
|
+
className={cn("min-w-32 shadow-lg", className)}
|
|
139
|
+
align={align}
|
|
140
|
+
alignOffset={alignOffset}
|
|
141
|
+
side={side}
|
|
142
|
+
sideOffset={sideOffset}
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function DropdownMenuCheckboxItem({
|
|
149
|
+
className,
|
|
150
|
+
children,
|
|
151
|
+
inset,
|
|
152
|
+
...props
|
|
153
|
+
}: MenuPrimitive.CheckboxItem.Props & { inset?: boolean }) {
|
|
154
|
+
return (
|
|
155
|
+
<MenuPrimitive.CheckboxItem
|
|
156
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
157
|
+
data-inset={inset}
|
|
158
|
+
className={cn(
|
|
159
|
+
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground data-inset:pl-8 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
160
|
+
className
|
|
161
|
+
)}
|
|
162
|
+
{...props}
|
|
163
|
+
>
|
|
164
|
+
<span
|
|
165
|
+
className="pointer-events-none absolute right-2 flex items-center justify-center"
|
|
166
|
+
data-slot="dropdown-menu-checkbox-item-indicator"
|
|
167
|
+
>
|
|
168
|
+
<MenuPrimitive.CheckboxItemIndicator>
|
|
169
|
+
<CheckIcon />
|
|
170
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
171
|
+
</span>
|
|
172
|
+
{children}
|
|
173
|
+
</MenuPrimitive.CheckboxItem>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
|
|
178
|
+
return <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function DropdownMenuRadioItem({
|
|
182
|
+
className,
|
|
183
|
+
children,
|
|
184
|
+
inset,
|
|
185
|
+
...props
|
|
186
|
+
}: MenuPrimitive.RadioItem.Props & { inset?: boolean }) {
|
|
187
|
+
return (
|
|
188
|
+
<MenuPrimitive.RadioItem
|
|
189
|
+
data-slot="dropdown-menu-radio-item"
|
|
190
|
+
data-inset={inset}
|
|
191
|
+
className={cn(
|
|
192
|
+
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground data-inset:pl-8 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
193
|
+
className
|
|
194
|
+
)}
|
|
195
|
+
{...props}
|
|
196
|
+
>
|
|
197
|
+
<span
|
|
198
|
+
className="pointer-events-none absolute right-2 flex items-center justify-center"
|
|
199
|
+
data-slot="dropdown-menu-radio-item-indicator"
|
|
200
|
+
>
|
|
201
|
+
<MenuPrimitive.RadioItemIndicator>
|
|
202
|
+
<CheckIcon />
|
|
203
|
+
</MenuPrimitive.RadioItemIndicator>
|
|
204
|
+
</span>
|
|
205
|
+
{children}
|
|
206
|
+
</MenuPrimitive.RadioItem>
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {
|
|
211
|
+
return (
|
|
212
|
+
<MenuPrimitive.Separator
|
|
213
|
+
data-slot="dropdown-menu-separator"
|
|
214
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
215
|
+
{...props}
|
|
216
|
+
/>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
|
|
221
|
+
return (
|
|
222
|
+
<span
|
|
223
|
+
data-slot="dropdown-menu-shortcut"
|
|
224
|
+
className={cn(
|
|
225
|
+
"ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
|
|
226
|
+
className
|
|
227
|
+
)}
|
|
228
|
+
{...props}
|
|
229
|
+
/>
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export {
|
|
234
|
+
DropdownMenu,
|
|
235
|
+
DropdownMenuPortal,
|
|
236
|
+
DropdownMenuTrigger,
|
|
237
|
+
DropdownMenuContent,
|
|
238
|
+
DropdownMenuGroup,
|
|
239
|
+
DropdownMenuLabel,
|
|
240
|
+
DropdownMenuItem,
|
|
241
|
+
DropdownMenuCheckboxItem,
|
|
242
|
+
DropdownMenuRadioGroup,
|
|
243
|
+
DropdownMenuRadioItem,
|
|
244
|
+
DropdownMenuSeparator,
|
|
245
|
+
DropdownMenuShortcut,
|
|
246
|
+
DropdownMenuSub,
|
|
247
|
+
DropdownMenuSubTrigger,
|
|
248
|
+
DropdownMenuSubContent,
|
|
249
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils"
|
|
4
|
+
|
|
5
|
+
// EmptyState: what a view says when there is nothing to show: an icon, a
|
|
6
|
+
// title, one line of help and, if it helps, the action that fixes it.
|
|
7
|
+
|
|
8
|
+
interface EmptyStateProps extends Omit<React.ComponentProps<"div">, "title"> {
|
|
9
|
+
icon?: React.ReactNode
|
|
10
|
+
title: React.ReactNode
|
|
11
|
+
description?: React.ReactNode
|
|
12
|
+
/** Usually one Button. */
|
|
13
|
+
action?: React.ReactNode
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function EmptyState({ icon, title, description, action, className, ...props }: EmptyStateProps) {
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
data-slot="empty-state"
|
|
20
|
+
className={cn("flex h-full flex-col items-center justify-center gap-2 p-8 text-center", className)}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
{icon ? <div className="text-muted-foreground [&_svg]:size-8">{icon}</div> : null}
|
|
24
|
+
<p className="font-heading text-base font-medium">{title}</p>
|
|
25
|
+
{description ? <p className="max-w-xs text-sm text-muted-foreground">{description}</p> : null}
|
|
26
|
+
{action ? <div className="mt-2">{action}</div> : null}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { EmptyState }
|
|
32
|
+
export type { EmptyStateProps }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Input as InputPrimitive } from "@base-ui/react/input"
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
7
|
+
return (
|
|
8
|
+
<InputPrimitive
|
|
9
|
+
type={type}
|
|
10
|
+
data-slot="input"
|
|
11
|
+
className={cn(
|
|
12
|
+
"h-8 w-full min-w-0 rounded-none border border-input bg-transparent px-2.5 py-1 text-xs transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-xs dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { Input }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils"
|
|
4
|
+
|
|
5
|
+
function Label({ className, ...props }: React.ComponentProps<"label">) {
|
|
6
|
+
return (
|
|
7
|
+
<label
|
|
8
|
+
data-slot="label"
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex items-center gap-2 text-xs leading-none select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Label }
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu"
|
|
2
|
+
import { cva } from "class-variance-authority"
|
|
3
|
+
import { CaretDownIcon } from "@phosphor-icons/react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
|
|
7
|
+
// Base UI renders content through a shared Portal > Positioner > Popup >
|
|
8
|
+
// Viewport. NavigationMenu bundles that, so `NavigationMenuContent` items
|
|
9
|
+
// morph inside one popup like the shadcn viewport variant.
|
|
10
|
+
function NavigationMenu({
|
|
11
|
+
className,
|
|
12
|
+
children,
|
|
13
|
+
align = "start",
|
|
14
|
+
...props
|
|
15
|
+
}: NavigationMenuPrimitive.Root.Props & Pick<NavigationMenuPrimitive.Positioner.Props, "align">) {
|
|
16
|
+
return (
|
|
17
|
+
<NavigationMenuPrimitive.Root
|
|
18
|
+
data-slot="navigation-menu"
|
|
19
|
+
className={cn("group/navigation-menu relative flex max-w-max flex-1 items-center justify-center", className)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
<NavigationMenuPositioner align={align} />
|
|
24
|
+
</NavigationMenuPrimitive.Root>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function NavigationMenuList({ className, ...props }: NavigationMenuPrimitive.List.Props) {
|
|
29
|
+
return (
|
|
30
|
+
<NavigationMenuPrimitive.List
|
|
31
|
+
data-slot="navigation-menu-list"
|
|
32
|
+
className={cn("group flex flex-1 list-none items-center justify-center gap-1", className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function NavigationMenuItem({ className, ...props }: NavigationMenuPrimitive.Item.Props) {
|
|
39
|
+
return (
|
|
40
|
+
<NavigationMenuPrimitive.Item
|
|
41
|
+
data-slot="navigation-menu-item"
|
|
42
|
+
className={cn("relative", className)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const navigationMenuTriggerStyle = cva(
|
|
49
|
+
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium outline-none transition-[color,box-shadow] hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-accent/50 data-popup-open:text-accent-foreground data-popup-open:hover:bg-accent data-popup-open:focus:bg-accent"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
function NavigationMenuTrigger({
|
|
53
|
+
className,
|
|
54
|
+
children,
|
|
55
|
+
...props
|
|
56
|
+
}: NavigationMenuPrimitive.Trigger.Props) {
|
|
57
|
+
return (
|
|
58
|
+
<NavigationMenuPrimitive.Trigger
|
|
59
|
+
data-slot="navigation-menu-trigger"
|
|
60
|
+
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
|
61
|
+
{...props}
|
|
62
|
+
>
|
|
63
|
+
{children}{" "}
|
|
64
|
+
<NavigationMenuPrimitive.Icon
|
|
65
|
+
render={
|
|
66
|
+
<CaretDownIcon
|
|
67
|
+
className="relative top-px ml-1 size-3 transition duration-300 group-data-popup-open:rotate-180"
|
|
68
|
+
aria-hidden="true"
|
|
69
|
+
/>
|
|
70
|
+
}
|
|
71
|
+
/>
|
|
72
|
+
</NavigationMenuPrimitive.Trigger>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function NavigationMenuContent({ className, ...props }: NavigationMenuPrimitive.Content.Props) {
|
|
77
|
+
return (
|
|
78
|
+
<NavigationMenuPrimitive.Content
|
|
79
|
+
data-slot="navigation-menu-content"
|
|
80
|
+
className={cn(
|
|
81
|
+
"w-full p-2 pr-2.5 transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0 md:w-auto **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
|
|
82
|
+
className
|
|
83
|
+
)}
|
|
84
|
+
{...props}
|
|
85
|
+
/>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function NavigationMenuPositioner({
|
|
90
|
+
className,
|
|
91
|
+
side = "bottom",
|
|
92
|
+
sideOffset = 6,
|
|
93
|
+
align = "start",
|
|
94
|
+
alignOffset = 0,
|
|
95
|
+
...props
|
|
96
|
+
}: NavigationMenuPrimitive.Positioner.Props) {
|
|
97
|
+
return (
|
|
98
|
+
<NavigationMenuPrimitive.Portal>
|
|
99
|
+
<NavigationMenuPrimitive.Positioner
|
|
100
|
+
side={side}
|
|
101
|
+
sideOffset={sideOffset}
|
|
102
|
+
align={align}
|
|
103
|
+
alignOffset={alignOffset}
|
|
104
|
+
className={cn(
|
|
105
|
+
"isolate z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-200",
|
|
106
|
+
className
|
|
107
|
+
)}
|
|
108
|
+
{...props}
|
|
109
|
+
>
|
|
110
|
+
<NavigationMenuPrimitive.Popup
|
|
111
|
+
data-slot="navigation-menu-popup"
|
|
112
|
+
className="relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) overflow-hidden rounded-md border bg-popover text-popover-foreground shadow transition-[width,height,opacity,scale] duration-200 data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
|
|
113
|
+
>
|
|
114
|
+
<NavigationMenuPrimitive.Viewport data-slot="navigation-menu-viewport" className="relative size-full overflow-hidden" />
|
|
115
|
+
</NavigationMenuPrimitive.Popup>
|
|
116
|
+
</NavigationMenuPrimitive.Positioner>
|
|
117
|
+
</NavigationMenuPrimitive.Portal>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// `active` marks the current page; plain anchor unless `render` is given.
|
|
122
|
+
function NavigationMenuLink({ className, ...props }: NavigationMenuPrimitive.Link.Props) {
|
|
123
|
+
return (
|
|
124
|
+
<NavigationMenuPrimitive.Link
|
|
125
|
+
data-slot="navigation-menu-link"
|
|
126
|
+
className={cn(
|
|
127
|
+
"flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 data-active:bg-accent/50 data-active:text-accent-foreground data-active:hover:bg-accent data-active:focus:bg-accent [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
128
|
+
className
|
|
129
|
+
)}
|
|
130
|
+
{...props}
|
|
131
|
+
/>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export {
|
|
136
|
+
NavigationMenu,
|
|
137
|
+
NavigationMenuList,
|
|
138
|
+
NavigationMenuItem,
|
|
139
|
+
NavigationMenuContent,
|
|
140
|
+
NavigationMenuTrigger,
|
|
141
|
+
NavigationMenuLink,
|
|
142
|
+
NavigationMenuPositioner,
|
|
143
|
+
navigationMenuTriggerStyle,
|
|
144
|
+
}
|