@hubpav/hwio-ui 0.9.0 → 0.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubpav/hwio-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "HARDWARIO web design system for Astro: DaisyUI 5 themes generated from the brand tokens, the Tailwind entry stylesheet and the HWio* components.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"test": "node --test \"test/**/*.test.mjs\""
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@hubpav/hwio-brand": "0.
|
|
36
|
-
"@hubpav/hwio-web-runtime": "0.
|
|
37
|
-
"@hubpav/hwio-web-worker": "0.
|
|
35
|
+
"@hubpav/hwio-brand": "0.10.0",
|
|
36
|
+
"@hubpav/hwio-web-runtime": "0.10.0",
|
|
37
|
+
"@hubpav/hwio-web-worker": "0.10.0",
|
|
38
38
|
"daisyui": "5.7.47"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
3
|
* Sticky site header: logo slot, primary nav (flat links, or mega items whose `mega:<key>` slot opens as a
|
|
4
|
-
* full-width panel under the bar), CTAs, language switcher, theme toggle, and
|
|
5
|
-
*
|
|
4
|
+
* full-width panel under the bar), CTAs, language switcher, theme toggle, and on small screens a menu sheet,
|
|
5
|
+
* where a mega item lists its `children` as a collapsible group instead.
|
|
6
6
|
*
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* The small-screen menu (owner, 2026-11-02: the drawer's close button sat elsewhere than the hamburger and
|
|
8
|
+
* the page jumped sideways and to the top when it opened): one toggle button stays in the bar and turns from
|
|
9
|
+
* the menu icon into the close icon in place; the sheet opens directly under the bar, full width, scrolling
|
|
10
|
+
* inside itself. The nav runtime locks the page without moving it (the scrollbar's width is kept as padding),
|
|
11
|
+
* makes the page behind inert, moves focus into the sheet without scrolling, and closes on the toggle,
|
|
12
|
+
* Escape, a followed link, another popup and a resize to the desktop layout. Below `sm` the bar drops the
|
|
13
|
+
* language dropdown (the sheet carries the pills), so logo, theme toggle and the 48 px toggle fit 320 px.
|
|
11
14
|
*
|
|
12
15
|
* The theme toggle and the language dropdown are quiet utilities (.hwio-utility: the nav's weight, size
|
|
13
|
-
* and gray, thin icons). The language dropdown, the mega panels and the
|
|
16
|
+
* and gray, thin icons). The language dropdown, the mega panels and the menu close each other: one
|
|
14
17
|
* popup at a time (@hubpav/hwio-web-runtime/popup). A CTA with an icon collapses to the icon alone below
|
|
15
18
|
* 2xl; an external CTA's arrow shows only beside its full label.
|
|
16
19
|
*
|
|
@@ -27,7 +30,7 @@ import HWioThemeToggle from './HWioThemeToggle.astro';
|
|
|
27
30
|
interface NavItem extends HWioLinkItem {
|
|
28
31
|
/** Key of the `mega:<key>` slot; the item becomes a button that opens the panel (hover, click, keyboard). */
|
|
29
32
|
mega?: string;
|
|
30
|
-
/** Links listed under the item in the
|
|
33
|
+
/** Links listed under the item in the small-screen menu (the panel is desktop-only); a description renders as a second, muted line. */
|
|
31
34
|
children?: (HWioLinkItem & { description?: string })[];
|
|
32
35
|
}
|
|
33
36
|
interface Props {
|
|
@@ -37,17 +40,17 @@ interface Props {
|
|
|
37
40
|
showThemeToggle?: boolean;
|
|
38
41
|
scrollspy?: boolean;
|
|
39
42
|
labels: { nav: string; menu: string; close: string; theme?: string; languages?: string };
|
|
40
|
-
/** Viewport from which the inline nav and CTAs replace the
|
|
43
|
+
/** Viewport from which the inline nav and CTAs replace the small-screen menu (default lg; xl for long navs). */
|
|
41
44
|
desktopFrom?: 'lg' | 'xl';
|
|
42
45
|
/** 'tight' (with desktopFrom xl only): smaller nav labels and padding from 1280 to 1535 px. Default 'default'. */
|
|
43
46
|
navDensity?: 'default' | 'tight';
|
|
44
47
|
id?: string;
|
|
45
48
|
class?: string;
|
|
46
49
|
}
|
|
47
|
-
const { nav, ctas = [], languages = [], showThemeToggle = true, scrollspy = false, labels, desktopFrom = 'lg', navDensity = 'default', id = 'hwio-nav-
|
|
50
|
+
const { nav, ctas = [], languages = [], showThemeToggle = true, scrollspy = false, labels, desktopFrom = 'lg', navDensity = 'default', id = 'hwio-nav-menu', class: className = '' } = Astro.props;
|
|
48
51
|
const BREAK = {
|
|
49
|
-
lg: { nav: 'hidden flex-1 self-stretch justify-center lg:flex', ctas: 'hidden items-center gap-2 lg:flex',
|
|
50
|
-
xl: { nav: 'hidden flex-1 self-stretch justify-center xl:flex', ctas: 'hidden items-center gap-2 xl:flex',
|
|
52
|
+
lg: { nav: 'hidden flex-1 self-stretch justify-center lg:flex', ctas: 'hidden items-center gap-2 lg:flex', menu: 'lg:hidden', from: '64rem', panels: 'hidden lg:block', densityTight: '' },
|
|
53
|
+
xl: { nav: 'hidden flex-1 self-stretch justify-center xl:flex', ctas: 'hidden items-center gap-2 xl:flex', menu: 'xl:hidden', from: '80rem', panels: 'hidden xl:block', densityTight: 'hwio-nav-tight' },
|
|
51
54
|
} as const;
|
|
52
55
|
const bp = BREAK[desktopFrom];
|
|
53
56
|
const CTA_VARIANT = { primary: 'primary', secondary: 'secondary', ghost: 'ghost' } as const;
|
|
@@ -90,55 +93,48 @@ const linkAttrs = (item: HWioLinkItem) => (item.external ? { target: '_blank', r
|
|
|
90
93
|
</HWioButton>
|
|
91
94
|
))}
|
|
92
95
|
</div>
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
</ul>
|
|
133
|
-
<div class="mt-4 flex flex-col gap-2">
|
|
134
|
-
{ctas.map((cta) => (
|
|
135
|
-
<HWioButton href={cta.href} external={cta.external} variant={CTA_VARIANT[cta.variant ?? 'primary']} track={{ id: cta.track?.id ?? 'header-cta', location: 'mobile-menu' }}>{cta.label}</HWioButton>
|
|
136
|
-
))}
|
|
137
|
-
</div>
|
|
138
|
-
{languages.length > 1 && <div class="mt-4"><HWioLanguageSwitcher items={languages} label={labels.languages ?? 'Language'} variant="pills" /></div>}
|
|
139
|
-
</div>
|
|
96
|
+
<button type="button" class:list={['btn btn-ghost btn-square hwio-menu-toggle', bp.menu]} aria-label={labels.menu} aria-controls={`${id}-panel`} aria-expanded="false" data-hwio-menu-toggle={id} data-hwio-menu-until={bp.from} data-hwio-label-open={labels.menu} data-hwio-label-close={labels.close}>
|
|
97
|
+
<HWioIcon name="menu" size="lg" class="hwio-menu-icon-open" />
|
|
98
|
+
<HWioIcon name="close" size="lg" class="hwio-menu-icon-close" />
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
<div id={`${id}-panel`} class:list={['hwio-mobile-menu', bp.menu]} data-hwio-menu-panel={id} aria-label={labels.nav} role="region" hidden>
|
|
103
|
+
<div class="container flex flex-col gap-6 py-4 pb-10">
|
|
104
|
+
<ul class="menu -mx-3 w-auto max-w-xl p-0 text-base">
|
|
105
|
+
{nav.map((item) => (
|
|
106
|
+
<li>
|
|
107
|
+
{item.children?.length ? (
|
|
108
|
+
<details>
|
|
109
|
+
<summary>{item.label}</summary>
|
|
110
|
+
<ul>
|
|
111
|
+
{item.children.map((child) => (
|
|
112
|
+
<li>
|
|
113
|
+
<a href={child.href} {...linkAttrs(child)} data-track={child.track?.id} data-track-location="mobile-menu">
|
|
114
|
+
{/* One grid cell: DaisyUI lays menu items out as a column-flow grid, so the two lines nest in one span. */}
|
|
115
|
+
<span class="flex flex-col items-start gap-0.5">
|
|
116
|
+
<span class="inline-flex items-center gap-1">{child.label}{child.external && <HWioIcon name="external" size="sm" class="opacity-50" />}</span>
|
|
117
|
+
{child.description && <span class="hwio-menu-desc">{child.description}</span>}
|
|
118
|
+
</span>
|
|
119
|
+
</a>
|
|
120
|
+
</li>
|
|
121
|
+
))}
|
|
122
|
+
</ul>
|
|
123
|
+
</details>
|
|
124
|
+
) : (
|
|
125
|
+
<a href={item.href} {...linkAttrs(item)} aria-current={item.current ? 'page' : undefined} data-track={item.track?.id} data-track-location="mobile-menu">{item.label}</a>
|
|
126
|
+
)}
|
|
127
|
+
</li>
|
|
128
|
+
))}
|
|
129
|
+
</ul>
|
|
130
|
+
{ctas.length > 0 && (
|
|
131
|
+
<div class="flex max-w-xl flex-col gap-2 sm:flex-row">
|
|
132
|
+
{ctas.map((cta) => (
|
|
133
|
+
<HWioButton href={cta.href} external={cta.external} variant={CTA_VARIANT[cta.variant ?? 'primary']} track={{ id: cta.track?.id ?? 'header-cta', location: 'mobile-menu' }} class="sm:flex-1">{cta.label}</HWioButton>
|
|
134
|
+
))}
|
|
140
135
|
</div>
|
|
141
|
-
|
|
136
|
+
)}
|
|
137
|
+
{languages.length > 1 && <HWioLanguageSwitcher items={languages} label={labels.languages ?? 'Language'} variant="pills" />}
|
|
142
138
|
</div>
|
|
143
139
|
</div>
|
|
144
140
|
{megaItems.length > 0 && (
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
|
-
* Language switcher: `pills` (a joined row of language codes, for the
|
|
3
|
+
* Language switcher: `pills` (a joined row of language codes, for the small-screen menu) or `dropdown` (a
|
|
4
4
|
* `<details>` menu). The dropdown closes on a click outside, on Escape (focus back on its summary),
|
|
5
|
-
* when focus leaves it and when another popup opens (a mega panel, the
|
|
5
|
+
* when focus leaves it and when another popup opens (a mega panel, the small-screen menu, another dropdown);
|
|
6
6
|
* opening it closes an open mega panel (@hubpav/hwio-web-runtime/dropdown).
|
|
7
7
|
*/
|
|
8
8
|
import HWioIcon from './HWioIcon.astro';
|
package/src/styles/styles.css
CHANGED
|
@@ -459,7 +459,7 @@
|
|
|
459
459
|
[data-theme$='-dark'] .hwio-backdrop-light { display: none; }
|
|
460
460
|
[data-theme$='-dark'] .hwio-backdrop-dark { display: block; }
|
|
461
461
|
|
|
462
|
-
/* Menu panels (HWioMegaLinks) and the
|
|
462
|
+
/* Menu panels (HWioMegaLinks) and the small-screen menu's second lines (owner, 2026-10-30): a caps group title
|
|
463
463
|
in Gray, link labels that never wrap, descriptions small (13 px), Regular, Gray and balanced. */
|
|
464
464
|
.hwio-mega-title {
|
|
465
465
|
color: var(--hwio-text-secondary, color-mix(in oklab, var(--color-base-content) 72%, transparent));
|
|
@@ -528,7 +528,7 @@
|
|
|
528
528
|
/* Text links: themes whose primary is too light for text on the page ground (er3o teal) set
|
|
529
529
|
--hwio-link to their text-safe hue; the default keeps DaisyUI's primary. */
|
|
530
530
|
.btn-link { color: var(--hwio-link, var(--color-primary)); }
|
|
531
|
-
/* DaisyUI marks a focused menu item (header nav,
|
|
531
|
+
/* DaisyUI marks a focused menu item (header nav, small-screen menu, the language dropdown's `.menu.dropdown-content`)
|
|
532
532
|
with a 10 % tint only and sets outline-style: none, on every `details > summary` unconditionally. That
|
|
533
533
|
fails WCAG 2.4.7; restore the system ring from the base layer. Same selector shape as DaisyUI's rule. */
|
|
534
534
|
.menu :where(li > :not(ul, menu, details, .menu-title), li > details > summary):focus-visible {
|
|
@@ -536,13 +536,74 @@
|
|
|
536
536
|
outline-offset: 2px;
|
|
537
537
|
}
|
|
538
538
|
/* DaisyUI 5.7.30+ paints a menu link carrying aria-current like .menu-active (the neutral fill). The
|
|
539
|
-
header marks the current page with its own label colour and bar (.hwio-nav-current) and the
|
|
539
|
+
header marks the current page with its own label colour and bar (.hwio-nav-current) and the small-screen menu
|
|
540
540
|
lists it as a plain row, so those links keep the ground; .menu-active items (the language dropdown)
|
|
541
541
|
keep DaisyUI's fill. currentColor as `color` inherits, so the nav's own colour utilities still win. */
|
|
542
542
|
.menu :where(li) > [aria-current]:not([aria-current='false'], [aria-current=''], .menu-active, .btn) {
|
|
543
543
|
--menu-active-bg: transparent;
|
|
544
544
|
--menu-active-fg: currentColor;
|
|
545
545
|
}
|
|
546
|
+
/* HWioHeader small-screen menu (owner, 2026-11-02). One toggle in the bar: the menu and the close icon share
|
|
547
|
+
one grid cell and cross-fade, so the close sits exactly where the hamburger was. The sheet opens under the
|
|
548
|
+
bar (the header is sticky at the top, so top: 100% is the bar's bottom edge), full width, and scrolls
|
|
549
|
+
inside itself; it fades in and settles 8 px, without motion when the visitor asks for less. While it is
|
|
550
|
+
open the nav runtime sets html.hwio-scroll-lock: overflow hidden, with the scrollbar's width kept as
|
|
551
|
+
padding, so nothing on the page moves. The icons stack by position, never by display, so the toggle's
|
|
552
|
+
lg:hidden / xl:hidden keeps working. */
|
|
553
|
+
.hwio-menu-toggle {
|
|
554
|
+
position: relative;
|
|
555
|
+
}
|
|
556
|
+
.hwio-menu-toggle > svg {
|
|
557
|
+
position: absolute;
|
|
558
|
+
inset: 0;
|
|
559
|
+
margin: auto;
|
|
560
|
+
transition: opacity 160ms ease-out, rotate 200ms ease-out, scale 200ms ease-out;
|
|
561
|
+
}
|
|
562
|
+
.hwio-menu-toggle[aria-expanded='true'] > .hwio-menu-icon-open,
|
|
563
|
+
.hwio-menu-toggle:not([aria-expanded='true']) > .hwio-menu-icon-close {
|
|
564
|
+
opacity: 0;
|
|
565
|
+
rotate: 45deg;
|
|
566
|
+
scale: 0.7;
|
|
567
|
+
}
|
|
568
|
+
.hwio-mobile-menu {
|
|
569
|
+
position: absolute;
|
|
570
|
+
inset-inline: 0;
|
|
571
|
+
top: 100%;
|
|
572
|
+
height: calc(100dvh - 4rem - 1px);
|
|
573
|
+
overflow-y: auto;
|
|
574
|
+
overscroll-behavior: contain;
|
|
575
|
+
border-top: 1px solid var(--color-base-300);
|
|
576
|
+
background-color: var(--color-base-100);
|
|
577
|
+
opacity: 0;
|
|
578
|
+
translate: 0 -0.5rem;
|
|
579
|
+
transition: opacity 180ms ease-out, translate 180ms ease-out;
|
|
580
|
+
}
|
|
581
|
+
@media (width >= 64rem) {
|
|
582
|
+
.hwio-mobile-menu { height: calc(100dvh - 72px - 1px); }
|
|
583
|
+
}
|
|
584
|
+
.hwio-mobile-menu[data-state='open'] {
|
|
585
|
+
opacity: 1;
|
|
586
|
+
translate: 0 0;
|
|
587
|
+
}
|
|
588
|
+
/* With the sheet open the bar is solid: its translucent ground would show the page's heading behind the logo. */
|
|
589
|
+
header:has(> .hwio-mobile-menu:not([hidden])) {
|
|
590
|
+
background-color: var(--color-base-100);
|
|
591
|
+
}
|
|
592
|
+
html.hwio-scroll-lock {
|
|
593
|
+
overflow: hidden;
|
|
594
|
+
padding-inline-end: var(--hwio-scrollbar, 0px);
|
|
595
|
+
}
|
|
596
|
+
/* The base body's overflow-x: hidden would make the body its own scroll container once the root stops
|
|
597
|
+
propagating overflow: the sticky header would then stick to the body's top, off screen, and the page
|
|
598
|
+
would jump. While locked the root clips, so the body goes back to visible. */
|
|
599
|
+
html.hwio-scroll-lock body {
|
|
600
|
+
overflow: visible;
|
|
601
|
+
}
|
|
602
|
+
@media (prefers-reduced-motion: reduce) {
|
|
603
|
+
.hwio-menu-toggle > svg,
|
|
604
|
+
.hwio-mobile-menu { transition: none; }
|
|
605
|
+
.hwio-mobile-menu { translate: none; }
|
|
606
|
+
}
|
|
546
607
|
/* HWioHeader navDensity="tight" (desktopFrom="xl"): from 1280 to 1535 px the container is 1280 px wide
|
|
547
608
|
while the full nav and CTAs show, and long SK/PL/CS labels overflowed it. Labels drop from 15 to 14 px
|
|
548
609
|
and item padding from 12 to 8 px in that range only; the current-page bar follows the padding.
|