@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.
Files changed (214) hide show
  1. package/README.md +69 -35
  2. package/package.json +45 -177
  3. package/src/cn.ts +3 -0
  4. package/src/index.ts +12 -603
  5. package/src/theme.ts +62 -0
  6. package/src/tokens.ts +11 -0
  7. package/styles/base.css +17 -0
  8. package/styles/canvas.css +77 -52
  9. package/styles/components/alert.css +66 -0
  10. package/styles/components/app-shell.css +46 -0
  11. package/styles/components/avatar.css +22 -0
  12. package/styles/components/badge.css +83 -0
  13. package/styles/components/breadcrumb.css +35 -0
  14. package/styles/components/button-group.css +23 -0
  15. package/styles/components/button.css +107 -0
  16. package/styles/components/calendar.css +73 -0
  17. package/styles/components/card.css +58 -0
  18. package/styles/components/checkbox.css +55 -0
  19. package/styles/components/code-block.css +18 -0
  20. package/styles/components/combobox.css +75 -0
  21. package/styles/components/command.css +94 -0
  22. package/styles/components/data-table.css +142 -0
  23. package/styles/components/dialog.css +72 -0
  24. package/styles/components/dropdown.css +54 -0
  25. package/styles/components/empty-state.css +17 -0
  26. package/styles/components/field.css +27 -0
  27. package/styles/components/filter-panel.css +58 -0
  28. package/styles/components/form.css +27 -0
  29. package/styles/components/icon.css +8 -0
  30. package/styles/components/input-group.css +45 -0
  31. package/styles/components/input.css +56 -0
  32. package/styles/components/kbd.css +15 -0
  33. package/styles/components/page-header.css +52 -0
  34. package/styles/components/pagination.css +48 -0
  35. package/styles/components/popover.css +14 -0
  36. package/styles/components/radio.css +28 -0
  37. package/styles/components/row-menu.css +69 -0
  38. package/styles/components/section-card.css +49 -0
  39. package/styles/components/select.css +57 -0
  40. package/styles/components/separator.css +32 -0
  41. package/styles/components/sheet.css +70 -0
  42. package/styles/components/sidebar.css +146 -0
  43. package/styles/components/skeleton.css +32 -0
  44. package/styles/components/spinner.css +26 -0
  45. package/styles/components/stat-card.css +71 -0
  46. package/styles/components/stepper.css +63 -0
  47. package/styles/components/switch.css +45 -0
  48. package/styles/components/tabs.css +40 -0
  49. package/styles/components/textarea.css +31 -0
  50. package/styles/components/toast.css +95 -0
  51. package/styles/components/tooltip.css +53 -0
  52. package/styles/components/topbar.css +24 -0
  53. package/styles/components/typography.css +105 -0
  54. package/styles/patterns/backdrops.css +35 -0
  55. package/styles/patterns/density.css +66 -0
  56. package/styles/patterns/focus.css +22 -0
  57. package/styles/patterns/glass.css +85 -0
  58. package/styles/patterns/high-contrast.css +70 -0
  59. package/styles/patterns/reduced-motion.css +12 -0
  60. package/styles/patterns/scrollbar.css +10 -0
  61. package/styles/reset.css +89 -0
  62. package/styles/tokens/colors.css +106 -0
  63. package/styles/tokens/motion.css +33 -0
  64. package/styles/tokens/radius.css +10 -0
  65. package/styles/tokens/shadows.css +35 -0
  66. package/styles/tokens/spacing.css +19 -0
  67. package/styles/tokens/typography.css +6 -0
  68. package/styles/tokens/z-index.css +12 -0
  69. package/styles/utilities/display.css +66 -0
  70. package/styles/utilities/flexbox.css +240 -0
  71. package/styles/utilities/gap.css +288 -0
  72. package/styles/utilities/grid.css +138 -0
  73. package/styles/utilities/position.css +78 -0
  74. package/styles/utilities/sizing.css +138 -0
  75. package/tsconfig.json +20 -21
  76. package/src/components/atoms/README.md +0 -11
  77. package/src/components/atoms/aspect-ratio.tsx +0 -32
  78. package/src/components/atoms/avatar.tsx +0 -98
  79. package/src/components/atoms/badge.tsx +0 -44
  80. package/src/components/atoms/brand-mark.tsx +0 -74
  81. package/src/components/atoms/button.tsx +0 -105
  82. package/src/components/atoms/checkbox.tsx +0 -63
  83. package/src/components/atoms/flex-box.tsx +0 -105
  84. package/src/components/atoms/icon.tsx +0 -34
  85. package/src/components/atoms/input.tsx +0 -92
  86. package/src/components/atoms/label.tsx +0 -41
  87. package/src/components/atoms/logo.tsx +0 -89
  88. package/src/components/atoms/progress.tsx +0 -55
  89. package/src/components/atoms/radio-group.tsx +0 -122
  90. package/src/components/atoms/scroll-area.tsx +0 -106
  91. package/src/components/atoms/section.tsx +0 -48
  92. package/src/components/atoms/separator.tsx +0 -45
  93. package/src/components/atoms/skeleton.tsx +0 -17
  94. package/src/components/atoms/slider.tsx +0 -93
  95. package/src/components/atoms/spinner.tsx +0 -47
  96. package/src/components/atoms/switch.tsx +0 -60
  97. package/src/components/atoms/textarea.tsx +0 -78
  98. package/src/components/atoms/toggle.tsx +0 -80
  99. package/src/components/charts/activity-heatmap.tsx +0 -186
  100. package/src/components/charts/axes.tsx +0 -21
  101. package/src/components/charts/chart-container.tsx +0 -254
  102. package/src/components/charts/chart-legend.tsx +0 -67
  103. package/src/components/charts/chart-tooltip.tsx +0 -161
  104. package/src/components/charts/chart-types.tsx +0 -49
  105. package/src/components/charts/containers.tsx +0 -11
  106. package/src/components/charts/data.tsx +0 -16
  107. package/src/components/charts/details.tsx +0 -25
  108. package/src/components/charts/dot-pulse.tsx +0 -61
  109. package/src/components/charts/gauge.tsx +0 -106
  110. package/src/components/charts/grids.tsx +0 -8
  111. package/src/components/charts/index.ts +0 -62
  112. package/src/components/charts/labeled-bar-list.tsx +0 -85
  113. package/src/components/charts/metric-breakdown.tsx +0 -316
  114. package/src/components/charts/references.tsx +0 -8
  115. package/src/components/charts/service-health-list.tsx +0 -85
  116. package/src/components/charts/sparkline-area.tsx +0 -80
  117. package/src/components/charts/sparkline.tsx +0 -52
  118. package/src/components/charts/stacked-bar.tsx +0 -104
  119. package/src/components/charts/text.tsx +0 -10
  120. package/src/components/charts/world-heat-map-inner.tsx +0 -317
  121. package/src/components/charts/world-heat-map.tsx +0 -184
  122. package/src/components/molecules/README.md +0 -12
  123. package/src/components/molecules/action-bar.tsx +0 -73
  124. package/src/components/molecules/activity-item.tsx +0 -74
  125. package/src/components/molecules/alert.tsx +0 -86
  126. package/src/components/molecules/animated-background.tsx +0 -92
  127. package/src/components/molecules/auth-shell.tsx +0 -95
  128. package/src/components/molecules/brand-lockup.tsx +0 -48
  129. package/src/components/molecules/breadcrumb.tsx +0 -157
  130. package/src/components/molecules/button-group.tsx +0 -104
  131. package/src/components/molecules/calendar.tsx +0 -217
  132. package/src/components/molecules/card.tsx +0 -102
  133. package/src/components/molecules/client-brand.tsx +0 -95
  134. package/src/components/molecules/code-block.tsx +0 -86
  135. package/src/components/molecules/countdown-button.tsx +0 -92
  136. package/src/components/molecules/empty-state.tsx +0 -56
  137. package/src/components/molecules/error-state.tsx +0 -42
  138. package/src/components/molecules/field-display.tsx +0 -35
  139. package/src/components/molecules/input-otp.tsx +0 -74
  140. package/src/components/molecules/launcher-card.tsx +0 -152
  141. package/src/components/molecules/loading-state.tsx +0 -36
  142. package/src/components/molecules/notification-item.tsx +0 -67
  143. package/src/components/molecules/notification-list.tsx +0 -45
  144. package/src/components/molecules/number-badge.tsx +0 -53
  145. package/src/components/molecules/or-separator.tsx +0 -38
  146. package/src/components/molecules/page-header.tsx +0 -88
  147. package/src/components/molecules/page-tabs.tsx +0 -94
  148. package/src/components/molecules/pagination.tsx +0 -150
  149. package/src/components/molecules/password-input.tsx +0 -83
  150. package/src/components/molecules/password-strength-meter.tsx +0 -104
  151. package/src/components/molecules/phone-input.tsx +0 -200
  152. package/src/components/molecules/search-bar.tsx +0 -64
  153. package/src/components/molecules/secret-field.tsx +0 -158
  154. package/src/components/molecules/section-card.tsx +0 -91
  155. package/src/components/molecules/social-buttons.tsx +0 -165
  156. package/src/components/molecules/stat-card.tsx +0 -100
  157. package/src/components/molecules/status-badge.tsx +0 -42
  158. package/src/components/molecules/stepper.tsx +0 -96
  159. package/src/components/molecules/table.tsx +0 -157
  160. package/src/components/molecules/terminal.tsx +0 -74
  161. package/src/components/molecules/toggle-group.tsx +0 -145
  162. package/src/components/molecules/tooltip.tsx +0 -155
  163. package/src/components/molecules/user-avatar-chip.tsx +0 -71
  164. package/src/components/organisms/README.md +0 -14
  165. package/src/components/organisms/accordion.tsx +0 -154
  166. package/src/components/organisms/alert-dialog.tsx +0 -277
  167. package/src/components/organisms/carousel.tsx +0 -244
  168. package/src/components/organisms/collapsible.tsx +0 -69
  169. package/src/components/organisms/command.tsx +0 -144
  170. package/src/components/organisms/context-menu.tsx +0 -339
  171. package/src/components/organisms/dashboard-grid.tsx +0 -369
  172. package/src/components/organisms/data-table.tsx +0 -330
  173. package/src/components/organisms/dialog.tsx +0 -312
  174. package/src/components/organisms/drawer.tsx +0 -123
  175. package/src/components/organisms/dropdown-menu.tsx +0 -440
  176. package/src/components/organisms/editors/code-editor.tsx +0 -144
  177. package/src/components/organisms/editors/index.ts +0 -4
  178. package/src/components/organisms/editors/markdown-editor.tsx +0 -153
  179. package/src/components/organisms/editors/markdown-renderer.ts +0 -27
  180. package/src/components/organisms/editors/prose-canvas-classes.ts +0 -45
  181. package/src/components/organisms/editors/rich-text-editor.tsx +0 -126
  182. package/src/components/organisms/editors/toolbar/md-toolbar.tsx +0 -129
  183. package/src/components/organisms/editors/toolbar/rte-toolbar.tsx +0 -211
  184. package/src/components/organisms/editors/toolbar/toolbar-shell.tsx +0 -45
  185. package/src/components/organisms/editors/use-codemirror-theme.ts +0 -61
  186. package/src/components/organisms/error-boundary.tsx +0 -61
  187. package/src/components/organisms/form.tsx +0 -174
  188. package/src/components/organisms/hover-card.tsx +0 -115
  189. package/src/components/organisms/menubar.tsx +0 -498
  190. package/src/components/organisms/navbar.tsx +0 -104
  191. package/src/components/organisms/navigation-menu.tsx +0 -235
  192. package/src/components/organisms/popover.tsx +0 -149
  193. package/src/components/organisms/resizable.tsx +0 -58
  194. package/src/components/organisms/schema-form.tsx +0 -232
  195. package/src/components/organisms/select.tsx +0 -309
  196. package/src/components/organisms/sheet.tsx +0 -265
  197. package/src/components/organisms/sidebar.tsx +0 -1040
  198. package/src/components/organisms/sonner.tsx +0 -96
  199. package/src/components/organisms/tabs.tsx +0 -133
  200. package/src/components/organisms/theme-provider.tsx +0 -101
  201. package/src/hooks/use-mobile.tsx +0 -19
  202. package/src/lib/portal-container.tsx +0 -35
  203. package/src/lib/utils.ts +0 -6
  204. package/src/native.ts +0 -23
  205. package/src/tokens/colors.ts +0 -91
  206. package/src/tokens/index.ts +0 -3
  207. package/src/tokens/spacing.ts +0 -55
  208. package/src/tokens/typography.ts +0 -27
  209. package/styles/dashboard-grid.css +0 -47
  210. package/styles/fonts/Roboto-VariableFont_wdth_wght.ttf +0 -0
  211. package/styles/glass.css +0 -175
  212. package/styles/leaflet.css +0 -13
  213. package/styles/tokens.css +0 -317
  214. 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 };