@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,158 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
-
5
- import { cn } from "../../lib/utils";
6
- import { Icon } from "../atoms/icon";
7
- import { Input } from "../atoms/input";
8
- import { Label } from "../atoms/label";
9
-
10
- export type SecretFieldStatus = "idle" | "validating" | "valid" | "invalid";
11
-
12
- export interface SecretFieldProps {
13
- id: string;
14
- value: string;
15
- onChange: (value: string) => void;
16
- /**
17
- * Async validator. Return `true` (or any string) on success; throw or return
18
- * `false` on failure. The thrown message is shown below the input.
19
- */
20
- onValidate?: (value: string) => Promise<boolean | string>;
21
- /**
22
- * Called after successful validation with the validated value.
23
- * Use this to persist the value once it's confirmed valid.
24
- */
25
- onSave?: (value: string) => void;
26
- /**
27
- * Notifies when the validation status changes. Useful for parent state.
28
- */
29
- onStatusChange?: (status: SecretFieldStatus) => void;
30
- placeholder?: string;
31
- label?: string;
32
- disabled?: boolean;
33
- /** Minimum length before auto-validation starts. Default 3. */
34
- minLength?: number;
35
- /** Show the reveal (eye) toggle. Default true. */
36
- revealable?: boolean;
37
- className?: string;
38
- }
39
-
40
- export function SecretField({
41
- id,
42
- value,
43
- onChange,
44
- onValidate,
45
- onSave,
46
- onStatusChange,
47
- placeholder,
48
- label,
49
- disabled,
50
- minLength = 3,
51
- revealable = true,
52
- className,
53
- }: SecretFieldProps) {
54
- const [visible, setVisible] = React.useState(false);
55
- const [status, setStatus] = React.useState<SecretFieldStatus>("idle");
56
- const [error, setError] = React.useState("");
57
- const debounceRef = React.useRef(0);
58
- const lastValidatedRef = React.useRef("");
59
-
60
- React.useEffect(() => {
61
- onStatusChange?.(status);
62
- }, [status, onStatusChange]);
63
-
64
- React.useEffect(() => {
65
- if (!onValidate) return;
66
- if (!value || value.length < minLength) {
67
- setStatus("idle");
68
- setError("");
69
- return;
70
- }
71
- /* c8 ignore next -- race-condition guard: only triggered by rapid input */
72
- if (value === lastValidatedRef.current) return;
73
-
74
- const id = ++debounceRef.current;
75
- setStatus("validating");
76
- setError("");
77
-
78
- const timer = setTimeout(async () => {
79
- /* c8 ignore next -- race-condition guard: debounce re-entry unreachable in tests */
80
- if (id !== debounceRef.current) return;
81
- try {
82
- const result = await onValidate(value);
83
- /* c8 ignore next -- race-condition guard: await-resolved stale id unreachable in tests */
84
- if (id !== debounceRef.current) return;
85
- if (result === true || typeof result === "string") {
86
- setStatus("valid");
87
- setError("");
88
- lastValidatedRef.current = value;
89
- onSave?.(value);
90
- } else {
91
- setStatus("invalid");
92
- setError("Validation failed");
93
- }
94
- } catch (err) {
95
- /* c8 ignore next -- race-condition guard: await-rejected stale id unreachable in tests */
96
- if (id !== debounceRef.current) return;
97
- setStatus("invalid");
98
- setError(err instanceof Error ? err.message : "Validation failed");
99
- }
100
- }, 800);
101
-
102
- return () => clearTimeout(timer);
103
- }, [value, minLength, onValidate, onSave]);
104
-
105
- const handleChange = (v: string) => {
106
- onChange(v);
107
- if (error) setError("");
108
- lastValidatedRef.current = "";
109
- };
110
-
111
- return (
112
- <div className={cn("space-y-1", className)}>
113
- {label && (
114
- <Label htmlFor={id} className="text-xs">
115
- {label}
116
- </Label>
117
- )}
118
- <div className="relative">
119
- <Input
120
- id={id}
121
- type={visible ? "text" : "password"}
122
- value={value}
123
- onChange={(e) => handleChange(e.target.value)}
124
- placeholder={placeholder}
125
- className={cn(
126
- "font-mono text-sm",
127
- revealable && "pr-16",
128
- !revealable && "pr-10",
129
- status === "invalid" && "border-destructive",
130
- status === "valid" && "border-green-500/50",
131
- )}
132
- disabled={disabled}
133
- />
134
- <div className="absolute right-2.5 top-1/2 -translate-y-1/2 flex items-center gap-1.5">
135
- {status === "validating" && (
136
- <Icon name="LoaderCircle" className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
137
- )}
138
- {status === "valid" && <Icon name="CircleCheck" className="h-3.5 w-3.5 text-green-500" />}
139
- {status === "invalid" && <Icon name="CircleX" className="h-3.5 w-3.5 text-destructive" />}
140
- {revealable && (
141
- <button
142
- type="button"
143
- onClick={() => setVisible((v) => !v)}
144
- aria-label={visible ? "Hide value" : "Show value"}
145
- className="text-muted-foreground hover:text-foreground transition-colors"
146
- tabIndex={-1}
147
- >
148
- <Icon name={visible ? "EyeOff" : "Eye"} className="h-4 w-4" />
149
- </button>
150
- )}
151
- </div>
152
- </div>
153
- {error && <p className="text-xs text-destructive">{error}</p>}
154
- </div>
155
- );
156
- }
157
-
158
- SecretField.displayName = "SecretField";
@@ -1,91 +0,0 @@
1
- import type * as React from "react";
2
-
3
- import { cn } from "../../lib/utils";
4
- import { Icon } from "../atoms/icon";
5
- import { Alert, AlertDescription } from "./alert";
6
- import { Card, CardContent, CardHeader } from "./card";
7
-
8
- export interface SectionCardProps {
9
- title?: string | React.ReactNode;
10
- subtitle?: string;
11
- /**
12
- * Icon rendered to the left of the title (16px Lucide via the Canvas
13
- * `<Icon>` atom is the canonical pattern).
14
- */
15
- icon?: React.ReactNode;
16
- /** Right-aligned action slot inside the header. */
17
- actions?: React.ReactNode;
18
- /** @deprecated Use `actions`. */
19
- headerActions?: React.ReactNode;
20
- children?: React.ReactNode;
21
- loading?: boolean;
22
- error?: string | boolean | null;
23
- emptyMessage?: string;
24
- padding?: boolean;
25
- className?: string;
26
- }
27
-
28
- export function SectionCard({
29
- title,
30
- subtitle,
31
- icon,
32
- actions,
33
- headerActions,
34
- children,
35
- loading = false,
36
- error,
37
- emptyMessage,
38
- padding = true,
39
- className,
40
- }: SectionCardProps) {
41
- const resolvedActions = actions ?? headerActions;
42
- const hasHeader = title || subtitle || icon || resolvedActions;
43
- return (
44
- <Card className={className}>
45
- {hasHeader && (
46
- <>
47
- <CardHeader className="flex flex-row items-center justify-between space-y-0 px-5 pb-3 pt-[18px]">
48
- <div className="flex flex-1 flex-col gap-1">
49
- {(title || icon) && (
50
- <div className="flex items-center gap-2">
51
- {icon}
52
- {title &&
53
- (typeof title === "string" ? (
54
- <span className="text-[15px] font-semibold leading-none">{title}</span>
55
- ) : (
56
- title
57
- ))}
58
- </div>
59
- )}
60
- {subtitle && <p className="text-sm text-muted-foreground">{subtitle}</p>}
61
- </div>
62
- {resolvedActions && <div className="flex items-center gap-2">{resolvedActions}</div>}
63
- </CardHeader>
64
- <div data-slot="card-divider" className="mx-5 mb-3.5 h-px bg-border" />
65
- </>
66
- )}
67
- <CardContent className={cn(padding ? "px-5 pb-[18px] pt-0" : "p-0")}>
68
- {loading ? (
69
- <div className="flex items-center justify-center py-8">
70
- <Icon name="LoaderCircle" className="h-6 w-6 animate-spin text-muted-foreground" />
71
- </div>
72
- ) : error ? (
73
- <Alert variant="destructive">
74
- <Icon name="CircleX" className="h-4 w-4" />
75
- <AlertDescription>
76
- {typeof error === "string" ? error : "An error occurred"}
77
- </AlertDescription>
78
- </Alert>
79
- ) : emptyMessage && !children ? (
80
- <div className="flex items-center justify-center py-8 text-center">
81
- <p className="text-sm text-muted-foreground">{emptyMessage}</p>
82
- </div>
83
- ) : (
84
- children
85
- )}
86
- </CardContent>
87
- </Card>
88
- );
89
- }
90
-
91
- SectionCard.displayName = "SectionCard";
@@ -1,165 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
-
5
- import { cn } from "../../lib/utils";
6
- import { Button } from "../atoms/button";
7
-
8
- /* ──────────────────────────────────────────────────────────────────
9
- Brand glyphs. Tiny, currentColor for monochrome marks
10
- (GitHub, Microsoft outline, generic SSO). Multi-color marks
11
- (Google, Apple) inline their official palette.
12
- ────────────────────────────────────────────────────────────────── */
13
-
14
- const GitHubGlyph = () => (
15
- <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
16
- <title>GitHub</title>
17
- <path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2c-3.2.69-3.87-1.54-3.87-1.54-.52-1.33-1.27-1.69-1.27-1.69-1.04-.71.08-.7.08-.7 1.15.08 1.75 1.18 1.75 1.18 1.02 1.75 2.68 1.24 3.34.95.1-.74.4-1.24.72-1.52-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.28 1.18-3.08-.12-.29-.51-1.46.11-3.04 0 0 .97-.31 3.18 1.18a11 11 0 0 1 5.78 0c2.21-1.49 3.18-1.18 3.18-1.18.62 1.58.23 2.75.11 3.04.74.8 1.18 1.82 1.18 3.08 0 4.42-2.69 5.39-5.26 5.68.41.35.77 1.04.77 2.11v3.13c0 .31.21.67.79.56A11.51 11.51 0 0 0 23.5 12c0-6.35-5.15-11.5-11.5-11.5z" />
18
- </svg>
19
- );
20
-
21
- const GoogleGlyph = () => (
22
- <svg width="16" height="16" viewBox="0 0 24 24" aria-hidden>
23
- <title>Google</title>
24
- <path
25
- fill="#4285F4"
26
- d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.76h3.56c2.08-1.92 3.28-4.74 3.28-8.09z"
27
- />
28
- <path
29
- fill="#34A853"
30
- d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.56-2.76c-.98.66-2.24 1.06-3.72 1.06-2.86 0-5.29-1.93-6.15-4.53H2.18v2.84A11 11 0 0 0 12 23z"
31
- />
32
- <path
33
- fill="#FBBC05"
34
- d="M5.85 14.11A6.6 6.6 0 0 1 5.5 12c0-.73.13-1.44.35-2.11V7.05H2.18A11 11 0 0 0 1 12c0 1.78.43 3.46 1.18 4.95l3.67-2.84z"
35
- />
36
- <path
37
- fill="#EA4335"
38
- d="M12 5.38c1.62 0 3.06.56 4.2 1.64l3.15-3.15C17.45 2.1 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.05l3.67 2.84C6.71 7.31 9.14 5.38 12 5.38z"
39
- />
40
- </svg>
41
- );
42
-
43
- const AppleGlyph = () => (
44
- <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
45
- <title>Apple</title>
46
- <path d="M17.05 12.04c-.03-2.85 2.32-4.22 2.43-4.29-1.32-1.94-3.39-2.21-4.12-2.24-1.75-.18-3.42 1.03-4.31 1.03-.9 0-2.27-1.01-3.74-.98-1.92.03-3.7 1.12-4.69 2.83-2.01 3.47-.51 8.6 1.43 11.43.96 1.38 2.09 2.93 3.56 2.87 1.44-.06 1.98-.92 3.71-.92 1.73 0 2.22.92 3.74.89 1.54-.03 2.52-1.4 3.46-2.79 1.1-1.6 1.55-3.15 1.58-3.23-.03-.01-3.03-1.16-3.05-4.6zM14.27 3.65c.79-.96 1.32-2.29 1.18-3.62-1.14.05-2.52.76-3.34 1.71-.73.84-1.37 2.19-1.2 3.49 1.27.1 2.57-.65 3.36-1.58z" />
47
- </svg>
48
- );
49
-
50
- const MicrosoftGlyph = () => (
51
- <svg width="16" height="16" viewBox="0 0 24 24" aria-hidden>
52
- <title>Microsoft</title>
53
- <path fill="#F25022" d="M1 1h10v10H1z" />
54
- <path fill="#7FBA00" d="M13 1h10v10H13z" />
55
- <path fill="#00A4EF" d="M1 13h10v10H1z" />
56
- <path fill="#FFB900" d="M13 13h10v10H13z" />
57
- </svg>
58
- );
59
-
60
- const SsoGlyph = () => (
61
- <svg
62
- width="16"
63
- height="16"
64
- viewBox="0 0 24 24"
65
- fill="none"
66
- stroke="currentColor"
67
- strokeWidth={2}
68
- strokeLinecap="round"
69
- strokeLinejoin="round"
70
- aria-hidden
71
- >
72
- <title>SSO</title>
73
- <rect x="3" y="11" width="18" height="11" rx="2" />
74
- <path d="M7 11V7a5 5 0 0 1 10 0v4" />
75
- </svg>
76
- );
77
-
78
- /**
79
- * Known social/SSO providers. Each entry pairs a glyph with a default
80
- * label; the caller can override the label per-button if needed.
81
- */
82
- export type SocialProvider = "github" | "google" | "apple" | "microsoft" | "sso";
83
-
84
- interface ProviderMeta {
85
- glyph: React.ReactNode;
86
- label: string;
87
- }
88
-
89
- const PROVIDERS: Record<SocialProvider, ProviderMeta> = {
90
- github: { glyph: <GitHubGlyph />, label: "Continue with GitHub" },
91
- google: { glyph: <GoogleGlyph />, label: "Continue with Google" },
92
- apple: { glyph: <AppleGlyph />, label: "Continue with Apple" },
93
- microsoft: { glyph: <MicrosoftGlyph />, label: "Continue with Microsoft" },
94
- sso: { glyph: <SsoGlyph />, label: "Continue with SSO" },
95
- };
96
-
97
- export interface SocialButtonProps
98
- extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
99
- /** Provider identifier. Picks the glyph and default label. */
100
- provider: SocialProvider;
101
- /** Override the default label (e.g. "Continue with Okta"). */
102
- label?: string;
103
- }
104
-
105
- /**
106
- * Single provider button. Outline variant with the provider glyph on the
107
- * leading edge. Use directly when you want one provider, or compose
108
- * several inside `SocialButtons`.
109
- */
110
- const SocialButton = React.forwardRef<HTMLButtonElement, SocialButtonProps>(
111
- ({ provider, label, className, ...props }, ref) => {
112
- const meta = PROVIDERS[provider];
113
- return (
114
- <Button
115
- ref={ref}
116
- type="button"
117
- variant="outline"
118
- className={cn("w-full justify-center gap-2", className)}
119
- {...props}
120
- >
121
- {meta.glyph}
122
- <span>{label ?? meta.label}</span>
123
- </Button>
124
- );
125
- },
126
- );
127
- SocialButton.displayName = "SocialButton";
128
-
129
- export interface SocialButtonsProps extends React.HTMLAttributes<HTMLDivElement> {
130
- /**
131
- * Provider list rendered vertically. Pass `["github", "google"]` for the
132
- * common CIAM case, or `["sso"]` for a single corporate SSO button.
133
- * Defaults to `["github", "google"]`.
134
- */
135
- providers?: SocialProvider[];
136
- /** Per-provider click handler. Receives the provider id. */
137
- onProviderClick?: (provider: SocialProvider) => void;
138
- /** When true, all buttons render disabled (e.g. while signing in). */
139
- disabled?: boolean;
140
- }
141
-
142
- /**
143
- * Vertical stack of social/SSO provider buttons. Typically placed at the
144
- * top of a sign-in or sign-up card, above an `OrSeparator`.
145
- *
146
- * Stays purely presentational: the consumer wires `onProviderClick` to
147
- * the OAuth2 initiation flow.
148
- */
149
- const SocialButtons = React.forwardRef<HTMLDivElement, SocialButtonsProps>(
150
- ({ providers = ["github", "google"], onProviderClick, disabled, className, ...props }, ref) => (
151
- <div ref={ref} className={cn("flex flex-col gap-2.5", className)} {...props}>
152
- {providers.map((p) => (
153
- <SocialButton
154
- key={p}
155
- provider={p}
156
- disabled={disabled}
157
- onClick={() => onProviderClick?.(p)}
158
- />
159
- ))}
160
- </div>
161
- ),
162
- );
163
- SocialButtons.displayName = "SocialButtons";
164
-
165
- export { SocialButton, SocialButtons };
@@ -1,100 +0,0 @@
1
- import { ArrowDown, ArrowUp } from "lucide-react";
2
- import type * as React from "react";
3
-
4
- import { cn } from "../../lib/utils";
5
- import { Card, CardContent } from "./card";
6
-
7
- export interface StatCardProps {
8
- title: string;
9
- value: React.ReactNode;
10
- icon?: React.ReactNode;
11
- colorVariant?: "primary" | "blue" | "purple" | "success" | "warning" | "amber" | "destructive";
12
- /** Optional delta line shown under the value (e.g. "+4.2%"). */
13
- delta?: React.ReactNode;
14
- /** Tone for the delta — `up` is green, `down` is red, `neutral` is muted. */
15
- deltaTone?: "up" | "down" | "neutral";
16
- /** Caption shown next to the delta (e.g. "vs. last 7d"). */
17
- deltaCaption?: React.ReactNode;
18
- /**
19
- * When `true` (default) and `delta` is a string, prepend an ArrowUp /
20
- * ArrowDown icon based on `deltaTone`. Pass `false` to opt out, or pass
21
- * `delta` as ReactNode to fully control rendering yourself.
22
- */
23
- deltaArrow?: boolean;
24
- /** Optional content rendered below the delta row (e.g. a sparkline or mini chart). */
25
- children?: React.ReactNode;
26
- className?: string;
27
- }
28
-
29
- const COLOR: Record<NonNullable<StatCardProps["colorVariant"]>, string> = {
30
- primary: "bg-primary/10 text-primary",
31
- blue: "bg-[hsl(var(--stat-blue)/0.1)] text-[hsl(var(--stat-blue))]",
32
- purple: "bg-[hsl(var(--stat-purple)/0.1)] text-[hsl(var(--stat-purple))]",
33
- success: "bg-[hsl(var(--stat-success)/0.1)] text-[hsl(var(--stat-success))]",
34
- warning: "bg-[hsl(var(--stat-amber)/0.1)] text-[hsl(var(--stat-amber))]",
35
- amber: "bg-[hsl(var(--stat-amber)/0.1)] text-[hsl(var(--stat-amber))]",
36
- destructive: "bg-[hsl(var(--stat-destructive)/0.1)] text-[hsl(var(--stat-destructive))]",
37
- };
38
-
39
- const DELTA_TONE: Record<NonNullable<StatCardProps["deltaTone"]>, string> = {
40
- up: "text-green-600 dark:text-green-500",
41
- down: "text-red-600 dark:text-red-500",
42
- neutral: "text-muted-foreground",
43
- };
44
-
45
- export function StatCard({
46
- title,
47
- value,
48
- icon,
49
- colorVariant = "primary",
50
- delta,
51
- deltaTone = "up",
52
- deltaCaption,
53
- deltaArrow = true,
54
- children,
55
- className,
56
- }: StatCardProps) {
57
- const showArrow = deltaArrow && typeof delta === "string" && deltaTone !== "neutral";
58
- const ArrowGlyph = deltaTone === "down" ? ArrowDown : ArrowUp;
59
- return (
60
- <Card className={className}>
61
- <CardContent className="p-5">
62
- <div className="flex items-start justify-between gap-3">
63
- <div className="min-w-0 flex-1">
64
- <p className="truncate text-[13px] font-medium text-muted-foreground">{title}</p>
65
- <p className="mt-1 text-[28px] font-semibold leading-tight tracking-[-0.02em]">
66
- {value}
67
- </p>
68
- </div>
69
- {icon && (
70
- <div
71
- className={cn(
72
- "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg",
73
- COLOR[colorVariant],
74
- )}
75
- >
76
- {icon}
77
- </div>
78
- )}
79
- </div>
80
- {delta != null && (
81
- <div className="mt-3 flex items-center gap-1.5 text-xs">
82
- <span
83
- className={cn(
84
- "inline-flex items-center gap-0.5 font-mono font-medium",
85
- DELTA_TONE[deltaTone],
86
- )}
87
- >
88
- {showArrow && <ArrowGlyph className="h-3 w-3" aria-hidden />}
89
- {delta}
90
- </span>
91
- {deltaCaption != null && <span className="text-muted-foreground">{deltaCaption}</span>}
92
- </div>
93
- )}
94
- {children != null && <div className="mt-3">{children}</div>}
95
- </CardContent>
96
- </Card>
97
- );
98
- }
99
-
100
- StatCard.displayName = "StatCard";
@@ -1,42 +0,0 @@
1
- import { cva, type VariantProps } from "class-variance-authority";
2
- import * as React from "react";
3
-
4
- import { cn } from "../../lib/utils";
5
-
6
- const statusBadgeVariants = cva(
7
- "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium",
8
- {
9
- variants: {
10
- status: {
11
- success: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
12
- warning: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400",
13
- error: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400",
14
- info: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",
15
- neutral: "bg-muted text-muted-foreground",
16
- },
17
- },
18
- defaultVariants: {
19
- status: "neutral",
20
- },
21
- },
22
- );
23
-
24
- export interface StatusBadgeProps
25
- extends React.HTMLAttributes<HTMLSpanElement>,
26
- VariantProps<typeof statusBadgeVariants> {
27
- dot?: boolean;
28
- }
29
-
30
- const StatusBadge = React.forwardRef<HTMLSpanElement, StatusBadgeProps>(
31
- ({ status, dot = true, className, children, ...props }, ref) => {
32
- return (
33
- <span ref={ref} className={cn(statusBadgeVariants({ status }), className)} {...props}>
34
- {dot && <span className="h-1.5 w-1.5 rounded-full bg-current" />}
35
- {children}
36
- </span>
37
- );
38
- },
39
- );
40
- StatusBadge.displayName = "StatusBadge";
41
-
42
- export { StatusBadge, statusBadgeVariants };
@@ -1,96 +0,0 @@
1
- "use client";
2
-
3
- import type * as React from "react";
4
-
5
- import { cn } from "../../lib/utils";
6
- import { Icon } from "../atoms/icon";
7
-
8
- export type StepStatus = "pending" | "active" | "complete" | "error";
9
-
10
- export interface StepperStep {
11
- id: string;
12
- label: string;
13
- status: StepStatus;
14
- /** Optional icon override (atoms/icon IconName or React node). */
15
- icon?: React.ReactNode;
16
- disabled?: boolean;
17
- }
18
-
19
- export interface StepperProps {
20
- steps: StepperStep[];
21
- orientation?: "horizontal" | "vertical";
22
- onStepClick?: (id: string) => void;
23
- className?: string;
24
- }
25
-
26
- const STATUS_STYLES: Record<StepStatus, { dot: string; label: string }> = {
27
- pending: {
28
- dot: "border-muted-foreground/30 text-muted-foreground",
29
- label: "text-muted-foreground",
30
- },
31
- active: {
32
- dot: "border-primary bg-primary text-primary-foreground",
33
- label: "text-foreground font-medium",
34
- },
35
- complete: {
36
- dot: "border-green-500 bg-green-500 text-white",
37
- label: "text-foreground",
38
- },
39
- error: {
40
- dot: "border-destructive bg-destructive text-destructive-foreground",
41
- label: "text-destructive",
42
- },
43
- };
44
-
45
- export function Stepper({ steps, orientation = "vertical", onStepClick, className }: StepperProps) {
46
- const vertical = orientation === "vertical";
47
- return (
48
- <ol
49
- className={cn("flex", vertical ? "flex-col gap-1" : "flex-row items-center gap-2", className)}
50
- >
51
- {steps.map((step, i) => {
52
- const styles = STATUS_STYLES[step.status];
53
- const clickable = !!onStepClick && !step.disabled;
54
- return (
55
- <li key={step.id} className={cn("flex items-center gap-3", vertical ? "py-2" : "flex-1")}>
56
- <button
57
- type="button"
58
- disabled={!clickable}
59
- onClick={() => clickable && onStepClick(step.id)}
60
- className={cn(
61
- "flex items-center gap-3 rounded-md transition-colors",
62
- vertical && "w-full px-2 py-1 text-left",
63
- clickable && "hover:bg-accent/50",
64
- )}
65
- >
66
- <span
67
- className={cn(
68
- "flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border text-xs",
69
- styles.dot,
70
- )}
71
- >
72
- {step.icon ? (
73
- typeof step.icon === "string" ? (
74
- <Icon name={step.icon as never} className="h-3.5 w-3.5" />
75
- ) : (
76
- step.icon
77
- )
78
- ) : step.status === "complete" ? (
79
- <Icon name="Check" className="h-3.5 w-3.5" />
80
- ) : step.status === "error" ? (
81
- <Icon name="X" className="h-3.5 w-3.5" />
82
- ) : (
83
- i + 1
84
- )}
85
- </span>
86
- <span className={cn("text-sm", styles.label)}>{step.label}</span>
87
- </button>
88
- {!vertical && i < steps.length - 1 && <span className="mx-2 h-px flex-1 bg-border" />}
89
- </li>
90
- );
91
- })}
92
- </ol>
93
- );
94
- }
95
-
96
- Stepper.displayName = "Stepper";