@menumalsuite/design-system 0.0.3 → 0.0.5

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.
@@ -0,0 +1,476 @@
1
+ /* AUTO-GENERATED — non editare manualmente. Usa: node scripts/generate-theme.js */
2
+ @import "tailwindcss";
3
+ @import "@nuxt/ui";
4
+
5
+ /*
6
+ * AUTO-GENERATED — DO NOT EDIT MANUALLY
7
+ *
8
+ * Bidirectional sync via GitFig:
9
+ * Figma → GitHub: designer edits variables in Figma, GitFig pushes tokens/,
10
+ * GitHub Action regenerates this file automatically.
11
+ * GitHub → Figma: developer edits tokens/*.json, pushes to main,
12
+ * designer opens GitFig in Figma → Pull to sync variables.
13
+ *
14
+ * To regenerate manually: node scripts/generate-theme.js
15
+ * Source of truth: tokens/ (W3C DTCG format)
16
+ * Last updated: 2026-05-18T07:55:55.828Z
17
+ */
18
+
19
+ @theme {
20
+ /* ================================================
21
+ FONTS & TYPOGRAPHY
22
+ ================================================ */
23
+ /* --- Font Sizes --- */
24
+ --text-lg: 1.125rem;
25
+ --text-sm: 0.875rem;
26
+ --text-xl: 1.25rem;
27
+ --text-xs: 0.75rem;
28
+ --text-2xl: 1.5rem;
29
+ --text-3xl: 1.875rem;
30
+ --text-4xl: 2.25rem;
31
+ --text-5xl: 3rem;
32
+ --text-6xl: 3.75rem;
33
+ --text-7xl: 4.5rem;
34
+ --text-8xl: 6rem;
35
+ --text-9xl: 8rem;
36
+ --text-base: 1rem;
37
+
38
+ /* --- Fonts --- */
39
+ --font-mono: 'Roboto Mono', monospace;
40
+ --font-sans: 'Inter', sans-serif;
41
+ --font-serif: 'Georgia', serif;
42
+
43
+ /* --- Font Weights --- */
44
+ --font-weight-bold: 700;
45
+ --font-weight-thin: 100;
46
+ --font-weight-black: 900;
47
+ --font-weight-light: 300;
48
+ --font-weight-medium: 500;
49
+ --font-weight-normal: 400;
50
+ --font-weight-semibold: 600;
51
+ --font-weight-extrabold: 800;
52
+ --font-weight-extralight: 200;
53
+
54
+ /* --- Line Heights --- */
55
+ --leading-1: 1rem;
56
+ --leading-2: 1.25rem;
57
+ --leading-3: 1.5rem;
58
+ --leading-4: 1.75rem;
59
+ --leading-5: 2rem;
60
+ --leading-6: 2.25rem;
61
+ --leading-7: 2.5rem;
62
+ --leading-8: 3rem;
63
+ --leading-9: 3rem;
64
+ --leading-10: 3.75rem;
65
+ --leading-11: 4.5rem;
66
+ --leading-12: 6rem;
67
+ --leading-13: 8rem;
68
+
69
+ /* --- Letter Spacing --- */
70
+ --tracking-wide: 0.02500000037252903em;
71
+ --tracking-tight: -0.02500000037252903em;
72
+ --tracking-wider: 0.05000000074505806em;
73
+ --tracking-normal: 0em;
74
+ --tracking-widest: 0.10000000149011612em;
75
+ --tracking-tighter: -0.05000000074505806em;
76
+
77
+ /* ================================================
78
+ SPACING
79
+ ================================================ */
80
+ --spacing-0: 0px;
81
+ --spacing-1: 0.25rem;
82
+ --spacing-2: 0.5rem;
83
+ --spacing-3: 0.75rem;
84
+ --spacing-4: 1rem;
85
+ --spacing-5: 1.25rem;
86
+ --spacing-6: 1.5rem;
87
+ --spacing-7: 1.75rem;
88
+ --spacing-8: 2rem;
89
+ --spacing-9: 2.25rem;
90
+ --spacing-10: 2.5rem;
91
+ --spacing-11: 2.75rem;
92
+ --spacing-12: 3rem;
93
+ --spacing-14: 3.5rem;
94
+ --spacing-16: 4rem;
95
+ --spacing-20: 5rem;
96
+ --spacing-24: 6rem;
97
+ --spacing-28: 7rem;
98
+ --spacing-32: 8rem;
99
+ --spacing-36: 9rem;
100
+ --spacing-40: 10rem;
101
+ --spacing-44: 11rem;
102
+ --spacing-48: 12rem;
103
+ --spacing-52: 13rem;
104
+ --spacing-56: 14rem;
105
+ --spacing-60: 15rem;
106
+ --spacing-64: 16rem;
107
+ --spacing-72: 18rem;
108
+ --spacing-80: 20rem;
109
+ --spacing-96: 24rem;
110
+ --spacing-0-5: 0.125rem;
111
+ --spacing-1-5: 0.375rem;
112
+ --spacing-2-5: 0.625rem;
113
+ --spacing-3-5: 0.875rem;
114
+
115
+ /* ================================================
116
+ BORDER RADIUS
117
+ ================================================ */
118
+ --radius-lg: 0.5rem;
119
+ --radius-md: 0.375rem;
120
+ --radius-sm: 0.125rem;
121
+ --radius-xl: 0.75rem;
122
+ --radius-2xl: 1rem;
123
+ --radius-3xl: 1.5rem;
124
+ --radius-full: 9999px;
125
+ --radius-none: 0px;
126
+ --radius: 0.25rem;
127
+
128
+ /* ================================================
129
+ LAYOUT
130
+ ================================================ */
131
+ /* --- Container --- */
132
+ --container-lg: 32rem;
133
+ --container-md: 28rem;
134
+ --container-sm: 24rem;
135
+ --container-xl: 36rem;
136
+ --container-xs: 20rem;
137
+ --container-2xl: 42rem;
138
+ --container-3xl: 48rem;
139
+ --container-4xl: 56rem;
140
+ --container-5xl: 64rem;
141
+ --container-6xl: 72rem;
142
+ --container-7xl: 80rem;
143
+
144
+ /* --- Breakpoint --- */
145
+ --breakpoint-lg: 1024px;
146
+ --breakpoint-md: 768px;
147
+ --breakpoint-sm: 630px;
148
+ --breakpoint-xl: 1280px;
149
+ --breakpoint-2xl: 1536px;
150
+
151
+ /* ================================================
152
+ SHADOWS
153
+ ================================================ */
154
+ --shadow-lg: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
155
+ --shadow-md: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
156
+ --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
157
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
158
+ --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
159
+ --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
160
+ --shadow-2xs: 0 1px 0 0 rgb(0 0 0 / 0.05);
161
+ --shadow-none: none;
162
+ --shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
163
+ --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
164
+
165
+ /* ================================================
166
+ BLUR
167
+ ================================================ */
168
+ --blur-lg: 16px;
169
+ --blur-md: 12px;
170
+ --blur-sm: 4px;
171
+ --blur-xl: 24px;
172
+ --blur-2xl: 40px;
173
+ --blur-3xl: 64px;
174
+ --blur-none: 0px;
175
+ --blur: 8px;
176
+
177
+ /* ================================================
178
+ BORDER WIDTH
179
+ ================================================ */
180
+ --border-width-0: 0px;
181
+ --border-width-2: 2px;
182
+ --border-width-4: 4px;
183
+ --border-width-8: 8px;
184
+ --border-width: 1px;
185
+
186
+ /* ================================================
187
+ COLORS
188
+ ================================================ */
189
+ /* Red */
190
+ --color-red-50: #fef2f2;
191
+ --color-red-100: #fee2e2;
192
+ --color-red-200: #fecaca;
193
+ --color-red-300: #fca5a5;
194
+ --color-red-400: #f87171;
195
+ --color-red-500: #ef4444;
196
+ --color-red-600: #dc2626;
197
+ --color-red-700: #b91c1c;
198
+ --color-red-800: #991b1b;
199
+ --color-red-900: #7f1d1d;
200
+ --color-red-950: #450a0a;
201
+
202
+ /* Sky */
203
+ --color-sky-50: #f0f9ff;
204
+ --color-sky-100: #e0f2fe;
205
+ --color-sky-200: #bae6fd;
206
+ --color-sky-300: #7dd3fc;
207
+ --color-sky-400: #38bdf8;
208
+ --color-sky-500: #0ea5e9;
209
+ --color-sky-600: #0284c7;
210
+ --color-sky-700: #0369a1;
211
+ --color-sky-800: #075985;
212
+ --color-sky-900: #0c4a6e;
213
+ --color-sky-950: #082f49;
214
+
215
+ /* Blue */
216
+ --color-blue-50: #eff6ff;
217
+ --color-blue-100: #dbeafe;
218
+ --color-blue-200: #bfdbfe;
219
+ --color-blue-300: #93c5fd;
220
+ --color-blue-400: #60a5fa;
221
+ --color-blue-500: #3b82f6;
222
+ --color-blue-600: #2563eb;
223
+ --color-blue-700: #1d4ed8;
224
+ --color-blue-800: #1e40af;
225
+ --color-blue-900: #1e3a8a;
226
+ --color-blue-950: #172554;
227
+
228
+ /* Cyan */
229
+ --color-cyan-50: #ecfeff;
230
+ --color-cyan-100: #cffafe;
231
+ --color-cyan-200: #a5f3fc;
232
+ --color-cyan-300: #67e8f9;
233
+ --color-cyan-400: #22d3ee;
234
+ --color-cyan-500: #06b6d4;
235
+ --color-cyan-600: #0891b2;
236
+ --color-cyan-700: #0e7490;
237
+ --color-cyan-800: #155e75;
238
+ --color-cyan-900: #164e63;
239
+ --color-cyan-950: #083344;
240
+
241
+ /* Gray */
242
+ --color-gray-50: #f9fafb;
243
+ --color-gray-100: #f3f4f6;
244
+ --color-gray-200: #e5e7eb;
245
+ --color-gray-300: #d1d5db;
246
+ --color-gray-400: #9ca3af;
247
+ --color-gray-500: #6b7280;
248
+ --color-gray-600: #4b5563;
249
+ --color-gray-700: #374151;
250
+ --color-gray-800: #1f2937;
251
+ --color-gray-900: #111827;
252
+ --color-gray-950: #030712;
253
+
254
+ /* Lime */
255
+ --color-lime-50: #f7fee7;
256
+ --color-lime-100: #ecfccb;
257
+ --color-lime-200: #d9f99d;
258
+ --color-lime-300: #bef264;
259
+ --color-lime-400: #a3e635;
260
+ --color-lime-500: #84cc16;
261
+ --color-lime-600: #65a30d;
262
+ --color-lime-700: #4d7c0f;
263
+ --color-lime-800: #3f6212;
264
+ --color-lime-900: #365314;
265
+ --color-lime-950: #1a2e05;
266
+
267
+ /* Pink */
268
+ --color-pink-50: #fdf2f8;
269
+ --color-pink-100: #fce7f3;
270
+ --color-pink-200: #fbcfe8;
271
+ --color-pink-300: #f9a8d4;
272
+ --color-pink-400: #f472b6;
273
+ --color-pink-500: #ec4899;
274
+ --color-pink-600: #db2777;
275
+ --color-pink-700: #be185d;
276
+ --color-pink-800: #9d174d;
277
+ --color-pink-900: #831843;
278
+ --color-pink-950: #500724;
279
+
280
+ /* Rose */
281
+ --color-rose-50: #fff1f2;
282
+ --color-rose-100: #ffe4e6;
283
+ --color-rose-200: #fecdd3;
284
+ --color-rose-300: #fda4af;
285
+ --color-rose-400: #fb7185;
286
+ --color-rose-500: #f43f5e;
287
+ --color-rose-600: #e11d48;
288
+ --color-rose-700: #be123c;
289
+ --color-rose-800: #9f1239;
290
+ --color-rose-900: #881337;
291
+ --color-rose-950: #4c0519;
292
+
293
+ /* Teal */
294
+ --color-teal-50: #f0fdfa;
295
+ --color-teal-100: #ccfbf1;
296
+ --color-teal-200: #99f6e4;
297
+ --color-teal-300: #5eead4;
298
+ --color-teal-400: #2dd4bf;
299
+ --color-teal-500: #14b8a6;
300
+ --color-teal-600: #0d9488;
301
+ --color-teal-700: #0f766e;
302
+ --color-teal-800: #115e59;
303
+ --color-teal-900: #134e4a;
304
+ --color-teal-950: #042f2e;
305
+
306
+ /* Zinc */
307
+ --color-zinc-50: #fafafa;
308
+ --color-zinc-100: #f4f4f5;
309
+ --color-zinc-200: #e4e4e7;
310
+ --color-zinc-300: #d4d4d8;
311
+ --color-zinc-400: #a1a1aa;
312
+ --color-zinc-500: #71717a;
313
+ --color-zinc-600: #52525b;
314
+ --color-zinc-700: #3f3f46;
315
+ --color-zinc-800: #27272a;
316
+ --color-zinc-900: #18181b;
317
+ --color-zinc-950: #09090b;
318
+
319
+ /* Amber */
320
+ --color-amber-50: #fffbeb;
321
+ --color-amber-100: #fef3c7;
322
+ --color-amber-200: #fde68a;
323
+ --color-amber-300: #fcd34d;
324
+ --color-amber-400: #fbbf24;
325
+ --color-amber-500: #f59e0b;
326
+ --color-amber-600: #d97706;
327
+ --color-amber-700: #b45309;
328
+ --color-amber-800: #92400e;
329
+ --color-amber-900: #78350f;
330
+ --color-amber-950: #451a03;
331
+ --color-black: #000002;
332
+ --color-white: #ffffff;
333
+
334
+ /* Green */
335
+ --color-green-50: #f0fdf4;
336
+ --color-green-100: #dcfce7;
337
+ --color-green-200: #bbf7d0;
338
+ --color-green-300: #86efac;
339
+ --color-green-400: #4ade80;
340
+ --color-green-500: #22c55e;
341
+ --color-green-600: #16a34a;
342
+ --color-green-700: #15803d;
343
+ --color-green-800: #166534;
344
+ --color-green-900: #14532d;
345
+ --color-green-950: #052e16;
346
+
347
+ /* Slate */
348
+ --color-slate-50: #f8fafc;
349
+ --color-slate-100: #f1f5f9;
350
+ --color-slate-200: #e2e8f0;
351
+ --color-slate-300: #cbd5e1;
352
+ --color-slate-400: #94a3b8;
353
+ --color-slate-500: #64748b;
354
+ --color-slate-600: #475569;
355
+ --color-slate-700: #334155;
356
+ --color-slate-800: #1e293b;
357
+ --color-slate-900: #0f172a;
358
+ --color-slate-950: #020617;
359
+
360
+ /* Stone */
361
+ --color-stone-50: #fafaf9;
362
+ --color-stone-100: #f5f5f4;
363
+ --color-stone-200: #e7e5e4;
364
+ --color-stone-300: #d6d3d1;
365
+ --color-stone-400: #a8a29e;
366
+ --color-stone-500: #78716c;
367
+ --color-stone-600: #57534e;
368
+ --color-stone-700: #44403c;
369
+ --color-stone-800: #292524;
370
+ --color-stone-900: #1c1917;
371
+ --color-stone-950: #0c0a09;
372
+
373
+ /* Indigo */
374
+ --color-indigo-50: #eef2ff;
375
+ --color-indigo-100: #e0e7ff;
376
+ --color-indigo-200: #c7d2fe;
377
+ --color-indigo-300: #a5b4fc;
378
+ --color-indigo-400: #818cf8;
379
+ --color-indigo-500: #6366f1;
380
+ --color-indigo-600: #4f46e5;
381
+ --color-indigo-700: #4338ca;
382
+ --color-indigo-800: #3730a3;
383
+ --color-indigo-900: #312e81;
384
+ --color-indigo-950: #1e1b4b;
385
+
386
+ /* Orange */
387
+ --color-orange-50: #fff7ed;
388
+ --color-orange-100: #ffedd5;
389
+ --color-orange-200: #fed7aa;
390
+ --color-orange-300: #fdba74;
391
+ --color-orange-400: #fb923c;
392
+ --color-orange-500: #f97316;
393
+ --color-orange-600: #ea580c;
394
+ --color-orange-700: #c2410c;
395
+ --color-orange-800: #9a3412;
396
+ --color-orange-900: #7c2d12;
397
+ --color-orange-950: #431407;
398
+
399
+ /* Purple */
400
+ --color-purple-50: #faf5ff;
401
+ --color-purple-100: #f3e8ff;
402
+ --color-purple-200: #e9d5ff;
403
+ --color-purple-300: #d8b4fe;
404
+ --color-purple-400: #c084fc;
405
+ --color-purple-500: #a855f7;
406
+ --color-purple-600: #9333ea;
407
+ --color-purple-700: #7e22ce;
408
+ --color-purple-800: #6b21a8;
409
+ --color-purple-900: #581c87;
410
+ --color-purple-950: #3b0764;
411
+
412
+ /* Violet */
413
+ --color-violet-50: #f5f3ff;
414
+ --color-violet-100: #ede9fe;
415
+ --color-violet-200: #ddd6fe;
416
+ --color-violet-300: #c4b5fd;
417
+ --color-violet-400: #a78bfa;
418
+ --color-violet-500: #8b5cf6;
419
+ --color-violet-600: #7c3aed;
420
+ --color-violet-700: #6d28d9;
421
+ --color-violet-800: #5b21b6;
422
+ --color-violet-900: #4c1d95;
423
+ --color-violet-950: #2e1065;
424
+
425
+ /* Yellow */
426
+ --color-yellow-50: #fefce8;
427
+ --color-yellow-100: #fef9c3;
428
+ --color-yellow-200: #fef08a;
429
+ --color-yellow-300: #fde047;
430
+ --color-yellow-400: #facc15;
431
+ --color-yellow-500: #eab308;
432
+ --color-yellow-600: #ca8a04;
433
+ --color-yellow-700: #a16207;
434
+ --color-yellow-800: #854d0e;
435
+ --color-yellow-900: #713f12;
436
+ --color-yellow-950: #422006;
437
+
438
+ /* Emerald */
439
+ --color-emerald-50: #ecfdf5;
440
+ --color-emerald-100: #d1fae5;
441
+ --color-emerald-200: #a7f3d0;
442
+ --color-emerald-300: #6ee7b7;
443
+ --color-emerald-400: #34d399;
444
+ --color-emerald-500: #10b981;
445
+ --color-emerald-600: #059669;
446
+ --color-emerald-700: #047857;
447
+ --color-emerald-800: #065f46;
448
+ --color-emerald-900: #064e3b;
449
+ --color-emerald-950: #022c22;
450
+
451
+ /* Fuchsia */
452
+ --color-fuchsia-50: #fdf4ff;
453
+ --color-fuchsia-100: #fae8ff;
454
+ --color-fuchsia-200: #f5d0fe;
455
+ --color-fuchsia-300: #f0abfc;
456
+ --color-fuchsia-400: #e879f9;
457
+ --color-fuchsia-500: #d946ef;
458
+ --color-fuchsia-600: #c026d3;
459
+ --color-fuchsia-700: #a21caf;
460
+ --color-fuchsia-800: #86198f;
461
+ --color-fuchsia-900: #701a75;
462
+ --color-fuchsia-950: #4a044e;
463
+
464
+ /* Neutral */
465
+ --color-neutral-50: #fafafa;
466
+ --color-neutral-100: #f5f5f5;
467
+ --color-neutral-200: #e5e5e5;
468
+ --color-neutral-300: #d4d4d4;
469
+ --color-neutral-400: #a3a3a3;
470
+ --color-neutral-500: #737373;
471
+ --color-neutral-600: #525252;
472
+ --color-neutral-700: #404040;
473
+ --color-neutral-800: #262626;
474
+ --color-neutral-900: #171717;
475
+ --color-neutral-950: #0a0a0a;
476
+ }
@@ -0,0 +1,83 @@
1
+ # MButton
2
+
3
+ Thin wrapper around Nuxt UI `UButton`. All `UButton` props pass through via `v-bind="$props"`.
4
+
5
+ ## When to use
6
+
7
+ - **Primary** — main page action, one per screen
8
+ - **Secondary / neutral outline** — alternative or less urgent actions
9
+ - **Ghost** — low-emphasis, toolbars, inline actions
10
+ - **Large (`lg`/`xl`)** — prominent CTAs, hero sections
11
+ - **Small (`xs`/`sm`)** — tables, compact spaces
12
+
13
+ ## Props
14
+
15
+ Inherits all props from `UButton`. Key props:
16
+
17
+ | Prop | Type | Default | Description |
18
+ | -------------- | -------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------- |
19
+ | `label` | `string` | — | Button text |
20
+ | `color` | `'primary' \| 'secondary' \| 'success' \| 'info' \| 'warning' \| 'error' \| 'neutral'` | `'primary'` | Color variant |
21
+ | `variant` | `'solid' \| 'outline' \| 'soft' \| 'subtle' \| 'ghost' \| 'link'` | `'solid'` | Visual style |
22
+ | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Size |
23
+ | `icon` | `string` | — | Leading icon (Iconify name, e.g. `i-lucide-rocket`) |
24
+ | `trailingIcon` | `string` | — | Trailing icon |
25
+ | `square` | `boolean` | `false` | Icon-only square button |
26
+ | `loading` | `boolean` | `false` | Shows spinner, disables interaction |
27
+ | `disabled` | `boolean` | `false` | Disabled state |
28
+ | `block` | `boolean` | `false` | Full width |
29
+ | `to` | `string \| RouteLocationRaw` | — | Internal or external link |
30
+ | `target` | `string` | — | Link target (e.g. `_blank`) |
31
+
32
+ ## Slots
33
+
34
+ | Slot | Description |
35
+ | ---------- | ---------------------------------- |
36
+ | `default` | Button content (overrides `label`) |
37
+ | `leading` | Content before label |
38
+ | `trailing` | Content after label |
39
+
40
+ ## Usage
41
+
42
+ ```vue
43
+ <!-- Primary action -->
44
+ <MButton label="Save" color="primary" @click="handleSave" />
45
+
46
+ <!-- Secondary action -->
47
+ <MButton
48
+ label="Cancel"
49
+ color="neutral"
50
+ variant="outline"
51
+ @click="handleCancel"
52
+ />
53
+
54
+ <!-- Ghost / low-emphasis -->
55
+ <MButton label="More" color="neutral" variant="ghost" />
56
+
57
+ <!-- With leading icon -->
58
+ <MButton label="Launch" icon="i-lucide-rocket" color="primary" />
59
+
60
+ <!-- Icon only -->
61
+ <MButton icon="i-lucide-search" square />
62
+
63
+ <!-- All sizes -->
64
+ <MButton label="xs" size="xs" />
65
+ <MButton label="sm" size="sm" />
66
+ <MButton label="md" size="md" />
67
+ <MButton label="lg" size="lg" />
68
+ <MButton label="xl" size="xl" />
69
+
70
+ <!-- Loading state -->
71
+ <MButton label="Submitting..." loading />
72
+
73
+ <!-- Full width -->
74
+ <MButton label="Continue" block color="primary" />
75
+
76
+ <!-- External link -->
77
+ <MButton
78
+ label="Open Nuxt"
79
+ to="https://nuxt.com"
80
+ target="_blank"
81
+ trailingIcon="i-lucide-external-link"
82
+ />
83
+ ```
@@ -1,89 +1,18 @@
1
1
  <template>
2
- <button type="button" :class="classes" @click="onClick" :style="style">
3
- {{ label }}
4
- <span v-if="fetchStatus">({{ fetchStatus }})</span>
5
- </button>
2
+ <UButton v-bind="{ ...$props, ...$attrs }">
3
+ <template v-if="$slots.leading" #leading>
4
+ <slot name="leading" />
5
+ </template>
6
+ <slot />
7
+ <template v-if="$slots.trailing" #trailing>
8
+ <slot name="trailing" />
9
+ </template>
10
+ </UButton>
6
11
  </template>
7
12
 
8
13
  <script lang="ts" setup>
9
- const props = withDefaults(
10
- defineProps<{
11
- label: string;
12
- primary?: boolean;
13
- size?: "small" | "medium" | "large";
14
- backgroundColor?: string;
15
- to?: string;
16
- apiUrl?: string;
17
- }>(),
18
- { primary: false },
19
- );
14
+ import type { ButtonProps } from "@nuxt/ui";
20
15
 
21
- const emit = defineEmits<{
22
- (e: "click", id: number): void;
23
- }>();
24
-
25
- const router = useRouter();
26
- const fetchStatus = ref<string | null>(null);
27
-
28
- const classes = computed(() => ({
29
- "storybook-button": true,
30
- "storybook-button--primary": props.primary,
31
- "storybook-button--secondary": !props.primary,
32
- [`storybook-button--${props.size || "medium"}`]: true,
33
- }));
34
-
35
- const style = computed(() => ({
36
- backgroundColor: props.backgroundColor,
37
- }));
38
-
39
- const onClick = async () => {
40
- emit("click", 1);
41
-
42
- if (props.apiUrl) {
43
- fetchStatus.value = "loading...";
44
- try {
45
- await $fetch(props.apiUrl);
46
- fetchStatus.value = "ok";
47
- } catch {
48
- fetchStatus.value = "error";
49
- }
50
- }
51
-
52
- if (props.to) {
53
- await router.push(props.to);
54
- }
55
- };
16
+ defineOptions({ inheritAttrs: false });
17
+ defineProps<ButtonProps>();
56
18
  </script>
57
-
58
- <style>
59
- .storybook-button {
60
- display: inline-block;
61
- cursor: pointer;
62
- border: 0;
63
- border-radius: 3em;
64
- font-weight: 700;
65
- line-height: 1;
66
- font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
67
- }
68
- .storybook-button--primary {
69
- background-color: #555ab9;
70
- color: white;
71
- }
72
- .storybook-button--secondary {
73
- box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
74
- background-color: transparent;
75
- color: #333;
76
- }
77
- .storybook-button--small {
78
- padding: 10px 16px;
79
- font-size: 12px;
80
- }
81
- .storybook-button--medium {
82
- padding: 11px 20px;
83
- font-size: 14px;
84
- }
85
- .storybook-button--large {
86
- padding: 12px 24px;
87
- font-size: 16px;
88
- }
89
- </style>
package/app.config.ts CHANGED
@@ -1,14 +1,8 @@
1
1
  export default defineAppConfig({
2
- myLayer: {
3
- name: "Hello from Nuxt layer",
2
+ ui: {
3
+ colors: {
4
+ primary: "blue",
5
+ neutral: "neutral",
6
+ },
4
7
  },
5
8
  });
6
-
7
- declare module "@nuxt/schema" {
8
- interface AppConfigInput {
9
- myLayer?: {
10
- /** Project name */
11
- name?: string;
12
- };
13
- }
14
- }
package/nuxt.config.ts CHANGED
@@ -1,10 +1,14 @@
1
+ import { fileURLToPath } from 'node:url'
2
+
1
3
  // https://nuxt.com/docs/api/configuration/nuxt-config
2
4
  export default defineNuxtConfig({
3
5
  devtools: { enabled: true },
4
- components: [{ path: './app/components', pathPrefix: false, global: true }],
6
+ modules: ["@nuxt/ui"],
7
+ css: [fileURLToPath(new URL('./app/assets/css/main.css', import.meta.url))],
5
8
  vite: {
6
9
  optimizeDeps: {
7
10
  include: ["@vue/devtools-core", "@vue/devtools-kit"],
8
11
  },
9
12
  },
13
+ compatibilityDate: "2026-05-13",
10
14
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@menumalsuite/design-system",
3
3
  "type": "module",
4
- "version": "0.0.3",
4
+ "version": "0.0.5",
5
5
  "main": "./nuxt.config.ts",
6
6
  "exports": {
7
7
  ".": "./nuxt.config.ts"
@@ -17,32 +17,24 @@
17
17
  "build": "nuxt build .playground",
18
18
  "generate": "nuxt generate .playground",
19
19
  "preview": "nuxt preview .playground",
20
- "storybook": "storybook dev -p 6006",
21
- "build-storybook": "storybook build",
22
- "lint": "eslint ."
20
+ "lint": "eslint .",
21
+ "generate:theme": "node scripts/generate-theme.js",
22
+ "generate:theme:watch": "node --watch scripts/generate-theme.js"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "nuxt": ">=4.0.0",
26
26
  "vue": ">=3.0.0"
27
27
  },
28
28
  "devDependencies": {
29
- "@chromatic-com/storybook": "^5.1.2",
30
29
  "@nuxt/eslint": "latest",
31
- "@storybook/addon-a11y": "^10.3.6",
32
- "@storybook/addon-docs": "^10.3.6",
33
- "@storybook/addon-vitest": "^10.3.6",
34
- "@storybook/vue3-vite": "^10.3.6",
35
30
  "@types/node": "^24.12.2",
36
- "@vitejs/plugin-vue": "^6.0.6",
37
- "@vitest/browser-playwright": "^4.1.5",
38
- "@vitest/coverage-v8": "^4.1.5",
39
31
  "eslint": "^10.3.0",
40
- "nuxt": "^4.4.4",
41
- "playwright": "^1.59.1",
42
- "storybook": "^10.3.6",
32
+ "nuxt": "^4.4.5",
43
33
  "typescript": "^6.0.3",
44
- "unplugin-auto-import": "^21.0.0",
45
- "vitest": "^4.1.5",
46
- "vue": "^3.5.34"
34
+ "vue": "3.5.34"
35
+ },
36
+ "dependencies": {
37
+ "@nuxt/ui": "^4.7.1",
38
+ "tailwindcss": "^4.3.0"
47
39
  }
48
40
  }
@@ -1,67 +0,0 @@
1
- import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
- import * as ButtonStories from './MButton.stories';
3
-
4
- <Meta of={ButtonStories} />
5
-
6
- # Button
7
-
8
- Core interactive element for actions and navigation.
9
-
10
- ## When to use
11
-
12
- - **Primary** — main page action, one per screen
13
- - **Secondary** — secondary, alternative or less urgent actions
14
- - **Large** — prominent CTAs, hero sections
15
- - **Small** — toolbars, tables, compact spaces
16
-
17
- ## Variants
18
-
19
- <Canvas of={ButtonStories.Primary} />
20
- <Canvas of={ButtonStories.Secondary} />
21
-
22
- ## Sizes
23
-
24
- <Canvas of={ButtonStories.Large} />
25
- <Canvas of={ButtonStories.Small} />
26
-
27
- ## Props
28
-
29
- <Controls />
30
-
31
- ## Usage
32
-
33
- Basic button:
34
-
35
- ```vue
36
- <MButton label="Save" primary @click="handleSave" />
37
- ```
38
-
39
- Secondary action:
40
-
41
- ```vue
42
- <MButton label="Cancel" @click="handleCancel" />
43
- ```
44
-
45
- Navigation — pushes route via `useRouter`:
46
-
47
- ```vue
48
- <MButton label="Go to dashboard" to="/dashboard" />
49
- ```
50
-
51
- API call — fires `$fetch` on click, shows inline loading/ok/error:
52
-
53
- ```vue
54
- <MButton label="Submit" apiUrl="/api/submit" primary />
55
- ```
56
-
57
- Small in a table row:
58
-
59
- ```vue
60
- <MButton label="Edit" size="small" @click="edit(row.id)" />
61
- ```
62
-
63
- ## Notes
64
-
65
- - `to` triggers internal navigation via `useRouter`
66
- - `apiUrl` fires a `$fetch` on click and displays loading/ok/error state
67
- - `backgroundColor` overrides the theme color — use with caution
@@ -1,36 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/vue3-vite";
2
- import { fn } from "storybook/test";
3
- import MButton from "./MButton.vue";
4
-
5
- const meta = {
6
- title: "Components/MButton",
7
- component: MButton,
8
- // tags: ['autodocs'],
9
- argTypes: {
10
- size: { control: "select", options: ["small", "medium", "large"] },
11
- backgroundColor: { control: "color" },
12
- },
13
- args: {
14
- primary: false,
15
- onClick: fn(),
16
- },
17
- } satisfies Meta<typeof MButton>;
18
-
19
- export default meta;
20
- type Story = StoryObj<typeof meta>;
21
-
22
- export const Primary: Story = {
23
- args: { primary: true, label: "Button" },
24
- };
25
-
26
- export const Secondary: Story = {
27
- args: { primary: false, label: "Button" },
28
- };
29
-
30
- export const Large: Story = {
31
- args: { label: "Button", size: "large" },
32
- };
33
-
34
- export const Small: Story = {
35
- args: { label: "Button", size: "small" },
36
- };
@@ -1,59 +0,0 @@
1
- import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
- import * as HeaderStories from './MHeader.stories';
3
-
4
- <Meta of={HeaderStories} />
5
-
6
- # Header
7
-
8
- Main navigation bar of the application.
9
-
10
- ## When to use
11
-
12
- Global layout component — place in `app.vue` or the main layout. One instance per page.
13
-
14
- ## Variants
15
-
16
- <Canvas of={HeaderStories.LoggedIn} />
17
- <Canvas of={HeaderStories.LoggedOut} />
18
-
19
- ## Props
20
-
21
- <Controls />
22
-
23
- ## Usage
24
-
25
- In `app.vue` or a layout file:
26
-
27
- ```vue
28
- <template>
29
- <MHeader
30
- :user="authUser"
31
- @login="openLoginModal"
32
- @logout="signOut"
33
- @create-account="openSignupModal"
34
- />
35
- <slot />
36
- </template>
37
-
38
- <script setup>
39
- const authUser = useCurrentUser()
40
- </script>
41
- ```
42
-
43
- Logged-out state:
44
-
45
- ```vue
46
- <MHeader :user="null" @login="handleLogin" @create-account="handleSignup" />
47
- ```
48
-
49
- Logged-in state:
50
-
51
- ```vue
52
- <MHeader :user="{ name: 'Jane Doe' }" @logout="handleLogout" />
53
- ```
54
-
55
- ## Notes
56
-
57
- - Handles three events internally: `login`, `logout`, `createAccount`
58
- - The `user` prop is `null` when unauthenticated
59
- - Internal buttons use `<MButton>` from the design system
@@ -1,26 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/vue3-vite";
2
- import { fn } from "storybook/test";
3
- import MHeader from "./MHeader.vue";
4
-
5
- const meta = {
6
- title: "Components/MHeader",
7
- component: MHeader,
8
- parameters: { layout: "fullscreen" },
9
- args: {
10
- onLogin: fn(),
11
- onLogout: fn(),
12
- onCreateAccount: fn(),
13
- },
14
- // tags: ['autodocs'],
15
- } satisfies Meta<typeof MHeader>;
16
-
17
- export default meta;
18
- type Story = StoryObj<typeof meta>;
19
-
20
- export const LoggedIn: Story = {
21
- args: { user: { name: "Jane Doe" } },
22
- };
23
-
24
- export const LoggedOut: Story = {
25
- args: { user: null },
26
- };
@@ -1,76 +0,0 @@
1
- <template>
2
- <header>
3
- <div class="storybook-header">
4
- <div>
5
- <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
6
- <g fill="none" fill-rule="evenodd">
7
- <path
8
- d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
9
- fill="#FFF"
10
- />
11
- <path
12
- d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
13
- fill="#555AB9"
14
- />
15
- <path
16
- d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
17
- fill="#91BAF8"
18
- />
19
- </g>
20
- </svg>
21
- <h1>Acme</h1>
22
- </div>
23
- <div>
24
- <span class="welcome" v-if="user">Welcome, <b>{{ user.name }}</b>!</span>
25
- <Button size="small" @click="$emit('logout')" label="Log out" v-if="user" />
26
- <Button size="small" @click="$emit('login')" label="Log in" v-if="!user" />
27
- <Button primary size="small" @click="$emit('createAccount')" label="Sign up" v-if="!user" />
28
- </div>
29
- </div>
30
- </header>
31
- </template>
32
-
33
- <script lang="ts" setup>
34
- defineProps<{ user: { name: string } | null }>();
35
-
36
- defineEmits<{
37
- (event: 'createAccount'): void;
38
- (event: 'login'): void;
39
- (event: 'logout'): void;
40
- }>();
41
- </script>
42
-
43
- <style>
44
- .storybook-header {
45
- display: flex;
46
- justify-content: space-between;
47
- align-items: center;
48
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
49
- padding: 15px 20px;
50
- font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
51
- }
52
-
53
- .storybook-header svg {
54
- display: inline-block;
55
- vertical-align: top;
56
- }
57
-
58
- .storybook-header h1 {
59
- display: inline-block;
60
- vertical-align: top;
61
- margin: 6px 0 6px 10px;
62
- font-weight: 700;
63
- font-size: 20px;
64
- line-height: 1;
65
- }
66
-
67
- .storybook-header button + button {
68
- margin-left: 10px;
69
- }
70
-
71
- .storybook-header .welcome {
72
- margin-right: 10px;
73
- color: #333;
74
- font-size: 14px;
75
- }
76
- </style>
@@ -1,56 +0,0 @@
1
- import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
- import * as PageStories from './MPage.stories';
3
-
4
- <Meta of={PageStories} />
5
-
6
- # Page
7
-
8
- Base page template with header and main content.
9
-
10
- ## When to use
11
-
12
- Starting point for new pages — includes `<Header>` and manages authentication state internally.
13
-
14
- ## Variants
15
-
16
- <Canvas of={PageStories.LoggedOut} />
17
- <Canvas of={PageStories.LoggedIn} />
18
-
19
- ## Props
20
-
21
- <Controls />
22
-
23
- ## Usage
24
-
25
- Basic page:
26
-
27
- ```vue
28
- <MPage />
29
- ```
30
-
31
- Extended with custom content via default slot:
32
-
33
- ```vue
34
- <MPage>
35
- <section>
36
- <h2>Welcome</h2>
37
- <p>Your dashboard content here.</p>
38
- </section>
39
- </MPage>
40
- ```
41
-
42
- Inside a Nuxt layout (`layouts/default.vue`):
43
-
44
- ```vue
45
- <template>
46
- <MPage>
47
- <slot />
48
- </MPage>
49
- </template>
50
- ```
51
-
52
- ## Notes
53
-
54
- - Manages `user` state internally — no need to pass it from outside
55
- - Composes `<MHeader>` + `<MButton>` from the design system
56
- - Extend with slots for page-specific content
@@ -1,28 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/vue3-vite";
2
- import { expect, userEvent, within } from "storybook/test";
3
- import MPage from "./MPage.vue";
4
-
5
- const meta = {
6
- title: "Components/MPage",
7
- component: MPage,
8
- parameters: { layout: "fullscreen" },
9
- // tags: ['autodocs'],
10
- } satisfies Meta<typeof MPage>;
11
-
12
- export default meta;
13
- type Story = StoryObj<typeof meta>;
14
-
15
- export const LoggedIn: Story = {
16
- play: async ({ canvasElement }: any) => {
17
- const canvas = within(canvasElement);
18
- const loginButton = canvas.getByRole("button", { name: /Log in/i });
19
- await expect(loginButton).toBeInTheDocument();
20
- await userEvent.click(loginButton);
21
- await expect(loginButton).not.toBeInTheDocument();
22
-
23
- const logoutButton = canvas.getByRole("button", { name: /Log out/i });
24
- await expect(logoutButton).toBeInTheDocument();
25
- },
26
- };
27
-
28
- export const LoggedOut: Story = {};
@@ -1,121 +0,0 @@
1
- <template>
2
- <article>
3
- <Header :user="user" @login="onLogin" @logout="onLogout" @create-account="onCreateAccount" />
4
-
5
- <section class="storybook-page">
6
- <h2>Pages in Storybook</h2>
7
- <p>
8
- We recommend building UIs with a
9
- <a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
10
- <strong>component-driven</strong>
11
- </a>
12
- process starting with atomic components and ending with pages.
13
- </p>
14
- <p>
15
- Render pages with mock data. This makes it easy to build and review page states without
16
- needing to navigate to them in your app. Here are some handy patterns for managing page data
17
- in Storybook:
18
- </p>
19
- <ul>
20
- <li>
21
- Use a higher-level connected component. Storybook helps you compose such data from the
22
- "args" of child component stories
23
- </li>
24
- <li>
25
- Assemble data in the page component from your services. You can mock these services out
26
- using Storybook.
27
- </li>
28
- </ul>
29
- <p>
30
- Get a guided tutorial on component-driven development at
31
- <a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer">Storybook tutorials</a>.
32
- Read more in the
33
- <a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">docs</a>.
34
- </p>
35
- <div class="tip-wrapper">
36
- <span class="tip">Tip</span>
37
- Adjust the width of the canvas with the
38
- <svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
39
- <g fill="none" fill-rule="evenodd">
40
- <path
41
- d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
42
- id="a"
43
- fill="#999"
44
- />
45
- </g>
46
- </svg>
47
- Viewports addon in the toolbar
48
- </div>
49
- </section>
50
- </article>
51
- </template>
52
-
53
- <script lang="ts" setup>
54
- const user = ref<{ name: string } | null>(null);
55
-
56
- const onLogin = () => { user.value = { name: 'Jane Doe' }; };
57
- const onLogout = () => { user.value = null; };
58
- const onCreateAccount = () => { user.value = { name: 'Jane Doe' }; };
59
- </script>
60
-
61
- <style>
62
- .storybook-page {
63
- margin: 0 auto;
64
- padding: 48px 20px;
65
- max-width: 600px;
66
- color: #333;
67
- font-size: 14px;
68
- line-height: 24px;
69
- font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
70
- }
71
-
72
- .storybook-page h2 {
73
- display: inline-block;
74
- vertical-align: top;
75
- margin: 0 0 4px;
76
- font-weight: 700;
77
- font-size: 32px;
78
- line-height: 1;
79
- }
80
-
81
- .storybook-page p { margin: 1em 0; }
82
- .storybook-page a { color: inherit; }
83
-
84
- .storybook-page ul {
85
- margin: 1em 0;
86
- padding-left: 30px;
87
- }
88
-
89
- .storybook-page li { margin-bottom: 8px; }
90
-
91
- .storybook-page .tip {
92
- display: inline-block;
93
- vertical-align: top;
94
- margin-right: 10px;
95
- border-radius: 1em;
96
- background: #e7fdd8;
97
- padding: 4px 12px;
98
- color: #357a14;
99
- font-weight: 700;
100
- font-size: 11px;
101
- line-height: 12px;
102
- }
103
-
104
- .storybook-page .tip-wrapper {
105
- margin-top: 40px;
106
- margin-bottom: 40px;
107
- font-size: 13px;
108
- line-height: 20px;
109
- }
110
-
111
- .storybook-page .tip-wrapper svg {
112
- display: inline-block;
113
- vertical-align: top;
114
- margin-top: 3px;
115
- margin-right: 4px;
116
- width: 12px;
117
- height: 12px;
118
- }
119
-
120
- .storybook-page .tip-wrapper svg path { fill: #1ea7fd; }
121
- </style>