diff --git a/.dockerignore b/.dockerignore index df59db5..afd6a06 100644 --- a/.dockerignore +++ b/.dockerignore @@ -85,5 +85,3 @@ blob-report # Content collections output files .content-collections -# Output base directory of the documentation -generated-docs/ diff --git a/.github/workflows/branch-preview.yml b/.github/workflows/branch-preview.yml deleted file mode 100644 index fb225fe..0000000 --- a/.github/workflows/branch-preview.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: ๐Ÿค– Branch Preview - -concurrency: - group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - push: - branches: [main] - -jobs: - deploy: - name: ๐Ÿš€ Deploy Branch Preview - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - 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: - app_name: ${{github.event.repository.name}}-${{ github.ref_name }} - env_vars: | - APP_ENV=staging - GITHUB_OWNER=${{github.repository_owner}} - GITHUB_REPO=${{github.event.repository.name}} - GITHUB_REPO_URL=https://github.com/${{ github.repository }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2dfac97..a53e919 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,11 @@ -name: ๐Ÿš€ Validation & Deploy Pipeline +name: ๐Ÿš€ Validation Pipeline concurrency: group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true on: pull_request: branches: [main] + permissions: actions: write contents: read @@ -15,74 +16,112 @@ jobs: name: โฌฃ Biome lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: biomejs/setup-biome@v2 - - run: biome ci . --reporter=github + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v4 + - name: Setup Biome + uses: biomejs/setup-biome@v2 + - name: Run Biome + run: biome ci . - typecheck: - needs: lint - name: ๐Ÿ”Ž Type check + validate: + name: ๐Ÿ”Ž Validate runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - 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" - cache: "pnpm" + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Install dependencies + run: pnpm install - run: pnpm install --prefer-offline --frozen-lockfile - - run: pnpm run typecheck + - run: pnpm exec playwright install chromium --with-deps + - name: ๐Ÿ”Ž Test + run: pnpm run test + - name: โœ‚๏ธ Check unused code + run: pnpm run check:unused - check-unused: - needs: lint - name: โœ‚๏ธ Check unused code + build-docs: + name: โฌ†๏ธ 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" - - run: pnpm install --prefer-offline --frozen-lockfile - - run: pnpm run check:unused + cache: pnpm + + - name: Install deps + run: pnpm install --prefer-offline --frozen-lockfile - vitest: - needs: typecheck - name: โšก Unit Tests + - name: Generate docs + env: + APP_ENV: production + run: pnpm run generate:docs + + - name: Pack generated docs (tarball) + run: | + tar -czf docs-generated.tgz 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: app/utils/versions.ts + if-no-files-found: error + + deploy-docs-pr-preview: + name: ๐Ÿš€ Deploy Docs + needs: [build-docs] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + + - name: Download generated docs (tgz) + uses: actions/download-artifact@v4 with: - node-version-file: "package.json" - cache: "pnpm" - - run: pnpm install --prefer-offline --frozen-lockfile - - run: pnpm exec playwright install chromium --with-deps - - run: pnpm run test:cov - - name: "Report Coverage" - # Only works if you set `reportOnFailure: true` in your vite config as specified above - if: always() - uses: davelosert/vitest-coverage-report-action@v2 + name: docs-generated-tgz + path: . + - name: Unpack generated docs + run: | + tar -xzf docs-generated.tgz + ls -laR generated-docs | sed -n '1,200p' + - name: Download versions file + uses: actions/download-artifact@v4 + with: + name: docs-versions + path: app/utils/ - deploy: - needs: [lint, typecheck, check-unused, vitest] - name: ๐Ÿš€ Deploy PR Preview - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - 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: - app_name: ${{github.event.repository.name}}-${{ github.event.number }} - env_vars: | - APP_ENV=staging - GITHUB_OWNER=${{github.repository_owner}} - GITHUB_REPO=${{github.event.repository.name}} - GITHUB_REPO_URL=https://github.com/${{ github.repository }} + - 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: + app_name: ${{ github.event.repository.name }}-${{ 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/build-documentation.yml b/.github/workflows/publish-documentation.yml similarity index 53% rename from .github/workflows/build-documentation.yml rename to .github/workflows/publish-documentation.yml index 2872860..0309b4e 100644 --- a/.github/workflows/build-documentation.yml +++ b/.github/workflows/publish-documentation.yml @@ -11,35 +11,72 @@ concurrency: 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" + 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 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: app/utils/versions.ts + if-no-files-found: error - deploy: - name: ๐Ÿš€ Deploy Release + 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: | + tar -xzf docs-generated.tgz + ls -laR 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: @@ -48,6 +85,7 @@ jobs: FLY_REGION: ${{ vars.FLY_REGION }} with: app_name: ${{github.event.repository.name}}-${{ github.ref_name }} + use_isolated_workspace: true env_vars: | APP_ENV=production GITHUB_OWNER=${{ github.repository_owner }} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9dc8168 --- /dev/null +++ b/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/Dockerfile b/Dockerfile index 0534ba1..ba5788d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,51 +1,38 @@ - # syntax = docker/dockerfile:1.4 -# Base dependencies stage -ARG NODE_VERSION=22.17.0 +ARG NODE_VERSION=22 FROM node:${NODE_VERSION}-slim AS base LABEL fly_launch_runtime="Node.js" - -# Node.js app lives here WORKDIR /app +ENV NODE_ENV=production -# Set production environment -ENV NODE_ENV="production" - -# Install pnpm -ARG PNPM_VERSION=10.13.0 +ARG PNPM_VERSION=10.18.0 RUN npm install -g pnpm@$PNPM_VERSION - -# Throw-away build stage to reduce size of final image +# --- Build stage --- +# We consume the docs generated by the CI FROM base AS build -# Install packages needed to build node modules RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3 git + apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3 && \ + rm -rf /var/lib/apt/lists/* -# Install node modules COPY .npmrc package.json pnpm-lock.yaml ./ -RUN pnpm install --frozen-lockfile --prod=false +RUN pnpm install --prod=false --frozen-lockfile + -# Copy application code COPY . . -# Build application -RUN pnpm run generate:docs RUN pnpm run build -# Remove development dependencies +# Prune dev deps RUN pnpm prune --prod - -# Final stage for app image +# --- Runtime stage --- FROM base -# Copy built application COPY --from=build /app /app -# Start the server by default, this can be overwritten at runtime EXPOSE 3000 -CMD [ "pnpm", "run", "start" ] +CMD ["pnpm","run","start"] \ No newline at end of file diff --git a/LICENSE b/LICENSE index 3a910a1..5a34b88 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Forge 42 +Copyright (c) 2025 Forge 42 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index a2e38fa..5269a36 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Welcome to Forge 42 Documentation Template +# Welcome to Documentation Template This template is designed to support a flexible content structure using `.md` and `.mdx` files organized into folders. It enables deeply nested sections and subsections, making it easy to manage complex documentation with a clear and scalable hierarchy. @@ -110,3 +110,54 @@ pnpm run dev 6. After you see that everything works with the current content inside the `content` folder, remove those files and add your own 7. Happy coding! + +## Features + +### Versioned Documentation +Generate per-tag version folders under `generated-docs/` (for example `v1.0.0/`) to publish and serve multiple doc versions. + +**Example:** +````bash +pnpm run generate:docs --versions "^1.0.0" +```` + +### Local Development Experience +Run `pnpm run dev` to start the dev server and use the hot-reloading `.content-collections/` workflow for fast iteration. The dev server serves live content from `.content-collections/` (your working tree), so it usually shows only the current workspace docs. + +**If you need to preview versioned outputs locally, you have two simple options:** + +1. Run the generator and serve the `generated-docs/` output (example: `pnpm run generate:docs --branch main --versions="^1.0.0"`). This produces version folders under `generated-docs/` but disables hot-reloading because the site reads the generated output instead of the live `.content-collections/` folder. + +2. Create a PR which produces both the versioned `generated-docs/` artifacts and the current snapshot โ€” this is useful for previewing how versioned docs and the live snapshot appear together. + +**Note:** When running the generator you must pass the default branch via `--branch` (for example `--branch main`) so the script can deterministically build the default-branch snapshot. + + +### Automated Docs Generation +The `pnpm run generate:docs` script automates: +- Building the `generated-docs/` folder structure +- Writing `app/utils/versions.ts` which the site consumes to show available versions + +### CI/CD Ready +Includes example GitHub Actions workflows to: +- Build documentation +- Pack the `generated-docs` artifact +- Deploy preview or release sites + +### Docker + Fly Deployment +Includes `Dockerfile` and sample Fly workflows: +- CI uploads the generated docs artifact +- Deploy job unpacks it into the runner workspace +- Image build includes `generated-docs/` + +### Type-Safe and Tested +Built-in quality tooling: +- TypeScript (`tsc`) for type checking +- Biome for linting +- Vitest for testing + +### Accessibility & Performance Focused +Opinionated UI primitives and tooling optimized for: +- Accessibility standards +- Fast page loads +- Great developer experience \ No newline at end of file diff --git a/app/components/backdrop.tsx b/app/components/backdrop.tsx index b76b818..2e49cf4 100644 --- a/app/components/backdrop.tsx +++ b/app/components/backdrop.tsx @@ -1,9 +1,12 @@ import { cn } from "~/utils/css" -export const Backdrop = ({ onClose }: { onClose: () => void }) => ( +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/app/components/code-block/code-block-elements.tsx b/app/components/code-block/code-block-elements.tsx index 7006da8..3efe019 100644 --- a/app/components/code-block/code-block-elements.tsx +++ b/app/components/code-block/code-block-elements.tsx @@ -57,7 +57,7 @@ export const PreElement = ({ lines, className = "", ...props }: PreElementProps)
diff --git a/app/components/code-block/code-block-syntax-highlighter.ts b/app/components/code-block/code-block-syntax-highlighter.ts
index 9c12f56..11980b5 100644
--- a/app/components/code-block/code-block-syntax-highlighter.ts
+++ b/app/components/code-block/code-block-syntax-highlighter.ts
@@ -10,23 +10,25 @@ const MASTER_REGEX = new RegExp(
 		// whitespace
 		"\\s+",
 		// single-line comment
-		"//.*?(?=\\n|$)",
+		"\\/\\/[^\\n\\r]*(?=\\n|$)",
 		// multi-line comment
-		"/\\*[\\s\\S]*?\\*/",
+		"\\/\\*[\\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_$]*",
-		// operators and punctuation
-		"===|!==|<=|>=|==|!=|&&|\\|\\||\\+\\+|--|[+\\-*/%=<>!?:(){}\\[\\];,.]|[+\\-*/%]=",
 		// arrow function
 		"=>",
+		// operators & punctuation
+		"===|!==|<=|>=|==|!=|&&|\\|\\||\\+\\+|--|[+\\-*%=<>!?:(){}\\[\\];,.]|\\/(?![/*])|[+\\-*/%]=",
 	].join("|"),
-	"g"
+	"gm"
 )
 
 const KEYWORDS = [
diff --git a/app/components/command-k/components/results-footer.tsx b/app/components/command-k/components/results-footer.tsx
index 3915e87..4aef83a 100644
--- a/app/components/command-k/components/results-footer.tsx
+++ b/app/components/command-k/components/results-footer.tsx
@@ -15,7 +15,7 @@ export const ResultsFooter = ({
 
 	return (
 		
-
+
{t("text.result", { count: resultsCount })}
diff --git a/app/components/command-k/components/search-history.tsx b/app/components/command-k/components/search-history.tsx index 4deb0e1..e227513 100644 --- a/app/components/command-k/components/search-history.tsx +++ b/app/components/command-k/components/search-history.tsx @@ -35,7 +35,7 @@ const ClearHistoryButton = ({ onClear }: Pick) => type="button" onClick={onClear} className={cn( - "flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors", + "flex items-center gap-1 rounded px-2 py-1 text-sm", "text-[var(--color-result-meta)] hover:bg-[var(--color-history-clear-hover-bg)] hover:text-[var(--color-history-clear-hover-text)]" )} title="Clear history" diff --git a/app/components/command-k/components/search-input.tsx b/app/components/command-k/components/search-input.tsx index da71c9f..5dcaaa6 100644 --- a/app/components/command-k/components/search-input.tsx +++ b/app/components/command-k/components/search-input.tsx @@ -35,7 +35,7 @@ export function SearchInput({ value, onChange, placeholder, ref }: SearchInputPr
diff --git a/app/components/command-k/components/search-result.tsx b/app/components/command-k/components/search-result.tsx index 79b5749..03bf909 100644 --- a/app/components/command-k/components/search-result.tsx +++ b/app/components/command-k/components/search-result.tsx @@ -22,7 +22,7 @@ const ResultIcon = ({ return (
@@ -42,7 +42,7 @@ const ResultTitle = ({ }) => (
@@ -52,7 +52,7 @@ const ResultTitle = ({ ) const ResultMetadata = ({ item, matchType }: Pick) => ( -
+
{item.title} {matchType === "paragraph" && item.subtitle ? > {item.subtitle} : null}
diff --git a/app/components/command-k/components/trigger-button.tsx b/app/components/command-k/components/trigger-button.tsx index e95d791..5d1ba8e 100644 --- a/app/components/command-k/components/trigger-button.tsx +++ b/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 transition-all duration-200 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)]" @@ -26,7 +26,7 @@ export const TriggerButton = ({
@@ -34,7 +34,7 @@ export const TriggerButton = ({ diff --git a/app/components/command-k/create-search-index.ts b/app/components/command-k/create-search-index.ts index bd6997f..f410db0 100644 --- a/app/components/command-k/create-search-index.ts +++ b/app/components/command-k/create-search-index.ts @@ -1,4 +1,4 @@ -import type { Page } from "content-collections" +import type { Page } from "content-collections-types" import slug from "slug" import { getPageSlug } from "~/utils/get-page-slug" diff --git a/app/components/icon-link.tsx b/app/components/icon-link.tsx index 280ee9e..3d64596 100644 --- a/app/components/icon-link.tsx +++ b/app/components/icon-link.tsx @@ -13,7 +13,7 @@ export const IconLink = ({ name, className, ...props }: IconLinkProps) => { return ( { href={href} {...props} > - + ) } diff --git a/app/components/logo.tsx b/app/components/logo.tsx index 86d1eed..7362223 100644 --- a/app/components/logo.tsx +++ b/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" + 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/app/components/modal.tsx b/app/components/modal.tsx index f68b779..6c2eef6 100644 --- a/app/components/modal.tsx +++ b/app/components/modal.tsx @@ -62,26 +62,37 @@ export const Modal = ({ 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() + } + if (!isOpen) return null return ( -
- -
-
e.stopPropagation()} - onClick={(e) => e.stopPropagation()} - > - {children} + <> + +
+
+
e.stopPropagation()} + > + {children} +
-
+ ) } diff --git a/app/components/page-mdx-article.tsx b/app/components/page-mdx-article.tsx index 0639b00..538cfdb 100644 --- a/app/components/page-mdx-article.tsx +++ b/app/components/page-mdx-article.tsx @@ -1,16 +1,16 @@ -import type { Page } from "content-collections" +import type { Page } from "content-collections-types" import { Title } from "~/ui/title" import { MDXWrapper } from "./mdx-wrapper" export default function PageMdxArticle({ page }: { page: Page }) { return ( -
+
{page.title} {page.description && ( -

+

{page.description}

)} diff --git a/app/components/page-navigation.tsx b/app/components/page-navigation.tsx index fe3c772..dde6f3d 100644 --- a/app/components/page-navigation.tsx +++ b/app/components/page-navigation.tsx @@ -64,7 +64,7 @@ export function PageNavigation({ previous, next }: PageNavigationProps) { return (