Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,16 @@ const preview: Preview = {
attributeName: 'data-theme',
}),
(story, context) => {
const { accentColor, locale } = context.globals as {
const { accentColor, locale, theme } = context.globals as {
accentColor?: string
locale?: string
theme?: string
}

const themeClass = theme === 'Light' ? 'light' : 'dark'
document.documentElement.classList.remove('light', 'dark')
document.documentElement.classList.add(themeClass)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Set accent color from globals
if (accentColor) {
document.documentElement.style.setProperty('--accent-color', `var(--swatch-${accentColor})`)
Expand Down
60 changes: 60 additions & 0 deletions app/components/About/GovernanceList.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { Meta, StoryObj } from '@storybook-vue/nuxt'
import type { GitHubContributor } from '~~/server/api/contributors.get'
import GovernanceList from './GovernanceList.vue'

const members: GitHubContributor[] = [
{
login: 'mock-steward-a',
id: 1001,
avatar_url: 'https://api.dicebear.com/9.x/initials/svg?seed=steward-a',
html_url: 'https://github.com/mock-steward-a',
contributions: 2800,
role: 'steward',
sponsors_url: 'https://github.com/sponsors/',
},
{
login: 'mock-core-a',
id: 1003,
avatar_url: 'https://api.dicebear.com/9.x/initials/svg?seed=core-a',
html_url: 'https://github.com/mock-core-a',
contributions: 9000,
role: 'core',
sponsors_url: null,
},
{
login: 'mock-maintainer-a',
id: 1004,
avatar_url: 'https://api.dicebear.com/9.x/initials/svg?seed=maintainer-a',
html_url: 'https://github.com/mock-maintainer-a',
contributions: 210,
role: 'maintainer',
sponsors_url: null,
},
]

const meta = {
component: GovernanceList,
tags: ['autodocs'],
args: {
members,
},
} satisfies Meta<typeof GovernanceList>

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {}

/** A single member with a sponsor link. */
export const SingleMember: Story = {
args: {
members: [members[0]!],
},
}

/** No members returned — renders an empty grid. */
export const Empty: Story = {
args: {
members: [],
},
}
47 changes: 47 additions & 0 deletions app/components/About/LogoImg.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Meta, StoryObj } from '@storybook-vue/nuxt'
import LogoImg from './LogoImg.vue'
import LogoVercel from '~/assets/logos/sponsors/vercel.svg'
import LogoVercelLight from '~/assets/logos/sponsors/vercel-light.svg'
import LogoNuxt from '~/assets/logos/oss-partners/nuxt.svg'

const meta = {
component: LogoImg,
tags: ['autodocs'],
decorators: [() => ({ template: '<div style="height: 36px; width: 160px;"><story /></div>' })],
} satisfies Meta<typeof LogoImg>

export default meta
type Story = StoryObj<typeof meta>

/** A single logo used regardless of theme. */
export const SingleSource: Story = {
args: {
src: LogoNuxt,
alt: 'Nuxt',
},
}

/** Separate assets swapped between dark and light themes. */
export const DarkAndLight: Story = {
args: {
src: {
dark: LogoVercel,
light: LogoVercelLight,
},
alt: 'Vercel',
},
}

/**
* A dark-only asset with `light: 'auto'` — grayscaled and inverted in light
* mode so a single logo works on both backgrounds.
*/
export const AutoInvert: Story = {
args: {
src: {
dark: LogoNuxt,
light: 'auto',
},
alt: 'Nuxt',
},
}
43 changes: 43 additions & 0 deletions app/components/About/LogoList.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Meta, StoryObj } from '@storybook-vue/nuxt'
import LogoList from './LogoList.vue'
import { SPONSORS } from '~/assets/logos/sponsors'
import { OSS_PARTNERS } from '~/assets/logos/oss-partners'

const meta = {
component: LogoList,
tags: ['autodocs'],
} satisfies Meta<typeof LogoList>

export default meta
type Story = StoryObj<typeof meta>

/** Sponsor logos laid out in the wide grid used on the about page. */
export const Sponsors: Story = {
args: {
list: SPONSORS.gold,
},
render: args => ({
components: { LogoList },
setup() {
return { args }
},
template: `<LogoList v-bind="args" class="grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-4 grid-flow-row-dense" />`,
}),
}

/**
* OSS partners use a denser grid and include a grouped entry, which renders
* with the bracketed container spanning multiple columns.
*/
export const OssPartners: Story = {
args: {
list: OSS_PARTNERS,
},
render: args => ({
components: { LogoList },
setup() {
return { args }
},
template: `<LogoList v-bind="args" class="grid grid-cols-[repeat(auto-fill,minmax(64px,1fr))] gap-4 grid-flow-row-dense" />`,
}),
}
41 changes: 41 additions & 0 deletions app/components/Alert.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Meta, StoryObj } from '@storybook-vue/nuxt'
import Alert from './Alert.vue'

const meta = {
component: Alert,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['warning', 'error'],
},
},
args: {
variant: 'warning',
title: 'Heads up',
default: 'Something you might want to know about this package.',
},
} satisfies Meta<typeof Alert>

export default meta
type Story = StoryObj<typeof meta>

/** Non-critical notice, announced politely via `role="status"`. */
export const Warning: Story = {}

/** Critical notice, announced assertively via `role="alert"`. */
export const Error: Story = {
args: {
variant: 'error',
title: 'Something went wrong',
default: 'We could not load this data. Please try again.',
},
}

/** The title is optional — the body slot renders on its own. */
export const WithoutTitle: Story = {
args: {
title: undefined,
default: 'A short message with no heading.',
},
}
41 changes: 41 additions & 0 deletions app/components/BaseCard.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Meta, StoryObj } from '@storybook-vue/nuxt'
import BaseCard from './BaseCard.vue'

const meta = {
component: BaseCard,
tags: ['autodocs'],
render: args => ({
components: { BaseCard },
setup() {
return { args }
},
template: `
<div class="max-w-md">
<BaseCard v-bind="args">
<h3 class="font-semibold text-fg">example-package</h3>
<p class="text-sm text-fg-muted mt-1">A short description of what this package does.</p>
</BaseCard>
</div>
`,
}),
} satisfies Meta<typeof BaseCard>

export default meta
type Story = StoryObj<typeof meta>

/** Default card used across search results and listings. */
export const Default: Story = {}

/** Highlighted state for the result that exactly matches the query. */
export const ExactMatch: Story = {
args: {
isExactMatch: true,
},
}

/** Keyboard/pointer selection state. */
export const Selected: Story = {
args: {
selected: true,
},
}
17 changes: 17 additions & 0 deletions app/components/Brand/Customize.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Meta, StoryObj } from '@storybook-vue/nuxt'
import Customize from './Customize.vue'

const meta = {
component: Customize,
tags: ['autodocs'],
decorators: [() => ({ template: '<div class="max-w-3xl p-4"><story /></div>' })],
} satisfies Meta<typeof Customize>

export default meta
type Story = StoryObj<typeof meta>

/**
* Interactive brand logo customizer: pick an accent color and background, then
* download the result as SVG or PNG.
*/
export const Default: Story = {}
66 changes: 66 additions & 0 deletions app/components/Code/DirectoryListing.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { Meta, StoryObj } from '@storybook-vue/nuxt'
import type { PackageFileTree } from '#shared/types/npm-registry'
import DirectoryListing from './DirectoryListing.vue'

const tree: PackageFileTree[] = [
{
name: 'src',
path: 'src',
type: 'directory',
size: 5120,
children: [
{ name: 'index.ts', path: 'src/index.ts', type: 'file', size: 1024 },
{
name: 'utils',
path: 'src/utils',
type: 'directory',
size: 2048,
children: [{ name: 'format.ts', path: 'src/utils/format.ts', type: 'file', size: 512 }],
},
],
},
{
name: 'dist',
path: 'dist',
type: 'directory',
size: 8192,
children: [{ name: 'index.js', path: 'dist/index.js', type: 'file', size: 4096 }],
},
{ name: 'package.json', path: 'package.json', type: 'file', size: 640 },
{ name: 'README.md', path: 'README.md', type: 'file', size: 2200 },
]

const baseRoute = {
params: { org: undefined, packageName: 'mock-package', version: '1.0.0' },
}

const meta = {
component: DirectoryListing,
tags: ['autodocs'],
args: {
tree,
currentPath: '',
baseUrl: 'mock-package@1.0.0',
baseRoute,
},
} satisfies Meta<typeof DirectoryListing>

export default meta
type Story = StoryObj<typeof meta>

/** Package root. `dist` is flagged as possibly-unnecessary content. */
export const Root: Story = {}

/** A nested directory, showing the `..` parent link at the top. */
export const Nested: Story = {
args: {
currentPath: 'src',
},
}

/** A path with no contents renders the empty state. */
export const Empty: Story = {
args: {
currentPath: 'does-not-exist',
},
}
Loading
Loading