@olympusoss/canvas 2.20.1 → 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.
- package/package.json +41 -177
- package/src/cn.ts +3 -0
- package/src/index.ts +12 -603
- package/src/theme.ts +41 -0
- package/src/tokens.ts +11 -0
- package/styles/base.css +17 -0
- package/styles/canvas.css +69 -52
- package/styles/components/alert.css +66 -0
- package/styles/components/app-shell.css +46 -0
- package/styles/components/avatar.css +15 -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 +38 -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/tsconfig.json +20 -21
- package/README.md +0 -60
- 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,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";
|