@diwauhris/ui 1.6.0 → 1.7.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.
- package/lib/assets/ui.css +53 -4
- package/lib/index.cjs +396 -53
- package/lib/index.mjs +392 -54
- package/lib/types/ui-library/gallery/column-manager/ColumnManager.d.ts +66 -0
- package/lib/types/ui-library/gallery/pagination/Pagination.d.ts +17 -1
- package/lib/types/ui-library/gallery/status-dot/MaskedValue.d.ts +54 -0
- package/lib/types/ui-library/gallery/status-page/StatusPage.d.ts +112 -0
- package/lib/types/ui-library/gallery/validation-summary/ValidationSummary.d.ts +23 -1
- package/lib/types/ui-library/index.d.ts +6 -0
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -1450,7 +1450,17 @@ function SegmentedControl({ options, defaultValue, value: controlledValue, onVal
|
|
|
1450
1450
|
}
|
|
1451
1451
|
//#endregion
|
|
1452
1452
|
//#region src/ui-library/gallery/validation-summary/ValidationSummary.tsx
|
|
1453
|
-
function
|
|
1453
|
+
function scrollToField(domId) {
|
|
1454
|
+
const el = document.getElementById(domId);
|
|
1455
|
+
if (!el) return;
|
|
1456
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1457
|
+
el.scrollIntoView({
|
|
1458
|
+
behavior: prefersReducedMotion ? "auto" : "smooth",
|
|
1459
|
+
block: "center"
|
|
1460
|
+
});
|
|
1461
|
+
(el.querySelector("input,select,textarea,[tabindex]") ?? el).focus();
|
|
1462
|
+
}
|
|
1463
|
+
function ValidationSummary({ issues, heading = "Review these fields before saving", fieldIds, fieldLabels, className = "" }) {
|
|
1454
1464
|
if (issues.length === 0) return null;
|
|
1455
1465
|
return /* @__PURE__ */ jsxs("div", {
|
|
1456
1466
|
role: "alert",
|
|
@@ -1460,14 +1470,24 @@ function ValidationSummary({ issues, heading = "Review these fields before savin
|
|
|
1460
1470
|
children: heading
|
|
1461
1471
|
}), /* @__PURE__ */ jsx("ul", {
|
|
1462
1472
|
className: "mt-2 list-disc space-y-1 pl-5",
|
|
1463
|
-
children: issues.map((issue) =>
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1473
|
+
children: issues.map((issue) => {
|
|
1474
|
+
const displayLabel = fieldLabels?.[issue.field] ?? issue.field;
|
|
1475
|
+
const domId = fieldIds?.[issue.field];
|
|
1476
|
+
const label = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1477
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1478
|
+
className: "font-semibold",
|
|
1479
|
+
children: [displayLabel, ":"]
|
|
1480
|
+
}),
|
|
1481
|
+
" ",
|
|
1482
|
+
issue.message
|
|
1483
|
+
] });
|
|
1484
|
+
return /* @__PURE__ */ jsx("li", { children: domId ? /* @__PURE__ */ jsx("button", {
|
|
1485
|
+
type: "button",
|
|
1486
|
+
onClick: () => scrollToField(domId),
|
|
1487
|
+
className: "underline decoration-amber-500/60 underline-offset-2 hover:decoration-amber-700 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-amber-600 rounded text-left",
|
|
1488
|
+
children: label
|
|
1489
|
+
}) : label }, `${issue.field}:${issue.message}`);
|
|
1490
|
+
})
|
|
1471
1491
|
})]
|
|
1472
1492
|
});
|
|
1473
1493
|
}
|
|
@@ -2854,53 +2874,72 @@ function getPageNumbers(current, total) {
|
|
|
2854
2874
|
pages.push(total);
|
|
2855
2875
|
return pages;
|
|
2856
2876
|
}
|
|
2857
|
-
function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination" }) {
|
|
2858
|
-
if (totalPages <= 1) return null;
|
|
2877
|
+
function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination", totalItems, pageSize, itemLabel = "items" }) {
|
|
2878
|
+
if (totalPages <= 1 && !totalItems) return null;
|
|
2879
|
+
const showRecordCount = typeof totalItems === "number" && typeof pageSize === "number";
|
|
2880
|
+
const rangeStart = showRecordCount ? (page - 1) * pageSize + 1 : null;
|
|
2881
|
+
const rangeEnd = showRecordCount ? Math.min(page * pageSize, totalItems) : null;
|
|
2859
2882
|
const pageNumbers = getPageNumbers(page, totalPages);
|
|
2860
|
-
return /* @__PURE__ */
|
|
2861
|
-
"
|
|
2862
|
-
children: /* @__PURE__ */
|
|
2863
|
-
|
|
2864
|
-
|
|
2883
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2884
|
+
className: "flex flex-col items-center gap-1.5",
|
|
2885
|
+
children: [totalPages > 1 && /* @__PURE__ */ jsx("nav", {
|
|
2886
|
+
"aria-label": ariaLabel,
|
|
2887
|
+
children: /* @__PURE__ */ jsxs("ol", {
|
|
2888
|
+
className: "list-none flex items-center gap-1",
|
|
2889
|
+
role: "list",
|
|
2890
|
+
children: [
|
|
2891
|
+
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
2892
|
+
type: "button",
|
|
2893
|
+
onClick: () => onPageChange(page - 1),
|
|
2894
|
+
disabled: page <= 1,
|
|
2895
|
+
"aria-label": "Previous page",
|
|
2896
|
+
className: "flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40",
|
|
2897
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, {
|
|
2898
|
+
size: 16,
|
|
2899
|
+
"aria-hidden": "true"
|
|
2900
|
+
})
|
|
2901
|
+
}) }),
|
|
2902
|
+
pageNumbers.map((n, idx) => n === "ellipsis" ? /* @__PURE__ */ jsx("li", {
|
|
2903
|
+
"aria-hidden": "true",
|
|
2904
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
2905
|
+
className: "flex h-9 w-9 items-center justify-center text-xs font-bold text-slate-300",
|
|
2906
|
+
children: "…"
|
|
2907
|
+
})
|
|
2908
|
+
}, `ellipsis-${idx}`) : /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
2909
|
+
type: "button",
|
|
2910
|
+
onClick: () => onPageChange(n),
|
|
2911
|
+
"aria-label": `Page ${n}`,
|
|
2912
|
+
"aria-current": n === page ? "page" : void 0,
|
|
2913
|
+
className: ["flex h-9 w-9 items-center justify-center rounded-md text-xs font-bold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30", n === page ? "bg-brand-blue text-white shadow-md shadow-brand-blue/20" : "border border-slate-200 bg-white text-slate-500 hover:border-slate-300 hover:text-slate-800"].filter(Boolean).join(" "),
|
|
2914
|
+
children: n
|
|
2915
|
+
}) }, n)),
|
|
2916
|
+
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
2917
|
+
type: "button",
|
|
2918
|
+
onClick: () => onPageChange(page + 1),
|
|
2919
|
+
disabled: page >= totalPages,
|
|
2920
|
+
"aria-label": "Next page",
|
|
2921
|
+
className: "flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40",
|
|
2922
|
+
children: /* @__PURE__ */ jsx(ChevronRight, {
|
|
2923
|
+
size: 16,
|
|
2924
|
+
"aria-hidden": "true"
|
|
2925
|
+
})
|
|
2926
|
+
}) })
|
|
2927
|
+
]
|
|
2928
|
+
})
|
|
2929
|
+
}), showRecordCount && /* @__PURE__ */ jsxs("p", {
|
|
2930
|
+
className: "text-[11px] font-bold tabular-nums text-slate-400",
|
|
2931
|
+
"aria-live": "polite",
|
|
2865
2932
|
children: [
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
"aria-hidden": "true"
|
|
2875
|
-
})
|
|
2876
|
-
}) }),
|
|
2877
|
-
pageNumbers.map((n, idx) => n === "ellipsis" ? /* @__PURE__ */ jsx("li", {
|
|
2878
|
-
"aria-hidden": "true",
|
|
2879
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
2880
|
-
className: "flex h-9 w-9 items-center justify-center text-xs font-bold text-slate-300",
|
|
2881
|
-
children: "…"
|
|
2882
|
-
})
|
|
2883
|
-
}, `ellipsis-${idx}`) : /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
2884
|
-
type: "button",
|
|
2885
|
-
onClick: () => onPageChange(n),
|
|
2886
|
-
"aria-label": `Page ${n}`,
|
|
2887
|
-
"aria-current": n === page ? "page" : void 0,
|
|
2888
|
-
className: ["flex h-9 w-9 items-center justify-center rounded-md text-xs font-bold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30", n === page ? "bg-brand-blue text-white shadow-md shadow-brand-blue/20" : "border border-slate-200 bg-white text-slate-500 hover:border-slate-300 hover:text-slate-800"].filter(Boolean).join(" "),
|
|
2889
|
-
children: n
|
|
2890
|
-
}) }, n)),
|
|
2891
|
-
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
2892
|
-
type: "button",
|
|
2893
|
-
onClick: () => onPageChange(page + 1),
|
|
2894
|
-
disabled: page >= totalPages,
|
|
2895
|
-
"aria-label": "Next page",
|
|
2896
|
-
className: "flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 disabled:cursor-not-allowed disabled:opacity-40",
|
|
2897
|
-
children: /* @__PURE__ */ jsx(ChevronRight, {
|
|
2898
|
-
size: 16,
|
|
2899
|
-
"aria-hidden": "true"
|
|
2900
|
-
})
|
|
2901
|
-
}) })
|
|
2933
|
+
"Showing ",
|
|
2934
|
+
rangeStart,
|
|
2935
|
+
"–",
|
|
2936
|
+
rangeEnd,
|
|
2937
|
+
" of ",
|
|
2938
|
+
totalItems,
|
|
2939
|
+
" ",
|
|
2940
|
+
itemLabel
|
|
2902
2941
|
]
|
|
2903
|
-
})
|
|
2942
|
+
})]
|
|
2904
2943
|
});
|
|
2905
2944
|
}
|
|
2906
2945
|
//#endregion
|
|
@@ -3821,6 +3860,100 @@ function EmptyState({ title, hint, icon, action, className = "" }) {
|
|
|
3821
3860
|
});
|
|
3822
3861
|
}
|
|
3823
3862
|
//#endregion
|
|
3863
|
+
//#region src/ui-library/gallery/status-page/StatusPage.tsx
|
|
3864
|
+
/**
|
|
3865
|
+
* Named presets for the three common status states.
|
|
3866
|
+
* Each preset supplies code, eyebrow, title, and description.
|
|
3867
|
+
* Consumer always provides actions — presets never include navigation.
|
|
3868
|
+
*
|
|
3869
|
+
* Usage:
|
|
3870
|
+
* <StatusPage
|
|
3871
|
+
* {...STATUS_PAGE_PRESETS.notFound}
|
|
3872
|
+
* primaryAction={{ label: 'Go to Dashboard', onClick: () => navigate('/dashboard') }}
|
|
3873
|
+
* secondaryAction={{ label: 'Go Back', onClick: () => navigate(-1) }}
|
|
3874
|
+
* />
|
|
3875
|
+
*/
|
|
3876
|
+
var STATUS_PAGE_PRESETS = {
|
|
3877
|
+
notFound: {
|
|
3878
|
+
code: "404",
|
|
3879
|
+
eyebrow: "404 — Not Found",
|
|
3880
|
+
title: "Page Not Found",
|
|
3881
|
+
description: "The page you're looking for doesn't exist or may have been moved."
|
|
3882
|
+
},
|
|
3883
|
+
forbidden: {
|
|
3884
|
+
code: "403",
|
|
3885
|
+
eyebrow: "403 — Access Restricted",
|
|
3886
|
+
title: "Access Restricted",
|
|
3887
|
+
description: "You don't have permission to view this page. Contact your system administrator if you need access."
|
|
3888
|
+
},
|
|
3889
|
+
serverError: {
|
|
3890
|
+
code: "500",
|
|
3891
|
+
eyebrow: "500 — Server Error",
|
|
3892
|
+
title: "Something Went Wrong",
|
|
3893
|
+
description: "We encountered an unexpected problem. Please try again or return to the dashboard."
|
|
3894
|
+
}
|
|
3895
|
+
};
|
|
3896
|
+
var BASE_ANCHOR = "inline-flex items-center justify-center transition-all duration-150 ease-out active:scale-[.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 select-none h-10 gap-2 rounded-md px-4 text-sm";
|
|
3897
|
+
var ANCHOR_PRIMARY = `${BASE_ANCHOR} font-bold bg-brand-blue text-white shadow-lg shadow-brand-blue/25 hover:bg-brand-navy hover:shadow-xl hover:shadow-brand-blue/30 focus-visible:ring-brand-blue/30`;
|
|
3898
|
+
var ANCHOR_OUTLINE = `${BASE_ANCHOR} font-semibold border border-slate-300 bg-white text-slate-700 shadow-sm hover:border-brand-blue/40 hover:bg-blue-50/40 hover:text-brand-blue focus-visible:ring-brand-blue/20`;
|
|
3899
|
+
function ActionButton({ action, variant }) {
|
|
3900
|
+
if (action.href) return /* @__PURE__ */ jsx("a", {
|
|
3901
|
+
href: action.href,
|
|
3902
|
+
className: variant === "primary" ? ANCHOR_PRIMARY : ANCHOR_OUTLINE,
|
|
3903
|
+
children: action.label
|
|
3904
|
+
});
|
|
3905
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
3906
|
+
variant,
|
|
3907
|
+
onClick: action.onClick,
|
|
3908
|
+
children: action.label
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
function StatusPage({ eyebrow, title, description, code, icon, primaryAction, secondaryAction, className = "" }) {
|
|
3912
|
+
const hasActions = primaryAction || secondaryAction;
|
|
3913
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3914
|
+
className: `relative flex min-h-screen items-center justify-center bg-surface-page px-6 py-16 overflow-hidden ${className}`,
|
|
3915
|
+
children: [code ? /* @__PURE__ */ jsx("span", {
|
|
3916
|
+
"aria-hidden": "true",
|
|
3917
|
+
className: "pointer-events-none absolute select-none font-heading font-bold text-brand-navy opacity-[0.07] text-[80px] sm:text-[120px] leading-none",
|
|
3918
|
+
children: code
|
|
3919
|
+
}) : icon ? /* @__PURE__ */ jsx("span", {
|
|
3920
|
+
"aria-hidden": "true",
|
|
3921
|
+
className: "pointer-events-none absolute select-none text-brand-navy opacity-[0.12]",
|
|
3922
|
+
style: { fontSize: 0 },
|
|
3923
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3924
|
+
className: "block [&>svg]:h-24 [&>svg]:w-24",
|
|
3925
|
+
children: icon
|
|
3926
|
+
})
|
|
3927
|
+
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
3928
|
+
className: "relative z-10 max-w-sm w-full os-slide-up",
|
|
3929
|
+
children: [
|
|
3930
|
+
eyebrow && /* @__PURE__ */ jsx("p", {
|
|
3931
|
+
className: "mb-3 text-[11px] font-bold uppercase tracking-[0.18em] text-brand-sky",
|
|
3932
|
+
children: eyebrow
|
|
3933
|
+
}),
|
|
3934
|
+
/* @__PURE__ */ jsx("h1", {
|
|
3935
|
+
className: "font-heading text-3xl font-bold text-brand-navy leading-tight",
|
|
3936
|
+
children: title
|
|
3937
|
+
}),
|
|
3938
|
+
/* @__PURE__ */ jsx("p", {
|
|
3939
|
+
className: "mt-3 text-sm font-medium text-slate-500 leading-relaxed",
|
|
3940
|
+
children: description
|
|
3941
|
+
}),
|
|
3942
|
+
hasActions && /* @__PURE__ */ jsxs("div", {
|
|
3943
|
+
className: "mt-8 flex flex-wrap items-center gap-3",
|
|
3944
|
+
children: [primaryAction && /* @__PURE__ */ jsx(ActionButton, {
|
|
3945
|
+
action: primaryAction,
|
|
3946
|
+
variant: "primary"
|
|
3947
|
+
}), secondaryAction && /* @__PURE__ */ jsx(ActionButton, {
|
|
3948
|
+
action: secondaryAction,
|
|
3949
|
+
variant: "outline"
|
|
3950
|
+
})]
|
|
3951
|
+
})
|
|
3952
|
+
]
|
|
3953
|
+
})]
|
|
3954
|
+
});
|
|
3955
|
+
}
|
|
3956
|
+
//#endregion
|
|
3824
3957
|
//#region src/ui-library/gallery/employee-card/EmployeeCard.tsx
|
|
3825
3958
|
function EmployeeCard({ name, role, avatarSrc, status, meta, actions, className = "" }) {
|
|
3826
3959
|
return /* @__PURE__ */ jsxs("article", {
|
|
@@ -7409,6 +7542,211 @@ function StatusDot({ status, label, className = "" }) {
|
|
|
7409
7542
|
});
|
|
7410
7543
|
}
|
|
7411
7544
|
//#endregion
|
|
7545
|
+
//#region src/ui-library/gallery/column-manager/ColumnManager.tsx
|
|
7546
|
+
/**
|
|
7547
|
+
* ColumnManager — Design System Component
|
|
7548
|
+
*
|
|
7549
|
+
* A dropdown panel for managing table column visibility and order.
|
|
7550
|
+
* Provides checkboxes to show/hide individual columns and up/down
|
|
7551
|
+
* buttons to reorder them. Includes a reset action.
|
|
7552
|
+
*
|
|
7553
|
+
* Purely presentational — all state is owned by the caller.
|
|
7554
|
+
*
|
|
7555
|
+
* Usage:
|
|
7556
|
+
* const [hidden, setHidden] = useState<string[]>([]);
|
|
7557
|
+
* const [order, setOrder] = useState<string[] | null>(null);
|
|
7558
|
+
*
|
|
7559
|
+
* <ColumnManager
|
|
7560
|
+
* columns={[{ key: 'name', label: 'Name' }, { key: 'dept', label: 'Department' }]}
|
|
7561
|
+
* hiddenColumnKeys={hidden}
|
|
7562
|
+
* columnOrder={order}
|
|
7563
|
+
* onHiddenColumnKeysChange={setHidden}
|
|
7564
|
+
* onColumnOrderChange={setOrder}
|
|
7565
|
+
* onReset={() => { setHidden([]); setOrder(null); }}
|
|
7566
|
+
* />
|
|
7567
|
+
*
|
|
7568
|
+
* Accessibility:
|
|
7569
|
+
* - Trigger button has aria-expanded and aria-controls.
|
|
7570
|
+
* - Panel has role="region" and aria-label.
|
|
7571
|
+
* - Each checkbox is labelled via htmlFor + id.
|
|
7572
|
+
* - Reorder buttons have descriptive aria-labels.
|
|
7573
|
+
* - Panel closes on Escape, focus returns to the trigger.
|
|
7574
|
+
*/
|
|
7575
|
+
/**
|
|
7576
|
+
* Apply a stored column order to the runtime column array.
|
|
7577
|
+
*
|
|
7578
|
+
* Rules:
|
|
7579
|
+
* - Keys in `order` that don't exist in `columns` are silently ignored
|
|
7580
|
+
* (forward compatibility when columns are removed).
|
|
7581
|
+
* - Columns not present in `order` are appended at the end in their
|
|
7582
|
+
* original relative order (forward compatibility when columns are added).
|
|
7583
|
+
* - When `order` is null the original array is returned unchanged.
|
|
7584
|
+
*/
|
|
7585
|
+
function applyColumnOrder(columns, order) {
|
|
7586
|
+
if (!order) return columns;
|
|
7587
|
+
const byKey = new Map(columns.map((c) => [c.key, c]));
|
|
7588
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7589
|
+
const result = [];
|
|
7590
|
+
for (const key of order) {
|
|
7591
|
+
const col = byKey.get(key);
|
|
7592
|
+
if (col) {
|
|
7593
|
+
result.push(col);
|
|
7594
|
+
seen.add(key);
|
|
7595
|
+
}
|
|
7596
|
+
}
|
|
7597
|
+
for (const col of columns) if (!seen.has(col.key)) result.push(col);
|
|
7598
|
+
return result;
|
|
7599
|
+
}
|
|
7600
|
+
function ColumnManager({ columns, hiddenColumnKeys, columnOrder, onHiddenColumnKeysChange, onColumnOrderChange, onReset, triggerLabel = "Columns" }) {
|
|
7601
|
+
const [open, setOpen] = useState(false);
|
|
7602
|
+
const triggerRef = useRef(null);
|
|
7603
|
+
const panelRef = useRef(null);
|
|
7604
|
+
const panelId = "cm-panel";
|
|
7605
|
+
const orderedColumns = applyColumnOrder(columns, columnOrder);
|
|
7606
|
+
const hiddenSet = new Set(hiddenColumnKeys);
|
|
7607
|
+
const hiddenCount = hiddenColumnKeys.length;
|
|
7608
|
+
useEffect(() => {
|
|
7609
|
+
if (!open) return;
|
|
7610
|
+
const handlePointerDown = (e) => {
|
|
7611
|
+
if (triggerRef.current?.contains(e.target) || panelRef.current?.contains(e.target)) return;
|
|
7612
|
+
setOpen(false);
|
|
7613
|
+
};
|
|
7614
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
7615
|
+
return () => document.removeEventListener("pointerdown", handlePointerDown);
|
|
7616
|
+
}, [open]);
|
|
7617
|
+
useEffect(() => {
|
|
7618
|
+
if (!open) return;
|
|
7619
|
+
const handleKeyDown = (e) => {
|
|
7620
|
+
if (e.key === "Escape") {
|
|
7621
|
+
setOpen(false);
|
|
7622
|
+
triggerRef.current?.focus();
|
|
7623
|
+
}
|
|
7624
|
+
};
|
|
7625
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
7626
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
7627
|
+
}, [open]);
|
|
7628
|
+
function toggleColumn(key) {
|
|
7629
|
+
onHiddenColumnKeysChange(hiddenSet.has(key) ? hiddenColumnKeys.filter((k) => k !== key) : [...hiddenColumnKeys, key]);
|
|
7630
|
+
}
|
|
7631
|
+
function moveUp(index) {
|
|
7632
|
+
if (index === 0) return;
|
|
7633
|
+
const next = [...orderedColumns];
|
|
7634
|
+
[next[index - 1], next[index]] = [next[index], next[index - 1]];
|
|
7635
|
+
onColumnOrderChange(next.map((c) => c.key));
|
|
7636
|
+
}
|
|
7637
|
+
function moveDown(index) {
|
|
7638
|
+
if (index === orderedColumns.length - 1) return;
|
|
7639
|
+
const next = [...orderedColumns];
|
|
7640
|
+
[next[index], next[index + 1]] = [next[index + 1], next[index]];
|
|
7641
|
+
onColumnOrderChange(next.map((c) => c.key));
|
|
7642
|
+
}
|
|
7643
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7644
|
+
className: "relative inline-block",
|
|
7645
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
7646
|
+
ref: triggerRef,
|
|
7647
|
+
type: "button",
|
|
7648
|
+
"aria-expanded": open,
|
|
7649
|
+
"aria-controls": open ? panelId : void 0,
|
|
7650
|
+
onClick: () => setOpen((v) => !v),
|
|
7651
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-slate-200 bg-white px-3 py-2 text-xs font-bold text-slate-600 shadow-sm transition hover:border-slate-300 hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
7652
|
+
children: [
|
|
7653
|
+
triggerLabel,
|
|
7654
|
+
hiddenCount > 0 && /* @__PURE__ */ jsxs("span", {
|
|
7655
|
+
className: "rounded-full bg-brand-blue/10 px-1.5 py-0.5 font-bold text-brand-blue",
|
|
7656
|
+
children: [hiddenCount, " hidden"]
|
|
7657
|
+
}),
|
|
7658
|
+
/* @__PURE__ */ jsx(ChevronDown, {
|
|
7659
|
+
size: 13,
|
|
7660
|
+
className: `transition-transform ${open ? "rotate-180" : ""}`,
|
|
7661
|
+
"aria-hidden": "true"
|
|
7662
|
+
})
|
|
7663
|
+
]
|
|
7664
|
+
}), open && /* @__PURE__ */ jsxs("div", {
|
|
7665
|
+
id: panelId,
|
|
7666
|
+
ref: panelRef,
|
|
7667
|
+
role: "region",
|
|
7668
|
+
"aria-label": "Manage columns",
|
|
7669
|
+
className: "absolute right-0 top-full z-30 mt-1 flex w-64 flex-col overflow-hidden rounded-xl border border-slate-200 bg-white shadow-xl",
|
|
7670
|
+
style: { maxHeight: 400 },
|
|
7671
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
7672
|
+
className: "flex shrink-0 items-center justify-between border-b border-slate-100 px-4 py-2.5",
|
|
7673
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
7674
|
+
className: "text-xs font-bold text-slate-700",
|
|
7675
|
+
children: "Columns"
|
|
7676
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7677
|
+
type: "button",
|
|
7678
|
+
onClick: () => {
|
|
7679
|
+
onReset();
|
|
7680
|
+
setOpen(false);
|
|
7681
|
+
},
|
|
7682
|
+
className: "rounded-md px-2 py-1 text-[11px] font-bold text-slate-400 hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-brand-blue/30 transition",
|
|
7683
|
+
children: "Reset"
|
|
7684
|
+
})]
|
|
7685
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
7686
|
+
className: "overflow-y-auto",
|
|
7687
|
+
role: "list",
|
|
7688
|
+
children: orderedColumns.map((col, idx) => {
|
|
7689
|
+
const visible = !hiddenSet.has(col.key);
|
|
7690
|
+
const checkboxId = `cm-col-${col.key}`;
|
|
7691
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
7692
|
+
className: "flex items-center gap-2 border-b border-slate-50 px-3 py-2 last:border-0",
|
|
7693
|
+
children: [
|
|
7694
|
+
/* @__PURE__ */ jsx("input", {
|
|
7695
|
+
type: "checkbox",
|
|
7696
|
+
id: checkboxId,
|
|
7697
|
+
checked: visible,
|
|
7698
|
+
onChange: () => toggleColumn(col.key),
|
|
7699
|
+
className: "h-3.5 w-3.5 rounded accent-brand-blue focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-brand-blue/30"
|
|
7700
|
+
}),
|
|
7701
|
+
/* @__PURE__ */ jsx("label", {
|
|
7702
|
+
htmlFor: checkboxId,
|
|
7703
|
+
className: `flex-1 cursor-pointer text-xs font-medium ${visible ? "text-slate-700" : "text-slate-400"}`,
|
|
7704
|
+
children: col.label
|
|
7705
|
+
}),
|
|
7706
|
+
/* @__PURE__ */ jsxs("div", {
|
|
7707
|
+
className: "flex shrink-0 gap-0.5",
|
|
7708
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
7709
|
+
type: "button",
|
|
7710
|
+
"aria-label": `Move ${col.label} up`,
|
|
7711
|
+
disabled: idx === 0,
|
|
7712
|
+
onClick: () => moveUp(idx),
|
|
7713
|
+
className: "rounded p-1 text-slate-300 hover:bg-slate-100 hover:text-slate-600 disabled:cursor-not-allowed disabled:opacity-30 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-brand-blue/30 transition",
|
|
7714
|
+
children: "▲"
|
|
7715
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7716
|
+
type: "button",
|
|
7717
|
+
"aria-label": `Move ${col.label} down`,
|
|
7718
|
+
disabled: idx === orderedColumns.length - 1,
|
|
7719
|
+
onClick: () => moveDown(idx),
|
|
7720
|
+
className: "rounded p-1 text-slate-300 hover:bg-slate-100 hover:text-slate-600 disabled:cursor-not-allowed disabled:opacity-30 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-brand-blue/30 transition",
|
|
7721
|
+
children: "▼"
|
|
7722
|
+
})]
|
|
7723
|
+
})
|
|
7724
|
+
]
|
|
7725
|
+
}, col.key);
|
|
7726
|
+
})
|
|
7727
|
+
})]
|
|
7728
|
+
})]
|
|
7729
|
+
});
|
|
7730
|
+
}
|
|
7731
|
+
//#endregion
|
|
7732
|
+
//#region src/ui-library/gallery/status-dot/MaskedValue.tsx
|
|
7733
|
+
function MaskedValue({ value, masked, maskText = "••••• •••••", maskLabel = "Value restricted", className = "" }) {
|
|
7734
|
+
if (!masked) return className ? /* @__PURE__ */ jsx("span", {
|
|
7735
|
+
className,
|
|
7736
|
+
children: value
|
|
7737
|
+
}) : /* @__PURE__ */ jsx(Fragment, { children: value });
|
|
7738
|
+
return /* @__PURE__ */ jsx("span", {
|
|
7739
|
+
"aria-label": maskLabel,
|
|
7740
|
+
title: maskLabel,
|
|
7741
|
+
className: [
|
|
7742
|
+
"inline-flex select-none items-center rounded-md bg-slate-100 px-2 py-0.5",
|
|
7743
|
+
"font-mono text-xs font-medium text-slate-400 tracking-wider",
|
|
7744
|
+
className
|
|
7745
|
+
].filter(Boolean).join(" "),
|
|
7746
|
+
children: maskText
|
|
7747
|
+
});
|
|
7748
|
+
}
|
|
7749
|
+
//#endregion
|
|
7412
7750
|
//#region src/ui-library/gallery/status-actions/statusActions.ts
|
|
7413
7751
|
/**
|
|
7414
7752
|
* statusActions — Gallery-native lifecycle transition utilities.
|
|
@@ -7592,4 +7930,4 @@ var shadow = {
|
|
|
7592
7930
|
overlay: "shadow-xl"
|
|
7593
7931
|
};
|
|
7594
7932
|
//#endregion
|
|
7595
|
-
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActivityFeed, Alert, ApprovalTimeline, AreaChart, Avatar, Badge, BarChart, BarList, Breadcrumb, Button, CHART_PALETTE, Calendar, Card, CardContent, CardFooter, CardHeader, Checkbox, Combobox, CommandPalette, ConfirmAction, ConfirmDialog, DatePicker, DescriptionItem, DescriptionList, Dialog, DialogBody, DialogFooter, Divider, Drawer, Dropdown, EmployeeCard, EmptyState, ErrorBanner, Field, FieldContext, FieldInput, FileUpload, FilterChip, Heatmap, IconButton, Input, IsoCubeBlock, Kanban, LineChart, Menu, Modal, OrgChart, OrgUnitTree, OtpInput, PafTemplate, PageHeader, Pagination, PayslipTemplate, PermissionMatrix, Popover, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioItem, ReportTemplate, Section, SegmentedControl, Select, SidebarGroup, SidebarItem, SidebarNav, SidebarSection, Skeleton, SkeletonAvatar, SkeletonCard, SkeletonTableRow, SkeletonText, Spinner, Statistic, StatusBadge, StatusDot, Stepper, Switch, Tab, TabList, TabPanel, TabPanels, Tabs, Textarea, TimekeepingTemplate, Timeline, TimelineEmptyState, TimelineFooter, TimelineItem, TimelineValueCard, Toaster, Toolbar, Tooltip, TreeView, ValidationSummary, colors_exports as colors, dimension_exports as dimension, radius_exports as radius, shadow_exports as shadow, statusMenuItems, toast, typography_exports as typography, useFieldContext };
|
|
7933
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActivityFeed, Alert, ApprovalTimeline, AreaChart, Avatar, Badge, BarChart, BarList, Breadcrumb, Button, CHART_PALETTE, Calendar, Card, CardContent, CardFooter, CardHeader, Checkbox, ColumnManager, Combobox, CommandPalette, ConfirmAction, ConfirmDialog, DatePicker, DescriptionItem, DescriptionList, Dialog, DialogBody, DialogFooter, Divider, Drawer, Dropdown, EmployeeCard, EmptyState, ErrorBanner, Field, FieldContext, FieldInput, FileUpload, FilterChip, Heatmap, IconButton, Input, IsoCubeBlock, Kanban, LineChart, MaskedValue, Menu, Modal, OrgChart, OrgUnitTree, OtpInput, PafTemplate, PageHeader, Pagination, PayslipTemplate, PermissionMatrix, Popover, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioItem, ReportTemplate, STATUS_PAGE_PRESETS, Section, SegmentedControl, Select, SidebarGroup, SidebarItem, SidebarNav, SidebarSection, Skeleton, SkeletonAvatar, SkeletonCard, SkeletonTableRow, SkeletonText, Spinner, Statistic, StatusBadge, StatusDot, StatusPage, Stepper, Switch, Tab, TabList, TabPanel, TabPanels, Tabs, Textarea, TimekeepingTemplate, Timeline, TimelineEmptyState, TimelineFooter, TimelineItem, TimelineValueCard, Toaster, Toolbar, Tooltip, TreeView, ValidationSummary, applyColumnOrder, colors_exports as colors, dimension_exports as dimension, radius_exports as radius, shadow_exports as shadow, statusMenuItems, toast, typography_exports as typography, useFieldContext };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ColumnManager — Design System Component
|
|
3
|
+
*
|
|
4
|
+
* A dropdown panel for managing table column visibility and order.
|
|
5
|
+
* Provides checkboxes to show/hide individual columns and up/down
|
|
6
|
+
* buttons to reorder them. Includes a reset action.
|
|
7
|
+
*
|
|
8
|
+
* Purely presentational — all state is owned by the caller.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const [hidden, setHidden] = useState<string[]>([]);
|
|
12
|
+
* const [order, setOrder] = useState<string[] | null>(null);
|
|
13
|
+
*
|
|
14
|
+
* <ColumnManager
|
|
15
|
+
* columns={[{ key: 'name', label: 'Name' }, { key: 'dept', label: 'Department' }]}
|
|
16
|
+
* hiddenColumnKeys={hidden}
|
|
17
|
+
* columnOrder={order}
|
|
18
|
+
* onHiddenColumnKeysChange={setHidden}
|
|
19
|
+
* onColumnOrderChange={setOrder}
|
|
20
|
+
* onReset={() => { setHidden([]); setOrder(null); }}
|
|
21
|
+
* />
|
|
22
|
+
*
|
|
23
|
+
* Accessibility:
|
|
24
|
+
* - Trigger button has aria-expanded and aria-controls.
|
|
25
|
+
* - Panel has role="region" and aria-label.
|
|
26
|
+
* - Each checkbox is labelled via htmlFor + id.
|
|
27
|
+
* - Reorder buttons have descriptive aria-labels.
|
|
28
|
+
* - Panel closes on Escape, focus returns to the trigger.
|
|
29
|
+
*/
|
|
30
|
+
export interface ColumnManagerColumn {
|
|
31
|
+
/** Unique key matching the data column identifier. */
|
|
32
|
+
key: string;
|
|
33
|
+
/** Human-readable column label shown in the panel. */
|
|
34
|
+
label: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ColumnManagerProps {
|
|
37
|
+
/** All available columns in their default order. */
|
|
38
|
+
columns: ColumnManagerColumn[];
|
|
39
|
+
/** Keys of currently hidden columns. */
|
|
40
|
+
hiddenColumnKeys: string[];
|
|
41
|
+
/**
|
|
42
|
+
* Current column order as an array of keys.
|
|
43
|
+
* Pass null to use the default order from `columns`.
|
|
44
|
+
*/
|
|
45
|
+
columnOrder: string[] | null;
|
|
46
|
+
/** Called with the new hidden-keys array when visibility changes. */
|
|
47
|
+
onHiddenColumnKeysChange: (keys: string[]) => void;
|
|
48
|
+
/** Called with the new key order when a column is moved. */
|
|
49
|
+
onColumnOrderChange: (order: string[]) => void;
|
|
50
|
+
/** Called when the user clicks Reset — caller restores defaults. */
|
|
51
|
+
onReset: () => void;
|
|
52
|
+
/** Label for the trigger button. Default: "Columns" */
|
|
53
|
+
triggerLabel?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Apply a stored column order to the runtime column array.
|
|
57
|
+
*
|
|
58
|
+
* Rules:
|
|
59
|
+
* - Keys in `order` that don't exist in `columns` are silently ignored
|
|
60
|
+
* (forward compatibility when columns are removed).
|
|
61
|
+
* - Columns not present in `order` are appended at the end in their
|
|
62
|
+
* original relative order (forward compatibility when columns are added).
|
|
63
|
+
* - When `order` is null the original array is returned unchanged.
|
|
64
|
+
*/
|
|
65
|
+
export declare function applyColumnOrder(columns: ColumnManagerColumn[], order: string[] | null): ColumnManagerColumn[];
|
|
66
|
+
export declare function ColumnManager({ columns, hiddenColumnKeys, columnOrder, onHiddenColumnKeysChange, onColumnOrderChange, onReset, triggerLabel, }: ColumnManagerProps): import("react").JSX.Element;
|
|
@@ -23,5 +23,21 @@ export interface PaginationProps {
|
|
|
23
23
|
onPageChange: (page: number) => void;
|
|
24
24
|
/** Accessible label for the navigation landmark (default: "Pagination") */
|
|
25
25
|
ariaLabel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Total number of records across all pages.
|
|
28
|
+
* When provided alongside `pageSize`, renders a "Showing X–Y of Z {itemLabel}" line.
|
|
29
|
+
*/
|
|
30
|
+
totalItems?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Number of records per page.
|
|
33
|
+
* Required together with `totalItems` to compute the Showing X–Y range.
|
|
34
|
+
*/
|
|
35
|
+
pageSize?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Label for the item type shown in the record count footer.
|
|
38
|
+
* Example: "employees", "records", "results"
|
|
39
|
+
* Default: "items"
|
|
40
|
+
*/
|
|
41
|
+
itemLabel?: string;
|
|
26
42
|
}
|
|
27
|
-
export declare function Pagination({ page, totalPages, onPageChange, ariaLabel, }: PaginationProps): import("react").JSX.Element | null;
|
|
43
|
+
export declare function Pagination({ page, totalPages, onPageChange, ariaLabel, totalItems, pageSize, itemLabel, }: PaginationProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MaskedValue — Design System Component
|
|
3
|
+
*
|
|
4
|
+
* Renders a value or a redacted placeholder based on a boolean condition.
|
|
5
|
+
* Used when a value exists but should be hidden from the current viewer
|
|
6
|
+
* (e.g. salary data only visible to users with the appropriate permission).
|
|
7
|
+
*
|
|
8
|
+
* The masking decision is made by the caller — MaskedValue is purely
|
|
9
|
+
* presentational. It does not know about permissions, roles, or data fetching.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* // Show real value
|
|
13
|
+
* <MaskedValue value={formatCurrency(salary)} masked={false} />
|
|
14
|
+
*
|
|
15
|
+
* // Hide value — consumer decides based on permission/context
|
|
16
|
+
* <MaskedValue
|
|
17
|
+
* value={formatCurrency(salary)}
|
|
18
|
+
* masked={!canViewSalary}
|
|
19
|
+
* maskLabel="Salary is restricted"
|
|
20
|
+
* />
|
|
21
|
+
*
|
|
22
|
+
* Accessibility:
|
|
23
|
+
* - When masked, the placeholder pill has aria-label={maskLabel} so
|
|
24
|
+
* screen readers announce the reason rather than "••••• •••••".
|
|
25
|
+
* - When unmasked, renders children directly — no extra ARIA.
|
|
26
|
+
*/
|
|
27
|
+
import type { ReactNode } from 'react';
|
|
28
|
+
export interface MaskedValueProps {
|
|
29
|
+
/**
|
|
30
|
+
* The real value to display when not masked.
|
|
31
|
+
* Accepts any ReactNode — text, formatted currency, a Badge, etc.
|
|
32
|
+
*/
|
|
33
|
+
value: ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* When true, renders the mask placeholder instead of value.
|
|
36
|
+
* When false, renders value directly.
|
|
37
|
+
*/
|
|
38
|
+
masked: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Text shown in the masked placeholder pill.
|
|
41
|
+
* Default: '••••• •••••'
|
|
42
|
+
*/
|
|
43
|
+
maskText?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Accessible label for the masked placeholder — announces WHY
|
|
46
|
+
* the value is hidden, not just that it is hidden.
|
|
47
|
+
* Example: "Salary restricted — requires elevated access"
|
|
48
|
+
* Default: "Value restricted"
|
|
49
|
+
*/
|
|
50
|
+
maskLabel?: string;
|
|
51
|
+
/** Additional class on the root element (both masked and unmasked states). */
|
|
52
|
+
className?: string;
|
|
53
|
+
}
|
|
54
|
+
export declare function MaskedValue({ value, masked, maskText, maskLabel, className, }: MaskedValueProps): import("react").JSX.Element;
|