@annondeveloper/ui-kit 0.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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +215 -0
  3. package/dist/chunk-5OKSXPWK.js +270 -0
  4. package/dist/chunk-5OKSXPWK.js.map +1 -0
  5. package/dist/cli/index.js +430 -0
  6. package/dist/form.d.ts +65 -0
  7. package/dist/form.js +148 -0
  8. package/dist/form.js.map +1 -0
  9. package/dist/index.d.ts +942 -0
  10. package/dist/index.js +2812 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/select-nnBJUO8U.d.ts +26 -0
  13. package/package.json +114 -0
  14. package/src/components/animated-counter.stories.tsx +68 -0
  15. package/src/components/animated-counter.tsx +85 -0
  16. package/src/components/avatar.tsx +106 -0
  17. package/src/components/badge.stories.tsx +70 -0
  18. package/src/components/badge.tsx +97 -0
  19. package/src/components/button.stories.tsx +101 -0
  20. package/src/components/button.tsx +67 -0
  21. package/src/components/card.tsx +128 -0
  22. package/src/components/checkbox.stories.tsx +64 -0
  23. package/src/components/checkbox.tsx +58 -0
  24. package/src/components/confirm-dialog.stories.tsx +96 -0
  25. package/src/components/confirm-dialog.tsx +145 -0
  26. package/src/components/data-table.stories.tsx +125 -0
  27. package/src/components/data-table.tsx +791 -0
  28. package/src/components/dropdown-menu.tsx +111 -0
  29. package/src/components/empty-state.stories.tsx +42 -0
  30. package/src/components/empty-state.tsx +43 -0
  31. package/src/components/filter-pill.stories.tsx +71 -0
  32. package/src/components/filter-pill.tsx +45 -0
  33. package/src/components/form-input.stories.tsx +91 -0
  34. package/src/components/form-input.tsx +77 -0
  35. package/src/components/log-viewer.tsx +212 -0
  36. package/src/components/metric-card.tsx +141 -0
  37. package/src/components/pipeline-stage.tsx +134 -0
  38. package/src/components/popover.tsx +72 -0
  39. package/src/components/port-status-grid.tsx +102 -0
  40. package/src/components/progress.tsx +128 -0
  41. package/src/components/radio-group.tsx +162 -0
  42. package/src/components/select.stories.tsx +52 -0
  43. package/src/components/select.tsx +92 -0
  44. package/src/components/severity-timeline.tsx +125 -0
  45. package/src/components/sheet.tsx +164 -0
  46. package/src/components/skeleton.stories.tsx +64 -0
  47. package/src/components/skeleton.tsx +62 -0
  48. package/src/components/slider.tsx +208 -0
  49. package/src/components/sparkline.tsx +104 -0
  50. package/src/components/status-badge.stories.tsx +84 -0
  51. package/src/components/status-badge.tsx +71 -0
  52. package/src/components/status-pulse.stories.tsx +56 -0
  53. package/src/components/status-pulse.tsx +78 -0
  54. package/src/components/success-checkmark.stories.tsx +67 -0
  55. package/src/components/success-checkmark.tsx +53 -0
  56. package/src/components/tabs.tsx +177 -0
  57. package/src/components/threshold-gauge.tsx +149 -0
  58. package/src/components/time-range-selector.tsx +86 -0
  59. package/src/components/toast.stories.tsx +70 -0
  60. package/src/components/toast.tsx +48 -0
  61. package/src/components/toggle-switch.stories.tsx +66 -0
  62. package/src/components/toggle-switch.tsx +51 -0
  63. package/src/components/tooltip.tsx +62 -0
  64. package/src/components/truncated-text.stories.tsx +56 -0
  65. package/src/components/truncated-text.tsx +80 -0
  66. package/src/components/uptime-tracker.tsx +138 -0
  67. package/src/components/utilization-bar.tsx +103 -0
  68. package/src/theme.css +178 -0
  69. package/src/utils.ts +123 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 annondeveloper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # @annondeveloper/ui-kit
2
+
3
+ Production-grade React UI component library with dark/light theme support, built on Tailwind CSS v4, Framer Motion, and Radix UI primitives.
4
+
5
+ ## Features
6
+
7
+ - **17 components** — Button, DataTable, Select, Badge, ConfirmDialog, and more
8
+ - **Dark/light theme** — CSS custom properties, toggleable via `.light` class on `<html>`
9
+ - **Accessible** — Radix UI primitives for Select, Dialog, Tooltip
10
+ - **Animated** — Framer Motion with `useReducedMotion()` respect
11
+ - **Responsive** — Desktop-first, mobile-capable
12
+ - **Tree-shakeable** — ESM-only, import only what you use
13
+ - **Type-safe** — Full TypeScript with exported types
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @annondeveloper/ui-kit
19
+ ```
20
+
21
+ ### Peer Dependencies
22
+
23
+ ```bash
24
+ npm install react react-dom framer-motion lucide-react clsx tailwind-merge sonner \
25
+ @radix-ui/react-select @radix-ui/react-alert-dialog @radix-ui/react-tooltip
26
+ ```
27
+
28
+ ## Setup
29
+
30
+ ### 1. Import the theme CSS
31
+
32
+ ```tsx
33
+ // app/layout.tsx or main entry
34
+ import '@annondeveloper/ui-kit/src/theme.css'
35
+ ```
36
+
37
+ Or copy the CSS custom properties into your own `globals.css`.
38
+
39
+ ### 2. Use components
40
+
41
+ ```tsx
42
+ import { Button, DataTable, Badge, Select, AnimatedCounter } from '@annondeveloper/ui-kit'
43
+
44
+ function App() {
45
+ return (
46
+ <Button variant="primary" loading={isPending}>
47
+ Save Changes
48
+ </Button>
49
+ )
50
+ }
51
+ ```
52
+
53
+ ## Components
54
+
55
+ ### Layout & Feedback
56
+
57
+ | Component | Description |
58
+ |-----------|-------------|
59
+ | `Button` | 5 variants (primary/secondary/danger/outline/ghost), 4 sizes, loading state |
60
+ | `Badge` | Color-coded label with 10 color presets |
61
+ | `EmptyState` | Placeholder with icon, title, description, optional actions |
62
+ | `Skeleton` / `SkeletonText` / `SkeletonCard` | Loading placeholders with shimmer animation |
63
+ | `SuccessCheckmark` | Animated SVG checkmark for success states |
64
+ | `StatusBadge` | Configurable status indicator (accepts custom status map) |
65
+ | `StatusPulse` | Animated status dot with configurable pulse behavior |
66
+
67
+ ### Forms
68
+
69
+ | Component | Description |
70
+ |-----------|-------------|
71
+ | `Select` | Radix UI select with theme-safe styling |
72
+ | `Checkbox` | Forwarded ref checkbox with indeterminate support |
73
+ | `ToggleSwitch` | Icon-based boolean toggle |
74
+ | `FormInput` | Labeled input with hint text + shared class constants |
75
+ | `FilterPill` | Rounded filter toggle pill with optional count |
76
+
77
+ ### Data Display
78
+
79
+ | Component | Description |
80
+ |-----------|-------------|
81
+ | `DataTable` | Full-featured table — sort, filter, search, paginate, density, column picker, CSV export |
82
+ | `AnimatedCounter` | Smooth number animation with rAF easing |
83
+ | `TruncatedText` | Auto-truncation with Radix tooltip + copy-to-clipboard |
84
+
85
+ ### Overlays
86
+
87
+ | Component | Description |
88
+ |-----------|-------------|
89
+ | `ConfirmDialog` | Radix AlertDialog with Framer Motion animations, danger variant |
90
+ | `Toaster` / `toast` | Sonner wrapper with theme-aware styling |
91
+
92
+ ## Utilities
93
+
94
+ ```tsx
95
+ import { cn, fmtBytes, fmtRelative, fmtBps, fmtPct } from '@annondeveloper/ui-kit'
96
+
97
+ // Tailwind class merging
98
+ cn('px-4 py-2', isActive && 'bg-blue-500', className)
99
+
100
+ // Formatters
101
+ fmtBytes(1073741824) // "1.0 GB"
102
+ fmtRelative('2024-01-01') // "3 months ago"
103
+ fmtBps(1000000000) // "1.0 Gbps"
104
+ fmtPct(0.956) // "95.6%"
105
+ ```
106
+
107
+ ## Theme System
108
+
109
+ The library uses CSS custom properties for theming. Dark mode is the default; add `.light` class to `<html>` for light mode.
110
+
111
+ ```css
112
+ /* Dark mode (default) */
113
+ :root {
114
+ --bg-base: 220 15% 8%;
115
+ --bg-surface: 220 14% 11%;
116
+ --text-primary: 220 10% 93%;
117
+ --brand-primary: 217 91% 60%;
118
+ --status-ok: 142 71% 45%;
119
+ --status-critical: 0 84% 60%;
120
+ }
121
+
122
+ /* Light mode */
123
+ .light {
124
+ --bg-base: 220 14% 96%;
125
+ --bg-surface: 0 0% 100%;
126
+ --text-primary: 220 14% 10%;
127
+ }
128
+ ```
129
+
130
+ All components use `hsl(var(--token))` syntax — never hardcoded colors.
131
+
132
+ ## Badge Factory
133
+
134
+ Create domain-specific badge variants without modifying the library:
135
+
136
+ ```tsx
137
+ import { createBadgeVariant } from '@annondeveloper/ui-kit'
138
+
139
+ const SeverityBadge = createBadgeVariant({
140
+ colorMap: { critical: 'red', warning: 'yellow', info: 'blue' },
141
+ labelMap: { critical: 'Critical', warning: 'Warning', info: 'Info' },
142
+ })
143
+
144
+ // Usage: <SeverityBadge value="critical" />
145
+ ```
146
+
147
+ ## StatusBadge Configuration
148
+
149
+ ```tsx
150
+ import { StatusBadge, defaultStatusMap } from '@annondeveloper/ui-kit'
151
+
152
+ // Use defaults
153
+ <StatusBadge status="ok" />
154
+
155
+ // Or provide custom status map
156
+ const myStatuses = {
157
+ healthy: { label: 'Healthy', color: 'hsl(var(--status-ok))', dot: 'bg-green-500' },
158
+ degraded: { label: 'Degraded', color: 'hsl(var(--status-warning))', dot: 'bg-yellow-500' },
159
+ }
160
+ <StatusBadge status="healthy" statusMap={myStatuses} />
161
+ ```
162
+
163
+ ## DataTable
164
+
165
+ The most feature-rich component — a full data grid built on TanStack Table:
166
+
167
+ ```tsx
168
+ import { DataTable } from '@annondeveloper/ui-kit'
169
+ import type { ColumnDef } from '@tanstack/react-table'
170
+
171
+ const columns: ColumnDef<User>[] = [
172
+ { id: 'name', header: 'Name', accessorKey: 'name' },
173
+ { id: 'email', header: 'Email', accessorKey: 'email' },
174
+ { id: 'role', header: 'Role', accessorKey: 'role', size: 100 },
175
+ ]
176
+
177
+ <DataTable
178
+ columns={columns}
179
+ data={users}
180
+ isLoading={isLoading}
181
+ searchPlaceholder="Search users..."
182
+ defaultPageSize={25}
183
+ exportFilename="users"
184
+ onRowClick={(row) => router.push(`/users/${row.id}`)}
185
+ emptyState={{ icon: Users, title: 'No users', description: 'Get started by inviting team members.' }}
186
+ />
187
+ ```
188
+
189
+ Features: global search, per-column filters (text/enum/number), multi-column sort, pagination, density control (compact/comfortable/spacious), column visibility picker, CSV export, row click handler, loading skeleton, empty state, Framer Motion row animations.
190
+
191
+ ## Publishing
192
+
193
+ ### GitHub Packages
194
+
195
+ ```bash
196
+ npm login --registry=https://npm.pkg.github.com --scope=@annondeveloper
197
+ npm publish
198
+ ```
199
+
200
+ ### npm
201
+
202
+ ```bash
203
+ npm login
204
+ npm publish --access public
205
+ ```
206
+
207
+ ### JSR
208
+
209
+ ```bash
210
+ npx jsr publish
211
+ ```
212
+
213
+ ## License
214
+
215
+ MIT
@@ -0,0 +1,270 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import * as RadixSelect from '@radix-ui/react-select';
5
+ import { Minus, Check, ChevronDown, ToggleRight, ToggleLeft } from 'lucide-react';
6
+ import { forwardRef } from 'react';
7
+
8
+ // src/utils.ts
9
+ function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
12
+ function fmtBps(bps) {
13
+ const v = Math.max(0, bps);
14
+ if (v >= 1e12) return `${(v / 1e12).toFixed(1)} Tbps`;
15
+ if (v >= 1e9) return `${(v / 1e9).toFixed(1)} Gbps`;
16
+ if (v >= 1e6) return `${(v / 1e6).toFixed(1)} Mbps`;
17
+ if (v >= 1e3) return `${(v / 1e3).toFixed(1)} Kbps`;
18
+ return `${v.toFixed(0)} bps`;
19
+ }
20
+ function fmtSpeed(bps) {
21
+ if (bps >= 1e12) return `${(bps / 1e12).toFixed(0)}T`;
22
+ if (bps >= 1e9) return `${(bps / 1e9).toFixed(0)}G`;
23
+ if (bps >= 1e6) return `${(bps / 1e6).toFixed(0)}M`;
24
+ if (bps >= 1e3) return `${(bps / 1e3).toFixed(0)}K`;
25
+ return `${bps.toFixed(0)}`;
26
+ }
27
+ function fmtUtil(bps, speedBps) {
28
+ if (speedBps <= 0) return "\u2014";
29
+ const pct = Math.max(0, bps) / speedBps * 100;
30
+ if (pct < 0.01) return "0%";
31
+ if (pct < 1) return `${pct.toFixed(2)}%`;
32
+ if (pct < 10) return `${pct.toFixed(1)}%`;
33
+ return `${pct.toFixed(0)}%`;
34
+ }
35
+ function fmtBytes(bytes) {
36
+ if (bytes >= 1e12) return `${(bytes / 1e12).toFixed(1)} TB`;
37
+ if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`;
38
+ if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(1)} MB`;
39
+ if (bytes >= 1e3) return `${(bytes / 1e3).toFixed(1)} KB`;
40
+ return `${bytes.toFixed(0)} B`;
41
+ }
42
+ function fmtPct(v, decimals = 1) {
43
+ return `${v.toFixed(decimals)}%`;
44
+ }
45
+ function fmtUptime(secs) {
46
+ if (secs < 60) return `${secs}s`;
47
+ if (secs < 3600) return `${Math.floor(secs / 60)}m`;
48
+ if (secs < 86400) return `${Math.floor(secs / 3600)}h ${Math.floor(secs % 3600 / 60)}m`;
49
+ return `${Math.floor(secs / 86400)}d ${Math.floor(secs % 86400 / 3600)}h`;
50
+ }
51
+ function fmtRelative(isoStr) {
52
+ const diff = (Date.now() - new Date(isoStr).getTime()) / 1e3;
53
+ if (diff < 60) return "just now";
54
+ if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
55
+ if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
56
+ return `${Math.floor(diff / 86400)}d ago`;
57
+ }
58
+ function fmtCompact(n) {
59
+ const abs = Math.abs(n);
60
+ const sign = n < 0 ? "-" : "";
61
+ if (abs >= 1e12) return `${sign}${(abs / 1e12).toFixed(1)}T`;
62
+ if (abs >= 1e9) return `${sign}${(abs / 1e9).toFixed(1)}B`;
63
+ if (abs >= 1e6) return `${sign}${(abs / 1e6).toFixed(1)}M`;
64
+ if (abs >= 1e3) return `${sign}${(abs / 1e3).toFixed(1)}K`;
65
+ if (abs >= 1) return `${sign}${abs.toFixed(0)}`;
66
+ if (abs >= 0.01) return `${sign}${abs.toFixed(2)}`;
67
+ return `${sign}${abs.toFixed(0)}`;
68
+ }
69
+ function fmtDuration(secs) {
70
+ if (secs < 1e-3) return "0s";
71
+ if (secs < 1) return `${(secs * 1e3).toFixed(0)}ms`;
72
+ if (secs < 60) return `${secs.toFixed(1)}s`;
73
+ if (secs < 3600) return `${Math.floor(secs / 60)}m ${Math.floor(secs % 60)}s`;
74
+ return `${Math.floor(secs / 3600)}h ${Math.floor(secs % 3600 / 60)}m`;
75
+ }
76
+ function stripCidr(ip) {
77
+ return ip.replace(/\/\d+$/, "");
78
+ }
79
+ function clamp(v, min, max) {
80
+ return Math.min(Math.max(v, min), max);
81
+ }
82
+ var defaultUtilColorMap = {
83
+ high: { threshold: 80, className: "text-[hsl(var(--status-critical))]" },
84
+ medium: { threshold: 60, className: "text-[hsl(var(--status-warning))]" },
85
+ low: { className: "text-[hsl(var(--status-ok))]" }
86
+ };
87
+ function utilColor(pct, colorMap = defaultUtilColorMap) {
88
+ if (pct >= colorMap.high.threshold) return colorMap.high.className;
89
+ if (pct >= colorMap.medium.threshold) return colorMap.medium.className;
90
+ return colorMap.low.className;
91
+ }
92
+ var INPUT_CLS = cn(
93
+ "w-full rounded-lg border border-[hsl(var(--border-default))]",
94
+ "bg-[hsl(var(--bg-base))] px-3 py-2 text-sm",
95
+ "text-[hsl(var(--text-primary))] placeholder:text-[hsl(var(--text-tertiary))]",
96
+ "focus:outline-none focus:ring-2 focus:ring-[hsl(var(--brand-primary))]",
97
+ "disabled:opacity-50 disabled:cursor-not-allowed"
98
+ );
99
+ var LABEL_CLS = cn(
100
+ "mb-1.5 block text-xs font-medium uppercase tracking-wider",
101
+ "text-[hsl(var(--text-secondary))]"
102
+ );
103
+ var TEXTAREA_CLS = cn(
104
+ INPUT_CLS,
105
+ "resize-none font-mono text-xs leading-relaxed"
106
+ );
107
+ function FormInput({
108
+ label,
109
+ value,
110
+ onChange,
111
+ type = "text",
112
+ placeholder,
113
+ required,
114
+ disabled,
115
+ hint,
116
+ className,
117
+ autoComplete
118
+ }) {
119
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-1.5", className), children: [
120
+ /* @__PURE__ */ jsxs("label", { className: LABEL_CLS, children: [
121
+ label,
122
+ required && /* @__PURE__ */ jsx("span", { className: "text-[hsl(var(--status-critical))] ml-0.5", children: "*" })
123
+ ] }),
124
+ /* @__PURE__ */ jsx(
125
+ "input",
126
+ {
127
+ type,
128
+ value,
129
+ onChange: (e) => onChange(e.target.value),
130
+ placeholder,
131
+ required,
132
+ disabled,
133
+ autoComplete,
134
+ className: INPUT_CLS
135
+ }
136
+ ),
137
+ hint && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-[hsl(var(--text-tertiary))]", children: hint })
138
+ ] });
139
+ }
140
+ function Select({
141
+ value,
142
+ onValueChange,
143
+ options,
144
+ placeholder,
145
+ className,
146
+ disabled
147
+ }) {
148
+ return /* @__PURE__ */ jsxs(RadixSelect.Root, { value, onValueChange, disabled, children: [
149
+ /* @__PURE__ */ jsxs(
150
+ RadixSelect.Trigger,
151
+ {
152
+ suppressHydrationWarning: true,
153
+ className: cn(
154
+ "flex w-full items-center justify-between gap-2 rounded-lg",
155
+ "border border-[hsl(var(--border-default))] bg-[hsl(var(--bg-base))]",
156
+ "px-3 py-2 text-sm text-[hsl(var(--text-primary))]",
157
+ "hover:border-[hsl(var(--border-strong))] focus:outline-none",
158
+ "focus:ring-2 focus:ring-[hsl(var(--brand-primary))]",
159
+ "disabled:opacity-50 disabled:cursor-not-allowed",
160
+ "data-[placeholder]:text-[hsl(var(--text-tertiary))]",
161
+ className
162
+ ),
163
+ children: [
164
+ /* @__PURE__ */ jsx(RadixSelect.Value, { placeholder }),
165
+ /* @__PURE__ */ jsx(RadixSelect.Icon, { children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 text-[hsl(var(--text-tertiary))] shrink-0" }) })
166
+ ]
167
+ }
168
+ ),
169
+ /* @__PURE__ */ jsx(RadixSelect.Portal, { children: /* @__PURE__ */ jsx(
170
+ RadixSelect.Content,
171
+ {
172
+ position: "popper",
173
+ sideOffset: 4,
174
+ className: cn(
175
+ "z-50 min-w-[var(--radix-select-trigger-width)] overflow-hidden",
176
+ "rounded-xl border border-[hsl(var(--border-default))]",
177
+ "bg-[hsl(var(--bg-elevated))] shadow-xl",
178
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
179
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
180
+ ),
181
+ children: /* @__PURE__ */ jsx(RadixSelect.Viewport, { className: "p-1", children: options.map((opt) => /* @__PURE__ */ jsxs(
182
+ RadixSelect.Item,
183
+ {
184
+ value: opt.value,
185
+ className: cn(
186
+ "relative flex cursor-pointer select-none items-center",
187
+ "rounded-lg py-2 pl-8 pr-3 text-sm",
188
+ "text-[hsl(var(--text-primary))]",
189
+ "outline-none",
190
+ "hover:bg-[hsl(var(--bg-overlay))]",
191
+ "focus:bg-[hsl(var(--brand-primary))]/10 focus:text-[hsl(var(--brand-primary))]",
192
+ "data-[state=checked]:text-[hsl(var(--brand-primary))]"
193
+ ),
194
+ children: [
195
+ /* @__PURE__ */ jsx(RadixSelect.ItemIndicator, { className: "absolute left-2 flex items-center", children: /* @__PURE__ */ jsx(Check, { className: "size-4" }) }),
196
+ /* @__PURE__ */ jsx(RadixSelect.ItemText, { children: opt.label })
197
+ ]
198
+ },
199
+ opt.value
200
+ )) })
201
+ }
202
+ ) })
203
+ ] });
204
+ }
205
+ var Checkbox = forwardRef(
206
+ ({ indeterminate, checked, size = "md", className, ...props }, ref) => {
207
+ const isChecked = checked || indeterminate;
208
+ const sizeClass = size === "sm" ? "h-3.5 w-3.5" : "h-4 w-4";
209
+ const iconSize = size === "sm" ? "h-2.5 w-2.5" : "h-3 w-3";
210
+ return /* @__PURE__ */ jsxs("label", { className: cn("relative inline-flex items-center cursor-pointer", className), children: [
211
+ /* @__PURE__ */ jsx(
212
+ "input",
213
+ {
214
+ ref,
215
+ type: "checkbox",
216
+ checked,
217
+ className: "sr-only peer",
218
+ ...props
219
+ }
220
+ ),
221
+ /* @__PURE__ */ jsx(
222
+ "div",
223
+ {
224
+ className: cn(
225
+ sizeClass,
226
+ "rounded border transition-colors duration-150",
227
+ "flex items-center justify-center shrink-0",
228
+ isChecked ? "bg-[hsl(var(--brand-primary))] border-[hsl(var(--brand-primary))]" : "bg-transparent border-[hsl(var(--border-strong))] hover:border-[hsl(var(--brand-primary))]",
229
+ "peer-focus-visible:ring-2 peer-focus-visible:ring-[hsl(var(--brand-primary))] peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-[hsl(var(--bg-base))]",
230
+ "peer-disabled:opacity-50 peer-disabled:pointer-events-none"
231
+ ),
232
+ children: indeterminate ? /* @__PURE__ */ jsx(Minus, { className: cn(iconSize, "text-[hsl(var(--text-on-brand))] stroke-[3]") }) : checked ? /* @__PURE__ */ jsx(Check, { className: cn(iconSize, "text-[hsl(var(--text-on-brand))] stroke-[3]") }) : null
233
+ }
234
+ )
235
+ ] });
236
+ }
237
+ );
238
+ Checkbox.displayName = "Checkbox";
239
+ function ToggleSwitch({
240
+ enabled,
241
+ onChange,
242
+ size = "md",
243
+ disabled,
244
+ label,
245
+ className
246
+ }) {
247
+ const iconSize = size === "sm" ? "size-5" : "size-6";
248
+ return /* @__PURE__ */ jsx(
249
+ "button",
250
+ {
251
+ type: "button",
252
+ role: "switch",
253
+ "aria-checked": enabled,
254
+ "aria-label": label,
255
+ disabled,
256
+ onClick: () => onChange(!enabled),
257
+ className: cn(
258
+ "inline-flex items-center transition-colors",
259
+ "text-[hsl(var(--text-secondary))] hover:text-[hsl(var(--text-primary))]",
260
+ "disabled:opacity-50 disabled:cursor-not-allowed",
261
+ className
262
+ ),
263
+ children: enabled ? /* @__PURE__ */ jsx(ToggleRight, { className: cn(iconSize, "text-[hsl(var(--status-ok))]") }) : /* @__PURE__ */ jsx(ToggleLeft, { className: cn(iconSize, "text-[hsl(var(--text-tertiary))]") })
264
+ }
265
+ );
266
+ }
267
+
268
+ export { Checkbox, FormInput, INPUT_CLS, LABEL_CLS, Select, TEXTAREA_CLS, ToggleSwitch, clamp, cn, defaultUtilColorMap, fmtBps, fmtBytes, fmtCompact, fmtDuration, fmtPct, fmtRelative, fmtSpeed, fmtUptime, fmtUtil, stripCidr, utilColor };
269
+ //# sourceMappingURL=chunk-5OKSXPWK.js.map
270
+ //# sourceMappingURL=chunk-5OKSXPWK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/components/form-input.tsx","../src/components/select.tsx","../src/components/checkbox.tsx","../src/components/toggle-switch.tsx"],"names":["jsxs","jsx","Check"],"mappings":"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","file":"chunk-5OKSXPWK.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\n/** Merge Tailwind CSS class names with conflict resolution. */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\n/** Format bits/sec into human-readable string (clamps negative to 0). */\nexport function fmtBps(bps: number): string {\n const v = Math.max(0, bps)\n if (v >= 1e12) return `${(v / 1e12).toFixed(1)} Tbps`\n if (v >= 1e9) return `${(v / 1e9).toFixed(1)} Gbps`\n if (v >= 1e6) return `${(v / 1e6).toFixed(1)} Mbps`\n if (v >= 1e3) return `${(v / 1e3).toFixed(1)} Kbps`\n return `${v.toFixed(0)} bps`\n}\n\n/** Format link speed for interface display (e.g. 1000000000 -> \"1G\"). */\nexport function fmtSpeed(bps: number): string {\n if (bps >= 1e12) return `${(bps / 1e12).toFixed(0)}T`\n if (bps >= 1e9) return `${(bps / 1e9).toFixed(0)}G`\n if (bps >= 1e6) return `${(bps / 1e6).toFixed(0)}M`\n if (bps >= 1e3) return `${(bps / 1e3).toFixed(0)}K`\n return `${bps.toFixed(0)}`\n}\n\n/** Format utilization as percentage of link speed. */\nexport function fmtUtil(bps: number, speedBps: number): string {\n if (speedBps <= 0) return '\\u2014'\n const pct = (Math.max(0, bps) / speedBps) * 100\n if (pct < 0.01) return '0%'\n if (pct < 1) return `${pct.toFixed(2)}%`\n if (pct < 10) return `${pct.toFixed(1)}%`\n return `${pct.toFixed(0)}%`\n}\n\n/** Format bytes into human-readable string. */\nexport function fmtBytes(bytes: number): string {\n if (bytes >= 1e12) return `${(bytes / 1e12).toFixed(1)} TB`\n if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`\n if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(1)} MB`\n if (bytes >= 1e3) return `${(bytes / 1e3).toFixed(1)} KB`\n return `${bytes.toFixed(0)} B`\n}\n\n/** Format percent. */\nexport function fmtPct(v: number, decimals = 1): string {\n return `${v.toFixed(decimals)}%`\n}\n\n/** Format duration in seconds to human-readable uptime string. */\nexport function fmtUptime(secs: number): string {\n if (secs < 60) return `${secs}s`\n if (secs < 3600) return `${Math.floor(secs / 60)}m`\n if (secs < 86400) return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`\n return `${Math.floor(secs / 86400)}d ${Math.floor((secs % 86400) / 3600)}h`\n}\n\n/** Format ISO timestamp to relative time (\"3m ago\"). */\nexport function fmtRelative(isoStr: string): string {\n const diff = (Date.now() - new Date(isoStr).getTime()) / 1000\n if (diff < 60) return 'just now'\n if (diff < 3600) return `${Math.floor(diff / 60)}m ago`\n if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`\n return `${Math.floor(diff / 86400)}d ago`\n}\n\n/** Format large numbers compactly (e.g. 480933305 -> \"480.9M\", 1234 -> \"1.2K\"). */\nexport function fmtCompact(n: number): string {\n const abs = Math.abs(n)\n const sign = n < 0 ? '-' : ''\n if (abs >= 1e12) return `${sign}${(abs / 1e12).toFixed(1)}T`\n if (abs >= 1e9) return `${sign}${(abs / 1e9).toFixed(1)}B`\n if (abs >= 1e6) return `${sign}${(abs / 1e6).toFixed(1)}M`\n if (abs >= 1e3) return `${sign}${(abs / 1e3).toFixed(1)}K`\n if (abs >= 1) return `${sign}${abs.toFixed(0)}`\n if (abs >= 0.01) return `${sign}${abs.toFixed(2)}`\n return `${sign}${abs.toFixed(0)}`\n}\n\n/** Format seconds duration compactly (e.g. for replication lag display). */\nexport function fmtDuration(secs: number): string {\n if (secs < 0.001) return '0s'\n if (secs < 1) return `${(secs * 1000).toFixed(0)}ms`\n if (secs < 60) return `${secs.toFixed(1)}s`\n if (secs < 3600) return `${Math.floor(secs / 60)}m ${Math.floor(secs % 60)}s`\n return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`\n}\n\n/** Strip CIDR suffix from IP address (e.g. \"10.0.0.1/32\" -> \"10.0.0.1\"). */\nexport function stripCidr(ip: string): string {\n return ip.replace(/\\/\\d+$/, '')\n}\n\n/** Clamp a number between min and max. */\nexport function clamp(v: number, min: number, max: number): number {\n return Math.min(Math.max(v, min), max)\n}\n\n/** Color thresholds for utilization percentage mapping. */\nexport interface UtilColorMap {\n high: { threshold: number; className: string }\n medium: { threshold: number; className: string }\n low: { className: string }\n}\n\n/** Default utilization color map using CSS custom property token classes. */\nexport const defaultUtilColorMap: UtilColorMap = {\n high: { threshold: 80, className: 'text-[hsl(var(--status-critical))]' },\n medium: { threshold: 60, className: 'text-[hsl(var(--status-warning))]' },\n low: { className: 'text-[hsl(var(--status-ok))]' },\n}\n\n/**\n * Get utilization color class from value 0-100.\n * Accepts an optional color map to override the default thresholds and classes.\n */\nexport function utilColor(pct: number, colorMap: UtilColorMap = defaultUtilColorMap): string {\n if (pct >= colorMap.high.threshold) return colorMap.high.className\n if (pct >= colorMap.medium.threshold) return colorMap.medium.className\n return colorMap.low.className\n}\n","'use client'\n\nimport { cn } from '../utils'\n\n// ── Shared class constants ──────────────────────────────────────────────────\n// Import these in any page that needs raw class strings (e.g. for <textarea>)\n\nexport const INPUT_CLS = cn(\n 'w-full rounded-lg border border-[hsl(var(--border-default))]',\n 'bg-[hsl(var(--bg-base))] px-3 py-2 text-sm',\n 'text-[hsl(var(--text-primary))] placeholder:text-[hsl(var(--text-tertiary))]',\n 'focus:outline-none focus:ring-2 focus:ring-[hsl(var(--brand-primary))]',\n 'disabled:opacity-50 disabled:cursor-not-allowed',\n)\n\nexport const LABEL_CLS = cn(\n 'mb-1.5 block text-xs font-medium uppercase tracking-wider',\n 'text-[hsl(var(--text-secondary))]',\n)\n\nexport const TEXTAREA_CLS = cn(\n INPUT_CLS,\n 'resize-none font-mono text-xs leading-relaxed',\n)\n\nexport interface FormInputProps {\n /** Label text displayed above the input. */\n label: string\n /** Current input value. */\n value: string\n /** Callback when value changes. */\n onChange: (value: string) => void\n /** HTML input type. */\n type?: string\n /** Placeholder text. */\n placeholder?: string\n /** Mark the field as required. */\n required?: boolean\n /** Disable the input. */\n disabled?: boolean\n /** Help text shown below the input. */\n hint?: string\n className?: string\n /** HTML autocomplete attribute. */\n autoComplete?: string\n}\n\n/**\n * @description A themed form input with label, validation indicator, and optional hint text.\n * Uses CSS custom property tokens for dark/light mode compatibility.\n */\nexport function FormInput({\n label, value, onChange, type = 'text',\n placeholder, required, disabled, hint, className, autoComplete,\n}: FormInputProps) {\n return (\n <div className={cn('space-y-1.5', className)}>\n <label className={LABEL_CLS}>\n {label}\n {required && <span className=\"text-[hsl(var(--status-critical))] ml-0.5\">*</span>}\n </label>\n <input\n type={type}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n required={required}\n disabled={disabled}\n autoComplete={autoComplete}\n className={INPUT_CLS}\n />\n {hint && (\n <p className=\"text-[10px] text-[hsl(var(--text-tertiary))]\">{hint}</p>\n )}\n </div>\n )\n}\n","'use client'\n\nimport * as RadixSelect from '@radix-ui/react-select'\nimport { ChevronDown, Check } from 'lucide-react'\nimport { cn } from '../utils'\n\nexport interface SelectOption {\n value: string\n label: string\n}\n\nexport interface SelectProps {\n /** Currently selected value. */\n value: string\n /** Callback when selection changes. */\n onValueChange: (v: string) => void\n /** Available options. */\n options: SelectOption[]\n /** Placeholder text when no value is selected. */\n placeholder?: string\n className?: string\n /** Disable the select. */\n disabled?: boolean\n}\n\n/**\n * @description A themed select dropdown built on Radix UI Select.\n * Supports dark/light mode via CSS custom property tokens.\n */\nexport function Select({\n value, onValueChange, options, placeholder, className, disabled,\n}: SelectProps) {\n return (\n <RadixSelect.Root value={value} onValueChange={onValueChange} disabled={disabled}>\n <RadixSelect.Trigger\n suppressHydrationWarning\n className={cn(\n 'flex w-full items-center justify-between gap-2 rounded-lg',\n 'border border-[hsl(var(--border-default))] bg-[hsl(var(--bg-base))]',\n 'px-3 py-2 text-sm text-[hsl(var(--text-primary))]',\n 'hover:border-[hsl(var(--border-strong))] focus:outline-none',\n 'focus:ring-2 focus:ring-[hsl(var(--brand-primary))]',\n 'disabled:opacity-50 disabled:cursor-not-allowed',\n 'data-[placeholder]:text-[hsl(var(--text-tertiary))]',\n className,\n )}\n >\n <RadixSelect.Value placeholder={placeholder} />\n <RadixSelect.Icon>\n <ChevronDown className=\"size-4 text-[hsl(var(--text-tertiary))] shrink-0\" />\n </RadixSelect.Icon>\n </RadixSelect.Trigger>\n\n <RadixSelect.Portal>\n <RadixSelect.Content\n position=\"popper\"\n sideOffset={4}\n className={cn(\n 'z-50 min-w-[var(--radix-select-trigger-width)] overflow-hidden',\n 'rounded-xl border border-[hsl(var(--border-default))]',\n 'bg-[hsl(var(--bg-elevated))] shadow-xl',\n 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',\n 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',\n )}\n >\n <RadixSelect.Viewport className=\"p-1\">\n {options.map((opt) => (\n <RadixSelect.Item\n key={opt.value}\n value={opt.value}\n className={cn(\n 'relative flex cursor-pointer select-none items-center',\n 'rounded-lg py-2 pl-8 pr-3 text-sm',\n 'text-[hsl(var(--text-primary))]',\n 'outline-none',\n 'hover:bg-[hsl(var(--bg-overlay))]',\n 'focus:bg-[hsl(var(--brand-primary))]/10 focus:text-[hsl(var(--brand-primary))]',\n 'data-[state=checked]:text-[hsl(var(--brand-primary))]',\n )}\n >\n <RadixSelect.ItemIndicator className=\"absolute left-2 flex items-center\">\n <Check className=\"size-4\" />\n </RadixSelect.ItemIndicator>\n <RadixSelect.ItemText>{opt.label}</RadixSelect.ItemText>\n </RadixSelect.Item>\n ))}\n </RadixSelect.Viewport>\n </RadixSelect.Content>\n </RadixSelect.Portal>\n </RadixSelect.Root>\n )\n}\n","'use client'\n\nimport { forwardRef, type InputHTMLAttributes } from 'react'\nimport { Check, Minus } from 'lucide-react'\nimport { cn } from '../utils'\n\nexport interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {\n /** Show an indeterminate (minus) indicator instead of a checkmark. */\n indeterminate?: boolean\n /** Size variant. */\n size?: 'sm' | 'md'\n}\n\n/**\n * @description A themed checkbox with indeterminate state support.\n * Uses CSS custom property tokens for dark/light mode compatibility.\n */\nconst Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(\n ({ indeterminate, checked, size = 'md', className, ...props }, ref) => {\n const isChecked = checked || indeterminate\n const sizeClass = size === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4'\n const iconSize = size === 'sm' ? 'h-2.5 w-2.5' : 'h-3 w-3'\n\n return (\n <label className={cn('relative inline-flex items-center cursor-pointer', className)}>\n <input\n ref={ref}\n type=\"checkbox\"\n checked={checked}\n className=\"sr-only peer\"\n {...props}\n />\n <div\n className={cn(\n sizeClass,\n 'rounded border transition-colors duration-150',\n 'flex items-center justify-center shrink-0',\n isChecked\n ? 'bg-[hsl(var(--brand-primary))] border-[hsl(var(--brand-primary))]'\n : 'bg-transparent border-[hsl(var(--border-strong))] hover:border-[hsl(var(--brand-primary))]',\n 'peer-focus-visible:ring-2 peer-focus-visible:ring-[hsl(var(--brand-primary))] peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-[hsl(var(--bg-base))]',\n 'peer-disabled:opacity-50 peer-disabled:pointer-events-none',\n )}\n >\n {indeterminate ? (\n <Minus className={cn(iconSize, 'text-[hsl(var(--text-on-brand))] stroke-[3]')} />\n ) : checked ? (\n <Check className={cn(iconSize, 'text-[hsl(var(--text-on-brand))] stroke-[3]')} />\n ) : null}\n </div>\n </label>\n )\n },\n)\n\nCheckbox.displayName = 'Checkbox'\n\nexport { Checkbox }\n","'use client'\n\nimport { ToggleLeft, ToggleRight } from 'lucide-react'\nimport { cn } from '../utils'\n\nexport interface ToggleSwitchProps {\n /** Whether the toggle is on. */\n enabled: boolean\n /** Callback when toggled. */\n onChange: (enabled: boolean) => void\n /** Size variant. */\n size?: 'sm' | 'md'\n /** Disable the toggle. */\n disabled?: boolean\n /** Accessible label. */\n label?: string\n className?: string\n}\n\n/**\n * @description A themed toggle switch using lucide icons for on/off states.\n * Supports dark/light mode via CSS custom property tokens.\n */\nexport function ToggleSwitch({\n enabled, onChange, size = 'md', disabled, label, className,\n}: ToggleSwitchProps) {\n const iconSize = size === 'sm' ? 'size-5' : 'size-6'\n\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={enabled}\n aria-label={label}\n disabled={disabled}\n onClick={() => onChange(!enabled)}\n className={cn(\n 'inline-flex items-center transition-colors',\n 'text-[hsl(var(--text-secondary))] hover:text-[hsl(var(--text-primary))]',\n 'disabled:opacity-50 disabled:cursor-not-allowed',\n className,\n )}\n >\n {enabled ? (\n <ToggleRight className={cn(iconSize, 'text-[hsl(var(--status-ok))]')} />\n ) : (\n <ToggleLeft className={cn(iconSize, 'text-[hsl(var(--text-tertiary))]')} />\n )}\n </button>\n )\n}\n"]}