nuxt-unified-ui 0.5.7 → 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-unified-ui",
3
3
  "type": "module",
4
- "version": "0.5.7",
4
+ "version": "0.5.9",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./index.d.ts",
7
7
  "exports": {
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "@formkit/tempo": "1.1.0",
23
- "@iconify-json/lucide": "1.2.126",
23
+ "@iconify-json/lucide": "1.2.129",
24
24
  "@nuxt/kit": "4.5.2",
25
25
  "@nuxt/ui": "4.11.0",
26
26
  "@nuxtjs/i18n": "10.6.0",
@@ -4,8 +4,8 @@ description: >-
4
4
  Single skill for the nuxt-unified-ui Nuxt layer and mandatory Nuxt code style:
5
5
  install/extend the layer, required CSS, modules/config, radashi radXxx
6
6
  auto-imports, un-form / useForm, launchFormPickerDialog /
7
- launchChoicePickerDialog, toast helpers, un-card / un-typography, pages /
8
- definePageMeta, ufetch / useUFetch wrapping, unified resources
7
+ launchChoicePickerDialog, toast helpers, un-card / un-typography, pages /
8
+ definePageMeta, page /* params */ and /* seo */ blocks, ufetch / useUFetch wrapping, unified resources
9
9
  (server plugins, REST handleResource*, resource-manager dashboard /
10
10
  customization), attribute order/defaults, and whitespace/formatting
11
11
  conventions for all Nuxt-generated code. Use when working in or consuming
@@ -71,7 +71,7 @@ Absolute highlights:
71
71
  - `{{ ... }}` on its own line (static + dynamic text may mix)
72
72
  - `/* section */` comments; imports co-located under the section that uses them
73
73
  - Light naming: `handleXxx` handlers, `it` in short callbacks, descriptive `for...of`, computeds use block + `return`
74
- - Pages: explicit `definePageMeta.name`, reactive route params, named `navigateTo` / `nuxt-link` → [pages.md](references/pages.md)
74
+ - Pages: explicit `definePageMeta.name`, `/* params */` for `route.params` / `route.query`, required `/* seo */` (`useHead` + `useSeoMeta`, `useJsonld` when installed) → [pages.md](references/pages.md)
75
75
  - Fetching: `ufetch(url, {` one line; `useUFetch` with URL on next line + `data*Data` / `is*Loading` / `refresh*` → [data-fetching.md](references/data-fetching.md)
76
76
 
77
77
  ---
@@ -361,7 +361,9 @@ Group with `/* name */`:
361
361
  | Common section | Contents |
362
362
  |----------------|----------|
363
363
  | `/* interface */` | props, emits, models |
364
- | `/* page */` | `definePageMeta`, `useHead` |
364
+ | `/* page */` | `definePageMeta` only |
365
+ | `/* params */` | `useRoute` + reactive computeds for `route.params` and `route.query` |
366
+ | `/* seo */` | `useHead` + `useSeoMeta` (and `useJsonld` when the project has it) |
365
367
  | domain names | `/* login */`, `/* resource */`, `/* captcha */`, … |
366
368
  | `/* outlets */` | `defineExpose` |
367
369
 
@@ -411,11 +413,13 @@ function refreshAll() {
411
413
 
412
414
  **Pages**
413
415
 
414
- 1. `/* page */`
415
- 2. Route/params
416
- 3. Data / forms / domain sections
417
- 4. Watchers / lifecycle
418
- 5. Handlers
416
+ 1. `/* page */` — `definePageMeta` only
417
+ 2. `/* params */` — only when the page reads `route.params` or `route.query`
418
+ 3. Domain sections SEO needs (fetches / derived data)
419
+ 4. `/* seo */` — required; under the last block it reads
420
+ 5. Remaining domain sections
421
+ 6. Watchers / lifecycle
422
+ 7. Handlers
419
423
 
420
424
  ### Import co-location
421
425
 
@@ -648,7 +652,7 @@ Refs unwrap automatically — do not write `.value` in template expressions or i
648
652
 
649
653
  These are part of the same “shape” conventions when generating app code:
650
654
 
651
- - Pages / named routes / reactive params → [pages.md](pages.md)
655
+ - Pages / named routes / `/* params */` / `/* seo */` → [pages.md](pages.md)
652
656
  - `ufetch` / `useUFetch` wrapping, naming, options order → [data-fetching.md](data-fetching.md)
653
657
 
654
658
  ### `useUFetch` wrapping (summary)
@@ -801,5 +805,5 @@ export default defineEventHandler(async event => {
801
805
  - [ ] Every section is named; imports are co-located with the section that uses them
802
806
  - [ ] `handleXxx` for action handlers; `it` for short callbacks; descriptive loop names
803
807
  - [ ] Computeds that return structures use block + `return`
804
- - [ ] Pages: explicit `definePageMeta.name`, reactive route params, named navigation ([pages.md](pages.md))
808
+ - [ ] Pages: explicit `definePageMeta.name`, `/* params */` + `/* seo */` placement, named navigation ([pages.md](pages.md))
805
809
  - [ ] Fetching: `ufetch` / `useUFetch` wrap styles and destructure names ([data-fetching.md](data-fetching.md))
@@ -13,13 +13,12 @@ definePageMeta({
13
13
  name: 'dashboard.patients.single',
14
14
  });
15
15
 
16
- useHead({
17
- title: $t('patients.single.pageTitle'),
18
- });
19
16
 
17
+ /* params */
20
18
 
21
19
  const route = useRoute();
22
20
 
21
+
23
22
  const patientUid = computed(() => {
24
23
  return route.params.patientUid;
25
24
  });
@@ -27,11 +26,22 @@ const patientUid = computed(() => {
27
26
 
28
27
  /* data */
29
28
 
30
- const { data: patientData, pending: isPatientLoading, refresh: refreshPatient } = useUFetch(
29
+ const { data: patientData, pending: isPatientPending, refresh: refreshPatient } = useUFetch(
31
30
  computed(() => `/api/patients/${patientUid.value}`),
32
31
  );
33
32
 
34
33
 
34
+ /* seo */
35
+
36
+ useHead({
37
+ title: () => patientData.value?.name,
38
+ });
39
+
40
+ useSeoMeta({
41
+ description: () => patientData.value?.description,
42
+ });
43
+
44
+
35
45
  /* handlers */
36
46
 
37
47
  async function handleAction() {
@@ -56,40 +66,147 @@ async function handleAction() {
56
66
  </template>
57
67
  ```
58
68
 
69
+ A page with no dynamic params and no fetched SEO fields puts `/* seo */` directly under `/* page */`.
70
+
59
71
  ## Script ordering (pages)
60
72
 
61
- 1. `/* page */` — `definePageMeta`, then `useHead` (head may move below data when title depends on fetched data)
62
- 2. Route / params (`useRoute`, reactive param computeds)
63
- 3. Domain sections (`/* data */`, forms, etc.) including `useUFetch`
64
- 4. Watchers / lifecycle
65
- 5. Handlers (`handleXxx`)
73
+ 1. `/* page */` — only `definePageMeta`
74
+ 2. `/* params */` — only when the page reads `route.params` or `route.query` (see Page params)
75
+ 3. Domain sections the SEO block needs (`useUFetch`, derived data, )
76
+ 4. `/* seo */` — required on every page (see Page SEO)
77
+ 5. Remaining domain sections (forms, sessions, UI state, …)
78
+ 6. Watchers / lifecycle
79
+ 7. Handlers (`handleXxx`)
66
80
 
67
- ## `definePageMeta`
81
+ Do **not** put `useHead` / `useSeoMeta` / `useJsonld` inside `/* page */`. Those belong in `/* seo */`.
68
82
 
69
- - **Always** set an explicit `name`
70
- - Dot notation: `dashboard.home`, `flash-cards.single`, `authentication.login`
71
- - `layout: 'empty'` for login / full-bleed auth-style pages only
72
- - Never use `layout: false`
83
+ ## Page params
84
+
85
+ If a page reads `route.params` or `route.query`, add a `/* params */` block **immediately under** `/* page */`.
86
+
87
+ 1. Declare `const route = useRoute();`
88
+ 2. Two blank lines
89
+ 3. One `computed` per value — `route.params` first, then `route.query` — keep each reactive; do not snapshot into a bare `const`
90
+
91
+ Consecutive computeds: **one** blank line between them.
73
92
 
74
93
  ```ts
75
- definePageMeta({
76
- name: 'authentication.login',
77
- layout: 'empty',
94
+ /* params */
95
+
96
+ const route = useRoute();
97
+
98
+
99
+ const flashCardSlug = computed(() => {
100
+ return route.params.flashCardSlug;
78
101
  });
79
102
  ```
80
103
 
81
- ## Dynamic routes
104
+ ```ts
105
+ /* params */
82
106
 
83
- - Files: `[patientUid].vue`, `[flashCardSlug]/index.vue`
84
- - Params: **camelCase** in brackets and when reading `route.params`
85
- - Keep params **reactive** for async workflows — do not snapshot once into a bare `const`:
107
+ const route = useRoute();
108
+
109
+
110
+ const returnUrl = computed(() => {
111
+ return route.query.returnUrl;
112
+ });
113
+ ```
86
114
 
87
115
  ```ts
116
+ /* params */
117
+
88
118
  const route = useRoute();
89
119
 
120
+
90
121
  const flashCardSlug = computed(() => {
91
122
  return route.params.flashCardSlug;
92
123
  });
124
+
125
+ const journeySlug = computed(() => {
126
+ return route.query.journey;
127
+ });
128
+ ```
129
+
130
+ - Files: `[patientUid].vue`, `[flashCardSlug]/index.vue`
131
+ - Params: **camelCase** in brackets and when reading `route.params`
132
+ - Query keys stay as they appear on the URL; the computed name is camelCase (`returnUrl` for `returnUrl`)
133
+ - Omit `/* params */` entirely on pages that read neither `route.params` nor `route.query` — do not create an empty block. If something else needs `route` (for example `route.fullPath`), call `useRoute()` in that domain section instead.
134
+
135
+ ## Page SEO
136
+
137
+ Every page **must** have a `/* seo */` block.
138
+
139
+ **Placement**
140
+
141
+ 1. Default: directly under `/* page */`
142
+ 2. If the page has `/* params */`: under `/* params */`
143
+ 3. If `/* seo */` needs data from any other block: under **that** block (after the fetch / derived computeds), not above it
144
+
145
+ Blocks that SEO does not read (sessions, local UI state, handlers) stay **below** `/* seo */`.
146
+
147
+ **Contents (in this order, one blank line between calls)**
148
+
149
+ 1. `useHead({ title })` — required
150
+ 2. `useSeoMeta({ description })` — required
151
+ 3. `useJsonld(() => …)` — only when the project has `nuxt-jsonld` set up (`package.json` / `nuxt.config` modules), and only on public indexable pages. Skip it on `noindex` / dashboard / auth pages.
152
+
153
+ Static strings when the copy is fixed; getters when the value comes from fetched data (`() => flashCardData.value?.name`). Optional chaining is fine on title/description getters.
154
+
155
+ ```ts
156
+ /* seo */
157
+
158
+ useHead({
159
+ title: 'Flash Cards',
160
+ });
161
+
162
+ useSeoMeta({
163
+ description: 'Browse free flash card decks for practice and study.',
164
+ });
165
+ ```
166
+
167
+ ```ts
168
+ /* seo */
169
+
170
+ useHead({
171
+ title: () => flashCardData.value?.name,
172
+ });
173
+
174
+ useSeoMeta({
175
+ description: () => flashCardData.value?.description,
176
+ });
177
+ ```
178
+
179
+ **`useJsonld` data guard**
180
+
181
+ When JSON-LD is included, inline the schema in the page (no shared `makeXxxJsonld` helpers). Guard absent data with a ternary — return `null` so no tag is emitted; do **not** replace this guard with optional chaining inside the object:
182
+
183
+ ```ts
184
+ useJsonld(() => !flashCardData.value ? null : {
185
+ '@context': 'https://schema.org',
186
+ '@graph': [
187
+ {
188
+ '@type': 'LearningResource',
189
+ 'name': flashCardData.value.name,
190
+ 'url': `https://khoshghadam.com/flash-cards/${flashCardData.value.slug}`,
191
+ },
192
+ ],
193
+ });
194
+ ```
195
+
196
+ Keep `useJsonld(() => !data ? null : {` on **one line**. Object properties follow the usual multi-line literal rules. `data` is the fetched document or list the schema reads.
197
+
198
+ ## `definePageMeta`
199
+
200
+ - **Always** set an explicit `name`
201
+ - Dot notation: `dashboard.home`, `flash-cards.single`, `authentication.login`
202
+ - `layout: 'empty'` for login / full-bleed auth-style pages only
203
+ - Never use `layout: false`
204
+
205
+ ```ts
206
+ definePageMeta({
207
+ name: 'authentication.login',
208
+ layout: 'empty',
209
+ });
93
210
  ```
94
211
 
95
212
  ## Navigation