@olympusoss/canvas 2.20.2 → 4.0.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 (208) hide show
  1. package/package.json +41 -177
  2. package/src/cn.ts +3 -0
  3. package/src/index.ts +12 -603
  4. package/src/theme.ts +41 -0
  5. package/src/tokens.ts +11 -0
  6. package/styles/base.css +17 -0
  7. package/styles/canvas.css +69 -52
  8. package/styles/components/alert.css +66 -0
  9. package/styles/components/app-shell.css +46 -0
  10. package/styles/components/avatar.css +15 -0
  11. package/styles/components/badge.css +83 -0
  12. package/styles/components/breadcrumb.css +35 -0
  13. package/styles/components/button-group.css +23 -0
  14. package/styles/components/button.css +107 -0
  15. package/styles/components/calendar.css +73 -0
  16. package/styles/components/card.css +58 -0
  17. package/styles/components/checkbox.css +55 -0
  18. package/styles/components/code-block.css +18 -0
  19. package/styles/components/combobox.css +75 -0
  20. package/styles/components/command.css +94 -0
  21. package/styles/components/data-table.css +142 -0
  22. package/styles/components/dialog.css +72 -0
  23. package/styles/components/dropdown.css +54 -0
  24. package/styles/components/empty-state.css +17 -0
  25. package/styles/components/field.css +27 -0
  26. package/styles/components/filter-panel.css +58 -0
  27. package/styles/components/form.css +27 -0
  28. package/styles/components/icon.css +8 -0
  29. package/styles/components/input-group.css +45 -0
  30. package/styles/components/input.css +56 -0
  31. package/styles/components/kbd.css +15 -0
  32. package/styles/components/page-header.css +52 -0
  33. package/styles/components/pagination.css +48 -0
  34. package/styles/components/popover.css +14 -0
  35. package/styles/components/radio.css +28 -0
  36. package/styles/components/row-menu.css +69 -0
  37. package/styles/components/section-card.css +49 -0
  38. package/styles/components/select.css +57 -0
  39. package/styles/components/separator.css +32 -0
  40. package/styles/components/sheet.css +70 -0
  41. package/styles/components/sidebar.css +146 -0
  42. package/styles/components/skeleton.css +32 -0
  43. package/styles/components/spinner.css +26 -0
  44. package/styles/components/stat-card.css +71 -0
  45. package/styles/components/stepper.css +63 -0
  46. package/styles/components/switch.css +45 -0
  47. package/styles/components/tabs.css +40 -0
  48. package/styles/components/textarea.css +31 -0
  49. package/styles/components/toast.css +95 -0
  50. package/styles/components/tooltip.css +53 -0
  51. package/styles/components/topbar.css +24 -0
  52. package/styles/components/typography.css +105 -0
  53. package/styles/patterns/backdrops.css +35 -0
  54. package/styles/patterns/density.css +66 -0
  55. package/styles/patterns/focus.css +38 -0
  56. package/styles/patterns/glass.css +85 -0
  57. package/styles/patterns/high-contrast.css +70 -0
  58. package/styles/patterns/reduced-motion.css +12 -0
  59. package/styles/patterns/scrollbar.css +10 -0
  60. package/styles/reset.css +89 -0
  61. package/styles/tokens/colors.css +106 -0
  62. package/styles/tokens/motion.css +33 -0
  63. package/styles/tokens/radius.css +10 -0
  64. package/styles/tokens/shadows.css +35 -0
  65. package/styles/tokens/spacing.css +19 -0
  66. package/styles/tokens/typography.css +6 -0
  67. package/styles/tokens/z-index.css +12 -0
  68. package/tsconfig.json +20 -21
  69. package/README.md +0 -60
  70. package/src/components/atoms/README.md +0 -11
  71. package/src/components/atoms/aspect-ratio.tsx +0 -32
  72. package/src/components/atoms/avatar.tsx +0 -98
  73. package/src/components/atoms/badge.tsx +0 -44
  74. package/src/components/atoms/brand-mark.tsx +0 -74
  75. package/src/components/atoms/button.tsx +0 -105
  76. package/src/components/atoms/checkbox.tsx +0 -63
  77. package/src/components/atoms/flex-box.tsx +0 -105
  78. package/src/components/atoms/icon.tsx +0 -34
  79. package/src/components/atoms/input.tsx +0 -92
  80. package/src/components/atoms/label.tsx +0 -41
  81. package/src/components/atoms/logo.tsx +0 -89
  82. package/src/components/atoms/progress.tsx +0 -55
  83. package/src/components/atoms/radio-group.tsx +0 -122
  84. package/src/components/atoms/scroll-area.tsx +0 -106
  85. package/src/components/atoms/section.tsx +0 -48
  86. package/src/components/atoms/separator.tsx +0 -45
  87. package/src/components/atoms/skeleton.tsx +0 -17
  88. package/src/components/atoms/slider.tsx +0 -93
  89. package/src/components/atoms/spinner.tsx +0 -47
  90. package/src/components/atoms/switch.tsx +0 -60
  91. package/src/components/atoms/textarea.tsx +0 -78
  92. package/src/components/atoms/toggle.tsx +0 -80
  93. package/src/components/charts/activity-heatmap.tsx +0 -186
  94. package/src/components/charts/axes.tsx +0 -21
  95. package/src/components/charts/chart-container.tsx +0 -254
  96. package/src/components/charts/chart-legend.tsx +0 -67
  97. package/src/components/charts/chart-tooltip.tsx +0 -161
  98. package/src/components/charts/chart-types.tsx +0 -49
  99. package/src/components/charts/containers.tsx +0 -11
  100. package/src/components/charts/data.tsx +0 -16
  101. package/src/components/charts/details.tsx +0 -25
  102. package/src/components/charts/dot-pulse.tsx +0 -61
  103. package/src/components/charts/gauge.tsx +0 -106
  104. package/src/components/charts/grids.tsx +0 -8
  105. package/src/components/charts/index.ts +0 -62
  106. package/src/components/charts/labeled-bar-list.tsx +0 -85
  107. package/src/components/charts/metric-breakdown.tsx +0 -316
  108. package/src/components/charts/references.tsx +0 -8
  109. package/src/components/charts/service-health-list.tsx +0 -85
  110. package/src/components/charts/sparkline-area.tsx +0 -80
  111. package/src/components/charts/sparkline.tsx +0 -52
  112. package/src/components/charts/stacked-bar.tsx +0 -104
  113. package/src/components/charts/text.tsx +0 -10
  114. package/src/components/charts/world-heat-map-inner.tsx +0 -317
  115. package/src/components/charts/world-heat-map.tsx +0 -184
  116. package/src/components/molecules/README.md +0 -12
  117. package/src/components/molecules/action-bar.tsx +0 -73
  118. package/src/components/molecules/activity-item.tsx +0 -74
  119. package/src/components/molecules/alert.tsx +0 -86
  120. package/src/components/molecules/animated-background.tsx +0 -92
  121. package/src/components/molecules/auth-shell.tsx +0 -95
  122. package/src/components/molecules/brand-lockup.tsx +0 -48
  123. package/src/components/molecules/breadcrumb.tsx +0 -157
  124. package/src/components/molecules/button-group.tsx +0 -104
  125. package/src/components/molecules/calendar.tsx +0 -217
  126. package/src/components/molecules/card.tsx +0 -102
  127. package/src/components/molecules/client-brand.tsx +0 -95
  128. package/src/components/molecules/code-block.tsx +0 -86
  129. package/src/components/molecules/countdown-button.tsx +0 -92
  130. package/src/components/molecules/empty-state.tsx +0 -56
  131. package/src/components/molecules/error-state.tsx +0 -42
  132. package/src/components/molecules/field-display.tsx +0 -35
  133. package/src/components/molecules/input-otp.tsx +0 -74
  134. package/src/components/molecules/launcher-card.tsx +0 -152
  135. package/src/components/molecules/loading-state.tsx +0 -36
  136. package/src/components/molecules/notification-item.tsx +0 -67
  137. package/src/components/molecules/notification-list.tsx +0 -45
  138. package/src/components/molecules/number-badge.tsx +0 -53
  139. package/src/components/molecules/or-separator.tsx +0 -38
  140. package/src/components/molecules/page-header.tsx +0 -88
  141. package/src/components/molecules/page-tabs.tsx +0 -94
  142. package/src/components/molecules/pagination.tsx +0 -150
  143. package/src/components/molecules/password-input.tsx +0 -83
  144. package/src/components/molecules/password-strength-meter.tsx +0 -104
  145. package/src/components/molecules/phone-input.tsx +0 -200
  146. package/src/components/molecules/search-bar.tsx +0 -64
  147. package/src/components/molecules/secret-field.tsx +0 -158
  148. package/src/components/molecules/section-card.tsx +0 -91
  149. package/src/components/molecules/social-buttons.tsx +0 -165
  150. package/src/components/molecules/stat-card.tsx +0 -100
  151. package/src/components/molecules/status-badge.tsx +0 -42
  152. package/src/components/molecules/stepper.tsx +0 -96
  153. package/src/components/molecules/table.tsx +0 -157
  154. package/src/components/molecules/terminal.tsx +0 -74
  155. package/src/components/molecules/toggle-group.tsx +0 -145
  156. package/src/components/molecules/tooltip.tsx +0 -155
  157. package/src/components/molecules/user-avatar-chip.tsx +0 -71
  158. package/src/components/organisms/README.md +0 -14
  159. package/src/components/organisms/accordion.tsx +0 -154
  160. package/src/components/organisms/alert-dialog.tsx +0 -277
  161. package/src/components/organisms/carousel.tsx +0 -244
  162. package/src/components/organisms/collapsible.tsx +0 -69
  163. package/src/components/organisms/command.tsx +0 -144
  164. package/src/components/organisms/context-menu.tsx +0 -339
  165. package/src/components/organisms/dashboard-grid.tsx +0 -369
  166. package/src/components/organisms/data-table.tsx +0 -330
  167. package/src/components/organisms/dialog.tsx +0 -312
  168. package/src/components/organisms/drawer.tsx +0 -123
  169. package/src/components/organisms/dropdown-menu.tsx +0 -440
  170. package/src/components/organisms/editors/code-editor.tsx +0 -144
  171. package/src/components/organisms/editors/index.ts +0 -4
  172. package/src/components/organisms/editors/markdown-editor.tsx +0 -153
  173. package/src/components/organisms/editors/markdown-renderer.ts +0 -27
  174. package/src/components/organisms/editors/prose-canvas-classes.ts +0 -45
  175. package/src/components/organisms/editors/rich-text-editor.tsx +0 -126
  176. package/src/components/organisms/editors/toolbar/md-toolbar.tsx +0 -129
  177. package/src/components/organisms/editors/toolbar/rte-toolbar.tsx +0 -211
  178. package/src/components/organisms/editors/toolbar/toolbar-shell.tsx +0 -45
  179. package/src/components/organisms/editors/use-codemirror-theme.ts +0 -61
  180. package/src/components/organisms/error-boundary.tsx +0 -61
  181. package/src/components/organisms/form.tsx +0 -174
  182. package/src/components/organisms/hover-card.tsx +0 -115
  183. package/src/components/organisms/menubar.tsx +0 -498
  184. package/src/components/organisms/navbar.tsx +0 -104
  185. package/src/components/organisms/navigation-menu.tsx +0 -235
  186. package/src/components/organisms/popover.tsx +0 -149
  187. package/src/components/organisms/resizable.tsx +0 -58
  188. package/src/components/organisms/schema-form.tsx +0 -232
  189. package/src/components/organisms/select.tsx +0 -309
  190. package/src/components/organisms/sheet.tsx +0 -265
  191. package/src/components/organisms/sidebar.tsx +0 -1040
  192. package/src/components/organisms/sonner.tsx +0 -96
  193. package/src/components/organisms/tabs.tsx +0 -133
  194. package/src/components/organisms/theme-provider.tsx +0 -101
  195. package/src/hooks/use-mobile.tsx +0 -19
  196. package/src/lib/portal-container.tsx +0 -35
  197. package/src/lib/utils.ts +0 -6
  198. package/src/native.ts +0 -23
  199. package/src/tokens/colors.ts +0 -91
  200. package/src/tokens/index.ts +0 -3
  201. package/src/tokens/spacing.ts +0 -55
  202. package/src/tokens/typography.ts +0 -27
  203. package/styles/dashboard-grid.css +0 -47
  204. package/styles/fonts/Roboto-VariableFont_wdth_wght.ttf +0 -0
  205. package/styles/glass.css +0 -175
  206. package/styles/leaflet.css +0 -13
  207. package/styles/tokens.css +0 -317
  208. package/tailwind.config.ts +0 -70
@@ -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";
@@ -1,53 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "../../lib/utils";
4
-
5
- export interface NumberBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
6
- /** Numeric count. When omitted (or `dot` is true), renders a tone-tinted dot. */
7
- count?: number;
8
- /** Cap displayed count — anything above renders as `${max}+`. Default 99. */
9
- max?: number;
10
- /** Color tone. */
11
- tone?: "destructive" | "default" | "muted";
12
- /** Render as a dot regardless of count. */
13
- dot?: boolean;
14
- }
15
-
16
- const TONE: Record<NonNullable<NumberBadgeProps["tone"]>, string> = {
17
- destructive: "bg-destructive text-destructive-foreground",
18
- default: "bg-primary text-primary-foreground",
19
- muted: "bg-muted text-muted-foreground",
20
- };
21
-
22
- /**
23
- * Small overlay badge for counts on icon buttons (notification bell, inbox,
24
- * etc.). Position it absolutely against a `relative` parent — typically by
25
- * pairing with `absolute -right-1 -top-1` or similar utility classes via
26
- * `className`. Defaults to top-right placement.
27
- */
28
- export const NumberBadge = React.forwardRef<HTMLSpanElement, NumberBadgeProps>(
29
- ({ count, max = 99, tone = "destructive", dot, className, ...props }, ref) => {
30
- const isDot = dot || count == null;
31
- const display = !isDot && count != null && count > max ? `${max}+` : `${count ?? ""}`;
32
- return (
33
- <span
34
- ref={ref}
35
- role="status"
36
- aria-label={isDot ? "New" : `${count} new`}
37
- className={cn(
38
- "pointer-events-none absolute -right-1 -top-1 inline-flex items-center justify-center font-mono font-medium tabular-nums",
39
- isDot
40
- ? "h-1.5 w-1.5 rounded-full"
41
- : "min-w-[1.125rem] rounded-full px-1 text-[10px] leading-none",
42
- !isDot && "h-[1.125rem]",
43
- TONE[tone],
44
- className,
45
- )}
46
- {...props}
47
- >
48
- {!isDot && display}
49
- </span>
50
- );
51
- },
52
- );
53
- NumberBadge.displayName = "NumberBadge";
@@ -1,38 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "../../lib/utils";
4
-
5
- export interface OrSeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
6
- /**
7
- * Text rendered between the two rule segments.
8
- * @default "or"
9
- */
10
- label?: React.ReactNode;
11
- }
12
-
13
- /**
14
- * Horizontal divider with a centred label, e.g. between social-provider
15
- * buttons and an email/password form. Two `border-t` rules flank a short
16
- * upper-case label sitting on the card background.
17
- *
18
- * Place inside a card whose background is `bg-card`; the label inherits
19
- * that surface so the rules visually meet behind it.
20
- */
21
- const OrSeparator = React.forwardRef<HTMLDivElement, OrSeparatorProps>(
22
- ({ label = "or", className, ...props }, ref) => (
23
- <div
24
- ref={ref}
25
- role="separator"
26
- aria-orientation="horizontal"
27
- className={cn("flex items-center gap-3 py-1", className)}
28
- {...props}
29
- >
30
- <div className="h-px flex-1 bg-border" />
31
- <span className="text-[11px] uppercase tracking-[0.08em] text-muted-foreground">{label}</span>
32
- <div className="h-px flex-1 bg-border" />
33
- </div>
34
- ),
35
- );
36
- OrSeparator.displayName = "OrSeparator";
37
-
38
- export { OrSeparator };
@@ -1,88 +0,0 @@
1
- import type * as React from "react";
2
-
3
- import { cn } from "../../lib/utils";
4
- import { Icon } from "../atoms/icon";
5
-
6
- export interface PageHeaderBreadcrumb {
7
- label: string;
8
- href?: string;
9
- }
10
-
11
- export interface PageHeaderProps {
12
- title: string | React.ReactNode;
13
- subtitle?: string | React.ReactNode;
14
- icon?: React.ReactNode;
15
- actions?: React.ReactNode;
16
- breadcrumbs?: PageHeaderBreadcrumb[];
17
- /**
18
- * Optional link wrapper. Pass Next.js `Link` or React Router `Link` to make
19
- * breadcrumb links client-side routable. Defaults to a plain `<a>`.
20
- */
21
- linkComponent?: React.ComponentType<{
22
- href: string;
23
- className?: string;
24
- children: React.ReactNode;
25
- }>;
26
- className?: string;
27
- }
28
-
29
- export function PageHeader({
30
- title,
31
- subtitle,
32
- icon,
33
- actions,
34
- breadcrumbs,
35
- linkComponent: LinkComp,
36
- className,
37
- }: PageHeaderProps) {
38
- return (
39
- <div className={cn("mb-6 flex items-start justify-between", className)}>
40
- <div className="space-y-1">
41
- {breadcrumbs && breadcrumbs.length > 0 && (
42
- <div className="mb-2 flex items-center gap-1 text-sm text-muted-foreground">
43
- {breadcrumbs.map((crumb, i) => (
44
- <span key={crumb.label} className="flex items-center gap-1">
45
- {crumb.href ? (
46
- LinkComp ? (
47
- <LinkComp href={crumb.href} className="hover:text-brand transition-colors">
48
- {crumb.label}
49
- </LinkComp>
50
- ) : (
51
- <a href={crumb.href} className="hover:text-brand transition-colors">
52
- {crumb.label}
53
- </a>
54
- )
55
- ) : (
56
- <span className="text-foreground">{crumb.label}</span>
57
- )}
58
- {i < breadcrumbs.length - 1 && <Icon name="ChevronRight" className="h-3 w-3" />}
59
- </span>
60
- ))}
61
- </div>
62
- )}
63
- <div className="flex items-center gap-3">
64
- {icon}
65
- {typeof title === "string" ? (
66
- <h1 className="text-[22px] font-semibold tracking-[-0.02em] text-foreground">
67
- {title}
68
- </h1>
69
- ) : (
70
- title
71
- )}
72
- </div>
73
- {subtitle && (
74
- <div>
75
- {typeof subtitle === "string" ? (
76
- <p className="text-sm text-muted-foreground">{subtitle}</p>
77
- ) : (
78
- subtitle
79
- )}
80
- </div>
81
- )}
82
- </div>
83
- {actions && <div className="flex items-center gap-2">{actions}</div>}
84
- </div>
85
- );
86
- }
87
-
88
- PageHeader.displayName = "PageHeader";
@@ -1,94 +0,0 @@
1
- "use client";
2
-
3
- import type * as React from "react";
4
-
5
- import { cn } from "../../lib/utils";
6
-
7
- export interface PageTabsItem {
8
- label: string;
9
- value: string;
10
- icon?: React.ReactNode;
11
- badge?: number | string;
12
- }
13
-
14
- export interface PageTabsProps {
15
- tabs: PageTabsItem[];
16
- value: string;
17
- onChange: (value: string) => void;
18
- variant?: "default" | "pills" | "underline";
19
- }
20
-
21
- function renderBadge(badge: number | string | undefined, active: boolean): React.ReactNode {
22
- if (badge === undefined) return null;
23
- return (
24
- <span
25
- className={cn(
26
- "ml-1 rounded-full px-1.5 py-0.5 text-xs font-medium",
27
- active ? "bg-primary/10 text-primary" : "bg-muted-foreground/10 text-muted-foreground",
28
- )}
29
- >
30
- {badge}
31
- </span>
32
- );
33
- }
34
-
35
- export function PageTabs({ tabs, value, onChange, variant = "default" }: PageTabsProps) {
36
- if (variant === "underline") {
37
- return (
38
- <div className="flex gap-4 border-b border-border">
39
- {tabs.map((tab) => {
40
- const active = value === tab.value;
41
- return (
42
- <button
43
- key={tab.value}
44
- type="button"
45
- onClick={() => onChange(tab.value)}
46
- className={cn(
47
- "inline-flex items-center gap-1.5 border-b-2 px-1 pb-3 text-sm font-medium transition-colors",
48
- active
49
- ? "border-primary text-primary"
50
- : "border-transparent text-muted-foreground hover:border-border hover:text-foreground",
51
- )}
52
- >
53
- {tab.icon && <span className="h-4 w-4">{tab.icon}</span>}
54
- {tab.label}
55
- {renderBadge(tab.badge, active)}
56
- </button>
57
- );
58
- })}
59
- </div>
60
- );
61
- }
62
-
63
- const isPills = variant === "pills";
64
- return (
65
- <div className={cn("flex flex-wrap gap-1", isPills && "rounded-lg bg-muted p-1")}>
66
- {tabs.map((tab) => {
67
- const active = value === tab.value;
68
- return (
69
- <button
70
- key={tab.value}
71
- type="button"
72
- onClick={() => onChange(tab.value)}
73
- className={cn(
74
- "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
75
- isPills
76
- ? active
77
- ? "bg-background text-foreground shadow-sm"
78
- : "text-muted-foreground hover:text-foreground"
79
- : active
80
- ? "bg-accent text-accent-foreground"
81
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
82
- )}
83
- >
84
- {tab.icon && <span className="h-4 w-4">{tab.icon}</span>}
85
- {tab.label}
86
- {renderBadge(tab.badge, active)}
87
- </button>
88
- );
89
- })}
90
- </div>
91
- );
92
- }
93
-
94
- PageTabs.displayName = "PageTabs";