@menumalsuite/design-system 0.0.2 → 0.0.4

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,2 @@
1
+ @import "tailwindcss";
2
+ @import "@nuxt/ui";
@@ -0,0 +1,73 @@
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 label="Cancel" color="neutral" variant="outline" @click="handleCancel" />
48
+
49
+ <!-- Ghost / low-emphasis -->
50
+ <MButton label="More" color="neutral" variant="ghost" />
51
+
52
+ <!-- With leading icon -->
53
+ <MButton label="Launch" icon="i-lucide-rocket" color="primary" />
54
+
55
+ <!-- Icon only -->
56
+ <MButton icon="i-lucide-search" square />
57
+
58
+ <!-- All sizes -->
59
+ <MButton label="xs" size="xs" />
60
+ <MButton label="sm" size="sm" />
61
+ <MButton label="md" size="md" />
62
+ <MButton label="lg" size="lg" />
63
+ <MButton label="xl" size="xl" />
64
+
65
+ <!-- Loading state -->
66
+ <MButton label="Submitting..." loading />
67
+
68
+ <!-- Full width -->
69
+ <MButton label="Continue" block color="primary" />
70
+
71
+ <!-- External link -->
72
+ <MButton label="Open Nuxt" to="https://nuxt.com" target="_blank" trailingIcon="i-lucide-external-link" />
73
+ ```
@@ -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.2",
4
+ "version": "0.0.4",
5
5
  "main": "./nuxt.config.ts",
6
6
  "exports": {
7
7
  ".": "./nuxt.config.ts"
@@ -17,8 +17,6 @@
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
20
  "lint": "eslint ."
23
21
  },
24
22
  "peerDependencies": {
@@ -26,23 +24,15 @@
26
24
  "vue": ">=3.0.0"
27
25
  },
28
26
  "devDependencies": {
29
- "@chromatic-com/storybook": "^5.1.2",
30
27
  "@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
28
  "@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
29
  "eslint": "^10.3.0",
40
- "nuxt": "^4.4.4",
41
- "playwright": "^1.59.1",
42
- "storybook": "^10.3.6",
30
+ "nuxt": "^4.4.5",
43
31
  "typescript": "^6.0.3",
44
- "unplugin-auto-import": "^21.0.0",
45
- "vitest": "^4.1.5",
46
- "vue": "^3.5.34"
32
+ "vue": "3.5.34"
33
+ },
34
+ "dependencies": {
35
+ "@nuxt/ui": "^4.7.1",
36
+ "tailwindcss": "^4.3.0"
47
37
  }
48
38
  }
@@ -1,35 +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
- ## Notes
32
-
33
- - `to` triggers internal navigation via `useRouter`
34
- - `apiUrl` fires a `$fetch` on click and displays loading/ok/error state
35
- - `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,27 +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
- ## Notes
24
-
25
- - Handles three events internally: `login`, `logout`, `createAccount`
26
- - The `user` prop is `null` when unauthenticated
27
- - Internal buttons use `<Button>` 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,27 +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
- ## Notes
24
-
25
- - Manages `user` state internally — no need to pass it from outside
26
- - Composes `<Header>` + `<Button>` from the design system
27
- - 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>