@assure-one/design-system 1.26.0 → 1.27.0

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/index.js CHANGED
@@ -18249,6 +18249,8 @@ var SEARCH_THRESHOLD = 8;
18249
18249
  var HOVER_OPEN_MS = 90;
18250
18250
  var HOVER_CLOSE_MS = 220;
18251
18251
  var FLYOUT_WIDTH = 224;
18252
+ var MIN_FLYOUT_HEIGHT = 120;
18253
+ var VIEWPORT_MARGIN = 24;
18252
18254
  var DEFAULT_GUTTER_PX = 24;
18253
18255
  function DisplayMenu({
18254
18256
  sections,
@@ -18265,7 +18267,8 @@ function DisplayMenu({
18265
18267
  const [openKey, setOpenKey] = useState(null);
18266
18268
  const [flyout, setFlyout] = useState({
18267
18269
  top: 0,
18268
- side: "left"
18270
+ side: "left",
18271
+ maxHeight: 0
18269
18272
  });
18270
18273
  const [panelShift, setPanelShift] = useState(0);
18271
18274
  const [rootQuery, setRootQuery] = useState("");
@@ -18289,10 +18292,14 @@ function DisplayMenu({
18289
18292
  if (!panel || !row) return;
18290
18293
  const panelRect = panel.getBoundingClientRect();
18291
18294
  const rowRect = row.getBoundingClientRect();
18292
- const maxTop = Math.max(0, window.innerHeight - panelRect.top - 24 - 120);
18295
+ const maxTop = Math.max(0, window.innerHeight - panelRect.top - 24 - MIN_FLYOUT_HEIGHT);
18293
18296
  const top = Math.min(Math.max(rowRect.top - panelRect.top - 4, 0), maxTop);
18297
+ const maxHeight = Math.max(
18298
+ MIN_FLYOUT_HEIGHT,
18299
+ window.innerHeight - (panelRect.top + top) - VIEWPORT_MARGIN
18300
+ );
18294
18301
  const side = panelRect.left >= FLYOUT_WIDTH + 16 ? "left" : "right";
18295
- setFlyout({ top, side });
18302
+ setFlyout({ top, side, maxHeight });
18296
18303
  }, []);
18297
18304
  const openSection = useCallback(
18298
18305
  (key) => {
@@ -18423,100 +18430,108 @@ function DisplayMenu({
18423
18430
  }
18424
18431
  )
18425
18432
  ] }),
18426
- /* @__PURE__ */ jsx("div", { role: "menu", "aria-label": triggerLabel, className: "max-h-80 overflow-y-auto p-1", children: rootRows.length === 0 ? /* @__PURE__ */ jsx(EmptyRow, {}) : rootRows.map((row) => {
18427
- if (row.kind === "option") {
18428
- const { section: section2, option } = row;
18429
- const isActive = section2.selected.has(option.key);
18430
- const isMulti = section2.mode === "multi";
18431
- return /* @__PURE__ */ jsxs(
18432
- "button",
18433
- {
18434
- type: "button",
18435
- role: isMulti ? "menuitemcheckbox" : "menuitemradio",
18436
- "aria-checked": isActive,
18437
- onMouseEnter: () => {
18438
- clearTimers();
18439
- setOpenKey(null);
18440
- },
18441
- onClick: () => section2.onSelect(option.key),
18442
- className: "flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-[13px] transition-colors hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-none",
18443
- children: [
18444
- section2.icon && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", "aria-hidden": "true", children: section2.icon }),
18445
- /* @__PURE__ */ jsx("span", { className: "shrink-0 truncate text-muted-foreground", children: section2.label }),
18446
- /* @__PURE__ */ jsx(
18447
- ChevronRightIcon,
18448
- {
18449
- size: 12,
18450
- className: "shrink-0 text-muted-foreground",
18451
- "aria-hidden": "true"
18452
- }
18453
- ),
18454
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-foreground", children: option.label }),
18455
- isActive && /* @__PURE__ */ jsx(CheckIcon, { size: 14, className: "shrink-0 text-brand" })
18456
- ]
18457
- },
18458
- `${section2.key}:${option.key}`
18459
- );
18460
- }
18461
- const { section } = row;
18462
- const count = section.selected.size;
18463
- const valueLabel = section.summary ?? (section.mode === "single" ? section.options.find((o) => section.selected.has(o.key))?.label ?? "" : "");
18464
- const isOpen = section.key === openKey;
18465
- return (
18466
- /* Hover handlers sit on the wrapper so the cursor can cross
18467
- * the gap to `section.trailing` without closing the flyout.
18468
- * The wrapper is not itself a control — the button inside is,
18469
- * and it carries the keyboard equivalent (ArrowRight / Enter /
18470
- * Space open the same flyout). Nothing here is mouse-only. */
18471
- // eslint-disable-next-line jsx-a11y/no-static-element-interactions
18472
- /* @__PURE__ */ jsxs(
18473
- "div",
18474
- {
18475
- ref: (el) => {
18476
- if (el) rowRefs.current.set(section.key, el);
18477
- else rowRefs.current.delete(section.key);
18478
- },
18479
- className: "flex items-center gap-0.5",
18480
- onMouseEnter: () => scheduleOpen(section.key),
18481
- onMouseLeave: scheduleClose,
18482
- children: [
18483
- /* @__PURE__ */ jsxs(
18484
- "button",
18485
- {
18486
- type: "button",
18487
- role: "menuitem",
18488
- "aria-haspopup": "menu",
18489
- "aria-expanded": isOpen,
18490
- onClick: () => openSection(section.key),
18491
- onKeyDown: (e) => {
18492
- if (e.key === "ArrowRight" || e.key === "Enter" || e.key === " ") {
18493
- e.preventDefault();
18494
- openSection(section.key);
18495
- requestAnimationFrame(
18496
- () => flyoutRef.current?.querySelector("input, [role='menuitemcheckbox'], [role='menuitemradio']")?.focus()
18497
- );
18433
+ /* @__PURE__ */ jsx(
18434
+ "div",
18435
+ {
18436
+ role: "menu",
18437
+ "aria-label": triggerLabel,
18438
+ className: "scrollbar-thin max-h-[min(32rem,70vh)] overflow-y-auto p-1",
18439
+ children: rootRows.length === 0 ? /* @__PURE__ */ jsx(EmptyRow, {}) : rootRows.map((row) => {
18440
+ if (row.kind === "option") {
18441
+ const { section: section2, option } = row;
18442
+ const isActive = section2.selected.has(option.key);
18443
+ const isMulti = section2.mode === "multi";
18444
+ return /* @__PURE__ */ jsxs(
18445
+ "button",
18446
+ {
18447
+ type: "button",
18448
+ role: isMulti ? "menuitemcheckbox" : "menuitemradio",
18449
+ "aria-checked": isActive,
18450
+ onMouseEnter: () => {
18451
+ clearTimers();
18452
+ setOpenKey(null);
18453
+ },
18454
+ onClick: () => section2.onSelect(option.key),
18455
+ className: "flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-[13px] transition-colors hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-none",
18456
+ children: [
18457
+ section2.icon && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", "aria-hidden": "true", children: section2.icon }),
18458
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 truncate text-muted-foreground", children: section2.label }),
18459
+ /* @__PURE__ */ jsx(
18460
+ ChevronRightIcon,
18461
+ {
18462
+ size: 12,
18463
+ className: "shrink-0 text-muted-foreground",
18464
+ "aria-hidden": "true"
18498
18465
  }
18499
- },
18500
- className: cn(
18501
- "flex min-w-0 flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-left text-[13px] text-foreground transition-colors hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-none",
18502
- isOpen && "bg-muted/60"
18503
18466
  ),
18504
- children: [
18505
- section.icon && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", "aria-hidden": "true", children: section.icon }),
18506
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: section.label }),
18507
- section.mode === "multi" && count > 0 && /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-full bg-brand-bg px-1.5 text-[11px] font-medium text-brand", children: count }),
18508
- section.mode !== "multi" && valueLabel && /* @__PURE__ */ jsx("span", { className: "max-w-24 shrink-0 truncate text-[13px] text-muted-foreground", children: valueLabel }),
18509
- /* @__PURE__ */ jsx(ChevronRightIcon, { size: 13, className: "shrink-0 text-muted-foreground" })
18510
- ]
18511
- }
18512
- ),
18513
- section.trailing
18514
- ]
18515
- },
18516
- section.key
18517
- )
18518
- );
18519
- }) }),
18467
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-foreground", children: option.label }),
18468
+ isActive && /* @__PURE__ */ jsx(CheckIcon, { size: 14, className: "shrink-0 text-brand" })
18469
+ ]
18470
+ },
18471
+ `${section2.key}:${option.key}`
18472
+ );
18473
+ }
18474
+ const { section } = row;
18475
+ const count = section.selected.size;
18476
+ const valueLabel = section.summary ?? (section.mode === "single" ? section.options.find((o) => section.selected.has(o.key))?.label ?? "" : "");
18477
+ const isOpen = section.key === openKey;
18478
+ return (
18479
+ /* Hover handlers sit on the wrapper so the cursor can cross
18480
+ * the gap to `section.trailing` without closing the flyout.
18481
+ * The wrapper is not itself a control — the button inside is,
18482
+ * and it carries the keyboard equivalent (ArrowRight / Enter /
18483
+ * Space open the same flyout). Nothing here is mouse-only. */
18484
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
18485
+ /* @__PURE__ */ jsxs(
18486
+ "div",
18487
+ {
18488
+ ref: (el) => {
18489
+ if (el) rowRefs.current.set(section.key, el);
18490
+ else rowRefs.current.delete(section.key);
18491
+ },
18492
+ className: "flex items-center gap-0.5",
18493
+ onMouseEnter: () => scheduleOpen(section.key),
18494
+ onMouseLeave: scheduleClose,
18495
+ children: [
18496
+ /* @__PURE__ */ jsxs(
18497
+ "button",
18498
+ {
18499
+ type: "button",
18500
+ role: "menuitem",
18501
+ "aria-haspopup": "menu",
18502
+ "aria-expanded": isOpen,
18503
+ onClick: () => openSection(section.key),
18504
+ onKeyDown: (e) => {
18505
+ if (e.key === "ArrowRight" || e.key === "Enter" || e.key === " ") {
18506
+ e.preventDefault();
18507
+ openSection(section.key);
18508
+ requestAnimationFrame(
18509
+ () => flyoutRef.current?.querySelector("input, [role='menuitemcheckbox'], [role='menuitemradio']")?.focus()
18510
+ );
18511
+ }
18512
+ },
18513
+ className: cn(
18514
+ "flex min-w-0 flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-left text-[13px] text-foreground transition-colors hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-none",
18515
+ isOpen && "bg-muted/60"
18516
+ ),
18517
+ children: [
18518
+ section.icon && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", "aria-hidden": "true", children: section.icon }),
18519
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: section.label }),
18520
+ section.mode === "multi" && count > 0 && /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-full bg-brand-bg px-1.5 text-[11px] font-medium text-brand", children: count }),
18521
+ section.mode !== "multi" && valueLabel && /* @__PURE__ */ jsx("span", { className: "max-w-24 shrink-0 truncate text-[13px] text-muted-foreground", children: valueLabel }),
18522
+ /* @__PURE__ */ jsx(ChevronRightIcon, { size: 13, className: "shrink-0 text-muted-foreground" })
18523
+ ]
18524
+ }
18525
+ ),
18526
+ section.trailing
18527
+ ]
18528
+ },
18529
+ section.key
18530
+ )
18531
+ );
18532
+ })
18533
+ }
18534
+ ),
18520
18535
  /* @__PURE__ */ jsx("div", { className: "border-t border-border p-1", children: /* @__PURE__ */ jsxs(
18521
18536
  Button,
18522
18537
  {
@@ -18538,9 +18553,12 @@ function DisplayMenu({
18538
18553
  role: "menu",
18539
18554
  "aria-label": activeSection.label,
18540
18555
  tabIndex: -1,
18541
- style: { top: flyout.top, width: FLYOUT_WIDTH },
18556
+ style: { top: flyout.top, width: FLYOUT_WIDTH, maxHeight: flyout.maxHeight },
18542
18557
  className: cn(
18543
- "absolute z-50 rounded-lg border border-border bg-popover shadow-md",
18558
+ // Column + overflow-hidden so the option list (the only flexible
18559
+ // part) absorbs the height cap, while the search box and Clear
18560
+ // footer stay pinned and fully visible.
18561
+ "absolute z-50 flex flex-col overflow-hidden rounded-lg border border-border bg-popover shadow-md",
18544
18562
  // -mx-1 lets the flyout tuck against the panel so there's no
18545
18563
  // dead gap for the cursor to fall through on the way over.
18546
18564
  flyout.side === "left" ? "right-full -mr-1" : "left-full -ml-1"
@@ -18548,7 +18566,7 @@ function DisplayMenu({
18548
18566
  onMouseEnter: clearTimers,
18549
18567
  onMouseLeave: scheduleClose,
18550
18568
  children: [
18551
- showFlyoutSearch && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 border-b border-border px-2.5 py-1.5", children: [
18569
+ showFlyoutSearch && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5 border-b border-border px-2.5 py-1.5", children: [
18552
18570
  /* @__PURE__ */ jsx(SearchIcon, { size: 13, className: "shrink-0 text-muted-foreground", "aria-hidden": "true" }),
18553
18571
  /* @__PURE__ */ jsx(
18554
18572
  "input",
@@ -18562,7 +18580,7 @@ function DisplayMenu({
18562
18580
  }
18563
18581
  )
18564
18582
  ] }),
18565
- /* @__PURE__ */ jsx("div", { className: "max-h-64 overflow-y-auto p-1", children: activeSection.content ? (
18583
+ /* @__PURE__ */ jsx("div", { className: "scrollbar-thin min-h-0 flex-1 max-h-[min(24rem,60vh)] overflow-y-auto p-1", children: activeSection.content ? (
18566
18584
  /* Consumer-supplied controls (date range, numeric bound).
18567
18585
  * Rendered raw — the section owns its own labelling and
18568
18586
  * layout; the flyout only provides the surface. */
@@ -18615,7 +18633,7 @@ function DisplayMenu({
18615
18633
  option.key
18616
18634
  );
18617
18635
  }) }),
18618
- activeSection.mode === "multi" && activeSection.selected.size > 0 && activeSection.onClear && /* @__PURE__ */ jsx("div", { className: "border-t border-border p-1", children: /* @__PURE__ */ jsxs(
18636
+ activeSection.mode === "multi" && activeSection.selected.size > 0 && activeSection.onClear && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t border-border p-1", children: /* @__PURE__ */ jsxs(
18619
18637
  Button,
18620
18638
  {
18621
18639
  variant: "ghost",