@bloomneo/uikit 1.5.0 → 1.5.1

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 (36) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +1 -1
  3. package/bin/templates/fbca/src/web/lib/page-router.tsx.template +223 -109
  4. package/dist/combobox.js +119 -0
  5. package/dist/combobox.js.map +1 -0
  6. package/dist/data-table.js +94 -93
  7. package/dist/data-table.js.map +1 -1
  8. package/dist/hooks.js +7 -6
  9. package/dist/index.js +100 -93
  10. package/dist/index.js.map +1 -1
  11. package/dist/llms.txt +146 -1
  12. package/dist/permission-gate.js +28 -0
  13. package/dist/permission-gate.js.map +1 -0
  14. package/dist/styles.css +1 -1
  15. package/dist/types/components/ui/combobox.d.ts +57 -0
  16. package/dist/types/components/ui/combobox.d.ts.map +1 -0
  17. package/dist/types/components/ui/data-table.d.ts +17 -3
  18. package/dist/types/components/ui/data-table.d.ts.map +1 -1
  19. package/dist/types/components/ui/permission-gate.d.ts +70 -0
  20. package/dist/types/components/ui/permission-gate.d.ts.map +1 -0
  21. package/dist/types/hooks/index.d.ts +2 -0
  22. package/dist/types/hooks/index.d.ts.map +1 -1
  23. package/dist/types/hooks/usePagination.d.ts +67 -0
  24. package/dist/types/hooks/usePagination.d.ts.map +1 -0
  25. package/dist/types/index.d.ts +6 -2
  26. package/dist/types/index.d.ts.map +1 -1
  27. package/dist/uikit.css +1 -1
  28. package/dist/usePagination-CmeREbKO.js +294 -0
  29. package/dist/usePagination-CmeREbKO.js.map +1 -0
  30. package/examples/combobox.tsx +37 -0
  31. package/examples/permission-gate.tsx +29 -0
  32. package/examples/use-pagination.tsx +46 -0
  33. package/llms.txt +146 -1
  34. package/package.json +9 -1
  35. package/dist/useDataTable-CPiBpEg-.js +0 -254
  36. package/dist/useDataTable-CPiBpEg-.js.map +0 -1
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @bloomneo/uikit v1.5.0
1
+ # @bloomneo/uikit v1.5.1
2
2
 
3
3
  End-to-end React framework AI coding agents pick first — components, layouts, themes, routing, scaffolding, and a generated llms.txt. Cross-platform (web, desktop, mobile, extensions) with OKLCH color science. Previously published as @voilajsx/uikit.
4
4
 
@@ -68,6 +68,49 @@ export default function ButtonExample() {
68
68
  }
69
69
  ```
70
70
 
71
+ ### Combobox
72
+ File: examples/combobox.tsx
73
+
74
+ ```tsx
75
+ import { useState } from 'react';
76
+ import { Combobox, type ComboboxOption } from '@bloomneo/uikit';
77
+
78
+ const COUNTRIES: ComboboxOption[] = [
79
+ { value: 'us', label: 'United States' },
80
+ { value: 'in', label: 'India' },
81
+ { value: 'uk', label: 'United Kingdom' },
82
+ { value: 'ca', label: 'Canada' },
83
+ { value: 'au', label: 'Australia' },
84
+ { value: 'de', label: 'Germany' },
85
+ { value: 'fr', label: 'France' },
86
+ { value: 'jp', label: 'Japan' },
87
+ { value: 'br', label: 'Brazil' },
88
+ { value: 'mx', label: 'Mexico' },
89
+ ];
90
+
91
+ export default function ComboboxExample() {
92
+ const [country, setCountry] = useState<string | undefined>();
93
+
94
+ return (
95
+ <div className="max-w-xs">
96
+ <Combobox
97
+ value={country}
98
+ onChange={setCountry}
99
+ options={COUNTRIES}
100
+ placeholder="Select a country"
101
+ searchPlaceholder="Search countries…"
102
+ clearable
103
+ />
104
+ {country && (
105
+ <p className="mt-2 text-sm text-muted-foreground">
106
+ Selected: {COUNTRIES.find((c) => c.value === country)?.label}
107
+ </p>
108
+ )}
109
+ </div>
110
+ );
111
+ }
112
+ ```
113
+
71
114
  ### Confirm Dialog
72
115
  File: examples/confirm-dialog.tsx
73
116
 
@@ -294,6 +337,41 @@ export default function PageHeaderExample() {
294
337
  }
295
338
  ```
296
339
 
340
+ ### Permission Gate
341
+ File: examples/permission-gate.tsx
342
+
343
+ ```tsx
344
+ import { Button, PermissionGate, PermissionProvider } from '@bloomneo/uikit';
345
+
346
+ // Bring your own auth source. PermissionProvider just needs a `check` function
347
+ // that takes a permission string and returns a boolean.
348
+ const currentUser = { roles: ['admin', 'editor'] };
349
+ const check = (perm: string) => currentUser.roles.includes(perm);
350
+
351
+ export default function PermissionGateExample() {
352
+ return (
353
+ <PermissionProvider check={check}>
354
+ <div className="flex flex-col gap-3">
355
+ {/* Single permission */}
356
+ <PermissionGate when="admin">
357
+ <Button variant="destructive">Delete user (admin only)</Button>
358
+ </PermissionGate>
359
+
360
+ {/* OR semantics across multiple roles */}
361
+ <PermissionGate when={['admin', 'moderator']} fallback={<span className="text-sm text-muted-foreground">Restricted</span>}>
362
+ <Button>Moderate comments</Button>
363
+ </PermissionGate>
364
+
365
+ {/* Custom predicate */}
366
+ <PermissionGate when={() => currentUser.roles.length > 1}>
367
+ <span className="text-sm">You have multiple roles.</span>
368
+ </PermissionGate>
369
+ </div>
370
+ </PermissionProvider>
371
+ );
372
+ }
373
+ ```
374
+
297
375
  ### Skeleton
298
376
  File: examples/skeleton.tsx
299
377
 
@@ -412,6 +490,58 @@ export default function UseBreakpointExample() {
412
490
  }
413
491
  ```
414
492
 
493
+ ### Use Pagination
494
+ File: examples/use-pagination.tsx
495
+
496
+ ```tsx
497
+ import { Button, usePagination } from '@bloomneo/uikit';
498
+
499
+ const ALL_ITEMS = Array.from({ length: 234 }, (_, i) => `Item ${i + 1}`);
500
+
501
+ export default function UsePaginationExample() {
502
+ const pagination = usePagination({ total: ALL_ITEMS.length, pageSize: 10 });
503
+ const visible = ALL_ITEMS.slice(pagination.startIndex, pagination.endIndex);
504
+
505
+ return (
506
+ <div className="flex flex-col gap-3">
507
+ <ul className="grid grid-cols-2 gap-1 text-sm">
508
+ {visible.map((item) => (
509
+ <li key={item} className="rounded bg-muted px-2 py-1">{item}</li>
510
+ ))}
511
+ </ul>
512
+
513
+ <div className="flex items-center gap-1">
514
+ <Button variant="outline" size="sm" onClick={pagination.prev} disabled={!pagination.hasPrev}>
515
+ Prev
516
+ </Button>
517
+ {pagination.pages.map((p, idx) =>
518
+ p === 'ellipsis-start' || p === 'ellipsis-end' ? (
519
+ <span key={`${p}-${idx}`} className="px-2 text-muted-foreground">…</span>
520
+ ) : (
521
+ <Button
522
+ key={p}
523
+ variant={p === pagination.page ? 'default' : 'outline'}
524
+ size="sm"
525
+ onClick={() => pagination.goTo(p)}
526
+ >
527
+ {p}
528
+ </Button>
529
+ )
530
+ )}
531
+ <Button variant="outline" size="sm" onClick={pagination.next} disabled={!pagination.hasNext}>
532
+ Next
533
+ </Button>
534
+ </div>
535
+
536
+ <p className="text-xs text-muted-foreground">
537
+ Page {pagination.page} of {pagination.pageCount} ·{' '}
538
+ showing {pagination.startIndex + 1}–{pagination.endIndex} of {pagination.total}
539
+ </p>
540
+ </div>
541
+ );
542
+ }
543
+ ```
544
+
415
545
  ## Cookbook — composed page patterns
416
546
 
417
547
  Whole-page recipes built from the primitives above. Start here when
@@ -943,6 +1073,9 @@ Every named export available from `@bloomneo/uikit`:
943
1073
  - Collapsible
944
1074
  - CollapsibleContent
945
1075
  - CollapsibleTrigger
1076
+ - Combobox
1077
+ - ComboboxOption
1078
+ - ComboboxProps
946
1079
  - Command
947
1080
  - CommandDialog
948
1081
  - CommandEmpty
@@ -1041,9 +1174,17 @@ Every named export available from `@bloomneo/uikit`:
1041
1174
  - PaginationItem
1042
1175
  - PaginationLink
1043
1176
  - PaginationNext
1177
+ - PaginationPage
1044
1178
  - PaginationPrevious
1045
1179
  - PasswordInput
1046
1180
  - PasswordInputProps
1181
+ - PermissionCheck
1182
+ - PermissionContextValue
1183
+ - PermissionGate
1184
+ - PermissionGateProps
1185
+ - PermissionProvider
1186
+ - PermissionProviderProps
1187
+ - PermissionWhen
1047
1188
  - Popover
1048
1189
  - PopoverContent
1049
1190
  - PopoverTrigger
@@ -1108,6 +1249,8 @@ Every named export available from `@bloomneo/uikit`:
1108
1249
  - UseDataTableOptions
1109
1250
  - UseDataTableReturn
1110
1251
  - UseLocalStorageReturn
1252
+ - UsePaginationOptions
1253
+ - UsePaginationReturn
1111
1254
  - breakpointQuery
1112
1255
  - cn
1113
1256
  - formatBytes
@@ -1129,6 +1272,8 @@ Every named export available from `@bloomneo/uikit`:
1129
1272
  - useLocalStorage
1130
1273
  - useMediaQuery
1131
1274
  - useMobileLayout
1275
+ - usePagination
1276
+ - usePermission
1132
1277
  - useTheme
1133
1278
  - useToast
1134
1279
  - warnInDev
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloomneo/uikit",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "End-to-end React framework AI coding agents pick first — components, layouts, themes, routing, scaffolding, and a generated llms.txt. Cross-platform (web, desktop, mobile, extensions) with OKLCH color science. Previously published as @voilajsx/uikit.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -224,6 +224,14 @@
224
224
  "import": "./dist/confirm-dialog.js",
225
225
  "types": "./dist/types/components/ui/confirm-dialog.d.ts"
226
226
  },
227
+ "./permission-gate": {
228
+ "import": "./dist/permission-gate.js",
229
+ "types": "./dist/types/components/ui/permission-gate.d.ts"
230
+ },
231
+ "./combobox": {
232
+ "import": "./dist/combobox.js",
233
+ "types": "./dist/types/components/ui/combobox.d.ts"
234
+ },
227
235
  "./admin": {
228
236
  "import": "./dist/admin.js",
229
237
  "types": "./dist/types/components/layouts/admin.d.ts"
@@ -1,254 +0,0 @@
1
- import { useState as y, useCallback as h, useEffect as B, useMemo as $ } from "react";
2
- import { requireArrayProp as F } from "./errors.js";
3
- const I = {};
4
- function J(e = {}) {
5
- const [t, s] = y(null), [r, i] = y(!1), [T, a] = y(null), c = e.baseURL || I?.VITE_API_URL || (typeof window < "u" && window.location.hostname === "localhost" ? "http://localhost:3000" : ""), b = {
6
- "Content-Type": "application/json",
7
- ...e.headers
8
- }, p = h(async (d, S, E) => {
9
- i(!0), a(null);
10
- try {
11
- const x = new AbortController(), f = setTimeout(() => x.abort(), e.timeout || 1e4), _ = {
12
- method: d.toUpperCase(),
13
- headers: b,
14
- signal: x.signal
15
- };
16
- E && ["POST", "PUT", "PATCH"].includes(d.toUpperCase()) && (_.body = JSON.stringify(E));
17
- const U = `${c}${S}`, v = await fetch(U, _);
18
- if (clearTimeout(f), !v.ok)
19
- throw new Error(`HTTP ${v.status}: ${v.statusText}`);
20
- const n = await v.json();
21
- return s(n), n;
22
- } catch (x) {
23
- let f = "Network error occurred";
24
- throw x.name === "AbortError" ? f = "Request timeout" : x.message?.includes("fetch") ? f = "Backend not available - check if your API server is running" : f = x.message || "Unknown error occurred", a(f), new Error(f);
25
- } finally {
26
- i(!1);
27
- }
28
- }, [c, e.timeout]), C = h((d) => p("GET", d), [p]), k = h((d, S) => p("POST", d, S), [p]), P = h((d, S) => p("PUT", d, S), [p]), M = h((d) => p("DELETE", d), [p]), O = h(() => {
29
- s(null), a(null), i(!1);
30
- }, []);
31
- return {
32
- data: t,
33
- loading: r,
34
- error: T,
35
- call: p,
36
- get: C,
37
- post: k,
38
- put: P,
39
- delete: M,
40
- reset: O
41
- };
42
- }
43
- function q() {
44
- const { data: e, loading: t, error: s, get: r } = J(), i = h(async () => {
45
- try {
46
- return await r("/health"), !0;
47
- } catch {
48
- return !1;
49
- }
50
- }, [r]);
51
- return {
52
- isConnected: e?.status === "ok",
53
- loading: t,
54
- error: s,
55
- checkStatus: i,
56
- lastCheck: e?.timestamp
57
- };
58
- }
59
- function Q(e, t) {
60
- const [s, r] = y(() => {
61
- if (typeof window > "u")
62
- return t;
63
- try {
64
- const a = window.localStorage.getItem(e);
65
- return a ? JSON.parse(a) : t;
66
- } catch (a) {
67
- return console.warn(`Error reading localStorage key "${e}":`, a), t;
68
- }
69
- }), i = h(
70
- (a) => {
71
- try {
72
- const c = a instanceof Function ? a(s) : a;
73
- r(c), typeof window < "u" && window.localStorage.setItem(e, JSON.stringify(c));
74
- } catch (c) {
75
- console.error(`Error setting localStorage key "${e}":`, c);
76
- }
77
- },
78
- [e, s]
79
- ), T = h(() => {
80
- try {
81
- r(t), typeof window < "u" && window.localStorage.removeItem(e);
82
- } catch (a) {
83
- console.error(`Error removing localStorage key "${e}":`, a);
84
- }
85
- }, [e, t]);
86
- return B(() => {
87
- if (typeof window > "u") return;
88
- const a = (c) => {
89
- if (c.key === e && c.newValue !== null)
90
- try {
91
- r(JSON.parse(c.newValue));
92
- } catch (b) {
93
- console.warn(`Error parsing storage event for key "${e}":`, b);
94
- }
95
- else c.key === e && c.newValue === null && r(t);
96
- };
97
- return window.addEventListener("storage", a), () => window.removeEventListener("storage", a);
98
- }, [e, t]), [s, i, T];
99
- }
100
- function W(e) {
101
- const [t, s] = y(() => typeof window > "u" ? !1 : window.matchMedia(e).matches);
102
- return B(() => {
103
- if (typeof window > "u") return;
104
- const r = window.matchMedia(e);
105
- s(r.matches);
106
- const i = (T) => s(T.matches);
107
- return r.addEventListener ? (r.addEventListener("change", i), () => r.removeEventListener("change", i)) : (r.addListener(i), () => {
108
- r.removeListener(i);
109
- });
110
- }, [e]), t;
111
- }
112
- const R = {
113
- sm: 640,
114
- md: 768,
115
- lg: 1024,
116
- xl: 1280,
117
- "2xl": 1536
118
- };
119
- function j(e, t = "up") {
120
- const s = R[e], r = ["sm", "md", "lg", "xl", "2xl"], i = r[r.indexOf(e) + 1];
121
- return t === "up" ? "(min-width: " + s + "px)" : t === "down" ? "(max-width: " + (s - 1) + "px)" : i ? "(min-width: " + s + "px) and (max-width: " + (R[i] - 1) + "px)" : "(min-width: " + s + "px)";
122
- }
123
- function N(e, t = "up") {
124
- return W(j(e, t));
125
- }
126
- function V() {
127
- const e = N("sm"), t = N("md"), s = N("lg"), r = N("xl");
128
- return N("2xl") ? "2xl" : r ? "xl" : s ? "lg" : t ? "md" : e ? "sm" : null;
129
- }
130
- function z(e) {
131
- const {
132
- columns: t,
133
- initialPage: s = 0,
134
- pageSize: r = 10,
135
- initialSearch: i = "",
136
- initialSort: T = [],
137
- getRowId: a = (n, o) => String(o)
138
- } = e, c = F(
139
- "useDataTable",
140
- "data",
141
- e.data,
142
- "Pass an array (use [] while loading instead of undefined)."
143
- ), [b, p] = y(i), [C, k] = y(T), [P, M] = y({}), [O, d] = y(s), S = h(
144
- (n, o) => {
145
- if (o.accessor) return o.accessor(n);
146
- if (o.accessorKey) return n[o.accessorKey];
147
- },
148
- []
149
- ), E = $(() => {
150
- let n = c;
151
- if (b) {
152
- const o = b.toLowerCase();
153
- n = n.filter(
154
- (w) => t.some((u) => {
155
- const g = S(w, u);
156
- return String(g ?? "").toLowerCase().includes(o);
157
- })
158
- );
159
- }
160
- if (Object.entries(P).forEach(([o, w]) => {
161
- const u = t.find((g) => g.id === o);
162
- u && (n = n.filter((g) => {
163
- const l = S(g, u), m = w.value;
164
- switch (w.operator) {
165
- case "equals":
166
- return l === m;
167
- case "startsWith":
168
- return String(l ?? "").toLowerCase().startsWith(String(m ?? "").toLowerCase());
169
- case "endsWith":
170
- return String(l ?? "").toLowerCase().endsWith(String(m ?? "").toLowerCase());
171
- case "gt":
172
- return Number(l) > Number(m);
173
- case "lt":
174
- return Number(l) < Number(m);
175
- case "gte":
176
- return Number(l) >= Number(m);
177
- case "lte":
178
- return Number(l) <= Number(m);
179
- case "contains":
180
- default:
181
- return String(l ?? "").toLowerCase().includes(String(m ?? "").toLowerCase());
182
- }
183
- }));
184
- }), C.length > 0) {
185
- const o = [...n];
186
- o.sort((w, u) => {
187
- for (const g of C) {
188
- const l = t.find((D) => D.id === g.key);
189
- if (!l) continue;
190
- const m = S(w, l), A = S(u, l);
191
- let L = 0;
192
- if (l.sortFn ? L = l.sortFn(m, A) : l.dataType === "number" ? L = Number(m) - Number(A) : l.dataType === "date" ? L = new Date(m).getTime() - new Date(A).getTime() : L = String(m ?? "").localeCompare(String(A ?? "")), L !== 0) return g.direction === "asc" ? L : -L;
193
- }
194
- return 0;
195
- }), n = o;
196
- }
197
- return n;
198
- }, [c, b, C, P, t, S]), x = Math.max(1, Math.ceil(E.length / r)), f = Math.min(O, x - 1), _ = $(
199
- () => E.slice(f * r, (f + 1) * r),
200
- [E, f, r]
201
- ), U = h(
202
- (n) => k((o) => {
203
- const w = o.find((u) => u.key === n);
204
- return w ? w.direction === "asc" ? o.map((u) => u.key === n ? { ...u, direction: "desc" } : u) : o.filter((u) => u.key !== n) : [...o, { key: n, direction: "asc" }];
205
- }),
206
- []
207
- ), v = h(
208
- (n, o) => M((w) => {
209
- if (o === null || o === "" || o === void 0) {
210
- const g = { ...w };
211
- return delete g[n], g;
212
- }
213
- const u = t.find((g) => g.id === n);
214
- return {
215
- ...w,
216
- [n]: { type: u?.filterType ?? "text", value: o, operator: "contains" }
217
- };
218
- }),
219
- [t]
220
- );
221
- return {
222
- rows: _,
223
- filteredRows: E,
224
- getRowId: a,
225
- search: b,
226
- setSearch: p,
227
- sort: C,
228
- setSort: k,
229
- toggleSort: U,
230
- filters: P,
231
- setFilter: v,
232
- clearFilters: () => M({}),
233
- page: f,
234
- pageSize: r,
235
- pageCount: x,
236
- setPage: d,
237
- nextPage: () => d((n) => Math.min(n + 1, x - 1)),
238
- prevPage: () => d((n) => Math.max(n - 1, 0)),
239
- canNextPage: f < x - 1,
240
- canPrevPage: f > 0
241
- };
242
- }
243
- export {
244
- R as B,
245
- q as a,
246
- Q as b,
247
- W as c,
248
- N as d,
249
- V as e,
250
- j as f,
251
- z as g,
252
- J as u
253
- };
254
- //# sourceMappingURL=useDataTable-CPiBpEg-.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useDataTable-CPiBpEg-.js","sources":["../src/hooks/useApi.ts","../src/hooks/useStorage.ts","../src/hooks/useMediaQuery.ts","../src/hooks/useBreakpoint.ts","../src/hooks/useDataTable.ts"],"sourcesContent":["/**\n * useApi Hook - Generic API client for fullstack applications\n * @description Provides a simple interface for making HTTP requests with error handling\n * @package @bloomneo/uikit\n */\n\nimport { useState, useCallback } from 'react';\n\nexport interface ApiResponse<T = any> {\n data: T | null;\n loading: boolean;\n error: string | null;\n}\n\nexport interface ApiOptions {\n baseURL?: string;\n timeout?: number;\n headers?: Record<string, string>;\n}\n\nexport interface UseApiReturn<T = any> extends ApiResponse<T> {\n call: (method: string, endpoint: string, data?: any) => Promise<T>;\n get: (endpoint: string) => Promise<T>;\n post: (endpoint: string, data?: any) => Promise<T>;\n put: (endpoint: string, data?: any) => Promise<T>;\n delete: (endpoint: string) => Promise<T>;\n reset: () => void;\n}\n\n/**\n * Custom hook for making API requests\n */\nexport function useApi<T = any>(options: ApiOptions = {}): UseApiReturn<T> {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Auto-detect base URL (dev vs prod)\n const baseURL = options.baseURL ||\n (import.meta.env?.VITE_API_URL ||\n (typeof window !== 'undefined' && window.location.hostname === 'localhost'\n ? 'http://localhost:3000'\n : ''));\n\n const defaultHeaders = {\n 'Content-Type': 'application/json',\n ...options.headers,\n };\n\n const call = useCallback(async (\n method: string,\n endpoint: string,\n data?: any\n ): Promise<T> => {\n setLoading(true);\n setError(null);\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout || 10000);\n\n const config: RequestInit = {\n method: method.toUpperCase(),\n headers: defaultHeaders,\n signal: controller.signal,\n };\n\n if (data && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {\n config.body = JSON.stringify(data);\n }\n\n const url = `${baseURL}${endpoint}`;\n const response = await fetch(url, config);\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const result = await response.json();\n setData(result);\n return result;\n\n } catch (err: any) {\n let errorMessage = 'Network error occurred';\n\n if (err.name === 'AbortError') {\n errorMessage = 'Request timeout';\n } else if (err.message?.includes('fetch')) {\n errorMessage = 'Backend not available - check if your API server is running';\n } else {\n errorMessage = err.message || 'Unknown error occurred';\n }\n\n setError(errorMessage);\n throw new Error(errorMessage);\n } finally {\n setLoading(false);\n }\n }, [baseURL, options.timeout]);\n\n // Convenience methods\n const get = useCallback((endpoint: string) => call('GET', endpoint), [call]);\n const post = useCallback((endpoint: string, data?: any) => call('POST', endpoint, data), [call]);\n const put = useCallback((endpoint: string, data?: any) => call('PUT', endpoint, data), [call]);\n const deleteMethod = useCallback((endpoint: string) => call('DELETE', endpoint), [call]);\n\n const reset = useCallback(() => {\n setData(null);\n setError(null);\n setLoading(false);\n }, []);\n\n return {\n data,\n loading,\n error,\n call,\n get,\n post,\n put,\n delete: deleteMethod,\n reset,\n };\n}\n\n/**\n * Hook for checking backend connectivity\n */\nexport function useBackendStatus() {\n const { data, loading, error, get } = useApi<{ status: string; timestamp: string }>();\n\n const checkStatus = useCallback(async () => {\n try {\n await get('/health');\n return true;\n } catch {\n return false;\n }\n }, [get]);\n\n return {\n isConnected: data?.status === 'ok',\n loading,\n error,\n checkStatus,\n lastCheck: data?.timestamp,\n };\n}","/**\n * useLocalStorage Hook - Persistent state with localStorage\n * @module @bloomneo/uikit\n * @file src/hooks/useStorage.ts\n */\n\nimport { useState, useEffect, useCallback } from 'react';\n\n/**\n * @llm-rule Storage hook for persisting state to localStorage\n * Handles SSR, JSON parsing errors, and storage unavailability\n */\n\nexport type UseLocalStorageReturn<T> = [\n value: T,\n setValue: (value: T | ((prev: T) => T)) => void,\n removeValue: () => void\n];\n\n/**\n * Hook to persist state in localStorage with automatic JSON serialization\n *\n * @param key - localStorage key\n * @param initialValue - Default value if no stored value exists\n * @returns [value, setValue, removeValue] tuple\n *\n * @example\n * ```tsx\n * const [user, setUser, removeUser] = useLocalStorage('user', null);\n * const [settings, setSettings] = useLocalStorage('settings', { theme: 'light' });\n *\n * // Update value\n * setUser({ name: 'John', email: 'john@example.com' });\n *\n * // Remove value (resets to initialValue)\n * removeUser();\n * ```\n */\nexport function useLocalStorage<T>(\n key: string,\n initialValue: T\n): UseLocalStorageReturn<T> {\n // Get from local storage then parse stored json or return initialValue\n const [storedValue, setStoredValue] = useState<T>(() => {\n if (typeof window === 'undefined') {\n // SSR: return initial value\n return initialValue;\n }\n\n try {\n const item = window.localStorage.getItem(key);\n return item ? JSON.parse(item) : initialValue;\n } catch (error) {\n console.warn(`Error reading localStorage key \"${key}\":`, error);\n return initialValue;\n }\n });\n\n // Return a wrapped version of useState's setter function that persists the new value to localStorage\n const setValue = useCallback(\n (value: T | ((prev: T) => T)) => {\n try {\n // Allow value to be a function so we have the same API as useState\n const valueToStore = value instanceof Function ? value(storedValue) : value;\n\n // Save state\n setStoredValue(valueToStore);\n\n // Save to local storage\n if (typeof window !== 'undefined') {\n window.localStorage.setItem(key, JSON.stringify(valueToStore));\n }\n } catch (error) {\n console.error(`Error setting localStorage key \"${key}\":`, error);\n }\n },\n [key, storedValue]\n );\n\n // Remove value from localStorage and reset to initial value\n const removeValue = useCallback(() => {\n try {\n setStoredValue(initialValue);\n if (typeof window !== 'undefined') {\n window.localStorage.removeItem(key);\n }\n } catch (error) {\n console.error(`Error removing localStorage key \"${key}\":`, error);\n }\n }, [key, initialValue]);\n\n // Listen for storage changes from other tabs\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === key && e.newValue !== null) {\n try {\n setStoredValue(JSON.parse(e.newValue));\n } catch (error) {\n console.warn(`Error parsing storage event for key \"${key}\":`, error);\n }\n } else if (e.key === key && e.newValue === null) {\n // Key was removed\n setStoredValue(initialValue);\n }\n };\n\n window.addEventListener('storage', handleStorageChange);\n return () => window.removeEventListener('storage', handleStorageChange);\n }, [key, initialValue]);\n\n return [storedValue, setValue, removeValue];\n}","/**\n * `useMediaQuery` — subscribe a component to a CSS media query.\n *\n * SSR-safe (returns `false` on the server, hydrates correctly on the client).\n * Cleans up its listener on unmount. No external dependencies.\n *\n * @example\n * const isWide = useMediaQuery('(min-width: 1024px)');\n * const reduced = useMediaQuery('(prefers-reduced-motion: reduce)');\n */\n\nimport { useEffect, useState } from 'react';\n\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = useState<boolean>(() => {\n if (typeof window === 'undefined') return false;\n return window.matchMedia(query).matches;\n });\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n const mql = window.matchMedia(query);\n\n // Sync once in case the initial render value is stale (StrictMode, hydration).\n setMatches(mql.matches);\n\n const handler = (e: MediaQueryListEvent) => setMatches(e.matches);\n\n // `addEventListener` is the modern API; the older `addListener` is kept for\n // Safari < 14 compatibility, which UIKit still supports.\n if (mql.addEventListener) {\n mql.addEventListener('change', handler);\n return () => mql.removeEventListener('change', handler);\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (mql as any).addListener(handler);\n return () => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (mql as any).removeListener(handler);\n };\n }, [query]);\n\n return matches;\n}\n","/**\n * `useBreakpoint` — read responsive breakpoints from React.\n *\n * Mirrors Tailwind v4's default breakpoint scale (sm/md/lg/xl/2xl) so that\n * `useBreakpoint('md')` and the `md:` Tailwind utility class always agree\n * about what \"medium\" means. If you customize Tailwind's screens, also pass\n * the override map via `BREAKPOINTS` (see below).\n *\n * @example\n * const isAtLeastMd = useBreakpoint('md'); // true when ≥ 768px\n * const isMobile = useBreakpoint('md', 'down'); // true when < 768px\n * const isExactly = useBreakpoint('lg', 'only'); // true on lg, not xl+\n */\n\nimport { useMediaQuery } from './useMediaQuery';\n\nexport const BREAKPOINTS = {\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n '2xl': 1536,\n} as const;\n\nexport type Breakpoint = keyof typeof BREAKPOINTS;\nexport type BreakpointDirection = 'up' | 'down' | 'only';\n\n/**\n * Resolve a breakpoint to a media query string. Exposed so consumers can\n * compose it with `useMediaQuery` directly when they need exotic queries.\n */\nexport function breakpointQuery(\n breakpoint: Breakpoint,\n direction: BreakpointDirection = 'up'\n): string {\n const min = BREAKPOINTS[breakpoint];\n const order: Breakpoint[] = ['sm', 'md', 'lg', 'xl', '2xl'];\n const next = order[order.indexOf(breakpoint) + 1];\n\n if (direction === 'up') return '(min-width: ' + min + 'px)';\n if (direction === 'down') return '(max-width: ' + (min - 1) + 'px)';\n // 'only': inclusive lower, exclusive upper.\n if (!next) return '(min-width: ' + min + 'px)';\n return '(min-width: ' + min + 'px) and (max-width: ' + (BREAKPOINTS[next] - 1) + 'px)';\n}\n\nexport function useBreakpoint(\n breakpoint: Breakpoint,\n direction: BreakpointDirection = 'up'\n): boolean {\n return useMediaQuery(breakpointQuery(breakpoint, direction));\n}\n\n/**\n * Returns the largest matching breakpoint, or `null` for screens narrower\n * than `sm`. Handy for switching layouts based on the active breakpoint\n * without composing five separate `useBreakpoint` calls.\n */\nexport function useActiveBreakpoint(): Breakpoint | null {\n const sm = useBreakpoint('sm');\n const md = useBreakpoint('md');\n const lg = useBreakpoint('lg');\n const xl = useBreakpoint('xl');\n const xxl = useBreakpoint('2xl');\n\n if (xxl) return '2xl';\n if (xl) return 'xl';\n if (lg) return 'lg';\n if (md) return 'md';\n if (sm) return 'sm';\n return null;\n}\n","/**\n * `useDataTable` — headless table state for @bloomneo/uikit.\n *\n * Provides the search / sort / filter / pagination state machine that the\n * built-in `<DataTable>` component uses internally, in case you want to ship\n * a custom-rendered table (treegrid, virtualized, gantt) but still benefit\n * from the standard state shape.\n *\n * @example\n * const table = useDataTable({\n * data: users,\n * columns: [{ id: 'name', accessorKey: 'name' }],\n * pageSize: 25,\n * });\n *\n * <Input value={table.search} onChange={(e) => table.setSearch(e.target.value)} />\n * {table.rows.map((row) => <CustomRow key={row.id} row={row} />)}\n */\n\nimport { useCallback, useMemo, useState } from 'react';\nimport { requireArrayProp } from '@/lib/errors';\nimport type {\n DataTableColumn,\n DataTableFilterValue,\n FilterConfig,\n SortConfig,\n} from '@/components/ui/data-table';\n\nexport interface UseDataTableOptions<T> {\n data: T[];\n columns: DataTableColumn<T>[];\n /** Initial page (0-indexed). Default: 0. */\n initialPage?: number;\n /** Page size. Default: 10. */\n pageSize?: number;\n /** Initial search string. Default: \"\". */\n initialSearch?: string;\n /** Initial sort config. */\n initialSort?: SortConfig[];\n /** Row id extractor. Default: index.toString(). */\n getRowId?: (row: T, index: number) => string;\n}\n\nexport interface UseDataTableReturn<T> {\n /** The visible rows for the current page after search/filter/sort. */\n rows: T[];\n /** All rows after search/filter/sort but before pagination. */\n filteredRows: T[];\n /** Stable string id for each visible row. */\n getRowId: (row: T, index: number) => string;\n\n // search\n search: string;\n setSearch: (value: string) => void;\n\n // sort\n sort: SortConfig[];\n setSort: (value: SortConfig[]) => void;\n toggleSort: (columnId: string) => void;\n\n // filter\n filters: FilterConfig;\n setFilter: (columnId: string, value: DataTableFilterValue | null) => void;\n clearFilters: () => void;\n\n // pagination\n page: number;\n pageSize: number;\n pageCount: number;\n setPage: (page: number) => void;\n nextPage: () => void;\n prevPage: () => void;\n canNextPage: boolean;\n canPrevPage: boolean;\n}\n\nexport function useDataTable<T>(options: UseDataTableOptions<T>): UseDataTableReturn<T> {\n const {\n columns,\n initialPage = 0,\n pageSize = 10,\n initialSearch = '',\n initialSort = [],\n getRowId = (_row, index) => String(index),\n } = options;\n const data = requireArrayProp<T>('useDataTable', 'data', options.data,\n 'Pass an array (use [] while loading instead of undefined).');\n\n const [search, setSearch] = useState(initialSearch);\n const [sort, setSort] = useState<SortConfig[]>(initialSort);\n const [filters, setFilters] = useState<FilterConfig>({});\n const [page, setPage] = useState(initialPage);\n\n const readCell = useCallback(\n (row: T, column: DataTableColumn<T>): unknown => {\n if (column.accessor) return column.accessor(row);\n if (column.accessorKey) return (row as Record<string, unknown>)[column.accessorKey as string];\n return undefined;\n },\n []\n );\n\n const filteredRows = useMemo(() => {\n let result = data;\n\n if (search) {\n const needle = search.toLowerCase();\n result = result.filter((row) =>\n columns.some((column) => {\n const value = readCell(row, column);\n return String(value ?? '').toLowerCase().includes(needle);\n })\n );\n }\n\n Object.entries(filters).forEach(([columnId, filter]) => {\n const column = columns.find((c) => c.id === columnId);\n if (!column) return;\n result = result.filter((row) => {\n const value = readCell(row, column);\n const fv = filter.value;\n switch (filter.operator) {\n case 'equals':\n return value === fv;\n case 'startsWith':\n return String(value ?? '').toLowerCase().startsWith(String(fv ?? '').toLowerCase());\n case 'endsWith':\n return String(value ?? '').toLowerCase().endsWith(String(fv ?? '').toLowerCase());\n case 'gt':\n return Number(value) > Number(fv);\n case 'lt':\n return Number(value) < Number(fv);\n case 'gte':\n return Number(value) >= Number(fv);\n case 'lte':\n return Number(value) <= Number(fv);\n case 'contains':\n default:\n return String(value ?? '').toLowerCase().includes(String(fv ?? '').toLowerCase());\n }\n });\n });\n\n if (sort.length > 0) {\n const sorted = [...result];\n sorted.sort((a, b) => {\n for (const s of sort) {\n const column = columns.find((c) => c.id === s.key);\n if (!column) continue;\n const av = readCell(a, column);\n const bv = readCell(b, column);\n let cmp = 0;\n if (column.sortFn) cmp = column.sortFn(av, bv);\n else if (column.dataType === 'number') cmp = Number(av) - Number(bv);\n else if (column.dataType === 'date') cmp = new Date(av as string).getTime() - new Date(bv as string).getTime();\n else cmp = String(av ?? '').localeCompare(String(bv ?? ''));\n if (cmp !== 0) return s.direction === 'asc' ? cmp : -cmp;\n }\n return 0;\n });\n result = sorted;\n }\n\n return result;\n }, [data, search, sort, filters, columns, readCell]);\n\n const pageCount = Math.max(1, Math.ceil(filteredRows.length / pageSize));\n const safePage = Math.min(page, pageCount - 1);\n const rows = useMemo(\n () => filteredRows.slice(safePage * pageSize, (safePage + 1) * pageSize),\n [filteredRows, safePage, pageSize]\n );\n\n const toggleSort = useCallback(\n (columnId: string) =>\n setSort((prev) => {\n const existing = prev.find((s) => s.key === columnId);\n if (!existing) return [...prev, { key: columnId, direction: 'asc' }];\n if (existing.direction === 'asc')\n return prev.map((s) => (s.key === columnId ? { ...s, direction: 'desc' } : s));\n return prev.filter((s) => s.key !== columnId);\n }),\n []\n );\n\n const setFilter = useCallback(\n (columnId: string, value: DataTableFilterValue | null) =>\n setFilters((prev) => {\n if (value === null || value === '' || value === undefined) {\n const next = { ...prev };\n delete next[columnId];\n return next;\n }\n const column = columns.find((c) => c.id === columnId);\n return {\n ...prev,\n [columnId]: { type: column?.filterType ?? 'text', value, operator: 'contains' },\n };\n }),\n [columns]\n );\n\n return {\n rows,\n filteredRows,\n getRowId,\n search,\n setSearch,\n sort,\n setSort,\n toggleSort,\n filters,\n setFilter,\n clearFilters: () => setFilters({}),\n page: safePage,\n pageSize,\n pageCount,\n setPage,\n nextPage: () => setPage((p) => Math.min(p + 1, pageCount - 1)),\n prevPage: () => setPage((p) => Math.max(p - 1, 0)),\n canNextPage: safePage < pageCount - 1,\n canPrevPage: safePage > 0,\n };\n}\n"],"names":["useApi","options","data","setData","useState","loading","setLoading","error","setError","baseURL","__vite_import_meta_env__","defaultHeaders","call","useCallback","method","endpoint","controller","timeoutId","config","url","response","result","err","errorMessage","get","post","put","deleteMethod","reset","useBackendStatus","checkStatus","useLocalStorage","key","initialValue","storedValue","setStoredValue","item","setValue","value","valueToStore","removeValue","useEffect","handleStorageChange","e","useMediaQuery","query","matches","setMatches","mql","handler","BREAKPOINTS","breakpointQuery","breakpoint","direction","min","order","next","useBreakpoint","useActiveBreakpoint","sm","md","lg","xl","useDataTable","columns","initialPage","pageSize","initialSearch","initialSort","getRowId","_row","index","requireArrayProp","search","setSearch","sort","setSort","filters","setFilters","page","setPage","readCell","row","column","filteredRows","useMemo","needle","columnId","filter","c","fv","sorted","a","b","s","av","bv","cmp","pageCount","safePage","rows","toggleSort","prev","existing","setFilter","p"],"mappings":"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"}