@pk-design/react-tailwind 1.11.1 → 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/BottomNavigation/BottomNavigation.js +2 -2
- package/dist/cjs/components/BottomNavigation/BottomNavigation.js.map +1 -1
- package/dist/cjs/components/Drawer/Drawer.js +93 -9
- package/dist/cjs/components/Drawer/Drawer.js.map +1 -1
- package/dist/cjs/components/MobileHeader/MobileHeader.js +4 -2
- package/dist/cjs/components/MobileHeader/MobileHeader.js.map +1 -1
- package/dist/es/components/BottomNavigation/BottomNavigation.js +2 -2
- package/dist/es/components/BottomNavigation/BottomNavigation.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/es/components/MobileHeader/MobileHeader.js +4 -2
- package/dist/es/components/MobileHeader/MobileHeader.js.map +1 -1
- package/dist/react-tailwind.cjs.js +99 -13
- package/dist/react-tailwind.cjs.js.map +1 -1
- package/dist/react-tailwind.es.js +99 -13
- package/dist/react-tailwind.es.js.map +1 -1
- package/dist/types/components/BottomNavigation/BottomNavigation.d.ts +1 -1
- package/dist/types/components/BottomNavigation/BottomNavigation.types.d.ts +5 -0
- package/dist/types/components/Drawer/Drawer.types.d.ts +14 -0
- package/package.json +2 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var buildClassname = require('../../utils/build-classname.js');
|
|
5
5
|
|
|
6
|
-
function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels = 'always', theme = 'light', activeIndicatorStyle = 'line', containerClass = '', itemClass = '', linkAs = 'a', linkHrefProp = 'href', ariaLabel = 'Bottom Navigation', hidden = false, }) {
|
|
6
|
+
function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels = 'always', theme = 'light', activeIndicatorStyle = 'line', containerClass = '', itemClass = '', linkAs = 'a', linkHrefProp = 'href', ariaLabel = 'Bottom Navigation', hidden = false, rounded = false, }) {
|
|
7
7
|
const handleTabClick = (item) => {
|
|
8
8
|
var _a;
|
|
9
9
|
if (item.disabled)
|
|
@@ -19,7 +19,7 @@ function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels
|
|
|
19
19
|
dark: 'bg-gray-900 text-gray-400 border-t border-gray-800',
|
|
20
20
|
primary: 'bg-blue-600 text-blue-100 border-t border-blue-700',
|
|
21
21
|
}[theme];
|
|
22
|
-
return (jsxRuntime.jsx("nav", { "aria-label": ariaLabel, className: buildClassname.buildClassName('w-full flex items-center justify-around select-none shadow-lg transition-colors relative', fixed && 'fixed bottom-0 left-0 right-0 z-40', 'pb-[env(safe-area-inset-bottom,6px)] px-2', themeStyles, containerClass), "data-testid": "bottom-navigation", children: items.map((item) => {
|
|
22
|
+
return (jsxRuntime.jsx("nav", { "aria-label": ariaLabel, className: buildClassname.buildClassName('w-full flex items-center justify-around select-none shadow-lg transition-colors relative', fixed && 'fixed bottom-0 left-0 right-0 z-40', 'pb-[env(safe-area-inset-bottom,6px)] px-2', themeStyles, rounded && 'rounded-t-2xl border-x', containerClass), "data-testid": "bottom-navigation", children: items.map((item) => {
|
|
23
23
|
const isActive = item.id === activeId;
|
|
24
24
|
const showLabelText = showLabels === 'always' || (showLabels === 'active' && isActive);
|
|
25
25
|
const activeItemColor = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BottomNavigation.js","sources":["../../../../../src/components/BottomNavigation/BottomNavigation.tsx"],"sourcesContent":["import React, { type ElementType } from 'react'\nimport type { BottomNavigationProps, BottomNavigationItem } from './BottomNavigation.types'\nimport { buildClassName } from '../../utils/build-classname'\n\nexport function BottomNavigation({\n items,\n activeId,\n onChange,\n fixed = true,\n showLabels = 'always',\n theme = 'light',\n activeIndicatorStyle = 'line',\n containerClass = '',\n itemClass = '',\n linkAs = 'a',\n linkHrefProp = 'href',\n ariaLabel = 'Bottom Navigation',\n hidden = false,\n}: BottomNavigationProps) {\n const handleTabClick = (item: BottomNavigationItem) => {\n if (item.disabled) return\n item.onClick?.()\n onChange?.(item.id)\n }\n\n // Removed from the tree, not just covered, so hidden tabs can't take focus.\n if (hidden) return null\n\n const themeStyles = {\n light:\n 'bg-white dark:bg-gray-900 border-t border-[var(--ui-border)] text-gray-600 dark:text-gray-400',\n dark: 'bg-gray-900 text-gray-400 border-t border-gray-800',\n primary: 'bg-blue-600 text-blue-100 border-t border-blue-700',\n }[theme]\n\n return (\n <nav\n aria-label={ariaLabel}\n className={buildClassName(\n 'w-full flex items-center justify-around select-none shadow-lg transition-colors relative',\n fixed && 'fixed bottom-0 left-0 right-0 z-40',\n 'pb-[env(safe-area-inset-bottom,6px)] px-2',\n themeStyles,\n containerClass,\n )}\n data-testid=\"bottom-navigation\"\n >\n {items.map((item) => {\n const isActive = item.id === activeId\n const showLabelText = showLabels === 'always' || (showLabels === 'active' && isActive)\n\n const activeItemColor = {\n light: 'text-blue-600 dark:text-blue-400 font-bold',\n dark: 'text-white font-bold',\n primary: 'text-white font-bold',\n }[theme]\n\n const inactiveItemColor = {\n light:\n 'text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 font-medium',\n dark: 'text-gray-400 hover:text-gray-200 font-medium',\n primary: 'text-blue-200 hover:text-white font-medium',\n }[theme]\n\n const pillActiveStyles = {\n light:\n 'bg-blue-100 dark:bg-blue-950/80 text-blue-600 dark:text-blue-400 shadow-xs scale-105',\n dark: 'bg-gray-800 text-white ring-1 ring-gray-700 shadow-xs scale-105',\n primary: 'bg-white/25 text-white shadow-xs scale-105',\n }[theme]\n\n const badgeColor = {\n danger: 'bg-red-500 text-white',\n info: 'bg-blue-500 text-white',\n success: 'bg-emerald-500 text-white',\n }[item.badgeVariant || 'danger']\n\n // A disabled item stays a button: a link can't be disabled.\n const isLink = Boolean(item.href) && !item.disabled\n const ItemElement: ElementType = isLink ? linkAs : 'button'\n const elementProps = isLink\n ? { [linkHrefProp]: item.href }\n : { type: 'button', disabled: item.disabled }\n\n return (\n <ItemElement\n key={item.id}\n {...elementProps}\n onClick={() => handleTabClick(item)}\n className={buildClassName(\n 'flex-1 flex flex-col items-center justify-center pt-2 pb-1.5 px-1 transition-all duration-200 relative cursor-pointer group',\n item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none',\n isActive ? activeItemColor : inactiveItemColor,\n itemClass,\n )}\n data-testid={`bottom-nav-item-${item.id}`}\n aria-current={isActive ? 'page' : undefined}\n // With its label hidden, the item still needs a name; the icon is decorative.\n aria-label={!showLabelText && typeof item.label === 'string' ? item.label : undefined}\n >\n {/* Active Indicator Bar on Top Border */}\n {activeIndicatorStyle === 'line' && isActive && (\n <span\n className={buildClassName(\n 'absolute -top-[1px] h-0.75 w-9 rounded-full transition-all duration-200 z-10',\n theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400',\n )}\n />\n )}\n\n {/* Icon & Badge Container */}\n <div\n className={buildClassName(\n 'relative inline-flex items-center justify-center transition-all duration-200',\n activeIndicatorStyle === 'pill'\n ? buildClassName(\n 'px-4 py-1 rounded-full',\n isActive\n ? pillActiveStyles\n : 'group-hover:bg-gray-100/50 dark:group-hover:bg-gray-800/40',\n )\n : 'px-1 py-0.5',\n )}\n >\n <span\n className=\"text-xl transition-transform duration-150 group-active:scale-95\"\n aria-hidden=\"true\"\n >\n {isActive && item.activeIcon ? item.activeIcon : item.icon}\n </span>\n\n {/* Notification Badge */}\n {item.badge !== undefined && item.badge !== null && (\n <span\n className={buildClassName(\n 'absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full text-[10px] font-bold flex items-center justify-center shadow-xs z-10',\n badgeColor,\n )}\n data-testid={`bottom-nav-badge-${item.id}`}\n >\n {item.badge}\n </span>\n )}\n </div>\n\n {/* Label */}\n {showLabelText && (\n <span\n className={buildClassName(\n 'text-[11px] leading-tight mt-1 truncate max-w-full transition-colors duration-150',\n isActive ? 'font-bold' : 'font-medium opacity-90',\n )}\n >\n {item.label}\n </span>\n )}\n\n {/* Active Dot Indicator */}\n {activeIndicatorStyle === 'dot' && isActive && (\n <span\n className={buildClassName(\n 'size-1.5 rounded-full mt-1 transition-all duration-200',\n theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400',\n )}\n />\n )}\n </ItemElement>\n )\n })}\n </nav>\n )\n}\n\nBottomNavigation.displayName = 'BottomNavigation'\n"],"names":["_jsx","buildClassName","_jsxs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BottomNavigation.js","sources":["../../../../../src/components/BottomNavigation/BottomNavigation.tsx"],"sourcesContent":["import React, { type ElementType } from 'react'\nimport type { BottomNavigationProps, BottomNavigationItem } from './BottomNavigation.types'\nimport { buildClassName } from '../../utils/build-classname'\n\nexport function BottomNavigation({\n items,\n activeId,\n onChange,\n fixed = true,\n showLabels = 'always',\n theme = 'light',\n activeIndicatorStyle = 'line',\n containerClass = '',\n itemClass = '',\n linkAs = 'a',\n linkHrefProp = 'href',\n ariaLabel = 'Bottom Navigation',\n hidden = false,\n rounded = false,\n}: BottomNavigationProps) {\n const handleTabClick = (item: BottomNavigationItem) => {\n if (item.disabled) return\n item.onClick?.()\n onChange?.(item.id)\n }\n\n // Removed from the tree, not just covered, so hidden tabs can't take focus.\n if (hidden) return null\n\n const themeStyles = {\n light:\n 'bg-white dark:bg-gray-900 border-t border-[var(--ui-border)] text-gray-600 dark:text-gray-400',\n dark: 'bg-gray-900 text-gray-400 border-t border-gray-800',\n primary: 'bg-blue-600 text-blue-100 border-t border-blue-700',\n }[theme]\n\n return (\n <nav\n aria-label={ariaLabel}\n className={buildClassName(\n 'w-full flex items-center justify-around select-none shadow-lg transition-colors relative',\n fixed && 'fixed bottom-0 left-0 right-0 z-40',\n 'pb-[env(safe-area-inset-bottom,6px)] px-2',\n themeStyles,\n rounded && 'rounded-t-2xl border-x',\n containerClass,\n )}\n data-testid=\"bottom-navigation\"\n >\n {items.map((item) => {\n const isActive = item.id === activeId\n const showLabelText = showLabels === 'always' || (showLabels === 'active' && isActive)\n\n const activeItemColor = {\n light: 'text-blue-600 dark:text-blue-400 font-bold',\n dark: 'text-white font-bold',\n primary: 'text-white font-bold',\n }[theme]\n\n const inactiveItemColor = {\n light:\n 'text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 font-medium',\n dark: 'text-gray-400 hover:text-gray-200 font-medium',\n primary: 'text-blue-200 hover:text-white font-medium',\n }[theme]\n\n const pillActiveStyles = {\n light:\n 'bg-blue-100 dark:bg-blue-950/80 text-blue-600 dark:text-blue-400 shadow-xs scale-105',\n dark: 'bg-gray-800 text-white ring-1 ring-gray-700 shadow-xs scale-105',\n primary: 'bg-white/25 text-white shadow-xs scale-105',\n }[theme]\n\n const badgeColor = {\n danger: 'bg-red-500 text-white',\n info: 'bg-blue-500 text-white',\n success: 'bg-emerald-500 text-white',\n }[item.badgeVariant || 'danger']\n\n // A disabled item stays a button: a link can't be disabled.\n const isLink = Boolean(item.href) && !item.disabled\n const ItemElement: ElementType = isLink ? linkAs : 'button'\n const elementProps = isLink\n ? { [linkHrefProp]: item.href }\n : { type: 'button', disabled: item.disabled }\n\n return (\n <ItemElement\n key={item.id}\n {...elementProps}\n onClick={() => handleTabClick(item)}\n className={buildClassName(\n 'flex-1 flex flex-col items-center justify-center pt-2 pb-1.5 px-1 transition-all duration-200 relative cursor-pointer group',\n item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none',\n isActive ? activeItemColor : inactiveItemColor,\n itemClass,\n )}\n data-testid={`bottom-nav-item-${item.id}`}\n aria-current={isActive ? 'page' : undefined}\n // With its label hidden, the item still needs a name; the icon is decorative.\n aria-label={!showLabelText && typeof item.label === 'string' ? item.label : undefined}\n >\n {/* Active Indicator Bar on Top Border */}\n {activeIndicatorStyle === 'line' && isActive && (\n <span\n className={buildClassName(\n 'absolute -top-[1px] h-0.75 w-9 rounded-full transition-all duration-200 z-10',\n theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400',\n )}\n />\n )}\n\n {/* Icon & Badge Container */}\n <div\n className={buildClassName(\n 'relative inline-flex items-center justify-center transition-all duration-200',\n activeIndicatorStyle === 'pill'\n ? buildClassName(\n 'px-4 py-1 rounded-full',\n isActive\n ? pillActiveStyles\n : 'group-hover:bg-gray-100/50 dark:group-hover:bg-gray-800/40',\n )\n : 'px-1 py-0.5',\n )}\n >\n <span\n className=\"text-xl transition-transform duration-150 group-active:scale-95\"\n aria-hidden=\"true\"\n >\n {isActive && item.activeIcon ? item.activeIcon : item.icon}\n </span>\n\n {/* Notification Badge */}\n {item.badge !== undefined && item.badge !== null && (\n <span\n className={buildClassName(\n 'absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full text-[10px] font-bold flex items-center justify-center shadow-xs z-10',\n badgeColor,\n )}\n data-testid={`bottom-nav-badge-${item.id}`}\n >\n {item.badge}\n </span>\n )}\n </div>\n\n {/* Label */}\n {showLabelText && (\n <span\n className={buildClassName(\n 'text-[11px] leading-tight mt-1 truncate max-w-full transition-colors duration-150',\n isActive ? 'font-bold' : 'font-medium opacity-90',\n )}\n >\n {item.label}\n </span>\n )}\n\n {/* Active Dot Indicator */}\n {activeIndicatorStyle === 'dot' && isActive && (\n <span\n className={buildClassName(\n 'size-1.5 rounded-full mt-1 transition-all duration-200',\n theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400',\n )}\n />\n )}\n </ItemElement>\n )\n })}\n </nav>\n )\n}\n\nBottomNavigation.displayName = 'BottomNavigation'\n"],"names":["_jsx","buildClassName","_jsxs"],"mappings":"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"}
|
|
@@ -33,27 +33,58 @@ const panelSizeMap = {
|
|
|
33
33
|
'2xl': 'max-w-2xl',
|
|
34
34
|
full: 'w-full',
|
|
35
35
|
};
|
|
36
|
-
|
|
36
|
+
// How much shorter than the window the visual viewport must be before it's
|
|
37
|
+
// taken to be the on-screen keyboard rather than the URL bar (≈ 56px).
|
|
38
|
+
const KEYBOARD_THRESHOLD = 150;
|
|
39
|
+
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, }) => {
|
|
37
40
|
const align = useBreakpoint.useResponsiveValue(alignProp);
|
|
38
41
|
const size = useBreakpoint.useResponsiveValue(sizeProp);
|
|
42
|
+
const breakpoint = useBreakpoint.useBreakpoint();
|
|
43
|
+
const fullscreen = phoneLayout === 'fullscreen' && breakpoint === 'compact';
|
|
39
44
|
const showTitleSection = Boolean(title || description || showBackButton || showCloseButton);
|
|
40
|
-
const shouldShowDragHandle = dragHandle !== null && dragHandle !== void 0 ? dragHandle : align === 'bottom';
|
|
41
|
-
|
|
45
|
+
const shouldShowDragHandle = !fullscreen && (dragHandle !== null && dragHandle !== void 0 ? dragHandle : align === 'bottom');
|
|
46
|
+
useCloseOnBack(fullscreen && isOpen, onClose);
|
|
47
|
+
// The visible area while the on-screen keyboard is open; null otherwise.
|
|
48
|
+
const [keyboardViewport, setKeyboardViewport] = React.useState(null);
|
|
42
49
|
React.useEffect(() => {
|
|
43
50
|
if (!isOpen) {
|
|
44
|
-
|
|
51
|
+
setKeyboardViewport(null);
|
|
45
52
|
return;
|
|
46
53
|
}
|
|
54
|
+
// Follow the visual viewport only while the on-screen keyboard takes space
|
|
55
|
+
// (it's then much shorter than the window), so the drawer and its footer
|
|
56
|
+
// stay above the keyboard. Otherwise it's pinned to the edges: tracking every resize and
|
|
57
|
+
// scroll in state re-rendered the drawer on each frame as the browser's
|
|
58
|
+
// URL bar moved, and its sticky header and footer jumped while scrolling.
|
|
59
|
+
let frame = 0;
|
|
60
|
+
const measure = () => {
|
|
61
|
+
frame = 0;
|
|
62
|
+
const vv = window.visualViewport;
|
|
63
|
+
if (!vv)
|
|
64
|
+
return;
|
|
65
|
+
const keyboardOpen = window.innerHeight - vv.height > KEYBOARD_THRESHOLD;
|
|
66
|
+
const top = Math.round(vv.offsetTop);
|
|
67
|
+
const height = Math.round(vv.height);
|
|
68
|
+
// The browser scrolls the visible area to the focused field, so the
|
|
69
|
+
// drawer follows its offset as well as its height. An unchanged value
|
|
70
|
+
// doesn't re-render.
|
|
71
|
+
setKeyboardViewport((current) => !keyboardOpen
|
|
72
|
+
? null
|
|
73
|
+
: current && current.top === top && current.height === height
|
|
74
|
+
? current
|
|
75
|
+
: { top, height });
|
|
76
|
+
};
|
|
47
77
|
const updateHeight = () => {
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
}
|
|
78
|
+
if (!frame)
|
|
79
|
+
frame = window.requestAnimationFrame(measure);
|
|
51
80
|
};
|
|
52
|
-
|
|
81
|
+
measure();
|
|
53
82
|
const vv = typeof window !== 'undefined' ? window.visualViewport : null;
|
|
54
83
|
vv === null || vv === void 0 ? void 0 : vv.addEventListener('resize', updateHeight);
|
|
55
84
|
vv === null || vv === void 0 ? void 0 : vv.addEventListener('scroll', updateHeight);
|
|
56
85
|
return () => {
|
|
86
|
+
if (frame)
|
|
87
|
+
window.cancelAnimationFrame(frame);
|
|
57
88
|
vv === null || vv === void 0 ? void 0 : vv.removeEventListener('resize', updateHeight);
|
|
58
89
|
vv === null || vv === void 0 ? void 0 : vv.removeEventListener('scroll', updateHeight);
|
|
59
90
|
};
|
|
@@ -63,9 +94,62 @@ const Drawer = ({ isOpen, onClose = () => { }, closeOnOutsideClick = true, align
|
|
|
63
94
|
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
64
95
|
}
|
|
65
96
|
};
|
|
66
|
-
return (jsxRuntime.jsxs(react.Dialog, { open: isOpen, as: "div", className: "relative z-50 focus:outline-none", onClose: handleDialogClose, transition: true, children: [backdrop && (jsxRuntime.jsx(react.DialogBackdrop, { className: "fixed inset-0 bg-black/40 backdrop-blur-xs transition-opacity duration-200" })), jsxRuntime.jsx("div", { className: "fixed
|
|
97
|
+
return (jsxRuntime.jsxs(react.Dialog, { open: isOpen, as: "div", className: "relative z-50 focus:outline-none", onClose: handleDialogClose, transition: true, children: [backdrop && (jsxRuntime.jsx(react.DialogBackdrop, { className: "fixed inset-0 bg-black/40 backdrop-blur-xs transition-opacity duration-200" })), jsxRuntime.jsx("div", { className: "fixed inset-0", style: keyboardViewport
|
|
98
|
+
? {
|
|
99
|
+
top: `${keyboardViewport.top}px`,
|
|
100
|
+
height: `${keyboardViewport.height}px`,
|
|
101
|
+
bottom: 'auto',
|
|
102
|
+
}
|
|
103
|
+
: undefined, "data-testid": "drawer-viewport", children: jsxRuntime.jsx("div", { className: buildClassname.buildClassName('flex', fullscreen ? 'h-full' : alignmentMap[align]), children: jsxRuntime.jsxs(react.DialogPanel, { transition: true, className: buildClassname.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
|
|
104
|
+
? ['h-full data-[closed]:translate-y-4']
|
|
105
|
+
: [panelAlignmentMap[align], panelSizeMap[size], panelClass])), "data-layout": fullscreen ? 'fullscreen' : undefined, children: [shouldShowDragHandle && (jsxRuntime.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 && (jsxRuntime.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: [jsxRuntime.jsx(Button.Button, { iconOnly: true, "aria-label": "Close", variant: "plain", theme: "secondary", onClick: onClose, className: "shrink-0", children: jsxRuntime.jsx(X.default, { className: "size-5" }) }), title && (jsxRuntime.jsx(react.DialogTitle, { as: "h3", className: "min-w-0 flex-1 truncate text-base font-semibold text-gray-900 dark:text-white", children: title })), headerAction && jsxRuntime.jsx("div", { className: "ml-auto shrink-0", children: headerAction })] })), !fullscreen && showTitleSection && (jsxRuntime.jsxs("div", { className: buildClassname.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: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-w-0", children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [showBackButton && (jsxRuntime.jsx(Button.Button, { iconOnly: true, "aria-label": "Back", variant: "plain", theme: "secondary", size: "xs", onClick: onClose, children: jsxRuntime.jsx(ChevronLeft.default, { className: "size-4" }) })), title && (jsxRuntime.jsx(react.DialogTitle, { as: "h3", className: buildClassname.buildClassName('text-lg font-semibold text-gray-900 dark:text-white truncate tracking-tight min-w-0', titleClass), children: title }))] }), showCloseButton && (jsxRuntime.jsx(Button.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: jsxRuntime.jsx(X.default, { className: "size-4" }) }))] }), description && (jsxRuntime.jsx("p", { className: buildClassname.buildClassName('text-sm text-gray-500 dark:text-gray-400 font-normal truncate', descriptionClass), children: description }))] })), jsxRuntime.jsxs("div", { className: buildClassname.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 && (jsxRuntime.jsx("p", { className: buildClassname.buildClassName('text-sm text-gray-500 dark:text-gray-400', descriptionClass), children: description })), children] }), !fullscreen && footer && (jsxRuntime.jsx("div", { className: buildClassname.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 }))] }) }) })] }));
|
|
67
106
|
};
|
|
68
107
|
Drawer.displayName = 'Drawer';
|
|
108
|
+
/**
|
|
109
|
+
* While `active`, the browser's back button (and Android's back gesture)
|
|
110
|
+
* closes the drawer instead of leaving the page: opening adds a history entry
|
|
111
|
+
* (keeping the current state, so routers see the same location), and back
|
|
112
|
+
* removes it. Closed any other way, the entry is removed too, but only if it's
|
|
113
|
+
* still the latest one, so a form that saves and then navigates isn't undone.
|
|
114
|
+
*/
|
|
115
|
+
function useCloseOnBack(active, onClose) {
|
|
116
|
+
const onCloseRef = React.useRef(onClose);
|
|
117
|
+
React.useEffect(() => {
|
|
118
|
+
onCloseRef.current = onClose;
|
|
119
|
+
});
|
|
120
|
+
const token = React.useId();
|
|
121
|
+
React.useEffect(() => {
|
|
122
|
+
if (!active || typeof window === 'undefined')
|
|
123
|
+
return;
|
|
124
|
+
const pushEntry = () => { var _a; return window.history.pushState({ ...((_a = window.history.state) !== null && _a !== void 0 ? _a : {}), drawer: token }, ''); };
|
|
125
|
+
pushEntry();
|
|
126
|
+
let entryPresent = true;
|
|
127
|
+
let disposed = false;
|
|
128
|
+
const handlePop = () => {
|
|
129
|
+
var _a;
|
|
130
|
+
if (((_a = window.history.state) === null || _a === void 0 ? void 0 : _a.drawer) === token)
|
|
131
|
+
return;
|
|
132
|
+
entryPresent = false;
|
|
133
|
+
onCloseRef.current();
|
|
134
|
+
// onClose may step back within the drawer instead of closing it; if it's
|
|
135
|
+
// still open afterwards, take back its entry so the next back works too.
|
|
136
|
+
window.setTimeout(() => {
|
|
137
|
+
if (disposed)
|
|
138
|
+
return;
|
|
139
|
+
pushEntry();
|
|
140
|
+
entryPresent = true;
|
|
141
|
+
}, 0);
|
|
142
|
+
};
|
|
143
|
+
window.addEventListener('popstate', handlePop);
|
|
144
|
+
return () => {
|
|
145
|
+
var _a;
|
|
146
|
+
disposed = true;
|
|
147
|
+
window.removeEventListener('popstate', handlePop);
|
|
148
|
+
if (entryPresent && ((_a = window.history.state) === null || _a === void 0 ? void 0 : _a.drawer) === token)
|
|
149
|
+
window.history.back();
|
|
150
|
+
};
|
|
151
|
+
}, [active, token]);
|
|
152
|
+
}
|
|
69
153
|
|
|
70
154
|
exports.Drawer = Drawer;
|
|
71
155
|
//# sourceMappingURL=Drawer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.js","sources":["../../../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["import React from 'react'\nimport { Dialog, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react'\nimport { buildClassName } from '../../utils/build-classname'\nimport { Button } from '../Button'\nimport ChevronLeftIcon from '../Icons/ChevronLeft.svg'\nimport XIcon from '../Icons/X.svg'\nimport { useResponsiveValue } from '../../hooks/use-breakpoint'\nimport type { AlignmentMap, DrawerProps, SizeMap } from './Drawer.types'\n\nconst alignmentMap: AlignmentMap = {\n center: 'items-center justify-center h-full',\n start: 'justify-start h-full',\n end: 'justify-end h-full',\n top: 'items-start justify-center w-full',\n bottom: 'items-end justify-center h-full',\n}\n\nconst panelAlignmentMap: AlignmentMap = {\n center: 'data-[closed]:scale-95 rounded-2xl',\n start: 'data-[closed]:-translate-x-1/2 rounded-r-2xl',\n end: 'data-[closed]:translate-x-1/2 rounded-l-2xl',\n top: 'data-[closed]:-translate-y-1/2 rounded-b-2xl',\n // Bottom sheets clear the home indicator on notched phones (0 elsewhere).\n bottom:\n 'data-[closed]:translate-y-full rounded-t-2xl md:rounded-t-3xl max-h-[88vh] max-h-[88dvh] pb-[env(safe-area-inset-bottom,0px)]',\n}\n\nconst panelSizeMap: SizeMap = {\n xs: 'max-w-xs',\n sm: 'max-w-sm',\n md: 'max-w-md',\n lg: 'max-w-lg',\n xl: 'max-w-xl',\n '2xl': 'max-w-2xl',\n full: 'w-full',\n}\n\nexport const Drawer: React.FC<DrawerProps> = ({\n isOpen,\n onClose = () => {},\n closeOnOutsideClick = true,\n align: alignProp = 'end',\n size: sizeProp = 'md',\n backdrop = true,\n title,\n description,\n children,\n footer,\n footerSticky = false,\n panelClass = '',\n contentClass = '',\n titleClass = '',\n descriptionClass = '',\n titleSticky = false,\n showBackButton = false,\n showCloseButton = true,\n dragHandle,\n}) => {\n const align = useResponsiveValue(alignProp)\n const size = useResponsiveValue(sizeProp)\n const showTitleSection = Boolean(title || description || showBackButton || showCloseButton)\n const shouldShowDragHandle = dragHandle ?? align === 'bottom'\n const [viewportHeight, setViewportHeight] = React.useState<number | null>(null)\n\n React.useEffect(() => {\n if (!isOpen) {\n setViewportHeight(null)\n return\n }\n\n const updateHeight = () => {\n if (typeof window !== 'undefined' && window.visualViewport) {\n setViewportHeight(window.visualViewport.height)\n }\n }\n\n updateHeight()\n const vv = typeof window !== 'undefined' ? window.visualViewport : null\n vv?.addEventListener('resize', updateHeight)\n vv?.addEventListener('scroll', updateHeight)\n\n return () => {\n vv?.removeEventListener('resize', updateHeight)\n vv?.removeEventListener('scroll', updateHeight)\n }\n }, [isOpen])\n\n const handleDialogClose = () => {\n if (closeOnOutsideClick) {\n onClose?.()\n }\n }\n\n return (\n <Dialog\n open={isOpen}\n as=\"div\"\n className=\"relative z-50 focus:outline-none\"\n onClose={handleDialogClose}\n transition\n >\n {backdrop && (\n <DialogBackdrop className=\"fixed inset-0 bg-black/40 backdrop-blur-xs transition-opacity duration-200\" />\n )}\n\n <div\n className=\"fixed left-0 top-0 w-full\"\n style={{ height: viewportHeight ? `${viewportHeight}px` : '100dvh' }}\n >\n <div className={buildClassName('flex', alignmentMap[align])}>\n <DialogPanel\n transition\n className={buildClassName(\n 'w-full max-w-full bg-white dark:bg-gray-800 shadow-xl flex flex-col min-w-0',\n 'overflow-y-auto overflow-x-hidden overscroll-contain touch-pan-y [-webkit-overflow-scrolling:touch] duration-200 ease-out data-[closed]:opacity-0',\n panelAlignmentMap[align],\n panelSizeMap[size],\n panelClass,\n )}\n >\n {/* Top drag handle indicator for mobile bottom sheet */}\n {shouldShowDragHandle && (\n <div\n className=\"w-12 h-1.5 rounded-full bg-gray-300 dark:bg-gray-600 mx-auto my-2.5 shrink-0 cursor-grab\"\n data-testid=\"drawer-drag-handle\"\n />\n )}\n\n {/* Header Section */}\n {showTitleSection && (\n <div\n className={buildClassName(\n 'space-y-1 py-3 px-4 md:px-6 flex flex-col border-b border-[var(--ui-border)] shrink-0',\n titleSticky && 'sticky top-0 bg-white dark:bg-gray-800 z-10',\n )}\n >\n <div className=\"flex items-center justify-between gap-3 min-w-0\">\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n {showBackButton && (\n <Button\n iconOnly\n aria-label=\"Back\"\n variant=\"plain\"\n theme=\"secondary\"\n size=\"xs\"\n onClick={onClose}\n >\n <ChevronLeftIcon className=\"size-4\" />\n </Button>\n )}\n {title && (\n <DialogTitle\n as=\"h3\"\n className={buildClassName(\n 'text-lg font-semibold text-gray-900 dark:text-white truncate tracking-tight min-w-0',\n titleClass,\n )}\n >\n {title}\n </DialogTitle>\n )}\n </div>\n\n {showCloseButton && (\n <Button\n iconOnly\n aria-label=\"Close drawer\"\n variant=\"plain\"\n theme=\"secondary\"\n size=\"xs\"\n onClick={onClose}\n className=\"rounded-full text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 shrink-0\"\n >\n <XIcon className=\"size-4\" />\n </Button>\n )}\n </div>\n\n {description && (\n <p\n className={buildClassName(\n 'text-sm text-gray-500 dark:text-gray-400 font-normal truncate',\n descriptionClass,\n )}\n >\n {description}\n </p>\n )}\n </div>\n )}\n\n {/* Content Body */}\n <div className={buildClassName('flex-1 p-4 md:p-6 space-y-4 min-w-0', contentClass)}>\n {children}\n </div>\n\n {/* Footer Section */}\n {footer && (\n <div\n className={buildClassName(\n 'p-4 border-t border-[var(--ui-border-muted)] bg-white dark:bg-gray-800 shrink-0 min-w-0',\n footerSticky && 'sticky bottom-0 z-10',\n )}\n >\n {footer}\n </div>\n )}\n </DialogPanel>\n </div>\n </div>\n </Dialog>\n )\n}\n\nDrawer.displayName = 'Drawer'\n"],"names":["useResponsiveValue","_jsxs","Dialog","_jsx","DialogBackdrop","buildClassName","DialogPanel","Button","ChevronLeftIcon","DialogTitle","XIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Drawer.js","sources":["../../../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["import React from 'react'\nimport { Dialog, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react'\nimport { buildClassName } from '../../utils/build-classname'\nimport { Button } from '../Button'\nimport ChevronLeftIcon from '../Icons/ChevronLeft.svg'\nimport XIcon from '../Icons/X.svg'\nimport { useBreakpoint, useResponsiveValue } from '../../hooks/use-breakpoint'\nimport type { AlignmentMap, DrawerProps, SizeMap } from './Drawer.types'\n\nconst alignmentMap: AlignmentMap = {\n center: 'items-center justify-center h-full',\n start: 'justify-start h-full',\n end: 'justify-end h-full',\n top: 'items-start justify-center w-full',\n bottom: 'items-end justify-center h-full',\n}\n\nconst panelAlignmentMap: AlignmentMap = {\n center: 'data-[closed]:scale-95 rounded-2xl',\n start: 'data-[closed]:-translate-x-1/2 rounded-r-2xl',\n end: 'data-[closed]:translate-x-1/2 rounded-l-2xl',\n top: 'data-[closed]:-translate-y-1/2 rounded-b-2xl',\n // Bottom sheets clear the home indicator on notched phones (0 elsewhere).\n bottom:\n 'data-[closed]:translate-y-full rounded-t-2xl md:rounded-t-3xl max-h-[88vh] max-h-[88dvh] pb-[env(safe-area-inset-bottom,0px)]',\n}\n\nconst panelSizeMap: SizeMap = {\n xs: 'max-w-xs',\n sm: 'max-w-sm',\n md: 'max-w-md',\n lg: 'max-w-lg',\n xl: 'max-w-xl',\n '2xl': 'max-w-2xl',\n full: 'w-full',\n}\n\n// How much shorter than the window the visual viewport must be before it's\n// taken to be the on-screen keyboard rather than the URL bar (≈ 56px).\nconst KEYBOARD_THRESHOLD = 150\nexport const Drawer: React.FC<DrawerProps> = ({\n isOpen,\n onClose = () => {},\n closeOnOutsideClick = true,\n align: alignProp = 'end',\n size: sizeProp = 'md',\n backdrop = true,\n title,\n description,\n children,\n footer,\n footerSticky = false,\n panelClass = '',\n contentClass = '',\n titleClass = '',\n descriptionClass = '',\n titleSticky = false,\n showBackButton = false,\n showCloseButton = true,\n dragHandle,\n phoneLayout = 'default',\n headerAction,\n}) => {\n const align = useResponsiveValue(alignProp)\n const size = useResponsiveValue(sizeProp)\n const breakpoint = useBreakpoint()\n const fullscreen = phoneLayout === 'fullscreen' && breakpoint === 'compact'\n const showTitleSection = Boolean(title || description || showBackButton || showCloseButton)\n const shouldShowDragHandle = !fullscreen && (dragHandle ?? align === 'bottom')\n useCloseOnBack(fullscreen && isOpen, onClose)\n // The visible area while the on-screen keyboard is open; null otherwise.\n const [keyboardViewport, setKeyboardViewport] = React.useState<{\n top: number\n height: number\n } | null>(null)\n\n React.useEffect(() => {\n if (!isOpen) {\n setKeyboardViewport(null)\n return\n }\n\n // Follow the visual viewport only while the on-screen keyboard takes space\n // (it's then much shorter than the window), so the drawer and its footer\n // stay above the keyboard. Otherwise it's pinned to the edges: tracking every resize and\n // scroll in state re-rendered the drawer on each frame as the browser's\n // URL bar moved, and its sticky header and footer jumped while scrolling.\n let frame = 0\n const measure = () => {\n frame = 0\n const vv = window.visualViewport\n if (!vv) return\n const keyboardOpen = window.innerHeight - vv.height > KEYBOARD_THRESHOLD\n const top = Math.round(vv.offsetTop)\n const height = Math.round(vv.height)\n // The browser scrolls the visible area to the focused field, so the\n // drawer follows its offset as well as its height. An unchanged value\n // doesn't re-render.\n setKeyboardViewport((current) =>\n !keyboardOpen\n ? null\n : current && current.top === top && current.height === height\n ? current\n : { top, height },\n )\n }\n const updateHeight = () => {\n if (!frame) frame = window.requestAnimationFrame(measure)\n }\n\n measure()\n const vv = typeof window !== 'undefined' ? window.visualViewport : null\n vv?.addEventListener('resize', updateHeight)\n vv?.addEventListener('scroll', updateHeight)\n\n return () => {\n if (frame) window.cancelAnimationFrame(frame)\n vv?.removeEventListener('resize', updateHeight)\n vv?.removeEventListener('scroll', updateHeight)\n }\n }, [isOpen])\n\n const handleDialogClose = () => {\n if (closeOnOutsideClick) {\n onClose?.()\n }\n }\n\n return (\n <Dialog\n open={isOpen}\n as=\"div\"\n className=\"relative z-50 focus:outline-none\"\n onClose={handleDialogClose}\n transition\n >\n {backdrop && (\n <DialogBackdrop className=\"fixed inset-0 bg-black/40 backdrop-blur-xs transition-opacity duration-200\" />\n )}\n\n {/* Pinned to all four edges rather than given a height: mobile browsers\n move fixed edges with the URL bar on every frame, while 100dvh only\n updates once its animation ends (the drawer briefly came up short).\n With the keyboard open it takes the visible area's offset and height. */}\n <div\n className=\"fixed inset-0\"\n style={\n keyboardViewport\n ? {\n top: `${keyboardViewport.top}px`,\n height: `${keyboardViewport.height}px`,\n bottom: 'auto',\n }\n : undefined\n }\n data-testid=\"drawer-viewport\"\n >\n <div className={buildClassName('flex', fullscreen ? 'h-full' : alignmentMap[align])}>\n <DialogPanel\n transition\n className={buildClassName(\n 'w-full max-w-full bg-white dark:bg-gray-800 shadow-xl flex flex-col min-w-0',\n 'overflow-y-auto overflow-x-hidden overscroll-contain touch-pan-y [-webkit-overflow-scrolling:touch] duration-200 ease-out data-[closed]:opacity-0',\n ...(fullscreen\n ? ['h-full data-[closed]:translate-y-4']\n : [panelAlignmentMap[align], panelSizeMap[size], panelClass]),\n )}\n data-layout={fullscreen ? 'fullscreen' : undefined}\n >\n {/* Top drag handle indicator for mobile bottom sheet */}\n {shouldShowDragHandle && (\n <div\n className=\"w-12 h-1.5 rounded-full bg-gray-300 dark:bg-gray-600 mx-auto my-2.5 shrink-0 cursor-grab\"\n data-testid=\"drawer-drag-handle\"\n />\n )}\n\n {/* Phone full-screen bar: the only fixed part, so the fields keep the room. */}\n {fullscreen && (\n <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\">\n <Button\n iconOnly\n aria-label=\"Close\"\n variant=\"plain\"\n theme=\"secondary\"\n onClick={onClose}\n className=\"shrink-0\"\n >\n <XIcon className=\"size-5\" />\n </Button>\n {title && (\n <DialogTitle\n as=\"h3\"\n className=\"min-w-0 flex-1 truncate text-base font-semibold text-gray-900 dark:text-white\"\n >\n {title}\n </DialogTitle>\n )}\n {headerAction && <div className=\"ml-auto shrink-0\">{headerAction}</div>}\n </div>\n )}\n\n {/* Header Section */}\n {!fullscreen && showTitleSection && (\n <div\n className={buildClassName(\n 'space-y-1 py-3 px-4 md:px-6 flex flex-col border-b border-[var(--ui-border)] shrink-0',\n titleSticky && 'sticky top-0 bg-white dark:bg-gray-800 z-10',\n )}\n >\n <div className=\"flex items-center justify-between gap-3 min-w-0\">\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n {showBackButton && (\n <Button\n iconOnly\n aria-label=\"Back\"\n variant=\"plain\"\n theme=\"secondary\"\n size=\"xs\"\n onClick={onClose}\n >\n <ChevronLeftIcon className=\"size-4\" />\n </Button>\n )}\n {title && (\n <DialogTitle\n as=\"h3\"\n className={buildClassName(\n 'text-lg font-semibold text-gray-900 dark:text-white truncate tracking-tight min-w-0',\n titleClass,\n )}\n >\n {title}\n </DialogTitle>\n )}\n </div>\n\n {showCloseButton && (\n <Button\n iconOnly\n aria-label=\"Close drawer\"\n variant=\"plain\"\n theme=\"secondary\"\n size=\"xs\"\n onClick={onClose}\n className=\"rounded-full text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 shrink-0\"\n >\n <XIcon className=\"size-4\" />\n </Button>\n )}\n </div>\n\n {description && (\n <p\n className={buildClassName(\n 'text-sm text-gray-500 dark:text-gray-400 font-normal truncate',\n descriptionClass,\n )}\n >\n {description}\n </p>\n )}\n </div>\n )}\n\n {/* Content Body */}\n <div\n className={buildClassName(\n 'flex-1 p-4 md:p-6 space-y-4 min-w-0',\n contentClass,\n fullscreen && 'pb-[calc(1rem+env(safe-area-inset-bottom,0px))]',\n )}\n >\n {fullscreen && description && (\n <p\n className={buildClassName(\n 'text-sm text-gray-500 dark:text-gray-400',\n descriptionClass,\n )}\n >\n {description}\n </p>\n )}\n {children}\n </div>\n\n {/* Footer Section (the phone full-screen bar carries the action instead) */}\n {!fullscreen && footer && (\n <div\n className={buildClassName(\n 'p-4 border-t border-[var(--ui-border-muted)] bg-white dark:bg-gray-800 shrink-0 min-w-0',\n footerSticky && 'sticky bottom-0 z-10',\n )}\n >\n {footer}\n </div>\n )}\n </DialogPanel>\n </div>\n </div>\n </Dialog>\n )\n}\n\nDrawer.displayName = 'Drawer'\n\n/**\n * While `active`, the browser's back button (and Android's back gesture)\n * closes the drawer instead of leaving the page: opening adds a history entry\n * (keeping the current state, so routers see the same location), and back\n * removes it. Closed any other way, the entry is removed too, but only if it's\n * still the latest one, so a form that saves and then navigates isn't undone.\n */\nfunction useCloseOnBack(active: boolean, onClose: () => void) {\n const onCloseRef = React.useRef(onClose)\n React.useEffect(() => {\n onCloseRef.current = onClose\n })\n const token = React.useId()\n\n React.useEffect(() => {\n if (!active || typeof window === 'undefined') return\n const pushEntry = () =>\n window.history.pushState({ ...(window.history.state ?? {}), drawer: token }, '')\n pushEntry()\n let entryPresent = true\n let disposed = false\n const handlePop = () => {\n if (window.history.state?.drawer === token) return\n entryPresent = false\n onCloseRef.current()\n // onClose may step back within the drawer instead of closing it; if it's\n // still open afterwards, take back its entry so the next back works too.\n window.setTimeout(() => {\n if (disposed) return\n pushEntry()\n entryPresent = true\n }, 0)\n }\n window.addEventListener('popstate', handlePop)\n return () => {\n disposed = true\n window.removeEventListener('popstate', handlePop)\n if (entryPresent && window.history.state?.drawer === token) window.history.back()\n }\n }, [active, token])\n}\n"],"names":["useResponsiveValue","useBreakpoint","_jsxs","Dialog","_jsx","DialogBackdrop","buildClassName","DialogPanel","Button","XIcon","DialogTitle","ChevronLeftIcon"],"mappings":"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"}
|
|
@@ -41,7 +41,9 @@ function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabe
|
|
|
41
41
|
};
|
|
42
42
|
return (jsxRuntime.jsxs("header", { className: buildClassname.buildClassName('w-full transition-colors select-none z-30', sticky && 'sticky top-0', transparent
|
|
43
43
|
? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-[var(--ui-border-muted)]'
|
|
44
|
-
: 'bg-white dark:bg-gray-900 border-b border-[var(--ui-border)] shadow-xs', 'pt-[env(safe-area-inset-top,0px)]', className), "data-testid": "mobile-header", children: [jsxRuntime.jsx("div", { className: "h-
|
|
44
|
+
: 'bg-white dark:bg-gray-900 border-b border-[var(--ui-border)] shadow-xs', 'pt-[env(safe-area-inset-top,0px)]', className), "data-testid": "mobile-header", children: [jsxRuntime.jsx("div", { className: "h-16 px-4 flex items-center justify-between gap-2 relative", children: searchable && isSearchActive ? (jsxRuntime.jsxs("div", { className: "flex-1 flex items-center gap-2", "data-testid": "mobile-header-search-container", children: [jsxRuntime.jsxs("div", { className: "relative flex-1 flex items-center", children: [jsxRuntime.jsx("span", { className: "absolute left-3 pointer-events-none", children: jsxRuntime.jsx(SearchIcon, {}) }), jsxRuntime.jsx("input", { type: "text", value: searchValue, onChange: handleSearchInput, placeholder: searchPlaceholder, autoFocus: true,
|
|
45
|
+
// 16px text: iOS zooms the page into any field with smaller text.
|
|
46
|
+
className: "w-full pl-9 pr-8 py-2 text-base bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white rounded-full border border-transparent focus:border-blue-500 focus:outline-hidden transition-all", "data-testid": "mobile-header-search-input" }), searchValue && (jsxRuntime.jsx("button", { type: "button", onClick: handleClearSearch, "aria-label": "Clear search", className: "absolute right-2.5 p-0.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 cursor-pointer", children: jsxRuntime.jsx(ClearIcon, {}) }))] }), jsxRuntime.jsx("button", { type: "button", onClick: () => setIsSearchActive(false), className: "text-xs font-semibold text-blue-600 dark:text-blue-400 px-2 py-1.5 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-950/40 cursor-pointer", children: "Cancel" })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasLeadingSlot && (jsxRuntime.jsx("div", { className: buildClassname.buildClassName('flex items-center gap-1 shrink-0 z-10',
|
|
45
47
|
// A Back button is its own 40px target; reserve the width only for other content.
|
|
46
48
|
!onBack && 'min-w-[40px]'), "data-testid": "mobile-header-leading", children: onBack ? (jsxRuntime.jsxs("button", { type: "button", onClick: onBack, "aria-label": backLabel,
|
|
47
49
|
// 40px-tall touch target hanging into the row padding: the chevron
|
|
@@ -52,7 +54,7 @@ function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabe
|
|
|
52
54
|
// a centred title keeps even padding.
|
|
53
55
|
titleAlign === 'center' ? 'px-2' : 'pr-2', titleAlign === 'center'
|
|
54
56
|
? 'absolute inset-x-12 text-center pointer-events-none flex flex-col items-center justify-center'
|
|
55
|
-
: 'text-left'), children: [title && (jsxRuntime.jsx("div", { className: "text-
|
|
57
|
+
: 'text-left'), children: [title && (jsxRuntime.jsx("div", { className: "text-lg leading-6 font-semibold text-gray-900 dark:text-white truncate pointer-events-auto", "data-testid": "mobile-header-title", children: title })), subtitle && (jsxRuntime.jsx("div", { className: "text-[13px] leading-5 text-gray-500 dark:text-gray-400 truncate pointer-events-auto", "data-testid": "mobile-header-subtitle", children: subtitle }))] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10", children: [searchable && (jsxRuntime.jsx("button", { type: "button", onClick: () => setIsSearchActive(true), "aria-label": "Search", className: "p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-search-toggle", children: jsxRuntime.jsx(SearchIcon, {}) })), visibleActions.map((action) => {
|
|
56
58
|
const badgeColor = {
|
|
57
59
|
danger: 'bg-red-500 text-white',
|
|
58
60
|
info: 'bg-blue-500 text-white',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MobileHeader.js","sources":["../../../../../src/components/MobileHeader/MobileHeader.tsx"],"sourcesContent":["import React, { useState } from 'react'\nimport type { MobileHeaderProps, MobileHeaderAction } from './MobileHeader.types'\nimport { buildClassName } from '../../utils/build-classname'\nimport { ActionSheet } from '../ActionSheet'\n\nfunction ChevronLeftIcon() {\n return (\n <svg className=\"size-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2.5} d=\"M15 19l-7-7 7-7\" />\n </svg>\n )\n}\n\nfunction SearchIcon() {\n return (\n <svg className=\"size-4 text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n )\n}\n\nfunction MoreIcon() {\n return (\n <svg className=\"size-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"5\" r=\"1.75\" />\n <circle cx=\"12\" cy=\"12\" r=\"1.75\" />\n <circle cx=\"12\" cy=\"19\" r=\"1.75\" />\n </svg>\n )\n}\n\nfunction ClearIcon() {\n return (\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n )\n}\n\nexport function MobileHeader({\n title,\n subtitle,\n titleAlign = 'center',\n onBack,\n backLabel = 'Back',\n leading,\n actions,\n maxVisibleActions,\n overflowLabel = 'More actions',\n trailing,\n searchable = false,\n searchValue: externalSearchValue,\n onSearchChange,\n searchPlaceholder = 'Search...',\n sticky = true,\n transparent = false,\n bottomSlot,\n className = '',\n}: MobileHeaderProps) {\n const [isSearchActive, setIsSearchActive] = useState(false)\n const [internalSearchValue, setInternalSearchValue] = useState('')\n const [isOverflowOpen, setIsOverflowOpen] = useState(false)\n const hasLeadingSlot = Boolean(onBack || leading) || titleAlign === 'center'\n\n // The overflow button takes one of the visible slots.\n const allActions = actions ?? []\n const shouldOverflow = maxVisibleActions !== undefined && allActions.length > maxVisibleActions\n const visibleActions = shouldOverflow\n ? allActions.slice(0, Math.max(maxVisibleActions - 1, 0))\n : allActions\n const overflowActions = shouldOverflow ? allActions.slice(visibleActions.length) : []\n\n const searchValue = externalSearchValue !== undefined ? externalSearchValue : internalSearchValue\n\n const handleSearchInput = (e: React.ChangeEvent<HTMLInputElement>) => {\n const val = e.target.value\n setInternalSearchValue(val)\n onSearchChange?.(val)\n }\n\n const handleClearSearch = () => {\n setInternalSearchValue('')\n onSearchChange?.('')\n }\n\n return (\n <header\n className={buildClassName(\n 'w-full transition-colors select-none z-30',\n sticky && 'sticky top-0',\n transparent\n ? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-[var(--ui-border-muted)]'\n : 'bg-white dark:bg-gray-900 border-b border-[var(--ui-border)] shadow-xs',\n 'pt-[env(safe-area-inset-top,0px)]',\n className,\n )}\n data-testid=\"mobile-header\"\n >\n {/* Main Header Row */}\n <div className=\"h-14 px-4 flex items-center justify-between gap-2 relative\">\n {/* Search Mode Header View */}\n {searchable && isSearchActive ? (\n <div\n className=\"flex-1 flex items-center gap-2\"\n data-testid=\"mobile-header-search-container\"\n >\n <div className=\"relative flex-1 flex items-center\">\n <span className=\"absolute left-3 pointer-events-none\">\n <SearchIcon />\n </span>\n <input\n type=\"text\"\n value={searchValue}\n onChange={handleSearchInput}\n placeholder={searchPlaceholder}\n autoFocus\n className=\"w-full pl-9 pr-8 py-1.5 text-xs bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white rounded-full border border-transparent focus:border-blue-500 focus:outline-hidden transition-all\"\n data-testid=\"mobile-header-search-input\"\n />\n {searchValue && (\n <button\n type=\"button\"\n onClick={handleClearSearch}\n aria-label=\"Clear search\"\n className=\"absolute right-2.5 p-0.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 cursor-pointer\"\n >\n <ClearIcon />\n </button>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => setIsSearchActive(false)}\n className=\"text-xs font-semibold text-blue-600 dark:text-blue-400 px-2 py-1.5 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-950/40 cursor-pointer\"\n >\n Cancel\n </button>\n </div>\n ) : (\n <>\n {/* Left Leading / Back Section. Omitted for a left-aligned title with nothing\n before it, so the title lines up with the page content; kept for a centred\n title, where the empty slot balances the actions. */}\n {hasLeadingSlot && (\n <div\n className={buildClassName(\n 'flex items-center gap-1 shrink-0 z-10',\n // A Back button is its own 40px target; reserve the width only for other content.\n !onBack && 'min-w-[40px]',\n )}\n data-testid=\"mobile-header-leading\"\n >\n {onBack ? (\n <button\n type=\"button\"\n onClick={onBack}\n aria-label={backLabel}\n // 40px-tall touch target hanging into the row padding: the chevron\n // sits on the content edge, with no right padding, so the title\n // follows ~10px after it.\n className=\"flex min-h-10 min-w-8 items-center justify-start gap-0.5 pl-2.5 pr-0 sm:pr-2 -ml-2.5 text-blue-600 dark:text-blue-400 hover:text-blue-700 rounded-xl transition-colors cursor-pointer\"\n data-testid=\"mobile-header-back-btn\"\n >\n <ChevronLeftIcon />\n <span className=\"text-xs font-semibold hidden sm:inline\">{backLabel}</span>\n </button>\n ) : (\n leading\n )}\n </div>\n )}\n\n {/* Title & Subtitle Section */}\n <div\n className={buildClassName(\n 'flex-1 min-w-0',\n // The row's gap already separates a left title from a leading element;\n // a centred title keeps even padding.\n titleAlign === 'center' ? 'px-2' : 'pr-2',\n titleAlign === 'center'\n ? 'absolute inset-x-12 text-center pointer-events-none flex flex-col items-center justify-center'\n : 'text-left',\n )}\n >\n {title && (\n <div\n className=\"text-sm font-bold text-gray-900 dark:text-white truncate pointer-events-auto\"\n data-testid=\"mobile-header-title\"\n >\n {title}\n </div>\n )}\n {subtitle && (\n <div\n className=\"text-[11px] text-gray-500 dark:text-gray-400 truncate pointer-events-auto\"\n data-testid=\"mobile-header-subtitle\"\n >\n {subtitle}\n </div>\n )}\n </div>\n\n {/* Right Actions Section */}\n <div className=\"flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10\">\n {searchable && (\n <button\n type=\"button\"\n onClick={() => setIsSearchActive(true)}\n aria-label=\"Search\"\n className=\"p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer\"\n data-testid=\"mobile-header-search-toggle\"\n >\n <SearchIcon />\n </button>\n )}\n\n {visibleActions.map((action: MobileHeaderAction) => {\n const badgeColor = {\n danger: 'bg-red-500 text-white',\n info: 'bg-blue-500 text-white',\n success: 'bg-emerald-500 text-white',\n }[action.badgeVariant || 'danger']\n\n return (\n <button\n key={action.id}\n type=\"button\"\n disabled={action.disabled}\n onClick={action.onClick}\n aria-label={action.label}\n className={buildClassName(\n 'p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors relative cursor-pointer',\n action.disabled && 'opacity-40 cursor-not-allowed pointer-events-none',\n )}\n data-testid={`mobile-header-action-${action.id}`}\n >\n <span className=\"text-base\">{action.icon}</span>\n {action.badge !== undefined && action.badge !== null && (\n <span\n className={buildClassName(\n 'absolute top-1 right-1 min-w-[14px] h-3.5 px-0.5 rounded-full text-[9px] font-bold flex items-center justify-center shadow-xs',\n badgeColor,\n )}\n data-testid={`mobile-header-badge-${action.id}`}\n >\n {action.badge}\n </span>\n )}\n </button>\n )\n })}\n\n {overflowActions.length > 0 && (\n <button\n type=\"button\"\n onClick={() => setIsOverflowOpen(true)}\n aria-label={overflowLabel}\n aria-haspopup=\"dialog\"\n aria-expanded={isOverflowOpen}\n className=\"p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer\"\n data-testid=\"mobile-header-overflow\"\n >\n <MoreIcon />\n </button>\n )}\n\n {trailing}\n </div>\n </>\n )}\n </div>\n\n {overflowActions.length > 0 && (\n <ActionSheet\n isOpen={isOverflowOpen}\n onClose={() => setIsOverflowOpen(false)}\n actions={overflowActions.map((action) => ({\n id: action.id,\n label: action.label,\n icon: action.icon,\n disabled: action.disabled,\n onClick: action.onClick,\n }))}\n />\n )}\n\n {/* Optional Bottom Sub-Header Slot (e.g. Category tabs or filter bar) */}\n {bottomSlot && (\n <div\n className=\"px-3 py-2 border-t border-[var(--ui-border-muted)]\"\n data-testid=\"mobile-header-bottom-slot\"\n >\n {bottomSlot}\n </div>\n )}\n </header>\n )\n}\n\nMobileHeader.displayName = 'MobileHeader'\n"],"names":["_jsx","_jsxs","useState","buildClassName","_Fragment","ActionSheet"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MobileHeader.js","sources":["../../../../../src/components/MobileHeader/MobileHeader.tsx"],"sourcesContent":["import React, { useState } from 'react'\nimport type { MobileHeaderProps, MobileHeaderAction } from './MobileHeader.types'\nimport { buildClassName } from '../../utils/build-classname'\nimport { ActionSheet } from '../ActionSheet'\n\nfunction ChevronLeftIcon() {\n return (\n <svg className=\"size-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2.5} d=\"M15 19l-7-7 7-7\" />\n </svg>\n )\n}\n\nfunction SearchIcon() {\n return (\n <svg className=\"size-4 text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n )\n}\n\nfunction MoreIcon() {\n return (\n <svg className=\"size-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"5\" r=\"1.75\" />\n <circle cx=\"12\" cy=\"12\" r=\"1.75\" />\n <circle cx=\"12\" cy=\"19\" r=\"1.75\" />\n </svg>\n )\n}\n\nfunction ClearIcon() {\n return (\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n )\n}\n\nexport function MobileHeader({\n title,\n subtitle,\n titleAlign = 'center',\n onBack,\n backLabel = 'Back',\n leading,\n actions,\n maxVisibleActions,\n overflowLabel = 'More actions',\n trailing,\n searchable = false,\n searchValue: externalSearchValue,\n onSearchChange,\n searchPlaceholder = 'Search...',\n sticky = true,\n transparent = false,\n bottomSlot,\n className = '',\n}: MobileHeaderProps) {\n const [isSearchActive, setIsSearchActive] = useState(false)\n const [internalSearchValue, setInternalSearchValue] = useState('')\n const [isOverflowOpen, setIsOverflowOpen] = useState(false)\n const hasLeadingSlot = Boolean(onBack || leading) || titleAlign === 'center'\n\n // The overflow button takes one of the visible slots.\n const allActions = actions ?? []\n const shouldOverflow = maxVisibleActions !== undefined && allActions.length > maxVisibleActions\n const visibleActions = shouldOverflow\n ? allActions.slice(0, Math.max(maxVisibleActions - 1, 0))\n : allActions\n const overflowActions = shouldOverflow ? allActions.slice(visibleActions.length) : []\n\n const searchValue = externalSearchValue !== undefined ? externalSearchValue : internalSearchValue\n\n const handleSearchInput = (e: React.ChangeEvent<HTMLInputElement>) => {\n const val = e.target.value\n setInternalSearchValue(val)\n onSearchChange?.(val)\n }\n\n const handleClearSearch = () => {\n setInternalSearchValue('')\n onSearchChange?.('')\n }\n\n return (\n <header\n className={buildClassName(\n 'w-full transition-colors select-none z-30',\n sticky && 'sticky top-0',\n transparent\n ? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-[var(--ui-border-muted)]'\n : 'bg-white dark:bg-gray-900 border-b border-[var(--ui-border)] shadow-xs',\n 'pt-[env(safe-area-inset-top,0px)]',\n className,\n )}\n data-testid=\"mobile-header\"\n >\n {/* Main Header Row: 64px, the height of a standard phone top bar, so a\n title and a subtitle line both have room. */}\n <div className=\"h-16 px-4 flex items-center justify-between gap-2 relative\">\n {/* Search Mode Header View */}\n {searchable && isSearchActive ? (\n <div\n className=\"flex-1 flex items-center gap-2\"\n data-testid=\"mobile-header-search-container\"\n >\n <div className=\"relative flex-1 flex items-center\">\n <span className=\"absolute left-3 pointer-events-none\">\n <SearchIcon />\n </span>\n <input\n type=\"text\"\n value={searchValue}\n onChange={handleSearchInput}\n placeholder={searchPlaceholder}\n autoFocus\n // 16px text: iOS zooms the page into any field with smaller text.\n className=\"w-full pl-9 pr-8 py-2 text-base bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white rounded-full border border-transparent focus:border-blue-500 focus:outline-hidden transition-all\"\n data-testid=\"mobile-header-search-input\"\n />\n {searchValue && (\n <button\n type=\"button\"\n onClick={handleClearSearch}\n aria-label=\"Clear search\"\n className=\"absolute right-2.5 p-0.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 cursor-pointer\"\n >\n <ClearIcon />\n </button>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => setIsSearchActive(false)}\n className=\"text-xs font-semibold text-blue-600 dark:text-blue-400 px-2 py-1.5 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-950/40 cursor-pointer\"\n >\n Cancel\n </button>\n </div>\n ) : (\n <>\n {/* Left Leading / Back Section. Omitted for a left-aligned title with nothing\n before it, so the title lines up with the page content; kept for a centred\n title, where the empty slot balances the actions. */}\n {hasLeadingSlot && (\n <div\n className={buildClassName(\n 'flex items-center gap-1 shrink-0 z-10',\n // A Back button is its own 40px target; reserve the width only for other content.\n !onBack && 'min-w-[40px]',\n )}\n data-testid=\"mobile-header-leading\"\n >\n {onBack ? (\n <button\n type=\"button\"\n onClick={onBack}\n aria-label={backLabel}\n // 40px-tall touch target hanging into the row padding: the chevron\n // sits on the content edge, with no right padding, so the title\n // follows ~10px after it.\n className=\"flex min-h-10 min-w-8 items-center justify-start gap-0.5 pl-2.5 pr-0 sm:pr-2 -ml-2.5 text-blue-600 dark:text-blue-400 hover:text-blue-700 rounded-xl transition-colors cursor-pointer\"\n data-testid=\"mobile-header-back-btn\"\n >\n <ChevronLeftIcon />\n <span className=\"text-xs font-semibold hidden sm:inline\">{backLabel}</span>\n </button>\n ) : (\n leading\n )}\n </div>\n )}\n\n {/* Title & Subtitle Section */}\n <div\n className={buildClassName(\n 'flex-1 min-w-0',\n // The row's gap already separates a left title from a leading element;\n // a centred title keeps even padding.\n titleAlign === 'center' ? 'px-2' : 'pr-2',\n titleAlign === 'center'\n ? 'absolute inset-x-12 text-center pointer-events-none flex flex-col items-center justify-center'\n : 'text-left',\n )}\n >\n {title && (\n <div\n className=\"text-lg leading-6 font-semibold text-gray-900 dark:text-white truncate pointer-events-auto\"\n data-testid=\"mobile-header-title\"\n >\n {title}\n </div>\n )}\n {subtitle && (\n <div\n className=\"text-[13px] leading-5 text-gray-500 dark:text-gray-400 truncate pointer-events-auto\"\n data-testid=\"mobile-header-subtitle\"\n >\n {subtitle}\n </div>\n )}\n </div>\n\n {/* Right Actions Section */}\n <div className=\"flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10\">\n {searchable && (\n <button\n type=\"button\"\n onClick={() => setIsSearchActive(true)}\n aria-label=\"Search\"\n className=\"p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer\"\n data-testid=\"mobile-header-search-toggle\"\n >\n <SearchIcon />\n </button>\n )}\n\n {visibleActions.map((action: MobileHeaderAction) => {\n const badgeColor = {\n danger: 'bg-red-500 text-white',\n info: 'bg-blue-500 text-white',\n success: 'bg-emerald-500 text-white',\n }[action.badgeVariant || 'danger']\n\n return (\n <button\n key={action.id}\n type=\"button\"\n disabled={action.disabled}\n onClick={action.onClick}\n aria-label={action.label}\n className={buildClassName(\n 'p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors relative cursor-pointer',\n action.disabled && 'opacity-40 cursor-not-allowed pointer-events-none',\n )}\n data-testid={`mobile-header-action-${action.id}`}\n >\n <span className=\"text-base\">{action.icon}</span>\n {action.badge !== undefined && action.badge !== null && (\n <span\n className={buildClassName(\n 'absolute top-1 right-1 min-w-[14px] h-3.5 px-0.5 rounded-full text-[9px] font-bold flex items-center justify-center shadow-xs',\n badgeColor,\n )}\n data-testid={`mobile-header-badge-${action.id}`}\n >\n {action.badge}\n </span>\n )}\n </button>\n )\n })}\n\n {overflowActions.length > 0 && (\n <button\n type=\"button\"\n onClick={() => setIsOverflowOpen(true)}\n aria-label={overflowLabel}\n aria-haspopup=\"dialog\"\n aria-expanded={isOverflowOpen}\n className=\"p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer\"\n data-testid=\"mobile-header-overflow\"\n >\n <MoreIcon />\n </button>\n )}\n\n {trailing}\n </div>\n </>\n )}\n </div>\n\n {overflowActions.length > 0 && (\n <ActionSheet\n isOpen={isOverflowOpen}\n onClose={() => setIsOverflowOpen(false)}\n actions={overflowActions.map((action) => ({\n id: action.id,\n label: action.label,\n icon: action.icon,\n disabled: action.disabled,\n onClick: action.onClick,\n }))}\n />\n )}\n\n {/* Optional Bottom Sub-Header Slot (e.g. Category tabs or filter bar) */}\n {bottomSlot && (\n <div\n className=\"px-3 py-2 border-t border-[var(--ui-border-muted)]\"\n data-testid=\"mobile-header-bottom-slot\"\n >\n {bottomSlot}\n </div>\n )}\n </header>\n )\n}\n\nMobileHeader.displayName = 'MobileHeader'\n"],"names":["_jsx","_jsxs","useState","buildClassName","_Fragment","ActionSheet"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { buildClassName } from '../../utils/build-classname.js';
|
|
3
3
|
|
|
4
|
-
function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels = 'always', theme = 'light', activeIndicatorStyle = 'line', containerClass = '', itemClass = '', linkAs = 'a', linkHrefProp = 'href', ariaLabel = 'Bottom Navigation', hidden = false, }) {
|
|
4
|
+
function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels = 'always', theme = 'light', activeIndicatorStyle = 'line', containerClass = '', itemClass = '', linkAs = 'a', linkHrefProp = 'href', ariaLabel = 'Bottom Navigation', hidden = false, rounded = false, }) {
|
|
5
5
|
const handleTabClick = (item) => {
|
|
6
6
|
var _a;
|
|
7
7
|
if (item.disabled)
|
|
@@ -17,7 +17,7 @@ function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels
|
|
|
17
17
|
dark: 'bg-gray-900 text-gray-400 border-t border-gray-800',
|
|
18
18
|
primary: 'bg-blue-600 text-blue-100 border-t border-blue-700',
|
|
19
19
|
}[theme];
|
|
20
|
-
return (jsx("nav", { "aria-label": ariaLabel, className: buildClassName('w-full flex items-center justify-around select-none shadow-lg transition-colors relative', fixed && 'fixed bottom-0 left-0 right-0 z-40', 'pb-[env(safe-area-inset-bottom,6px)] px-2', themeStyles, containerClass), "data-testid": "bottom-navigation", children: items.map((item) => {
|
|
20
|
+
return (jsx("nav", { "aria-label": ariaLabel, className: buildClassName('w-full flex items-center justify-around select-none shadow-lg transition-colors relative', fixed && 'fixed bottom-0 left-0 right-0 z-40', 'pb-[env(safe-area-inset-bottom,6px)] px-2', themeStyles, rounded && 'rounded-t-2xl border-x', containerClass), "data-testid": "bottom-navigation", children: items.map((item) => {
|
|
21
21
|
const isActive = item.id === activeId;
|
|
22
22
|
const showLabelText = showLabels === 'always' || (showLabels === 'active' && isActive);
|
|
23
23
|
const activeItemColor = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BottomNavigation.js","sources":["../../../../../src/components/BottomNavigation/BottomNavigation.tsx"],"sourcesContent":["import React, { type ElementType } from 'react'\nimport type { BottomNavigationProps, BottomNavigationItem } from './BottomNavigation.types'\nimport { buildClassName } from '../../utils/build-classname'\n\nexport function BottomNavigation({\n items,\n activeId,\n onChange,\n fixed = true,\n showLabels = 'always',\n theme = 'light',\n activeIndicatorStyle = 'line',\n containerClass = '',\n itemClass = '',\n linkAs = 'a',\n linkHrefProp = 'href',\n ariaLabel = 'Bottom Navigation',\n hidden = false,\n}: BottomNavigationProps) {\n const handleTabClick = (item: BottomNavigationItem) => {\n if (item.disabled) return\n item.onClick?.()\n onChange?.(item.id)\n }\n\n // Removed from the tree, not just covered, so hidden tabs can't take focus.\n if (hidden) return null\n\n const themeStyles = {\n light:\n 'bg-white dark:bg-gray-900 border-t border-[var(--ui-border)] text-gray-600 dark:text-gray-400',\n dark: 'bg-gray-900 text-gray-400 border-t border-gray-800',\n primary: 'bg-blue-600 text-blue-100 border-t border-blue-700',\n }[theme]\n\n return (\n <nav\n aria-label={ariaLabel}\n className={buildClassName(\n 'w-full flex items-center justify-around select-none shadow-lg transition-colors relative',\n fixed && 'fixed bottom-0 left-0 right-0 z-40',\n 'pb-[env(safe-area-inset-bottom,6px)] px-2',\n themeStyles,\n containerClass,\n )}\n data-testid=\"bottom-navigation\"\n >\n {items.map((item) => {\n const isActive = item.id === activeId\n const showLabelText = showLabels === 'always' || (showLabels === 'active' && isActive)\n\n const activeItemColor = {\n light: 'text-blue-600 dark:text-blue-400 font-bold',\n dark: 'text-white font-bold',\n primary: 'text-white font-bold',\n }[theme]\n\n const inactiveItemColor = {\n light:\n 'text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 font-medium',\n dark: 'text-gray-400 hover:text-gray-200 font-medium',\n primary: 'text-blue-200 hover:text-white font-medium',\n }[theme]\n\n const pillActiveStyles = {\n light:\n 'bg-blue-100 dark:bg-blue-950/80 text-blue-600 dark:text-blue-400 shadow-xs scale-105',\n dark: 'bg-gray-800 text-white ring-1 ring-gray-700 shadow-xs scale-105',\n primary: 'bg-white/25 text-white shadow-xs scale-105',\n }[theme]\n\n const badgeColor = {\n danger: 'bg-red-500 text-white',\n info: 'bg-blue-500 text-white',\n success: 'bg-emerald-500 text-white',\n }[item.badgeVariant || 'danger']\n\n // A disabled item stays a button: a link can't be disabled.\n const isLink = Boolean(item.href) && !item.disabled\n const ItemElement: ElementType = isLink ? linkAs : 'button'\n const elementProps = isLink\n ? { [linkHrefProp]: item.href }\n : { type: 'button', disabled: item.disabled }\n\n return (\n <ItemElement\n key={item.id}\n {...elementProps}\n onClick={() => handleTabClick(item)}\n className={buildClassName(\n 'flex-1 flex flex-col items-center justify-center pt-2 pb-1.5 px-1 transition-all duration-200 relative cursor-pointer group',\n item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none',\n isActive ? activeItemColor : inactiveItemColor,\n itemClass,\n )}\n data-testid={`bottom-nav-item-${item.id}`}\n aria-current={isActive ? 'page' : undefined}\n // With its label hidden, the item still needs a name; the icon is decorative.\n aria-label={!showLabelText && typeof item.label === 'string' ? item.label : undefined}\n >\n {/* Active Indicator Bar on Top Border */}\n {activeIndicatorStyle === 'line' && isActive && (\n <span\n className={buildClassName(\n 'absolute -top-[1px] h-0.75 w-9 rounded-full transition-all duration-200 z-10',\n theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400',\n )}\n />\n )}\n\n {/* Icon & Badge Container */}\n <div\n className={buildClassName(\n 'relative inline-flex items-center justify-center transition-all duration-200',\n activeIndicatorStyle === 'pill'\n ? buildClassName(\n 'px-4 py-1 rounded-full',\n isActive\n ? pillActiveStyles\n : 'group-hover:bg-gray-100/50 dark:group-hover:bg-gray-800/40',\n )\n : 'px-1 py-0.5',\n )}\n >\n <span\n className=\"text-xl transition-transform duration-150 group-active:scale-95\"\n aria-hidden=\"true\"\n >\n {isActive && item.activeIcon ? item.activeIcon : item.icon}\n </span>\n\n {/* Notification Badge */}\n {item.badge !== undefined && item.badge !== null && (\n <span\n className={buildClassName(\n 'absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full text-[10px] font-bold flex items-center justify-center shadow-xs z-10',\n badgeColor,\n )}\n data-testid={`bottom-nav-badge-${item.id}`}\n >\n {item.badge}\n </span>\n )}\n </div>\n\n {/* Label */}\n {showLabelText && (\n <span\n className={buildClassName(\n 'text-[11px] leading-tight mt-1 truncate max-w-full transition-colors duration-150',\n isActive ? 'font-bold' : 'font-medium opacity-90',\n )}\n >\n {item.label}\n </span>\n )}\n\n {/* Active Dot Indicator */}\n {activeIndicatorStyle === 'dot' && isActive && (\n <span\n className={buildClassName(\n 'size-1.5 rounded-full mt-1 transition-all duration-200',\n theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400',\n )}\n />\n )}\n </ItemElement>\n )\n })}\n </nav>\n )\n}\n\nBottomNavigation.displayName = 'BottomNavigation'\n"],"names":["_jsx","_jsxs"],"mappings":";;;AAIM,SAAU,gBAAgB,CAAC,EAC/B,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,KAAK,GAAG,IAAI,EACZ,UAAU,GAAG,QAAQ,EACrB,KAAK,GAAG,OAAO,EACf,oBAAoB,GAAG,MAAM,EAC7B,cAAc,GAAG,EAAE,EACnB,SAAS,GAAG,EAAE,EACd,MAAM,GAAG,GAAG,EACZ,YAAY,GAAG,MAAM,EACrB,SAAS,GAAG,mBAAmB,EAC/B,MAAM,GAAG,KAAK,GACQ,EAAA;AACtB,IAAA,MAAM,cAAc,GAAG,CAAC,IAA0B,KAAI;;QACpD,IAAI,IAAI,CAAC,QAAQ;YAAE;AACnB,QAAA,CAAA,EAAA,GAAA,IAAI,CAAC,OAAO,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,IAAA,CAAI;QAChB,QAAQ,KAAA,IAAA,IAAR,QAAQ,KAAA,MAAA,GAAA,MAAA,GAAR,QAAQ,CAAG,IAAI,CAAC,EAAE,CAAC;AACrB,IAAA,CAAC;;AAGD,IAAA,IAAI,MAAM;AAAE,QAAA,OAAO,IAAI;AAEvB,IAAA,MAAM,WAAW,GAAG;AAClB,QAAA,KAAK,EACH,+FAA+F;AACjG,QAAA,IAAI,EAAE,oDAAoD;AAC1D,QAAA,OAAO,EAAE,oDAAoD;KAC9D,CAAC,KAAK,CAAC;AAER,IAAA,QACEA,GAAA,CAAA,KAAA,EAAA,EAAA,YAAA,EACc,SAAS,EACrB,SAAS,EAAE,cAAc,CACvB,0FAA0F,EAC1F,KAAK,IAAI,oCAAoC,EAC7C,2CAA2C,EAC3C,WAAW,EACX,cAAc,CACf,EAAA,aAAA,EACW,mBAAmB,EAAA,QAAA,EAE9B,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,KAAI;AAClB,YAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,KAAK,QAAQ;AACrC,YAAA,MAAM,aAAa,GAAG,UAAU,KAAK,QAAQ,KAAK,UAAU,KAAK,QAAQ,IAAI,QAAQ,CAAC;AAEtF,YAAA,MAAM,eAAe,GAAG;AACtB,gBAAA,KAAK,EAAE,4CAA4C;AACnD,gBAAA,IAAI,EAAE,sBAAsB;AAC5B,gBAAA,OAAO,EAAE,sBAAsB;aAChC,CAAC,KAAK,CAAC;AAER,YAAA,MAAM,iBAAiB,GAAG;AACxB,gBAAA,KAAK,EACH,2FAA2F;AAC7F,gBAAA,IAAI,EAAE,+CAA+C;AACrD,gBAAA,OAAO,EAAE,4CAA4C;aACtD,CAAC,KAAK,CAAC;AAER,YAAA,MAAM,gBAAgB,GAAG;AACvB,gBAAA,KAAK,EACH,sFAAsF;AACxF,gBAAA,IAAI,EAAE,iEAAiE;AACvE,gBAAA,OAAO,EAAE,4CAA4C;aACtD,CAAC,KAAK,CAAC;AAER,YAAA,MAAM,UAAU,GAAG;AACjB,gBAAA,MAAM,EAAE,uBAAuB;AAC/B,gBAAA,IAAI,EAAE,wBAAwB;AAC9B,gBAAA,OAAO,EAAE,2BAA2B;AACrC,aAAA,CAAC,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC;;AAGhC,YAAA,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ;YACnD,MAAM,WAAW,GAAgB,MAAM,GAAG,MAAM,GAAG,QAAQ;YAC3D,MAAM,YAAY,GAAG;kBACjB,EAAE,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI;AAC7B,kBAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE;YAE/C,QACEC,IAAA,CAAC,WAAW,EAAA,EAAA,GAEN,YAAY,EAChB,OAAO,EAAE,MAAM,cAAc,CAAC,IAAI,CAAC,EACnC,SAAS,EAAE,cAAc,CACvB,6HAA6H,EAC7H,IAAI,CAAC,QAAQ,IAAI,mDAAmD,EACpE,QAAQ,GAAG,eAAe,GAAG,iBAAiB,EAC9C,SAAS,CACV,EAAA,aAAA,EACY,CAAA,gBAAA,EAAmB,IAAI,CAAC,EAAE,CAAA,CAAE,EAAA,cAAA,EAC3B,QAAQ,GAAG,MAAM,GAAG,SAAS,EAAA,YAAA,EAE/B,CAAC,aAAa,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,GAAG,IAAI,CAAC,KAAK,GAAG,SAAS,EAAA,QAAA,EAAA,CAGpF,oBAAoB,KAAK,MAAM,IAAI,QAAQ,KAC1CD,GAAA,CAAA,MAAA,EAAA,EACE,SAAS,EAAE,cAAc,CACvB,8EAA8E,EAC9E,KAAK,KAAK,SAAS,GAAG,UAAU,GAAG,8BAA8B,CAClE,EAAA,CACD,CACH,EAGDC,IAAA,CAAA,KAAA,EAAA,EACE,SAAS,EAAE,cAAc,CACvB,8EAA8E,EAC9E,oBAAoB,KAAK;AACvB,8BAAE,cAAc,CACZ,wBAAwB,EACxB;AACE,kCAAE;kCACA,4DAA4D;AAEpE,8BAAE,aAAa,CAClB,EAAA,QAAA,EAAA,CAEDD,GAAA,CAAA,MAAA,EAAA,EACE,SAAS,EAAC,iEAAiE,EAAA,aAAA,EAC/D,MAAM,EAAA,QAAA,EAEjB,QAAQ,IAAI,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,EAAA,CACrD,EAGN,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,KAC9CA,GAAA,CAAA,MAAA,EAAA,EACE,SAAS,EAAE,cAAc,CACvB,mIAAmI,EACnI,UAAU,CACX,EAAA,aAAA,EACY,CAAA,iBAAA,EAAoB,IAAI,CAAC,EAAE,CAAA,CAAE,EAAA,QAAA,EAEzC,IAAI,CAAC,KAAK,EAAA,CACN,CACR,CAAA,EAAA,CACG,EAGL,aAAa,KACZA,GAAA,CAAA,MAAA,EAAA,EACE,SAAS,EAAE,cAAc,CACvB,mFAAmF,EACnF,QAAQ,GAAG,WAAW,GAAG,wBAAwB,CAClD,EAAA,QAAA,EAEA,IAAI,CAAC,KAAK,EAAA,CACN,CACR,EAGA,oBAAoB,KAAK,KAAK,IAAI,QAAQ,KACzCA,GAAA,CAAA,MAAA,EAAA,EACE,SAAS,EAAE,cAAc,CACvB,wDAAwD,EACxD,KAAK,KAAK,SAAS,GAAG,UAAU,GAAG,8BAA8B,CAClE,EAAA,CACD,CACH,CAAA,EAAA,EA/EI,IAAI,CAAC,EAAE,CAgFA;QAElB,CAAC,CAAC,EAAA,CACE;AAEV;AAEA,gBAAgB,CAAC,WAAW,GAAG,kBAAkB;;;;"}
|
|
1
|
+
{"version":3,"file":"BottomNavigation.js","sources":["../../../../../src/components/BottomNavigation/BottomNavigation.tsx"],"sourcesContent":["import React, { type ElementType } from 'react'\nimport type { BottomNavigationProps, BottomNavigationItem } from './BottomNavigation.types'\nimport { buildClassName } from '../../utils/build-classname'\n\nexport function BottomNavigation({\n items,\n activeId,\n onChange,\n fixed = true,\n showLabels = 'always',\n theme = 'light',\n activeIndicatorStyle = 'line',\n containerClass = '',\n itemClass = '',\n linkAs = 'a',\n linkHrefProp = 'href',\n ariaLabel = 'Bottom Navigation',\n hidden = false,\n rounded = false,\n}: BottomNavigationProps) {\n const handleTabClick = (item: BottomNavigationItem) => {\n if (item.disabled) return\n item.onClick?.()\n onChange?.(item.id)\n }\n\n // Removed from the tree, not just covered, so hidden tabs can't take focus.\n if (hidden) return null\n\n const themeStyles = {\n light:\n 'bg-white dark:bg-gray-900 border-t border-[var(--ui-border)] text-gray-600 dark:text-gray-400',\n dark: 'bg-gray-900 text-gray-400 border-t border-gray-800',\n primary: 'bg-blue-600 text-blue-100 border-t border-blue-700',\n }[theme]\n\n return (\n <nav\n aria-label={ariaLabel}\n className={buildClassName(\n 'w-full flex items-center justify-around select-none shadow-lg transition-colors relative',\n fixed && 'fixed bottom-0 left-0 right-0 z-40',\n 'pb-[env(safe-area-inset-bottom,6px)] px-2',\n themeStyles,\n rounded && 'rounded-t-2xl border-x',\n containerClass,\n )}\n data-testid=\"bottom-navigation\"\n >\n {items.map((item) => {\n const isActive = item.id === activeId\n const showLabelText = showLabels === 'always' || (showLabels === 'active' && isActive)\n\n const activeItemColor = {\n light: 'text-blue-600 dark:text-blue-400 font-bold',\n dark: 'text-white font-bold',\n primary: 'text-white font-bold',\n }[theme]\n\n const inactiveItemColor = {\n light:\n 'text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 font-medium',\n dark: 'text-gray-400 hover:text-gray-200 font-medium',\n primary: 'text-blue-200 hover:text-white font-medium',\n }[theme]\n\n const pillActiveStyles = {\n light:\n 'bg-blue-100 dark:bg-blue-950/80 text-blue-600 dark:text-blue-400 shadow-xs scale-105',\n dark: 'bg-gray-800 text-white ring-1 ring-gray-700 shadow-xs scale-105',\n primary: 'bg-white/25 text-white shadow-xs scale-105',\n }[theme]\n\n const badgeColor = {\n danger: 'bg-red-500 text-white',\n info: 'bg-blue-500 text-white',\n success: 'bg-emerald-500 text-white',\n }[item.badgeVariant || 'danger']\n\n // A disabled item stays a button: a link can't be disabled.\n const isLink = Boolean(item.href) && !item.disabled\n const ItemElement: ElementType = isLink ? linkAs : 'button'\n const elementProps = isLink\n ? { [linkHrefProp]: item.href }\n : { type: 'button', disabled: item.disabled }\n\n return (\n <ItemElement\n key={item.id}\n {...elementProps}\n onClick={() => handleTabClick(item)}\n className={buildClassName(\n 'flex-1 flex flex-col items-center justify-center pt-2 pb-1.5 px-1 transition-all duration-200 relative cursor-pointer group',\n item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none',\n isActive ? activeItemColor : inactiveItemColor,\n itemClass,\n )}\n data-testid={`bottom-nav-item-${item.id}`}\n aria-current={isActive ? 'page' : undefined}\n // With its label hidden, the item still needs a name; the icon is decorative.\n aria-label={!showLabelText && typeof item.label === 'string' ? item.label : undefined}\n >\n {/* Active Indicator Bar on Top Border */}\n {activeIndicatorStyle === 'line' && isActive && (\n <span\n className={buildClassName(\n 'absolute -top-[1px] h-0.75 w-9 rounded-full transition-all duration-200 z-10',\n theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400',\n )}\n />\n )}\n\n {/* Icon & Badge Container */}\n <div\n className={buildClassName(\n 'relative inline-flex items-center justify-center transition-all duration-200',\n activeIndicatorStyle === 'pill'\n ? buildClassName(\n 'px-4 py-1 rounded-full',\n isActive\n ? pillActiveStyles\n : 'group-hover:bg-gray-100/50 dark:group-hover:bg-gray-800/40',\n )\n : 'px-1 py-0.5',\n )}\n >\n <span\n className=\"text-xl transition-transform duration-150 group-active:scale-95\"\n aria-hidden=\"true\"\n >\n {isActive && item.activeIcon ? item.activeIcon : item.icon}\n </span>\n\n {/* Notification Badge */}\n {item.badge !== undefined && item.badge !== null && (\n <span\n className={buildClassName(\n 'absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full text-[10px] font-bold flex items-center justify-center shadow-xs z-10',\n badgeColor,\n )}\n data-testid={`bottom-nav-badge-${item.id}`}\n >\n {item.badge}\n </span>\n )}\n </div>\n\n {/* Label */}\n {showLabelText && (\n <span\n className={buildClassName(\n 'text-[11px] leading-tight mt-1 truncate max-w-full transition-colors duration-150',\n isActive ? 'font-bold' : 'font-medium opacity-90',\n )}\n >\n {item.label}\n </span>\n )}\n\n {/* Active Dot Indicator */}\n {activeIndicatorStyle === 'dot' && isActive && (\n <span\n className={buildClassName(\n 'size-1.5 rounded-full mt-1 transition-all duration-200',\n theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400',\n )}\n />\n )}\n </ItemElement>\n )\n })}\n </nav>\n )\n}\n\nBottomNavigation.displayName = 'BottomNavigation'\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|