@kennycorrea/ui 0.9.0 → 0.9.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 +10 -0
- package/README.md +3 -0
- package/dist/atoms/Drawer/Drawer.d.ts +2 -2
- package/dist/atoms/Drawer/Drawer.js +25 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.1 — 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **`Drawer` renders inside the nearest `<Theme>`** (`.radix-themes`) instead of under
|
|
7
|
+
`<body>`. Outside a Theme none of the house tokens or the accent are defined, so in 0.9.0 the
|
|
8
|
+
sheet lost the app's appearance — a dark app got a light sheet. Falls back to `<body>` when
|
|
9
|
+
there is no Theme; `container` still overrides it.
|
|
10
|
+
- In `modal` mode, everything outside the sheet goes `inert` at every level up to `<body>`, not
|
|
11
|
+
only the sheet's siblings, now that the sheet sits inside the Theme rather than beside it.
|
|
12
|
+
|
|
3
13
|
## 0.9.0 — 2026-10-18
|
|
4
14
|
|
|
5
15
|
### Added
|
package/README.md
CHANGED
|
@@ -343,5 +343,8 @@ above that (`maxWidthClassName` to change it), safe-area aware, themed from the
|
|
|
343
343
|
`UiStringsProvider`.
|
|
344
344
|
- Motion is a CSS transform, switched off under `prefers-reduced-motion`.
|
|
345
345
|
|
|
346
|
+
It portals into the nearest `<Theme>` so it inherits the appearance and accent, falling back to
|
|
347
|
+
`<body>`; `container` overrides that.
|
|
348
|
+
|
|
346
349
|
Built by hand rather than on Radix Dialog because the non-modal half must not do what Dialog
|
|
347
350
|
does to the rest of the page. Both examples are in `examples/Drawers.tsx`.
|
|
@@ -43,7 +43,7 @@ export interface DrawerProps {
|
|
|
43
43
|
/** Caps the width on wider screens. Default `max-w-lg`; pass `max-w-none` for full width. */
|
|
44
44
|
maxWidthClassName?: string;
|
|
45
45
|
className?: string;
|
|
46
|
-
/** Where the portal mounts. Default `document.body`. */
|
|
46
|
+
/** Where the portal mounts. Default: the nearest `<Theme>` (`.radix-themes`), else `document.body`. */
|
|
47
47
|
container?: HTMLElement | null;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
@@ -59,4 +59,4 @@ export interface DrawerProps {
|
|
|
59
59
|
* from inside the body only counts when the body is scrolled to its top and moving down, so the
|
|
60
60
|
* content still scrolls normally.
|
|
61
61
|
*/
|
|
62
|
-
export declare function Drawer({ mode, open: openProp, defaultOpen, onOpenChange, onClose, snap: snapProp, defaultSnap, onSnapChange, dismissible, title, description, peek, children, footer, showCloseButton, closeLabel, expandLabel, collapseLabel, maxWidthClassName, className, container, }: DrawerProps): import("react").
|
|
62
|
+
export declare function Drawer({ mode, open: openProp, defaultOpen, onOpenChange, onClose, snap: snapProp, defaultSnap, onSnapChange, dismissible, title, description, peek, children, footer, showCloseButton, closeLabel, expandLabel, collapseLabel, maxWidthClassName, className, container, }: DrawerProps): import("react").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useId, useRef, useState, } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState, } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { useUiStrings } from '../../strings.js';
|
|
5
5
|
import { cn } from '../../utils/cn.js';
|
|
@@ -53,21 +53,32 @@ export function Drawer({ mode = 'modal', open: openProp, defaultOpen = true, onO
|
|
|
53
53
|
setOpen(false);
|
|
54
54
|
onClose?.();
|
|
55
55
|
}, [setOpen, onClose]);
|
|
56
|
+
// Portal into the nearest <Theme> so the sheet inherits its appearance, accent and tokens;
|
|
57
|
+
// a portal under <body> would sit outside `.radix-themes`, where none of them are defined.
|
|
58
|
+
const anchorRef = useRef(null);
|
|
59
|
+
const [themeRoot, setThemeRoot] = useState(null);
|
|
60
|
+
useLayoutEffect(() => {
|
|
61
|
+
setThemeRoot(anchorRef.current?.closest('.radix-themes') ?? document.body);
|
|
62
|
+
}, []);
|
|
63
|
+
const target = container ?? themeRoot;
|
|
56
64
|
// Modal: everything else on the page goes inert, focus moves in, and comes back on close.
|
|
57
65
|
useEffect(() => {
|
|
58
|
-
|
|
66
|
+
// The wrapper only exists once the target is known, so wait for it.
|
|
67
|
+
if (!open || !modal || !target)
|
|
59
68
|
return;
|
|
60
69
|
const wrapper = wrapperRef.current;
|
|
61
|
-
const root = wrapper?.parentElement;
|
|
62
70
|
const previous = document.activeElement;
|
|
63
71
|
const made = [];
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
// Every sibling on the way up to <body>: the app inside the Theme, and anything beside it.
|
|
73
|
+
for (let node = wrapper ?? null; node && node !== document.body;) {
|
|
74
|
+
const parent = node.parentElement;
|
|
75
|
+
for (const el of Array.from(parent?.children ?? [])) {
|
|
76
|
+
if (el !== node && !el.hasAttribute('inert')) {
|
|
67
77
|
el.setAttribute('inert', '');
|
|
68
78
|
made.push(el);
|
|
69
79
|
}
|
|
70
80
|
}
|
|
81
|
+
node = parent;
|
|
71
82
|
}
|
|
72
83
|
const first = sheetRef.current?.querySelector(FOCUSABLE);
|
|
73
84
|
(first ?? sheetRef.current)?.focus();
|
|
@@ -76,7 +87,7 @@ export function Drawer({ mode = 'modal', open: openProp, defaultOpen = true, onO
|
|
|
76
87
|
el.removeAttribute('inert');
|
|
77
88
|
previous?.focus?.();
|
|
78
89
|
};
|
|
79
|
-
}, [open, modal]);
|
|
90
|
+
}, [open, modal, target]);
|
|
80
91
|
useEffect(() => {
|
|
81
92
|
if (!open || !modal || !dismissible)
|
|
82
93
|
return;
|
|
@@ -176,15 +187,15 @@ export function Drawer({ mode = 'modal', open: openProp, defaultOpen = true, onO
|
|
|
176
187
|
}
|
|
177
188
|
setSnap(expanded ? 'collapsed' : 'expanded');
|
|
178
189
|
};
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return null;
|
|
190
|
+
// A hidden marker stays in the React tree's DOM position, so the portal can find its Theme.
|
|
191
|
+
const anchor = _jsx("span", { ref: anchorRef, hidden: true, "data-drawer-anchor": "" });
|
|
192
|
+
if (!open || !target)
|
|
193
|
+
return anchor;
|
|
184
194
|
const dragHandlers = {
|
|
185
195
|
onPointerMove: move,
|
|
186
196
|
onPointerUp: end,
|
|
187
197
|
onPointerCancel: cancel,
|
|
188
198
|
};
|
|
189
|
-
return createPortal(_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 border border-b-0 border-hairline bg-sheet text-ink shadow-2xl', 'pb-[env(safe-area-inset-bottom)] outline-none', !dragging &&
|
|
199
|
+
return (_jsxs(_Fragment, { children: [anchor, createPortal(_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 border border-b-0 border-hairline bg-sheet text-ink shadow-2xl', 'pb-[env(safe-area-inset-bottom)] outline-none', !dragging &&
|
|
200
|
+
'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] })] }), target)] }));
|
|
190
201
|
}
|