nuxt-unified-ui 0.5.0 → 0.5.1
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
|
@@ -4,10 +4,11 @@ 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,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
launchChoicePickerDialog, toast helpers, un-card / un-typography, pages /
|
|
8
|
+
definePageMeta, ufetch / useUFetch wrapping, attribute order/defaults, and
|
|
9
|
+
whitespace/formatting conventions for all Nuxt-generated code. Use when
|
|
10
|
+
working in or consuming nuxt-unified-ui, or whenever generating Vue/Nuxt code
|
|
11
|
+
that must match unified code style.
|
|
11
12
|
---
|
|
12
13
|
|
|
13
14
|
# nuxt-unified-ui
|
|
@@ -29,6 +30,8 @@ This is the **only** installable skill in this repo. Deep topics live under `ref
|
|
|
29
30
|
| Topic | File |
|
|
30
31
|
|-------|------|
|
|
31
32
|
| **Code style (mandatory)** | [references/code-style.md](references/code-style.md) |
|
|
33
|
+
| Pages / routing | [references/pages.md](references/pages.md) |
|
|
34
|
+
| Data fetching (`ufetch` / `useUFetch`) | [references/data-fetching.md](references/data-fetching.md) |
|
|
32
35
|
| Layer install + required CSS | [references/layer-setup.md](references/layer-setup.md) |
|
|
33
36
|
| Public surface inventory | [references/public-surface.md](references/public-surface.md) |
|
|
34
37
|
| Forms (`useForm` / `un-form`) | [references/forms.md](references/forms.md) |
|
|
@@ -55,10 +58,12 @@ Absolute highlights:
|
|
|
55
58
|
- Script object literals always multi-line (even one property)
|
|
56
59
|
- Kebab-case tags (`u-button`, `un-card`)
|
|
57
60
|
- `v-if` / `v-for` on `<template>` wrappers — not on rendered nodes
|
|
58
|
-
- **2+ attributes → one per line**; non-self-closing `>` on same line as last attr; multi-line self-closing `/>` on its own line
|
|
61
|
+
- **2+ attributes → one per line**; attribute order + default omissions (`variant="subtle"`, omit neutral `color`, `loading-auto`); non-self-closing `>` on same line as last attr; multi-line self-closing `/>` on its own line
|
|
59
62
|
- `{{ ... }}` on its own line
|
|
60
63
|
- `/* section */` comments; imports co-located under the section that uses them
|
|
61
64
|
- Light naming: `handleXxx` handlers, `it` in short callbacks, descriptive `for...of`, computeds use block + `return`
|
|
65
|
+
- Pages: explicit `definePageMeta.name`, reactive route params, named `navigateTo` / `nuxt-link` → [pages.md](references/pages.md)
|
|
66
|
+
- Fetching: `ufetch(url, {` one line; `useUFetch` with URL on next line + `data*Data` / `is*Loading` / `refresh*` → [data-fetching.md](references/data-fetching.md)
|
|
62
67
|
|
|
63
68
|
---
|
|
64
69
|
|
|
@@ -132,6 +137,8 @@ From `nuxt.config.ts`: `@vueuse/nuxt`, `@nuxt/ui`, `@nuxtjs/i18n`; `ui.colorMode
|
|
|
132
137
|
| Page chrome | `un-typography` + `un-card` |
|
|
133
138
|
| Custom field | `registerFormExtraElement` in a plugin |
|
|
134
139
|
| Utilities | `radXxx` → [radashi.md](references/radashi.md) |
|
|
140
|
+
| New page / route | [pages.md](references/pages.md) |
|
|
141
|
+
| List/detail fetch or mutation | [data-fetching.md](references/data-fetching.md) |
|
|
135
142
|
| Formatting any of the above | [code-style.md](references/code-style.md) |
|
|
136
143
|
|
|
137
144
|
## Do / don’t
|
|
@@ -330,18 +330,64 @@ Keep tight `v-if` / `v-else` chains adjacent (no blank line between matching bra
|
|
|
330
330
|
When wrapping, order attributes as:
|
|
331
331
|
|
|
332
332
|
1. Refs / identity: `ref`, `id`, `name`
|
|
333
|
-
2.
|
|
333
|
+
2. Component visual props: `variant`, `color`, `size`, `icon`, static `label`
|
|
334
334
|
3. Static presentation: `class`, `style`
|
|
335
335
|
4. Data bindings: `:items`, `:data`, `:placeholder`, `:value`, dynamic `:label`, …
|
|
336
336
|
5. `v-model` / `:model-value` / `v-model:*`
|
|
337
|
-
6. Navigation / state: `to`, `href`, `block`, `disabled`, `loading`, `loading-auto`, `fluid-body`, …
|
|
337
|
+
6. Navigation / state: `to`, `href`, `block`, `disabled`, `loading`, `loading-auto`, `fluid-body`, `scrollable`, …
|
|
338
338
|
7. Events last: `@click`, `@update:*`, …
|
|
339
339
|
|
|
340
340
|
Practical shortcuts:
|
|
341
341
|
|
|
342
342
|
- `u-button`: `variant` → `color` → `size` → `icon` → label/value → `block` → `disabled` → `loading-auto` → events
|
|
343
|
-
- `u-input` / `u-select*`: user-facing props → `:loading
|
|
343
|
+
- `u-input` / `u-select*`: user-facing props (`:placeholder`, `:label`) → transient state (`:loading`, `:disabled`) → data source (`:items`) → `class` → `v-model` → events
|
|
344
|
+
- Action objects (`:actions`, `:append-actions`, table row actions): `vIf` → `color` → `icon` → `label` → `tooltip` → `disabled` → `to` → `onClick`
|
|
345
|
+
- Tab / select item objects: `value` → `icon` → `label`
|
|
344
346
|
|
|
347
|
+
### Default attribute values (omit noise)
|
|
348
|
+
|
|
349
|
+
Prefer defaults by **omitting** props rather than restating them:
|
|
350
|
+
|
|
351
|
+
| Component / context | Default convention |
|
|
352
|
+
|---------------------|--------------------|
|
|
353
|
+
| `u-button` | Prefer `variant="subtle"` over `ghost` for new buttons |
|
|
354
|
+
| Async `u-button` clicks | Prefer `loading-auto` over hand-rolled `isLoading` when nothing else depends on that flag |
|
|
355
|
+
| `u-badge` | Always `variant="subtle"`; use `icon` + `:label` (no default slot text); **do not** set `size`; **omit** `color` for neutral (use `undefined` in ternaries, never `color="neutral"`) |
|
|
356
|
+
| `u-tooltip` | Do not set `:delay-duration` — use the default delay |
|
|
357
|
+
| Action objects where subtle is the local default | Omit `variant: 'subtle'` unless overriding |
|
|
358
|
+
| Icons | Always `lucide:*` prefix |
|
|
359
|
+
|
|
360
|
+
```vue
|
|
361
|
+
<!-- ✅ omit neutral color; subtle badge -->
|
|
362
|
+
<u-badge
|
|
363
|
+
variant="subtle"
|
|
364
|
+
:label="item.name"
|
|
365
|
+
/>
|
|
366
|
+
|
|
367
|
+
<!-- ✅ ternary falls back with undefined -->
|
|
368
|
+
<u-badge
|
|
369
|
+
variant="subtle"
|
|
370
|
+
:color="item.digital ? 'info' : undefined"
|
|
371
|
+
:label="item.digital ? 'Digital' : 'Physical'"
|
|
372
|
+
/>
|
|
373
|
+
|
|
374
|
+
<!-- ❌ restating neutral / wrong variant -->
|
|
375
|
+
<u-badge
|
|
376
|
+
variant="outline"
|
|
377
|
+
color="neutral"
|
|
378
|
+
:label="item.name"
|
|
379
|
+
/>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
```vue
|
|
383
|
+
<!-- ✅ async button -->
|
|
384
|
+
<u-button
|
|
385
|
+
variant="subtle"
|
|
386
|
+
icon="lucide:trash"
|
|
387
|
+
loading-auto
|
|
388
|
+
@click="handleDelete()"
|
|
389
|
+
/>
|
|
390
|
+
```
|
|
345
391
|
### `>` and `/>` placement
|
|
346
392
|
|
|
347
393
|
**Non-self-closing**, multi-attribute: `>` on the **same line** as the last attribute:
|
|
@@ -408,6 +454,38 @@ Refs unwrap automatically — do not write `.value` in template expressions or i
|
|
|
408
454
|
|
|
409
455
|
---
|
|
410
456
|
|
|
457
|
+
## Pages, routing, and data fetching
|
|
458
|
+
|
|
459
|
+
These are part of the same “shape” conventions when generating app code:
|
|
460
|
+
|
|
461
|
+
- Pages / named routes / reactive params → [pages.md](pages.md)
|
|
462
|
+
- `ufetch` / `useUFetch` wrapping, naming, options order → [data-fetching.md](data-fetching.md)
|
|
463
|
+
|
|
464
|
+
### `useUFetch` wrapping (summary)
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
const { data: ordersData, pending: isOrdersLoading, refresh: refreshOrders } = useUFetch(
|
|
468
|
+
computed(() => `/api/patients/${patientUid.value}/orders`),
|
|
469
|
+
{
|
|
470
|
+
query: {
|
|
471
|
+
limit: itemsPerPage,
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
);
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
- First line: destructure + `= useUFetch(`
|
|
478
|
+
- Next line: URL (string or `computed`)
|
|
479
|
+
- Optional multi-line options object
|
|
480
|
+
- Closing `);` alone on the last line
|
|
481
|
+
- Consecutive `useUFetch` calls: **one** blank line between them
|
|
482
|
+
|
|
483
|
+
### `ufetch` wrapping (summary)
|
|
484
|
+
|
|
485
|
+
Keep `ufetch(url, {` on one line — do not break the URL above `{`.
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
411
489
|
## Light naming (reading shape)
|
|
412
490
|
|
|
413
491
|
| Context | Convention |
|
|
@@ -498,6 +576,9 @@ Leading blank line at top of file is fine when the local tree uses it. Prefer `a
|
|
|
498
576
|
| `{{ x }}` glued to tags | interpolation on its own line |
|
|
499
577
|
| Hoisted import block | imports co-located under section |
|
|
500
578
|
| `computed(() => [ ... ])` | `computed(() => { return [ ... ]; })` |
|
|
579
|
+
| `color="neutral"` on badge | omit `color` / use `undefined` |
|
|
580
|
+
| `ufetch(\n url,\n {` | `ufetch(url, {` on one line |
|
|
581
|
+
| One-line `useUFetch(...)` | URL on next line; options multi-line |
|
|
501
582
|
|
|
502
583
|
---
|
|
503
584
|
|
|
@@ -513,8 +594,10 @@ Leading blank line at top of file is fine when the local tree uses it. Prefer `a
|
|
|
513
594
|
- [ ] Kebab-case component tags
|
|
514
595
|
- [ ] `v-if` / `v-for` on `<template>` wrappers
|
|
515
596
|
- [ ] 2+ attributes → one per line; `>` same line as last attr; multi-line self-closing `/>` on own line
|
|
516
|
-
- [ ] Attribute order respected
|
|
597
|
+
- [ ] Attribute order + default-value omissions respected (`subtle`, no neutral color noise, `loading-auto`)
|
|
517
598
|
- [ ] `{{ }}` on own line
|
|
518
599
|
- [ ] Section comments + import co-location where the file has sections
|
|
519
600
|
- [ ] `handleXxx` for action handlers; `it` for short callbacks; descriptive loop names
|
|
520
601
|
- [ ] Computeds that return structures use block + `return`
|
|
602
|
+
- [ ] Pages: explicit `definePageMeta.name`, reactive route params, named navigation ([pages.md](pages.md))
|
|
603
|
+
- [ ] Fetching: `ufetch` / `useUFetch` wrap styles and destructure names ([data-fetching.md](data-fetching.md))
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Data fetching (`ufetch` / `useUFetch`)
|
|
2
|
+
|
|
3
|
+
Prefer host-app unified API helpers (`ufetch`, `useUFetch`) over raw `useFetch` / `useAsyncData` / `$fetch` for app API calls. Formatting rules below are mandatory; wire paths to whatever your app’s API plugin expects (relative paths — do not manually prepend `baseApiUrl`).
|
|
4
|
+
|
|
5
|
+
Combine with [code-style.md](code-style.md).
|
|
6
|
+
|
|
7
|
+
## `ufetch` (imperative)
|
|
8
|
+
|
|
9
|
+
One-off requests (submit, delete, button click):
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const response = await ufetch(`/api/resources/${id}`, {
|
|
13
|
+
silent: true,
|
|
14
|
+
method: 'post',
|
|
15
|
+
body: {
|
|
16
|
+
field: value,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Rules:
|
|
22
|
+
|
|
23
|
+
- Name the result `response` (not `result`), unless a more specific name is clearer (`loginResponse`)
|
|
24
|
+
- Inline `body` / `query` objects in the options — extract only when large/reused
|
|
25
|
+
- `silent: true` — suppress automatic error toast when handling errors locally
|
|
26
|
+
- `responseType: 'blob'` — file downloads
|
|
27
|
+
|
|
28
|
+
### Options order
|
|
29
|
+
|
|
30
|
+
- Mutations: behavior flags (`silent`, `responseType`) → `method` → `body` → `query` / other
|
|
31
|
+
- Reads without `method`/`body`: behavior flags before `query`
|
|
32
|
+
|
|
33
|
+
### Call wrapping (critical)
|
|
34
|
+
|
|
35
|
+
Keep `ufetch(url, {` on **one line**. Do not break the URL onto its own line above `{`:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// ✅
|
|
39
|
+
const response = await ufetch(`/api/items/${item.uid}/documents`, {
|
|
40
|
+
silent: true,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ❌
|
|
44
|
+
const response = await ufetch(
|
|
45
|
+
`/api/items/${item.uid}/documents`,
|
|
46
|
+
{
|
|
47
|
+
silent: true,
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### After mutation
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const response = await ufetch(url, {
|
|
56
|
+
method: 'post',
|
|
57
|
+
body: {
|
|
58
|
+
field: value,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
await refresh();
|
|
64
|
+
|
|
65
|
+
toastSuccess({
|
|
66
|
+
title: 'Created successfully.',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
formValue.value = '';
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Call `refresh()` **before** resetting local form UI state. Put side effects in dialog button `onClick` when the mutation is launched from a picker (see [dialogs.md](dialogs.md)).
|
|
73
|
+
|
|
74
|
+
### Response guards
|
|
75
|
+
|
|
76
|
+
Fail-fast after `ufetch`:
|
|
77
|
+
|
|
78
|
+
1. Special non-success statuses first when relevant
|
|
79
|
+
2. Invalid success → early `return toastError({ ... })`
|
|
80
|
+
3. Success path without deep `else` nesting
|
|
81
|
+
|
|
82
|
+
Prefer direct access on `response` (`response.status`) over optional chaining when the call is expected to return a body.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## `useUFetch` (reactive)
|
|
87
|
+
|
|
88
|
+
For route/param/reactive-driven lists and detail loads:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const { data: ordersData, pending: isOrdersPending, refresh: refreshOrders } = useUFetch(
|
|
92
|
+
computed(() => `/api/patients/${patientUid.value}/orders`),
|
|
93
|
+
{
|
|
94
|
+
query: {
|
|
95
|
+
page: computed(() => currentPage.value - 1),
|
|
96
|
+
limit: itemsPerPage,
|
|
97
|
+
search: searchTerm,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Wrapping style (critical)
|
|
104
|
+
|
|
105
|
+
Always format as:
|
|
106
|
+
|
|
107
|
+
1. `const { ... } = useUFetch(` on the first line
|
|
108
|
+
2. URL argument on the next line (string **or** `computed(() => ...)`)
|
|
109
|
+
3. Optional options object as a multi-line second argument
|
|
110
|
+
4. Closing `);` on its own line
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// ✅ string URL + options
|
|
114
|
+
const { data: mediaData, pending: isMediaPending, refresh: refreshMedia } = useUFetch(
|
|
115
|
+
'/api/media',
|
|
116
|
+
{
|
|
117
|
+
query: {
|
|
118
|
+
'sort': '_id:-1',
|
|
119
|
+
'limit': itemsPerPage,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// ✅ computed URL only
|
|
125
|
+
const { data: patientData, pending: isPatientPending, refresh: refreshPatient } = useUFetch(
|
|
126
|
+
computed(() => `/api/patients/${patientUid.value}`),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// ❌ crammed one-liner or URL broken like ufetch's wrong form without the useUFetch indent pattern
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Destructuring names
|
|
133
|
+
|
|
134
|
+
| Key | Convention | Example |
|
|
135
|
+
|-----|------------|---------|
|
|
136
|
+
| `data` | suffix `Data` | `ordersData`, `flashCardsData` |
|
|
137
|
+
| `pending` | `is` + name + `Pending` | `isOrdersPending` |
|
|
138
|
+
| `refresh` | prefix `refresh` | `refreshOrders` |
|
|
139
|
+
|
|
140
|
+
### Spacing between consecutive calls
|
|
141
|
+
|
|
142
|
+
When several `useUFetch` calls sit in the same section, use **one** blank line between them (not two):
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const { data: mediaData, pending: isMediaPending, refresh: refreshMedia } = useUFetch(
|
|
146
|
+
'/api/media',
|
|
147
|
+
{
|
|
148
|
+
query: {
|
|
149
|
+
'limit': itemsPerPage,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const { data: mediaCountData, refresh: refreshMediaCount } = useUFetch(
|
|
155
|
+
'/api/media/count',
|
|
156
|
+
);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Major section boundaries elsewhere still use double blanks.
|
|
160
|
+
|
|
161
|
+
### Conditional fetching
|
|
162
|
+
|
|
163
|
+
When the request must wait on a prop/id, prefer a reactive gate (e.g. reactive `method` or `enabled`) over one-shot `immediate: !!prop` evaluated only at mount:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const { data: itemsData, pending: isItemsPending, refresh: refreshItems } = useUFetch(
|
|
167
|
+
computed(() => `/api/groups/${props.groupUid}/items`),
|
|
168
|
+
{
|
|
169
|
+
method: computed(() => props.groupUid ? 'get' : ''),
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Do **not** use `{ immediate: !!props.groupUid }` when the dependency can appear later.
|
|
175
|
+
|
|
176
|
+
### Query values
|
|
177
|
+
|
|
178
|
+
- Refs and computeds are fine inside `query`
|
|
179
|
+
- Keep query object multi-line with trailing commas
|
|
180
|
+
- Quoted keys are fine when matching API conventions (`'filter'`, `'sort'`)
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Do / don’t
|
|
185
|
+
|
|
186
|
+
**Do**
|
|
187
|
+
|
|
188
|
+
- Use `ufetch` / `useUFetch` for app API traffic
|
|
189
|
+
- Keep `ufetch(url, {` on one line; wrap `useUFetch` with URL on the next line
|
|
190
|
+
- Refresh lists before clearing local form state after mutations
|
|
191
|
+
|
|
192
|
+
**Don’t**
|
|
193
|
+
|
|
194
|
+
- Reach for raw `$fetch` / `useFetch` for the same app API
|
|
195
|
+
- Manually prepend `baseApiUrl`
|
|
196
|
+
- Break `useUFetch` destructuring into ad-hoc formatting that hides the URL argument
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Pages and routing
|
|
2
|
+
|
|
3
|
+
Page shape conventions for Nuxt apps using this stack. Always combine with [code-style.md](code-style.md).
|
|
4
|
+
|
|
5
|
+
## Page script skeleton
|
|
6
|
+
|
|
7
|
+
```vue
|
|
8
|
+
<script setup>
|
|
9
|
+
|
|
10
|
+
/* page */
|
|
11
|
+
|
|
12
|
+
definePageMeta({
|
|
13
|
+
name: 'dashboard.patients.single',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
useHead({
|
|
17
|
+
title: $t('patients.single.pageTitle'),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
const route = useRoute();
|
|
22
|
+
|
|
23
|
+
const patientUid = computed(() => {
|
|
24
|
+
return route.params.patientUid;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/* data */
|
|
29
|
+
|
|
30
|
+
const { data: patientData, pending: isPatientLoading, refresh: refreshPatient } = useUFetch(
|
|
31
|
+
computed(() => `/api/patients/${patientUid.value}`),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
/* handlers */
|
|
36
|
+
|
|
37
|
+
async function handleAction() {
|
|
38
|
+
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<div>
|
|
48
|
+
|
|
49
|
+
<h1 class="text-2xl font-semibold">
|
|
50
|
+
{{ $t('patients.single.title') }}
|
|
51
|
+
</h1>
|
|
52
|
+
|
|
53
|
+
<!-- content -->
|
|
54
|
+
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Script ordering (pages)
|
|
60
|
+
|
|
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`)
|
|
66
|
+
|
|
67
|
+
## `definePageMeta`
|
|
68
|
+
|
|
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`
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
definePageMeta({
|
|
76
|
+
name: 'authentication.login',
|
|
77
|
+
layout: 'empty',
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Dynamic routes
|
|
82
|
+
|
|
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`:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const route = useRoute();
|
|
89
|
+
|
|
90
|
+
const flashCardSlug = computed(() => {
|
|
91
|
+
return route.params.flashCardSlug;
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Navigation
|
|
96
|
+
|
|
97
|
+
Always prefer **named routes**:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
await navigateTo({
|
|
101
|
+
name: 'authentication.account',
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```vue
|
|
106
|
+
<nuxt-link
|
|
107
|
+
:to="{
|
|
108
|
+
name: 'flash-cards.single',
|
|
109
|
+
params: {
|
|
110
|
+
flashCardSlug,
|
|
111
|
+
},
|
|
112
|
+
}">
|
|
113
|
+
...
|
|
114
|
+
</nuxt-link>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Navigation in action objects
|
|
118
|
+
|
|
119
|
+
When an action only navigates, use `to` — not `onClick: () => navigateTo(...)`:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
{
|
|
123
|
+
icon: 'lucide:arrow-left',
|
|
124
|
+
label: 'Back',
|
|
125
|
+
to: {
|
|
126
|
+
name: 'orders.single',
|
|
127
|
+
params: {
|
|
128
|
+
orderUid,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Page headings
|
|
135
|
+
|
|
136
|
+
- Primary title: `h1` with `class="text-2xl font-semibold"` (match local siblings if they consistently differ)
|
|
137
|
+
- Subtitle / secondary line: size-based hierarchy (`text-sm` / `text-xs`) — see text-hierarchy rules in [code-style.md](code-style.md)
|
|
138
|
+
|
|
139
|
+
## Item / tab object property order
|
|
140
|
+
|
|
141
|
+
For objects in `:items` arrays (tabs, selects, menus):
|
|
142
|
+
|
|
143
|
+
`value` → `icon` → `label` → other props
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
{
|
|
147
|
+
value: 'preview',
|
|
148
|
+
icon: 'lucide:eye',
|
|
149
|
+
label: $t('section.preview'),
|
|
150
|
+
}
|
|
151
|
+
```
|