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 +14 -0
- package/dist/index.cjs +34 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.mjs +34 -10
- package/dist/index.mjs.map +1 -1
- package/llms.txt +3 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -122,7 +122,9 @@ interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "al
|
|
|
122
122
|
src?: string;
|
|
123
123
|
/** Alternative text (required for accessibility; pass "" for decorative images). */
|
|
124
124
|
alt: string;
|
|
125
|
-
/** object-fit value.
|
|
125
|
+
/** object-fit value. When set, the image fills its container by default (`width`/`height: 100%`); pass an
|
|
126
|
+
* explicit `width`/`height` (or size the container / `style`) to give it a fixed box that `fit` crops within
|
|
127
|
+
* — an explicit `width`/`height` now wins over the fill (#378). */
|
|
126
128
|
fit?: "cover" | "contain" | "fill" | "none" | "scale-down";
|
|
127
129
|
/** Border radius token (e.g. "md", "lg", "full"). */
|
|
128
130
|
radius?: string;
|
|
@@ -2915,8 +2917,14 @@ interface DialogProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"
|
|
|
2915
2917
|
footer?: React.ReactNode;
|
|
2916
2918
|
/** `"full"` is a near-fullscreen panel (fills the viewport minus a small margin). @default "md" */
|
|
2917
2919
|
size?: "sm" | "md" | "lg" | "full";
|
|
2918
|
-
/** Keep the header + footer fixed and scroll only the body
|
|
2920
|
+
/** Keep the header + footer fixed and scroll only the body when content exceeds the viewport — so the
|
|
2921
|
+
* action buttons never scroll off-screen (#371). Set `false` for a panel that should scroll as one block
|
|
2922
|
+
* (rare; note a non-portaled child that deliberately overflows the panel would be clipped). @default true */
|
|
2919
2923
|
scrollBody?: boolean;
|
|
2924
|
+
/** Draw a rule between header/body and body/footer. Suppressed automatically where the region is empty
|
|
2925
|
+
* (no footer; a header with only a close button and no title/description). Set `false` for a panel that
|
|
2926
|
+
* should read as one continuous surface. @default true */
|
|
2927
|
+
dividers?: boolean;
|
|
2920
2928
|
/** Close when the backdrop is clicked. @default true */
|
|
2921
2929
|
closeOnBackdrop?: boolean;
|
|
2922
2930
|
children?: React.ReactNode;
|
|
@@ -2962,6 +2970,10 @@ interface DrawerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"
|
|
|
2962
2970
|
height?: "sm" | "md" | "lg" | number | string;
|
|
2963
2971
|
/** Close when the backdrop is clicked. @default true */
|
|
2964
2972
|
closeOnBackdrop?: boolean;
|
|
2973
|
+
/** Draw a rule between header/body and body/footer (matches `Dialog`). Suppressed automatically where the
|
|
2974
|
+
* region is empty (no footer; a header with only a close button and no title/description). Set `false` for a
|
|
2975
|
+
* panel that should read as one continuous surface. @default true */
|
|
2976
|
+
dividers?: boolean;
|
|
2965
2977
|
children?: React.ReactNode;
|
|
2966
2978
|
}
|
|
2967
2979
|
|
package/dist/index.d.ts
CHANGED
|
@@ -122,7 +122,9 @@ interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "al
|
|
|
122
122
|
src?: string;
|
|
123
123
|
/** Alternative text (required for accessibility; pass "" for decorative images). */
|
|
124
124
|
alt: string;
|
|
125
|
-
/** object-fit value.
|
|
125
|
+
/** object-fit value. When set, the image fills its container by default (`width`/`height: 100%`); pass an
|
|
126
|
+
* explicit `width`/`height` (or size the container / `style`) to give it a fixed box that `fit` crops within
|
|
127
|
+
* — an explicit `width`/`height` now wins over the fill (#378). */
|
|
126
128
|
fit?: "cover" | "contain" | "fill" | "none" | "scale-down";
|
|
127
129
|
/** Border radius token (e.g. "md", "lg", "full"). */
|
|
128
130
|
radius?: string;
|
|
@@ -2915,8 +2917,14 @@ interface DialogProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"
|
|
|
2915
2917
|
footer?: React.ReactNode;
|
|
2916
2918
|
/** `"full"` is a near-fullscreen panel (fills the viewport minus a small margin). @default "md" */
|
|
2917
2919
|
size?: "sm" | "md" | "lg" | "full";
|
|
2918
|
-
/** Keep the header + footer fixed and scroll only the body
|
|
2920
|
+
/** Keep the header + footer fixed and scroll only the body when content exceeds the viewport — so the
|
|
2921
|
+
* action buttons never scroll off-screen (#371). Set `false` for a panel that should scroll as one block
|
|
2922
|
+
* (rare; note a non-portaled child that deliberately overflows the panel would be clipped). @default true */
|
|
2919
2923
|
scrollBody?: boolean;
|
|
2924
|
+
/** Draw a rule between header/body and body/footer. Suppressed automatically where the region is empty
|
|
2925
|
+
* (no footer; a header with only a close button and no title/description). Set `false` for a panel that
|
|
2926
|
+
* should read as one continuous surface. @default true */
|
|
2927
|
+
dividers?: boolean;
|
|
2920
2928
|
/** Close when the backdrop is clicked. @default true */
|
|
2921
2929
|
closeOnBackdrop?: boolean;
|
|
2922
2930
|
children?: React.ReactNode;
|
|
@@ -2962,6 +2970,10 @@ interface DrawerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"
|
|
|
2962
2970
|
height?: "sm" | "md" | "lg" | number | string;
|
|
2963
2971
|
/** Close when the backdrop is clicked. @default true */
|
|
2964
2972
|
closeOnBackdrop?: boolean;
|
|
2973
|
+
/** Draw a rule between header/body and body/footer (matches `Dialog`). Suppressed automatically where the
|
|
2974
|
+
* region is empty (no footer; a header with only a close button and no title/description). Set `false` for a
|
|
2975
|
+
* panel that should read as one continuous surface. @default true */
|
|
2976
|
+
dividers?: boolean;
|
|
2965
2977
|
children?: React.ReactNode;
|
|
2966
2978
|
}
|
|
2967
2979
|
|
package/dist/index.mjs
CHANGED
|
@@ -304,6 +304,8 @@ var Image = React51__default.forwardRef(function Image2({
|
|
|
304
304
|
className = "",
|
|
305
305
|
style,
|
|
306
306
|
onError,
|
|
307
|
+
width,
|
|
308
|
+
height,
|
|
307
309
|
...rest
|
|
308
310
|
}, ref) {
|
|
309
311
|
const __twcStyles = useScopedStyles("twc-image-styles", IMAGE_CSS);
|
|
@@ -313,6 +315,14 @@ var Image = React51__default.forwardRef(function Image2({
|
|
|
313
315
|
objectFit: fit,
|
|
314
316
|
borderRadius: radius ? `var(--radius-${radius})` : void 0,
|
|
315
317
|
aspectRatio,
|
|
318
|
+
// #378: only when `fit` is set, also emit explicit width/height as inline style — that's the sole case
|
|
319
|
+
// where the `[data-fit] { width/height: 100% }` rule overrides the width/height ATTRIBUTES (a
|
|
320
|
+
// presentational attr loses to a real CSS rule), silently sizing `<Image width height fit>` to the
|
|
321
|
+
// container/intrinsic size. Inline style beats that rule so the props mean what they say (object-fit
|
|
322
|
+
// still crops within the box), placed before `...style` so a caller's own `style` still wins. Gated on
|
|
323
|
+
// `fit` so the no-fit path stays attribute-only — a consumer's own CSS/className can still override it.
|
|
324
|
+
...fit && width != null ? { width } : null,
|
|
325
|
+
...fit && height != null ? { height } : null,
|
|
316
326
|
...style
|
|
317
327
|
};
|
|
318
328
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -326,6 +336,8 @@ var Image = React51__default.forwardRef(function Image2({
|
|
|
326
336
|
loading,
|
|
327
337
|
className: `twc-image ${className}`.trim(),
|
|
328
338
|
"data-fit": fit || void 0,
|
|
339
|
+
width,
|
|
340
|
+
height,
|
|
329
341
|
style: s,
|
|
330
342
|
onError: (e) => {
|
|
331
343
|
onError?.(e);
|
|
@@ -15412,6 +15424,10 @@ var DIALOG_CSS = `
|
|
|
15412
15424
|
.twc-dialog__close svg { width: 18px; height: 18px; }
|
|
15413
15425
|
.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
15426
|
.twc-dialog__footer { display: flex; gap: var(--space-2); justify-content: flex-end; padding: 0 var(--space-5) var(--space-5); }
|
|
15427
|
+
/* #372: header/body and body/footer rules (on by default). Per-region data-divider so the rule is
|
|
15428
|
+
suppressed when the region is empty (no footer; a header with only a close button and no title/description). */
|
|
15429
|
+
.twc-dialog__header[data-divider="true"] { padding-bottom: var(--space-4); border-bottom: var(--border-thin) solid var(--color-divider); }
|
|
15430
|
+
.twc-dialog__footer[data-divider="true"] { padding-top: var(--space-4); border-top: var(--border-thin) solid var(--color-divider); }
|
|
15415
15431
|
`;
|
|
15416
15432
|
function Dialog({
|
|
15417
15433
|
open,
|
|
@@ -15421,7 +15437,8 @@ function Dialog({
|
|
|
15421
15437
|
footer,
|
|
15422
15438
|
children,
|
|
15423
15439
|
size = "md",
|
|
15424
|
-
scrollBody =
|
|
15440
|
+
scrollBody = true,
|
|
15441
|
+
dividers = true,
|
|
15425
15442
|
closeOnBackdrop = true,
|
|
15426
15443
|
className = "",
|
|
15427
15444
|
...rest
|
|
@@ -15462,7 +15479,7 @@ function Dialog({
|
|
|
15462
15479
|
}, children: [
|
|
15463
15480
|
__twcStyles,
|
|
15464
15481
|
/* @__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: [
|
|
15482
|
+
title || description || onClose ? /* @__PURE__ */ jsxs("div", { className: "twc-dialog__header", "data-divider": dividers && (title || description) && (children || footer) ? "true" : void 0, children: [
|
|
15466
15483
|
/* @__PURE__ */ jsxs("div", { className: "twc-dialog__titles", children: [
|
|
15467
15484
|
title ? /* @__PURE__ */ jsx("div", { className: "twc-dialog__title", id: titleId, children: title }) : null,
|
|
15468
15485
|
description ? /* @__PURE__ */ jsx("div", { className: "twc-dialog__desc", id: descId, children: description }) : null
|
|
@@ -15470,7 +15487,7 @@ function Dialog({
|
|
|
15470
15487
|
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
15488
|
] }) : null,
|
|
15472
15489
|
children ? /* @__PURE__ */ jsx("div", { className: "twc-dialog__body", children }) : null,
|
|
15473
|
-
footer ? /* @__PURE__ */ jsx("div", { className: "twc-dialog__footer", children: footer }) : null
|
|
15490
|
+
footer ? /* @__PURE__ */ jsx("div", { className: "twc-dialog__footer", "data-divider": dividers && children ? "true" : void 0, children: footer }) : null
|
|
15474
15491
|
] })
|
|
15475
15492
|
] });
|
|
15476
15493
|
return renderPortal(overlay);
|
|
@@ -15570,7 +15587,11 @@ var DRAWER_CSS = `
|
|
|
15570
15587
|
.twc-drawer__close:hover { background: var(--color-surface-sunken); color: var(--color-text); }
|
|
15571
15588
|
.twc-drawer__close svg { width: 18px; height: 18px; }
|
|
15572
15589
|
.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);
|
|
15590
|
+
.twc-drawer__footer { display: flex; gap: var(--space-2); justify-content: flex-end; padding: var(--space-4) var(--space-5); }
|
|
15591
|
+
/* #372: header/footer rules (on by default), consistent with Dialog. Per-region data-divider so an empty
|
|
15592
|
+
region grows no stray line (no footer; a header with only a close button and no title/description). */
|
|
15593
|
+
.twc-drawer__header[data-divider="true"] { border-bottom: var(--border-thin) solid var(--color-divider); }
|
|
15594
|
+
.twc-drawer__footer[data-divider="true"] { border-top: var(--border-thin) solid var(--color-divider); }
|
|
15574
15595
|
@media (prefers-reduced-motion: reduce) {
|
|
15575
15596
|
.twc-drawer__overlay[data-state], .twc-drawer[data-state] { animation-duration: 1ms; }
|
|
15576
15597
|
}
|
|
@@ -15586,6 +15607,7 @@ function Drawer({
|
|
|
15586
15607
|
width,
|
|
15587
15608
|
height,
|
|
15588
15609
|
closeOnBackdrop = true,
|
|
15610
|
+
dividers = true,
|
|
15589
15611
|
children,
|
|
15590
15612
|
className = "",
|
|
15591
15613
|
style,
|
|
@@ -15631,7 +15653,7 @@ function Drawer({
|
|
|
15631
15653
|
}, children: [
|
|
15632
15654
|
__twcStyles,
|
|
15633
15655
|
/* @__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: [
|
|
15656
|
+
title || description || onClose ? /* @__PURE__ */ jsxs("div", { className: "twc-drawer__header", "data-divider": dividers && (title || description) ? "true" : void 0, children: [
|
|
15635
15657
|
/* @__PURE__ */ jsxs("div", { className: "twc-drawer__titles", children: [
|
|
15636
15658
|
title ? /* @__PURE__ */ jsx("div", { className: "twc-drawer__title", id: titleId, children: title }) : null,
|
|
15637
15659
|
description ? /* @__PURE__ */ jsx("div", { className: "twc-drawer__desc", id: descId, children: description }) : null
|
|
@@ -15639,7 +15661,7 @@ function Drawer({
|
|
|
15639
15661
|
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
15662
|
] }) : null,
|
|
15641
15663
|
/* @__PURE__ */ jsx("div", { className: "twc-drawer__body", children }),
|
|
15642
|
-
footer ? /* @__PURE__ */ jsx("div", { className: "twc-drawer__footer", children: footer }) : null
|
|
15664
|
+
footer ? /* @__PURE__ */ jsx("div", { className: "twc-drawer__footer", "data-divider": dividers ? "true" : void 0, children: footer }) : null
|
|
15643
15665
|
] })
|
|
15644
15666
|
] });
|
|
15645
15667
|
return renderPortal(overlay);
|
|
@@ -16340,7 +16362,9 @@ var POPOVER_CSS = `
|
|
|
16340
16362
|
.twc-popover__arrow { position: absolute; width: 11px; height: 11px; background: var(--color-surface-raised);
|
|
16341
16363
|
border-left: var(--border-thin) solid var(--color-border); border-top: var(--border-thin) solid var(--color-border);
|
|
16342
16364
|
transform: rotate(45deg); }
|
|
16343
|
-
|
|
16365
|
+
/* #373: cap the panel to the room place() measured and scroll INSIDE the inner (not the panel \u2014 the panel
|
|
16366
|
+
must keep overflow visible so the absolutely-positioned arrow isn't clipped) so a tall panel's footer stays reachable. */
|
|
16367
|
+
.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
16368
|
.twc-popover__title { font-size: var(--text-sm); font-weight: var(--font-bold); color: var(--color-text); margin-bottom: 4px; }
|
|
16345
16369
|
`;
|
|
16346
16370
|
function Popover({
|
|
@@ -16402,7 +16426,7 @@ function Popover({
|
|
|
16402
16426
|
l = Math.max(M, Math.min(l, vw - w - M));
|
|
16403
16427
|
left = l;
|
|
16404
16428
|
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 });
|
|
16429
|
+
return setPos({ top, bottom, left, width: w, flip, arrow, maxHeight: flip ? spaceAbove : spaceBelow });
|
|
16406
16430
|
}
|
|
16407
16431
|
const ph = popRef.current ? popRef.current.offsetHeight : 120;
|
|
16408
16432
|
let onRight = placement === "right";
|
|
@@ -16414,7 +16438,7 @@ function Popover({
|
|
|
16414
16438
|
const cy = r2.top + r2.height / 2;
|
|
16415
16439
|
top = Math.max(M, Math.min(cy - ph / 2, vh - ph - M));
|
|
16416
16440
|
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 });
|
|
16441
|
+
setPos({ top, left, width: w, flip: false, arrow, maxHeight: vh - 2 * M });
|
|
16418
16442
|
}, [placement, align, width]);
|
|
16419
16443
|
React51__default.useEffect(() => {
|
|
16420
16444
|
if (!open) return;
|
|
@@ -16480,7 +16504,7 @@ function Popover({
|
|
|
16480
16504
|
children: [
|
|
16481
16505
|
__twcStyles,
|
|
16482
16506
|
/* @__PURE__ */ jsx("span", { className: "twc-popover__arrow", style: pos.arrow, "aria-hidden": "true" }),
|
|
16483
|
-
/* @__PURE__ */ jsxs("div", { className: "twc-popover__inner", children: [
|
|
16507
|
+
/* @__PURE__ */ jsxs("div", { className: "twc-popover__inner", style: { maxHeight: pos.maxHeight }, children: [
|
|
16484
16508
|
title ? /* @__PURE__ */ jsx("div", { className: "twc-popover__title", id: `${popId}-title`, children: title }) : null,
|
|
16485
16509
|
children
|
|
16486
16510
|
] })
|