diff --git a/.github/workflows/branch-preview.yml b/.github/workflows/branch-preview.yml new file mode 100644 index 00000000..95f30e2a --- /dev/null +++ b/.github/workflows/branch-preview.yml @@ -0,0 +1,95 @@ +name: ๐Ÿค– Branch Preview + +concurrency: + group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: [main] + +jobs: + build-docs: + name: Build Docs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: "package.json" + cache: pnpm + + - name: Install deps + run: pnpm install --prefer-offline --frozen-lockfile + + - name: Generate docs + working-directory: docs + env: + APP_ENV: production + run: pnpm run generate:docs + + - name: Pack generated docs (tarball) + run: | + tar -czf docs-generated.tgz -C docs generated-docs + ls -lh docs-generated.tgz + - name: Upload generated docs (tgz) + uses: actions/upload-artifact@v4 + with: + name: docs-generated-tgz + path: docs-generated.tgz + if-no-files-found: error + + - name: Upload versions file + uses: actions/upload-artifact@v4 + with: + name: docs-versions + path: docs/app/utils/versions.ts + if-no-files-found: error + + deploy-docs-on-release: + needs: [build-docs] + name: Deploy Docs for Branch Preview + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download generated docs (tgz) + uses: actions/download-artifact@v4 + with: + name: docs-generated-tgz + path: . + + - name: Unpack generated docs into docs/ + run: | + set -euxo pipefail + tar -xzf docs-generated.tgz -C docs + ls -laR docs/generated-docs | sed -n '1,200p' + - name: Download versions file + uses: actions/download-artifact@v4 + with: + name: docs-versions + path: docs/app/utils + + - uses: forge-42/fly-deploy@v1.0.0-rc.2 + id: deploy + env: + FLY_ORG: ${{ vars.FLY_ORG }} + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + FLY_REGION: ${{ vars.FLY_REGION }} + with: + workspace_name: docs + app_name: ${{github.event.repository.name}}-${{ github.ref_name }} + use_isolated_workspace: true + env_vars: | + APP_ENV=production + GITHUB_OWNER=${{ github.repository_owner }} + GITHUB_REPO=${{ github.event.repository.name }} + GITHUB_REPO_URL=https://github.com/${{ github.repository }} diff --git a/.github/workflows/validate.yaml b/.github/workflows/ci.yml similarity index 74% rename from .github/workflows/validate.yaml rename to .github/workflows/ci.yml index 39389d1d..a33dbb9e 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/ci.yml @@ -1,136 +1,149 @@ -name: ๐Ÿš€ Validation Pipeline -concurrency: - group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -on: - push: - branches: [main] - pull_request: - branches: [main] - -permissions: - actions: write - contents: read - # Required to put a comment into the pull-request - pull-requests: write -jobs: - lint: - name: โฌฃ Biome lint - runs-on: ubuntu-latest - steps: - - name: โฌ‡๏ธ Checkout repo - uses: actions/checkout@v4 - - name: Setup Biome - uses: biomejs/setup-biome@v2 - - name: Run Biome - run: biome ci . - - validate: - name: ๐Ÿ”Ž Validate - runs-on: ubuntu-latest - steps: - - name: ๐Ÿ›‘ Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.12.1 - - name: โฌ‡๏ธ Checkout repo - uses: actions/checkout@v4 - - name: โŽ” Setup node - uses: actions/setup-node@v4 - with: - node-version-file: "package.json" - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Install dependencies - run: pnpm install - - name: ๐Ÿ”Ž Validate - run: pnpm run test - - build-docs: - if: ${{ github.event_name == 'pull_request' }} - name: Build Docs - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - # checkout the PR head branch - ref: ${{ github.head_ref }} - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version-file: "package.json" - cache: pnpm - - - name: Install deps - run: pnpm install --prefer-offline --frozen-lockfile - - - name: Generate docs - working-directory: docs - env: - APP_ENV: production - run: pnpm run generate:docs - - - name: Pack generated docs (tarball) - run: | - tar -czf docs-generated.tgz -C docs generated-docs - ls -lh docs-generated.tgz - - - name: Upload generated docs (tgz) - uses: actions/upload-artifact@v4 - with: - name: docs-generated-tgz - path: docs-generated.tgz - if-no-files-found: error - - - name: Upload versions file - uses: actions/upload-artifact@v4 - with: - name: docs-versions - path: docs/app/utils/versions.ts - if-no-files-found: error - - deploy-docs-pr-preview: - if: ${{ github.event_name == 'pull_request' }} - needs: [lint, validate, build-docs] - name: Deploy Docs PR Preview - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Download generated docs (tgz) - uses: actions/download-artifact@v4 - with: - name: docs-generated-tgz - path: . - - - name: Unpack generated docs into docs/ - run: | - set -euxo pipefail - tar -xzf docs-generated.tgz -C docs - ls -laR docs/generated-docs | sed -n '1,200p' - - - name: Download versions file - uses: actions/download-artifact@v4 - with: - name: docs-versions - path: docs/app/utils - - - uses: forge-42/fly-deploy@v1.0.0-rc.2 - id: deploy - env: - FLY_ORG: ${{ vars.FLY_ORG }} - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - FLY_REGION: ${{ vars.FLY_REGION }} - with: - workspace_name: docs - app_name: react-router-devtools-docs-pr-${{ github.event.number }} - use_isolated_workspace: true - env_vars: | - APP_ENV=production - GITHUB_OWNER=${{ github.repository_owner }} - GITHUB_REPO=${{ github.event.repository.name }} - GITHUB_REPO_URL=https://github.com/${{ github.repository }} +name: ๐Ÿš€ Validation Pipeline +concurrency: + group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + actions: write + contents: read + # Required to put a comment into the pull-request + pull-requests: write +jobs: + lint: + name: โฌฃ Biome lint + runs-on: ubuntu-latest + steps: + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v4 + - name: Setup Biome + uses: biomejs/setup-biome@v2 + - name: Run Biome + run: biome ci . + + validate: + name: ๐Ÿ”Ž Validate + runs-on: ubuntu-latest + steps: + - name: ๐Ÿ›‘ Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.12.1 + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v4 + - name: โŽ” Setup node + uses: actions/setup-node@v4 + with: + node-version-file: "package.json" + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Install dependencies + run: pnpm install + - name: Install Playwright browsers + # downloads browser binaries required by Playwright (Chromium/Firefox/WebKit) + run: pnpm exec playwright install --with-deps + - name: ๐Ÿ”Ž Validate + run: pnpm run test + + build-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: "package.json" + cache: pnpm + + # One install at the workspace root is enough + - name: Install deps (root) + run: pnpm install --prefer-offline --frozen-lockfile + + # Decide where the docs app lives: ./docs or . + - name: Resolve DOCS_DIR + id: paths + shell: bash + run: | + if [ -d docs ] && [ -f docs/package.json ]; then + DOCS_DIR="docs" + else + DOCS_DIR="." + fi + + # expose for later steps + echo "DOCS_DIR=$DOCS_DIR" >> "$GITHUB_OUTPUT" + + # ok to print within this step using the shell variable + echo "Using DOCS_DIR=$DOCS_DIR" + + - name: Generate docs + env: + APP_ENV: production + run: pnpm -C "${{ steps.paths.outputs.DOCS_DIR }}" run generate:docs + + - name: Pack generated docs (tarball) + run: | + OUT_BASE="${{ steps.paths.outputs.DOCS_DIR }}" + tar -czf docs-generated.tgz -C "$OUT_BASE" generated-docs + ls -lh docs-generated.tgz + + - name: Upload generated docs (tgz) + uses: actions/upload-artifact@v4 + with: + name: docs-generated-tgz + path: docs-generated.tgz + if-no-files-found: error + + - name: Upload versions file + uses: actions/upload-artifact@v4 + with: + name: docs-versions + path: ${{ steps.paths.outputs.DOCS_DIR }}/app/utils/versions.ts + if-no-files-found: error + + deploy-docs-pr-preview: + if: ${{ github.event_name == 'pull_request' }} + needs: [lint, validate, build-docs] + name: Deploy Docs PR Preview + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download generated docs (tgz) + uses: actions/download-artifact@v4 + with: + name: docs-generated-tgz + path: . + + - name: Unpack generated docs into docs/ + run: | + set -euxo pipefail + tar -xzf docs-generated.tgz -C docs + ls -laR docs/generated-docs | sed -n '1,200p' + - name: Download versions file + uses: actions/download-artifact@v4 + with: + name: docs-versions + path: docs/app/utils + + - uses: forge-42/fly-deploy@v1.0.0-rc.2 + id: deploy + env: + FLY_ORG: ${{ vars.FLY_ORG }} + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + FLY_REGION: ${{ vars.FLY_REGION }} + with: + workspace_name: docs + app_name: react-router-devtools-docs-pr-${{ github.event.number }} + use_isolated_workspace: true + env_vars: | + APP_ENV=production + GITHUB_OWNER=${{ github.repository_owner }} + GITHUB_REPO=${{ github.event.repository.name }} + GITHUB_REPO_URL=https://github.com/${{ github.repository }} diff --git a/.github/workflows/pr-close.yml b/.github/workflows/pr-close.yml new file mode 100644 index 00000000..d241fdac --- /dev/null +++ b/.github/workflows/pr-close.yml @@ -0,0 +1,25 @@ +name: ๐Ÿงน PR Close + +concurrency: + group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + branches: [main] + types: closed + +jobs: + + destroy-pr-preview: + name: ๐Ÿงน Destroy PR Preview + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: forge-42/fly-destroy@v1.0.0-rc.2 + id: destroy + env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + FLY_ORG: ${{ vars.FLY_ORG }} + with: + app_name: ${{github.event.repository.name}}-${{ github.event.number }} diff --git a/.github/workflows/publish-commit.yaml b/.github/workflows/publish-commit.yaml deleted file mode 100644 index 81f03c96..00000000 --- a/.github/workflows/publish-commit.yaml +++ /dev/null @@ -1,29 +0,0 @@ -name: ๐Ÿš€ pkg-pr-new -on: [push, pull_request] - -concurrency: - group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - run: corepack enable - - uses: actions/setup-node@v4 - with: - node-version-file: "package.json" - - - name: Install dependencies - run: pnpm install - - - name: Build - run: pnpm run build:all - - - run: npx pkg-pr-new publish ./packages/* diff --git a/.github/workflows/publish-documentation.yaml b/.github/workflows/publish-documentation.yml similarity index 94% rename from .github/workflows/publish-documentation.yaml rename to .github/workflows/publish-documentation.yml index d5836bcd..b9af2e84 100644 --- a/.github/workflows/publish-documentation.yaml +++ b/.github/workflows/publish-documentation.yml @@ -1,100 +1,96 @@ -name: ๐Ÿ“š๐Ÿš€ Build documentation on release - -on: - release: - types: [published] - workflow_dispatch: {} - -concurrency: - group: docs-build-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build-docs: - name: Build Docs - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version-file: "package.json" - cache: pnpm - - - name: Install deps - run: pnpm install --prefer-offline --frozen-lockfile - - - name: Generate docs - working-directory: docs - env: - APP_ENV: production - run: pnpm run generate:docs - - - name: Pack generated docs (tarball) - run: | - tar -czf docs-generated.tgz -C docs generated-docs - ls -lh docs-generated.tgz - - - name: Upload generated docs (tgz) - uses: actions/upload-artifact@v4 - with: - name: docs-generated-tgz - path: docs-generated.tgz - if-no-files-found: error - - - name: Upload versions file - uses: actions/upload-artifact@v4 - with: - name: docs-versions - path: docs/app/utils/versions.ts - if-no-files-found: error - - deploy-docs-on-release: - needs: [build-docs] - name: Deploy Docs - environment: - name: docs-release - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Download generated docs (tgz) - uses: actions/download-artifact@v4 - with: - name: docs-generated-tgz - path: . - - - name: Unpack generated docs into docs/ - run: | - set -euxo pipefail - tar -xzf docs-generated.tgz -C docs - ls -laR docs/generated-docs | sed -n '1,200p' - - - name: Download versions file - uses: actions/download-artifact@v4 - with: - name: docs-versions - path: docs/app/utils - - - uses: forge-42/fly-deploy@v1.0.0-rc.2 - id: deploy - env: - FLY_ORG: ${{ vars.FLY_ORG }} - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - FLY_REGION: ${{ vars.FLY_REGION }} - with: - workspace_name: docs - app_name: ${{github.event.repository.name}}-${{ github.ref_name }} - use_isolated_workspace: true - env_vars: | - APP_ENV=production - GITHUB_OWNER=${{ github.repository_owner }} - GITHUB_REPO=${{ github.event.repository.name }} - GITHUB_REPO_URL=https://github.com/${{ github.repository }} +name: ๐Ÿ“š๐Ÿš€ Build documentation on release + +on: + release: + types: [published] + workflow_dispatch: {} + +concurrency: + group: docs-build-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-docs: + name: Build Docs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: "package.json" + cache: pnpm + + - name: Install deps + run: pnpm install --prefer-offline --frozen-lockfile + + - name: Generate docs + working-directory: docs + env: + APP_ENV: production + run: pnpm run generate:docs + + - name: Pack generated docs (tarball) + run: | + tar -czf docs-generated.tgz -C docs generated-docs + ls -lh docs-generated.tgz + - name: Upload generated docs (tgz) + uses: actions/upload-artifact@v4 + with: + name: docs-generated-tgz + path: docs-generated.tgz + if-no-files-found: error + + - name: Upload versions file + uses: actions/upload-artifact@v4 + with: + name: docs-versions + path: docs/app/utils/versions.ts + if-no-files-found: error + + deploy-docs-on-release: + needs: [build-docs] + name: Deploy Docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download generated docs (tgz) + uses: actions/download-artifact@v4 + with: + name: docs-generated-tgz + path: . + + - name: Unpack generated docs into docs/ + run: | + set -euxo pipefail + tar -xzf docs-generated.tgz -C docs + ls -laR docs/generated-docs | sed -n '1,200p' + - name: Download versions file + uses: actions/download-artifact@v4 + with: + name: docs-versions + path: docs/app/utils + + - uses: forge-42/fly-deploy@v1.0.0-rc.2 + id: deploy + env: + FLY_ORG: ${{ vars.FLY_ORG }} + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + FLY_REGION: ${{ vars.FLY_REGION }} + with: + workspace_name: docs + app_name: ${{github.event.repository.name}}-${{ github.ref_name }} + use_isolated_workspace: true + env_vars: | + APP_ENV=production + GITHUB_OWNER=${{ github.repository_owner }} + GITHUB_REPO=${{ github.event.repository.name }} + GITHUB_REPO_URL=https://github.com/${{ github.repository }} \ No newline at end of file diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml deleted file mode 100644 index 212e8c7a..00000000 --- a/.github/workflows/publish.yaml +++ /dev/null @@ -1,48 +0,0 @@ -name: Release - -on: - push: - branches: - - main - -jobs: - release: - name: Release - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - actions: write - id-token: write - steps: - - name: Checkout Repo - uses: actions/checkout@v3 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version-file: "package.json" - - - name: Install Dependencies - run: pnpm install - - # - name: ๐Ÿ” Setup npm auth - # run: | - # echo "registry=https://registry.npmjs.org" >> ~/.npmrc - # echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc - - - name: Create Release Pull Request or Publish to npm - id: changesets - uses: changesets/action@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - with: - title: "๐Ÿš€ Release PR" - commit: "chore: release" - version: pnpm run version - publish: pnpm run release - createGithubReleases: true diff --git a/docs/.dockerignore b/docs/.dockerignore index f24a8306..93987521 100644 --- a/docs/.dockerignore +++ b/docs/.dockerignore @@ -84,3 +84,5 @@ blob-report # Content collections output files .content-collections + +# test test \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..df59db5d --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,89 @@ +node_modules +public/build +build +dist +out +coverage +.history +.react-router + +# Other Coverage tools +*.lcov + +# macOS +.DS_* + +# Cache Directories and files +.cache +.yarn* +.env* +!.env.example +.swp* +.turbo +.npm +.stylelintcache +*.tsbuildinfo +.node_repl_history + +# Lock files from other package managers +package-lock.json +yarn.lock + +# General tempory files and directories +t?mp +.t?mp +*.t?mp + +# Docusaurus cache and generated files +.docusaurus + +# Output of 'npm pack' +*.tgz +*.tar +*.tar.gz +*.tar.bz2 +*.tbz +*.zip + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* +vite.config.ts.* + +# Playwright various test reports +test-results +playwright-report +blob-report + + +# Editors +.idea/workspace.xml +.idea/usage.statistics.xml +.idea/shelf + +# Dont commit sqlite database files +*.db +*.sqlite +*.sqlite3 +*.db-journal + + +# Content collections output files +.content-collections + +# Output base directory of the documentation +generated-docs/ diff --git a/docs/.react-router/types/app/+types/root.ts b/docs/.react-router/types/app/+types/root.ts deleted file mode 100644 index 5bd414e1..00000000 --- a/docs/.react-router/types/app/+types/root.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Generated by React Router - -import type { GetInfo, GetAnnotations } from "react-router/internal"; - -type Module = typeof import("../root.js") - -type Info = GetInfo<{ - file: "root.tsx", - module: Module -}> - -type Matches = [{ - id: "root"; - module: typeof import("../root.js"); -}]; - -type Annotations = GetAnnotations; - -export namespace Route { - // links - export type LinkDescriptors = Annotations["LinkDescriptors"]; - export type LinksFunction = Annotations["LinksFunction"]; - - // meta - export type MetaArgs = Annotations["MetaArgs"]; - export type MetaDescriptors = Annotations["MetaDescriptors"]; - export type MetaFunction = Annotations["MetaFunction"]; - - // headers - export type HeadersArgs = Annotations["HeadersArgs"]; - export type HeadersFunction = Annotations["HeadersFunction"]; - - // middleware - export type MiddlewareFunction = Annotations["MiddlewareFunction"]; - - // clientMiddleware - export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"]; - - // loader - export type LoaderArgs = Annotations["LoaderArgs"]; - - // clientLoader - export type ClientLoaderArgs = Annotations["ClientLoaderArgs"]; - - // action - export type ActionArgs = Annotations["ActionArgs"]; - - // clientAction - export type ClientActionArgs = Annotations["ClientActionArgs"]; - - // HydrateFallback - export type HydrateFallbackProps = Annotations["HydrateFallbackProps"]; - - // Component - export type ComponentProps = Annotations["ComponentProps"]; - - // ErrorBoundary - export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"]; -} \ No newline at end of file diff --git a/docs/.vscode/extensions.json b/docs/.vscode/extensions.json new file mode 100644 index 00000000..06e74342 --- /dev/null +++ b/docs/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["codeforge.remix-forge", "biomejs.biome"] +} diff --git a/docs/.vscode/settings.json b/docs/.vscode/settings.json new file mode 100644 index 00000000..85f2f2ce --- /dev/null +++ b/docs/.vscode/settings.json @@ -0,0 +1,47 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnType": false, + "editor.renderWhitespace": "all", + "editor.rulers": [120, 160], + "editor.codeActionsOnSave": { + "source.fixAll": "always", + "source.organizeImports": "never", + "source.organizeImports.biome": "always", + "quickfix.biome": "always" + }, + "eslint.enable": false, + "prettier.enable": false, + "editor.insertSpaces": false, + "editor.detectIndentation": false, + "editor.tabSize": 2, + "editor.trimAutoWhitespace": true, + "workbench.colorCustomizations": { + "editorWhitespace.foreground": "#333" + }, + "files.trimTrailingWhitespace": true, + "files.trimTrailingWhitespaceInRegexAndStrings": true, + "files.trimFinalNewlines": true, + "[yaml]": { + "editor.defaultFormatter": "redhat.vscode-yaml" + }, + "biome.enabled": true, + "editor.defaultFormatter": "biomejs.biome", + "[javascript][typescript][typescriptreact][javascriptreact][json][jsonc][vue][astro][svelte][css][graphql]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "typescript.tsdk": "node_modules/typescript/lib", + "explorer.fileNesting.patterns": { + "*.ts": "${basename}.*.${extname}", + ".env": ".env.*", + "*.tsx": "${basename}.*.${extname},${basename}.*.ts", + "package.json": "*.json, *.yml, *.config.js, *.config.ts, *.yaml, *.workspace.ts", + "readme*": "AUTHORS, Authors, BACKERS*, Backers*, CHANGELOG*, CITATION*, CODEOWNERS, CODE_OF_CONDUCT*, CONTRIBUTING*, CONTRIBUTORS, COPYING*, CREDITS, Changelog*, Citation*, Code_Of_Conduct*, Codeowners, Contributing*, Contributors, Copying*, Credits, GOVERNANCE.MD, Governance.md, HISTORY.MD, History.md, LICENSE*, License*, MAINTAINERS, Maintainers, README-*, README_*, RELEASE_NOTES*, ROADMAP.MD, Readme-*, Readme_*, Release_Notes*, Roadmap.md, SECURITY.MD, SPONSORS*, Security.md, Sponsors*, authors, backers*, changelog*, citation*, code_of_conduct*, codeowners, contributing*, contributors, copying*, credits, governance.md, history.md, license*, maintainers, readme-*, readme_*, release_notes*, roadmap.md, security.md, sponsors*", + "Readme*": "AUTHORS, Authors, BACKERS*, Backers*, CHANGELOG*, CITATION*, CODEOWNERS, CODE_OF_CONDUCT*, CONTRIBUTING*, CONTRIBUTORS, COPYING*, CREDITS, Changelog*, Citation*, Code_Of_Conduct*, Codeowners, Contributing*, Contributors, Copying*, Credits, GOVERNANCE.MD, Governance.md, HISTORY.MD, History.md, LICENSE*, License*, MAINTAINERS, Maintainers, README-*, README_*, RELEASE_NOTES*, ROADMAP.MD, Readme-*, Readme_*, Release_Notes*, Roadmap.md, SECURITY.MD, SPONSORS*, Security.md, Sponsors*, authors, backers*, changelog*, citation*, code_of_conduct*, codeowners, contributing*, contributors, copying*, credits, governance.md, history.md, license*, maintainers, readme-*, readme_*, release_notes*, roadmap.md, security.md, sponsors*", + "README*": "AUTHORS, Authors, BACKERS*, Backers*, CHANGELOG*, CITATION*, CODEOWNERS, CODE_OF_CONDUCT*, CONTRIBUTING*, CONTRIBUTORS, COPYING*, CREDITS, Changelog*, Citation*, Code_Of_Conduct*, Codeowners, Contributing*, Contributors, Copying*, Credits, GOVERNANCE.MD, Governance.md, HISTORY.MD, History.md, LICENSE*, License*, MAINTAINERS, Maintainers, README-*, README_*, RELEASE_NOTES*, ROADMAP.MD, Readme-*, Readme_*, Release_Notes*, Roadmap.md, SECURITY.MD, SPONSORS*, Security.md, Sponsors*, authors, backers*, changelog*, citation*, code_of_conduct*, codeowners, contributing*, contributors, copying*, credits, governance.md, history.md, license*, maintainers, readme-*, readme_*, release_notes*, roadmap.md, security.md, sponsors*", + "Dockerfile": "*.dockerfile, .devcontainer.*, .dockerignore, captain-definition, compose.*, docker-compose.*, dockerfile*" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "editor.formatOnPaste": true +} diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..9dc81681 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/docs/Dockerfile b/docs/Dockerfile index 379b9275..dc5b932e 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -35,4 +35,4 @@ FROM base COPY --from=build /app /app EXPOSE 3000 -CMD ["pnpm","run","start"] +CMD ["pnpm","run","start"] \ No newline at end of file diff --git a/docs/app/components/FeaturesSection.tsx b/docs/app/components/FeaturesSection.tsx deleted file mode 100644 index 857c4fc0..00000000 --- a/docs/app/components/FeaturesSection.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { StickyScroll } from "./ui/sticky-scroll-reveral" - -const content = [ - { - title: "Data display", - description: ( -
- See every - loader data, route params and server information - in real time. See your deferred data load in and see your cached loader information . No more - console.logGet the information you need when you need it. -
- ), - content: ( -
- linear board demo -
- ), - }, - { - title: "Event Timeline", - description: - "See all your navigations, submissions and everything in between in real-time with the timeline tab. No more guessing, no more wondering, see exactly what happens behind the hood.", - content: ( -
- linear board demo -
- ), - }, - { - title: "Routes tab", - description: ( -
- See all your project routes in a - tree/listview, directly open them in the browser with a click of a - button, and see which routes are currently active on the page. - No more guessing. -
- ), - content: ( -
- linear board demo -
- ), - }, - { - title: "Hydration mismatch detector", - description: ( -
- See the difference in HTML rendered on the - server and the - client and figure out what is causing your hydration issues. -
- ), - content: ( -
- linear board demo -
- ), - }, - { - title: "Server logging", - description: ( -
- See your server logs in real time: - - Granular logs for everything that your server does so you don't have to wonder ever again! -
- ), - content: ( -
- linear board demo -
- ), - }, -] -export function FeaturesSection() { - return ( -
- -
- ) -} diff --git a/docs/app/components/backdrop.tsx b/docs/app/components/backdrop.tsx index 42987b13..2e49cf4e 100644 --- a/docs/app/components/backdrop.tsx +++ b/docs/app/components/backdrop.tsx @@ -1,9 +1,12 @@ import { cn } from "~/utils/css" -export const Backdrop = ({ onClose, className }: { onClose: () => void, className?: string }) => ( +export const Backdrop = ({ onClose, className }: { onClose: () => void; className?: string }) => ( // biome-ignore lint/a11y/useKeyWithClickEvents: We don't need keyboard events for backdrop
{ if (e.target === e.currentTarget) { onClose() diff --git a/docs/app/components/code-block/code-block-elements.tsx b/docs/app/components/code-block/code-block-elements.tsx index 9ec0c0d2..3efe0193 100644 --- a/docs/app/components/code-block/code-block-elements.tsx +++ b/docs/app/components/code-block/code-block-elements.tsx @@ -57,7 +57,7 @@ export const PreElement = ({ lines, className = "", ...props }: PreElementProps)
diff --git a/docs/app/components/code-block/code-block-syntax-highlighter.ts b/docs/app/components/code-block/code-block-syntax-highlighter.ts
index da802101..dc09da3d 100644
--- a/docs/app/components/code-block/code-block-syntax-highlighter.ts
+++ b/docs/app/components/code-block/code-block-syntax-highlighter.ts
@@ -6,33 +6,31 @@
 type TokenType = "keyword" | "string" | "number" | "comment" | "operator" | "punctuation" | "function" | "text"
 
 const MASTER_REGEX = new RegExp(
-  [
-    // whitespace
-    "\\s+",
-    // single-line comment
-    "\\/\\/[^\\n\\r]*",
-    // multi-line comment
-    "\\/\\*[\\s\\S]*?\\*\\/",
-    // hash comment at start of line
-    "^\\s*#.*$",
-    // backtick inline code
-    "\\`(?:[^`\\\\]|\\\\.)*\\`",
-    // strings
-    "(['\"])(?:(?!\\1)[^\\\\]|\\\\.)*\\1",
-    // numbers
-    "\\d+\\.?\\d*",
-    // identifiers
-    "[a-zA-Z_$][a-zA-Z0-9_$]*",
-    // arrow function
-    "=>",
-    // operators & punctuation
-    "===|!==|<=|>=|==|!=|&&|\\|\\||\\+\\+|--|[+\\-*%=<>!?:(){}\\[\\];,.]|\\/(?![/*])|[+\\-*/%]=",
-  ].join("|"),
-  "gm"
+	[
+		// whitespace
+		"\\s+",
+		// single-line comment
+		"\\/\\/[^\\n\\r]*",
+		// multi-line comment
+		"\\/\\*[\\s\\S]*?\\*\\/",
+		// hash comment at start of line
+		"^\\s*#.*$",
+		// backtick inline code
+		"\\`(?:[^`\\\\]|\\\\.)*\\`",
+		// strings
+		"(['\"])(?:(?!\\1)[^\\\\]|\\\\.)*\\1",
+		// numbers
+		"\\d+\\.?\\d*",
+		// identifiers
+		"[a-zA-Z_$][a-zA-Z0-9_$]*",
+		// arrow function
+		"=>",
+		// operators & punctuation
+		"===|!==|<=|>=|==|!=|&&|\\|\\||\\+\\+|--|[+\\-*%=<>!?:(){}\\[\\];,.]|\\/(?![/*])|[+\\-*/%]=",
+	].join("|"),
+	"gm"
 )
 
-
-
 const KEYWORDS = [
 	"import",
 	"export",
diff --git a/docs/app/components/code-block/copy-button.tsx b/docs/app/components/code-block/copy-button.tsx
index 9f6859e8..79e9c19b 100644
--- a/docs/app/components/code-block/copy-button.tsx
+++ b/docs/app/components/code-block/copy-button.tsx
@@ -24,7 +24,7 @@ export const CopyButton = ({ lines }: { lines: string[] }) => {
 			onClick={handleCopy}
 			disabled={disabled}
 			className={cn(
-				"absolute top-3 right-3 flex items-center gap-1 rounded px-2 py-1 transition-all text-sm md:text-base",
+				"absolute top-3 right-3 flex items-center gap-1 rounded px-2 py-1 text-xs transition-all sm:text-sm md:text-base",
 				"bg-[var(--color-code-copy-bg)] text-[var(--color-code-copy-text)]",
 				"opacity-0 group-hover:opacity-100",
 				!disabled && "hover:bg-[var(--color-code-copy-hover-bg)]",
diff --git a/docs/app/components/command-k/components/command-k.tsx b/docs/app/components/command-k/components/command-k.tsx
index d7cf170a..57b3986e 100644
--- a/docs/app/components/command-k/components/command-k.tsx
+++ b/docs/app/components/command-k/components/command-k.tsx
@@ -126,7 +126,7 @@ export const CommandK = ({ placeholder, version }: CommandPaletteProps) => {
 				}}
 				placeholder={searchPlaceholder}
 			/>
-			
+
{renderBody()}
diff --git a/docs/app/components/command-k/components/empty-state.tsx b/docs/app/components/command-k/components/empty-state.tsx index 45569fb3..fc79b791 100644 --- a/docs/app/components/command-k/components/empty-state.tsx +++ b/docs/app/components/command-k/components/empty-state.tsx @@ -18,7 +18,7 @@ export const EmptyState = ({ query }: { query?: string }) => { return (

{t("text.start_typing_to_search")}

-
+
diff --git a/docs/app/components/command-k/components/results-footer-note.tsx b/docs/app/components/command-k/components/results-footer-note.tsx index 9c2409e1..1c2dd418 100644 --- a/docs/app/components/command-k/components/results-footer-note.tsx +++ b/docs/app/components/command-k/components/results-footer-note.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next" export const ResultsFooterNote = () => { const { t } = useTranslation() return ( - + {t("p.search_by")}{" "} diff --git a/docs/app/components/command-k/components/trigger-button.tsx b/docs/app/components/command-k/components/trigger-button.tsx index 8de2d77c..5d1ba8e0 100644 --- a/docs/app/components/command-k/components/trigger-button.tsx +++ b/docs/app/components/command-k/components/trigger-button.tsx @@ -12,7 +12,7 @@ export const TriggerButton = ({ type="button" onClick={onOpen} className={cn( - "group flex items-center gap-2 rounded-lg border px-2 py-1.5 text-sm shadow-sm xl:px-3 xl:py-2", + "group flex items-center gap-2 rounded-lg border px-2 py-1.5 text-sm shadow-sm xl:px-3 xl:py-2", "border-[var(--color-trigger-border)] bg-[var(--color-trigger-bg)] text-[var(--color-trigger-text)]", "hover:border-[var(--color-trigger-hover-border)] hover:bg-[var(--color-trigger-hover-bg)] hover:shadow-md", "focus:border-[var(--color-trigger-focus-border)] focus:outline-none focus:ring-2 focus:ring-[var(--color-trigger-focus-ring)]" diff --git a/docs/app/components/github-contribute-links.tsx b/docs/app/components/github-contribute-links.tsx index 9c8a5cb3..02fb3a6d 100644 --- a/docs/app/components/github-contribute-links.tsx +++ b/docs/app/components/github-contribute-links.tsx @@ -16,7 +16,7 @@ export default function GithubContributeLinks({ pagePath }: { pagePath: string } const { issueUrl, editUrl } = createGitHubContributionLinks({ pagePath, owner: GITHUB_OWNER, repo: GITHUB_REPO }) return ( -
+
{t("links.report_an_issue_on_this_page")} diff --git a/docs/app/components/link/Link.browser.test.tsx b/docs/app/components/link/Link.browser.test.tsx deleted file mode 100644 index 626d12d3..00000000 --- a/docs/app/components/link/Link.browser.test.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { waitFor } from "@testing-library/react" -import { userEvent } from "@vitest/browser/context" -import { useLocation } from "react-router" -import type { StubRouteEntry } from "tests/setup.browser" -import { Link, type LinkProps } from "./link" -const getEntries: (linkProps?: LinkProps) => StubRouteEntry[] = (linkProps) => [ - { - path: "/first", - Component: () => { - const url = useLocation() - return ( - <> -

- {url.pathname} + {url.search} -

- - go - - - ) - }, - }, - { - path: "/second", - Component: () => { - const url = useLocation() - return ( - <> -

- {url.pathname} - {url.search} -

- go - - ) - }, - }, -] -describe("Link", () => { - it("if the url is /first and you redirect to /second nothing is added to the url", async ({ renderStub }) => { - const { getByText } = await renderStub({ - entries: getEntries(), - props: { - initialEntries: ["/first"], - }, - }) - const link = getByText("go") - await userEvent.click(link) - const url = getByText("/second") - expect(url).toBeDefined() - await waitFor(() => { - expect(url.element()).toBeDefined() - expect(url.element()).toHaveTextContent("/second") - }) - }) - - it("if the url is /first?a=1 and you redirect to /second without keepSearchParams nothing is added to the url", async ({ - renderStub, - }) => { - const { getByText } = await renderStub({ - entries: getEntries(), - props: { - initialEntries: ["/first?a=1"], - }, - }) - const link = getByText("go") - await userEvent.click(link) - const url = getByText("/second") - await waitFor(() => { - expect(url.element()).toBeDefined() - expect(url.element()).toHaveTextContent("/second") - }) - }) - - it("if the url is /first?a=1 and you redirect to /second with keepSearchParams search params are kept", async ({ - renderStub, - }) => { - const { getByText } = await renderStub({ - entries: getEntries({ keepSearchParams: true, to: "/second" }), - props: { - initialEntries: ["/first?a=1"], - }, - }) - const link = getByText("go") - await userEvent.click(link) - const url = getByText("/second") - await waitFor(() => { - expect(url.element()).toBeDefined() - expect(url.element()).toHaveTextContent("/second?a=1") - }) - }) - - it("if the url is /first?a=1&lng=en and you redirect to /second with keepSearchParams search params and language are kept", async ({ - renderStub, - }) => { - const { getByText } = await renderStub({ - entries: getEntries({ keepSearchParams: true, to: "/second" }), - props: { - initialEntries: ["/first?a=1&lng=en"], - }, - }) - const link = getByText("go") - await userEvent.click(link) - const url = getByText("/second") - await waitFor(() => { - expect(url.element()).toBeDefined() - expect(url.element()).toHaveTextContent("/second?a=1&lng=en") - }) - }) - - it("if the url is /first?a=1&lng=en and you redirect to /second without keepSearchParams language is kept", async ({ - renderStub, - }) => { - const { getByText } = await renderStub({ - entries: getEntries({ to: "/second" }), - props: { - initialEntries: ["/first?lng=en"], - }, - }) - const link = getByText("go") - await userEvent.click(link) - const url = getByText("/second") - await waitFor(() => { - expect(url.element()).toBeDefined() - expect(url.element()).toHaveTextContent("/second?lng=en") - }) - }) - - it("if the url is /first?a=1&lng=en and you redirect to /second with a language override it is changed and search params are removed", async ({ - renderStub, - }) => { - const { getByText } = await renderStub({ - entries: getEntries({ to: "/second", language: "bs" }), - props: { - initialEntries: ["/first?lng=en"], - }, - }) - const link = getByText("go") - await userEvent.click(link) - const url = getByText("/second") - await waitFor(() => { - expect(url.element()).toBeDefined() - expect(url.element()).toHaveTextContent("/second?lng=bs") - }) - }) - - it("if the url is /first?a=1&lng=en and you redirect to /second with a language override it is changed and search params are kept with keepSearchParams", async ({ - renderStub, - }) => { - const { getByText } = await renderStub({ - entries: getEntries({ to: "/second", language: "bs", keepSearchParams: true }), - props: { - initialEntries: ["/first?a=a&lng=en"], - }, - }) - const link = getByText("go") - await userEvent.click(link) - const url = getByText("/second") - await waitFor(() => { - expect(url.element()).toBeDefined() - expect(url.element()).toHaveTextContent("/second?a=a&lng=bs") - }) - }) -}) diff --git a/docs/app/components/logo.tsx b/docs/app/components/logo.tsx index 7ffc1ed8..73622238 100644 --- a/docs/app/components/logo.tsx +++ b/docs/app/components/logo.tsx @@ -7,7 +7,7 @@ export const Logo = ({ children }: { children: ReactNode }) => { // biome-ignore lint/a11y/useKeyWithClickEvents: we don't need keyboard access for this
navigate(href("/:version?/home"))} - className="relative block font-semibold font-space text-[var(--color-text-active)] text-lg md:text-2xl xl:text-3xl cursor-pointer" + className="relative block cursor-pointer font-semibold font-space text-[var(--color-text-active)] text-lg md:text-2xl xl:text-3xl" > {children}
diff --git a/docs/app/components/modal.tsx b/docs/app/components/modal.tsx index a6259677..6c2eef6d 100644 --- a/docs/app/components/modal.tsx +++ b/docs/app/components/modal.tsx @@ -4,91 +4,95 @@ import { cn } from "~/utils/css" import { Backdrop } from "./backdrop" interface ModalProps { - isOpen: boolean - onClose: () => void - children: ReactNode - className?: string - getInitialFocus?: () => HTMLElement | null - restoreFocus?: boolean - ariaLabel?: string + isOpen: boolean + onClose: () => void + children: ReactNode + className?: string + getInitialFocus?: () => HTMLElement | null + restoreFocus?: boolean + ariaLabel?: string } export const Modal = ({ - isOpen, - onClose, - children, - className, - getInitialFocus, - restoreFocus = true, - ariaLabel, + isOpen, + onClose, + children, + className, + getInitialFocus, + restoreFocus = true, + ariaLabel, }: ModalProps) => { - const modalRef = useRef(null) - const previouslyFocusedRef = useRef(null) + const modalRef = useRef(null) + const previouslyFocusedRef = useRef(null) - useScrollLock(isOpen) + useScrollLock(isOpen) - useEffect(() => { - if (!isOpen) return - previouslyFocusedRef.current = document.activeElement as HTMLElement | null - return () => { - if (restoreFocus) previouslyFocusedRef.current?.focus?.() - } - }, [isOpen, restoreFocus]) + useEffect(() => { + if (!isOpen) return + previouslyFocusedRef.current = document.activeElement as HTMLElement | null + return () => { + if (restoreFocus) previouslyFocusedRef.current?.focus?.() + } + }, [isOpen, restoreFocus]) - useEffect(() => { - if (!isOpen) return - const id = requestAnimationFrame(() => { - const candidate = getInitialFocus?.() - if (candidate) { candidate.focus(); return } - const root = modalRef.current - if (!root) return - const firstFocusable = root.querySelector( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ) - firstFocusable?.focus() - }) - return () => cancelAnimationFrame(id) - }, [isOpen, getInitialFocus]) + useEffect(() => { + if (!isOpen) return + const id = requestAnimationFrame(() => { + const candidate = getInitialFocus?.() + if (candidate) { + candidate.focus() + return + } + const root = modalRef.current + if (!root) return + const firstFocusable = root.querySelector( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ) + firstFocusable?.focus() + }) + return () => cancelAnimationFrame(id) + }, [isOpen, getInitialFocus]) - useEffect(() => { - if (!isOpen) return - const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose() } - window.addEventListener("keydown", onKey) - return () => window.removeEventListener("keydown", onKey) - }, [isOpen, onClose]) + useEffect(() => { + if (!isOpen) return + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose() + } + window.addEventListener("keydown", onKey) + return () => window.removeEventListener("keydown", onKey) + }, [isOpen, onClose]) - const handleOverlayPointerDown = (e: React.PointerEvent) => { - const root = modalRef.current - if (!root) return - if (!root.contains(e.target as Node)) onClose() - } + const handleOverlayPointerDown = (e: React.PointerEvent) => { + const root = modalRef.current + if (!root) return + if (!root.contains(e.target as Node)) onClose() + } - if (!isOpen) return null + if (!isOpen) return null - return ( - <> - -
-
-
e.stopPropagation()} - > - {children} -
-
-
- - ) + return ( + <> + +
+
+
e.stopPropagation()} + > + {children} +
+
+
+ + ) } diff --git a/docs/app/components/page-mdx-article.tsx b/docs/app/components/page-mdx-article.tsx index c9795a11..538cfdbb 100644 --- a/docs/app/components/page-mdx-article.tsx +++ b/docs/app/components/page-mdx-article.tsx @@ -4,13 +4,13 @@ import { MDXWrapper } from "./mdx-wrapper" export default function PageMdxArticle({ page }: { page: Page }) { return ( -
+
{page.title} {page.description && ( -

+

{page.description}

)} diff --git a/docs/app/components/sidebar/sidebar-content.tsx b/docs/app/components/sidebar/sidebar-content.tsx index c972a041..63720218 100644 --- a/docs/app/components/sidebar/sidebar-content.tsx +++ b/docs/app/components/sidebar/sidebar-content.tsx @@ -18,7 +18,7 @@ export const SidebarContent = ({ const version = useCurrentVersion() return (