Skip to content

Commit d3153e8

Browse files
authored
Merge branch 'main' into shuuji3/chore/minor-update-contibuting-docs
2 parents 032369d + 16411a5 commit d3153e8

230 files changed

Lines changed: 25451 additions & 3492 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
#secure password, can use openssl rand -hex 32
2-
NUXT_SESSION_PASSWORD=""
2+
NUXT_SESSION_PASSWORD=""
3+
4+
#HMAC secret for image proxy URL signing, can use openssl rand -hex 32
5+
NUXT_IMAGE_PROXY_SECRET=""

.github/workflows/chromatic.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: chromatic
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
11+
cancel-in-progress: true
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
chromatic:
18+
name: 📚 Chromatic
19+
runs-on: ubuntu-24.04-arm
20+
21+
steps:
22+
- name: ☑️ Checkout
23+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
24+
with:
25+
fetch-depth: 0
26+
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
27+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
28+
29+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
30+
with:
31+
node-version: lts/*
32+
33+
- uses: pnpm/action-setup@1e1c8eafbd745f64b1ef30a7d7ed7965034c486c # 1e1c8eafbd745f64b1ef30a7d7ed7965034c486c
34+
name: 🟧 Install pnpm
35+
with:
36+
cache: true
37+
38+
- name: 📦 Install dependencies
39+
run: pnpm install
40+
41+
- name: 🧪 Run Chromatic Visual and Accessibility Tests
42+
uses: chromaui/action@a8ce9c58f59be5cc7090cadfc8f130fb08fcf0c3 # v15.1.0
43+
env:
44+
CHROMATIC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}
45+
CHROMATIC_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
46+
CHROMATIC_SLUG: ${{ github.repository }}

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,9 @@ file-tree-sprite.svg
4545

4646
# output
4747
.vercel
48+
49+
# Storybook
50+
*storybook.log
51+
storybook-static
52+
4853
.nvmrc

.storybook/main.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { StorybookConfig } from '@storybook-vue/nuxt'
2+
3+
const config = {
4+
stories: ['../app/**/*.stories.@(js|ts)'],
5+
addons: ['@storybook/addon-a11y', '@storybook/addon-docs', '@storybook/addon-themes'],
6+
framework: '@storybook-vue/nuxt',
7+
features: {
8+
backgrounds: false,
9+
},
10+
async viteFinal(config) {
11+
// Replace the built-in vue-docgen plugin with a fault-tolerant version.
12+
// vue-docgen-api can crash on components that import types from other
13+
// .vue files (it tries to parse the SFC with @babel/parser as plain TS).
14+
// This wrapper catches those errors so the build doesn't fail.
15+
const docgenPlugin = config.plugins?.find(
16+
(p): p is Extract<typeof p, { name: string }> =>
17+
!!p && typeof p === 'object' && 'name' in p && p.name === 'storybook:vue-docgen-plugin',
18+
)
19+
20+
if (docgenPlugin && 'transform' in docgenPlugin) {
21+
const hook = docgenPlugin.transform
22+
// Vite plugin hooks can be a function or an object with a `handler` property
23+
const originalFn = typeof hook === 'function' ? hook : hook?.handler
24+
if (originalFn) {
25+
const wrapped = async function (this: unknown, ...args: unknown[]) {
26+
try {
27+
return await originalFn.apply(this, args)
28+
} catch {
29+
return undefined
30+
}
31+
}
32+
if (typeof hook === 'function') {
33+
docgenPlugin.transform = wrapped as typeof hook
34+
} else if (hook) {
35+
hook.handler = wrapped as typeof hook.handler
36+
}
37+
}
38+
}
39+
40+
return config
41+
},
42+
} satisfies StorybookConfig
43+
44+
export default config

.storybook/preview.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import type { Preview } from '@storybook-vue/nuxt'
2+
import { withThemeByDataAttribute } from '@storybook/addon-themes'
3+
import { currentLocales } from '../config/i18n'
4+
import { fn } from 'storybook/test'
5+
import { ACCENT_COLORS } from '../shared/utils/constants'
6+
7+
// related: https://github.com/npmx-dev/npmx.dev/blob/1431d24be555bca5e1ae6264434d49ca15173c43/test/nuxt/setup.ts#L12-L26
8+
// Stub Nuxt specific globals
9+
// @ts-expect-error - dynamic global name
10+
globalThis['__NUXT_COLOR_MODE__'] ??= {
11+
preference: 'system',
12+
value: 'dark',
13+
getColorScheme: fn(() => 'dark'),
14+
addColorScheme: fn(),
15+
removeColorScheme: fn(),
16+
}
17+
// @ts-expect-error - dynamic global name
18+
globalThis.defineOgImageComponent = fn()
19+
20+
const preview: Preview = {
21+
parameters: {
22+
controls: {
23+
matchers: {
24+
color: /(background|color)$/i,
25+
date: /Date$/i,
26+
},
27+
},
28+
},
29+
// Provides toolbars to switch things like theming and language
30+
globalTypes: {
31+
locale: {
32+
name: 'Locale',
33+
description: 'UI language',
34+
defaultValue: 'en-US',
35+
toolbar: {
36+
icon: 'globe',
37+
dynamicTitle: true,
38+
items: [
39+
// English is at the top so it's easier to reset to it
40+
{ value: 'en-US', title: 'English (US)' },
41+
...currentLocales
42+
.filter(locale => locale.code !== 'en-US')
43+
.map(locale => ({ value: locale.code, title: locale.name })),
44+
],
45+
},
46+
},
47+
accentColor: {
48+
name: 'Accent Color',
49+
description: 'Accent color',
50+
toolbar: {
51+
icon: 'paintbrush',
52+
dynamicTitle: true,
53+
items: [
54+
...Object.keys(ACCENT_COLORS.light).map(color => ({
55+
value: color,
56+
title: color.charAt(0).toUpperCase() + color.slice(1),
57+
})),
58+
{ value: undefined, title: 'No Accent' },
59+
],
60+
},
61+
},
62+
},
63+
decorators: [
64+
withThemeByDataAttribute({
65+
themes: {
66+
Light: 'light',
67+
Dark: 'dark',
68+
},
69+
defaultTheme: 'Dark',
70+
attributeName: 'data-theme',
71+
}),
72+
(story, context) => {
73+
const { locale, accentColor } = context.globals as {
74+
locale: string
75+
accentColor?: string
76+
}
77+
78+
// Set accent color from globals
79+
if (accentColor) {
80+
document.documentElement.style.setProperty('--accent-color', `var(--swatch-${accentColor})`)
81+
} else {
82+
document.documentElement.style.removeProperty('--accent-color')
83+
}
84+
85+
return {
86+
template: '<story />',
87+
// Set locale from globals
88+
created() {
89+
if (this.$i18n) {
90+
this.$i18n.setLocale(locale)
91+
}
92+
},
93+
updated() {
94+
if (this.$i18n) {
95+
this.$i18n.setLocale(locale)
96+
}
97+
},
98+
}
99+
},
100+
],
101+
}
102+
103+
export default preview

.vscode/settings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@
33
"editor.formatOnSave": true,
44
"i18n-ally.keystyle": "nested",
55
"i18n-ally.localesPaths": ["./i18n/locales"],
6-
"typescript.tsdk": "node_modules/typescript/lib"
6+
"typescript.tsdk": "node_modules/typescript/lib",
7+
"explorer.fileNesting.enabled": true,
8+
"explorer.fileNesting.patterns": {
9+
"*.vue": "${capture}.stories.ts"
10+
}
711
}

CONTRIBUTING.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ This focus helps guide our project decisions as a community and what we choose t
6060
- [Lighthouse performance tests](#lighthouse-performance-tests)
6161
- [End to end tests](#end-to-end-tests)
6262
- [Test fixtures (mocking external APIs)](#test-fixtures-mocking-external-apis)
63+
- [Storybook](#storybook)
64+
- [Component categories](#component-categories)
65+
- [Coverage guidelines](#coverage-guidelines)
66+
- [Project conventions](#project-conventions)
67+
- [Configuration](#configuration)
68+
- [Global app settings](#global-app-settings)
69+
- [Known limitations](#known-limitations)
6370
- [Submitting changes](#submitting-changes)
6471
- [Before submitting](#before-submitting)
6572
- [Pull request process](#pull-request-process)
@@ -886,6 +893,117 @@ The mock connector supports test endpoints for state manipulation:
886893
- `/__test__/user-packages` - Set user's packages
887894
- `/__test__/package` - Set package collaborators
888895

896+
## Storybook
897+
898+
Storybook is a development environment for UI components that helps catch UI changes and provides integrations for various testing types. For testing, Storybook offers:
899+
900+
- **Accessibility tests** - Built-in a11y checks
901+
- **Visual tests** - Compare JPG screenshots
902+
- **Vitest tests** - Use stories directly in the unit tests
903+
904+
### Component categories
905+
906+
The plan is to organize components into 3 categories.
907+
908+
#### UI Library Components
909+
910+
Generic and reusable components used throughout the application.
911+
912+
- Examples: Button, Input, Modal, Card
913+
- **Testing focus:** Props, variants, accessibility
914+
- **Coverage:** All variants and states
915+
916+
#### Composite Components
917+
918+
Single-use components that encapsulate one feature.
919+
920+
- Examples: UserProfile, WeeklyDownloadStats
921+
- **Testing focus:** Integration patterns, user interactions
922+
- **Coverage:** Common usage scenarios
923+
924+
#### Page Components
925+
926+
**Full-page layouts** should match what the users see.
927+
928+
- Examples: HomePage, Dashboard, CheckoutPage
929+
- **Testing focus:** Layout, responsive behavior, integration testing
930+
- **Coverage:** Critical user flows and breakpoints
931+
932+
### Coverage guidelines
933+
934+
#### Which Components Need Stories?
935+
936+
TBD
937+
938+
### Project conventions
939+
940+
#### Place `.stories.ts` files next to the component
941+
942+
```sh
943+
components/
944+
├── Button.vue
945+
└── Button.stories.ts
946+
```
947+
948+
#### Story Template
949+
950+
```ts
951+
// *.stories.ts
952+
import type { Meta, StoryObj } from '@storybook-vue/nuxt'
953+
import Component from './Button.vue'
954+
955+
const meta = {
956+
component: Component,
957+
// component scope configuration goes here
958+
} satisfies Meta<typeof Component>
959+
960+
export default meta
961+
type Story = StoryObj<typeof meta>
962+
963+
export const Default: Story = {
964+
// story scope configuration goes here
965+
}
966+
```
967+
968+
#### JSDocs Annotation
969+
970+
The component should include descriptive comments.
971+
972+
```ts
973+
// Button.vue
974+
<script setup lang="ts">
975+
const props = withDefaults(
976+
defineProps<{
977+
/** Whether the button is disabled */
978+
disabled?: boolean
979+
/**
980+
* HTML button type attribute
981+
* @default "button"
982+
type?: 'button' | 'submit'
983+
// ...
984+
}>)
985+
</script>
986+
```
987+
988+
### Configuration
989+
990+
Stories can be configured at three levels:
991+
992+
- **Global scope** (`.storybook/preview.ts`) - Applies to all stories
993+
- **Component scope** - Applies to all stories for a specific component
994+
- **Story scope** - Applies to individual stories only
995+
996+
### Global app settings
997+
998+
Global application settings are added to the Storybook toolbar for easy testing and viewing. Configure these in `.storybook/preview.ts` under the `globalTypes` and `decorators` properties.
999+
1000+
### Known limitations
1001+
1002+
- Changing `i18n` in the toolbar doesn't update the language. A manual story reload is required.
1003+
- `autodocs` currently is non-functional due bugs, its usage is discouraged at this time.
1004+
- `pnpm storybook` may log warnings or non-breaking errors for Nuxt modules due to the lack of mocks. If the UI renders correctly, these can be safely ignored.
1005+
- Do not `import type` from `.vue` files. The `vue-docgen-api` parser used by `@storybook/addon-docs` cannot follow type imports across SFCs and will crash. Extract shared types into a separate `.ts` file instead.
1006+
8891007
## Submitting changes
8901008
8911009
### Before submitting

app/app.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ if (import.meta.server) {
4747
setJsonLd(createWebSiteSchema())
4848
}
4949
50+
const keyboardShortcuts = useKeyboardShortcuts()
51+
5052
onKeyDown(
5153
'/',
5254
e => {
53-
if (isEditableElement(e.target)) return
55+
if (!keyboardShortcuts.value || isEditableElement(e.target)) return
5456
e.preventDefault()
5557
5658
const searchInput = document.querySelector<HTMLInputElement>(
@@ -70,7 +72,7 @@ onKeyDown(
7072
onKeyDown(
7173
'?',
7274
e => {
73-
if (isEditableElement(e.target)) return
75+
if (!keyboardShortcuts.value || isEditableElement(e.target)) return
7476
e.preventDefault()
7577
showKbdHints.value = true
7678
},
@@ -80,7 +82,7 @@ onKeyDown(
8082
onKeyUp(
8183
'?',
8284
e => {
83-
if (isEditableElement(e.target)) return
85+
if (!keyboardShortcuts.value || isEditableElement(e.target)) return
8486
e.preventDefault()
8587
showKbdHints.value = false
8688
},

0 commit comments

Comments
 (0)