diff --git a/.storybook/main.ts b/.storybook/main.ts index 45af6e1491..06e3153da9 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -2,7 +2,12 @@ import type { StorybookConfig } from '@storybook-vue/nuxt' const config = { stories: ['../app/**/*.stories.@(js|ts)'], - addons: ['@storybook/addon-a11y', '@storybook/addon-docs', '@storybook/addon-themes'], + addons: [ + '@storybook/addon-a11y', + '@storybook/addon-docs', + '@storybook/addon-themes', + 'storybook-i18n', + ], framework: '@storybook-vue/nuxt', staticDirs: ['./.public'], features: { @@ -19,34 +24,6 @@ const config = { } }, }) - // Replace the built-in vue-docgen plugin with a fault-tolerant version. - // vue-docgen-api can crash on components that import types from other - // .vue files (it tries to parse the SFC with @babel/parser as plain TS). - // This wrapper catches those errors so the build doesn't fail. - const docgenPlugin = config.plugins?.find( - (p): p is Extract => - !!p && typeof p === 'object' && 'name' in p && p.name === 'storybook:vue-docgen-plugin', - ) - - if (docgenPlugin && 'transform' in docgenPlugin) { - const hook = docgenPlugin.transform - // Vite plugin hooks can be a function or an object with a `handler` property - const originalFn = typeof hook === 'function' ? hook : hook?.handler - if (originalFn) { - const wrapped = async function (this: unknown, ...args: unknown[]) { - try { - return await originalFn.apply(this, args) - } catch { - return undefined - } - } - if (typeof hook === 'function') { - docgenPlugin.transform = wrapped as typeof hook - } else if (hook) { - hook.handler = wrapped as typeof hook.handler - } - } - } return config }, diff --git a/.storybook/manager.ts b/.storybook/manager.ts index 023e4fd21d..e56c80a6a6 100644 --- a/.storybook/manager.ts +++ b/.storybook/manager.ts @@ -1,11 +1,6 @@ import { addons } from 'storybook/manager-api' -import { create } from 'storybook/theming' - -const npmxTheme = create({ - brandTitle: 'npmx Storybook', - brandImage: '/npmx-storybook.svg', -}) +import npmxDark from './theme' addons.setConfig({ - theme: npmxTheme, + theme: npmxDark, }) diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 0000000000..49c5636054 --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,6 @@ + diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 3242f7788a..fd820f4269 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,8 +1,10 @@ import type { Preview } from '@storybook-vue/nuxt' import { withThemeByDataAttribute } from '@storybook/addon-themes' +import { addons } from 'storybook/preview-api' import { currentLocales } from '../config/i18n' import { fn } from 'storybook/test' import { ACCENT_COLORS } from '../shared/utils/constants' +import npmxDark from './theme' // related: https://github.com/npmx-dev/npmx.dev/blob/1431d24be555bca5e1ae6264434d49ca15173c43/test/nuxt/setup.ts#L12-L26 // Stub Nuxt specific globals @@ -17,6 +19,14 @@ globalThis['__NUXT_COLOR_MODE__'] ??= { // @ts-expect-error - dynamic global name globalThis.defineOgImageComponent = fn() +// Subscribe to locale changes from storybook-i18n addon (once, outside decorator) +let currentI18nInstance: any = null +addons.getChannel().on('LOCALE_CHANGED', (newLocale: string) => { + if (currentI18nInstance) { + currentI18nInstance.setLocale(newLocale) + } +}) + const preview: Preview = { parameters: { controls: { @@ -25,25 +35,22 @@ const preview: Preview = { date: /Date$/i, }, }, + docs: { + theme: npmxDark, + }, + }, + initialGlobals: { + locale: 'en-US', + locales: currentLocales.reduce( + (acc, locale) => { + acc[locale.code] = locale.name + return acc + }, + {} as Record, + ), }, // Provides toolbars to switch things like theming and language globalTypes: { - locale: { - name: 'Locale', - description: 'UI language', - defaultValue: 'en-US', - toolbar: { - icon: 'globe', - dynamicTitle: true, - items: [ - // English is at the top so it's easier to reset to it - { value: 'en-US', title: 'English (US)' }, - ...currentLocales - .filter(locale => locale.code !== 'en-US') - .map(locale => ({ value: locale.code, title: locale.name })), - ], - }, - }, accentColor: { name: 'Accent Color', description: 'Accent color', @@ -70,9 +77,9 @@ const preview: Preview = { attributeName: 'data-theme', }), (story, context) => { - const { locale, accentColor } = context.globals as { - locale: string + const { accentColor, locale } = context.globals as { accentColor?: string + locale?: string } // Set accent color from globals @@ -84,14 +91,12 @@ const preview: Preview = { return { template: '', - // Set locale from globals created() { - if (this.$i18n) { - this.$i18n.setLocale(locale) - } - }, - updated() { - if (this.$i18n) { + // Store i18n instance for LOCALE_CHANGED events + currentI18nInstance = this.$i18n + + // Set initial locale when component is created + if (locale && this.$i18n) { this.$i18n.setLocale(locale) } }, diff --git a/.storybook/theme.ts b/.storybook/theme.ts new file mode 100644 index 0000000000..01c0a3f81c --- /dev/null +++ b/.storybook/theme.ts @@ -0,0 +1,13 @@ +import { create } from 'storybook/theming/create' + +const npmxDark = create({ + base: 'dark', + + brandTitle: 'npmx Storybook', + brandImage: '/npmx-storybook.svg', + + // UI + appContentBg: '#101010', // oklch(0.171 0 0) +}) + +export default npmxDark diff --git a/app/components/Button/Base.stories.ts b/app/components/Button/Base.stories.ts deleted file mode 100644 index 9de0744377..0000000000 --- a/app/components/Button/Base.stories.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { Meta, StoryObj } from '@storybook-vue/nuxt' -import Component from './Base.vue' - -const meta = { - component: Component, -} satisfies Meta - -export default meta -type Story = StoryObj - -export const Primary: Story = { - args: { - default: 'Primary Button', - }, -} - -export const Secondary: Story = { - args: { - default: 'Secondary Button', - variant: 'secondary', - }, -} - -export const Small: Story = { - args: { - default: 'Small Button', - size: 'small', - variant: 'secondary', - }, -} - -export const Disabled: Story = { - args: { - default: 'Disabled Button', - disabled: true, - }, -} - -export const WithIcon: Story = { - args: { - default: 'Search', - classicon: 'i-lucide:search', - variant: 'secondary', - }, -} - -export const WithKeyboardShortcut: Story = { - args: { - ariaKeyshortcuts: '/', - default: 'Search', - variant: 'secondary', - }, -} - -export const Block: Story = { - args: { - block: true, - default: 'Full Width Button', - }, -} diff --git a/app/components/Button/Base.vue b/app/components/Button/Base.vue index f37d57be06..88464d6986 100644 --- a/app/components/Button/Base.vue +++ b/app/components/Button/Base.vue @@ -1,6 +1,13 @@