kmcom-nuxt-layers 6.2.1 → 6.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/docs/MIGRATION-v6.3.0.md +234 -0
- package/docs/MIGRATION-v6.4.0.md +215 -0
- package/docs/MIGRATION.md +3 -0
- package/docs/adr/0003-modular-step-system-replaces-fluid-clamps.md +16 -0
- package/docs/component-levels.md +117 -0
- package/docs/layout-shift-causes.md +18 -0
- package/layers/content/app/components/Blog/ItemPage.vue +2 -7
- package/layers/content/app/components/Blog/List.vue +9 -2
- package/layers/content/app/components/Blog/Page.vue +9 -19
- package/layers/content/app/components/Gallery/Detail.vue +1 -0
- package/layers/content/app/components/Gallery/Grid.vue +9 -2
- package/layers/content/app/components/Gallery/ImageDetail.vue +0 -6
- package/layers/content/app/components/Gallery/ImagePage.vue +10 -6
- package/layers/content/app/components/Gallery/Page.vue +9 -19
- package/layers/content/app/components/Gallery/SlugPage.vue +2 -7
- package/layers/content/app/components/NuxtContent/Detail.vue +20 -11
- package/layers/content/app/components/NuxtContent/IndexTemplate.vue +21 -0
- package/layers/content/app/components/NuxtContent/List.vue +6 -1
- package/layers/content/app/components/Portfolio/ColorPalette.vue +2 -2
- package/layers/content/app/components/Portfolio/ItemPage.vue +2 -6
- package/layers/content/app/components/Portfolio/List.vue +19 -8
- package/layers/content/app/components/Portfolio/Page.vue +9 -19
- package/layers/content/app/composables/useContentItemSeo.ts +19 -0
- package/layers/content/app/composables/useContentSection.ts +9 -0
- package/layers/content/app/utils/galleryImageMeta.test.ts +30 -0
- package/layers/content/app/utils/galleryImageMeta.ts +11 -0
- package/layers/content/nuxt.config.ts +1 -1
- package/layers/core/app/app.config.ts +2 -2
- package/layers/core/app/app.vue +2 -1
- package/layers/core/app/assets/css/core.css +5 -11
- package/layers/core/app/assets/css/tokens/z-index.css +16 -0
- package/layers/core/app/components/DiagnosticsPage.vue +41 -41
- package/layers/core/app/components/LoadingScreen.vue +2 -0
- package/layers/core/app/utils/loading.test.ts +2 -2
- package/layers/core/app/utils/loading.ts +1 -1
- package/layers/delivery/feeds/app/components/Feeds/Index.vue +36 -75
- package/layers/delivery/feeds/app/components/Feeds/RouteCard.vue +6 -8
- package/layers/design-system/theming/nuxt.config.ts +1 -3
- package/layers/design-system/typography/app/assets/css/typography.css +41 -5
- package/layers/design-system/visual/app/components/Base/Modal.vue +34 -86
- package/layers/design-system/visual/app/components/HoverDimGroup.test.ts +38 -0
- package/layers/design-system/visual/app/components/HoverDimGroup.vue +54 -0
- package/layers/design-system/visual/app/components/Progress/Bar.vue +9 -29
- package/layers/motion/animations/app/components/Motion/Cursor.vue +1 -1
- package/layers/motion/animations/app/components/Motion/Marquee.vue +5 -0
- package/layers/motion/animations/app/components/Motion/MarqueeText.vue +2 -1
- package/layers/motion/animations/app/components/Motion/Staggered.test.ts +40 -0
- package/layers/motion/animations/app/components/Motion/Staggered.vue +37 -42
- package/layers/motion/animations/app/components/Motion/TextReveal.vue +8 -1
- package/layers/motion/animations/app/components/Motion/VelocityEffect.vue +6 -0
- package/layers/motion/animations/app/composables/useCountUp.ts +6 -0
- package/layers/motion/animations/app/composables/useCursorFollower.ts +4 -1
- package/layers/motion/animations/app/utils/pointerMotion.ts +7 -2
- package/layers/motion/scroll/app/components/Motion/HorizontalScroll.vue +9 -0
- package/layers/motion/scroll/app/components/Motion/Parallax.vue +13 -12
- package/layers/motion/scroll/app/components/Motion/PinnedSection.vue +16 -9
- package/layers/motion/scroll/app/composables/useReducedMotion.test.ts +23 -0
- package/layers/motion/scroll/app/composables/useReducedMotion.ts +14 -0
- package/layers/motion/scroll/app/utils/parallax.test.ts +24 -0
- package/layers/motion/scroll/app/utils/parallax.ts +16 -0
- package/layers/motion/scroll/nuxt.config.ts +1 -1
- package/layers/structure/layout/app/components/Layout/Grid/Debug.vue +1 -1
- package/layers/structure/layout/app/components/Layout/Page/index.vue +9 -10
- package/layers/structure/navigation/app/app.config.ts +37 -14
- package/layers/structure/navigation/app/components/Links/Group.vue +12 -38
- package/layers/structure/navigation/app/components/Links/Named.vue +9 -0
- package/layers/structure/navigation/app/components/Mast/Footer.vue +1 -1
- package/layers/structure/navigation/app/components/Mast/Nav.vue +14 -11
- package/layers/structure/navigation/app/components/Mast/NavModal.vue +95 -45
- package/layers/structure/navigation/app/components/Site/Logo.vue +35 -0
- package/layers/structure/navigation/app/components/Site/Subtitle.vue +10 -0
- package/layers/structure/navigation/app/components/Site/Title.vue +8 -0
- package/layers/structure/navigation/app/composables/mastNav.ts +38 -13
- package/layers/structure/navigation/app/types/nav.ts +34 -0
- package/layers/structure/navigation/nuxt.config.ts +7 -1
- package/package.json +1 -1
- /package/layers/{design-system/theming/app/assets/css/tokens.css → core/app/assets/css/tokens/elevation-motion.css} +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Migration Guide — v6.2.x → v6.3.0
|
|
2
|
+
|
|
3
|
+
**Audience:** an AI coding agent (or developer) updating a downstream Nuxt app that consumes these
|
|
4
|
+
layers.
|
|
5
|
+
**What changed:** the 2026-10 atomic design audit — components moved to the right level, tokens
|
|
6
|
+
moved into `core`, motion respects reduced motion, modals use `UModal`, and organisms no longer set
|
|
7
|
+
page meta.
|
|
8
|
+
|
|
9
|
+
Work top to bottom. Each change has a **Detect** step (how to tell whether it applies) and an
|
|
10
|
+
**Apply** step. Skip anything Detect doesn't find. Run the **Verify** checklist before declaring
|
|
11
|
+
done. An app component with the same name as a layer component overrides it, so a change to a layer
|
|
12
|
+
component you've already overridden doesn't apply to you.
|
|
13
|
+
|
|
14
|
+
## 0. Preconditions
|
|
15
|
+
|
|
16
|
+
- Update the package to `^6.3.0` and reinstall.
|
|
17
|
+
- Run `npx nuxt prepare` so the new auto-imports are typed.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Required changes
|
|
22
|
+
|
|
23
|
+
### 1. `MastNav` no longer positions itself
|
|
24
|
+
|
|
25
|
+
The hamburger button dropped `fixed top-0 right-0`. Where it sits is now the parent's decision.
|
|
26
|
+
|
|
27
|
+
**Detect:** the app renders `<MastNav` and has no `app/components/Mast/Nav.vue` of its own.
|
|
28
|
+
|
|
29
|
+
**Apply:** put the old positioning back where you render it, or position it differently:
|
|
30
|
+
|
|
31
|
+
```vue
|
|
32
|
+
<!-- BEFORE -->
|
|
33
|
+
<MastNav />
|
|
34
|
+
|
|
35
|
+
<!-- AFTER -->
|
|
36
|
+
<MastNav class="fixed top-0 right-0" />
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Detail organisms no longer set page meta
|
|
40
|
+
|
|
41
|
+
`NuxtContentDetail` and `GalleryImageDetail` used to call `useSeoMeta`. That moved up into the page
|
|
42
|
+
components (`BlogItemPage`, `PortfolioItemPage`, `GallerySlugPage`, `GalleryImagePage`), so an
|
|
43
|
+
organism never decides page meta.
|
|
44
|
+
|
|
45
|
+
**Detect:** grep your own pages for `<NuxtContentDetail` or `<GalleryImageDetail`. If you only use
|
|
46
|
+
the layer's `*Page` components, nothing changes for you.
|
|
47
|
+
|
|
48
|
+
**Apply:** set the meta in your page. For a content item, `useContentItemSeo` does what the
|
|
49
|
+
organism used to do:
|
|
50
|
+
|
|
51
|
+
```vue
|
|
52
|
+
<script setup lang="ts">
|
|
53
|
+
const slug = useRoute().params.slug as string
|
|
54
|
+
// Registers title/description/og:image, then fetches. Call it before any other await.
|
|
55
|
+
await useContentItemSeo('blog', slug)
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<NuxtContentDetail collection="blog" :slug />
|
|
60
|
+
</template>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For a gallery image, `galleryImageMeta(item, index)` (auto-imported util) returns the
|
|
64
|
+
`{ title, description, ogImage }` the organism used to set. Pass it to `useSeoMeta` with getters,
|
|
65
|
+
as `GalleryImagePage` does.
|
|
66
|
+
|
|
67
|
+
### 3. `BaseModal` and `MastNavModal` are built on `UModal`
|
|
68
|
+
|
|
69
|
+
Same props, emits and slots (`open`, `title`, `description`, `size`, `update:open`, `close`,
|
|
70
|
+
`#header`, default, `#footer="{ dismiss }"`). `UModal` now handles the dialog role, focus trap,
|
|
71
|
+
Escape key and teleport. What can break:
|
|
72
|
+
|
|
73
|
+
| Before | After |
|
|
74
|
+
| ------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
75
|
+
| Rendered in place | Teleported to `<body>`: scoped or parent-selector CSS no longer reaches it |
|
|
76
|
+
| `#header` replaced only the title block, so the close button stayed | `#header` replaces the whole header, close button included, so add your own |
|
|
77
|
+
| `.base-modal-*` / `.nav-modal-*` transition classes | Gone: `UModal` runs its own transitions |
|
|
78
|
+
| `z-50` (`BaseModal`), `useZIndex('modal')` (`MastNavModal`) | `z-(--z-modal)` = 400 |
|
|
79
|
+
| `size="full"` = `max-w-full` | `size="full"` = `UModal` `fullscreen` |
|
|
80
|
+
|
|
81
|
+
**Detect:** grep for `<BaseModal` with a `#header` slot, CSS that targets `base-modal-` or
|
|
82
|
+
`nav-modal-`, and styles that reach into the modal from a parent. If you ship your own
|
|
83
|
+
`app/components/Mast/NavModal.vue`, your override still runs and its `nav-modal` CSS still applies.
|
|
84
|
+
|
|
85
|
+
**Apply:** put a close button inside a custom `#header`. Move modal styling to the `ui`/`class`
|
|
86
|
+
props or global CSS. Delete dead transition CSS.
|
|
87
|
+
|
|
88
|
+
### 4. Cursor and grid-debug overlays left `z-9999`
|
|
89
|
+
|
|
90
|
+
`MotionCursor` now uses `z-(--z-cursor)` = 600, and `LayoutGridDebug` uses `z-(--z-debug)` = 700.
|
|
91
|
+
Both still sit above the layout scale (header 100 … toast 500).
|
|
92
|
+
|
|
93
|
+
**Detect:** the app has its own elements above `z-index: 600` that should stay _under_ the custom
|
|
94
|
+
cursor, or it relied on the cursor beating something at `z-9999`.
|
|
95
|
+
|
|
96
|
+
**Apply:** move those elements onto the scale (`z-(--z-modal)`, `z-(--z-toast)`, …), or raise
|
|
97
|
+
`--z-cursor` in your own `:root`.
|
|
98
|
+
|
|
99
|
+
### 5. `NuxtContentList` `status` is a typed union
|
|
100
|
+
|
|
101
|
+
`status` changed from `string` to `'idle' | 'pending' | 'success' | 'error'`, and `'error'` now
|
|
102
|
+
renders an error alert instead of the empty message.
|
|
103
|
+
|
|
104
|
+
**Detect:** typecheck errors on `<NuxtContentList :status=…>`.
|
|
105
|
+
|
|
106
|
+
**Apply:** pass the `status` from `useAsyncData`/`useFetch` (already that union). To change the error
|
|
107
|
+
UI, use the new `error-message` prop or the `#error` slot.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Recommended — deprecated components
|
|
112
|
+
|
|
113
|
+
Each one still works in v6.3 and prints a dev-only console warning. They'll be removed in the next
|
|
114
|
+
major.
|
|
115
|
+
|
|
116
|
+
### 6. `LinksGroup` → `HoverDimGroup`
|
|
117
|
+
|
|
118
|
+
Renamed and moved to `visual`: it dims any children, not just links. The `tag` prop is unchanged.
|
|
119
|
+
|
|
120
|
+
**Detect:** grep for `<LinksGroup`.
|
|
121
|
+
|
|
122
|
+
**Apply:** rename the tag.
|
|
123
|
+
|
|
124
|
+
```vue
|
|
125
|
+
<!-- BEFORE -->
|
|
126
|
+
<LinksGroup tag="nav">…</LinksGroup>
|
|
127
|
+
<!-- AFTER -->
|
|
128
|
+
<HoverDimGroup tag="nav">…</HoverDimGroup>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 7. `LinksNamed` → `ULink`
|
|
132
|
+
|
|
133
|
+
**Detect:** grep for `<LinksNamed`.
|
|
134
|
+
|
|
135
|
+
**Apply:**
|
|
136
|
+
|
|
137
|
+
```vue
|
|
138
|
+
<!-- BEFORE -->
|
|
139
|
+
<LinksNamed name="blog-slug" :params="{ slug }" color="primary">Read</LinksNamed>
|
|
140
|
+
<!-- AFTER -->
|
|
141
|
+
<ULink :to="{ name: 'blog-slug', params: { slug } }" class="text-primary">Read</ULink>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 8. `SiteTitle` / `SiteSubtitle` → `useSite()`
|
|
145
|
+
|
|
146
|
+
**Detect:** grep for `<SiteTitle` or `<SiteSubtitle`.
|
|
147
|
+
|
|
148
|
+
**Apply:**
|
|
149
|
+
|
|
150
|
+
```vue
|
|
151
|
+
<script setup lang="ts">
|
|
152
|
+
const site = useSite()
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<template>
|
|
156
|
+
<span v-for="(word, i) in site.titleWords" :key="i" class="site-word">{{ word }}</span>
|
|
157
|
+
</template>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Behaviour changes — check, usually no code
|
|
163
|
+
|
|
164
|
+
### 9. Motion respects reduced motion
|
|
165
|
+
|
|
166
|
+
When the OS asks for reduced motion, or the ThemePicker motion setting is `on`, these now render
|
|
167
|
+
static or paused: `MotionStaggered`, `MotionTextReveal`, `MotionCountUp` (jumps to the end value),
|
|
168
|
+
`MotionMarquee`, `MotionMarqueeText`, `MotionVelocityEffect`, `MotionParallax`,
|
|
169
|
+
`MotionPinnedSection` (items shown, no pin), `MotionHorizontalScroll` (native horizontal scroll),
|
|
170
|
+
the pointer-follow effects (`MotionTilt`, `MotionMagnetic`) and the cursor follower.
|
|
171
|
+
|
|
172
|
+
**Check:** e2e tests that emulate `prefers-reduced-motion: reduce` and still expect motion.
|
|
173
|
+
|
|
174
|
+
### 10. `MotionStaggered` now actually reveals its children
|
|
175
|
+
|
|
176
|
+
Before, `ref`/`class` on the `<slot>` never reached the DOM, so items stayed at `opacity: 0` for
|
|
177
|
+
good. It now staggers its rendered **element** children (text nodes aren't animated) and adds the
|
|
178
|
+
`transitions` layer's `animate-<animation>` class.
|
|
179
|
+
|
|
180
|
+
**Check:** remove any workaround CSS that forced `.motion-staggered > *` visible.
|
|
181
|
+
|
|
182
|
+
### 11. `LayoutPage` sets meta with `useSeoMeta`
|
|
183
|
+
|
|
184
|
+
It used `useHead`. Now `og:title` is always set, and a page's own `useSeoMeta` overrides
|
|
185
|
+
`LayoutPage`'s values instead of adding duplicate tags.
|
|
186
|
+
|
|
187
|
+
**Check:** nothing to do, unless you relied on the duplicate tags.
|
|
188
|
+
|
|
189
|
+
### 12. Wider `extends`
|
|
190
|
+
|
|
191
|
+
`content` now extends `layout` and `visual`, and `scroll` and `navigation` now extend `visual`. An
|
|
192
|
+
app that loads `content` or `navigation` therefore also gets the `layout`/`visual` components and
|
|
193
|
+
composables auto-imported. App components with the same name still win.
|
|
194
|
+
|
|
195
|
+
**Check:** after `nuxt prepare`, look for auto-import name clashes. The likeliest new one is
|
|
196
|
+
`useReducedMotion` (scroll layer), which `motion-v` also exports.
|
|
197
|
+
|
|
198
|
+
### 13. Colour and markup tweaks
|
|
199
|
+
|
|
200
|
+
- `Feeds` (index page) and `FeedsRouteCard` use semantic colours (`bg-elevated`, `text-muted`,
|
|
201
|
+
`border-default`). Only the RSS/Atom/JSON format hues stay fixed.
|
|
202
|
+
- `MastFooter` copyright: `text-neutral-300` → `text-muted`.
|
|
203
|
+
- `BlogPage`, `GalleryPage` and `PortfolioPage` render through the new `NuxtContentIndexTemplate`
|
|
204
|
+
(`UPageHeader` + `UPageBody` inside `LayoutSection` > `LayoutGridItem`).
|
|
205
|
+
|
|
206
|
+
**Check:** visual regression on the feeds page, the footer and the content index pages, in light and
|
|
207
|
+
dark mode.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## New — optional
|
|
212
|
+
|
|
213
|
+
| Addition | Where | Use |
|
|
214
|
+
| ------------------------------------- | ------------------------------------------ | --------------------------------------------------------- |
|
|
215
|
+
| `title`, `description` props | `BlogPage`, `GalleryPage`, `PortfolioPage` | Override the index heading and meta (defaults unchanged) |
|
|
216
|
+
| `items` prop | `BlogList`, `GalleryGrid`, `PortfolioList` | Pass data in. The component only fetches when it's absent |
|
|
217
|
+
| `error-message` prop, `#error` slot | `NuxtContentList` | Custom error state |
|
|
218
|
+
| `NuxtContentIndexTemplate` | content | Heading + body skeleton for your own index pages |
|
|
219
|
+
| `useContentSection(section)` | content | 404 when `contentLayer.sections.<section>` is `false` |
|
|
220
|
+
| `useContentItemSeo(collection, slug)` | content | Fetch an item and set its page meta |
|
|
221
|
+
| `useReducedMotion()` | scroll | `ComputedRef<boolean>`: OS setting + ThemePicker override |
|
|
222
|
+
| `--z-header` … `--z-debug` | core CSS | Stacking scale: `z-(--z-modal)` |
|
|
223
|
+
| Shadow / duration tokens | core CSS | Moved from `theming`, so they work without it |
|
|
224
|
+
| `docs/component-levels.md` | repo | Atomic level of every component |
|
|
225
|
+
|
|
226
|
+
## Verify
|
|
227
|
+
|
|
228
|
+
- [ ] `npx nuxt prepare && npx nuxi typecheck` is clean.
|
|
229
|
+
- [ ] Nav button is where it was (§1).
|
|
230
|
+
- [ ] Each content item page has the right `<title>` and `og:image` (§2).
|
|
231
|
+
- [ ] Modals open, close on Escape and backdrop, and custom headers have a close button (§3).
|
|
232
|
+
- [ ] The cursor sits above modals and toasts (§4).
|
|
233
|
+
- [ ] No `[navigation] … is deprecated` warnings in the dev console (§6–8).
|
|
234
|
+
- [ ] With reduced motion on, nothing moves and all content is visible (§9–10).
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Migration Guide — v6.3.x → v6.4.0
|
|
2
|
+
|
|
3
|
+
**Audience:** an AI coding agent (or developer) updating a downstream Nuxt app that consumes these
|
|
4
|
+
layers.
|
|
5
|
+
**What changed:** the duplicate overlay provider is gone, the nav is plain shared state with a
|
|
6
|
+
configurable trigger and modal, a new `SiteLogo` wordmark, and a per-element display type range.
|
|
7
|
+
Background: [`NAV-LOGO-OVERLAY-PLAN.md`](../NAV-LOGO-OVERLAY-PLAN.md).
|
|
8
|
+
|
|
9
|
+
Work top to bottom. Each change has a **Detect** step and an **Apply** step; skip anything Detect
|
|
10
|
+
doesn't find. An app component with the same name as a layer component overrides it, so a change
|
|
11
|
+
to a layer component you've overridden doesn't reach you until you delete the override.
|
|
12
|
+
|
|
13
|
+
## 0. Preconditions
|
|
14
|
+
|
|
15
|
+
- Update the package to `^6.4.0`, reinstall, run `npx nuxt prepare`.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Required changes
|
|
20
|
+
|
|
21
|
+
### 1. Remove your own `<UOverlayProvider />`
|
|
22
|
+
|
|
23
|
+
`UApp` already renders one. A second one made every `useOverlay()`/`createModal` overlay render
|
|
24
|
+
twice (doubled modals, layout shift). `core`'s `app.vue` no longer adds one.
|
|
25
|
+
|
|
26
|
+
**Detect:** grep your `app.vue` and layouts for `<UOverlayProvider`.
|
|
27
|
+
|
|
28
|
+
**Apply:** delete it. Keep `<UApp>`.
|
|
29
|
+
|
|
30
|
+
### 2. Render `<MastNavModal />` once in your layout
|
|
31
|
+
|
|
32
|
+
`<MastNav>` is only a trigger now. The modal is rendered once by the layout, and every trigger on
|
|
33
|
+
the page opens that one modal.
|
|
34
|
+
|
|
35
|
+
**Detect:** a layout or page renders `<MastNav` but not `<MastNavModal`.
|
|
36
|
+
|
|
37
|
+
**Apply:**
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<!-- app/layouts/default.vue -->
|
|
41
|
+
<template>
|
|
42
|
+
<MastNav class="fixed top-0 right-0 z-(--z-header)" />
|
|
43
|
+
<MastNavModal />
|
|
44
|
+
<slot />
|
|
45
|
+
</template>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. `useMastNav()` returns shared state
|
|
49
|
+
|
|
50
|
+
| Before (v6.3) | After (v6.4) |
|
|
51
|
+
| --------------------------------- | --------------------------------------------------- |
|
|
52
|
+
| `{ open(props?), close, patch }` | `{ isOpen, open, close, toggle }` |
|
|
53
|
+
| An overlay created per app | `useState('mast-nav:open')`; `isOpen` is read-only |
|
|
54
|
+
| `open()` called `lockScrolling()` | UModal locks body scroll; `open()` only stops Lenis |
|
|
55
|
+
|
|
56
|
+
**Detect:** grep for `useMastNav().patch`, `patch(` on the nav, or `open({` with props.
|
|
57
|
+
|
|
58
|
+
**Apply:** drop the props (the modal reads `mastNav` config and its own props instead), and read
|
|
59
|
+
`isOpen` where you used to track open state yourself.
|
|
60
|
+
|
|
61
|
+
### 4. `MastNav` is a `UButton`
|
|
62
|
+
|
|
63
|
+
The trigger was a bare `<button>` with a hamburger icon, an `#icon` slot and an inline
|
|
64
|
+
`z-index` (dropdown layer). It's now a `UButton` whose props come from `mastNav.trigger`, and
|
|
65
|
+
attrs on the component win.
|
|
66
|
+
|
|
67
|
+
**Detect:** `<MastNav>` with an `#icon` slot, or a fixed trigger that relied on the old inline
|
|
68
|
+
z-index.
|
|
69
|
+
|
|
70
|
+
**Apply:**
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// app/app.config.ts
|
|
74
|
+
mastNav: {
|
|
75
|
+
trigger: { label: 'Menu', icon: undefined, variant: 'ghost', size: 'xl' },
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- Replace `#icon` with `trigger.icon` (or `icon="…"` on the component). The default slot now fills
|
|
80
|
+
the button label.
|
|
81
|
+
- Add `z-(--z-header)` (or your own) to a fixed trigger that has to sit over page content.
|
|
82
|
+
- The default look moved from `p-4` + 28px icon to `UButton` neutral/ghost/xl. Restate `class` if
|
|
83
|
+
you need the old size.
|
|
84
|
+
|
|
85
|
+
### 5. Loading screen is opt-in, and `core` renders it
|
|
86
|
+
|
|
87
|
+
`core`'s `app.vue` now renders `<LoadingScreen />`, so you can delete an `app.vue` that existed only
|
|
88
|
+
to add it. So that every other app doesn't suddenly get a loading screen,
|
|
89
|
+
`coreLayer.loading.enabled` now defaults to **`false`**.
|
|
90
|
+
|
|
91
|
+
**Detect:** your app shows the loading screen (it renders `<LoadingScreen />` or relied on the
|
|
92
|
+
default being on).
|
|
93
|
+
|
|
94
|
+
**Apply:**
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// app/app.config.ts
|
|
98
|
+
coreLayer: { loading: { enabled: true } },
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Then delete your `app.vue` if `<LoadingScreen />` was its only difference from `core`'s.
|
|
102
|
+
|
|
103
|
+
### 6. `mastNav.links` is typed as `MastNavLink[]`
|
|
104
|
+
|
|
105
|
+
Route `params`/`query` values are `string | number` (the app.config type used to allow
|
|
106
|
+
`unknown`).
|
|
107
|
+
|
|
108
|
+
**Detect:** typecheck errors in `app.config.ts` under `mastNav.links`.
|
|
109
|
+
|
|
110
|
+
**Apply:** stringify the offending values.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Behaviour changes — check, usually no code
|
|
115
|
+
|
|
116
|
+
### 7. The current page's link is active
|
|
117
|
+
|
|
118
|
+
A link used to be active only when `useState('activeSection')` matched its `id`. It's now also
|
|
119
|
+
active when its `to` resolves to the current route, except on the smooth-scroll index, where every
|
|
120
|
+
link points at `index`. Active links get `aria-current="page"` plus `mastNav.ui.linkActive`. The
|
|
121
|
+
default weight (light → normal) keys off `aria-current`.
|
|
122
|
+
|
|
123
|
+
### 8. No more scrollbar jump when the nav opens
|
|
124
|
+
|
|
125
|
+
With classic (non-overlay) scrollbars the page used to widen by the scrollbar on open, because the
|
|
126
|
+
nav hid `html` overflow before the dialog measured the scrollbar. UModal now owns the lock and pads
|
|
127
|
+
`body`, and Lenis stops after it. `useSmoothScroll().lockScrolling()` is unchanged for other
|
|
128
|
+
callers.
|
|
129
|
+
|
|
130
|
+
### 9. `HoverDimGroup` renders nested fragments
|
|
131
|
+
|
|
132
|
+
Children inside `<template v-for>` around a `<slot>` (fragments two deep) used to be dropped
|
|
133
|
+
silently. They render and dim now. Check any `HoverDimGroup` that rendered fewer children than
|
|
134
|
+
you passed.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## New — optional
|
|
139
|
+
|
|
140
|
+
### `MastNavModal` styling and slots
|
|
141
|
+
|
|
142
|
+
The layer keeps the behaviour (dialog, focus trap, Escape, scroll lock, smooth-scroll vs router,
|
|
143
|
+
active link). The look comes from config and slots:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
// app/app.config.ts: each key you set replaces that default string (no class merging)
|
|
147
|
+
mastNav: {
|
|
148
|
+
ui: {
|
|
149
|
+
content: 'bg-default text-default',
|
|
150
|
+
close: 'absolute right-[5vw] top-[3vh]',
|
|
151
|
+
nav: 'flex w-full flex-col items-start gap-2 px-12',
|
|
152
|
+
link: 'cursor-pointer py-3 text-step-4 tracking-wider transition-all',
|
|
153
|
+
linkActive: 'underline',
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
| API | Purpose |
|
|
159
|
+
| ------------------------------------ | --------------------------------------------------------------- |
|
|
160
|
+
| `ui` prop | Per-instance override, merged per key over `mastNav.ui` |
|
|
161
|
+
| `links` prop | Replace `mastNav.links`, e.g. filtered by your page flags |
|
|
162
|
+
| `open` prop / `update:open` | Manual control; omit to follow `useMastNav().isOpen` |
|
|
163
|
+
| `#link="{ link, active, navigate }"` | Replace a link's markup; `navigate()` scrolls/routes and closes |
|
|
164
|
+
| `#close="{ close }"` | Replace the close button |
|
|
165
|
+
| `#header`, `#footer` | Content above/below the links |
|
|
166
|
+
| `hidden: true` on a link | Skip it without a custom `links` prop |
|
|
167
|
+
|
|
168
|
+
### `SiteLogo`
|
|
169
|
+
|
|
170
|
+
`site.title` as a wordmark on the step scale. Replaces the deprecated `SiteTitle`.
|
|
171
|
+
|
|
172
|
+
```vue
|
|
173
|
+
<SiteLogo />
|
|
174
|
+
<!-- stacked, text-step-1 -->
|
|
175
|
+
<SiteLogo size="display" :stacked="false" />
|
|
176
|
+
<!-- one line -->
|
|
177
|
+
<SiteLogo v-slot="{ words }">…</SiteLogo>
|
|
178
|
+
<!-- your own markup -->
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
// app/app.config.ts: sizes merge per key
|
|
183
|
+
siteLogo: {
|
|
184
|
+
stacked: true,
|
|
185
|
+
ui: { root: 'font-display leading-none tracking-tight', word: '' },
|
|
186
|
+
sizes: { display: 'text-step-display [--display-from:4] [--display-to:9]' },
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Per-element display range
|
|
191
|
+
|
|
192
|
+
`text-step-display` and plain `h1` now read `--display-from` (step at ≤360px, default 5) and
|
|
193
|
+
`--display-to` (step at ≥1280px, default 7) on the element itself:
|
|
194
|
+
|
|
195
|
+
```html
|
|
196
|
+
<span class="text-step-display [--display-from:4] [--display-to:9]">…</span>
|
|
197
|
+
<!-- ≈42px at 390px, 154px at 1280px -->
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Defaults are unchanged in Chromium, Firefox and WebKit. If you override `--step-display` on an
|
|
201
|
+
element with your own unlayered CSS, that still works; the two numbers are the simpler way.
|
|
202
|
+
|
|
203
|
+
## Deprecated
|
|
204
|
+
|
|
205
|
+
`SiteTitle` → `<SiteLogo :stacked="false">`. Removal in the next major, alongside `SiteSubtitle`,
|
|
206
|
+
`LinksNamed` and `LinksGroup`.
|
|
207
|
+
|
|
208
|
+
## Verify
|
|
209
|
+
|
|
210
|
+
- [ ] `npx nuxt prepare && npx nuxi typecheck` is clean.
|
|
211
|
+
- [ ] Opening any modal puts exactly one `[role="dialog"]` in the DOM (§1).
|
|
212
|
+
- [ ] The nav trigger opens one modal. Escape and the close button close it, focus returns to the
|
|
213
|
+
trigger, and the page stays where it was (§2–4, §8).
|
|
214
|
+
- [ ] The current page's link is highlighted (§7).
|
|
215
|
+
- [ ] The loading screen shows only where you enabled it (§5).
|
package/docs/MIGRATION.md
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
> **Upgrading?** v6.3 → v6.4: [`MIGRATION-v6.4.0.md`](./MIGRATION-v6.4.0.md) · v6.2 → v6.3:
|
|
2
|
+
> [`MIGRATION-v6.3.0.md`](./MIGRATION-v6.3.0.md). The guides below cover older versions.
|
|
3
|
+
|
|
1
4
|
# Agent Migration Guide — Update an app to nuxt-layers v2.2.x
|
|
2
5
|
|
|
3
6
|
**Audience:** an AI coding agent (or developer) updating a downstream Nuxt app that consumes these layers.
|
|
@@ -56,3 +56,19 @@ the element using it.
|
|
|
56
56
|
steps (h1 4, h2 3, h3 2, h4 1) and `p`/`li` keep the body's 1.7 leading. Otherwise the
|
|
57
57
|
24px copy sat at or above Nuxt UI's fixed `text-2xl`/`text-xl` prose headings. This needs
|
|
58
58
|
`text-step-*` registered as a font-size group in tailwind-merge (`ui.tv.twMergeConfig`).
|
|
59
|
+
|
|
60
|
+
## Amendment (v6.4.0): per-element display range
|
|
61
|
+
|
|
62
|
+
The display step was computed once at `:root`, so retuning it for one element (a hero wordmark)
|
|
63
|
+
meant restating the whole `tan(atan2(clamp(…)))` expression. Its two ends are now plain numbers,
|
|
64
|
+
`--display-from: 5` and `--display-to: 7`, and `text-step-display` and the `h1` base rule
|
|
65
|
+
recompute `--step-display` from them on the element. `class="[--display-from:4]
|
|
66
|
+
[--display-to:9]"` retunes one element without touching the formula.
|
|
67
|
+
|
|
68
|
+
- At the defaults the middle term reduces to the old `4.2176rem + 3.4784vw·k`; computed sizes
|
|
69
|
+
are identical (Chromium, Firefox and WebKit, 360–1600px).
|
|
70
|
+
- The accessibility rules above are unchanged: the `tan(atan2(1rem, 16px))` factor keeps the
|
|
71
|
+
exponent independent of root font size, and the clamp keeps its rem terms.
|
|
72
|
+
- `--step-display` still exists at `:root` (TypographyHeadline levels and theming read it). An
|
|
73
|
+
unlayered `--step-display` override on an element still beats the utility's, so existing
|
|
74
|
+
overrides keep working.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Component levels
|
|
2
|
+
|
|
3
|
+
Atomic design level for every UI component under `layers/**/app/components`. Folders stay by
|
|
4
|
+
domain (`Motion/`, `Layout/`); this table is the level record. `tests/vitest/component-levels.test.ts`
|
|
5
|
+
fails when a component is added without a row here, or a row points at a deleted file.
|
|
6
|
+
|
|
7
|
+
Dependencies point down: atom < molecule < organism < template < page. **behaviour** sits beside
|
|
8
|
+
atom: a slot wrapper that adds behaviour (motion, hover, error capture) and renders whatever it is
|
|
9
|
+
given, so it may wrap any level. `render/shader` pipeline nodes are a render graph, not UI, and are
|
|
10
|
+
left out.
|
|
11
|
+
|
|
12
|
+
| Level | Meaning |
|
|
13
|
+
| --------- | ------------------------------------------------ |
|
|
14
|
+
| atom | One element or vendor primitive |
|
|
15
|
+
| behaviour | Slot wrapper adding behaviour, no markup opinion |
|
|
16
|
+
| molecule | A few atoms doing one job |
|
|
17
|
+
| organism | A standalone section |
|
|
18
|
+
| template | Page-type skeleton: props and slots only |
|
|
19
|
+
| page | Binds route and data, fills one template |
|
|
20
|
+
|
|
21
|
+
| Component | Level |
|
|
22
|
+
| -------------------------------------------------------------------------- | --------- |
|
|
23
|
+
| `layers/content/app/components/Blog/Article.vue` | organism |
|
|
24
|
+
| `layers/content/app/components/Blog/ItemPage.vue` | page |
|
|
25
|
+
| `layers/content/app/components/Blog/List.vue` | organism |
|
|
26
|
+
| `layers/content/app/components/Blog/Page.vue` | page |
|
|
27
|
+
| `layers/content/app/components/Gallery/Detail.vue` | organism |
|
|
28
|
+
| `layers/content/app/components/Gallery/Grid.vue` | organism |
|
|
29
|
+
| `layers/content/app/components/Gallery/ImageDetail.vue` | organism |
|
|
30
|
+
| `layers/content/app/components/Gallery/ImagePage.vue` | page |
|
|
31
|
+
| `layers/content/app/components/Gallery/Lightbox.vue` | organism |
|
|
32
|
+
| `layers/content/app/components/Gallery/Page.vue` | page |
|
|
33
|
+
| `layers/content/app/components/Gallery/SlugPage.vue` | page |
|
|
34
|
+
| `layers/content/app/components/NuxtContent/Detail.vue` | template |
|
|
35
|
+
| `layers/content/app/components/NuxtContent/IndexTemplate.vue` | template |
|
|
36
|
+
| `layers/content/app/components/NuxtContent/List.vue` | organism |
|
|
37
|
+
| `layers/content/app/components/NuxtContent/Surround.vue` | molecule |
|
|
38
|
+
| `layers/content/app/components/NuxtContent/Toc.vue` | molecule |
|
|
39
|
+
| `layers/content/app/components/Portfolio/ColorPalette.vue` | organism |
|
|
40
|
+
| `layers/content/app/components/Portfolio/Detail.vue` | organism |
|
|
41
|
+
| `layers/content/app/components/Portfolio/ItemPage.vue` | page |
|
|
42
|
+
| `layers/content/app/components/Portfolio/List.vue` | organism |
|
|
43
|
+
| `layers/content/app/components/Portfolio/Page.vue` | page |
|
|
44
|
+
| `layers/content/app/components/Portfolio/Typography.vue` | organism |
|
|
45
|
+
| `layers/content/app/components/content/Figure.vue` | molecule |
|
|
46
|
+
| `layers/core/app/components/AppContainer.vue` | atom |
|
|
47
|
+
| `layers/core/app/components/DiagnosticsPage.vue` | organism |
|
|
48
|
+
| `layers/core/app/components/Element.vue` | atom |
|
|
49
|
+
| `layers/core/app/components/ElementTw.vue` | atom |
|
|
50
|
+
| `layers/core/app/components/ErrorBoundary.vue` | behaviour |
|
|
51
|
+
| `layers/core/app/components/LoadingScreen.vue` | organism |
|
|
52
|
+
| `layers/data/forms/app/components/Form/Contact.vue` | organism |
|
|
53
|
+
| `layers/data/forms/app/components/Form/Field.vue` | molecule |
|
|
54
|
+
| `layers/delivery/feeds/app/components/Feeds/Index.vue` | organism |
|
|
55
|
+
| `layers/delivery/feeds/app/components/Feeds/RouteCard.vue` | molecule |
|
|
56
|
+
| `layers/delivery/scripts/app/components/BaselineStatus.vue` | atom |
|
|
57
|
+
| `layers/design-system/theming/app/components/ThemePicker/AccentButton.vue` | atom |
|
|
58
|
+
| `layers/design-system/theming/app/components/ThemePicker/Colors.vue` | molecule |
|
|
59
|
+
| `layers/design-system/theming/app/components/ThemePicker/Menu.vue` | organism |
|
|
60
|
+
| `layers/design-system/theming/app/components/ThemePicker/MenuButton.vue` | atom |
|
|
61
|
+
| `layers/design-system/typography/app/components/Typography/CodeBlock.vue` | atom |
|
|
62
|
+
| `layers/design-system/typography/app/components/Typography/Headline.vue` | atom |
|
|
63
|
+
| `layers/design-system/typography/app/components/Typography/QuoteBlock.vue` | molecule |
|
|
64
|
+
| `layers/design-system/typography/app/components/Typography/RepeatText.vue` | atom |
|
|
65
|
+
| `layers/design-system/typography/app/components/Typography/TextStroke.vue` | atom |
|
|
66
|
+
| `layers/design-system/typography/app/components/Typography/index.vue` | atom |
|
|
67
|
+
| `layers/design-system/visual/app/components/Accent/Blob.vue` | atom |
|
|
68
|
+
| `layers/design-system/visual/app/components/Accent/Scene.vue` | molecule |
|
|
69
|
+
| `layers/design-system/visual/app/components/Base/Modal.vue` | molecule |
|
|
70
|
+
| `layers/design-system/visual/app/components/Gradient/Background.vue` | atom |
|
|
71
|
+
| `layers/design-system/visual/app/components/Gradient/Text.vue` | atom |
|
|
72
|
+
| `layers/design-system/visual/app/components/HoverDimGroup.vue` | behaviour |
|
|
73
|
+
| `layers/design-system/visual/app/components/Media/Picture.vue` | atom |
|
|
74
|
+
| `layers/design-system/visual/app/components/Progress/Bar.vue` | atom |
|
|
75
|
+
| `layers/design-system/visual/app/components/Progress/Circular.vue` | atom |
|
|
76
|
+
| `layers/design-system/visual/app/components/Tint/Overlay.vue` | atom |
|
|
77
|
+
| `layers/motion/animations/app/components/Motion/CountUp.vue` | atom |
|
|
78
|
+
| `layers/motion/animations/app/components/Motion/Cursor.vue` | atom |
|
|
79
|
+
| `layers/motion/animations/app/components/Motion/Magnetic.vue` | behaviour |
|
|
80
|
+
| `layers/motion/animations/app/components/Motion/Marquee.vue` | behaviour |
|
|
81
|
+
| `layers/motion/animations/app/components/Motion/MarqueeText.vue` | molecule |
|
|
82
|
+
| `layers/motion/animations/app/components/Motion/Staggered.vue` | behaviour |
|
|
83
|
+
| `layers/motion/animations/app/components/Motion/TextReveal.vue` | atom |
|
|
84
|
+
| `layers/motion/animations/app/components/Motion/Tilt.vue` | behaviour |
|
|
85
|
+
| `layers/motion/animations/app/components/Motion/VelocityEffect.vue` | behaviour |
|
|
86
|
+
| `layers/motion/scroll/app/components/Motion/HorizontalScroll.vue` | behaviour |
|
|
87
|
+
| `layers/motion/scroll/app/components/Motion/Parallax.vue` | behaviour |
|
|
88
|
+
| `layers/motion/scroll/app/components/Motion/PinnedSection.vue` | behaviour |
|
|
89
|
+
| `layers/motion/scroll/app/components/Motion/ScrollLink.vue` | atom |
|
|
90
|
+
| `layers/motion/scroll/app/components/Motion/ScrollProgress.vue` | atom |
|
|
91
|
+
| `layers/motion/scroll/app/components/Motion/ScrollScene.vue` | behaviour |
|
|
92
|
+
| `layers/motion/scroll/app/components/Motion/ScrollStats.vue` | molecule |
|
|
93
|
+
| `layers/motion/scroll/app/components/Motion/ScrollStep.vue` | behaviour |
|
|
94
|
+
| `layers/motion/transitions/app/components/Motion/Transition.vue` | behaviour |
|
|
95
|
+
| `layers/render/canvas/app/components/ShaderCanvas.vue` | atom |
|
|
96
|
+
| `layers/structure/layout/app/components/HStack.vue` | atom |
|
|
97
|
+
| `layers/structure/layout/app/components/Layout/Grid/Debug.vue` | atom |
|
|
98
|
+
| `layers/structure/layout/app/components/Layout/Grid/Item.vue` | atom |
|
|
99
|
+
| `layers/structure/layout/app/components/Layout/Main.vue` | template |
|
|
100
|
+
| `layers/structure/layout/app/components/Layout/Page/Header.vue` | organism |
|
|
101
|
+
| `layers/structure/layout/app/components/Layout/Page/index.vue` | template |
|
|
102
|
+
| `layers/structure/layout/app/components/Layout/Section/Gallery.vue` | organism |
|
|
103
|
+
| `layers/structure/layout/app/components/Layout/Section/Hero.vue` | organism |
|
|
104
|
+
| `layers/structure/layout/app/components/Layout/Section/Split.vue` | organism |
|
|
105
|
+
| `layers/structure/layout/app/components/Layout/Section/index.vue` | organism |
|
|
106
|
+
| `layers/structure/layout/app/components/Spacer.vue` | atom |
|
|
107
|
+
| `layers/structure/layout/app/components/VStack.vue` | atom |
|
|
108
|
+
| `layers/structure/layout/app/components/ZStack.vue` | atom |
|
|
109
|
+
| `layers/structure/navigation/app/components/Links/Group.vue` | behaviour |
|
|
110
|
+
| `layers/structure/navigation/app/components/Links/Named.vue` | atom |
|
|
111
|
+
| `layers/structure/navigation/app/components/Mast/Footer.vue` | organism |
|
|
112
|
+
| `layers/structure/navigation/app/components/Mast/Header.vue` | organism |
|
|
113
|
+
| `layers/structure/navigation/app/components/Mast/Nav.vue` | organism |
|
|
114
|
+
| `layers/structure/navigation/app/components/Mast/NavModal.vue` | organism |
|
|
115
|
+
| `layers/structure/navigation/app/components/Site/Logo.vue` | atom |
|
|
116
|
+
| `layers/structure/navigation/app/components/Site/Subtitle.vue` | atom |
|
|
117
|
+
| `layers/structure/navigation/app/components/Site/Title.vue` | atom |
|
|
@@ -86,9 +86,27 @@ Catalogue of layout shift (CLS) issues found and fixed in this monorepo, with ro
|
|
|
86
86
|
|
|
87
87
|
---
|
|
88
88
|
|
|
89
|
+
## 11. Duplicate `UOverlayProvider` — `core/app.vue`
|
|
90
|
+
|
|
91
|
+
**Cause:** `core`'s `app.vue` rendered `<UOverlayProvider />` inside `<UApp>`, which already renders one. Both read the same `useOverlay()` state, so every `useOverlay().create()` overlay (including the old `createModal`-based nav) rendered twice: a doubled modal and a layout shift on open.
|
|
92
|
+
|
|
93
|
+
**Fix:** Delete the extra provider (v6.4.0). `UApp` owns overlays and toasts.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 12. Nav modal hid the scrollbar before Reka measured it — `useMastNav`
|
|
98
|
+
|
|
99
|
+
**Cause:** `useMastNav().open()` called `lockScrolling()` (`html { overflow: hidden }`) before the modal opened. Reka's body scroll lock then measured a 0px scrollbar and added no padding, so with classic (non-overlay) scrollbars the page widened by the scrollbar width on open.
|
|
100
|
+
|
|
101
|
+
**Fix:** Let UModal/Reka own the overflow lock (it pads `body` for the scrollbar) and stop Lenis on the next tick, after Reka has measured (v6.4.0). A stopped Lenis sets `overflow: clip` on `html`, so the order matters.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
89
105
|
## Key Rules Going Forward
|
|
90
106
|
|
|
91
107
|
- Never use `@layer` / `@utility` in CSS files loaded via `css: []` — use plain selectors instead.
|
|
92
108
|
- Never use runtime `resolveComponent` to conditionally render components in Nuxt 4 — components are compile-time, not runtime-registered.
|
|
93
109
|
- Use `useState` (not `useLocalStorage`) for any value rendered during SSR that might differ between server and client.
|
|
94
110
|
- Keep accessibility-only elements (`aria-live` regions, announcers) outside CSS grid containers to prevent them participating in grid flow.
|
|
111
|
+
- Never add a `<UOverlayProvider />` next to `<UApp>` — `UApp` renders one.
|
|
112
|
+
- When a dialog locks scroll, don't hide `html` overflow first: the lock has to measure the scrollbar to compensate for it.
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { ContentLayerConfig } from '#layers/content/app/types/content'
|
|
3
|
-
|
|
4
2
|
const { slug } = defineProps<{
|
|
5
3
|
slug: string
|
|
6
4
|
}>()
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (contentLayer?.sections?.blog === false) {
|
|
11
|
-
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
|
12
|
-
}
|
|
6
|
+
useContentSection('blog')
|
|
7
|
+
await useContentItemSeo('blog', slug)
|
|
13
8
|
</script>
|
|
14
9
|
|
|
15
10
|
<template>
|