@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.
Files changed (59) hide show
  1. package/package.json +72 -0
  2. package/src/components/ui/avatar.tsx +384 -0
  3. package/src/components/ui/badge.tsx +49 -0
  4. package/src/components/ui/banner.tsx +364 -0
  5. package/src/components/ui/breadcrumb.tsx +120 -0
  6. package/src/components/ui/button.tsx +66 -0
  7. package/src/components/ui/calendar.tsx +220 -0
  8. package/src/components/ui/card.tsx +136 -0
  9. package/src/components/ui/chart.tsx +378 -0
  10. package/src/components/ui/checkbox.tsx +160 -0
  11. package/src/components/ui/coach-mark.tsx +361 -0
  12. package/src/components/ui/collapsible.tsx +33 -0
  13. package/src/components/ui/command.tsx +232 -0
  14. package/src/components/ui/date-picker-field.tsx +186 -0
  15. package/src/components/ui/dialog.tsx +171 -0
  16. package/src/components/ui/drag-handle-grip.tsx +10 -0
  17. package/src/components/ui/drawer.tsx +134 -0
  18. package/src/components/ui/dropdown-menu.tsx +422 -0
  19. package/src/components/ui/field.tsx +238 -0
  20. package/src/components/ui/form.tsx +137 -0
  21. package/src/components/ui/input-group.tsx +156 -0
  22. package/src/components/ui/input-mask.tsx +135 -0
  23. package/src/components/ui/input.tsx +22 -0
  24. package/src/components/ui/kbd.tsx +55 -0
  25. package/src/components/ui/label.tsx +25 -0
  26. package/src/components/ui/payment-card-fields.tsx +65 -0
  27. package/src/components/ui/popover.tsx +46 -0
  28. package/src/components/ui/radio-group.tsx +217 -0
  29. package/src/components/ui/select.tsx +191 -0
  30. package/src/components/ui/selection-tile-grid.tsx +246 -0
  31. package/src/components/ui/separator.tsx +28 -0
  32. package/src/components/ui/sheet.tsx +147 -0
  33. package/src/components/ui/sidebar.tsx +716 -0
  34. package/src/components/ui/skeleton.tsx +13 -0
  35. package/src/components/ui/sonner.tsx +39 -0
  36. package/src/components/ui/status-badge.tsx +109 -0
  37. package/src/components/ui/table.tsx +117 -0
  38. package/src/components/ui/tabs.tsx +90 -0
  39. package/src/components/ui/textarea.tsx +18 -0
  40. package/src/components/ui/tip.tsx +21 -0
  41. package/src/components/ui/toggle-group.tsx +89 -0
  42. package/src/components/ui/toggle-switch.tsx +31 -0
  43. package/src/components/ui/toggle.tsx +48 -0
  44. package/src/components/ui/tooltip.tsx +59 -0
  45. package/src/components/ui/view-segmented-control.tsx +160 -0
  46. package/src/globals.css +1795 -0
  47. package/src/hooks/.gitkeep +0 -0
  48. package/src/hooks/use-app-theme.ts +172 -0
  49. package/src/hooks/use-coach-mark.ts +342 -0
  50. package/src/hooks/use-mobile.ts +31 -0
  51. package/src/hooks/use-mod-key-label.ts +29 -0
  52. package/src/index.ts +55 -0
  53. package/src/lib/compose-refs.ts +15 -0
  54. package/src/lib/date-filter.ts +67 -0
  55. package/src/lib/utils.ts +6 -0
  56. package/src/theme/apply-windows-contrast-theme.ts +29 -0
  57. package/src/theme/windows-contrast-theme.json +147 -0
  58. package/src/theme.css +1130 -0
  59. 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 }