@olympusoss/canvas 2.20.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +69 -35
  2. package/package.json +45 -177
  3. package/src/cn.ts +3 -0
  4. package/src/index.ts +12 -603
  5. package/src/theme.ts +62 -0
  6. package/src/tokens.ts +11 -0
  7. package/styles/base.css +17 -0
  8. package/styles/canvas.css +77 -52
  9. package/styles/components/alert.css +66 -0
  10. package/styles/components/app-shell.css +46 -0
  11. package/styles/components/avatar.css +22 -0
  12. package/styles/components/badge.css +83 -0
  13. package/styles/components/breadcrumb.css +35 -0
  14. package/styles/components/button-group.css +23 -0
  15. package/styles/components/button.css +107 -0
  16. package/styles/components/calendar.css +73 -0
  17. package/styles/components/card.css +58 -0
  18. package/styles/components/checkbox.css +55 -0
  19. package/styles/components/code-block.css +18 -0
  20. package/styles/components/combobox.css +75 -0
  21. package/styles/components/command.css +94 -0
  22. package/styles/components/data-table.css +142 -0
  23. package/styles/components/dialog.css +72 -0
  24. package/styles/components/dropdown.css +54 -0
  25. package/styles/components/empty-state.css +17 -0
  26. package/styles/components/field.css +27 -0
  27. package/styles/components/filter-panel.css +58 -0
  28. package/styles/components/form.css +27 -0
  29. package/styles/components/icon.css +8 -0
  30. package/styles/components/input-group.css +45 -0
  31. package/styles/components/input.css +56 -0
  32. package/styles/components/kbd.css +15 -0
  33. package/styles/components/page-header.css +52 -0
  34. package/styles/components/pagination.css +48 -0
  35. package/styles/components/popover.css +14 -0
  36. package/styles/components/radio.css +28 -0
  37. package/styles/components/row-menu.css +69 -0
  38. package/styles/components/section-card.css +49 -0
  39. package/styles/components/select.css +57 -0
  40. package/styles/components/separator.css +32 -0
  41. package/styles/components/sheet.css +70 -0
  42. package/styles/components/sidebar.css +146 -0
  43. package/styles/components/skeleton.css +32 -0
  44. package/styles/components/spinner.css +26 -0
  45. package/styles/components/stat-card.css +71 -0
  46. package/styles/components/stepper.css +63 -0
  47. package/styles/components/switch.css +45 -0
  48. package/styles/components/tabs.css +40 -0
  49. package/styles/components/textarea.css +31 -0
  50. package/styles/components/toast.css +95 -0
  51. package/styles/components/tooltip.css +53 -0
  52. package/styles/components/topbar.css +24 -0
  53. package/styles/components/typography.css +105 -0
  54. package/styles/patterns/backdrops.css +35 -0
  55. package/styles/patterns/density.css +66 -0
  56. package/styles/patterns/focus.css +22 -0
  57. package/styles/patterns/glass.css +85 -0
  58. package/styles/patterns/high-contrast.css +70 -0
  59. package/styles/patterns/reduced-motion.css +12 -0
  60. package/styles/patterns/scrollbar.css +10 -0
  61. package/styles/reset.css +89 -0
  62. package/styles/tokens/colors.css +106 -0
  63. package/styles/tokens/motion.css +33 -0
  64. package/styles/tokens/radius.css +10 -0
  65. package/styles/tokens/shadows.css +35 -0
  66. package/styles/tokens/spacing.css +19 -0
  67. package/styles/tokens/typography.css +6 -0
  68. package/styles/tokens/z-index.css +12 -0
  69. package/styles/utilities/display.css +66 -0
  70. package/styles/utilities/flexbox.css +240 -0
  71. package/styles/utilities/gap.css +288 -0
  72. package/styles/utilities/grid.css +138 -0
  73. package/styles/utilities/position.css +78 -0
  74. package/styles/utilities/sizing.css +138 -0
  75. package/tsconfig.json +20 -21
  76. package/src/components/atoms/README.md +0 -11
  77. package/src/components/atoms/aspect-ratio.tsx +0 -32
  78. package/src/components/atoms/avatar.tsx +0 -98
  79. package/src/components/atoms/badge.tsx +0 -44
  80. package/src/components/atoms/brand-mark.tsx +0 -74
  81. package/src/components/atoms/button.tsx +0 -105
  82. package/src/components/atoms/checkbox.tsx +0 -63
  83. package/src/components/atoms/flex-box.tsx +0 -105
  84. package/src/components/atoms/icon.tsx +0 -34
  85. package/src/components/atoms/input.tsx +0 -92
  86. package/src/components/atoms/label.tsx +0 -41
  87. package/src/components/atoms/logo.tsx +0 -89
  88. package/src/components/atoms/progress.tsx +0 -55
  89. package/src/components/atoms/radio-group.tsx +0 -122
  90. package/src/components/atoms/scroll-area.tsx +0 -106
  91. package/src/components/atoms/section.tsx +0 -48
  92. package/src/components/atoms/separator.tsx +0 -45
  93. package/src/components/atoms/skeleton.tsx +0 -17
  94. package/src/components/atoms/slider.tsx +0 -93
  95. package/src/components/atoms/spinner.tsx +0 -47
  96. package/src/components/atoms/switch.tsx +0 -60
  97. package/src/components/atoms/textarea.tsx +0 -78
  98. package/src/components/atoms/toggle.tsx +0 -80
  99. package/src/components/charts/activity-heatmap.tsx +0 -186
  100. package/src/components/charts/axes.tsx +0 -21
  101. package/src/components/charts/chart-container.tsx +0 -254
  102. package/src/components/charts/chart-legend.tsx +0 -67
  103. package/src/components/charts/chart-tooltip.tsx +0 -161
  104. package/src/components/charts/chart-types.tsx +0 -49
  105. package/src/components/charts/containers.tsx +0 -11
  106. package/src/components/charts/data.tsx +0 -16
  107. package/src/components/charts/details.tsx +0 -25
  108. package/src/components/charts/dot-pulse.tsx +0 -61
  109. package/src/components/charts/gauge.tsx +0 -106
  110. package/src/components/charts/grids.tsx +0 -8
  111. package/src/components/charts/index.ts +0 -62
  112. package/src/components/charts/labeled-bar-list.tsx +0 -85
  113. package/src/components/charts/metric-breakdown.tsx +0 -316
  114. package/src/components/charts/references.tsx +0 -8
  115. package/src/components/charts/service-health-list.tsx +0 -85
  116. package/src/components/charts/sparkline-area.tsx +0 -80
  117. package/src/components/charts/sparkline.tsx +0 -52
  118. package/src/components/charts/stacked-bar.tsx +0 -104
  119. package/src/components/charts/text.tsx +0 -10
  120. package/src/components/charts/world-heat-map-inner.tsx +0 -317
  121. package/src/components/charts/world-heat-map.tsx +0 -184
  122. package/src/components/molecules/README.md +0 -12
  123. package/src/components/molecules/action-bar.tsx +0 -73
  124. package/src/components/molecules/activity-item.tsx +0 -74
  125. package/src/components/molecules/alert.tsx +0 -86
  126. package/src/components/molecules/animated-background.tsx +0 -92
  127. package/src/components/molecules/auth-shell.tsx +0 -95
  128. package/src/components/molecules/brand-lockup.tsx +0 -48
  129. package/src/components/molecules/breadcrumb.tsx +0 -157
  130. package/src/components/molecules/button-group.tsx +0 -104
  131. package/src/components/molecules/calendar.tsx +0 -217
  132. package/src/components/molecules/card.tsx +0 -102
  133. package/src/components/molecules/client-brand.tsx +0 -95
  134. package/src/components/molecules/code-block.tsx +0 -86
  135. package/src/components/molecules/countdown-button.tsx +0 -92
  136. package/src/components/molecules/empty-state.tsx +0 -56
  137. package/src/components/molecules/error-state.tsx +0 -42
  138. package/src/components/molecules/field-display.tsx +0 -35
  139. package/src/components/molecules/input-otp.tsx +0 -74
  140. package/src/components/molecules/launcher-card.tsx +0 -152
  141. package/src/components/molecules/loading-state.tsx +0 -36
  142. package/src/components/molecules/notification-item.tsx +0 -67
  143. package/src/components/molecules/notification-list.tsx +0 -45
  144. package/src/components/molecules/number-badge.tsx +0 -53
  145. package/src/components/molecules/or-separator.tsx +0 -38
  146. package/src/components/molecules/page-header.tsx +0 -88
  147. package/src/components/molecules/page-tabs.tsx +0 -94
  148. package/src/components/molecules/pagination.tsx +0 -150
  149. package/src/components/molecules/password-input.tsx +0 -83
  150. package/src/components/molecules/password-strength-meter.tsx +0 -104
  151. package/src/components/molecules/phone-input.tsx +0 -200
  152. package/src/components/molecules/search-bar.tsx +0 -64
  153. package/src/components/molecules/secret-field.tsx +0 -158
  154. package/src/components/molecules/section-card.tsx +0 -91
  155. package/src/components/molecules/social-buttons.tsx +0 -165
  156. package/src/components/molecules/stat-card.tsx +0 -100
  157. package/src/components/molecules/status-badge.tsx +0 -42
  158. package/src/components/molecules/stepper.tsx +0 -96
  159. package/src/components/molecules/table.tsx +0 -157
  160. package/src/components/molecules/terminal.tsx +0 -74
  161. package/src/components/molecules/toggle-group.tsx +0 -145
  162. package/src/components/molecules/tooltip.tsx +0 -155
  163. package/src/components/molecules/user-avatar-chip.tsx +0 -71
  164. package/src/components/organisms/README.md +0 -14
  165. package/src/components/organisms/accordion.tsx +0 -154
  166. package/src/components/organisms/alert-dialog.tsx +0 -277
  167. package/src/components/organisms/carousel.tsx +0 -244
  168. package/src/components/organisms/collapsible.tsx +0 -69
  169. package/src/components/organisms/command.tsx +0 -144
  170. package/src/components/organisms/context-menu.tsx +0 -339
  171. package/src/components/organisms/dashboard-grid.tsx +0 -369
  172. package/src/components/organisms/data-table.tsx +0 -330
  173. package/src/components/organisms/dialog.tsx +0 -312
  174. package/src/components/organisms/drawer.tsx +0 -123
  175. package/src/components/organisms/dropdown-menu.tsx +0 -440
  176. package/src/components/organisms/editors/code-editor.tsx +0 -144
  177. package/src/components/organisms/editors/index.ts +0 -4
  178. package/src/components/organisms/editors/markdown-editor.tsx +0 -153
  179. package/src/components/organisms/editors/markdown-renderer.ts +0 -27
  180. package/src/components/organisms/editors/prose-canvas-classes.ts +0 -45
  181. package/src/components/organisms/editors/rich-text-editor.tsx +0 -126
  182. package/src/components/organisms/editors/toolbar/md-toolbar.tsx +0 -129
  183. package/src/components/organisms/editors/toolbar/rte-toolbar.tsx +0 -211
  184. package/src/components/organisms/editors/toolbar/toolbar-shell.tsx +0 -45
  185. package/src/components/organisms/editors/use-codemirror-theme.ts +0 -61
  186. package/src/components/organisms/error-boundary.tsx +0 -61
  187. package/src/components/organisms/form.tsx +0 -174
  188. package/src/components/organisms/hover-card.tsx +0 -115
  189. package/src/components/organisms/menubar.tsx +0 -498
  190. package/src/components/organisms/navbar.tsx +0 -104
  191. package/src/components/organisms/navigation-menu.tsx +0 -235
  192. package/src/components/organisms/popover.tsx +0 -149
  193. package/src/components/organisms/resizable.tsx +0 -58
  194. package/src/components/organisms/schema-form.tsx +0 -232
  195. package/src/components/organisms/select.tsx +0 -309
  196. package/src/components/organisms/sheet.tsx +0 -265
  197. package/src/components/organisms/sidebar.tsx +0 -1040
  198. package/src/components/organisms/sonner.tsx +0 -96
  199. package/src/components/organisms/tabs.tsx +0 -133
  200. package/src/components/organisms/theme-provider.tsx +0 -101
  201. package/src/hooks/use-mobile.tsx +0 -19
  202. package/src/lib/portal-container.tsx +0 -35
  203. package/src/lib/utils.ts +0 -6
  204. package/src/native.ts +0 -23
  205. package/src/tokens/colors.ts +0 -91
  206. package/src/tokens/index.ts +0 -3
  207. package/src/tokens/spacing.ts +0 -55
  208. package/src/tokens/typography.ts +0 -27
  209. package/styles/dashboard-grid.css +0 -47
  210. package/styles/fonts/Roboto-VariableFont_wdth_wght.ttf +0 -0
  211. package/styles/glass.css +0 -171
  212. package/styles/leaflet.css +0 -13
  213. package/styles/tokens.css +0 -317
  214. package/tailwind.config.ts +0 -70
@@ -1,86 +0,0 @@
1
- "use client";
2
-
3
- import { Check, Copy } from "lucide-react";
4
- import * as React from "react";
5
-
6
- import { cn } from "../../lib/utils";
7
- import { Button } from "../atoms/button";
8
-
9
- export interface CodeBlockProps extends React.HTMLAttributes<HTMLDivElement> {
10
- code: string;
11
- language?: string;
12
- showCopy?: boolean;
13
- /**
14
- * `"light"` (default) renders against `bg-muted`; `"dark"` switches to a
15
- * near-black terminal palette (`#0a0a0b` background, `#e4e4e7` text) for
16
- * use on marketing surfaces.
17
- */
18
- theme?: "light" | "dark";
19
- }
20
-
21
- const CodeBlock = React.forwardRef<HTMLDivElement, CodeBlockProps>(
22
- ({ code, language, showCopy = true, theme = "light", className, ...props }, ref) => {
23
- const [copied, setCopied] = React.useState(false);
24
- const isDark = theme === "dark";
25
-
26
- const handleCopy = React.useCallback(async () => {
27
- await navigator.clipboard.writeText(code);
28
- setCopied(true);
29
- setTimeout(() => setCopied(false), 2000);
30
- }, [code]);
31
-
32
- return (
33
- <div
34
- ref={ref}
35
- data-slot="code-block"
36
- className={cn(
37
- "relative overflow-hidden rounded-md",
38
- isDark ? "border border-border" : "bg-muted",
39
- className,
40
- )}
41
- style={isDark ? { background: "#0a0a0b" } : undefined}
42
- {...props}
43
- >
44
- {(language || showCopy) && (
45
- <div
46
- className={cn(
47
- "flex items-center justify-between px-4 py-2",
48
- isDark ? undefined : "border-b",
49
- )}
50
- style={isDark ? { borderBottom: "1px solid #222" } : undefined}
51
- >
52
- {language ? (
53
- <span
54
- className={cn("text-xs font-medium", !isDark && "text-muted-foreground")}
55
- style={isDark ? { color: "#6b7280" } : undefined}
56
- >
57
- {language}
58
- </span>
59
- ) : (
60
- <span />
61
- )}
62
- {showCopy && (
63
- <Button
64
- variant="ghost"
65
- size="icon"
66
- className={cn(
67
- "h-6 w-6",
68
- isDark && "text-zinc-400 hover:bg-white/5 hover:text-zinc-100",
69
- )}
70
- onClick={handleCopy}
71
- >
72
- {copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
73
- </Button>
74
- )}
75
- </div>
76
- )}
77
- <pre className="overflow-x-auto p-4" style={isDark ? { color: "#e4e4e7" } : undefined}>
78
- <code className="text-sm">{code}</code>
79
- </pre>
80
- </div>
81
- );
82
- },
83
- );
84
- CodeBlock.displayName = "CodeBlock";
85
-
86
- export { CodeBlock };
@@ -1,92 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
-
5
- import { cn } from "../../lib/utils";
6
- import { Button, type ButtonProps } from "../atoms/button";
7
-
8
- export interface CountdownButtonProps
9
- extends Omit<ButtonProps, "disabled" | "onClick" | "children"> {
10
- /**
11
- * Seconds to wait before the button becomes clickable. The countdown
12
- * starts when the component mounts and resets every time `triggerKey`
13
- * changes.
14
- * @default 30
15
- */
16
- duration?: number;
17
- /**
18
- * Bump this value to restart the countdown (e.g. after a successful
19
- * resend, set it to `Date.now()` so the button locks out again).
20
- */
21
- triggerKey?: string | number;
22
- /** Called when the button is clicked and the countdown has elapsed. */
23
- onClick?: () => void;
24
- /**
25
- * Render-prop for the button label. Receives `secondsLeft` (0 when
26
- * ready). Default renders `"Resend"` / `"Resend in 23s"`.
27
- */
28
- children?: React.ReactNode | ((secondsLeft: number) => React.ReactNode);
29
- /**
30
- * Action verb shown in the default label. Used as `${verb}` when ready
31
- * and `${verb} in Ns` while counting down.
32
- * @default "Resend"
33
- */
34
- verb?: string;
35
- }
36
-
37
- /**
38
- * Button that locks out for a duration after mount or after `triggerKey`
39
- * changes. Renders countdown text while disabled.
40
- *
41
- * Use for "resend code" / "resend email" flows where the user shouldn't be
42
- * able to spam the action. Triggering a new send from elsewhere should
43
- * update `triggerKey` to restart the lockout.
44
- */
45
- const CountdownButton = React.forwardRef<HTMLButtonElement, CountdownButtonProps>(
46
- (
47
- { duration = 30, triggerKey, onClick, children, verb = "Resend", className, ...buttonProps },
48
- ref,
49
- ) => {
50
- const [secondsLeft, setSecondsLeft] = React.useState(duration);
51
-
52
- React.useEffect(() => {
53
- setSecondsLeft(duration);
54
- const interval = setInterval(() => {
55
- setSecondsLeft((s) => {
56
- if (s <= 1) {
57
- clearInterval(interval);
58
- return 0;
59
- }
60
- return s - 1;
61
- });
62
- }, 1000);
63
- return () => clearInterval(interval);
64
- }, [duration, triggerKey]);
65
-
66
- const ready = secondsLeft <= 0;
67
- const labelNode =
68
- typeof children === "function"
69
- ? children(secondsLeft)
70
- : children !== undefined
71
- ? children
72
- : ready
73
- ? verb
74
- : `${verb} in ${secondsLeft}s`;
75
-
76
- return (
77
- <Button
78
- ref={ref}
79
- type="button"
80
- disabled={!ready}
81
- onClick={() => ready && onClick?.()}
82
- className={cn(className)}
83
- {...buttonProps}
84
- >
85
- {labelNode}
86
- </Button>
87
- );
88
- },
89
- );
90
- CountdownButton.displayName = "CountdownButton";
91
-
92
- export { CountdownButton };
@@ -1,56 +0,0 @@
1
- "use client";
2
-
3
- import { Inbox } from "lucide-react";
4
- import * as React from "react";
5
-
6
- import { cn } from "../../lib/utils";
7
- import { Button } from "../atoms/button";
8
-
9
- export interface EmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
10
- icon?: React.ReactNode;
11
- /** Primary message. Renders below the icon pill, 15px font-medium. */
12
- title?: string;
13
- /**
14
- * @deprecated Use `title`. Retained as an alias so existing call sites
15
- * (e.g. DataTable's empty fallback) keep working.
16
- */
17
- message?: string;
18
- description?: string;
19
- action?: {
20
- label: string;
21
- onClick: () => void;
22
- };
23
- }
24
-
25
- const EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(
26
- ({ icon, title, message, description, action, className, ...props }, ref) => {
27
- const heading = title ?? message ?? "No items found";
28
- return (
29
- <div
30
- ref={ref}
31
- data-slot="empty-state"
32
- className={cn(
33
- "flex flex-col items-center justify-center gap-3 py-12 text-muted-foreground",
34
- className,
35
- )}
36
- {...props}
37
- >
38
- <div className="mb-1 inline-flex rounded-full bg-muted p-3 text-muted-foreground">
39
- {icon ?? <Inbox className="h-5 w-5" />}
40
- </div>
41
- <div className="text-center">
42
- <p className="text-[15px] font-medium text-foreground">{heading}</p>
43
- {description && <p className="mt-1 text-xs text-muted-foreground">{description}</p>}
44
- </div>
45
- {action && (
46
- <Button variant="outline" size="sm" onClick={action.onClick}>
47
- {action.label}
48
- </Button>
49
- )}
50
- </div>
51
- );
52
- },
53
- );
54
- EmptyState.displayName = "EmptyState";
55
-
56
- export { EmptyState };
@@ -1,42 +0,0 @@
1
- "use client";
2
-
3
- import { AlertCircle } from "lucide-react";
4
- import * as React from "react";
5
-
6
- import { cn } from "../../lib/utils";
7
- import { Button } from "../atoms/button";
8
-
9
- export interface ErrorStateProps extends React.HTMLAttributes<HTMLDivElement> {
10
- message?: string;
11
- onRetry?: () => void;
12
- retryLabel?: string;
13
- }
14
-
15
- const ErrorState = React.forwardRef<HTMLDivElement, ErrorStateProps>(
16
- (
17
- { message = "Something went wrong.", onRetry, retryLabel = "Try again", className, ...props },
18
- ref,
19
- ) => {
20
- return (
21
- <div
22
- ref={ref}
23
- className={cn(
24
- "flex flex-col items-center justify-center gap-3 py-8 text-muted-foreground",
25
- className,
26
- )}
27
- {...props}
28
- >
29
- <AlertCircle className="h-8 w-8 text-destructive" />
30
- <p className="text-sm">{message}</p>
31
- {onRetry && (
32
- <Button variant="outline" size="sm" onClick={onRetry}>
33
- {retryLabel}
34
- </Button>
35
- )}
36
- </div>
37
- );
38
- },
39
- );
40
- ErrorState.displayName = "ErrorState";
41
-
42
- export { ErrorState };
@@ -1,35 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "../../lib/utils";
4
-
5
- export interface FieldDisplayProps extends React.HTMLAttributes<HTMLDListElement> {
6
- label: string;
7
- value?: React.ReactNode;
8
- mono?: boolean;
9
- }
10
-
11
- const FieldDisplay = React.forwardRef<HTMLDListElement, FieldDisplayProps>(
12
- ({ label, value, mono = false, className, ...props }, ref) => {
13
- return (
14
- <dl
15
- ref={ref}
16
- className={cn("grid grid-cols-[180px_1fr] items-baseline gap-4 py-2", className)}
17
- {...props}
18
- >
19
- <dt className="text-[13px] font-medium text-muted-foreground">{label}</dt>
20
- <dd
21
- className={cn(
22
- "break-words text-[13px]",
23
- mono && "font-mono text-[12.5px]",
24
- value ? "text-foreground" : "text-muted-foreground",
25
- )}
26
- >
27
- {value ?? "—"}
28
- </dd>
29
- </dl>
30
- );
31
- },
32
- );
33
- FieldDisplay.displayName = "FieldDisplay";
34
-
35
- export { FieldDisplay };
@@ -1,74 +0,0 @@
1
- "use client";
2
-
3
- import { OTPInput, OTPInputContext } from "input-otp";
4
- import { Minus } from "lucide-react";
5
- import * as React from "react";
6
-
7
- import { cn } from "../../lib/utils";
8
-
9
- const InputOTP = React.forwardRef<
10
- React.ElementRef<typeof OTPInput>,
11
- React.ComponentPropsWithoutRef<typeof OTPInput>
12
- >(({ className, containerClassName, ...props }, ref) => (
13
- <OTPInput
14
- ref={ref}
15
- containerClassName={cn(
16
- "flex items-center gap-2 has-[:disabled]:opacity-50",
17
- containerClassName,
18
- )}
19
- className={cn("disabled:cursor-not-allowed", className)}
20
- {...props}
21
- />
22
- ));
23
- InputOTP.displayName = "InputOTP";
24
-
25
- const InputOTPGroup = React.forwardRef<
26
- React.ElementRef<"div">,
27
- React.ComponentPropsWithoutRef<"div">
28
- >(({ className, ...props }, ref) => (
29
- <div ref={ref} className={cn("flex items-center", className)} {...props} />
30
- ));
31
- InputOTPGroup.displayName = "InputOTPGroup";
32
-
33
- const InputOTPSlot = React.forwardRef<
34
- React.ElementRef<"div">,
35
- React.ComponentPropsWithoutRef<"div"> & { index: number }
36
- >(({ index, className, ...props }, ref) => {
37
- const inputOTPContext = React.useContext(OTPInputContext);
38
- const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
39
-
40
- /* c8 ignore next -- hasFakeCaret is only set by the input-otp library for a live selection range, which jsdom's fireEvent doesn't produce */
41
- const fakeCaret = hasFakeCaret ? (
42
- <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
43
- <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
44
- </div>
45
- ) : null;
46
-
47
- return (
48
- <div
49
- ref={ref}
50
- className={cn(
51
- "relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
52
- isActive && "z-10 ring-1 ring-ring",
53
- className,
54
- )}
55
- {...props}
56
- >
57
- {char}
58
- {fakeCaret}
59
- </div>
60
- );
61
- });
62
- InputOTPSlot.displayName = "InputOTPSlot";
63
-
64
- const InputOTPSeparator = React.forwardRef<
65
- React.ElementRef<"div">,
66
- React.ComponentPropsWithoutRef<"div">
67
- >(({ ...props }, ref) => (
68
- <div ref={ref} role="separator" {...props}>
69
- <Minus />
70
- </div>
71
- ));
72
- InputOTPSeparator.displayName = "InputOTPSeparator";
73
-
74
- export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot };
@@ -1,152 +0,0 @@
1
- // molecules: can import tokens/, lib/utils, atoms/.
2
- "use client";
3
-
4
- import * as React from "react";
5
-
6
- import { cn } from "../../lib/utils";
7
-
8
- const TONE_STYLES = {
9
- default: {
10
- bg: "hsl(var(--primary) / 0.1)",
11
- fg: "hsl(var(--primary))",
12
- ring: "hsl(var(--primary) / 0.2)",
13
- },
14
- indigo: {
15
- bg: "hsl(231 92% 96%)",
16
- fg: "hsl(231 60% 38%)",
17
- ring: "hsl(231 80% 60% / 0.25)",
18
- },
19
- violet: {
20
- bg: "hsl(262 90% 96%)",
21
- fg: "hsl(262 55% 42%)",
22
- ring: "hsl(262 80% 60% / 0.25)",
23
- },
24
- slate: {
25
- bg: "hsl(230 25% 95%)",
26
- fg: "hsl(230 30% 30%)",
27
- ring: "hsl(230 20% 60% / 0.25)",
28
- },
29
- } as const;
30
-
31
- export type LauncherCardTone = keyof typeof TONE_STYLES;
32
-
33
- export interface LauncherCardProps {
34
- /**
35
- * Top-left identifier — typically a letter (`"C"`) or a small icon.
36
- */
37
- badge: React.ReactNode;
38
- /** Card heading. */
39
- title: string;
40
- /** One-or-two sentence body copy below the title. */
41
- description: string;
42
- /**
43
- * Colour key for the badge background, foreground, and hover ring.
44
- * Defaults to `"default"`, which uses the `--primary` token.
45
- */
46
- tone?: LauncherCardTone;
47
- /**
48
- * If set, the entire card becomes a link with a hover-lift effect.
49
- * Pair with `linkComponent` to route through a framework-specific Link
50
- * component (e.g. Next.js).
51
- */
52
- href?: string;
53
- /**
54
- * `target` attribute applied when `href` is set. Defaults to `"_self"`.
55
- */
56
- target?: React.HTMLAttributeAnchorTarget;
57
- /**
58
- * `rel` attribute applied when `href` is set. Defaults to `"noopener"`
59
- * when `target === "_blank"`.
60
- */
61
- rel?: string;
62
- /**
63
- * Override the rendered link element when `href` is set
64
- * (e.g. `Link` from `next/link`). Falls back to `<a>`.
65
- */
66
- linkComponent?: React.ElementType;
67
- /**
68
- * Footer slot. Typically a small CTA row (e.g. arrow link) or, when the
69
- * card is non-interactive, an inline status / detail block.
70
- */
71
- children?: React.ReactNode;
72
- className?: string;
73
- }
74
-
75
- const SHARED_CLASSES =
76
- "group flex flex-col gap-4 rounded-[14px] border border-border bg-card p-6 text-card-foreground";
77
-
78
- const INTERACTIVE_CLASSES =
79
- "no-underline transition-[transform,box-shadow,border-color] duration-200 hover:-translate-y-0.5 hover:shadow-[0_20px_40px_-20px_var(--launcher-tone-ring),0_8px_16px_-8px_rgb(0_0_0/0.08)] hover:[border-color:var(--launcher-tone-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2";
80
-
81
- const LauncherCard = React.forwardRef<HTMLElement, LauncherCardProps>(
82
- (
83
- {
84
- badge,
85
- title,
86
- description,
87
- tone = "default",
88
- href,
89
- target,
90
- rel,
91
- linkComponent,
92
- children,
93
- className,
94
- ...props
95
- },
96
- ref,
97
- ) => {
98
- const t = TONE_STYLES[tone];
99
- const interactive = Boolean(href);
100
- const toneVars = {
101
- "--launcher-tone-fg": t.fg,
102
- "--launcher-tone-ring": t.ring,
103
- } as React.CSSProperties;
104
-
105
- const inner = (
106
- <>
107
- <div className="flex items-center gap-3.5">
108
- <div
109
- className="flex h-11 w-11 items-center justify-center rounded-[10px] text-lg font-semibold tracking-tight"
110
- style={{ background: t.bg, color: t.fg }}
111
- >
112
- {badge}
113
- </div>
114
- <div className="text-[17px] font-semibold tracking-tight">{title}</div>
115
- </div>
116
- <p className="m-0 flex-1 text-sm leading-relaxed text-muted-foreground">{description}</p>
117
- {children != null && <div className="mt-1">{children}</div>}
118
- </>
119
- );
120
-
121
- if (interactive) {
122
- const LinkEl = linkComponent || "a";
123
- const resolvedRel = rel ?? (target === "_blank" ? "noopener" : undefined);
124
- return (
125
- <LinkEl
126
- ref={ref as React.Ref<HTMLAnchorElement>}
127
- href={href}
128
- target={target}
129
- rel={resolvedRel}
130
- className={cn(SHARED_CLASSES, INTERACTIVE_CLASSES, className)}
131
- style={toneVars}
132
- {...props}
133
- >
134
- {inner}
135
- </LinkEl>
136
- );
137
- }
138
-
139
- return (
140
- <div
141
- ref={ref as React.Ref<HTMLDivElement>}
142
- className={cn(SHARED_CLASSES, className)}
143
- {...props}
144
- >
145
- {inner}
146
- </div>
147
- );
148
- },
149
- );
150
- LauncherCard.displayName = "LauncherCard";
151
-
152
- export { LauncherCard };
@@ -1,36 +0,0 @@
1
- import { Loader2 } from "lucide-react";
2
- import * as React from "react";
3
-
4
- import { cn } from "../../lib/utils";
5
-
6
- export interface LoadingStateProps extends React.HTMLAttributes<HTMLDivElement> {
7
- message?: string;
8
- size?: "sm" | "default" | "lg";
9
- }
10
-
11
- const sizeMap = {
12
- sm: "h-4 w-4",
13
- default: "h-6 w-6",
14
- lg: "h-8 w-8",
15
- };
16
-
17
- const LoadingState = React.forwardRef<HTMLDivElement, LoadingStateProps>(
18
- ({ message = "Loading...", size = "default", className, ...props }, ref) => {
19
- return (
20
- <div
21
- ref={ref}
22
- className={cn(
23
- "flex flex-col items-center justify-center gap-3 py-8 text-muted-foreground",
24
- className,
25
- )}
26
- {...props}
27
- >
28
- <Loader2 className={cn("animate-spin", sizeMap[size])} />
29
- {message && <p className="text-sm">{message}</p>}
30
- </div>
31
- );
32
- },
33
- );
34
- LoadingState.displayName = "LoadingState";
35
-
36
- export { LoadingState };
@@ -1,67 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "../../lib/utils";
4
-
5
- export interface NotificationItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
6
- /** Leading icon — typically a Canvas `<Icon name="…" />`. */
7
- icon?: React.ReactNode;
8
- /** Tint variant for the icon's circular background. */
9
- iconTone?: "neutral" | "destructive" | "info" | "success" | "warning";
10
- /** Bold title line. */
11
- title: React.ReactNode;
12
- /** Optional secondary line (description / metadata). */
13
- description?: React.ReactNode;
14
- /** Right-aligned timestamp (relative or absolute). */
15
- timestamp?: React.ReactNode;
16
- /** When provided, the entire row becomes clickable. */
17
- onClick?: () => void;
18
- }
19
-
20
- const TONE: Record<NonNullable<NotificationItemProps["iconTone"]>, string> = {
21
- neutral: "bg-muted text-muted-foreground",
22
- destructive: "bg-[hsl(var(--stat-destructive)/0.1)] text-[hsl(var(--stat-destructive))]",
23
- info: "bg-[hsl(var(--stat-blue)/0.1)] text-[hsl(var(--stat-blue))]",
24
- success: "bg-[hsl(var(--stat-success)/0.1)] text-[hsl(var(--stat-success))]",
25
- warning: "bg-[hsl(var(--stat-amber)/0.1)] text-[hsl(var(--stat-amber))]",
26
- };
27
-
28
- export const NotificationItem = React.forwardRef<HTMLDivElement, NotificationItemProps>(
29
- (
30
- { icon, iconTone = "neutral", title, description, timestamp, onClick, className, ...props },
31
- ref,
32
- ) => {
33
- const Comp = onClick ? "button" : "div";
34
- return (
35
- <Comp
36
- ref={ref as never}
37
- type={onClick ? "button" : undefined}
38
- onClick={onClick}
39
- className={cn(
40
- "flex w-full items-start gap-3 px-3 py-3 text-left transition-colors",
41
- onClick && "cursor-pointer hover:bg-accent",
42
- className,
43
- )}
44
- {...(props as Record<string, unknown>)}
45
- >
46
- {icon && (
47
- <div
48
- className={cn(
49
- "flex h-8 w-8 shrink-0 items-center justify-center rounded-full",
50
- TONE[iconTone],
51
- )}
52
- >
53
- {icon}
54
- </div>
55
- )}
56
- <div className="min-w-0 flex-1 space-y-0.5">
57
- <div className="text-[13px] font-semibold text-foreground">{title}</div>
58
- {description && <div className="text-[12.5px] text-muted-foreground">{description}</div>}
59
- {timestamp && (
60
- <div className="font-mono text-[11px] text-muted-foreground">{timestamp}</div>
61
- )}
62
- </div>
63
- </Comp>
64
- );
65
- },
66
- );
67
- NotificationItem.displayName = "NotificationItem";
@@ -1,45 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "../../lib/utils";
4
-
5
- export interface NotificationListProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
6
- /** Header title — defaults to "Notifications". */
7
- title?: React.ReactNode;
8
- /** Optional count chip rendered to the right of the title (e.g. "3 new"). */
9
- count?: React.ReactNode;
10
- /** Footer slot — typically a single full-width "View all" link/button. */
11
- footer?: React.ReactNode;
12
- /** Notification rows — typically `<NotificationItem>` instances. */
13
- children?: React.ReactNode;
14
- }
15
-
16
- export const NotificationList = React.forwardRef<HTMLDivElement, NotificationListProps>(
17
- ({ title = "Notifications", count, footer, children, className, ...props }, ref) => {
18
- return (
19
- <div
20
- ref={ref}
21
- className={cn(
22
- "w-80 overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-lg",
23
- className,
24
- )}
25
- {...props}
26
- >
27
- <div className="flex items-center justify-between border-b border-border px-3 py-2.5">
28
- <span className="text-[13px] font-semibold">{title}</span>
29
- {count != null && (
30
- <span className="inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
31
- {count}
32
- </span>
33
- )}
34
- </div>
35
- <div className="max-h-96 divide-y divide-border overflow-y-auto">{children}</div>
36
- {footer && (
37
- <div className="border-t border-border bg-muted/30 px-3 py-2 text-center text-[12.5px] font-medium">
38
- {footer}
39
- </div>
40
- )}
41
- </div>
42
- );
43
- },
44
- );
45
- NotificationList.displayName = "NotificationList";