@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.
- package/README.md +69 -35
- package/package.json +45 -177
- package/src/cn.ts +3 -0
- package/src/index.ts +12 -603
- package/src/theme.ts +62 -0
- package/src/tokens.ts +11 -0
- package/styles/base.css +17 -0
- package/styles/canvas.css +77 -52
- package/styles/components/alert.css +66 -0
- package/styles/components/app-shell.css +46 -0
- package/styles/components/avatar.css +22 -0
- package/styles/components/badge.css +83 -0
- package/styles/components/breadcrumb.css +35 -0
- package/styles/components/button-group.css +23 -0
- package/styles/components/button.css +107 -0
- package/styles/components/calendar.css +73 -0
- package/styles/components/card.css +58 -0
- package/styles/components/checkbox.css +55 -0
- package/styles/components/code-block.css +18 -0
- package/styles/components/combobox.css +75 -0
- package/styles/components/command.css +94 -0
- package/styles/components/data-table.css +142 -0
- package/styles/components/dialog.css +72 -0
- package/styles/components/dropdown.css +54 -0
- package/styles/components/empty-state.css +17 -0
- package/styles/components/field.css +27 -0
- package/styles/components/filter-panel.css +58 -0
- package/styles/components/form.css +27 -0
- package/styles/components/icon.css +8 -0
- package/styles/components/input-group.css +45 -0
- package/styles/components/input.css +56 -0
- package/styles/components/kbd.css +15 -0
- package/styles/components/page-header.css +52 -0
- package/styles/components/pagination.css +48 -0
- package/styles/components/popover.css +14 -0
- package/styles/components/radio.css +28 -0
- package/styles/components/row-menu.css +69 -0
- package/styles/components/section-card.css +49 -0
- package/styles/components/select.css +57 -0
- package/styles/components/separator.css +32 -0
- package/styles/components/sheet.css +70 -0
- package/styles/components/sidebar.css +146 -0
- package/styles/components/skeleton.css +32 -0
- package/styles/components/spinner.css +26 -0
- package/styles/components/stat-card.css +71 -0
- package/styles/components/stepper.css +63 -0
- package/styles/components/switch.css +45 -0
- package/styles/components/tabs.css +40 -0
- package/styles/components/textarea.css +31 -0
- package/styles/components/toast.css +95 -0
- package/styles/components/tooltip.css +53 -0
- package/styles/components/topbar.css +24 -0
- package/styles/components/typography.css +105 -0
- package/styles/patterns/backdrops.css +35 -0
- package/styles/patterns/density.css +66 -0
- package/styles/patterns/focus.css +22 -0
- package/styles/patterns/glass.css +85 -0
- package/styles/patterns/high-contrast.css +70 -0
- package/styles/patterns/reduced-motion.css +12 -0
- package/styles/patterns/scrollbar.css +10 -0
- package/styles/reset.css +89 -0
- package/styles/tokens/colors.css +106 -0
- package/styles/tokens/motion.css +33 -0
- package/styles/tokens/radius.css +10 -0
- package/styles/tokens/shadows.css +35 -0
- package/styles/tokens/spacing.css +19 -0
- package/styles/tokens/typography.css +6 -0
- package/styles/tokens/z-index.css +12 -0
- package/styles/utilities/display.css +66 -0
- package/styles/utilities/flexbox.css +240 -0
- package/styles/utilities/gap.css +288 -0
- package/styles/utilities/grid.css +138 -0
- package/styles/utilities/position.css +78 -0
- package/styles/utilities/sizing.css +138 -0
- package/tsconfig.json +20 -21
- package/src/components/atoms/README.md +0 -11
- package/src/components/atoms/aspect-ratio.tsx +0 -32
- package/src/components/atoms/avatar.tsx +0 -98
- package/src/components/atoms/badge.tsx +0 -44
- package/src/components/atoms/brand-mark.tsx +0 -74
- package/src/components/atoms/button.tsx +0 -105
- package/src/components/atoms/checkbox.tsx +0 -63
- package/src/components/atoms/flex-box.tsx +0 -105
- package/src/components/atoms/icon.tsx +0 -34
- package/src/components/atoms/input.tsx +0 -92
- package/src/components/atoms/label.tsx +0 -41
- package/src/components/atoms/logo.tsx +0 -89
- package/src/components/atoms/progress.tsx +0 -55
- package/src/components/atoms/radio-group.tsx +0 -122
- package/src/components/atoms/scroll-area.tsx +0 -106
- package/src/components/atoms/section.tsx +0 -48
- package/src/components/atoms/separator.tsx +0 -45
- package/src/components/atoms/skeleton.tsx +0 -17
- package/src/components/atoms/slider.tsx +0 -93
- package/src/components/atoms/spinner.tsx +0 -47
- package/src/components/atoms/switch.tsx +0 -60
- package/src/components/atoms/textarea.tsx +0 -78
- package/src/components/atoms/toggle.tsx +0 -80
- package/src/components/charts/activity-heatmap.tsx +0 -186
- package/src/components/charts/axes.tsx +0 -21
- package/src/components/charts/chart-container.tsx +0 -254
- package/src/components/charts/chart-legend.tsx +0 -67
- package/src/components/charts/chart-tooltip.tsx +0 -161
- package/src/components/charts/chart-types.tsx +0 -49
- package/src/components/charts/containers.tsx +0 -11
- package/src/components/charts/data.tsx +0 -16
- package/src/components/charts/details.tsx +0 -25
- package/src/components/charts/dot-pulse.tsx +0 -61
- package/src/components/charts/gauge.tsx +0 -106
- package/src/components/charts/grids.tsx +0 -8
- package/src/components/charts/index.ts +0 -62
- package/src/components/charts/labeled-bar-list.tsx +0 -85
- package/src/components/charts/metric-breakdown.tsx +0 -316
- package/src/components/charts/references.tsx +0 -8
- package/src/components/charts/service-health-list.tsx +0 -85
- package/src/components/charts/sparkline-area.tsx +0 -80
- package/src/components/charts/sparkline.tsx +0 -52
- package/src/components/charts/stacked-bar.tsx +0 -104
- package/src/components/charts/text.tsx +0 -10
- package/src/components/charts/world-heat-map-inner.tsx +0 -317
- package/src/components/charts/world-heat-map.tsx +0 -184
- package/src/components/molecules/README.md +0 -12
- package/src/components/molecules/action-bar.tsx +0 -73
- package/src/components/molecules/activity-item.tsx +0 -74
- package/src/components/molecules/alert.tsx +0 -86
- package/src/components/molecules/animated-background.tsx +0 -92
- package/src/components/molecules/auth-shell.tsx +0 -95
- package/src/components/molecules/brand-lockup.tsx +0 -48
- package/src/components/molecules/breadcrumb.tsx +0 -157
- package/src/components/molecules/button-group.tsx +0 -104
- package/src/components/molecules/calendar.tsx +0 -217
- package/src/components/molecules/card.tsx +0 -102
- package/src/components/molecules/client-brand.tsx +0 -95
- package/src/components/molecules/code-block.tsx +0 -86
- package/src/components/molecules/countdown-button.tsx +0 -92
- package/src/components/molecules/empty-state.tsx +0 -56
- package/src/components/molecules/error-state.tsx +0 -42
- package/src/components/molecules/field-display.tsx +0 -35
- package/src/components/molecules/input-otp.tsx +0 -74
- package/src/components/molecules/launcher-card.tsx +0 -152
- package/src/components/molecules/loading-state.tsx +0 -36
- package/src/components/molecules/notification-item.tsx +0 -67
- package/src/components/molecules/notification-list.tsx +0 -45
- package/src/components/molecules/number-badge.tsx +0 -53
- package/src/components/molecules/or-separator.tsx +0 -38
- package/src/components/molecules/page-header.tsx +0 -88
- package/src/components/molecules/page-tabs.tsx +0 -94
- package/src/components/molecules/pagination.tsx +0 -150
- package/src/components/molecules/password-input.tsx +0 -83
- package/src/components/molecules/password-strength-meter.tsx +0 -104
- package/src/components/molecules/phone-input.tsx +0 -200
- package/src/components/molecules/search-bar.tsx +0 -64
- package/src/components/molecules/secret-field.tsx +0 -158
- package/src/components/molecules/section-card.tsx +0 -91
- package/src/components/molecules/social-buttons.tsx +0 -165
- package/src/components/molecules/stat-card.tsx +0 -100
- package/src/components/molecules/status-badge.tsx +0 -42
- package/src/components/molecules/stepper.tsx +0 -96
- package/src/components/molecules/table.tsx +0 -157
- package/src/components/molecules/terminal.tsx +0 -74
- package/src/components/molecules/toggle-group.tsx +0 -145
- package/src/components/molecules/tooltip.tsx +0 -155
- package/src/components/molecules/user-avatar-chip.tsx +0 -71
- package/src/components/organisms/README.md +0 -14
- package/src/components/organisms/accordion.tsx +0 -154
- package/src/components/organisms/alert-dialog.tsx +0 -277
- package/src/components/organisms/carousel.tsx +0 -244
- package/src/components/organisms/collapsible.tsx +0 -69
- package/src/components/organisms/command.tsx +0 -144
- package/src/components/organisms/context-menu.tsx +0 -339
- package/src/components/organisms/dashboard-grid.tsx +0 -369
- package/src/components/organisms/data-table.tsx +0 -330
- package/src/components/organisms/dialog.tsx +0 -312
- package/src/components/organisms/drawer.tsx +0 -123
- package/src/components/organisms/dropdown-menu.tsx +0 -440
- package/src/components/organisms/editors/code-editor.tsx +0 -144
- package/src/components/organisms/editors/index.ts +0 -4
- package/src/components/organisms/editors/markdown-editor.tsx +0 -153
- package/src/components/organisms/editors/markdown-renderer.ts +0 -27
- package/src/components/organisms/editors/prose-canvas-classes.ts +0 -45
- package/src/components/organisms/editors/rich-text-editor.tsx +0 -126
- package/src/components/organisms/editors/toolbar/md-toolbar.tsx +0 -129
- package/src/components/organisms/editors/toolbar/rte-toolbar.tsx +0 -211
- package/src/components/organisms/editors/toolbar/toolbar-shell.tsx +0 -45
- package/src/components/organisms/editors/use-codemirror-theme.ts +0 -61
- package/src/components/organisms/error-boundary.tsx +0 -61
- package/src/components/organisms/form.tsx +0 -174
- package/src/components/organisms/hover-card.tsx +0 -115
- package/src/components/organisms/menubar.tsx +0 -498
- package/src/components/organisms/navbar.tsx +0 -104
- package/src/components/organisms/navigation-menu.tsx +0 -235
- package/src/components/organisms/popover.tsx +0 -149
- package/src/components/organisms/resizable.tsx +0 -58
- package/src/components/organisms/schema-form.tsx +0 -232
- package/src/components/organisms/select.tsx +0 -309
- package/src/components/organisms/sheet.tsx +0 -265
- package/src/components/organisms/sidebar.tsx +0 -1040
- package/src/components/organisms/sonner.tsx +0 -96
- package/src/components/organisms/tabs.tsx +0 -133
- package/src/components/organisms/theme-provider.tsx +0 -101
- package/src/hooks/use-mobile.tsx +0 -19
- package/src/lib/portal-container.tsx +0 -35
- package/src/lib/utils.ts +0 -6
- package/src/native.ts +0 -23
- package/src/tokens/colors.ts +0 -91
- package/src/tokens/index.ts +0 -3
- package/src/tokens/spacing.ts +0 -55
- package/src/tokens/typography.ts +0 -27
- package/styles/dashboard-grid.css +0 -47
- package/styles/fonts/Roboto-VariableFont_wdth_wght.ttf +0 -0
- package/styles/glass.css +0 -171
- package/styles/leaflet.css +0 -13
- package/styles/tokens.css +0 -317
- package/tailwind.config.ts +0 -70
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
|
|
5
|
-
import { cn } from "../../lib/utils";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* `<WorldHeatMap>` — Leaflet-based geographic scatter / heat-map.
|
|
9
|
-
*
|
|
10
|
-
* Public surface lives here; the actual react-leaflet tree is in
|
|
11
|
-
* `world-heat-map-inner.tsx` and is loaded via `React.lazy()` so neither
|
|
12
|
-
* `leaflet` nor `react-leaflet` lands in canvas's main bundle. Both are
|
|
13
|
-
* declared as **peer-optional** in `package.json` — consumers who use the map
|
|
14
|
-
* install them explicitly; consumers who don't, don't pay.
|
|
15
|
-
*
|
|
16
|
-
* Consumers must also import the Leaflet base stylesheet at app entry:
|
|
17
|
-
*
|
|
18
|
-
* import "@olympusoss/canvas/styles/leaflet.css";
|
|
19
|
-
*
|
|
20
|
-
* Visual identity matches the deployed admin dashboard reference (CartoDB
|
|
21
|
-
* tiles, hidden controls, triple-layered CircleMarker per point).
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
// ── Public types ─────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
export interface WorldHeatMapPoint {
|
|
27
|
-
/** Latitude. */
|
|
28
|
-
lat: number;
|
|
29
|
-
/** Longitude. */
|
|
30
|
-
lng: number;
|
|
31
|
-
/** Tooltip label (e.g. "Munich, DE"). */
|
|
32
|
-
label: string;
|
|
33
|
-
/** Drives marker radius. */
|
|
34
|
-
count: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface WorldHeatMapProps {
|
|
38
|
-
/** Geo-located points to render. Empty array shows the empty-state overlay. */
|
|
39
|
-
points: WorldHeatMapPoint[];
|
|
40
|
-
/** Container height. Number → px, string passes through. Default `"100%"`. */
|
|
41
|
-
height?: number | string;
|
|
42
|
-
/** Initial map center `[lat, lng]`. Default `[20, 0]`. */
|
|
43
|
-
center?: [number, number];
|
|
44
|
-
/** Initial zoom. Default `3`. */
|
|
45
|
-
zoom?: number;
|
|
46
|
-
/** Tile basemap. `"auto"` follows canvas's `useTheme()`. Default `"auto"`. */
|
|
47
|
-
tileTheme?: "auto" | "dark" | "light";
|
|
48
|
-
/** Show Leaflet zoom + attribution controls. Default `false`. */
|
|
49
|
-
showControls?: boolean;
|
|
50
|
-
/** Marker fill (any CSS colour, including `hsl(var(--chart-1))`). Default `"hsl(var(--chart-1))"`. */
|
|
51
|
-
markerColor?: string;
|
|
52
|
-
/** `[min, max]` marker radius in pixels (log-scaled on `count`). Default `[4, 20]`. */
|
|
53
|
-
markerRadiusRange?: [number, number];
|
|
54
|
-
/** Per-marker click handler. */
|
|
55
|
-
onMarkerClick?: (point: WorldHeatMapPoint) => void;
|
|
56
|
-
/** Show "Fewer / More" legend overlay (bottom-left). Default `true`. */
|
|
57
|
-
showLegend?: boolean;
|
|
58
|
-
/** Optional title overlaid top-left. */
|
|
59
|
-
title?: string;
|
|
60
|
-
/** Override the empty-state node. Default: a small "No geographic data available" card. */
|
|
61
|
-
emptyState?: React.ReactNode;
|
|
62
|
-
/** Pass-through className on the outer `.world-heat-map` wrapper. */
|
|
63
|
-
className?: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ── Lazy-loaded inner ───────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
const WorldHeatMapInner = React.lazy(() => import("./world-heat-map-inner"));
|
|
69
|
-
|
|
70
|
-
// ── Error boundary (peer-missing or runtime-load failure) ───
|
|
71
|
-
|
|
72
|
-
interface WorldHeatMapErrorBoundaryProps {
|
|
73
|
-
fallback: React.ReactNode;
|
|
74
|
-
children: React.ReactNode;
|
|
75
|
-
}
|
|
76
|
-
interface WorldHeatMapErrorBoundaryState {
|
|
77
|
-
hasError: boolean;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
class WorldHeatMapErrorBoundary extends React.Component<
|
|
81
|
-
WorldHeatMapErrorBoundaryProps,
|
|
82
|
-
WorldHeatMapErrorBoundaryState
|
|
83
|
-
> {
|
|
84
|
-
state: WorldHeatMapErrorBoundaryState = { hasError: false };
|
|
85
|
-
|
|
86
|
-
/* c8 ignore start -- only triggers when the lazy-loaded Leaflet inner throws (peer missing or runtime load failure); both are jsdom-incompatible to simulate */
|
|
87
|
-
static getDerivedStateFromError(): WorldHeatMapErrorBoundaryState {
|
|
88
|
-
return { hasError: true };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
render(): React.ReactNode {
|
|
92
|
-
return this.state.hasError ? this.props.fallback : this.props.children;
|
|
93
|
-
}
|
|
94
|
-
/* c8 ignore stop */
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ── Shell (SSR / loading / error) ───────────────────────────
|
|
98
|
-
|
|
99
|
-
type ShellState = "loading" | "error";
|
|
100
|
-
|
|
101
|
-
interface ShellProps {
|
|
102
|
-
height?: number | string;
|
|
103
|
-
className?: string;
|
|
104
|
-
state: ShellState;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function resolveHeight(height: number | string | undefined): string {
|
|
108
|
-
if (height === undefined) return "100%";
|
|
109
|
-
return typeof height === "number" ? `${height}px` : height;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function Shell({ height, className, state }: ShellProps) {
|
|
113
|
-
const heightStr = resolveHeight(height);
|
|
114
|
-
return (
|
|
115
|
-
<div
|
|
116
|
-
className={cn(
|
|
117
|
-
"relative flex items-center justify-center overflow-hidden rounded-xl bg-muted/40 text-xs text-muted-foreground world-heat-map",
|
|
118
|
-
className,
|
|
119
|
-
)}
|
|
120
|
-
style={{ height: heightStr }}
|
|
121
|
-
>
|
|
122
|
-
{state === "loading" ? (
|
|
123
|
-
<>
|
|
124
|
-
<svg
|
|
125
|
-
width="20"
|
|
126
|
-
height="20"
|
|
127
|
-
viewBox="0 0 20 20"
|
|
128
|
-
aria-hidden="true"
|
|
129
|
-
className="mr-2 animate-spin opacity-50"
|
|
130
|
-
>
|
|
131
|
-
<title>Loading</title>
|
|
132
|
-
<circle
|
|
133
|
-
cx="10"
|
|
134
|
-
cy="10"
|
|
135
|
-
r="8"
|
|
136
|
-
fill="none"
|
|
137
|
-
stroke="currentColor"
|
|
138
|
-
strokeWidth="2"
|
|
139
|
-
strokeDasharray="40"
|
|
140
|
-
strokeLinecap="round"
|
|
141
|
-
/>
|
|
142
|
-
</svg>
|
|
143
|
-
Loading map…
|
|
144
|
-
</>
|
|
145
|
-
) : (
|
|
146
|
-
<div className="px-4 py-2 text-center">
|
|
147
|
-
Map dependencies missing — install <code>leaflet</code> and <code>react-leaflet</code>.
|
|
148
|
-
</div>
|
|
149
|
-
)}
|
|
150
|
-
</div>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ── Public component ────────────────────────────────────────
|
|
155
|
-
|
|
156
|
-
export function WorldHeatMap(props: WorldHeatMapProps) {
|
|
157
|
-
const [mounted, setMounted] = React.useState(false);
|
|
158
|
-
|
|
159
|
-
React.useEffect(() => {
|
|
160
|
-
setMounted(true);
|
|
161
|
-
}, []);
|
|
162
|
-
|
|
163
|
-
// SSR / hydration guard — prevents `window`/`document` access during the
|
|
164
|
-
// server pass and the first client render before hydration completes.
|
|
165
|
-
if (!mounted) {
|
|
166
|
-
return <Shell height={props.height} className={props.className} state="loading" />;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/* c8 ignore start -- post-mount tree mounts the lazy Leaflet inner; jsdom can't run Leaflet so the Suspense + ErrorBoundary tree is verified visually in the docs */
|
|
170
|
-
return (
|
|
171
|
-
<WorldHeatMapErrorBoundary
|
|
172
|
-
fallback={<Shell height={props.height} className={props.className} state="error" />}
|
|
173
|
-
>
|
|
174
|
-
<React.Suspense
|
|
175
|
-
fallback={<Shell height={props.height} className={props.className} state="loading" />}
|
|
176
|
-
>
|
|
177
|
-
<WorldHeatMapInner {...props} />
|
|
178
|
-
</React.Suspense>
|
|
179
|
-
</WorldHeatMapErrorBoundary>
|
|
180
|
-
);
|
|
181
|
-
/* c8 ignore stop */
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
WorldHeatMap.displayName = "WorldHeatMap";
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Molecules
|
|
2
|
-
|
|
3
|
-
Small compositions of atoms. Meaningful UI semantics. No app-state model.
|
|
4
|
-
|
|
5
|
-
**Can import**: `tokens/`, `lib/utils`, `atoms/`, React.
|
|
6
|
-
|
|
7
|
-
**Cannot import**: anything from `organisms/` or `templates/`.
|
|
8
|
-
|
|
9
|
-
Molecules don't own interactive state more complex than a local toggle. If it
|
|
10
|
-
has open/close state, selection, or form integration, it's an organism.
|
|
11
|
-
|
|
12
|
-
See [CONTRIBUTING.md](../../../CONTRIBUTING.md) for the full atomic-design rules.
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type * as React from "react";
|
|
4
|
-
|
|
5
|
-
import { cn } from "../../lib/utils";
|
|
6
|
-
import { Button } from "../atoms/button";
|
|
7
|
-
import { Icon } from "../atoms/icon";
|
|
8
|
-
|
|
9
|
-
export interface ActionBarAction {
|
|
10
|
-
label: string;
|
|
11
|
-
onClick: () => void;
|
|
12
|
-
icon?: React.ReactNode;
|
|
13
|
-
loading?: boolean;
|
|
14
|
-
disabled?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ActionBarSecondary extends ActionBarAction {
|
|
18
|
-
variant?: "outline" | "ghost";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ActionBarProps {
|
|
22
|
-
primaryAction?: ActionBarAction;
|
|
23
|
-
secondaryActions?: ActionBarSecondary[];
|
|
24
|
-
align?: "left" | "right" | "center" | "space-between";
|
|
25
|
-
className?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const ALIGN: Record<NonNullable<ActionBarProps["align"]>, string> = {
|
|
29
|
-
left: "justify-start",
|
|
30
|
-
right: "justify-end",
|
|
31
|
-
center: "justify-center",
|
|
32
|
-
"space-between": "justify-between",
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export function ActionBar({
|
|
36
|
-
primaryAction,
|
|
37
|
-
secondaryActions = [],
|
|
38
|
-
align = "right",
|
|
39
|
-
className,
|
|
40
|
-
}: ActionBarProps) {
|
|
41
|
-
return (
|
|
42
|
-
<div className={cn("flex items-center gap-2", ALIGN[align], className)}>
|
|
43
|
-
{secondaryActions.map((a) => (
|
|
44
|
-
<Button
|
|
45
|
-
key={a.label}
|
|
46
|
-
onClick={a.onClick}
|
|
47
|
-
disabled={a.disabled || a.loading}
|
|
48
|
-
variant={a.variant ?? "outline"}
|
|
49
|
-
size="sm"
|
|
50
|
-
>
|
|
51
|
-
{a.icon && <span className="mr-1">{a.icon}</span>}
|
|
52
|
-
{a.label}
|
|
53
|
-
</Button>
|
|
54
|
-
))}
|
|
55
|
-
{primaryAction && (
|
|
56
|
-
<Button
|
|
57
|
-
onClick={primaryAction.onClick}
|
|
58
|
-
disabled={primaryAction.disabled || primaryAction.loading}
|
|
59
|
-
size="sm"
|
|
60
|
-
>
|
|
61
|
-
{primaryAction.loading ? (
|
|
62
|
-
<Icon name="LoaderCircle" className="mr-1 h-4 w-4 animate-spin" />
|
|
63
|
-
) : (
|
|
64
|
-
primaryAction.icon && <span className="mr-1">{primaryAction.icon}</span>
|
|
65
|
-
)}
|
|
66
|
-
{primaryAction.label}
|
|
67
|
-
</Button>
|
|
68
|
-
)}
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
ActionBar.displayName = "ActionBar";
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../../lib/utils";
|
|
4
|
-
|
|
5
|
-
export interface ActivityItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
|
6
|
-
/** Bold subject (the actor). */
|
|
7
|
-
subject: React.ReactNode;
|
|
8
|
-
/** Muted action verb + object. */
|
|
9
|
-
action: React.ReactNode;
|
|
10
|
-
/** Right-aligned timestamp. */
|
|
11
|
-
timestamp?: React.ReactNode;
|
|
12
|
-
/** Leading icon or avatar slot. */
|
|
13
|
-
leading?: React.ReactNode;
|
|
14
|
-
/** When provided, the row becomes clickable. */
|
|
15
|
-
onClick?: () => void;
|
|
16
|
-
/**
|
|
17
|
-
* Index in a list — when 0, the top border is suppressed so consecutive
|
|
18
|
-
* items only render dividers between rows. Defaults to 1 (border on).
|
|
19
|
-
*/
|
|
20
|
-
index?: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const ActivityItem = React.forwardRef<HTMLLIElement, ActivityItemProps>(
|
|
24
|
-
({ subject, action, timestamp, leading, onClick, index = 1, className, ...props }, ref) => {
|
|
25
|
-
const Row = onClick ? "button" : "div";
|
|
26
|
-
return (
|
|
27
|
-
<li
|
|
28
|
-
ref={ref}
|
|
29
|
-
className={cn(
|
|
30
|
-
"flex items-start gap-3 py-3",
|
|
31
|
-
index > 0 && "border-t border-border",
|
|
32
|
-
className,
|
|
33
|
-
)}
|
|
34
|
-
{...props}
|
|
35
|
-
>
|
|
36
|
-
{leading && <div className="shrink-0">{leading}</div>}
|
|
37
|
-
<Row
|
|
38
|
-
type={onClick ? "button" : undefined}
|
|
39
|
-
onClick={onClick}
|
|
40
|
-
className={cn(
|
|
41
|
-
"flex w-full items-start justify-between gap-4 text-left",
|
|
42
|
-
onClick && "transition-colors hover:text-foreground",
|
|
43
|
-
)}
|
|
44
|
-
>
|
|
45
|
-
<p className="m-0 min-w-0 text-[13.5px]">
|
|
46
|
-
<span className="font-medium text-foreground">{subject}</span>{" "}
|
|
47
|
-
<span className="text-muted-foreground">{action}</span>
|
|
48
|
-
</p>
|
|
49
|
-
{timestamp != null && (
|
|
50
|
-
<span className="shrink-0 font-mono text-[11px] text-muted-foreground">
|
|
51
|
-
{timestamp}
|
|
52
|
-
</span>
|
|
53
|
-
)}
|
|
54
|
-
</Row>
|
|
55
|
-
</li>
|
|
56
|
-
);
|
|
57
|
-
},
|
|
58
|
-
);
|
|
59
|
-
ActivityItem.displayName = "ActivityItem";
|
|
60
|
-
|
|
61
|
-
export interface ActivityFeedProps extends React.HTMLAttributes<HTMLUListElement> {
|
|
62
|
-
children?: React.ReactNode;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export const ActivityFeed = React.forwardRef<HTMLUListElement, ActivityFeedProps>(
|
|
66
|
-
({ children, className, ...props }, ref) => {
|
|
67
|
-
return (
|
|
68
|
-
<ul ref={ref} className={cn("m-0 list-none p-0", className)} {...props}>
|
|
69
|
-
{children}
|
|
70
|
-
</ul>
|
|
71
|
-
);
|
|
72
|
-
},
|
|
73
|
-
);
|
|
74
|
-
ActivityFeed.displayName = "ActivityFeed";
|
|
@@ -1,86 +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 alertVariants = cva(
|
|
7
|
-
"relative w-full rounded-lg border border-border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
|
8
|
-
{
|
|
9
|
-
variants: {
|
|
10
|
-
variant: {
|
|
11
|
-
default: "bg-background text-foreground",
|
|
12
|
-
destructive:
|
|
13
|
-
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
defaultVariants: {
|
|
17
|
-
variant: "default",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
export interface AlertProps
|
|
23
|
-
extends React.HTMLAttributes<HTMLDivElement>,
|
|
24
|
-
VariantProps<typeof alertVariants> {
|
|
25
|
-
/**
|
|
26
|
-
* Visual emphasis preset. `default` is informational, `destructive`
|
|
27
|
-
* uses the danger palette for errors and warnings.
|
|
28
|
-
* @default "default"
|
|
29
|
-
*/
|
|
30
|
-
variant?: "default" | "destructive";
|
|
31
|
-
/**
|
|
32
|
-
* Optional leading icon (lucide-react), `<AlertTitle>`, and
|
|
33
|
-
* `<AlertDescription>`. The icon — when present as a direct child —
|
|
34
|
-
* is auto-positioned in the top-left.
|
|
35
|
-
*/
|
|
36
|
-
children?: React.ReactNode;
|
|
37
|
-
/** Tailwind / CSS classes merged onto the alert via `cn()`. */
|
|
38
|
-
className?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
42
|
-
({ className, variant, ...props }, ref) => (
|
|
43
|
-
<div
|
|
44
|
-
ref={ref}
|
|
45
|
-
role="alert"
|
|
46
|
-
data-slot="alert"
|
|
47
|
-
className={cn(alertVariants({ variant }), className)}
|
|
48
|
-
{...props}
|
|
49
|
-
/>
|
|
50
|
-
),
|
|
51
|
-
);
|
|
52
|
-
Alert.displayName = "Alert";
|
|
53
|
-
|
|
54
|
-
export interface AlertTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
|
55
|
-
/** The alert headline. Renders as an `<h5>`. */
|
|
56
|
-
children?: React.ReactNode;
|
|
57
|
-
/** Tailwind / CSS classes merged onto the title via `cn()`. */
|
|
58
|
-
className?: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const AlertTitle = React.forwardRef<HTMLParagraphElement, AlertTitleProps>(
|
|
62
|
-
({ className, ...props }, ref) => (
|
|
63
|
-
<h5
|
|
64
|
-
ref={ref}
|
|
65
|
-
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
|
66
|
-
{...props}
|
|
67
|
-
/>
|
|
68
|
-
),
|
|
69
|
-
);
|
|
70
|
-
AlertTitle.displayName = "AlertTitle";
|
|
71
|
-
|
|
72
|
-
export interface AlertDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
|
|
73
|
-
/** Body copy of the alert. Renders as a `<div>` so it can hold paragraphs and lists. */
|
|
74
|
-
children?: React.ReactNode;
|
|
75
|
-
/** Tailwind / CSS classes merged onto the description via `cn()`. */
|
|
76
|
-
className?: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const AlertDescription = React.forwardRef<HTMLParagraphElement, AlertDescriptionProps>(
|
|
80
|
-
({ className, ...props }, ref) => (
|
|
81
|
-
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
|
|
82
|
-
),
|
|
83
|
-
);
|
|
84
|
-
AlertDescription.displayName = "AlertDescription";
|
|
85
|
-
|
|
86
|
-
export { Alert, AlertDescription, AlertTitle };
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { cn } from "../../lib/utils";
|
|
2
|
-
|
|
3
|
-
export interface AnimatedBackgroundOrb {
|
|
4
|
-
/** CSS color for the radial gradient (hex / rgb / hsl / token). */
|
|
5
|
-
color: string;
|
|
6
|
-
/**
|
|
7
|
-
* Diameter in pixels.
|
|
8
|
-
* @default 500
|
|
9
|
-
*/
|
|
10
|
-
size?: number;
|
|
11
|
-
/**
|
|
12
|
-
* Opacity 0–1. Lower values let more of the page bg bleed through.
|
|
13
|
-
* @default 0.2
|
|
14
|
-
*/
|
|
15
|
-
opacity?: number;
|
|
16
|
-
/**
|
|
17
|
-
* Gaussian blur radius in pixels. Larger values produce softer orbs.
|
|
18
|
-
* @default 100
|
|
19
|
-
*/
|
|
20
|
-
blur?: number;
|
|
21
|
-
/** Tailwind position classes, e.g. `"-top-32 -right-32"` or `"left-1/2 top-1/3"`. */
|
|
22
|
-
position?: string;
|
|
23
|
-
/**
|
|
24
|
-
* CSS `animation` shorthand. Pair with the `orb-float-1` / `orb-float-2`
|
|
25
|
-
* keyframes shipped in canvas's tokens, or your own.
|
|
26
|
-
*/
|
|
27
|
-
animation?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface AnimatedBackgroundProps {
|
|
31
|
-
/**
|
|
32
|
-
* Orbs to render. Defaults to a 3-orb indigo/purple/cyan composition
|
|
33
|
-
* that mirrors the auth-screen backdrop. Pass an empty array `[]` to
|
|
34
|
-
* render no orbs (useful for testing).
|
|
35
|
-
*/
|
|
36
|
-
orbs?: AnimatedBackgroundOrb[];
|
|
37
|
-
/**
|
|
38
|
-
* Tailwind / CSS classes merged onto the root container via `cn()`.
|
|
39
|
-
* Defaults to `pointer-events-none fixed inset-0 overflow-hidden`.
|
|
40
|
-
*/
|
|
41
|
-
className?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const DEFAULT_ORBS: AnimatedBackgroundOrb[] = [
|
|
45
|
-
{
|
|
46
|
-
color: "#6366f1",
|
|
47
|
-
size: 500,
|
|
48
|
-
opacity: 0.2,
|
|
49
|
-
blur: 120,
|
|
50
|
-
position: "-top-32 -right-32",
|
|
51
|
-
animation: "orb-float-1 8s ease-in-out infinite",
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
color: "#8b5cf6",
|
|
55
|
-
size: 400,
|
|
56
|
-
opacity: 0.15,
|
|
57
|
-
blur: 100,
|
|
58
|
-
position: "-bottom-32 -left-32",
|
|
59
|
-
animation: "orb-float-2 10s ease-in-out infinite",
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
color: "#06b6d4",
|
|
63
|
-
size: 300,
|
|
64
|
-
opacity: 0.1,
|
|
65
|
-
blur: 80,
|
|
66
|
-
position: "left-1/2 top-1/3 -translate-x-1/2",
|
|
67
|
-
animation: "orb-float-1 12s ease-in-out 2s infinite",
|
|
68
|
-
},
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
export function AnimatedBackground({ orbs = DEFAULT_ORBS, className }: AnimatedBackgroundProps) {
|
|
72
|
-
return (
|
|
73
|
-
<div className={cn("pointer-events-none fixed inset-0 overflow-hidden", className)}>
|
|
74
|
-
{orbs.map((orb, i) => (
|
|
75
|
-
<div
|
|
76
|
-
key={i}
|
|
77
|
-
className={cn("absolute rounded-full", orb.position)}
|
|
78
|
-
style={{
|
|
79
|
-
width: orb.size ?? 500,
|
|
80
|
-
height: orb.size ?? 500,
|
|
81
|
-
opacity: orb.opacity ?? 0.2,
|
|
82
|
-
filter: `blur(${orb.blur ?? 100}px)`,
|
|
83
|
-
background: `radial-gradient(circle, ${orb.color} 0%, transparent 70%)`,
|
|
84
|
-
animation: orb.animation,
|
|
85
|
-
}}
|
|
86
|
-
/>
|
|
87
|
-
))}
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
AnimatedBackground.displayName = "AnimatedBackground";
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
|
|
5
|
-
import { cn } from "../../lib/utils";
|
|
6
|
-
import { Card, CardContent, CardHeader } from "./card";
|
|
7
|
-
|
|
8
|
-
export interface AuthShellProps {
|
|
9
|
-
/**
|
|
10
|
-
* Brand lockup rendered above the card. Compose a `ClientBrand` (or
|
|
11
|
-
* your own component) and pass it in. Slot is left empty to keep this
|
|
12
|
-
* shell unaware of OAuth2 / Hydra specifics.
|
|
13
|
-
*/
|
|
14
|
-
brand?: React.ReactNode;
|
|
15
|
-
/** Card title. Pairs with `subtitle`. */
|
|
16
|
-
title?: React.ReactNode;
|
|
17
|
-
/** Optional one-line subtitle shown beneath `title`. */
|
|
18
|
-
subtitle?: React.ReactNode;
|
|
19
|
-
/** Card body content. */
|
|
20
|
-
children?: React.ReactNode;
|
|
21
|
-
/**
|
|
22
|
-
* Optional footer line beneath the card (legal / privacy / support).
|
|
23
|
-
*/
|
|
24
|
-
footer?: React.ReactNode;
|
|
25
|
-
/**
|
|
26
|
-
* Card width preset. `default` = 408px (single-column flows like login,
|
|
27
|
-
* recovery), `wide` = 460px (multi-step or denser flows like consent).
|
|
28
|
-
* @default "default"
|
|
29
|
-
*/
|
|
30
|
-
width?: "default" | "wide";
|
|
31
|
-
/**
|
|
32
|
-
* Allow children to break the card frame (useful for full-bleed flow
|
|
33
|
-
* pickers or media). When `false` the card content gets the standard
|
|
34
|
-
* `p-6` padding.
|
|
35
|
-
* @default false
|
|
36
|
-
*/
|
|
37
|
-
flush?: boolean;
|
|
38
|
-
/** Tailwind / CSS classes merged onto the outer wrapper via `cn()`. */
|
|
39
|
-
className?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Centered single-card layout for auth flows.
|
|
44
|
-
*
|
|
45
|
-
* Renders, top to bottom:
|
|
46
|
-
* 1. Brand slot (`brand`) above the card
|
|
47
|
-
* 2. Card with optional centred title + subtitle, then `children`
|
|
48
|
-
* 3. Optional footer line beneath the card
|
|
49
|
-
*
|
|
50
|
-
* Layout is intentionally narrow and prescriptive: this is the
|
|
51
|
-
* agreed-upon shape for every Olympus auth screen (login, register,
|
|
52
|
-
* recovery, reset, verify, OTP, consent, logout, status). For app shells
|
|
53
|
-
* with sidebars or multi-pane layouts, compose your own flex shell
|
|
54
|
-
* directly (see `Sidebar` + `SidebarInset`).
|
|
55
|
-
*/
|
|
56
|
-
const AuthShell = React.forwardRef<HTMLDivElement, AuthShellProps>(
|
|
57
|
-
(
|
|
58
|
-
{ brand, title, subtitle, children, footer, width = "default", flush = false, className },
|
|
59
|
-
ref,
|
|
60
|
-
) => (
|
|
61
|
-
<div
|
|
62
|
-
ref={ref}
|
|
63
|
-
className={cn(
|
|
64
|
-
"relative z-10 flex min-h-screen flex-col items-center justify-center px-6 py-12",
|
|
65
|
-
className,
|
|
66
|
-
)}
|
|
67
|
-
>
|
|
68
|
-
{brand && <div className="mb-7">{brand}</div>}
|
|
69
|
-
|
|
70
|
-
<Card
|
|
71
|
-
className={cn(
|
|
72
|
-
"w-full shadow-lg shadow-black/5 dark:shadow-black/30",
|
|
73
|
-
width === "wide" ? "max-w-[460px]" : "max-w-[408px]",
|
|
74
|
-
)}
|
|
75
|
-
>
|
|
76
|
-
{title && (
|
|
77
|
-
<CardHeader className={cn("text-center", subtitle ? "pb-3" : "pb-4")}>
|
|
78
|
-
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
|
|
79
|
-
{subtitle && <p className="mt-1.5 text-sm text-muted-foreground">{subtitle}</p>}
|
|
80
|
-
</CardHeader>
|
|
81
|
-
)}
|
|
82
|
-
<CardContent className={cn(flush ? "p-0" : "pt-0", !title && !flush && "pt-6")}>
|
|
83
|
-
{children}
|
|
84
|
-
</CardContent>
|
|
85
|
-
</Card>
|
|
86
|
-
|
|
87
|
-
{footer && (
|
|
88
|
-
<div className="mt-6 max-w-[460px] text-center text-xs text-muted-foreground">{footer}</div>
|
|
89
|
-
)}
|
|
90
|
-
</div>
|
|
91
|
-
),
|
|
92
|
-
);
|
|
93
|
-
AuthShell.displayName = "AuthShell";
|
|
94
|
-
|
|
95
|
-
export { AuthShell };
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../../lib/utils";
|
|
4
|
-
|
|
5
|
-
export interface BrandLockupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
-
/**
|
|
7
|
-
* Logo node rendered to the left of the wordmark — typically `<Logo />`
|
|
8
|
-
* for the canvas Olympus ring, or any other brand mark of the consumer's
|
|
9
|
-
* choosing. Required so canvas stays brand-agnostic.
|
|
10
|
-
*/
|
|
11
|
-
logo: React.ReactNode;
|
|
12
|
-
/** Wordmark next to the logo (e.g. "Athena", "Hera"). */
|
|
13
|
-
productName: string;
|
|
14
|
-
/** Optional secondary line under the wordmark. */
|
|
15
|
-
subtitle?: string;
|
|
16
|
-
/** Visual size — sm = sidebar collapsed, md = sidebar expanded, lg = hero. */
|
|
17
|
-
size?: "sm" | "md" | "lg";
|
|
18
|
-
/** When true, renders only the logo without the wordmark or subtitle. */
|
|
19
|
-
collapsed?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const SIZE: Record<NonNullable<BrandLockupProps["size"]>, { name: string; sub: string }> = {
|
|
23
|
-
sm: { name: "text-[13px]", sub: "text-[10px]" },
|
|
24
|
-
md: { name: "text-sm", sub: "text-[11px]" },
|
|
25
|
-
lg: { name: "text-2xl", sub: "text-xs" },
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export const BrandLockup = React.forwardRef<HTMLDivElement, BrandLockupProps>(
|
|
29
|
-
({ logo, productName, subtitle, size = "md", collapsed = false, className, ...props }, ref) => {
|
|
30
|
-
const sz = SIZE[size];
|
|
31
|
-
return (
|
|
32
|
-
<div ref={ref} className={cn("flex items-center gap-2.5", className)} {...props}>
|
|
33
|
-
<span className="shrink-0">{logo}</span>
|
|
34
|
-
{!collapsed && (
|
|
35
|
-
<div className="flex flex-col leading-tight">
|
|
36
|
-
<span className={cn(sz.name, "font-semibold tracking-tight text-foreground")}>
|
|
37
|
-
{productName}
|
|
38
|
-
</span>
|
|
39
|
-
{subtitle && (
|
|
40
|
-
<span className={cn(sz.sub, "font-mono text-muted-foreground")}>{subtitle}</span>
|
|
41
|
-
)}
|
|
42
|
-
</div>
|
|
43
|
-
)}
|
|
44
|
-
</div>
|
|
45
|
-
);
|
|
46
|
-
},
|
|
47
|
-
);
|
|
48
|
-
BrandLockup.displayName = "BrandLockup";
|