@exxatdesignux/ui 0.0.5
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/package.json +72 -0
- package/src/components/ui/avatar.tsx +384 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/banner.tsx +364 -0
- package/src/components/ui/breadcrumb.tsx +120 -0
- package/src/components/ui/button.tsx +66 -0
- package/src/components/ui/calendar.tsx +220 -0
- package/src/components/ui/card.tsx +136 -0
- package/src/components/ui/chart.tsx +378 -0
- package/src/components/ui/checkbox.tsx +160 -0
- package/src/components/ui/coach-mark.tsx +361 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +232 -0
- package/src/components/ui/date-picker-field.tsx +186 -0
- package/src/components/ui/dialog.tsx +171 -0
- package/src/components/ui/drag-handle-grip.tsx +10 -0
- package/src/components/ui/drawer.tsx +134 -0
- package/src/components/ui/dropdown-menu.tsx +422 -0
- package/src/components/ui/field.tsx +238 -0
- package/src/components/ui/form.tsx +137 -0
- package/src/components/ui/input-group.tsx +156 -0
- package/src/components/ui/input-mask.tsx +135 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/kbd.tsx +55 -0
- package/src/components/ui/label.tsx +25 -0
- package/src/components/ui/payment-card-fields.tsx +65 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/radio-group.tsx +217 -0
- package/src/components/ui/select.tsx +191 -0
- package/src/components/ui/selection-tile-grid.tsx +246 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +147 -0
- package/src/components/ui/sidebar.tsx +716 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +39 -0
- package/src/components/ui/status-badge.tsx +109 -0
- package/src/components/ui/table.tsx +117 -0
- package/src/components/ui/tabs.tsx +90 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tip.tsx +21 -0
- package/src/components/ui/toggle-group.tsx +89 -0
- package/src/components/ui/toggle-switch.tsx +31 -0
- package/src/components/ui/toggle.tsx +48 -0
- package/src/components/ui/tooltip.tsx +59 -0
- package/src/components/ui/view-segmented-control.tsx +160 -0
- package/src/globals.css +1795 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/hooks/use-app-theme.ts +172 -0
- package/src/hooks/use-coach-mark.ts +342 -0
- package/src/hooks/use-mobile.ts +31 -0
- package/src/hooks/use-mod-key-label.ts +29 -0
- package/src/index.ts +55 -0
- package/src/lib/compose-refs.ts +15 -0
- package/src/lib/date-filter.ts +67 -0
- package/src/lib/utils.ts +6 -0
- package/src/theme/apply-windows-contrast-theme.ts +29 -0
- package/src/theme/windows-contrast-theme.json +147 -0
- package/src/theme.css +1130 -0
- package/src/types/react-payment-inputs.d.ts +20 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils"
|
|
2
|
+
|
|
3
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
data-slot="skeleton"
|
|
7
|
+
className={cn("animate-pulse rounded-md bg-muted", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Skeleton }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/** Shadcn Sonner wrapper — do **not** use for product `toast()` calls; see **`AGENTS.md` §6.5** and **`.cursor/rules/exxat-no-toast.mdc`**. */
|
|
4
|
+
import { useTheme } from "next-themes"
|
|
5
|
+
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
|
6
|
+
|
|
7
|
+
const Toaster = ({ ...props }: ToasterProps) => {
|
|
8
|
+
const { theme = "system" } = useTheme()
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Sonner
|
|
12
|
+
theme={theme as ToasterProps["theme"]}
|
|
13
|
+
className="toaster group"
|
|
14
|
+
icons={{
|
|
15
|
+
success: <i className="fa-light fa-circle-check size-4" aria-hidden="true" />,
|
|
16
|
+
info: <i className="fa-light fa-circle-info size-4" aria-hidden="true" />,
|
|
17
|
+
warning: <i className="fa-light fa-triangle-exclamation size-4" aria-hidden="true" />,
|
|
18
|
+
error: <i className="fa-light fa-octagon-xmark size-4" aria-hidden="true" />,
|
|
19
|
+
loading: <i className="fa-light fa-spinner size-4 animate-spin" aria-hidden="true" />,
|
|
20
|
+
}}
|
|
21
|
+
style={
|
|
22
|
+
{
|
|
23
|
+
"--normal-bg": "var(--popover)",
|
|
24
|
+
"--normal-text": "var(--popover-foreground)",
|
|
25
|
+
"--normal-border": "var(--border)",
|
|
26
|
+
"--border-radius": "var(--radius)",
|
|
27
|
+
} as React.CSSProperties
|
|
28
|
+
}
|
|
29
|
+
toastOptions={{
|
|
30
|
+
classNames: {
|
|
31
|
+
toast: "cn-toast",
|
|
32
|
+
},
|
|
33
|
+
}}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { Toaster }
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* StatusBadge — the small "Beta"/"New"/"Alpha" chip we ship in the sidebar,
|
|
10
|
+
* lifted into a reusable primitive so it can decorate any surface: tabs,
|
|
11
|
+
* buttons, table headers, card titles, chart cards, panels.
|
|
12
|
+
*
|
|
13
|
+
* Why a dedicated component (instead of reusing generic `<Badge>`): each
|
|
14
|
+
* status has a canonical color + accessible announcement. Hard-coding the
|
|
15
|
+
* Tailwind classes at every callsite drifts; consumers should pass `status`
|
|
16
|
+
* and let the primitive handle visuals + `aria-label`.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* <StatusBadge status="beta" /> // pill, default size
|
|
20
|
+
* <StatusBadge status="new" size="sm" />
|
|
21
|
+
* <StatusBadge status="beta" variant="dot" /> // small dot (e.g. in collapsed sidebar)
|
|
22
|
+
*
|
|
23
|
+
* // Anchor on a target element:
|
|
24
|
+
* <span className="relative inline-flex">
|
|
25
|
+
* Tab label
|
|
26
|
+
* <StatusBadge status="beta" className="ml-1.5" />
|
|
27
|
+
* </span>
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const statusBadgeVariants = cva(
|
|
31
|
+
"inline-flex shrink-0 items-center justify-center font-semibold leading-none tracking-wide uppercase select-none border border-transparent",
|
|
32
|
+
{
|
|
33
|
+
variants: {
|
|
34
|
+
status: {
|
|
35
|
+
beta:
|
|
36
|
+
// Warm yellow — matches sidebar Beta. HC/forced-colors fall back to
|
|
37
|
+
// system tokens so the chip stays visible under custom contrast.
|
|
38
|
+
"bg-yellow-400 text-yellow-950 hc:bg-transparent hc:border-foreground hc:text-foreground forced-colors:bg-[Canvas] forced-colors:text-[CanvasText] forced-colors:border-[CanvasText]",
|
|
39
|
+
new:
|
|
40
|
+
"bg-brand text-brand-foreground hc:bg-transparent hc:border-foreground hc:text-foreground forced-colors:bg-[Highlight] forced-colors:text-[HighlightText] forced-colors:border-[HighlightText]",
|
|
41
|
+
alpha:
|
|
42
|
+
"bg-orange-500 text-white hc:bg-transparent hc:border-foreground hc:text-foreground forced-colors:bg-[Canvas] forced-colors:text-[CanvasText] forced-colors:border-[CanvasText]",
|
|
43
|
+
preview:
|
|
44
|
+
"bg-sky-500 text-white hc:bg-transparent hc:border-foreground hc:text-foreground forced-colors:bg-[Canvas] forced-colors:text-[CanvasText] forced-colors:border-[CanvasText]",
|
|
45
|
+
deprecated:
|
|
46
|
+
"bg-muted text-muted-foreground border-border hc:border-foreground forced-colors:bg-[Canvas] forced-colors:text-[GrayText] forced-colors:border-[GrayText]",
|
|
47
|
+
},
|
|
48
|
+
size: {
|
|
49
|
+
xs: "h-3.5 min-w-3.5 px-1 text-[9px] rounded-sm",
|
|
50
|
+
sm: "h-4 min-w-4 px-1.5 text-[10px] rounded-full",
|
|
51
|
+
md: "h-5 min-w-5 px-1.5 text-[11px] rounded-full",
|
|
52
|
+
},
|
|
53
|
+
variant: {
|
|
54
|
+
pill: "",
|
|
55
|
+
// `dot` — same color, no label, for collapsed sidebars or tight chips.
|
|
56
|
+
dot: "h-2 w-2 min-w-0 p-0 rounded-full",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
defaultVariants: {
|
|
60
|
+
status: "beta",
|
|
61
|
+
size: "sm",
|
|
62
|
+
variant: "pill",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const STATUS_LABEL: Record<NonNullable<StatusBadgeProps["status"]>, string> = {
|
|
68
|
+
beta: "Beta",
|
|
69
|
+
new: "New",
|
|
70
|
+
alpha: "Alpha",
|
|
71
|
+
preview: "Preview",
|
|
72
|
+
deprecated: "Deprecated",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface StatusBadgeProps
|
|
76
|
+
extends Omit<React.ComponentProps<"span">, "children">,
|
|
77
|
+
VariantProps<typeof statusBadgeVariants> {
|
|
78
|
+
/** Override the visible label (still keeps status color). */
|
|
79
|
+
label?: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function StatusBadge({
|
|
83
|
+
className,
|
|
84
|
+
status = "beta",
|
|
85
|
+
size = "sm",
|
|
86
|
+
variant = "pill",
|
|
87
|
+
label,
|
|
88
|
+
"aria-label": ariaLabel,
|
|
89
|
+
...props
|
|
90
|
+
}: StatusBadgeProps) {
|
|
91
|
+
const visibleLabel = label ?? STATUS_LABEL[status!]
|
|
92
|
+
// Dot variant has no visible text — keep the accessible name.
|
|
93
|
+
const a11yLabel = ariaLabel ?? visibleLabel
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<span
|
|
97
|
+
data-slot="status-badge"
|
|
98
|
+
data-status={status}
|
|
99
|
+
data-variant={variant}
|
|
100
|
+
aria-label={a11yLabel}
|
|
101
|
+
className={cn(statusBadgeVariants({ status, size, variant }), className)}
|
|
102
|
+
{...props}
|
|
103
|
+
>
|
|
104
|
+
{variant === "dot" ? <span className="sr-only">{a11yLabel}</span> : visibleLabel}
|
|
105
|
+
</span>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { statusBadgeVariants }
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="table-container"
|
|
11
|
+
className="relative w-full overflow-x-auto"
|
|
12
|
+
>
|
|
13
|
+
<table
|
|
14
|
+
data-slot="table"
|
|
15
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
23
|
+
return (
|
|
24
|
+
<thead
|
|
25
|
+
data-slot="table-header"
|
|
26
|
+
className={cn("[&_tr]:border-b", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
33
|
+
return (
|
|
34
|
+
<tbody
|
|
35
|
+
data-slot="table-body"
|
|
36
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
43
|
+
return (
|
|
44
|
+
<tfoot
|
|
45
|
+
data-slot="table-footer"
|
|
46
|
+
className={cn(
|
|
47
|
+
"border-t bg-interactive-hover-subtle font-medium [&>tr]:last:border-b-0",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
56
|
+
return (
|
|
57
|
+
<tr
|
|
58
|
+
data-slot="table-row"
|
|
59
|
+
className={cn(
|
|
60
|
+
"border-b transition-colors hover:bg-interactive-hover-subtle data-[state=selected]:bg-interactive-hover",
|
|
61
|
+
className
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function TableHead({ className, scope = "col", ...props }: React.ComponentProps<"th">) {
|
|
69
|
+
return (
|
|
70
|
+
<th
|
|
71
|
+
data-slot="table-head"
|
|
72
|
+
scope={scope}
|
|
73
|
+
className={cn(
|
|
74
|
+
"h-10 px-2 text-start align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pe-0",
|
|
75
|
+
className
|
|
76
|
+
)}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
83
|
+
return (
|
|
84
|
+
<td
|
|
85
|
+
data-slot="table-cell"
|
|
86
|
+
className={cn(
|
|
87
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0",
|
|
88
|
+
className
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function TableCaption({
|
|
96
|
+
className,
|
|
97
|
+
...props
|
|
98
|
+
}: React.ComponentProps<"caption">) {
|
|
99
|
+
return (
|
|
100
|
+
<caption
|
|
101
|
+
data-slot="table-caption"
|
|
102
|
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
Table,
|
|
110
|
+
TableHeader,
|
|
111
|
+
TableBody,
|
|
112
|
+
TableFooter,
|
|
113
|
+
TableHead,
|
|
114
|
+
TableRow,
|
|
115
|
+
TableCell,
|
|
116
|
+
TableCaption,
|
|
117
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
import { Tabs as TabsPrimitive } from "radix-ui"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
|
|
9
|
+
function Tabs({
|
|
10
|
+
className,
|
|
11
|
+
orientation = "horizontal",
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
14
|
+
return (
|
|
15
|
+
<TabsPrimitive.Root
|
|
16
|
+
data-slot="tabs"
|
|
17
|
+
data-orientation={orientation}
|
|
18
|
+
className={cn(
|
|
19
|
+
"group/tabs flex gap-2 data-horizontal:flex-col",
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const tabsListVariants = cva(
|
|
28
|
+
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
|
|
29
|
+
{
|
|
30
|
+
variants: {
|
|
31
|
+
variant: {
|
|
32
|
+
default: "bg-[var(--brand-color)]/12",
|
|
33
|
+
line: "gap-1 bg-transparent",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: {
|
|
37
|
+
variant: "default",
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
function TabsList({
|
|
43
|
+
className,
|
|
44
|
+
variant = "default",
|
|
45
|
+
...props
|
|
46
|
+
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
|
47
|
+
VariantProps<typeof tabsListVariants>) {
|
|
48
|
+
return (
|
|
49
|
+
<TabsPrimitive.List
|
|
50
|
+
data-slot="tabs-list"
|
|
51
|
+
data-variant={variant}
|
|
52
|
+
className={cn(tabsListVariants({ variant }), className)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function TabsTrigger({
|
|
59
|
+
className,
|
|
60
|
+
...props
|
|
61
|
+
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
62
|
+
return (
|
|
63
|
+
<TabsPrimitive.Trigger
|
|
64
|
+
data-slot="tabs-trigger"
|
|
65
|
+
className={cn(
|
|
66
|
+
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-interactive-hover-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 dark:text-muted-foreground dark:hover:text-interactive-hover-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",
|
|
67
|
+
"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",
|
|
68
|
+
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
|
69
|
+
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-end-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function TabsContent({
|
|
78
|
+
className,
|
|
79
|
+
...props
|
|
80
|
+
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
81
|
+
return (
|
|
82
|
+
<TabsPrimitive.Content
|
|
83
|
+
data-slot="tabs-content"
|
|
84
|
+
className={cn("flex-1 text-sm outline-none", className)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * 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-[80px] w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 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-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/15 dark:disabled:bg-input/80 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,21 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip"
|
|
4
|
+
|
|
5
|
+
interface TipProps {
|
|
6
|
+
/** Plain string or text + `<Kbd />` — see `.cursor/rules/exxat-kbd-shortcuts.mdc` */
|
|
7
|
+
label: React.ReactNode
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
side?: "top" | "bottom" | "left" | "right"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Tip({ label, children, side = "top" }: TipProps) {
|
|
13
|
+
return (
|
|
14
|
+
<Tooltip>
|
|
15
|
+
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
|
16
|
+
<TooltipContent side={side} className="flex flex-wrap items-center gap-1.5">
|
|
17
|
+
{label}
|
|
18
|
+
</TooltipContent>
|
|
19
|
+
</Tooltip>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { type VariantProps } from "class-variance-authority"
|
|
5
|
+
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
import { toggleVariants } from "./toggle"
|
|
9
|
+
|
|
10
|
+
const ToggleGroupContext = React.createContext<
|
|
11
|
+
VariantProps<typeof toggleVariants> & {
|
|
12
|
+
spacing?: number
|
|
13
|
+
orientation?: "horizontal" | "vertical"
|
|
14
|
+
}
|
|
15
|
+
>({
|
|
16
|
+
size: "default",
|
|
17
|
+
variant: "default",
|
|
18
|
+
spacing: 0,
|
|
19
|
+
orientation: "horizontal",
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
function ToggleGroup({
|
|
23
|
+
className,
|
|
24
|
+
variant,
|
|
25
|
+
size,
|
|
26
|
+
spacing = 0,
|
|
27
|
+
orientation = "horizontal",
|
|
28
|
+
children,
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
|
31
|
+
VariantProps<typeof toggleVariants> & {
|
|
32
|
+
spacing?: number
|
|
33
|
+
orientation?: "horizontal" | "vertical"
|
|
34
|
+
}) {
|
|
35
|
+
return (
|
|
36
|
+
<ToggleGroupPrimitive.Root
|
|
37
|
+
data-slot="toggle-group"
|
|
38
|
+
data-variant={variant}
|
|
39
|
+
data-size={size}
|
|
40
|
+
data-spacing={spacing}
|
|
41
|
+
data-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-lg data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-vertical:flex-col data-vertical:items-stretch",
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
<ToggleGroupContext.Provider
|
|
50
|
+
value={{ variant, size, spacing, orientation }}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</ToggleGroupContext.Provider>
|
|
54
|
+
</ToggleGroupPrimitive.Root>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ToggleGroupItem({
|
|
59
|
+
className,
|
|
60
|
+
children,
|
|
61
|
+
variant = "default",
|
|
62
|
+
size = "default",
|
|
63
|
+
...props
|
|
64
|
+
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
|
65
|
+
VariantProps<typeof toggleVariants>) {
|
|
66
|
+
const context = React.useContext(ToggleGroupContext)
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<ToggleGroupPrimitive.Item
|
|
70
|
+
data-slot="toggle-group-item"
|
|
71
|
+
data-variant={context.variant || variant}
|
|
72
|
+
data-size={context.size || size}
|
|
73
|
+
data-spacing={context.spacing}
|
|
74
|
+
className={cn(
|
|
75
|
+
"shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-s-lg group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-e-lg group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-s-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-s group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
|
|
76
|
+
toggleVariants({
|
|
77
|
+
variant: context.variant || variant,
|
|
78
|
+
size: context.size || size,
|
|
79
|
+
}),
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
{...props}
|
|
83
|
+
>
|
|
84
|
+
{children}
|
|
85
|
+
</ToggleGroupPrimitive.Item>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { ToggleGroup, ToggleGroupItem }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
interface ToggleSwitchProps {
|
|
6
|
+
checked: boolean
|
|
7
|
+
onChange: (value: boolean) => void
|
|
8
|
+
id?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function ToggleSwitch({ checked, onChange, id }: ToggleSwitchProps) {
|
|
12
|
+
return (
|
|
13
|
+
<button
|
|
14
|
+
id={id}
|
|
15
|
+
type="button"
|
|
16
|
+
role="switch"
|
|
17
|
+
aria-checked={checked}
|
|
18
|
+
onClick={() => onChange(!checked)}
|
|
19
|
+
className={cn(
|
|
20
|
+
"relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-input transition-colors",
|
|
21
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
22
|
+
checked ? "bg-primary" : "bg-input"
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
<span className={cn(
|
|
26
|
+
"pointer-events-none inline-block size-4 rounded-full bg-primary-foreground shadow-sm transition-transform",
|
|
27
|
+
checked ? "translate-x-4" : "translate-x-0"
|
|
28
|
+
)} />
|
|
29
|
+
</button>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
import { Toggle as TogglePrimitive } from "radix-ui"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
|
|
9
|
+
const toggleVariants = cva(
|
|
10
|
+
// Unselected: dark foreground text (matches primary text, not blue)
|
|
11
|
+
// Selected : solid primary fill + white text — looks like a filled button
|
|
12
|
+
"group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none text-foreground hover:bg-interactive-hover hover:text-interactive-hover-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-[state=on]:bg-primary data-[state=on]:text-primary-foreground data-[state=on]:shadow-xs dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
default: "bg-transparent",
|
|
17
|
+
outline: "border border-[var(--brand-color)]/30 bg-transparent hover:border-[var(--brand-color)]/60",
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
default: "h-8 min-w-8 px-2",
|
|
21
|
+
sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-1.5 text-[0.8rem]",
|
|
22
|
+
lg: "h-9 min-w-9 px-2.5",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: "default",
|
|
27
|
+
size: "default",
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
function Toggle({
|
|
33
|
+
className,
|
|
34
|
+
variant = "default",
|
|
35
|
+
size = "default",
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<typeof TogglePrimitive.Root> &
|
|
38
|
+
VariantProps<typeof toggleVariants>) {
|
|
39
|
+
return (
|
|
40
|
+
<TogglePrimitive.Root
|
|
41
|
+
data-slot="toggle"
|
|
42
|
+
className={cn(toggleVariants({ variant, size, className }))}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { Toggle, toggleVariants }
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
function TooltipProvider({
|
|
9
|
+
delayDuration = 0,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
12
|
+
return (
|
|
13
|
+
<TooltipPrimitive.Provider
|
|
14
|
+
data-slot="tooltip-provider"
|
|
15
|
+
delayDuration={delayDuration}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Tooltip({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
24
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function TooltipTrigger({
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
30
|
+
return (
|
|
31
|
+
<TooltipPrimitive.Trigger data-slot="tooltip-trigger" suppressHydrationWarning {...props} />
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function TooltipContent({
|
|
36
|
+
className,
|
|
37
|
+
sideOffset = 0,
|
|
38
|
+
children,
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
41
|
+
return (
|
|
42
|
+
<TooltipPrimitive.Portal>
|
|
43
|
+
<TooltipPrimitive.Content
|
|
44
|
+
data-slot="tooltip-content"
|
|
45
|
+
sideOffset={sideOffset}
|
|
46
|
+
className={cn(
|
|
47
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pe-1.5 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-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 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",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
|
|
54
|
+
</TooltipPrimitive.Content>
|
|
55
|
+
</TooltipPrimitive.Portal>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
|