@invisibleloop/pulse 0.2.15 → 0.4.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/CLAUDE.md +33 -22
- package/docs/public/pulse-ui.css +76 -0
- package/docs/public/pulse-ui.js +24 -9
- package/docs/server.js +106 -187
- package/docs/src/pages/components/nav.js +56 -6
- package/docs/src/pages/hydration.js +19 -10
- package/docs/src/pages/server-api.js +40 -34
- package/docs/src/pages/spec.js +3 -5
- package/examples/contact.js +0 -1
- package/examples/counter.js +0 -1
- package/examples/dev.server.js +10 -8
- package/examples/pricing.js +0 -1
- package/examples/products.js +0 -1
- package/examples/quiz.js +0 -1
- package/examples/todos.js +0 -1
- 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/agent/checklist.md +1 -5
- package/src/server/index.js +63 -3
- package/src/server/server.test.js +6 -6
- package/src/spec/schema.js +0 -7
- package/src/ui/nav.js +57 -10
package/CLAUDE.md
CHANGED
|
@@ -40,7 +40,6 @@ A spec is a plain JS object. Every property is optional except `route`, `state`,
|
|
|
40
40
|
```js
|
|
41
41
|
export const mySpec = {
|
|
42
42
|
route: '/path', // URL pattern, supports :params
|
|
43
|
-
hydrate: '/path/to/spec.js',// browser-importable path → enables hydration
|
|
44
43
|
|
|
45
44
|
meta: {
|
|
46
45
|
title: 'Page Title',
|
|
@@ -199,29 +198,41 @@ The server auto-detects the manifest from `staticDir/dist/manifest.json` when `s
|
|
|
199
198
|
```js
|
|
200
199
|
import { createServer } from './src/server/index.js'
|
|
201
200
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
201
|
+
// Pass URL objects — hydrate is auto-derived, no need to set it in specs.
|
|
202
|
+
// Specs with mutations/actions/persist are hydrated automatically.
|
|
203
|
+
// Purely server-rendered specs get zero JavaScript.
|
|
204
|
+
await createServer(
|
|
205
|
+
[
|
|
206
|
+
new URL('./src/pages/home.js', import.meta.url),
|
|
207
|
+
new URL('./src/pages/contact.js', import.meta.url),
|
|
208
|
+
],
|
|
209
|
+
{
|
|
210
|
+
port: 3000,
|
|
211
|
+
stream: true, // streaming SSR (default)
|
|
212
|
+
staticDir: 'public', // serve static files + auto-load manifest
|
|
213
|
+
root: new URL('.', import.meta.url), // project root for deriving browser paths
|
|
214
|
+
manifest: null, // explicit manifest path or object (overrides auto-detect)
|
|
215
|
+
defaultCache: 3600, // default HTML cache TTL in seconds for all pages (prod only)
|
|
216
|
+
// also accepts true (3600s + swr 86400s) or { public, maxAge, staleWhileRevalidate }
|
|
217
|
+
// spec.cache overrides per-page; in-process + Cache-Control headers both set
|
|
218
|
+
fetcherTimeout: 5000, // ms before any server fetcher times out (null = no limit)
|
|
219
|
+
// spec.serverTimeout overrides per-page
|
|
220
|
+
shutdownTimeout: 30000, // ms to wait for in-flight requests before force-exit on SIGTERM/SIGINT
|
|
221
|
+
healthCheck: '/healthz', // built-in health endpoint path, or false to disable
|
|
222
|
+
csp: { // extra sources merged into the framework's default CSP
|
|
223
|
+
'style-src': ['https://fonts.googleapis.com'],
|
|
224
|
+
'font-src': ['https://fonts.gstatic.com'],
|
|
225
|
+
},
|
|
226
|
+
resolveBrand: async (host) => db.brands.findBySlug(host.split('.')[0]),
|
|
227
|
+
// multi-brand: result cached 60s, attached to ctx.brand
|
|
228
|
+
onRequest: (req, res) => { /* return false to short-circuit */ },
|
|
229
|
+
onError: (err, req, res) => { /* custom error handling */ }
|
|
230
|
+
}
|
|
231
|
+
)
|
|
223
232
|
```
|
|
224
233
|
|
|
234
|
+
`createServer` is async — always `await` it. Spec objects are still accepted alongside URL objects for backwards compatibility.
|
|
235
|
+
|
|
225
236
|
All specs are validated at startup — bad specs throw before the server accepts connections.
|
|
226
237
|
|
|
227
238
|
## Multi-brand Sites
|
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) }
|
package/docs/server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import fs
|
|
2
|
-
import { createServer }
|
|
3
|
-
import { initLayoutManifest }
|
|
4
|
-
import { metrics }
|
|
5
|
-
import { metricsStore }
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import { createServer } from '../src/server/index.js'
|
|
3
|
+
import { initLayoutManifest } from './src/lib/layout.js'
|
|
4
|
+
import { metrics } from './src/lib/stats.js'
|
|
5
|
+
import { metricsStore } from './src/lib/metrics-store.js'
|
|
6
6
|
metricsStore.current = metrics
|
|
7
7
|
|
|
8
8
|
// Populate hashed asset paths in layout.js before any page renders
|
|
@@ -10,194 +10,113 @@ try {
|
|
|
10
10
|
const raw = fs.readFileSync(new URL('./public/dist/manifest.json', import.meta.url), 'utf8')
|
|
11
11
|
initLayoutManifest(JSON.parse(raw))
|
|
12
12
|
} catch { /* dev: no manifest yet, layout falls back to unhashed paths */ }
|
|
13
|
-
import home from './src/pages/home.js'
|
|
14
|
-
import howItWorks from './src/pages/how-it-works.js'
|
|
15
|
-
import faq from './src/pages/faq.js'
|
|
16
|
-
import config from './src/pages/config.js'
|
|
17
|
-
import gettingStarted from './src/pages/getting-started.js'
|
|
18
|
-
import slashCommands from './src/pages/slash-commands.js'
|
|
19
|
-
import promptExamples from './src/pages/prompt-examples.js'
|
|
20
|
-
import components from './src/pages/components.js'
|
|
21
|
-
import projectStructure from './src/pages/project-structure.js'
|
|
22
|
-
import spec from './src/pages/spec.js'
|
|
23
|
-
import state from './src/pages/state.js'
|
|
24
|
-
import mutations from './src/pages/mutations.js'
|
|
25
|
-
import actions from './src/pages/actions.js'
|
|
26
|
-
import validation from './src/pages/validation.js'
|
|
27
|
-
import constraints from './src/pages/constraints.js'
|
|
28
|
-
import persist from './src/pages/persist.js'
|
|
29
|
-
import serverData from './src/pages/server-data.js'
|
|
30
|
-
import routing from './src/pages/routing.js'
|
|
31
|
-
import streaming from './src/pages/streaming.js'
|
|
32
|
-
import caching from './src/pages/caching.js'
|
|
33
|
-
import guard from './src/pages/guard.js'
|
|
34
|
-
import rawResponses from './src/pages/raw-responses.js'
|
|
35
|
-
import serverApi from './src/pages/server-api.js'
|
|
36
|
-
import hydration from './src/pages/hydration.js'
|
|
37
|
-
import navigation from './src/pages/navigation.js'
|
|
38
|
-
import images from './src/pages/images.js'
|
|
39
|
-
import extending from './src/pages/extending.js'
|
|
40
|
-
import deploy from './src/pages/deploy.js'
|
|
41
|
-
import supabase from './src/pages/supabase.js'
|
|
42
|
-
import auth from './src/pages/auth.js'
|
|
43
|
-
import stripe from './src/pages/stripe.js'
|
|
44
|
-
import meta from './src/pages/meta.js'
|
|
45
|
-
import performance from './src/pages/performance.js'
|
|
46
|
-
import accessibility from './src/pages/accessibility.js'
|
|
47
|
-
import testing from './src/pages/testing.js'
|
|
48
|
-
import store from './src/pages/store.js'
|
|
49
13
|
|
|
50
|
-
|
|
51
|
-
import compButton from './src/pages/components/button.js'
|
|
52
|
-
import compBadge from './src/pages/components/badge.js'
|
|
53
|
-
import compCard from './src/pages/components/card.js'
|
|
54
|
-
import compInput from './src/pages/components/input.js'
|
|
55
|
-
import compSelect from './src/pages/components/select.js'
|
|
56
|
-
import compTextarea from './src/pages/components/textarea.js'
|
|
57
|
-
import compAlert from './src/pages/components/alert.js'
|
|
58
|
-
import compStat from './src/pages/components/stat.js'
|
|
59
|
-
import compAvatar from './src/pages/components/avatar.js'
|
|
60
|
-
import compEmpty from './src/pages/components/empty.js'
|
|
61
|
-
import compTable from './src/pages/components/table.js'
|
|
62
|
-
import compNav from './src/pages/components/nav.js'
|
|
63
|
-
import compHero from './src/pages/components/hero.js'
|
|
64
|
-
import compAppBadge from './src/pages/components/app-badge.js'
|
|
65
|
-
import compFeature from './src/pages/components/feature.js'
|
|
66
|
-
import compTestimonial from './src/pages/components/testimonial.js'
|
|
67
|
-
import compPricing from './src/pages/components/pricing.js'
|
|
68
|
-
import compAccordion from './src/pages/components/accordion.js'
|
|
69
|
-
import compContainer from './src/pages/components/container.js'
|
|
70
|
-
import compSection from './src/pages/components/section.js'
|
|
71
|
-
import compGrid from './src/pages/components/grid.js'
|
|
72
|
-
import compStack from './src/pages/components/stack.js'
|
|
73
|
-
import compCluster from './src/pages/components/cluster.js'
|
|
74
|
-
import compDivider from './src/pages/components/divider.js'
|
|
75
|
-
import compBanner from './src/pages/components/banner.js'
|
|
76
|
-
import compMedia from './src/pages/components/media.js'
|
|
77
|
-
import compTooltip from './src/pages/components/tooltip.js'
|
|
78
|
-
import compModal from './src/pages/components/modal.js'
|
|
79
|
-
import compCarousel from './src/pages/components/carousel.js'
|
|
80
|
-
import compCta from './src/pages/components/cta.js'
|
|
81
|
-
import compFooter from './src/pages/components/footer.js'
|
|
82
|
-
import compCodeWindow from './src/pages/components/code-window.js'
|
|
83
|
-
import compFieldset from './src/pages/components/fieldset.js'
|
|
84
|
-
import compToggle from './src/pages/components/toggle.js'
|
|
85
|
-
import compCheckbox from './src/pages/components/checkbox.js'
|
|
86
|
-
import compFileUpload from './src/pages/components/fileupload.js'
|
|
87
|
-
import compSlider from './src/pages/components/slider.js'
|
|
88
|
-
import compSegmented from './src/pages/components/segmented.js'
|
|
89
|
-
import compRadio from './src/pages/components/radio.js'
|
|
90
|
-
import compRating from './src/pages/components/rating.js'
|
|
91
|
-
import compSearch from './src/pages/components/search.js'
|
|
92
|
-
import compSpinner from './src/pages/components/spinner.js'
|
|
93
|
-
import compProgress from './src/pages/components/progress.js'
|
|
94
|
-
import compBreadcrumbs from './src/pages/components/breadcrumbs.js'
|
|
95
|
-
import compStepper from './src/pages/components/stepper.js'
|
|
96
|
-
import compImage from './src/pages/components/image.js'
|
|
97
|
-
import compPullquote from './src/pages/components/pullquote.js'
|
|
98
|
-
import compProse from './src/pages/components/prose.js'
|
|
99
|
-
import compHeading from './src/pages/components/heading.js'
|
|
100
|
-
import compList from './src/pages/components/list.js'
|
|
101
|
-
import compTimeline from './src/pages/components/timeline.js'
|
|
102
|
-
import compCharts from './src/pages/components/charts.js'
|
|
103
|
-
import compIcons from './src/pages/components/icons.js'
|
|
104
|
-
|
|
105
|
-
createServer(
|
|
14
|
+
await createServer(
|
|
106
15
|
[
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
meta,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
16
|
+
// Framework
|
|
17
|
+
new URL('./src/pages/home.js', import.meta.url),
|
|
18
|
+
new URL('./src/pages/how-it-works.js', import.meta.url),
|
|
19
|
+
new URL('./src/pages/faq.js', import.meta.url),
|
|
20
|
+
new URL('./src/pages/config.js', import.meta.url),
|
|
21
|
+
new URL('./src/pages/getting-started.js', import.meta.url),
|
|
22
|
+
new URL('./src/pages/slash-commands.js', import.meta.url),
|
|
23
|
+
new URL('./src/pages/prompt-examples.js', import.meta.url),
|
|
24
|
+
new URL('./src/pages/components.js', import.meta.url),
|
|
25
|
+
new URL('./src/pages/project-structure.js', import.meta.url),
|
|
26
|
+
new URL('./src/pages/spec.js', import.meta.url),
|
|
27
|
+
// State & Behaviour
|
|
28
|
+
new URL('./src/pages/state.js', import.meta.url),
|
|
29
|
+
new URL('./src/pages/mutations.js', import.meta.url),
|
|
30
|
+
new URL('./src/pages/actions.js', import.meta.url),
|
|
31
|
+
new URL('./src/pages/validation.js', import.meta.url),
|
|
32
|
+
new URL('./src/pages/constraints.js', import.meta.url),
|
|
33
|
+
new URL('./src/pages/persist.js', import.meta.url),
|
|
34
|
+
// Server
|
|
35
|
+
new URL('./src/pages/server-data.js', import.meta.url),
|
|
36
|
+
new URL('./src/pages/store.js', import.meta.url),
|
|
37
|
+
new URL('./src/pages/routing.js', import.meta.url),
|
|
38
|
+
new URL('./src/pages/streaming.js', import.meta.url),
|
|
39
|
+
new URL('./src/pages/caching.js', import.meta.url),
|
|
40
|
+
new URL('./src/pages/guard.js', import.meta.url),
|
|
41
|
+
new URL('./src/pages/raw-responses.js', import.meta.url),
|
|
42
|
+
new URL('./src/pages/server-api.js', import.meta.url),
|
|
43
|
+
new URL('./src/pages/extending.js', import.meta.url),
|
|
44
|
+
// Client
|
|
45
|
+
new URL('./src/pages/hydration.js', import.meta.url),
|
|
46
|
+
new URL('./src/pages/navigation.js', import.meta.url),
|
|
47
|
+
new URL('./src/pages/images.js', import.meta.url),
|
|
48
|
+
// Deployment
|
|
49
|
+
new URL('./src/pages/deploy.js', import.meta.url),
|
|
50
|
+
// Reference
|
|
51
|
+
new URL('./src/pages/meta.js', import.meta.url),
|
|
52
|
+
new URL('./src/pages/performance.js', import.meta.url),
|
|
53
|
+
new URL('./src/pages/accessibility.js', import.meta.url),
|
|
54
|
+
new URL('./src/pages/testing.js', import.meta.url),
|
|
55
|
+
// Integrations
|
|
56
|
+
new URL('./src/pages/supabase.js', import.meta.url),
|
|
57
|
+
new URL('./src/pages/auth.js', import.meta.url),
|
|
58
|
+
new URL('./src/pages/stripe.js', import.meta.url),
|
|
59
|
+
// UI Components
|
|
60
|
+
new URL('./src/pages/components/alert.js', import.meta.url),
|
|
61
|
+
new URL('./src/pages/components/avatar.js', import.meta.url),
|
|
62
|
+
new URL('./src/pages/components/badge.js', import.meta.url),
|
|
63
|
+
new URL('./src/pages/components/breadcrumbs.js', import.meta.url),
|
|
64
|
+
new URL('./src/pages/components/button.js', import.meta.url),
|
|
65
|
+
new URL('./src/pages/components/card.js', import.meta.url),
|
|
66
|
+
new URL('./src/pages/components/checkbox.js', import.meta.url),
|
|
67
|
+
new URL('./src/pages/components/carousel.js', import.meta.url),
|
|
68
|
+
new URL('./src/pages/components/charts.js', import.meta.url),
|
|
69
|
+
new URL('./src/pages/components/empty.js', import.meta.url),
|
|
70
|
+
new URL('./src/pages/components/fieldset.js', import.meta.url),
|
|
71
|
+
new URL('./src/pages/components/fileupload.js', import.meta.url),
|
|
72
|
+
new URL('./src/pages/components/heading.js', import.meta.url),
|
|
73
|
+
new URL('./src/pages/components/icons.js', import.meta.url),
|
|
74
|
+
new URL('./src/pages/components/image.js', import.meta.url),
|
|
75
|
+
new URL('./src/pages/components/input.js', import.meta.url),
|
|
76
|
+
new URL('./src/pages/components/list.js', import.meta.url),
|
|
77
|
+
new URL('./src/pages/components/modal.js', import.meta.url),
|
|
78
|
+
new URL('./src/pages/components/progress.js', import.meta.url),
|
|
79
|
+
new URL('./src/pages/components/prose.js', import.meta.url),
|
|
80
|
+
new URL('./src/pages/components/pullquote.js', import.meta.url),
|
|
81
|
+
new URL('./src/pages/components/radio.js', import.meta.url),
|
|
82
|
+
new URL('./src/pages/components/rating.js', import.meta.url),
|
|
83
|
+
new URL('./src/pages/components/search.js', import.meta.url),
|
|
84
|
+
new URL('./src/pages/components/segmented.js', import.meta.url),
|
|
85
|
+
new URL('./src/pages/components/select.js', import.meta.url),
|
|
86
|
+
new URL('./src/pages/components/slider.js', import.meta.url),
|
|
87
|
+
new URL('./src/pages/components/spinner.js', import.meta.url),
|
|
88
|
+
new URL('./src/pages/components/stat.js', import.meta.url),
|
|
89
|
+
new URL('./src/pages/components/stepper.js', import.meta.url),
|
|
90
|
+
new URL('./src/pages/components/table.js', import.meta.url),
|
|
91
|
+
new URL('./src/pages/components/textarea.js', import.meta.url),
|
|
92
|
+
new URL('./src/pages/components/timeline.js', import.meta.url),
|
|
93
|
+
new URL('./src/pages/components/toggle.js', import.meta.url),
|
|
94
|
+
new URL('./src/pages/components/tooltip.js', import.meta.url),
|
|
95
|
+
// Landing Components
|
|
96
|
+
new URL('./src/pages/components/accordion.js', import.meta.url),
|
|
97
|
+
new URL('./src/pages/components/app-badge.js', import.meta.url),
|
|
98
|
+
new URL('./src/pages/components/cta.js', import.meta.url),
|
|
99
|
+
new URL('./src/pages/components/feature.js', import.meta.url),
|
|
100
|
+
new URL('./src/pages/components/hero.js', import.meta.url),
|
|
101
|
+
new URL('./src/pages/components/nav.js', import.meta.url),
|
|
102
|
+
new URL('./src/pages/components/pricing.js', import.meta.url),
|
|
103
|
+
new URL('./src/pages/components/testimonial.js', import.meta.url),
|
|
104
|
+
// Layout Components
|
|
105
|
+
new URL('./src/pages/components/banner.js', import.meta.url),
|
|
106
|
+
new URL('./src/pages/components/cluster.js', import.meta.url),
|
|
107
|
+
new URL('./src/pages/components/code-window.js', import.meta.url),
|
|
108
|
+
new URL('./src/pages/components/container.js', import.meta.url),
|
|
109
|
+
new URL('./src/pages/components/divider.js', import.meta.url),
|
|
110
|
+
new URL('./src/pages/components/footer.js', import.meta.url),
|
|
111
|
+
new URL('./src/pages/components/grid.js', import.meta.url),
|
|
112
|
+
new URL('./src/pages/components/media.js', import.meta.url),
|
|
113
|
+
new URL('./src/pages/components/section.js', import.meta.url),
|
|
114
|
+
new URL('./src/pages/components/stack.js', import.meta.url),
|
|
197
115
|
],
|
|
198
116
|
{
|
|
199
117
|
port: process.env.PORT ? Number(process.env.PORT) : 4000,
|
|
200
118
|
staticDir: new URL('./public', import.meta.url).pathname,
|
|
119
|
+
root: new URL('.', import.meta.url),
|
|
201
120
|
defaultCache: true,
|
|
202
121
|
csp: {
|
|
203
122
|
'img-src': ['https://picsum.photos', 'https://images.unsplash.com'],
|
|
@@ -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
|
`,
|