@invisibleloop/pulse 0.2.15 → 0.3.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/docs/public/pulse-ui.css +76 -0
- package/docs/public/pulse-ui.js +24 -9
- package/docs/src/pages/components/nav.js +56 -6
- package/package.json +1 -1
- package/public/.pulse-ui-version +1 -1
- package/public/pulse-ui.css +76 -0
- package/public/pulse-ui.js +24 -9
- package/src/ui/nav.js +57 -10
package/docs/public/pulse-ui.css
CHANGED
|
@@ -1280,6 +1280,82 @@ hr.ui-divider {
|
|
|
1280
1280
|
}
|
|
1281
1281
|
.ui-nav-mobile .ui-nav-link:last-child { border-bottom: none; }
|
|
1282
1282
|
.ui-nav-mobile-action { padding-top: .75rem; }
|
|
1283
|
+
.ui-nav-mobile-section {
|
|
1284
|
+
font-size: .7rem;
|
|
1285
|
+
font-weight: 600;
|
|
1286
|
+
text-transform: uppercase;
|
|
1287
|
+
letter-spacing: .08em;
|
|
1288
|
+
color: var(--ui-muted, var(--muted, #6b6b80));
|
|
1289
|
+
margin: .75rem 0 .25rem;
|
|
1290
|
+
padding: 0;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/* ─── Mega nav ───────────────────────────────────────────────────────────── */
|
|
1294
|
+
.ui-nav-mega-wrap { position: relative; }
|
|
1295
|
+
|
|
1296
|
+
.ui-nav-mega-trigger {
|
|
1297
|
+
background: none;
|
|
1298
|
+
border: none;
|
|
1299
|
+
cursor: pointer;
|
|
1300
|
+
display: inline-flex;
|
|
1301
|
+
align-items: center;
|
|
1302
|
+
gap: .3rem;
|
|
1303
|
+
padding: 0;
|
|
1304
|
+
font: inherit;
|
|
1305
|
+
font-size: .875rem;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
.ui-nav-mega-chevron { transition: transform .2s ease; flex-shrink: 0; }
|
|
1309
|
+
.ui-nav-mega-wrap--open .ui-nav-mega-chevron { transform: rotate(180deg); }
|
|
1310
|
+
|
|
1311
|
+
.ui-nav-mega-panel {
|
|
1312
|
+
position: absolute;
|
|
1313
|
+
top: calc(100% + .875rem);
|
|
1314
|
+
left: 50%;
|
|
1315
|
+
transform: translateX(-50%);
|
|
1316
|
+
background: var(--ui-bg, var(--bg, #0d0d10));
|
|
1317
|
+
border: 1px solid var(--ui-border, var(--border, #232329));
|
|
1318
|
+
border-radius: var(--ui-radius, 8px);
|
|
1319
|
+
box-shadow: 0 8px 40px rgba(0,0,0,.35);
|
|
1320
|
+
padding: 1.5rem;
|
|
1321
|
+
z-index: 200;
|
|
1322
|
+
min-width: 360px;
|
|
1323
|
+
}
|
|
1324
|
+
.ui-nav-mega-panel[hidden] { display: none !important; }
|
|
1325
|
+
|
|
1326
|
+
.ui-nav-mega-inner { display: flex; gap: 2rem; }
|
|
1327
|
+
|
|
1328
|
+
.ui-nav-mega-col { flex: 1; min-width: 140px; }
|
|
1329
|
+
|
|
1330
|
+
.ui-nav-mega-heading {
|
|
1331
|
+
font-size: .7rem;
|
|
1332
|
+
font-weight: 600;
|
|
1333
|
+
text-transform: uppercase;
|
|
1334
|
+
letter-spacing: .08em;
|
|
1335
|
+
color: var(--ui-muted, var(--muted, #6b6b80));
|
|
1336
|
+
margin: 0 0 .5rem;
|
|
1337
|
+
padding: 0 .5rem;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
.ui-nav-mega-item {
|
|
1341
|
+
display: flex;
|
|
1342
|
+
flex-direction: column;
|
|
1343
|
+
gap: .15rem;
|
|
1344
|
+
padding: .5rem;
|
|
1345
|
+
border-radius: calc(var(--ui-radius, 8px) * .65);
|
|
1346
|
+
transition: background .15s;
|
|
1347
|
+
}
|
|
1348
|
+
.ui-nav-mega-item:hover { background: var(--ui-surface, var(--surface, #111116)); }
|
|
1349
|
+
.ui-nav-mega-item-label {
|
|
1350
|
+
font-size: .875rem;
|
|
1351
|
+
font-weight: 500;
|
|
1352
|
+
color: var(--ui-text, var(--text, #e2e2ea));
|
|
1353
|
+
text-decoration: none;
|
|
1354
|
+
}
|
|
1355
|
+
.ui-nav-mega-item-label:hover { color: var(--ui-accent, var(--accent, #9b8dff)); }
|
|
1356
|
+
.ui-nav-mega-item-desc { font-size: .75rem; color: var(--ui-muted, var(--muted, #6b6b80)); }
|
|
1357
|
+
|
|
1358
|
+
[data-theme="light"] .ui-nav-mega-panel { box-shadow: 0 8px 40px rgba(0,0,0,.12); }
|
|
1283
1359
|
|
|
1284
1360
|
/* ─── App Badge ──────────────────────────────────────────────────────────── */
|
|
1285
1361
|
.ui-app-badge {
|
package/docs/public/pulse-ui.js
CHANGED
|
@@ -131,19 +131,34 @@ document.addEventListener('keydown', (e) => {
|
|
|
131
131
|
function initNav(el) {
|
|
132
132
|
const burger = el.querySelector('.ui-nav-burger')
|
|
133
133
|
const mobile = el.querySelector('.ui-nav-mobile')
|
|
134
|
-
if (!burger || !mobile) return
|
|
135
134
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
// Mobile burger
|
|
136
|
+
if (burger && mobile) {
|
|
137
|
+
const open = () => { el.classList.add('ui-nav--open'); burger.setAttribute('aria-expanded', 'true') }
|
|
138
|
+
const close = () => { el.classList.remove('ui-nav--open'); burger.setAttribute('aria-expanded', 'false') }
|
|
139
|
+
const toggle = () => el.classList.contains('ui-nav--open') ? close() : open()
|
|
139
140
|
|
|
140
|
-
|
|
141
|
+
burger.addEventListener('click', toggle)
|
|
142
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close() })
|
|
143
|
+
document.addEventListener('click', (e) => { if (!el.contains(e.target)) close() })
|
|
144
|
+
mobile.querySelectorAll('.ui-nav-link').forEach(a => a.addEventListener('click', close))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Mega nav dropdowns
|
|
148
|
+
el.querySelectorAll('.ui-nav-mega-wrap').forEach(wrap => {
|
|
149
|
+
const trigger = wrap.querySelector('.ui-nav-mega-trigger')
|
|
150
|
+
const panel = wrap.querySelector('.ui-nav-mega-panel')
|
|
151
|
+
if (!trigger || !panel) return
|
|
141
152
|
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
const openMega = () => { panel.hidden = false; wrap.classList.add('ui-nav-mega-wrap--open'); trigger.setAttribute('aria-expanded', 'true') }
|
|
154
|
+
const closeMega = () => { panel.hidden = true; wrap.classList.remove('ui-nav-mega-wrap--open'); trigger.setAttribute('aria-expanded', 'false') }
|
|
155
|
+
const toggleMega = () => panel.hidden ? openMega() : closeMega()
|
|
144
156
|
|
|
145
|
-
|
|
146
|
-
|
|
157
|
+
trigger.addEventListener('click', (e) => { e.stopPropagation(); toggleMega() })
|
|
158
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeMega() })
|
|
159
|
+
document.addEventListener('click', (e) => { if (!wrap.contains(e.target)) closeMega() })
|
|
160
|
+
panel.querySelectorAll('.ui-nav-mega-item').forEach(a => a.addEventListener('click', closeMega))
|
|
161
|
+
})
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
function runNavs() { document.querySelectorAll('.ui-nav').forEach(initNav) }
|
|
@@ -41,6 +41,47 @@ export default {
|
|
|
41
41
|
})`
|
|
42
42
|
)}
|
|
43
43
|
|
|
44
|
+
<h2 class="doc-h2" id="mega">Mega nav</h2>
|
|
45
|
+
<p>Replace a link's <code>href</code> with a <code>mega</code> array to render a dropdown panel. Each entry is a column with an optional heading and a list of items. Items support an optional <code>description</code> line.</p>
|
|
46
|
+
${demo(
|
|
47
|
+
uiNav({
|
|
48
|
+
logo: '<strong>MyApp</strong>',
|
|
49
|
+
logoHref: '/',
|
|
50
|
+
links: [
|
|
51
|
+
{ label: 'Products', mega: [
|
|
52
|
+
{ heading: 'Platform', items: [
|
|
53
|
+
{ label: 'Analytics', href: '#', description: 'Dashboards and usage metrics' },
|
|
54
|
+
{ label: 'Automation', href: '#', description: 'Scheduled jobs and webhooks' },
|
|
55
|
+
{ label: 'Storage', href: '#', description: 'Files, blobs, and databases' },
|
|
56
|
+
]},
|
|
57
|
+
{ heading: 'Integrations', items: [
|
|
58
|
+
{ label: 'Supabase', href: '#' },
|
|
59
|
+
{ label: 'Stripe', href: '#' },
|
|
60
|
+
{ label: 'Auth0', href: '#' },
|
|
61
|
+
]},
|
|
62
|
+
]},
|
|
63
|
+
{ label: 'Pricing', href: '#pricing' },
|
|
64
|
+
{ label: 'Docs', href: '#docs' },
|
|
65
|
+
],
|
|
66
|
+
action: button({ label: 'Get started', size: 'sm' }),
|
|
67
|
+
}),
|
|
68
|
+
`nav({
|
|
69
|
+
links: [
|
|
70
|
+
{ label: 'Products', mega: [
|
|
71
|
+
{ heading: 'Platform', items: [
|
|
72
|
+
{ label: 'Analytics', href: '/analytics', description: 'Dashboards and usage metrics' },
|
|
73
|
+
{ label: 'Automation', href: '/automation', description: 'Scheduled jobs and webhooks' },
|
|
74
|
+
]},
|
|
75
|
+
{ heading: 'Integrations', items: [
|
|
76
|
+
{ label: 'Supabase', href: '/supabase' },
|
|
77
|
+
{ label: 'Stripe', href: '/stripe' },
|
|
78
|
+
]},
|
|
79
|
+
]},
|
|
80
|
+
{ label: 'Pricing', href: '/pricing' },
|
|
81
|
+
],
|
|
82
|
+
})`
|
|
83
|
+
)}
|
|
84
|
+
|
|
44
85
|
<h2 class="doc-h2" id="mobile">Mobile view</h2>
|
|
45
86
|
<p>At < 640px the links and action are hidden and replaced with a burger button. The panel opens as an overlay — no layout shift.</p>
|
|
46
87
|
|
|
@@ -73,12 +114,21 @@ export default {
|
|
|
73
114
|
${table(
|
|
74
115
|
['Prop', 'Type', 'Default', 'Description'],
|
|
75
116
|
[
|
|
76
|
-
['<code>logo</code>',
|
|
77
|
-
['<code>logoHref</code>',
|
|
78
|
-
['<code>links</code>',
|
|
79
|
-
['<code>action</code>',
|
|
80
|
-
['<code>sticky</code>', 'boolean',
|
|
81
|
-
['<code>burgerAlign</code>', 'string',
|
|
117
|
+
['<code>logo</code>', 'string (HTML)', '—', 'Raw HTML slot — SVG, img, or text'],
|
|
118
|
+
['<code>logoHref</code>', 'string', "'/'", ''],
|
|
119
|
+
['<code>links</code>', 'array', '[]', '<code>{ label, href }[]</code> or <code>{ label, mega: Column[] }</code>'],
|
|
120
|
+
['<code>action</code>', 'string (HTML)', '—', 'Raw HTML slot — shown in desktop bar and mobile menu'],
|
|
121
|
+
['<code>sticky</code>', 'boolean', 'false', 'position: sticky with backdrop blur'],
|
|
122
|
+
['<code>burgerAlign</code>', 'string', "'right'", "'right' or 'left' — mobile burger position"],
|
|
123
|
+
]
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
<h3 class="doc-h3" style="margin-top:2rem">Mega nav column</h3>
|
|
127
|
+
${table(
|
|
128
|
+
['Prop', 'Type', 'Description'],
|
|
129
|
+
[
|
|
130
|
+
['<code>heading</code>', 'string', 'Optional column heading (uppercase label)'],
|
|
131
|
+
['<code>items</code>', 'array', '<code>{ label, href, description? }[]</code>'],
|
|
82
132
|
]
|
|
83
133
|
)}
|
|
84
134
|
`,
|
package/package.json
CHANGED
package/public/.pulse-ui-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.3.0
|
package/public/pulse-ui.css
CHANGED
|
@@ -1280,6 +1280,82 @@ hr.ui-divider {
|
|
|
1280
1280
|
}
|
|
1281
1281
|
.ui-nav-mobile .ui-nav-link:last-child { border-bottom: none; }
|
|
1282
1282
|
.ui-nav-mobile-action { padding-top: .75rem; }
|
|
1283
|
+
.ui-nav-mobile-section {
|
|
1284
|
+
font-size: .7rem;
|
|
1285
|
+
font-weight: 600;
|
|
1286
|
+
text-transform: uppercase;
|
|
1287
|
+
letter-spacing: .08em;
|
|
1288
|
+
color: var(--ui-muted, var(--muted, #6b6b80));
|
|
1289
|
+
margin: .75rem 0 .25rem;
|
|
1290
|
+
padding: 0;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/* ─── Mega nav ───────────────────────────────────────────────────────────── */
|
|
1294
|
+
.ui-nav-mega-wrap { position: relative; }
|
|
1295
|
+
|
|
1296
|
+
.ui-nav-mega-trigger {
|
|
1297
|
+
background: none;
|
|
1298
|
+
border: none;
|
|
1299
|
+
cursor: pointer;
|
|
1300
|
+
display: inline-flex;
|
|
1301
|
+
align-items: center;
|
|
1302
|
+
gap: .3rem;
|
|
1303
|
+
padding: 0;
|
|
1304
|
+
font: inherit;
|
|
1305
|
+
font-size: .875rem;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
.ui-nav-mega-chevron { transition: transform .2s ease; flex-shrink: 0; }
|
|
1309
|
+
.ui-nav-mega-wrap--open .ui-nav-mega-chevron { transform: rotate(180deg); }
|
|
1310
|
+
|
|
1311
|
+
.ui-nav-mega-panel {
|
|
1312
|
+
position: absolute;
|
|
1313
|
+
top: calc(100% + .875rem);
|
|
1314
|
+
left: 50%;
|
|
1315
|
+
transform: translateX(-50%);
|
|
1316
|
+
background: var(--ui-bg, var(--bg, #0d0d10));
|
|
1317
|
+
border: 1px solid var(--ui-border, var(--border, #232329));
|
|
1318
|
+
border-radius: var(--ui-radius, 8px);
|
|
1319
|
+
box-shadow: 0 8px 40px rgba(0,0,0,.35);
|
|
1320
|
+
padding: 1.5rem;
|
|
1321
|
+
z-index: 200;
|
|
1322
|
+
min-width: 360px;
|
|
1323
|
+
}
|
|
1324
|
+
.ui-nav-mega-panel[hidden] { display: none !important; }
|
|
1325
|
+
|
|
1326
|
+
.ui-nav-mega-inner { display: flex; gap: 2rem; }
|
|
1327
|
+
|
|
1328
|
+
.ui-nav-mega-col { flex: 1; min-width: 140px; }
|
|
1329
|
+
|
|
1330
|
+
.ui-nav-mega-heading {
|
|
1331
|
+
font-size: .7rem;
|
|
1332
|
+
font-weight: 600;
|
|
1333
|
+
text-transform: uppercase;
|
|
1334
|
+
letter-spacing: .08em;
|
|
1335
|
+
color: var(--ui-muted, var(--muted, #6b6b80));
|
|
1336
|
+
margin: 0 0 .5rem;
|
|
1337
|
+
padding: 0 .5rem;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
.ui-nav-mega-item {
|
|
1341
|
+
display: flex;
|
|
1342
|
+
flex-direction: column;
|
|
1343
|
+
gap: .15rem;
|
|
1344
|
+
padding: .5rem;
|
|
1345
|
+
border-radius: calc(var(--ui-radius, 8px) * .65);
|
|
1346
|
+
transition: background .15s;
|
|
1347
|
+
}
|
|
1348
|
+
.ui-nav-mega-item:hover { background: var(--ui-surface, var(--surface, #111116)); }
|
|
1349
|
+
.ui-nav-mega-item-label {
|
|
1350
|
+
font-size: .875rem;
|
|
1351
|
+
font-weight: 500;
|
|
1352
|
+
color: var(--ui-text, var(--text, #e2e2ea));
|
|
1353
|
+
text-decoration: none;
|
|
1354
|
+
}
|
|
1355
|
+
.ui-nav-mega-item-label:hover { color: var(--ui-accent, var(--accent, #9b8dff)); }
|
|
1356
|
+
.ui-nav-mega-item-desc { font-size: .75rem; color: var(--ui-muted, var(--muted, #6b6b80)); }
|
|
1357
|
+
|
|
1358
|
+
[data-theme="light"] .ui-nav-mega-panel { box-shadow: 0 8px 40px rgba(0,0,0,.12); }
|
|
1283
1359
|
|
|
1284
1360
|
/* ─── App Badge ──────────────────────────────────────────────────────────── */
|
|
1285
1361
|
.ui-app-badge {
|
package/public/pulse-ui.js
CHANGED
|
@@ -131,19 +131,34 @@ document.addEventListener('keydown', (e) => {
|
|
|
131
131
|
function initNav(el) {
|
|
132
132
|
const burger = el.querySelector('.ui-nav-burger')
|
|
133
133
|
const mobile = el.querySelector('.ui-nav-mobile')
|
|
134
|
-
if (!burger || !mobile) return
|
|
135
134
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
// Mobile burger
|
|
136
|
+
if (burger && mobile) {
|
|
137
|
+
const open = () => { el.classList.add('ui-nav--open'); burger.setAttribute('aria-expanded', 'true') }
|
|
138
|
+
const close = () => { el.classList.remove('ui-nav--open'); burger.setAttribute('aria-expanded', 'false') }
|
|
139
|
+
const toggle = () => el.classList.contains('ui-nav--open') ? close() : open()
|
|
139
140
|
|
|
140
|
-
|
|
141
|
+
burger.addEventListener('click', toggle)
|
|
142
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close() })
|
|
143
|
+
document.addEventListener('click', (e) => { if (!el.contains(e.target)) close() })
|
|
144
|
+
mobile.querySelectorAll('.ui-nav-link').forEach(a => a.addEventListener('click', close))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Mega nav dropdowns
|
|
148
|
+
el.querySelectorAll('.ui-nav-mega-wrap').forEach(wrap => {
|
|
149
|
+
const trigger = wrap.querySelector('.ui-nav-mega-trigger')
|
|
150
|
+
const panel = wrap.querySelector('.ui-nav-mega-panel')
|
|
151
|
+
if (!trigger || !panel) return
|
|
141
152
|
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
const openMega = () => { panel.hidden = false; wrap.classList.add('ui-nav-mega-wrap--open'); trigger.setAttribute('aria-expanded', 'true') }
|
|
154
|
+
const closeMega = () => { panel.hidden = true; wrap.classList.remove('ui-nav-mega-wrap--open'); trigger.setAttribute('aria-expanded', 'false') }
|
|
155
|
+
const toggleMega = () => panel.hidden ? openMega() : closeMega()
|
|
144
156
|
|
|
145
|
-
|
|
146
|
-
|
|
157
|
+
trigger.addEventListener('click', (e) => { e.stopPropagation(); toggleMega() })
|
|
158
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeMega() })
|
|
159
|
+
document.addEventListener('click', (e) => { if (!wrap.contains(e.target)) closeMega() })
|
|
160
|
+
panel.querySelectorAll('.ui-nav-mega-item').forEach(a => a.addEventListener('click', closeMega))
|
|
161
|
+
})
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
function runNavs() { document.querySelectorAll('.ui-nav').forEach(initNav) }
|
package/src/ui/nav.js
CHANGED
|
@@ -9,21 +9,69 @@
|
|
|
9
9
|
* reveals an absolutely-positioned dropdown that overlays page content.
|
|
10
10
|
* Wired automatically by pulse-ui.js — no extra JS needed.
|
|
11
11
|
*
|
|
12
|
+
* Links may be plain links or mega-nav dropdowns:
|
|
13
|
+
* { label: 'Docs', href: '/docs' }
|
|
14
|
+
* { label: 'Products', mega: [
|
|
15
|
+
* { heading: 'Solutions', items: [
|
|
16
|
+
* { label: 'Analytics', href: '/analytics', description: 'Track metrics' },
|
|
17
|
+
* ]},
|
|
18
|
+
* ]}
|
|
19
|
+
*
|
|
12
20
|
* @param {object} opts
|
|
13
|
-
* @param {string} opts.logo
|
|
14
|
-
* @param {string} opts.logoHref
|
|
15
|
-
* @param {Array<{label:
|
|
16
|
-
* @param {string} opts.action
|
|
17
|
-
* @param {boolean} opts.sticky
|
|
21
|
+
* @param {string} opts.logo - Raw HTML slot — SVG, img, or text
|
|
22
|
+
* @param {string} opts.logoHref - Logo link destination (default: '/')
|
|
23
|
+
* @param {Array<{label:string, href?:string, mega?:Array}>} opts.links
|
|
24
|
+
* @param {string} opts.action - Raw HTML slot — typically a button()
|
|
25
|
+
* @param {boolean} opts.sticky - Position sticky with backdrop blur
|
|
18
26
|
* @param {'right'|'left'} opts.burgerAlign - Mobile burger position (default: 'right')
|
|
19
27
|
* @param {string} opts.class
|
|
20
28
|
*/
|
|
21
29
|
|
|
22
30
|
import { escHtml as e } from '../html.js'
|
|
23
|
-
import { iconMenu, iconX } from './icons.js'
|
|
31
|
+
import { iconMenu, iconX, iconChevronDown } from './icons.js'
|
|
24
32
|
|
|
25
33
|
let _navId = 0
|
|
26
34
|
|
|
35
|
+
function megaItem({ label = '', href = '', description = '' }) {
|
|
36
|
+
return `<div class="ui-nav-mega-item">
|
|
37
|
+
<a href="${e(href)}" class="ui-nav-mega-item-label">${e(label)}</a>
|
|
38
|
+
${description ? `<span class="ui-nav-mega-item-desc">${e(description)}</span>` : ''}
|
|
39
|
+
</div>`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function megaPanel(columns, panelId) {
|
|
43
|
+
const cols = columns.map(col => `
|
|
44
|
+
<div class="ui-nav-mega-col">
|
|
45
|
+
${col.heading ? `<p class="ui-nav-mega-heading">${e(col.heading)}</p>` : ''}
|
|
46
|
+
${(col.items || []).map(megaItem).join('')}
|
|
47
|
+
</div>`).join('')
|
|
48
|
+
return `<div class="ui-nav-mega-panel" id="${panelId}" hidden>
|
|
49
|
+
<div class="ui-nav-mega-inner">${cols}
|
|
50
|
+
</div>
|
|
51
|
+
</div>`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderLink(link, navId, idx) {
|
|
55
|
+
if (link.mega) {
|
|
56
|
+
const panelId = `${navId}-mega-${idx}`
|
|
57
|
+
return `<div class="ui-nav-mega-wrap">
|
|
58
|
+
<button type="button" class="ui-nav-link ui-nav-mega-trigger" aria-expanded="false" aria-haspopup="true" aria-controls="${panelId}">
|
|
59
|
+
${e(link.label || '')}${iconChevronDown({ size: 13, class: 'ui-nav-mega-chevron' })}
|
|
60
|
+
</button>
|
|
61
|
+
${megaPanel(link.mega, panelId)}
|
|
62
|
+
</div>`
|
|
63
|
+
}
|
|
64
|
+
return `<a href="${e(link.href || '')}" class="ui-nav-link">${e(link.label || '')}</a>`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function renderMobileLink(link) {
|
|
68
|
+
if (link.mega) {
|
|
69
|
+
const items = link.mega.flatMap(col => col.items || [])
|
|
70
|
+
return `<p class="ui-nav-mobile-section">${e(link.label || '')}</p>` + items.map(item => megaItem(item)).join('')
|
|
71
|
+
}
|
|
72
|
+
return `<a href="${e(link.href || '')}" class="ui-nav-link">${e(link.label || '')}</a>`
|
|
73
|
+
}
|
|
74
|
+
|
|
27
75
|
export function nav({
|
|
28
76
|
logo = '',
|
|
29
77
|
logoHref = '/',
|
|
@@ -36,9 +84,8 @@ export function nav({
|
|
|
36
84
|
const id = `ui-nav-${++_navId}`
|
|
37
85
|
const classes = ['ui-nav', sticky && 'ui-nav--sticky', burgerAlign === 'left' && 'ui-nav--burger-left', cls].filter(Boolean).join(' ')
|
|
38
86
|
|
|
39
|
-
const linksHtml
|
|
40
|
-
|
|
41
|
-
).join('')
|
|
87
|
+
const linksHtml = links.map((l, i) => renderLink(l, id, i)).join('')
|
|
88
|
+
const mobileLinksHtml = links.map(renderMobileLink).join('')
|
|
42
89
|
|
|
43
90
|
const burgerHtml = links.length ? `
|
|
44
91
|
<button class="ui-nav-burger" type="button" aria-label="Toggle menu" aria-expanded="false" aria-controls="${id}-mobile">
|
|
@@ -48,7 +95,7 @@ export function nav({
|
|
|
48
95
|
|
|
49
96
|
const mobileMenuHtml = links.length ? `
|
|
50
97
|
<div class="ui-nav-mobile" id="${id}-mobile" aria-label="Mobile navigation">
|
|
51
|
-
<nav>${
|
|
98
|
+
<nav>${mobileLinksHtml}</nav>
|
|
52
99
|
</div>` : ''
|
|
53
100
|
|
|
54
101
|
return `<header class="${e(classes)}">
|