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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-unified-ui",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.5.1",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./index.d.ts",
7
7
  "exports": {
@@ -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, and the
8
- whitespace/formatting/code-shape conventions for all Nuxt-generated code. Use
9
- when working in or consuming nuxt-unified-ui, or whenever generating Vue/Nuxt
10
- code that must match unified code style.
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. Visual props: `variant`, `color`, `size`, `icon`, static `label`
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`/`:disabled` → `:items` → `class` → `v-model` → events
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
+ ```