@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 CHANGED
@@ -482,6 +482,11 @@
482
482
  margin: auto;
483
483
  }
484
484
 
485
+ .-mx-5 {
486
+ margin-left: -1.25rem;
487
+ margin-right: -1.25rem;
488
+ }
489
+
485
490
  .mx-1 {
486
491
  margin-left: 0.25rem;
487
492
  margin-right: 0.25rem;
@@ -510,6 +515,10 @@
510
515
  margin-top: -0.125rem;
511
516
  }
512
517
 
518
+ .-mt-4 {
519
+ margin-top: -1rem;
520
+ }
521
+
513
522
  .mb-0\.5 {
514
523
  margin-bottom: 0.125rem;
515
524
  }
@@ -610,10 +619,6 @@
610
619
  margin-top: 2.5rem;
611
620
  }
612
621
 
613
- .mt-12 {
614
- margin-top: 3rem;
615
- }
616
-
617
622
  .mt-2 {
618
623
  margin-top: 0.5rem;
619
624
  }
@@ -3310,6 +3315,10 @@
3310
3315
  line-height: 1;
3311
3316
  }
3312
3317
 
3318
+ .text-\[100px\] {
3319
+ font-size: 100px;
3320
+ }
3321
+
3313
3322
  .text-\[10px\] {
3314
3323
  font-size: 10px;
3315
3324
  }
@@ -3350,6 +3359,10 @@
3350
3359
  font-size: 7px;
3351
3360
  }
3352
3361
 
3362
+ .text-\[80px\] {
3363
+ font-size: 80px;
3364
+ }
3365
+
3353
3366
  .text-\[8px\] {
3354
3367
  font-size: 8px;
3355
3368
  }
@@ -3798,6 +3811,14 @@
3798
3811
  text-decoration-line: line-through;
3799
3812
  }
3800
3813
 
3814
+ .decoration-amber-500\/60 {
3815
+ text-decoration-color: rgb(245 158 11 / 0.6);
3816
+ }
3817
+
3818
+ .underline-offset-2 {
3819
+ text-underline-offset: 2px;
3820
+ }
3821
+
3801
3822
  .antialiased {
3802
3823
  -webkit-font-smoothing: antialiased;
3803
3824
  -moz-osx-font-smoothing: grayscale;
@@ -3813,6 +3834,10 @@
3813
3834
  color: rgb(148 163 184 / var(--tw-placeholder-opacity, 1));
3814
3835
  }
3815
3836
 
3837
+ .accent-brand-blue {
3838
+ accent-color: #034EA2;
3839
+ }
3840
+
3816
3841
  .opacity-0 {
3817
3842
  opacity: 0;
3818
3843
  }
@@ -3853,6 +3878,14 @@
3853
3878
  opacity: 0.8;
3854
3879
  }
3855
3880
 
3881
+ .opacity-\[0\.07\] {
3882
+ opacity: 0.07;
3883
+ }
3884
+
3885
+ .opacity-\[0\.12\] {
3886
+ opacity: 0.12;
3887
+ }
3888
+
3856
3889
  .shadow {
3857
3890
  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
3858
3891
  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
@@ -4673,6 +4706,9 @@ body {
4673
4706
  .hover\:underline:hover {
4674
4707
  text-decoration-line: underline;
4675
4708
  }
4709
+ .hover\:decoration-amber-700:hover {
4710
+ text-decoration-color: #b45309;
4711
+ }
4676
4712
  .hover\:opacity-100:hover {
4677
4713
  opacity: 1;
4678
4714
  }
@@ -4784,6 +4820,10 @@ body {
4784
4820
  --tw-ring-opacity: 1;
4785
4821
  --tw-ring-color: rgb(253 230 138 / var(--tw-ring-opacity, 1));
4786
4822
  }
4823
+ .focus-visible\:ring-amber-600:focus-visible {
4824
+ --tw-ring-opacity: 1;
4825
+ --tw-ring-color: rgb(217 119 6 / var(--tw-ring-opacity, 1));
4826
+ }
4787
4827
  .focus-visible\:ring-brand-blue\/15:focus-visible {
4788
4828
  --tw-ring-color: rgb(3 78 162 / 0.15);
4789
4829
  }
@@ -5052,6 +5092,9 @@ body {
5052
5092
  padding-left: 2rem;
5053
5093
  padding-right: 2rem;
5054
5094
  }
5095
+ .sm\:text-\[120px\] {
5096
+ font-size: 120px;
5097
+ }
5055
5098
  }
5056
5099
  @media (min-width: 768px) {
5057
5100
  .md\:relative {
@@ -5180,3 +5223,9 @@ body {
5180
5223
  grid-template-columns: 340px minmax(0,1fr);
5181
5224
  }
5182
5225
  }
5226
+ .\[\&\>svg\]\:h-24>svg {
5227
+ height: 6rem;
5228
+ }
5229
+ .\[\&\>svg\]\:w-24>svg {
5230
+ width: 6rem;
5231
+ }
package/lib/index.cjs CHANGED
@@ -1475,7 +1475,17 @@ function SegmentedControl({ options, defaultValue, value: controlledValue, onVal
1475
1475
  }
1476
1476
  //#endregion
1477
1477
  //#region src/ui-library/gallery/validation-summary/ValidationSummary.tsx
1478
- function ValidationSummary({ issues, heading = "Review these fields before saving", className = "" }) {
1478
+ function scrollToField(domId) {
1479
+ const el = document.getElementById(domId);
1480
+ if (!el) return;
1481
+ const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1482
+ el.scrollIntoView({
1483
+ behavior: prefersReducedMotion ? "auto" : "smooth",
1484
+ block: "center"
1485
+ });
1486
+ (el.querySelector("input,select,textarea,[tabindex]") ?? el).focus();
1487
+ }
1488
+ function ValidationSummary({ issues, heading = "Review these fields before saving", fieldIds, fieldLabels, className = "" }) {
1479
1489
  if (issues.length === 0) return null;
1480
1490
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1481
1491
  role: "alert",
@@ -1485,14 +1495,24 @@ function ValidationSummary({ issues, heading = "Review these fields before savin
1485
1495
  children: heading
1486
1496
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1487
1497
  className: "mt-2 list-disc space-y-1 pl-5",
1488
- children: issues.map((issue) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
1489
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1490
- className: "font-semibold",
1491
- children: [issue.field, ":"]
1492
- }),
1493
- " ",
1494
- issue.message
1495
- ] }, `${issue.field}:${issue.message}`))
1498
+ children: issues.map((issue) => {
1499
+ const displayLabel = fieldLabels?.[issue.field] ?? issue.field;
1500
+ const domId = fieldIds?.[issue.field];
1501
+ const label = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1503
+ className: "font-semibold",
1504
+ children: [displayLabel, ":"]
1505
+ }),
1506
+ " ",
1507
+ issue.message
1508
+ ] });
1509
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: domId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1510
+ type: "button",
1511
+ onClick: () => scrollToField(domId),
1512
+ 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",
1513
+ children: label
1514
+ }) : label }, `${issue.field}:${issue.message}`);
1515
+ })
1496
1516
  })]
1497
1517
  });
1498
1518
  }
@@ -2879,53 +2899,72 @@ function getPageNumbers(current, total) {
2879
2899
  pages.push(total);
2880
2900
  return pages;
2881
2901
  }
2882
- function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination" }) {
2883
- if (totalPages <= 1) return null;
2902
+ function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination", totalItems, pageSize, itemLabel = "items" }) {
2903
+ if (totalPages <= 1 && !totalItems) return null;
2904
+ const showRecordCount = typeof totalItems === "number" && typeof pageSize === "number";
2905
+ const rangeStart = showRecordCount ? (page - 1) * pageSize + 1 : null;
2906
+ const rangeEnd = showRecordCount ? Math.min(page * pageSize, totalItems) : null;
2884
2907
  const pageNumbers = getPageNumbers(page, totalPages);
2885
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
2886
- "aria-label": ariaLabel,
2887
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
2888
- className: "list-none flex items-center gap-1",
2889
- role: "list",
2908
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2909
+ className: "flex flex-col items-center gap-1.5",
2910
+ children: [totalPages > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
2911
+ "aria-label": ariaLabel,
2912
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
2913
+ className: "list-none flex items-center gap-1",
2914
+ role: "list",
2915
+ children: [
2916
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2917
+ type: "button",
2918
+ onClick: () => onPageChange(page - 1),
2919
+ disabled: page <= 1,
2920
+ "aria-label": "Previous 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__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, {
2923
+ size: 16,
2924
+ "aria-hidden": "true"
2925
+ })
2926
+ }) }),
2927
+ pageNumbers.map((n, idx) => n === "ellipsis" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
2928
+ "aria-hidden": "true",
2929
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2930
+ className: "flex h-9 w-9 items-center justify-center text-xs font-bold text-slate-300",
2931
+ children: "…"
2932
+ })
2933
+ }, `ellipsis-${idx}`) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2934
+ type: "button",
2935
+ onClick: () => onPageChange(n),
2936
+ "aria-label": `Page ${n}`,
2937
+ "aria-current": n === page ? "page" : void 0,
2938
+ 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(" "),
2939
+ children: n
2940
+ }) }, n)),
2941
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2942
+ type: "button",
2943
+ onClick: () => onPageChange(page + 1),
2944
+ disabled: page >= totalPages,
2945
+ "aria-label": "Next page",
2946
+ 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",
2947
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
2948
+ size: 16,
2949
+ "aria-hidden": "true"
2950
+ })
2951
+ }) })
2952
+ ]
2953
+ })
2954
+ }), showRecordCount && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2955
+ className: "text-[11px] font-bold tabular-nums text-slate-400",
2956
+ "aria-live": "polite",
2890
2957
  children: [
2891
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, {
2898
- size: 16,
2899
- "aria-hidden": "true"
2900
- })
2901
- }) }),
2902
- pageNumbers.map((n, idx) => n === "ellipsis" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
2903
- "aria-hidden": "true",
2904
- children: /* @__PURE__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
2923
- size: 16,
2924
- "aria-hidden": "true"
2925
- })
2926
- }) })
2958
+ "Showing ",
2959
+ rangeStart,
2960
+ "–",
2961
+ rangeEnd,
2962
+ " of ",
2963
+ totalItems,
2964
+ " ",
2965
+ itemLabel
2927
2966
  ]
2928
- })
2967
+ })]
2929
2968
  });
2930
2969
  }
2931
2970
  //#endregion
@@ -3846,6 +3885,100 @@ function EmptyState({ title, hint, icon, action, className = "" }) {
3846
3885
  });
3847
3886
  }
3848
3887
  //#endregion
3888
+ //#region src/ui-library/gallery/status-page/StatusPage.tsx
3889
+ /**
3890
+ * Named presets for the three common status states.
3891
+ * Each preset supplies code, eyebrow, title, and description.
3892
+ * Consumer always provides actions — presets never include navigation.
3893
+ *
3894
+ * Usage:
3895
+ * <StatusPage
3896
+ * {...STATUS_PAGE_PRESETS.notFound}
3897
+ * primaryAction={{ label: 'Go to Dashboard', onClick: () => navigate('/dashboard') }}
3898
+ * secondaryAction={{ label: 'Go Back', onClick: () => navigate(-1) }}
3899
+ * />
3900
+ */
3901
+ var STATUS_PAGE_PRESETS = {
3902
+ notFound: {
3903
+ code: "404",
3904
+ eyebrow: "404 — Not Found",
3905
+ title: "Page Not Found",
3906
+ description: "The page you're looking for doesn't exist or may have been moved."
3907
+ },
3908
+ forbidden: {
3909
+ code: "403",
3910
+ eyebrow: "403 — Access Restricted",
3911
+ title: "Access Restricted",
3912
+ description: "You don't have permission to view this page. Contact your system administrator if you need access."
3913
+ },
3914
+ serverError: {
3915
+ code: "500",
3916
+ eyebrow: "500 — Server Error",
3917
+ title: "Something Went Wrong",
3918
+ description: "We encountered an unexpected problem. Please try again or return to the dashboard."
3919
+ }
3920
+ };
3921
+ 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";
3922
+ 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`;
3923
+ 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`;
3924
+ function ActionButton({ action, variant }) {
3925
+ if (action.href) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
3926
+ href: action.href,
3927
+ className: variant === "primary" ? ANCHOR_PRIMARY : ANCHOR_OUTLINE,
3928
+ children: action.label
3929
+ });
3930
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3931
+ variant,
3932
+ onClick: action.onClick,
3933
+ children: action.label
3934
+ });
3935
+ }
3936
+ function StatusPage({ eyebrow, title, description, code, icon, primaryAction, secondaryAction, className = "" }) {
3937
+ const hasActions = primaryAction || secondaryAction;
3938
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3939
+ className: `relative flex min-h-screen items-center justify-center bg-surface-page px-6 py-16 overflow-hidden ${className}`,
3940
+ children: [code ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3941
+ "aria-hidden": "true",
3942
+ className: "pointer-events-none absolute select-none font-heading font-bold text-brand-navy opacity-[0.07] text-[80px] sm:text-[120px] leading-none",
3943
+ children: code
3944
+ }) : icon ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3945
+ "aria-hidden": "true",
3946
+ className: "pointer-events-none absolute select-none text-brand-navy opacity-[0.12]",
3947
+ style: { fontSize: 0 },
3948
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3949
+ className: "block [&>svg]:h-24 [&>svg]:w-24",
3950
+ children: icon
3951
+ })
3952
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3953
+ className: "relative z-10 max-w-sm w-full os-slide-up",
3954
+ children: [
3955
+ eyebrow && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3956
+ className: "mb-3 text-[11px] font-bold uppercase tracking-[0.18em] text-brand-sky",
3957
+ children: eyebrow
3958
+ }),
3959
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
3960
+ className: "font-heading text-3xl font-bold text-brand-navy leading-tight",
3961
+ children: title
3962
+ }),
3963
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3964
+ className: "mt-3 text-sm font-medium text-slate-500 leading-relaxed",
3965
+ children: description
3966
+ }),
3967
+ hasActions && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3968
+ className: "mt-8 flex flex-wrap items-center gap-3",
3969
+ children: [primaryAction && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionButton, {
3970
+ action: primaryAction,
3971
+ variant: "primary"
3972
+ }), secondaryAction && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionButton, {
3973
+ action: secondaryAction,
3974
+ variant: "outline"
3975
+ })]
3976
+ })
3977
+ ]
3978
+ })]
3979
+ });
3980
+ }
3981
+ //#endregion
3849
3982
  //#region src/ui-library/gallery/employee-card/EmployeeCard.tsx
3850
3983
  function EmployeeCard({ name, role, avatarSrc, status, meta, actions, className = "" }) {
3851
3984
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
@@ -7434,6 +7567,211 @@ function StatusDot({ status, label, className = "" }) {
7434
7567
  });
7435
7568
  }
7436
7569
  //#endregion
7570
+ //#region src/ui-library/gallery/column-manager/ColumnManager.tsx
7571
+ /**
7572
+ * ColumnManager — Design System Component
7573
+ *
7574
+ * A dropdown panel for managing table column visibility and order.
7575
+ * Provides checkboxes to show/hide individual columns and up/down
7576
+ * buttons to reorder them. Includes a reset action.
7577
+ *
7578
+ * Purely presentational — all state is owned by the caller.
7579
+ *
7580
+ * Usage:
7581
+ * const [hidden, setHidden] = useState<string[]>([]);
7582
+ * const [order, setOrder] = useState<string[] | null>(null);
7583
+ *
7584
+ * <ColumnManager
7585
+ * columns={[{ key: 'name', label: 'Name' }, { key: 'dept', label: 'Department' }]}
7586
+ * hiddenColumnKeys={hidden}
7587
+ * columnOrder={order}
7588
+ * onHiddenColumnKeysChange={setHidden}
7589
+ * onColumnOrderChange={setOrder}
7590
+ * onReset={() => { setHidden([]); setOrder(null); }}
7591
+ * />
7592
+ *
7593
+ * Accessibility:
7594
+ * - Trigger button has aria-expanded and aria-controls.
7595
+ * - Panel has role="region" and aria-label.
7596
+ * - Each checkbox is labelled via htmlFor + id.
7597
+ * - Reorder buttons have descriptive aria-labels.
7598
+ * - Panel closes on Escape, focus returns to the trigger.
7599
+ */
7600
+ /**
7601
+ * Apply a stored column order to the runtime column array.
7602
+ *
7603
+ * Rules:
7604
+ * - Keys in `order` that don't exist in `columns` are silently ignored
7605
+ * (forward compatibility when columns are removed).
7606
+ * - Columns not present in `order` are appended at the end in their
7607
+ * original relative order (forward compatibility when columns are added).
7608
+ * - When `order` is null the original array is returned unchanged.
7609
+ */
7610
+ function applyColumnOrder(columns, order) {
7611
+ if (!order) return columns;
7612
+ const byKey = new Map(columns.map((c) => [c.key, c]));
7613
+ const seen = /* @__PURE__ */ new Set();
7614
+ const result = [];
7615
+ for (const key of order) {
7616
+ const col = byKey.get(key);
7617
+ if (col) {
7618
+ result.push(col);
7619
+ seen.add(key);
7620
+ }
7621
+ }
7622
+ for (const col of columns) if (!seen.has(col.key)) result.push(col);
7623
+ return result;
7624
+ }
7625
+ function ColumnManager({ columns, hiddenColumnKeys, columnOrder, onHiddenColumnKeysChange, onColumnOrderChange, onReset, triggerLabel = "Columns" }) {
7626
+ const [open, setOpen] = (0, react.useState)(false);
7627
+ const triggerRef = (0, react.useRef)(null);
7628
+ const panelRef = (0, react.useRef)(null);
7629
+ const panelId = "cm-panel";
7630
+ const orderedColumns = applyColumnOrder(columns, columnOrder);
7631
+ const hiddenSet = new Set(hiddenColumnKeys);
7632
+ const hiddenCount = hiddenColumnKeys.length;
7633
+ (0, react.useEffect)(() => {
7634
+ if (!open) return;
7635
+ const handlePointerDown = (e) => {
7636
+ if (triggerRef.current?.contains(e.target) || panelRef.current?.contains(e.target)) return;
7637
+ setOpen(false);
7638
+ };
7639
+ document.addEventListener("pointerdown", handlePointerDown);
7640
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
7641
+ }, [open]);
7642
+ (0, react.useEffect)(() => {
7643
+ if (!open) return;
7644
+ const handleKeyDown = (e) => {
7645
+ if (e.key === "Escape") {
7646
+ setOpen(false);
7647
+ triggerRef.current?.focus();
7648
+ }
7649
+ };
7650
+ document.addEventListener("keydown", handleKeyDown);
7651
+ return () => document.removeEventListener("keydown", handleKeyDown);
7652
+ }, [open]);
7653
+ function toggleColumn(key) {
7654
+ onHiddenColumnKeysChange(hiddenSet.has(key) ? hiddenColumnKeys.filter((k) => k !== key) : [...hiddenColumnKeys, key]);
7655
+ }
7656
+ function moveUp(index) {
7657
+ if (index === 0) return;
7658
+ const next = [...orderedColumns];
7659
+ [next[index - 1], next[index]] = [next[index], next[index - 1]];
7660
+ onColumnOrderChange(next.map((c) => c.key));
7661
+ }
7662
+ function moveDown(index) {
7663
+ if (index === orderedColumns.length - 1) return;
7664
+ const next = [...orderedColumns];
7665
+ [next[index], next[index + 1]] = [next[index + 1], next[index]];
7666
+ onColumnOrderChange(next.map((c) => c.key));
7667
+ }
7668
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7669
+ className: "relative inline-block",
7670
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7671
+ ref: triggerRef,
7672
+ type: "button",
7673
+ "aria-expanded": open,
7674
+ "aria-controls": open ? panelId : void 0,
7675
+ onClick: () => setOpen((v) => !v),
7676
+ 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",
7677
+ children: [
7678
+ triggerLabel,
7679
+ hiddenCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7680
+ className: "rounded-full bg-brand-blue/10 px-1.5 py-0.5 font-bold text-brand-blue",
7681
+ children: [hiddenCount, " hidden"]
7682
+ }),
7683
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {
7684
+ size: 13,
7685
+ className: `transition-transform ${open ? "rotate-180" : ""}`,
7686
+ "aria-hidden": "true"
7687
+ })
7688
+ ]
7689
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7690
+ id: panelId,
7691
+ ref: panelRef,
7692
+ role: "region",
7693
+ "aria-label": "Manage columns",
7694
+ 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",
7695
+ style: { maxHeight: 400 },
7696
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7697
+ className: "flex shrink-0 items-center justify-between border-b border-slate-100 px-4 py-2.5",
7698
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7699
+ className: "text-xs font-bold text-slate-700",
7700
+ children: "Columns"
7701
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7702
+ type: "button",
7703
+ onClick: () => {
7704
+ onReset();
7705
+ setOpen(false);
7706
+ },
7707
+ 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",
7708
+ children: "Reset"
7709
+ })]
7710
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
7711
+ className: "overflow-y-auto",
7712
+ role: "list",
7713
+ children: orderedColumns.map((col, idx) => {
7714
+ const visible = !hiddenSet.has(col.key);
7715
+ const checkboxId = `cm-col-${col.key}`;
7716
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
7717
+ className: "flex items-center gap-2 border-b border-slate-50 px-3 py-2 last:border-0",
7718
+ children: [
7719
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7720
+ type: "checkbox",
7721
+ id: checkboxId,
7722
+ checked: visible,
7723
+ onChange: () => toggleColumn(col.key),
7724
+ 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"
7725
+ }),
7726
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
7727
+ htmlFor: checkboxId,
7728
+ className: `flex-1 cursor-pointer text-xs font-medium ${visible ? "text-slate-700" : "text-slate-400"}`,
7729
+ children: col.label
7730
+ }),
7731
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7732
+ className: "flex shrink-0 gap-0.5",
7733
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7734
+ type: "button",
7735
+ "aria-label": `Move ${col.label} up`,
7736
+ disabled: idx === 0,
7737
+ onClick: () => moveUp(idx),
7738
+ 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",
7739
+ children: "▲"
7740
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7741
+ type: "button",
7742
+ "aria-label": `Move ${col.label} down`,
7743
+ disabled: idx === orderedColumns.length - 1,
7744
+ onClick: () => moveDown(idx),
7745
+ 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",
7746
+ children: "▼"
7747
+ })]
7748
+ })
7749
+ ]
7750
+ }, col.key);
7751
+ })
7752
+ })]
7753
+ })]
7754
+ });
7755
+ }
7756
+ //#endregion
7757
+ //#region src/ui-library/gallery/status-dot/MaskedValue.tsx
7758
+ function MaskedValue({ value, masked, maskText = "••••• •••••", maskLabel = "Value restricted", className = "" }) {
7759
+ if (!masked) return className ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7760
+ className,
7761
+ children: value
7762
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: value });
7763
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7764
+ "aria-label": maskLabel,
7765
+ title: maskLabel,
7766
+ className: [
7767
+ "inline-flex select-none items-center rounded-md bg-slate-100 px-2 py-0.5",
7768
+ "font-mono text-xs font-medium text-slate-400 tracking-wider",
7769
+ className
7770
+ ].filter(Boolean).join(" "),
7771
+ children: maskText
7772
+ });
7773
+ }
7774
+ //#endregion
7437
7775
  //#region src/ui-library/gallery/status-actions/statusActions.ts
7438
7776
  /**
7439
7777
  * statusActions — Gallery-native lifecycle transition utilities.
@@ -7638,6 +7976,7 @@ exports.CardContent = CardContent;
7638
7976
  exports.CardFooter = CardFooter;
7639
7977
  exports.CardHeader = CardHeader;
7640
7978
  exports.Checkbox = Checkbox;
7979
+ exports.ColumnManager = ColumnManager;
7641
7980
  exports.Combobox = Combobox;
7642
7981
  exports.CommandPalette = CommandPalette;
7643
7982
  exports.ConfirmAction = ConfirmAction;
@@ -7665,6 +8004,7 @@ exports.Input = Input;
7665
8004
  exports.IsoCubeBlock = IsoCubeBlock;
7666
8005
  exports.Kanban = Kanban;
7667
8006
  exports.LineChart = LineChart;
8007
+ exports.MaskedValue = MaskedValue;
7668
8008
  exports.Menu = Menu;
7669
8009
  exports.Modal = Modal;
7670
8010
  exports.OrgChart = OrgChart;
@@ -7682,6 +8022,7 @@ exports.Progress = Progress;
7682
8022
  exports.RadioGroup = RadioGroup;
7683
8023
  exports.RadioItem = RadioItem;
7684
8024
  exports.ReportTemplate = ReportTemplate;
8025
+ exports.STATUS_PAGE_PRESETS = STATUS_PAGE_PRESETS;
7685
8026
  exports.Section = Section;
7686
8027
  exports.SegmentedControl = SegmentedControl;
7687
8028
  exports.Select = Select;
@@ -7698,6 +8039,7 @@ exports.Spinner = Spinner;
7698
8039
  exports.Statistic = Statistic;
7699
8040
  exports.StatusBadge = StatusBadge;
7700
8041
  exports.StatusDot = StatusDot;
8042
+ exports.StatusPage = StatusPage;
7701
8043
  exports.Stepper = Stepper;
7702
8044
  exports.Switch = Switch;
7703
8045
  exports.Tab = Tab;
@@ -7722,6 +8064,7 @@ exports.Toolbar = Toolbar;
7722
8064
  exports.Tooltip = Tooltip;
7723
8065
  exports.TreeView = TreeView;
7724
8066
  exports.ValidationSummary = ValidationSummary;
8067
+ exports.applyColumnOrder = applyColumnOrder;
7725
8068
  Object.defineProperty(exports, "colors", {
7726
8069
  enumerable: true,
7727
8070
  get: function() {