@classytic/fluid 0.3.4 → 0.3.6
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/dist/client/hooks.mjs +52 -26
- package/dist/dashboard.d.mts +2 -0
- package/dist/dashboard.mjs +1 -1
- package/dist/forms.mjs +21 -6
- package/package.json +1 -1
package/dist/client/hooks.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { t as useMediaQuery } from "../use-media-query-BnVNIKT4.mjs";
|
|
|
6
6
|
import { t as useScrollDetection } from "../use-scroll-detection-CsgsQYvy.mjs";
|
|
7
7
|
import { n as useDebouncedCallback, t as useDebounce } from "../use-debounce-xmZucz5e.mjs";
|
|
8
8
|
import { t as useKeyboardShortcut } from "../use-keyboard-shortcut-Bl6YM5Q7.mjs";
|
|
9
|
-
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
9
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
10
10
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
11
11
|
|
|
12
12
|
//#region src/hooks/use-base-search.ts
|
|
@@ -391,6 +391,30 @@ const storage = {
|
|
|
391
391
|
|
|
392
392
|
//#endregion
|
|
393
393
|
//#region src/hooks/use-local-storage.ts
|
|
394
|
+
function parseSnapshot(rawValue, initialValue) {
|
|
395
|
+
try {
|
|
396
|
+
const parsed = JSON.parse(rawValue);
|
|
397
|
+
if (parsed && typeof parsed === "object" && "__expiresAt" in parsed && typeof parsed.__expiresAt === "number") {
|
|
398
|
+
if (Date.now() > parsed.__expiresAt) return {
|
|
399
|
+
value: initialValue,
|
|
400
|
+
expired: true
|
|
401
|
+
};
|
|
402
|
+
return {
|
|
403
|
+
value: parsed.value ?? initialValue,
|
|
404
|
+
expired: false
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
value: parsed ?? initialValue,
|
|
409
|
+
expired: false
|
|
410
|
+
};
|
|
411
|
+
} catch {
|
|
412
|
+
return {
|
|
413
|
+
value: initialValue,
|
|
414
|
+
expired: false
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
394
418
|
/**
|
|
395
419
|
* useLocalStorage — Persist state in localStorage with type safety and optional expiry.
|
|
396
420
|
*
|
|
@@ -411,12 +435,14 @@ const storage = {
|
|
|
411
435
|
function useLocalStorage(key, initialValue, ttl) {
|
|
412
436
|
const ttlRef = useRef(ttl);
|
|
413
437
|
ttlRef.current = ttl;
|
|
438
|
+
const initialValueRef = useRef(initialValue);
|
|
439
|
+
const serializedInitialRef = useRef(() => JSON.stringify(initialValue));
|
|
414
440
|
const getSnapshot = useCallback(() => {
|
|
415
|
-
if (typeof window === "undefined") return
|
|
416
|
-
return window.localStorage.getItem(key) ??
|
|
417
|
-
}, [key
|
|
418
|
-
const getServerSnapshot = useCallback(() =>
|
|
419
|
-
useSyncExternalStore(useCallback((onStoreChange) => {
|
|
441
|
+
if (typeof window === "undefined") return serializedInitialRef.current();
|
|
442
|
+
return window.localStorage.getItem(key) ?? serializedInitialRef.current();
|
|
443
|
+
}, [key]);
|
|
444
|
+
const getServerSnapshot = useCallback(() => serializedInitialRef.current(), []);
|
|
445
|
+
const rawValue = useSyncExternalStore(useCallback((onStoreChange) => {
|
|
420
446
|
const handleStorage = (e) => {
|
|
421
447
|
if (e.key === key || e.key === null) onStoreChange();
|
|
422
448
|
};
|
|
@@ -430,30 +456,30 @@ function useLocalStorage(key, initialValue, ttl) {
|
|
|
430
456
|
window.removeEventListener("local-storage-change", handleLocal);
|
|
431
457
|
};
|
|
432
458
|
}, [key]), getSnapshot, getServerSnapshot);
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
459
|
+
const parsedSnapshot = useMemo(() => parseSnapshot(rawValue, initialValueRef.current), [rawValue]);
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
if (!parsedSnapshot.expired || typeof window === "undefined") return;
|
|
462
|
+
storage.remove(key);
|
|
463
|
+
window.dispatchEvent(new CustomEvent("local-storage-change", { detail: { key } }));
|
|
464
|
+
}, [key, parsedSnapshot.expired]);
|
|
437
465
|
const notifyChange = useCallback(() => {
|
|
438
466
|
window.dispatchEvent(new CustomEvent("local-storage-change", { detail: { key } }));
|
|
439
467
|
}, [key]);
|
|
468
|
+
const setValue = useCallback((updater) => {
|
|
469
|
+
const init = initialValueRef.current;
|
|
470
|
+
const current = parseSnapshot(typeof window === "undefined" ? JSON.stringify(init) : window.localStorage.getItem(key) ?? JSON.stringify(init), init).value;
|
|
471
|
+
const nextValue = updater instanceof Function ? updater(current) : updater;
|
|
472
|
+
storage.set(key, nextValue, ttlRef.current);
|
|
473
|
+
notifyChange();
|
|
474
|
+
}, [key, notifyChange]);
|
|
475
|
+
const removeValue = useCallback(() => {
|
|
476
|
+
storage.remove(key);
|
|
477
|
+
notifyChange();
|
|
478
|
+
}, [key, notifyChange]);
|
|
440
479
|
return [
|
|
441
|
-
value,
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const current = currentValue !== null ? currentValue : initialValue;
|
|
445
|
-
const nextValue = updater instanceof Function ? updater(current) : updater;
|
|
446
|
-
storage.set(key, nextValue, ttlRef.current);
|
|
447
|
-
notifyChange();
|
|
448
|
-
}, [
|
|
449
|
-
key,
|
|
450
|
-
initialValue,
|
|
451
|
-
notifyChange
|
|
452
|
-
]),
|
|
453
|
-
useCallback(() => {
|
|
454
|
-
storage.remove(key);
|
|
455
|
-
notifyChange();
|
|
456
|
-
}, [key, notifyChange])
|
|
480
|
+
parsedSnapshot.value,
|
|
481
|
+
setValue,
|
|
482
|
+
removeValue
|
|
457
483
|
];
|
|
458
484
|
}
|
|
459
485
|
|
package/dist/dashboard.d.mts
CHANGED
|
@@ -49,6 +49,8 @@ interface NavItem extends NavPermissions {
|
|
|
49
49
|
url: string;
|
|
50
50
|
icon?: LucideIcon;
|
|
51
51
|
isActive?: boolean;
|
|
52
|
+
/** When true, the collapsible sub-menu is expanded by default (even when not active). */
|
|
53
|
+
defaultOpen?: boolean;
|
|
52
54
|
badge?: string | number;
|
|
53
55
|
items?: NavSubItem[];
|
|
54
56
|
}
|
package/dist/dashboard.mjs
CHANGED
|
@@ -379,7 +379,7 @@ function SidebarBrand({ title, icon, href = "/", className, tooltip }) {
|
|
|
379
379
|
function SidebarNavItem({ item, onClick }) {
|
|
380
380
|
const hasSubItems = item.items && item.items.length > 0;
|
|
381
381
|
const Icon = item.icon;
|
|
382
|
-
const [open, setOpen] = useState(item.isActive ?? false);
|
|
382
|
+
const [open, setOpen] = useState(item.defaultOpen ?? item.isActive ?? false);
|
|
383
383
|
if (hasSubItems) return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Collapsible, {
|
|
384
384
|
open,
|
|
385
385
|
onOpenChange: setOpen,
|
package/dist/forms.mjs
CHANGED
|
@@ -372,6 +372,7 @@ function SelectInput({ control, items = [], groups = [], name, label, placeholde
|
|
|
372
372
|
const rawValue = field ? field.value?.toString() : localValue;
|
|
373
373
|
const handleChange = (newValue) => {
|
|
374
374
|
const actualValue = newValue === CLEAR_VALUE ? "" : newValue;
|
|
375
|
+
if ((field ? String(field.value ?? "") : localValue) === actualValue) return;
|
|
375
376
|
if (field) field.onChange(actualValue);
|
|
376
377
|
else setLocalValue(actualValue);
|
|
377
378
|
onValueChange?.(actualValue);
|
|
@@ -1475,18 +1476,32 @@ TagChoiceInput.displayName = "TagChoiceInput";
|
|
|
1475
1476
|
*/
|
|
1476
1477
|
function ComboboxInput({ control, name, label, placeholder = "Select...", emptyText = "No items found.", description, helperText, required, disabled, items = [], className, labelClassName, inputClassName, onValueChange, renderOption, value: propValue, onChange: propOnChange }) {
|
|
1477
1478
|
const descriptionText = description || helperText;
|
|
1478
|
-
const
|
|
1479
|
+
const resolvedMapRef = useRef(/* @__PURE__ */ new Map());
|
|
1480
|
+
const currentValueRef = useRef("");
|
|
1481
|
+
useEffect(() => {
|
|
1482
|
+
resolvedMapRef.current = new Map(items.map((item) => [item.value, item]));
|
|
1483
|
+
}, [items]);
|
|
1484
|
+
const onValueChangeRef = useRef(onValueChange);
|
|
1485
|
+
onValueChangeRef.current = onValueChange;
|
|
1486
|
+
const propOnChangeRef = useRef(propOnChange);
|
|
1487
|
+
propOnChangeRef.current = propOnChange;
|
|
1488
|
+
const fieldRef = useRef();
|
|
1489
|
+
const handleValueChange = useCallback((newItem) => {
|
|
1479
1490
|
const safeValue = newItem?.value || "";
|
|
1491
|
+
if (safeValue === currentValueRef.current) return;
|
|
1492
|
+
const field = fieldRef.current;
|
|
1480
1493
|
if (field) field.onChange(safeValue);
|
|
1481
|
-
else if (
|
|
1482
|
-
|
|
1483
|
-
}, [
|
|
1494
|
+
else if (propOnChangeRef.current) propOnChangeRef.current(safeValue);
|
|
1495
|
+
onValueChangeRef.current?.(safeValue);
|
|
1496
|
+
}, []);
|
|
1484
1497
|
const renderCombobox = (currentValue, field, isDisabled, fieldState) => {
|
|
1498
|
+
fieldRef.current = field;
|
|
1499
|
+
currentValueRef.current = currentValue ?? "";
|
|
1485
1500
|
const ariaDescribedBy = [descriptionText ? `${name}-description` : void 0, fieldState?.invalid ? `${name}-error` : void 0].filter(Boolean).join(" ") || void 0;
|
|
1486
1501
|
return /* @__PURE__ */ jsxs(Combobox, {
|
|
1487
1502
|
items,
|
|
1488
|
-
value: currentValue ? items.find((item) => item.value === currentValue) ?? null : null,
|
|
1489
|
-
onValueChange:
|
|
1503
|
+
value: currentValue ? resolvedMapRef.current.get(currentValue) ?? items.find((item) => item.value === currentValue) ?? null : null,
|
|
1504
|
+
onValueChange: handleValueChange,
|
|
1490
1505
|
disabled: isDisabled,
|
|
1491
1506
|
children: [/* @__PURE__ */ jsx(ComboboxInput$1, {
|
|
1492
1507
|
placeholder,
|