@kennycorrea/ui 0.11.2 → 0.11.4
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 +7 -0
- package/dist/atoms/Card/Card.d.ts +5 -3
- package/dist/atoms/Card/Card.js +21 -4
- package/dist/atoms/Drawer/Drawer.js +1 -1
- package/dist/atoms/Sheet/Sheet.js +1 -1
- package/package.json +1 -1
- package/tokens.css +21 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.4 — 2026-10-20
|
|
4
|
+
|
|
5
|
+
- **`Card`** — Themes' unlayered `.rt-Card` padding (16px at size 2) beat the layered `p-0`, so every card was padded on top of `CardHeader`/`CardBody` (double padding; header dividers short of the edges). The zero is now `!p-0` plus an unlayered `.ui-card.rt-Card { padding: 0 }` in `tokens.css`. Padding utilities a caller passes (`p-*`, `px-*`, `md:py-*`…) are promoted to `!` so they still win.
|
|
6
|
+
- **`SegmentedControl fullWidth`** — items share the width (`minmax(0, 1fr)`, track `flex: 1 1 0`, inline padding 8px at `md` / 6px at `sm`; labels ellipsize) instead of growing the track past the pill, which pushed the thumb and buttons outside the rounded background and lost the end padding (four labels in 343px). The thumb lines up with each item inside the `--seg-pad` inset. Non-full controls still size to content and scroll. Example: `examples/SegmentedFull.tsx`.
|
|
7
|
+
- **`Drawer`, `Sheet`** — no outer hairline border; the elevation shadow and the rounded top carry the edge. Footer dividers stay.
|
|
8
|
+
- **Menus, popovers, selects** — Themes' `--shadow-5` starts with a `0 0 0 1px` ring that read as a border. `.rt-BaseMenuContent` (DropdownMenu, ContextMenu), `.rt-PopoverContent` and `.rt-SelectContent` get the same elevation without the ring, in light and dark (`--ui-float-shadow`). Covers Themes' own `DropdownMenu` used directly in apps.
|
|
9
|
+
|
|
3
10
|
## 0.11.2 — 2026-10-19
|
|
4
11
|
|
|
5
12
|
- **`ChipGroup`** — centring the selected chip no longer calls `scrollIntoView`, whose `block: 'nearest'` scrolled the whole page to a strip below the fold (about 1000 px on a long page). The strip's own scroller is moved with `scrollTo({ left })`, and nothing else; `prefers-reduced-motion` gets an instant scroll.
|
|
@@ -21,9 +21,11 @@ export interface CardProps extends HTMLAttributes<HTMLDivElement>, CardStyleProp
|
|
|
21
21
|
* Radix Themes' Card, with the padding taken off.
|
|
22
22
|
*
|
|
23
23
|
* Themes pads its card for you; the house card is built from a header and a body with a
|
|
24
|
-
* rule between them, so the padding belongs to those rather than to the card.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* rule between them, so the padding belongs to those rather than to the card. Themes' padding
|
|
25
|
+
* is unlayered CSS and beats a layered `p-0`, so the zero is `!p-0` (plus an unlayered rule in
|
|
26
|
+
* `tokens.css`), and any padding utility a caller passes is promoted to `!` so it still wins.
|
|
27
|
+
* Variant and density reach the header, title and body through context, and each can still
|
|
28
|
+
* override them.
|
|
27
29
|
*/
|
|
28
30
|
export declare function Card({ raised, variant, density, className, ...props }: CardProps): import("react").JSX.Element;
|
|
29
31
|
export interface CardPartProps extends HTMLAttributes<HTMLDivElement>, CardStyleProps {
|
package/dist/atoms/Card/Card.js
CHANGED
|
@@ -32,17 +32,34 @@ function useStyleAttrs(own) {
|
|
|
32
32
|
...responsiveAttrs('density', own.density ?? inherited.density),
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
const PADDING_CLASS = /^(?:[\w-]+:)*-?p[xytrblse]?-/;
|
|
36
|
+
/** `md:px-4` → `md:!px-4`; other classes untouched. Already-important classes are left alone. */
|
|
37
|
+
function importantPadding(className) {
|
|
38
|
+
if (!className)
|
|
39
|
+
return className;
|
|
40
|
+
return className
|
|
41
|
+
.split(/\s+/)
|
|
42
|
+
.map((c) => {
|
|
43
|
+
if (!c || c.includes('!') || !PADDING_CLASS.test(c))
|
|
44
|
+
return c;
|
|
45
|
+
const i = c.lastIndexOf(':') + 1;
|
|
46
|
+
return `${c.slice(0, i)}!${c.slice(i)}`;
|
|
47
|
+
})
|
|
48
|
+
.join(' ');
|
|
49
|
+
}
|
|
35
50
|
/**
|
|
36
51
|
* Radix Themes' Card, with the padding taken off.
|
|
37
52
|
*
|
|
38
53
|
* Themes pads its card for you; the house card is built from a header and a body with a
|
|
39
|
-
* rule between them, so the padding belongs to those rather than to the card.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
54
|
+
* rule between them, so the padding belongs to those rather than to the card. Themes' padding
|
|
55
|
+
* is unlayered CSS and beats a layered `p-0`, so the zero is `!p-0` (plus an unlayered rule in
|
|
56
|
+
* `tokens.css`), and any padding utility a caller passes is promoted to `!` so it still wins.
|
|
57
|
+
* Variant and density reach the header, title and body through context, and each can still
|
|
58
|
+
* override them.
|
|
42
59
|
*/
|
|
43
60
|
export function Card({ raised = false, variant = 'outlined', density = 'comfortable', className, ...props }) {
|
|
44
61
|
const attrs = useStyleAttrs({ variant, density });
|
|
45
|
-
return (_jsx(CardContext.Provider, { value: { variant, density }, children: _jsx(ThemedCard, { size: "2", variant: raised ? 'classic' : 'surface', className: cn('ui-card p-0', className), ...attrs, ...props }) }));
|
|
62
|
+
return (_jsx(CardContext.Provider, { value: { variant, density }, children: _jsx(ThemedCard, { size: "2", variant: raised ? 'classic' : 'surface', className: cn('ui-card !p-0', importantPadding(className)), ...attrs, ...props }) }));
|
|
46
63
|
}
|
|
47
64
|
export function CardHeader({ variant, density, className, ...props }) {
|
|
48
65
|
const attrs = useStyleAttrs({ variant, density });
|
|
@@ -192,6 +192,6 @@ export function Drawer({ mode = 'modal', open: openProp, defaultOpen = true, onO
|
|
|
192
192
|
onPointerUp: end,
|
|
193
193
|
onPointerCancel: cancel,
|
|
194
194
|
};
|
|
195
|
-
return (_jsxs(_Fragment, { children: [anchor, _jsx(ThemePortal, { target: target, children: _jsxs("div", { ref: wrapperRef, "data-drawer-root": "", children: [modal ? (_jsx("div", { "data-testid": "drawer-scrim", "aria-hidden": "true", className: "fixed inset-0 z-40 bg-scrim", onClick: dismissible ? close : undefined })) : null, _jsxs("div", { ref: sheetRef, role: modal ? 'dialog' : 'region', "aria-modal": modal ? true : undefined, "aria-labelledby": titleId, "aria-describedby": description ? descId : undefined, tabIndex: -1, "data-snap": snap, "data-mode": mode, onKeyDown: trapTab, style: { transform: `translateY(${Math.max(offset, -40)}px)` }, className: cn('fixed inset-x-0 bottom-0 z-50 mx-auto flex w-full flex-col', 'rounded-t-2xl
|
|
195
|
+
return (_jsxs(_Fragment, { children: [anchor, _jsx(ThemePortal, { target: target, children: _jsxs("div", { ref: wrapperRef, "data-drawer-root": "", children: [modal ? (_jsx("div", { "data-testid": "drawer-scrim", "aria-hidden": "true", className: "fixed inset-0 z-40 bg-scrim", onClick: dismissible ? close : undefined })) : null, _jsxs("div", { ref: sheetRef, role: modal ? 'dialog' : 'region', "aria-modal": modal ? true : undefined, "aria-labelledby": titleId, "aria-describedby": description ? descId : undefined, tabIndex: -1, "data-snap": snap, "data-mode": mode, onKeyDown: trapTab, style: { transform: `translateY(${Math.max(offset, -40)}px)` }, className: cn('fixed inset-x-0 bottom-0 z-50 mx-auto flex w-full flex-col', 'rounded-t-2xl bg-sheet text-ink shadow-2xl', 'pb-[env(safe-area-inset-bottom)] outline-none', !dragging &&
|
|
196
196
|
'transition-transform duration-200 ease-out motion-reduce:transition-none', maxWidthClassName, className), children: [_jsxs("div", { className: "touch-none select-none px-5 pt-2", onPointerDown: (e) => begin(e, false), ...dragHandlers, children: [_jsx("button", { type: "button", "aria-expanded": expanded, "aria-controls": bodyId, "aria-label": expanded ? (collapseLabel ?? strings.collapse) : (expandLabel ?? strings.expand), onClick: onHandleClick, className: "mx-auto flex h-6 w-16 cursor-grab items-center justify-center rounded-full focus-visible:outline-2 focus-visible:outline-brand-700 active:cursor-grabbing", children: _jsx("span", { "aria-hidden": "true", className: "h-1.5 w-10 rounded-full bg-hairline" }) }), _jsxs("div", { className: "flex items-start gap-3 pb-3 pt-1", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("h2", { id: titleId, className: "m-0 text-base font-medium text-brand-900", children: title }), description ? (_jsx("p", { id: descId, className: "m-0 mt-1 text-sm text-ink-muted", children: description })) : null] }), showCloseButton && dismissible ? (_jsx("button", { type: "button", "aria-label": closeLabel ?? strings.close, onClick: close, className: "-mr-1 flex size-8 shrink-0 items-center justify-center rounded-full text-ink-muted hover:bg-brand-700/8 focus-visible:outline-2 focus-visible:outline-brand-700", children: _jsx("svg", { "aria-hidden": "true", width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: _jsx("path", { d: "M3.5 3.5l8 8m0-8l-8 8", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }) })) : null] })] }), !expanded && peek ? _jsx("div", { className: "px-5 pb-4", children: peek }) : null, _jsx("div", { ref: bodyRef, id: bodyId, hidden: !expanded, className: "max-h-[80vh] overflow-y-auto overscroll-contain px-5 pb-4", onPointerDown: (e) => begin(e, true), ...dragHandlers, children: children }), footer ? (_jsx("div", { className: "flex justify-end gap-2 border-t border-hairline px-5 py-3", children: footer })) : null] })] }) })] }));
|
|
197
197
|
}
|
|
@@ -248,7 +248,7 @@ export function Sheet({ open, onOpenChange, title, description, children, footer
|
|
|
248
248
|
const content = (_jsxs("div", { ref: rootRef, "data-sheet-root": "", className: "contents", children: [_jsx(RadixDialog.Overlay, { "data-testid": "sheet-scrim", className: "ui-sheet-scrim fixed inset-0 z-40 bg-scrim" }), _jsxs(RadixDialog.Content, { ref: contentRef, "data-detent": detent, "data-dragging": dragging ? '' : undefined, ...(description ? {} : { 'aria-describedby': undefined }), onEscapeKeyDown: dismissGuard, onPointerDownOutside: dismissGuard, onInteractOutside: dismissGuard,
|
|
249
249
|
// The sheet moves focus to its first control; for a sheet that is the handle or close
|
|
250
250
|
// button, which is correct, so Radix's default is kept.
|
|
251
|
-
style: style, className: cn('ui-sheet fixed inset-x-0 bottom-0 z-50 mx-auto flex w-full flex-col overflow-hidden', 'rounded-t-2xl
|
|
251
|
+
style: style, className: cn('ui-sheet fixed inset-x-0 bottom-0 z-50 mx-auto flex w-full flex-col overflow-hidden', 'rounded-t-2xl bg-sheet text-ink shadow-2xl outline-none', maxWidthClassName, className), children: [_jsxs("div", { className: "touch-none select-none px-5 pt-2", onPointerDown: (e) => begin(e, false), ...dragHandlers, children: [cycles ? (_jsx("button", { type: "button", "aria-label": raising ? (expandLabel ?? strings.expand) : (collapseLabel ?? strings.collapse), onClick: onHandleClick, className: "mx-auto flex h-6 w-16 cursor-grab items-center justify-center rounded-full focus-visible:outline-2 focus-visible:outline-brand-700 active:cursor-grabbing", children: _jsx("span", { "aria-hidden": "true", className: "h-1.5 w-10 rounded-full bg-hairline" }) })) : (_jsx("div", { "aria-hidden": "true", className: "mx-auto flex h-6 w-16 items-center justify-center", children: _jsx("span", { className: "h-1.5 w-10 rounded-full bg-hairline" }) })), _jsxs("div", { className: "flex items-start gap-3 pb-3 pt-1", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(RadixDialog.Title, { className: "m-0 text-base font-medium text-brand-900", children: title }), description ? (_jsx(RadixDialog.Description, { className: "m-0 mt-1 text-sm text-ink-muted", children: description })) : null] }), showCloseButton && dismissible ? (_jsx(RadixDialog.Close, { "aria-label": closeLabel ?? strings.close, className: "-mr-1 flex size-8 shrink-0 items-center justify-center rounded-full text-ink-muted hover:bg-brand-700/8 focus-visible:outline-2 focus-visible:outline-brand-700", children: _jsx("svg", { "aria-hidden": "true", width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: _jsx("path", { d: "M3.5 3.5l8 8m0-8l-8 8", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }) })) : null] })] }), _jsx("div", { ref: bodyRef, "data-sheet-body": "", className: "min-h-0 flex-1 overflow-y-auto overscroll-contain px-5 pb-4", onPointerDown: (e) => begin(e, true), ...dragHandlers, children: children }), footer ? (_jsx("div", { "data-sheet-footer": "", className: "flex shrink-0 justify-end gap-2 border-t border-hairline px-5 pt-3",
|
|
252
252
|
// Over the keyboard there is no home indicator to clear, only a gap to leave.
|
|
253
253
|
style: {
|
|
254
254
|
paddingBottom: keyboard ? '0.75rem' : 'max(0.75rem, var(--safe-bottom, 0px))',
|
package/package.json
CHANGED
package/tokens.css
CHANGED
|
@@ -592,6 +592,9 @@
|
|
|
592
592
|
* `md` is 768. Ascending media queries of equal specificity, so the widest match wins. The card's
|
|
593
593
|
* pseudo-elements carry the surface and ring, so grouped/plain switch them off with `content`.
|
|
594
594
|
*/
|
|
595
|
+
/* Unlayered, like Themes' `.rt-Card` padding, and more specific: the header and body pad, the card
|
|
596
|
+
does not. A caller's padding class reaches the card as `!p-*` (see Card.tsx), which still wins. */
|
|
597
|
+
.ui-card.rt-Card { padding: 0; }
|
|
595
598
|
.ui-card-header, .ui-card-body { padding: 16px 20px; }
|
|
596
599
|
.ui-card-title { font-size: 1rem; font-weight: 500; }
|
|
597
600
|
.ui-card.rt-Card[data-variant="outlined"] { background: transparent; --base-card-border-radius: var(--card-border-radius);}
|
|
@@ -704,13 +707,13 @@
|
|
|
704
707
|
/* ---- Grouped list: ListSection / ListRow ---- */
|
|
705
708
|
.ui-list-section { --list-lead-w: 28px; --list-gap: 12px; }
|
|
706
709
|
.ui-list-section[data-variant="inset"] { margin-inline: var(--space-row-x); }
|
|
707
|
-
.ui-list-header, .ui-list-footer { font-size: 0.8125rem; line-height: 1.
|
|
710
|
+
.ui-list-header, .ui-list-footer { font-size: 0.8125rem; line-height: 1.125rem; color: var(--gray-11); padding: 6px calc(var(--space-row-x) + 4px); }
|
|
708
711
|
.ui-list-header { text-transform: uppercase; letter-spacing: 0.02em; font-size: 0.75rem; }
|
|
709
712
|
.ui-list { list-style: none; margin: 0; padding: 0; background: var(--surface-grouped); overflow: hidden; }
|
|
710
713
|
.ui-list-section[data-variant="inset"] .ui-list { border-radius: var(--radius-group); }
|
|
711
714
|
.ui-list-item { position: relative; list-style: none; }
|
|
712
715
|
/* Hairline above every row but the first; it starts after the leading slot when this row has one. */
|
|
713
|
-
.ui-list-item + .ui-list-item::before { content: ""; position: absolute; top: 0; right: 0; height:
|
|
716
|
+
.ui-list-item + .ui-list-item::before { content: ""; position: absolute; top: 0; right: 0; height: 0; border-top: 1px solid var(--separator-inset); left: var(--space-row-x); }
|
|
714
717
|
.ui-list-item + .ui-list-item[data-has-lead]::before { left: calc(var(--space-row-x) + var(--list-lead-w) + var(--list-gap)); }
|
|
715
718
|
.ui-list-section[data-variant="full"] .ui-list-item + .ui-list-item::before { left: 0; }
|
|
716
719
|
.ui-list-row { display: flex; align-items: center; gap: var(--list-gap); box-sizing: border-box; width: 100%; min-height: var(--tap-min); padding: var(--space-row-y) var(--space-row-x); background: transparent; border: 0; margin: 0; font: inherit; text-align: start; color: var(--gray-12); text-decoration: none; -webkit-tap-highlight-color: transparent; }
|
|
@@ -841,6 +844,12 @@
|
|
|
841
844
|
.ui-segmented__label { display: block; overflow: hidden; text-overflow: ellipsis; }
|
|
842
845
|
.ui-segmented[data-size="md"] .ui-segmented__item { min-height: 32px; padding: 0 16px; font-size: 14px; }
|
|
843
846
|
.ui-segmented[data-size="sm"] .ui-segmented__item { min-height: 24px; padding: 0 12px; font-size: 12px; }
|
|
847
|
+
/* Full width: items share the track instead of growing it past the pill. Equal 1fr columns from a
|
|
848
|
+
zero basis keep the thumb (100% / count) on each item exactly; labels ellipsize. */
|
|
849
|
+
.ui-segmented[data-full="true"] { overflow-x: hidden; }
|
|
850
|
+
.ui-segmented[data-full="true"] .ui-segmented__track { grid-auto-columns: minmax(0, 1fr); flex: 1 1 0; }
|
|
851
|
+
.ui-segmented[data-full="true"][data-size="md"] .ui-segmented__item { padding: 0 8px; }
|
|
852
|
+
.ui-segmented[data-full="true"][data-size="sm"] .ui-segmented__item { padding: 0 6px; }
|
|
844
853
|
.ui-segmented__item[aria-checked="true"] { color: var(--gray-12); }
|
|
845
854
|
.ui-segmented__item:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
846
855
|
.ui-segmented__item:focus-visible { outline: 2px solid var(--focus-8); outline-offset: 1px; }
|
|
@@ -917,3 +926,13 @@
|
|
|
917
926
|
.ui-button, .ui-list-row[data-interactive], .ui-tabbar__item, .ui-stat[data-interactive] { transition: none; }
|
|
918
927
|
.ui-button:active, .ui-list-row[data-interactive]:active, .ui-tabbar__item:active, .ui-stat[data-interactive]:active { transform: none; }
|
|
919
928
|
}
|
|
929
|
+
|
|
930
|
+
/* Floating content: Themes' --shadow-5 opens with a `0 0 0 1px` ring that reads as a border on
|
|
931
|
+
menus, popovers and selects. Same elevation, without the ring. Unlayered to beat Themes. */
|
|
932
|
+
.radix-themes {
|
|
933
|
+
--ui-float-shadow: 0 12px 60px var(--black-a3), 0 12px 32px -16px var(--gray-a5);
|
|
934
|
+
}
|
|
935
|
+
.radix-themes:is(.dark, .dark-theme), :is(.dark, .dark-theme) .radix-themes:not(.light, .light-theme) {
|
|
936
|
+
--ui-float-shadow: 0 12px 60px var(--black-a5), 0 12px 32px -16px var(--black-a7);
|
|
937
|
+
}
|
|
938
|
+
.rt-BaseMenuContent, .rt-SelectContent, .rt-PopoverContent { box-shadow: var(--ui-float-shadow); }
|