nuxt-ui-basekit 0.1.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.
Files changed (31) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/app/assets/css/basekit.css +86 -0
  4. package/app/components/BaseKitBackLink.vue +41 -0
  5. package/app/components/BaseKitChoiceCard.vue +107 -0
  6. package/app/components/BaseKitConfirmModal.vue +55 -0
  7. package/app/components/BaseKitDataTable.vue +293 -0
  8. package/app/components/BaseKitEmptyState.vue +39 -0
  9. package/app/components/BaseKitFileUpload.vue +90 -0
  10. package/app/components/BaseKitIconPicker.vue +130 -0
  11. package/app/components/BaseKitMarkdownEditor.vue +112 -0
  12. package/app/components/BaseKitPending.vue +54 -0
  13. package/app/components/BaseKitRecordPicker.vue +154 -0
  14. package/app/components/BaseKitSettingRow.vue +66 -0
  15. package/app/components/BaseKitStatTile.vue +64 -0
  16. package/app/components/BaseKitTabs.vue +161 -0
  17. package/app/components/BaseKitViewLink.vue +53 -0
  18. package/app/components/charts/BaseKitChartBars.vue +87 -0
  19. package/app/components/charts/BaseKitChartColumns.vue +319 -0
  20. package/app/components/charts/BaseKitChartDonut.vue +175 -0
  21. package/app/components/charts/BaseKitChartFigure.vue +87 -0
  22. package/app/components/charts/BaseKitChartMeter.vue +57 -0
  23. package/app/composables/useBaseKit.ts +155 -0
  24. package/app/composables/useChartPalette.ts +89 -0
  25. package/app/composables/useChartWidth.ts +41 -0
  26. package/app/composables/useConfirm.ts +16 -0
  27. package/app/stores/confirm.ts +56 -0
  28. package/app/utils/html-to-markdown.ts +47 -0
  29. package/app/utils/markdown.ts +24 -0
  30. package/nuxt.config.ts +43 -0
  31. package/package.json +78 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mirko Haaser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # nuxt-ui-basekit
2
+
3
+ Basis-Komponenten für Nuxt 4 auf [Nuxt UI](https://ui.nuxt.com) — als Nuxt-Layer.
4
+ Tabellen, Reiter, Diagramme, Auswahl-Dialoge, Leerzustände: die Bausteine, die
5
+ in jedem Verwaltungs-Frontend wieder anfallen und die Nuxt UI nicht mitbringt.
6
+
7
+ Das Paket kennt keine Anwendung. Es hat keine Stores außer einem für die
8
+ Rückfrage, keine Endpunkte, keine Übersetzungsschlüssel und keine Farben, die
9
+ zu einer Marke gehören. Was es anzeigt, bekommt es hereingereicht.
10
+
11
+ ## Einbinden
12
+
13
+ ```bash
14
+ npm i -D nuxt-ui-basekit
15
+ ```
16
+
17
+ ```ts
18
+ // nuxt.config.ts
19
+ export default defineNuxtConfig({
20
+ extends: ['nuxt-ui-basekit'],
21
+ })
22
+ ```
23
+
24
+ `@nuxt/ui`, `nuxt` und `pinia` sind Peers, sie stehen im Konsumenten und
25
+ werden nicht mitgeliefert. Die Komponenten sind danach global verfügbar, ohne
26
+ Import.
27
+
28
+ ## Was drin ist
29
+
30
+ | Gruppe | Komponenten |
31
+ |---|---|
32
+ | Struktur | `BaseKitTabs` · `BaseKitSettingRow` · `BaseKitEmptyState` · `BaseKitChoiceCard` · `BaseKitPending` |
33
+ | Listen | `BaseKitDataTable` · `BaseKitStatTile` |
34
+ | Auswahl | `BaseKitRecordPicker` · `BaseKitIconPicker` · `BaseKitFileUpload` |
35
+ | Wege | `BaseKitBackLink` · `BaseKitViewLink` |
36
+ | Rückfrage | `BaseKitConfirmModal` + `useConfirm()` |
37
+ | Text | `BaseKitMarkdownEditor` |
38
+ | Diagramme | `BaseKitChartBars` · `-Columns` · `-Donut` · `-Meter` · `-Figure` |
39
+
40
+ Jede Komponente trägt ihre Erklärung im Kopf der Datei — was sie tut, wann sie
41
+ die richtige Wahl ist und wann nicht.
42
+
43
+ ## Beschriftungen und Sprache
44
+
45
+ Die Komponenten rufen kein `vue-i18n` auf und kennen keine
46
+ Übersetzungsschlüssel. Ohne Zutun rendern sie deutsche Voreinstellungen; wer
47
+ eigene Texte oder mehrere Sprachen führt, reicht sie über ein Plugin herein:
48
+
49
+ ```ts
50
+ // plugins/basekit.ts
51
+ import { computed } from 'vue'
52
+ import { baseKitKey, BASEKIT_DEFAULTS } from 'nuxt-ui-basekit/labels'
53
+
54
+ export default defineNuxtPlugin((nuxtApp) => {
55
+ const config = computed(() => {
56
+ const { t, locale } = nuxtApp.$i18n as Composer
57
+
58
+ return {
59
+ locale: locale.value,
60
+ labels: { ...BASEKIT_DEFAULTS.labels, search: t('common.search') },
61
+ }
62
+ })
63
+
64
+ nuxtApp.vueApp.provide(baseKitKey, config)
65
+ })
66
+ ```
67
+
68
+ Zwei Dinge, die dabei Zeit kosten können:
69
+
70
+ - **`useI18n()` gehört nicht ins Plugin.** Es verlangt einen
71
+ Komponenten-Setup-Kontext und wirft dort `MUST_BE_CALL_SETUP_TOP`
72
+ (Fehlercode 26). Beim serverseitigen Rendern heißt das: 500 auf jeder Seite.
73
+ Die Instanz kommt über `nuxtApp.$i18n`, und zwar erst beim Lesen des
74
+ `computed` — dann ist auch die Plugin-Reihenfolge gleichgültig.
75
+ - **Einzelne Beschriftungen** bleiben als Prop überschreibbar. Die Tabelle
76
+ trägt nur, was ohne Angabe herauskommt.
77
+
78
+ ## Farben
79
+
80
+ Die Komponenten greifen ausschließlich auf `--basekit-*` zu. Die
81
+ Voreinstellungen stehen in `app/assets/css/basekit.css` und werden vom Layer
82
+ geladen. Ein Projekt mit eigenem Theme legt sie in seinem eigenen Stylesheet
83
+ darauf:
84
+
85
+ ```css
86
+ :root {
87
+ --basekit-accent: var(--meine-markenfarbe, #2563eb);
88
+ --basekit-surface: var(--meine-kartenflaeche, #ffffff);
89
+ }
90
+ ```
91
+
92
+ Die fünf Diagrammfarben sind als Satz geprüft — Lichtheitsband, Chroma und
93
+ Abstand zwischen Nachbarn, auch unter Farbfehlsichtigkeit. Wer sie
94
+ überschreibt, sollte das als Satz tun und nicht einzeln; wer mehr als fünf
95
+ Reihen hat, fasst zusammen, statt eine sechste Farbe zu erfinden.
96
+
97
+ ## Entwickeln
98
+
99
+ ```bash
100
+ npm install
101
+ npx nuxi prepare # erzeugt .nuxt/tsconfig.json, sonst laufen die Tests nicht
102
+ npm test
103
+ npm run lint # nuxi typecheck
104
+ ```
105
+
106
+ `tests/grenze.test.ts` hält fest, was das Paket nicht darf: keine Importe
107
+ außerhalb des Pakets, keine Abhängigkeit, die nicht in der `package.json`
108
+ steht, kein `vue-i18n`, keine Übersetzungsschlüssel, keine fremden
109
+ CSS-Variablen. Der Test ist der Grund, warum sich das Paket überhaupt
110
+ herausschneiden ließ — ohne ihn wäre die Trennung nach dem dritten Feature
111
+ wieder zu.
112
+
113
+ ### Lockfile
114
+
115
+ Wer auf macOS oder Windows eine Abhängigkeit hinzufügt, muss das Lockfile
116
+ anschließend einmal unter Linux/x64 nachziehen — sonst fehlen die
117
+ plattformspezifischen Optional-Pakete (`@emnapi/*` und Verwandtschaft), und
118
+ `npm ci` im Workflow bricht ab:
119
+
120
+ ```bash
121
+ docker run --rm --platform linux/amd64 -v "$PWD:/app" -w /app \
122
+ node:24 npm install --package-lock-only
123
+ ```
124
+
125
+ Die Architektur ist nicht gleichgültig: auf Apple Silicon läuft der Container
126
+ ohne `--platform` als arm64, und dann fehlen genau die x64-Varianten, die der
127
+ Runner braucht. Danach lokal einmal `npm ci`, das holt die eigenen Binaries
128
+ zurück.
129
+
130
+ ## Veröffentlichen
131
+
132
+ Zwei Workflows unter `.github/workflows`. `pruefen` läuft auf `main` und in
133
+ jedem Pull Request und führt `nuxi prepare`, `nuxi typecheck` und die Tests
134
+ aus. `veroeffentlichen` hängt an einem Tag `vX.Y.Z`, prüft noch einmal und
135
+ schiebt das Paket dann nach npmjs.com. Ein Build-Schritt fehlt, weil der Layer
136
+ als Rohquelle ausgeliefert wird und Nuxt ihn im Konsumenten übersetzt.
137
+
138
+ Eine neue Fassung:
139
+
140
+ ```bash
141
+ npm version patch # hebt die package.json und setzt den Tag
142
+ git push --follow-tags
143
+ ```
144
+
145
+ Der Job vergleicht den Tag mit der Zahl in der `package.json` und bricht ab,
146
+ wenn die beiden auseinanderlaufen. Veröffentlicht wird mit `--provenance`:
147
+ auf npmjs steht dann nachprüfbar, aus welchem Commit das Paket stammt.
148
+
149
+ Dafür braucht das Repo ein Secret `NPM_TOKEN` — ein Automation-Token aus dem
150
+ npm-Konto, einzutragen unter *Settings → Secrets and variables → Actions*.
151
+
152
+ ## Herkunft
153
+
154
+ Entstanden in einem Verwaltungs-Frontend und dort über Monate in Gebrauch,
155
+ bevor es hier ein eigenes Paket wurde.
156
+
157
+ ## Lizenz
158
+
159
+ MIT — siehe [LICENSE](LICENSE).
@@ -0,0 +1,86 @@
1
+ /* ==========================================================================
2
+ * BaseKit — Gestaltungs-Token
3
+ *
4
+ * Die Komponenten greifen ausschließlich auf `--basekit-*` zu und nie auf
5
+ * Variablen einer bestimmten Anwendung. Hier stehen die Voreinstellungen.
6
+ *
7
+ * Eine Anwendung mit eigenem Theme legt die Token an einer Stelle auf ihre
8
+ * eigenen Variablen — ein Block in ihrem Stylesheet, geladen nach diesem:
9
+ *
10
+ * :root {
11
+ * --basekit-accent: var(--meine-markenfarbe, #2563eb);
12
+ * --basekit-surface: var(--meine-kartenflaeche, #ffffff);
13
+ * }
14
+ *
15
+ * Fällt der Block weg, rendern die Komponenten in den Werten von hier weiter.
16
+ * ========================================================================== */
17
+
18
+ :root {
19
+ --basekit-surface: #ffffff;
20
+ --basekit-surface-muted: #f4f6fa;
21
+ --basekit-border: #e5e8ee;
22
+ --basekit-border-strong: #d6dbe4;
23
+ --basekit-text: #101828;
24
+ --basekit-text-muted: #667085;
25
+ --basekit-accent: #2563eb;
26
+ --basekit-accent-text: #ffffff;
27
+ --basekit-chrome: #0b2239;
28
+ --basekit-chrome-text: rgb(255 255 255 / 82%);
29
+ }
30
+
31
+ .dark {
32
+ --basekit-surface: #101828;
33
+ --basekit-surface-muted: #0b1220;
34
+ --basekit-border: #1f2937;
35
+ --basekit-border-strong: #374151;
36
+ --basekit-text: #e5e7eb;
37
+ --basekit-text-muted: #9ca3af;
38
+ }
39
+
40
+ /* --------------------------------------------------------------------------
41
+ * Diagramm-Farben (BaseKitChart*)
42
+ *
43
+ * Bewusst **nicht** aus der Markenfarbe abgeleitet. Ein Theme darf jede
44
+ * Akzentfarbe tragen — vier davon nebeneinander in einer gestapelten Fläche
45
+ * ergäben Reihen, die man nicht mehr auseinanderhält, und bei einer
46
+ * Rot-Grün-Schwäche gar nichts mehr. Die fünf Reihenfarben stehen deshalb
47
+ * fest und sind gegen die Kartenfläche geprüft (Lichtheitsband, Chroma,
48
+ * CVD-Abstand ΔE ≥ 8 zwischen Nachbarn, Normalsicht ΔE ≥ 15).
49
+ *
50
+ * Reihenfolge ist Teil der Prüfung: wer sie umsortiert oder eine sechste
51
+ * Farbe dazustellt, muss neu messen. Über fünf Reihen fasst die Übersicht
52
+ * zusammen, statt eine Farbe zu erfinden.
53
+ *
54
+ * Ein Kundenprojekt darf die Werte überschreiben — dann aber bitte als
55
+ * geprüften Satz und nicht einzeln.
56
+ * ------------------------------------------------------------------------ */
57
+ :root {
58
+ --basekit-chart-1: #2a78d6;
59
+ --basekit-chart-2: #eb6834;
60
+ --basekit-chart-3: #1baf7a;
61
+ --basekit-chart-4: #eda100;
62
+ --basekit-chart-5: #e87ba4;
63
+
64
+ /* Die Fläche, in der ein Diagramm sitzt. Trägt die 2-px-Lücke zwischen
65
+ * gestapelten Segmenten und den Ring um Punkte — deshalb muss sie der
66
+ * Kartenfarbe entsprechen und nicht „irgendwie weiß" sein. */
67
+ --basekit-chart-surface: var(--ui-bg, #ffffff);
68
+ --basekit-chart-grid: rgb(0 0 0 / 8%);
69
+ --basekit-chart-axis: rgb(0 0 0 / 18%);
70
+ /* Zurückgenommene Reihe: Kontext, nicht Aussage. */
71
+ --basekit-chart-muted: rgb(0 0 0 / 22%);
72
+ }
73
+
74
+ .dark {
75
+ --basekit-chart-1: #3987e5;
76
+ --basekit-chart-2: #d95926;
77
+ --basekit-chart-3: #199e70;
78
+ --basekit-chart-4: #c98500;
79
+ --basekit-chart-5: #d55181;
80
+
81
+ --basekit-chart-surface: var(--ui-bg, #1e293b);
82
+ --basekit-chart-grid: rgb(255 255 255 / 10%);
83
+ --basekit-chart-axis: rgb(255 255 255 / 22%);
84
+ --basekit-chart-muted: rgb(255 255 255 / 28%);
85
+ }
86
+
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Rücksprung von einer Detailseite zur zugehörigen Liste.
4
+ *
5
+ * Steht immer an derselben Stelle: oben rechts im Seitenkopf. Vorher hatte
6
+ * das jede Seite anders gelöst — mal ein Knopf über der Überschrift, mal ein
7
+ * kleiner Link darüber, mal ein Knopf rechts im Kopf, und beschriftet war er
8
+ * mal mit dem Namen der Liste („Rubriken"), mal mit „Zurück". Wer zwischen
9
+ * zwei Bereichen wechselt, sucht den Weg zurück dann jedes Mal neu.
10
+ *
11
+ * Die Beschriftung ist deshalb fest und nennt nicht das Ziel, sondern die
12
+ * Richtung. `label` überschreibt sie für die Fälle, in denen es nicht zu
13
+ * einer Liste zurückgeht, sondern zum übergeordneten Datensatz.
14
+ */
15
+ import { computed } from 'vue'
16
+ import { useBaseKitLabels } from '../composables/useBaseKit'
17
+
18
+ const props = withDefaults(defineProps<{
19
+ to: string
20
+ label?: string
21
+ }>(), {
22
+ label: undefined,
23
+ })
24
+
25
+ const labels = useBaseKitLabels()
26
+
27
+ const text = computed(() => props.label ?? labels.value.back)
28
+ </script>
29
+
30
+ <template>
31
+ <UButton
32
+ :to="props.to"
33
+ icon="i-lucide-arrow-left"
34
+ size="sm"
35
+ color="neutral"
36
+ variant="ghost"
37
+ class="shrink-0"
38
+ >
39
+ {{ text }}
40
+ </UButton>
41
+ </template>
@@ -0,0 +1,107 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Auswahl-Karte — Pattern für „aus einer Handvoll Varianten eine wählen“,
4
+ * wenn die Varianten sich zeigen lassen (Themes, Layouts, Vorlagen).
5
+ * Ein Dropdown zeigt nur Namen; hier steht die Vorschau daneben.
6
+ *
7
+ * ┌──────────────────────────┐
8
+ * │ │ ← Slot `preview` (stilisierte Vorschau)
9
+ * │ Vorschau │
10
+ * ├──────────────────────────┤
11
+ * │ Titel [ Aktiv ] │ ← aktiv: Badge, sonst Knopf „Übernehmen“
12
+ * │ Beschreibung │
13
+ * └──────────────────────────┘
14
+ *
15
+ * Verwendung (Karten in einem Grid, eine pro Variante):
16
+ *
17
+ * <div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
18
+ * <BaseKitChoiceCard
19
+ * v-for="o in options"
20
+ * :key="o.id"
21
+ * :title="o.label"
22
+ * :active="o.id === activeId"
23
+ * :active-label="t('common.active')"
24
+ * :apply-label="t('common.apply')"
25
+ * :pending="pending"
26
+ * @apply="apply(o.id)"
27
+ * >
28
+ * <template #preview><MeineVorschau :id="o.id" /></template>
29
+ * </BaseKitChoiceCard>
30
+ * </div>
31
+ *
32
+ * Die Karte selbst löst nichts aus — gewählt wird über den Knopf. Bei einer
33
+ * Auswahl, die sofort für alle gilt, ist ein Klick daneben sonst schnell
34
+ * passiert.
35
+ */
36
+ withDefaults(defineProps<{
37
+ /** Name der Variante. */
38
+ title: string
39
+ /** Kurze Erläuterung unter dem Titel. */
40
+ description?: string
41
+ /** Diese Variante ist die aktive. */
42
+ active?: boolean
43
+ /** Läuft gerade ein Speichervorgang (sperrt den Knopf). */
44
+ pending?: boolean
45
+ /** Beschriftung des Knopfes für „diese Variante übernehmen“. */
46
+ applyLabel?: string
47
+ /** Beschriftung des Aktiv-Kennzeichens. */
48
+ activeLabel?: string
49
+ /** Seitenverhältnis der Vorschaufläche. */
50
+ ratio?: string
51
+ }>(), {
52
+ description: undefined,
53
+ active: false,
54
+ pending: false,
55
+ applyLabel: 'Übernehmen',
56
+ activeLabel: 'Aktiv',
57
+ ratio: 'aspect-[16/10]',
58
+ })
59
+
60
+ defineEmits<{ apply: [] }>()
61
+ </script>
62
+
63
+ <template>
64
+ <div
65
+ class="basekit-choice-card overflow-hidden rounded-lg border bg-white transition-colors dark:bg-neutral-900"
66
+ :class="active
67
+ ? 'border-primary-500 ring-1 ring-primary-500'
68
+ : 'border-neutral-200 dark:border-neutral-800'"
69
+ :aria-current="active ? 'true' : undefined"
70
+ >
71
+ <div :class="['overflow-hidden border-b border-neutral-200 dark:border-neutral-800', ratio]">
72
+ <slot name="preview" />
73
+ </div>
74
+
75
+ <div class="flex items-start justify-between gap-3 p-3">
76
+ <div class="min-w-0">
77
+ <h3 class="truncate text-sm font-medium">
78
+ {{ title }}
79
+ </h3>
80
+ <p v-if="description" class="mt-0.5 text-xs text-neutral-500">
81
+ {{ description }}
82
+ </p>
83
+ </div>
84
+
85
+ <UBadge
86
+ v-if="active"
87
+ color="primary"
88
+ variant="subtle"
89
+ icon="i-lucide-check"
90
+ class="shrink-0"
91
+ >
92
+ {{ activeLabel }}
93
+ </UBadge>
94
+ <UButton
95
+ v-else
96
+ size="xs"
97
+ color="neutral"
98
+ variant="subtle"
99
+ class="shrink-0"
100
+ :loading="pending"
101
+ @click="$emit('apply')"
102
+ >
103
+ {{ applyLabel }}
104
+ </UButton>
105
+ </div>
106
+ </div>
107
+ </template>
@@ -0,0 +1,55 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Global gemountetes Bestätigungs-Modal — wird vom `basekit:confirm`-Store
4
+ * gesteuert (siehe `useConfirm()`). Genau eine Instanz pro Layout genügt,
5
+ * sie beantwortet alle Abfragen.
6
+ *
7
+ * Ersetzt native `window.confirm`-Dialoge: gethemt, dark-mode-fähig,
8
+ * destruktive Aktionen als roter Bestätigen-Button. Schließen ohne Klick
9
+ * (Escape/Backdrop) zählt als Abbruch.
10
+ */
11
+ import { computed } from 'vue'
12
+ import { useBaseKitLabels } from '../composables/useBaseKit'
13
+ import { useConfirmStore } from '../stores/confirm'
14
+
15
+ const store = useConfirmStore()
16
+ const labels = useBaseKitLabels()
17
+
18
+ const opts = computed(() => store.options)
19
+
20
+ const open = computed({
21
+ get: () => store.open,
22
+ set: (value: boolean) => {
23
+ // Backdrop-/Escape-Schließen ohne Bestätigung → Abbruch.
24
+ if (!value) store.settle(false)
25
+ },
26
+ })
27
+ </script>
28
+
29
+ <template>
30
+ <UModal
31
+ :open="open"
32
+ :title="opts.title ?? labels.confirmTitle"
33
+ :ui="{ content: 'max-w-md' }"
34
+ @update:open="open = $event"
35
+ >
36
+ <template #body>
37
+ <p class="text-sm text-muted">
38
+ {{ opts.description ?? labels.confirmBody }}
39
+ </p>
40
+ <div class="mt-6 flex justify-end gap-2">
41
+ <UButton type="button" color="neutral" variant="ghost" @click="store.settle(false)">
42
+ {{ opts.cancelLabel ?? labels.cancel }}
43
+ </UButton>
44
+ <UButton
45
+ type="button"
46
+ :color="opts.color ?? 'primary'"
47
+ :icon="opts.icon"
48
+ @click="store.settle(true)"
49
+ >
50
+ {{ opts.confirmLabel ?? labels.confirm }}
51
+ </UButton>
52
+ </div>
53
+ </template>
54
+ </UModal>
55
+ </template>