@pk-design/react-tailwind 1.9.0 → 1.10.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 +14 -5
- package/dist/cjs/components/BottomNavigation/BottomNavigation.js.map +1 -1
- package/dist/cjs/components/Drawer/Drawer.js +6 -2
- package/dist/cjs/components/Drawer/Drawer.js.map +1 -1
- package/dist/cjs/components/MobileHeader/MobileHeader.js +21 -3
- package/dist/cjs/components/MobileHeader/MobileHeader.js.map +1 -1
- package/dist/cjs/components/PullToRefresh/PullToRefresh.js +145 -57
- package/dist/cjs/components/PullToRefresh/PullToRefresh.js.map +1 -1
- package/dist/cjs/components/StickyActionBar/StickyActionBar.js +2 -2
- package/dist/cjs/components/StickyActionBar/StickyActionBar.js.map +1 -1
- package/dist/cjs/hooks/use-breakpoint.js +48 -0
- package/dist/cjs/hooks/use-breakpoint.js.map +1 -0
- package/dist/cjs/index.js +5 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/components/BottomNavigation/BottomNavigation.js +14 -5
- package/dist/es/components/BottomNavigation/BottomNavigation.js.map +1 -1
- package/dist/es/components/Drawer/Drawer.js +6 -2
- package/dist/es/components/Drawer/Drawer.js.map +1 -1
- package/dist/es/components/MobileHeader/MobileHeader.js +21 -3
- package/dist/es/components/MobileHeader/MobileHeader.js.map +1 -1
- package/dist/es/components/PullToRefresh/PullToRefresh.js +146 -58
- package/dist/es/components/PullToRefresh/PullToRefresh.js.map +1 -1
- package/dist/es/components/StickyActionBar/StickyActionBar.js +2 -2
- package/dist/es/components/StickyActionBar/StickyActionBar.js.map +1 -1
- package/dist/es/hooks/use-breakpoint.js +43 -0
- package/dist/es/hooks/use-breakpoint.js.map +1 -0
- package/dist/es/index.js +1 -0
- package/dist/es/index.js.map +1 -1
- package/dist/react-tailwind.cjs.js +229 -69
- package/dist/react-tailwind.cjs.js.map +1 -1
- package/dist/react-tailwind.es.js +227 -71
- 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 +28 -3
- package/dist/types/components/Drawer/Drawer.types.d.ts +8 -4
- package/dist/types/components/MobileHeader/MobileHeader.d.ts +1 -1
- package/dist/types/components/MobileHeader/MobileHeader.types.d.ts +13 -0
- package/dist/types/components/PullToRefresh/PullToRefresh.d.ts +1 -1
- package/dist/types/components/PullToRefresh/PullToRefresh.types.d.ts +45 -6
- package/dist/types/components/StickyActionBar/StickyActionBar.types.d.ts +12 -0
- package/dist/types/hooks/use-breakpoint.d.ts +27 -0
- package/dist/types/hooks/use-breakpoint.test.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/test-utils/mock-viewport.d.ts +7 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PullToRefresh.js","sources":["../../../../../src/components/PullToRefresh/PullToRefresh.tsx"],"sourcesContent":["import React, { useRef, useState, useCallback } from 'react'\nimport type { PullToRefreshProps } from './PullToRefresh.types'\nimport { Loader } from '../Loader'\nimport { buildClassName } from '../../utils/build-classname'\n\nfunction PullArrowIcon({ progress }: { progress: number }) {\n const rotation = Math.min(180, progress * 180)\n return (\n <svg\n className=\"size-4 text-blue-600 dark:text-blue-400 transition-transform duration-150\"\n style={{ transform: `rotate(${rotation}deg)` }}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2.5}\n d=\"M19 14l-7 7m0 0l-7-7m7 7V3\"\n />\n </svg>\n )\n}\n\nexport function PullToRefresh({\n onRefresh,\n pullThreshold = 70,\n maxPullDistance = 120,\n disabled = false,\n refreshing: externalRefreshing,\n pullingContent,\n refreshingContent,\n children,\n className = '',\n contentClass = '',\n}: PullToRefreshProps) {\n const containerRef = useRef<HTMLDivElement>(null)\n const startYRef = useRef<number>(0)\n const isPullingRef = useRef<boolean>(false)\n\n const [pullDistance, setPullDistance] = useState<number>(0)\n const [internalRefreshing, setInternalRefreshing] = useState<boolean>(false)\n\n const isRefreshing = externalRefreshing !== undefined ? externalRefreshing : internalRefreshing\n\n const handleTouchStart = useCallback(\n (e: React.TouchEvent<HTMLDivElement>) => {\n if (disabled || isRefreshing) return\n const container = containerRef.current\n if (container && container.scrollTop === 0) {\n startYRef.current = e.touches[0].clientY\n isPullingRef.current = true\n }\n },\n [disabled, isRefreshing],\n )\n\n const handleTouchMove = useCallback(\n (e: React.TouchEvent<HTMLDivElement>) => {\n if (!isPullingRef.current || disabled || isRefreshing) return\n const container = containerRef.current\n if (!container || container.scrollTop > 0) return\n\n const currentY = e.touches[0].clientY\n const rawDistance = currentY - startYRef.current\n\n if (rawDistance > 0) {\n // Rubber-band resistance formula\n const dampened = Math.min(maxPullDistance, rawDistance * 0.45)\n setPullDistance(dampened)\n if (e.cancelable) {\n e.preventDefault()\n }\n } else {\n setPullDistance(0)\n }\n },\n [disabled, isRefreshing, maxPullDistance],\n )\n\n const handleTouchEnd = useCallback(async () => {\n if (!isPullingRef.current || disabled || isRefreshing) return\n isPullingRef.current = false\n\n if (pullDistance >= pullThreshold) {\n setPullDistance(pullThreshold)\n setInternalRefreshing(true)\n try {\n await onRefresh()\n } finally {\n setInternalRefreshing(false)\n setPullDistance(0)\n }\n } else {\n setPullDistance(0)\n }\n }, [disabled, isRefreshing, onRefresh, pullDistance, pullThreshold])\n\n const progress = Math.min(1, pullDistance / pullThreshold)\n const isReady = pullDistance >= pullThreshold\n\n return (\n <div\n ref={containerRef}\n onTouchStart={handleTouchStart}\n onTouchMove={handleTouchMove}\n onTouchEnd={handleTouchEnd}\n className={buildClassName('relative overflow-y-auto overscroll-none', className)}\n data-testid=\"pull-to-refresh-container\"\n >\n {/* Pull / Refresh Indicator Header */}\n <div\n className=\"left-0 right-0 top-0 flex items-center justify-center pointer-events-none transition-all duration-150 z-20\"\n style={{\n height: isRefreshing ? `${pullThreshold}px` : `${pullDistance}px`,\n opacity: pullDistance > 0 || isRefreshing ? 1 : 0,\n }}\n data-testid=\"pull-to-refresh-indicator\"\n >\n <div className=\"flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-white dark:bg-gray-800 border border-[var(--ui-border)] shadow-md text-xs font-semibold text-gray-700 dark:text-gray-200\">\n {isRefreshing ? (\n refreshingContent || (\n <>\n <Loader size=\"sm\" />\n <span>Refreshing...</span>\n </>\n )\n ) : pullingContent ? (\n pullingContent\n ) : (\n <>\n <PullArrowIcon progress={progress} />\n <span>{isReady ? 'Release to refresh' : 'Pull down to refresh'}</span>\n </>\n )}\n </div>\n </div>\n\n {/* Main Content */}\n <div\n className={buildClassName('transition-transform duration-150', contentClass)}\n style={{\n transform: isRefreshing\n ? `translateY(${pullThreshold}px)`\n : pullDistance > 0\n ? `translateY(${pullDistance}px)`\n : 'translateY(0px)',\n }}\n data-testid=\"pull-to-refresh-content\"\n >\n {children}\n </div>\n </div>\n )\n}\n\nPullToRefresh.displayName = 'PullToRefresh'\n"],"names":["_jsx","useRef","useState","useCallback","_jsxs","buildClassName","Loader","_Fragment"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PullToRefresh.js","sources":["../../../../../src/components/PullToRefresh/PullToRefresh.tsx"],"sourcesContent":["import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport type { PullToRefreshProps } from './PullToRefresh.types'\nimport { Loader } from '../Loader'\nimport { buildClassName } from '../../utils/build-classname'\n\n// Resistance applied to the finger's travel, so the pull feels elastic.\nconst RESISTANCE = 0.45\n// Finger travel (px) before deciding whether a gesture is a pull or a sideways swipe.\nconst AXIS_LOCK_SLOP = 8\n// Overlay badge size and the gap it rests at below the container's top edge.\nconst BADGE_SIZE = 40\nconst BADGE_TOP_GAP = 10\n\nfunction PullArrowIcon({ progress, className }: { progress: number; className?: string }) {\n return (\n <svg\n className={className}\n style={{ transform: `rotate(${Math.min(180, progress * 180)}deg)` }}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2.5}\n d=\"M19 14l-7 7m0 0l-7-7m7 7V3\"\n />\n </svg>\n )\n}\n\ntype Axis = 'pending' | 'vertical' | 'horizontal'\n\nexport function PullToRefresh({\n onRefresh,\n onError,\n variant = 'push',\n pullThreshold = 70,\n maxPullDistance = 120,\n disabled = false,\n refreshing: externalRefreshing,\n pullingContent,\n refreshingContent,\n pullLabel = 'Pull down to refresh',\n releaseLabel = 'Release to refresh',\n refreshingLabel = 'Refreshing...',\n refreshedLabel = 'Content refreshed',\n children,\n className = '',\n contentClass = '',\n}: PullToRefreshProps) {\n const containerRef = useRef<HTMLDivElement>(null)\n const [pullDistance, setPullDistance] = useState(0)\n const [isDragging, setIsDragging] = useState(false)\n const [internalRefreshing, setInternalRefreshing] = useState(false)\n const [announcement, setAnnouncement] = useState('')\n\n const isRefreshing = externalRefreshing !== undefined ? externalRefreshing : internalRefreshing\n\n // The touch listeners are attached once, natively (see below), so they read\n // the latest props and state through this ref instead of stale closures.\n const latest = useRef({\n disabled,\n isRefreshing,\n pullThreshold,\n maxPullDistance,\n onRefresh,\n onError,\n refreshedLabel,\n })\n useLayoutEffect(() => {\n latest.current = {\n disabled,\n isRefreshing,\n pullThreshold,\n maxPullDistance,\n onRefresh,\n onError,\n refreshedLabel,\n }\n })\n\n useEffect(() => {\n const container = containerRef.current\n if (!container) return\n\n let mounted = true\n let pulling = false\n let axis: Axis = 'pending'\n let startX = 0\n let startY = 0\n let distance = 0\n\n const setDistance = (next: number) => {\n distance = next\n setPullDistance(next)\n }\n\n // Ends a gesture without refreshing: a cancelled touch, a sideways swipe,\n // or content that scrolled away from the top.\n const reset = () => {\n pulling = false\n setIsDragging(false)\n setDistance(0)\n }\n\n const refresh = async () => {\n const { pullThreshold: threshold, onRefresh: run, onError: report } = latest.current\n setDistance(threshold)\n setInternalRefreshing(true)\n try {\n await run()\n if (mounted) setAnnouncement(latest.current.refreshedLabel)\n } catch (error) {\n report?.(error)\n } finally {\n if (mounted) {\n setInternalRefreshing(false)\n setDistance(0)\n }\n }\n }\n\n const handleTouchStart = (e: TouchEvent) => {\n const { disabled: isDisabled, isRefreshing: busy } = latest.current\n if (isDisabled || busy || container.scrollTop > 0 || e.touches.length !== 1) return\n startX = e.touches[0].clientX ?? 0\n startY = e.touches[0].clientY ?? 0\n axis = 'pending'\n pulling = true\n setAnnouncement('')\n setIsDragging(true)\n setDistance(0)\n }\n\n const handleTouchMove = (e: TouchEvent) => {\n if (!pulling) return\n if (latest.current.disabled || latest.current.isRefreshing || container.scrollTop > 0) {\n reset()\n return\n }\n\n const dx = (e.touches[0]?.clientX ?? 0) - startX\n const dy = (e.touches[0]?.clientY ?? 0) - startY\n\n if (axis === 'pending') {\n if (Math.abs(dx) < AXIS_LOCK_SLOP && Math.abs(dy) < AXIS_LOCK_SLOP) return\n axis = dy > 0 && Math.abs(dy) > Math.abs(dx) ? 'vertical' : 'horizontal'\n // A sideways swipe or an upward scroll belongs to the content, not to us.\n if (axis === 'horizontal') {\n reset()\n return\n }\n }\n\n const next = Math.min(latest.current.maxPullDistance, Math.max(0, dy) * RESISTANCE)\n setDistance(next)\n // Needs the non-passive listener: stops the browser's own pull-to-refresh/overscroll.\n if (next > 0 && e.cancelable) e.preventDefault()\n }\n\n const handleTouchEnd = () => {\n if (!pulling) return\n pulling = false\n setIsDragging(false)\n if (distance >= latest.current.pullThreshold) void refresh()\n else setDistance(0)\n }\n\n // React registers touch listeners as passive, where preventDefault() is\n // ignored, so these are attached natively with touchmove non-passive.\n container.addEventListener('touchstart', handleTouchStart, { passive: true })\n container.addEventListener('touchmove', handleTouchMove, { passive: false })\n container.addEventListener('touchend', handleTouchEnd)\n container.addEventListener('touchcancel', reset)\n\n return () => {\n mounted = false\n container.removeEventListener('touchstart', handleTouchStart)\n container.removeEventListener('touchmove', handleTouchMove)\n container.removeEventListener('touchend', handleTouchEnd)\n container.removeEventListener('touchcancel', reset)\n }\n }, [])\n\n const progress = Math.min(1, pullDistance / pullThreshold)\n const isReady = pullDistance >= pullThreshold\n const offset = isRefreshing ? pullThreshold : pullDistance\n // Follow the finger exactly while dragging; animate only the settle afterwards.\n const motionClass = isDragging\n ? 'transition-none'\n : 'transition-transform duration-200 ease-out motion-reduce:transition-none'\n\n return (\n <div\n ref={containerRef}\n className={buildClassName('relative overflow-y-auto overscroll-none', className)}\n data-testid=\"pull-to-refresh-container\"\n >\n <span role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {isRefreshing ? refreshingLabel : announcement}\n </span>\n\n {variant === 'overlay' ? (\n <div\n className={buildClassName(\n 'pointer-events-none absolute inset-x-0 top-0 z-20 flex justify-center',\n motionClass,\n )}\n style={{\n transform: `translateY(${offset - BADGE_SIZE + BADGE_TOP_GAP}px)`,\n opacity: offset > 0 ? 1 : 0,\n }}\n aria-hidden=\"true\"\n data-testid=\"pull-to-refresh-indicator\"\n >\n <div className=\"grid size-10 place-items-center rounded-full bg-[var(--ui-primary)] text-white shadow-lg\">\n {isRefreshing\n ? (refreshingContent ?? <Loader size=\"xs\" className=\"text-white\" />)\n : (pullingContent ?? <PullArrowIcon progress={progress} className=\"size-4\" />)}\n </div>\n </div>\n ) : (\n // Absolutely positioned, so the content's own translate is the only shift.\n <div\n className=\"pointer-events-none absolute inset-x-0 top-0 z-20 flex items-center justify-center overflow-hidden\"\n style={{ height: `${offset}px`, opacity: offset > 0 ? 1 : 0 }}\n aria-hidden=\"true\"\n data-testid=\"pull-to-refresh-indicator\"\n >\n <div className=\"flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-white dark:bg-gray-800 border border-[var(--ui-border)] shadow-md text-xs font-semibold text-gray-700 dark:text-gray-200\">\n {isRefreshing\n ? (refreshingContent ?? (\n <>\n <Loader size=\"sm\" />\n <span>{refreshingLabel}</span>\n </>\n ))\n : (pullingContent ?? (\n <>\n <PullArrowIcon\n progress={progress}\n className=\"size-4 text-[var(--ui-primary)] dark:text-blue-400\"\n />\n <span>{isReady ? releaseLabel : pullLabel}</span>\n </>\n ))}\n </div>\n </div>\n )}\n\n <div\n className={buildClassName(variant === 'push' && motionClass, contentClass)}\n style={variant === 'push' ? { transform: `translateY(${offset}px)` } : undefined}\n data-testid=\"pull-to-refresh-content\"\n >\n {children}\n </div>\n </div>\n )\n}\n\nPullToRefresh.displayName = 'PullToRefresh'\n"],"names":["_jsx","useRef","useState","useLayoutEffect","useEffect","_jsxs","buildClassName","Loader","_Fragment"],"mappings":"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"}
|
|
@@ -7,14 +7,14 @@ var Drawer = require('../Drawer/Drawer.js');
|
|
|
7
7
|
|
|
8
8
|
const ChevronUpIcon = ({ className }) => (jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, "aria-hidden": "true", children: jsxRuntime.jsx("polyline", { points: "18 15 12 9 6 15" }) }));
|
|
9
9
|
const StickyActionBar = React.forwardRef((props, ref) => {
|
|
10
|
-
const { summaryContent, actionsContent, drawerContent, drawerTitle = 'Summary Details', position = 'fixed', containerMaxWidth = 'max-w-5xl', className, ...restProps } = props;
|
|
10
|
+
const { summaryContent, actionsContent, drawerContent, drawerTitle = 'Summary Details', position = 'fixed', containerMaxWidth = 'max-w-5xl', safeArea = true, bottomOffset, className, style, ...restProps } = props;
|
|
11
11
|
const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
|
|
12
12
|
const positionClass = position === 'fixed'
|
|
13
13
|
? 'fixed bottom-0 left-0 right-0 z-30'
|
|
14
14
|
: position === 'sticky'
|
|
15
15
|
? 'sticky bottom-0 z-30'
|
|
16
16
|
: 'relative z-10';
|
|
17
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { ref: ref, className: buildClassname.buildClassName(positionClass, 'border-t border-[var(--ui-border)] bg-white/90 p-3.5 backdrop-blur-md shadow-lg dark:bg-gray-900/90 transition-all', className), "data-testid": "sticky-action-bar", ...restProps, children: jsxRuntime.jsxs("div", { className: buildClassname.buildClassName('mx-auto flex items-center justify-between gap-3', containerMaxWidth), children: [summaryContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: drawerContent ? (jsxRuntime.jsxs("button", { type: "button", onClick: () => setIsDrawerOpen(true), className: "flex items-center gap-2 text-left transition-opacity hover:opacity-80 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)] rounded-lg p-1 -m-1", "aria-label": "Expand summary details", "data-testid": "summary-trigger", children: [jsxRuntime.jsx("div", { children: summaryContent }), jsxRuntime.jsx(ChevronUpIcon, { className: "size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0" })] })) : (jsxRuntime.jsx("div", { children: summaryContent })) })), actionsContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2 shrink-0 ml-auto", "data-testid": "actions-stack", children: actionsContent }))] }) }), drawerContent && (jsxRuntime.jsx(Drawer.Drawer, { isOpen: isDrawerOpen, onClose: () => setIsDrawerOpen(false), align: "bottom", size: "full", title: drawerTitle, showBackButton: true, contentClass: "px-4 pb-6 max-h-[calc(100svh-6rem)] overflow-y-auto", children: drawerContent }))] }));
|
|
17
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { ref: ref, className: buildClassname.buildClassName(positionClass, 'border-t border-[var(--ui-border)] bg-white/90 p-3.5 backdrop-blur-md shadow-lg dark:bg-gray-900/90 transition-all', safeArea && 'pb-[calc(0.875rem+env(safe-area-inset-bottom,0px))]', className), style: bottomOffset && position !== 'relative' ? { bottom: bottomOffset, ...style } : style, "data-testid": "sticky-action-bar", ...restProps, children: jsxRuntime.jsxs("div", { className: buildClassname.buildClassName('mx-auto flex items-center justify-between gap-3', containerMaxWidth), children: [summaryContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: drawerContent ? (jsxRuntime.jsxs("button", { type: "button", onClick: () => setIsDrawerOpen(true), className: "flex items-center gap-2 text-left transition-opacity hover:opacity-80 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)] rounded-lg p-1 -m-1", "aria-label": "Expand summary details", "data-testid": "summary-trigger", children: [jsxRuntime.jsx("div", { children: summaryContent }), jsxRuntime.jsx(ChevronUpIcon, { className: "size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0" })] })) : (jsxRuntime.jsx("div", { children: summaryContent })) })), actionsContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2 shrink-0 ml-auto", "data-testid": "actions-stack", children: actionsContent }))] }) }), drawerContent && (jsxRuntime.jsx(Drawer.Drawer, { isOpen: isDrawerOpen, onClose: () => setIsDrawerOpen(false), align: "bottom", size: "full", title: drawerTitle, showBackButton: true, contentClass: "px-4 pb-6 max-h-[calc(100svh-6rem)] overflow-y-auto", children: drawerContent }))] }));
|
|
18
18
|
});
|
|
19
19
|
StickyActionBar.displayName = 'StickyActionBar';
|
|
20
20
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StickyActionBar.js","sources":["../../../../../src/components/StickyActionBar/StickyActionBar.tsx"],"sourcesContent":["import { forwardRef, useState } from 'react'\nimport { buildClassName } from '../../utils/build-classname'\nimport { Drawer } from '../Drawer'\nimport type { StickyActionBarProps } from './StickyActionBar.types'\n\nconst ChevronUpIcon = ({ className }: { className?: string }) => (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n aria-hidden=\"true\"\n >\n <polyline points=\"18 15 12 9 6 15\" />\n </svg>\n)\n\nexport const StickyActionBar = forwardRef<HTMLDivElement, StickyActionBarProps>((props, ref) => {\n const {\n summaryContent,\n actionsContent,\n drawerContent,\n drawerTitle = 'Summary Details',\n position = 'fixed',\n containerMaxWidth = 'max-w-5xl',\n className,\n ...restProps\n } = props\n\n const [isDrawerOpen, setIsDrawerOpen] = useState(false)\n\n const positionClass =\n position === 'fixed'\n ? 'fixed bottom-0 left-0 right-0 z-30'\n : position === 'sticky'\n ? 'sticky bottom-0 z-30'\n : 'relative z-10'\n\n return (\n <>\n <div\n ref={ref}\n className={buildClassName(\n positionClass,\n 'border-t border-[var(--ui-border)] bg-white/90 p-3.5 backdrop-blur-md shadow-lg dark:bg-gray-900/90 transition-all',\n className,\n )}\n data-testid=\"sticky-action-bar\"\n {...restProps}\n >\n <div\n className={buildClassName(\n 'mx-auto flex items-center justify-between gap-3',\n containerMaxWidth,\n )}\n >\n {/* Summary Content Slot */}\n {summaryContent && (\n <div className=\"flex items-center gap-2\">\n {drawerContent ? (\n <button\n type=\"button\"\n onClick={() => setIsDrawerOpen(true)}\n className=\"flex items-center gap-2 text-left transition-opacity hover:opacity-80 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)] rounded-lg p-1 -m-1\"\n aria-label=\"Expand summary details\"\n data-testid=\"summary-trigger\"\n >\n <div>{summaryContent}</div>\n <ChevronUpIcon className=\"size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0\" />\n </button>\n ) : (\n <div>{summaryContent}</div>\n )}\n </div>\n )}\n\n {/* Actions Stack Slot */}\n {actionsContent && (\n <div className=\"flex items-center gap-2 shrink-0 ml-auto\" data-testid=\"actions-stack\">\n {actionsContent}\n </div>\n )}\n </div>\n </div>\n\n {/* Optional Expandable Details Drawer */}\n {drawerContent && (\n <Drawer\n isOpen={isDrawerOpen}\n onClose={() => setIsDrawerOpen(false)}\n align=\"bottom\"\n size=\"full\"\n title={drawerTitle}\n showBackButton\n contentClass=\"px-4 pb-6 max-h-[calc(100svh-6rem)] overflow-y-auto\"\n >\n {drawerContent}\n </Drawer>\n )}\n </>\n )\n})\n\nStickyActionBar.displayName = 'StickyActionBar'\n"],"names":["_jsx","forwardRef","useState","_jsxs","_Fragment","buildClassName","Drawer"],"mappings":";;;;;;;AAKA,MAAM,aAAa,GAAG,CAAC,EAAE,SAAS,EAA0B,MAC1DA,cAAA,CAAA,KAAA,EAAA,EACE,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,SAAS,EAAE,SAAS,EAAA,aAAA,EACR,MAAM,YAElBA,cAAA,CAAA,UAAA,EAAA,EAAU,MAAM,EAAC,iBAAiB,EAAA,CAAG,EAAA,CACjC,CACP;AAEM,MAAM,eAAe,GAAGC,gBAAU,CAAuC,CAAC,KAAK,EAAE,GAAG,KAAI;
|
|
1
|
+
{"version":3,"file":"StickyActionBar.js","sources":["../../../../../src/components/StickyActionBar/StickyActionBar.tsx"],"sourcesContent":["import { forwardRef, useState } from 'react'\nimport { buildClassName } from '../../utils/build-classname'\nimport { Drawer } from '../Drawer'\nimport type { StickyActionBarProps } from './StickyActionBar.types'\n\nconst ChevronUpIcon = ({ className }: { className?: string }) => (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n aria-hidden=\"true\"\n >\n <polyline points=\"18 15 12 9 6 15\" />\n </svg>\n)\n\nexport const StickyActionBar = forwardRef<HTMLDivElement, StickyActionBarProps>((props, ref) => {\n const {\n summaryContent,\n actionsContent,\n drawerContent,\n drawerTitle = 'Summary Details',\n position = 'fixed',\n containerMaxWidth = 'max-w-5xl',\n safeArea = true,\n bottomOffset,\n className,\n style,\n ...restProps\n } = props\n\n const [isDrawerOpen, setIsDrawerOpen] = useState(false)\n\n const positionClass =\n position === 'fixed'\n ? 'fixed bottom-0 left-0 right-0 z-30'\n : position === 'sticky'\n ? 'sticky bottom-0 z-30'\n : 'relative z-10'\n\n return (\n <>\n <div\n ref={ref}\n className={buildClassName(\n positionClass,\n 'border-t border-[var(--ui-border)] bg-white/90 p-3.5 backdrop-blur-md shadow-lg dark:bg-gray-900/90 transition-all',\n safeArea && 'pb-[calc(0.875rem+env(safe-area-inset-bottom,0px))]',\n className,\n )}\n style={bottomOffset && position !== 'relative' ? { bottom: bottomOffset, ...style } : style}\n data-testid=\"sticky-action-bar\"\n {...restProps}\n >\n <div\n className={buildClassName(\n 'mx-auto flex items-center justify-between gap-3',\n containerMaxWidth,\n )}\n >\n {/* Summary Content Slot */}\n {summaryContent && (\n <div className=\"flex items-center gap-2\">\n {drawerContent ? (\n <button\n type=\"button\"\n onClick={() => setIsDrawerOpen(true)}\n className=\"flex items-center gap-2 text-left transition-opacity hover:opacity-80 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)] rounded-lg p-1 -m-1\"\n aria-label=\"Expand summary details\"\n data-testid=\"summary-trigger\"\n >\n <div>{summaryContent}</div>\n <ChevronUpIcon className=\"size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0\" />\n </button>\n ) : (\n <div>{summaryContent}</div>\n )}\n </div>\n )}\n\n {/* Actions Stack Slot */}\n {actionsContent && (\n <div className=\"flex items-center gap-2 shrink-0 ml-auto\" data-testid=\"actions-stack\">\n {actionsContent}\n </div>\n )}\n </div>\n </div>\n\n {/* Optional Expandable Details Drawer */}\n {drawerContent && (\n <Drawer\n isOpen={isDrawerOpen}\n onClose={() => setIsDrawerOpen(false)}\n align=\"bottom\"\n size=\"full\"\n title={drawerTitle}\n showBackButton\n contentClass=\"px-4 pb-6 max-h-[calc(100svh-6rem)] overflow-y-auto\"\n >\n {drawerContent}\n </Drawer>\n )}\n </>\n )\n})\n\nStickyActionBar.displayName = 'StickyActionBar'\n"],"names":["_jsx","forwardRef","useState","_jsxs","_Fragment","buildClassName","Drawer"],"mappings":";;;;;;;AAKA,MAAM,aAAa,GAAG,CAAC,EAAE,SAAS,EAA0B,MAC1DA,cAAA,CAAA,KAAA,EAAA,EACE,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,SAAS,EAAE,SAAS,EAAA,aAAA,EACR,MAAM,YAElBA,cAAA,CAAA,UAAA,EAAA,EAAU,MAAM,EAAC,iBAAiB,EAAA,CAAG,EAAA,CACjC,CACP;AAEM,MAAM,eAAe,GAAGC,gBAAU,CAAuC,CAAC,KAAK,EAAE,GAAG,KAAI;AAC7F,IAAA,MAAM,EACJ,cAAc,EACd,cAAc,EACd,aAAa,EACb,WAAW,GAAG,iBAAiB,EAC/B,QAAQ,GAAG,OAAO,EAClB,iBAAiB,GAAG,WAAW,EAC/B,QAAQ,GAAG,IAAI,EACf,YAAY,EACZ,SAAS,EACT,KAAK,EACL,GAAG,SAAS,EACb,GAAG,KAAK;IAET,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAGC,cAAQ,CAAC,KAAK,CAAC;AAEvD,IAAA,MAAM,aAAa,GACjB,QAAQ,KAAK;AACX,UAAE;UACA,QAAQ,KAAK;AACb,cAAE;cACA,eAAe;AAEvB,IAAA,QACEC,eAAA,CAAAC,mBAAA,EAAA,EAAA,QAAA,EAAA,CACEJ,cAAA,CAAA,KAAA,EAAA,EACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAEK,6BAAc,CACvB,aAAa,EACb,oHAAoH,EACpH,QAAQ,IAAI,qDAAqD,EACjE,SAAS,CACV,EACD,KAAK,EAAE,YAAY,IAAI,QAAQ,KAAK,UAAU,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAA,aAAA,EAC/E,mBAAmB,EAAA,GAC3B,SAAS,YAEbF,eAAA,CAAA,KAAA,EAAA,EACE,SAAS,EAAEE,6BAAc,CACvB,iDAAiD,EACjD,iBAAiB,CAClB,EAAA,QAAA,EAAA,CAGA,cAAc,KACbL,cAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,yBAAyB,EAAA,QAAA,EACrC,aAAa,IACZG,eAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,MAAM,eAAe,CAAC,IAAI,CAAC,EACpC,SAAS,EAAC,8LAA8L,EAAA,YAAA,EAC7L,wBAAwB,EAAA,aAAA,EACvB,iBAAiB,aAE7BH,cAAA,CAAA,KAAA,EAAA,EAAA,QAAA,EAAM,cAAc,GAAO,EAC3BA,cAAA,CAAC,aAAa,EAAA,EAAC,SAAS,EAAC,6DAA6D,GAAG,CAAA,EAAA,CAClF,KAETA,cAAA,CAAA,KAAA,EAAA,EAAA,QAAA,EAAM,cAAc,EAAA,CAAO,CAC5B,GACG,CACP,EAGA,cAAc,KACbA,wBAAK,SAAS,EAAC,0CAA0C,EAAA,aAAA,EAAa,eAAe,YAClF,cAAc,EAAA,CACX,CACP,CAAA,EAAA,CACG,EAAA,CACF,EAGL,aAAa,KACZA,cAAA,CAACM,aAAM,EAAA,EACL,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,MAAM,eAAe,CAAC,KAAK,CAAC,EACrC,KAAK,EAAC,QAAQ,EACd,IAAI,EAAC,MAAM,EACX,KAAK,EAAE,WAAW,EAClB,cAAc,QACd,YAAY,EAAC,qDAAqD,EAAA,QAAA,EAEjE,aAAa,GACP,CACV,CAAA,EAAA,CACA;AAEP,CAAC;AAED,eAAe,CAAC,WAAW,GAAG,iBAAiB;;;;"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
|
|
5
|
+
const BREAKPOINT_QUERIES = {
|
|
6
|
+
medium: '(min-width: 768px)',
|
|
7
|
+
expanded: '(min-width: 1024px)',
|
|
8
|
+
};
|
|
9
|
+
// jsdom and some embedded webviews have no matchMedia; treat every query as
|
|
10
|
+
// unmatched there (the compact layout) rather than throwing.
|
|
11
|
+
const hasMatchMedia = () => typeof window !== 'undefined' && typeof window.matchMedia === 'function';
|
|
12
|
+
/** Live result of a CSS media query. `false` during server rendering or without `matchMedia`. */
|
|
13
|
+
const useMediaQuery = (query) => {
|
|
14
|
+
const subscribe = React.useCallback((onChange) => {
|
|
15
|
+
if (!hasMatchMedia())
|
|
16
|
+
return () => { };
|
|
17
|
+
const list = window.matchMedia(query);
|
|
18
|
+
list.addEventListener('change', onChange);
|
|
19
|
+
return () => list.removeEventListener('change', onChange);
|
|
20
|
+
}, [query]);
|
|
21
|
+
return React.useSyncExternalStore(subscribe, () => hasMatchMedia() && window.matchMedia(query).matches, () => false);
|
|
22
|
+
};
|
|
23
|
+
/** Current layout range; `compact` during server rendering. */
|
|
24
|
+
const useBreakpoint = () => {
|
|
25
|
+
const isMedium = useMediaQuery(BREAKPOINT_QUERIES.medium);
|
|
26
|
+
const isExpanded = useMediaQuery(BREAKPOINT_QUERIES.expanded);
|
|
27
|
+
if (isExpanded)
|
|
28
|
+
return 'expanded';
|
|
29
|
+
return isMedium ? 'medium' : 'compact';
|
|
30
|
+
};
|
|
31
|
+
/** Resolves a `ResponsiveValue` for the current viewport. */
|
|
32
|
+
const useResponsiveValue = (value) => {
|
|
33
|
+
var _a, _b, _c;
|
|
34
|
+
const breakpoint = useBreakpoint();
|
|
35
|
+
if (typeof value !== 'object' || value === null || !('base' in value))
|
|
36
|
+
return value;
|
|
37
|
+
if (breakpoint === 'expanded')
|
|
38
|
+
return (_b = (_a = value.lg) !== null && _a !== void 0 ? _a : value.md) !== null && _b !== void 0 ? _b : value.base;
|
|
39
|
+
if (breakpoint === 'medium')
|
|
40
|
+
return (_c = value.md) !== null && _c !== void 0 ? _c : value.base;
|
|
41
|
+
return value.base;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
exports.BREAKPOINT_QUERIES = BREAKPOINT_QUERIES;
|
|
45
|
+
exports.useBreakpoint = useBreakpoint;
|
|
46
|
+
exports.useMediaQuery = useMediaQuery;
|
|
47
|
+
exports.useResponsiveValue = useResponsiveValue;
|
|
48
|
+
//# sourceMappingURL=use-breakpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-breakpoint.js","sources":["../../../../src/hooks/use-breakpoint.tsx"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react'\n\n/**\n * Layout ranges matching Tailwind's `md` (768px) and `lg` (1024px) tokens, so a\n * JS decision and an `md:`/`lg:` class always agree:\n * - `compact`: phones\n * - `medium`: tablets and narrow windows\n * - `expanded`: desktop\n *\n * Width only. Unlike `useIsMobile`, touch capability is never considered, so a\n * phone in landscape and a narrow desktop window get the layout for their size.\n */\nexport type Breakpoint = 'compact' | 'medium' | 'expanded'\n\nexport const BREAKPOINT_QUERIES = {\n medium: '(min-width: 768px)',\n expanded: '(min-width: 1024px)',\n} as const\n\n// jsdom and some embedded webviews have no matchMedia; treat every query as\n// unmatched there (the compact layout) rather than throwing.\nconst hasMatchMedia = () => typeof window !== 'undefined' && typeof window.matchMedia === 'function'\n\n/** Live result of a CSS media query. `false` during server rendering or without `matchMedia`. */\nexport const useMediaQuery = (query: string): boolean => {\n const subscribe = useCallback(\n (onChange: () => void) => {\n if (!hasMatchMedia()) return () => {}\n const list = window.matchMedia(query)\n list.addEventListener('change', onChange)\n return () => list.removeEventListener('change', onChange)\n },\n [query],\n )\n\n return useSyncExternalStore(\n subscribe,\n () => hasMatchMedia() && window.matchMedia(query).matches,\n () => false,\n )\n}\n\n/** Current layout range; `compact` during server rendering. */\nexport const useBreakpoint = (): Breakpoint => {\n const isMedium = useMediaQuery(BREAKPOINT_QUERIES.medium)\n const isExpanded = useMediaQuery(BREAKPOINT_QUERIES.expanded)\n if (isExpanded) return 'expanded'\n return isMedium ? 'medium' : 'compact'\n}\n\n/** A value per breakpoint; missing ranges fall back to the next smaller one. */\nexport type ResponsiveValue<T> = T | { base: T; md?: T; lg?: T }\n\n/** Resolves a `ResponsiveValue` for the current viewport. */\nexport const useResponsiveValue = <T,>(value: ResponsiveValue<T>): T => {\n const breakpoint = useBreakpoint()\n if (typeof value !== 'object' || value === null || !('base' in value)) return value as T\n if (breakpoint === 'expanded') return value.lg ?? value.md ?? value.base\n if (breakpoint === 'medium') return value.md ?? value.base\n return value.base\n}\n"],"names":["useCallback","useSyncExternalStore"],"mappings":";;;;AAcO,MAAM,kBAAkB,GAAG;AAChC,IAAA,MAAM,EAAE,oBAAoB;AAC5B,IAAA,QAAQ,EAAE,qBAAqB;;AAGjC;AACA;AACA,MAAM,aAAa,GAAG,MAAM,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU;AAEpG;AACO,MAAM,aAAa,GAAG,CAAC,KAAa,KAAa;AACtD,IAAA,MAAM,SAAS,GAAGA,iBAAW,CAC3B,CAAC,QAAoB,KAAI;QACvB,IAAI,CAAC,aAAa,EAAE;AAAE,YAAA,OAAO,MAAK,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC;AACrC,QAAA,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC;QACzC,OAAO,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC;AAC3D,IAAA,CAAC,EACD,CAAC,KAAK,CAAC,CACR;IAED,OAAOC,0BAAoB,CACzB,SAAS,EACT,MAAM,aAAa,EAAE,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,OAAO,EACzD,MAAM,KAAK,CACZ;AACH;AAEA;AACO,MAAM,aAAa,GAAG,MAAiB;IAC5C,MAAM,QAAQ,GAAG,aAAa,CAAC,kBAAkB,CAAC,MAAM,CAAC;IACzD,MAAM,UAAU,GAAG,aAAa,CAAC,kBAAkB,CAAC,QAAQ,CAAC;AAC7D,IAAA,IAAI,UAAU;AAAE,QAAA,OAAO,UAAU;IACjC,OAAO,QAAQ,GAAG,QAAQ,GAAG,SAAS;AACxC;AAKA;AACO,MAAM,kBAAkB,GAAG,CAAK,KAAyB,KAAO;;AACrE,IAAA,MAAM,UAAU,GAAG,aAAa,EAAE;AAClC,IAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,EAAE,MAAM,IAAI,KAAK,CAAC;AAAE,QAAA,OAAO,KAAU;IACxF,IAAI,UAAU,KAAK,UAAU;AAAE,QAAA,OAAO,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,KAAK,CAAC,EAAE,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,KAAK,CAAC,EAAE,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,KAAK,CAAC,IAAI;IACxE,IAAI,UAAU,KAAK,QAAQ;QAAE,OAAO,CAAA,EAAA,GAAA,KAAK,CAAC,EAAE,mCAAI,KAAK,CAAC,IAAI;IAC1D,OAAO,KAAK,CAAC,IAAI;AACnB;;;;;;;"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -58,6 +58,7 @@ var Textarea = require('./components/Textarea/Textarea.js');
|
|
|
58
58
|
var ToastProvider = require('./components/Toast/ToastProvider.js');
|
|
59
59
|
var Toast = require('./components/Toast/Toast.js');
|
|
60
60
|
var VirtualizedDataTable = require('./components/VirtualizedDataTable/VirtualizedDataTable.js');
|
|
61
|
+
var useBreakpoint = require('./hooks/use-breakpoint.js');
|
|
61
62
|
var useDebounce = require('./hooks/use-debounce.js');
|
|
62
63
|
var useIsMobile = require('./hooks/use-is-mobile.js');
|
|
63
64
|
var useIsTablet = require('./hooks/use-is-tablet.js');
|
|
@@ -151,6 +152,10 @@ exports.ToastProvider = ToastProvider.ToastProvider;
|
|
|
151
152
|
exports.useToast = ToastProvider.useToast;
|
|
152
153
|
exports.Toast = Toast.Toast;
|
|
153
154
|
exports.VirtualizedDataTable = VirtualizedDataTable.VirtualizedDataTable;
|
|
155
|
+
exports.BREAKPOINT_QUERIES = useBreakpoint.BREAKPOINT_QUERIES;
|
|
156
|
+
exports.useBreakpoint = useBreakpoint.useBreakpoint;
|
|
157
|
+
exports.useMediaQuery = useBreakpoint.useMediaQuery;
|
|
158
|
+
exports.useResponsiveValue = useBreakpoint.useResponsiveValue;
|
|
154
159
|
exports.useDebounce = useDebounce.useDebounce;
|
|
155
160
|
exports.useDebounceFn = useDebounce.useDebounceFn;
|
|
156
161
|
exports.useIsMobile = useIsMobile.useIsMobile;
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,20 +1,23 @@
|
|
|
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 = '', }) {
|
|
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, }) {
|
|
5
5
|
const handleTabClick = (item) => {
|
|
6
6
|
var _a;
|
|
7
7
|
if (item.disabled)
|
|
8
8
|
return;
|
|
9
9
|
(_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item);
|
|
10
|
-
onChange(item.id);
|
|
10
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(item.id);
|
|
11
11
|
};
|
|
12
|
+
// Removed from the tree, not just covered, so hidden tabs can't take focus.
|
|
13
|
+
if (hidden)
|
|
14
|
+
return null;
|
|
12
15
|
const themeStyles = {
|
|
13
16
|
light: 'bg-white dark:bg-gray-900 border-t border-[var(--ui-border)] text-gray-600 dark:text-gray-400',
|
|
14
17
|
dark: 'bg-gray-900 text-gray-400 border-t border-gray-800',
|
|
15
18
|
primary: 'bg-blue-600 text-blue-100 border-t border-blue-700',
|
|
16
19
|
}[theme];
|
|
17
|
-
return (jsx("nav", { "aria-label":
|
|
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) => {
|
|
18
21
|
const isActive = item.id === activeId;
|
|
19
22
|
const showLabelText = showLabels === 'always' || (showLabels === 'active' && isActive);
|
|
20
23
|
const activeItemColor = {
|
|
@@ -37,11 +40,17 @@ function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels
|
|
|
37
40
|
info: 'bg-blue-500 text-white',
|
|
38
41
|
success: 'bg-emerald-500 text-white',
|
|
39
42
|
}[item.badgeVariant || 'danger'];
|
|
40
|
-
|
|
43
|
+
// A disabled item stays a button: a link can't be disabled.
|
|
44
|
+
const isLink = Boolean(item.href) && !item.disabled;
|
|
45
|
+
const ItemElement = isLink ? linkAs : 'button';
|
|
46
|
+
const elementProps = isLink
|
|
47
|
+
? { [linkHrefProp]: item.href }
|
|
48
|
+
: { type: 'button', disabled: item.disabled };
|
|
49
|
+
return (jsxs(ItemElement, { ...elementProps, onClick: () => handleTabClick(item), className: buildClassName('flex-1 flex flex-col items-center justify-center pt-2 pb-1.5 px-1 transition-all duration-200 relative cursor-pointer group', item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none', isActive ? activeItemColor : inactiveItemColor, itemClass), "data-testid": `bottom-nav-item-${item.id}`, "aria-current": isActive ? 'page' : undefined, "aria-label": !showLabelText && typeof item.label === 'string' ? item.label : undefined, children: [activeIndicatorStyle === 'line' && isActive && (jsx("span", { className: buildClassName('absolute -top-[1px] h-0.75 w-9 rounded-full transition-all duration-200 z-10', theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400') })), jsxs("div", { className: buildClassName('relative inline-flex items-center justify-center transition-all duration-200', activeIndicatorStyle === 'pill'
|
|
41
50
|
? buildClassName('px-4 py-1 rounded-full', isActive
|
|
42
51
|
? pillActiveStyles
|
|
43
52
|
: 'group-hover:bg-gray-100/50 dark:group-hover:bg-gray-800/40')
|
|
44
|
-
: 'px-1 py-0.5'), children: [jsx("span", { className: "text-xl transition-transform duration-150 group-active:scale-95", children: isActive && item.activeIcon ? item.activeIcon : item.icon }), item.badge !== undefined && item.badge !== null && (jsx("span", { className: buildClassName('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', badgeColor), "data-testid": `bottom-nav-badge-${item.id}`, children: item.badge }))] }), showLabelText && (jsx("span", { className: buildClassName('text-[11px] leading-tight mt-1 truncate max-w-full transition-colors duration-150', isActive ? 'font-bold' : 'font-medium opacity-90'), children: item.label })), activeIndicatorStyle === 'dot' && isActive && (jsx("span", { className: buildClassName('size-1.5 rounded-full mt-1 transition-all duration-200', theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400') }))] }, item.id));
|
|
53
|
+
: 'px-1 py-0.5'), children: [jsx("span", { className: "text-xl transition-transform duration-150 group-active:scale-95", "aria-hidden": "true", children: isActive && item.activeIcon ? item.activeIcon : item.icon }), item.badge !== undefined && item.badge !== null && (jsx("span", { className: buildClassName('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', badgeColor), "data-testid": `bottom-nav-badge-${item.id}`, children: item.badge }))] }), showLabelText && (jsx("span", { className: buildClassName('text-[11px] leading-tight mt-1 truncate max-w-full transition-colors duration-150', isActive ? 'font-bold' : 'font-medium opacity-90'), children: item.label })), activeIndicatorStyle === 'dot' && isActive && (jsx("span", { className: buildClassName('size-1.5 rounded-full mt-1 transition-all duration-200', theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400') }))] }, item.id));
|
|
45
54
|
}) }));
|
|
46
55
|
}
|
|
47
56
|
BottomNavigation.displayName = 'BottomNavigation';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BottomNavigation.js","sources":["../../../../../src/components/BottomNavigation/BottomNavigation.tsx"],"sourcesContent":["import React 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}: BottomNavigationProps) {\n const handleTabClick = (item: BottomNavigationItem) => {\n if (item.disabled) return\n item.onClick?.()\n onChange(item.id)\n }\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=\"Bottom Navigation\"\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 return (\n <button\n key={item.id}\n type=\"button\"\n disabled={item.disabled}\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-selected={isActive}\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 className=\"text-xl transition-transform duration-150 group-active:scale-95\">\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 </button>\n )\n })}\n </nav>\n )\n}\n\nBottomNavigation.displayName = 'BottomNavigation'\n"],"names":["_jsx","_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}: 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":"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"}
|
|
@@ -5,6 +5,7 @@ import { buildClassName } from '../../utils/build-classname.js';
|
|
|
5
5
|
import { Button } from '../Button/Button.js';
|
|
6
6
|
import SvgChevronLeft from '../Icons/ChevronLeft.svg.js';
|
|
7
7
|
import SvgX from '../Icons/X.svg.js';
|
|
8
|
+
import { useResponsiveValue } from '../../hooks/use-breakpoint.js';
|
|
8
9
|
|
|
9
10
|
const alignmentMap = {
|
|
10
11
|
center: 'items-center justify-center h-full',
|
|
@@ -18,7 +19,8 @@ const panelAlignmentMap = {
|
|
|
18
19
|
start: 'data-[closed]:-translate-x-1/2 rounded-r-2xl',
|
|
19
20
|
end: 'data-[closed]:translate-x-1/2 rounded-l-2xl',
|
|
20
21
|
top: 'data-[closed]:-translate-y-1/2 rounded-b-2xl',
|
|
21
|
-
|
|
22
|
+
// Bottom sheets clear the home indicator on notched phones (0 elsewhere).
|
|
23
|
+
bottom: '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)]',
|
|
22
24
|
};
|
|
23
25
|
const panelSizeMap = {
|
|
24
26
|
xs: 'max-w-xs',
|
|
@@ -29,7 +31,9 @@ const panelSizeMap = {
|
|
|
29
31
|
'2xl': 'max-w-2xl',
|
|
30
32
|
full: 'w-full',
|
|
31
33
|
};
|
|
32
|
-
const Drawer = ({ isOpen, onClose = () => { }, closeOnOutsideClick = true, align = 'end', size = 'md', backdrop = true, title, description, children, footer, footerSticky = false, panelClass = '', contentClass = '', titleClass = '', descriptionClass = '', titleSticky = false, showBackButton = false, showCloseButton = true, dragHandle, }) => {
|
|
34
|
+
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, }) => {
|
|
35
|
+
const align = useResponsiveValue(alignProp);
|
|
36
|
+
const size = useResponsiveValue(sizeProp);
|
|
33
37
|
const showTitleSection = Boolean(title || description || showBackButton || showCloseButton);
|
|
34
38
|
const shouldShowDragHandle = dragHandle !== null && dragHandle !== void 0 ? dragHandle : align === 'bottom';
|
|
35
39
|
const [viewportHeight, setViewportHeight] = React__default.useState(null);
|
|
@@ -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 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:\n 'data-[closed]:translate-y-full rounded-t-2xl md:rounded-t-3xl max-h-[88vh] max-h-[88dvh]',\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 = 'end',\n size = '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 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":["React","_jsxs","_jsx","ChevronLeftIcon","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 { 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":["React","_jsxs","_jsx","ChevronLeftIcon","XIcon"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { buildClassName } from '../../utils/build-classname.js';
|
|
4
|
+
import { ActionSheet } from '../ActionSheet/ActionSheet.js';
|
|
4
5
|
|
|
5
6
|
function ChevronLeftIcon() {
|
|
6
7
|
return (jsx("svg", { className: "size-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M15 19l-7-7 7-7" }) }));
|
|
@@ -8,12 +9,23 @@ function ChevronLeftIcon() {
|
|
|
8
9
|
function SearchIcon() {
|
|
9
10
|
return (jsx("svg", { className: "size-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }));
|
|
10
11
|
}
|
|
12
|
+
function MoreIcon() {
|
|
13
|
+
return (jsxs("svg", { className: "size-5", fill: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: [jsx("circle", { cx: "12", cy: "5", r: "1.75" }), jsx("circle", { cx: "12", cy: "12", r: "1.75" }), jsx("circle", { cx: "12", cy: "19", r: "1.75" })] }));
|
|
14
|
+
}
|
|
11
15
|
function ClearIcon() {
|
|
12
16
|
return (jsx("svg", { className: "size-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }));
|
|
13
17
|
}
|
|
14
|
-
function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabel = 'Back', leading, actions, searchable = false, searchValue: externalSearchValue, onSearchChange, searchPlaceholder = 'Search...', sticky = true, transparent = false, bottomSlot, className = '', }) {
|
|
18
|
+
function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabel = 'Back', leading, actions, maxVisibleActions, overflowLabel = 'More actions', trailing, searchable = false, searchValue: externalSearchValue, onSearchChange, searchPlaceholder = 'Search...', sticky = true, transparent = false, bottomSlot, className = '', }) {
|
|
15
19
|
const [isSearchActive, setIsSearchActive] = useState(false);
|
|
16
20
|
const [internalSearchValue, setInternalSearchValue] = useState('');
|
|
21
|
+
const [isOverflowOpen, setIsOverflowOpen] = useState(false);
|
|
22
|
+
// The overflow button takes one of the visible slots.
|
|
23
|
+
const allActions = actions !== null && actions !== void 0 ? actions : [];
|
|
24
|
+
const shouldOverflow = maxVisibleActions !== undefined && allActions.length > maxVisibleActions;
|
|
25
|
+
const visibleActions = shouldOverflow
|
|
26
|
+
? allActions.slice(0, Math.max(maxVisibleActions - 1, 0))
|
|
27
|
+
: allActions;
|
|
28
|
+
const overflowActions = shouldOverflow ? allActions.slice(visibleActions.length) : [];
|
|
17
29
|
const searchValue = externalSearchValue !== undefined ? externalSearchValue : internalSearchValue;
|
|
18
30
|
const handleSearchInput = (e) => {
|
|
19
31
|
const val = e.target.value;
|
|
@@ -28,14 +40,20 @@ function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabe
|
|
|
28
40
|
? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-[var(--ui-border-muted)]'
|
|
29
41
|
: '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: [jsx("div", { className: "h-14 px-3.5 flex items-center justify-between gap-3 relative", children: searchable && isSearchActive ? (jsxs("div", { className: "flex-1 flex items-center gap-2", "data-testid": "mobile-header-search-container", children: [jsxs("div", { className: "relative flex-1 flex items-center", children: [jsx("span", { className: "absolute left-3 pointer-events-none", children: jsx(SearchIcon, {}) }), jsx("input", { type: "text", value: searchValue, onChange: handleSearchInput, placeholder: searchPlaceholder, autoFocus: true, 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", "data-testid": "mobile-header-search-input" }), searchValue && (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: jsx(ClearIcon, {}) }))] }), 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" })] })) : (jsxs(Fragment, { children: [jsx("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 z-10", children: onBack ? (jsxs("button", { type: "button", onClick: onBack, "aria-label": backLabel, className: "flex items-center gap-0.5 text-blue-600 dark:text-blue-400 hover:text-blue-700 p-1 -ml-1 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-back-btn", children: [jsx(ChevronLeftIcon, {}), jsx("span", { className: "text-xs font-semibold hidden sm:inline", children: backLabel })] })) : (leading) }), jsxs("div", { className: buildClassName('flex-1 min-w-0 px-2', titleAlign === 'center'
|
|
30
42
|
? 'absolute inset-x-12 text-center pointer-events-none flex flex-col items-center justify-center'
|
|
31
|
-
: 'text-left'), children: [title && (jsx("div", { className: "text-sm font-bold text-gray-900 dark:text-white truncate pointer-events-auto", "data-testid": "mobile-header-title", children: title })), subtitle && (jsx("div", { className: "text-[11px] text-gray-500 dark:text-gray-400 truncate pointer-events-auto", "data-testid": "mobile-header-subtitle", children: subtitle }))] }), jsxs("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10", children: [searchable && (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: jsx(SearchIcon, {}) })),
|
|
43
|
+
: 'text-left'), children: [title && (jsx("div", { className: "text-sm font-bold text-gray-900 dark:text-white truncate pointer-events-auto", "data-testid": "mobile-header-title", children: title })), subtitle && (jsx("div", { className: "text-[11px] text-gray-500 dark:text-gray-400 truncate pointer-events-auto", "data-testid": "mobile-header-subtitle", children: subtitle }))] }), jsxs("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10", children: [searchable && (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: jsx(SearchIcon, {}) })), visibleActions.map((action) => {
|
|
32
44
|
const badgeColor = {
|
|
33
45
|
danger: 'bg-red-500 text-white',
|
|
34
46
|
info: 'bg-blue-500 text-white',
|
|
35
47
|
success: 'bg-emerald-500 text-white',
|
|
36
48
|
}[action.badgeVariant || 'danger'];
|
|
37
49
|
return (jsxs("button", { type: "button", disabled: action.disabled, onClick: action.onClick, "aria-label": action.label, className: buildClassName('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', action.disabled && 'opacity-40 cursor-not-allowed pointer-events-none'), "data-testid": `mobile-header-action-${action.id}`, children: [jsx("span", { className: "text-base", children: action.icon }), action.badge !== undefined && action.badge !== null && (jsx("span", { className: buildClassName('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', badgeColor), "data-testid": `mobile-header-badge-${action.id}`, children: action.badge }))] }, action.id));
|
|
38
|
-
})
|
|
50
|
+
}), overflowActions.length > 0 && (jsx("button", { type: "button", onClick: () => setIsOverflowOpen(true), "aria-label": overflowLabel, "aria-haspopup": "dialog", "aria-expanded": isOverflowOpen, 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-overflow", children: jsx(MoreIcon, {}) })), trailing] })] })) }), overflowActions.length > 0 && (jsx(ActionSheet, { isOpen: isOverflowOpen, onClose: () => setIsOverflowOpen(false), actions: overflowActions.map((action) => ({
|
|
51
|
+
id: action.id,
|
|
52
|
+
label: action.label,
|
|
53
|
+
icon: action.icon,
|
|
54
|
+
disabled: action.disabled,
|
|
55
|
+
onClick: action.onClick,
|
|
56
|
+
})) })), bottomSlot && (jsx("div", { className: "px-3 py-2 border-t border-[var(--ui-border-muted)]", "data-testid": "mobile-header-bottom-slot", children: bottomSlot }))] }));
|
|
39
57
|
}
|
|
40
58
|
MobileHeader.displayName = 'MobileHeader';
|
|
41
59
|
|
|
@@ -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'\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 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 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\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-3.5 flex items-center justify-between gap-3 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 */}\n <div className=\"flex items-center gap-1 min-w-[40px] shrink-0 z-10\">\n {onBack ? (\n <button\n type=\"button\"\n onClick={onBack}\n aria-label={backLabel}\n className=\"flex items-center gap-0.5 text-blue-600 dark:text-blue-400 hover:text-blue-700 p-1 -ml-1 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 {/* Title & Subtitle Section */}\n <div\n className={buildClassName(\n 'flex-1 min-w-0 px-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 {actions?.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 </div>\n </>\n )}\n </div>\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","_Fragment"],"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\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-3.5 flex items-center justify-between gap-3 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 */}\n <div className=\"flex items-center gap-1 min-w-[40px] shrink-0 z-10\">\n {onBack ? (\n <button\n type=\"button\"\n onClick={onBack}\n aria-label={backLabel}\n className=\"flex items-center gap-0.5 text-blue-600 dark:text-blue-400 hover:text-blue-700 p-1 -ml-1 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 {/* Title & Subtitle Section */}\n <div\n className={buildClassName(\n 'flex-1 min-w-0 px-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","_Fragment"],"mappings":"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"}
|