@olympusoss/canvas 2.6.19
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 +179 -0
- package/src/components/atoms/README.md +11 -0
- package/src/components/atoms/aspect-ratio.tsx +32 -0
- package/src/components/atoms/avatar.tsx +98 -0
- package/src/components/atoms/badge.tsx +44 -0
- package/src/components/atoms/brand-mark.tsx +74 -0
- package/src/components/atoms/button.tsx +104 -0
- package/src/components/atoms/checkbox.tsx +63 -0
- package/src/components/atoms/flex-box.tsx +105 -0
- package/src/components/atoms/icon.tsx +34 -0
- package/src/components/atoms/input.tsx +91 -0
- package/src/components/atoms/label.tsx +41 -0
- package/src/components/atoms/logo.tsx +89 -0
- package/src/components/atoms/progress.tsx +55 -0
- package/src/components/atoms/radio-group.tsx +122 -0
- package/src/components/atoms/scroll-area.tsx +106 -0
- package/src/components/atoms/section.tsx +48 -0
- package/src/components/atoms/separator.tsx +45 -0
- package/src/components/atoms/skeleton.tsx +17 -0
- package/src/components/atoms/slider.tsx +93 -0
- package/src/components/atoms/switch.tsx +60 -0
- package/src/components/atoms/textarea.tsx +78 -0
- package/src/components/atoms/toggle.tsx +80 -0
- package/src/components/charts/activity-heatmap.tsx +96 -0
- package/src/components/charts/axes.tsx +21 -0
- package/src/components/charts/chart-container.tsx +195 -0
- package/src/components/charts/chart-legend.tsx +67 -0
- package/src/components/charts/chart-tooltip.tsx +161 -0
- package/src/components/charts/chart-types.tsx +49 -0
- package/src/components/charts/containers.tsx +11 -0
- package/src/components/charts/data.tsx +16 -0
- package/src/components/charts/details.tsx +25 -0
- package/src/components/charts/gauge.tsx +106 -0
- package/src/components/charts/grids.tsx +8 -0
- package/src/components/charts/index.ts +62 -0
- package/src/components/charts/labeled-bar-list.tsx +85 -0
- package/src/components/charts/references.tsx +8 -0
- package/src/components/charts/service-health-list.tsx +73 -0
- package/src/components/charts/sparkline.tsx +52 -0
- package/src/components/charts/stacked-bar.tsx +104 -0
- package/src/components/charts/text.tsx +10 -0
- package/src/components/charts/world-heat-map-inner.tsx +317 -0
- package/src/components/charts/world-heat-map.tsx +184 -0
- package/src/components/molecules/README.md +12 -0
- package/src/components/molecules/action-bar.tsx +73 -0
- package/src/components/molecules/activity-item.tsx +74 -0
- package/src/components/molecules/alert.tsx +80 -0
- package/src/components/molecules/animated-background.tsx +92 -0
- package/src/components/molecules/brand-lockup.tsx +48 -0
- package/src/components/molecules/breadcrumb.tsx +161 -0
- package/src/components/molecules/button-group.tsx +104 -0
- package/src/components/molecules/calendar.tsx +216 -0
- package/src/components/molecules/card.tsx +101 -0
- package/src/components/molecules/code-block.tsx +48 -0
- package/src/components/molecules/empty-state.tsx +55 -0
- package/src/components/molecules/error-state.tsx +42 -0
- package/src/components/molecules/field-display.tsx +35 -0
- package/src/components/molecules/input-otp.tsx +74 -0
- package/src/components/molecules/loading-state.tsx +36 -0
- package/src/components/molecules/notification-item.tsx +67 -0
- package/src/components/molecules/notification-list.tsx +45 -0
- package/src/components/molecules/number-badge.tsx +53 -0
- package/src/components/molecules/page-header.tsx +88 -0
- package/src/components/molecules/page-tabs.tsx +94 -0
- package/src/components/molecules/pagination.tsx +150 -0
- package/src/components/molecules/phone-input.tsx +200 -0
- package/src/components/molecules/search-bar.tsx +64 -0
- package/src/components/molecules/secret-field.tsx +158 -0
- package/src/components/molecules/section-card.tsx +91 -0
- package/src/components/molecules/stat-card.tsx +96 -0
- package/src/components/molecules/status-badge.tsx +42 -0
- package/src/components/molecules/stepper.tsx +96 -0
- package/src/components/molecules/table.tsx +157 -0
- package/src/components/molecules/toggle-group.tsx +145 -0
- package/src/components/molecules/tooltip.tsx +150 -0
- package/src/components/molecules/user-avatar-chip.tsx +71 -0
- package/src/components/organisms/README.md +14 -0
- package/src/components/organisms/accordion.tsx +149 -0
- package/src/components/organisms/alert-dialog.tsx +269 -0
- package/src/components/organisms/carousel.tsx +244 -0
- package/src/components/organisms/collapsible.tsx +69 -0
- package/src/components/organisms/command.tsx +143 -0
- package/src/components/organisms/context-menu.tsx +333 -0
- package/src/components/organisms/dashboard-grid.tsx +360 -0
- package/src/components/organisms/data-table.tsx +330 -0
- package/src/components/organisms/dialog.tsx +304 -0
- package/src/components/organisms/drawer.tsx +100 -0
- package/src/components/organisms/dropdown-menu.tsx +434 -0
- package/src/components/organisms/editors/code-editor.tsx +144 -0
- package/src/components/organisms/editors/index.ts +4 -0
- package/src/components/organisms/editors/markdown-editor.tsx +153 -0
- package/src/components/organisms/editors/markdown-renderer.ts +27 -0
- package/src/components/organisms/editors/prose-canvas-classes.ts +45 -0
- package/src/components/organisms/editors/rich-text-editor.tsx +126 -0
- package/src/components/organisms/editors/toolbar/md-toolbar.tsx +129 -0
- package/src/components/organisms/editors/toolbar/rte-toolbar.tsx +211 -0
- package/src/components/organisms/editors/toolbar/toolbar-shell.tsx +45 -0
- package/src/components/organisms/editors/use-codemirror-theme.ts +61 -0
- package/src/components/organisms/error-boundary.tsx +61 -0
- package/src/components/organisms/form.tsx +174 -0
- package/src/components/organisms/hover-card.tsx +114 -0
- package/src/components/organisms/menubar.tsx +491 -0
- package/src/components/organisms/navbar.tsx +101 -0
- package/src/components/organisms/navigation-menu.tsx +234 -0
- package/src/components/organisms/popover.tsx +144 -0
- package/src/components/organisms/resizable.tsx +39 -0
- package/src/components/organisms/schema-form.tsx +232 -0
- package/src/components/organisms/select.tsx +303 -0
- package/src/components/organisms/sheet.tsx +256 -0
- package/src/components/organisms/sidebar.tsx +1037 -0
- package/src/components/organisms/sonner.tsx +96 -0
- package/src/components/organisms/tabs.tsx +132 -0
- package/src/components/organisms/theme-provider.tsx +101 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/index.ts +547 -0
- package/src/lib/portal-container.tsx +35 -0
- package/src/lib/utils.ts +6 -0
- package/src/native.ts +23 -0
- package/src/tokens/colors.ts +91 -0
- package/src/tokens/index.ts +3 -0
- package/src/tokens/spacing.ts +55 -0
- package/src/tokens/typography.ts +27 -0
- package/styles/canvas.css +55 -0
- package/styles/dashboard-grid.css +47 -0
- package/styles/leaflet.css +13 -0
- package/styles/tokens.css +234 -0
- package/tailwind.config.ts +70 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,73 @@
|
|
|
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";
|
|
@@ -0,0 +1,74 @@
|
|
|
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";
|
|
@@ -0,0 +1,80 @@
|
|
|
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 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 ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
Alert.displayName = "Alert";
|
|
47
|
+
|
|
48
|
+
export interface AlertTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
|
49
|
+
/** The alert headline. Renders as an `<h5>`. */
|
|
50
|
+
children?: React.ReactNode;
|
|
51
|
+
/** Tailwind / CSS classes merged onto the title via `cn()`. */
|
|
52
|
+
className?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const AlertTitle = React.forwardRef<HTMLParagraphElement, AlertTitleProps>(
|
|
56
|
+
({ className, ...props }, ref) => (
|
|
57
|
+
<h5
|
|
58
|
+
ref={ref}
|
|
59
|
+
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
AlertTitle.displayName = "AlertTitle";
|
|
65
|
+
|
|
66
|
+
export interface AlertDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
|
|
67
|
+
/** Body copy of the alert. Renders as a `<div>` so it can hold paragraphs and lists. */
|
|
68
|
+
children?: React.ReactNode;
|
|
69
|
+
/** Tailwind / CSS classes merged onto the description via `cn()`. */
|
|
70
|
+
className?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const AlertDescription = React.forwardRef<HTMLParagraphElement, AlertDescriptionProps>(
|
|
74
|
+
({ className, ...props }, ref) => (
|
|
75
|
+
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
AlertDescription.displayName = "AlertDescription";
|
|
79
|
+
|
|
80
|
+
export { Alert, AlertDescription, AlertTitle };
|
|
@@ -0,0 +1,92 @@
|
|
|
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";
|
|
@@ -0,0 +1,48 @@
|
|
|
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";
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
2
|
+
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
|
|
7
|
+
export interface BreadcrumbProps extends React.ComponentPropsWithoutRef<"nav"> {
|
|
8
|
+
/**
|
|
9
|
+
* Custom separator element used between items. Defaults to a chevron
|
|
10
|
+
* via `<BreadcrumbSeparator>`.
|
|
11
|
+
*/
|
|
12
|
+
separator?: React.ReactNode;
|
|
13
|
+
/** A `<BreadcrumbList>` containing items. */
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
/** Tailwind / CSS classes merged onto the `<nav>` via `cn()`. */
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Breadcrumb = React.forwardRef<HTMLElement, BreadcrumbProps>(({ ...props }, ref) => (
|
|
20
|
+
<nav ref={ref} aria-label="breadcrumb" {...props} />
|
|
21
|
+
));
|
|
22
|
+
Breadcrumb.displayName = "Breadcrumb";
|
|
23
|
+
|
|
24
|
+
export interface BreadcrumbListProps extends React.ComponentPropsWithoutRef<"ol"> {
|
|
25
|
+
/** A flat list of `<BreadcrumbItem>`s, separated by `<BreadcrumbSeparator>`. */
|
|
26
|
+
children?: React.ReactNode;
|
|
27
|
+
/** Tailwind / CSS classes merged onto the `<ol>` via `cn()`. */
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const BreadcrumbList = React.forwardRef<HTMLOListElement, BreadcrumbListProps>(
|
|
32
|
+
({ className, ...props }, ref) => (
|
|
33
|
+
<ol
|
|
34
|
+
ref={ref}
|
|
35
|
+
className={cn(
|
|
36
|
+
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
BreadcrumbList.displayName = "BreadcrumbList";
|
|
44
|
+
|
|
45
|
+
export interface BreadcrumbItemProps extends React.ComponentPropsWithoutRef<"li"> {
|
|
46
|
+
/** Typically a `<BreadcrumbLink>` or `<BreadcrumbPage>`. */
|
|
47
|
+
children?: React.ReactNode;
|
|
48
|
+
/** Tailwind / CSS classes merged onto the `<li>` via `cn()`. */
|
|
49
|
+
className?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const BreadcrumbItem = React.forwardRef<HTMLLIElement, BreadcrumbItemProps>(
|
|
53
|
+
({ className, ...props }, ref) => (
|
|
54
|
+
<li ref={ref} className={cn("inline-flex items-center gap-1.5", className)} {...props} />
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
BreadcrumbItem.displayName = "BreadcrumbItem";
|
|
58
|
+
|
|
59
|
+
export interface BreadcrumbLinkProps extends React.ComponentPropsWithoutRef<"a"> {
|
|
60
|
+
/**
|
|
61
|
+
* Render as a Radix Slot — useful for wrapping a router `<Link>` so
|
|
62
|
+
* it inherits breadcrumb styling.
|
|
63
|
+
* @default false
|
|
64
|
+
*/
|
|
65
|
+
asChild?: boolean;
|
|
66
|
+
/** Anchor target. Used when not in `asChild` mode. */
|
|
67
|
+
href?: string;
|
|
68
|
+
/** Link label. */
|
|
69
|
+
children?: React.ReactNode;
|
|
70
|
+
/** Tailwind / CSS classes merged onto the link via `cn()`. */
|
|
71
|
+
className?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const BreadcrumbLink = React.forwardRef<HTMLAnchorElement, BreadcrumbLinkProps>(
|
|
75
|
+
({ asChild, className, ...props }, ref) => {
|
|
76
|
+
const Comp = asChild ? Slot : "a";
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Comp
|
|
80
|
+
ref={ref}
|
|
81
|
+
className={cn("transition-colors hover:text-foreground", className)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
BreadcrumbLink.displayName = "BreadcrumbLink";
|
|
88
|
+
|
|
89
|
+
export interface BreadcrumbPageProps extends React.ComponentPropsWithoutRef<"span"> {
|
|
90
|
+
/**
|
|
91
|
+
* Label of the current page. This is the last breadcrumb item and is
|
|
92
|
+
* non-clickable; sets `aria-current="page"` for assistive tech.
|
|
93
|
+
*/
|
|
94
|
+
children?: React.ReactNode;
|
|
95
|
+
/** Tailwind / CSS classes merged onto the page span via `cn()`. */
|
|
96
|
+
className?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const BreadcrumbPage = React.forwardRef<HTMLSpanElement, BreadcrumbPageProps>(
|
|
100
|
+
({ className, ...props }, ref) => (
|
|
101
|
+
<span
|
|
102
|
+
ref={ref}
|
|
103
|
+
role="link"
|
|
104
|
+
aria-disabled="true"
|
|
105
|
+
aria-current="page"
|
|
106
|
+
className={cn("font-normal text-foreground", className)}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
BreadcrumbPage.displayName = "BreadcrumbPage";
|
|
112
|
+
|
|
113
|
+
export interface BreadcrumbSeparatorProps extends React.ComponentProps<"li"> {
|
|
114
|
+
/**
|
|
115
|
+
* Override the default chevron with a custom node (e.g. a slash, an
|
|
116
|
+
* arrow icon).
|
|
117
|
+
*/
|
|
118
|
+
children?: React.ReactNode;
|
|
119
|
+
/** Tailwind / CSS classes merged onto the separator via `cn()`. */
|
|
120
|
+
className?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const BreadcrumbSeparator = ({ children, className, ...props }: BreadcrumbSeparatorProps) => (
|
|
124
|
+
<li
|
|
125
|
+
role="presentation"
|
|
126
|
+
aria-hidden="true"
|
|
127
|
+
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
|
|
128
|
+
{...props}
|
|
129
|
+
>
|
|
130
|
+
{children ?? <ChevronRight />}
|
|
131
|
+
</li>
|
|
132
|
+
);
|
|
133
|
+
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
|
|
134
|
+
|
|
135
|
+
export interface BreadcrumbEllipsisProps extends React.ComponentProps<"span"> {
|
|
136
|
+
/** Tailwind / CSS classes merged onto the ellipsis via `cn()`. */
|
|
137
|
+
className?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const BreadcrumbEllipsis = ({ className, ...props }: BreadcrumbEllipsisProps) => (
|
|
141
|
+
<span
|
|
142
|
+
role="presentation"
|
|
143
|
+
aria-hidden="true"
|
|
144
|
+
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
|
145
|
+
{...props}
|
|
146
|
+
>
|
|
147
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
148
|
+
<span className="sr-only">More</span>
|
|
149
|
+
</span>
|
|
150
|
+
);
|
|
151
|
+
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
|
|
152
|
+
|
|
153
|
+
export {
|
|
154
|
+
Breadcrumb,
|
|
155
|
+
BreadcrumbEllipsis,
|
|
156
|
+
BreadcrumbItem,
|
|
157
|
+
BreadcrumbLink,
|
|
158
|
+
BreadcrumbList,
|
|
159
|
+
BreadcrumbPage,
|
|
160
|
+
BreadcrumbSeparator,
|
|
161
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import type * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
import { Separator } from "../atoms/separator";
|
|
7
|
+
|
|
8
|
+
const buttonGroupVariants = cva(
|
|
9
|
+
"flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
orientation: {
|
|
13
|
+
horizontal:
|
|
14
|
+
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
|
|
15
|
+
vertical:
|
|
16
|
+
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
orientation: "horizontal",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export interface ButtonGroupProps
|
|
26
|
+
extends React.ComponentProps<"div">,
|
|
27
|
+
VariantProps<typeof buttonGroupVariants> {
|
|
28
|
+
/**
|
|
29
|
+
* `horizontal` (default) lays buttons left-to-right with shared borders;
|
|
30
|
+
* `vertical` stacks them with shared horizontal borders.
|
|
31
|
+
* @default "horizontal"
|
|
32
|
+
*/
|
|
33
|
+
orientation?: "horizontal" | "vertical";
|
|
34
|
+
/** A row/column of `<Button>`s, `<ButtonGroupText>`s, or separators. */
|
|
35
|
+
children?: React.ReactNode;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function ButtonGroup({ className, orientation, ...props }: ButtonGroupProps) {
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
role="group"
|
|
43
|
+
data-slot="button-group"
|
|
44
|
+
data-orientation={orientation}
|
|
45
|
+
className={cn(buttonGroupVariants({ orientation }), className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ButtonGroupTextProps extends React.ComponentProps<"div"> {
|
|
52
|
+
/**
|
|
53
|
+
* Render as a Radix Slot — useful for wrapping a label or icon as the
|
|
54
|
+
* group's text element.
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
asChild?: boolean;
|
|
58
|
+
/** Text or icon content. */
|
|
59
|
+
children?: React.ReactNode;
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ButtonGroupText({ className, asChild = false, ...props }: ButtonGroupTextProps) {
|
|
64
|
+
const Comp = asChild ? Slot : "div";
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Comp
|
|
68
|
+
className={cn(
|
|
69
|
+
"bg-muted shadow-xs flex items-center gap-2 rounded-md border px-4 text-sm font-medium [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
|
|
70
|
+
className,
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ButtonGroupSeparatorProps extends React.ComponentProps<typeof Separator> {
|
|
78
|
+
/**
|
|
79
|
+
* Layout direction.
|
|
80
|
+
* @default "vertical"
|
|
81
|
+
*/
|
|
82
|
+
orientation?: "horizontal" | "vertical";
|
|
83
|
+
className?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function ButtonGroupSeparator({
|
|
87
|
+
className,
|
|
88
|
+
orientation = "vertical",
|
|
89
|
+
...props
|
|
90
|
+
}: ButtonGroupSeparatorProps) {
|
|
91
|
+
return (
|
|
92
|
+
<Separator
|
|
93
|
+
data-slot="button-group-separator"
|
|
94
|
+
orientation={orientation}
|
|
95
|
+
className={cn(
|
|
96
|
+
"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
|
|
97
|
+
className,
|
|
98
|
+
)}
|
|
99
|
+
{...props}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants };
|