@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.
- package/LICENSE +21 -0
- package/README.md +215 -0
- package/dist/chunk-5OKSXPWK.js +270 -0
- package/dist/chunk-5OKSXPWK.js.map +1 -0
- package/dist/cli/index.js +430 -0
- package/dist/form.d.ts +65 -0
- package/dist/form.js +148 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +942 -0
- package/dist/index.js +2812 -0
- package/dist/index.js.map +1 -0
- package/dist/select-nnBJUO8U.d.ts +26 -0
- package/package.json +114 -0
- package/src/components/animated-counter.stories.tsx +68 -0
- package/src/components/animated-counter.tsx +85 -0
- package/src/components/avatar.tsx +106 -0
- package/src/components/badge.stories.tsx +70 -0
- package/src/components/badge.tsx +97 -0
- package/src/components/button.stories.tsx +101 -0
- package/src/components/button.tsx +67 -0
- package/src/components/card.tsx +128 -0
- package/src/components/checkbox.stories.tsx +64 -0
- package/src/components/checkbox.tsx +58 -0
- package/src/components/confirm-dialog.stories.tsx +96 -0
- package/src/components/confirm-dialog.tsx +145 -0
- package/src/components/data-table.stories.tsx +125 -0
- package/src/components/data-table.tsx +791 -0
- package/src/components/dropdown-menu.tsx +111 -0
- package/src/components/empty-state.stories.tsx +42 -0
- package/src/components/empty-state.tsx +43 -0
- package/src/components/filter-pill.stories.tsx +71 -0
- package/src/components/filter-pill.tsx +45 -0
- package/src/components/form-input.stories.tsx +91 -0
- package/src/components/form-input.tsx +77 -0
- package/src/components/log-viewer.tsx +212 -0
- package/src/components/metric-card.tsx +141 -0
- package/src/components/pipeline-stage.tsx +134 -0
- package/src/components/popover.tsx +72 -0
- package/src/components/port-status-grid.tsx +102 -0
- package/src/components/progress.tsx +128 -0
- package/src/components/radio-group.tsx +162 -0
- package/src/components/select.stories.tsx +52 -0
- package/src/components/select.tsx +92 -0
- package/src/components/severity-timeline.tsx +125 -0
- package/src/components/sheet.tsx +164 -0
- package/src/components/skeleton.stories.tsx +64 -0
- package/src/components/skeleton.tsx +62 -0
- package/src/components/slider.tsx +208 -0
- package/src/components/sparkline.tsx +104 -0
- package/src/components/status-badge.stories.tsx +84 -0
- package/src/components/status-badge.tsx +71 -0
- package/src/components/status-pulse.stories.tsx +56 -0
- package/src/components/status-pulse.tsx +78 -0
- package/src/components/success-checkmark.stories.tsx +67 -0
- package/src/components/success-checkmark.tsx +53 -0
- package/src/components/tabs.tsx +177 -0
- package/src/components/threshold-gauge.tsx +149 -0
- package/src/components/time-range-selector.tsx +86 -0
- package/src/components/toast.stories.tsx +70 -0
- package/src/components/toast.tsx +48 -0
- package/src/components/toggle-switch.stories.tsx +66 -0
- package/src/components/toggle-switch.tsx +51 -0
- package/src/components/tooltip.tsx +62 -0
- package/src/components/truncated-text.stories.tsx +56 -0
- package/src/components/truncated-text.tsx +80 -0
- package/src/components/uptime-tracker.tsx +138 -0
- package/src/components/utilization-bar.tsx +103 -0
- package/src/theme.css +178 -0
- 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"]}
|