@pk-design/react-tailwind 1.12.0 → 1.13.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/cjs/components/Drawer/Drawer.js +93 -9
- package/dist/cjs/components/Drawer/Drawer.js.map +1 -1
- package/dist/es/components/Drawer/Drawer.js +94 -10
- package/dist/es/components/Drawer/Drawer.js.map +1 -1
- package/dist/react-tailwind.cjs.js +93 -9
- package/dist/react-tailwind.cjs.js.map +1 -1
- package/dist/react-tailwind.es.js +93 -9
- package/dist/react-tailwind.es.js.map +1 -1
- package/dist/types/components/Drawer/Drawer.types.d.ts +14 -0
- package/package.json +1 -1
|
@@ -384,27 +384,58 @@ const panelSizeMap = {
|
|
|
384
384
|
'2xl': 'max-w-2xl',
|
|
385
385
|
full: 'w-full',
|
|
386
386
|
};
|
|
387
|
-
|
|
387
|
+
// How much shorter than the window the visual viewport must be before it's
|
|
388
|
+
// taken to be the on-screen keyboard rather than the URL bar (≈ 56px).
|
|
389
|
+
const KEYBOARD_THRESHOLD = 150;
|
|
390
|
+
const Drawer = ({ isOpen, onClose = () => { }, closeOnOutsideClick = true, align: alignProp = 'end', size: sizeProp = 'md', backdrop = true, title, description, children, footer, footerSticky = false, panelClass = '', contentClass = '', titleClass = '', descriptionClass = '', titleSticky = false, showBackButton = false, showCloseButton = true, dragHandle, phoneLayout = 'default', headerAction, }) => {
|
|
388
391
|
const align = useResponsiveValue(alignProp);
|
|
389
392
|
const size = useResponsiveValue(sizeProp);
|
|
393
|
+
const breakpoint = useBreakpoint();
|
|
394
|
+
const fullscreen = phoneLayout === 'fullscreen' && breakpoint === 'compact';
|
|
390
395
|
const showTitleSection = Boolean(title || description || showBackButton || showCloseButton);
|
|
391
|
-
const shouldShowDragHandle = dragHandle !== null && dragHandle !== void 0 ? dragHandle : align === 'bottom';
|
|
392
|
-
|
|
396
|
+
const shouldShowDragHandle = !fullscreen && (dragHandle !== null && dragHandle !== void 0 ? dragHandle : align === 'bottom');
|
|
397
|
+
useCloseOnBack(fullscreen && isOpen, onClose);
|
|
398
|
+
// The visible area while the on-screen keyboard is open; null otherwise.
|
|
399
|
+
const [keyboardViewport, setKeyboardViewport] = React__default.useState(null);
|
|
393
400
|
React__default.useEffect(() => {
|
|
394
401
|
if (!isOpen) {
|
|
395
|
-
|
|
402
|
+
setKeyboardViewport(null);
|
|
396
403
|
return;
|
|
397
404
|
}
|
|
405
|
+
// Follow the visual viewport only while the on-screen keyboard takes space
|
|
406
|
+
// (it's then much shorter than the window), so the drawer and its footer
|
|
407
|
+
// stay above the keyboard. Otherwise it's pinned to the edges: tracking every resize and
|
|
408
|
+
// scroll in state re-rendered the drawer on each frame as the browser's
|
|
409
|
+
// URL bar moved, and its sticky header and footer jumped while scrolling.
|
|
410
|
+
let frame = 0;
|
|
411
|
+
const measure = () => {
|
|
412
|
+
frame = 0;
|
|
413
|
+
const vv = window.visualViewport;
|
|
414
|
+
if (!vv)
|
|
415
|
+
return;
|
|
416
|
+
const keyboardOpen = window.innerHeight - vv.height > KEYBOARD_THRESHOLD;
|
|
417
|
+
const top = Math.round(vv.offsetTop);
|
|
418
|
+
const height = Math.round(vv.height);
|
|
419
|
+
// The browser scrolls the visible area to the focused field, so the
|
|
420
|
+
// drawer follows its offset as well as its height. An unchanged value
|
|
421
|
+
// doesn't re-render.
|
|
422
|
+
setKeyboardViewport((current) => !keyboardOpen
|
|
423
|
+
? null
|
|
424
|
+
: current && current.top === top && current.height === height
|
|
425
|
+
? current
|
|
426
|
+
: { top, height });
|
|
427
|
+
};
|
|
398
428
|
const updateHeight = () => {
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
}
|
|
429
|
+
if (!frame)
|
|
430
|
+
frame = window.requestAnimationFrame(measure);
|
|
402
431
|
};
|
|
403
|
-
|
|
432
|
+
measure();
|
|
404
433
|
const vv = typeof window !== 'undefined' ? window.visualViewport : null;
|
|
405
434
|
vv === null || vv === void 0 ? void 0 : vv.addEventListener('resize', updateHeight);
|
|
406
435
|
vv === null || vv === void 0 ? void 0 : vv.addEventListener('scroll', updateHeight);
|
|
407
436
|
return () => {
|
|
437
|
+
if (frame)
|
|
438
|
+
window.cancelAnimationFrame(frame);
|
|
408
439
|
vv === null || vv === void 0 ? void 0 : vv.removeEventListener('resize', updateHeight);
|
|
409
440
|
vv === null || vv === void 0 ? void 0 : vv.removeEventListener('scroll', updateHeight);
|
|
410
441
|
};
|
|
@@ -414,9 +445,62 @@ const Drawer = ({ isOpen, onClose = () => { }, closeOnOutsideClick = true, align
|
|
|
414
445
|
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
415
446
|
}
|
|
416
447
|
};
|
|
417
|
-
return (jsxs(Dialog, { open: isOpen, as: "div", className: "relative z-50 focus:outline-none", onClose: handleDialogClose, transition: true, children: [backdrop && (jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/40 backdrop-blur-xs transition-opacity duration-200" })), jsx("div", { className: "fixed
|
|
448
|
+
return (jsxs(Dialog, { open: isOpen, as: "div", className: "relative z-50 focus:outline-none", onClose: handleDialogClose, transition: true, children: [backdrop && (jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/40 backdrop-blur-xs transition-opacity duration-200" })), jsx("div", { className: "fixed inset-0", style: keyboardViewport
|
|
449
|
+
? {
|
|
450
|
+
top: `${keyboardViewport.top}px`,
|
|
451
|
+
height: `${keyboardViewport.height}px`,
|
|
452
|
+
bottom: 'auto',
|
|
453
|
+
}
|
|
454
|
+
: undefined, "data-testid": "drawer-viewport", children: jsx("div", { className: buildClassName('flex', fullscreen ? 'h-full' : alignmentMap[align]), children: jsxs(DialogPanel, { transition: true, className: buildClassName('w-full max-w-full bg-white dark:bg-gray-800 shadow-xl flex flex-col min-w-0', 'overflow-y-auto overflow-x-hidden overscroll-contain touch-pan-y [-webkit-overflow-scrolling:touch] duration-200 ease-out data-[closed]:opacity-0', ...(fullscreen
|
|
455
|
+
? ['h-full data-[closed]:translate-y-4']
|
|
456
|
+
: [panelAlignmentMap[align], panelSizeMap[size], panelClass])), "data-layout": fullscreen ? 'fullscreen' : undefined, children: [shouldShowDragHandle && (jsx("div", { className: "w-12 h-1.5 rounded-full bg-gray-300 dark:bg-gray-600 mx-auto my-2.5 shrink-0 cursor-grab", "data-testid": "drawer-drag-handle" })), fullscreen && (jsxs("div", { className: "sticky top-0 z-10 flex h-14 shrink-0 items-center gap-1 border-b border-[var(--ui-border)] bg-white pl-1 pr-3 pt-[env(safe-area-inset-top,0px)] box-content dark:bg-gray-800", children: [jsx(Button, { iconOnly: true, "aria-label": "Close", variant: "plain", theme: "secondary", onClick: onClose, className: "shrink-0", children: jsx(SvgX, { className: "size-5" }) }), title && (jsx(DialogTitle, { as: "h3", className: "min-w-0 flex-1 truncate text-base font-semibold text-gray-900 dark:text-white", children: title })), headerAction && jsx("div", { className: "ml-auto shrink-0", children: headerAction })] })), !fullscreen && showTitleSection && (jsxs("div", { className: buildClassName('space-y-1 py-3 px-4 md:px-6 flex flex-col border-b border-[var(--ui-border)] shrink-0', titleSticky && 'sticky top-0 bg-white dark:bg-gray-800 z-10'), children: [jsxs("div", { className: "flex items-center justify-between gap-3 min-w-0", children: [jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [showBackButton && (jsx(Button, { iconOnly: true, "aria-label": "Back", variant: "plain", theme: "secondary", size: "xs", onClick: onClose, children: jsx(SvgChevronLeft, { className: "size-4" }) })), title && (jsx(DialogTitle, { as: "h3", className: buildClassName('text-lg font-semibold text-gray-900 dark:text-white truncate tracking-tight min-w-0', titleClass), children: title }))] }), showCloseButton && (jsx(Button, { iconOnly: true, "aria-label": "Close drawer", variant: "plain", theme: "secondary", size: "xs", onClick: onClose, className: "rounded-full text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 shrink-0", children: jsx(SvgX, { className: "size-4" }) }))] }), description && (jsx("p", { className: buildClassName('text-sm text-gray-500 dark:text-gray-400 font-normal truncate', descriptionClass), children: description }))] })), jsxs("div", { className: buildClassName('flex-1 p-4 md:p-6 space-y-4 min-w-0', contentClass, fullscreen && 'pb-[calc(1rem+env(safe-area-inset-bottom,0px))]'), children: [fullscreen && description && (jsx("p", { className: buildClassName('text-sm text-gray-500 dark:text-gray-400', descriptionClass), children: description })), children] }), !fullscreen && footer && (jsx("div", { className: buildClassName('p-4 border-t border-[var(--ui-border-muted)] bg-white dark:bg-gray-800 shrink-0 min-w-0', footerSticky && 'sticky bottom-0 z-10'), children: footer }))] }) }) })] }));
|
|
418
457
|
};
|
|
419
458
|
Drawer.displayName = 'Drawer';
|
|
459
|
+
/**
|
|
460
|
+
* While `active`, the browser's back button (and Android's back gesture)
|
|
461
|
+
* closes the drawer instead of leaving the page: opening adds a history entry
|
|
462
|
+
* (keeping the current state, so routers see the same location), and back
|
|
463
|
+
* removes it. Closed any other way, the entry is removed too, but only if it's
|
|
464
|
+
* still the latest one, so a form that saves and then navigates isn't undone.
|
|
465
|
+
*/
|
|
466
|
+
function useCloseOnBack(active, onClose) {
|
|
467
|
+
const onCloseRef = React__default.useRef(onClose);
|
|
468
|
+
React__default.useEffect(() => {
|
|
469
|
+
onCloseRef.current = onClose;
|
|
470
|
+
});
|
|
471
|
+
const token = React__default.useId();
|
|
472
|
+
React__default.useEffect(() => {
|
|
473
|
+
if (!active || typeof window === 'undefined')
|
|
474
|
+
return;
|
|
475
|
+
const pushEntry = () => { var _a; return window.history.pushState({ ...((_a = window.history.state) !== null && _a !== void 0 ? _a : {}), drawer: token }, ''); };
|
|
476
|
+
pushEntry();
|
|
477
|
+
let entryPresent = true;
|
|
478
|
+
let disposed = false;
|
|
479
|
+
const handlePop = () => {
|
|
480
|
+
var _a;
|
|
481
|
+
if (((_a = window.history.state) === null || _a === void 0 ? void 0 : _a.drawer) === token)
|
|
482
|
+
return;
|
|
483
|
+
entryPresent = false;
|
|
484
|
+
onCloseRef.current();
|
|
485
|
+
// onClose may step back within the drawer instead of closing it; if it's
|
|
486
|
+
// still open afterwards, take back its entry so the next back works too.
|
|
487
|
+
window.setTimeout(() => {
|
|
488
|
+
if (disposed)
|
|
489
|
+
return;
|
|
490
|
+
pushEntry();
|
|
491
|
+
entryPresent = true;
|
|
492
|
+
}, 0);
|
|
493
|
+
};
|
|
494
|
+
window.addEventListener('popstate', handlePop);
|
|
495
|
+
return () => {
|
|
496
|
+
var _a;
|
|
497
|
+
disposed = true;
|
|
498
|
+
window.removeEventListener('popstate', handlePop);
|
|
499
|
+
if (entryPresent && ((_a = window.history.state) === null || _a === void 0 ? void 0 : _a.drawer) === token)
|
|
500
|
+
window.history.back();
|
|
501
|
+
};
|
|
502
|
+
}, [active, token]);
|
|
503
|
+
}
|
|
420
504
|
|
|
421
505
|
function ActionSheet({ isOpen, onClose, title, description, actions, showCancelButton = true, cancelLabel = 'Cancel', onCancel, closeOnBackdropClick = true, dragHandle = true, panelClass = '', containerClass = '', }) {
|
|
422
506
|
const handleItemClick = (action) => {
|