@olympusoss/canvas 2.20.2 → 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 -175
- package/styles/leaflet.css +0 -13
- package/styles/tokens.css +0 -317
- package/tailwind.config.ts +0 -70
|
@@ -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";
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
|
|
4
|
-
import { cn } from "../../lib/utils";
|
|
5
|
-
import { type ButtonProps, buttonVariants } from "../atoms/button";
|
|
6
|
-
|
|
7
|
-
export interface PaginationProps extends React.ComponentProps<"nav"> {
|
|
8
|
-
/** A `<PaginationContent>` with `<PaginationItem>`s. */
|
|
9
|
-
children?: React.ReactNode;
|
|
10
|
-
/** Tailwind / CSS classes merged onto the `<nav>` via `cn()`. */
|
|
11
|
-
className?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const Pagination = ({ className, ...props }: PaginationProps) => (
|
|
15
|
-
<nav
|
|
16
|
-
role="navigation"
|
|
17
|
-
aria-label="pagination"
|
|
18
|
-
className={cn("mx-auto flex w-full justify-center", className)}
|
|
19
|
-
{...props}
|
|
20
|
-
/>
|
|
21
|
-
);
|
|
22
|
-
Pagination.displayName = "Pagination";
|
|
23
|
-
|
|
24
|
-
export interface PaginationContentProps extends React.ComponentProps<"ul"> {
|
|
25
|
-
/** A flat list of `<PaginationItem>`s. */
|
|
26
|
-
children?: React.ReactNode;
|
|
27
|
-
/** Tailwind / CSS classes merged onto the `<ul>` via `cn()`. */
|
|
28
|
-
className?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const PaginationContent = React.forwardRef<HTMLUListElement, PaginationContentProps>(
|
|
32
|
-
({ className, ...props }, ref) => (
|
|
33
|
-
<ul ref={ref} className={cn("flex flex-row items-center gap-1", className)} {...props} />
|
|
34
|
-
),
|
|
35
|
-
);
|
|
36
|
-
PaginationContent.displayName = "PaginationContent";
|
|
37
|
-
|
|
38
|
-
export interface PaginationItemProps extends React.ComponentProps<"li"> {
|
|
39
|
-
/** Typically a `<PaginationLink>`, `<PaginationPrevious>`, `<PaginationNext>`, or `<PaginationEllipsis>`. */
|
|
40
|
-
children?: React.ReactNode;
|
|
41
|
-
/** Tailwind / CSS classes merged onto the `<li>` via `cn()`. */
|
|
42
|
-
className?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const PaginationItem = React.forwardRef<HTMLLIElement, PaginationItemProps>(
|
|
46
|
-
({ className, ...props }, ref) => <li ref={ref} className={cn("", className)} {...props} />,
|
|
47
|
-
);
|
|
48
|
-
PaginationItem.displayName = "PaginationItem";
|
|
49
|
-
|
|
50
|
-
export interface PaginationLinkProps extends React.ComponentProps<"a"> {
|
|
51
|
-
/**
|
|
52
|
-
* Mark this link as the current page. Adds `aria-current="page"` and
|
|
53
|
-
* applies the outline button variant.
|
|
54
|
-
* @default false
|
|
55
|
-
*/
|
|
56
|
-
isActive?: boolean;
|
|
57
|
-
/**
|
|
58
|
-
* Button-style size preset (inherited from `<Button>`).
|
|
59
|
-
* @default "icon"
|
|
60
|
-
*/
|
|
61
|
-
size?: ButtonProps["size"];
|
|
62
|
-
/** Page number or label. */
|
|
63
|
-
children?: React.ReactNode;
|
|
64
|
-
/** Anchor target. */
|
|
65
|
-
href?: string;
|
|
66
|
-
/** Tailwind / CSS classes merged onto the link via `cn()`. */
|
|
67
|
-
className?: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const PaginationLink = ({ className, isActive, size = "icon", ...props }: PaginationLinkProps) => (
|
|
71
|
-
<a
|
|
72
|
-
aria-current={isActive ? "page" : undefined}
|
|
73
|
-
className={cn(
|
|
74
|
-
buttonVariants({
|
|
75
|
-
variant: isActive ? "outline" : "ghost",
|
|
76
|
-
size,
|
|
77
|
-
}),
|
|
78
|
-
className,
|
|
79
|
-
)}
|
|
80
|
-
{...props}
|
|
81
|
-
/>
|
|
82
|
-
);
|
|
83
|
-
PaginationLink.displayName = "PaginationLink";
|
|
84
|
-
|
|
85
|
-
export interface PaginationPreviousProps extends Omit<PaginationLinkProps, "size"> {
|
|
86
|
-
/** Anchor target for the previous page. */
|
|
87
|
-
href?: string;
|
|
88
|
-
/** Tailwind / CSS classes merged via `cn()`. */
|
|
89
|
-
className?: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const PaginationPrevious = ({ className, ...props }: PaginationPreviousProps) => (
|
|
93
|
-
<PaginationLink
|
|
94
|
-
aria-label="Go to previous page"
|
|
95
|
-
size="default"
|
|
96
|
-
className={cn("gap-1 pl-2.5", className)}
|
|
97
|
-
{...props}
|
|
98
|
-
>
|
|
99
|
-
<ChevronLeft className="h-4 w-4" />
|
|
100
|
-
<span>Previous</span>
|
|
101
|
-
</PaginationLink>
|
|
102
|
-
);
|
|
103
|
-
PaginationPrevious.displayName = "PaginationPrevious";
|
|
104
|
-
|
|
105
|
-
export interface PaginationNextProps extends Omit<PaginationLinkProps, "size"> {
|
|
106
|
-
/** Anchor target for the next page. */
|
|
107
|
-
href?: string;
|
|
108
|
-
/** Tailwind / CSS classes merged via `cn()`. */
|
|
109
|
-
className?: string;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const PaginationNext = ({ className, ...props }: PaginationNextProps) => (
|
|
113
|
-
<PaginationLink
|
|
114
|
-
aria-label="Go to next page"
|
|
115
|
-
size="default"
|
|
116
|
-
className={cn("gap-1 pr-2.5", className)}
|
|
117
|
-
{...props}
|
|
118
|
-
>
|
|
119
|
-
<span>Next</span>
|
|
120
|
-
<ChevronRight className="h-4 w-4" />
|
|
121
|
-
</PaginationLink>
|
|
122
|
-
);
|
|
123
|
-
PaginationNext.displayName = "PaginationNext";
|
|
124
|
-
|
|
125
|
-
export interface PaginationEllipsisProps extends React.ComponentProps<"span"> {
|
|
126
|
-
/** Tailwind / CSS classes merged via `cn()`. */
|
|
127
|
-
className?: string;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const PaginationEllipsis = ({ className, ...props }: PaginationEllipsisProps) => (
|
|
131
|
-
<span
|
|
132
|
-
aria-hidden
|
|
133
|
-
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
|
134
|
-
{...props}
|
|
135
|
-
>
|
|
136
|
-
<MoreHorizontal className="h-4 w-4" />
|
|
137
|
-
<span className="sr-only">More pages</span>
|
|
138
|
-
</span>
|
|
139
|
-
);
|
|
140
|
-
PaginationEllipsis.displayName = "PaginationEllipsis";
|
|
141
|
-
|
|
142
|
-
export {
|
|
143
|
-
Pagination,
|
|
144
|
-
PaginationContent,
|
|
145
|
-
PaginationEllipsis,
|
|
146
|
-
PaginationItem,
|
|
147
|
-
PaginationLink,
|
|
148
|
-
PaginationNext,
|
|
149
|
-
PaginationPrevious,
|
|
150
|
-
};
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Eye, EyeOff } from "lucide-react";
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
|
|
6
|
-
import { cn } from "../../lib/utils";
|
|
7
|
-
import { Button } from "../atoms/button";
|
|
8
|
-
import { Input } from "../atoms/input";
|
|
9
|
-
|
|
10
|
-
export interface PasswordInputProps
|
|
11
|
-
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> {
|
|
12
|
-
/**
|
|
13
|
-
* Initial visibility state. Pair with `onVisibilityChange` for a
|
|
14
|
-
* controlled input.
|
|
15
|
-
* @default false
|
|
16
|
-
*/
|
|
17
|
-
defaultVisible?: boolean;
|
|
18
|
-
/** Controlled visibility. Overrides `defaultVisible`. */
|
|
19
|
-
visible?: boolean;
|
|
20
|
-
/** Fired when the user clicks the eye toggle. */
|
|
21
|
-
onVisibilityChange?: (visible: boolean) => void;
|
|
22
|
-
/**
|
|
23
|
-
* Accessible label on the eye toggle button.
|
|
24
|
-
* @default "Toggle password visibility"
|
|
25
|
-
*/
|
|
26
|
-
toggleLabel?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Password input with a show/hide eye toggle. Renders as an `Input` (atom)
|
|
31
|
-
* with a `Button` (atom) absolutely positioned at the trailing edge.
|
|
32
|
-
*
|
|
33
|
-
* Use anywhere a password field is needed: login, registration, settings,
|
|
34
|
-
* reset flows.
|
|
35
|
-
*/
|
|
36
|
-
const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
|
|
37
|
-
(
|
|
38
|
-
{
|
|
39
|
-
defaultVisible = false,
|
|
40
|
-
visible: controlledVisible,
|
|
41
|
-
onVisibilityChange,
|
|
42
|
-
toggleLabel = "Toggle password visibility",
|
|
43
|
-
className,
|
|
44
|
-
...props
|
|
45
|
-
},
|
|
46
|
-
ref,
|
|
47
|
-
) => {
|
|
48
|
-
const [internalVisible, setInternalVisible] = React.useState(defaultVisible);
|
|
49
|
-
const visible = controlledVisible ?? internalVisible;
|
|
50
|
-
|
|
51
|
-
const toggle = () => {
|
|
52
|
-
const next = !visible;
|
|
53
|
-
if (controlledVisible === undefined) setInternalVisible(next);
|
|
54
|
-
onVisibilityChange?.(next);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<div className="relative">
|
|
59
|
-
<Input
|
|
60
|
-
ref={ref}
|
|
61
|
-
type={visible ? "text" : "password"}
|
|
62
|
-
className={cn("pr-10", className)}
|
|
63
|
-
{...props}
|
|
64
|
-
/>
|
|
65
|
-
<Button
|
|
66
|
-
type="button"
|
|
67
|
-
variant="ghost"
|
|
68
|
-
size="icon"
|
|
69
|
-
onClick={toggle}
|
|
70
|
-
aria-label={toggleLabel}
|
|
71
|
-
aria-pressed={visible}
|
|
72
|
-
className="absolute right-1 top-1/2 h-7 w-7 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
73
|
-
tabIndex={-1}
|
|
74
|
-
>
|
|
75
|
-
{visible ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
76
|
-
</Button>
|
|
77
|
-
</div>
|
|
78
|
-
);
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
PasswordInput.displayName = "PasswordInput";
|
|
82
|
-
|
|
83
|
-
export { PasswordInput };
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
|
|
5
|
-
import { cn } from "../../lib/utils";
|
|
6
|
-
|
|
7
|
-
export type PasswordStrengthLevel = 0 | 1 | 2 | 3 | 4;
|
|
8
|
-
|
|
9
|
-
export interface PasswordStrength {
|
|
10
|
-
/** 0 = empty, 1 = weak, 2 = fair, 3 = good, 4 = strong. */
|
|
11
|
-
level: PasswordStrengthLevel;
|
|
12
|
-
/** Short human label shown next to the meter. */
|
|
13
|
-
label: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Score a password 0-4 based on length + character variety. Returns the
|
|
18
|
-
* level plus a short label. Heuristic-only; for a true strength score use a
|
|
19
|
-
* dictionary-aware estimator like `@zxcvbn-ts/core` and map its score here.
|
|
20
|
-
*/
|
|
21
|
-
export function scorePassword(pw: string): PasswordStrength {
|
|
22
|
-
if (!pw) return { level: 0, label: "" };
|
|
23
|
-
let score = 0;
|
|
24
|
-
if (pw.length >= 8) score++;
|
|
25
|
-
if (pw.length >= 12) score++;
|
|
26
|
-
if (/[a-z]/.test(pw) && /[A-Z]/.test(pw)) score++;
|
|
27
|
-
if (/\d/.test(pw)) score++;
|
|
28
|
-
if (/[^A-Za-z0-9]/.test(pw)) score++;
|
|
29
|
-
if (pw.length < 6) score = Math.min(score, 1);
|
|
30
|
-
|
|
31
|
-
// score is clamped into [1, 4]: `Math.max(1, …)` provides the floor,
|
|
32
|
-
// `Math.min(4, …)` provides the ceiling. Higher-tier scorers can map
|
|
33
|
-
// dictionary results into the same shape.
|
|
34
|
-
const level = Math.min(4, Math.max(1, score)) as PasswordStrengthLevel;
|
|
35
|
-
const labels = ["", "Weak", "Fair", "Good", "Strong"];
|
|
36
|
-
return { level, label: labels[level] };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface PasswordStrengthMeterProps {
|
|
40
|
-
/** Password value to score. Empty string renders nothing. */
|
|
41
|
-
value: string;
|
|
42
|
-
/**
|
|
43
|
-
* Custom scorer. Override the default heuristic with a zxcvbn-based
|
|
44
|
-
* scorer for production use.
|
|
45
|
-
*/
|
|
46
|
-
score?: (pw: string) => PasswordStrength;
|
|
47
|
-
/** Tailwind / CSS classes merged onto the root via `cn()`. */
|
|
48
|
-
className?: string;
|
|
49
|
-
/**
|
|
50
|
-
* Hide the strength label text and show only the bar.
|
|
51
|
-
* @default false
|
|
52
|
-
*/
|
|
53
|
-
hideLabel?: boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Four-segment strength bar for a password input. Renders nothing for an
|
|
58
|
-
* empty value. Use beneath a password input in registration and reset
|
|
59
|
-
* flows.
|
|
60
|
-
*/
|
|
61
|
-
const PasswordStrengthMeter = React.forwardRef<HTMLDivElement, PasswordStrengthMeterProps>(
|
|
62
|
-
({ value, score = scorePassword, className, hideLabel = false }, ref) => {
|
|
63
|
-
const { level, label } = score(value);
|
|
64
|
-
if (level === 0) return null;
|
|
65
|
-
const segmentColors = [
|
|
66
|
-
"bg-muted",
|
|
67
|
-
"bg-destructive",
|
|
68
|
-
"bg-amber-500",
|
|
69
|
-
"bg-emerald-500",
|
|
70
|
-
"bg-emerald-600",
|
|
71
|
-
];
|
|
72
|
-
const labelColor =
|
|
73
|
-
level === 1
|
|
74
|
-
? "text-destructive"
|
|
75
|
-
: level === 2
|
|
76
|
-
? "text-amber-600 dark:text-amber-500"
|
|
77
|
-
: "text-emerald-600 dark:text-emerald-500";
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<div ref={ref} className={cn("flex items-center gap-2", className)}>
|
|
81
|
-
<div className="flex flex-1 gap-1" aria-hidden>
|
|
82
|
-
{[1, 2, 3, 4].map((seg) => (
|
|
83
|
-
<div
|
|
84
|
-
key={seg}
|
|
85
|
-
className={cn(
|
|
86
|
-
"h-1 flex-1 rounded-full transition-colors",
|
|
87
|
-
seg <= level ? segmentColors[level] : "bg-muted",
|
|
88
|
-
)}
|
|
89
|
-
/>
|
|
90
|
-
))}
|
|
91
|
-
</div>
|
|
92
|
-
{!hideLabel && (
|
|
93
|
-
<span className={cn("text-xs font-medium tabular-nums", labelColor)}>{label}</span>
|
|
94
|
-
)}
|
|
95
|
-
<span className="sr-only" role="status">
|
|
96
|
-
Password strength: {label}
|
|
97
|
-
</span>
|
|
98
|
-
</div>
|
|
99
|
-
);
|
|
100
|
-
},
|
|
101
|
-
);
|
|
102
|
-
PasswordStrengthMeter.displayName = "PasswordStrengthMeter";
|
|
103
|
-
|
|
104
|
-
export { PasswordStrengthMeter };
|