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
@@ -0,0 +1,155 @@
1
+ import { computed, inject, type ComputedRef, type InjectionKey } from 'vue'
2
+
3
+ /**
4
+ * Der einzige Draht zwischen den BaseKit-Komponenten und der Anwendung,
5
+ * die sie einsetzt.
6
+ *
7
+ * Die Komponenten sollen in jedem Nuxt-Projekt laufen. Sie dürfen deshalb
8
+ * weder `vue-i18n` aufrufen noch Übersetzungsschlüssel kennen: `t('common.search')`
9
+ * gibt es nur in der einen Anwendung, die diesen Schlüssel führt, und ein
10
+ * Paket, das solche Schlüssel voraussetzt, ist keins.
11
+ *
12
+ * Stattdessen reicht die Anwendung Sprache und Beschriftungen einmal herein —
13
+ * in einem Plugin, das die Werte aus ihrer eigenen Quelle zieht:
14
+ *
15
+ * // plugins/basekit.ts
16
+ * export default defineNuxtPlugin((nuxtApp) => {
17
+ * const config = computed(() => ({
18
+ * locale: 'de-DE',
19
+ * labels: { ...BASEKIT_DEFAULTS.labels, search: 'Suchen' },
20
+ * }))
21
+ * nuxtApp.vueApp.provide(baseKitKey, config)
22
+ * })
23
+ *
24
+ * Wer nichts bereitstellt, bekommt die Voreinstellungen unten. Wichtig, wenn
25
+ * die Werte aus einer i18n-Bibliothek kommen: `useI18n()` verlangt einen
26
+ * Komponenten-Setup-Kontext und wirft im Plugin. Die Instanz gehört über
27
+ * `nuxtApp.$i18n` geholt, und zwar erst beim Lesen des `computed`.
28
+ *
29
+ * Über `provide`/`inject`, nicht über `useNuxtApp()`: so laufen die
30
+ * Komponenten auch in einem Vitest-Mount ohne Nuxt-Kontext, und unter SSR
31
+ * teilen sich zwei Anfragen nichts.
32
+ *
33
+ * Einzelne Beschriftungen bleiben weiterhin als Prop überschreibbar — hier
34
+ * steht nur, was ohne Zutun herauskommt.
35
+ */
36
+ export interface BaseKitLabels {
37
+ /** Suchfeld über Listen und Auswahl-Dialogen. */
38
+ search: string
39
+ /** Filter-Eintrag „ohne Einschränkung". */
40
+ all: string
41
+ select: string
42
+ change: string
43
+ edit: string
44
+ cancel: string
45
+ confirm: string
46
+ /** Überschrift der Rückfrage, wenn der Aufrufer keine mitgibt. */
47
+ confirmTitle: string
48
+ /** Text der Rückfrage, wenn der Aufrufer keinen mitgibt. */
49
+ confirmBody: string
50
+ /** Leere Liste — es gibt noch nichts. */
51
+ empty: string
52
+ /** Leere Liste — die Suche greift, findet aber nichts. */
53
+ noResults: string
54
+ noResultsHint: string
55
+ back: string
56
+ view: string
57
+ upload: string
58
+ perPage: string
59
+ iconChoose: string
60
+ iconEmpty: string
61
+ /** Auswahl aufheben — im Symbolwähler die leere Wahl. */
62
+ iconClear: string
63
+ chartAsTable: string
64
+ chartAsChart: string
65
+ /** „1–25 von 300" — als Funktion, weil die Zahlen mitten im Satz stehen. */
66
+ paginationRange: (range: { from: number, to: number, total: number }) => string
67
+ /**
68
+ * Werkzeugleiste des Markdown-Editors. Eigene Gruppe, weil die Begriffe nur
69
+ * dort vorkommen und die obere Ebene sonst zur Hälfte aus ihnen bestünde.
70
+ */
71
+ markdown: {
72
+ bold: string
73
+ italic: string
74
+ h2: string
75
+ h3: string
76
+ bullet: string
77
+ ordered: string
78
+ quote: string
79
+ code: string
80
+ link: string
81
+ /** Abfrage beim Setzen eines Links. */
82
+ linkPrompt: string
83
+ }
84
+ }
85
+
86
+ export interface BaseKitConfig {
87
+ /**
88
+ * BCP-47-Kennung für `Intl` — steuert Tausendertrennung, Prozent- und
89
+ * Datumsformate in den Diagrammen.
90
+ */
91
+ locale: string
92
+ labels: BaseKitLabels
93
+ }
94
+
95
+ /**
96
+ * Voreinstellung: deutsch. Alle Projekte im Haus sind es, und eine Anwendung
97
+ * mit mehreren Sprachen überschreibt die Tabelle ohnehin komplett.
98
+ */
99
+ export const BASEKIT_DEFAULTS: BaseKitConfig = {
100
+ locale: 'de-DE',
101
+ labels: {
102
+ search: 'Suchen',
103
+ all: 'Alle',
104
+ select: 'Auswählen',
105
+ change: 'Ändern',
106
+ edit: 'Bearbeiten',
107
+ cancel: 'Abbrechen',
108
+ confirm: 'Bestätigen',
109
+ confirmTitle: 'Sind Sie sicher?',
110
+ confirmBody: 'Diese Aktion lässt sich nicht rückgängig machen.',
111
+ empty: 'Noch nichts vorhanden',
112
+ noResults: 'Keine Treffer',
113
+ noResultsHint: 'Andere Schreibweise oder weniger Filter probieren.',
114
+ back: 'Zurück zur Übersicht',
115
+ view: 'Ansehen',
116
+ upload: 'Datei wählen',
117
+ perPage: 'pro Seite',
118
+ iconChoose: 'Symbol wählen',
119
+ iconEmpty: 'Kein Symbol gefunden',
120
+ iconClear: 'Kein Symbol',
121
+ chartAsTable: 'Als Tabelle',
122
+ chartAsChart: 'Als Diagramm',
123
+ paginationRange: ({ from, to, total }) => `${from}–${to} von ${total}`,
124
+ markdown: {
125
+ bold: 'Fett',
126
+ italic: 'Kursiv',
127
+ h2: 'Überschrift 2',
128
+ h3: 'Überschrift 3',
129
+ bullet: 'Liste',
130
+ ordered: 'Nummerierte Liste',
131
+ quote: 'Zitat',
132
+ code: 'Code',
133
+ link: 'Link',
134
+ linkPrompt: 'Link-URL',
135
+ },
136
+ },
137
+ }
138
+
139
+ export const baseKitKey: InjectionKey<ComputedRef<BaseKitConfig>> = Symbol('basekit')
140
+
141
+ /**
142
+ * Konfiguration für eine BaseKit-Komponente. Ohne bereitgestellten Wert
143
+ * greifen die Voreinstellungen — die Komponente rendert dann auf Deutsch,
144
+ * statt leere Beschriftungen zu zeigen.
145
+ */
146
+ export function useBaseKit(): ComputedRef<BaseKitConfig> {
147
+ const provided = inject(baseKitKey, null)
148
+ return provided ?? computed(() => BASEKIT_DEFAULTS)
149
+ }
150
+
151
+ /** Kurzform für den häufigen Fall, dass nur die Beschriftungen gebraucht werden. */
152
+ export function useBaseKitLabels(): ComputedRef<BaseKitLabels> {
153
+ const config = useBaseKit()
154
+ return computed(() => config.value.labels)
155
+ }
@@ -0,0 +1,89 @@
1
+ import { computed } from 'vue'
2
+ import { useBaseKit } from './useBaseKit'
3
+
4
+ /**
5
+ * Reihenfarben und Zahlenformate für die `BaseKitChart*`-Komponenten.
6
+ *
7
+ * Die Farben stehen als CSS-Variablen in `main.css` und sind als **Satz**
8
+ * geprüft — Reihenfolge inklusive. Deshalb gibt es hier nur einen Zugriff per
9
+ * Index und keine Erzeugung: eine sechste Farbe wäre unter einer
10
+ * Farbfehlsichtigkeit von einer der fünf nicht mehr zu unterscheiden.
11
+ * Wer mehr Reihen hat, fasst zusammen.
12
+ */
13
+ export const BASEKIT_CHART_SLOTS = 5
14
+
15
+ /** Farbe für Reihe `index` (0-basiert). Ab Slot 6 die zurückgenommene Graustufe. */
16
+ export function baseKitChartColor(index: number): string {
17
+ return index < BASEKIT_CHART_SLOTS
18
+ ? `var(--basekit-chart-${index + 1})`
19
+ : 'var(--basekit-chart-muted)'
20
+ }
21
+
22
+ export function useChartFormat(): {
23
+ number: (value: number) => string
24
+ compact: (value: number) => string
25
+ percent: (value: number, total: number) => string
26
+ bytes: (value: number) => string
27
+ duration: (seconds: number) => string
28
+ } {
29
+ const config = useBaseKit()
30
+ const locale = computed(() => config.value.locale)
31
+
32
+ function number(value: number): string {
33
+ return new Intl.NumberFormat(locale.value).format(value)
34
+ }
35
+
36
+ /** Für Werte an Marken und in Kacheln: 12.400 → 12,4 Tsd. */
37
+ function compact(value: number): string {
38
+ return value < 10000
39
+ ? number(value)
40
+ : new Intl.NumberFormat(locale.value, { notation: 'compact', maximumFractionDigits: 1 }).format(value)
41
+ }
42
+
43
+ /**
44
+ * Anteil in Prozent. Abgerundet, solange nicht alles erreicht ist — sonst
45
+ * würde 9.999 von 10.000 als „100 %" durchgehen und die eine fehlende
46
+ * Übersetzung wäre weggerundet.
47
+ */
48
+ function percent(value: number, total: number): string {
49
+ if (total <= 0) return '—'
50
+ if (value >= total) {
51
+ return new Intl.NumberFormat(locale.value, { style: 'percent', maximumFractionDigits: 0 })
52
+ .format(1)
53
+ }
54
+
55
+ const exact = (value / total) * 100
56
+ const floored = Math.floor(exact * 10) / 10
57
+ return new Intl.NumberFormat(locale.value, {
58
+ style: 'percent',
59
+ maximumFractionDigits: 1,
60
+ }).format(floored / 100)
61
+ }
62
+
63
+ /**
64
+ * Dezimalpräfixe (kB, MB, GB) — nicht KiB/MiB. Speicheranbieter rechnen so
65
+ * ab, und die Zahl soll zu der auf der Rechnung passen.
66
+ */
67
+ function bytes(value: number): string {
68
+ const units = ['B', 'kB', 'MB', 'GB', 'TB']
69
+ let size = Math.max(0, value)
70
+ let unit = 0
71
+ while (size >= 1000 && unit < units.length - 1) {
72
+ size /= 1000
73
+ unit++
74
+ }
75
+ const digits = unit === 0 ? 0 : (size < 10 ? 1 : 0)
76
+ return `${new Intl.NumberFormat(locale.value, { maximumFractionDigits: digits }).format(size)} ${units[unit]}`
77
+ }
78
+
79
+ /** Laufzeit als „14 h 20 min", unter einer Stunde nur Minuten. */
80
+ function duration(seconds: number): string {
81
+ const total = Math.max(0, Math.round(seconds / 60))
82
+ const hours = Math.floor(total / 60)
83
+ const minutes = total % 60
84
+ if (hours === 0) return `${number(minutes)} min`
85
+ return minutes === 0 ? `${number(hours)} h` : `${number(hours)} h ${number(minutes)} min`
86
+ }
87
+
88
+ return { number, compact, percent, bytes, duration }
89
+ }
@@ -0,0 +1,41 @@
1
+ import { onBeforeUnmount, onMounted, ref, type Ref } from 'vue'
2
+
3
+ /**
4
+ * Misst die Breite eines Containers, damit ein SVG in echten Pixeln zeichnen
5
+ * kann statt über eine skalierte `viewBox`.
6
+ *
7
+ * Der Unterschied ist die Schrift: eine `viewBox`, die auf Containerbreite
8
+ * hochgerechnet wird, zieht Achsenbeschriftung und Werte mit hoch — auf einer
9
+ * breiten Karte steht dann 15-px-Text, wo 11 gemeint waren. Mit gemessener
10
+ * Breite bleibt Text Text und nur die Geometrie wächst.
11
+ *
12
+ * Vor dem Mounten (SSR, erster Frame) gilt `fallback`. Das Diagramm ist damit
13
+ * sofort da und rückt einmal zurecht, statt zu flackern.
14
+ */
15
+ export function useChartWidth(fallback = 640): {
16
+ el: Ref<HTMLElement | null>
17
+ width: Ref<number>
18
+ } {
19
+ const el = ref<HTMLElement | null>(null)
20
+ const width = ref(fallback)
21
+ let observer: ResizeObserver | null = null
22
+
23
+ onMounted(() => {
24
+ if (!el.value || typeof ResizeObserver === 'undefined') return
25
+
26
+ observer = new ResizeObserver((entries) => {
27
+ const measured = entries[0]?.contentRect.width ?? 0
28
+ // Unter 240 px wird die Geometrie unbrauchbar; dann lieber schmal
29
+ // zeichnen und den Container scrollen lassen.
30
+ if (measured > 0) width.value = Math.max(240, Math.round(measured))
31
+ })
32
+ observer.observe(el.value)
33
+ })
34
+
35
+ onBeforeUnmount(() => {
36
+ observer?.disconnect()
37
+ observer = null
38
+ })
39
+
40
+ return { el, width }
41
+ }
@@ -0,0 +1,16 @@
1
+ import { useConfirmStore } from '../stores/confirm'
2
+ import type { BaseKitConfirmOptions } from '../stores/confirm'
3
+
4
+ /**
5
+ * Bestätigungsabfrage als Ersatz für natives `window.confirm`.
6
+ *
7
+ * Öffnet das global gemountete `BaseKitConfirmModal` und liefert ein Promise,
8
+ * das mit `true`/`false` auflöst.
9
+ *
10
+ * const confirm = useConfirm()
11
+ * if (!(await confirm({ description: t('…'), color: 'error', confirmLabel: t('common.delete') }))) return
12
+ */
13
+ export function useConfirm(): (opts?: BaseKitConfirmOptions) => Promise<boolean> {
14
+ const store = useConfirmStore()
15
+ return (opts?: BaseKitConfirmOptions) => store.ask(opts)
16
+ }
@@ -0,0 +1,56 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref } from 'vue'
3
+
4
+ /** Optionen für eine Bestätigungsabfrage. */
5
+ export interface BaseKitConfirmOptions {
6
+ /** Titel des Dialogs (Default: `common.confirm.title`). */
7
+ title?: string
8
+ /** Erklärtext / Konsequenz — bei Destruktivem ausformulieren. */
9
+ description?: string
10
+ /** Beschriftung des Bestätigen-Buttons (Default: `common.confirm.confirm`). */
11
+ confirmLabel?: string
12
+ /** Beschriftung des Abbrechen-Buttons (Default: `common.cancel`). */
13
+ cancelLabel?: string
14
+ /** Farbe des Bestätigen-Buttons — `error` für destruktive Aktionen. */
15
+ color?: 'error' | 'primary'
16
+ /** Optionales Icon am Bestätigen-Button. */
17
+ icon?: string
18
+ }
19
+
20
+ /**
21
+ * Zentraler Bestätigungs-Store — ersetzt native `window.confirm`-Dialoge durch
22
+ * eine gethemte, dark-mode-fähige Abfrage.
23
+ *
24
+ * `ask()` öffnet das global gemountete `BaseKitConfirmModal` und liefert ein
25
+ * Promise, das mit `true` (bestätigt) oder `false` (abgebrochen/geschlossen)
26
+ * auflöst. Der Resolver lebt außerhalb der Reaktivität, damit genau eine
27
+ * Antwort pro Abfrage zurückgeht.
28
+ *
29
+ * Genutzt über das Composable `useConfirm()`:
30
+ * const confirm = useConfirm()
31
+ * if (!(await confirm({ description: t('…'), color: 'error' }))) return
32
+ */
33
+ export const useConfirmStore = defineStore('basekit:confirm', () => {
34
+ const open = ref(false)
35
+ const options = ref<BaseKitConfirmOptions>({})
36
+ let resolver: ((value: boolean) => void) | null = null
37
+
38
+ function ask(opts: BaseKitConfirmOptions = {}): Promise<boolean> {
39
+ // Läuft noch eine Abfrage, wird sie als abgebrochen aufgelöst.
40
+ resolver?.(false)
41
+ options.value = opts
42
+ open.value = true
43
+ return new Promise<boolean>((resolve) => {
44
+ resolver = resolve
45
+ })
46
+ }
47
+
48
+ /** Abfrage beantworten und Dialog schließen. */
49
+ function settle(value: boolean): void {
50
+ open.value = false
51
+ resolver?.(value)
52
+ resolver = null
53
+ }
54
+
55
+ return { open, options, ask, settle }
56
+ })
@@ -0,0 +1,47 @@
1
+ /**
2
+ * HTML → Markdown (turndown). Zwei Einsätze, beide nur im Admin/Editor — daher
3
+ * bewusst getrennt von `markdown.ts` (das im öffentlichen Widget-Bundle steckt
4
+ * und turndown nicht mitziehen soll):
5
+ * - Tiptap speichert HTML → hier nach Markdown serialisieren.
6
+ * - Legacy-Text-Blöcke mit rohem `config.html` einmalig nach Markdown wandeln.
7
+ *
8
+ * **turndown wird bewusst erst beim ersten Aufruf geladen.** Das Paket ist
9
+ * CommonJS; ein Import auf Modulebene landet im Server-Bundle und wirft dort
10
+ * beim Rendern „require is not defined in ES module scope". Der Editor läuft
11
+ * ohnehin nur im Browser — auf dem Server wird die Funktion nie aufgerufen.
12
+ */
13
+ type Turndown = { turndown: (html: string) => string }
14
+
15
+ let instance: Turndown | null = null
16
+
17
+ function service(): Turndown | null {
18
+ if (instance) return instance
19
+ if (import.meta.server) return null
20
+
21
+ // Synchroner Zugriff auf ein bereits geladenes Modul: der Editor ruft
22
+ // `prepare()` beim Einhängen auf, bevor gespeichert werden kann.
23
+ return instance
24
+ }
25
+
26
+ /**
27
+ * Lädt turndown im Browser vor. Die Editor-Komponenten rufen das beim
28
+ * Einhängen auf, damit {@link htmlToMarkdown} danach synchron bleiben kann.
29
+ */
30
+ export async function prepareHtmlToMarkdown(): Promise<void> {
31
+ if (instance || import.meta.server) return
32
+ const { default: TurndownService } = await import('turndown')
33
+ instance = new TurndownService({
34
+ headingStyle: 'atx',
35
+ codeBlockStyle: 'fenced',
36
+ bulletListMarker: '-',
37
+ }) as Turndown
38
+ }
39
+
40
+ export function htmlToMarkdown(html?: string | null): string {
41
+ if (!html) return ''
42
+ const td = service()
43
+ // Ohne geladenes turndown (Server, oder prepare() vergessen) lieber das
44
+ // Original zurückgeben als eine Ausnahme zu werfen — der Text ginge sonst
45
+ // beim Speichern verloren.
46
+ return td ? td.turndown(String(html)).trim() : String(html)
47
+ }
@@ -0,0 +1,24 @@
1
+ import MarkdownIt from 'markdown-it'
2
+
3
+ /**
4
+ * Markdown → HTML für den Text-Block (`richtext`-Widget, jetzt Markdown).
5
+ *
6
+ * Bewusst `html: false`: roher HTML im Markdown wird NICHT durchgereicht — das
7
+ * schließt die wichtigste XSS-Lücke ohne separaten Sanitizer (markdown-it
8
+ * blockt zudem `javascript:`-Links per Default-`validateLink`). `linkify` macht
9
+ * nackte URLs klickbar, `typographer` glättet Anführungszeichen/Bindestriche.
10
+ *
11
+ * Geteilt zwischen Anzeige und Editor-Vorschau, damit
12
+ * Bearbeiten und Ausgabe identisch aussehen.
13
+ */
14
+ const md: MarkdownIt = new MarkdownIt({
15
+ html: false,
16
+ linkify: true,
17
+ typographer: true,
18
+ breaks: false,
19
+ })
20
+
21
+ export function renderMarkdown(source?: string | null): string {
22
+ if (!source) return ''
23
+ return md.render(String(source))
24
+ }
package/nuxt.config.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { fileURLToPath } from 'node:url'
2
+
3
+ /**
4
+ * BaseKit als Nuxt-Layer.
5
+ *
6
+ * Der Layer registriert die Komponenten global und meldet die Composables und
7
+ * den Store für die Auto-Imports an. Er bringt bewusst **kein** UI-Modul mit:
8
+ * `@nuxt/ui` und `pinia` sind Peers, weil sie im Konsumenten ohnehin stehen
9
+ * und zwei Instanzen desselben Moduls sich in die Quere kommen.
10
+ *
11
+ * Die CSS-Datei trägt die Voreinstellungen der `--basekit-*`-Token. Wer eigene
12
+ * Farben führt, überschreibt sie in seinem eigenen Stylesheet — das nach
13
+ * diesem geladen wird.
14
+ */
15
+ export default defineNuxtConfig({
16
+ $meta: {
17
+ name: 'nuxt-ui-basekit',
18
+ },
19
+
20
+ css: ['nuxt-ui-basekit/app/assets/css/basekit.css'],
21
+
22
+ // Ohne Pfad-Präfix registrieren: `app/components/charts/BaseKitChartDonut.vue`
23
+ // wird zu `<BaseKitChartDonut>`, nicht `<ChartsBaseKitChartDonut>`.
24
+ // Absoluter Pfad, weil `~/components` im Konsumenten auf dessen Ordner
25
+ // zeigen würde.
26
+ components: [
27
+ {
28
+ path: fileURLToPath(new URL('./app/components', import.meta.url)),
29
+ pathPrefix: false,
30
+ },
31
+ ],
32
+
33
+ imports: {
34
+ dirs: [
35
+ fileURLToPath(new URL('./app/composables', import.meta.url)),
36
+ fileURLToPath(new URL('./app/stores', import.meta.url)),
37
+ ],
38
+ },
39
+
40
+ alias: {
41
+ 'nuxt-ui-basekit': fileURLToPath(new URL('./', import.meta.url)),
42
+ },
43
+ })
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "nuxt-ui-basekit",
3
+ "version": "0.1.0",
4
+ "description": "Markenfreie Basis-Komponenten für Nuxt 4 auf Nuxt UI — Tabellen, Reiter, Diagramme, Auswahl und Leerzustände als Nuxt-Layer.",
5
+ "keywords": [
6
+ "nuxt",
7
+ "nuxt-layer",
8
+ "nuxt-ui",
9
+ "vue",
10
+ "components",
11
+ "charts",
12
+ "data-table"
13
+ ],
14
+ "type": "module",
15
+ "engines": {
16
+ "node": ">=24"
17
+ },
18
+ "main": "./nuxt.config.ts",
19
+ "files": [
20
+ "app",
21
+ "nuxt.config.ts",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "exports": {
26
+ ".": {
27
+ "types": "./nuxt.config.ts",
28
+ "import": "./nuxt.config.ts"
29
+ },
30
+ "./labels": "./app/composables/useBaseKit.ts",
31
+ "./app/*.vue": "./app/*.vue",
32
+ "./app/*.css": "./app/*.css",
33
+ "./app/*": "./app/*.ts",
34
+ "./package.json": "./package.json"
35
+ },
36
+ "scripts": {
37
+ "dev:prepare": "nuxi prepare",
38
+ "lint": "nuxi typecheck",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest"
41
+ },
42
+ "peerDependencies": {
43
+ "@nuxt/ui": "^4.0.0",
44
+ "nuxt": "^4.0.0",
45
+ "pinia": "^2.2.0 || ^3.0.0"
46
+ },
47
+ "dependencies": {
48
+ "@tiptap/extension-link": "^3.27.0",
49
+ "@tiptap/pm": "^3.27.0",
50
+ "@tiptap/starter-kit": "^3.27.0",
51
+ "@tiptap/vue-3": "^3.27.0",
52
+ "markdown-it": "^14.2.0",
53
+ "turndown": "^7.2.4"
54
+ },
55
+ "devDependencies": {
56
+ "@nuxt/ui": "^4.7.1",
57
+ "@types/markdown-it": "^14.1.2",
58
+ "@types/turndown": "^5.0.6",
59
+ "@vitejs/plugin-vue": "^6.0.6",
60
+ "@vue/test-utils": "^2.4.10",
61
+ "happy-dom": "^20.9.0",
62
+ "nuxt": "^4.0.0",
63
+ "pinia": "^2.2.4",
64
+ "typescript": "^5.5.0",
65
+ "vitest": "^4.1.6",
66
+ "vue-tsc": "^2.1.0"
67
+ },
68
+ "repository": {
69
+ "type": "git",
70
+ "url": "git+https://github.com/McGo/nuxt-ui-basekit.git"
71
+ },
72
+ "homepage": "https://github.com/McGo/nuxt-ui-basekit#readme",
73
+ "bugs": {
74
+ "url": "https://github.com/McGo/nuxt-ui-basekit/issues"
75
+ },
76
+ "author": "Mirko Haaser <kontakt@mirko-haaser.de>",
77
+ "license": "MIT"
78
+ }