kmcom-nuxt-layers 6.2.0 → 6.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/MIGRATION-v6.3.0.md +234 -0
- package/docs/MIGRATION.md +2 -0
- package/docs/component-levels.md +116 -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/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/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/visual/app/components/Base/Modal.vue +34 -86
- package/layers/design-system/visual/app/components/HoverDimGroup.vue +46 -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/plugins/locomotive-scroll.client.ts +9 -1
- 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/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 +3 -1
- package/layers/structure/navigation/app/components/Mast/NavModal.vue +34 -39
- 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/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).
|
package/docs/MIGRATION.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
> **Upgrading to v6.3.0?** See [`MIGRATION-v6.3.0.md`](./MIGRATION-v6.3.0.md). The guides below cover older versions.
|
|
2
|
+
|
|
1
3
|
# Agent Migration Guide — Update an app to nuxt-layers v2.2.x
|
|
2
4
|
|
|
3
5
|
**Audience:** an AI coding agent (or developer) updating a downstream Nuxt app that consumes these layers.
|
|
@@ -0,0 +1,116 @@
|
|
|
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/Subtitle.vue` | atom |
|
|
116
|
+
| `layers/structure/navigation/app/components/Site/Title.vue` | atom |
|
|
@@ -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>
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import type { BlogCollectionItem } from '@nuxt/content'
|
|
3
|
+
|
|
2
4
|
import type { BlogQueryOptions } from '../../types/content'
|
|
3
5
|
|
|
4
|
-
const { options = {} } = defineProps<{
|
|
6
|
+
const { items = undefined, options = {} } = defineProps<{
|
|
7
|
+
items?: BlogCollectionItem[]
|
|
5
8
|
options?: BlogQueryOptions
|
|
6
9
|
}>()
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
// Fetch only when the parent didn't pass items (e.g. a related-items list it already has).
|
|
12
|
+
// ponytail: decided once at setup — toggling `items` after mount won't start or stop the fetch
|
|
13
|
+
const fetched = items ? undefined : await useBlogPosts(options)
|
|
14
|
+
const posts = computed(() => items ?? fetched?.data.value)
|
|
15
|
+
const status = computed(() => (items ? 'success' : (fetched?.status.value ?? 'idle')))
|
|
9
16
|
</script>
|
|
10
17
|
|
|
11
18
|
<template>
|
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
2
|
+
const { title = 'Blog', description = 'Articles and posts' } = defineProps<{
|
|
3
|
+
title?: string
|
|
4
|
+
description?: string
|
|
5
|
+
}>()
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (contentLayer?.sections?.blog === false) {
|
|
7
|
-
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
useSeoMeta({
|
|
11
|
-
title: 'Blog',
|
|
12
|
-
description: 'Articles and posts',
|
|
13
|
-
})
|
|
7
|
+
useContentSection('blog')
|
|
8
|
+
useSeoMeta({ title: () => title, description: () => description })
|
|
14
9
|
</script>
|
|
15
10
|
|
|
16
11
|
<template>
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
<UPageBody>
|
|
21
|
-
<BlogList />
|
|
22
|
-
</UPageBody>
|
|
23
|
-
</LayoutGridItem>
|
|
24
|
-
</LayoutSection>
|
|
12
|
+
<NuxtContentIndexTemplate :title :description>
|
|
13
|
+
<BlogList />
|
|
14
|
+
</NuxtContentIndexTemplate>
|
|
25
15
|
</template>
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
</div>
|
|
67
67
|
</NuxtLink>
|
|
68
68
|
<button
|
|
69
|
+
:aria-label="`View ${img.alt || 'image'} larger`"
|
|
69
70
|
class="absolute top-2 right-2 z-10 cursor-pointer rounded-full bg-black/50 p-1.5 text-white opacity-0 transition-opacity duration-300 group-hover:opacity-100 hover:bg-black/70"
|
|
70
71
|
@click.stop="() => openLightbox(asGallery(item).images!, idx)"
|
|
71
72
|
>
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import type { GalleryCollectionItem } from '@nuxt/content'
|
|
3
|
+
|
|
2
4
|
import type { GalleryQueryOptions } from '../../types/content'
|
|
3
5
|
|
|
4
|
-
const { options = {} } = defineProps<{
|
|
6
|
+
const { items: passed = undefined, options = {} } = defineProps<{
|
|
7
|
+
items?: GalleryCollectionItem[]
|
|
5
8
|
options?: GalleryQueryOptions
|
|
6
9
|
}>()
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
// Fetch only when the parent didn't pass items (e.g. a related-items list it already has).
|
|
12
|
+
// ponytail: decided once at setup — toggling `items` after mount won't start or stop the fetch
|
|
13
|
+
const fetched = passed ? undefined : await useGalleryItems(options)
|
|
14
|
+
const items = computed(() => passed ?? fetched?.data.value)
|
|
15
|
+
const status = computed(() => (passed ? 'success' : (fetched?.status.value ?? 'idle')))
|
|
9
16
|
</script>
|
|
10
17
|
|
|
11
18
|
<template>
|
|
@@ -13,12 +13,6 @@
|
|
|
13
13
|
const totalImages = computed(() => item.value?.images?.length ?? 0)
|
|
14
14
|
const hasPrevious = computed(() => index > 0)
|
|
15
15
|
const hasNext = computed(() => index < totalImages.value - 1)
|
|
16
|
-
|
|
17
|
-
useSeoMeta({
|
|
18
|
-
title: () => image.value?.title || image.value?.alt || `Image ${index + 1}`,
|
|
19
|
-
description: () => image.value?.caption || item.value?.title,
|
|
20
|
-
ogImage: () => image.value?.src,
|
|
21
|
-
})
|
|
22
16
|
</script>
|
|
23
17
|
|
|
24
18
|
<template>
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type { GalleryCollectionItem } from '@nuxt/content'
|
|
3
3
|
|
|
4
4
|
const { slug, index } = defineProps<{
|
|
5
5
|
slug: string
|
|
6
6
|
index: number
|
|
7
7
|
}>()
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
useContentSection('gallery')
|
|
10
|
+
|
|
11
|
+
const { data } = await useCollectionItem('gallery', slug)
|
|
12
|
+
const meta = computed(() => galleryImageMeta(data.value as GalleryCollectionItem | null, index))
|
|
13
|
+
useSeoMeta({
|
|
14
|
+
title: () => meta.value.title,
|
|
15
|
+
description: () => meta.value.description,
|
|
16
|
+
ogImage: () => meta.value.ogImage,
|
|
17
|
+
})
|
|
14
18
|
</script>
|
|
15
19
|
|
|
16
20
|
<template>
|
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
2
|
+
const { title = 'Gallery', description = 'Photo collections and visual work' } = defineProps<{
|
|
3
|
+
title?: string
|
|
4
|
+
description?: string
|
|
5
|
+
}>()
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (contentLayer?.sections?.gallery === false) {
|
|
7
|
-
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
useSeoMeta({
|
|
11
|
-
title: 'Gallery',
|
|
12
|
-
description: 'Photo collections and visual work',
|
|
13
|
-
})
|
|
7
|
+
useContentSection('gallery')
|
|
8
|
+
useSeoMeta({ title: () => title, description: () => description })
|
|
14
9
|
</script>
|
|
15
10
|
|
|
16
11
|
<template>
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
<UPageBody>
|
|
21
|
-
<GalleryGrid />
|
|
22
|
-
</UPageBody>
|
|
23
|
-
</LayoutGridItem>
|
|
24
|
-
</LayoutSection>
|
|
12
|
+
<NuxtContentIndexTemplate :title :description>
|
|
13
|
+
<GalleryGrid />
|
|
14
|
+
</NuxtContentIndexTemplate>
|
|
25
15
|
</template>
|
|
@@ -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?.gallery === false) {
|
|
11
|
-
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
|
12
|
-
}
|
|
6
|
+
useContentSection('gallery')
|
|
7
|
+
await useContentItemSeo('gallery', slug)
|
|
13
8
|
</script>
|
|
14
9
|
|
|
15
10
|
<template>
|
|
@@ -20,23 +20,32 @@
|
|
|
20
20
|
const surroundLinks = computed<ContentSurroundLink[] | undefined>(
|
|
21
21
|
() => surround.value ?? undefined
|
|
22
22
|
)
|
|
23
|
-
const itemImage = computed(() => (item.value as { image?: string } | null)?.image)
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
description: item.value
|
|
28
|
-
|
|
24
|
+
// Optional props only bound when present (exactOptionalPropertyTypes rejects `undefined`)
|
|
25
|
+
const headerProps = computed(() =>
|
|
26
|
+
item.value?.description ? { description: item.value.description } : {}
|
|
27
|
+
)
|
|
28
|
+
const surroundProps = computed(() =>
|
|
29
|
+
surroundLinks.value?.length ? { surround: surroundLinks.value } : {}
|
|
30
|
+
)
|
|
31
|
+
const tocProps = computed(() => {
|
|
32
|
+
const links = item.value?.body?.toc?.links
|
|
33
|
+
return links ? { links } : {}
|
|
29
34
|
})
|
|
30
35
|
</script>
|
|
31
36
|
|
|
32
37
|
<template>
|
|
33
38
|
<div>
|
|
34
39
|
<USkeleton v-if="status === 'pending'" class="h-96 w-full" />
|
|
40
|
+
<UAlert
|
|
41
|
+
v-else-if="status === 'error'"
|
|
42
|
+
color="error"
|
|
43
|
+
variant="subtle"
|
|
44
|
+
icon="lucide:circle-alert"
|
|
45
|
+
title="This page couldn't be loaded. Try again shortly."
|
|
46
|
+
/>
|
|
35
47
|
<UPage v-else-if="item">
|
|
36
|
-
<UPageHeader
|
|
37
|
-
:title="item.title"
|
|
38
|
-
v-bind="item.description ? { description: item.description } : {}"
|
|
39
|
-
>
|
|
48
|
+
<UPageHeader :title="item.title" v-bind="headerProps">
|
|
40
49
|
<template v-if="$slots.headline" #headline>
|
|
41
50
|
<slot name="headline" :item />
|
|
42
51
|
</template>
|
|
@@ -51,11 +60,11 @@
|
|
|
51
60
|
<slot name="after-content" :item />
|
|
52
61
|
|
|
53
62
|
<USeparator class="my-8" />
|
|
54
|
-
<NuxtContentSurround v-bind="
|
|
63
|
+
<NuxtContentSurround v-bind="surroundProps" />
|
|
55
64
|
</UPageBody>
|
|
56
65
|
|
|
57
66
|
<template v-if="!hideToc" #right>
|
|
58
|
-
<NuxtContentToc v-bind="
|
|
67
|
+
<NuxtContentToc v-bind="tocProps" />
|
|
59
68
|
</template>
|
|
60
69
|
</UPage>
|
|
61
70
|
<div v-else class="text-muted py-12 text-center">{{ notFoundMessage }}</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Content index skeleton — a page header over a list. Props and slots only: fetching, meta
|
|
4
|
+
* and the section guard belong to the *Page component that fills it.
|
|
5
|
+
*/
|
|
6
|
+
const { title, description = undefined } = defineProps<{
|
|
7
|
+
title: string
|
|
8
|
+
description?: string
|
|
9
|
+
}>()
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<LayoutSection>
|
|
14
|
+
<LayoutGridItem>
|
|
15
|
+
<UPageHeader :title v-bind="description ? { description } : {}" />
|
|
16
|
+
<UPageBody>
|
|
17
|
+
<slot />
|
|
18
|
+
</UPageBody>
|
|
19
|
+
</LayoutGridItem>
|
|
20
|
+
</LayoutSection>
|
|
21
|
+
</template>
|