Skip to content

Commit 7a56f94

Browse files
committed
Improve Storybook setup and add more stories
1 parent eff116d commit 7a56f94

23 files changed

Lines changed: 668 additions & 151 deletions

.storybook/main.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import type { StorybookConfig } from '@storybook-vue/nuxt'
1+
import type { StorybookConfig } from '@nuxtjs/storybook'
22

33
const config = {
44
stories: ['../app/**/*.stories.@(js|ts|mdx)'],
55
addons: ['@storybook/addon-a11y', '@storybook/addon-docs'],
66
framework: '@storybook-vue/nuxt',
7+
features: {
8+
backgrounds: false,
9+
},
710
} satisfies StorybookConfig
811
export default config

.storybook/preview.ts

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
1-
import type { Preview } from '@storybook-vue/nuxt'
1+
import type { Preview } from '@nuxtjs/storybook'
2+
import { currentLocales } from '../config/i18n'
3+
import { fn } from 'storybook/test'
4+
import { ACCENT_COLORS } from '../shared/utils/constants'
5+
6+
// Stub Nuxt specific globals
7+
globalThis['__NUXT_COLOR_MODE__'] ??= {
8+
preference: 'system',
9+
value: 'dark',
10+
getColorScheme: fn(() => 'dark'),
11+
addColorScheme: fn(),
12+
removeColorScheme: fn(),
13+
}
14+
globalThis.defineOgImageComponent = fn()
215

316
const preview: Preview = {
417
parameters: {
@@ -9,6 +22,83 @@ const preview: Preview = {
922
},
1023
},
1124
},
25+
// Provides toolbars to switch things like theming and language
26+
globalTypes: {
27+
locale: {
28+
name: 'Locale',
29+
description: 'UI language',
30+
defaultValue: 'en',
31+
toolbar: {
32+
icon: 'globe',
33+
dynamicTitle: true,
34+
items: [
35+
// English is at the top so it's easier to reset to it
36+
{ value: 'en-US', title: 'English (US)' },
37+
...currentLocales
38+
.filter(locale => locale.code !== 'en-US')
39+
.map(locale => ({ value: locale.code, title: locale.name })),
40+
],
41+
},
42+
},
43+
accentColor: {
44+
name: 'Accent Color',
45+
description: 'Accent color',
46+
toolbar: {
47+
icon: 'paintbrush',
48+
dynamicTitle: true,
49+
items: [
50+
...Object.keys(ACCENT_COLORS.light).map(color => ({ value: color, title: color })),
51+
{ value: 'clear', title: 'clear' },
52+
],
53+
},
54+
},
55+
theme: {
56+
name: 'Theme',
57+
description: 'Color mode',
58+
toolbar: {
59+
icon: 'moon',
60+
dynamicTitle: true,
61+
items: [
62+
{ value: 'light', icon: 'sun', title: 'Light' },
63+
{ value: 'dark', icon: 'moon', title: 'Dark' },
64+
],
65+
},
66+
},
67+
},
68+
decorators: [
69+
(story, context) => {
70+
const { locale, theme, accentColor } = context.globals as {
71+
locale: string
72+
theme: string
73+
accentColor?: string
74+
}
75+
76+
// Set theme from globals
77+
document.documentElement.setAttribute('data-theme', theme)
78+
79+
// Set accent color from globals
80+
if (accentColor) {
81+
document.documentElement.style.setProperty('--accent-color', `var(--swatch-${accentColor})`)
82+
} else if (accentColor === 'clear') {
83+
document.documentElement.style.removeProperty('--accent-color')
84+
}
85+
86+
return {
87+
template: '<story />',
88+
// Set locale from globals
89+
created() {
90+
if (this.$i18n) {
91+
this.$i18n.setLocale(locale)
92+
}
93+
},
94+
updated() {
95+
if (this.$i18n) {
96+
this.$i18n.setLocale(locale)
97+
}
98+
},
99+
}
100+
},
101+
],
12102
}
13103

14104
export default preview
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { Meta, StoryObj } from '@nuxtjs/storybook'
2+
import AppFooter from './AppFooter.vue'
3+
4+
const meta = {
5+
component: AppFooter,
6+
parameters: {
7+
layout: 'fullscreen',
8+
},
9+
globals: {
10+
viewport: { value: undefined },
11+
},
12+
} satisfies Meta<typeof AppFooter>
13+
14+
export default meta
15+
type Story = StoryObj<typeof meta>
16+
17+
export const Default: Story = {}
18+
19+
export const InContext: Story = {
20+
render: () => ({
21+
components: { AppFooter },
22+
template: `
23+
<div style="display: flex; flex-direction: column; min-height: 100vh;">
24+
<div style="flex: 1; padding: 2rem;">
25+
<h1>Some page content</h1>
26+
<span>See footer at the bottom</span>
27+
</div>
28+
<AppFooter />
29+
</div>
30+
`,
31+
}),
32+
}
33+
34+
export const InLongPage: Story = {
35+
render: () => ({
36+
components: { AppFooter },
37+
template: `
38+
<div style="display: flex; flex-direction: column; min-height: 100vh;">
39+
<div style="flex: 1; padding: 2rem;">
40+
<h1>Footer is all the way at the bottom!</h1> <br />
41+
${Array.from({ length: 50 }, (_, i) => `<p key="${i}">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>`).join('')}
42+
</div>
43+
<AppFooter />
44+
</div>
45+
`,
46+
}),
47+
}
48+
49+
export const MobileView: Story = {
50+
...InContext,
51+
globals: {
52+
viewport: { value: 'mobile1' },
53+
},
54+
}
55+
56+
export const TabletView: Story = {
57+
...InContext,
58+
globals: {
59+
viewport: { value: 'tablet' },
60+
},
61+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Meta, StoryObj } from '@nuxtjs/storybook'
2+
import AppHeader from './AppHeader.vue'
3+
4+
const meta = {
5+
component: AppHeader,
6+
parameters: {
7+
layout: 'fullscreen',
8+
},
9+
globals: {
10+
viewport: { value: undefined },
11+
},
12+
} satisfies Meta<typeof AppHeader>
13+
14+
export default meta
15+
16+
export const Default: StoryObj<typeof AppHeader> = {}
17+
18+
export const Mobile: StoryObj<typeof AppHeader> = {
19+
globals: {
20+
viewport: { value: 'mobile1' },
21+
},
22+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import type { Meta, StoryObj } from '@nuxtjs/storybook'
2+
import ButtonBase from './Base.vue'
3+
4+
const meta = {
5+
component: ButtonBase,
6+
tags: ['autodocs'],
7+
} satisfies Meta<typeof ButtonBase>
8+
9+
export default meta
10+
type Story = StoryObj<typeof meta>
11+
12+
export const Primary: Story = {
13+
args: {
14+
variant: 'primary',
15+
size: 'medium',
16+
},
17+
render: args => ({
18+
components: { ButtonBase },
19+
setup() {
20+
return { args }
21+
},
22+
template: '<ButtonBase v-bind="args">Primary Button</ButtonBase>',
23+
}),
24+
}
25+
26+
export const Secondary: Story = {
27+
args: {
28+
variant: 'secondary',
29+
size: 'medium',
30+
},
31+
render: args => ({
32+
components: { ButtonBase },
33+
setup() {
34+
return { args }
35+
},
36+
template: '<ButtonBase v-bind="args">Secondary Button</ButtonBase>',
37+
}),
38+
}
39+
40+
export const Small: Story = {
41+
args: {
42+
variant: 'secondary',
43+
size: 'small',
44+
},
45+
render: args => ({
46+
components: { ButtonBase },
47+
setup() {
48+
return { args }
49+
},
50+
template: '<ButtonBase v-bind="args">Small Button</ButtonBase>',
51+
}),
52+
}
53+
54+
export const Disabled: Story = {
55+
args: {
56+
variant: 'primary',
57+
size: 'medium',
58+
disabled: true,
59+
},
60+
render: args => ({
61+
components: { ButtonBase },
62+
setup() {
63+
return { args }
64+
},
65+
template: '<ButtonBase v-bind="args">Disabled Button</ButtonBase>',
66+
}),
67+
}
68+
69+
export const WithIcon: Story = {
70+
args: {
71+
variant: 'secondary',
72+
size: 'medium',
73+
classicon: 'i-carbon:search',
74+
},
75+
render: args => ({
76+
components: { ButtonBase },
77+
setup() {
78+
return { args }
79+
},
80+
template: '<ButtonBase v-bind="args">Search</ButtonBase>',
81+
}),
82+
}
83+
84+
export const WithKeyboardShortcut: Story = {
85+
args: {
86+
variant: 'secondary',
87+
size: 'medium',
88+
ariaKeyshortcuts: '/',
89+
},
90+
render: args => ({
91+
components: { ButtonBase },
92+
setup() {
93+
return { args }
94+
},
95+
template: '<ButtonBase v-bind="args">Search</ButtonBase>',
96+
}),
97+
}
98+
99+
export const Block: Story = {
100+
args: {
101+
variant: 'primary',
102+
size: 'medium',
103+
block: true,
104+
},
105+
render: args => ({
106+
components: { ButtonBase },
107+
setup() {
108+
return { args }
109+
},
110+
template: '<ButtonBase v-bind="args">Full Width Button</ButtonBase>',
111+
}),
112+
}

app/components/Button/Base.vue

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,36 @@
11
<script setup lang="ts">
22
const props = withDefaults(
33
defineProps<{
4+
/**
5+
* Whether the button is disabled
6+
*/
47
disabled?: boolean
8+
/**
9+
* HTML button type attribute
10+
* @default "button"
11+
*/
512
type?: 'button' | 'submit'
13+
/**
14+
* Button visual style variant
15+
* @default "secondary"
16+
*/
617
variant?: 'primary' | 'secondary'
18+
/**
19+
* Button size
20+
* @default "medium"
21+
*/
722
size?: 'small' | 'medium'
23+
/**
24+
* Keyboard shortcut hint
25+
*/
826
ariaKeyshortcuts?: string
27+
/**
28+
* Whether the button should take full width
29+
*/
930
block?: boolean
10-
31+
/**
32+
* Icon class (e.g., i-carbon-add)
33+
*/
1134
classicon?: string
1235
}>(),
1336
{

0 commit comments

Comments
 (0)