twico-ui 1.36.1 → 1.37.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.d.cts CHANGED
@@ -2915,8 +2915,14 @@ interface DialogProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"
2915
2915
  footer?: React.ReactNode;
2916
2916
  /** `"full"` is a near-fullscreen panel (fills the viewport minus a small margin). @default "md" */
2917
2917
  size?: "sm" | "md" | "lg" | "full";
2918
- /** Keep the header + footer fixed and scroll only the body (for long content). @default false */
2918
+ /** Keep the header + footer fixed and scroll only the body when content exceeds the viewport — so the
2919
+ * action buttons never scroll off-screen (#371). Set `false` for a panel that should scroll as one block
2920
+ * (rare; note a non-portaled child that deliberately overflows the panel would be clipped). @default true */
2919
2921
  scrollBody?: boolean;
2922
+ /** Draw a rule between header/body and body/footer. Suppressed automatically where the region is empty
2923
+ * (no footer; a header with only a close button and no title/description). Set `false` for a panel that
2924
+ * should read as one continuous surface. @default true */
2925
+ dividers?: boolean;
2920
2926
  /** Close when the backdrop is clicked. @default true */
2921
2927
  closeOnBackdrop?: boolean;
2922
2928
  children?: React.ReactNode;
@@ -2962,6 +2968,10 @@ interface DrawerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"
2962
2968
  height?: "sm" | "md" | "lg" | number | string;
2963
2969
  /** Close when the backdrop is clicked. @default true */
2964
2970
  closeOnBackdrop?: boolean;
2971
+ /** Draw a rule between header/body and body/footer (matches `Dialog`). Suppressed automatically where the
2972
+ * region is empty (no footer; a header with only a close button and no title/description). Set `false` for a
2973
+ * panel that should read as one continuous surface. @default true */
2974
+ dividers?: boolean;
2965
2975
  children?: React.ReactNode;
2966
2976
  }
2967
2977
 
package/dist/index.d.ts CHANGED
@@ -2915,8 +2915,14 @@ interface DialogProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"
2915
2915
  footer?: React.ReactNode;
2916
2916
  /** `"full"` is a near-fullscreen panel (fills the viewport minus a small margin). @default "md" */
2917
2917
  size?: "sm" | "md" | "lg" | "full";
2918
- /** Keep the header + footer fixed and scroll only the body (for long content). @default false */
2918
+ /** Keep the header + footer fixed and scroll only the body when content exceeds the viewport — so the
2919
+ * action buttons never scroll off-screen (#371). Set `false` for a panel that should scroll as one block
2920
+ * (rare; note a non-portaled child that deliberately overflows the panel would be clipped). @default true */
2919
2921
  scrollBody?: boolean;
2922
+ /** Draw a rule between header/body and body/footer. Suppressed automatically where the region is empty
2923
+ * (no footer; a header with only a close button and no title/description). Set `false` for a panel that
2924
+ * should read as one continuous surface. @default true */
2925
+ dividers?: boolean;
2920
2926
  /** Close when the backdrop is clicked. @default true */
2921
2927
  closeOnBackdrop?: boolean;
2922
2928
  children?: React.ReactNode;
@@ -2962,6 +2968,10 @@ interface DrawerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"
2962
2968
  height?: "sm" | "md" | "lg" | number | string;
2963
2969
  /** Close when the backdrop is clicked. @default true */
2964
2970
  closeOnBackdrop?: boolean;
2971
+ /** Draw a rule between header/body and body/footer (matches `Dialog`). Suppressed automatically where the
2972
+ * region is empty (no footer; a header with only a close button and no title/description). Set `false` for a
2973
+ * panel that should read as one continuous surface. @default true */
2974
+ dividers?: boolean;
2965
2975
  children?: React.ReactNode;
2966
2976
  }
2967
2977
 
package/dist/index.mjs CHANGED
@@ -15412,6 +15412,10 @@ var DIALOG_CSS = `
15412
15412
  .twc-dialog__close svg { width: 18px; height: 18px; }
15413
15413
  .twc-dialog__body { padding: var(--space-4) var(--space-5); font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-normal); }
15414
15414
  .twc-dialog__footer { display: flex; gap: var(--space-2); justify-content: flex-end; padding: 0 var(--space-5) var(--space-5); }
15415
+ /* #372: header/body and body/footer rules (on by default). Per-region data-divider so the rule is
15416
+ suppressed when the region is empty (no footer; a header with only a close button and no title/description). */
15417
+ .twc-dialog__header[data-divider="true"] { padding-bottom: var(--space-4); border-bottom: var(--border-thin) solid var(--color-divider); }
15418
+ .twc-dialog__footer[data-divider="true"] { padding-top: var(--space-4); border-top: var(--border-thin) solid var(--color-divider); }
15415
15419
  `;
15416
15420
  function Dialog({
15417
15421
  open,
@@ -15421,7 +15425,8 @@ function Dialog({
15421
15425
  footer,
15422
15426
  children,
15423
15427
  size = "md",
15424
- scrollBody = false,
15428
+ scrollBody = true,
15429
+ dividers = true,
15425
15430
  closeOnBackdrop = true,
15426
15431
  className = "",
15427
15432
  ...rest
@@ -15462,7 +15467,7 @@ function Dialog({
15462
15467
  }, children: [
15463
15468
  __twcStyles,
15464
15469
  /* @__PURE__ */ jsxs("div", { ref: dialogRef, className: `twc-dialog ${className}`, "data-state": state, "data-size": size, "data-scroll-body": scrollBody ? "true" : void 0, role: "dialog", "aria-modal": "true", tabIndex: -1, "aria-labelledby": title ? titleId : void 0, "aria-label": !title ? "Dialog" : void 0, "aria-describedby": description ? descId : void 0, ...rest, children: [
15465
- title || description || onClose ? /* @__PURE__ */ jsxs("div", { className: "twc-dialog__header", children: [
15470
+ title || description || onClose ? /* @__PURE__ */ jsxs("div", { className: "twc-dialog__header", "data-divider": dividers && (title || description) && (children || footer) ? "true" : void 0, children: [
15466
15471
  /* @__PURE__ */ jsxs("div", { className: "twc-dialog__titles", children: [
15467
15472
  title ? /* @__PURE__ */ jsx("div", { className: "twc-dialog__title", id: titleId, children: title }) : null,
15468
15473
  description ? /* @__PURE__ */ jsx("div", { className: "twc-dialog__desc", id: descId, children: description }) : null
@@ -15470,7 +15475,7 @@ function Dialog({
15470
15475
  onClose ? /* @__PURE__ */ jsx("button", { className: "twc-dialog__close", onClick: onClose, "aria-label": "Close", type: "button", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
15471
15476
  ] }) : null,
15472
15477
  children ? /* @__PURE__ */ jsx("div", { className: "twc-dialog__body", children }) : null,
15473
- footer ? /* @__PURE__ */ jsx("div", { className: "twc-dialog__footer", children: footer }) : null
15478
+ footer ? /* @__PURE__ */ jsx("div", { className: "twc-dialog__footer", "data-divider": dividers && children ? "true" : void 0, children: footer }) : null
15474
15479
  ] })
15475
15480
  ] });
15476
15481
  return renderPortal(overlay);
@@ -15570,7 +15575,11 @@ var DRAWER_CSS = `
15570
15575
  .twc-drawer__close:hover { background: var(--color-surface-sunken); color: var(--color-text); }
15571
15576
  .twc-drawer__close svg { width: 18px; height: 18px; }
15572
15577
  .twc-drawer__body { flex: 1; overflow-y: auto; padding: var(--space-2) var(--space-5) var(--space-5); font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-normal); }
15573
- .twc-drawer__footer { display: flex; gap: var(--space-2); justify-content: flex-end; padding: var(--space-4) var(--space-5); border-top: var(--border-thin) solid var(--color-divider); }
15578
+ .twc-drawer__footer { display: flex; gap: var(--space-2); justify-content: flex-end; padding: var(--space-4) var(--space-5); }
15579
+ /* #372: header/footer rules (on by default), consistent with Dialog. Per-region data-divider so an empty
15580
+ region grows no stray line (no footer; a header with only a close button and no title/description). */
15581
+ .twc-drawer__header[data-divider="true"] { border-bottom: var(--border-thin) solid var(--color-divider); }
15582
+ .twc-drawer__footer[data-divider="true"] { border-top: var(--border-thin) solid var(--color-divider); }
15574
15583
  @media (prefers-reduced-motion: reduce) {
15575
15584
  .twc-drawer__overlay[data-state], .twc-drawer[data-state] { animation-duration: 1ms; }
15576
15585
  }
@@ -15586,6 +15595,7 @@ function Drawer({
15586
15595
  width,
15587
15596
  height,
15588
15597
  closeOnBackdrop = true,
15598
+ dividers = true,
15589
15599
  children,
15590
15600
  className = "",
15591
15601
  style,
@@ -15631,7 +15641,7 @@ function Drawer({
15631
15641
  }, children: [
15632
15642
  __twcStyles,
15633
15643
  /* @__PURE__ */ jsxs("div", { ref: panelRef, className: `twc-drawer ${className}`, "data-side": side, "data-state": state, role: "dialog", "aria-modal": "true", tabIndex: -1, "aria-labelledby": title ? titleId : void 0, "aria-label": !title ? "Drawer" : void 0, "aria-describedby": description ? descId : void 0, style: { ...dim ? sizeVar : null, ...style }, ...rest, children: [
15634
- title || description || onClose ? /* @__PURE__ */ jsxs("div", { className: "twc-drawer__header", children: [
15644
+ title || description || onClose ? /* @__PURE__ */ jsxs("div", { className: "twc-drawer__header", "data-divider": dividers && (title || description) ? "true" : void 0, children: [
15635
15645
  /* @__PURE__ */ jsxs("div", { className: "twc-drawer__titles", children: [
15636
15646
  title ? /* @__PURE__ */ jsx("div", { className: "twc-drawer__title", id: titleId, children: title }) : null,
15637
15647
  description ? /* @__PURE__ */ jsx("div", { className: "twc-drawer__desc", id: descId, children: description }) : null
@@ -15639,7 +15649,7 @@ function Drawer({
15639
15649
  onClose ? /* @__PURE__ */ jsx("button", { className: "twc-drawer__close", onClick: onClose, "aria-label": "Close", type: "button", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
15640
15650
  ] }) : null,
15641
15651
  /* @__PURE__ */ jsx("div", { className: "twc-drawer__body", children }),
15642
- footer ? /* @__PURE__ */ jsx("div", { className: "twc-drawer__footer", children: footer }) : null
15652
+ footer ? /* @__PURE__ */ jsx("div", { className: "twc-drawer__footer", "data-divider": dividers ? "true" : void 0, children: footer }) : null
15643
15653
  ] })
15644
15654
  ] });
15645
15655
  return renderPortal(overlay);
@@ -16340,7 +16350,9 @@ var POPOVER_CSS = `
16340
16350
  .twc-popover__arrow { position: absolute; width: 11px; height: 11px; background: var(--color-surface-raised);
16341
16351
  border-left: var(--border-thin) solid var(--color-border); border-top: var(--border-thin) solid var(--color-border);
16342
16352
  transform: rotate(45deg); }
16343
- .twc-popover__inner { padding: var(--space-4); font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-normal); }
16353
+ /* #373: cap the panel to the room place() measured and scroll INSIDE the inner (not the panel \u2014 the panel
16354
+ must keep overflow visible so the absolutely-positioned arrow isn't clipped) so a tall panel's footer stays reachable. */
16355
+ .twc-popover__inner { padding: var(--space-4); font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-normal); overflow-y: auto; }
16344
16356
  .twc-popover__title { font-size: var(--text-sm); font-weight: var(--font-bold); color: var(--color-text); margin-bottom: 4px; }
16345
16357
  `;
16346
16358
  function Popover({
@@ -16402,7 +16414,7 @@ function Popover({
16402
16414
  l = Math.max(M, Math.min(l, vw - w - M));
16403
16415
  left = l;
16404
16416
  arrow = { left: Math.min(Math.max(r2.left + r2.width / 2 - l - 5.5, 10), w - 20), [flip ? "bottom" : "top"]: -6 };
16405
- return setPos({ top, bottom, left, width: w, flip, arrow });
16417
+ return setPos({ top, bottom, left, width: w, flip, arrow, maxHeight: flip ? spaceAbove : spaceBelow });
16406
16418
  }
16407
16419
  const ph = popRef.current ? popRef.current.offsetHeight : 120;
16408
16420
  let onRight = placement === "right";
@@ -16414,7 +16426,7 @@ function Popover({
16414
16426
  const cy = r2.top + r2.height / 2;
16415
16427
  top = Math.max(M, Math.min(cy - ph / 2, vh - ph - M));
16416
16428
  arrow = { top: Math.min(Math.max(cy - top - 5.5, 8), ph - 18), [onRight ? "left" : "right"]: -6 };
16417
- setPos({ top, left, width: w, flip: false, arrow });
16429
+ setPos({ top, left, width: w, flip: false, arrow, maxHeight: vh - 2 * M });
16418
16430
  }, [placement, align, width]);
16419
16431
  React51__default.useEffect(() => {
16420
16432
  if (!open) return;
@@ -16480,7 +16492,7 @@ function Popover({
16480
16492
  children: [
16481
16493
  __twcStyles,
16482
16494
  /* @__PURE__ */ jsx("span", { className: "twc-popover__arrow", style: pos.arrow, "aria-hidden": "true" }),
16483
- /* @__PURE__ */ jsxs("div", { className: "twc-popover__inner", children: [
16495
+ /* @__PURE__ */ jsxs("div", { className: "twc-popover__inner", style: { maxHeight: pos.maxHeight }, children: [
16484
16496
  title ? /* @__PURE__ */ jsx("div", { className: "twc-popover__title", id: `${popId}-title`, children: title }) : null,
16485
16497
  children
16486
16498
  ] })