@medalsocial/meda 2.5.2 → 2.5.3
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/shell/icon-rail.js +39 -11
- package/package.json +1 -1
package/dist/shell/icon-rail.js
CHANGED
|
@@ -1,48 +1,76 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
|
4
|
-
import { Fragment, useState } from 'react';
|
|
4
|
+
import { Fragment, useEffect, useState } from 'react';
|
|
5
5
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../components/ui/tooltip.js';
|
|
6
6
|
import { cn } from '../lib/utils.js';
|
|
7
7
|
import { useShellViewport } from './use-shell-viewport.js';
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
// Item styling
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
|
+
// Short-viewport scaling: below 850px viewport height items compact (smaller
|
|
12
|
+
// icon frame, tighter spacing, smaller label); below 700px labels hide so the
|
|
13
|
+
// rail degrades to icon-only. The rail additionally scrolls (see railClass) as
|
|
14
|
+
// the hard guarantee that every item stays reachable on any height.
|
|
11
15
|
function itemClass(isActive, labelVisibility) {
|
|
12
16
|
if (labelVisibility === 'visible') {
|
|
13
|
-
return cn('group relative flex min-h-[4rem] w-full flex-col items-center justify-start gap-1 px-1 py-1 text-center transition-colors', isActive ? 'text-foreground' : 'text-muted-foreground hover:text-foreground');
|
|
17
|
+
return cn('group relative flex min-h-[4rem] w-full shrink-0 flex-col items-center justify-start gap-1 px-1 py-1 text-center transition-colors', '[@media(max-height:850px)]:min-h-[3.25rem] [@media(max-height:850px)]:gap-0.5', '[@media(max-height:700px)]:min-h-0', isActive ? 'text-foreground' : 'text-muted-foreground hover:text-foreground');
|
|
14
18
|
}
|
|
15
|
-
return cn('group relative flex h-11 w-11 items-center justify-center rounded-xl transition-colors', isActive
|
|
19
|
+
return cn('group relative flex h-11 w-11 shrink-0 items-center justify-center rounded-xl transition-colors', '[@media(max-height:700px)]:h-9 [@media(max-height:700px)]:w-9', isActive
|
|
16
20
|
? 'bg-primary text-primary-foreground shadow-sm ring-2 ring-primary/60'
|
|
17
21
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground');
|
|
18
22
|
}
|
|
19
23
|
function iconFrameClass(isActive) {
|
|
20
|
-
return cn('relative inline-flex h-11 w-11 items-center justify-center rounded-xl transition-colors', isActive
|
|
24
|
+
return cn('relative inline-flex h-11 w-11 items-center justify-center rounded-xl transition-colors', '[@media(max-height:850px)]:h-9 [@media(max-height:850px)]:w-9', isActive
|
|
21
25
|
? 'bg-primary text-primary-foreground shadow-sm ring-2 ring-primary/60'
|
|
22
26
|
: 'group-hover:bg-accent group-hover:text-foreground');
|
|
23
27
|
}
|
|
24
28
|
function railClass(labelVisibility, className) {
|
|
25
|
-
return cn('flex h-full shrink-0 flex-col items-center bg-shell-rail',
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
return cn('flex h-full shrink-0 flex-col items-center bg-shell-rail',
|
|
30
|
+
// Hard reachability guarantee on short viewports: the rail scrolls when the
|
|
31
|
+
// compact tiers still can't fit every item. Scrollbar is hidden — wheel,
|
|
32
|
+
// trackpad, and keyboard focus traversal still scroll the overflow.
|
|
33
|
+
'min-h-0 overflow-y-auto overflow-x-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden', labelVisibility === 'visible'
|
|
34
|
+
? 'w-[var(--shell-rail-label-width)] px-2 py-4 [@media(max-height:850px)]:py-2'
|
|
35
|
+
: 'w-[var(--shell-rail-width)] py-3.5 [@media(max-height:850px)]:py-2', className);
|
|
28
36
|
}
|
|
29
37
|
export function RailDivider({ pinnedBottom, onToggle }) {
|
|
30
|
-
return (_jsx("button", { type: "button", "data-testid": "rail-divider", onClick: onToggle, "aria-label": pinnedBottom ? 'Pull utility items up' : 'Push utility items down', className: cn('my-3 flex h-6 w-8 items-center justify-center rounded-md', 'text-muted-foreground hover:bg-accent hover:text-foreground transition-colors'), children: pinnedBottom ? (_jsx(ChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(ChevronDown, { size: 14, "aria-hidden": "true" })) }));
|
|
38
|
+
return (_jsx("button", { type: "button", "data-testid": "rail-divider", onClick: onToggle, "aria-label": pinnedBottom ? 'Pull utility items up' : 'Push utility items down', className: cn('my-3 flex h-6 w-8 shrink-0 items-center justify-center rounded-md', '[@media(max-height:850px)]:my-1', 'text-muted-foreground hover:bg-accent hover:text-foreground transition-colors'), children: pinnedBottom ? (_jsx(ChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(ChevronDown, { size: 14, "aria-hidden": "true" })) }));
|
|
39
|
+
}
|
|
40
|
+
// Mirrors the [@media(max-height:700px)] CSS tier that hides visible-mode
|
|
41
|
+
// labels, so those items regain a tooltip fallback exactly when their label
|
|
42
|
+
// disappears. SSR-safe: initial false, resolves post-mount.
|
|
43
|
+
function useIsShortViewport() {
|
|
44
|
+
const [isShort, setIsShort] = useState(false);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function')
|
|
47
|
+
return;
|
|
48
|
+
const mql = window.matchMedia('(max-height: 700px)');
|
|
49
|
+
const onChange = () => setIsShort(mql.matches);
|
|
50
|
+
onChange();
|
|
51
|
+
mql.addEventListener('change', onChange);
|
|
52
|
+
return () => mql.removeEventListener('change', onChange);
|
|
53
|
+
}, []);
|
|
54
|
+
return isShort;
|
|
31
55
|
}
|
|
32
56
|
// ---------------------------------------------------------------------------
|
|
33
57
|
// IconRail
|
|
34
58
|
// ---------------------------------------------------------------------------
|
|
35
59
|
export function IconRail({ mainItems, utilityItems = [], footer, activeId, renderLink, labelVisibility = 'tooltip', className, }) {
|
|
36
60
|
const band = useShellViewport();
|
|
61
|
+
const isShortViewport = useIsShortViewport();
|
|
37
62
|
const [pinnedBottom, setPinnedBottom] = useState(true);
|
|
38
63
|
if (band === 'mobile')
|
|
39
64
|
return null;
|
|
40
65
|
const showLabels = labelVisibility === 'visible';
|
|
66
|
+
// At ≤700px the visible-mode label is hidden by CSS, so items fall back to
|
|
67
|
+
// tooltip labels like tooltip mode does.
|
|
68
|
+
const useTooltipFallback = !showLabels || isShortViewport;
|
|
41
69
|
const renderItem = (item) => {
|
|
42
70
|
const isActive = item.id === activeId;
|
|
43
71
|
const klass = itemClass(isActive, labelVisibility);
|
|
44
72
|
const IconComp = item.icon;
|
|
45
|
-
const inner = showLabels ? (_jsxs(_Fragment, { children: [_jsxs("span", { "data-slot": "icon-rail-icon-frame", className: iconFrameClass(isActive), children: [_jsx(IconComp, { size: 26, "aria-hidden": "true" }), item.badge ? _jsx("span", { className: "absolute right-1 top-1", children: item.badge }) : null] }), _jsx("span", { "data-slot": "icon-rail-label", className: "max-w-full truncate text-[12px] leading-4", children: item.label })] })) : (_jsxs(_Fragment, { children: [_jsx(IconComp, { size: 22, "aria-hidden": "true" }), item.badge ? _jsx("span", { className: "absolute right-1 top-1", children: item.badge }) : null] }));
|
|
73
|
+
const inner = showLabels ? (_jsxs(_Fragment, { children: [_jsxs("span", { "data-slot": "icon-rail-icon-frame", className: iconFrameClass(isActive), children: [_jsx(IconComp, { size: 26, "aria-hidden": "true", className: "[@media(max-height:850px)]:size-5" }), item.badge ? _jsx("span", { className: "absolute right-1 top-1", children: item.badge }) : null] }), _jsx("span", { "data-slot": "icon-rail-label", className: "max-w-full truncate text-[12px] leading-4 [@media(max-height:850px)]:text-[11px] [@media(max-height:700px)]:hidden", children: item.label })] })) : (_jsxs(_Fragment, { children: [_jsx(IconComp, { size: 22, "aria-hidden": "true", className: "[@media(max-height:700px)]:size-[18px]" }), item.badge ? _jsx("span", { className: "absolute right-1 top-1", children: item.badge }) : null] }));
|
|
46
74
|
const linkProps = {
|
|
47
75
|
href: item.to,
|
|
48
76
|
'aria-label': item.label,
|
|
@@ -54,10 +82,10 @@ export function IconRail({ mainItems, utilityItems = [], footer, activeId, rende
|
|
|
54
82
|
// renderLink consumers receive the className so they can apply it themselves
|
|
55
83
|
renderLink({ item, isActive, className: klass, children: inner, linkProps })) : (_jsx("a", { ...linkProps }));
|
|
56
84
|
const trigger = (_jsx("span", { "data-testid": `icon-rail-trigger-${item.id}`, className: klass, children: linkContent }));
|
|
57
|
-
if (
|
|
85
|
+
if (!useTooltipFallback) {
|
|
58
86
|
return _jsx(Fragment, { children: trigger }, item.id);
|
|
59
87
|
}
|
|
60
88
|
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: trigger }), _jsx(TooltipContent, { side: "right", children: item.label })] }, item.id));
|
|
61
89
|
};
|
|
62
|
-
return (_jsx(TooltipProvider, { children: _jsxs("nav", { "data-testid": "icon-rail", "aria-label": "Primary", className: railClass(labelVisibility, className), children: [_jsx("div", { className: cn('flex flex-col items-center', showLabels ? 'w-full gap-1' : 'gap-1'), children: mainItems.map(renderItem) }), utilityItems.length > 0 && (_jsxs(_Fragment, { children: [_jsx(RailDivider, { pinnedBottom: pinnedBottom, onToggle: () => setPinnedBottom((prev) => !prev) }), _jsx("div", { "data-testid": "utility-items-wrapper", className: cn('flex flex-col items-center', showLabels ? 'w-full gap-1' : 'gap-1', pinnedBottom && 'mt-auto'), children: utilityItems.map(renderItem) })] })), footer && (_jsx("div", { className: cn('pt-3', pinnedBottom || utilityItems.length === 0 ? 'mt-auto' : ''), children: footer }))] }) }));
|
|
90
|
+
return (_jsx(TooltipProvider, { children: _jsxs("nav", { "data-testid": "icon-rail", "aria-label": "Primary", className: railClass(labelVisibility, className), children: [_jsx("div", { className: cn('flex shrink-0 flex-col items-center', showLabels ? 'w-full gap-1' : 'gap-1'), children: mainItems.map(renderItem) }), utilityItems.length > 0 && (_jsxs(_Fragment, { children: [_jsx(RailDivider, { pinnedBottom: pinnedBottom, onToggle: () => setPinnedBottom((prev) => !prev) }), _jsx("div", { "data-testid": "utility-items-wrapper", className: cn('flex shrink-0 flex-col items-center', showLabels ? 'w-full gap-1' : 'gap-1', pinnedBottom && 'mt-auto'), children: utilityItems.map(renderItem) })] })), footer && (_jsx("div", { className: cn('shrink-0 pt-3 [@media(max-height:850px)]:pt-1.5', pinnedBottom || utilityItems.length === 0 ? 'mt-auto' : ''), children: footer }))] }) }));
|
|
63
91
|
}
|