@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.
- package/LICENSE +21 -0
- package/README.md +117 -2
- package/THIRD_PARTY_NOTICES.md +253 -0
- package/package.json +50 -3
- package/src/chat/index.ts +9 -0
- package/src/chat/message.tsx +80 -0
- package/src/chat/prompt-input.tsx +121 -0
- package/src/chat/reasoning.tsx +182 -0
- package/src/chat/scroller.tsx +147 -0
- package/src/chat/suggestion.tsx +65 -0
- package/src/chat/tool.tsx +143 -0
- package/src/chat/types.ts +8 -0
- package/src/components/avatar.tsx +35 -0
- package/src/components/badge.tsx +40 -0
- package/src/components/breadcrumb.tsx +100 -0
- package/src/components/button.tsx +56 -0
- package/src/components/card.tsx +75 -0
- package/src/components/checkbox.tsx +25 -0
- package/src/components/color-field.tsx +261 -0
- package/src/components/color-swatch.tsx +52 -0
- package/src/components/context-menu.tsx +249 -0
- package/src/components/dialog.tsx +133 -0
- package/src/components/dropdown-menu.tsx +249 -0
- package/src/components/empty-state.tsx +35 -0
- package/src/components/input.tsx +20 -0
- package/src/components/label.tsx +19 -0
- package/src/components/navigation-menu.tsx +150 -0
- package/src/components/number-field.tsx +432 -0
- package/src/components/popover.tsx +75 -0
- package/src/components/select.tsx +189 -0
- package/src/components/separator.tsx +21 -0
- package/src/components/sheet.tsx +132 -0
- package/src/components/side-panel.tsx +189 -0
- package/src/components/sidebar.tsx +698 -0
- package/src/components/skeleton.tsx +14 -0
- package/src/components/slider.tsx +71 -0
- package/src/components/snackbar.tsx +143 -0
- package/src/components/status-bar.tsx +45 -0
- package/src/components/tabs.tsx +75 -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 +304 -0
- package/src/components/tooltip.tsx +54 -0
- package/src/graphics/adsr.ts +78 -0
- package/src/graphics/curve-editor.tsx +567 -0
- package/src/graphics/curve-edits.ts +146 -0
- package/src/graphics/index.ts +16 -0
- package/src/graphics/scale.ts +25 -0
- package/src/graphics/use-canvas.ts +56 -0
- package/src/graphics/use-tokens.ts +35 -0
- package/src/icons.ts +3 -0
- package/src/index.css +199 -0
- package/src/index.ts +35 -0
- package/src/lib/use-mobile.ts +18 -0
- package/src/lib/utils.ts +6 -0
- 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 }
|