@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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -2
  3. package/package.json +51 -3
  4. package/src/components/avatar.tsx +35 -0
  5. package/src/components/badge.tsx +42 -0
  6. package/src/components/breadcrumb.tsx +102 -0
  7. package/src/components/button.tsx +56 -0
  8. package/src/components/card.tsx +92 -0
  9. package/src/components/checkbox.tsx +27 -0
  10. package/src/components/color-field.tsx +248 -0
  11. package/src/components/color-swatch.tsx +52 -0
  12. package/src/components/context-menu.tsx +249 -0
  13. package/src/components/dialog.tsx +137 -0
  14. package/src/components/dropdown-menu.tsx +249 -0
  15. package/src/components/empty-state.tsx +32 -0
  16. package/src/components/input.tsx +20 -0
  17. package/src/components/label.tsx +18 -0
  18. package/src/components/navigation-menu.tsx +144 -0
  19. package/src/components/number-field.tsx +428 -0
  20. package/src/components/popover.tsx +82 -0
  21. package/src/components/select.tsx +201 -0
  22. package/src/components/separator.tsx +25 -0
  23. package/src/components/sheet.tsx +124 -0
  24. package/src/components/side-panel.tsx +166 -0
  25. package/src/components/sidebar.tsx +735 -0
  26. package/src/components/skeleton.tsx +14 -0
  27. package/src/components/slider.tsx +75 -0
  28. package/src/components/snackbar.tsx +141 -0
  29. package/src/components/status-bar.tsx +38 -0
  30. package/src/components/tabs.tsx +78 -0
  31. package/src/components/textarea.tsx +18 -0
  32. package/src/components/toggle-group.tsx +86 -0
  33. package/src/components/toggle.tsx +43 -0
  34. package/src/components/toolbar.tsx +307 -0
  35. package/src/components/tooltip.tsx +54 -0
  36. package/src/graphics/adsr.ts +70 -0
  37. package/src/graphics/curve-editor.tsx +525 -0
  38. package/src/graphics/curve-edits.ts +129 -0
  39. package/src/graphics/index.ts +16 -0
  40. package/src/graphics/scale.ts +22 -0
  41. package/src/graphics/use-canvas.ts +56 -0
  42. package/src/graphics/use-tokens.ts +32 -0
  43. package/src/icons.ts +3 -0
  44. package/src/index.css +199 -0
  45. package/src/index.ts +37 -0
  46. package/src/lib/use-mobile.ts +18 -0
  47. 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
+ }