@preset.nz/ux-kit 0.0.0-stage → 0.1.1

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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +117 -2
  3. package/THIRD_PARTY_NOTICES.md +253 -0
  4. package/package.json +50 -3
  5. package/src/chat/index.ts +9 -0
  6. package/src/chat/message.tsx +80 -0
  7. package/src/chat/prompt-input.tsx +121 -0
  8. package/src/chat/reasoning.tsx +182 -0
  9. package/src/chat/scroller.tsx +147 -0
  10. package/src/chat/suggestion.tsx +65 -0
  11. package/src/chat/tool.tsx +143 -0
  12. package/src/chat/types.ts +8 -0
  13. package/src/components/avatar.tsx +35 -0
  14. package/src/components/badge.tsx +40 -0
  15. package/src/components/breadcrumb.tsx +100 -0
  16. package/src/components/button.tsx +56 -0
  17. package/src/components/card.tsx +75 -0
  18. package/src/components/checkbox.tsx +25 -0
  19. package/src/components/color-field.tsx +261 -0
  20. package/src/components/color-swatch.tsx +52 -0
  21. package/src/components/context-menu.tsx +249 -0
  22. package/src/components/dialog.tsx +133 -0
  23. package/src/components/dropdown-menu.tsx +249 -0
  24. package/src/components/empty-state.tsx +35 -0
  25. package/src/components/input.tsx +20 -0
  26. package/src/components/label.tsx +19 -0
  27. package/src/components/navigation-menu.tsx +150 -0
  28. package/src/components/number-field.tsx +432 -0
  29. package/src/components/popover.tsx +75 -0
  30. package/src/components/select.tsx +189 -0
  31. package/src/components/separator.tsx +21 -0
  32. package/src/components/sheet.tsx +132 -0
  33. package/src/components/side-panel.tsx +189 -0
  34. package/src/components/sidebar.tsx +698 -0
  35. package/src/components/skeleton.tsx +14 -0
  36. package/src/components/slider.tsx +71 -0
  37. package/src/components/snackbar.tsx +143 -0
  38. package/src/components/status-bar.tsx +45 -0
  39. package/src/components/tabs.tsx +75 -0
  40. package/src/components/textarea.tsx +18 -0
  41. package/src/components/toggle-group.tsx +86 -0
  42. package/src/components/toggle.tsx +43 -0
  43. package/src/components/toolbar.tsx +304 -0
  44. package/src/components/tooltip.tsx +54 -0
  45. package/src/graphics/adsr.ts +78 -0
  46. package/src/graphics/curve-editor.tsx +567 -0
  47. package/src/graphics/curve-edits.ts +146 -0
  48. package/src/graphics/index.ts +16 -0
  49. package/src/graphics/scale.ts +25 -0
  50. package/src/graphics/use-canvas.ts +56 -0
  51. package/src/graphics/use-tokens.ts +35 -0
  52. package/src/icons.ts +3 -0
  53. package/src/index.css +199 -0
  54. package/src/index.ts +35 -0
  55. package/src/lib/use-mobile.ts +18 -0
  56. package/src/lib/utils.ts +6 -0
  57. package/src/shadcn-tailwind.css +665 -0
@@ -0,0 +1,71 @@
1
+ import { Slider as SliderPrimitive } from "@base-ui/react/slider"
2
+ import * as React from "react"
3
+
4
+ import { cn } from "../lib/utils"
5
+
6
+ // Pass a number for one thumb or an array for a range; one thumb per value.
7
+ // While a thumb is dragged the slider shows only its own value and ignores incoming `value`
8
+ // props (an app that round-trips every change would fight the pointer with stale values); on
9
+ // release it re-syncs to the prop. The track and thumb are neutral, not the accent: a slider
10
+ // is read at a glance and a coloured fill is noise next to the fields around it.
11
+ function Slider({
12
+ className,
13
+ defaultValue,
14
+ value: valueProp,
15
+ onValueChange,
16
+ onValueCommitted,
17
+ min = 0,
18
+ max = 100,
19
+ ...props
20
+ }: SliderPrimitive.Root.Props) {
21
+ const [live, setLive] = React.useState<typeof valueProp | null>(null)
22
+ const value = live ?? valueProp
23
+ const _values = React.useMemo(
24
+ () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min]),
25
+ [value, defaultValue, min],
26
+ )
27
+
28
+ return (
29
+ <SliderPrimitive.Root
30
+ data-slot="slider"
31
+ defaultValue={defaultValue}
32
+ value={value}
33
+ onValueChange={(v, d) => {
34
+ if (valueProp !== undefined) setLive(v)
35
+ onValueChange?.(v, d)
36
+ }}
37
+ onValueCommitted={(v, d) => {
38
+ setLive(null)
39
+ onValueCommitted?.(v, d)
40
+ }}
41
+ min={min}
42
+ max={max}
43
+ thumbAlignment="edge"
44
+ className={cn("data-[orientation=vertical]:h-full", className)}
45
+ {...props}
46
+ >
47
+ <SliderPrimitive.Control className="relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col">
48
+ <SliderPrimitive.Track
49
+ data-slot="slider-track"
50
+ className="relative grow overflow-hidden rounded-none bg-muted select-none data-[orientation=horizontal]:h-1 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1"
51
+ >
52
+ <SliderPrimitive.Indicator
53
+ data-slot="slider-range"
54
+ className="bg-muted-foreground/50 select-none data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
55
+ />
56
+ </SliderPrimitive.Track>
57
+ {Array.from({ length: _values.length }, (_, index) => (
58
+ <SliderPrimitive.Thumb
59
+ data-slot="slider-thumb"
60
+ // biome-ignore lint/suspicious/noArrayIndexKey: thumbs are positional; the count is fixed by the value array and they have no identity
61
+ key={index}
62
+ index={index}
63
+ className="block h-4 w-2 shrink-0 rounded-[1px] border border-muted-foreground bg-background ring-ring/50 transition-[color,box-shadow] select-none hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden has-[:focus-visible]:ring-4 data-disabled:pointer-events-none data-disabled:opacity-50"
64
+ />
65
+ ))}
66
+ </SliderPrimitive.Control>
67
+ </SliderPrimitive.Root>
68
+ )
69
+ }
70
+
71
+ export { Slider }
@@ -0,0 +1,143 @@
1
+ import { Toast } from "@base-ui/react/toast"
2
+ import { CheckCircleIcon, WarningIcon, XCircleIcon, XIcon } from "@phosphor-icons/react"
3
+ import type * as React from "react"
4
+
5
+ import { cn } from "../lib/utils"
6
+
7
+ // The kit's one toast: Strata's snackbar, on Base UI Toast.
8
+ // One manager for the module, so `notify()` works outside React. Mount one provider per window.
9
+ const manager = Toast.createToastManager()
10
+
11
+ export function SnackbarProvider({ children }: { children: React.ReactNode }) {
12
+ return <Toast.Provider toastManager={manager}>{children}</Toast.Provider>
13
+ }
14
+
15
+ export function SnackbarViewport({ className }: { className?: string }) {
16
+ return (
17
+ <Toast.Viewport
18
+ className={cn(
19
+ "pointer-events-none absolute bottom-3 left-3 z-50 flex w-72 flex-col gap-2 outline-none",
20
+ className,
21
+ )}
22
+ >
23
+ <SnackbarList />
24
+ </Toast.Viewport>
25
+ )
26
+ }
27
+
28
+ export type SnackbarKind = "plain" | "success" | "warning" | "error"
29
+
30
+ // Colour per kind, from the kit's tokens. Warning fills with `--warning`; the others keep the
31
+ // popover surface and take the kind from the icon and a coloured leading edge.
32
+ const KIND: Record<SnackbarKind, { root: string; icon: React.ReactNode | null }> = {
33
+ plain: { root: "", icon: null },
34
+ success: {
35
+ root: "border-l-2 border-l-primary",
36
+ icon: <CheckCircleIcon weight="fill" className="mt-px size-4 shrink-0 text-primary" />,
37
+ },
38
+ warning: {
39
+ root: "border-warning bg-warning text-warning-foreground",
40
+ icon: <WarningIcon weight="fill" className="mt-px size-4 shrink-0" />,
41
+ },
42
+ error: {
43
+ root: "border-l-2 border-l-destructive",
44
+ icon: <XCircleIcon weight="fill" className="mt-px size-4 shrink-0 text-destructive" />,
45
+ },
46
+ }
47
+
48
+ function SnackbarList() {
49
+ const { toasts } = Toast.useToastManager()
50
+ return (
51
+ <>
52
+ {toasts.map((t) => {
53
+ const kind = KIND[(t.type as SnackbarKind | undefined) ?? "plain"] ?? KIND.plain
54
+ return (
55
+ <Toast.Root
56
+ key={t.id}
57
+ toast={t}
58
+ className={cn(
59
+ "pointer-events-auto flex items-start gap-2 rounded-sm border border-border bg-popover px-3 py-2 text-xs text-popover-foreground shadow-md outline-none",
60
+ "transition-opacity duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0",
61
+ kind.root,
62
+ )}
63
+ >
64
+ {kind.icon}
65
+ <div className="min-w-0 flex-1">
66
+ <Toast.Title className="font-medium">{t.title}</Toast.Title>
67
+ {t.description && (
68
+ <Toast.Description className="mt-0.5 opacity-80">{t.description}</Toast.Description>
69
+ )}
70
+ </div>
71
+ <Toast.Action
72
+ className={cn(
73
+ "inline-flex items-center rounded-sm px-2 py-0.5 text-xs font-medium",
74
+ "text-current/80 hover:bg-foreground/10 hover:text-current",
75
+ "focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
76
+ )}
77
+ />
78
+ <Toast.Close
79
+ aria-label="Dismiss"
80
+ className={cn(
81
+ "inline-flex size-4 items-center justify-center rounded-sm",
82
+ "opacity-60 hover:bg-foreground/10 hover:opacity-100",
83
+ "focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
84
+ )}
85
+ >
86
+ <XIcon weight="bold" className="size-3" />
87
+ </Toast.Close>
88
+ </Toast.Root>
89
+ )
90
+ })}
91
+ </>
92
+ )
93
+ }
94
+
95
+ export type SnackbarOptions = {
96
+ message: string
97
+ /** A second line under the message. */
98
+ description?: React.ReactNode
99
+ /** Plain by default. */
100
+ kind?: SnackbarKind
101
+ action?: { label: string; onClick: () => void }
102
+ /** Milliseconds before it goes; 5000 by default. */
103
+ timeout?: number
104
+ /** Stays until dismissed (`dismiss(id)` or its close button). Wins over `timeout`. */
105
+ persistent?: boolean
106
+ }
107
+
108
+ type Manager = Pick<ReturnType<typeof Toast.createToastManager>, "add" | "close">
109
+
110
+ const show = (m: Manager, opts: SnackbarOptions) =>
111
+ m.add({
112
+ title: opts.message,
113
+ description: opts.description,
114
+ type: opts.kind ?? "plain",
115
+ timeout: opts.persistent ? 0 : (opts.timeout ?? 5000),
116
+ actionProps: opts.action
117
+ ? { children: opts.action.label, onClick: opts.action.onClick }
118
+ : undefined,
119
+ })
120
+
121
+ // Must be called under a SnackbarProvider.
122
+ export function useSnackbar() {
123
+ const m = Toast.useToastManager()
124
+ return {
125
+ /** Returns the toast's id, for `dismiss`. */
126
+ show: (opts: SnackbarOptions) => show(m, opts),
127
+ /** One toast by id, or every toast with no id. */
128
+ dismiss: (id?: string) => m.close(id),
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Raise a snackbar from anywhere, no hook: command handlers, stores, event listeners. Needs a
134
+ * mounted SnackbarProvider to show it. Same options as `useSnackbar().show`; returns the id.
135
+ */
136
+ export function notify(opts: SnackbarOptions): string {
137
+ return show(manager, opts)
138
+ }
139
+
140
+ /** Dismiss one toast by id, or every toast with no id; the counterpart of `notify`. */
141
+ export function dismissNotification(id?: string) {
142
+ manager.close(id)
143
+ }
@@ -0,0 +1,45 @@
1
+ import type * as React from "react"
2
+
3
+ import { cn } from "../lib/utils"
4
+
5
+ // StatusBar: a thin bar at the bottom for document state, counts and transient
6
+ // messages (ux-patterns.md). `StatusBar` is the strip, `StatusItem` one cell,
7
+ // `StatusSpacer` pushes what follows to the right. Contents stay app-side.
8
+
9
+ function StatusBar({ className, ...props }: React.ComponentProps<"div">) {
10
+ return (
11
+ <div
12
+ data-slot="status-bar"
13
+ role="status"
14
+ className={cn(
15
+ "flex h-6 shrink-0 items-center gap-3 border-t border-border bg-background px-3 text-xs text-muted-foreground",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ interface StatusItemProps extends React.ComponentProps<"span"> {
24
+ /** Small leading label, e.g. "Nodes". */
25
+ label?: string
26
+ }
27
+
28
+ function StatusItem({ label, className, children, ...props }: StatusItemProps) {
29
+ return (
30
+ <span
31
+ data-slot="status-item"
32
+ className={cn("flex items-center gap-1 tabular-nums", className)}
33
+ {...props}
34
+ >
35
+ {label ? <span className="opacity-70">{label}</span> : null}
36
+ <span className="text-foreground">{children}</span>
37
+ </span>
38
+ )
39
+ }
40
+
41
+ function StatusSpacer({ className, ...props }: React.ComponentProps<"div">) {
42
+ return <div data-slot="status-spacer" className={cn("flex-1", className)} {...props} />
43
+ }
44
+
45
+ export { StatusBar, StatusItem, StatusSpacer }
@@ -0,0 +1,75 @@
1
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "../lib/utils"
5
+
6
+ function Tabs({ className, orientation = "horizontal", ...props }: TabsPrimitive.Root.Props) {
7
+ return (
8
+ <TabsPrimitive.Root
9
+ data-slot="tabs"
10
+ orientation={orientation}
11
+ className={cn("group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className)}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ const tabsListVariants = cva(
18
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none",
19
+ {
20
+ variants: {
21
+ variant: {
22
+ default: "bg-muted",
23
+ line: "gap-1 bg-transparent",
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: "default",
28
+ },
29
+ },
30
+ )
31
+
32
+ function TabsList({
33
+ className,
34
+ variant = "default",
35
+ ...props
36
+ }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
37
+ return (
38
+ <TabsPrimitive.List
39
+ data-slot="tabs-list"
40
+ data-variant={variant}
41
+ className={cn(tabsListVariants({ variant }), className)}
42
+ {...props}
43
+ />
44
+ )
45
+ }
46
+
47
+ // Base UI calls the trigger `Tab` and the content `Panel`; the kit keeps the
48
+ // shadcn names (TabsTrigger, TabsContent) so apps read the same.
49
+ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
50
+ return (
51
+ <TabsPrimitive.Tab
52
+ data-slot="tabs-trigger"
53
+ className={cn(
54
+ "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
55
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
56
+ "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
57
+ "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
58
+ className,
59
+ )}
60
+ {...props}
61
+ />
62
+ )
63
+ }
64
+
65
+ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
66
+ return (
67
+ <TabsPrimitive.Panel
68
+ data-slot="tabs-content"
69
+ className={cn("flex-1 text-sm outline-none", className)}
70
+ {...props}
71
+ />
72
+ )
73
+ }
74
+
75
+ export { Tabs, TabsContent, TabsList, TabsTrigger, tabsListVariants }
@@ -0,0 +1,18 @@
1
+ import type * as React from "react"
2
+
3
+ import { cn } from "../lib/utils"
4
+
5
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ "flex min-h-20 w-full rounded-none border border-input bg-transparent px-2.5 py-2 text-xs transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export { Textarea }
@@ -0,0 +1,86 @@
1
+ import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
2
+ import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group"
3
+ import type { VariantProps } from "class-variance-authority"
4
+ import * as React from "react"
5
+
6
+ import { cn } from "../lib/utils"
7
+ import { toggleVariants } from "./toggle"
8
+
9
+ const ToggleGroupContext = React.createContext<
10
+ VariantProps<typeof toggleVariants> & {
11
+ spacing?: number
12
+ orientation?: "horizontal" | "vertical"
13
+ }
14
+ >({
15
+ size: "default",
16
+ variant: "default",
17
+ spacing: 2,
18
+ orientation: "horizontal",
19
+ })
20
+
21
+ // Base UI: `value` is always an array and `multiple` replaces Radix's
22
+ // `type="single" | "multiple"`. Items are keyed by their `value` prop.
23
+ function ToggleGroup({
24
+ className,
25
+ variant,
26
+ size,
27
+ spacing = 2,
28
+ orientation = "horizontal",
29
+ children,
30
+ ...props
31
+ }: ToggleGroupPrimitive.Props &
32
+ VariantProps<typeof toggleVariants> & {
33
+ spacing?: number
34
+ }) {
35
+ return (
36
+ <ToggleGroupPrimitive
37
+ data-slot="toggle-group"
38
+ data-variant={variant}
39
+ data-size={size}
40
+ data-spacing={spacing}
41
+ orientation={orientation}
42
+ style={{ "--gap": spacing } as React.CSSProperties}
43
+ className={cn(
44
+ "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch",
45
+ className,
46
+ )}
47
+ {...props}
48
+ >
49
+ <ToggleGroupContext.Provider value={{ variant, size, spacing, orientation }}>
50
+ {children}
51
+ </ToggleGroupContext.Provider>
52
+ </ToggleGroupPrimitive>
53
+ )
54
+ }
55
+
56
+ function ToggleGroupItem({
57
+ className,
58
+ children,
59
+ variant = "default",
60
+ size = "default",
61
+ ...props
62
+ }: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
63
+ const context = React.useContext(ToggleGroupContext)
64
+
65
+ return (
66
+ <TogglePrimitive
67
+ data-slot="toggle-group-item"
68
+ data-variant={context.variant || variant}
69
+ data-size={context.size || size}
70
+ data-spacing={context.spacing}
71
+ className={cn(
72
+ "shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-[orientation=horizontal]/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-[orientation=vertical]/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-[orientation=horizontal]/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-[orientation=vertical]/toggle-group:data-[spacing=0]:last:rounded-b-md group-data-[orientation=horizontal]/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-[orientation=vertical]/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-[orientation=horizontal]/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-[orientation=vertical]/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
73
+ toggleVariants({
74
+ variant: context.variant || variant,
75
+ size: context.size || size,
76
+ }),
77
+ className,
78
+ )}
79
+ {...props}
80
+ >
81
+ {children}
82
+ </TogglePrimitive>
83
+ )
84
+ }
85
+
86
+ export { ToggleGroup, ToggleGroupItem }
@@ -0,0 +1,43 @@
1
+ import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "../lib/utils"
5
+
6
+ const toggleVariants = cva(
7
+ "group/toggle inline-flex items-center justify-center gap-1 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-pressed:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-transparent",
12
+ outline: "border border-input bg-transparent shadow-xs hover:bg-muted",
13
+ },
14
+ size: {
15
+ default:
16
+ "h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
17
+ sm: "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
18
+ lg: "h-10 min-w-10 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ variant: "default",
23
+ size: "default",
24
+ },
25
+ },
26
+ )
27
+
28
+ function Toggle({
29
+ className,
30
+ variant = "default",
31
+ size = "default",
32
+ ...props
33
+ }: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
34
+ return (
35
+ <TogglePrimitive
36
+ data-slot="toggle"
37
+ className={cn(toggleVariants({ variant, size, className }))}
38
+ {...props}
39
+ />
40
+ )
41
+ }
42
+
43
+ export { Toggle, toggleVariants }