diff --git a/.github/agentscan.yml b/.github/agentscan.yml new file mode 100644 index 0000000000..0104707937 --- /dev/null +++ b/.github/agentscan.yml @@ -0,0 +1,20 @@ +version: 1 +trusted-author-associations: + - owner + - member +honeypot: true +mode: labels +messages: + honeypot-first-time: |- + Hello! Thank you for opening your **first PR** to npmx, **@{username}**! 🚀 + + Here's what will happen next: + + 1. Our GitHub bots will run to check your changes.\ + If they spot any issues, you will see some error messages on this PR.\ + Don't hesitate to ask any questions if you're not sure what these mean! + + 2. In a few minutes, you'll be able to see a preview of your changes on Vercel + + 3. One or more of our maintainers will take a look and may ask you to make changes.\ + We try to be responsive, but don't worry if this takes a few days. diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 00b98ae8e7..34bc863fd5 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 72795a3b3c..b66bd886ae 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -20,7 +20,7 @@ jobs: steps: - name: ☑️ Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} @@ -34,7 +34,7 @@ jobs: sfw: true - name: 🧪 Run Chromatic Visual and Accessibility Tests - uses: chromaui/action@94713c544284a14195de3b50ef24301579f1877e # v18.0.1 + uses: chromaui/action@14cfaef73576e69f95f47f60058063f46ca38719 # v18.1.0 with: buildCommand: vp run build-storybook outputDir: storybook-static diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 123cb48fc8..2ef968ad3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -64,7 +64,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -103,7 +103,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -144,16 +144,23 @@ jobs: name: 🖥️ Browser tests runs-on: ubuntu-24.04-arm container: - image: mcr.microsoft.com/playwright:v1.61.1-noble@sha256:5b8f294aff9041b7191c34a4bab3ac270157a28774d4b0660e9743297b697e48 + image: mcr.microsoft.com/playwright:v1.62.1-noble@sha256:dcc5531e97840b9b5e794f2814476b21571c5124a3fca2267d73041f56e7580e steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: 👑 Fix Git ownership run: git config --global --add safe.directory /__w/npmx.dev/npmx.dev + # Required to build better-sqlite3 for docs presumably because of the + # use of the playwright container. We should remove this if we stop having + # to build better-sqlite3 and the prebuilt binary starts working. + - name: 🚧 Install build tools + run: apt-get update && apt-get install --yes build-essential + shell: bash + - uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1.15.0 with: node-version: lts/* @@ -188,7 +195,7 @@ jobs: mode: [dark, light] steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -212,7 +219,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -230,7 +237,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/dependency-diff.yml b/.github/workflows/dependency-diff.yml index bd64e8ba7d..4a04d8766a 100644 --- a/.github/workflows/dependency-diff.yml +++ b/.github/workflows/dependency-diff.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-slim steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/deploy-canary.yml b/.github/workflows/deploy-canary.yml index 679a918fa7..54099c4943 100644 --- a/.github/workflows/deploy-canary.yml +++ b/.github/workflows/deploy-canary.yml @@ -17,7 +17,7 @@ jobs: name: 🚀 Deploy to canary (main.npmx.dev) runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 2 diff --git a/.github/workflows/lunaria.yml b/.github/workflows/lunaria.yml index 8ef40235c4..bbca2f3f86 100644 --- a/.github/workflows/lunaria.yml +++ b/.github/workflows/lunaria.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Necessary for Lunaria to work properly # Makes the action clone the entire git history diff --git a/.github/workflows/mirror-tangled.yml b/.github/workflows/mirror-tangled.yml index 2e6e083e6b..cfabd9ea83 100644 --- a/.github/workflows/mirror-tangled.yml +++ b/.github/workflows/mirror-tangled.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index f208d99a52..94ad0eed38 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -21,7 +21,7 @@ jobs: pull-requests: write # create or update the release pull request steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index e9690fbc82..3d6a17d8e6 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -23,7 +23,7 @@ jobs: skipped: ${{ steps.check.outputs.skip }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: true @@ -100,7 +100,7 @@ jobs: environment: npm-publish steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: release persist-credentials: false diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 82e13a418c..a02e7eea3e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -19,7 +19,7 @@ jobs: permissions: issues: write # mark and close stale bug issues steps: - - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 with: days-before-issue-stale: 30 days-before-issue-close: 7 @@ -37,7 +37,7 @@ jobs: permissions: pull-requests: write # mark and close stale pull requests steps: - - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 with: days-before-issue-stale: -1 days-before-issue-close: -1 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 55d7a59ab0..6135e3efbe 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -26,11 +26,11 @@ jobs: contents: read # checkout repository steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7 + - uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 with: persona: pedantic # Use annotations instead of SARIF as this doesn't need special permissions diff --git a/.storybook/main.ts b/.storybook/main.ts index e7f7270185..98ce1928ed 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -13,6 +13,7 @@ const config = { '@storybook/addon-docs', '@storybook/addon-themes', 'storybook-i18n', + 'msw-storybook-addon', ], framework: '@storybook-vue/nuxt', staticDirs: ['./.public', { from: '../public', to: '/' }], diff --git a/.storybook/preview.ts b/.storybook/preview.ts index e64a50168e..291056cc4b 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -4,12 +4,10 @@ import { addons } from 'storybook/preview-api' import { currentLocales } from '../config/i18n' import { fn } from 'storybook/test' import { ACCENT_COLORS } from '../shared/utils/constants' -import { initialize, mswLoader } from 'msw-storybook-addon' +import { mswLoader } from 'msw-storybook-addon/csf3' import npmxDark from './theme' -initialize() - // @ts-expect-error - dynamic global name globalThis.defineOgImage = fn() @@ -69,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) + // Set accent color from globals if (accentColor) { document.documentElement.style.setProperty('--accent-color', `var(--swatch-${accentColor})`) @@ -95,7 +98,7 @@ const preview: Preview = { } }, ], - loaders: [mswLoader], + loaders: [mswLoader()], } export default preview diff --git a/.vscode/settings.json b/.vscode/settings.json index ead94c6acf..d03f13095a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,20 @@ "explorer.fileNesting.enabled": true, "explorer.fileNesting.patterns": { "*.vue": "${capture}.stories.ts" + }, + "[json]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[vue]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[yaml]": { + "editor.defaultFormatter": "oxc.oxc-vscode" } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 237d15a28d..9eb942b099 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,6 @@ This focus helps guide our project decisions as a community and what we choose t - [Test fixtures (mocking external APIs)](#test-fixtures-mocking-external-apis) - [Storybook](#storybook) - [Component categories](#component-categories) - - [Coverage guidelines](#coverage-guidelines) - [Project conventions](#project-conventions) - [Configuration](#configuration) - [Global app settings](#global-app-settings) @@ -82,8 +81,10 @@ This focus helps guide our project decisions as a community and what we choose t ### Prerequisites -- [Node.js](https://nodejs.org/) (LTS version recommended) -- [pnpm](https://pnpm.io/) v10.28.1 or later +- [Node.js](https://nodejs.org/) +- [pnpm](https://pnpm.io/) + +Please use the version pinned in the `engines.node` and `packageManager` field of [package.json](./package.json). ### Setup @@ -100,7 +101,13 @@ This focus helps guide our project decisions as a community and what we choose t pnpm dev ``` -4. (optional) if you want to test the admin UI/flow, you can run the local connector: +4. start the connector with mock data: + + ```bash + pnpm mock-connector + ``` + + or with real data (requires npm login): ```bash pnpm npmx-connector @@ -169,7 +176,7 @@ rm -rf .nuxt/cache/nitro/handlers/ rm -rf .nuxt/cache/nitro/handlers/npmx-picks/ ``` -Alternatively, you can bypass the cache entirely in development by adding `shouldBypassCache: () => import.meta.dev` to your `defineCachedEventHandler` options: +Alternatively, you can bypass the cache for a specific API entirely in development by adding `shouldBypassCache: () => import.meta.dev` to your `defineCachedEventHandler` options: ```ts export default defineCachedEventHandler( @@ -227,7 +234,7 @@ The connector will check your npm authentication, generate a connection token, a ### Mock connector (for local development) -If you're working on admin features (org management, package access controls, operations queue) and don't want to use your real npm account, you can run the mock connector instead: +If you don't want to use your real npm account, you can run the mock connector instead: ```bash pnpm mock-connector @@ -257,11 +264,9 @@ pnpm mock-connector --empty # start with no prepopulated data ## Code style -When committing changes, try to keep an eye out for unintended formatting updates. These can make a pull request look noisier than it really is and slow down the review process. Sometimes IDEs automatically reformat files on save, which can unintentionally introduce extra changes. - -To help with this, the project uses `oxfmt` to handle formatting via a pre-commit hook. The hook will automatically reformat files when needed. If something can’t be fixed automatically, it will let you know what needs to be updated before you can commit. +When committing changes, try to keep an eye out for unintended formatting updates. These can make a pull request look noisier than it really is and slow down the review process. Sometimes IDEs automatically reformat files on save, which can unintentionally introduce extra changes if your formatter is not configured correctly. To prevent this, you can configure your IDE to use `oxc` as the formatter, which is aligned with the linter used inside the workflows for this project. -If you want to get ahead of any formatting issues, you can also run `pnpm lint:fix` before committing to fix formatting across the whole project. +Before you commit, staged files will automatically be linted and formatted using a [pre-commit hook](#pre-commit-hooks). Alternatively, you can manually run `pnpm lint:fix` before committing to fix formatting across the whole project. ### npmx name @@ -706,9 +711,7 @@ import { MyComponent } from '#components' describe('MyComponent', () => { it('should have no accessibility violations', async () => { const component = await mountSuspended(MyComponent, { - props: { - /* required props */ - }, + props: {/* required props */}, }) const results = await runAxe(component) expect(results.violations).toEqual([]) @@ -798,7 +801,7 @@ docker run --rm \ -e NODE_OPTIONS="--max-old-space-size=4096" \ -v $(pwd):/work \ -w /work \ - mcr.microsoft.com/playwright:v1.58.2-noble \ + mcr.microsoft.com/playwright:v1.62.1-noble \ sh -c "npm install -g pnpm && pnpm install && pnpm vp run build:test && pnpm vp run test:browser:prebuilt --update-snapshots" ``` @@ -829,6 +832,16 @@ Fixtures are stored in `test/fixtures/` with this structure: ``` test/fixtures/ +├── algolia/ +│ └── search/ # Algolia search results +├── esm-sh/ +│ ├── doc-nodes/ # Parsed documentation nodes (exports, types, functions) +│ ├── headers/ # esm.sh response headers (used to resolve type declarations) +│ └── types/ # TypeScript type declaration files +├── github/ # GitHub API responses (contributor stats) +├── jsdelivr/ # jsDelivr Data API responses (package file lists) +├── jsdelivr-cdn/ # jsDelivr CDN responses (raw package file content) +├── microlink/ # Microlink API responses (homepage link previews) ├── npm-registry/ │ ├── packuments/ # Package metadata (vue.json, @nuxt/kit.json) │ ├── search/ # Search results (vue.json, nuxt.json) @@ -885,7 +898,6 @@ cli/src/ ├── mock-app.ts # H3 mock app + MockConnectorServer class └── mock-server.ts # CLI entry point (pnpm mock-connector) -test/test-utils/ # Re-exports from cli/src/ for test convenience test/e2e/helpers/ # E2E-specific wrappers (fixtures, global setup) ``` @@ -974,14 +986,10 @@ Single-use components that encapsulate one feature. - **Testing focus:** Layout, responsive behavior, integration testing - **Coverage:** Critical user flows and breakpoints -### Coverage guidelines - -#### Which Components Need Stories? - -TBD - ### Project conventions +Stories should follow the [Component Story Format (CSF)](https://storybook.js.org/docs/api/csf). + #### Place `.stories.ts` files next to the component ```sh @@ -1228,7 +1236,16 @@ This provides the following benefits: ## Pre-commit hooks -The project uses `lint-staged` with `simple-git-hooks` to automatically lint files on commit. +Before committing, the following things will run against staged files: + +- **`*.{js,ts,mjs,cjs,vue}`** - `vp lint --fix` (auto-fix lint errors) +- **`*.vue`** - UnoCSS class checker +- **`*.{js,ts,mjs,cjs,vue,json,yml,md,html,css}`** - `vp fmt` (auto-format) +- **`i18n/locales/*`** - regenerate Lunaria tracking data and `i18n/schema.json` + +If something can't be fixed automatically, the commit will be blocked. Run `pnpm lint:fix` beforehand to resolve any issues proactively. + +The configuration for the pre-commit hook is defined in the [`staged` block of vite.config.ts](vite.config.ts#L168-L174) and is automatically installed by running `vp config`, which will run any time you run `pnpm install`. ## Using AI diff --git a/app/assets/logos/sponsors/index.ts b/app/assets/logos/sponsors/index.ts index 6a4347eeee..de52c48609 100644 --- a/app/assets/logos/sponsors/index.ts +++ b/app/assets/logos/sponsors/index.ts @@ -40,7 +40,7 @@ export const SPONSORS = { light: LogoCodeRabbitLight, }, normalisingIndent: '0.875rem', - url: 'https://www.coderabbit.ai', + url: 'https://coderabbit.link/npmx', }, ], silver: [ diff --git a/app/assets/main.css b/app/assets/main.css index 1735c87de6..e824da64da 100644 --- a/app/assets/main.css +++ b/app/assets/main.css @@ -15,13 +15,14 @@ /* text colors */ --fg: oklch(0.982 0 0); - --fg-muted: oklch(0.749 0 0); - --fg-subtle: oklch(0.673 0 0); + --fg-muted: oklch(0.849 0 0); + --fg-subtle: oklch(0.773 0 0); /* border, separator colors */ --border: oklch(0.269 0 0); --border-subtle: oklch(0.239 0 0); --border-hover: oklch(0.371 0 0); + --border-elevated: oklch(0.369 0 0); /* accent color, set by user from settings */ --accent: var(--accent-color, oklch(0.787 0.128 230.318)); @@ -81,19 +82,38 @@ --bg-elevated: oklch(0.264 0 0); } +:root[data-theme='dark'][data-fg-theme='contrast'] { + --fg: oklch(1 0 0); + --fg-muted: oklch(0.95 0 0); + --fg-subtle: oklch(0.9 0 0); +} + +:root[data-theme='dark'][data-fg-theme='standard'] { + --fg: oklch(0.982 0 0); + --fg-muted: oklch(0.849 0 0); + --fg-subtle: oklch(0.773 0 0); +} + +:root[data-theme='dark'][data-fg-theme='muted'] { + --fg: oklch(0.969 0 0); + --fg-muted: oklch(0.749 0 0); + --fg-subtle: oklch(0.673 0 0); +} + :root[data-theme='light'] { --bg: var(--bg-color, oklch(1 0 0)); --bg-subtle: var(--bg-subtle-color, oklch(0.979 0.001 286.375)); --bg-muted: var(--bg-muted-color, oklch(0.955 0.001 286.76)); --bg-elevated: var(--bg-elevated-color, oklch(0.94 0.002 287.29)); - --fg: oklch(0.146 0 0); - --fg-muted: oklch(0.398 0 0); - --fg-subtle: oklch(0.48 0 0); + --fg: oklch(0.046 0 0); + --fg-muted: oklch(0.198 0 0); + --fg-subtle: oklch(0.28 0 0); --border: oklch(0.8514 0 0); --border-subtle: oklch(0.922 0 0); --border-hover: oklch(0.715 0 0); + --border-elevated: oklch(0.75 0 0); --accent: var(--accent-color, oklch(0.5 0.16 247.27)); --accent-muted: var(--accent-color, oklch(0.205 0 0)); @@ -147,29 +167,49 @@ --bg-subtle: var(--bg-subtle-color, oklch(0.979 0 0)); } +:root[data-theme='light'][data-fg-theme='contrast'] { + --fg: oklch(0 0 0); + --fg-muted: oklch(0.1 0 0); + --fg-subtle: oklch(0.2 0 0); +} + +:root[data-theme='light'][data-fg-theme='standard'] { + --fg: oklch(0.046 0 0); + --fg-muted: oklch(0.198 0 0); + --fg-subtle: oklch(0.28 0 0); +} + +:root[data-theme='light'][data-fg-theme='muted'] { + --fg: oklch(0.146 0 0); + --fg-muted: oklch(0.398 0 0); + --fg-subtle: oklch(0.48 0 0); +} + @media (prefers-contrast: more) { :root[data-theme='dark'] { /* text colors */ --fg: oklch(1 0 0); - --fg-muted: oklch(0.769 0 0); - --fg-subtle: oklch(0.693 0 0); + --fg-muted: oklch(0.869 0 0); + --fg-subtle: oklch(0.793 0 0); /* border, separator colors */ --border: oklch(0.769 0 0); --border-subtle: oklch(0.739 0 0); --border-hover: oklch(0.771 0 0); + --border-elevated: oklch(0.869 0 0); } :root[data-theme='light'] { /* text colors */ --fg: oklch(0 0 0); - --fg-muted: oklch(0.329 0 0); - --fg-subtle: oklch(0.4 0 0); + --fg-muted: oklch(0.229 0 0); + --fg-subtle: oklch(0.3 0 0); /* border, separator colors */ --border: oklch(0.3514 0 0); --border-subtle: oklch(0.422 0 0); --border-hover: oklch(0.315 0 0); + --border-elevated: oklch(0.25 0 0); } } diff --git a/app/components/About/GovernanceList.stories.ts b/app/components/About/GovernanceList.stories.ts new file mode 100644 index 0000000000..5dd085c49c --- /dev/null +++ b/app/components/About/GovernanceList.stories.ts @@ -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 + +export default meta +type Story = StoryObj + +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: [], + }, +} diff --git a/app/components/About/LogoImg.stories.ts b/app/components/About/LogoImg.stories.ts new file mode 100644 index 0000000000..bb1bab3902 --- /dev/null +++ b/app/components/About/LogoImg.stories.ts @@ -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: '
' })], +} satisfies Meta + +export default meta +type Story = StoryObj + +/** 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', + }, +} diff --git a/app/components/About/LogoList.stories.ts b/app/components/About/LogoList.stories.ts new file mode 100644 index 0000000000..696684cf90 --- /dev/null +++ b/app/components/About/LogoList.stories.ts @@ -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 + +export default meta +type Story = StoryObj + +/** 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: ``, + }), +} + +/** + * 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: ``, + }), +} diff --git a/app/components/Alert.stories.ts b/app/components/Alert.stories.ts new file mode 100644 index 0000000000..63439e8c39 --- /dev/null +++ b/app/components/Alert.stories.ts @@ -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 + +export default meta +type Story = StoryObj + +/** 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.', + }, +} diff --git a/app/components/BaseCard.stories.ts b/app/components/BaseCard.stories.ts new file mode 100644 index 0000000000..c30fafcd4f --- /dev/null +++ b/app/components/BaseCard.stories.ts @@ -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: ` +
+ +

example-package

+

A short description of what this package does.

+
+
+ `, + }), +} satisfies Meta + +export default meta +type Story = StoryObj + +/** 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, + }, +} diff --git a/app/components/Brand/Customize.stories.ts b/app/components/Brand/Customize.stories.ts new file mode 100644 index 0000000000..13f34da591 --- /dev/null +++ b/app/components/Brand/Customize.stories.ts @@ -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: '
' })], +} satisfies Meta + +export default meta +type Story = StoryObj + +/** + * Interactive brand logo customizer: pick an accent color and background, then + * download the result as SVG or PNG. + */ +export const Default: Story = {} diff --git a/app/components/Brand/Customize.vue b/app/components/Brand/Customize.vue index 083f3dfb1b..98d54cb8dd 100644 --- a/app/components/Brand/Customize.vue +++ b/app/components/Brand/Customize.vue @@ -82,9 +82,9 @@ async function downloadCustomPng() { const img = new Image() const loaded = new Promise((resolve, reject) => { - // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener) + // oxlint-disable-next-line unicorn/prefer-add-event-listener img.onload = () => resolve() - // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener) + // oxlint-disable-next-line unicorn/prefer-add-event-listener img.onerror = () => reject(new Error('Failed to load custom SVG')) }) img.src = url diff --git a/app/components/Button/Base.vue b/app/components/Button/Base.vue index 1a321e888d..be7926289c 100644 --- a/app/components/Button/Base.vue +++ b/app/components/Button/Base.vue @@ -48,7 +48,7 @@ defineExpose({