twico-ui 1.36.1 → 1.37.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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to Twico UI are documented here (newest first). This file is generated automatically on each release by [semantic-release](https://semantic-release.gitbook.io/) from [Conventional Commits](https://www.conventionalcommits.org/) — do not edit it by hand. The same notes appear on each [GitHub Release](https://github.com/EncryptionL/twico-ui/releases).
4
4
 
5
+ ## [1.37.1](https://github.com/EncryptionL/twico-ui/compare/v1.37.0...v1.37.1) (2026-09-14)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **image:** let explicit width/height win over fit's container fill ([#378](https://github.com/EncryptionL/twico-ui/issues/378)) ([770823d](https://github.com/EncryptionL/twico-ui/commit/770823ddced42288e623bb00e8f92886bd01d373))
11
+
12
+ # [1.37.0](https://github.com/EncryptionL/twico-ui/compare/v1.36.1...v1.37.0) (2026-09-11)
13
+
14
+
15
+ ### Features
16
+
17
+ * **overlay:** safer Dialog/Drawer defaults + Popover height clamp ([#371](https://github.com/EncryptionL/twico-ui/issues/371), [#372](https://github.com/EncryptionL/twico-ui/issues/372), [#373](https://github.com/EncryptionL/twico-ui/issues/373)) ([0f7a386](https://github.com/EncryptionL/twico-ui/commit/0f7a38656956bb9ef7cdd0331d2b5e6776aa7d7c))
18
+
5
19
  ## [1.36.1](https://github.com/EncryptionL/twico-ui/compare/v1.36.0...v1.36.1) (2026-09-09)
6
20
 
7
21
 
package/dist/index.cjs CHANGED
@@ -325,6 +325,8 @@ var Image = React51__namespace.default.forwardRef(function Image2({
325
325
  className = "",
326
326
  style,
327
327
  onError,
328
+ width,
329
+ height,
328
330
  ...rest
329
331
  }, ref) {
330
332
  const __twcStyles = useScopedStyles("twc-image-styles", IMAGE_CSS);
@@ -334,6 +336,14 @@ var Image = React51__namespace.default.forwardRef(function Image2({
334
336
  objectFit: fit,
335
337
  borderRadius: radius ? `var(--radius-${radius})` : void 0,
336
338
  aspectRatio,
339
+ // #378: only when `fit` is set, also emit explicit width/height as inline style — that's the sole case
340
+ // where the `[data-fit] { width/height: 100% }` rule overrides the width/height ATTRIBUTES (a
341
+ // presentational attr loses to a real CSS rule), silently sizing `<Image width height fit>` to the
342
+ // container/intrinsic size. Inline style beats that rule so the props mean what they say (object-fit
343
+ // still crops within the box), placed before `...style` so a caller's own `style` still wins. Gated on
344
+ // `fit` so the no-fit path stays attribute-only — a consumer's own CSS/className can still override it.
345
+ ...fit && width != null ? { width } : null,
346
+ ...fit && height != null ? { height } : null,
337
347
  ...style
338
348
  };
339
349
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -347,6 +357,8 @@ var Image = React51__namespace.default.forwardRef(function Image2({
347
357
  loading,
348
358
  className: `twc-image ${className}`.trim(),
349
359
  "data-fit": fit || void 0,
360
+ width,
361
+ height,
350
362
  style: s,
351
363
  onError: (e) => {
352
364
  onError?.(e);
@@ -15433,6 +15445,10 @@ var DIALOG_CSS = `
15433
15445
  .twc-dialog__close svg { width: 18px; height: 18px; }
15434
15446
  .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); }
15435
15447
  .twc-dialog__footer { display: flex; gap: var(--space-2); justify-content: flex-end; padding: 0 var(--space-5) var(--space-5); }
15448
+ /* #372: header/body and body/footer rules (on by default). Per-region data-divider so the rule is
15449
+ suppressed when the region is empty (no footer; a header with only a close button and no title/description). */
15450
+ .twc-dialog__header[data-divider="true"] { padding-bottom: var(--space-4); border-bottom: var(--border-thin) solid var(--color-divider); }
15451
+ .twc-dialog__footer[data-divider="true"] { padding-top: var(--space-4); border-top: var(--border-thin) solid var(--color-divider); }
15436
15452
  `;
15437
15453
  function Dialog({
15438
15454
  open,
@@ -15442,7 +15458,8 @@ function Dialog({
15442
15458
  footer,
15443
15459
  children,
15444
15460
  size = "md",
15445
- scrollBody = false,
15461
+ scrollBody = true,
15462
+ dividers = true,
15446
15463
  closeOnBackdrop = true,
15447
15464
  className = "",
15448
15465
  ...rest
@@ -15483,7 +15500,7 @@ function Dialog({
15483
15500
  }, children: [
15484
15501
  __twcStyles,
15485
15502
  /* @__PURE__ */ jsxRuntime.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: [
15486
- title || description || onClose ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dialog__header", children: [
15503
+ title || description || onClose ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dialog__header", "data-divider": dividers && (title || description) && (children || footer) ? "true" : void 0, children: [
15487
15504
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dialog__titles", children: [
15488
15505
  title ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dialog__title", id: titleId, children: title }) : null,
15489
15506
  description ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dialog__desc", id: descId, children: description }) : null
@@ -15491,7 +15508,7 @@ function Dialog({
15491
15508
  onClose ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: "twc-dialog__close", onClick: onClose, "aria-label": "Close", type: "button", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
15492
15509
  ] }) : null,
15493
15510
  children ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dialog__body", children }) : null,
15494
- footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dialog__footer", children: footer }) : null
15511
+ footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dialog__footer", "data-divider": dividers && children ? "true" : void 0, children: footer }) : null
15495
15512
  ] })
15496
15513
  ] });
15497
15514
  return renderPortal(overlay);
@@ -15591,7 +15608,11 @@ var DRAWER_CSS = `
15591
15608
  .twc-drawer__close:hover { background: var(--color-surface-sunken); color: var(--color-text); }
15592
15609
  .twc-drawer__close svg { width: 18px; height: 18px; }
15593
15610
  .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); }
15594
- .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); }
15611
+ .twc-drawer__footer { display: flex; gap: var(--space-2); justify-content: flex-end; padding: var(--space-4) var(--space-5); }
15612
+ /* #372: header/footer rules (on by default), consistent with Dialog. Per-region data-divider so an empty
15613
+ region grows no stray line (no footer; a header with only a close button and no title/description). */
15614
+ .twc-drawer__header[data-divider="true"] { border-bottom: var(--border-thin) solid var(--color-divider); }
15615
+ .twc-drawer__footer[data-divider="true"] { border-top: var(--border-thin) solid var(--color-divider); }
15595
15616
  @media (prefers-reduced-motion: reduce) {
15596
15617
  .twc-drawer__overlay[data-state], .twc-drawer[data-state] { animation-duration: 1ms; }
15597
15618
  }
@@ -15607,6 +15628,7 @@ function Drawer({
15607
15628
  width,
15608
15629
  height,
15609
15630
  closeOnBackdrop = true,
15631
+ dividers = true,
15610
15632
  children,
15611
15633
  className = "",
15612
15634
  style,
@@ -15652,7 +15674,7 @@ function Drawer({
15652
15674
  }, children: [
15653
15675
  __twcStyles,
15654
15676
  /* @__PURE__ */ jsxRuntime.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: [
15655
- title || description || onClose ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-drawer__header", children: [
15677
+ title || description || onClose ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-drawer__header", "data-divider": dividers && (title || description) ? "true" : void 0, children: [
15656
15678
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-drawer__titles", children: [
15657
15679
  title ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drawer__title", id: titleId, children: title }) : null,
15658
15680
  description ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drawer__desc", id: descId, children: description }) : null
@@ -15660,7 +15682,7 @@ function Drawer({
15660
15682
  onClose ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: "twc-drawer__close", onClick: onClose, "aria-label": "Close", type: "button", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
15661
15683
  ] }) : null,
15662
15684
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drawer__body", children }),
15663
- footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drawer__footer", children: footer }) : null
15685
+ footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drawer__footer", "data-divider": dividers ? "true" : void 0, children: footer }) : null
15664
15686
  ] })
15665
15687
  ] });
15666
15688
  return renderPortal(overlay);
@@ -16361,7 +16383,9 @@ var POPOVER_CSS = `
16361
16383
  .twc-popover__arrow { position: absolute; width: 11px; height: 11px; background: var(--color-surface-raised);
16362
16384
  border-left: var(--border-thin) solid var(--color-border); border-top: var(--border-thin) solid var(--color-border);
16363
16385
  transform: rotate(45deg); }
16364
- .twc-popover__inner { padding: var(--space-4); font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-normal); }
16386
+ /* #373: cap the panel to the room place() measured and scroll INSIDE the inner (not the panel \u2014 the panel
16387
+ must keep overflow visible so the absolutely-positioned arrow isn't clipped) so a tall panel's footer stays reachable. */
16388
+ .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; }
16365
16389
  .twc-popover__title { font-size: var(--text-sm); font-weight: var(--font-bold); color: var(--color-text); margin-bottom: 4px; }
16366
16390
  `;
16367
16391
  function Popover({
@@ -16423,7 +16447,7 @@ function Popover({
16423
16447
  l = Math.max(M, Math.min(l, vw - w - M));
16424
16448
  left = l;
16425
16449
  arrow = { left: Math.min(Math.max(r2.left + r2.width / 2 - l - 5.5, 10), w - 20), [flip ? "bottom" : "top"]: -6 };
16426
- return setPos({ top, bottom, left, width: w, flip, arrow });
16450
+ return setPos({ top, bottom, left, width: w, flip, arrow, maxHeight: flip ? spaceAbove : spaceBelow });
16427
16451
  }
16428
16452
  const ph = popRef.current ? popRef.current.offsetHeight : 120;
16429
16453
  let onRight = placement === "right";
@@ -16435,7 +16459,7 @@ function Popover({
16435
16459
  const cy = r2.top + r2.height / 2;
16436
16460
  top = Math.max(M, Math.min(cy - ph / 2, vh - ph - M));
16437
16461
  arrow = { top: Math.min(Math.max(cy - top - 5.5, 8), ph - 18), [onRight ? "left" : "right"]: -6 };
16438
- setPos({ top, left, width: w, flip: false, arrow });
16462
+ setPos({ top, left, width: w, flip: false, arrow, maxHeight: vh - 2 * M });
16439
16463
  }, [placement, align, width]);
16440
16464
  React51__namespace.default.useEffect(() => {
16441
16465
  if (!open) return;
@@ -16501,7 +16525,7 @@ function Popover({
16501
16525
  children: [
16502
16526
  __twcStyles,
16503
16527
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-popover__arrow", style: pos.arrow, "aria-hidden": "true" }),
16504
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-popover__inner", children: [
16528
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-popover__inner", style: { maxHeight: pos.maxHeight }, children: [
16505
16529
  title ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-popover__title", id: `${popId}-title`, children: title }) : null,
16506
16530
  children
16507
16531
  ] })