@prmichaelsen/agentbase-ux 0.0.2

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 (61) hide show
  1. package/README.md +62 -0
  2. package/dist/__tests__/setup.d.ts +2 -0
  3. package/dist/__tests__/setup.d.ts.map +1 -0
  4. package/dist/__tests__/test-utils.d.ts +6 -0
  5. package/dist/__tests__/test-utils.d.ts.map +1 -0
  6. package/dist/components/Button.d.ts +19 -0
  7. package/dist/components/Button.d.ts.map +1 -0
  8. package/dist/components/Button.test.d.ts +2 -0
  9. package/dist/components/Button.test.d.ts.map +1 -0
  10. package/dist/components/ConfirmationModal.d.ts +17 -0
  11. package/dist/components/ConfirmationModal.d.ts.map +1 -0
  12. package/dist/components/ConfirmationModal.test.d.ts +2 -0
  13. package/dist/components/ConfirmationModal.test.d.ts.map +1 -0
  14. package/dist/components/MenuItem.d.ts +17 -0
  15. package/dist/components/MenuItem.d.ts.map +1 -0
  16. package/dist/components/MenuItem.test.d.ts +2 -0
  17. package/dist/components/MenuItem.test.d.ts.map +1 -0
  18. package/dist/components/Modal.d.ts +18 -0
  19. package/dist/components/Modal.d.ts.map +1 -0
  20. package/dist/components/Modal.test.d.ts +2 -0
  21. package/dist/components/Modal.test.d.ts.map +1 -0
  22. package/dist/components/Paginator.d.ts +14 -0
  23. package/dist/components/Paginator.d.ts.map +1 -0
  24. package/dist/components/Paginator.test.d.ts +2 -0
  25. package/dist/components/Paginator.test.d.ts.map +1 -0
  26. package/dist/components/Popover.d.ts +18 -0
  27. package/dist/components/Popover.d.ts.map +1 -0
  28. package/dist/components/SlideOverPanel.d.ts +12 -0
  29. package/dist/components/SlideOverPanel.d.ts.map +1 -0
  30. package/dist/components/SlideOverPanel.test.d.ts +2 -0
  31. package/dist/components/SlideOverPanel.test.d.ts.map +1 -0
  32. package/dist/components/Slider.d.ts +36 -0
  33. package/dist/components/Slider.d.ts.map +1 -0
  34. package/dist/components/Slider.test.d.ts +2 -0
  35. package/dist/components/Slider.test.d.ts.map +1 -0
  36. package/dist/components/ToggleSwitch.d.ts +17 -0
  37. package/dist/components/ToggleSwitch.d.ts.map +1 -0
  38. package/dist/components/ToggleSwitch.test.d.ts +2 -0
  39. package/dist/components/ToggleSwitch.test.d.ts.map +1 -0
  40. package/dist/hooks/useDebounce.d.ts +2 -0
  41. package/dist/hooks/useDebounce.d.ts.map +1 -0
  42. package/dist/hooks/useDebounce.test.d.ts +2 -0
  43. package/dist/hooks/useDebounce.test.d.ts.map +1 -0
  44. package/dist/index.d.ts +18 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +746 -0
  47. package/dist/index.js.map +7 -0
  48. package/dist/lib/theming.d.ts +61 -0
  49. package/dist/lib/theming.d.ts.map +1 -0
  50. package/dist/lib/theming.test.d.ts +2 -0
  51. package/dist/lib/theming.test.d.ts.map +1 -0
  52. package/dist/tokens.css +88 -0
  53. package/dist/utils/clipboard.d.ts +9 -0
  54. package/dist/utils/clipboard.d.ts.map +1 -0
  55. package/dist/utils/clipboard.test.d.ts +2 -0
  56. package/dist/utils/clipboard.test.d.ts.map +1 -0
  57. package/dist/utils/format-time.d.ts +6 -0
  58. package/dist/utils/format-time.d.ts.map +1 -0
  59. package/dist/utils/format-time.test.d.ts +2 -0
  60. package/dist/utils/format-time.test.d.ts.map +1 -0
  61. package/package.json +43 -0
package/dist/index.js ADDED
@@ -0,0 +1,746 @@
1
+ // src/lib/theming.tsx
2
+ import { createContext, useContext } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var darkTheme = {
5
+ // Layout
6
+ page: "bg-bg-page text-text-primary",
7
+ sidebar: "bg-bg-sidebar border-r border-border-default",
8
+ card: "bg-bg-card border border-border-default rounded-lg",
9
+ elevated: "bg-bg-elevated",
10
+ hover: "hover:bg-bg-hover",
11
+ active: "bg-bg-active",
12
+ border: "border border-border-default",
13
+ borderSubtle: "border border-border-subtle",
14
+ input: "bg-bg-input border border-border-default text-text-primary placeholder:text-text-muted",
15
+ inputFocus: "focus:border-brand-primary focus:ring-1 focus:ring-brand-primary/30",
16
+ // Text
17
+ textPrimary: "text-text-primary",
18
+ textSecondary: "text-text-secondary",
19
+ textMuted: "text-text-muted",
20
+ // Buttons
21
+ buttonPrimary: "bg-brand-primary text-white hover:bg-brand-primary/90",
22
+ buttonSecondary: "bg-brand-secondary text-white hover:bg-brand-secondary/90",
23
+ buttonGhost: "bg-transparent text-text-secondary hover:bg-bg-hover hover:text-text-primary",
24
+ buttonDanger: "bg-brand-danger text-white hover:bg-brand-danger/90",
25
+ buttonSuccess: "bg-brand-success text-white hover:bg-brand-success/90",
26
+ buttonWarning: "bg-brand-warning text-white hover:bg-brand-warning/90",
27
+ // Overlay
28
+ overlay: "bg-black/50 backdrop-blur-sm",
29
+ // Toggle
30
+ toggleOn: "bg-brand-primary",
31
+ toggleOff: "bg-bg-elevated",
32
+ // Slider
33
+ sliderTrack: "bg-bg-elevated",
34
+ sliderFill: "bg-brand-primary",
35
+ // Tabs
36
+ tabActive: "bg-brand-primary text-white",
37
+ tabInactive: "text-text-secondary hover:bg-bg-hover hover:text-text-primary",
38
+ // Menu
39
+ menuItem: "text-text-secondary hover:text-text-primary hover:bg-bg-hover",
40
+ menuItemHover: "bg-bg-hover text-text-primary",
41
+ menuItemDanger: "text-brand-danger hover:bg-brand-danger/10",
42
+ // Messages
43
+ messageSelf: "bg-brand-primary/20 border-l-2 border-brand-primary/50",
44
+ messageOther: "bg-bg-card",
45
+ messageAgent: "bg-brand-accent/10 border-l-2 border-brand-accent/50",
46
+ messageSystem: "bg-bg-elevated text-text-muted text-sm italic",
47
+ // Status
48
+ statusOnline: "bg-brand-success",
49
+ statusOffline: "bg-text-muted",
50
+ statusAway: "bg-brand-warning",
51
+ // Badges
52
+ badgeDefault: "bg-bg-elevated text-text-secondary",
53
+ badgeSuccess: "bg-brand-success/15 text-brand-success",
54
+ badgeWarning: "bg-brand-warning/15 text-brand-warning",
55
+ badgeDanger: "bg-brand-danger/15 text-brand-danger",
56
+ badgeInfo: "bg-brand-info/15 text-brand-info",
57
+ // Notifications
58
+ notificationBadge: "bg-brand-danger text-white",
59
+ notificationUnread: "bg-brand-primary/5",
60
+ notificationRead: "bg-bg-card"
61
+ };
62
+ var lightTheme = {
63
+ // Layout
64
+ page: "bg-bg-page text-text-primary",
65
+ sidebar: "bg-bg-sidebar border-r border-border-default",
66
+ card: "bg-bg-card border border-border-default rounded-lg shadow-sm",
67
+ elevated: "bg-bg-elevated",
68
+ hover: "hover:bg-bg-hover",
69
+ active: "bg-bg-active",
70
+ border: "border border-border-default",
71
+ borderSubtle: "border border-border-subtle",
72
+ input: "bg-bg-input border border-border-default text-text-primary placeholder:text-text-muted",
73
+ inputFocus: "focus:border-brand-primary focus:ring-1 focus:ring-brand-primary/30",
74
+ // Text
75
+ textPrimary: "text-text-primary",
76
+ textSecondary: "text-text-secondary",
77
+ textMuted: "text-text-muted",
78
+ // Buttons
79
+ buttonPrimary: "bg-brand-primary text-white hover:bg-brand-primary/90",
80
+ buttonSecondary: "bg-brand-secondary text-white hover:bg-brand-secondary/90",
81
+ buttonGhost: "bg-transparent text-text-secondary hover:bg-bg-hover hover:text-text-primary",
82
+ buttonDanger: "bg-brand-danger text-white hover:bg-brand-danger/90",
83
+ buttonSuccess: "bg-brand-success text-white hover:bg-brand-success/90",
84
+ buttonWarning: "bg-brand-warning text-white hover:bg-brand-warning/90",
85
+ // Overlay
86
+ overlay: "bg-black/40 backdrop-blur-sm",
87
+ // Toggle
88
+ toggleOn: "bg-brand-primary",
89
+ toggleOff: "bg-border-default",
90
+ // Slider
91
+ sliderTrack: "bg-bg-elevated",
92
+ sliderFill: "bg-brand-primary",
93
+ // Tabs
94
+ tabActive: "bg-brand-primary text-white",
95
+ tabInactive: "text-text-secondary hover:bg-bg-hover hover:text-text-primary",
96
+ // Menu
97
+ menuItem: "text-text-secondary hover:text-text-primary hover:bg-bg-hover",
98
+ menuItemHover: "bg-bg-hover text-text-primary",
99
+ menuItemDanger: "text-brand-danger hover:bg-brand-danger/10",
100
+ // Messages
101
+ messageSelf: "bg-brand-primary/10 border-l-2 border-brand-primary/40",
102
+ messageOther: "bg-bg-card",
103
+ messageAgent: "bg-brand-accent/5 border-l-2 border-brand-accent/40",
104
+ messageSystem: "bg-bg-elevated text-text-muted text-sm italic",
105
+ // Status
106
+ statusOnline: "bg-brand-success",
107
+ statusOffline: "bg-text-muted",
108
+ statusAway: "bg-brand-warning",
109
+ // Badges
110
+ badgeDefault: "bg-bg-elevated text-text-secondary",
111
+ badgeSuccess: "bg-brand-success/15 text-brand-success",
112
+ badgeWarning: "bg-brand-warning/15 text-brand-warning",
113
+ badgeDanger: "bg-brand-danger/15 text-brand-danger",
114
+ badgeInfo: "bg-brand-info/15 text-brand-info",
115
+ // Notifications
116
+ notificationBadge: "bg-brand-danger text-white",
117
+ notificationUnread: "bg-brand-primary/5",
118
+ notificationRead: "bg-bg-card"
119
+ };
120
+ var themes = { dark: darkTheme, light: lightTheme };
121
+ var ThemingContext = createContext(darkTheme);
122
+ function ThemingProvider({
123
+ theme = "dark",
124
+ children
125
+ }) {
126
+ return /* @__PURE__ */ jsx(ThemingContext.Provider, { value: themes[theme], children: /* @__PURE__ */ jsx("div", { "data-theme": theme, className: themes[theme].page, children }) });
127
+ }
128
+ function useTheme() {
129
+ return useContext(ThemingContext);
130
+ }
131
+
132
+ // src/components/Button.tsx
133
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
134
+ var sizeStyles = {
135
+ sm: "px-3 py-1.5 text-sm",
136
+ md: "px-4 py-2 text-base",
137
+ lg: "px-6 py-3 text-lg"
138
+ };
139
+ function Button({
140
+ variant = "primary",
141
+ size = "md",
142
+ icon,
143
+ children,
144
+ fullWidth = false,
145
+ className = "",
146
+ disabled = false,
147
+ ...props
148
+ }) {
149
+ const t = useTheme();
150
+ const variantStyles = {
151
+ primary: t.buttonPrimary,
152
+ secondary: t.buttonSecondary,
153
+ danger: t.buttonDanger,
154
+ success: t.buttonSuccess,
155
+ ghost: t.buttonGhost
156
+ };
157
+ const combinedClassName = [
158
+ "font-medium rounded-lg transition-all flex items-center justify-center gap-2",
159
+ variantStyles[variant],
160
+ sizeStyles[size],
161
+ fullWidth ? "w-full" : "",
162
+ disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
163
+ className
164
+ ].filter(Boolean).join(" ");
165
+ return /* @__PURE__ */ jsxs("button", { className: combinedClassName, disabled, ...props, children: [
166
+ icon && /* @__PURE__ */ jsx2("span", { className: "flex-shrink-0", children: icon }),
167
+ children
168
+ ] });
169
+ }
170
+
171
+ // src/components/Modal.tsx
172
+ import { useEffect } from "react";
173
+ import { createPortal } from "react-dom";
174
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
175
+ var maxWidthClasses = {
176
+ sm: "max-w-sm",
177
+ md: "max-w-md",
178
+ lg: "max-w-lg",
179
+ xl: "max-w-xl",
180
+ "2xl": "max-w-2xl"
181
+ };
182
+ function Modal({
183
+ isOpen,
184
+ onClose,
185
+ children,
186
+ title,
187
+ style,
188
+ maxWidth = "md",
189
+ persistent = false
190
+ }) {
191
+ const t = useTheme();
192
+ useEffect(() => {
193
+ if (persistent) return;
194
+ const handleEscapeKey = (event) => {
195
+ if (event.key === "Escape" && isOpen) onClose();
196
+ };
197
+ document.addEventListener("keydown", handleEscapeKey);
198
+ if (isOpen) {
199
+ document.body.style.overflow = "hidden";
200
+ } else {
201
+ document.body.style.overflow = "";
202
+ }
203
+ return () => {
204
+ document.removeEventListener("keydown", handleEscapeKey);
205
+ document.body.style.overflow = "";
206
+ };
207
+ }, [isOpen, onClose, persistent]);
208
+ const handleBackdropClick = (e) => {
209
+ if (e.target === e.currentTarget) onClose();
210
+ };
211
+ if (!isOpen) return null;
212
+ if (typeof document === "undefined") return null;
213
+ return createPortal(
214
+ /* @__PURE__ */ jsx3(
215
+ "div",
216
+ {
217
+ className: `fixed inset-0 z-[55] flex items-center justify-center overflow-hidden ${t.overlay}`,
218
+ onClick: persistent ? void 0 : handleBackdropClick,
219
+ style: { width: "100vw", height: "100vh", top: 0, left: 0, margin: 0, paddingTop: "env(safe-area-inset-top)" },
220
+ children: /* @__PURE__ */ jsxs2(
221
+ "div",
222
+ {
223
+ className: `relative max-h-[90vh] w-full ${maxWidthClasses[maxWidth]} overflow-auto rounded-xl ${t.card} p-6 shadow-xl m-4`,
224
+ onClick: (e) => e.stopPropagation(),
225
+ style: { margin: "1rem", ...style || {} },
226
+ children: [
227
+ !persistent && /* @__PURE__ */ jsx3(
228
+ "button",
229
+ {
230
+ type: "button",
231
+ className: `absolute top-4 right-4 z-10 inline-flex h-8 w-8 items-center justify-center rounded-full ${t.textMuted} ${t.hover} transition-colors`,
232
+ onClick: onClose,
233
+ "aria-label": "Close modal",
234
+ children: /* @__PURE__ */ jsx3("svg", { className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
235
+ }
236
+ ),
237
+ title && /* @__PURE__ */ jsx3("div", { className: "mb-4 pr-8", children: /* @__PURE__ */ jsx3("h3", { className: `text-xl font-bold ${t.textPrimary}`, children: title }) }),
238
+ /* @__PURE__ */ jsx3("div", { children })
239
+ ]
240
+ }
241
+ )
242
+ }
243
+ ),
244
+ document.body
245
+ );
246
+ }
247
+
248
+ // src/components/ConfirmationModal.tsx
249
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
250
+ var iconPaths = {
251
+ danger: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z",
252
+ warning: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z",
253
+ info: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
254
+ };
255
+ function ConfirmationModal({
256
+ isOpen,
257
+ onClose,
258
+ onConfirm,
259
+ title,
260
+ message,
261
+ confirmText = "Confirm",
262
+ cancelText = "Cancel",
263
+ variant = "danger",
264
+ isLoading = false
265
+ }) {
266
+ const t = useTheme();
267
+ const variantStyles = {
268
+ danger: { icon: "bg-brand-danger", button: t.buttonDanger },
269
+ warning: { icon: "bg-brand-warning", button: t.buttonWarning },
270
+ info: { icon: "bg-brand-info", button: t.buttonSecondary }
271
+ };
272
+ const styles = variantStyles[variant];
273
+ return /* @__PURE__ */ jsx4(Modal, { isOpen, onClose: isLoading ? () => {
274
+ } : onClose, children: /* @__PURE__ */ jsxs3("div", { className: "w-full", children: [
275
+ /* @__PURE__ */ jsx4("div", { className: "mb-6 flex justify-center", children: /* @__PURE__ */ jsx4("div", { className: `flex h-16 w-16 items-center justify-center rounded-full ${styles.icon}`, children: /* @__PURE__ */ jsx4("svg", { className: "h-8 w-8 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: iconPaths[variant] }) }) }) }),
276
+ /* @__PURE__ */ jsx4("h3", { className: `mb-4 text-xl font-bold text-center ${t.textPrimary}`, children: title }),
277
+ /* @__PURE__ */ jsx4("div", { className: `mb-6 text-center whitespace-pre-line ${t.textSecondary}`, children: message }),
278
+ /* @__PURE__ */ jsxs3("div", { className: "flex gap-3", children: [
279
+ /* @__PURE__ */ jsx4(
280
+ "button",
281
+ {
282
+ onClick: onClose,
283
+ disabled: isLoading,
284
+ className: `flex-1 px-4 py-2 rounded-lg transition-colors ${t.buttonGhost} ${t.border} disabled:opacity-50 disabled:cursor-not-allowed`,
285
+ children: cancelText
286
+ }
287
+ ),
288
+ /* @__PURE__ */ jsx4(
289
+ "button",
290
+ {
291
+ onClick: onConfirm,
292
+ disabled: isLoading,
293
+ className: `flex-1 px-4 py-2 rounded-lg transition-colors ${styles.button} disabled:opacity-50 disabled:cursor-not-allowed`,
294
+ children: isLoading ? "Processing..." : confirmText
295
+ }
296
+ )
297
+ ] })
298
+ ] }) });
299
+ }
300
+
301
+ // src/components/MenuItem.tsx
302
+ import { Loader2 } from "lucide-react";
303
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
304
+ function MenuItem({ icon: Icon, label, onClick, disabled, danger, loading, className, suffix }) {
305
+ const t = useTheme();
306
+ const colorClasses = danger ? t.menuItemDanger : t.menuItem;
307
+ return /* @__PURE__ */ jsxs4(
308
+ "button",
309
+ {
310
+ onClick,
311
+ disabled: disabled || loading,
312
+ className: `w-full flex items-center gap-2 px-3 py-2 text-sm rounded-lg transition-colors ${colorClasses} disabled:opacity-50${className ? ` ${className}` : ""}`,
313
+ children: [
314
+ loading ? /* @__PURE__ */ jsx5(Loader2, { className: "w-3.5 h-3.5 animate-spin" }) : /* @__PURE__ */ jsx5(Icon, { className: "w-3.5 h-3.5" }),
315
+ label,
316
+ suffix && /* @__PURE__ */ jsx5("span", { className: "ml-auto", children: suffix })
317
+ ]
318
+ }
319
+ );
320
+ }
321
+
322
+ // src/components/Paginator.tsx
323
+ import { useState, useRef, useEffect as useEffect2, useCallback } from "react";
324
+ import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react";
325
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
326
+ function Paginator({
327
+ currentPage,
328
+ totalPages,
329
+ onPageChange,
330
+ siblings = 2
331
+ }) {
332
+ const t = useTheme();
333
+ const [editValue, setEditValue] = useState(String(currentPage));
334
+ const [editing, setEditing] = useState(false);
335
+ const inputRef = useRef(null);
336
+ useEffect2(() => {
337
+ if (!editing) setEditValue(String(currentPage));
338
+ }, [currentPage, editing]);
339
+ const clamp = useCallback((page) => Math.max(1, Math.min(totalPages, page)), [totalPages]);
340
+ const commitEdit = useCallback(() => {
341
+ setEditing(false);
342
+ const parsed = parseInt(editValue, 10);
343
+ if (!isNaN(parsed)) {
344
+ const clamped = clamp(parsed);
345
+ onPageChange(clamped);
346
+ setEditValue(String(clamped));
347
+ } else {
348
+ setEditValue(String(currentPage));
349
+ }
350
+ }, [editValue, clamp, onPageChange, currentPage]);
351
+ const handleKeyDown = useCallback((e) => {
352
+ if (e.key === "Enter") {
353
+ commitEdit();
354
+ inputRef.current?.blur();
355
+ } else if (e.key === "Escape") {
356
+ setEditing(false);
357
+ setEditValue(String(currentPage));
358
+ inputRef.current?.blur();
359
+ }
360
+ }, [commitEdit, currentPage]);
361
+ const pages = [];
362
+ const start = Math.max(1, currentPage - siblings);
363
+ const end = Math.min(totalPages, currentPage + siblings);
364
+ for (let i = start; i <= end; i++) {
365
+ if (i !== currentPage) pages.push(i);
366
+ }
367
+ const before = pages.filter((p) => p < currentPage);
368
+ const after = pages.filter((p) => p > currentPage);
369
+ const navBtn = `p-1 ${t.textMuted} ${t.hover} disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded`;
370
+ const pageBtn = `px-1.5 py-0.5 text-xs rounded transition-colors ${t.textMuted} ${t.hover}`;
371
+ if (totalPages <= 1) return null;
372
+ return /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-center gap-0.5", children: [
373
+ /* @__PURE__ */ jsx6("button", { onClick: () => onPageChange(1), disabled: currentPage <= 1, className: navBtn, "aria-label": "First page", children: /* @__PURE__ */ jsx6(ChevronsLeft, { className: "w-4 h-4" }) }),
374
+ /* @__PURE__ */ jsx6("button", { onClick: () => onPageChange(clamp(currentPage - 1)), disabled: currentPage <= 1, className: navBtn, "aria-label": "Previous page", children: /* @__PURE__ */ jsx6(ChevronLeft, { className: "w-4 h-4" }) }),
375
+ before.map((p) => /* @__PURE__ */ jsx6("button", { onClick: () => onPageChange(p), className: pageBtn, children: p }, p)),
376
+ /* @__PURE__ */ jsx6(
377
+ "input",
378
+ {
379
+ ref: inputRef,
380
+ type: "text",
381
+ inputMode: "numeric",
382
+ value: editing ? editValue : String(currentPage),
383
+ onChange: (e) => setEditValue(e.target.value),
384
+ onFocus: () => {
385
+ setEditing(true);
386
+ setTimeout(() => inputRef.current?.select(), 0);
387
+ },
388
+ onBlur: commitEdit,
389
+ onKeyDown: handleKeyDown,
390
+ className: `w-8 text-center text-xs font-medium rounded-md py-0.5 outline-none ${t.tabActive} ${t.inputFocus}`,
391
+ "aria-label": "Current page"
392
+ }
393
+ ),
394
+ after.map((p) => /* @__PURE__ */ jsx6("button", { onClick: () => onPageChange(p), className: pageBtn, children: p }, p)),
395
+ /* @__PURE__ */ jsx6("button", { onClick: () => onPageChange(clamp(currentPage + 1)), disabled: currentPage >= totalPages, className: navBtn, "aria-label": "Next page", children: /* @__PURE__ */ jsx6(ChevronRight, { className: "w-4 h-4" }) }),
396
+ /* @__PURE__ */ jsx6("button", { onClick: () => onPageChange(totalPages), disabled: currentPage >= totalPages, className: navBtn, "aria-label": "Last page", children: /* @__PURE__ */ jsx6(ChevronsRight, { className: "w-4 h-4" }) })
397
+ ] });
398
+ }
399
+
400
+ // src/components/Popover.tsx
401
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState2 } from "react";
402
+ import { createPortal as createPortal2 } from "react-dom";
403
+ import { jsx as jsx7 } from "react/jsx-runtime";
404
+ function Popover({ open, anchorRef, onClose, children, className, anchor = "below" }) {
405
+ const t = useTheme();
406
+ const popoverRef = useRef2(null);
407
+ const [adjustedStyle, setAdjustedStyle] = useState2({});
408
+ useEffect3(() => {
409
+ if (!open) return;
410
+ function handlePointerDown(e) {
411
+ const target = e.target;
412
+ if (popoverRef.current && !popoverRef.current.contains(target) && anchorRef.current && !anchorRef.current.contains(target)) {
413
+ onClose();
414
+ }
415
+ }
416
+ function handleKeyDown(e) {
417
+ if (e.key === "Escape") onClose();
418
+ }
419
+ document.addEventListener("pointerdown", handlePointerDown);
420
+ document.addEventListener("touchstart", handlePointerDown);
421
+ document.addEventListener("keydown", handleKeyDown);
422
+ return () => {
423
+ document.removeEventListener("pointerdown", handlePointerDown);
424
+ document.removeEventListener("touchstart", handlePointerDown);
425
+ document.removeEventListener("keydown", handleKeyDown);
426
+ };
427
+ }, [open, onClose, anchorRef]);
428
+ useEffect3(() => {
429
+ if (!open || !popoverRef.current || !anchorRef.current) return;
430
+ const anchorRect = anchorRef.current.getBoundingClientRect();
431
+ const popoverRect = popoverRef.current.getBoundingClientRect();
432
+ const padding = 16;
433
+ const style2 = { position: "absolute", zIndex: 50 };
434
+ let left = anchorRect.left + window.scrollX;
435
+ if (left + popoverRect.width > window.innerWidth - padding) {
436
+ left = Math.max(padding, window.innerWidth - popoverRect.width - padding);
437
+ }
438
+ if (left < padding) left = padding;
439
+ style2.left = left;
440
+ const spaceBelow = window.innerHeight - anchorRect.bottom;
441
+ const spaceAbove = anchorRect.top;
442
+ if (anchor === "below") {
443
+ if (spaceBelow >= popoverRect.height + padding || spaceBelow >= spaceAbove) {
444
+ style2.top = anchorRect.bottom + window.scrollY;
445
+ } else {
446
+ style2.bottom = document.documentElement.scrollHeight - anchorRect.top - window.scrollY;
447
+ }
448
+ } else {
449
+ if (spaceAbove >= popoverRect.height + padding || spaceAbove >= spaceBelow) {
450
+ style2.bottom = document.documentElement.scrollHeight - anchorRect.top - window.scrollY;
451
+ } else {
452
+ style2.top = anchorRect.bottom + window.scrollY;
453
+ }
454
+ }
455
+ setAdjustedStyle(style2);
456
+ }, [open, anchor, anchorRef]);
457
+ if (!open || !anchorRef.current) return null;
458
+ const rect = anchorRef.current.getBoundingClientRect();
459
+ const initialStyle = {
460
+ position: "absolute",
461
+ left: rect.left + window.scrollX,
462
+ zIndex: 50,
463
+ visibility: adjustedStyle.left != null ? "visible" : "hidden",
464
+ ...anchor === "above" ? { bottom: document.documentElement.scrollHeight - rect.top - window.scrollY } : { top: rect.bottom + window.scrollY }
465
+ };
466
+ const style = adjustedStyle.left != null ? adjustedStyle : initialStyle;
467
+ const defaultClasses = `${t.elevated} ${t.border} shadow-lg rounded-lg`;
468
+ return createPortal2(
469
+ /* @__PURE__ */ jsx7("div", { ref: popoverRef, style, className: className ?? defaultClasses, children }),
470
+ document.body
471
+ );
472
+ }
473
+
474
+ // src/components/Slider.tsx
475
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
476
+ function fillStyle(value, min, max) {
477
+ const pct = max === min ? 0 : (value - min) / (max - min) * 100;
478
+ return {
479
+ background: `linear-gradient(90deg, var(--color-brand-primary) 0%, var(--color-brand-secondary) ${pct}%, var(--color-bg-elevated) ${pct}%)`
480
+ };
481
+ }
482
+ function Slider(props) {
483
+ const t = useTheme();
484
+ const { disabled, className } = props;
485
+ if (props.options) {
486
+ const { options, labels, value: value2, onChange: onChange2 } = props;
487
+ const index = options.indexOf(value2);
488
+ const currentIndex = index === -1 ? 0 : index;
489
+ const getLabel = (option) => {
490
+ if (typeof labels === "function") return labels(option);
491
+ return String(option);
492
+ };
493
+ return /* @__PURE__ */ jsxs6("div", { className, children: [
494
+ /* @__PURE__ */ jsx8(
495
+ "input",
496
+ {
497
+ type: "range",
498
+ min: 0,
499
+ max: options.length - 1,
500
+ step: 1,
501
+ value: currentIndex,
502
+ onChange: (e) => onChange2(options[parseInt(e.target.value)]),
503
+ className: "w-full slider-styled",
504
+ style: fillStyle(currentIndex, 0, options.length - 1),
505
+ disabled
506
+ }
507
+ ),
508
+ labels && /* @__PURE__ */ jsx8("div", { className: `flex justify-between text-xs mt-1 ${t.textMuted}`, children: options.map((option) => /* @__PURE__ */ jsx8("span", { children: getLabel(option) }, String(option))) })
509
+ ] });
510
+ }
511
+ const { min, max, step, value, onChange } = props;
512
+ return /* @__PURE__ */ jsx8("div", { className, children: /* @__PURE__ */ jsx8(
513
+ "input",
514
+ {
515
+ type: "range",
516
+ min,
517
+ max,
518
+ step,
519
+ value,
520
+ onChange: (e) => onChange(parseFloat(e.target.value)),
521
+ className: "w-full slider-styled",
522
+ style: fillStyle(value, min, max),
523
+ disabled
524
+ }
525
+ ) });
526
+ }
527
+
528
+ // src/components/SlideOverPanel.tsx
529
+ import { useEffect as useEffect4, useState as useState3 } from "react";
530
+ import { Fragment, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
531
+ function SlideOverPanel({ open, onClose, children }) {
532
+ const t = useTheme();
533
+ const [mounted, setMounted] = useState3(false);
534
+ const [visible, setVisible] = useState3(false);
535
+ useEffect4(() => {
536
+ if (open) {
537
+ setMounted(true);
538
+ requestAnimationFrame(() => {
539
+ requestAnimationFrame(() => setVisible(true));
540
+ });
541
+ } else {
542
+ setVisible(false);
543
+ const timer = setTimeout(() => setMounted(false), 200);
544
+ return () => clearTimeout(timer);
545
+ }
546
+ }, [open]);
547
+ if (!mounted) return null;
548
+ return /* @__PURE__ */ jsxs7(Fragment, { children: [
549
+ /* @__PURE__ */ jsx9(
550
+ "div",
551
+ {
552
+ className: `fixed inset-0 z-20 transition-opacity duration-200 ${t.overlay} ${visible ? "opacity-100" : "opacity-0"}`,
553
+ onClick: onClose
554
+ }
555
+ ),
556
+ /* @__PURE__ */ jsx9(
557
+ "div",
558
+ {
559
+ className: `fixed top-0 right-0 bottom-0 w-72 ${t.sidebar} overflow-y-auto z-30 transition-transform duration-200 ${visible ? "translate-x-0" : "translate-x-full"}`,
560
+ style: { paddingTop: "env(safe-area-inset-top)" },
561
+ children
562
+ }
563
+ )
564
+ ] });
565
+ }
566
+
567
+ // src/components/ToggleSwitch.tsx
568
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
569
+ var sizeConfig = {
570
+ sm: { track: "w-9 h-5", knob: "w-4 h-4", knobTranslate: "translate-x-4" },
571
+ md: { track: "w-11 h-6", knob: "w-5 h-5", knobTranslate: "translate-x-5" },
572
+ lg: { track: "w-14 h-7", knob: "w-6 h-6", knobTranslate: "translate-x-7" }
573
+ };
574
+ function ToggleSwitch({
575
+ checked,
576
+ onChange,
577
+ size = "md",
578
+ label,
579
+ description,
580
+ disabled = false,
581
+ id,
582
+ ...props
583
+ }) {
584
+ const t = useTheme();
585
+ const config = sizeConfig[size];
586
+ const handleClick = () => {
587
+ if (!disabled) onChange(!checked);
588
+ };
589
+ const handleKeyDown = (e) => {
590
+ if (e.key === " " || e.key === "Enter") {
591
+ e.preventDefault();
592
+ if (!disabled) onChange(!checked);
593
+ }
594
+ };
595
+ const toggle = /* @__PURE__ */ jsx10(
596
+ "button",
597
+ {
598
+ type: "button",
599
+ role: "switch",
600
+ "aria-checked": checked,
601
+ "aria-label": label,
602
+ id,
603
+ disabled,
604
+ onClick: handleClick,
605
+ onKeyDown: handleKeyDown,
606
+ className: [
607
+ "relative inline-flex items-center rounded-full transition-all duration-200 flex-shrink-0",
608
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary focus-visible:ring-offset-2",
609
+ config.track,
610
+ checked ? t.toggleOn : t.toggleOff,
611
+ disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
612
+ ].join(" "),
613
+ ...props,
614
+ children: /* @__PURE__ */ jsx10(
615
+ "span",
616
+ {
617
+ className: [
618
+ "inline-flex items-center justify-center rounded-full bg-white shadow-sm transition-transform duration-200",
619
+ config.knob,
620
+ checked ? config.knobTranslate : "translate-x-0.5"
621
+ ].join(" ")
622
+ }
623
+ )
624
+ }
625
+ );
626
+ if (!label) return toggle;
627
+ return /* @__PURE__ */ jsxs8("div", { className: ["flex items-center justify-between gap-3", disabled ? "opacity-50" : ""].join(" "), children: [
628
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-col min-w-0", children: [
629
+ /* @__PURE__ */ jsx10(
630
+ "label",
631
+ {
632
+ htmlFor: id,
633
+ className: [
634
+ "text-sm font-medium",
635
+ t.textPrimary,
636
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
637
+ ].join(" "),
638
+ onClick: handleClick,
639
+ children: label
640
+ }
641
+ ),
642
+ description && /* @__PURE__ */ jsx10("span", { className: `text-xs mt-0.5 ${t.textMuted}`, children: description })
643
+ ] }),
644
+ toggle
645
+ ] });
646
+ }
647
+
648
+ // src/hooks/useDebounce.ts
649
+ import { useState as useState4, useEffect as useEffect5 } from "react";
650
+ function useDebounce(value, delay) {
651
+ const [debouncedValue, setDebouncedValue] = useState4(value);
652
+ useEffect5(() => {
653
+ const handler = setTimeout(() => {
654
+ setDebouncedValue(value);
655
+ }, delay);
656
+ return () => {
657
+ clearTimeout(handler);
658
+ };
659
+ }, [value, delay]);
660
+ return debouncedValue;
661
+ }
662
+
663
+ // src/utils/format-time.ts
664
+ function formatExactTime(dateString) {
665
+ const date = new Date(dateString);
666
+ const time = date.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true });
667
+ const weekday = date.toLocaleDateString("en-US", { weekday: "short" });
668
+ const month = date.getMonth() + 1;
669
+ const day = date.getDate();
670
+ const year = date.getFullYear().toString().slice(-2);
671
+ return `${time} ${weekday} ${month}/${day}/${year}`;
672
+ }
673
+ function getRelativeTime(dateString) {
674
+ const date = new Date(dateString);
675
+ const now = /* @__PURE__ */ new Date();
676
+ const diffMs = now.getTime() - date.getTime();
677
+ const diffMins = Math.floor(diffMs / 6e4);
678
+ const diffHours = Math.floor(diffMs / 36e5);
679
+ const diffDays = Math.floor(diffMs / 864e5);
680
+ if (diffMins < 1) return "Just now";
681
+ if (diffMins < 60) return `${diffMins}m ago`;
682
+ if (diffHours < 24) return `${diffHours}h ago`;
683
+ if (diffDays < 7) return `${diffDays}d ago`;
684
+ if (diffDays < 30) {
685
+ const weeks = Math.floor(diffDays / 7);
686
+ return `${weeks}w ago`;
687
+ }
688
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
689
+ }
690
+
691
+ // src/utils/clipboard.ts
692
+ async function shareOrCopyUrl(url) {
693
+ try {
694
+ if (navigator.share) {
695
+ await navigator.share({ url });
696
+ return "shared";
697
+ }
698
+ } catch (err) {
699
+ if (err?.name === "AbortError") return "cancelled";
700
+ }
701
+ const copied = await copyToClipboard(url);
702
+ return copied ? "copied" : "failed";
703
+ }
704
+ async function copyToClipboard(text) {
705
+ try {
706
+ if (navigator.clipboard?.writeText) {
707
+ await navigator.clipboard.writeText(text);
708
+ return true;
709
+ }
710
+ } catch {
711
+ }
712
+ const textarea = document.createElement("textarea");
713
+ textarea.value = text;
714
+ textarea.style.position = "fixed";
715
+ textarea.style.opacity = "0";
716
+ document.body.appendChild(textarea);
717
+ textarea.select();
718
+ try {
719
+ document.execCommand("copy");
720
+ return true;
721
+ } catch {
722
+ return false;
723
+ } finally {
724
+ document.body.removeChild(textarea);
725
+ }
726
+ }
727
+ export {
728
+ Button,
729
+ ConfirmationModal,
730
+ MenuItem,
731
+ Modal,
732
+ Paginator,
733
+ Popover,
734
+ SlideOverPanel,
735
+ Slider,
736
+ ThemingProvider,
737
+ ToggleSwitch,
738
+ copyToClipboard,
739
+ formatExactTime,
740
+ getRelativeTime,
741
+ shareOrCopyUrl,
742
+ themes,
743
+ useDebounce,
744
+ useTheme
745
+ };
746
+ //# sourceMappingURL=index.js.map