From 642d65188196b4b5c31fab7efaa83e67a88f884c Mon Sep 17 00:00:00 2001 From: Darius Cepulis Date: Thu, 6 Nov 2025 12:20:46 -0600 Subject: [PATCH] docs: update site/README and add site/CLAUDE (#172) --- eslint.config.mjs | 1 + site/CLAUDE.md | 642 ++++++++++++++++++ site/README.md | 382 +++-------- site/src/components/Aside.astro | 6 +- site/src/content/docs/how-to/write-guides.mdx | 170 ++--- .../docs/sidebar.ts => docs.config.ts} | 0 site/src/{test/setup.ts => test-setup.ts} | 0 site/src/utils/docs/__tests__/sidebar.test.ts | 4 +- site/src/utils/docs/routing.ts | 2 +- site/src/utils/docs/sidebar.ts | 2 +- site/vitest.config.ts | 2 +- 11 files changed, 827 insertions(+), 384 deletions(-) create mode 100644 site/CLAUDE.md rename site/src/{config/docs/sidebar.ts => docs.config.ts} (100%) rename site/src/{test/setup.ts => test-setup.ts} (100%) diff --git a/eslint.config.mjs b/eslint.config.mjs index 677b359f..b1b3ef60 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -22,6 +22,7 @@ export default antfu( // Many are ignored by default. // https://github.com/antfu/eslint-config/blob/main/src/globs.ts#L56 ignores: [ + '**/CLAUDE.md', '**/.astro/', '**/.vercel/', '**/dist/', diff --git a/site/CLAUDE.md b/site/CLAUDE.md new file mode 100644 index 00000000..f5e1306b --- /dev/null +++ b/site/CLAUDE.md @@ -0,0 +1,642 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +The **Video.js v10 documentation site** is an Astro-based static site generator with React islands for interactivity. The site serves documentation, blog posts, and interactive demos for the Video.js v10 library. + +Key architectural feature: **Multi-framework documentation** — the same content generates separate routes for different framework/style combinations (e.g., HTML + CSS, React + CSS), allowing framework-specific documentation from shared MDX sources. + +## Commands + +From `site/` directory: + +| Command | Purpose | +| -------------------- | ---------------------------------------------------- | +| `pnpm dev` | Start dev server at `localhost:4321` | +| `pnpm build` | Build production site to `./dist/` | +| `pnpm preview` | Preview production build locally | +| `pnpm test` | Run all tests once | +| `pnpm test:watch` | Run tests in watch mode | +| `pnpm test:ui` | Run Vitest with web UI | +| `pnpm test:coverage` | Generate coverage report | +| `pnpm astro ...` | Run Astro CLI (e.g., `pnpm astro check`) | + +**From monorepo root:** +- `pnpm dev:site` — Start site dev server +- `pnpm build:site` — Build site + +**Running single test file:** +```bash +pnpm test sidebar.test.ts +``` + +## Astro MCP Server + +An Astro MCP server is likely available. **Always use Astro best practices and standard patterns** for maintainability. Consult the MCP for Astro-specific guidance when working with: +- Astro components and layouts +- Content collections +- Routing patterns +- Integrations +- Build optimizations + +Following Astro conventions ensures consistency and makes the codebase easier to maintain. + +## Tailwind v4 Configuration & Gotchas + +This project uses **Tailwind v4** with a **custom configuration**. Standard Tailwind tokens may not be available. + +### CRITICAL: Always Check globals.css First + +**Before using any Tailwind utility class**, read `src/styles/globals.css` to verify: +- Custom color tokens (e.g., `dark-110`, `light-100`, not standard Tailwind colors) +- Custom text size tokens (e.g., `text-h1`, `text-h2`, not standard `text-4xl`, `text-5xl`) +- Custom tracking values +- Custom font weight values +- Available custom variants +- And possibly more + +### Custom Variant: `intent:` (NOT `hover:` or `focus-visible:`) + +Use the **`intent:` variant** instead of `hover:` and `focus-visible:`: + +```astro + + + + + +``` + +The `intent:` variant is defined as: +```css +@custom-variant intent (&:hover, &:focus-within); +``` + +### Arbitrary Tailwind: Last Resort Only + +**Avoid arbitrary variants like `[&:hover]` or `text-[pink]`**. Use them only as a last resort. + +**Prefer inline styles** when Tailwind utilities don't exist: + +```astro + +
Content
+ + +
Content
+``` + +### Use `clsx` for Class Concatenation + +Always use **`clsx`** (or `cn` helper if available) for conditional classes: + +```tsx +import clsx from 'clsx'; + + +``` + +## Project Structure + +``` +site/ +├── src/ +│ ├── components/ # Astro + React components +│ ├── content/ # Content collections (blog/, docs/, authors.json) +│ ├── layouts/ # Page layouts (Base, Blog, Docs, Markdown) +│ ├── pages/ # Route pages (file-based routing) +│ ├── stores/ # Nanostores for cross-island state +│ ├── styles/ # Global CSS, Tailwind imports +│ ├── types/ # TypeScript type definitions +│ ├── utils/ # Utilities and helpers +│ │ └── docs/ # Documentation-specific utilities +│ │ ├── sidebar.ts # Sidebar filtering and navigation +│ │ ├── routing.ts # Docs URL building and redirects +│ │ └── __tests__/ # Tests for docs utilities +│ ├── consts.ts # Site-wide constants +│ ├── content.config.ts # Content collection schemas +│ ├── docs.config.ts # Documentation sidebar structure +│ └── test-setup.ts # Vitest setup file +├── public/ # Static assets (served untransformed) +├── integrations/ # Custom Astro integrations +│ └── pagefind.ts # Pagefind search integration +├── astro.config.mjs # Astro configuration +├── tsconfig.json # TypeScript config with path aliases +└── vitest.config.ts # Test configuration +``` + +## Multi-Framework Documentation Architecture + +### Framework/Style Combinations + +Documentation is generated for **multiple framework and style combinations** from the same MDX source files. + +**Current support** (defined in `src/types/docs.ts`): +- **Frameworks**: `html`, `react` +- **Styles**: `css` (more may be added) + +**URL pattern:** +``` +/docs/framework/{framework}/style/{style}/{...slug}/ +``` + +**Example:** +- `src/content/docs/how-to/installation.mdx` generates: + - `/docs/framework/html/style/css/how-to/installation/` + - `/docs/framework/react/style/css/how-to/installation/` + +### Content Restriction Mechanisms + +**1. Within MDX content:** + +Use `` or `` components to show framework/style-specific content: + +```mdx + + Use `useState` to manage state. + + + + Use `data-` attributes to manage state. + +``` + +**2. In sidebar config (`src/docs.config.ts`):** + +```ts +const sidebar: Sidebar = [ + { + sidebarLabel: 'Getting started', + contents: [ + { slug: 'how-to/installation' }, // Available to all + { + slug: 'how-to/react-hooks', + frameworks: ['react'] // Only for React + }, + ], + }, +]; +``` + +### Sidebar Configuration + +**Structure** (`src/docs.config.ts`): +- Export a `sidebar` constant of type `Sidebar` +- Hierarchical: Sections contain Guides +- Each Guide has: + - `slug`: Path relative to `src/content/docs/` (without `.mdx`) + - `sidebarLabel` (optional): Override display name + - `frameworks` (optional): Restrict to specific frameworks + - `styles` (optional): Restrict to specific styles + - `devOnly` (optional): Show only in development mode + +**Example:** +```ts +export const sidebar: Sidebar = [ + { + sidebarLabel: 'Components', + contents: [ + { slug: 'reference/play-button' }, + { slug: 'reference/mute-button', sidebarLabel: 'Mute' }, + ], + }, +]; +``` + +## Documentation Utilities + +### Key Utility Functions (`src/utils/docs/`) + +**`sidebar.ts`** — Sidebar filtering and navigation: +- `filterSidebar()`: Filter sidebar by framework/style, remove empty sections +- `findFirstGuide()`: Get first available guide for framework/style combo +- `findGuideBySlug()`: Search sidebar recursively for a guide +- `getAdjacentGuides()`: Get prev/next guides for navigation +- `getValidStylesForGuide()`: Determine valid styles for a guide +- `getSectionsForGuide()`: Get breadcrumb trail to a guide + +**`routing.ts`** — URL building and redirect logic: +- `buildDocsUrl()`: Construct docs URLs from framework/style/slug +- `resolveIndexRedirect()`: Intelligent redirect for index pages + - Handles user preferences from localStorage + - Validates framework/style combinations + - Falls back to defaults when invalid + +### Docs Routing Pattern + +**Nested index pages** handle redirects at each level: +``` +/docs/ → redirect to first guide +/docs/framework/ → redirect to first guide +/docs/framework/{framework}/ → redirect to first guide +/docs/framework/{framework}/style/ → redirect to first guide +/docs/framework/{framework}/style/{style}/ → redirect to first guide +/docs/framework/{framework}/style/{style}/{...slug} → render guide +``` + +Each index page uses `resolveIndexRedirect()` to determine where to redirect based on: +1. URL params (framework, style) +2. User preferences (from localStorage via Nanostores) +3. Defaults (when invalid or missing) + +## Content Collections + +Defined in `src/content.config.ts` using Astro's Content Collections API. + +### IMPORTANT: Only MDX Files Supported + +**We only support `.mdx` files, NOT `.md` files.** + +All content must be written in **MDX format** to support: +- React components within content +- Framework/style conditional rendering (``, ``) +- Custom typography components +- Interactive examples + +### Blog Collection (`src/content/blog/`) + +**Filename convention:** `YYYY-MM-DD-slug.mdx` +- Date prefix automatically removed from slug +- Example: `2024-01-15-new-release.mdx` → slug: `new-release`, URL: `/blog/new-release/` + +**Schema:** +```ts +{ + title: string; + description: string; + pubDate: Date; // From filename or git history + authors: string[]; // Reference to authors.json + devOnly?: boolean; // Show only in development +} +``` + +### Docs Collection (`src/content/docs/`) + +**Subdirectories:** +- `how-to/` — Outcome-focused guides (per Diátaxis framework) +- `concepts/` — Understanding-focused guides +- `reference/` — API documentation + +**Schema:** +```ts +{ + title: string; + description: string; + frameworkTitle?: { // Per-framework title overrides + html?: string; + react?: string; + }; + updatedDate?: Date; // From git history +} +``` + +### Authors Collection (`src/content/authors.json`) + +```ts +{ + [key: string]: { + name: string; + bio?: string; + avatar?: string; + socialLinks?: { platform: string; url: string }[]; + } +} +``` + +### Git Integration + +`src/utils/gitService.ts` uses `simple-git` to enrich content with metadata: +- Blog posts: `pubDate` from filename or first commit +- All content: `updatedDate` from last modification + +## State Management with Nanostores + +**Why Nanostores?** Astro's island architecture means each React component with `client:load` is an isolated React root. React Context doesn't work across islands, so we use Nanostores for cross-island state. + +**Store locations** (`src/stores/`): +- `preferences.ts`: User framework/style preferences (persisted to localStorage) +- `homePageDemos.ts`: Home page demo state +- `tabs.ts`: Tab component state + +**Usage pattern:** +```ts +import { useStore } from '@nanostores/react'; +import { $preferences } from '@/stores/preferences'; + +function MyComponent() { + const prefs = useStore($preferences); + // ... +} +``` + +## Testing + +### Configuration (`vitest.config.ts`) + +```ts +{ + globals: true, // No import needed for describe, it, expect + environment: 'jsdom', // Browser-like environment + setupFiles: ['./src/test-setup.ts'], // Imports @testing-library/jest-dom + coverage: { + provider: 'v8', + include: ['src/utils/**', 'src/components/**', 'src/types/**'], + exclude: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**'], + }, +} +``` + +### Test Organization + +Tests are **colocated** with source code in `__tests__/` directories: +``` +src/utils/docs/ +├── sidebar.ts +├── routing.ts +└── __tests__/ + ├── sidebar.test.ts + └── routing.test.ts +``` + +### Testing Patterns + +**Mock framework/style configuration:** +```ts +vi.mock('@/types/docs', async () => { + const actual = await vi.importActual('@/types/docs'); + return { + ...actual, + FRAMEWORK_STYLES: { html: ['css'], react: ['css'] }, + }; +}); +``` + +**Test complex utilities:** +- Sidebar filtering with nested sections +- Route resolution and redirect logic +- Framework/style validation + +## Technology Stack + +- **[Astro 5.14.4](https://astro.build)**: Static site generation with island architecture +- **[React 18](https://react.dev)**: Client-side interactive components (`client:load`) +- **[Tailwind v4](https://tailwindcss.com)**: CSS utility classes via `@tailwindcss/vite` +- **[Nanostores 1.0.1](https://github.com/nanostores/nanostores)**: Cross-island state +- **[Base UI 1.0.0-beta.4](https://base-ui.com)**: Headless accessible components +- **[Pagefind 1.4.0](https://pagefind.app)**: Static search with build-time indexing +- **[Shiki 3.13.0](https://shiki.style)**: Syntax highlighting +- **[Vitest 3.2.4](https://vitest.dev)**: Testing framework +- **[clsx](https://github.com/lukeed/clsx)**: Class name concatenation utility + +## Custom Astro Integration: Pagefind + +**Location:** `integrations/pagefind.ts` + +**Purpose:** Integrates Pagefind static search into Astro build pipeline. + +**Development mode:** +- Serves Pagefind index from previous production build +- Uses `sirv` middleware to serve `/pagefind/*` routes +- Warns if index doesn't exist (needs `pnpm build` first) + +**Production mode:** +- Runs Pagefind CLI after Astro build completes +- Indexes all HTML files in `dist/` +- Maps Astro logger levels to Pagefind CLI flags + +**Usage in `astro.config.mjs`:** +```js +import pagefind from './integrations/pagefind'; + +export default defineConfig({ + integrations: [pagefind()], +}); +``` + +## TypeScript Configuration + +**Path aliases** (`tsconfig.json`): +```json +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + } +} +``` + +**Import examples:** +```ts +import { sidebar } from '@/docs.config'; +import type { Sidebar } from '@/types/docs'; +import { filterSidebar } from '@/utils/docs/sidebar'; +``` + +**Strict mode enabled:** +- `noUncheckedIndexedAccess: true` +- `exactOptionalPropertyTypes: true` + +## Key Architecture Patterns + +### 1. Recursive Sidebar Filtering + +Sidebar filtering is recursive because sections can contain guides or nested sections: + +```ts +export function filterSidebar( + sidebar: Sidebar, + framework: SupportedFramework, + style: AnySupportedStyle, +): Sidebar { + return sidebar + .map((section) => ({ + ...section, + contents: section.contents.filter((item) => + isItemVisible(item, framework, style) + ), + })) + .filter((section) => section.contents.length > 0); +} +``` + +### 2. Type Guards for Framework/Style Validation + +**Defined in `src/types/docs.ts`:** + +```ts +export const FRAMEWORK_STYLES = { + html: ['css'], + react: ['css'], +} as const; + +export function isValidFramework(value: unknown): value is SupportedFramework { + return typeof value === 'string' && value in FRAMEWORK_STYLES; +} + +export function isValidStyleForFramework( + framework: SupportedFramework, + style: unknown, +): style is AnySupportedStyle { + return typeof style === 'string' + && FRAMEWORK_STYLES[framework].includes(style as any); +} +``` + +### 3. Git-Enriched Content Metadata + +Content collections automatically enrich metadata from git history: + +```ts +// In content.config.ts +const blog = defineCollection({ + loader: globWithParser({ + pattern: '**/*.mdx', + base: './src/content/blog', + async parseData(frontmatter, fileUrl) { + const filePath = fileURLToPath(fileUrl); + const updatedDate = await getLastModifiedDate(filePath); + return { ...frontmatter, updatedDate }; + }, + }), +}); +``` + +### 4. Island Architecture with React + +Each React component with `client:load` is an **independent React root**: + +```astro +--- +import Tabs from '@/components/Tabs.tsx'; +import Search from '@/components/Search/Search.tsx'; +--- + + + +``` + +**Consequence:** React Context doesn't work across islands. Use Nanostores instead. + +## MDX Component Typography + +**Location:** `src/components/typography/` + +Standard MDX elements (headings, paragraphs, lists, etc.) are defined here and used across all MDX layouts (blog, docs, markdown pages). + +**Usage in layouts:** +```astro +--- +import { components } from '@/components/typography'; +--- + + +``` + +## Important Development Notes + +### Writing Documentation + +Read `src/content/docs/how-to/write-guides.mdx` for comprehensive guide-writing instructions. + +Key points: +- **Use `.mdx` files only** (not `.md`) +- Use `` and `` for framework/style-specific content +- Follow Diátaxis framework: how-to vs. concept guides +- Add new guides to `src/docs.config.ts` sidebar +- Use `devOnly: true` for internal documentation + +### Search Indexing + +Pagefind indexes HTML files after build. During development: +1. Run `pnpm build` at least once to generate search index +2. Dev server serves the index from previous build +3. Search won't include new content until next build + +### Adding Framework/Style Support + +To add a new framework or style: + +1. Update `FRAMEWORK_STYLES` in `src/types/docs.ts` +2. Update type definitions (`SupportedFramework`, `AnySupportedStyle`) +3. Add corresponding page routes in `src/pages/docs/framework/[framework]/` +4. Update sidebar filtering logic if needed (usually automatic) +5. Update tests to include new framework/style + +### Blog Post Naming + +**CRITICAL:** Blog post filenames MUST be date-prefixed: +``` +YYYY-MM-DD-slug.mdx +``` + +The date prefix is automatically removed from the slug during content collection transformation by `src/utils/globWithParser.ts`. + +**Example:** +- File: `2024-01-15-new-release.mdx` +- Slug: `new-release` +- URL: `/blog/new-release/` + +### Development-Only Content + +Use `devOnly: true` in frontmatter or sidebar config to hide content in production: + +```ts +// In docs.config.ts +{ slug: 'how-to/write-guides', devOnly: true } +``` + +```mdx +--- +title: Internal Documentation +devOnly: true +--- +``` + +## Common Tasks + +### Adding a New Docs Guide + +1. Create MDX file in `src/content/docs/{how-to|concepts|reference}/your-guide.mdx` +2. Add frontmatter with `title` and `description` +3. Add to sidebar in `src/docs.config.ts` +4. Optional: Restrict to specific frameworks/styles +5. Test with `pnpm dev` and verify all framework/style combinations + +### Running Tests for Specific Utility + +```bash +# Run sidebar tests +pnpm test sidebar.test.ts + +# Run in watch mode +pnpm test:watch sidebar.test.ts + +# With UI +pnpm test:ui +``` + +### Debugging Redirect Logic + +The `resolveIndexRedirect()` function in `src/utils/docs/routing.ts` returns a `reason` field explaining why a particular redirect was chosen: + +```ts +const result = resolveIndexRedirect({ preferences, params }); +console.log(result.reason); // e.g., "using preference framework and style" +``` + +### Checking TypeScript + +```bash +pnpm astro check +``` + +This runs Astro's built-in TypeScript checker across `.astro`, `.ts`, and `.tsx` files. diff --git a/site/README.md b/site/README.md index 6d141cb2..1bee4ff1 100644 --- a/site/README.md +++ b/site/README.md @@ -1,121 +1,109 @@ -## 🚀 Project Structure +# Video.js Website -Inside of your Astro project, you'll see the following folders and files: +For docs, blog, and more: [v10.videojs.org](https://v10.videojs.org). + +Mostly a standard [Astro](https://astro.build/) project. + +## Project Structure ```text -├── public/ +├── public/ # assets served, un-transformed, as v10.videojs.org/[filename] ├── src/ +│ ├── assets/ # assets that might be imported into components, pages, etc. │ ├── components/ -│ ├── content/ -│ ├── layouts/ -│ └── pages/ +│ ├── content/ # MDX goes here +│ ├── examples/ # temporary, until we figure out how to generate component docs +│ ├── layouts/ # Astro components that are typically used to wrap pages +│ ├── pages/ # where Astro looks to generate routes +│ ├── stores/ # we communicate between components with nanostores +│ ├── styles/ +│ ├── types/ +│ ├── utils/ +│ ├── consts.ts # stuff a lot of components, utils, pages, etc. use +│ ├── content.config.ts # [read more](https://docs.astro.build/en/guides/content-collections/) +│ ├── docs.config.ts # Where we define the docs sidebar +│ └── test-setup.ts # for vitest ├── astro.config.mjs -├── README.md +├── CLAUDE.md ├── package.json -└── tsconfig.json +├── README.md +├── TODO.md # not comprehensive. Should be turned into issues, eventually. +├── tsconfig.json +└── vitest.config.ts ``` -Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. +## Commands -There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. +If you're in the monorepo's root... -The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more. +| Command | Action | +| :---------------- | :------------------------------------------ | +| `pnpm dev:site` | Starts local dev server at `localhost:4321` | +| `pnpm build:site` | Build the production site to `site/dist/` | -Any static assets, like images, can be placed in the `public/` directory. +If you're in `site/`... -## 🧞 Commands +| Command | Action | +| :------------------- | :----------------------------------------------- | +| `pnpm install` | Installs dependencies | +| `pnpm dev` | Starts local dev server at `localhost:4321` | +| `pnpm build` | Build your production site to `./dist/` | +| `pnpm preview` | Preview your build locally, before deploying | +| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` | +| `pnpm test` | Run tests with Vitest | +| `pnpm test:watch` | Run tests in watch mode | +| `pnpm test:ui` | Run Vitest with its web-based UI | +| `pnpm test:coverage` | Generate test coverage report | -All commands are run from the root of the project, from a terminal: +## Technology Stack -| Command | Action | -| :--------------------- | :----------------------------------------------- | -| `pnpm install` | Installs dependencies | -| `pnpm dev` | Starts local dev server at `localhost:4321` | -| `pnpm build` | Build your production site to `./dist/` | -| `pnpm preview` | Preview your build locally, before deploying | -| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` | -| `pnpm astro -- --help` | Get help using the Astro CLI | +Here are most of the technologies you should get to know when you're building this site: -## 🚀 Project architecture +- [**Astro**](https://astro.build) - Mostly-static site generation with [island architecture](https://docs.astro.build/en/concepts/islands/) +- [**React**](https://react.dev) - Most of our client-side interactivity is built with React components (each with `client:load` is an isolated React root) +- [**Tailwind v4**](https://tailwindcss.com) - CSS utility class generator +- [**Nanostores**](https://github.com/nanostores/nanostores) - Shared client-side state (React Context doesn't work across islands) +- [**Base UI**](https://base-ui.com) - Headless accessible components +- [**Pagefind**](https://pagefind.app) - Static search with build-time indexing -### Overview +## Content -The website serves two main purposes: +### The blog -1. **Blog** - News, updates, and announcements about Video.js -2. **Documentation** - Multi-framework documentation system with conditional content +Let's start with the blog because it's more simple. -### Project Structure +- Blog posts are written in and stored in [`src/content/blog/`](src/content/blog/) as [MDX](https://mdxjs.com) files +- Astro's [Content Collections API](https://docs.astro.build/en/guides/content-collections/) transforms the MDX into data +- That data is rendered in `src/pages/blog/[...slug].astro` +- Standard MDX typography is defined in `src/components/typography/` -```text -site/ -├── src/ -│ ├── components/ # Reusable UI components -│ │ └── docs/ # Documentation-specific components -│ ├── config/ # Configuration files -│ │ └── docs/ # Documentation sidebar configuration -│ ├── content/ # Content collections (blog, docs) -│ │ ├── blog/ # Blog posts (Markdown/MDX) -│ │ └── docs/ # Documentation pages (MDX) -│ ├── layouts/ # Page layouts -│ ├── pages/ # Route definitions -│ ├── styles/ # Global styles -│ ├── types/ # TypeScript type definitions -│ ├── utils/ # Utility functions -│ └── consts.ts # Site-wide constants -├── public/ # Static assets (fonts, favicon, images) -└── astro.config.mjs # Astro configuration -``` +The only weird thing about the blog? Blog posts use date-prefixed filenames: `YYYY-MM-DD-slug.mdx`. For example: `2024-01-15-new-release.mdx`. The date prefix is removed by [utils/globWithParser.ts](src/utils/globWithParser.ts) during content collection transformation, so the post's slug just becomes `new-release` (and its url, `/blog/new-release/`). -### Key Features +### The docs -#### 1. Blog System +#### How to add a docs page -The blog uses Astro's Content Collections API with a custom loader for automatic metadata extraction. +Just looking to add a doc and don't really care about the implementation? -##### Filename Convention +Check out [`src/content/docs/docs/how-to/write-docs.mdx`](src/content/docs/docs/how-to/write-docs.mdx). -Blog posts use date-prefixed filenames: `YYYY-MM-DD-slug.{md,mdx}` +Still interested in implementation? Ok, let's dive in: -Example: `2024-01-15-new-release.md` +#### Docs are generated for multiple frameworks and styles -##### Automatic Metadata +We want docs to feel idiomatic, no matter your framework or styling preference. React users shouldn't have to learn about Web Components, HTML users shouldn't have to understand React Hooks, and so on. -- **Publication date** is extracted from the filename -- **Updated date** is pulled from git history (last commit that modified the file) -- **URL slug** has the date prefix removed for clean URLs (`/blog/new-release/`) +We currently support two frameworks (HTML, React) and one styling approach (CSS). This is defined in [types/docs.ts](src/types/docs.ts). -##### Implementation +Every doc is generated for every framework / style combination. E.g., `how-to/installation.mdx` becomes: -See [content.config.ts](src/content.config.ts) for the blog collection definition and [utils/globWithParser.ts](src/utils/globWithParser.ts) for the custom loader implementation. +- `/docs/framework/html/style/css/how-to/installation/` +- `/docs/framework/react/style/css/how-to/installation/` -#### 2. Multi-Framework Documentation System +Content that applies to only certain frameworks or styles can be restricted in two ways: -The most sophisticated part of the website is the documentation system, which adapts content based on: - -- **Framework** (HTML, React) -- **Styling approach** (CSS, more coming soon) - -##### URL Structure - -```text -/docs/framework/{framework}/style/{style}/{slug}/ -``` - -Example: `/docs/framework/react/style/css/concepts/state-management/` - -##### Framework/Style Matrix - -| Framework | Available Styles | -| --------- | ---------------- | -| HTML | css | -| React | css | - -##### Content Filtering - -Documentation pages can be restricted to specific frameworks or styles: - -**In sidebar config** ([config/docs/sidebar.ts](src/config/docs/sidebar.ts)): +1. Within the MDX content itself, by wrapping framework- or style-specific content in `` or `` components. (Read more about these components in [`src/content/docs/docs/how-to/write-docs.mdx`](src/content/docs/docs/how-to/write-docs.mdx).) +2. In the sidebar config ([docs.config.ts](src/docs.config.ts)), by specifying `frameworks` and/or `styles` on a per-guide basis, e.g., ```ts const sidebar = { @@ -131,221 +119,25 @@ const sidebar = { }; ``` -**Within MDX content** (using conditional components): +### Guides -```mdx -This content only appears in React docs. +The docs consist of two parts: -This content only appears when Tailwind is selected. -``` +1. Guides, written in [MDX](https://mdxjs.com) +2. 🚧 Not yet built 🚧 References, generated from source code -##### Static Site Generation +Let's talk about guides, first. -The docs system generates all valid framework/style/slug combinations at build time: +You'll learn most of what you need to know about writing guides by reading [`src/content/docs/docs/how-to/write-docs.mdx`](src/content/docs/docs/how-to/write-docs.mdx). -1. Filter sidebar based on framework/style -2. Generate pages only for guides visible in that combination -3. Each page is pre-rendered with the appropriate filtered sidebar +High-level primer? -See [pages/docs/framework/[framework]/style/[style]/[...slug].astro](src/pages/docs/framework/[framework]/style/[style]/[...slug].astro) for implementation. +- Guides are written in MDX and stored in `src/content/docs/` +- Guides are separated into how-to guides (focused on an outcome) and concept guides (focused on understanding) according to the [Diátaxis](https://diataxis.fr) framework. +- Astro's [Content Collections API](https://docs.astro.build/en/guides/content-collections/) transforms the MDX into data +- That data is rendered in `src/pages/docs/framework/[framework]/style/[style]/[...slug].astro` +- Standard MDX typography is defined in `src/components/typography/` -##### Smart Navigation +### Generated references -The framework/style selectors ([components/docs/Selectors.tsx](src/components/docs/Selectors.tsx)) attempt to preserve the current guide when switching: - -1. User switches from "React" to "HTML" -2. System checks if current guide supports HTML -3. If yes: Navigate to same guide with HTML/best-style -4. If no: Navigate to first available guide for HTML - -This creates a seamless experience where users don't lose their place when switching contexts. - -### Custom Utilities - -#### globWithParser ([utils/globWithParser.ts](src/utils/globWithParser.ts)) - -Wraps Astro's `glob` loader to provide access to both transformed entry IDs and original filenames. This is essential for extracting dates from date-prefixed blog post filenames while maintaining clean URLs. - -**How it works:** - -1. Wraps the `generateId` function to capture ID transformations -2. Stores mapping: `transformed ID → original filename` -3. Injects custom parser that receives both values -4. Parser adds metadata to entry before schema validation - -**Why it's needed:** - -- `generateId` transforms: `2024-01-15-post.md` → `post` -- Parser needs access to `2024-01-15-post.md` to extract the date -- Without this utility, the original filename is lost after transformation - -### Content Collections - -#### Blog Collection - -- **Location**: `src/content/blog/` -- **Formats**: Markdown (`.md`) and MDX (`.mdx`) -- **Required frontmatter**: `title`, `description` -- **Auto-injected**: `pubDate` (from filename), `updatedDate` (from git) - -#### Docs Collection - -- **Location**: `src/content/docs/` -- **Formats**: MDX only (needs conditional components) -- **Required frontmatter**: `title`, `description` -- **Auto-injected**: `updatedDate` (from git) -- **Visibility**: Controlled by sidebar configuration - -### Layouts - -#### Base Layout - -Base HTML structure with: - -- SEO meta tags (Open Graph, Twitter Cards) -- Font preloading -- Client-side routing (Astro Transitions) - -#### BlogPost Layout - -Extends Base with blog-specific elements: - -- Hero image display -- Publication and updated dates -- Article formatting - -#### Docs Layout - -Extends Base with documentation features: - -- Filtered sidebar navigation -- Framework/style selectors (React component with client-side routing) -- Two-column layout (sidebar + content) - -### Configuration - -#### Site Configuration - -[astro.config.mjs](astro.config.mjs) includes: - -- MDX integration for rich content authoring -- React integration for interactive components (selectors) -- Sitemap generation -- RSS feed generation -- Tailwind CSS via Vite plugin - -### Sidebar Configuration - -[config/docs/sidebar.ts](src/config/docs/sidebar.ts) defines the documentation structure: - -- Hierarchical sections and subsections -- Guide definitions with framework/style restrictions -- Custom sidebar labels (overrides doc titles) - -### Type System - -The documentation system is fully typed in TypeScript: - -- `SupportedFramework` - Union type of available frameworks -- `SupportedStyle` - Style type specific to a framework -- `Guide` - Guide definition with optional restrictions -- `Section` - Recursive type for sidebar sections -- `Sidebar` - Array of top-level sections - -See [types/docs.ts](src/types/docs.ts) for complete type definitions. - -### Development Workflow - -#### Adding a Blog Post - -1. Create file: `src/content/blog/YYYY-MM-DD-slug.md` -2. Add frontmatter: `title`, `description` -3. Write content in Markdown or MDX -4. The `pubDate` is automatically extracted from filename -5. The `updatedDate` is automatically pulled from git on subsequent commits - -#### Adding a Documentation Page - -1. Create file: `src/content/docs/category/page-name.mdx` -2. Add frontmatter: `title`, `description` -3. Add to sidebar in `src/config/docs/sidebar.ts`: - - ```ts - const sidebar = { - title: 'Category', - guides: [ - { - slug: 'category/page-name', - frameworks: ['react'], // optional - styles: ['tailwind', 'css'] // optional - } - ] - }; - ``` - -4. Use conditional components for framework/style-specific content - -#### Adding a New Framework - -1. Update `FRAMEWORK_STYLES` in [types/docs.ts](src/types/docs.ts) -2. Add available styles for the framework -3. Update documentation to include framework-specific guides -4. The system will automatically generate all URL combinations - -#### Adding a New Style - -1. Update `FRAMEWORK_STYLES` in [types/docs.ts](src/types/docs.ts) -2. Add the style to appropriate framework(s) -3. Update documentation guides to support the new style -4. The system will automatically generate all URL combinations - -### Build Output - -The site is statically generated with: - -- Pre-rendered HTML for all pages -- Client-side routing for SPA-like navigation -- Optimized images via Astro's image optimization -- Sitemap at `/sitemap-index.xml` -- RSS feed at `/rss.xml` - -### Dependencies - -#### Core Framework - -- **Astro** - Static site generator with island architecture -- **React** - For interactive components (framework/style selectors) -- **TypeScript** - Type safety throughout - -#### Content Processing - -- **@astrojs/mdx** - MDX support for rich content -- **@astrojs/rss** - RSS feed generation -- **@astrojs/sitemap** - Sitemap generation - -#### Styling - -- **Tailwind CSS** - Via @tailwindcss/vite plugin -- **@astrojs/react** - React component support - -#### Git Integration - -- **simple-git** - For extracting file modification dates from git history - -### Performance Considerations - -- All pages are pre-rendered at build time -- Client-side routing eliminates full page reloads -- Images are optimized with Astro's built-in image optimization -- Fonts are preloaded to prevent layout shift -- Minimal JavaScript (only for interactive selectors) - -### Future Enhancements - -Areas for potential improvement: - -- Add search functionality -- Implement active link highlighting in sidebar -- Create custom 404 page with framework/style context -- Add automated testing for sidebar filtering logic -- Consider server-side rendering for dynamic content +🚧 Under construction 🚧 diff --git a/site/src/components/Aside.astro b/site/src/components/Aside.astro index c10442b5..30ba0b72 100644 --- a/site/src/components/Aside.astro +++ b/site/src/components/Aside.astro @@ -13,6 +13,10 @@ type Props = { const { type, title, class: className } = Astro.props; +if (!['note', 'tip', 'caution', 'danger'].includes(type)) { + throw new Error(`Invalid aside type: ${type}`); +} + const icons: Record = { note: Info, tip: Lightbulb, @@ -48,7 +52,7 @@ const renderTitle = title ?? defaultTitles[type]; >
-
+
` and `` +First, understand that the guide you write will be rendered for every framework / style combination (e.g., HTML + CSS, React + CSS) unless you restrict it in the sidebar config as shown above. + +Use the `` and `` components to conditionally show content to just one framework or style: -Use the `` component to show content only for specific frameworks. For example, ```mdx React-only content ``` -will render: React-only content - -## Style-Specific Content + Use the `` component to show content only for specific styling approaches. For example, ```mdx - Css-only content + CSS-only content ``` -will render: - Css-only content + CSS-only content + + ## Use Github-Flavored Markdown ### Headings @@ -55,16 +90,12 @@ Don't use H1 (or in markdown, `# H1`). We already have an H1 at the top of the p ## H2 with `code` - ### H3 with `code` - #### H4 with `code` - ##### H5 with `code` - ###### H6 with `code` ### Paragraph @@ -81,31 +112,21 @@ We don't actually support images yet, lol. The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations. -#### Blockquote without attribution - -##### Syntax - ```markdown > Tiam, ad mint andaepu dandae nostion secatur sequo quae. > **Note** that you can use _Markdown syntax_ within a blockquote. ``` -##### Output - > Tiam, ad mint andaepu dandae nostion secatur sequo quae. > **Note** that you can use _Markdown syntax_ within a blockquote. -#### Blockquote with attribution - -##### Syntax +### Footnotes ```markdown > Don't communicate by sharing memory, share memory by communicating.
> — Rob Pike[^1] ``` -##### Output - > Don't communicate by sharing memory, share memory by communicating.
> — Rob Pike[^1] @@ -113,15 +134,12 @@ The blockquote element represents content that is quoted from another source, op ### Tables -#### Syntax - ```markdown | Italics | Bold | Code | | --------- | -------- | ------ | | _italics_ | **bold** | `code` | ``` -#### Output | Italics | Bold | Code | | --------- | -------- | ------ | @@ -129,8 +147,6 @@ The blockquote element represents content that is quoted from another source, op ### Code Blocks -#### Syntax - we can use 3 backticks ``` in new line and write snippet and close with 3 backticks on new line and to highlight language specific syntax, write one word of language name after first 3 backticks, for eg. html, javascript, css, markdown, typescript, txt, bash ````markdown @@ -148,7 +164,6 @@ we can use 3 backticks ``` in new line and write snippet and close with 3 backti ``` ```` -#### Output ```html @@ -167,40 +182,30 @@ we can use 3 backticks ``` in new line and write snippet and close with 3 backti #### Ordered List -##### Syntax - ```markdown 1. First item 2. Second item 3. Third item ``` -##### Output - 1. First item 2. Second item 3. Third item #### Unordered List -##### Syntax - ```markdown - List item - Another item - And another item ``` -##### Output - - List item - Another item - And another item #### Nested list -##### Syntax - ```markdown - Fruit - Apple @@ -211,7 +216,7 @@ we can use 3 backticks ``` in new line and write snippet and close with 3 backti - Cheese ``` -##### Output +Which looks like: - Fruit - Apple @@ -221,9 +226,9 @@ we can use 3 backticks ``` in new line and write snippet and close with 3 backti - Milk - Cheese -### Other Elements — abbr, sub, sup, kbd, mark +### HTML elements, like abbr, sub, sup, kbd, mark -#### Syntax +You can also write raw HTML within your markdown. This is useful for elements like... ```markdown GIF is a bitmap image format. @@ -237,7 +242,7 @@ Press CTRL + ALT + Delete to end the session. Most salamanders are nocturnal, and hunt for insects, worms, and other small creatures. ``` -#### Output +Which would output: GIF is a bitmap image format. @@ -249,7 +254,9 @@ Press CTRL + ALT + Delete to end the session. Most salamanders are nocturnal, and hunt for insects, worms, and other small creatures. -## Asides / Callouts / Admonitions + +## Other custom components +### Asides / Callouts / Admonitions Use the ` -## Code and Code Frames - -### Default frame - -Regular markdown code blocks automatically get wrapped in tabs with a copy button: - -````markdown -```ts -console.log('Hello, TypeScript!'); -``` -```` -Renders -```ts -console.log('Hello, TypeScript!'); -``` - ### Tabs -Use ``, ``, and `` to show multiple code examples side-by-side: +Use ``, ``, ``, and `` to show multiple items side-by-side. + + + ````mdx @@ -318,7 +316,8 @@ Use ``, ``, and `` to show multiple code examples side ```` -Which renders + + TypeScript @@ -336,18 +335,26 @@ Which renders -**Important notes:** -- Use `` to wrap Tab components, and place TabsPanel components directly as children of TabsRoot -- Set `initial` on the first Tab and first TabsPanel to make them active by default -- All child components (`TabsList`, `Tab`, `TabsPanel`) require `client:load` directive in MDX -- Use descriptive `label` prop on TabsList for accessibility +### ``, for showing code from a `{variable}` or `?raw` imported file -## Displaying Code from Files +While MDX supports inline variables like `{someVariable}`, this doesn't work inside of markdown code blocks. +If you tried +````mdx +```tsx +{someVariable} +``` +```` +You would just get +```tsx +{someVariable} +``` -Use the `` component to display code imported from source files with syntax highlighting. Supports any language that [Shiki supports](https://shiki.style/languages). +To work around this, use `` instead of triple backticks. -**Important:** Unlike regular markdown code blocks, `` does **not** automatically get wrapped in a frame. You should use `TabsRoot` with a single `TabPanel` to make it look pretty + ```mdx import componentCode from '@/examples/react/Component.tsx?raw'; @@ -379,11 +386,12 @@ function Component() { -## Wrapping Live Demos +## Literally any other component -There are two main patterns for displaying live demos, depending on whether you want to show code alongside the demo. +This is just MDX, so you can import any old component and use it. To make that imported component look pretty, you might consider wrapping it in `` or placing it inside of a `` alongside code, as shown below. -### Option 1: Standalone Demo with Minimal Frame + +### `` to limit max width and provide a nice border Use the `` frame component to wrap standalone demos with a styled border and background: @@ -395,11 +403,9 @@ import MinimalFrame from '@/components/frames/Minimal.astro'; ``` -This is best for demos that don't need accompanying code, or when the code is shown separately elsewhere on the page. +### `` supports arbitrary content, too -### Option 2: Demo with Code in TabsRoot - -Place the demo directly inside the `tabs-panels` Fragment (as a sibling to `` elements) to keep code and demo together: +You can place any old content underneath ``, and it'll show alongside whatever tabbed content you're showing. This is a pattern we've used so far to display code alongside a rendered example (e.g., PlayButton). ```mdx import { MyDemo } from '@/examples/react/MyDemo'; @@ -421,6 +427,4 @@ import ServerCode from '@/components/Code/ServerCode.astro'; -``` - -The demo will appear at the bottom of the tabs component, creating a cohesive unit of code and preview. This pattern is used throughout our resource documentation (see PlayButton for an example). \ No newline at end of file +``` \ No newline at end of file diff --git a/site/src/config/docs/sidebar.ts b/site/src/docs.config.ts similarity index 100% rename from site/src/config/docs/sidebar.ts rename to site/src/docs.config.ts diff --git a/site/src/test/setup.ts b/site/src/test-setup.ts similarity index 100% rename from site/src/test/setup.ts rename to site/src/test-setup.ts diff --git a/site/src/utils/docs/__tests__/sidebar.test.ts b/site/src/utils/docs/__tests__/sidebar.test.ts index bb3af701..b7f183a8 100644 --- a/site/src/utils/docs/__tests__/sidebar.test.ts +++ b/site/src/utils/docs/__tests__/sidebar.test.ts @@ -422,7 +422,7 @@ describe('sidebar utilities', () => { describe('findFirstGuide with real sidebar config', () => { it('should return a guide for every valid framework/style combination', async () => { // Import the real sidebar config - const { sidebar: realSidebar } = await import('../../../config/docs/sidebar'); + const { sidebar: realSidebar } = await import('../../../docs.config'); const { ALL_FRAMEWORK_STYLE_COMBINATIONS } = await import('../../../types/docs'); // Test each valid combination @@ -437,7 +437,7 @@ describe('sidebar utilities', () => { describe('sidebar config validation', () => { it('should not have duplicate slugs in sidebar config', async () => { // Import the real sidebar config - const { sidebar: realSidebar } = await import('../../../config/docs/sidebar'); + const { sidebar: realSidebar } = await import('../../../docs.config'); const allSlugs = getAllGuideSlugs(realSidebar); const uniqueSlugs = new Set(allSlugs); diff --git a/site/src/utils/docs/routing.ts b/site/src/utils/docs/routing.ts index 25853540..65a92069 100644 --- a/site/src/utils/docs/routing.ts +++ b/site/src/utils/docs/routing.ts @@ -1,5 +1,5 @@ import type { AnySupportedStyle, Sidebar, SupportedFramework } from '@/types/docs'; -import { sidebar as defaultSidebar } from '@/config/docs/sidebar'; +import { sidebar as defaultSidebar } from '@/docs.config'; import { DEFAULT_FRAMEWORK, getDefaultStyle, isValidFramework, isValidStyleForFramework } from '@/types/docs'; import { findFirstGuide, findGuideBySlug, getValidFrameworksForGuide, getValidStylesForGuide, isItemVisible } from './sidebar'; diff --git a/site/src/utils/docs/sidebar.ts b/site/src/utils/docs/sidebar.ts index 9ba4201f..59fe9456 100644 --- a/site/src/utils/docs/sidebar.ts +++ b/site/src/utils/docs/sidebar.ts @@ -1,6 +1,6 @@ import type { AnySupportedStyle, Guide, Section, Sidebar, SupportedFramework, SupportedStyle } from '@/types/docs'; -import { sidebar } from '../../config/docs/sidebar'; +import { sidebar } from '../../docs.config'; import { FRAMEWORK_STYLES, isSection, isValidStyleForFramework } from '../../types/docs'; /** diff --git a/site/vitest.config.ts b/site/vitest.config.ts index 5b291d9c..0794f62f 100644 --- a/site/vitest.config.ts +++ b/site/vitest.config.ts @@ -7,7 +7,7 @@ export default getViteConfig({ test: { globals: true, environment: 'jsdom', - setupFiles: ['./src/test/setup.ts'], + setupFiles: ['./src/test-setup.ts'], coverage: { provider: 'v8', reporter: ['text', 'json', 'html'],