diff --git a/.claude/skills/gamut-create-skill/SKILL.md b/.claude/skills/gamut-create-skill/SKILL.md index 0413760183..582825081a 100644 --- a/.claude/skills/gamut-create-skill/SKILL.md +++ b/.claude/skills/gamut-create-skill/SKILL.md @@ -252,7 +252,7 @@ For skills covering a concept rather than a single component (e.g. `gamut-color- --- -## Reference skills by type +## Prop Reference skills by type | Type | Skill | Read it for | | ---------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------- | diff --git a/.gitignore b/.gitignore index e877cbac30..1d69c55010 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,10 @@ tmp dist/storybook dist/docs +# Astro / Starlight build +packages/starlight/dist +packages/starlight/.astro + # NX dist/out-tsc *.tsbuildinfo diff --git a/docs/adr/0001-documentation-site-information-architecture.md b/docs/adr/0001-documentation-site-information-architecture.md new file mode 100644 index 0000000000..1e5eb4f1ac --- /dev/null +++ b/docs/adr/0001-documentation-site-information-architecture.md @@ -0,0 +1,153 @@ +# ADR 0001: Information architecture for the new Gamut documentation site + +- **Status:** Proposed +- **Date:** 2026-08-18 +- **Ticket:** GMT-1727 +- **Deciders:** Gamut maintainers + +## Context + +Gamut's documentation lives in a Storybook (`packages/styleguide`) whose structure has grown organically. An audit against the [Diátaxis framework](https://diataxis.fr) identified structural problems: + +1. **Navigation is organized by Gamut's internal taxonomy, not user need.** The sidebar (Atoms / Molecules / Organisms / Foundations / Layouts / Typography) follows Atomic Design. Readers must know how Gamut classifies a component before they can find it, and cross-cutting tasks (theming an app, building a form, supporting dark mode) have no home. +2. **Documentation modes are interleaved.** Component pages mix design guidance, task recipes, and API reference in inconsistent orders (e.g., `Alert` documents `closeButtonProps` as prose bullets between example sections; `Menu` buries accessibility-critical role guidance mid-page). Section vocabularies diverge per page ("Specifications", "HTML element", "Roles"). +3. **There are no tutorials.** Nothing walks a newcomer from zero to a working page. `Installation` — the most task-critical page — is buried mid-list in a grab-bag "Meta" section. +4. **"Meta" is a junk drawer** spanning how-to (Installation, Contributing), reference (ESLint rules), and explanation (Brand, Best practices, FAQs), organized by authors' filing convenience rather than reader need. + +We are building a new documentation website (Astro + Starlight). This is the moment to fix the information architecture rather than port the current structure. + +Diátaxis distinguishes four documentation modes by user need — **tutorials** (learning), **how-to guides** (working toward a goal), **reference** (looking up facts), and **explanation** (understanding) — and holds that each unit of content should serve one mode, with navigation letting readers stay in their mode. It applies fractally: at site scale and again within a topic area such as a single component. + +## Decision + +### 1. Top-level navigation follows the Diátaxis modes + +The sidebar's top level maps to the reader's lifecycle — learn it, do a task, look something up, understand it deeper: + +```text +Getting started TUTORIALS — Installation, Build your first page, Using this site +Guides HOW-TO — Theming your app, Building forms, Supporting dark + mode, Writing UX copy, Migrating to logical + properties, Contributing to Gamut +Components REFERENCE — all components, grouped by function (see §2) +Foundations REFERENCE — design tokens, themes, system props, icon/asset + catalogs, ESLint rules, tooling +Concepts EXPLANATION — architecture of the system, theming model, color + modes, brand, best practices, voice & tone, FAQs +``` + +The "Meta" section is dissolved: its pages distribute into Getting started (Installation, Usage guide), Guides (Contributing), Foundations (ESLint rules, Deep Controls), and Concepts (Brand, Best practices, FAQs). Storybook's `Foundations/` folder splits along the same line: token values and theme palettes move to the new Foundations section; the rationale behind them moves to Concepts. + +### 2. Components are grouped by function, not by Atomic Design tier + +Readers arrive with a need ("I have to tell the user something went wrong"), not knowledge of Gamut's composition hierarchy. The Atoms/Molecules/Organisms tiers are retired from navigation (the tier may survive as a metadata badge on component pages). Components are grouped by what they do: + +- **Actions** — Button, CTAButton, FillButton, StrokeButton, TextButton, IconButton, Toggle, Menu, Tag +- **Containers** — Box, FlexBox, GridBox, Card, ContentContainer, LayoutGrid, Disclosure, Drawer +- **Inputs & forms** — Input, TextArea, Checkbox, Radio, Select, SelectDropdown, DatePicker; Form scaffolding (Form, FormGroup, FormGroupLabel, FormGroupDescription, FormRequiredText); ConnectedForm (ConnectedForm, ConnectedFormGroup, ConnectedFormInputs, SubmitButton); GridForm +- **Navigation** — Anchor, Breadcrumbs, Pagination, Tabs, SkipToContent +- **Feedback** — Alert, Toast, Toaster, Coachmark, Tips (ToolTip, InfoTip, PreviewTip) +- **Status** — Badge, ProgressBar, RadialProgress, Loaders (Spinner, Shimmer), FeatureShimmer +- **Overlays** — Modal, Dialog, Overlay, Popover, PopoverContainer, Flyout +- **Data display** — List & Tables (List [with ListRow, ListCol, TableHeader], DataTable, DataList), BarChart, Markdown +- **Typography** — Text +- **Media & assets** — Icons (Regular, Mini), Illustrations, Patterns, Animations (ExpandInCollapseOut, FadeInSlideOut, Rotation), Video +- **Utilities** — FocusTrap, DelayedRenderWrapper + +Placement rulings for components that could live in two categories (the losing category's landing page cross-links them): + +| Component | Ruling | Rationale | +| --------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Menu | Actions | The action-list role is the richer half of its API; nav role is cross-linked | +| Tag | Actions | Docs emphasize interactive selection/removal; read-only overlap with Badge is arbitrated by "When NOT to use" | +| Tips | Feedback | Readers think "the system explaining something"; Overlays is reserved for things you open and dismiss | +| Drawer | Containers | Collapses within page flow, unlike Flyout which floats above it (Overlays) | +| Anchor | Navigation | Functionally navigation despite its typography implementation | +| Toggle | Actions | Most effective as an immediate on/off action, like a button, rather than a value collected on form submission; cross-linked from Inputs & forms since it's visually/functionally close to Checkbox | + +Existing multi-component group index pages (Buttons, Tips, Modals, Loaders, List & Tables, etc.) survive as collapsible group nodes within their category. Each such group folder is named in Title Case on disk (`Buttons/`, `Tips/`, `ConnectedForm/`, `GridForm/`, `List & Tables/`) — Starlight takes a nested folder's sidebar group label directly from its literal directory name, with no case transformation, so this is the only way to get a properly capitalized group header. (Top-level categories don't have this problem: their labels are set explicitly in `astro.config.mjs`, independent of the `components/actions`-style lowercase folder underneath.) Every group's own index page — at every level, from `/components/` down to `Buttons/index` — uses the sidebar label `Overview` rather than repeating the group's name a second time as its own first child item. A folder name with spaces/punctuation (`List & Tables`) still produces a valid, if visually dense, URL slug (`list--tables`, since Astro's slugifier hyphenates every non-alphanumeric run individually rather than collapsing `&`+space into one hyphen) — same cosmetic tradeoff as the squished lowercase `ConnectedForm`/`GridForm` slugs, not fixed with a per-file `slug:` override. + +Astro's slug generation lowercases the URL regardless of this folder casing, with no separator reinserted at a word boundary — `ConnectedForm/` and `GridForm/` become `/connectedform/` and `/gridform/`, not `/connected-form/` or `/grid-form/`. This is accepted as-is rather than fixed with a `slug:` override on every file in those two folders; the payoff (a correctly capitalized sidebar group) is worth the minor URL cosmetic cost, and nothing currently depends on those URLs having a separator. + +### 3. Component pages re-apply Diátaxis at page scale, split across two systems by mode + +Every component page follows one template with a fixed section order grouped by mode — explanation, then how-to, then reference — so a reader in any mode can jump to their zone and stay there. Explanation and how-to are authored natively in Starlight; reference stays authored in Storybook, which already has purpose-built tooling for it (addon-docs Controls, exhaustive story canvases) that Starlight doesn't. The Starlight page pulls reference material in by embedding the live Storybook canvas, rather than duplicating it: + +```text +Header title, subtitle, status, Figma link, source link +── EXPLANATION (authored in Starlight) ── +Usage when to reach for it + Best practices design/UX guidance (not API guidance) + When NOT to use every bullet names and links the alternative component + Anatomy labeled diagram of the component's parts +── HOW-TO (authored in Starlight) ── +Patterns goal-first recipes — one intent sentence, then either the + matching Storybook story or (only if no story can show it) + a code snippet; shared verbatim across a component group, + written once on the group's index page (see rule 7) +── REFERENCE (authored in Storybook, embedded in Starlight) ── +Playground the single default `StoryEmbed`, as its own top-level + heading — only when no variant-like prop has any + dedicated story coverage. Mutually exclusive with + Prop Reference (see rule 4). +Prop Reference accessibility notes and per-prop story embeds — + written and maintained as Storybook stories/MDX, + surfaced here as `StoryEmbed`s grouped under one or + more `###` subheadings (`### Variant`, `### State`, + ...), one per variant-like prop, never as a bare + top-level embed. Present only once some variant-like + prop has dedicated story coverage (see rules 4–5). +``` + +Template rules: + +1. **Required always:** Header, Usage (with When NOT to use), and exactly one of Playground or Prop Reference. **Optional but fixed-position:** Anatomy (skip for non-visual utilities), Patterns (skip for stateless components). +2. **The heading vocabulary is closed.** No page invents new top-level sections ("Specifications", "HTML element"); new headings require updating the template first. `Variants`, `Accessibility`, and `Props` are retired as top-level Starlight headings — that material now lives under `Prop Reference`, in Storybook. A component with no variant-like prop worth breaking out gets a standalone `Playground` heading instead of an empty `Prop Reference` — see rule 4. +3. **Patterns prefer a matching Storybook story over a code snippet.** If a story already demonstrates the pattern — a dedicated story (`IconButton`'s `CloseOnClick`), or the same behavior reachable by changing a prop in the default story's Controls — embed or point to that story instead of showing static code. Write an inline snippet only when the pattern is a composition the Controls panel can't express (for example, wrapping the component in a `ToolTip` to explain a disabled state): nothing goes in a snippet that already renders live somewhere in Storybook. +4. **`Playground` and `Prop Reference` are mutually exclusive top-level headings, chosen by whether any variant-like prop has dedicated story coverage.** If no variant-like prop (`variant`, `type`, `spacing`, ...) has any dedicated per-value story, the page gets a standalone `## Playground` heading holding just the default `StoryEmbed` — no `Prop Reference` heading at all. As soon as a variant-like prop has _any_ dedicated story coverage — full or partial — the page gets `## Prop Reference` instead, and the default embed moves under that prop's own `###` subheading (`### Variant`, `### Spacing`) alongside whatever other values are covered (see rule 5 for the partial case): that subheading already _is_ the playground, so a separate `Playground` heading next to it would be redundant. `Menu` (`variant`, `spacing`) and `Tag` (`variant`) have full coverage, so both get `## Prop Reference` with no `Playground` heading at all; `ContentContainer` has partial coverage on `size`, so its default embed lives under `### Variant` in its `Prop Reference` section, next to the linked `wide` story; `FillButton` and `LayoutGrid` have no variant-like prop with any story coverage, so each gets a standalone `## Playground` heading and no `Prop Reference` section. +5. **Partial story coverage gets linked, not embedded — but still lives under the variant subheading, alongside the default embed if the page has one.** If a variant-like prop has a dedicated story for only _some_ of its values, list and link those values (rather than embedding them; embedding a partial set risks implying it's the whole picture) under that prop's `### Variant`/`### Variants` subheading, inside `Prop Reference`. `ContentContainer`'s `size` is covered for `medium` (the default story) and `wide`, but not `small`: the default embed goes under `### Variant` as usual, and `wide` is linked directly beneath it. The button family's `variant` is covered for `Primary`/`Secondary` only, on the shared `Button` overview story, which has no default story of its own to embed — so its `### Variant` section on the group page is links only. A value only reachable by changing a Control on a default story doesn't get an entry at all. Never write a variant list, or embed a variant grid, that Storybook itself can't back with a matching story per item. + 5a. **Two or more embeds stacked under the same subheading each get their own `####` label naming the specific value shown.** A bare stack of `StoryEmbed`s under one `### Variant`/`### Type`/`### State` heading forces the reader to guess which rendered example is which — a `Stroke` embed sitting next to a `Text` embed with no label reads as two unlabeled screenshots. Give each one its own `#### ` immediately above it (`#### Stroke`, `#### Text`; `#### General`, `#### Success`, …), matching `Menu`'s `#### Popover`/`#### Fixed` precedent. Skip the label only when a single embed already shows every value together in one canvas (`Anchor`'s `Modes` story renders all four variants side by side in one iframe) or when the subheading holds exactly one embed. +6. **Tutorials never appear on component pages** — learning-oriented material lives only in Getting started. +7. **Shared Patterns and shared Prop Reference material across a component group are written once, on the group's index page.** When sibling components in a multi-component group (Buttons: FillButton, StrokeButton, TextButton, IconButton, CTAButton; similarly Tips, Loaders, Modals) share the same how-to almost verbatim — rendering as a link, adding an icon, explaining a disabled state — that Pattern lives on the group's `index` page (e.g. `Buttons/index.md`), not repeated on every sibling. The same goes for reference material that exists at the family level rather than per component: Storybook's `Button` overview story demonstrates `variant` values and light/dark mode across the whole family (not any one button), so its `Primary`/`Secondary` stories are listed under the group page's own `Prop Reference` section (rule 5) and its light/dark-mode story is a group-page Pattern, rather than either being duplicated on every sibling or forced onto whichever button happens to be `component:` in that story's meta. A sibling's own page keeps Usage/Anatomy/Playground-or-Prop-Reference plus any Pattern genuinely specific to it — `IconButton`'s tooltip-persistence pattern stays on `IconButton`'s page, since no sibling has it — and links to the group page for the shared ones. + +If a component outgrows a single page (heavy guidelines plus a large API, e.g. GridForm), its explanation/how-to content splits into per-mode child pages along the template's section boundaries; its Storybook-side reference content is unaffected. + +## Consequences + +### Positive + +- Readers navigate by need: newcomers land in Getting started, task-driven developers in Guides, fact-lookups in Components/Foundations — without knowing Gamut's internal taxonomy. +- Predictable component pages: a fixed section order and closed heading vocabulary make every page scannable the same way, and give doc authors an unambiguous checklist. +- The migration is mostly mechanical: nearly every existing page has a destination in the new structure. Only two pages are net-new ("Build your first page" tutorial; "Architecture of the system" concept, largely extractable from Storybook's existing `Foundations/` prose). +- Cross-cutting guides (theming, forms, dark mode) finally have a home instead of being fragmented under component folders. +- **Splitting reference out to Storybook shrinks the per-component migration to Usage/Anatomy/Patterns only** — the largest, most tedious part of each page (an exhaustive Variants grid, a Props table, a working Playground) is never re-authored; it's already correct in Storybook and stays there. +- **One source of truth for reference material.** Variants, accessibility notes, and props are authored once, in the system built for that job (Storybook's addon-docs Controls and Canvas). Starlight and Storybook can't drift out of sync on prop tables because only one of them owns that content. +- **Patterns default to showing something real.** Preferring a matching Storybook story over a code snippet means most Patterns are a live, currently-rendering example rather than prose describing what code would theoretically do — and it can't silently go stale the way an unexecuted snippet can. +- **Grouped components stop repeating themselves.** Buttons, Tips, Loaders, and similar groups share most of their how-to content near-verbatim; writing it once on the group's index page instead of five times cuts both authoring effort and the chance one sibling's copy quietly drifts from the others. + +### Negative / risks + +- **Categorization is a judgment call.** Functional categories have ambiguous edges (see placement rulings). Mitigation: cross-links from the losing category, plus site search for readers who already know a component's name. +- **Deep links break.** Every existing Storybook URL path changes. Mitigation: redirect map from old story IDs to new site URLs. +- **Mixed-mode pages persist by design.** A component page containing explanation, how-to, and reference on one page bends strict Diátaxis (one mode per page). We accept this: the practical harm Diátaxis targets is _interleaving_, which the fixed section order eliminates, and one-page-per-component matches how design-system consumers work. +- **Template enforcement needs tooling.** Without a check, heading vocabularies will drift again. Mitigation: lint page headings against the allowed list in CI. +- **The site now has a hard runtime dependency on Storybook staying deployed** — this is a durable architectural split, not a transitional bridge until migration finishes. If the Storybook deployment goes down or a story ID changes without a redirect, every component page's Playground or Prop Reference section breaks. Mitigation: keep Storybook's build/deploy in CI as a first-class pipeline (not a legacy artifact slated for removal), and treat published story IDs as a stable contract. +- **Two authoring surfaces for one component page.** A contributor documenting a new prop now touches Storybook (to add/update the story) and, only if it changes usage guidance, Starlight. Mitigation: this is the same trade a "single source of truth" always makes; the Storybook-side workflow doesn't change from today's. +- **Embedding via iframe, rather than copying code into Starlight, is deliberate: Storybook stays the single source of truth for component code snippets.** A `.stories.tsx` file is the one place a variant's example code is written; Starlight never forks a second copy that can silently drift out of sync with the real component API. The cost is that Starlight's site search (Pagefind) can't index that code, prop names, or accessibility notes, since it lives inside an iframe pointed at a separate deployment — a reader searching Starlight for a prop name won't find it there. Mitigation: every `StoryEmbed` ships with a visible link to open the full story, so a reader can still get to that content manually; if the search gap proves painful in practice, the fallback is indexing Storybook's stories into Pagefind separately, not duplicating the code itself. +- **A Pattern living on the group's index page is one hop further from a sibling's own page than an inline section would be.** A reader on `FillButton`'s page has to notice and follow a link to `Buttons/index` to find "Rendering as a link," rather than finding it inline. Mitigation: every sibling's Patterns section opens with an explicit pointer to the group page rather than silently omitting the section; a Pattern only moves to the group page once it's confirmed identical across siblings, not merely similar. +- **Not every "shared" Pattern stays shared forever.** If one button's disabled-state or icon behavior diverges later, it has to be split back out of the group page into that component's own page without breaking the others' links to it. Mitigation: this is the same fixed-section-boundary mechanism §3 already uses for a component outgrowing one page — moving content back down is a known, designed-for operation, not a special case. +- **Title-Case group folders produce a squished, separator-less URL segment for compound names** (`ConnectedForm/` → `/connectedform/`, `GridForm/` → `/gridform/`), since Astro's slug generation lowercases but doesn't re-insert word breaks. Mitigation: accepted as a cosmetic cost, not fixed with per-file `slug:` overrides, since nothing currently depends on a separator being present; revisit only if that changes. + +## Alternatives considered + +1. **Port the current Atomic Design structure to the new site.** Rejected: reproduces every audited problem; the migration is the cheapest moment to fix IA. +2. **Flat alphabetical component list** (early proposal). Rejected in favor of functional grouping: alphabetical requires knowing the component's name, functional matches need-driven arrival; search covers name-based lookup. Industry precedent (Adobe Spectrum's Actions/Containers/Feedback/... grouping) favors functional. +3. **Strict Diátaxis: one page per mode per component** (à la Carbon/Material per-component tabs). Rejected for now: quadruples page count and maintenance for many small components. The template's section boundaries are the designated split points if a component outgrows one page. +4. **Organize top-level by audience (designers vs. engineers).** Rejected: most Gamut readers wear both hats within a single task; mode-based navigation serves the actual switching behavior. +5. **Repeat identical Patterns on every sibling page in a component group** (the original approach for Buttons: the same "rendering as a link," icon, and disabled-tooltip patterns copy-pasted across all five). Rejected: five near-identical copies of the same prose is exactly the drift risk this ADR is otherwise trying to eliminate for reference content — a wording fix or a newly-discovered edge case would need to land in five places to stay consistent. Writing it once on the group's index page and linking to it costs one extra click per sibling page in exchange for a single place to keep it correct. + +## References + +- Diátaxis framework: +- Adobe Spectrum component grouping (functional-category precedent): +- Current styleguide source: `packages/styleguide/src/lib` diff --git a/package.json b/package.json index 7fe3a3dc24..06e73efabe 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "@types/react-test-renderer": "^18.2.0", "@typescript-eslint/utils": "^8.57.0", "axios": "1.14.0", + "cookie": "^2.0.1", "error-ex": "1.3.4", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -125,6 +126,7 @@ "scripts": { "build": "nx run-many --target=build --all", "build-all": "yarn build", + "build-docs-site": "nx run starlight:build", "build-storybook": "nx run styleguide:build-storybook", "build:graph": "nx graph", "clean-builds": "nx run-many --target=clean --all", @@ -137,6 +139,7 @@ "prepare": "husky", "prettier": "prettier --ignore-path .prettierignore \"./**/*.{mdx,js,ts,tsx,json,css,scss}\"", "start": "yarn && yarn start:storybook", + "start:docs": "nx run starlight:dev", "start:storybook": "nx storybook styleguide", "test": "nx run-many --target=test --all", "test:storybook": "nx run styleguide:storybook-test", diff --git a/packages/starlight/astro.config.mjs b/packages/starlight/astro.config.mjs new file mode 100644 index 0000000000..dc0f462362 --- /dev/null +++ b/packages/starlight/astro.config.mjs @@ -0,0 +1,163 @@ +import react from '@astrojs/react'; +import starlight from '@astrojs/starlight'; +import { defineConfig } from 'astro/config'; +import starlightSidebarTopics from 'starlight-sidebar-topics'; + +// Sidebar structure follows docs/adr/0001-documentation-site-information-architecture.md. +// Top level maps to Diátaxis modes: Getting started (tutorials), Guides (how-to), +// Components + Reference (reference), Concepts (explanation). +// Each top-level section is its own topic (starlight-sidebar-topics), so its +// sidebar replaces the others instead of all five being stacked in one long list. +export default defineConfig({ + site: 'https://gamut.codecademy.com', + server: { + port: 3333, + }, + vite: { + ssr: { + // CommonJS modules imported with named imports inside @codecademy/gamut. + // Vite must bundle (rather than externalize) them during dev SSR to + // generate the named-export interop; without this the dev server fails + // with "[vite] Named export '…' not found". + noExternal: ['react-use'], + }, + }, + integrations: [ + react(), + starlight({ + title: 'Gamut', + description: "Codecademy's design system for the web", + social: [ + { + icon: 'github', + label: 'GitHub', + href: 'https://github.com/Codecademy/gamut', + }, + ], + editLink: { + baseUrl: + 'https://github.com/Codecademy/gamut/edit/main/packages/starlight/', + }, + customCss: ['./src/styles/gamut-core-theme.css'], + plugins: [ + starlightSidebarTopics( + [ + { + label: 'Getting started', + icon: 'rocket', + link: '/getting-started/installation/', + items: [{ autogenerate: { directory: 'getting-started' } }], + }, + { + label: 'Guides', + icon: 'open-book', + link: '/guides/', + items: [{ autogenerate: { directory: 'guides' } }], + }, + { + label: 'Foundations', + icon: 'information', + link: '/foundations/', + items: [{ autogenerate: { directory: 'foundations' } }], + }, + { + label: 'Components', + icon: 'puzzle', + link: '/components/', + items: [ + { + label: 'Overview', + slug: 'components', + }, + { + label: 'Actions', + items: [ + { autogenerate: { directory: 'components/actions' } }, + ], + }, + { + label: 'Containers', + items: [ + { autogenerate: { directory: 'components/containers' } }, + ], + }, + { + label: 'Inputs & forms', + items: [ + { + autogenerate: { + directory: 'components/inputs-and-forms', + }, + }, + ], + }, + { + label: 'Navigation', + items: [ + { autogenerate: { directory: 'components/navigation' } }, + ], + }, + { + label: 'Feedback', + items: [ + { autogenerate: { directory: 'components/feedback' } }, + ], + }, + { + label: 'Status', + items: [{ autogenerate: { directory: 'components/status' } }], + }, + { + label: 'Overlays', + items: [ + { autogenerate: { directory: 'components/overlays' } }, + ], + }, + { + label: 'Data display', + items: [ + { + autogenerate: { directory: 'components/data-display' }, + }, + ], + }, + { + label: 'Typography', + items: [ + { autogenerate: { directory: 'components/typography' } }, + ], + }, + { + label: 'Media & assets', + items: [ + { + autogenerate: { + directory: 'components/media-and-assets', + }, + }, + ], + }, + { + label: 'Utilities', + items: [ + { autogenerate: { directory: 'components/utilities' } }, + ], + }, + ], + }, + { + label: 'Concepts', + icon: 'document', + link: '/concepts/', + items: [{ autogenerate: { directory: 'concepts' } }], + }, + ], + { + // The splash-template homepage has no topic of its own. + exclude: ['/'], + } + ), + ], + }), + ], +}); diff --git a/packages/starlight/package.json b/packages/starlight/package.json new file mode 100644 index 0000000000..a37d6fd28e --- /dev/null +++ b/packages/starlight/package.json @@ -0,0 +1,34 @@ +{ + "name": "@codecademy/gamut-docs", + "description": "Gamut documentation site (Astro + Starlight)", + "version": "0.1.0", + "dependencies": { + "@astrojs/react": "^6.0.4", + "@astrojs/starlight": "^0.41.9", + "@codecademy/gamut": "workspace:*", + "@codecademy/gamut-icons": "workspace:*", + "@codecademy/gamut-styles": "workspace:*", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "astro": "^7.2.6", + "astro-live-code": "^0.0.6", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "starlight-sidebar-topics": "^0.8.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1" + }, + "license": "MIT", + "private": true, + "repository": "git@github.com:Codecademy/gamut.git", + "scripts": { + "build": "astro build", + "check": "astro check", + "dev": "astro dev", + "preview": "astro preview", + "start": "astro dev" + }, + "type": "module" +} diff --git a/packages/starlight/project.json b/packages/starlight/project.json new file mode 100644 index 0000000000..745bef591f --- /dev/null +++ b/packages/starlight/project.json @@ -0,0 +1,32 @@ +{ + "name": "starlight", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/starlight/src", + "projectType": "application", + "tags": [], + "targets": { + "dev": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/starlight", + "command": "astro dev" + } + }, + "build": { + "executor": "nx:run-commands", + "outputs": ["{projectRoot}/dist"], + "options": { + "cwd": "packages/starlight", + "command": "astro build" + } + }, + "preview": { + "executor": "nx:run-commands", + "dependsOn": ["build"], + "options": { + "cwd": "packages/starlight", + "command": "astro preview" + } + } + } +} diff --git a/packages/starlight/src/components/FigmaEmbed.astro b/packages/starlight/src/components/FigmaEmbed.astro new file mode 100644 index 0000000000..d1808b16d4 --- /dev/null +++ b/packages/starlight/src/components/FigmaEmbed.astro @@ -0,0 +1,29 @@ +--- +/** + * Embeds a Figma frame via Embed Kit 2.0 (embed.figma.com). Static HTML — + * no client JS. The target file must be shared as "anyone with the link can + * view" for anonymous visitors to see it; otherwise the iframe shows a + * Figma login wall. Keep a node-id in the URL so the embed opens zoomed to + * the relevant frame instead of the entire file. + */ +interface Props { + /** Full Figma URL, including node-id for a specific frame */ + url: string; + height?: string; +} + +const { url, height = '28rem' } = Astro.props; + +const embedUrl = new URL( + url.replace(/www\.figma\.com\/(file|design)\//, 'embed.figma.com/design/') +); +embedUrl.searchParams.set('embed-host', 'gamut-docs'); +--- + + diff --git a/packages/starlight/src/components/StoryEmbed.astro b/packages/starlight/src/components/StoryEmbed.astro new file mode 100644 index 0000000000..241650ecd9 --- /dev/null +++ b/packages/starlight/src/components/StoryEmbed.astro @@ -0,0 +1,45 @@ +--- +/** + * A component page's Reference section (per + * docs/adr/0001-documentation-site-information-architecture.md §3) embeds + * this rather than re-authoring variants/accessibility/props/playground + * content: an iframe onto a live Storybook story canvas, paired with a link + * to open that same story directly in Storybook. Storybook stays the single + * source of truth for that content — this is a durable part of the site's + * architecture, not a stopgap pending further migration. + * + * Requires the Storybook deployment to remain live at gamut.codecademy.com. + */ +interface Props { + /** Storybook story id, e.g. "atoms-buttons-fillbutton--default" */ + id: string; + height?: string; +} + +const { id, height = '10rem' } = Astro.props; + +const storybookOrigin = 'https://gamut.codecademy.com'; +const src = `${storybookOrigin}/iframe.html?id=${id}&viewMode=story`; + +// Storybook ids are "--<story-name>"; the docs page for the whole +// story group lives at "<title>--docs". +const storyGroupId = id.slice(0, id.lastIndexOf('--')); +const docsHref = `${storybookOrigin}/?path=/docs/${storyGroupId}--docs`; +--- + +<div style="margin: 1rem 0;"> + <iframe + src={src} + title={`Storybook example: ${id}`} + loading="lazy" + style={`width: 100%; height: ${height}; display: block; border: 1px solid var(--sl-color-gray-5); border-radius: 0.5rem 0.5rem 0 0; background: white;`} + ></iframe> + <a + href={docsHref} + target="_blank" + rel="noreferrer" + style="display: block; padding: 0.5rem 0.75rem; font-size: var(--sl-text-sm); border: 1px solid var(--sl-color-gray-5); border-top: none; border-radius: 0 0 0.5rem 0.5rem; background: var(--sl-color-bg-inline-code); color: var(--sl-color-text-accent);" + > + Open full story in Storybook → + </a> +</div> diff --git a/packages/starlight/src/content.config.ts b/packages/starlight/src/content.config.ts new file mode 100644 index 0000000000..514eb80b57 --- /dev/null +++ b/packages/starlight/src/content.config.ts @@ -0,0 +1,7 @@ +import { docsLoader } from '@astrojs/starlight/loaders'; +import { docsSchema } from '@astrojs/starlight/schema'; +import { defineCollection } from 'astro:content'; + +export const collections = { + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), +}; diff --git a/packages/starlight/src/content/docs/components/actions/Buttons/cta-button.mdx b/packages/starlight/src/content/docs/components/actions/Buttons/cta-button.mdx new file mode 100644 index 0000000000..ebbf83aabe --- /dev/null +++ b/packages/starlight/src/content/docs/components/actions/Buttons/cta-button.mdx @@ -0,0 +1,47 @@ +--- +title: CTAButton +description: A marketing-style button with an offset shadow treatment, used to prompt users to take a specific action. +--- + +import { Aside } from '@astrojs/starlight/components'; + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Button/CTAButton.tsx) + +<Aside type="tip"> + Use the `/gamut-buttons` skill in Cursor or Claude Code for AI-assisted + guidance on button selection, variants, disabled patterns, and ToolTip focus + management. +</Aside> + +## Usage + +Use this for a single, high-visibility marketing action — the offset shadow and accent typography are meant to stand out, not to be one of several buttons on a page. + +**Best practices:** + +- Use at most one `CTAButton` per view; its whole purpose is to draw attention to one action. +- Unlike the other buttons in this group, `CTAButton` has no `variant` choice (it's always the same treatment) and no `size` prop — it doesn't scale down for secondary contexts. Needing a smaller or less prominent button is a sign [FillButton](/components/actions/buttons/fill-button/) fits better. + +**When NOT to use:** + +- **Anything other than the single most important action on the page** — use [FillButton](/components/actions/buttons/fill-button/) instead. +- **An icon alongside the label** — `CTAButton` doesn't support the `icon`/`iconPosition` props the other buttons do; use [FillButton](/components/actions/buttons/fill-button/) if an inline icon is required. +- **Icon-only actions** — use [IconButton](/components/actions/buttons/icon-button/) instead; it requires an accessible name via its `tip` prop. +- **Outside its color-mode context** — don't place a light-mode button on a navy background; wrap the surface in `Background` instead of styling around it. + +### Anatomy + +The full button spec — variants, sizes, and states — in the Gamut Figma file: + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A0" /> + +## Usage examples + +`CTAButton` follows two of the button family's shared patterns with no differences of its own — see [Buttons: Usage examples](/components/actions/buttons/#usage-examples) for rendering as a link and disabling with a readable tooltip. (The leading/trailing icon pattern doesn't apply — `CTAButton` has no `icon` prop.) + +## Playground + +<StoryEmbed id="atoms-buttons-ctabutton--default" /> diff --git a/packages/starlight/src/content/docs/components/actions/Buttons/fill-button.mdx b/packages/starlight/src/content/docs/components/actions/Buttons/fill-button.mdx new file mode 100644 index 0000000000..8ff9ed6fef --- /dev/null +++ b/packages/starlight/src/content/docs/components/actions/Buttons/fill-button.mdx @@ -0,0 +1,50 @@ +--- +title: FillButton +description: A button that has a solid background color and is used for primary actions. +--- + +import { Aside } from '@astrojs/starlight/components'; + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Button/FillButton.tsx) + +<Aside type="tip"> + Use the `/gamut-buttons` skill in Cursor or Claude Code for AI-assisted + guidance on button selection, variants, disabled patterns, and ToolTip focus + management. +</Aside> + +## Usage + +Use this for primary actions. Include a leading or trailing icon from our mini icon set to clarify an action. + +**Best practices:** + +- Use `variant="primary"` for high priority actions (e.g. Submit). +- Use `variant="secondary"` for low priority actions (e.g. Close). +- Use `FillButton` for primary actions and [StrokeButton](/components/actions/buttons/stroke-button/) for secondary — don't use both at equal weight on the same screen. +- Hover, active, and disabled colors are handled by the component — don't override state colors with `color`/`bg` props. + +**When NOT to use:** + +- **Secondary / outlined actions** — use [StrokeButton](/components/actions/buttons/stroke-button/) instead. +- **Low-emphasis, inline actions** — use [TextButton](/components/actions/buttons/text-button/) instead. +- **Icon-only actions** — use [IconButton](/components/actions/buttons/icon-button/) instead; it requires an accessible name via its `tip` prop. +- **Marketing / high-visibility CTAs** — use [CTAButton](/components/actions/buttons/cta-button/) instead. +- **Outside its color-mode context** — don't place a light-mode button on a navy background; wrap the surface in `Background` instead of styling around it. + +### Anatomy + +The full button spec — variants, sizes, and states — in the Gamut Figma file: + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A0" /> + +## Usage examples + +`FillButton` follows the button family's shared patterns with no differences of its own — see [Buttons: Usage examples](/components/actions/buttons/#usage-examples) for rendering as a link, adding a leading/trailing icon, and disabling with a readable tooltip. + +## Playground + +<StoryEmbed id="atoms-buttons-fillbutton--default" /> diff --git a/packages/starlight/src/content/docs/components/actions/Buttons/icon-button.mdx b/packages/starlight/src/content/docs/components/actions/Buttons/icon-button.mdx new file mode 100644 index 0000000000..551654e98c --- /dev/null +++ b/packages/starlight/src/content/docs/components/actions/Buttons/icon-button.mdx @@ -0,0 +1,67 @@ +--- +title: IconButton +description: An icon-only button with a required accessible tooltip label. +--- + +import { Aside } from '@astrojs/starlight/components'; + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Button/IconButton.tsx) + +<Aside type="tip"> + Use the `/gamut-buttons` skill in Cursor or Claude Code for AI-assisted + guidance on button selection, variants, disabled patterns, and ToolTip focus + management. +</Aside> + +## Usage + +Use this for secondary or space-constrained actions with a recognizable icon and no room for a text label. `IconButton` defaults to `variant="secondary"` — every other button in this group defaults to `primary`. + +**Best practices:** + +- `tip` is required, not optional: it's both the tooltip text shown on hover/focus and the button's accessible name, so an `IconButton` is never unlabeled. +- Only use an icon whose meaning is already established elsewhere in the product (a search icon, a trash icon) — if the icon needs a caption to be understood, use [TextButton](/components/actions/buttons/text-button/) or [StrokeButton](/components/actions/buttons/stroke-button/) with a visible label instead. +- Use regular-set icons at `size="normal"` and mini-set icons at `size="small"`, matching the icon's detail to the button's size. + +**When NOT to use:** + +- **A visible label fits** — use [TextButton](/components/actions/buttons/text-button/), [StrokeButton](/components/actions/buttons/stroke-button/), or [FillButton](/components/actions/buttons/fill-button/) instead; don't reach for an icon-only button just to save space if a label would fit. +- **Marketing / high-visibility CTAs** — use [CTAButton](/components/actions/buttons/cta-button/) instead. +- **Outside its color-mode context** — don't place a light-mode button on a navy background; wrap the surface in `Background` instead of styling around it. + +### Anatomy + +The full button spec — variants, sizes, and states — in the Gamut Figma file: + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A0" /> + +## Usage examples + +`IconButton` follows one of the button family's shared patterns — see [Buttons: Usage examples](/components/actions/buttons/#usage-examples) for rendering as a link. (The leading/trailing icon and disabled-tooltip patterns don't apply here — `IconButton` is icon-only, and its `tip` prop already serves as its tooltip.) The following two patterns are specific to `IconButton`. + +### Keeping the tooltip open after a click + +By default, an `IconButton`'s tooltip closes immediately on click and only reappears once the cursor leaves and re-enters. To keep it open — useful when the click doesn't immediately confirm the action some other way — pass `tipProps={{ closeOnClick: false }}`. The first two buttons below close on click; the last two stay open: + +<StoryEmbed id="atoms-buttons-iconbutton--close-on-click" height="14rem" /> + +### Explaining why it's disabled + +Rather than wrapping the button in a second `ToolTip` (as with the other buttons), update `tip` itself to explain the disabled state — it's already the button's tooltip and accessible name. Use `aria-disabled` (without `disabled`) so the explanation stays reachable by keyboard: + +```tsx +import { MiniDeleteIcon } from '@codecademy/gamut-icons'; + +<IconButton + icon={MiniDeleteIcon} + tip="Remove item (finish editing first)" + aria-disabled +/>; +``` + +## Playground + +<StoryEmbed id="atoms-buttons-iconbutton--default" /> diff --git a/packages/starlight/src/content/docs/components/actions/Buttons/index.mdx b/packages/starlight/src/content/docs/components/actions/Buttons/index.mdx new file mode 100644 index 0000000000..a238d9028d --- /dev/null +++ b/packages/starlight/src/content/docs/components/actions/Buttons/index.mdx @@ -0,0 +1,63 @@ +--- +title: Buttons +description: Gamut's button family — five components sharing one shape, size scale, and icon behavior. +sidebar: + label: Overview +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +Gamut splits "button" into five components rather than one component with a `kind` prop, so each use case gets its own focused API: + +- [FillButton](/components/actions/buttons/fill-button/) — the solid, high-contrast button. Start here for most primary/secondary actions. +- [StrokeButton](/components/actions/buttons/stroke-button/) — an outlined, lower-emphasis alternative to FillButton. +- [TextButton](/components/actions/buttons/text-button/) — a button with no fill or border, for the lowest-emphasis actions. +- [IconButton](/components/actions/buttons/icon-button/) — an icon-only button with a required tooltip/accessible label. +- [CTAButton](/components/actions/buttons/cta-button/) — a marketing-style button with a distinct offset-shadow treatment. + +All five support `size="normal" | "small" | "large"` except `CTAButton`, which has no `size` prop at all. `FillButton`, `StrokeButton`, and `TextButton` additionally support a single inline `icon`, positioned with `iconPosition`; `CTAButton` has no `icon` prop, and `IconButton` is icon-only by definition, so there's no separate label to position an icon against. + +See [FillButton](/components/actions/buttons/fill-button/) for a fully documented example of the shared API. + +## Usage examples + +The patterns below are shared, close to verbatim, across the button family — per the documentation template, they're written once here rather than repeated on every sibling page. Each button's own page covers only what's genuinely specific to it and links back here for the rest. + +### Rendering as a link + +`FillButton`, `StrokeButton`, `TextButton`, `IconButton`, and `CTAButton` all render an anchor tag instead of a `<button>` when `href` is defined. `href` combined with `disabled` renders a `<button disabled>` instead — a link-style button can't stay an anchor while disabled. + +There's no story dedicated to this pattern specifically — set `href` in any button's Playground Controls to see it live. For example: + +<StoryEmbed id="atoms-buttons-fillbutton--default" /> + +### Leading or trailing icon + +`FillButton`, `StrokeButton`, and `TextButton` accept `icon` (from the mini icon set, for legibility at button sizes) and `iconPosition` to align it to either side of the label: + +<StoryEmbed id="atoms-buttons-button--inline-icons" height="8rem" /> + +`CTAButton` doesn't support an icon. `IconButton` is icon-only, so there's no separate label to position an icon against — see its own page for how it handles icons. + +### Disabled with a readable tooltip + +For `FillButton`, `StrokeButton`, `TextButton`, and `CTAButton`: the `disabled` prop removes the button from the tab order, so a tooltip explaining _why_ it's disabled can't be reached by keyboard. In that case, wrap the button in a `ToolTip` and use `aria-disabled` instead of `disabled` — Gamut's disabled styles also match `[aria-disabled='true']`, and the button stays focusable so the tooltip is reachable. `ToolTip`'s own `Disabled` story demonstrates exactly this, with `FillButton`: + +<StoryEmbed id="molecules-tips-tooltip--disabled" height="12rem" /> + +The same wiring works with `StrokeButton`, `TextButton`, or `CTAButton` in place of `FillButton`. `IconButton` has its own version of this pattern, since its `tip` prop already serves as its tooltip — see [IconButton](/components/actions/buttons/icon-button/#usage-examples). + +### Using buttons together in light and dark mode + +When several buttons from this family appear together (as in a form or toolbar), check that the full set — not just one button in isolation — reads correctly in both color modes: + +<StoryEmbed id="atoms-buttons-button--buttons-light-mode" height="30rem" /> + +<StoryEmbed id="atoms-buttons-button--buttons-dark-mode" height="30rem" /> + +## Prop Reference + +`variant` has a dedicated story for two of its four values — the rest are only reachable by changing the `variant` Control on each button's own default story (see each button's own Prop Reference section): + +- [Primary](https://gamut.codecademy.com/?path=/story/atoms-buttons-button--primary) +- [Secondary](https://gamut.codecademy.com/?path=/story/atoms-buttons-button--secondary) diff --git a/packages/starlight/src/content/docs/components/actions/Buttons/stroke-button.mdx b/packages/starlight/src/content/docs/components/actions/Buttons/stroke-button.mdx new file mode 100644 index 0000000000..acedbc1ef8 --- /dev/null +++ b/packages/starlight/src/content/docs/components/actions/Buttons/stroke-button.mdx @@ -0,0 +1,48 @@ +--- +title: StrokeButton +description: A button that has an outline around its text and is used for secondary actions. +--- + +import { Aside } from '@astrojs/starlight/components'; + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Button/StrokeButton.tsx) + +<Aside type="tip"> + Use the `/gamut-buttons` skill in Cursor or Claude Code for AI-assisted + guidance on button selection, variants, disabled patterns, and ToolTip focus + management. +</Aside> + +## Usage + +Use this for secondary actions. Include a leading or trailing icon from our mini icon set to clarify an action. + +**Best practices:** + +- Use `StrokeButton` for secondary actions alongside a [FillButton](/components/actions/buttons/fill-button/) primary — don't use both at equal weight on the same screen. +- Hover, active, and disabled colors are handled by the component — don't override state colors with `color`/`bg` props. + +**When NOT to use:** + +- **Primary / high-emphasis actions** — use [FillButton](/components/actions/buttons/fill-button/) instead. +- **Low-emphasis, inline actions** — use [TextButton](/components/actions/buttons/text-button/) instead. +- **Icon-only actions** — use [IconButton](/components/actions/buttons/icon-button/) instead; it requires an accessible name via its `tip` prop. +- **Marketing / high-visibility CTAs** — use [CTAButton](/components/actions/buttons/cta-button/) instead. +- **Outside its color-mode context** — don't place a light-mode button on a navy background; wrap the surface in `Background` instead of styling around it. + +### Anatomy + +The full button spec — variants, sizes, and states — in the Gamut Figma file: + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A0" /> + +## Usage examples + +`StrokeButton` follows the button family's shared patterns with no differences of its own — see [Buttons: Usage examples](/components/actions/buttons/#usage-examples) for rendering as a link, adding a leading/trailing icon, and disabling with a readable tooltip. + +## Playground + +<StoryEmbed id="atoms-buttons-strokebutton--default" /> diff --git a/packages/starlight/src/content/docs/components/actions/Buttons/text-button.mdx b/packages/starlight/src/content/docs/components/actions/Buttons/text-button.mdx new file mode 100644 index 0000000000..2978300e1b --- /dev/null +++ b/packages/starlight/src/content/docs/components/actions/Buttons/text-button.mdx @@ -0,0 +1,49 @@ +--- +title: TextButton +description: A button that usually only has text and is used for tertiary actions. +--- + +import { Aside } from '@astrojs/starlight/components'; + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Button/TextButton.tsx) + +<Aside type="tip"> + Use the `/gamut-buttons` skill in Cursor or Claude Code for AI-assisted + guidance on button selection, variants, disabled patterns, and ToolTip focus + management. +</Aside> + +## Usage + +Use this for tertiary actions. Include a leading or trailing icon from our mini icon set to clarify an action or to distinguish the button from other bold text. + +**Best practices:** + +- Our text buttons use bold formatting to differentiate themselves from normal text, as color alone does not meet the 3:1 contrast ratio required for accessibility. However, bold formatting may not be sufficient in all contexts, such as when placed alongside a bold heading. +- In these cases, we recommend including a leading or trailing icon to further distinguish the button from other bold text. This additional visual cue ensures that the text button is easily identifiable and accessible to all users. +- Hover, active, and disabled colors are handled by the component — don't override state colors with `color`/`bg` props. + +**When NOT to use:** + +- **Primary / high-emphasis actions** — use [FillButton](/components/actions/buttons/fill-button/) instead. +- **Secondary / outlined actions** — use [StrokeButton](/components/actions/buttons/stroke-button/) instead. +- **Icon-only actions** — use [IconButton](/components/actions/buttons/icon-button/) instead; it requires an accessible name via its `tip` prop. +- **Marketing / high-visibility CTAs** — use [CTAButton](/components/actions/buttons/cta-button/) instead. +- **Outside its color-mode context** — don't place a light-mode button on a navy background; wrap the surface in `Background` instead of styling around it. + +### Anatomy + +The full button spec — variants, sizes, and states — in the Gamut Figma file: + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A0" /> + +## Usage examples + +`TextButton` follows the button family's shared patterns with no differences of its own — see [Buttons: Usage examples](/components/actions/buttons/#usage-examples) for rendering as a link, adding a leading/trailing icon, and disabling with a readable tooltip. (The leading/trailing icon pattern is worth a second look here specifically — see Best practices above for why it matters more for `TextButton` than its siblings.) + +## Playground + +<StoryEmbed id="atoms-buttons-textbutton--default" /> diff --git a/packages/starlight/src/content/docs/components/actions/index.md b/packages/starlight/src/content/docs/components/actions/index.md new file mode 100644 index 0000000000..134ee2c779 --- /dev/null +++ b/packages/starlight/src/content/docs/components/actions/index.md @@ -0,0 +1,15 @@ +--- +title: Actions +description: Components a user acts through — triggering something, choosing from options, or removing something. +sidebar: + label: Overview +--- + +Things a user does: trigger something, choose from a set of options, or remove something. + +- [Buttons](/components/actions/buttons/) — FillButton, StrokeButton, TextButton, IconButton, CTAButton +- [Toggle](/components/actions/toggle/) +- [Menu](/components/actions/menu/) +- [Tag](/components/actions/tag/) + +Menu's action-list role lives here even though it also has a navigation role — see [Navigation](/components/navigation/) for that side of it. Tag lives here because its docs emphasize interactive selection/removal; for read-only labeling, see [Badge](/components/status/badge/) in Status. diff --git a/packages/starlight/src/content/docs/components/actions/menu.mdx b/packages/starlight/src/content/docs/components/actions/menu.mdx new file mode 100644 index 0000000000..76f0c7bdf9 --- /dev/null +++ b/packages/starlight/src/content/docs/components/actions/menu.mdx @@ -0,0 +1,98 @@ +--- +title: Menu +description: A list of actions, options, or navigation links, usable as a persistent menu or composed into a floating popover. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=30420-41119&m=dev) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Menu/Menu.tsx) + +## Usage + +Use `Menu` to organize and present a list of actions, options, or navigation links, composed from `MenuItem` and `MenuSeparator` children. + +**Best practices:** + +- If the menu presents actions or options, set `role="menu"` — this is why `Menu` lives in Actions rather than Navigation. +- If the menu only contains navigation links, don't set `role` on `Menu` at all — wrap it in a `<nav>` element instead, which implicitly provides the `navigation` role. +- `Menu` is stateless: it doesn't track which item is selected or disabled. Manage `active`/`disabled` on each `MenuItem` yourself. +- Nested `Menu`s inherit their `variant`/`spacing` from the root `Menu`, and automatically render as `role="group"` when the root is `role="menu"` — there's no need to repeat those props on nested menus. + +**When NOT to use:** + +- **Choosing from a predefined set of options in a form** — use SelectDropdown instead. +- **Multiple collapsible sections of navigation links** — use LayoutMenu instead. +- **Switching between views in the same context** — use [Tabs](/components/navigation/tabs/) instead. + +### Anatomy + +`Menu` is composed of `MenuItem` and (optionally) `MenuSeparator` children: + +1. **Leading icon** (optional, on `MenuItem`) — reinforces the item's purpose and improves scannability. +2. **Label** — the `MenuItem`'s content. Keep it to 1–3 words: a verb for actions, a noun for links to information. +3. **Menu separator** (optional) — groups items into sections when a menu has enough items that scanning it gets hard. + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=30420-41119&m=dev" /> + +## Usage examples + +### Building an action menu + +Set `role="menu"` on the root `Menu` when its items trigger actions. Each interactive `MenuItem` then renders with `role="menuitem"`, and its wrapping `<li>` gets `role="none"` so screen readers announce the item itself, not an extra list layer: + +<StoryEmbed id="molecules-menu--menu-role" height="16rem" /> + +### Building a navigation menu + +For a menu of links, wrap it in a `<nav>` element instead of setting `role` on `Menu` — the `<nav>` element already provides the `navigation` role natively: + +<StoryEmbed id="molecules-menu--nav-menu" height="16rem" /> + +### Building a menu with no role + +If `Menu` isn't given `role="menu"` and isn't wrapped in a `<nav>`, it renders as a plain `<ul>` with no ARIA role, and its `MenuItem` children render as plain `<li>` — reach for this only when the list isn't semantically a menu or navigation at all: + +<StoryEmbed id="molecules-menu--no-role-menu" height="16rem" /> + +### Building a floating menu + +Compose `Menu` inside `PopoverContainer` to open it from a trigger. After setting the base positioning, offset the y-axis by 48px per item at `spacing="normal"`, or 32px at `spacing="condensed"` — and give the floating menu `role="menu"` if it triggers actions: + +<StoryEmbed id="molecules-menu--floating-menu-example" height="24rem" /> + +### Icon-only items with tooltips + +Give every icon-only `MenuItem` a `label` — it becomes the item's `ToolTip` text if the item is interactive, or its `aria-label` if it isn't. Pass an object instead of a string to `label` for tooltip placement/alignment control: + +<StoryEmbed id="molecules-menu--icon-menu" height="14rem" /> + +### Grouping items with a separator + +Use `MenuSeparator` to divide a longer menu into sections: + +#### Popover + +<StoryEmbed id="molecules-menu--popover-menu-separator" height="16rem" /> + +#### Fixed + +<StoryEmbed id="molecules-menu--fixed-menu-separator" height="16rem" /> + +## Prop Reference + +### Variants + +#### Popover + +<StoryEmbed id="molecules-menu--popover" height="16rem" /> +#### Fixed +<StoryEmbed id="molecules-menu--fixed" height="16rem" /> + +### Spacing + +#### Condensed (Popover) + +<StoryEmbed id="molecules-menu--popover-condensed" height="16rem" /> +#### Condensed (Fixed) +<StoryEmbed id="molecules-menu--fixed-condensed" height="16rem" /> diff --git a/packages/starlight/src/content/docs/components/actions/tag.mdx b/packages/starlight/src/content/docs/components/actions/tag.mdx new file mode 100644 index 0000000000..c7c8118b42 --- /dev/null +++ b/packages/starlight/src/content/docs/components/actions/tag.mdx @@ -0,0 +1,94 @@ +--- +title: Tag +description: A compact label for categorizing content, representing a user's selections, or offering context-relevant actions. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=46539-40954) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Tag) + +## Usage + +Use a tag to categorize content, represent user inputs, or guide users to related actions. Tags can be dismissible individually (`selection`), or read-only when paired with other controls. + +**Best practices:** + +- Choose the variant (`readOnly`, `selection`, `navigation`, `suggestion`) that matches the use case — each enforces its own required/forbidden props, so mismatching a variant with the wrong handler prop is a type error, not a runtime surprise. +- Let tags wrap to a second line when they exceed the available width, rather than truncating them. +- When showing multiple tags, arrange them in a row and let them wrap, rather than scrolling or clipping. + +**When NOT to use:** + +- **Primary or critical actions** — use a [Button](/components/actions/buttons/) instead. +- **Standalone, high-priority navigation** — use an [Anchor](/components/navigation/anchor/) instead. +- **Highlighting a status or attribute, read-only** — use [Badge](/components/status/badge/) instead. + +### Anatomy + +1. **Leading icon** (optional, `icon`) — clarifies the tag's meaning further. +2. **Label** (`children`) — 1–2 keywords for most variants; `suggestion` can be a short phrase. Don't truncate it. +3. **Remove button** (`selection` only) — lets the user dismiss the tag; rendered automatically when `variant="selection"`. + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=46539-40954" /> + +## Usage examples + +### Removing a tag from a list + +`onDismiss` only fires the callback — `Tag` doesn't track its own presence in a list. Remove the tag from whatever state is tracking the list: + +```tsx +import { useState } from 'react'; +import { Tag } from '@codecademy/gamut'; + +export const DismissibleTagList = () => { + const [tags, setTags] = useState(['React', 'TypeScript', 'Astro']); + + return ( + <> + {tags.map((tag) => ( + <Tag + key={tag} + variant="selection" + onDismiss={() => setTags(tags.filter((t) => t !== tag))} + > + {tag} + </Tag> + ))} + </> + ); +}; +``` + +### Adding an icon + +Pass `icon` to render one to the left of the label, on any variant: + +<StoryEmbed id="atoms-tag--with-icon" height="10rem" /> + +### Wrapping long labels + +A tag's label wraps onto additional lines rather than truncating when it exceeds its container's width: + +<StoryEmbed id="atoms-tag--overflow" height="10rem" /> + +## Prop Reference + +### Variants + +#### Read-only + +<StoryEmbed id="atoms-tag--read-only" height="10rem" /> + +#### Selection + +<StoryEmbed id="atoms-tag--selection" height="10rem" /> + +#### Navigation + +<StoryEmbed id="atoms-tag--navigation" height="10rem" /> + +#### Suggestion + +<StoryEmbed id="atoms-tag--suggestion" height="10rem" /> diff --git a/packages/starlight/src/content/docs/components/actions/toggle.mdx b/packages/starlight/src/content/docs/components/actions/toggle.mdx new file mode 100644 index 0000000000..3f6b7b96f1 --- /dev/null +++ b/packages/starlight/src/content/docs/components/actions/toggle.mdx @@ -0,0 +1,72 @@ +--- +title: Toggle +description: An on/off switch control. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=30067-40001&node-type=frame) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Toggle) + +## Usage + +Use `Toggle` for a boolean value that's functionally similar to a checkbox, but is most effective when it triggers an immediate, visible action (like showing a section or changing copy) rather than waiting on a form submission. + +**Best practices:** + +- `Toggle` is a controlled component — its `checked` value must be driven by external state and `onChange`/`onClick`. +- Always give it a label: pass `label` as a string, or pass `ariaLabel` if the visible label is a `ReactNode` or absent. +- Use `as="input"` when the toggle lives in a form and a separate action (like a submit button) does something with its value; use `as="button"` when the toggle should act immediately on its own — this also gives it `role="switch"` and `aria-checked`, since a native button has no built-in switch semantics. +- `medium` (the default) is preferred inside forms; use `size="small"` for compact UI toggles outside a form context. + +**When NOT to use:** + +- **A value that's only read on form submission, with no immediate effect** — use [Checkbox](/components/inputs-and-forms/checkbox/) instead. + +## Usage examples + +### Building an input toggle + +Track `checked` in your own state and pass `onChange`: + +<StoryEmbed id="atoms-toggle--interactive-input-toggle" height="8rem" /> + +### Building a button toggle + +Same pattern, but with `as="button"` and `onClick` instead of `onChange` — Gamut's types prevent mixing `onClick` with `as="input"` or `onChange` with `as="button"`: + +<StoryEmbed id="atoms-toggle--interactive-button-toggle" height="8rem" /> + +### Using a React component as the label + +Pass a `ReactNode` to `label` when the label needs its own markup or styling — pair it with `ariaLabel` so the toggle still has an accessible name: + +<StoryEmbed id="atoms-toggle--react-component-label" height="6rem" /> + +## Prop Reference + +### Element + +<StoryEmbed id="atoms-toggle--input-element" height="6rem" /> +<StoryEmbed id="atoms-toggle--button-element" height="6rem" /> + +### Size + +<StoryEmbed id="atoms-toggle--medium" height="6rem" /> +<StoryEmbed id="atoms-toggle--small" height="6rem" /> + +### Label side + +<StoryEmbed id="atoms-toggle--label-left" height="6rem" /> +<StoryEmbed id="atoms-toggle--label-right" height="6rem" /> + +### Disabled + +Disabling `Toggle` changes its opacity and cursor, but still shows its current checked value: + +#### Unchecked + +<StoryEmbed id="atoms-toggle--disabled" height="6rem" /> + +#### Checked + +<StoryEmbed id="atoms-toggle--disabled-checked" height="6rem" /> diff --git a/packages/starlight/src/content/docs/components/containers/Boxes/box.mdx b/packages/starlight/src/content/docs/components/containers/Boxes/box.mdx new file mode 100644 index 0000000000..783ff9d220 --- /dev/null +++ b/packages/starlight/src/content/docs/components/containers/Boxes/box.mdx @@ -0,0 +1,47 @@ +--- +title: Box +description: A utility container component with nearly all of Gamut's system props available. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Box/Box.tsx) + +## Usage + +`Box` is a kitchen-sink utility component for basic layouts — a configurable building block for almost any occasion, with access to nearly all of Gamut's system props. + +**Best practices:** + +- Reach for `Box` first for a one-off layout need; only reach for [FlexBox](/components/containers/boxes/flex-box/) or [GridBox](/components/containers/boxes/grid-box/) once the container specifically needs to be a flex or grid context. +- Three boolean shorthand states are available on `Box` (and inherited by `FlexBox`/`GridBox`): `fit` (fills its container), `context` (establishes a positioning context), and `no-select` (disables text selection). +- `as` swaps the rendered element (for example `as="ul"` with `as="li"` children) — this comes from Emotion's styled-component `as` prop, not custom Gamut behavior. + +**When NOT to use:** + +- **Specifically a flex container** — use [FlexBox](/components/containers/boxes/flex-box/) instead. +- **Specifically a grid container** — use [GridBox](/components/containers/boxes/grid-box/) instead. + +## Usage examples + +### Bordered containers + +`Box` is commonly used to build a bordered container with its own padding, rather than reinventing that shape per context: + +<StoryEmbed id="layouts-boxes-box--bordered" height="8rem" /> + +### Creating a positioning context + +Pass `position="relative"` (or the `context` shorthand) on a `Box` to give descendants a stacking/positioning context, then position them with `position="absolute"` and edge props: + +<StoryEmbed id="layouts-boxes-box--positioning" height="18rem" /> + +### Rendering as a list + +Render an outer `Box` `as="ul"` (or `"ol"`) and its children `as="li"` to present a group of boxes as an accessible list. Adjust list markers with `listStyle`, `listStyleType`, `listStylePosition`, and `listStyleImage`: + +<StoryEmbed id="layouts-boxes-box--lists" height="14rem" /> + +## Playground + +<StoryEmbed id="layouts-boxes-box--default" /> diff --git a/packages/starlight/src/content/docs/components/containers/Boxes/flex-box.mdx b/packages/starlight/src/content/docs/components/containers/Boxes/flex-box.mdx new file mode 100644 index 0000000000..3fe8fa72a5 --- /dev/null +++ b/packages/starlight/src/content/docs/components/containers/Boxes/flex-box.mdx @@ -0,0 +1,33 @@ +--- +title: FlexBox +description: A Box preset to display:flex, with boolean shorthands for common flex configurations. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Box/FlexBox.tsx) + +## Usage + +`FlexBox` extends [Box](/components/containers/boxes/box/) with `display: flex` built in, plus boolean shorthands for common flex configurations. + +**Best practices:** + +- Use `FlexBox` when a container needs flex-specific behavior, not just a flex child prop like `flex` or `alignSelf` — those are already available directly on `Box`. +- Five boolean shorthands are available as a more concise alternative to the system props they set: + +```tsx +<FlexBox inline /> // display="inline-flex" +<FlexBox wrap /> // flexWrap="wrap" +<FlexBox center /> // alignItems="center" justifyContent="center" +<FlexBox row /> // flexDirection="row" +<FlexBox column /> // flexDirection="column" +``` + +**When NOT to use:** + +- **Only need flex child props** (`flex`, `alignSelf`, and similar) on a component that isn't itself the flex container — those are already part of `Box`'s system props; a plain `Box` is enough. + +## Playground + +<StoryEmbed id="layouts-boxes-flexbox--default" /> diff --git a/packages/starlight/src/content/docs/components/containers/Boxes/grid-box.mdx b/packages/starlight/src/content/docs/components/containers/Boxes/grid-box.mdx new file mode 100644 index 0000000000..9601a1bdf8 --- /dev/null +++ b/packages/starlight/src/content/docs/components/containers/Boxes/grid-box.mdx @@ -0,0 +1,24 @@ +--- +title: GridBox +description: A Box preset to display:grid, with boolean shorthands for centering and fitting content. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Box/GridBox.tsx) + +## Usage + +`GridBox` extends [Box](/components/containers/boxes/box/) with `display: grid` built in, plus two boolean shorthands: `center` (centers both axes) and `fitContent` (`gridTemplate: minmax(0, 1fr) / minmax(0, 1fr)`, useful for letting a single child fill and clip to the grid area). + +## Usage examples + +### A four-column grid + +Combine `columnGap`, `gridAutoColumns`, and `gridAutoFlow` to lay children out in even columns: + +<StoryEmbed id="layouts-boxes-gridbox--four-column-grid" height="10rem" /> + +## Playground + +<StoryEmbed id="layouts-boxes-gridbox--default" /> diff --git a/packages/starlight/src/content/docs/components/containers/Boxes/index.mdx b/packages/starlight/src/content/docs/components/containers/Boxes/index.mdx new file mode 100644 index 0000000000..28374ed8d8 --- /dev/null +++ b/packages/starlight/src/content/docs/components/containers/Boxes/index.mdx @@ -0,0 +1,20 @@ +--- +title: Boxes +description: Box, FlexBox, and GridBox — a kitchen-sink layout primitive and its flex/grid presets. +sidebar: + label: Overview +--- + +`Box` is Gamut's general-purpose layout primitive: a plain `div` with nearly every system prop available, meant to act as a configurable building block for almost any layout need. `FlexBox` and `GridBox` are `Box` with a display preset baked in, plus a few boolean shorthands for that layout mode: + +- [Box](/components/containers/boxes/box/) — the base primitive. Start here for anything that isn't specifically a flex or grid container. +- [FlexBox](/components/containers/boxes/flex-box/) — `Box` preset to `display: flex`. +- [GridBox](/components/containers/boxes/grid-box/) — `Box` preset to `display: grid`. + +All three also share three boolean shorthand states, defined once on `Box` and inherited by the other two: + +- `fit` — fills the container (`width: 1`, `height: 1`). +- `context` — establishes a positioning context (`position: relative`, `zIndex: 1`). +- `no-select` — disables text selection. + +See [Box](/components/containers/boxes/box/) for a fully documented example of the shared system-props API. diff --git a/packages/starlight/src/content/docs/components/containers/card.mdx b/packages/starlight/src/content/docs/components/containers/card.mdx new file mode 100644 index 0000000000..09c710b9e6 --- /dev/null +++ b/packages/starlight/src/content/docs/components/containers/card.mdx @@ -0,0 +1,58 @@ +--- +title: Card +description: A contained surface with background color variants, optional shadow/pattern treatments, and hover behavior. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=29975-39392&node-type=frame) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Card) + +## Usage + +Use `Card` to present a piece of information as a distinct, contained surface, with animated hover/shadow treatment available when the whole card is clickable. + +**Best practices:** + +- Use `variant="default"` (the default) for a background that follows the current color mode automatically; use `white`/`yellow`/`beige` inside a light-mode surface, or `navy`/`hyper` inside a dark-mode surface, when a static color is what's needed. +- Add `isInteractive` when wrapping `Card` in an `Anchor` — it applies a `'md'` border radius and a hover shadow effect, and a card with a pattern loses its pattern on hover. +- `borderRadius` defaults to `'none'` (non-interactive) or `'md'` (`isInteractive`), and can be overridden — the outline/pattern shadow automatically matches whatever `borderRadius` is set. + +**When NOT to use:** + +- **A plain rectangular container with no color-mode-aware background, shadow, or hover treatment** — a plain [Box](/components/containers/boxes/box/) is enough. + +### Anatomy + +The shadow/pattern treatment (`shadow`) always renders along the bottom edge plus one side (left or right, depending on the value): + +<FigmaEmbed url="https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=29975-39392&node-type=frame" /> + +## Usage examples + +### Using Card as a link + +Wrap `Card` in an `Anchor` and add `isInteractive` to get the hover shadow and `'md'` border radius: + +<StoryEmbed id="atoms-card--is-interactive" height="14rem" /> + +## Prop Reference + +### Variant + +<StoryEmbed id="atoms-card--dynamic" height="12rem" /> +<StoryEmbed id="atoms-card--static-light" height="10rem" /> +<StoryEmbed id="atoms-card--static-dark" height="10rem" /> + +### Shadow + +<StoryEmbed id="atoms-card--shadow-none" height="14rem" /> +<StoryEmbed id="atoms-card--shadow-outline" height="14rem" /> +<StoryEmbed id="atoms-card--shadow-pattern-left" height="14rem" /> +<StoryEmbed id="atoms-card--shadow-pattern-right" height="14rem" /> + +### Border radius + +`borderRadius` defaults to `'none'`, or `'md'` when `isInteractive` is set, and can be overridden independently of both: + +<StoryEmbed id="atoms-card--border-radius" height="14rem" /> diff --git a/packages/starlight/src/content/docs/components/containers/content-container.mdx b/packages/starlight/src/content/docs/components/containers/content-container.mdx new file mode 100644 index 0000000000..935d15ad9c --- /dev/null +++ b/packages/starlight/src/content/docs/components/containers/content-container.mdx @@ -0,0 +1,37 @@ +--- +title: ContentContainer +description: A container that holds page content within a maximum width, centers it, and applies responsive horizontal padding. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ZUJ7cfVgyoh1kiQYNN8Hbt) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/ContentContainer) + +## Usage + +Use `ContentContainer` when building a page layout, to contain content within a maximum width, center it, and get responsive horizontal padding that matches Gamut's grid system. + +**Best practices:** + +- Use it for full-page layouts, not for containing smaller areas of a page. + +**When NOT to use:** + +- **Containing a smaller area of the page** — use a [Box](/components/containers/boxes/box/), [FlexBox](/components/containers/boxes/flex-box/), or [GridBox](/components/containers/boxes/grid-box/) instead. + +<FigmaEmbed url="https://www.figma.com/file/ZUJ7cfVgyoh1kiQYNN8Hbt" /> + +## Prop Reference + +### Variant + +`size` has a dedicated story for `wide` in addition to the `medium` default — `small` is only reachable via the Controls panel. + +#### Medium + +<StoryEmbed id="layouts-contentcontainer--default" /> + +#### Wide + +<StoryEmbed id="layouts-contentcontainer--wide" /> diff --git a/packages/starlight/src/content/docs/components/containers/disclosure.mdx b/packages/starlight/src/content/docs/components/containers/disclosure.mdx new file mode 100644 index 0000000000..7d99f67a97 --- /dev/null +++ b/packages/starlight/src/content/docs/components/containers/disclosure.mdx @@ -0,0 +1,69 @@ +--- +title: Disclosure +description: An expandable container with a heading that toggles a collapsible content section. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=30186-40432&m=dev) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Disclosure/index.tsx) + +## Usage + +Use a `Disclosure` to progressively reveal information, letting a reader access additional detail only when they want it — hiding it up front reduces cognitive load and keeps the page scannable. + +**Best practices:** + +- Reach for a `Disclosure` when there's a good amount of information that not every reader needs, but some readers will want. +- `Disclosure` takes flat props (`heading`, `body`, `overline`, `subheading`, ...) rather than composed children — it builds its own toggle button and animated panel internally. + +**When NOT to use:** + +- **Several disclosures in a list** — use [List](/components/data-display/list-and-tables/list/) with its expandable row pattern instead. + +### Anatomy + +1. **Overline** (optional, `overline`) — short label above the heading. +2. **Heading** (required, `heading`) — the toggle's visible label; `headingLevel` sets its semantic level (`h2`–`h5`, default `h3`). +3. **Subheading** (optional, `subheading`) — text below the heading. +4. **Chevron icon** — decorative, rotates to indicate expanded/collapsed state. +5. **Body** (`body`) — the collapsible content. If it's plain text, keep its container to 600px wide for readability. +6. **Call-to-action button** (optional) — rendered only when both `ctaText` and `ctaCallback` are set. + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=30186-40432&m=dev" /> + +## Usage examples + +### Adding a call-to-action button + +Pass `ctaText` and `ctaCallback` together to render a button at the bottom of the body — both are required for it to appear. `buttonType` (`'FillButton' | 'StrokeButton' | 'TextButton'`, default `'TextButton'`) picks which button component renders it, and `buttonPlacement` (`'left' | 'right'`, default `'right'`) picks its alignment: + +```tsx +<Disclosure + heading="Why was I charged twice?" + body="Duplicate charges are refunded automatically within 3–5 business days." + ctaText="Contact support" + ctaCallback={() => openSupportChat()} + buttonType="StrokeButton" +/> +``` + +## Prop Reference + +The toggle button sets `aria-expanded` to reflect state; it doesn't currently link to the body panel with `aria-controls`. + +### Variant + +<StoryEmbed id="molecules-disclosure--default-background" height="10rem" /> +<StoryEmbed id="molecules-disclosure--subtle-bg-bordered" height="10rem" /> +<StoryEmbed id="molecules-disclosure--transparent" height="10rem" /> + +`hasBorder` layers a 1px solid border on top of any background variant: + +<StoryEmbed id="molecules-disclosure--has-border" height="10rem" /> + +### Spacing + +<StoryEmbed id="molecules-disclosure--normal" height="12rem" /> +<StoryEmbed id="molecules-disclosure--condensed" height="12rem" /> +<StoryEmbed id="molecules-disclosure--compact" height="12rem" /> diff --git a/packages/starlight/src/content/docs/components/containers/drawer.mdx b/packages/starlight/src/content/docs/components/containers/drawer.mdx new file mode 100644 index 0000000000..509daca484 --- /dev/null +++ b/packages/starlight/src/content/docs/components/containers/drawer.mdx @@ -0,0 +1,34 @@ +--- +title: Drawer +description: A panel that animates open to a responsive width within the page layout, rather than floating above it. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=30420-41014&node-type=frame) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Drawer) + +## Usage + +`Drawer` animates between `0` width and a responsive width — `75vw` below the `sm` breakpoint, `30rem` at `sm` and up — to reveal its content within the page's layout flow, rather than floating above it. + +**Best practices:** + +- `Drawer` is a controlled component: manage `expanded` in your own state, the same way you would a controlled form input. +- Whatever toggles the drawer needs `aria-expanded` reflecting the `expanded` value — `Drawer` doesn't set this itself, since it doesn't know what the trigger element is. + +**When NOT to use:** + +- **Floating above the page instead of expanding within layout flow** — use [Flyout](/components/overlays/flyout/) instead. + +### Anatomy + +`Drawer` is two nested pieces: an outer container that animates its own width, and an inner content box pinned to whichever edge `alignContentContainer` names (`'left'` or `'right'`, default `'right'`) — the content keeps its full target width throughout the animation, revealed as the outer container's width grows: + +<FigmaEmbed url="https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=30420-41014&node-type=frame" /> + +## Playground + +The story is built exactly the way described above, with a `StrokeButton` whose `aria-expanded` toggles the `Drawer`'s `expanded` prop: + +<StoryEmbed id="atoms-drawer--default" height="20rem" /> diff --git a/packages/starlight/src/content/docs/components/containers/index.md b/packages/starlight/src/content/docs/components/containers/index.md new file mode 100644 index 0000000000..d34871f284 --- /dev/null +++ b/packages/starlight/src/content/docs/components/containers/index.md @@ -0,0 +1,17 @@ +--- +title: Containers +description: Components that group, wrap, or lay out other content. +sidebar: + label: Overview +--- + +Components that group, wrap, or lay out other content. + +- [Boxes](/components/containers/boxes/) — Box, FlexBox, GridBox +- [Card](/components/containers/card/) +- [ContentContainer](/components/containers/content-container/) +- [LayoutGrid](/components/containers/layout-grid/) +- [Disclosure](/components/containers/disclosure/) +- [Drawer](/components/containers/drawer/) + +Drawer lives here because it collapses within page flow, unlike [Flyout](/components/overlays/flyout/), which floats above the page — see Overlays for that. diff --git a/packages/starlight/src/content/docs/components/containers/layout-grid.mdx b/packages/starlight/src/content/docs/components/containers/layout-grid.mdx new file mode 100644 index 0000000000..9ca6f8eff8 --- /dev/null +++ b/packages/starlight/src/content/docs/components/containers/layout-grid.mdx @@ -0,0 +1,51 @@ +--- +title: LayoutGrid +description: A flexible 12-column CSS grid, built from Column children with responsive row and column gaps. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Layout/index.tsx) + +## Usage + +Use `LayoutGrid` to build a flexible grid for full-page layouts. Any row of content snaps to 12 units of width; each cell is a `Column` with a grid-specified gap to its neighbors. Row and column gaps can be set responsively — a different gap at small viewports than at large ones. + +**Best practices:** + +- Use `LayoutGrid` for full-page layouts, not for containing smaller areas of a page. +- Keep row height consistent across a grid when using `rowspan`, so spanned columns line up cleanly. + +**When NOT to use:** + +- **Containing a smaller area of the page** — use a [Box](/components/containers/boxes/box/), [FlexBox](/components/containers/boxes/flex-box/), or [GridBox](/components/containers/boxes/grid-box/) instead. + +## Usage examples + +### Setting column and row gaps + +Set `columnGap` for horizontal space between cells, `rowGap` for vertical space, or `gap` for both — each accepts a responsive value: + +<StoryEmbed id="layouts-layoutgrid--grid-gap" height="12rem" /> + +### Setting a row height + +`rowHeight` sets the height of every row in the grid: + +<StoryEmbed id="layouts-layoutgrid--row-height" height="14rem" /> + +### Offsetting a column + +Offset a `Column` from the left edge with `offset`, on the same 0–12 scale as `size` — like `size`, it accepts a responsive value: + +<StoryEmbed id="layouts-layoutgrid--offset" height="14rem" /> + +### Spanning multiple rows + +Give a `Column` `rowspan` to have it span more than one row: + +<StoryEmbed id="layouts-layoutgrid--rowspans" height="16rem" /> + +## Playground + +<StoryEmbed id="layouts-layoutgrid--default" /> diff --git a/packages/starlight/src/content/docs/components/data-display/List and tables/List/index.mdx b/packages/starlight/src/content/docs/components/data-display/List and tables/List/index.mdx new file mode 100644 index 0000000000..36cdc7540a --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/List and tables/List/index.mdx @@ -0,0 +1,15 @@ +--- +title: List +description: A general-purpose list/table layout primitive, and the sub-components it's composed from. +sidebar: + label: Overview +--- + +`List` provides shared layout context — `variant`, `spacing`, and the like — to the components it's composed from: + +- [List](/components/data-display/list-and-tables/list/list/) — the primitive itself; renders the shared context and top-level layout. +- [TableHeader](/components/data-display/list-and-tables/list/table-header/) — a sticky header row, built directly from `ListCol` children. +- [ListRow](/components/data-display/list-and-tables/list/list-row/) — one row of content, optionally expandable. +- [ListCol](/components/data-display/list-and-tables/list/list-col/) — one cell within a row. + +[DataTable](/components/data-display/list-and-tables/data-table/) and [DataList](/components/data-display/list-and-tables/data-list/) are both built from these same components internally, not just visually similar to them. diff --git a/packages/starlight/src/content/docs/components/data-display/List and tables/List/list-col.mdx b/packages/starlight/src/content/docs/components/data-display/List and tables/List/list-col.mdx new file mode 100644 index 0000000000..6c7f9d2861 --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/List and tables/List/list-col.mdx @@ -0,0 +1,95 @@ +--- +title: ListCol +description: Column cells for list and table rows — sizing, types, alignment, and header semantics. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/List/ListCol.tsx) + +## Usage + +Use `ListCol` for each cell within a [ListRow](/components/data-display/list-and-tables/list/list-row/) or [TableHeader](/components/data-display/list-and-tables/list/table-header/) — it inherits layout context from the parent [List](/components/data-display/list-and-tables/list/), the same way `ListRow` does. + +**Best practices:** + +- `type` (`header`, `content`, `select`, `control`, `tableControl`, `expand`, `expandControl` — default `content`) drives both which HTML tag the cell renders as inside a table, and how it reorders when the row stacks responsively. Use at most one `control` column per row. +- Mark a cell `columnHeader` when it's placed directly inside `TableHeader`, rather than `type="header"`, which is for the primary column of a body row instead. +- Use `fill` to let a column grow and consume remaining row width, instead of sizing it with a fixed `size`. +- Responsive stacking triggers below `rowBreakpoint` (set on the parent `List`, default `'xs'`) or by container width — override it there, not on `ListCol` itself. + +**When NOT to use:** + +- **A cell inside `TableHeader`** that needs header semantics — use `columnHeader` instead; `type="header"` is for a body row's primary column. + +## Usage examples + +### Sizing columns + +`size` accepts `content` (default, sized to its own contents) or a fixed token: `sm`, `md`, `lg`, `xl`: + +<StoryEmbed + id="organisms-lists-tables-list-listcol--column-sizing" + height="16rem" +/> + +### Filling remaining space + +Pass `fill` on one or more columns in a row to have them grow and consume whatever width the fixed-size columns don't use: + +<StoryEmbed + id="organisms-lists-tables-list-listcol--filling-empty-space" + height="8rem" +/> + +### Right-aligning content + +Pass `justify="right"`. Avoid mixing left- and right-justified columns within the same list — it makes scanning harder: + +<StoryEmbed + id="organisms-lists-tables-list-listcol--justification" + height="8rem" +/> + +### Pinning a column while scrolling + +When the parent `List` has `scrollable`, its `type="header"` column stays pinned to the edge while the rest of the row scrolls horizontally: + +<StoryEmbed + id="organisms-lists-tables-list-listcol--horizontal-scrolling" + height="20rem" +/> + +### Composing a table header + +Place `columnHeader` `ListCol`s directly inside `TableHeader`, and pair them with `type="header"`/plain body cells in each `ListRow` below: + +<StoryEmbed + id="organisms-lists-tables-list-listcol--header-column-cells" + height="16rem" +/> + +## Prop Reference + +### Column type + +Responsive stacking reorders cells by role — header, content, and control — shown here as a labeled diagram: + +<StoryEmbed + id="organisms-lists-tables-list-listcol--responsive-anatomy" + height="12rem" +/> + +Below `rowBreakpoint`, columns stack according to that same role, shown here combined with `List as="ol"`: + +<StoryEmbed + id="organisms-lists-tables-list-listcol--responsive" + height="12rem" +/> + +### Playground + +<StoryEmbed + id="organisms-lists-tables-list-listcol--playground" + height="16rem" +/> diff --git a/packages/starlight/src/content/docs/components/data-display/List and tables/List/list-row.mdx b/packages/starlight/src/content/docs/components/data-display/List and tables/List/list-row.mdx new file mode 100644 index 0000000000..d0395d13ac --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/List and tables/List/list-row.mdx @@ -0,0 +1,51 @@ +--- +title: ListRow +description: A single row in a List, with optional expandable content. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/List/ListRow.tsx) + +## Usage + +Use `ListRow` to render one row inside a [List](/components/data-display/list-and-tables/list/) — it inherits `spacing`, `variant`, and layout from its parent `List` through context, so those props aren't set directly on `ListRow` itself. + +**Best practices:** + +- To make a row expandable, pass `expanded` and `renderExpanded` together (along with the optional `expandedRowAriaLabel`/`keepSpacingWhileExpanded`) — Gamut's types require all of these together or none of them. +- Expand a row either from a dedicated `ExpandControl` button in a `control` column, or by making the whole row clickable with `onClick` — pick one, not both, so the interaction target stays unambiguous. +- Passing `onClick` (outside a table) makes the row keyboard-activatable — it becomes `role="button"` with `tabIndex={0}`, Enter triggers it, and it gets hover/focus styling automatically. + +**When NOT to use:** + +- **The header row of a table-style List** — use [TableHeader](/components/data-display/list-and-tables/list/table-header/) instead. + +## Usage examples + +### Expanding from a dedicated control + +Pass `expanded`/`renderExpanded` and put an `ExpandControl` in a `control` column — its chevron rotates automatically to match `expanded`, and the rest of the row stays inert: + +<StoryEmbed + id="organisms-lists-tables-list-listrow--expandable-button" + height="20rem" +/> + +### Expanding the whole row + +Pass `onClick` alongside `expanded`/`renderExpanded` to make the entire row the expand trigger, instead of a separate button: + +<StoryEmbed + id="organisms-lists-tables-list-listrow--expandable-clickable-row" + height="20rem" +/> + +## Playground + +With `variant="plain"` on the parent `List`, wrap `ListRow` in your own `styled()` call to emphasize or group specific rows: + +<StoryEmbed + id="organisms-lists-tables-list-listrow--plain-styled" + height="16rem" +/> diff --git a/packages/starlight/src/content/docs/components/data-display/List and tables/List/list.mdx b/packages/starlight/src/content/docs/components/data-display/List and tables/List/list.mdx new file mode 100644 index 0000000000..f6f1f9d4e5 --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/List and tables/List/list.mdx @@ -0,0 +1,123 @@ +--- +title: List +description: A general-purpose list/table layout primitive. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=32521-43421&m=dev) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/List/List.tsx) + +## Usage + +Use `List` to lay out repeated rows of content. It can look and behave like a table, but isn't exclusively for tabular data. Reach for it directly when rows need mixed column content or row-level controls that [DataTable](/components/data-display/list-and-tables/data-table/) and [DataList](/components/data-display/list-and-tables/data-list/) don't expose. + +**Best practices:** + +- Compose `List` from `ListRow` and `ListCol` children, optionally with a `TableHeader` built directly from `ListCol`s (not wrapped in a `ListRow`). `variant`, `spacing`, and the other layout props are set once on `List` and inherited by its children through context — they aren't repeated on each `ListRow`/`ListCol`. +- Choose `spacing` and `variant` to match the density and visual weight the content needs. +- `disableContainerQuery` opts out of the responsive column-stacking behavior that kicks in below a breakpoint. Reach for it only when a `List` sits in a container whose width doesn't reflect the space actually available — a fixed-width sidebar, for example. + +**When NOT to use:** + +- **Bulk data that needs sorting, filtering, or pagination** — use [DataTable](/components/data-display/list-and-tables/data-table/) instead; it's `List` wired up with those controls out of the box. +- **Individual items a reader selects or expands one at a time** — use [DataList](/components/data-display/list-and-tables/data-list/) instead. + +### Anatomy + +1. **[TableHeader](/components/data-display/list-and-tables/list/table-header/)** (optional) — a header row built directly from `ListCol` children, not wrapped in a `ListRow`; sticky by default when the list scrolls. +2. **[ListRow](/components/data-display/list-and-tables/list/list-row/)** — one row of content; can be expandable (`expanded`/`renderExpanded`) or plain. +3. **[ListCol](/components/data-display/list-and-tables/list/list-col/)** — one cell within a row; `type` (`content`, `select`, `control`, `expand`, and more) drives its grid placement and how it stacks responsively below `rowBreakpoint`. + +## Usage examples + +### Building a realistic row + +A row combining icons, stat columns, and row-level controls (`TextButton`, `FillButton`, `IconButton`), at condensed spacing: + +<StoryEmbed + id="organisms-lists-tables-list-list--condensed-spacing-guide" + height="20rem" +/> + +### Rendering as an ordered list + +Pass `as="ol"`: + +<StoryEmbed + id="organisms-lists-tables-list-list--ordered-list" + height="16rem" +/> + +### Opting out of container-query stacking + +Pass `disableContainerQuery` when `List` sits in a container whose width doesn't reflect the space actually available: + +<StoryEmbed + id="organisms-lists-tables-list-list--disable-container-query" + height="20rem" +/> + +### Combining variant and spacing + +`variant` and `spacing` are independent — pick any combination, like `table` variant with `condensed` spacing: + +<StoryEmbed + id="organisms-lists-tables-list-list--condensed-table-guide" + height="16rem" +/> + +### Setting margin and padding + +`List` accepts the same [space system props](/foundations/system-props/) as any other Gamut component, for the space around the `List` itself: + +<StoryEmbed + id="organisms-lists-tables-list-list--space-system-props" + height="16rem" +/> + +## Prop Reference + +### Variant + +#### Default + +<StoryEmbed id="organisms-lists-tables-list-list--default" height="12rem" /> + +#### Table + +<StoryEmbed id="organisms-lists-tables-list-list--table" height="12rem" /> + +#### Card + +<StoryEmbed id="organisms-lists-tables-list-list--card" height="16rem" /> + +#### Block + +<StoryEmbed id="organisms-lists-tables-list-list--block" height="12rem" /> + +#### Plain + +<StoryEmbed id="organisms-lists-tables-list-list--plain" height="12rem" /> + +### Spacing + +#### Normal (default) + +<StoryEmbed + id="organisms-lists-tables-list-list--normal-spacing" + height="12rem" +/> + +#### Condensed + +<StoryEmbed + id="organisms-lists-tables-list-list--condensed-spacing" + height="12rem" +/> + +#### Compact + +<StoryEmbed + id="organisms-lists-tables-list-list--compact-spacing" + height="12rem" +/> diff --git a/packages/starlight/src/content/docs/components/data-display/List and tables/List/table-header.mdx b/packages/starlight/src/content/docs/components/data-display/List and tables/List/table-header.mdx new file mode 100644 index 0000000000..480e0435ea --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/List and tables/List/table-header.mdx @@ -0,0 +1,28 @@ +--- +title: TableHeader +description: Sticky header row for lists that render as HTML tables. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/List/TableHeader.tsx) + +## Usage + +Use `TableHeader` for the header row of a [List](/components/data-display/list-and-tables/list/) rendered `as="table"`. It's always sticky, and reads `spacing`, `scrollable`, and `variant` from the parent `List` the same way `ListRow` does — it has no layout props of its own beyond standard `<tr>` attributes. + +**Best practices:** + +- Place `ListCol` cells directly inside `TableHeader`, not wrapped in a `ListRow`, and mark each one `columnHeader` — see [ListCol](/components/data-display/list-and-tables/list/list-col/#composing-a-table-header). +- Add `aria-sort` to a header cell when its column is sortable. + +**When NOT to use:** + +- **A List that doesn't render `as="table"`** — plain and card-style lists don't need a `TableHeader` at all. + +## Playground + +<StoryEmbed + id="organisms-lists-tables-list-tableheader--with-table-header" + height="16rem" +/> diff --git a/packages/starlight/src/content/docs/components/data-display/List and tables/data-list.mdx b/packages/starlight/src/content/docs/components/data-display/List and tables/data-list.mdx new file mode 100644 index 0000000000..ba535481ca --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/List and tables/data-list.mdx @@ -0,0 +1,68 @@ +--- +title: DataList +description: An item-focused list with row expansion and selection. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/DataList/DataList.tsx) + +## Usage + +Use `DataList` for engaging with individual items one at a time — selecting them, or expanding a row to reveal more detail. It's built on [List](/components/data-display/list-and-tables/list/), restricted to `variant="default"` or `"card"`. + +**Best practices:** + +- Use [DataTable](/components/data-display/list-and-tables/data-table/) instead if the point is comparing information across items rather than engaging with one at a time — `DataList` always disables horizontal scrolling (`scrollable`/`shadow` are forced off), so it doesn't suit many columns. +- Omit `onRowSelect` to disable selection entirely, and omit `onRowExpand`/`expandedContent` to disable expansion. There's no separate `selectable` boolean — each capability is enabled purely by providing its handler. +- `disableContainerQuery` opts out of the responsive column-stacking behavior — the same escape hatch as [List](/components/data-display/list-and-tables/list/list/#opting-out-of-container-query-stacking). + +**When NOT to use:** + +- **Comparing many records with lots of columns** — use [DataTable](/components/data-display/list-and-tables/data-table/) instead; `DataList` can't scroll horizontally. +- **Mixed column content or row-level controls beyond what DataList's columns support** — use [List](/components/data-display/list-and-tables/list/) directly instead. + +## Usage examples + +### Expanding a row + +Pass `expanded` (the expanded row keys), `onRowExpand`, and `expandedContent` to reveal more detail inline — the expanded content can be anything, including a nested `DataTable`: + +<StoryEmbed id="organisms-lists-tables-datalist--expanded" height="20rem" /> + +### Selecting rows + +Pass `selected` and `onRowSelect`: + +<StoryEmbed id="organisms-lists-tables-datalist--selected" height="20rem" /> + +### Disabling selection and expansion + +Omit `onRowSelect`, `onRowExpand`, and `expandedContent` entirely to render plain, non-interactive rows: + +<StoryEmbed + id="organisms-lists-tables-datalist--non-selectable" + height="16rem" +/> + +### Handling an empty state + +<StoryEmbed id="organisms-lists-tables-datalist--empty-state" height="12rem" /> + +For full control — a custom heading, icon, and reason: + +<StoryEmbed + id="organisms-lists-tables-datalist--empty-state-custom" + height="14rem" +/> + +### Opting out of container-query stacking + +<StoryEmbed + id="organisms-lists-tables-datalist--disable-container-query" + height="20rem" +/> + +## Playground + +<StoryEmbed id="organisms-lists-tables-datalist--default" height="20rem" /> diff --git a/packages/starlight/src/content/docs/components/data-display/List and tables/data-table.mdx b/packages/starlight/src/content/docs/components/data-display/List and tables/data-table.mdx new file mode 100644 index 0000000000..2a45b24ae9 --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/List and tables/data-table.mdx @@ -0,0 +1,92 @@ +--- +title: DataTable +description: A sortable, filterable table for comparing bulk data. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/DataList/DataTable.tsx) + +## Usage + +Use `DataTable` for bulk analysis across rows — comparing, sorting, and filtering many records at once. It's built on [List](/components/data-display/list-and-tables/list/), locked to `variant="table"`, with sorting, filtering, and pagination wired in. + +**Best practices:** + +- Be careful about information overload — `DataTable` supports many columns, but that doesn't mean every column belongs on screen at once. +- `DataTable` can't select or expand rows — both the type and the runtime forcibly disable those handlers. Use [DataList](/components/data-display/list-and-tables/data-list/) instead if a reader needs to select or expand individual rows. +- `scrollable` defaults to `true`; pair it with `shadow` to signal that there's more content to scroll to. +- `disableContainerQuery` opts out of the responsive column-stacking behavior — the same escape hatch as [List](/components/data-display/list-and-tables/list/list/#opting-out-of-container-query-stacking). + +**When NOT to use:** + +- **Selecting or expanding individual rows** — use [DataList](/components/data-display/list-and-tables/data-list/) instead. +- **Mixed column content or row-level controls beyond what DataTable's columns support** — use [List](/components/data-display/list-and-tables/list/) directly instead. + +## Usage examples + +### Sorting and filtering columns + +Give a column `sortable` to let a reader sort by it, and `filters` to add a filter dropdown to its header: + +<StoryEmbed + id="organisms-lists-tables-datatable--full-data-table" + height="24rem" +/> + +### Handling an empty state + +`emptyMessage` overrides the default empty message: + +<StoryEmbed id="organisms-lists-tables-datatable--empty-state" height="12rem" /> + +For full control — a custom heading, icon, and reason — build the message from `DataTable`'s own row/column semantics: + +<StoryEmbed + id="organisms-lists-tables-datatable--empty-state-custom" + height="14rem" +/> + +### Showing a loading state + +Pass `loading` to render placeholder rows for each column: + +<StoryEmbed + id="organisms-lists-tables-datatable--loading-rows" + height="16rem" +/> + +### Scrolling wide tables + +Pair `scrollable` with `shadow` so a wide table signals there's more to scroll to horizontally: + +<StoryEmbed id="organisms-lists-tables-datatable--scrollable" height="20rem" /> + +### Adding a background color + +Wrap `DataTable` in a `Background` — its colors respond automatically to ColorMode: + +<StoryEmbed + id="organisms-lists-tables-datatable--background-colors" + height="20rem" +/> + +### Adding a floating row menu + +Render a kebab `IconButton` in a `control` column, opening a `Menu` inside a `PopoverContainer` — pair it with a `Dialog` for actions that need confirmation: + +<StoryEmbed + id="organisms-lists-tables-datatable--with-floating-menu" + height="24rem" +/> + +### Opting out of container-query stacking + +<StoryEmbed + id="organisms-lists-tables-datatable--disable-container-query" + height="20rem" +/> + +## Playground + +<StoryEmbed id="organisms-lists-tables-datatable--default" height="20rem" /> diff --git a/packages/starlight/src/content/docs/components/data-display/List and tables/index.mdx b/packages/starlight/src/content/docs/components/data-display/List and tables/index.mdx new file mode 100644 index 0000000000..da533d8f44 --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/List and tables/index.mdx @@ -0,0 +1,12 @@ +--- +title: List & Tables +description: A shared layout primitive, and the two data-driven components built on it. +sidebar: + label: Overview +--- + +- [List](/components/data-display/list-and-tables/list/) — the general-purpose layout primitive, for mixed column content or row-level controls the other two don't expose. Composed from [ListRow](/components/data-display/list-and-tables/list/list-row/), [ListCol](/components/data-display/list-and-tables/list/list-col/), and [TableHeader](/components/data-display/list-and-tables/list/table-header/). +- [DataTable](/components/data-display/list-and-tables/data-table/) — `List` locked to a sortable, filterable table, for bulk comparison across rows. +- [DataList](/components/data-display/list-and-tables/data-list/) — `List` restricted to selectable, expandable items, for engaging with one item at a time. + +`DataTable` and `DataList` are both built from `List`'s own `ListRow`/`ListCol`/`TableHeader` components internally, not just visually similar to them. diff --git a/packages/starlight/src/content/docs/components/data-display/bar-chart.mdx b/packages/starlight/src/content/docs/components/data-display/bar-chart.mdx new file mode 100644 index 0000000000..e984aeddfd --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/bar-chart.mdx @@ -0,0 +1,140 @@ +--- +title: BarChart +description: A horizontal bar chart for visualizing comparative data. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=55123-4176) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/BarChart) + +## Usage + +Use `BarChart` to visualize comparative data across categories, as a horizontal bar chart. + +**Best practices:** + +- Keep units consistent across every bar, and show 5–10 bars at a time — more gets hard to scan. +- Use the stacked variant (`seriesTwoValue`) to show progress toward a total, not just a comparison between categories. +- Sort descending when the chart's purpose is ranking. +- Keep bar-to-background contrast at least 3:1. + +**When NOT to use:** + +- **Progress or completion toward a single goal** — use [ProgressBar](/components/status/progress-bar/) instead. + +### Anatomy + +<FigmaEmbed url="https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=55123-4176" /> + +1. **Bar row** +2. **Category icon** (optional) +3. **Category label** +4. **Scale/value axis** — the minimum is always 0; dividers and the maximum are adjustable via `scaleInterval`/`maxScaleValue`. +5. **Series one value** +6. **Series two value** (optional, via `seriesTwoValue`) — creates the stacked effect. +7. **Series one bar** — defaults to the text color; gains an automatic white/navy border for contrast when needed. +8. **Series two bar** — defaults to the primary color; same automatic border rule. + +## Usage examples + +### Stacking two series + +Pass `seriesTwoValue` alongside `seriesOneValue` to show progress toward a total instead of a single comparison: + +<StoryEmbed id="organisms-barchart--stacked" height="20rem" /> + +### Adding category icons + +<StoryEmbed id="organisms-barchart--with-icons" height="20rem" /> + +### Animating on mount + +Pass `animate`: + +<StoryEmbed id="organisms-barchart--animated" height="20rem" /> + +### Making rows interactive + +Pass `onClick` or `href` per bar to render it as a button or a link — `BarChart` generates the accessible label automatically either way. + +#### Button rows + +<StoryEmbed id="organisms-barchart--interactive" height="20rem" /> + +#### Link rows + +<StoryEmbed id="organisms-barchart--with-links" height="20rem" /> + +### Choosing where the title lives + +Provide `title` directly (the default), hide it visually while keeping it in the accessibility tree with `hideTitle`, or skip `title` entirely and point `aria-labelledby` at an external heading instead. + +#### Visual title and description + +<StoryEmbed + id="organisms-barchart--with-visual-title-and-description" + height="20rem" +/> + +#### Hidden title and description + +<StoryEmbed + id="organisms-barchart--with-hidden-title-and-description" + height="20rem" +/> + +#### External title + +<StoryEmbed id="organisms-barchart--with-external-title" height="20rem" /> + +### Adding a sort control + +Pass `sortFns` to render a sort dropdown — omit it and no dropdown renders at all. Use the built-in `'alphabetically'`, `'numerically'`, and `'none'` options, or supply custom `{ label, value, sortFn }` objects for domain-specific sorting. + +#### Built-in options + +<StoryEmbed id="organisms-barchart--with-sorting" height="20rem" /> + +#### Custom sort functions + +<StoryEmbed id="organisms-barchart--with-custom-sorting" height="20rem" /> + +### Customizing colors + +`styleConfig` overrides the text, bar, and label colors — all as semantic color tokens, so they still adapt across light/dark ColorMode: + +<StoryEmbed id="organisms-barchart--custom-styles" height="20rem" /> + +### Setting a custom scale interval + +<StoryEmbed id="organisms-barchart--custom-scale" height="20rem" /> + +### Translating and customizing accessibility summaries + +`translations` overrides the sort label/options and the per-row accessibility summary text, along with `locale` for number formatting. + +#### String overrides + +<StoryEmbed id="organisms-barchart--with-string-translations" height="20rem" /> + +#### Function overrides + +<StoryEmbed + id="organisms-barchart--with-function-translations" + height="20rem" +/> + +## Responsive layout + +`BarChart` uses a CSS container query on its wrapping `<figure>`, so its layout responds to the width of its parent rather than the viewport — useful when the chart sits in a constrained space like a sidebar. Below 480px, the label and value stack together above the bar; at 480px and up, the label sits to the left of the bar and the value to its right. + +## Writing chart copy + +- Keep category labels to 1–3 words. +- Use a consistent unit label across every bar (for example, always "XP" or always "hours"). +- Use locale-aware number formatting for an international audience — see [Translating and customizing accessibility summaries](#translating-and-customizing-accessibility-summaries) for the `locale` option. + +## Playground + +<StoryEmbed id="organisms-barchart--default" height="20rem" /> diff --git a/packages/starlight/src/content/docs/components/data-display/index.md b/packages/starlight/src/content/docs/components/data-display/index.md new file mode 100644 index 0000000000..1a7b620cf3 --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/index.md @@ -0,0 +1,12 @@ +--- +title: Data display +description: Components for showing structured or bulk data. +sidebar: + label: Overview +--- + +Components for showing structured or bulk data. + +- [List & Tables](/components/data-display/list-and-tables/) — List, DataTable, DataList +- [BarChart](/components/data-display/bar-chart/) +- [Markdown](/components/data-display/markdown/) diff --git a/packages/starlight/src/content/docs/components/data-display/markdown.mdx b/packages/starlight/src/content/docs/components/data-display/markdown.mdx new file mode 100644 index 0000000000..0c68d70853 --- /dev/null +++ b/packages/starlight/src/content/docs/components/data-display/markdown.mdx @@ -0,0 +1,55 @@ +--- +title: Markdown +description: Renders Markdown content with Gamut typography styles. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Updating · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Markdown/index.tsx) + +## Usage + +Use `Markdown` to render a markdown (or markdown-plus-HTML) string with Gamut's typography styles already applied. + +**Best practices:** + +- Output is sanitized against an allowlist of tags and attributes before it ever reaches React — nothing renders unless it's on that allowlist, including anything added through `overrides`. This is the component's XSS defense; don't work around it by rendering raw HTML some other way. +- Use `inline` for markdown that sits inside running text — it renders a `<span>`, parsed with no block-level wrapping. Leave it `false` (the default) for standalone block content, which renders a `<div>`. +- `headerIds` (default `true`) generates deep-linkable ids on headings. Turn it off, or keep only one `Markdown` per page, to avoid duplicate ids. +- `spacing` (`'loose'`, `'tight'` — the default, or `'none'`) controls the type scale and margins applied to rendered elements. + +**When NOT to use:** + +- **Plain, non-markdown text** — use [Text](/components/typography/text/) instead; parsing and sanitizing a string that's already plain text is wasted work. + +## Usage examples + +### Overriding a standard tag + +Pass `overrides`, keyed by tag name, to replace how a specific element renders — for example, changing an `h3`'s font size: + +<StoryEmbed id="organisms-markdown--header-override" height="8rem" /> + +### Replacing a built-in override + +`a`, `iframe`, and `table` already have built-in overrides — to Gamut's `Anchor`, `Video`, and a scrollable table wrapper, respectively. Pass your own `overrides` entry for the same tag to replace one; there's no need for `skipDefaultOverrides` unless you want the tag to fall back to plain sanitized HTML instead: + +<StoryEmbed id="organisms-markdown--link-override" height="6rem" /> + +### Adding a custom tag + +Introduce an entirely custom, non-standard tag name in `overrides`, with `allowedAttributes` to let specific attributes through sanitization: + +<StoryEmbed id="organisms-markdown--overrides" height="12rem" /> + +### Rendering inline + +Pass `inline` to render as a `<span>` with no block-level wrapping, for markdown that sits inside a sentence: + +<StoryEmbed id="organisms-markdown--inline-markdown" height="8rem" /> + +## Playground + +The story below renders most of what `Markdown` supports in one pass — headings, lists (including nested lists and task-list checkboxes), blockquotes, code blocks, tables, `<details>`, and embedded video: + +<StoryEmbed id="organisms-markdown--full-example" height="40rem" /> diff --git a/packages/starlight/src/content/docs/components/feedback/Tips/index.mdx b/packages/starlight/src/content/docs/components/feedback/Tips/index.mdx new file mode 100644 index 0000000000..1b8f148737 --- /dev/null +++ b/packages/starlight/src/content/docs/components/feedback/Tips/index.mdx @@ -0,0 +1,38 @@ +--- +title: Tips +description: Small floating hints attached to an element. +sidebar: + label: Overview +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +Tips share one underlying mechanism — each chooses between the same inline and floating renderers — but differ in what triggers them and what they can show: + +- [ToolTip](/components/feedback/tips/tooltip/) — a short, plain-text tip triggered by hovering or focusing the element it describes. +- [InfoTip](/components/feedback/tips/infotip/) — a tip triggered by clicking its own info icon, for content longer than a ToolTip is meant to hold. +- [PreviewTip](/components/feedback/tips/previewtip/) — a richer tip on a link, previewing content — optionally with an avatar and username — from the link's destination. + +All three share a `placement` prop (`'inline'` default, or `'floating'`), a `narrow` prop that forces the tip to its narrowest width for tight or edge-of-page spaces, and, inline only, an `inheritDims` prop and a `zIndex` override. + +## Usage examples + +The patterns below are shared, close to verbatim, across the Tips family — per the documentation template, they're written once here rather than repeated on every sibling page. + +### Escaping a tight container with floating placement + +`placement="floating"` escapes the confines of a tight parent container. Every Tip's own docs warn to reach for it only when it's actually needed — inline placement keeps the tip in normal document flow and is the safer default: + +<StoryEmbed id="molecules-tips-tooltip--floating" height="14rem" /> + +The same prop works the same way on [InfoTip](https://gamut.codecademy.com/?path=/story/molecules-tips-infotip--placement) and [PreviewTip](https://gamut.codecademy.com/?path=/story/molecules-tips-previewtip--floating-preview-tip). + +### Overriding stacking order + +Inline tips accept `zIndex` to control stacking against surrounding content that has its own explicit `zIndex`: + +<StoryEmbed id="molecules-tips-infotip--z-index" height="12rem" /> + +## Prop Reference + +Alignment is a shared concept — a tip positions itself relative to whatever triggers it — but the exact values differ by component: `ToolTip` aligns to a side (`top-center`, `bottom-center`, `left-center`, `right-center`), while `InfoTip` and `PreviewTip` align to a corner (`top-left`, `top-right`, `bottom-left`, `bottom-right`). See each component's own Prop Reference section for its alignment story, and [ToolTip's horizontal alignment story](https://gamut.codecademy.com/?path=/story/molecules-tips-tooltip--horizontal-alignments) for how alignment adapts under RTL layouts. diff --git a/packages/starlight/src/content/docs/components/feedback/Tips/infotip.mdx b/packages/starlight/src/content/docs/components/feedback/Tips/infotip.mdx new file mode 100644 index 0000000000..48667f9dbc --- /dev/null +++ b/packages/starlight/src/content/docs/components/feedback/Tips/infotip.mdx @@ -0,0 +1,66 @@ +--- +title: InfoTip +description: A tip triggered by clicking its own info icon rather than the element it describes. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/branch/ayKNSg6QvZUjsgw0FFysW4/%F0%9F%93%90-Gamut?type=design&node-id=41538-55277&mode=design&t=fGkWf5GSl5cj5fQo-0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Tip/InfoTip/index.tsx) + +## Usage + +Use `InfoTip` to add supplementary information next to an element, triggered by clicking its own info icon rather than by hovering or focusing another element. + +**Best practices:** + +- Provide `ariaLabel` or `ariaLabelledby` — exactly one is required — to accessibly name the info button. +- InfoTip manages its own focus and keyboard behavior: opening moves focus into its content, `Tab`/`Shift+Tab` wrap within a floating tip, and `Escape` closes it — deferring to an enclosing `Modal`'s own `Escape` handling when the InfoTip is nested inside one. +- The info button's `aria-roledescription` defaults to "More information button" — override it with `ariaRoleDescription` for translation or other accessibility needs. +- Reserve `placement="floating"` for content that would otherwise be clipped by a tight parent container. + +**When NOT to use:** + +- **A hint on hover or focus of another element** — use [ToolTip](/components/feedback/tips/tooltip/) instead. +- **A preview of a link's destination** — use [PreviewTip](/components/feedback/tips/previewtip/) instead. + +## Usage examples + +### Choosing an emphasis + +`emphasis` accepts `'low'` (default, outline icon) or `'high'` (filled icon): + +<StoryEmbed id="molecules-tips-infotip--emphasis" height="8rem" /> + +### Content with links or buttons + +Pass a `ReactNode` to `info` to include interactive content like an `Anchor` — focus moves into the tip's content on open, so interactive content stays reachable by keyboard: + +<StoryEmbed id="molecules-tips-infotip--with-links-or-buttons" height="14rem" /> + +### Navigating with a keyboard + +Opening an InfoTip moves focus into its content. In a floating tip, `Tab` cycles forward through its focusable elements and wraps back to the InfoTip button after the last one; `Shift+Tab` from the button exits to the previous page element. In an inline tip, `Tab`/`Shift+Tab` just follow normal document flow. `Escape` always closes the tip and returns focus to the InfoTip button: + +<StoryEmbed id="molecules-tips-infotip--keyboard-navigation" height="14rem" /> + +### Nesting inside a Modal + +When an `InfoTip` opens inside a `Modal`, `Escape` closes the `InfoTip` first and the `Modal` second — `InfoTip` detects the enclosing modal automatically: + +<StoryEmbed id="molecules-tips-infotip--info-tip-inside-modal" height="16rem" /> + +### Choosing between ariaLabel and ariaLabelledby + +Use `ariaLabelledby` to point at visible text that already names the info icon's subject; use `ariaLabel` when there's no visible text to reference: + +<StoryEmbed id="molecules-tips-infotip--aria-label" height="10rem" /> + +## Prop Reference + +### Alignment + +`alignment` accepts `top-left`, `top-right` (default), `bottom-left`, or `bottom-right`: + +<StoryEmbed id="molecules-tips-infotip--alignments" height="16rem" /> + +`zIndex` overrides stacking order the same way across every Tip — see [Tips](/components/feedback/tips/#overriding-stacking-order). diff --git a/packages/starlight/src/content/docs/components/feedback/Tips/previewtip.mdx b/packages/starlight/src/content/docs/components/feedback/Tips/previewtip.mdx new file mode 100644 index 0000000000..9c257ddd77 --- /dev/null +++ b/packages/starlight/src/content/docs/components/feedback/Tips/previewtip.mdx @@ -0,0 +1,49 @@ +--- +title: PreviewTip +description: An Anchor that previews a snippet of content from its link destination. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/branch/ayKNSg6QvZUjsgw0FFysW4/%F0%9F%93%90-Gamut?type=design&node-id=41538-55277&mode=design&t=fGkWf5GSl5cj5fQo-0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Tip/PreviewTip/index.tsx) + +## Usage + +Use `PreviewTip` to preview a snippet of a link's destination without leaving the page. It renders as an `Anchor` — so it requires `href` — and shows `linkDescription` on hover or focus. + +**Best practices:** + +- Pass `avatar` (a `ReactNode`) together with `username` to render the avatar layout instead of plain preview text. +- Set `truncateLines` to control how many lines of `linkDescription` show before truncating; the default is 4. +- Pass `loading` to show a shimmer placeholder while the preview content is still being fetched. +- Reserve `placement="floating"` for tips that would otherwise be clipped by a tight parent container. + +**When NOT to use:** + +- **A hint that isn't previewing a link's destination** — use [ToolTip](/components/feedback/tips/tooltip/) or [InfoTip](/components/feedback/tips/infotip/) instead. + +## Usage examples + +### Previewing with an avatar + +Pass `avatar` and `username` to render the avatar layout instead of plain preview text: + +<StoryEmbed id="molecules-tips-previewtip--avatar-tip" height="10rem" /> + +### Showing a loading state + +Pass `loading` while the preview content is still being fetched: + +<StoryEmbed id="molecules-tips-previewtip--loading-tip" height="12rem" /> + +### Truncating longer previews + +`truncateLines` limits `linkDescription` to a set number of lines before truncating; the default is 4: + +<StoryEmbed id="molecules-tips-previewtip--truncation" height="14rem" /> + +## Playground + +`alignment` accepts `top-left`, `top-right` (default), `bottom-left`, or `bottom-right`, the same corner set as `InfoTip`: + +<StoryEmbed id="molecules-tips-previewtip--default" height="10rem" /> diff --git a/packages/starlight/src/content/docs/components/feedback/Tips/tooltip.mdx b/packages/starlight/src/content/docs/components/feedback/Tips/tooltip.mdx new file mode 100644 index 0000000000..fb32198f4e --- /dev/null +++ b/packages/starlight/src/content/docs/components/feedback/Tips/tooltip.mdx @@ -0,0 +1,62 @@ +--- +title: ToolTip +description: A short tip triggered by hovering or focusing the element it describes. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/branch/ayKNSg6QvZUjsgw0FFysW4/%F0%9F%93%90-Gamut?type=design&node-id=41538-55279&mode=design&t=fGkWf5GSl5cj5fQo-0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Tip/ToolTip/index.tsx) + +## Usage + +Use `ToolTip` to add a short, plain-text hint that appears on hovering or focusing the element it describes — further explaining an element's primary action, a disabled state, or labeling an icon-only control. + +**Best practices:** + +- For manual usage, wire `id` on `ToolTip` and `aria-describedby` on the element it describes yourself. `IconButton`'s `tip` prop does this automatically. +- For a disabled control that still needs to explain why, use `aria-disabled` instead of `disabled` — `disabled` removes the element from the tab order, so a keyboard user could never reach the tooltip. +- `closeOnClick` (default `true`) closes the tip on click, only reopening once the cursor leaves and re-enters. Pass `false` to keep it open through a click — for example, a "copy → copied" pattern. +- Reserve `placement="floating"` for tips that would otherwise be clipped or occluded by a tight parent container — see [Tips](/components/feedback/tips/) for why. + +**When NOT to use:** + +- **Content longer than a short phrase** — use [InfoTip](/components/feedback/tips/infotip/) instead; it's meant for longer supplementary content and doesn't require the reader to keep hovering to read it. +- **A preview of a link's destination** — use [PreviewTip](/components/feedback/tips/previewtip/) instead. + +## Usage examples + +### Icon-only buttons + +`IconButton`'s required `tip` prop wires a `ToolTip` automatically. If no `aria-label` is given, `tip`'s value becomes the accessible name; `tipProps` adjusts its alignment and placement: + +<StoryEmbed id="molecules-tips-tooltip--with-icon-button" height="10rem" /> + +### Disabled controls + +Use `aria-disabled` instead of `disabled` so the control stays focusable and its tooltip stays reachable by keyboard: + +<StoryEmbed id="molecules-tips-tooltip--disabled" height="10rem" /> + +### Keeping a tip open through a click + +Pass `closeOnClick={false}` to keep the tip open after a click — useful for a "copy → copied" style message: + +<StoryEmbed id="molecules-tips-tooltip--close-on-click" height="10rem" /> + +### Wrapping an arbitrary interactive element + +Wrap `ToolTip` directly around any interactive element, not just `IconButton`, wiring `id` and `aria-describedby` yourself: + +<StoryEmbed id="molecules-tips-tooltip--interactive-element" height="14rem" /> + +## Prop Reference + +### Alignment + +`alignment` accepts `top-center` (default), `bottom-center`, `left-center`, or `right-center`: + +<StoryEmbed id="molecules-tips-tooltip--alignments" height="16rem" /> + +Under RTL layouts, `left-center`/`right-center` mirror to follow reading direction: + +<StoryEmbed id="molecules-tips-tooltip--horizontal-alignments" height="16rem" /> diff --git a/packages/starlight/src/content/docs/components/feedback/alert.mdx b/packages/starlight/src/content/docs/components/feedback/alert.mdx new file mode 100644 index 0000000000..9b767545f5 --- /dev/null +++ b/packages/starlight/src/content/docs/components/feedback/alert.mdx @@ -0,0 +1,123 @@ +--- +title: Alert +description: An alert displays an important, succinct message and provides actions for users to address or dismiss it. It requires a user action to be dismissed. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=30186-40358) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Alert/Alert.tsx) + +## Usage + +Use an alert to display an important, succinct message with actions for users to address (or dismiss) the alert. It requires a user action to be dismissed. + +**Best practices:** + +- Choose the alert type that applies most to your case. +- Only use `feature` for product/feature announcements that are specific to the current page. +- Alerts should be persistent and non-modal — let the user ignore them or interact with them at any time. + +**When NOT to use:** + +- **Low priority feedback** — for decorative or celebratory feedback, use **Toast** instead. +- **Non-critical or promotional content** — for informational content that isn't urgent, use **Banner** instead. +- **Passive updates** — for non-urgent updates a user can check at their convenience, use **NotificationList** instead. +- **Form field validation** — for inline form errors, use inline messaging within the form field instead of an alert. + +## Anatomy + +An `Alert` is composed of up to four parts, left to right: + +1. **Icon** — automatically set by `type` (`general`, `success`, `error`, `subtle`, …). It's decorative (`aria-hidden`), so it needs no alt text. +2. **Text** (`children`) — what happened and, when applicable, what the user can do about it. Write conversationally, front-load the important part. +3. **CTA button** (optional, `cta`) — prompts the user to act on the alert. +4. **Dismiss button** (optional, rendered when `onClose` is passed) — lets the user remove the alert once they've acknowledged it. Only omit this when the alert must stay visible until a specific condition is met. + +When a floating alert's text would overflow, Alert truncates it to one line and adds an expand/collapse toggle automatically — no extra wiring required. + +## Usage examples + +### Handling dismissal + +Pass `onClose` to render a dismiss button and react to it. `Alert` doesn't track its own visibility — unmount it yourself once the user dismisses it: + +<StoryEmbed id="molecules-alert--dismissible" height="10rem" /> + +### Customizing the close button + +The close button's tooltip text, tooltip alignment, disabled state, and ref are configured through `closeButtonProps` rather than as top-level props, since they only apply when `onClose` is set: + +<StoryEmbed id="molecules-alert--close-button-customization" height="10rem" /> + +### Adding a call to action + +`cta` accepts either a `text` string or full `FillButton` children/props, so you can wire a click handler without composing a separate button element: + +```tsx +<Alert + type="general" + cta={{ text: 'View details', onClick: () => openDetails() }} +> + A new version is available. +</Alert> +``` + +## Prop Reference + +### Type + +`type` accepts `general`, `success`, `feature`, `notice`, `error`, or `subtle`, setting the icon and color treatment. `subtle` requires `placement="inline"` — see Placement below. + +#### General + +<StoryEmbed id="molecules-alert--general" height="5rem" /> + +#### Success + +<StoryEmbed id="molecules-alert--success" height="5rem" /> + +#### Feature + +<StoryEmbed id="molecules-alert--feature" height="5rem" /> + +#### Notice + +<StoryEmbed id="molecules-alert--notice" height="5rem" /> + +#### Error + +<StoryEmbed id="molecules-alert--error" height="5rem" /> + +### Placement + +`placement` accepts `inline` or `floating` (default). Floating alerts truncate long content behind the expand/collapse toggle described in Anatomy; inline alerts render in full. `subtle` only appears at `inline` placement. + +#### General, inline + +<StoryEmbed id="molecules-alert--general-inline" height="4rem" /> + +#### Subtle, inline + +<StoryEmbed id="molecules-alert--subtle-inline" height="4rem" /> + +#### Success, inline + +<StoryEmbed id="molecules-alert--success-inline" height="4rem" /> + +#### Feature, inline + +<StoryEmbed id="molecules-alert--feature-inline" height="4rem" /> + +#### Notice, inline + +<StoryEmbed id="molecules-alert--notice-inline" height="4rem" /> + +#### Error, inline + +<StoryEmbed id="molecules-alert--error-inline" height="4rem" /> + +### Small width + +Below 480px wide, the CTA button stacks below the text automatically: + +<StoryEmbed id="molecules-alert--small-width" height="10rem" /> diff --git a/packages/starlight/src/content/docs/components/feedback/coachmark.mdx b/packages/starlight/src/content/docs/components/feedback/coachmark.mdx new file mode 100644 index 0000000000..ca0db68eed --- /dev/null +++ b/packages/starlight/src/content/docs/components/feedback/coachmark.mdx @@ -0,0 +1,52 @@ +--- +title: Coachmark +description: A pointed callout that highlights a specific piece of UI. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1083%3A1134) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Coachmark/index.tsx) + +## Usage + +Use `Coachmark` to guide a user toward a specific piece of UI — introducing a new feature or explaining a complex interaction. Wrap it around the element to highlight; a function passed to `renderPopover` returns the popover content shown alongside it. + +**Best practices:** + +- Use coachmarks sparingly, so they don't overwhelm the reader with popovers. +- `Coachmark` has no built-in dismiss button, auto-dismiss timer, or multi-step sequencing — build a dismiss control into whatever `renderPopover` returns, and flip `shouldShow` back to `false` yourself when it's clicked. For a guided tour of several steps, manage that sequencing yourself across separate `Coachmark` instances. +- `skipFocusTrap` defaults to `true`, so the popover doesn't trap focus — set it to `false` only if the popover genuinely needs to hold focus until dismissed. + +**When NOT to use:** + +- **Long-term guidance or reference material** — point to a help center or documentation instead; a coachmark is for a one-time nudge, not something a reader returns to. + +### Anatomy + +1. **Target wrapper** — `Coachmark`'s children; the popover positions itself relative to this, and it's otherwise invisible. +2. **Popover container** — the visible callout box; `outline` swaps its style from a drop shadow to a bordered outline. +3. **Beak** (optional, `beak`) — a pointed triangle connecting the popover to its target. +4. **Pattern background** (optional, `pattern`) — a decorative pattern layer behind the popover. +5. **Content** — whatever `renderPopover` returns; entirely consumer-defined, including any dismiss control. + +## Usage examples + +### Showing and dismissing a coachmark + +`Coachmark` is fully controlled through `shouldShow` — flip it to `true` to show the popover, then back to `false` from inside the content returned by `renderPopover`: + +<StoryEmbed id="molecules-coachmark--default" height="14rem" /> + +### Delaying appearance + +Pass `delay` (in ms) to wait before the popover appears once `shouldShow` becomes `true`: + +<StoryEmbed id="molecules-coachmark--delay" height="14rem" /> + +## Prop Reference + +### Popover styling + +`popoverProps` configures the popover's `beak`, `outline`, `pattern`, and `position` (`'above' | 'below'`) — passed straight through to the underlying `Popover`, minus its `center` position option: + +<StoryEmbed id="molecules-coachmark--customized" height="16rem" /> diff --git a/packages/starlight/src/content/docs/components/feedback/index.md b/packages/starlight/src/content/docs/components/feedback/index.md new file mode 100644 index 0000000000..9e49c59ba8 --- /dev/null +++ b/packages/starlight/src/content/docs/components/feedback/index.md @@ -0,0 +1,13 @@ +--- +title: Feedback +description: The system explaining something to the user, as opposed to something the user opens and dismisses. +sidebar: + label: Overview +--- + +The system explaining something to the user — as distinct from Overlays, which is reserved for things the user opens and dismisses. + +- [Alert](/components/feedback/alert/) +- [Toast](/components/feedback/toast/), [Toaster](/components/feedback/toaster/) +- [Coachmark](/components/feedback/coachmark/) +- [Tips](/components/feedback/tips/) — ToolTip, InfoTip, PreviewTip diff --git a/packages/starlight/src/content/docs/components/feedback/toast.mdx b/packages/starlight/src/content/docs/components/feedback/toast.mdx new file mode 100644 index 0000000000..c1db981d23 --- /dev/null +++ b/packages/starlight/src/content/docs/components/feedback/toast.mdx @@ -0,0 +1,47 @@ +--- +title: Toast +description: A brief, dismissible, non-modal notification, rendered through Toaster. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Updating · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1086-0&m=dev) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Toast/Toast.tsx) + +## Usage + +Use `Toast` for low-priority, often decorative or celebratory feedback — for example, unlocking an achievement while learning. + +**Best practices:** + +- Keep only one toast on screen at a time. +- Keep copy brief — no more than three lines including the title — and relevant to every reader who might see it. +- Don't repeat a message that's already delivered through the notification bell. +- `Toast` has no dismiss timer or dismiss button of its own; whatever renders it is responsible for removing it, on a timeout or otherwise. In practice, that's [Toaster](/components/feedback/toaster/) — render `Toast` through it rather than mounting it directly. + +**When NOT to use:** + +- **A message the reader needs to act on** — use [Alert](/components/feedback/alert/) instead; a toast can disappear without any guarantee the reader saw it. + +## Usage examples + +### Custom icons + +`icon` accepts a URL, rendered as a background image, or any other `ReactNode`, centered in a 64×64 circle automatically. A custom `ReactNode` icon must be sized to fit that circle itself: + +<StoryEmbed id="molecules-toasts-toast--icons" height="10rem" /> + +### Rich content in the title or body + +Compose an icon or an `Anchor` directly into `title` or `children`. + +#### Icon in the title + +<StoryEmbed id="molecules-toasts-toast--content-icon" height="8rem" /> + +#### Anchor in the body + +<StoryEmbed id="molecules-toasts-toast--content-link" height="8rem" /> + +## Playground + +<StoryEmbed id="molecules-toasts-toast--default" height="8rem" /> diff --git a/packages/starlight/src/content/docs/components/feedback/toaster.mdx b/packages/starlight/src/content/docs/components/feedback/toaster.mdx new file mode 100644 index 0000000000..243df8b87d --- /dev/null +++ b/packages/starlight/src/content/docs/components/feedback/toaster.mdx @@ -0,0 +1,29 @@ +--- +title: Toaster +description: Renders and animates a stack of Toasts, fully controlled by the consumer. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Updating · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1086-0&m=dev) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Toaster/index.tsx) + +## Usage + +Use `Toaster` to render one shared, animated stack of [Toast](/components/feedback/toast/)s near the root of your app. Gamut ships no `useToast` hook or imperative toast API — `Toaster` is a fully controlled list renderer, so the array of active toasts lives in your own state. + +**Best practices:** + +- Keep a `toasts` array in your own state — each item is a unique `id` plus `Toast`'s `title`/`icon`/`children` — and pass it straight to `Toaster`'s `toasts` prop. +- Wire a single `onClose(id)` handler that filters the closed toast's `id` out of that state. `Toaster` calls it per toast; it never removes anything itself. +- If a toast should disappear automatically, add your own timeout that calls the same removal logic — neither `Toast` nor `Toaster` auto-dismiss. +- Render exactly one `Toaster`. It portals to `document.body`, fixed to the bottom-right of the screen, so a second instance would just stack on top of the first. + +**When NOT to use:** + +- **Feedback that needs to persist across a reload** — `Toaster`'s list lives in component state; anything not re-added after mount disappears with the page. + +## Playground + +The story below exercises the full controlled pattern: two buttons add or remove all four example toasts from state, and `Toaster` simply renders whatever that state currently holds: + +<StoryEmbed id="molecules-toasts-toaster--default" height="20rem" /> diff --git a/packages/starlight/src/content/docs/components/index.md b/packages/starlight/src/content/docs/components/index.md new file mode 100644 index 0000000000..c32e887e6f --- /dev/null +++ b/packages/starlight/src/content/docs/components/index.md @@ -0,0 +1,32 @@ +--- +title: Components +description: All Gamut components, grouped by what they do rather than by Atomic Design tier. +--- + +Components are grouped by what a reader needs to do, not by Gamut's internal Atoms/Molecules/Organisms tier: + +- [Actions](/components/actions/) — trigger, choose, or remove +- [Containers](/components/containers/) — group, wrap, or lay out content +- [Inputs & forms](/components/inputs-and-forms/) — collect input +- [Navigation](/components/navigation/) — move between pages or views +- [Feedback](/components/feedback/) — the system explaining something +- [Status](/components/status/) — communicate state at a glance +- [Overlays](/components/overlays/) — opened and dismissed, floating above the page +- [Data display](/components/data-display/) — structured or bulk data +- [Typography](/components/typography/) — text rendering +- [Media & assets](/components/media-and-assets/) — icons, illustrations, patterns, animation, video +- [Utilities](/components/utilities/) — non-visual helpers + +If you already know a component's name, search finds it regardless of category. + +## Placement rulings + +A few components could reasonably live in two categories. These are the calls this site makes, and why — each losing category cross-links back to the winner: + +| Component | Placed in | Why | +| --------- | ---------- | ------------------------------------------------------------------------------------------------------------------------ | +| Menu | Actions | The action-list role is the richer half of its API; the nav role is cross-linked from Navigation. | +| Tag | Actions | Docs emphasize interactive selection/removal; the read-only overlap with Badge is called out on Tag's "When NOT to use." | +| Tips | Feedback | Readers think "the system explaining something"; Overlays is reserved for things you open and dismiss. | +| Drawer | Containers | Collapses within page flow, unlike Flyout, which floats above it (Overlays). | +| Anchor | Navigation | Functionally navigation despite its typography implementation. | diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/connected-form-group.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/connected-form-group.mdx new file mode 100644 index 0000000000..10e29408f3 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/connected-form-group.mdx @@ -0,0 +1,64 @@ +--- +title: ConnectedFormGroup +description: A FormGroup wired to react-hook-form state. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Updating · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/ConnectedForm/ConnectedFormGroup.tsx) + +## Usage + +`ConnectedFormGroup` is the `FormGroup` that reads [ConnectedForm](/components/inputs-and-forms/connectedform/connected-form/)'s context for error and disabled states, and types its `field` prop to match whichever `ConnectedFormInput` you pass as `field.component`. + +**Best practices:** + +- Use it primarily through the `useConnectedForm` hook — see [ConnectedForm](/components/inputs-and-forms/connectedform/) for the full pattern. +- Options for `ConnectedRadioGroupInput` (and similar) need `{ label, value }` objects, not plain strings — the type system catches the mistake, but it's easy to reach for a plain array first: + +```tsx +<ConnectedFormGroup + name="radioGroup" + label="cool radio group dude" + field={{ + component: ConnectedRadioGroupInput, + // Correct — each option needs both label and value: + options: [ + { label: 'one', value: 'one' }, + { label: 'two', value: 'two' }, + { label: 'zero', value: 'zero' }, + ], + }} +/> +``` + +## Usage examples + +### Adding an InfoTip + +Pass `infotip` for a tip next to the label — it's auto-labeled by a string `label`, or you can override with `ariaLabel`/`ariaLabelledby` when the label is a `ReactNode`. + +#### Auto-labeled + +<StoryEmbed + id="organisms-connectedform-connectedformgroup--info-tip-auto-labelling" + height="8rem" +/> + +#### ReactNode label + +<StoryEmbed + id="organisms-connectedform-connectedformgroup--info-tip-with-react-node-label" + height="14rem" +/> + +## Prop Reference + +### State + +A `ConnectedFormGroup` can be `default`, `error` (via `customError`), or `disabled`: + +<StoryEmbed + id="organisms-connectedform-connectedformgroup--states" + height="10rem" +/> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/connected-form-inputs.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/connected-form-inputs.mdx new file mode 100644 index 0000000000..e8382b929e --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/connected-form-inputs.mdx @@ -0,0 +1,89 @@ +--- +title: ConnectedFormInputs +description: Field inputs pre-wired to react-hook-form state. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Updating · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/ConnectedForm/ConnectedInputs/) + +## Usage + +"ConnectedFormInputs" is a documentation umbrella over seven components — `ConnectedInput`, `ConnectedCheckbox`, `ConnectedRadio`, `ConnectedRadioGroup`, `ConnectedRadioGroupInput`, `ConnectedSelect`, `ConnectedTextArea`, and `ConnectedNestedCheckboxes` — each visually identical to its un-connected counterpart, wired to [ConnectedForm](/components/inputs-and-forms/connectedform/connected-form/)'s context for field registration, validation, and required/disabled state. + +**Best practices:** + +- Use these through [ConnectedFormGroup](/components/inputs-and-forms/connectedform/connected-form-group/)'s `field` prop, not on their own — using them outside `ConnectedFormGroup` removes most of the accessibility and type-safety they're built for. +- Use `ConnectedRadioGroupInput` rather than composing `ConnectedRadioGroup` and `ConnectedRadio` yourself — `ConnectedRadioGroup`/`ConnectedRadio` should rarely, if ever, appear outside it. + +**When NOT to use:** + +- **A field outside a `ConnectedForm`** — use the un-connected counterpart directly: [Input](/components/inputs-and-forms/input/), [Select](/components/inputs-and-forms/select/), [Checkbox](/components/inputs-and-forms/checkbox/), [TextArea](/components/inputs-and-forms/text-area/), or [Radio](/components/inputs-and-forms/radio/). + +## Usage examples + +### Delaying updates for performance + +For many nested and/or array-like fields, calling react-hook-form's `setValue` on every keystroke can get expensive. `useDebouncedField` is the escape hatch — it only commits on blur — but don't combine it with the standard connected input components, since both would register the same field independently: + +```tsx +// DebouncedInput.tsx +import { Input } from '@codecademy/gamut'; +import { useDebouncedField } from '@codecademy/gamut'; + +export const DebouncedInput: React.FC<{ name: string }> = ({ name }) => { + const { onChange, onBlur, value } = useDebouncedField({ name, type: 'text' }); + + return ( + <Input + name={name} + type="text" + onChange={onChange} + onBlur={onBlur} + value={value} + htmlFor={name} + /> + ); +}; +``` + +```tsx +// FormPage.tsx +import { ConnectedForm } from '@codecademy/gamut'; +import { DebouncedInput } from './DebouncedInput'; + +export const FormPage: React.FC = () => ( + <ConnectedForm + defaultValues={{ favoriteEpisode: 'Bad Blood' }} + onSubmit={(values) => submit(values)} + > + <DebouncedInput name="favoriteEpisode" /> + </ConnectedForm> +); +``` + +## Prop Reference + +### Input types + +Four of the seven have a visually identical, un-connected counterpart with its own full reference: + +| Connected input | Un-connected counterpart | +| ------------------- | --------------------------------------------------- | +| `ConnectedInput` | [Input](/components/inputs-and-forms/input/) | +| `ConnectedSelect` | [Select](/components/inputs-and-forms/select/) | +| `ConnectedCheckbox` | [Checkbox](/components/inputs-and-forms/checkbox/) | +| `ConnectedTextArea` | [TextArea](/components/inputs-and-forms/text-area/) | + +`ConnectedRadioGroupInput` (built from `ConnectedRadioGroup` + `ConnectedRadio`) is the connected equivalent of [Radio](/components/inputs-and-forms/radio/) grouped together — its `options` shape differs from a plain `Radio` group, so see the Playground below rather than the Radio page for its exact API. + +`ConnectedNestedCheckboxes` has no un-connected counterpart — it's a recursive parent/child checkbox tree (any depth), where a parent's checked/indeterminate state derives from its children, and the submitted value is an array of every selected descendant's value. + +### Playground + +The following form exercises every connected input type together — checkbox, select, input, radio group, textarea, and nested checkboxes: + +<StoryEmbed + id="organisms-connectedform-connectedform--default" + height="30rem" +/> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/connected-form.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/connected-form.mdx new file mode 100644 index 0000000000..d8d1b05fe4 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/connected-form.mdx @@ -0,0 +1,50 @@ +--- +title: ConnectedForm +description: A Form wired to react-hook-form state. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/ConnectedForm/ConnectedForm.tsx) + +## Usage + +`ConnectedForm` is the `Form` that provides [react-hook-form](https://react-hook-form.com) context to every `Connected` component nested inside it — see [ConnectedForm](/components/inputs-and-forms/connectedform/) for how it composes with `ConnectedFormGroup`, the connected inputs, and `SubmitButton`. + +**Best practices:** + +- Always pass `defaultValues` — it's required for reliable behavior, particularly resets. +- Every key in `validationRules` must have a matching key in `defaultValues`. +- `validation="onChange"` (the default is `"onChange"` via `useConnectedForm`) disables the submit button until every required field is valid; other [react-hook-form modes](https://react-hook-form.com/docs/useform#mode) validate at different times. +- `watchedFields` is useful for conditionally rendering fields based on another field's live value, but re-runs on every render — only reach for it when you actually need a value before submission. + +**When NOT to use:** + +- **A form that sticks to a 12-column grid with consistent spacing** — use [GridForm](/components/inputs-and-forms/gridform/) instead. + +## Usage examples + +### Watching field values + +Pass `watchedFields` to react to a field's value changing before submission — here, checking a checkbox reveals a radio group: + +<StoryEmbed + id="organisms-connectedform-connectedform--watched-fields" + height="16rem" +/> + +### Validating rules that depend on runtime state + +`validationRules` is memoized on mount. When a field's valid values depend on another field's current value, pass `customValidations` to that field instead — it re-evaluates on every render: + +<StoryEmbed + id="organisms-connectedform-connectedform--custom-field-validations" + height="14rem" +/> + +## Playground + +<StoryEmbed + id="organisms-connectedform-connectedform--default" + height="20rem" +/> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/index.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/index.mdx new file mode 100644 index 0000000000..21d264bfe2 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/index.mdx @@ -0,0 +1,96 @@ +--- +title: ConnectedForm +description: react-hook-form-connected wrappers around form scaffolding. +sidebar: + label: Overview +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +`ConnectedForm` is a flexible way to build forms with all the reliability of [GridForm](/components/inputs-and-forms/gridform/), but without its strict grid structure. Four pieces compose together: + +- [ConnectedForm](/components/inputs-and-forms/connectedform/connected-form/) — a `Form` that provides [react-hook-form](https://react-hook-form.com) context to every `Connected` component inside it +- [ConnectedFormGroup](/components/inputs-and-forms/connectedform/connected-form-group/) — a `FormGroup` that reads that context for error and disabled states +- [ConnectedFormInputs](/components/inputs-and-forms/connectedform/connected-form-inputs/) — the field components (`ConnectedInput`, `ConnectedSelect`, `ConnectedCheckbox`, and others) that read the context for registration, validation, required, and disabled states — meant to be used through `ConnectedFormGroup`, not on their own +- [SubmitButton](/components/inputs-and-forms/connectedform/submit-button/) — a submit button that reads the context for its loading/disabled state + +The primary access pattern is the `useConnectedForm` hook, which builds a type-safe `ConnectedForm` + `ConnectedFormGroup` pair from the `defaultValues` you give it — a field name that doesn't match a `defaultValues` key is a type error, not a runtime bug. It also returns a `FormRequiredText`, which you should render unless every field is optional. + +## Usage examples + +### Putting it all together + +<StoryEmbed + id="organisms-connectedform-connectedform--default" + height="30rem" +/> + +```tsx +import { + ConnectedCheckbox, + ConnectedInput, + ConnectedSelect, + SubmitButton, + useConnectedForm, +} from '@codecademy/gamut'; +import { TerminalIcon } from '@codecademy/gamut-icons'; + +export const GoodForm = () => { + const { + ConnectedFormGroup, + ConnectedForm, + connectedFormProps, + FormRequiredText, + } = useConnectedForm({ + defaultValues: { + thisField: true, + thatField: 'zero', + anotherField: 'state your name.', + }, + validationRules: { + thisField: { required: 'you need to check this.' }, + thatField: { + pattern: { + value: /^(?:(?!zero).)*$/, + message: 'literally anything but zero', + }, + }, + }, + }); + + return ( + <ConnectedForm + onSubmit={({ thisField }) => console.log(thisField)} + resetOnSubmit + {...connectedFormProps} + > + <SubmitButton>submit this form.</SubmitButton> + <ConnectedFormGroup + name="thisField" + label="cool checkbox bruh" + field={{ + component: ConnectedCheckbox, + label: 'check it ouuut', + }} + /> + <ConnectedFormGroup + name="thatField" + label="cool select dude" + field={{ + component: ConnectedSelect, + options: ['one', 'two', 'zero'], + }} + /> + <ConnectedFormGroup + name="anotherField" + label="cool input" + field={{ + component: ConnectedInput, + icon: TerminalIcon, + }} + /> + <FormRequiredText /> + </ConnectedForm> + ); +}; +``` diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/submit-button.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/submit-button.mdx new file mode 100644 index 0000000000..6de8a0d5e4 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/ConnectedForm/submit-button.mdx @@ -0,0 +1,23 @@ +--- +title: SubmitButton +description: A submit button aware of the connected form's submitting/valid state. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/ConnectedForm/SubmitButton.tsx) + +## Usage + +`SubmitButton` is a submit-type button that reads [ConnectedForm](/components/inputs-and-forms/connectedform/connected-form/)'s context for its loading/disabled state. It must be used within a `ConnectedForm`. + +**Best practices:** + +- `disabled` and `loading` each accept a plain `boolean` **or** a function of react-hook-form's `formState`. They default to `false`, not to `formState.isValid` — passing a static `false` (the default) means the button is never automatically disabled by validation. To tie it to form validity, pass a function explicitly: `disabled={(formState) => !formState.isValid}`. +- `as` swaps the rendered button component; it defaults to `FillButton`. + +## Prop Reference + +### State + +<StoryEmbed id="organisms-connectedform-submitbutton--states" height="8rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-group-description.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-group-description.mdx new file mode 100644 index 0000000000..728d7634fe --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-group-description.mdx @@ -0,0 +1,20 @@ +--- +title: FormGroupDescription +description: Supporting/help text for a FormGroup field. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/elements/FormGroupDescription.tsx) + +## Usage + +`FormGroupDescription` is the description text [FormGroup](/components/inputs-and-forms/form/form-group/) renders internally from its `description` prop — reach for it directly only when building custom form scaffolding outside `FormGroup`. + +**Best practices:** + +- When rendered by `FormGroup`, it's automatically given `aria-live="assertive"`. Used standalone, it isn't — add that yourself if the description can change after the initial render. + +## Playground + +<StoryEmbed id="atoms-formelements-formgroupdescription--default" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-group-label.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-group-label.mdx new file mode 100644 index 0000000000..35d779063f --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-group-label.mdx @@ -0,0 +1,53 @@ +--- +title: FormGroupLabel +description: An accessible label for a FormGroup field. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1189%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/elements/FormGroupLabel.tsx) + +## Usage + +`FormGroupLabel` is the label [FormGroup](/components/inputs-and-forms/form/form-group/) renders internally from its `label` prop — reach for it directly only when building custom form scaffolding outside `FormGroup`. + +**Best practices:** + +- `isSoloField` suppresses both the required asterisk and the "(optional)" text — use it when a field is the only one in its form and is always required. + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1189%3A0" /> + +## Usage examples + +`FormGroupLabel` follows the group's shared `htmlFor`-wiring pattern — see [Form scaffolding: Usage examples](/components/inputs-and-forms/form/#usage-examples). Passing `htmlFor` renders a real `<label>`; omitting it renders a `<div>` instead, for cases where the "label" doesn't correspond to a single labelable element. + +### With htmlFor + +<StoryEmbed + id="atoms-formelements-formgrouplabel--with-html-for" + height="4rem" +/> + +### Without htmlFor + +<StoryEmbed + id="atoms-formelements-formgrouplabel--without-html-for" + height="4rem" +/> + +## Prop Reference + +### Variant + +#### Default + +<StoryEmbed id="atoms-formelements-formgrouplabel--default" height="4rem" /> + +#### Large + +<StoryEmbed id="atoms-formelements-formgrouplabel--large-size" height="4rem" /> + +#### Disabled + +<StoryEmbed id="atoms-formelements-formgrouplabel--disabled" height="4rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-group.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-group.mdx new file mode 100644 index 0000000000..c19bb3dabb --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-group.mdx @@ -0,0 +1,59 @@ +--- +title: FormGroup +description: Groups a field with its label, description, and error region. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/elements/FormGroup.tsx) + +## Usage + +Use `FormGroup` to pair a form field with a label, an optional description, and an optional error message, in that fixed order. + +**Best practices:** + +- Pass `label` and `description` as plain strings, not JSX — `FormGroup` renders its own `FormGroupLabel`/`FormGroupDescription` internally from them, rather than accepting them as composed children. +- Pass `error` as a string to show an error message below the field, and pass the field's own `error` boolean too, so the field's styling matches the message. + +**When NOT to use:** + +- **The interface doesn't need form-shaped structure at all** — see [Form scaffolding](/components/inputs-and-forms/form/) for when to reach for these atoms directly instead of GridForm/ConnectedForm. + +## Usage examples + +`FormGroup` follows the group's shared `htmlFor`-wiring pattern — see [Form scaffolding: Usage examples](/components/inputs-and-forms/form/#usage-examples) for connecting its label to the field it wraps. + +### Adding an InfoTip + +Pass `infotip` to render an `InfoTip` next to the label. It's auto-labeled by the field's `label` unless you pass `infotip.ariaLabel`/`ariaLabelledby`. + +#### Low emphasis + +<StoryEmbed + id="atoms-formelements-formgroup--low-emphasis-info-tip" + height="8rem" +/> + +#### High emphasis + +<StoryEmbed + id="atoms-formelements-formgroup--high-emphasis-info-tip" + height="8rem" +/> + +## Prop Reference + +### State + +#### Default + +<StoryEmbed id="atoms-formelements-formgroup--default-state" height="10rem" /> + +#### Error + +<StoryEmbed id="atoms-formelements-formgroup--error" height="10rem" /> + +#### Valid + +<StoryEmbed id="atoms-formelements-formgroup--valid" height="10rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-required-text.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-required-text.mdx new file mode 100644 index 0000000000..0bfc7a36ae --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form-required-text.mdx @@ -0,0 +1,25 @@ +--- +title: FormRequiredText +description: The "required" indicator text for a field. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/elements/FormRequiredText.tsx) + +## Usage + +Include `FormRequiredText` in any form that has required fields, so readers know what the required-field asterisk convention means. + +**Best practices:** + +- Its text is fixed to "\* Required" — it always renders that, regardless of `children`. +- It's `aria-hidden` by default, matching the required asterisk rendered elsewhere in Gamut's form components (also `aria-hidden`). + +**When NOT to use:** + +- **No fields in the form are required** — omit it; there's nothing for it to explain. + +## Playground + +<StoryEmbed id="atoms-formelements-formrequiredtext--default" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form.mdx new file mode 100644 index 0000000000..dc6753a97f --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/form.mdx @@ -0,0 +1,24 @@ +--- +title: Form +description: The root form element and submit handling. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/elements/Form.tsx) + +## Usage + +`Form` is the styled `<form>` element that [GridForm](/components/inputs-and-forms/gridform/) and [ConnectedForm](/components/inputs-and-forms/connectedform/) are built on. + +**Best practices:** + +- `Form` always sets `method="post"` (overridable) and `noValidate` (not overridable) — it expects validation to be handled in JS, not the browser's native HTML validation UI. + +**When NOT to use:** + +- **Building an actual form** — use [GridForm](/components/inputs-and-forms/gridform/) or [ConnectedForm](/components/inputs-and-forms/connectedform/) instead; both add the accessibility and validation wiring `Form` alone doesn't. + +## Playground + +<StoryEmbed id="atoms-formelements-form--default" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/Form/index.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/index.mdx new file mode 100644 index 0000000000..fdcb9ea80f --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/Form/index.mdx @@ -0,0 +1,31 @@ +--- +title: Form scaffolding +description: The structural components every Gamut form is built from. +sidebar: + label: Overview +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +Most consumers shouldn't reach for these atoms directly — use [GridForm](/components/inputs-and-forms/gridform/) or [ConnectedForm](/components/inputs-and-forms/connectedform/) instead, both of which give you accessibility and [react-hook-form](https://react-hook-form.com) validation out of the box. Reach for these individually only when an interface needs form-shaped structure without the technical machinery of an actual form. + +- [Form](/components/inputs-and-forms/form/form/) — the root `<form>` element +- [FormGroup](/components/inputs-and-forms/form/form-group/) — wraps a field with its label, description, and error region +- [FormGroupLabel](/components/inputs-and-forms/form/form-group-label/) — the label `FormGroup` renders internally +- [FormGroupDescription](/components/inputs-and-forms/form/form-group-description/) — the description text `FormGroup` renders internally +- [FormRequiredText](/components/inputs-and-forms/form/form-required-text/) — the "\* Required" legend for forms with required fields + +## Usage examples + +### Wiring a FormGroup to its field manually + +`FormGroup` doesn't automatically connect its label, description, or error to the field it wraps — no `aria-describedby`, no `id` injection. Match `htmlFor` on `FormGroup` to `id` on the field yourself: + +<StoryEmbed + id="atoms-formelements-formgroup--form-group-html-for" + height="8rem" +/> + +Omitting `htmlFor` renders the label as a `<div>` instead of a `<label>` — pass `aria-labelledby` on the field directly to stay accessible without a real label element: + +<StoryEmbed id="atoms-formelements-formgroup--form-group-div" height="8rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/GridForm/index.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/GridForm/index.mdx new file mode 100644 index 0000000000..4990309ae6 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/GridForm/index.mdx @@ -0,0 +1,43 @@ +--- +title: GridForm +description: An efficient way to build and design forms on a 12-column grid. +sidebar: + label: Overview +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1689%3A3910) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/GridForm/GridForm.tsx) + +`GridForm` is large enough to split across pages, following the same explanation/how-to/reference split every component page uses — this page covers Usage and Anatomy; [Patterns](/components/inputs-and-forms/gridform/patterns/) and [Reference](/components/inputs-and-forms/gridform/reference/) are their own pages. + +## Usage + +Give `GridForm` a `fields` array — a list of field descriptions — and it strings together the right form scaffolding inside a [LayoutGrid](/components/containers/layout-grid/), built on [ConnectedForm](/components/inputs-and-forms/connectedform/) underneath. + +**Best practices:** + +- Let `GridForm` compute `defaultValues`, required-field detection, and solo-field detection from `fields` — only override `hasSoloField` when a form has a single visible field _plus_ a hidden or custom-rendered input (otherwise the detection is automatic). +- The Figma library ships matching `❖ GridForm` variants (Starter, Sections, Inline Submit) — detach the component to modify the underlying `📐 LayoutGrid`, then add, remove, or edit `⬦ Form Inputs`. + +**When NOT to use:** + +- **A form that doesn't fit a 12-column grid, or needs more layout freedom** — use [ConnectedForm](/components/inputs-and-forms/connectedform/) instead. + +### Anatomy + +`GridForm` renders a single `LayoutGrid` inside a `ConnectedForm`. Each field, section title, and the final submit/cancel row is its own grid `Column`: + +1. **Required-text legend** (`FormRequiredText`, unless `hideRequiredText` or the form has a single required field) — the first `Column`, unless the very first field is a section (then it moves into that section's title instead). +2. **Fields** — each wrapped in a `Column` sized/spanned by `field.size`/`field.rowspan`, except `hidden`/`sweet-container` fields, which render with no layout wrapper at all. +3. **Sections** (optional) — a `GridFormSectionTitle` `Column`, followed by that section's fields, followed by a `GridFormSectionBreak` (`<hr>`) — except after the last section. +4. **Submit / cancel row** — always the final grid item, built from the `submit` and optional `cancel` props. + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1689%3A3910" /> + +## Playground + +The playground below is a kitchen-sink form exercising most field types at once — see [Reference](/components/inputs-and-forms/gridform/reference/) for each field type on its own, and [Patterns](/components/inputs-and-forms/gridform/patterns/) for layout and state recipes: + +<StoryEmbed id="organisms-gridform-usage--default" height="34rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/GridForm/patterns.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/GridForm/patterns.mdx new file mode 100644 index 0000000000..d80f18e09d --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/GridForm/patterns.mdx @@ -0,0 +1,102 @@ +--- +title: 'GridForm: Patterns' +description: Layout and state recipes for GridForm. +sidebar: + label: Patterns +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +How-to recipes for [GridForm](/components/inputs-and-forms/gridform/) — see [Reference](/components/inputs-and-forms/gridform/reference/) for the full field-type catalog and validation options. + +## Customizing field layout and the required-text legend + +Use a field's `size` and `rowspan` (borrowed from [LayoutGrid](/components/containers/layout-grid/)) to lay out a form on its 12-column grid, and `requiredTextProps` to restyle the `FormRequiredText` legend so it's visually distinct from the text above it: + +<StoryEmbed id="organisms-gridform-layout--formatted" height="20rem" /> + +## Building sections + +Pass section objects (each with a `title` and its own `fields`) alongside loose fields in the `fields` array. A section's `layout` can be `"left"` (title takes 3 columns) or centered (the default); `as` sets the title's heading level (default `h2`) and must be a heading tag: + +<StoryEmbed id="organisms-gridform-layout--sections" height="40rem" /> + +## Hiding the required-text legend + +Pass `hideRequiredText` when a form has no required fields at all — otherwise leave the legend in place: + +<StoryEmbed id="organisms-gridform-layout--hide-required-text" height="14rem" /> + +## A solo-field form + +A form with exactly one visible field automatically skips the required-text legend and treats that field as required — no extra prop needed, unless the form also has a hidden or custom-rendered input alongside it (then pass `hasSoloField` explicitly): + +<StoryEmbed id="organisms-gridform-layout--solo-field" height="12rem" /> + +## Reacting to a field's value before submit + +Give a field an `onUpdate` callback to read its value in a parent component as it changes, without waiting for `onSubmit`: + +<StoryEmbed id="organisms-gridform-states--on-field-update" height="14rem" /> + +## Disabling fields + +### While editing + +Set `disabled` on any field to make it visually and functionally unusable: + +<StoryEmbed id="organisms-gridform-states--disabled-inputs" height="12rem" /> + +### After a successful submission + +`disableFieldsOnSubmit` disables every field once the form submits successfully. Pair it with `wasSubmitSuccessful` if you have server-side validation to run after the client-side checks pass: + +<StoryEmbed + id="organisms-gridform-states--disabled-fields-on-submit" + height="14rem" +/> + +## Resetting after a successful submission + +`resetOnSubmit` clears the form back to its defaults once it submits successfully — same `wasSubmitSuccessful` caveat as above: + +<StoryEmbed id="organisms-gridform-states--reset-on-submit" height="14rem" /> + +## Combining loading, disabling, and resetting + +`disableFieldsOnSubmit`, `resetOnSubmit`, and a `submit.loading` state driven by your own `onSubmit` handler compose into a full submit lifecycle: + +<StoryEmbed id="organisms-gridform-states--form-loading" height="14rem" /> + +## Custom and markdown errors + +A field can carry a `customError` in addition to its `validation` rules — a failing validation rule always wins over `customError` when both apply. Error text renders through Gamut's `Markdown` component, so a validation message can include a link. + +### Custom error + +<StoryEmbed id="organisms-gridform-states--custom-error" height="12rem" /> + +### Markdown error + +<StoryEmbed id="organisms-gridform-states--markdown-errors" height="12rem" /> + +## Adding a cancel button + +Pass `cancel` alongside `submit` to render a `TextButton` next to the submit button: + +<StoryEmbed id="organisms-gridform-buttons--cancel-button" height="10rem" /> + +## Making the submit button inline with a field + +Size a field and the submit button to share a row (for example, field `size={6}`, `submit.size={4}`). Pair this with `hideLabel` on the field so its label doesn't throw off the alignment — but if you hide the visible label, provide an `aria-label` or another nearby label so the field stays accessible: + +<StoryEmbed + id="organisms-gridform-buttons--submit-button-inline" + height="10rem" +/> + +## Adding an InfoTip to a field + +Pass `infotip` on any field — it's auto-labeled by the field's own label unless you override it with `ariaLabel`/`ariaLabelledby`: + +<StoryEmbed id="organisms-gridform-layout--info-tip" height="30rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/GridForm/reference.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/GridForm/reference.mdx new file mode 100644 index 0000000000..0d4ec665cb --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/GridForm/reference.mdx @@ -0,0 +1,202 @@ +--- +title: 'GridForm: Reference' +description: Every GridForm field type, validation option, and submit button configuration. +sidebar: + label: Reference +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +The full field-type catalog, validation rules, and submit button options for [GridForm](/components/inputs-and-forms/gridform/) — see [Patterns](/components/inputs-and-forms/gridform/patterns/) for layout and state recipes. + +## Field types + +Every field in `GridForm`'s `fields` array is a plain object with a `type` discriminator. + +### `text` + +Text inputs support any HTML input type (`text`, `email`, `password`, `number`, `tel`, `url`, `search`, `date`, `time`, and more) under the same shape. + +#### With an explicit type + +<StoryEmbed id="organisms-gridform-fields--text-field" height="10rem" /> + +#### With type omitted (defaults to text) + +<StoryEmbed id="organisms-gridform-fields--default-text-field" height="10rem" /> + +Placeholder text is a `type: 'text'` field using `placeholder` instead of a visible description — use it sparingly. Browsers often render placeholder text with too little contrast for AA, and it should only rephrase the label, never add information the label doesn't already have: + +<StoryEmbed + id="organisms-gridform-fields--placeholder-text-field" + height="10rem" +/> + +### `textarea` + +<StoryEmbed id="organisms-gridform-fields--textarea-field" height="12rem" /> + +### `select` + +<StoryEmbed id="organisms-gridform-fields--select-field" height="10rem" /> + +### `radio-group` + +<StoryEmbed id="organisms-gridform-fields--radio-group-field" height="12rem" /> + +### `file` + +Accepts a custom `validate` function for restricting file types/size: + +<StoryEmbed id="organisms-gridform-fields--file-upload-field" height="10rem" /> + +### `checkbox` + +`spacing="tight"` fits checkboxes into smaller areas. + +#### Default spacing + +<StoryEmbed id="organisms-gridform-fields--checkbox-field" height="12rem" /> + +#### Tight spacing + +<StoryEmbed id="organisms-gridform-fields--checkbox-spacing" height="12rem" /> + +### `nested-checkboxes` + +Infinite levels of nesting are supported. A parent is `checked` when every child is checked, `indeterminate` when some are, and the submitted value is an array of every selected value, including children: + +<StoryEmbed + id="organisms-gridform-fields--nested-checkboxes-field" + height="16rem" +/> + +### `custom` and `custom-group` + +`custom` takes a `render` prop for a fully custom field. `custom-group` wraps that same render prop in a `FormGroup` — use it when the custom field still needs a label and `GridForm`-managed error region: + +<StoryEmbed id="organisms-gridform-fields--custom-inputs" height="14rem" /> + +### `hidden` + +Submits `defaultValue` with the rest of the form, with no visible field at all: + +<StoryEmbed id="organisms-gridform-fields--hidden-input" height="10rem" /> + +### `sweet-container` + +A honeypot field — named to avoid tipping off bots that it's a spam trap, unlike the more recognizable "honeypot": + +<StoryEmbed id="organisms-gridform-fields--sweet-container" height="10rem" /> + +### InfoTips on fields + +#### Auto-labeled + +<StoryEmbed + id="organisms-gridform-fields--info-tip-auto-labelling" + height="10rem" +/> + +#### ReactNode label + +<StoryEmbed + id="organisms-gridform-fields--info-tip-with-react-node-label" + height="16rem" +/> + +## Validation + +`GridForm` validates through [react-hook-form](https://react-hook-form.com). Each field's `validation` property accepts its `RegisterOptions` (`required`, `pattern`, `validate`, `minLength`/`maxLength`, `min`/`max`). The form-level `validation` prop controls when that validation runs: + +- `"onSubmit"` (default) — validate only on submit. +- `"onChange"` — validate on every change; the submit button stays disabled until every required field is valid. +- `"onTouched"` — validate on blur. + +```tsx +// Required, with a custom message +{ type: 'text', name: 'email', label: 'Email', validation: { required: 'Please enter your email address' } } + +// Pattern +{ type: 'text', name: 'email', label: 'Email', validation: { pattern: { value: /^\S+@\S+$/, message: 'Enter a valid email address' } } } + +// Custom validate function +{ + type: 'file', + name: 'upload', + label: 'Upload', + validation: { + validate: (files: FileList) => { + const file = files[0]; + if (!['image/png', 'image/jpeg'].includes(file.type)) return 'PNG or JPEG only'; + if (file.size > 2 * 1024 * 1024) return 'Must be under 2MB'; + return true; + }, + }, +} + +// Length / range +{ type: 'text', name: 'username', label: 'Username', validation: { minLength: { value: 3, message: 'At least 3 characters' } } } +{ type: 'number', name: 'age', label: 'Age', validation: { min: { value: 18, message: 'Must be 18 or older' }, max: { value: 99, message: 'Must be 99 or younger' } } } +``` + +## Submit button + +### Type + +`submit.type` picks the button component: `'fill'` (default) or `'cta'`. + +#### Fill + +<StoryEmbed + id="organisms-gridform-buttons--submit-button-fill" + height="10rem" +/> + +#### CTA + +<StoryEmbed id="organisms-gridform-buttons--submit-button-cta" height="10rem" /> + +### Position + +`submit.position` picks its alignment: `'left'` (default), `'center'`, `'right'`, or `'stretch'`. + +#### Left + +<StoryEmbed + id="organisms-gridform-buttons--submit-button-left" + height="10rem" +/> + +#### Center + +<StoryEmbed + id="organisms-gridform-buttons--submit-button-center" + height="10rem" +/> + +#### Right + +<StoryEmbed + id="organisms-gridform-buttons--submit-button-right" + height="10rem" +/> + +#### Stretch + +<StoryEmbed + id="organisms-gridform-buttons--submit-button-stretch" + height="10rem" +/> + +### State + +`submit.loading` and `submit.disabled` mirror [SubmitButton](/components/inputs-and-forms/connectedform/submit-button/)'s own props. + +#### Loading + +<StoryEmbed id="organisms-gridform-buttons--loading" height="10rem" /> + +#### Disabled + +<StoryEmbed id="organisms-gridform-buttons--disabled" height="10rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/checkbox.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/checkbox.mdx new file mode 100644 index 0000000000..dca4dc1097 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/checkbox.mdx @@ -0,0 +1,101 @@ +--- +title: Checkbox +description: A binary or indeterminate selection control. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1189%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/inputs/Checkbox.tsx) + +## Usage + +Use `Checkbox` to handle boolean values within forms. + +**Best practices:** + +- `Checkbox` is a controlled component — its `checked` value must be driven by external state and `onChange`, the same as a native React controlled input. +- `indeterminate` and `checked` are mutually exclusive; when a checkbox is indeterminate, `checked` must be `false` (or omitted) — Gamut's types enforce this. +- If `label` is a `ReactNode` rather than a plain string, `aria-label` becomes required — Gamut's types enforce this too. +- Group related checkboxes with `<fieldset>`/`<legend>`, and put nested checkboxes in a list (`<ul>`/`<li>`). + +**When NOT to use:** + +- **One choice from a group of options** — use [Radio](/components/inputs-and-forms/radio/) instead. +- **An on/off action with its own trigger, outside a form** — use [Toggle](/components/actions/toggle/) instead. + +## Usage examples + +### Building a controlled checkbox + +Drive `checked` from your own state and update it in `onChange`. + +#### Custom checkbox + +<StoryEmbed id="atoms-forminputs-checkbox--custom-checkbox" height="6rem" /> + +#### Controlled checkbox + +<StoryEmbed + id="atoms-forminputs-checkbox--controlled-checkbox" + height="12rem" +/> + +### Building a parent/child indeterminate group + +Compute the parent's `checked`/`indeterminate` from its children's state — `checked` when all children are checked, `indeterminate` when some are, neither when none are: + +<StoryEmbed id="atoms-forminputs-checkbox--nested-checkboxes" height="14rem" /> + +### Labels as ReactNodes + +A `ReactNode` label (for example, one containing a link) needs `aria-label`, and — if the link itself needs to be independently reachable by screen readers — `dontAriaHideLabel`, since the visible label text is `aria-hidden` by default: + +<StoryEmbed + id="atoms-forminputs-checkbox--labels-as-react-nodes" + height="10rem" +/> + +### Adding an InfoTip + +Pass `infotip` to render an `InfoTip` next to the label — it's auto-labeled by the checkbox's own label unless you override with `ariaLabel`/`ariaLabelledby`: + +<StoryEmbed id="atoms-forminputs-checkbox--info-tip" height="6rem" /> + +### Wrapping a multiline label + +A long label wraps onto additional lines rather than truncating or overflowing its container: + +<StoryEmbed id="atoms-forminputs-checkbox--multiline" height="8rem" /> + +## Prop Reference + +### State + +#### Unchecked + +<StoryEmbed id="atoms-forminputs-checkbox--unchecked" height="4rem" /> + +#### Checked + +<StoryEmbed id="atoms-forminputs-checkbox--checked" height="4rem" /> + +#### Indeterminate + +<StoryEmbed id="atoms-forminputs-checkbox--indeterminate" height="4rem" /> + +#### Disabled + +<StoryEmbed id="atoms-forminputs-checkbox--disabled" height="4rem" /> + +#### Checked and disabled + +<StoryEmbed + id="atoms-forminputs-checkbox--checked-and-disabled" + height="4rem" +/> + +### Spacing + +`spacing="tight"` has a dedicated story; the default (`"base"`) is shown in every example above. + +- [Tight](https://gamut.codecademy.com/?path=/story/atoms-forminputs-checkbox--spacing-tight) diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/date-picker.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/date-picker.mdx new file mode 100644 index 0000000000..c1a74e72b1 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/date-picker.mdx @@ -0,0 +1,168 @@ +--- +title: DatePicker +description: Single-date or range selection with a segmented date field, calendar popover, and quick actions. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Updating · [Figma](https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=127461-42132) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/DatePicker/DatePicker.tsx) + +## Usage + +Use `DatePicker` to let a reader select a single date or a date range, either by typing into a segmented field or picking from a calendar popover. It's a fully controlled component — there's no internal date state, so `selectedDate`/`onSelected` (single mode) or `startDate`/`endDate`/`onStartSelected`/`onEndSelected` (range mode) are all required. + +**Best practices:** + +- Include quick actions when readers are likely to want common or recurring dates or ranges. +- Include `disableDate` to prevent selecting dates that are unavailable or invalid in context. + +**When NOT to use:** + +- **A fixed set of date-related options** (like "This month," "Last quarter") — use [Select](/components/inputs-and-forms/select/) or [SelectDropdown](/components/inputs-and-forms/select-dropdown/) instead. + +### Anatomy + +1. **Date input(s)** — one segmented month/day/year field in single mode, two (Start date/End date) in range mode. Typing updates the calendar immediately. +2. **Calendar** — opens on click of either date input, or `Alt`+`↓` when an input is focused. Closes automatically once selection is complete. Shows 2 months by default, collapsing to 1 below the `sm` breakpoint. +3. **Today marker** — highlights the current date. +4. **Disabled dates** (optional) — marked non-selectable. In range mode, a range that overlaps a disabled date is invalid; that click is instead treated as a new Start date. +5. **Clear** (range mode only) — clears the current selection. +6. **Quick actions** (optional) — up to 3 shortcuts in the calendar footer. + +<FigmaEmbed url="https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=127461-42132" /> + +## Usage examples + +### Building a single date picker + +<StoryEmbed id="organisms-datepicker--single-date" height="26rem" /> + +### Building a range picker + +<StoryEmbed id="organisms-datepicker--range" height="26rem" /> + +### Starting with a date already selected + +Set `selectedDate` (single mode) or `startDate`/`endDate` (range mode) to a non-null value up front to open with a date already chosen, instead of starting empty: + +#### Single date + +<StoryEmbed + id="organisms-datepicker--with-initial-date-single" + height="26rem" +/> + +#### Range + +<StoryEmbed id="organisms-datepicker--with-initial-date-range" height="26rem" /> + +### Disabling specific dates + +Pass a `disableDate` function returning `true` for any date that shouldn't be selectable. Use `matchDisabledDates` to disable a fixed list, or write your own predicate for rules like "no weekends". + +#### Disabling a fixed list + +<StoryEmbed id="organisms-datepicker--match-disabled-dates" height="26rem" /> + +#### Disabling weekends + +<StoryEmbed id="organisms-datepicker--range-disabled-weekends" height="26rem" /> + +#### Disabling before today + +<StoryEmbed + id="organisms-datepicker--range-disabled-before-today" + height="26rem" +/> + +#### Disabling more than 30 days before today + +<StoryEmbed + id="organisms-datepicker--range-disabled-more-than-30-days-before-today" + height="26rem" +/> + +#### Limiting to a 30-day window from the start date + +<StoryEmbed + id="organisms-datepicker--range-30-day-window-from-start" + height="26rem" +/> + +### Composing a custom layout + +Pass `children` to skip the default layout entirely — `DatePicker` still provides context, so you can compose your own arrangement of `DatePickerInput`(s) and `DatePickerCalendar` via `useDatePicker()`: + +<StoryEmbed id="organisms-datepicker--composed-with-context" height="26rem" /> + +### Localizing + +`locale` drives segment order, month/weekday names, and date `aria-label`s via `Intl` APIs. `translations` overrides the UI strings (button labels, quick-action text, validation messages) directly. + +#### Locale + +<StoryEmbed id="organisms-datepicker--locale" height="22rem" /> + +#### Custom translations + +<StoryEmbed id="organisms-datepicker--translations" height="26rem" /> + +## Prop Reference + +### Mode + +Covered above under Patterns — [single](#building-a-single-date-picker) and [range](#building-a-range-picker). + +### Placement + +`placement="inline"` (used throughout this page) renders the calendar in normal layout flow; `placement="floating"` escapes via a portal: + +<StoryEmbed id="organisms-datepicker--floating-placement" height="26rem" /> + +### Input size + +`inputSize="small"` (the default is `"base"`) reduces the date input's padding: + +<StoryEmbed id="organisms-datepicker--range-small" height="24rem" /> + +### Quick actions + +Default (single: Yesterday/Today/Tomorrow; range: Last 7/30/90 days), disabled (`quickActions={null}`), and custom (`CalendarQuickAction[]`) are each their own story, for both modes. + +#### Single, default + +<StoryEmbed + id="organisms-datepicker--single-default-quick-actions" + height="24rem" +/> + +#### Range, default + +<StoryEmbed + id="organisms-datepicker--range-default-quick-actions" + height="26rem" +/> + +#### Single, custom + +<StoryEmbed + id="organisms-datepicker--single-custom-quick-actions" + height="24rem" +/> + +#### Range, custom + +<StoryEmbed + id="organisms-datepicker--range-custom-quick-actions" + height="26rem" +/> + +- [Single, no quick actions](https://gamut.codecademy.com/?path=/story/organisms-datepicker--single-no-quick-actions) +- [Range, no quick actions](https://gamut.codecademy.com/?path=/story/organisms-datepicker--range-no-quick-actions) + +## Accessibility considerations + +- The calendar lives in a `role="dialog"` region — pair its `id` with `DatePickerCalendar`'s `dialogId`, and the date input's `aria-controls`, if you're composing a custom layout. +- Segments use `role="spinbutton"`: arrow keys adjust a segment's value, `Left`/`Right` move between segments, and `Alt`+`↓` opens the calendar (or moves focus into the grid, if it's already open). +- `Escape` closes the popover. diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/index.md b/packages/starlight/src/content/docs/components/inputs-and-forms/index.md new file mode 100644 index 0000000000..9976b1cf96 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/index.md @@ -0,0 +1,17 @@ +--- +title: Inputs & forms +description: Form fields, form scaffolding, and the wiring that connects them to form state. +sidebar: + label: Overview +--- + +Form fields, the scaffolding around them, and the wiring that connects them to form state. + +- Fields: [Input](/components/inputs-and-forms/input/), [TextArea](/components/inputs-and-forms/text-area/), [Checkbox](/components/inputs-and-forms/checkbox/), [Radio](/components/inputs-and-forms/radio/), [Select](/components/inputs-and-forms/select/), [SelectDropdown](/components/inputs-and-forms/select-dropdown/), [DatePicker](/components/inputs-and-forms/date-picker/) +- [Form scaffolding](/components/inputs-and-forms/form/) — Form, FormGroup, FormGroupLabel, FormGroupDescription, FormRequiredText +- [ConnectedForm](/components/inputs-and-forms/connectedform/) — ConnectedForm, ConnectedFormGroup, ConnectedFormInputs, SubmitButton +- [GridForm](/components/inputs-and-forms/gridform/) + +See [Building forms](/guides/building-forms/) for a task-oriented walkthrough instead of the per-component reference. + +`Toggle` lives in [Actions](/components/actions/) instead of here — it's most effective as an immediate on/off action rather than a value collected on form submission. diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/input.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/input.mdx new file mode 100644 index 0000000000..526dd29b5f --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/input.mdx @@ -0,0 +1,129 @@ +--- +title: Input +description: A single-line text, number, or file input field. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1189%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/inputs/Input.tsx) + +## Usage + +Use `Input` to handle `text`, `number`, and `file` input types within forms. + +**Best practices:** + +- Pair it with [FormGroup](/components/inputs-and-forms/form/form-group/) for a visible, accessible label — `Input` renders no label of its own, only an `id` for a label to point at. +- A custom `icon` is replaced by the error or valid-state icon whenever `error`/`valid` is set — don't rely on a custom icon staying visible in those states. +- Set `size="small"` for a more compact field; the default is `"base"`. + +**When NOT to use:** + +- **Long-form text entry** — use [TextArea](/components/inputs-and-forms/text-area/) instead. +- **A boolean value** — use [Checkbox](/components/inputs-and-forms/checkbox/) instead. +- **One choice from a visible set of options** — use [Radio](/components/inputs-and-forms/radio/) or [Select](/components/inputs-and-forms/select/) instead. + +## Usage examples + +### Pairing with FormGroup + +Give `FormGroup` an `htmlFor` and `Input` the matching `id` for an accessible label — `FormGroup` styles the error state to match too. + +#### Default + +<StoryEmbed id="atoms-forminputs-input--form-group-default" height="8rem" /> + +#### Error + +<StoryEmbed id="atoms-forminputs-input--form-group-error" height="10rem" /> + +### Adding a custom icon + +Pass `icon` (any `gamut-icons` component) to render it inside the field: + +<StoryEmbed id="atoms-forminputs-input--custom-icon" height="6rem" /> + +### Styling an iframe as an Input + +Use styled-components' `withComponent` on Gamut's `iFrameWrapper` to give a third-party iframe `Input`'s styling — you'll still need to handle the iframe's own behavior separately: + +```tsx +import { FormGroup, Input, iFrameWrapper } from '@codecademy/gamut'; + +import { GnarlyIframe } from './constants'; + +const StyledIframe = iFrameWrapper.withComponent(GnarlyIframe); + +export const CustomFormGroup: React.FC<{ + errorMessage?: string; + disabled?: boolean; +}> = ({ errorMessage, disabled }) => ( + <FormGroup error={errorMessage} disabled={disabled}> + <Input + as={StyledIframe} + onChange={() => {}} + onBlur={() => {}} + placeholder="Placeholder" + error={!!errorMessage} + /> + </FormGroup> +); +``` + +## Prop Reference + +### Type + +#### Text + +<StoryEmbed id="atoms-forminputs-input--default" height="6rem" /> + +#### Number + +<StoryEmbed id="atoms-forminputs-input--number" height="6rem" /> + +#### File + +<StoryEmbed id="atoms-forminputs-input--file" height="6rem" /> + +### State + +#### Error + +<StoryEmbed id="atoms-forminputs-input--error" height="6rem" /> + +#### Validated + +<StoryEmbed id="atoms-forminputs-input--validated" height="6rem" /> + +#### Disabled + +<StoryEmbed id="atoms-forminputs-input--disabled" height="6rem" /> + +#### Placeholder + +<StoryEmbed id="atoms-forminputs-input--placeholder" height="6rem" /> + +### Size + +`size="small"` reduces padding for a more compact field, across every type and state: + +#### Text + +<StoryEmbed id="atoms-forminputs-input--text-small" height="6rem" /> + +#### Text, error + +<StoryEmbed id="atoms-forminputs-input--text-small-error" height="6rem" /> + +#### Number + +<StoryEmbed id="atoms-forminputs-input--number-small" height="6rem" /> + +#### File + +<StoryEmbed id="atoms-forminputs-input--file-small" height="6rem" /> + +#### Disabled + +<StoryEmbed id="atoms-forminputs-input--disabled-small" height="6rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/radio.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/radio.mdx new file mode 100644 index 0000000000..5c1d6dab25 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/radio.mdx @@ -0,0 +1,67 @@ +--- +title: Radio +description: A single choice from a mutually exclusive set of options. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1189%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/inputs/Radio.tsx) + +## Usage + +Use `Radio` for one choice from a small, visible set of mutually exclusive options, grouped with `RadioGroup`. + +**Best practices:** + +- `Radio` is a controlled component — its `checked` value must be driven by external state and `onChange`. +- `RadioGroup` forwards `name` and `onChange` to every `Radio` child, so they stay semantically linked and mutually exclusive — build groups with `RadioGroup`, not by wiring `name` on each `Radio` yourself. +- Inside [ConnectedForm](/components/inputs-and-forms/connectedform/) or [GridForm](/components/inputs-and-forms/gridform/), use `ConnectedRadioGroupInput` or the equivalent GridForm field type instead — they wire up `checked`/`onChange` for you. +- Avoid passing `id` to `Radio` — it's appended as a suffix to the input's own id, but the visible `<label>`'s `htmlFor` doesn't get that same suffix, which can break the label/input association. `htmlFor` alone is enough to identify a `Radio`. + +**When NOT to use:** + +- **A boolean value** — use [Checkbox](/components/inputs-and-forms/checkbox/) instead. +- **Many options, or options that don't need to all be visible at once** — use [Select](/components/inputs-and-forms/select/) or [SelectDropdown](/components/inputs-and-forms/select-dropdown/) instead. + +## Usage examples + +### Building a RadioGroup + +`RadioGroup` renders a plain `<div>` and clones `name`/`onChange` onto its `Radio` children — track the selected value yourself and pass it back down: + +<StoryEmbed id="atoms-forminputs-radio--radio-group-component" height="8rem" /> + +### Adding InfoTips to a group or its options + +Wrap a `RadioGroup` in a `FormGroup` with `infotip` for a group-level tip, auto-linked to the group's label via `aria-labelledby`. Individual `Radio` options can carry their own `infotip` too: + +<StoryEmbed + id="atoms-forminputs-radio--radio-group-with-info-tips" + height="10rem" +/> + +### Using a custom label + +`label` is a `ReactNode`, so it can hold more than plain text. If a `Radio`'s label includes its own `InfoTip`, pass it as `infotip` rather than composing it into `label` directly, so it isn't announced twice by screen readers: + +<StoryEmbed id="atoms-forminputs-radio--custom-label" height="6rem" /> + +## Prop Reference + +### State + +#### Checked + +<StoryEmbed id="atoms-forminputs-radio--checked" height="4rem" /> + +#### Disabled + +<StoryEmbed id="atoms-forminputs-radio--disabled" height="4rem" /> + +#### Disabled and checked + +<StoryEmbed id="atoms-forminputs-radio--disabled-checked" height="4rem" /> + +#### Error + +<StoryEmbed id="atoms-forminputs-radio--error" height="4rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/select-dropdown.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/select-dropdown.mdx new file mode 100644 index 0000000000..6fe690f9e8 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/select-dropdown.mdx @@ -0,0 +1,209 @@ +--- +title: SelectDropdown +description: A custom single/multi-select dropdown with search, grouping, rich option content, and creatable options. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1189%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/SelectDropdown/SelectDropdown.tsx) + +## Usage + +Use `SelectDropdown` when a design calls for a styled dropdown menu, searchable options, multi-select tags, creatable options, or rich option content (icons, subtitles, grouping). It's built on [react-select](https://react-select.com), which is a meaningfully larger JavaScript dependency than [Select](/components/inputs-and-forms/select/) — prefer `Select` for a simple single-select field where that extra weight isn't earning its keep. + +**Best practices:** + +- Provide an `id` (or a `name`, used as a fallback `id`) matching the `htmlFor` of the associated `FormGroupLabel`/`<label>`. +- Don't pass `isSearchable={false}` together with `isCreatable` — creating an option requires a text input, so Gamut's types reject that combination. + +**When NOT to use:** + +- **A simple single-select with plain text options** — use [Select](/components/inputs-and-forms/select/) instead. + +## Usage examples + +### Pairing with FormGroup + +#### Default + +<StoryEmbed + id="atoms-forminputs-selectdropdown--form-group-select-dropdown" + height="10rem" +/> + +#### Error + +<StoryEmbed + id="atoms-forminputs-selectdropdown--form-group-error" + height="12rem" +/> + +### Selecting more than one option + +Set `multiple` to let users pick more than one option — selected values render as removable tags, and `onChange` receives the full array of selected option objects rather than a single value. Single-select is always controlled; multi-select supports both modes: + +| Mode | Uncontrolled | Controlled | +| ------ | ------------------------------------------------------ | ------------------------------------------------------------------ | +| Single | Not supported | Pass `value` (an option value string) and update it in `onChange`. | +| Multi | Omit `value`, or pass a non-array (`undefined`, `''`). | Pass `value` as `string[]` and update it in `onChange`. | + +#### Uncontrolled + +<StoryEmbed + id="atoms-forminputs-selectdropdown--multiple-select" + height="10rem" +/> + +#### Controlled + +<StoryEmbed + id="atoms-forminputs-selectdropdown--multiple-select-controlled" + height="10rem" +/> + +### Letting users create new options + +Set `isCreatable` to show an **Add "value"** row when the input doesn't match an existing option. Selecting it fires `onCreateOption` with the typed string — you own persisting it to `options`. `onChange` still fires too, with `meta.action === 'create-option'`. + +#### Single-select + +<StoryEmbed id="atoms-forminputs-selectdropdown--creatable" height="10rem" /> + +#### Multi-select, uncontrolled + +<StoryEmbed + id="atoms-forminputs-selectdropdown--creatable-multi-uncontrolled" + height="10rem" +/> + +#### Multi-select, controlled + +<StoryEmbed + id="atoms-forminputs-selectdropdown--creatable-multi" + height="10rem" +/> + +### Validating created options + +Use `isValidNewOption` to control when the "Add" row appears (minimum length, pattern matching, dedup, a cap on item count) and `validationMessage` to replace the in-menu "No options" text with validation-specific copy: + +<StoryEmbed + id="atoms-forminputs-selectdropdown--creatable-with-validation" + height="12rem" +/> + +### Submitting without JavaScript + +Pass `inputProps.hidden` to render a native `<input type="hidden">` with the selected value(s), so the field participates in a plain HTML form submission: + +<StoryEmbed + id="atoms-forminputs-selectdropdown--custom-input-props" + height="10rem" +/> + +## Prop Reference + +### Option content + +`icon`, `subtitle`, `rightLabel`, and `abbreviation` can each be added to an option to enrich how it renders. + +#### Icon + +<StoryEmbed id="atoms-forminputs-selectdropdown--icons" height="10rem" /> + +#### Subtitle + +<StoryEmbed id="atoms-forminputs-selectdropdown--subtitles" height="10rem" /> + +#### Right label + +<StoryEmbed id="atoms-forminputs-selectdropdown--right-labels" height="10rem" /> + +#### Abbreviation + +<StoryEmbed + id="atoms-forminputs-selectdropdown--abbreviated-input" + height="10rem" +/> + +#### Disabled option + +Set `disabled` on an individual option to keep it visible but unselectable: + +<StoryEmbed + id="atoms-forminputs-selectdropdown--disabled-options" + height="10rem" +/> + +### Grouped options + +#### Dividers + +<StoryEmbed + id="atoms-forminputs-selectdropdown--group-dividers" + height="10rem" +/> + +#### Labels + +<StoryEmbed id="atoms-forminputs-selectdropdown--group-labels" height="10rem" /> + +### Layout & positioning + +`inputWidth`/`dropdownWidth` size the input and menu independently, and `menuAlignment="right"` aligns the menu to the input's right edge instead of its left. + +#### Independent widths + +<StoryEmbed + id="atoms-forminputs-selectdropdown--independent-widths" + height="10rem" +/> + +#### Menu alignment + +<StoryEmbed + id="atoms-forminputs-selectdropdown--menu-alignment-right" + height="10rem" +/> + +#### Shown options limit + +`shownOptionsLimit` controls how many options are visible before the menu scrolls — the default and maximum is `6`: + +<StoryEmbed + id="atoms-forminputs-selectdropdown--shown-options-three" + height="14rem" +/> + +#### Z-index + +`zIndex` (default `auto`) raises the dropdown above other positioned elements, like a sticky table header: + +<StoryEmbed + id="atoms-forminputs-selectdropdown--z-index-on-menu" + height="14rem" +/> + +### Size + +#### Base + +<StoryEmbed id="atoms-forminputs-selectdropdown--base" height="8rem" /> + +#### Small + +<StoryEmbed id="atoms-forminputs-selectdropdown--small" height="8rem" /> + +### State + +#### Searchable + +<StoryEmbed id="atoms-forminputs-selectdropdown--searchable" height="8rem" /> + +#### Disabled + +<StoryEmbed id="atoms-forminputs-selectdropdown--disabled" height="8rem" /> + +#### Error + +<StoryEmbed id="atoms-forminputs-selectdropdown--error" height="8rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/select.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/select.mdx new file mode 100644 index 0000000000..69f1f7b443 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/select.mdx @@ -0,0 +1,53 @@ +--- +title: Select +description: A native-backed dropdown for single selection. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1189%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/inputs/Select.tsx) + +## Usage + +Use `Select` for single selection within a group of options, backed by a native `<select>` element. + +**When NOT to use:** + +- **Rich option content, multi-select, search, or creatable options** — use [SelectDropdown](/components/inputs-and-forms/select-dropdown/) instead; `Select` only supports plain text options and native browser dropdown behavior. +- **A small, always-visible set of options** — use [Radio](/components/inputs-and-forms/radio/) instead. + +## Usage examples + +### Pairing with FormGroup + +Give `FormGroup` an `htmlFor` and `Select` the matching `id` for an accessible label. + +#### Base + +<StoryEmbed id="atoms-forminputs-select--form-group-base" height="8rem" /> + +#### Small + +<StoryEmbed id="atoms-forminputs-select--form-group-small" height="8rem" /> + +## Prop Reference + +### Size + +#### Base + +<StoryEmbed id="atoms-forminputs-select--base" height="6rem" /> + +#### Small + +<StoryEmbed id="atoms-forminputs-select--small" height="6rem" /> + +### State + +#### Disabled + +<StoryEmbed id="atoms-forminputs-select--disabled" height="6rem" /> + +#### Error + +<StoryEmbed id="atoms-forminputs-select--error" height="6rem" /> diff --git a/packages/starlight/src/content/docs/components/inputs-and-forms/text-area.mdx b/packages/starlight/src/content/docs/components/inputs-and-forms/text-area.mdx new file mode 100644 index 0000000000..2036f94153 --- /dev/null +++ b/packages/starlight/src/content/docs/components/inputs-and-forms/text-area.mdx @@ -0,0 +1,40 @@ +--- +title: TextArea +description: A multi-line text input field. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1189%3A0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/inputs/TextArea.tsx) + +## Usage + +Use `TextArea` to handle long-form text entry within forms. + +**Best practices:** + +- Pair it with [FormGroup](/components/inputs-and-forms/form/form-group/) for a visible, accessible label, the same way as [Input](/components/inputs-and-forms/input/) — `TextArea` renders no label of its own. + +**When NOT to use:** + +- **Single-line text entry** — use [Input](/components/inputs-and-forms/input/) instead. + +## Prop Reference + +### State + +#### Default + +<StoryEmbed id="atoms-forminputs-textarea--default" height="10rem" /> + +#### Disabled + +<StoryEmbed id="atoms-forminputs-textarea--disabled" height="10rem" /> + +#### Error + +<StoryEmbed id="atoms-forminputs-textarea--error" height="10rem" /> + +#### Placeholder + +<StoryEmbed id="atoms-forminputs-textarea--placeholder" height="10rem" /> diff --git a/packages/starlight/src/content/docs/components/media-and-assets/Animations/expand-in-collapse-out.mdx b/packages/starlight/src/content/docs/components/media-and-assets/Animations/expand-in-collapse-out.mdx new file mode 100644 index 0000000000..c8ac32a18e --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/Animations/expand-in-collapse-out.mdx @@ -0,0 +1,26 @@ +--- +title: ExpandInCollapseOut +description: Animates a region's height open/closed. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Animation/ExpandInCollapseOut.tsx) + +## Usage + +Wrap `ExpandInCollapseOut` around the content to animate, inside an `AnimatePresence`, and conditionally render it from your own state — mounting animates it open, unmounting animates it closed. It has no props of its own beyond `children`. + +**Best practices:** + +- The animation is triggered by mounting and unmounting the component, not by a prop change — conditionally render it with `&&` or a ternary, the same way you would any other conditional JSX. +- It's a container for the content being expanded, not the trigger itself — pair it with a separate button or control that toggles the state driving it. + +**When NOT to use:** + +- **Rotating an icon, like a chevron, to indicate expanded or collapsed state** — use [Rotation](/components/media-and-assets/animations/rotation/) instead. +- **Content that should fade and slide, rather than change height** — use [FadeInSlideOut](/components/media-and-assets/animations/fade-in-slide-out/) instead. + +## Playground + +<StoryEmbed id="atoms-animations-expandincollapseout--default" height="12rem" /> diff --git a/packages/starlight/src/content/docs/components/media-and-assets/Animations/fade-in-slide-out.mdx b/packages/starlight/src/content/docs/components/media-and-assets/Animations/fade-in-slide-out.mdx new file mode 100644 index 0000000000..40a7379873 --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/Animations/fade-in-slide-out.mdx @@ -0,0 +1,25 @@ +--- +title: FadeInSlideOut +description: Fades and slides content in and out. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Animation/FadeInSlideOut.tsx) + +## Usage + +Wrap `FadeInSlideOut` around the content to animate, inside an `AnimatePresence`, and conditionally render it from your own state — the same mount/unmount-triggered pattern as [ExpandInCollapseOut](/components/media-and-assets/animations/expand-in-collapse-out/). It has no props of its own beyond `children`. + +**Best practices:** + +- Despite the name, it only fades in on entry — the slide happens only on exit, alongside a fade and a height collapse. +- The animation is triggered by mounting and unmounting, not by a prop change — conditionally render it with `&&` or a ternary. + +**When NOT to use:** + +- **A region that should animate by height alone, without fading or sliding** — use [ExpandInCollapseOut](/components/media-and-assets/animations/expand-in-collapse-out/) instead. + +## Playground + +<StoryEmbed id="atoms-animations-fadeinslideout--default" height="12rem" /> diff --git a/packages/starlight/src/content/docs/components/media-and-assets/Animations/index.md b/packages/starlight/src/content/docs/components/media-and-assets/Animations/index.md new file mode 100644 index 0000000000..b1c1a2027a --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/Animations/index.md @@ -0,0 +1,14 @@ +--- +title: Animations +description: Small motion primitives used to animate other components. +sidebar: + label: Overview +--- + +Three small motion primitives, all built on [Framer Motion](https://motion.dev). Each is a container for the content being animated, not the click target of whatever triggers it. + +- [ExpandInCollapseOut](/components/media-and-assets/animations/expand-in-collapse-out/) — animates a region's height open and closed; triggered by mounting/unmounting. +- [FadeInSlideOut](/components/media-and-assets/animations/fade-in-slide-out/) — fades in, and fades and slides out; also triggered by mounting/unmounting. +- [Rotation](/components/media-and-assets/animations/rotation/) — rotates its children by a set number of degrees; driven by a `rotated` boolean prop, not by mounting or unmounting. + +All three use Gamut's standard timing scale (`fast`: 150ms, `medium`: 200ms) rather than one-off durations. diff --git a/packages/starlight/src/content/docs/components/media-and-assets/Animations/rotation.mdx b/packages/starlight/src/content/docs/components/media-and-assets/Animations/rotation.mdx new file mode 100644 index 0000000000..5d93656deb --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/Animations/rotation.mdx @@ -0,0 +1,26 @@ +--- +title: Rotation +description: Rotates its children, e.g. a chevron icon on expand/collapse. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Animation/Rotation.tsx) + +## Usage + +Wrap `Rotation` around an icon or other small element, and toggle `rotated` from your own state. Unlike [ExpandInCollapseOut](/components/media-and-assets/animations/expand-in-collapse-out/) and [FadeInSlideOut](/components/media-and-assets/animations/fade-in-slide-out/), it's driven by a prop change, not by mounting or unmounting. + +**Best practices:** + +- A common use is rotating a chevron icon to indicate that a nearby element can expand and collapse. +- `degrees` (default `180`) sets how far it rotates — keep it under 360. +- `height`/`width` default to `16`, to match a mini icon. Override them if wrapping something larger. + +**When NOT to use:** + +- **Animating a region's height, rather than an icon's rotation** — use [ExpandInCollapseOut](/components/media-and-assets/animations/expand-in-collapse-out/) instead. + +## Playground + +<StoryEmbed id="atoms-animations-rotation--default" height="10rem" /> diff --git a/packages/starlight/src/content/docs/components/media-and-assets/Icons/index.md b/packages/starlight/src/content/docs/components/media-and-assets/Icons/index.md new file mode 100644 index 0000000000..f303f8bd48 --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/Icons/index.md @@ -0,0 +1,17 @@ +--- +title: Icons +description: Gamut's icon set, at two sizes. +sidebar: + label: Overview +--- + +- [Mini](/components/media-and-assets/icons/mini/) — 36 icons at a 16×16 scale, for buttons and inline text. +- [Regular](/components/media-and-assets/icons/regular/) — 335 icons at a 24×24 scale, categorized into Interface, Learning environment, Vendor, and Skills. + +Both sets share one import path and one accessibility contract: + +```tsx +import { AlertIcon, MiniStarIcon } from '@codecademy/gamut-icons'; +``` + +Every icon is either `aria-hidden` (the default, since an icon is usually paired with visible text that already conveys its meaning) or explicitly `aria-hidden={false}` with a required `aria-label` — Gamut's types enforce one or the other, never neither. diff --git a/packages/starlight/src/content/docs/components/media-and-assets/Icons/mini.mdx b/packages/starlight/src/content/docs/components/media-and-assets/Icons/mini.mdx new file mode 100644 index 0000000000..98bc692bd0 --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/Icons/mini.mdx @@ -0,0 +1,38 @@ +--- +title: Mini icons +description: The small icon set, used inside buttons and inline text. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1883-1795) · [Source](https://github.com/Codecademy/gamut/tree/main/packages/gamut-icons/src/svg/mini) + +## Usage + +Import a mini icon by name — every mini icon's component name is prefixed with `Mini`: + +```tsx +import { MiniStarIcon } from '@codecademy/gamut-icons'; + +<MiniStarIcon />; +``` + +**Best practices:** + +- Reach for mini icons inside buttons, inline text, and other small-resolution contexts — they're bolder and simpler than [Regular](/components/media-and-assets/icons/regular/) icons, designed to stay legible at 16×16. +- Pass `size` to scale an icon uniformly (sets width and height together); standard layout, color, space, and border system props cover everything else. +- Leave `aria-hidden` at its default when the icon sits next to visible text that already conveys its meaning. Set `aria-hidden={false}` with a required `aria-label` only when the icon is the sole conveyor of meaning. + +**When NOT to use:** + +- **Larger or standalone icon usage** — use [Regular](/components/media-and-assets/icons/regular/) icons instead. + +## Prop Reference + +### All icons + +<StoryEmbed id="atoms-icons-mini--all-mini-icons" height="30rem" /> + +### Playground + +<StoryEmbed id="atoms-icons-mini--default" height="8rem" /> diff --git a/packages/starlight/src/content/docs/components/media-and-assets/Icons/regular.mdx b/packages/starlight/src/content/docs/components/media-and-assets/Icons/regular.mdx new file mode 100644 index 0000000000..dc9b933ead --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/Icons/regular.mdx @@ -0,0 +1,60 @@ +--- +title: Regular icons +description: The full-size icon set. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1883-1795) · [Source](https://github.com/Codecademy/gamut/tree/main/packages/gamut-icons/src/svg/regular) + +## Usage + +Import a regular icon by name directly — no prefix: + +```tsx +import { AlertIcon } from '@codecademy/gamut-icons'; + +<AlertIcon />; +``` + +**Best practices:** + +- Same `size`/system-prop and `aria-hidden` contract as [Mini](/components/media-and-assets/icons/mini/) icons — see there for the accessibility rule. +- Use `size` to scale an icon uniformly; most regular usage renders larger than the 16px component fallback. + +**When NOT to use:** + +- **Small, inline, or button-embedded icon usage** — use [Mini](/components/media-and-assets/icons/mini/) icons instead; they're bolder and hold up better at small sizes. + +## Prop Reference + +### Interface + +General user-interface icons with affordances. + +<StoryEmbed id="atoms-icons-regular--regular-interface-icons" height="30rem" /> + +### Learning environment + +Icons with specific meanings relative to course content. + +<StoryEmbed + id="atoms-icons-regular--regular-learning-environment-icons" + height="20rem" +/> + +### Vendor + +Vendor-specific icons. + +<StoryEmbed id="atoms-icons-regular--regular-vendor-icons" height="10rem" /> + +### Skills + +Icons for the languages and subjects Codecademy teaches. + +<StoryEmbed id="atoms-icons-regular--regular-skill-icons" height="20rem" /> + +### Playground + +<StoryEmbed id="atoms-icons-regular--default" height="10rem" /> diff --git a/packages/starlight/src/content/docs/components/media-and-assets/illustrations.mdx b/packages/starlight/src/content/docs/components/media-and-assets/illustrations.mdx new file mode 100644 index 0000000000..96d7c0c507 --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/illustrations.mdx @@ -0,0 +1,37 @@ +--- +title: Illustrations +description: Gamut's illustration asset library. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/tree/main/packages/gamut-illustrations/src) + +## Usage + +Import an illustration by name from `@codecademy/gamut-illustrations`: + +```tsx +import { NumberBlocks } from '@codecademy/gamut-illustrations'; + +<NumberBlocks width="128px" />; +``` + +**Best practices:** + +- Illustrations are static, multi-color SVGs with their colors baked in — they don't take a `color` prop, only `width`, `height`, `className`, and `aria-hidden`. Reach for [Patterns](/components/media-and-assets/patterns/) instead if a graphic needs to inherit surrounding text color. +- Set `width` or `height` (or both) to size an illustration; whichever one is omitted is calculated from the other, or from CSS. + +**When NOT to use:** + +- **A decorative, recolorable background fill layered behind other content** — use [Patterns](/components/media-and-assets/patterns/) instead. + +## Prop Reference + +### All illustrations + +<StoryEmbed id="atoms-illustrations--all-illustrations" height="40rem" /> + +### Playground + +<StoryEmbed id="atoms-illustrations--default" height="16rem" /> diff --git a/packages/starlight/src/content/docs/components/media-and-assets/index.md b/packages/starlight/src/content/docs/components/media-and-assets/index.md new file mode 100644 index 0000000000..8a765f4c94 --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/index.md @@ -0,0 +1,14 @@ +--- +title: Media & assets +description: Icons, illustrations, patterns, animation helpers, and video. +sidebar: + label: Overview +--- + +Icons, illustrations, patterns, animation helpers, and video. + +- [Icons](/components/media-and-assets/icons/) — Regular, Mini +- [Illustrations](/components/media-and-assets/illustrations/) +- [Patterns](/components/media-and-assets/patterns/) +- [Animations](/components/media-and-assets/animations/) — ExpandInCollapseOut, FadeInSlideOut, Rotation +- [Video](/components/media-and-assets/video/) diff --git a/packages/starlight/src/content/docs/components/media-and-assets/patterns.mdx b/packages/starlight/src/content/docs/components/media-and-assets/patterns.mdx new file mode 100644 index 0000000000..7df822941c --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/patterns.mdx @@ -0,0 +1,38 @@ +--- +title: Patterns +description: Decorative background pattern components. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1124%3A1569) · [Source](https://github.com/Codecademy/gamut/tree/main/packages/gamut-patterns/src/svg) + +## Usage + +Import a pattern by name from `@codecademy/gamut-patterns`: + +```tsx +import { DotLoose } from '@codecademy/gamut-patterns'; + +<DotLoose width="128px" />; +``` + +**Best practices:** + +- Patterns fill with `currentColor` — they have no `color` prop of their own. Set `color` on a wrapping element to recolor one. +- Patterns accept the standard layout, positioning, and space system props, plus native SVG attributes. +- Always layer a pattern behind real content with `position="absolute"`, and never use it to convey information itself. [ProgressBar](/components/status/progress-bar/)'s `pattern` prop is a good example: its fill percentage is a separate element, with the pattern rendered as pure texture behind it. + +**When NOT to use:** + +- **A static, standalone graphic with its own baked-in colors** — use [Illustrations](/components/media-and-assets/illustrations/) instead. + +## Prop Reference + +### All patterns + +<StoryEmbed id="atoms-patterns--all-patterns" height="30rem" /> + +### Playground + +<StoryEmbed id="atoms-patterns--default" height="12rem" /> diff --git a/packages/starlight/src/content/docs/components/media-and-assets/video.mdx b/packages/starlight/src/content/docs/components/media-and-assets/video.mdx new file mode 100644 index 0000000000..a6cc99cee1 --- /dev/null +++ b/packages/starlight/src/content/docs/components/media-and-assets/video.mdx @@ -0,0 +1,51 @@ +--- +title: Video +description: A video player supporting HLS, DASH, YouTube, and Vimeo, with built-in accessibility features. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Updating · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=31226-41393) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Video/index.tsx) + +## Usage + +Use `Video` to play a direct video file or stream (MP4, HLS, DASH) or embed a YouTube/Vimeo URL — the same component handles both, and picks the right underlying player automatically. A YouTube or Vimeo URL with no caption tracks renders through a lighter embedded player; everything else (direct sources, or a YouTube/Vimeo URL that does have caption tracks) renders through the full-featured player with captions, chapters, and thumbnail support. + +**Best practices:** + +- Always provide `videoTitle` for accessibility. +- Include captions (`textTracks`) when they're available. +- Provide `placeholderImage` for a better initial-loading experience. +- Pass `translations` to override the default layout's UI text for i18n. +- Check that a video's sharing/embed settings actually allow it before going live with a YouTube or Vimeo URL — some videos can't be embedded. + +**When NOT to use:** + +- **Markdown content that already links to a video** — [Markdown](/components/data-display/markdown/)'s own `<video>` and YouTube/Vimeo `<iframe>` overrides render into `Video` automatically; don't wrap it a second time. + +## Usage examples + +### Embedding YouTube or Vimeo + +Pass a YouTube or Vimeo URL as `videoUrl`. + +#### YouTube + +<StoryEmbed id="molecules-video--youtube" height="16rem" /> + +#### Vimeo + +<StoryEmbed id="molecules-video--vimeo" height="16rem" /> + +### Adding captions, chapters, and thumbnails + +Pass `textTracks` (subtitles, captions, or chapters) and `thumbnails` for scrubber preview images — either one switches an otherwise lightly-embedded YouTube/Vimeo URL to the full-featured player too: + +<StoryEmbed + id="molecules-video--video-with-tracks-and-thumbnails" + height="20rem" +/> + +## Playground + +<StoryEmbed id="molecules-video--default" height="16rem" /> diff --git a/packages/starlight/src/content/docs/components/navigation/anchor.mdx b/packages/starlight/src/content/docs/components/navigation/anchor.mdx new file mode 100644 index 0000000000..c177d513fc --- /dev/null +++ b/packages/starlight/src/content/docs/components/navigation/anchor.mdx @@ -0,0 +1,70 @@ +--- +title: Anchor +description: A clickable text element that navigates to another page, resource, or location on the same page. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=993-0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Anchor) + +## Usage + +Use `Anchor` to navigate to another page, a resource, or a location on the same page. + +**Best practices:** + +- Include a leading or trailing icon to clarify the link's destination, or to further distinguish it from surrounding text. +- Consider `target="_blank"` when navigating away would interrupt a task the reader is in the middle of. +- `Anchor` is polymorphic: pass `href` to render a real `<a>`; omit it and pass `onClick` instead to render a real `<button>` styled the same way. Use the button form sparingly — only when a control needs to look like a link but can't navigate directly via `href`. + +**When NOT to use:** + +- **Performing an action** — use a [Button](/components/actions/buttons/) instead. + +### Anatomy + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=993-0" /> + +1. **Leading icon** (optional) — clarifies or reinforces the link's destination. +2. **Label** — states where the link goes. Use sentence case and aim for 3–5 words. +3. **Trailing icon** (optional) — reinforces forward movement to a new page. For links to external pages, use `MiniOpenIcon`. + +## Usage examples + +### Adding icons + +Pass `icon` and `iconPosition` (`'left' | 'right'`) to place an icon relative to the label: + +<StoryEmbed id="typography-anchor--icon-anchor" height="6rem" /> + +### Icons within running text + +An `Anchor` can sit inline inside a `Text` block, icon and all, without breaking the surrounding paragraph's flow: + +<StoryEmbed id="typography-anchor--icon-anchor-example" height="8rem" /> + +### Rendering as a button + +Omit `href` and pass `onClick` to render `Anchor` as a real `<button>` — useful when a link-styled control needs button semantics, for example triggering navigation programmatically instead of via a real `href`: + +<StoryEmbed id="typography-anchor--polymorphic-anchor" height="6rem" /> + +## Prop Reference + +### Variant + +- `inline` (default) — for use within paragraphs; styled with color and an underline so it stays visible in text-heavy content. +- `interface` — for menus, or anywhere the surrounding context already makes it clear that the text is clickable. +- `standard` — for links outside a paragraph or menu. +- `standard-secondary` — like `standard`, at a lower level of visual prominence, for secondary links where `standard` would feel too bold. + +#### Without an icon + +<StoryEmbed id="typography-anchor--modes" height="20rem" /> + +#### With an icon + +Every variant also renders with an icon, responsive to color mode: + +<StoryEmbed id="typography-anchor--icon-modes" height="20rem" /> diff --git a/packages/starlight/src/content/docs/components/navigation/breadcrumbs.mdx b/packages/starlight/src/content/docs/components/navigation/breadcrumbs.mdx new file mode 100644 index 0000000000..6908cf3d50 --- /dev/null +++ b/packages/starlight/src/content/docs/components/navigation/breadcrumbs.mdx @@ -0,0 +1,37 @@ +--- +title: Breadcrumbs +description: A trail of links showing the current page's position in a hierarchy. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=30186-40570&m=dev) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Breadcrumbs/index.tsx) + +## Usage + +Use `Breadcrumbs` to help a reader understand where the current page sits in a site's hierarchy and to navigate back to any level above it. + +Pass a `crumbs` array of `{ title, href? }` objects, in order from the top of the hierarchy down. The last crumb is treated as the current page: if it has no `href`, it renders as bold, non-clickable text with `aria-current="location"` instead of a link. + +**Best practices:** + +- Reserve `Breadcrumbs` for pages that sit at least two levels deep in a hierarchy — a single level back to a parent doesn't need a trail. +- Give every crumb a unique `title` within the same array; titles are used as list keys. +- Attach arbitrary data to a crumb with `payload`, then read it back in `onClick` — useful for analytics or intercepting navigation without changing the link itself. + +**When NOT to use:** + +- **A single "back" link** — a plain [Anchor](/components/navigation/anchor/) is enough when there's only one level to go back to. +- **Switching between sibling views on the same page** — use [Tabs](/components/navigation/tabs/) instead. + +## Usage examples + +### Passing a payload to onClick + +Attach arbitrary data to a clickable crumb with `payload`, then read it back in `onClick` — for example to intercept navigation or fire an analytics event instead of a plain page load: + +<StoryEmbed id="molecules-breadcrumbs--payload" height="6rem" /> + +## Playground + +<StoryEmbed id="molecules-breadcrumbs--default" height="6rem" /> diff --git a/packages/starlight/src/content/docs/components/navigation/index.md b/packages/starlight/src/content/docs/components/navigation/index.md new file mode 100644 index 0000000000..8281a364d9 --- /dev/null +++ b/packages/starlight/src/content/docs/components/navigation/index.md @@ -0,0 +1,16 @@ +--- +title: Navigation +description: Components that move a user between pages or views. +sidebar: + label: Overview +--- + +Components that move a user between pages or views. + +- [Anchor](/components/navigation/anchor/) — functionally navigation despite its typography implementation +- [Breadcrumbs](/components/navigation/breadcrumbs/) +- [Pagination](/components/navigation/pagination/) +- [Tabs](/components/navigation/tabs/) +- [SkipToContent](/components/navigation/skip-to-content/) + +For an action-list menu (not page navigation), see [Menu](/components/actions/menu/) in Actions. diff --git a/packages/starlight/src/content/docs/components/navigation/pagination.mdx b/packages/starlight/src/content/docs/components/navigation/pagination.mdx new file mode 100644 index 0000000000..363e9a395e --- /dev/null +++ b/packages/starlight/src/content/docs/components/navigation/pagination.mdx @@ -0,0 +1,66 @@ +--- +title: Pagination +description: Controls for moving between pages of a paginated list. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=8639%3A7128) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Pagination/index.tsx) + +## Usage + +Use `Pagination` to navigate between content split across multiple pages. + +**Best practices:** + +- `type` auto-resolves from `totalPages`: `'basic'` under 10 pages, `'includeSkipToButtons'` at 10 or more. Only set it explicitly for an edge case, like forcing skip-to buttons under 10 pages. +- Pass `pageNumber` to make `Pagination` controlled; omit it and `Pagination` manages the current page itself. +- When pages are separate URLs, set `isNavigation` and make `onChange` perform real navigation — otherwise browser back/forward and deep links to a specific page break. +- `variant` (`'stroke'` default, `'text'`) roughly mirrors Gamut's Button styles. + +**When NOT to use:** + +- **Infinite scroll or a "load more" pattern** — `Pagination` is page-index based, not cursor based. +- **Progress through a multi-step form or wizard** — its semantics are page navigation (`aria-current="page"`), not step completion. + +## Usage examples + +### Pagination as real navigation + +Set `isNavigation` when each page is its own URL, and perform the actual navigation inside `onChange` — this renders `Pagination` as a `<nav>` with real links instead of a plain button group: + +<StoryEmbed id="molecules-pagination--navigation" height="8rem" /> + +### Building controlled pagination + +Pass `pageNumber` to drive the current page from your own state, for example to keep it in sync with a query string. `Pagination` calls `onChange` whenever `pageNumber` changes, the same as it would for a click: + +<StoryEmbed id="molecules-pagination--controlled" height="12rem" /> + +## Prop Reference + +### Type + +`type` auto-resolves from `totalPages`: `basic` under 10 pages, `includeSkipToButtons` at 10 or more. + +#### Basic + +Page-number buttons plus previous/next. + +<StoryEmbed id="molecules-pagination--default" height="6rem" /> + +#### Include skip to buttons + +Adds first/last-page jump buttons and ellipsis buttons that skip ahead by `chapterSize`. + +<StoryEmbed id="molecules-pagination--include-skip-to-buttons" height="6rem" /> + +### Variant + +#### Stroke + +<StoryEmbed id="molecules-pagination--stroke" height="6rem" /> + +#### Text + +<StoryEmbed id="molecules-pagination--text" height="6rem" /> diff --git a/packages/starlight/src/content/docs/components/navigation/skip-to-content.mdx b/packages/starlight/src/content/docs/components/navigation/skip-to-content.mdx new file mode 100644 index 0000000000..9df4908713 --- /dev/null +++ b/packages/starlight/src/content/docs/components/navigation/skip-to-content.mdx @@ -0,0 +1,43 @@ +--- +title: SkipToContent +description: A keyboard-only link that skips repeated navigation. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/SkipToContent) + +## Usage + +Use `SkipToContent` to add a link that's hidden until focused (for example, by pressing <kbd>Tab</kbd>), letting keyboard users jump straight past repeated navigation to the page's main content. + +**Best practices:** + +- Pair every `SkipToContent` with a `SkipToContentTarget` sharing the same id, passed to `SkipToContent` via `contentId`. `SkipToContentTarget` defaults to `tabIndex={-1}`, so it can receive focus programmatically without joining the normal tab order. +- Place `SkipToContent` at the very top of the page, before the navigation it lets readers skip past. + +**When NOT to use:** + +- **A page without significant repeated navigation to skip past** — there's nothing for it to skip. +- **More than once per page** — a single skip link is enough; more than one can confuse tab order. + +## Usage examples + +### Pairing with a target + +`SkipToContent` and `SkipToContentTarget` share an id via `contentId`. Clicking, or activating with the keyboard, focuses and scrolls to the target: + +```tsx +<div> + <SkipToContent contentId="main-content" /> + <a href="/">Link 1</a> + <a href="/">Link 2</a> + <a href="/">Link 3</a> +</div> +<SkipToContentTarget id="main-content" /> +<FillButton>I should have focus after skip to content</FillButton> +``` + +## Playground + +<StoryEmbed id="atoms-skiptocontent--default" height="6rem" /> diff --git a/packages/starlight/src/content/docs/components/navigation/tabs.mdx b/packages/starlight/src/content/docs/components/navigation/tabs.mdx new file mode 100644 index 0000000000..c8ab703dcf --- /dev/null +++ b/packages/starlight/src/content/docs/components/navigation/tabs.mdx @@ -0,0 +1,83 @@ +--- +title: Tabs +description: Switches between sibling views without navigating away from the page. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=31226-41459) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Tabs) + +## Usage + +Use `Tabs` when there are multiple sections of equal importance, sharing the same structure, and only one should be visible at a time. + +**Best practices:** + +- `Tabs` is uncontrolled by default — it picks the first tab if no `defaultSelectedKey` is given. Pass `selectedKey` and `onSelectionChange` to make it controlled instead. +- If tracking selection via `onSelectionChange` on an otherwise uncontrolled `Tabs`, also pass `defaultSelectedKey` — without it, tracking fires twice. +- For link-based navigation between separate URLs rather than switching in-page content, use `TabNav`/`TabNavLink` instead of `Tabs`/`Tab`/`TabPanel`. +- `TabPanel` unmounts from the DOM while inactive by default; pass `shouldForceMount` to keep it mounted (still visually hidden) when a panel needs to preserve its state or stay in the DOM for other reasons. + +**When NOT to use:** + +- **Content that needs to be visible simultaneously** — tabs hide every section but one; don't use them for content a reader needs to compare or see all at once. +- **Only one section, with nothing to switch to** — a single tab has nothing to organize. + +### Anatomy + +1. **TabList** — contains the row of `Tab` triggers. +2. **Tab** — a single tab trigger; its `id` matches its corresponding `TabPanel`'s `id`. +3. **TabPanels** — wraps the content for every tab. +4. **TabPanel** — the content shown when its matching `Tab` is selected. + +For link-based navigation, swap `TabList`/`Tab` for a **TabNav** (a `<nav>` wrapping `TabNavLink`s, each with its own `href`) — see Patterns below. + +## Usage examples + +### Building controlled tabs + +Pass `selectedKey` and `onSelectionChange` to drive the active tab from your own state — the newly selected tab's `id` is passed to the callback: + +<StoryEmbed id="molecules-tabs--controlled" height="14rem" /> + +### Adding a status badge to a tab + +Compose a `Badge` into a `Tab`'s children to flag new or updated content: + +<StoryEmbed id="molecules-tabs--with-badge" height="8rem" /> + +### Tab-styled link navigation + +Swap `TabList`/`Tab` for `TabNav`/`TabNavLink` when tabs should navigate to a new URL instead of switching in-page content. Give `TabNav` its own `aria-label` to distinguish it from other navigation on the page: + +<StoryEmbed id="molecules-tabs--tabs-nav" height="8rem" /> + +### Tabs with interactive content + +When a `TabPanel` contains interactive content, like a button or a link, tabbing into the panel focuses that content directly instead of the whole panel: + +<StoryEmbed id="molecules-tabs--interactive-content" height="12rem" /> + +### Keeping inactive panels mounted + +Pass `shouldForceMount` to a `TabPanel` to keep it in the DOM, still visually hidden, while inactive — useful for preserving a panel's internal state across tab switches: + +<StoryEmbed id="molecules-tabs--force-mount" height="12rem" /> + +## Prop Reference + +### Variant + +#### Standard + +<StoryEmbed id="molecules-tabs--default" height="8rem" /> + +#### Block + +<StoryEmbed id="molecules-tabs--block-variant" height="8rem" /> + +### Disabled + +Disable an individual tab with `isDisabled` on the `Tab`, or disable several at once with `disabledKeys` on `Tabs`: + +<StoryEmbed id="molecules-tabs--disabled" height="8rem" /> diff --git a/packages/starlight/src/content/docs/components/overlays/dialog.mdx b/packages/starlight/src/content/docs/components/overlays/dialog.mdx new file mode 100644 index 0000000000..641c7c427c --- /dev/null +++ b/packages/starlight/src/content/docs/components/overlays/dialog.mdx @@ -0,0 +1,70 @@ +--- +title: Dialog +description: A structured, single-view confirmation prompt with binary options. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Updating · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=2449%3A3770) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Modals/Dialog.tsx) + +## Usage + +Use `Dialog` for a structured confirmation prompt with two options — for example, confirming a destructive action before it happens. + +**Best practices:** + +- Keep the message to 1–2 lines, write the title as a clear question, and keep both button labels distinct and specific about what each one does. +- Use `Dialog` sparingly — of Gamut's overlays, it's the most disruptive. Showing more than one at a time overwhelms the reader. +- Both `confirmCta` and `cancelCta` call `onRequestClose` automatically before their own `onClick` — you don't need to close the Dialog yourself. +- `size` accepts `'small'` (default), `'medium'`, or `'large'` — unlike [Modal](/components/overlays/modal/), there's no `fluid` option. + +**When NOT to use:** + +- **A general-purpose titled panel, or a multi-step flow** — use [Modal](/components/overlays/modal/) instead; `Dialog` is deliberately more restrictive: a single view, no `views`, no per-CTA `disabled`. +- **A bare overlay with no title or built-in buttons** — use [Overlay](/components/overlays/overlay/) directly instead. + +### Anatomy + +1. **Title** (required) — rendered as `h2`. +2. **Close button** — the same `closeButtonProps` shape as [Modal](/components/overlays/modal/#customizing-the-close-button); its tooltip defaults to "Close dialog". +3. **Content** — `children`. +4. **Cancel button** (optional, `cancelCta`) and **Confirm button** (required, `confirmCta`) — laid out side by side beneath the content. + +## Usage examples + +### Confirming a destructive action + +Pass `variant="danger"` for a destructive confirm action, paired with a `cancelCta` as the safe way out: + +<StoryEmbed id="molecules-modals-dialog--danger" height="12rem" /> + +### Customizing the close button + +The same `closeButtonProps` shape as [Modal](/components/overlays/modal/#customizing-the-close-button): + +<StoryEmbed + id="molecules-modals-dialog--close-button-customization" + height="14rem" +/> + +### Replacing the close button entirely + +<StoryEmbed id="molecules-modals-dialog--custom-close" height="14rem" /> + +### Managing focus + +<StoryEmbed id="molecules-modals-dialog--focus-management" height="14rem" /> + +### Responding to ColorMode + +<StoryEmbed id="molecules-modals-dialog--dark-mode" height="12rem" /> + +## Prop Reference + +### Variant + +#### Primary (default) + +<StoryEmbed id="molecules-modals-dialog--default" height="12rem" /> + +`danger` is covered above, under [confirming a destructive action](#confirming-a-destructive-action). diff --git a/packages/starlight/src/content/docs/components/overlays/flyout.mdx b/packages/starlight/src/content/docs/components/overlays/flyout.mdx new file mode 100644 index 0000000000..845e9ff50d --- /dev/null +++ b/packages/starlight/src/content/docs/components/overlays/flyout.mdx @@ -0,0 +1,33 @@ +--- +title: Flyout +description: A panel that slides in and floats above the page. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=30420-40034) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Flyout/index.tsx) + +## Usage + +Use `Flyout` for a general-purpose panel that slides in from the edge of the screen and floats above the rest of the page — for example, supplementary content triggered from a button. + +**Best practices:** + +- `Flyout` is a controlled component — drive `expanded` from your own state and close it in `onClose`. +- Give whatever toggles the flyout an `aria-expanded` attribute matching `expanded`, so its state is announced correctly. +- `Flyout` is built from [Overlay](/components/overlays/overlay/) (backdrop, focus trap, outside-click/Escape dismissal) and [Drawer](/components/containers/drawer/) (the slide animation and responsive width) — those are always on for `Flyout` and aren't individually configurable through its own props. + +**When NOT to use:** + +- **A blocking overlay for a focused task, centered on the page** — use [Modal](/components/overlays/modal/) instead. +- **A panel that collapses within page flow instead of floating above it** — use [Drawer](/components/containers/drawer/) directly instead. + +### Anatomy + +1. **Title** — a top-left `h1`, set via `title`. +2. **Close button** — always rendered; dismisses the flyout, with its accessible label coming from `closeLabel`. +3. **Content** — `children`, rendered below the title/close-button header row. + +## Playground + +<StoryEmbed id="molecules-flyout--default" height="20rem" /> diff --git a/packages/starlight/src/content/docs/components/overlays/index.md b/packages/starlight/src/content/docs/components/overlays/index.md new file mode 100644 index 0000000000..7a0dd1e827 --- /dev/null +++ b/packages/starlight/src/content/docs/components/overlays/index.md @@ -0,0 +1,14 @@ +--- +title: Overlays +description: Things a user explicitly opens and dismisses, floating above the page. +sidebar: + label: Overview +--- + +Things a user explicitly opens and dismisses, floating above the rest of the page. + +- [Modal](/components/overlays/modal/), [Dialog](/components/overlays/dialog/), [Overlay](/components/overlays/overlay/) +- [Popover](/components/overlays/popover/), [PopoverContainer](/components/overlays/popover-container/) +- [Flyout](/components/overlays/flyout/) + +Flyout lives here because it floats above the page, unlike [Drawer](/components/containers/drawer/), which collapses within page flow — see Containers for that. Tooltips and other explanatory hovercards live in [Tips](/components/feedback/tips/) under Feedback, not here. diff --git a/packages/starlight/src/content/docs/components/overlays/modal.mdx b/packages/starlight/src/content/docs/components/overlays/modal.mdx new file mode 100644 index 0000000000..dac9cfc508 --- /dev/null +++ b/packages/starlight/src/content/docs/components/overlays/modal.mdx @@ -0,0 +1,118 @@ +--- +title: Modal +description: A blocking, centered overlay for focused tasks. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=2449%3A3770) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Modals/Modal.tsx) + +## Usage + +Use `Modal` for a titled dialog that sits on top of a full-screen overlay, for a task that needs the reader's focused attention. + +**Best practices:** + +- Choose `size` (`small`, `medium`, `large`, or `fluid` — the default) to fit the content. `fluid` sizes to the content itself; the other three are fixed widths with a matching minimum height. +- Use `headingLevel="h1"` only if the Modal takes up the entire page; otherwise leave it at the default `h2`. +- Built on [Overlay](/components/overlays/overlay/) — `clickOutsideCloses`, `escapeCloses`, `shroud`, and `zIndex` all pass straight through to it. + +**When NOT to use:** + +- **A structured yes/no confirmation prompt** — use [Dialog](/components/overlays/dialog/) instead; it's a more constrained, purpose-built version of this pattern. +- **A bare overlay with no title or built-in buttons** — use [Overlay](/components/overlays/overlay/) directly instead. + +### Anatomy + +1. **Title** (optional) — rendered at `headingLevel` (default `h2`). +2. **Close button** (optional) — dismisses the Modal; its tooltip text defaults to "Close modal", and can be hidden via `closeButtonProps.hidden`. +3. **Image** (optional, single-view only) — a full-width, pre-sized illustration or photo, with a fixed height per `size`. +4. **Content** — `children` in single-view mode, or `views` in multi-view mode. + +## Usage examples + +### Customizing the close button + +Configure the close button's tooltip, alignment, disabled state, and ref through `closeButtonProps` — these only apply when the button isn't hidden: + +<StoryEmbed + id="molecules-modals-modal--close-button-customization" + height="16rem" +/> + +### Replacing the close button entirely + +Hide the built-in close button with `closeButtonProps={{ hidden: true }}` and provide your own control. Outside-click and Escape still work unless you disable those too: + +<StoryEmbed id="molecules-modals-modal--custom-close" height="16rem" /> + +### Closing on outside click or Escape + +Both are enabled by default, via `Overlay` underneath — set `clickOutsideCloses={false}` or `escapeCloses={false}` to turn either off: + +<StoryEmbed id="molecules-modals-modal--click-outside" height="16rem" /> + +### Scrolling long content + +Pass `scrollable` when content is taller than the Modal's fixed height: + +<StoryEmbed id="molecules-modals-modal--scrollable" height="20rem" /> + +### Managing focus + +Pass `containerFocusRef` to move focus to the Modal's container programmatically — useful for refocusing after an in-modal action, since the container already auto-focuses once on open: + +<StoryEmbed id="molecules-modals-modal--focus-management" height="16rem" /> + +### Adding an image + +Pass `image` for a full-width, pre-sized illustration or photo above the content: + +<StoryEmbed id="molecules-modals-modal--with-image" height="20rem" /> + +### Building a multi-step flow + +Pass `views` — an array of `{ children, title?, primaryCta, secondaryCta }` — instead of `children`, and `Modal` manages moving between them. `primaryCta`'s `actionType` is `'next'` or `'confirm'`; `secondaryCta`'s is `'back'` or `'cancel'`. `cancel` closes the Modal automatically; `next`, `back`, and `confirm` don't — call `onRequestClose` yourself wherever one of those should close it: + +<StoryEmbed id="molecules-modals-modal--multiple-views" height="20rem" /> + +#### Gating progress + +Pass `disabled` on a CTA to block moving forward until some condition is met: + +<StoryEmbed + id="molecules-modals-modal--multiple-views-disabled" + height="20rem" +/> + +#### Confirming a destructive action + +Give a `confirm` CTA `variant="danger"`: + +<StoryEmbed id="molecules-modals-modal--multiple-views-danger" height="16rem" /> + +### Confirming before closing + +Open a second Modal from `onRequestClose` when there's unsaved state, instead of closing immediately: + +<StoryEmbed id="molecules-modals-modal--confirmation-on-close" height="20rem" /> + +## Prop Reference + +### Size + +#### Fluid (default) + +<StoryEmbed id="molecules-modals-modal--fluid" height="16rem" /> + +#### Large + +<StoryEmbed id="molecules-modals-modal--large" height="20rem" /> + +#### Medium + +<StoryEmbed id="molecules-modals-modal--medium" height="16rem" /> + +#### Small + +<StoryEmbed id="molecules-modals-modal--small" height="12rem" /> diff --git a/packages/starlight/src/content/docs/components/overlays/overlay.mdx b/packages/starlight/src/content/docs/components/overlays/overlay.mdx new file mode 100644 index 0000000000..6d00154d4e --- /dev/null +++ b/packages/starlight/src/content/docs/components/overlays/overlay.mdx @@ -0,0 +1,27 @@ +--- +title: Overlay +description: The lower-level primitive Modal and Dialog are built on. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Overlay/index.tsx) + +## Usage + +Use `Overlay` only for actual full-screen overlay content, not dropdowns or other small interactive elements. It's a bare primitive: no background styling, no title, no buttons, and no animation. + +**Best practices:** + +- `Overlay` is a controlled component — pass `isOpen`, and close it yourself in `onRequestClose`, called for an outside click and/or the Escape key depending on `clickOutsideCloses`/`escapeCloses`. +- It renders a single child element, not arbitrary `children`, traps focus inside that element while open (unless `inline` is set), and portals to the end of `<body>` unless `inline` is set. +- `zIndex` defaults to `3` to sit above common UI like headers — override it only for custom stacking needs. + +**When NOT to use:** + +- **A titled panel with close and CTA buttons already built in** — use [Modal](/components/overlays/modal/) or [Dialog](/components/overlays/dialog/) instead; both are built on `Overlay` and add that structure. +- **A small floating element anchored to a trigger, like a dropdown** — use [Popover](/components/overlays/popover/) instead. + +## Playground + +<StoryEmbed id="molecules-modals-overlay--default" height="16rem" /> diff --git a/packages/starlight/src/content/docs/components/overlays/popover-container.mdx b/packages/starlight/src/content/docs/components/overlays/popover-container.mdx new file mode 100644 index 0000000000..c1ea327051 --- /dev/null +++ b/packages/starlight/src/content/docs/components/overlays/popover-container.mdx @@ -0,0 +1,57 @@ +--- +title: PopoverContainer +description: An unstyled primitive for positioning floating content relative to a target element. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/PopoverContainer) + +## Usage + +Use `PopoverContainer` when you need Gamut's floating-positioning logic without any of [Popover](/components/overlays/popover/)'s built-in visual chrome — for example, floating a `Menu` or a calendar dialog with your own styling. Despite the name, it isn't what `Popover` is built on; the two solve the same positioning problem independently, sharing only a couple of internal hooks. + +**Best practices:** + +- `PopoverContainer` positions its content and nothing else — it has no styles of its own, so whatever you pass it needs to supply its own visual chrome (background, border, shadow). +- Use `inline` to render relative to the nearest positioned ancestor instead of portaling to `<body>` — it avoids jumpy repositioning on scroll or resize, but skip it when the surrounding stacking context is too small, or an ancestor has `overflow: hidden`. +- Escalate positioning props only as far as needed: `alignment` alone is usually enough. Reach for `invertAxis`, then `offset`, then explicit `x`/`y` only when the default doesn't fit. +- Turn on `closeOnViewportExit` for informational popovers that should clean themselves up when their target scrolls away. Leave it off (the default) for interactive menus, where a reader might scroll the trigger out of view without meaning to dismiss it. +- `allowPageInteraction` (default `false`) lets the reader keep interacting with the rest of the page while the popover is open — turn it on only when the parent component already manages its own focus and accessibility, since `PopoverContainer` otherwise assumes it's the one in control. + +**When NOT to use:** + +- **A floating panel with built-in beak, outline, and pattern styling** — use [Popover](/components/overlays/popover/) instead. +- **Small, non-interactive content** — use [ToolTip](/components/feedback/tips/tooltip/) instead. + +## Usage examples + +### Composing with other components + +`PopoverContainer` has no visual styling of its own — pair it with something that supplies its own chrome, like [Menu](/components/actions/menu/#building-a-floating-menu) for a floating action menu. + +## Prop Reference + +### Alignment + +`alignment` accepts a target edge (`'top'`, `'bottom'`, `'left'`, `'right'`) or corner (`'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'`), default `'bottom-left'`: + +<StoryEmbed id="atoms-popovercontainer--alignment" height="20rem" /> + +### Inverting the axis + +For the four corner alignments, `invertAxis` (`'x'` or `'y'`) flips the content to the inside edge of the target instead of the outside: + +<StoryEmbed id="atoms-popovercontainer--invert-axis" height="20rem" /> + +### Offset + +`offset` pushes the popover further in the direction of `alignment`: + +<StoryEmbed id="atoms-popovercontainer--offset" height="20rem" /> + +### Explicit x/y offsets + +`x` and `y` set per-axis pixel offsets directly, independent of `alignment`: + +<StoryEmbed id="atoms-popovercontainer--x-and-y" height="20rem" /> diff --git a/packages/starlight/src/content/docs/components/overlays/popover.mdx b/packages/starlight/src/content/docs/components/overlays/popover.mdx new file mode 100644 index 0000000000..5abf4e6fe9 --- /dev/null +++ b/packages/starlight/src/content/docs/components/overlays/popover.mdx @@ -0,0 +1,117 @@ +--- +title: Popover +description: A non-blocking floating panel anchored to a trigger. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=31226-41577) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Popover/Popover.tsx) + +## Usage + +Use `Popover` for interactive floating content anchored to a target element — for example, a new-feature callout or a floating menu. + +**Best practices:** + +- `Popover` is controlled: pass `isOpen` and a `targetRef` pointing at the element it's anchored to. +- It closes itself automatically if its target scrolls fully out of the viewport — that's always on, not configurable, unlike [PopoverContainer](/components/overlays/popover-container/)'s opt-in `closeOnViewportExit`. +- `align`, `position`, and `beak` are RTL-aware — under `GamutProvider`'s logical properties, `left`/`right` follow reading direction rather than fixed physical sides. + +**When NOT to use:** + +- **Small, non-interactive content** — use [ToolTip](/components/feedback/tips/tooltip/) instead. +- **Purely positioning logic, with your own visual styling** — use [PopoverContainer](/components/overlays/popover-container/) instead; `Popover` always renders its own beak, outline, and pattern chrome. + +### Anatomy + +1. **Beak** (optional, `beak`) — a pointed triangle connecting the popover to its target; pass `left`/`right` to pin a corner, or `center` for a centered triangle. +2. **Pattern background** (optional, `pattern`) — a decorative pattern layer; unavailable when `position="center"`. +3. **Content** — `children`. + +## Usage examples + +### Animating on open + +Pass `animation="fade"`: + +<StoryEmbed id="molecules-popover--animation" height="10rem" /> + +### Handling content with no focusable element + +When a popover's content has nothing to focus, the popover itself becomes the fallback focus target — so opening it never throws a focus-trap error: + +<StoryEmbed id="molecules-popover--without-focus" height="10rem" /> + +## Prop Reference + +### Position + +`position` (`'above'`, `'below'`, or `'center'`) and `align` (`'left'`, `'right'`, or `'center'`) together place the popover relative to `targetRef`. + +#### Above + +<StoryEmbed id="molecules-popover--above" height="10rem" /> + +#### Below + +<StoryEmbed id="molecules-popover--below" height="10rem" /> + +#### Center, aligned left + +<StoryEmbed id="molecules-popover--center-left" height="10rem" /> + +#### Center, aligned right + +<StoryEmbed id="molecules-popover--center-right" height="10rem" /> + +### Beak + +#### Pinned to a corner + +<StoryEmbed id="molecules-popover--beak" height="10rem" /> + +#### Centered + +<StoryEmbed id="molecules-popover--beak-centered" height="8rem" /> + +### Outline + +<StoryEmbed id="molecules-popover--outline" height="10rem" /> + +### Pattern background + +#### Checker, dense + +<StoryEmbed id="molecules-popover--popover-checker-dense" height="10rem" /> + +#### Checker, loose + +<StoryEmbed id="molecules-popover--popover-checker-loose" height="10rem" /> + +#### Checker, regular + +<StoryEmbed id="molecules-popover--popover-checker-regular" height="10rem" /> + +#### Diagonal, dense + +<StoryEmbed id="molecules-popover--popover-diagonal-a-dense" height="10rem" /> + +#### Diagonal, loose + +<StoryEmbed id="molecules-popover--popover-diagonal-a-loose" height="10rem" /> + +#### Diagonal, regular + +<StoryEmbed id="molecules-popover--popover-diagonal-a-regular" height="10rem" /> + +### Variant + +#### Primary (default) + +<StoryEmbed id="molecules-popover--default" height="10rem" /> + +#### Secondary + +A smaller, tooltip-like style: a smaller beak and padding, with different colors in each ColorMode. + +<StoryEmbed id="molecules-popover--variant" height="10rem" /> diff --git a/packages/starlight/src/content/docs/components/status/Loaders/index.md b/packages/starlight/src/content/docs/components/status/Loaders/index.md new file mode 100644 index 0000000000..c7492b97ee --- /dev/null +++ b/packages/starlight/src/content/docs/components/status/Loaders/index.md @@ -0,0 +1,13 @@ +--- +title: Loaders +description: Indeterminate loading indicators. +sidebar: + label: Overview +--- + +Two indeterminate loading indicators — reach for one only when there's no known amount of progress to show. Once you know how much of a task is done, [ProgressBar](/components/status/progress-bar/) or [RadialProgress](/components/status/radial-progress/) communicates more. + +- [Spinner](/components/status/loaders/spinner/) — a spinning circle, sized via `size` and colored via `currentColor`. +- [Shimmer](/components/status/loaders/shimmer/) — a `Box` with an animated gradient sweep, sized and styled like any other `Box`. + +Neither has a family of shape, size, or color variants: `Spinner` takes exactly one custom prop (`size`); `Shimmer` takes only standard `Box`/system props. diff --git a/packages/starlight/src/content/docs/components/status/Loaders/shimmer.mdx b/packages/starlight/src/content/docs/components/status/Loaders/shimmer.mdx new file mode 100644 index 0000000000..6ed473a12e --- /dev/null +++ b/packages/starlight/src/content/docs/components/status/Loaders/shimmer.mdx @@ -0,0 +1,26 @@ +--- +title: Shimmer +description: An animated gradient sweep over a Box, used as a loading placeholder. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Updating · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=29975%3A39412) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Loading/Shimmer.tsx) + +## Usage + +Use `Shimmer` to preview the shape of content that's still loading. It accepts any `Box` prop (`height`, `width`, `bg`, spacing, and so on), so it can be sized and positioned like the content it's standing in for. + +**Best practices:** + +- `Shimmer` takes only standard `Box`/system props — there's no dedicated `shape`, `count`, or line-based skeleton API. Compose several `Shimmer`s, each sized to match a piece of real content, to build a multi-line placeholder. +- Its gradient adapts automatically to light and dark ColorMode. + +**When NOT to use:** + +- **A known amount of progress** — use [ProgressBar](/components/status/progress-bar/) or [RadialProgress](/components/status/radial-progress/) instead. +- **A small, compact loading indicator inside an already-loaded component** — use [Spinner](/components/status/loaders/spinner/) instead. + +## Playground + +<StoryEmbed id="atoms-loaders-shimmer--default" height="14rem" /> diff --git a/packages/starlight/src/content/docs/components/status/Loaders/spinner.mdx b/packages/starlight/src/content/docs/components/status/Loaders/spinner.mdx new file mode 100644 index 0000000000..1a441a499d --- /dev/null +++ b/packages/starlight/src/content/docs/components/status/Loaders/spinner.mdx @@ -0,0 +1,26 @@ +--- +title: Spinner +description: A rotating indeterminate loading indicator. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=29975%3A39412) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Loading/Spinner.tsx) + +## Usage + +Use `Spinner` for a compact, indeterminate loading indicator inside an already-loaded component — for example, inside a button while an action is in flight. + +**Best practices:** + +- `size` is the only prop `Spinner` exposes. Its color comes from `currentColor` — set it by wrapping `Spinner` in an element with the text color you want — and its rotation speed is fixed at one second per turn. +- Reserve it for small, compact spaces. For a loading state that covers a whole section or page, [Shimmer](/components/status/loaders/shimmer/) reads better, since it previews the shape of what's loading. + +**When NOT to use:** + +- **A known amount of progress** — use [ProgressBar](/components/status/progress-bar/) or [RadialProgress](/components/status/radial-progress/) instead. +- **A section-sized loading placeholder that previews content shape** — use [Shimmer](/components/status/loaders/shimmer/) instead. + +## Playground + +<StoryEmbed id="atoms-loaders-spinner--default" height="6rem" /> diff --git a/packages/starlight/src/content/docs/components/status/badge.mdx b/packages/starlight/src/content/docs/components/status/badge.mdx new file mode 100644 index 0000000000..4592280d37 --- /dev/null +++ b/packages/starlight/src/content/docs/components/status/badge.mdx @@ -0,0 +1,92 @@ +--- +title: Badge +description: A small, read-only label for statuses, attributes, or emphasized information. +--- + +import FigmaEmbed from '~/components/FigmaEmbed.astro'; +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?type=design&node-id=29959-39721&mode=design&t=27y0ZBTRsAgBmZ9z-0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Badge) + +## Usage + +Use `Badge` to display read-only information like statuses, attributes, or other information that needs emphasis. + +**Best practices:** + +- Use badges sparingly — too many on a page dilutes their effectiveness. +- Keep the label to 1–2 words. +- Custom colors (`variant="custom"`) should be used sparingly, applied consistently across similar contexts, and with sufficient contrast for accessibility. + +**When NOT to use:** + +- **Interactive, clickable text** — use a [Button](/components/actions/buttons/) or [Anchor](/components/navigation/anchor/) instead. +- **Labeling or categorizing content** — use [Tag](/components/actions/tag/) instead. +- **A count or notification dot** — `Badge` has no dedicated count/number-display mode; it's a labeled chip, not a counter. + +### Anatomy + +<FigmaEmbed url="https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?type=design&node-id=29959-39721&mode=design&t=27y0ZBTRsAgBmZ9z-0" /> + +1. **Leading icon** (optional) — reinforces the badge's message and improves scannability. It's always `aria-hidden`, so it's never a substitute for the label text. +2. **Label** — 1–2 words of read-only status or attribute text. + +## Usage examples + +### Sitting above unpredictable content + +Use `tertiaryFill` instead of `tertiary` when a badge needs to sit over other content — its opaque background stands in for `tertiary`'s transparent one, which would otherwise let whatever's underneath show through: + +<StoryEmbed id="atoms-badge--tertiary-fill" height="8rem" /> + +### Custom colors + +Set `variant="custom"` with `bg` or `background` — the two are mutually exclusive — for a badge that doesn't fit any of the built-in color schemes: + +<StoryEmbed id="atoms-badge--custom" height="6rem" /> + +### Leading icons at each size + +Icons are sized and spaced automatically to match `size`. + +#### Default size + +<StoryEmbed id="atoms-badge--default-size-with-icon" height="4rem" /> + +#### Small size + +<StoryEmbed id="atoms-badge--small-size-with-icon" height="4rem" /> + +## Prop Reference + +### Variant + +#### Primary (default) + +<StoryEmbed id="atoms-badge--default" height="4rem" /> + +#### Secondary + +<StoryEmbed id="atoms-badge--secondary" height="4rem" /> + +#### Tertiary + +<StoryEmbed id="atoms-badge--tertiary" height="4rem" /> + +#### Accent + +Doesn't respond to ColorMode, unlike every other variant. + +<StoryEmbed id="atoms-badge--accent" height="4rem" /> + +`tertiaryFill` and `custom` are covered above, under [sitting above unpredictable content](#sitting-above-unpredictable-content) and [custom colors](#custom-colors). + +### Size + +#### Default (base) + +<StoryEmbed id="atoms-badge--default-size" height="4rem" /> + +#### Small + +<StoryEmbed id="atoms-badge--small-size" height="4rem" /> diff --git a/packages/starlight/src/content/docs/components/status/feature-shimmer.mdx b/packages/starlight/src/content/docs/components/status/feature-shimmer.mdx new file mode 100644 index 0000000000..84084ba844 --- /dev/null +++ b/packages/starlight/src/content/docs/components/status/feature-shimmer.mdx @@ -0,0 +1,28 @@ +--- +title: FeatureShimmer +description: A shimmering highlight used to draw attention to a new feature. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=61818-3459) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/FeatureShimmer) + +## Usage + +Use `FeatureShimmer` to subtly draw attention to a secondary new feature or element — something worth noticing, but not the primary focus of the page. + +**Best practices:** + +- Apply it to only one element at a time. +- Don't show it indefinitely — cap how often a reader sees it (for example, up to 3 times across sessions), or stop showing it once they interact with the element. +- It animates only once the wrapped element scrolls into view, and automatically does nothing under `prefers-reduced-motion` — no extra wiring needed for either. + +**When NOT to use:** + +- **A primary feature or critical update** — use [Coachmark](/components/feedback/coachmark/) or [Badge](/components/status/badge/) instead. +- **A critical alert or error** — use [Alert](/components/feedback/alert/) instead. +- **An indeterminate loading state** — despite the similar name, `FeatureShimmer` shares no implementation with [Shimmer](/components/status/loaders/shimmer/); it's a one-shot attention sweep triggered by scrolling into view, not a continuous loading animation. Use `Shimmer` for loading placeholders instead. + +## Playground + +<StoryEmbed id="atoms-featureshimmer--default" height="12rem" /> diff --git a/packages/starlight/src/content/docs/components/status/index.md b/packages/starlight/src/content/docs/components/status/index.md new file mode 100644 index 0000000000..c4654397f3 --- /dev/null +++ b/packages/starlight/src/content/docs/components/status/index.md @@ -0,0 +1,15 @@ +--- +title: Status +description: Components that communicate a state at a glance, without needing to be dismissed. +sidebar: + label: Overview +--- + +Components that communicate a state at a glance, without needing to be dismissed. + +- [Badge](/components/status/badge/) +- [ProgressBar](/components/status/progress-bar/), [RadialProgress](/components/status/radial-progress/) +- [Loaders](/components/status/loaders/) — Spinner, Shimmer +- [FeatureShimmer](/components/status/feature-shimmer/) + +For a read-only label that overlaps visually with Badge, check [Tag](/components/actions/tag/) in Actions — Tag is for interactive, removable selections. diff --git a/packages/starlight/src/content/docs/components/status/progress-bar.mdx b/packages/starlight/src/content/docs/components/status/progress-bar.mdx new file mode 100644 index 0000000000..668bf315e1 --- /dev/null +++ b/packages/starlight/src/content/docs/components/status/progress-bar.mdx @@ -0,0 +1,96 @@ +--- +title: ProgressBar +description: A horizontal bar for displaying determinate progress. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=18448-19639&node-type=frame&t=zRqgxQkQhUaLzOe7-0) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/ProgressBar) + +## Usage + +Use `ProgressBar` to indicate a known amount of progress along a fixed course — for example, how many questions of a quiz are complete. + +**Best practices:** + +- Only the `large` and `xl` sizes show the percentage as text; reach for one of those when readers need the exact number, not just a sense of progress. +- Pass `flat="flat-top"` or `"flat-bottom"` when the bar sits flush against a Card's edge, so its own corner radius doesn't clash with the Card's. + +**When NOT to use:** + +- **Progress as a share of a whole, shown as a ring rather than a bar** — use [RadialProgress](/components/status/radial-progress/) instead. +- **Indeterminate loading, with no known amount of progress** — use [Spinner](/components/status/loaders/spinner/) or [Shimmer](/components/status/loaders/shimmer/) instead. + +## Usage examples + +### Setting a minimum visible fill + +Pass `minimumPercent` so a bar reads as "started" even when the real percentage is at or near zero — the bar fills to whichever is larger, `percent` or `minimumPercent`: + +<StoryEmbed id="atoms-progressbar--minimum-percent" height="6rem" /> + +### Adding a pattern background + +Pass a component from `@codecademy/gamut-patterns` to `pattern` for a textured background instead of a flat fill. + +#### Regular + +<StoryEmbed + id="atoms-progressbar--pattern-background-stripes-regular" + height="8rem" +/> + +#### Dense + +<StoryEmbed + id="atoms-progressbar--pattern-background-stripes-dense" + height="6rem" +/> + +### Sitting flush against a Card + +Pass `flat="flat-top"` or `"flat-bottom"` to square off the matching edge, so the bar sits flush against a Card's corner instead of showing its own rounded one. + +#### Flat top + +<StoryEmbed id="atoms-progressbar--flat-top" height="8rem" /> + +#### Flat bottom + +<StoryEmbed id="atoms-progressbar--flat-bottom" height="8rem" /> + +## Prop Reference + +### Variant + +#### Default + +<StoryEmbed id="atoms-progressbar--default" height="4rem" /> + +#### Blue + +<StoryEmbed id="atoms-progressbar--blue" height="4rem" /> + +#### Yellow + +<StoryEmbed id="atoms-progressbar--yellow" height="4rem" /> + +### Size + +Only `large` and `xl` show the percentage as text. + +#### Small + +<StoryEmbed id="atoms-progressbar--small" height="4rem" /> + +#### Medium + +<StoryEmbed id="atoms-progressbar--medium" height="4rem" /> + +#### Large + +<StoryEmbed id="atoms-progressbar--large" height="6rem" /> + +#### XL + +<StoryEmbed id="atoms-progressbar--xl" height="8rem" /> diff --git a/packages/starlight/src/content/docs/components/status/radial-progress.mdx b/packages/starlight/src/content/docs/components/status/radial-progress.mdx new file mode 100644 index 0000000000..83b9a4fa28 --- /dev/null +++ b/packages/starlight/src/content/docs/components/status/radial-progress.mdx @@ -0,0 +1,41 @@ +--- +title: RadialProgress +description: A circular display of progress as a percentage of a whole. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/RadialProgress) + +## Usage + +Use `RadialProgress` to display progress as a percentage of a whole, as a ring instead of a linear bar. + +**Best practices:** + +- Pass `children` to show a label — a percentage or an X/Y ratio — centered inside the ring. +- Pass `value` as a `[start, end]` tuple together with `duration` (in ms) to animate smoothly between two values on render, instead of jumping straight to the end value. +- Unlike [ProgressBar](/components/status/progress-bar/), `RadialProgress`'s progress label isn't in a live region. If its value updates after the initial render, pair it with your own live-region announcement so screen reader users learn about the change. + +**When NOT to use:** + +- **A horizontal bar instead of a ring** — use [ProgressBar](/components/status/progress-bar/) instead. +- **Indeterminate loading, with no known amount of progress** — use [Spinner](/components/status/loaders/spinner/) or [Shimmer](/components/status/loaders/shimmer/) instead. + +## Usage examples + +### Showing a label inside the ring + +Pass `children` to render a percentage or ratio centered inside the ring: + +<StoryEmbed id="atoms-radialprogress--children" height="8rem" /> + +### Animating between two values + +Pass `value` as `[start, end]` with `duration` (in ms) to animate the ring from `start`% to `end`% on render: + +<StoryEmbed id="atoms-radialprogress--animating" height="10rem" /> + +## Playground + +<StoryEmbed id="atoms-radialprogress--default" height="8rem" /> diff --git a/packages/starlight/src/content/docs/components/typography/index.md b/packages/starlight/src/content/docs/components/typography/index.md new file mode 100644 index 0000000000..3a022638f8 --- /dev/null +++ b/packages/starlight/src/content/docs/components/typography/index.md @@ -0,0 +1,12 @@ +--- +title: Typography +description: Text rendering. +sidebar: + label: Overview +--- + +Text rendering. + +- [Text](/components/typography/text/) + +Most other typography concerns (scale, weight, family) are tokens, not components — see [Foundations](/foundations/) and [Typography](/guides/writing-ux-copy/) guidance. diff --git a/packages/starlight/src/content/docs/components/typography/text.mdx b/packages/starlight/src/content/docs/components/typography/text.mdx new file mode 100644 index 0000000000..6a26fcb606 --- /dev/null +++ b/packages/starlight/src/content/docs/components/typography/text.mdx @@ -0,0 +1,69 @@ +--- +title: Text +description: The base component for rendering styled text with any semantic element. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Figma](https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=412-324&t=hkNVv1N6nDPVexfd-4) · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Typography/Text) + +## Usage + +`Text` is the preferred component for customizing typography, with three levels of precedence: + +1. `as` — a semantic HTML tag (`h1`–`h6`, `p`, `strong`, and more), with a sensible default style matching that tag. +2. `variant` — overrides the tag's default style with one of Gamut's semantic type scales, for when the semantically-correct tag and the required look don't match (for example, an `h1` that needs to read like an `h4`). +3. System props (`fontSize`, `lineHeight`, and the rest) — for anything the first two don't cover. + +**Best practices:** + +- Use `Text` for any HTML text element. +- Choose `as` for real semantic meaning first, respecting heading order — reach for `variant` only when the semantically-correct tag needs to look like a different one. +- See [Typography](/foundations/typography/) for line-length and readability guidance beyond an individual component's styling. + +**When NOT to use:** + +- **Items in a list** — use [List](/components/data-display/list-and-tables/list/list/) and [ListRow](/components/data-display/list-and-tables/list/list-row/) instead. +- **Anchors or links** — use [Anchor](/components/navigation/anchor/) instead. + +## Usage examples + +### Truncating long text + +Pass `truncate` (`'ellipsis'` or `'fade'`) together with `truncateLines` (1–5) — Gamut's types require both together, or neither: + +<StoryEmbed id="typography-text--truncation" height="20rem" /> + +### Adding a highlight + +Pass `highlight` for a bolded, marker-style background bar behind the text: + +<StoryEmbed id="typography-text--highlight" height="6rem" /> + +### Hiding text visually while keeping it for screen readers + +Pass `screenreader` to visually hide text while keeping it in the accessibility tree. Wrap the parent in a `Box` with `aria-labelledby` pointing at the `Text`'s `id`, and add `aria-hidden` on the `Text` itself if the wrapping element already conveys the same information, to avoid it being announced twice: + +<StoryEmbed id="typography-text--screenreader" height="10rem" /> + +## Prop Reference + +### HTML element variants + +`as` accepts any tag from `typographyElementVariants` (`h1`–`h6`, `p`, `small`, `strong`, `code`, `span`, `div`, `figcaption`, `label`), each with its own default style: + +<StoryEmbed id="typography-text--elements" height="30rem" /> + +### Style variants + +`variant` accepts a semantic type scale (`title-xxl` through `title-xs`, `p-large`, `p-base`, `p-small`), independent of `as`: + +<StoryEmbed id="typography-text--variants" height="20rem" /> + +### Font smoothing + +<StoryEmbed id="typography-text--font-smoothing" height="8rem" /> + +### Playground + +<StoryEmbed id="typography-text--default" height="6rem" /> diff --git a/packages/starlight/src/content/docs/components/utilities/delayed-render-wrapper.mdx b/packages/starlight/src/content/docs/components/utilities/delayed-render-wrapper.mdx new file mode 100644 index 0000000000..820bfa3f7d --- /dev/null +++ b/packages/starlight/src/content/docs/components/utilities/delayed-render-wrapper.mdx @@ -0,0 +1,30 @@ +--- +title: DelayedRenderWrapper +description: Delays rendering its children by a given duration. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/DelayedRenderWrapper/DelayedRenderWrapper.tsx) + +## Usage + +Use `DelayedRenderWrapper` to delay mounting its children by a given number of milliseconds. + +**Best practices:** + +- Nothing renders during the delay — not even a placeholder. If the wait itself matters to the reader, pair it with your own loading state rather than relying on `DelayedRenderWrapper` to show one. +- Pass `delay={0}`, or omit it, to render children immediately with no timer at all. +- Changing `delay` while its timer is still pending resets the wait — the component clears the previous timeout and starts a new one. +- Gamut's own [Coachmark](/components/feedback/coachmark/) uses this to delay when its popover first appears, starting the timer the moment it's asked to show — see [Coachmark's delay pattern](/components/feedback/coachmark/#delaying-appearance). + +**When NOT to use:** + +- **A delay that should show a loading state while it waits** — build your own; `DelayedRenderWrapper` renders nothing at all during the wait. + +## Playground + +<StoryEmbed + id="atoms-utilitycomponents-delayedrenderwrapper--default" + height="8rem" +/> diff --git a/packages/starlight/src/content/docs/components/utilities/focus-trap.mdx b/packages/starlight/src/content/docs/components/utilities/focus-trap.mdx new file mode 100644 index 0000000000..eb4dbf0cd6 --- /dev/null +++ b/packages/starlight/src/content/docs/components/utilities/focus-trap.mdx @@ -0,0 +1,27 @@ +--- +title: FocusTrap +description: Traps keyboard focus within its children, e.g. for a modal. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +**Status:** Current · [Source](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/FocusTrap) + +## Usage + +Use `FocusTrap` to keep keyboard focus cycling within its children for as long as it's active — for example, while a modal is open. It's a thin wrapper around [react-focus-on](https://github.com/theKashey/react-focus-on); pass `focusOnProps` to reach any of that library's own props directly. + +**Best practices:** + +- `active` (default `true`) is the on/off switch. Tab/Shift+Tab wrap within the trapped children only while it's active, and focus returns to whatever triggered it once the trap deactivates. +- Set `allowPageInteraction` for a non-modal trap, like a dropdown or popover, where the rest of the page should stay scrollable and clickable while focus still cycles within the trap. +- `onClickOutside`/`onEscapeKey` fire independently of `active` — wire them to whatever should actually close the thing the trap is protecting. +- Most of Gamut's overlay-style components already compose `FocusTrap` for you: [Overlay](/components/overlays/overlay/) (active whenever it's open, `allowPageInteraction` mirroring its own `allowScroll`) and [PopoverContainer](/components/overlays/popover-container/)/[Popover](/components/overlays/popover/) (always active with `allowPageInteraction`, unless a consumer opts `Popover` out with `skipFocusTrap`). Reach for `FocusTrap` directly only when composing something new. + +**When NOT to use:** + +- **A tooltip or other transient hint that shouldn't steal focus at all** — skip it entirely; [ToolTip](/components/feedback/tips/tooltip/) and [PreviewTip](/components/feedback/tips/previewtip/) intentionally opt out of focus trapping. + +## Playground + +<StoryEmbed id="atoms-utilitycomponents-focustrap--default" height="14rem" /> diff --git a/packages/starlight/src/content/docs/components/utilities/index.md b/packages/starlight/src/content/docs/components/utilities/index.md new file mode 100644 index 0000000000..93cc45d82b --- /dev/null +++ b/packages/starlight/src/content/docs/components/utilities/index.md @@ -0,0 +1,11 @@ +--- +title: Utilities +description: Non-visual helper components. +sidebar: + label: Overview +--- + +Non-visual helper components. + +- [FocusTrap](/components/utilities/focus-trap/) +- [DelayedRenderWrapper](/components/utilities/delayed-render-wrapper/) diff --git a/packages/starlight/src/content/docs/concepts/best-practices.md b/packages/starlight/src/content/docs/concepts/best-practices.md new file mode 100644 index 0000000000..df3c6b87fa --- /dev/null +++ b/packages/starlight/src/content/docs/concepts/best-practices.md @@ -0,0 +1,58 @@ +--- +title: Best practices +description: General design and engineering best practices that apply across components. +--- + +For best practices specific to one component — like alerts, errors, or confirmation dialogs — see that component's own page. + +## Prefer semantic tokens over raw values + +Access colors through Gamut's semantic aliases (`text`, `background`, `primary`, `secondary`) rather than raw color values — see [Color modes](/concepts/color-modes/) for why. Aliases guarantee you get the right color for the right theme and color mode, with the right types and states. + +```tsx +import { css } from '@codecademy/gamut-styles'; +import styled from '@emotion/styled'; + +// A single value +const Box = styled.div(css({ p: 4 })); + +// A semantic color, not a raw one +const OtherCoolThing = styled.div(css({ color: 'primary', p: 4 })); +``` + +`variant` and `states` extend the same idea to whole sets of styles — see [Compose and variants](/foundations/system-props/compose-and-variants/) for the full API. + +## Reach for system props first + +[System props](/foundations/system-props/) exist so writing custom, one-off styles is rarely necessary. They come with type-safe access to the right token scale for the context you're in, and a [responsive syntax](/foundations/system-props/responsive-properties/) shared across every prop: + +```tsx +import { Box } from '@codecademy/gamut'; + +const MyContainer = ({ children }) => ( + <Box px={[16, 32, 64, , 96]}>{children}</Box> +); +``` + +`eslint-plugin-gamut`'s [`no-inline-style`](/foundations/eslint-rules/#gamutno-inline-style) and [`no-css-standalone`](/foundations/eslint-rules/#gamutno-css-standalone) rules enforce this in CI — an inline `style` prop or a standalone stylesheet is almost always a sign a system prop or a `variant`/`states` call should have been used instead. + +## Avoid nested selectors + +Nested selectors — tag selectors like `div`/`p`/`span`, or component selectors like `${Box}` — can produce side effects that are hard to predict and even harder to keep consistent through later updates. Reach for system props, `gamut-styles` utility functions, or a layout component like `FlexBox`/`GridBox` instead: + +```tsx +// Avoid +const App = styled.main` + display: flex; + ${Box} { + align-self: start; + } +`; + +// Prefer +const App = ({ children }) => ( + <FlexBox as="main"> + <Box alignSelf="start">{children}</Box> + </FlexBox> +); +``` diff --git a/packages/starlight/src/content/docs/concepts/brand.md b/packages/starlight/src/content/docs/concepts/brand.md new file mode 100644 index 0000000000..a6e579b005 --- /dev/null +++ b/packages/starlight/src/content/docs/concepts/brand.md @@ -0,0 +1,8 @@ +--- +title: Brand +description: How Codecademy's brand identity relates to Gamut. +--- + +The [Brand Library](https://brand-storybook.codecademy.com/) is a separate collection of assets, components, and themes for branded and internal-only experiences. It's distinct from Gamut, which is built for Codecademy's core product experience — reach for it specifically when building something branded or internal, not as a substitute for Gamut components. + +Questions about Brand go to `#gamut-team` on Slack. diff --git a/packages/starlight/src/content/docs/concepts/color-modes.md b/packages/starlight/src/content/docs/concepts/color-modes.md new file mode 100644 index 0000000000..8cd3d6e616 --- /dev/null +++ b/packages/starlight/src/content/docs/concepts/color-modes.md @@ -0,0 +1,22 @@ +--- +title: Color modes +description: How light/dark color mode works conceptually, distinct from theming. +--- + +Gamut components are built on semantic color aliases, not raw color values. Each alias names what a color is _used for_, not what it looks like: + +- `text` — the standard color for all type. +- `background` — the base background color. +- `primary` — the color for interactive elements with a primary action. +- `secondary` — the color for interactive elements with a secondary action. + +Building components against these aliases, instead of specific values, guarantees: + +- Components work correctly in any context, without extra configuration. +- Color usage stays consistent and accessible between contexts. +- Code reads more meaningfully, since each alias hints at its own purpose. +- Configuration stays simple — swapping a color mode swaps every aliased value at once. + +A color mode is the layer that resolves each alias to an actual value, for a light or dark context. For the exact values each alias resolves to, see [Themes](/foundations/themes/). For how to actually apply a color mode or build a component that adapts to it, see [Supporting dark mode](/guides/supporting-dark-mode/). + +This is distinct from [theming](/guides/theming-your-app/): a theme swaps which _palette_ Gamut draws from (Core, Admin, LX Studio, Percipio); a color mode swaps _which half_ of that palette — light or dark — an alias resolves against. The two compose: the same alias resolves differently depending on both which theme and which color mode are active. diff --git a/packages/starlight/src/content/docs/concepts/faqs.md b/packages/starlight/src/content/docs/concepts/faqs.md new file mode 100644 index 0000000000..a2b9126c41 --- /dev/null +++ b/packages/starlight/src/content/docs/concepts/faqs.md @@ -0,0 +1,39 @@ +--- +title: FAQs +description: Frequently asked questions about using and contributing to Gamut. +--- + +## What is the contribution process? + +Thanks for your interest! We currently accept contributions from Codecademy and Skillsoft employees only, but we're working on a process to accept contributions from the wider community — watch this space. + +## To Gamut, or not to Gamut? + +**When should we create a general component in Gamut instead of a specific one in another application?** + +In general, Gamut components should be shared across multiple experiences in the monolith. If something can be built from existing primitives without adding logic, it usually doesn't need its own component. + +Good fits for Gamut: + +- Common, visually identifiable atoms, like buttons or form inputs. +- Atoms linked together with client logic, like an alert bar. + +Poor fits for Gamut: + +- Atoms customized for one particular use case, like a promotional card. +- Groups of components easily recreated from primitives, like a specific button-and-grid combination. + +Other design systems, like [Fluent](https://www.microsoft.com/design/fluent) and [Material Design](https://material.io/design), are a useful reference for what they choose to turn into generic components. + +### Where should a new component be implemented? + +**Should we implement components in separate repositories or in Gamut?** + +As a rule of thumb: + +1. The first or second time a component is written, implement it in its own repository if the designer thinks it might eventually belong in Gamut — keep its API simple and flexible (for example, plain props instead of hooking it up to Redux) to make migrating it later easier. +2. The third time it's needed, discuss moving it into Gamut. + +## What's going on with accessibility? + +Codecademy is committed to [WCAG 2.2](https://www.w3.org/WAI/standards-guidelines/wcag) accessibility, and it's a must-have in Gamut. Every component is expected to be fully AA compliant, and tested for accessibility via [Storybook's accessibility testing](https://storybook.js.org/docs/writing-tests/accessibility-testing) — visible under each story's Accessibility tab, and run in CI. diff --git a/packages/starlight/src/content/docs/concepts/index.md b/packages/starlight/src/content/docs/concepts/index.md new file mode 100644 index 0000000000..d65d955b91 --- /dev/null +++ b/packages/starlight/src/content/docs/concepts/index.md @@ -0,0 +1,12 @@ +--- +title: Concepts +description: Background on how and why Gamut is built the way it is. +--- + +Explanation: how and why the system is built the way it is, for when you want to understand rather than do. + +- [Color modes](/concepts/color-modes/) +- [Brand](/concepts/brand/) +- [Best practices](/concepts/best-practices/) +- [Voice & tone](/concepts/voice-and-tone/) +- [FAQs](/concepts/faqs/) diff --git a/packages/starlight/src/content/docs/concepts/voice-and-tone.md b/packages/starlight/src/content/docs/concepts/voice-and-tone.md new file mode 100644 index 0000000000..2af399c61b --- /dev/null +++ b/packages/starlight/src/content/docs/concepts/voice-and-tone.md @@ -0,0 +1,43 @@ +--- +title: Voice & tone +description: How Gamut's documentation and Codecademy's product copy sound. +--- + +These principles describe how Gamut's own documentation is written; the same voice and tone should carry through to product copy that uses Gamut components. For copy guidance specific to a component — alerts, toasts, confirmation dialogs, and the rest — see [Writing UX copy](/guides/writing-ux-copy/). + +## Voice and tone + +- Friendly and conversational — write as if explaining to a colleague. +- Prefer "we" when a pronoun is needed; "you" is acceptable when necessary (for example, "use your best judgment"). +- Encouraging without overpromising — be supportive, but realistic. +- Global-audience awareness — avoid idioms, slang, and culturally specific references. + +## Voice + +Use active voice, not passive — it makes clear who does what, and eliminates ambiguity about responsibility. "The component renders..." not "The component is rendered by...". Use imperative mood for instructions: "add the component," not "you should add the component." + +## Tense + +Use present tense for current functionality — it's the most direct form of a verb, and the easiest to follow. For example: "the `StrokeButton` component accepts a `variant` prop." Use future tense sparingly, only for confirmed features, and avoid past tense outside changelogs or historical context. + +## Pronouns + +Active voice and imperative mood often make pronouns unnecessary. When one is still needed, prefer "we"; "you" is acceptable when necessary. Avoid "I"/"my"/"me" entirely. + +## Articles + +Use articles ("a," "an," "the") for clarity; omit them in a list when brevity doesn't cost clarity. + +## Abbreviations and acronyms + +Spell out an abbreviation on first use — "Web Content Accessibility Guidelines (WCAG)" — except for terms common in web design and development (HTML, CSS, API, UI, UX, and the like), which don't need spelling out. + +## Capitalization + +- Sentence case for headings, buttons, and UI text. +- `PascalCase` for component names, `camelCase` for props and variables. +- Capitalize proper nouns: Codecademy, Storybook, Figma, GitHub. + +## Inclusivity + +Use language that makes contributors of any experience level or role — design or engineering — feel welcome. Define terms the first time they're introduced. diff --git a/packages/starlight/src/content/docs/foundations/System Props/compose-and-variants.md b/packages/starlight/src/content/docs/foundations/System Props/compose-and-variants.md new file mode 100644 index 0000000000..672cbac23e --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/System Props/compose-and-variants.md @@ -0,0 +1,77 @@ +--- +title: Compose and variants +description: Combining system props into one function, and building variant- or state-driven styles. +--- + +## Compose + +Combine several system prop functions into one with `variance.compose` — it merges them left to right (the last argument wins on conflicts) into a single function. + +```tsx +import { system } from '@codecademy/gamut-styles'; +import { variance } from '@codecademy/variance'; +import styled from '@emotion/styled'; + +const Grid = styled.div(variance.compose(system.layout, system.grid)); + +<Grid width="100%" gridTemplateColumns="1fr 1fr" />; +``` + +Composing has two real advantages over passing multiple functions to `styled` directly: + +- **Predictable CSS** — Emotion doesn't merge styles from separate functions, so uncomposed props can emit unordered, overlapping media queries, or lose to property ordering (`border` set after `borderColor` always wins, for example). Composed functions combine into one ordered set of styles instead. +- **Simpler types** — Emotion can't infer the combined prop types of two separate functions passed to `styled`, so you'd otherwise have to write that intersection type by hand. `variance.compose` infers it for you. + +## Variant + +`variant` builds on `css` — a lower-level, theme-aware function that turns a system-prop object into styles — to let a component switch between mutually exclusive styles via a single prop: + +```tsx +import { variant } from '@codecademy/gamut-styles'; + +const Anchor = styled.a( + variant({ + defaultVariant: 'interface', + variants: { + interface: { color: 'text' }, + inline: { color: 'primary' }, + }, + }) +); +``` + +## States + +`states` is `variant`'s counterpart for non-mutually-exclusive styles — each key is its own boolean prop, and any combination can be active together: + +```tsx +import { states } from '@codecademy/gamut-styles'; + +const FlexBox = styled.div( + states({ + fit: { width: 1, height: 1 }, + center: { alignItems: 'center', justifyContent: 'center' }, + }) +); + +<FlexBox fit center />; +``` + +## Beyond system props + +Both `variant` and `states` also accept any valid CSS property (typed to its normal CSS value, without responsive syntax) and nested selectors, so a variant can style a pseudo-class or include a property with no system-prop equivalent: + +```tsx +const Anchor = styled.a( + variant({ + variants: { + interface: { + color: 'navy', + '&:hover': { textDecoration: 'none', border: '2px solid currentColor' }, + }, + }, + }) +); +``` + +Don't mix a system prop and its underlying raw CSS property in the same call — for example, `mx` and `margin` together. Property ordering between them isn't guaranteed, so pick one. diff --git a/packages/starlight/src/content/docs/foundations/System Props/index.md b/packages/starlight/src/content/docs/foundations/System Props/index.md new file mode 100644 index 0000000000..f421a32eb5 --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/System Props/index.md @@ -0,0 +1,29 @@ +--- +title: System props +description: The full list of system prop groups and the CSS properties each one covers. +sidebar: + label: Overview +--- + +Gamut ships a set of style functions, out of the box, through `@codecademy/gamut-styles`, standardized across every component. These props are strongly typed, and can be added to any styled component. + +Every system prop: + +- Can represent more than one CSS property at once. +- May be restricted to a specific token scale, but always keeps access to global CSS values like `initial` and `none`. +- May transform the value you pass it into a standardized one — for example, `width={0.5}` becomes `width: 50%`. + +```tsx +import { variance } from '@codecademy/variance'; +import { system } from '@codecademy/gamut-styles'; + +const ExampleContainer = styled.div( + variance.compose(system.layout, system.positioning) +); + +<ExampleContainer position="absolute" width="50px" height="50px" />; +``` + +- [Prop groups](/foundations/system-props/prop-groups/) — what each group (`layout`, `space`, `typography`, and the rest) covers. +- [Compose and variants](/foundations/system-props/compose-and-variants/) — combining system props into one function, and building variant- or state-driven styles. +- [Responsive properties](/foundations/system-props/responsive-properties/) — the array/object syntax for breakpoint- and container-query-driven values. diff --git a/packages/starlight/src/content/docs/foundations/System Props/prop-groups.md b/packages/starlight/src/content/docs/foundations/System Props/prop-groups.md new file mode 100644 index 0000000000..71dd781d0c --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/System Props/prop-groups.md @@ -0,0 +1,141 @@ +--- +title: Prop groups +description: Every system prop group and what it covers. +--- + +Each group below is one example of what it covers, not an exhaustive prop list. For every individual prop in a group — its exact CSS properties, its scale, and its transform — open that group's own page in Storybook (for example, [Layout](https://gamut.codecademy.com/?path=/docs/foundations-system-props-layout--docs)) and see its prop table; that table is generated live from `variance/config.ts`, so it can't drift out of sync the way a static copy here would. + +## Layout + +Layout props control the visual structure and dimensions of elements — how components take up space, their display behavior, and how they align within their containers. + +```tsx +const LayoutExample = styled.div(system.layout); + +<LayoutExample + display="flex" + width="50%" + height="300px" + verticalAlign="middle" +/>; +``` + +## Space + +Space props apply margin and padding consistently, referencing the theme's spacing scale — including responsive array syntax. + +```tsx +const SpaceExample = styled.div(system.space); + +<SpaceExample p={8} my={[16, 24, 32]} />; +``` + +## Typography + +Typography props connect to the theme's font family, size, weight, and line-height scales, alongside text transforms and decorations. + +```tsx +const TextExample = styled.p(system.typography); + +<TextExample fontSize={16} fontFamily="accent" textTransform="uppercase" />; +``` + +## Color + +Color props control foreground, background, and border colors, restricted to the theme's color palette. `bg` is a shorthand for background color. + +```tsx +const ColorExample = styled.div(system.color); + +<ColorExample bg="navy" textColor="gray-100" borderColor="blue" />; +``` + +## Border + +Border props add and style borders on any side of an element, with directional props and horizontal/vertical shorthands. `borderRadius` references the theme's `borderRadii` scale. + +```tsx +const BorderExample = styled.div(system.border); + +<BorderExample + border={1} + borderLeft="none" + borderRightStyle="dotted" + borderRadius="md" +/>; +``` + +## Flex + +Flex props control flexbox layouts end to end — container behavior and individual item properties alike. + +```tsx +const FlexExample = styled.div(system.flex); + +<FlexExample flex={1} justifyContent="center" alignItems="flex-start" />; +``` + +## Grid + +Grid props control CSS Grid layouts — templates, auto-placement, and gaps between items. + +```tsx +const GridExample = styled.div(system.grid); + +<GridExample + gridTemplateColumns="max-content 1fr max-content" + columnGap={32} +/>; +``` + +## Background + +Background props control background images and patterns — sizing, positioning, and repetition. For solid background colors, use the color props instead. + +```tsx +const BackgroundExample = styled.div(system.background); + +<BackgroundExample background={`url(${myBg})`} backgroundSize="cover" />; +``` + +## Positioning + +Positioning props control how an element is placed within its parent and its stacking order — `position`, the individual edge props (or the `inset` shorthand for all four at once), and `zIndex`. + +```tsx +const PositioningExample = styled.div(system.positioning); + +<PositioningExample position="absolute" zIndex={2} top="0" left="0" />; +``` + +## Shadow + +Shadow props add box and text shadows, using standard CSS shadow syntax. + +```tsx +const ShadowExample = styled.div(system.shadow); + +<ShadowExample + boxShadow="0 0 4px rgba(0, 0, 0, .15)" + textShadow="0 0 4px rgba(0, 0, 0, .15)" +/>; +``` + +## List + +List props adjust bullet styles, positioning, and custom marker images when a component renders as a `ul` or `ol`. For richer list features, see [List](/components/data-display/list-and-tables/list/list/). + +```tsx +const ListExample = styled.div(system.list); + +<ListExample + as="ul" + listStyleType="square" + listStylePosition="inside" + listStyleImage="none" +> + <ListExample as="li">a list item</ListExample> +</ListExample>; +``` + +`border`, `color`, `positioning`, and `space` props all support both physical and logical CSS properties, resolved by `useLogicalProperties` on `GamutProvider` — see [Migrating to logical properties](/guides/migrating-to-logical-properties/). diff --git a/packages/starlight/src/content/docs/foundations/System Props/responsive-properties.md b/packages/starlight/src/content/docs/foundations/System Props/responsive-properties.md new file mode 100644 index 0000000000..68e666ebcf --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/System Props/responsive-properties.md @@ -0,0 +1,82 @@ +--- +title: Responsive properties +description: The array/object syntax every system prop accepts for breakpoint- and container-query-driven values. +--- + +import StoryEmbed from '~/components/StoryEmbed.astro'; + +Every system prop accepts a responsive syntax, so breakpoint-driven behavior is visible at the point a prop is set, rather than hidden inside a separate media query elsewhere. + +```tsx +<Box display={{ _: 'none', sm: 'block', c_md: 'flex' }} /> +``` + +## Object syntax + +Keyed by breakpoint name; unordered, so it's easiest to read when only a few breakpoints need a value: + +```tsx +<Text fontSize={{ lg: 26 }} /> +<Text fontSize={{ _: 14, xl: 64 }} /> +``` + +## Array syntax + +Ordered left to right, matching breakpoint order exactly — easiest to read when every breakpoint gets a value: + +```tsx +<Text fontSize={[14, 16, 18, 20, 26, 64]} /> +``` + +Skip a breakpoint with an empty slot. Unlike the object syntax, a skipped array slot still explicitly sets that breakpoint to `undefined`, rather than falling through to the next set value: + +```tsx +<Column size={[12, , 6]} /> +``` + +## Breakpoints + +Media-query breakpoints: `_` (none), `xs`, `sm`, `md`, `lg`, `xl`. + +Container-query breakpoints: `c_base`, `c_xs`, `c_sm`, `c_md`, `c_lg`, `c_xl` — resolving to the same widths as their media-query counterparts, but as `@container` rules instead of `@media` ones. + +## Container queries + +Container queries adapt a component to its parent's width instead of the viewport, so the same component looks right regardless of where it sits in a layout. They require the parent to declare itself as a CSS container, typically via `containerType="inline-size"`: + +```tsx +<FlexBox containerType="inline-size"> + <Box display={{ _: 'none', sm: 'block', c_md: 'flex' }} /> +</FlexBox> +``` + +Set a fallback media-query value alongside any container-query value, for browsers that don't support container queries yet. + +## Best practices + +- Use media breakpoints for page-level layout shifts; use container queries for components that need to react to their own allotted space, not the viewport. +- Always declare a `containerType` on the element whose descendants should listen for container breakpoints. +- Prefer object syntax for isolated, single-breakpoint overrides; prefer array syntax when a value changes at every breakpoint in sequence. + +## Examples + +### Media queries + +<StoryEmbed + id="foundations-system-responsiveproperties--media-queries" + height="10rem" +/> + +### Container queries + +<StoryEmbed + id="foundations-system-responsiveproperties--container-queries" + height="10rem" +/> + +### Combined queries + +<StoryEmbed + id="foundations-system-responsiveproperties--combined-queries" + height="10rem" +/> diff --git a/packages/starlight/src/content/docs/foundations/Tooling/Figma/code-connect.md b/packages/starlight/src/content/docs/foundations/Tooling/Figma/code-connect.md new file mode 100644 index 0000000000..0666d1e22b --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/Tooling/Figma/code-connect.md @@ -0,0 +1,16 @@ +--- +title: Code Connect +description: Exploring Code Connect features in Figma. +--- + +Code Connect gives Figma the actual code behind a component, so the Figma MCP can generate more accurate code snippets for designs that use it. See [Figma's Code Connect documentation](https://www.figma.com/code-connect-docs/) for more. + +Any changes to Code Connect files should go through the Gamut team, not an individual or separate team. + +## Finding Code Connected components + +A component set up with Code Connect shows a "Code Connect" section in the sidebar when selected in Figma. + +### Exploring behavior + +Components with Code Connect also have an "Explore behavior" button, opening a modal where you can edit the component's props — the modal's Code Connect section updates its code sample to match, giving a sense of how to adapt the component's usage. diff --git a/packages/starlight/src/content/docs/foundations/Tooling/Figma/figma-mcp.md b/packages/starlight/src/content/docs/foundations/Tooling/Figma/figma-mcp.md new file mode 100644 index 0000000000..73905ffcdf --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/Tooling/Figma/figma-mcp.md @@ -0,0 +1,86 @@ +--- +title: Figma MCP +description: Set up the Figma MCP to enable design-to-code generation, exclusive to Codecademy + Skillsoft employees. +--- + +MCP-generated code is experimental — always validate and adapt it to your own needs rather than shipping it directly. Using the Figma MCP requires a Dev or Full Figma account. + +This page adapts Figma's own [Dev Mode MCP documentation](https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Dev-Mode-MCP-Server) and [remote server installation guide](https://developers.figma.com/docs/figma-mcp-server/remote-server-installation/) to the Gamut repository's context — refer to those directly for the most current information. + +## Remote server (recommended) + +Connects directly to Figma's hosted MCP server at `https://mcp.figma.com/mcp`, authenticated via Figma OAuth — no need to install or run the Figma desktop app. + +### Cursor + +1. Open Cursor Settings (`Cmd+Shift+J`). +2. Select **Plugins** in the sidebar. +3. Search for "Figma" and click **Add to Cursor**. +4. Select **Add for myself** and complete authentication. + +Cursor uses `git` to install plugins — you may be prompted to install it if it's not already on your machine. + +### Claude Code (terminal) + +```bash +claude mcp add --scope user --transport http figma https://mcp.figma.com/mcp +``` + +Then, inside a session, run `/mcp` to authenticate, and again to confirm the Figma server is connected. + +### Claude Desktop + +1. Click **Code** in the left nav. +2. Click **Customize**. +3. Click **Connectors**. +4. Search for "Figma" and click **Connect**. +5. Accept the terms. +6. Configure tool permissions as needed. + +## Prompting your MCP client + +Either link directly to a design node: + +> Generate the code for this node: https://www.figma.com/design/... + +or select the node in Figma and ask for the current selection: + +> Generate the code for the current selection + +You may be prompted to allow commands like `get_code()` to run — these can also be added to your editor's allowlist. + +## Local server (alternative) + +Requires the Figma desktop app running in the background, and is more limited in what an agent can do. Download it from [Figma's downloads page](https://www.figma.com/downloads/). + +### Start the local server + +1. Enable Dev Mode in the Figma desktop client. +2. Open the "MCP server" section in the right-hand sidebar. +3. Open its settings modal and toggle the status on — or, from the Figma icon menu, choose **Actions** → "Enable desktop MCP server" and check the box. + +### Configure your editor + +**Cursor** — Settings → Cursor Settings → MCP → **+ Add MCP server**: + +```json +{ + "mcpServers": { + "Figma": { "url": "http://127.0.0.1:3845/mcp" } + } +} +``` + +**Claude Code**: + +```bash +claude mcp add --scope user --transport http figma http://127.0.0.1:3845/mcp +``` + +### Verifying the local server + +Visit `http://127.0.0.1:3845/mcp` in a browser — a response like `{"jsonrpc":"2.0","error":{"code":-32001,"message":"Invalid sessionId"},"id":null}` confirms it's running. + +## Feedback + +Share feedback with the Gamut team — a new rule you'd like implemented, support for another editor, or incorrect code generation. diff --git a/packages/starlight/src/content/docs/foundations/Tooling/Figma/index.md b/packages/starlight/src/content/docs/foundations/Tooling/Figma/index.md new file mode 100644 index 0000000000..0afb612f19 --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/Tooling/Figma/index.md @@ -0,0 +1,19 @@ +--- +title: Figma +description: Figma Make kit, Code Connect, and Dev Mode MCP for Codecademy + Skillsoft employees. +sidebar: + label: Overview +--- + +## Figma Make + +Designers: use the published Gamut Make kit in Figma when building in Make — add it from the org kit picker inside the Figma app; kits aren't configured via a local CLI. See Figma's [guide to Make kits](https://help.figma.com/hc/en-us/articles/39241689698839) for how kits work. + +Maintainers update the published kit directly in Figma, uploading or editing guidelines in the Make file's code explorer — see [Add guidelines to Figma Make](https://help.figma.com/hc/en-us/articles/33665861260823). Engineers should use Storybook and the Gamut plugin skills for implementation reference instead. + +## Design-to-code + +For generating code from Figma designs: + +- [Code Connect](/foundations/tooling/figma/code-connect/) +- [Figma MCP](/foundations/tooling/figma/figma-mcp/) diff --git a/packages/starlight/src/content/docs/foundations/Tooling/Gamut Plugin/best-practices.md b/packages/starlight/src/content/docs/foundations/Tooling/Gamut Plugin/best-practices.md new file mode 100644 index 0000000000..60228920d7 --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/Tooling/Gamut Plugin/best-practices.md @@ -0,0 +1,63 @@ +--- +title: Best practices +description: How to use Gamut agent tools, DESIGN.md, and the gamut-review skill effectively in app repos. +--- + +## Recommended setup + +From your app repo root, install the plugin with the theme matching your product: + +```sh +gamut plugin install cursor --theme core +``` + +Use `percipio`, `lxstudio`, `admin`, or `platform` when that matches your product. Refresh after upgrading `@codecademy/gamut`: + +```sh +gamut plugin update cursor --theme core --force +``` + +## Which artifact to use when + +| Artifact | Where it lives | Used for | +| -------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | +| `DESIGN.md` (from `DESIGN.*.md`) | App repo root | Coding agents and `gamut-review` — product tokens, semantic roles, patterns. | +| `skills/` | Cursor/Claude after `gamut plugin install` | Task playbooks — invoke for focused work (theming, ColorMode, buttons, layout, forms, auditing, and more). | +| `rules/` | Cursor/Claude after install | Always-on guardrails for Gamut code, like accessibility. | + +## Audit with gamut-review + +Before large PRs, or when onboarding an existing codebase, run the `gamut-review` skill. It checks for `DESIGN.md`, Gamut dependencies, `GamutProvider`, import patterns, hardcoded hex colors, and test conventions. Without `DESIGN.md` at the repo root, its color findings are lower-confidence — install with `--theme`, or copy the matching `DESIGN.*.md` manually. + +## Agent skills + +### Exported skills + +Installed into app repos via `gamut plugin install`, from [`packages/gamut/agent-tools/skills/`](https://github.com/Codecademy/gamut/tree/main/packages/gamut/agent-tools/skills/): + +- `gamut-review` — codebase audit playbook, including `styled()` wrappers that bypass system props and bespoke components that duplicate existing ones. +- `gamut-component-first` — check for an existing Gamut component before building bespoke UI; the proactive counterpart to `gamut-review`. +- `gamut-theming` — theme selection, `GamutProvider`, `theme.d.ts`. +- `gamut-color-mode` — ColorMode and semantic color. +- `gamut-buttons` — button atoms, variants, disabled patterns. +- `gamut-modal` — Modal/Dialog composition, dismiss and focus-trap behavior. +- `gamut-z-index` — the z-index scale and stacking order across floating/portalled components. +- `gamut-list` — List, ListRow, ListCol primitives for custom layouts. +- `gamut-menu` — Menu, MenuItem, MenuSeparator for action, navigation, and floating menus. +- `gamut-datatable` — DataTable for sortable/filterable bulk data comparison. +- `gamut-datalist` — DataList for item management with expansion and selection. +- `gamut-select-dropdown` — SelectDropdown single/multi modes, controlled value, and FormGroup wiring. +- `gamut-layout` — spacing scale, breakpoints, page grid. +- `gamut-system-props` — `system.*`/`Box`. +- `gamut-style-utilities` — `css`, `variant`, `states`. +- `gamut-typography`, `gamut-forms`, `gamut-accessibility`, `gamut-testing`. + +### Contributor-only skills + +Available only in the Gamut repo itself, never exported — read natively from [`.claude/skills/`](https://github.com/Codecademy/gamut/tree/main/.claude/skills/) by Claude Code, and by Cursor via its Claude-compatibility path. + +- `gamut-create-skill` — blueprint playbook for authoring a new Gamut agent skill from scratch. + +## Design-to-code (Figma MCP) + +MCP-generated code is experimental — always validate and adapt it before shipping. See [Figma MCP](/foundations/tooling/figma/figma-mcp/) for setup and prompting. [Code Connect](/foundations/tooling/figma/code-connect/) improves MCP output when a design uses connected Gamut components. diff --git a/packages/starlight/src/content/docs/foundations/Tooling/Gamut Plugin/index.md b/packages/starlight/src/content/docs/foundations/Tooling/Gamut Plugin/index.md new file mode 100644 index 0000000000..a76b978ab0 --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/Tooling/Gamut Plugin/index.md @@ -0,0 +1,11 @@ +--- +title: Gamut plugin +description: Install Gamut agent tools in Cursor or Claude Code and add product design context for AI-assisted development. +sidebar: + label: Overview +--- + +Use the `gamut` CLI from `@codecademy/gamut` to install skills and rules into your editor, and optionally copy a product-specific `DESIGN.md` to your app repo root. + +- [Install](/foundations/tooling/gamut-plugin/install/) +- [Best practices](/foundations/tooling/gamut-plugin/best-practices/) diff --git a/packages/starlight/src/content/docs/foundations/Tooling/Gamut Plugin/install.md b/packages/starlight/src/content/docs/foundations/Tooling/Gamut Plugin/install.md new file mode 100644 index 0000000000..f85bd2abac --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/Tooling/Gamut Plugin/install.md @@ -0,0 +1,67 @@ +--- +title: Install +description: Install and update the Gamut plugin for Cursor or Claude Code from the gamut CLI. +--- + +The plugin installs agent context — skills and rules. It doesn't add Gamut packages to your app; see the styleguide's own Installation docs for `package.json` setup, and the `gamut-theming` skill for `GamutProvider` setup. + +Run commands from your app repository root (or wherever you want `DESIGN.md` copied). + +## Install + +```sh +# Cursor (default) — skills and rules +gamut plugin install + +# Cursor + product DESIGN.md at repo root +gamut plugin install cursor --theme core + +# Claude Code + product DESIGN.md +gamut plugin install claude --theme percipio +``` + +### Options + +| Flag | Purpose | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--scope <scope>` | Cursor only. `all` (default): `skills/` and `rules/`. Or one directory: `skills`, `rules`, or `agents` (reserved; empty today). | +| `--theme <theme>` | Copies the matching `DESIGN.*.md` from agent-tools to `./DESIGN.md` at the repo root — product-specific context for agents and the `gamut-review` skill. | +| `--force` | Overwrites an existing `DESIGN.md` when used with `--theme`. | + +`gamut plugin install claude` always registers the full plugin through the Claude marketplace — `--scope` doesn't apply to it. + +### DESIGN.md sources + +| `--theme` | Copies from | Product | +| ------------------- | ---------------------- | ------------------------- | +| `core` | `DESIGN.Codecademy.md` | Codecademy public (Core) | +| `admin`, `platform` | `DESIGN.Codecademy.md` | Admin / learning platform | +| `percipio` | `DESIGN.Percipio.md` | Percipio | +| `lxstudio` | `DESIGN.LXStudio.md` | LX Studio | + +Aliases: `codecademy`, `cc`, `lx-studio`. Without `--theme`, `gamut plugin install` skips `DESIGN.md` — the `gamut-review` skill flags it as missing when auditing. As a manual fallback, copy the matching `DESIGN.*.md` to the repo root and rename it to `DESIGN.md`. + +## Update and remove + +```sh +# Check what is installed +gamut plugin list + +# Refresh after @codecademy/gamut updates +gamut plugin update +gamut plugin update cursor --theme core --force + +# Remove plugin content from an editor +gamut plugin remove +gamut plugin remove claude +``` + +## What gets installed + +From `packages/gamut/agent-tools/`: + +| Directory | Purpose | +| --------- | ---------------------------------------------------------------------------- | +| `skills/` | Task playbooks (theming, ColorMode, system props, forms, testing, and more). | +| `rules/` | Always-on guardrails, like accessibility. | +| `agents/` | Reserved for future Cursor agent definitions; empty in current releases. | diff --git a/packages/starlight/src/content/docs/foundations/Tooling/index.md b/packages/starlight/src/content/docs/foundations/Tooling/index.md new file mode 100644 index 0000000000..437f2b077c --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/Tooling/index.md @@ -0,0 +1,11 @@ +--- +title: Tooling +description: Editor, Figma, and AI tooling that integrates with Gamut. +sidebar: + label: Overview +--- + +Gamut agent tools for Cursor and Claude Code, product `DESIGN.md` context, and Figma workflows for design-to-code. + +- [Gamut plugin](/foundations/tooling/gamut-plugin/) — install Gamut's agent skills/rules into Cursor or Claude Code, and add product-specific design context. +- [Figma](/foundations/tooling/figma/) — the Gamut Make kit, Code Connect, and the Figma Dev Mode MCP. diff --git a/packages/starlight/src/content/docs/foundations/design-tokens.md b/packages/starlight/src/content/docs/foundations/design-tokens.md new file mode 100644 index 0000000000..daffaf4cb7 --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/design-tokens.md @@ -0,0 +1,54 @@ +--- +title: Design tokens +description: Color, spacing, radius, and other token values, and the keys used to access them. +--- + +Every theme exposes the same token categories, each accessible on the Emotion theme object via its own key. Exact values differ per theme — see [Themes](/foundations/themes/) for the available theme palettes, or open Storybook's Theme Switcher (paintbrush icon in the toolbar) to browse live values for any theme/color-mode combination. For the full, exhaustive listing of every value in a category — every color swatch, every spacing step, every border radius — see that theme's own page under Foundations → Theme in Storybook (for example, [Core Theme](https://gamut.codecademy.com/?path=/docs/foundations-theme-core-theme--docs)) rather than a static copy here. + +## Color — `colors` + +- Standard colors — the color atoms Codecademy designs should draw from. +- Solid swatches — accessed as `colors['<color>-<weight>']`. +- Navy/white swatches — semi-transparent except for a few fixed weights (`100`, `navy-800`, `navy-900`); the white weights only show in dark mode, and the navy weights only in light mode. + +See [Color modes](/concepts/color-modes/) for the semantic aliases (`text`, `background`, `primary`, `secondary`) built on top of these raw values. + +## Typography + +- Font family — `fontFamily` +- Font size — `fontSize` +- Font weight — `fontWeight` +- Line height — `lineHeight` + +## Space + +- Spacing scale — `spacing` + +## Layout + +- Breakpoints — `breakpoints`. See [Responsive properties](/foundations/system-props/responsive-properties/) for how these map to the prop syntax. + +## Border radius + +- `borderRadii` + +## Elevation + +- `elevation` — how a surface like Card renders shadow and lift. Three states — `rest`, `hover`, `hoverMirrored` — each with a `shadow` and a `transform` token, accessed as `elevation['<state>-<property>']`. `hoverMirrored` is for a surface that casts its shadow to the right instead of the left. Shadow colors reference the `shadow-primary` alias, so they follow color mode automatically; a theme can supply its own elevation scale — Percipio, for example, swaps the hard offset shadow for a soft, blurred one with no lift. + +## Accessing tokens directly + +Reach for a token directly only when a [system prop](/foundations/system-props/) can't express what you need — most components should consume tokens through system props instead. + +```tsx +// on the theme prop of any styled component +const styles = styled.div` + color: ${({ theme }) => theme.colors.blue}; +`; + +// or imported directly +import { theme } from '@codecademy/gamut-styles'; +const myStyles = css` + font-size: ${theme.fontSize[14]}; +`; +``` diff --git a/packages/starlight/src/content/docs/foundations/eslint-rules.md b/packages/starlight/src/content/docs/foundations/eslint-rules.md new file mode 100644 index 0000000000..8b311ca62a --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/eslint-rules.md @@ -0,0 +1,91 @@ +--- +title: ESLint rules +description: Gamut-specific ESLint rules and what they enforce. +--- + +`eslint-plugin-gamut` enforces consistent styling patterns and correct Gamut usage across projects. + +## `gamut/no-inline-style` + +**Level:** `error` + +Disallows inline `style` props on JSX elements — they can't be optimized by build tools and don't benefit from Gamut's design tokens. Use a styled component, system props, or a `states`-driven styled component instead: + +```tsx +// Instead of: +<Alert style={{ width: isOpen ? '100%' : '50%' }} /> + +// Use system props: +<Box width={isOpen ? '100%' : '50%'} /> +``` + +Disable it for third-party components that require inline styles, like framer-motion animations: + +```tsx +// eslint-disable-next-line gamut/no-inline-style +<motion.div style={{ transformOrigin: 'center' }} /> +``` + +## `gamut/no-css-standalone` + +**Level:** `error` + +Disallows importing standalone `.css`/`.scss` files — they bypass theming and can cause specificity conflicts. Use `css`, `variant`, or `states` from `gamut-styles` instead. + +## `gamut/import-paths` + +**Level:** `error` · Auto-fixable + +Enforces three import conventions: use relative imports within the same package, and never append `/src` or `/dist` to a Gamut package import. + +```tsx +// Instead of: +import { FillButton } from '@codecademy/gamut/src'; + +// Use: +import { FillButton } from '@codecademy/gamut'; +``` + +## `gamut/prefer-themed` + +**Level:** `off` by default · Auto-fixable + +Suggests the `themed()` utility over reaching directly into `theme` inside a styled component, for better type safety: + +```tsx +// Instead of: +const StyledDiv = styled.div` + color: ${({ theme }) => theme.colors.primary}; +`; + +// Prefer: +const StyledBox = styled(Box)(css({ color: themed('colors.primary') })); +``` + +## Configuration + +```js +// .eslintrc.js +module.exports = { + plugins: ['eslint-plugin-gamut'], + rules: { + 'gamut/no-inline-style': 'error', + 'gamut/no-css-standalone': 'error', + 'gamut/import-paths': 'error', + 'gamut/prefer-themed': 'off', + }, +}; +``` + +Override a rule for specific files with `overrides`: + +```js +module.exports = { + overrides: [ + { + files: ['packages/gamut-illustrations/**'], + rules: { 'gamut/no-inline-style': 'off' }, + }, + ], +}; +``` diff --git a/packages/starlight/src/content/docs/foundations/icon-and-asset-catalog.md b/packages/starlight/src/content/docs/foundations/icon-and-asset-catalog.md new file mode 100644 index 0000000000..80de0cf9c9 --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/icon-and-asset-catalog.md @@ -0,0 +1,42 @@ +--- +title: Icon & asset catalog +description: Every icon, illustration, and pattern available, searchable by name. +--- + +Browse the actual catalogs on their own pages: + +- [Icons](/components/media-and-assets/icons/) — [Mini](/components/media-and-assets/icons/mini/) (36 icons) and [Regular](/components/media-and-assets/icons/regular/) (335 icons). +- [Illustrations](/components/media-and-assets/illustrations/) — the full illustration set. +- [Patterns](/components/media-and-assets/patterns/) — the full decorative pattern set. + +## Contributing a new icon + +**Designers:** + +1. Find or request the icon on [Streamline](https://www.streamlinehq.com/) — "Ultimate Regular" for Regular icons, "Core Solid" for Mini icons. +2. Add it to the Figma library, following the design team's guide for [regular icons](https://scribehow.com/shared/How_to_add_a_regular_size_icon_to_the_Figma_library__89he3MpFQj-7d9CP6AFLQg) or [mini icons](https://scribehow.com/shared/How_to_add_a_mini_size_icon_to_the_Figma_library__mDFi_BuaQ-yHDsufs9tlxQ). +3. Hand the SVG off to an engineer — it should come from Streamline directly, not be exported from Figma. + +**Engineers:** + +1. Confirm the SVG matches the expected shape: Regular icons need `viewBox="0 0 24 24"` and `stroke-width="1.5"`; Mini icons need `viewBox="0 0 16 16"` and an outline style. +2. Add the Streamline slug (for example, `delete-1`) as the SVG's `<title>` if it's missing. +3. Add the file to `packages/gamut-icons/src/svg/regular/` or `.../mini/`, named `<icon-name>-icon.svg` (regular) or `mini-<icon-name>-icon.svg` (mini). +4. Run `yarn build-all && yarn start` to see it in Storybook. Regular icons default into the Interface category alphabetically — reassign one to a different category, like Vendor, in `constants.tsx` if needed. +5. Open a PR describing where the icon came from and what it's for. + +**Troubleshooting:** every icon is transformed by a custom Babel plugin so semi-opaque colors render consistently without overlay artifacts — this occasionally causes rendering surprises: + +- If an icon's source SVG has a `<mask>`, ask the designer to remove it; Gamut adds its own mask during the build. +- If an icon sets `fill="none"` on the root `<svg>`, move `fill="none"` onto each `<path>` instead. + +## Contributing a new illustration + +If the illustration you need isn't already in the set: + +1. In Figma, right-click the illustration's layer and copy it as an SVG. +2. Minify it with [SVGOMG](https://jakearchibald.github.io/svgomg/) — the default settings work, except you'll need to add the root `viewBox` attribute back afterward. +3. Paste the SVG into a new component at `packages/gamut-illustrations/src/<IllustrationName>.tsx`. +4. Run the ESLint auto-fixer to correct the SVG's attribute names for React (`stroke-width` → `strokeWidth`, and the like). +5. Export the new component from `packages/gamut-illustrations/src/index.ts`. +6. Open a `feat` pull request. diff --git a/packages/starlight/src/content/docs/foundations/index.md b/packages/starlight/src/content/docs/foundations/index.md new file mode 100644 index 0000000000..e95d97ffa5 --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/index.md @@ -0,0 +1,16 @@ +--- +title: Foundations +description: Design tokens, themes, system props, asset catalogs, and tooling — looked up, not read start to finish. +--- + +Facts you look up rather than read start to finish. + +- [Design tokens](/foundations/design-tokens/) +- [Themes](/foundations/themes/) +- [Layout](/foundations/layout/) +- [Typography](/foundations/typography/) +- [System props](/foundations/system-props/) +- [Style helpers](/foundations/style-helpers/) +- [Icon & asset catalog](/foundations/icon-and-asset-catalog/) +- [ESLint rules](/foundations/eslint-rules/) +- [Tooling](/foundations/tooling/) diff --git a/packages/starlight/src/content/docs/foundations/layout.md b/packages/starlight/src/content/docs/foundations/layout.md new file mode 100644 index 0000000000..cb831ece91 --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/layout.md @@ -0,0 +1,55 @@ +--- +title: Layout +description: Target screen sizes, the grid system, and spacing scale for page layout. +--- + +## Screen sizes + +Six target viewports drive Gamut's design work. These aren't the same as the `breakpoints` token — see [Responsive properties](/foundations/system-props/responsive-properties/) — they're the actual sizes designs are built for; breakpoints are the implementation detail that gets you there. + +| Alias | Dimensions | Max content width | Fold height | +| ----- | ---------- | ----------------- | ----------- | +| XL | 1440×900 | 1248px | 680px | +| LG | 1200×900 | 1072px | 680px | +| MD | 1024×768 | 896px | 680px | +| SM | 768×1024 | 704px | 680px | +| XS | 480×900 | 448px | 440px | +| Base | 320×480 | 288px | 440px | + +## Grid + +Gamut uses a 12-column grid, so every layout divides cleanly into sections of 2 or 3. Set a minimum column span per breakpoint with `size` on a `Column`: + +```tsx +// Object syntax +<Column size={{ _: 6, xs: 4 }}>Content</Column> + +// Array syntax +<Column size={[6, 4]} /> +``` + +The example above spans 6 columns at the smallest screen size, and 4 columns from the next breakpoint up. + +**Best practices:** + +- Start most design work at 1440px width, using the XL column grid. +- Adjust every design for each viewport's own screen height (the "fold"), not just its width. +- On wider layouts, rearrange content to use the extra space well — don't just stretch existing elements to fill it. +- An element without an explicit lockup (like a catalog card) doesn't need to fill a whole column width; aligning to a gridline on one axis, usually the left, is often enough. + +## Spacing + +Layout spacing is a practical subset of the full `spacing` scale — see [Design tokens](/foundations/design-tokens/) for the complete scale. + +| Use | Sizes (px) | +| --------------------- | -------------- | +| Horizontal spacing | 64, 48, 32, 16 | +| Column gaps (gutters) | 32, 24, 16, 8 | +| Row gaps | 32, 24, 16, 8 | + +**Best practices:** + +- The smallest unit of space for block elements is 8px. +- Place type with more precision — headlines, or text relative to other lines of type — on the 4px baseline grid instead. +- Default margins and padding are 16px; use other multiples of 8px for anything else. +- Use extra spacing intentionally, to group or separate elements and establish rhythm — keep objects aligned both vertically and horizontally wherever possible. diff --git a/packages/starlight/src/content/docs/foundations/style-helpers.md b/packages/starlight/src/content/docs/foundations/style-helpers.md new file mode 100644 index 0000000000..9d51f11f1b --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/style-helpers.md @@ -0,0 +1,80 @@ +--- +title: Style helpers +description: Gamut's helper functions for style utilities — unit conversion, shadows, font smoothing, and more. +--- + +## Converting units + +### Pixels to rem + +Gamut prefers `rem` units for most sizing, but a specific rem value isn't always expressive — `1.875rem` for what's really "30px." Use `pxRem` to convert a pixel value (or plain number) to its `rem` equivalent: + +```tsx +import { pxRem } from '@codecademy/gamut-styles'; + +pxRem(16); // => '1rem' +pxRem('30px'); // => '1.875rem' +``` + +## Style helpers + +### Box shadow + +Five depths of box shadow are available, each returning a CSS shadow value: + +```tsx +import { boxShadow } from '@codecademy/gamut-styles'; + +const Example = ` + ${boxShadow(3)} +`; +``` + +### Font smoothing + +Enable font smoothing to make typography more readable: + +```tsx +import { fontSmoothing } from '@codecademy/gamut-styles'; + +// As a template literal partial +const Smooth = ` + ${fontSmoothing} +`; + +// With an explicit value +const Smooth = ` + ${fontSmoothing({ fontSmoothing: 'subpixel' })} +`; +``` + +### No selection + +Prevent a reader from selecting an image or block of text: + +```tsx +import { noSelect } from '@codecademy/gamut-styles'; + +const Unselectable = styled.div` + ${noSelect} +`; +``` + +### Screen reader only + +Hide content visually while keeping it available to screen readers — `screenReaderOnly` keeps it out of the tab order entirely, `screenReaderOnlyFocusable` lets it receive focus: + +```tsx +import { + screenReaderOnly, + screenReaderOnlyFocusable, +} from '@codecademy/gamut-styles'; + +const HiddenLabel = ` + ${screenReaderOnly} +`; + +const HiddenLabelWithFocus = ` + ${screenReaderOnlyFocusable} +`; +``` diff --git a/packages/starlight/src/content/docs/foundations/themes.md b/packages/starlight/src/content/docs/foundations/themes.md new file mode 100644 index 0000000000..214304c470 --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/themes.md @@ -0,0 +1,22 @@ +--- +title: Themes +description: The theme palettes available (Core, Admin, LX Studio, Percipio, and Platform) and their values. +--- + +Every Emotion component in Gamut has typed access to the current theme's tokens, without any extra imports — most commonly through [system props](/foundations/system-props/) or the `theme` prop. + +## Available themes + +- **Core** — Codecademy's own public-facing theme. +- **Admin** — the internal admin/back-office theme. +- **LX Studio** — the theme for LX Studio. +- **Percipio** — the theme for Percipio; replaces Core's hard offset shadows with a soft, blurred one and no lift (see [Design tokens: Elevation](/foundations/design-tokens/#elevation)). +- **Platform** — the shared learning-platform theme. + +## Comparing themes live + +Use Storybook's Theme Switcher (paintbrush icon in the toolbar) to preview any component across every theme, paired with the Color Mode selector to check both light and dark variants of each. This is the fastest way to see a token's actual resolved value — exact hex codes and scale values are theme-specific and aren't reproduced statically here. + +## Choosing a theme + +See [Theming your app](/guides/theming-your-app/) for wiring up `GamutProvider` with the right theme for your product. diff --git a/packages/starlight/src/content/docs/foundations/typography.md b/packages/starlight/src/content/docs/foundations/typography.md new file mode 100644 index 0000000000..6507aab14d --- /dev/null +++ b/packages/starlight/src/content/docs/foundations/typography.md @@ -0,0 +1,28 @@ +--- +title: Typography +description: Type scale, line length, and readability guidelines for text. +--- + +See [Design tokens](/foundations/design-tokens/) for how to reach `fontFamily`, `fontSize`, `fontWeight`, and `lineHeight` in code. This page covers the design guidance around using them. Gamut uses "Title" to distinguish its visual sizing from semantic heading levels — a `title-md` value doesn't necessarily mean `h2`; see [Text](/components/typography/text/) for how the two compose. + +For the full, live-rendered scale — every font family, size, weight, and line-height value with a rendered example — see [Foundations: Typography](https://gamut.codecademy.com/?path=/docs/foundations-typography--docs) in Storybook rather than a static copy here. + +## Line length + +Line length — the number of characters per line — is a major factor in readability. + +- Aim for 45–85 characters per line; 66 is the ideal for web text. +- For most multi-line text areas, keep lines to an average of 66 characters, including spaces. +- For multi-column layouts, don't exceed 50 characters per line, including spaces. +- Adjust line length by changing the text size (to keep the same layout width) or the width of the text container (to keep the same type size) — whichever fits the design better. + +## Alignment + +- Left-align paragraphs by default — it's the easiest to read, and keeps layouts aligned to the grid. +- If you must center-align a paragraph, keep the lines especially short and avoid high word counts. +- Don't right-align text, except for numbers or equations. +- Don't adjust letter spacing. + +## Line height + +Place type boxes on the 8px placement grid, the same as any other element. diff --git a/packages/starlight/src/content/docs/getting-started/build-your-first-page.md b/packages/starlight/src/content/docs/getting-started/build-your-first-page.md new file mode 100644 index 0000000000..46b943a940 --- /dev/null +++ b/packages/starlight/src/content/docs/getting-started/build-your-first-page.md @@ -0,0 +1,134 @@ +--- +title: Build your first page +description: A hands-on tutorial that takes you from an installed Gamut app to a working page with layout, typography, a card, and a button. +sidebar: + order: 2 +--- + +This tutorial builds a small "welcome" page using five Gamut components: `Background`, `GridBox`, `Text`, `Card`, and `FillButton`. By the end you'll have a real page and a feel for how Gamut components compose. + +It assumes you've completed [Installation](/getting-started/installation/) and have `GamutProvider` wrapping your app. + +## 1. Lay down a colored surface + +`Background` paints a semantic color behind its children and keeps text inside it readable in both color modes. Start your page component with one: + +```tsx title="WelcomePage.tsx" +import { Background } from '@codecademy/gamut-styles'; + +export const WelcomePage = () => ( + <Background bg="beige" p={32}> + {/* content goes here */} + </Background> +); +``` + +Run your app. You should see a full-width beige panel with 32px of padding and nothing in it yet. + +## 2. Add a heading + +Drop in `Text` for the page title. The `as` prop controls which HTML element renders, independent of the visual style: + +```tsx +import { Background } from '@codecademy/gamut-styles'; +import { Text } from '@codecademy/gamut'; + +export const WelcomePage = () => ( + <Background bg="beige" p={32}> + <Text as="h1" fontSize={32} fontWeight="title"> + Welcome to Gamut + </Text> + </Background> +); +``` + +Refresh — you now have a styled heading sitting in the beige panel. + +## 3. Arrange content in a grid + +`GridBox` is a `display: grid` container that accepts Gamut's spacing and layout props. Use it to lay a card next to some supporting copy: + +```tsx +import { Background } from '@codecademy/gamut-styles'; +import { Text } from '@codecademy/gamut'; +import { GridBox } from '@codecademy/gamut'; + +export const WelcomePage = () => ( + <Background bg="beige" p={32}> + <Text as="h1" fontSize={32} fontWeight="title"> + Welcome to Gamut + </Text> + <GridBox + gridTemplateColumns={{ xs: '1fr', sm: '1fr 1fr' }} + gap={24} + mt={24} + > + <Text> + Gamut components read theme values for color, spacing, and typography, + so this page already matches the rest of a Gamut app. + </Text> + {/* card goes here */} + </GridBox> + </Background> +); +``` + +The `gridTemplateColumns` object is a responsive prop: one column on small screens, two from the `sm` breakpoint up. + +## 4. Add a card with a call to action + +`Card` gives you a contained surface with optional shadow and hover behavior. Put a `FillButton` inside it: + +```tsx +import { Background } from '@codecademy/gamut-styles'; +import { Card } from '@codecademy/gamut'; +import { FillButton } from '@codecademy/gamut'; +import { GridBox } from '@codecademy/gamut'; +import { Text } from '@codecademy/gamut'; + +export const WelcomePage = () => ( + <Background bg="beige" p={32}> + <Text as="h1" fontSize={32} fontWeight="title"> + Welcome to Gamut + </Text> + <GridBox + gridTemplateColumns={{ xs: '1fr', sm: '1fr 1fr' }} + gap={24} + mt={24} + > + <Text> + Gamut components read theme values for color, spacing, and typography, + so this page already matches the rest of a Gamut app. + </Text> + <Card shadow="soft" p={24}> + <Text as="h2" fontSize={20} fontWeight="title" mb={8}> + Ready to keep going? + </Text> + <Text mb={16}> + Explore the component reference to see everything available to you. + </Text> + <FillButton variant="primary" href="/components/"> + Browse components + </FillButton> + </Card> + </GridBox> + </Background> +); +``` + +## 5. Check your work + +You should now have a page with: + +- A beige background panel with consistent padding. +- A page heading and supporting copy. +- A responsive two-column layout that collapses to one column on narrow screens. +- A card containing a secondary heading, copy, and a primary button that links to the component reference. + +Every prop you used above — `bg`, `p`, `gap`, `mt`, `shadow`, `variant` — comes from Gamut's shared [system props](/foundations/system-props/) or a component's own variant list, which is why they work the same way across components. The [Components](/components/) reference documents each one, and each component page's **Prop Reference** section shows every value a prop like `shadow` or `variant` can take. + +## Next steps + +- Skim [Using this site](/getting-started/using-this-site/) to learn how the rest of the docs are organized. +- Read a [Guide](/guides/) for a specific task, like theming your app or building a form. +- Look up a component's full API in [Components](/components/). diff --git a/packages/starlight/src/content/docs/getting-started/installation.md b/packages/starlight/src/content/docs/getting-started/installation.md new file mode 100644 index 0000000000..dced89c1f0 --- /dev/null +++ b/packages/starlight/src/content/docs/getting-started/installation.md @@ -0,0 +1,106 @@ +--- +title: Installation +description: Add Gamut to a new application and wire up the provider, theme types, and test config. +sidebar: + order: 1 +--- + +## Add the packages + +```bash +yarn add @codecademy/gamut-kit @emotion/react @emotion/styled +``` + +Add the rest of the Gamut scope as no-op peer dependencies so your editor's IntelliSense picks up their types: + +```json title="package.json" +{ + "peerDependencies": { + "@codecademy/gamut": "*", + "@codecademy/gamut-icons": "*", + "@codecademy/gamut-illustrations": "*", + "@codecademy/gamut-patterns": "*", + "@codecademy/gamut-styles": "*", + "@codecademy/gamut-tests": "*", + "@codecademy/variance": "*" + } +} +``` + +## Wrap your app in `GamutProvider` + +```tsx title="index.tsx" +import React from 'react'; +import { render } from 'react-dom'; +import { GamutProvider } from '@codecademy/gamut-styles'; + +import { App } from './App'; + +const rootElement = document.getElementById('root'); + +render( + <GamutProvider> + <App /> + </GamutProvider>, + rootElement +); +``` + +`GamutProvider` does four things every Gamut app needs: + +1. Wraps your app in the theme context. +2. Creates an Emotion cache with Gamut's plugins. +3. Injects global styles and CSS variables. +4. Sets the current color mode context and variables. + +For Next.js, do this in `_app.tsx`. For Gatsby, do it in both `gatsby-ssr.js` and `gatsby-browser.js` via `wrapRootElement`. + +If your app enforces a strict Content-Security-Policy (`style-src` without `'unsafe-inline'`), pass the same nonce your CSP header uses so Emotion's injected `<style>` tags are allowed: + +```tsx +<GamutProvider nonce={yourCspNonce}> + <App /> +</GamutProvider> +``` + +## Add theme types + +Gamut components read their allowed props from your Emotion theme shape, so declare it once: + +```tsx title="theme.d.ts" +import '@emotion/react'; + +// Use whichever theme shape matches your app: `CoreTheme` or `PlatformTheme`. +import { CoreTheme } from '@codecademy/gamut-styles'; + +declare module '@emotion/react' { + export interface Theme extends CoreTheme {} +} +``` + +See the [Emotion TypeScript docs](https://emotion.sh/docs/typescript#define-a-theme) for background on this pattern. + +## Start building + +```tsx +import { Background } from '@codecademy/gamut-styles'; +import { Text } from '@codecademy/gamut'; + +export const App = () => ( + <Background bg="beige"> + <Text as="h1">Hello World!</Text> + </Background> +); +``` + +Continue to [Build your first page](/getting-started/build-your-first-page/) to put a few more components together. + +## Testing with Jest and Babel + +Gamut ships modern JavaScript from `node_modules`, and Jest skips transpiling `node_modules` by default — so importing Gamut in tests can surface parse errors unless your transform opts those packages in. + +```js title="jest.config.js" +transformIgnorePatterns: ['node_modules/(?!(@vidstack/react|@formatjs)/)'], +``` + +Add more scopes inside the non-capturing group if your dependency tree pulls in other untranspiled ESM packages. If tests only start failing after upgrading Gamut or a transitive dependency, compare your Jest/Babel config against Gamut's own and adjust `transformIgnorePatterns` and Babel plugins to match. diff --git a/packages/starlight/src/content/docs/getting-started/using-this-site.md b/packages/starlight/src/content/docs/getting-started/using-this-site.md new file mode 100644 index 0000000000..e21407afc8 --- /dev/null +++ b/packages/starlight/src/content/docs/getting-started/using-this-site.md @@ -0,0 +1,34 @@ +--- +title: Using this site +description: How this site's navigation is organized and what to expect on a component page. +sidebar: + order: 3 +--- + +This site is organized around what you're trying to do, not around Gamut's internal component taxonomy. Knowing the four sections below will get you to an answer faster than browsing will. + +## The four sections + +- **Getting started** — tutorials. Follow these in order if you're new to Gamut; they end with a working page. +- **Guides** — how-to. Task-oriented recipes for things you're already trying to build: theming an app, building a form, supporting dark mode, migrating to logical properties, and similar. +- **Components** — reference, grouped by what a component does (Actions, Containers, Inputs & forms, Navigation, Feedback, Status, Overlays, Data display, Typography, Media & assets, Utilities) rather than by Gamut's Atomic Design tier. If you know roughly what you need ("something to tell the user an action succeeded") but not its name, start with the category; use search if you already know the name. +- **Foundations** — everything else you look up rather than read start-to-finish: design tokens, themes, system props, icon and asset catalogs, ESLint rules, tooling. +- **Concepts** — explanation. Background on how and why the system is built the way it is: architecture, the theming model, color modes, brand, best practices, voice and tone, FAQs. + +If you're not sure which section has what you need, the search bar covers all of them at once. + +## How a component page is laid out + +Every component page follows the same section order, so once you know the pattern you can jump straight to the part you need on any page: + +1. **Header** — title, status, links to Figma and source. +2. **Usage** — when to reach for this component, including a "When NOT to use" list that names the alternative. +3. **Anatomy** — a labeled diagram of the component's parts (skipped for non-visual utilities). +4. **Patterns** — goal-first recipes that need real wiring (state, callbacks, composition), skipped for components with no state to wire up. +5. **Playground or Prop Reference** — a live, embedded Storybook example. If no prop drives a genuinely different look, it's a single **Playground** heading; as soon as a prop does (`variant`, `size`, `state`, and the like), it becomes **Prop Reference** instead, broken into one subheading per prop, each showing every value that prop can take. + +Sections 2–4 are explanation and how-to material; section 5 is reference, and always lives in Storybook rather than being retyped here — the embed is the source of truth. If you're deciding whether to use the component at all, start at Usage; if you just need to see what a prop looks like, skip straight to the bottom. + +## This site is a work in progress + +Gamut's documentation is migrating here from a Storybook-based styleguide. Pages that haven't been migrated yet are marked as such and link back to their current location. If you spot a gap, the [Contributing guide](/guides/contributing-to-gamut/) covers how to add or fix a page. diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/alerts.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/alerts.md new file mode 100644 index 0000000000..6f5d16c3d8 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/alerts.md @@ -0,0 +1,36 @@ +--- +title: Alerts +description: Make clear what you're alerting the reader to, then clarify next steps or the timing of a resolution. +--- + +[Alerts](/components/feedback/alert/) display important information and, if relevant, the actions a reader needs to take to resolve them. + +Alerts are similar to [error messages](/guides/writing-ux-copy/component-guidelines/error-messages/), but the approach differs slightly: an error message explains what went wrong and how to proceed, while with an alert, nothing has gone wrong _yet_. An alert should make clear what the issue or information is, and how to proceed — sometimes the reader needs to act, and sometimes they just need to know that _we'll_ act within a specific timeframe. + +## Examples + +| Type | Example copy | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `general`, with a CTA | "Please verify your email so we can make sure your account is secure. A link has been sent to name@email.com." — CTA: "Resend verification" | +| `success` | "Success! Your content has been assigned." | +| `error` | "We were unable to add this team member. Please refresh the page and try again." | +| `notice`, with a CTA | "Maintenance: Codecademy will be offline between 2AM and 4AM EST." — CTA: "Learn more" | +| `feature`, with a CTA | "Update your profile with your name to help your team with account management." — CTA: "Add your name" | + +## Best practices + +- **Start with the full context in mind.** Before writing, know the answers to: + 1. What will a learner be trying to do when this alert appears? + 2. What might happen if they don't receive this information? + 3. Does the learner need to act to remove the alert? + 4. If they can't resolve it themselves, what can we tell them to set expectations? +- **Explain and clarify how to proceed.** State the issue or information, and how to proceed. If there's nothing the reader can do, don't leave them hanging — give a timeline, or point them to support or an FAQ. +- **Write conversationally.** Alerts easily end up sounding robotic. Try explaining the situation out loud first, then write it down that way. +- **Front-load the most important information.** Alert text truncates after the first line, with an option to expand — put the most important part first. + +## Checklist + +- Does the alert explain the information or issue clearly? +- If relevant, have you explained what the reader can do, or what we're doing on our end? +- Is the important information front-loaded? +- Has someone unrelated to the project read the message and understood it? diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/alternative-text.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/alternative-text.md new file mode 100644 index 0000000000..5b0507d665 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/alternative-text.md @@ -0,0 +1,18 @@ +--- +title: Alternative text +description: Alternative text, or alt text, is a brief description of an image for readers who can't view it. +--- + +Alt text is a short written description of an image, read by screen readers and search engines, and shown in the browser on hover or when an image fails to load. + +## Best practices + +- Skip "picture of" or "image of" — a screen reader already announces the element as an image. +- Be descriptive. Alt text should carry all the information the image is being used to convey. +- Only write alt text for non-decorative images — decorative images don't add useful context, so their alt text can be left empty. +- Avoid images that contain text. If it can't be avoided, use the same text in the alt text as appears on the image. + +## Checklist + +- Is alt text included for every non-decorative image on the page? +- Does the alt text provide everything a reader needs to understand what the image conveys? diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/buttons.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/buttons.md new file mode 100644 index 0000000000..9d31beba56 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/buttons.md @@ -0,0 +1,23 @@ +--- +title: Buttons +description: Be specific, use action-oriented language, and emphasize value. Keep to 3 words or less, and use sentence case. +--- + +Buttons let learners "speak" to us — they select a button to tell us what they want (for example, **Try pro for free** or **Resume learning**), and we respond with the expected next step. See [Buttons](/components/actions/buttons/) for the actual component API. + +## Best practices + +- **Be specific.** A reader should know exactly what they're getting, or what happens next, when they select a button. If a button leaves you unsure, adjust the copy until it doesn't. Specificity also matters for accessibility — it's what a screen reader announces for the control. +- **Use action-oriented language.** Start with an action verb, rather than simply naming a destination. Use **Take the quiz**, not "Language quiz." +- **Emphasize the value people get, not how they get it.** Use **Try Pro for free**, not "Opt in." +- **Use the established system patterns for navigational and purely functional buttons** — **Next**, **Back**, **Close**. +- **Use the same word for the same thing**, to build trust and keep things clear — **Go to payment**, **Complete payment**, **Cancel payment**. +- **Avoid pronouns on the button itself** — in the moment, "you" could mean either Codecademy or the learner. Use **Go to dashboard**, not "Go to your dashboard." +- **When in doubt, A/B test.** CTA copy can meaningfully affect conversion — test it, especially for high-impact projects. + +## Checklist + +- Does the copy make it clear where the button will take a reader? +- Does the button lead with an action verb? +- Is the microcopy 3 words or less, especially for the primary button? +- Have you used sentence case, capitalizing only proper nouns? diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/confirmation-dialogs.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/confirmation-dialogs.md new file mode 100644 index 0000000000..bfa0c4e498 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/confirmation-dialogs.md @@ -0,0 +1,41 @@ +--- +title: Confirmation dialogs +description: Use the same verb from the triggering button through to the confirmation button, communicate consequences clearly, and keep the copy decision-focused. +--- + +Confirmation dialogs use [Dialog](/components/overlays/dialog/) to create intentional friction, verifying that a reader wants to take a high-impact action, such as: + +- An irreversible action, like submitting payment. +- Loss of data, time, or work, like deleting a course. +- An unexpected consequence, like losing learning history on an existing prototype when generating a new one. + +That friction improves trust and avoids unintentional actions, by making sure a reader understands the consequences before continuing — and gives us a place to offer alternatives or an undo option when one exists. + +## Best practices + +**Heading** + +- Ask or inform about one main action, mirroring the button that triggered the dialog. +- Frame the heading as a binary question when possible, with two unambiguous answers. +- Avoid a generic "Are you sure?" heading or body — it adds cognitive load without adding information, and can read as patronizing. + +**Body (optional)** + +- Add only the essential contextual consequences: what will happen, what will be lost or changed, and any critical conditions. +- Skip the body entirely if the heading is already self-explanatory. +- Keep it to 1–2 lines, unless more is genuinely needed. + +**Buttons (CTA1 and CTA2)** + +- Avoid "Yes"/"No" — they can be misread in global English and internationalization contexts. +- CTA1 matches the verb from the heading, to confirm the action. +- CTA2 clarifies the alternative or undo path. Be specific about the alternative where space allows; "Cancel" is a fallback when it doesn't. + +## Checklist + +- Is the action irreversible, destructive, or does it have unexpected consequences? If not, consider a different pattern. +- Does the same verb carry through the triggering action, the heading, and CTA1? +- Have you avoided filler language like "Are you sure you want to...?" +- Does the body front-load the critical consequence, in 1–3 lines? +- Are the buttons mutually exclusive, and do they avoid "Yes"/"No"? +- Is there a safer alternative or undo worth offering as CTA2? diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/error-messages.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/error-messages.md new file mode 100644 index 0000000000..3c0ba29f93 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/error-messages.md @@ -0,0 +1,26 @@ +--- +title: Error messages +description: Above all else, error messages should explain and resolve — say what went wrong, and how to proceed. +--- + +Error messages help a learner get where they're going. If they hit a snag — whether it's something they did or a problem on our end — the error message should tell them what went wrong, and how to proceed. We primarily use two components for this: red text below a form field, from [Input](/components/inputs-and-forms/input/), or an error [Alert](/components/feedback/alert/) banner. + +## Best practices + +- **Start with the full context in mind.** Before writing, know the answers to: + 1. What will a learner be trying to do when this error appears? + 2. Why might the system respond with an error? + 3. What can they do to get past it and finish what they set out to do? + 4. If there's nothing they can do to fix it, what alternative can we offer, like contacting support or reading an FAQ? +- **Explain and resolve.** Say what went wrong, and how to proceed. If there's genuinely no way to proceed, don't leave the reader hanging — point them to support or documentation. +- **Write conversationally.** Error messages easily end up sounding robotic. Try explaining the problem and its fix out loud first, then write it down that way. +- **Don't blame the learner**, even when the error is their fault. "You entered an invalid username" assigns blame; "We couldn't find an account with that username" doesn't. +- **Avoid the words "error" or "failure."** Focus on what went wrong, not on the fact that something went wrong at all. + +## Checklist + +- Does the message explain what went wrong? +- Does it explain how the reader can resolve the issue? +- Does it help the reader move forward, or make clear that they can't? +- Does it avoid the words "error" and "failure"? +- Has someone unrelated to the project read the message and understood it? diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/index.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/index.md new file mode 100644 index 0000000000..088f5fc540 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/index.md @@ -0,0 +1,17 @@ +--- +title: Component guidelines +description: Get up to speed on best practices for writing for user interfaces, including guidance for specific Gamut components. +sidebar: + label: Overview +--- + +Writing on your own? Use the [DIY UX writing in 8 steps](/guides/writing-ux-copy/diy-ux-writing-in-8-steps/) guide to make sure the copy is clear and useful. + +- [Alerts](/guides/writing-ux-copy/component-guidelines/alerts/) +- [Alternative text](/guides/writing-ux-copy/component-guidelines/alternative-text/) +- [Buttons](/guides/writing-ux-copy/component-guidelines/buttons/) +- [Confirmation dialogs](/guides/writing-ux-copy/component-guidelines/confirmation-dialogs/) +- [Error messages](/guides/writing-ux-copy/component-guidelines/error-messages/) +- [Notifications](/guides/writing-ux-copy/component-guidelines/notifications/) +- [Toasts](/guides/writing-ux-copy/component-guidelines/toasts/) +- [Tooltips](/guides/writing-ux-copy/component-guidelines/tooltips/) diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/notifications.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/notifications.md new file mode 100644 index 0000000000..05ba6f753d --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/notifications.md @@ -0,0 +1,19 @@ +--- +title: Notifications +description: Keep it brief (under 86 characters), make it actionable, and keep the messaging in support of the learner's journey. +--- + +Bell notifications share CTAs relevant to a learner's personal experience — a change to a course they're taking, a response to a forum post, or another call to action that supports their learning journey. + +## Best practices + +- **Notifications are personal.** Surface only calls to action relevant to that specific learner's experience. +- **Notifications support focused learning.** They should support a learner's journey, not distract from it — keep the messaging relevant to that learner's own interactions, content, goals, and interests. +- **Notifications are actionable.** Every notification needs a personalized, clickable action. If there's no relevant action to take, use a different communication channel instead. +- **Notifications are brief.** No more than 86 characters, and at most 3 lines. Unless otherwise specified, or removed by the learner, a notification disappears after 30 days. + +## Checklist + +- Does the notification include an action for the learner to take? +- Is the action clear? +- Is the notification copy 86 characters or less? diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/toasts.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/toasts.md new file mode 100644 index 0000000000..7fe7060e56 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/toasts.md @@ -0,0 +1,19 @@ +--- +title: Toasts +description: Keep it brief, make the message personal and relevant, and avoid distracting from the learning experience. +--- + +[Toasts](/components/feedback/toast/) are small messages in the bottom-right corner of the screen. They're most commonly used for immediate feedback when a learner completes a task in the Learning Environment — unlocking an achievement, for example — but can also communicate other information relevant to a learner's personal experience, like announcing a new platform feature to the learners who can use it. + +A toast can include any combination of a title, a description, and an icon (inline with the title, or left-justified). For example, a toast titled "📣 Calling all Chromebook users!" with the body "You can now install Codecademy as a free app directly on your Chromebook. Get the app." + +## Best practices + +- **Keep it brief.** At most 3 lines total, including an optional title. For a title plus a 2-line description, aim for a 30-character title and a 70-character description. +- **Make it personal and relevant.** Only show a toast to the learners it actually applies to — the Chromebook example above should only reach learners using Codecademy on a Chromebook. +- **Avoid distracting from learning.** Be cautious using toasts in the Learning Environment. If a message isn't pertinent to the learning experience itself, communicate it outside the LE instead. + +## Checklist + +- Is the toast copy at most 3 lines, including the title? +- Is the message relevant to every learner who will see it? diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/tooltips.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/tooltips.md new file mode 100644 index 0000000000..3001f51e03 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/Component Guidelines/tooltips.md @@ -0,0 +1,26 @@ +--- +title: Tooltips +description: Keep tooltips short and useful. Avoid using them to share information that's vital for learners to complete tasks. +--- + +[Tooltips](/components/feedback/tips/tooltip/) are short, helpful messages that appear when a learner hovers over an informative "i" icon, button, or other icon, providing extra context — for example, explaining why we ask for certain information. They shouldn't carry information a learner needs to complete a task. + +## Best practices + +- **Don't hide vital information in a tooltip.** If the information is necessary to complete a task, put it directly on the page instead — not somewhere a learner has to go looking for it. +- **Provide useful information.** Don't add a tooltip just to have one; it should add context, not repeat what's already on screen. +- **Keep it short.** Use just enough words to get the point across. + - Keep informational tooltips under 90 characters where possible, and no more than 135. + - Keep tooltips that just define an icon to 1–2 words. +- **Provide tooltips for unlabeled icons**, so a learner understands what clicking one will do. +- **Don't include links.** If you're tempted to link out from a tooltip, find another way to surface that information instead — a tooltip's value is not having to leave the screen. + +## Checklist + +- Is the information in the tooltip actually useful? +- Does it avoid repeating information already visible on screen? +- Is every piece of information a learner needs to complete a task visible _outside_ the tooltip? +- Does the tooltip meet the length guidelines above? +- Is the tooltip text-only, with no links to other pages? +- Is the copy at a 7th-grade reading level or below? Check with [Hemingway App](https://hemingwayapp.com). +- Has someone unrelated to the project read the message and understood it? diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/accessibility-guidelines.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/accessibility-guidelines.md new file mode 100644 index 0000000000..79fe0fb5bd --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/accessibility-guidelines.md @@ -0,0 +1,97 @@ +--- +title: Accessibility guidelines +description: In order for content to be accessible, it must be perceivable, operable, understandable, and robust. +--- + +The Web Content Accessibility Guidelines ([WCAG 2.2](https://www.w3.org/TR/WCAG22/)) define four principles for creating accessible content. Understanding them is key to writing accessible copy. + +## 1. Perceivable + +Information on screen must be perceivable to blind, deaf, low-vision, or color-blind learners. + +- Include meaningful and unique alt text for every non-decorative image or icon. +- Don't rely on visuals for words to make sense. +- If text is meant to be read, include it as text, not an image. +- Include captions for video and audio content. + +## 2. Operable + +Content should be easy to navigate, with touch and click targets that are easy to hit and content that's accessible with a screen reader. + +- Make hyperlink text long enough to be easy to hover over and click. +- Use meaningful, unique, and descriptive CTAs — avoid ambiguous link text like "Click here" or "Read more." +- If more than one label contains the same text, clarify with screen-reader-only text. +- Write so labels, tooltips, and input fields appear in a logical order. +- Avoid directional language, like "above" or "below." +- Give readers enough time to read the text. + +## 3. Understandable + +Content should be easy to understand, and appear and operate in predictable ways. + +- Use `h1`/`h2`/`h3` headings correctly and consistently. +- Check that copy sits at a 7th-grade reading level or below. +- Use short, clear sentences and paragraphs. +- Avoid jargon, slang, and idioms. +- Expand acronyms on first use. +- Use list formatting when appropriate. + +## 4. Robust + +- Write in a way that's platform-agnostic — "select" or "choose," not "tap" or "click." + +### A note on "meaningful" + +You'll see the word "meaningful" a lot in accessibility guidance. Meaningful text is useful text — it gives a reader the full context they need to understand a situation. Meaningful alt text describes what's important about an image a reader can't see; meaningful button text makes clear where a link goes, rather than an ambiguous standalone phrase like "click here" or "read more." + +## Accessible content checklist + +**Alt text** + +- Is alt text included for every non-decorative image on the page? +- Does the alt text provide everything a reader needs to understand what the image conveys? + +**Buttons** + +- Is the CTA meaningful — is it clear where clicking will take the reader? +- If there's more than one button on the page, is each button's text unique? +- If a button's text isn't unique, have you provided screen-reader-only text to clarify? + +**Headings** + +- Does the page include a single, unique `h1` title? +- Are `h2` and `h3` headings used correctly and consistently? + +**Hyperlinks** + +- Is the link text meaningful — is it clear where clicking will take the reader? +- Are hyperlinks long enough (2–3 words) to be easy to click? +- If there's more than one hyperlink on the page, is each link's text unique? +- If a link's text isn't unique, have you provided screen-reader-only text to clarify? + +**Imagery** + +- Do all non-decorative images or icons contain [alt text](/guides/writing-ux-copy/component-guidelines/alternative-text/)? +- Does the alt text provide everything a reader needs to understand what the image conveys? +- Are all words understandable without relying on imagery? + +**Readability** + +- Do readers have enough time to read the text? +- Is the reading level grade 7 or below? Check with [Hemingway App](https://hemingwayapp.com). +- Are sentences and paragraphs short and clear? +- Is the content free of jargon, slang, and idioms? +- Have you used list formatting where appropriate? + +**Screen-reader compatibility** + +- Does text appear in chronological order? +- Are headings used correctly and appropriately? +- Have you avoided directional language, like "the form on the right" or "the section below"? +- Is the language platform-agnostic? +- Have you passed the buttons, headings, hyperlinks, imagery, and readability checklists? + +**Video and audio content** + +- Are captions included for all audio and video content? +- For video, do the captions match what's happening on screen? diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/diy-ux-writing-in-8-steps.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/diy-ux-writing-in-8-steps.md new file mode 100644 index 0000000000..915dc71949 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/diy-ux-writing-in-8-steps.md @@ -0,0 +1,64 @@ +--- +title: DIY UX writing in 8 steps +description: Follow these steps to write clear, useful UI copy. +--- + +For best practices writing for a specific component — alerts, errors, confirmation dialogs, and the rest — see [Component guidelines](/guides/writing-ux-copy/component-guidelines/). + +## 1. Consider the big picture + +Before you start writing, consider the full context: + +- Where will learners be in their journey when they see this message? +- What is the learner's goal, and how will this message help them achieve it? +- What is our business goal, and how will this message help achieve it? +- What questions might learners have that we should answer? +- Make a list of all the information this message needs to address. + +## 2. Say it out loud, then write it down + +Once you know what information you need to include, say it out loud as if you're explaining the situation to one of our learners — speak directly to them, using "you." Then write it down. This is your first draft, and saying it out loud first helps keep the writing natural and conversational. + +## 3. Cut any unnecessary words + +Cut any words that aren't necessary for learners to understand the message, and remove anything repetitive or redundant. You can always add words back in afterward to make the message sound friendlier if it feels too robotic once trimmed. + +## 4. Use consistent language + +Match the words you use to the words already used elsewhere in the product for the same thing, including within a single component — between its heading, body copy, and button copy. Switching terms is an unnecessary source of confusion for learners. + +## 5. Ensure language includes everyone + +Use inclusive language that makes all learners feel welcome. Avoid idioms, slang, humor, and jargon, to keep the copy accessible to a global audience — and make sure every element has text a screen reader can speak, rather than relying on visuals alone. + +## 6. Do a readability check + +Paste your writing into [Hemingway App](https://hemingwayapp.com/): + +- Aim for a 7th-grade reading level or below. +- Simplify any sentence flagged "hard to read" or "very hard to read." + +## 7. Do a style check + +Check your writing against Codecademy's brand voice guidelines, and keep these in mind: + +- Use sentence case for headings and button copy. +- Always use oxford commas. +- Put spaces around em dashes ( — ). +- Use exclamation points sparingly; default to periods. +- Use periods at the end of complete sentences. + +## 8. Have someone else read your work + +Show the finished copy to someone unfamiliar with the project. They should be able to understand the information you listed out in step 1 without extra context. + +**When you're done, your copy should:** + +- Meet both user needs and business objectives. +- Sound natural and conversational. +- Be concise. +- Use consistent language. +- Be accessible and inclusive. +- Be easy to read and understand. +- Be on-brand. +- Clearly get the intended message across. diff --git a/packages/starlight/src/content/docs/guides/Writing UX Copy/index.md b/packages/starlight/src/content/docs/guides/Writing UX Copy/index.md new file mode 100644 index 0000000000..315759fe94 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/Writing UX Copy/index.md @@ -0,0 +1,21 @@ +--- +title: Writing UX copy +description: Voice, tone, and copy conventions for Gamut components. +sidebar: + label: Overview +--- + +Words are one of the most powerful tools for helping learners find success — surfacing the right copy and content to the right people at the right time. + +- [DIY UX writing in 8 steps](/guides/writing-ux-copy/diy-ux-writing-in-8-steps/) — a step-by-step process for writing clear, useful UI copy on your own. +- [Accessibility guidelines](/guides/writing-ux-copy/accessibility-guidelines/) — the four principles of accessible content, and a checklist to apply them. +- [Component guidelines](/guides/writing-ux-copy/component-guidelines/) — copy conventions for alerts, buttons, confirmation dialogs, error messages, notifications, toasts, tooltips, and alt text. + +Need help along the way? Reach out on Slack, or [submit a UX writing request](https://skillsoftdev.atlassian.net/wiki/spaces/UXW/database/4482760850?savedViewId=6f9f7fdc-f2b8-4746-b99b-689d804c979b). + +## Resources + +- [Hemingway App](https://hemingwayapp.com/) — check copy for grade level and readability. +- [Capitalize My Title](https://capitalizemytitle.com/style/AP/) — properly capitalize a title, AP style. +- [The UX Writing Library](https://www.uxwritinglibrary.com/) — a large compilation of UX writing tools and resources. +- [Polish](https://www.producthunt.com/posts/polish) — a browser extension for editing the text on any website. diff --git a/packages/starlight/src/content/docs/guides/building-forms.md b/packages/starlight/src/content/docs/guides/building-forms.md new file mode 100644 index 0000000000..af253abb71 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/building-forms.md @@ -0,0 +1,125 @@ +--- +title: Building forms +description: Compose form scaffolding, inputs, and ConnectedForm into a working form. +--- + +## Choosing an approach + +Three ways to build a form in Gamut, from most to least opinionated: + +- [GridForm](/components/inputs-and-forms/gridform/) — pass a flat array of field objects and get a validated, laid-out form for free. Fastest path for a standard form; reach for this first. +- [ConnectedForm](/components/inputs-and-forms/connectedform/) — compose `ConnectedFormGroup`/`ConnectedFormInputs` yourself, still wired to [react-hook-form](https://react-hook-form.com) validation and state, without GridForm's grid structure. Use this when a form's layout genuinely can't fit GridForm's grid. +- [Form scaffolding](/components/inputs-and-forms/form/) — the raw atoms (`Form`, `FormGroup`, `FormGroupLabel`, `FormGroupDescription`, `FormRequiredText`) with no validation or state wiring at all. Reach for these directly only when an interface needs form-shaped structure without an actual form behind it. + +## Building a basic form with GridForm + +Pass `fields` — a flat array of plain objects, each with a `type` discriminator — and `submit`: + +```tsx +import { GridForm } from '@codecademy/gamut'; + +export const ContactForm = () => ( + <GridForm + fields={[ + { + type: 'text', + name: 'name', + label: 'Name', + validation: { required: 'Enter your name' }, + }, + { + type: 'text', + name: 'email', + label: 'Email', + validation: { + pattern: { + value: /^\S+@\S+$/, + message: 'Enter a valid email address', + }, + }, + }, + { type: 'textarea', name: 'message', label: 'Message' }, + ]} + submit={{ children: 'Send' }} + onSubmit={(values) => submitContactForm(values)} + /> +); +``` + +See [GridForm: Reference](/components/inputs-and-forms/gridform/reference/) for every field type, and [GridForm: Patterns](/components/inputs-and-forms/gridform/patterns/) for sections, layout recipes, and state handling. + +## Validating fields + +Each field's `validation` accepts [react-hook-form](https://react-hook-form.com)'s `RegisterOptions` directly (`required`, `pattern`, `validate`, `minLength`/`maxLength`, `min`/`max`): + +```tsx +// Required, with a custom message +{ type: 'text', name: 'email', label: 'Email', validation: { required: 'Please enter your email address' } } + +// Custom validate function +{ + type: 'file', + name: 'upload', + label: 'Upload', + validation: { + validate: (files) => { + const file = files[0]; + if (!['image/png', 'image/jpeg'].includes(file.type)) return 'PNG or JPEG only'; + return true; + }, + }, +} +``` + +The form-level `validation` prop controls when validation runs — `"onSubmit"` (default), `"onChange"`, or `"onTouched"`. See [GridForm: Reference](/components/inputs-and-forms/gridform/reference/#validation) for the complete rule catalog. + +## Handling submit state + +Combine `disableFieldsOnSubmit`, `resetOnSubmit`, and your own `submit.loading` state, driven by your `onSubmit` handler, into a full submit lifecycle: + +```tsx +const [loading, setLoading] = useState(false); + +<GridForm + fields={fields} + submit={{ children: 'Save', loading }} + disableFieldsOnSubmit + onSubmit={async (values) => { + setLoading(true); + await save(values); + setLoading(false); + }} +/>; +``` + +See [GridForm: Patterns](/components/inputs-and-forms/gridform/patterns/#combining-loading-disabling-and-resetting) for the full recipe. + +## When GridForm's grid doesn't fit + +Use [ConnectedForm](/components/inputs-and-forms/connectedform/) instead — the same react-hook-form-backed validation and required/disabled state, without GridForm's grid structure. The `useConnectedForm` hook is the entry point: + +```tsx +import { ConnectedInput, useConnectedForm } from '@codecademy/gamut'; + +export const CustomLayoutForm = () => { + const { ConnectedForm, ConnectedFormGroup, connectedFormProps } = + useConnectedForm({ + defaultValues: { email: '' }, + }); + + return ( + <ConnectedForm + onSubmit={(values) => submit(values)} + {...connectedFormProps} + > + <ConnectedFormGroup + name="email" + label="Email" + field={{ component: ConnectedInput }} + /> + </ConnectedForm> + ); +}; +``` + +See [ConnectedForm](/components/inputs-and-forms/connectedform/) for the full pattern, including every connected input type. diff --git a/packages/starlight/src/content/docs/guides/contributing-to-gamut.md b/packages/starlight/src/content/docs/guides/contributing-to-gamut.md new file mode 100644 index 0000000000..ec5a313c31 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/contributing-to-gamut.md @@ -0,0 +1,295 @@ +--- +title: Contributing to Gamut +description: How to propose, build, and document changes to Gamut. +--- + +## Prework + +We track planned work for Gamut components in the [Gamut Board](https://skillsoftdev.atlassian.net/jira/software/c/projects/GMT/boards/3655/backlog) on JIRA. + +- If there's a ticket there you want to take on, send a Slack message to `#gamut-team` or come to Gamut Office Hours to talk it through. +- If the work you'd like to do isn't captured in a JIRA ticket, talk to us first and we'll help create one. +- To request work be done, discuss it with us on Slack or during Gamut Office Hours. +- To pitch a change to the design system, attend Gamut Crit, come to Gamut Office Hours, or message `#gamut-team`. + +## Writing components + +### Component structure + +Create your component as an `index.tsx` file in a PascalCase-named folder within its package directory — for example, `packages/gamut/src/ProgressBar/index.tsx`. Consider saving this shape as an editor snippet: + +```tsx +import React from 'react'; + +export type MyComponentProps = { + /* ... */ +}; + +export const MyComponent: React.FC<MyComponentProps> = ( + { + /* ... */ + } +) => { + // ... +}; +``` + +### Naming conventions + +Clear, descriptive names reduce the need for comments and make code self-documenting. + +**Variables and constants** + +- Use `camelCase`: `userName`, `isLoading`, `itemCount`. +- Use names that reveal purpose: `filteredResults`, not `arr`. +- Prefix booleans with `is`, `has`, `should`, or `can`: `isVisible`, `hasError`, `shouldRender`. +- Use `SCREAMING_SNAKE_CASE` for true constants: `MAX_RETRY_COUNT`, `DEFAULT_TIMEOUT`. +- Avoid single-letter names, except in short loops or math. +- Use plural names for arrays and collections: `users`, `menuItems`. + +**Functions and methods** + +- Use `camelCase`, starting with a verb that describes the action: `get`, `set`, `fetch`, `handle`, `render`, `calculate`. +- Prefix event handlers with `handle`: `handleSubmit`, `handleClickOutside`. +- Phrase a boolean-returning function as a question: `isValidEmail`, `canAccessResource`, `hasPermission`. +- Keep names concise but descriptive: `fetchUserProfile`, not `getUserProfileDataFromAPI`. + +**Components** + +- Use `PascalCase`: `Button`, `UserProfile`, `NavigationMenu`. +- Name the folder to match the component, and the file inside it to match the folder: `Button/Button.tsx`. +- Use names that indicate purpose — `SkipToContent`, `RadialProgress`, `Toggle` — and avoid generic ones like `Component`, `Container`, or `Wrapper` without further context. + +### Consistency + +- Use a single term for the same concept, in the heading, body copy, and code examples alike — and don't reuse a term for two different concepts. +- Keep component naming consistent across packages, following the patterns established by existing components. + +### Code comments + +Comments should explain _why_ code exists, not _what_ it does — well-named variables and functions already handle the "what." Reserve comments for non-obvious decisions, complex logic, and important context: + +```tsx +// Use binary search for O(log n) performance on sorted arrays +const index = binarySearch(sortedArray, target); + +// Per WCAG 2.2, focus must return to the trigger element on close +previousFocusRef.current?.focus(); + +// Safari doesn't support :focus-visible, fallback to :focus +// TODO: Remove when Safari 15+ is the minimum supported version + +// Delay state update to avoid a race condition with async validation +setTimeout(() => setIsValid(true), 0); +``` + +Skip a comment when the code is already self-explanatory: + +```tsx +// Avoid: the comment only restates the code +// Set loading to true +setIsLoading(true); + +// Prefer: the code is already self-documenting +setIsLoading(true); +``` + +Delete commented-out code instead of leaving it in place — git already tracks its history. + +**Style:** use `//` for single-line comments, with a space after the slashes; use `/** */` JSDoc comments on exports (functions, types, components); write complete sentences with proper punctuation; keep comments up to date as the code changes. + +### Props documentation + +Add a [JSDoc](https://jsdoc.app/) comment to a prop unless it's widespread and self-documenting, like `onClick`. These comments show up in TypeScript on hover, and in the props table of the component's Storybook story: + +```tsx +export type ButtonProps = { + /** + * The visual style variant of the button. + */ + variant: 'primary' | 'secondary'; + + /** + * Whether the button is disabled. + */ + disabled?: boolean; +}; +``` + +- Write full sentences. +- Start a boolean's description with "Whether". +- Document which props are required versus optional, and include type information. +- Use your judgment on borderline cases — when unsure, include the comment. + +### Unit tests + +Add unit tests in a `__tests__/MyComponent-test.tsx` file within the component's directory, using `setupRtl` from `gamut-tests`. Unit test all component logic, with the exception of class names on components that already contain other tested logic. + +## Writing stories + +Every component needs Storybook stories demonstrating its use, in a `.stories.tsx` file alongside a `.mdx` documentation file. This structure is the source every component page's `StoryEmbed`s pull from, so both files need to stay accurate. + +### File structure and naming + +The folder structure mirrors both Gamut's atomic-design tiers and the generated Storybook hierarchy. Find the right folder under `packages/styleguide/src/lib` (`Atoms`, `Molecules`, `Organisms`, and so on), then create a new folder containing `ComponentName.stories.tsx` and `ComponentName.mdx` — plus any example or utility files the stories need. + +- Non-component files with more than one word use a space and sentence case: `General principles.mdx`. +- Component-related files use the component's own `PascalCase` name: `RadialProgress.mdx`. + +### Writing the `.mdx` documentation file + +A component's `.mdx` file combines its interactive stories with written documentation, usage guidance, and metadata. A good one has four parts: + +1. **General information** — set in the file's `parameters` object: `title` (the component's name, used for linking), `subtitle` (what it does and when to reach for it), `source` (its package and a GitHub link), `design` (a Figma link), and `status`: + - `current` — stable, recommended for use. + - `updating` — in progress; the API may still change. + - `deprecated` — still supported, but slated for removal — don't use it for new work. + - `static` — reference material, with no active development. +2. **Flagship story and props** — a single default story showing the component's baseline state, with `sourceState="shown"` on its `Canvas` so the code is visible, and a connected props table right below it. +3. **Variation stories** — a subsection per meaningful behavior or configuration, each showing one variation with a short description and any variant-specific guidance. +4. **Usage instructions** — when to use the component (and when not to), plus any guidelines a reader should follow. + +### Writing the `.stories.tsx` code file + +Use concrete, realistic example values instead of placeholders like `foo`/`bar` — a boolean controlling a modal should be named `isModalOpen`, not `isBar`, so the example reads like something a consumer would actually write. + +Don't abstract a story's rendering into a separate helper component just to stay DRY — Storybook's "Show code" button can't see through that abstraction, so a reader who wants to copy the example gets an unhelpful stub instead of working code: + +```tsx +// Avoid: hides the real code behind an abstraction +export const Default: Story = { + render: (args) => <InfoTipExample {...args} />, +}; + +// Prefer: the actual code a reader can copy and use +export const Default: Story = { + render: (args) => ( + <FlexBox center m={24} py={64}> + <Text mr={4}>Some text that needs info</Text> + <InfoTip {...args} /> + </FlexBox> + ), +}; +``` + +### Group overview pages + +When a folder holds more than one related component or story, add an `About.mdx` file as its landing page — for example, the Icons folder's `About.mdx` links out to its Mini and Regular sub-pages. Give it a clear overview of what the folder contains and how its components relate, organized by importance or usage frequency, and keep it concise — it's an entry point, not detailed documentation. + +## Formatting + +**Numbers and units** + +- Use numerals for all numbers, with commas for thousands (1,000). +- Use standard units — `px`, `rem`, `em`, `%`. +- In prose, put a space between a number and its unit ("16 pixels"); in code, don't ("16px"). + +**Lists** + +- Bulleted lists are for unordered items — keep them in parallel structure, and end each item with a period only if it's a complete sentence. +- Numbered lists are for sequential steps or prioritized items — start each item with a capital letter. + +**Code blocks** + +- Use triple backticks with a language identifier (` ```tsx `, ` ```javascript `, ` ```css `). +- Include comments for complex examples, and keep examples concise and focused. + +**Headings** + +- Start at the second level (`##`) — the first level is set automatically from the page's title. +- Don't skip a heading level; it breaks the reading order. + +**Whitespace** + +- Separate sections with a blank line, and never stack multiple consecutive blank lines. +- Indent code consistently — 2 spaces for TypeScript/TSX, with tabs set to 2 spaces if you use them. + +## Linking + +**Internal links** + +In Storybook's own `.mdx` files, use the `LinkTo` component with an `id` matching the target story's id: + +```tsx +import { LinkTo } from '~styleguide/blocks'; + +<LinkTo id="Atoms/Animations/About">Animation</LinkTo>; +``` + +- Link text describes the destination, not the action — "See the Stories page," not "Click here." +- Make link text meaningful out of context: "the Stories page," not "click here." +- Link a component's name to its documentation. +- Verify the link actually works. +- Use at least 2–3 words, so the link is easy to click. +- Give each link unique text when more than one appears on the same page. + +**External links** + +Use a plain Markdown link for something like an external tool or reference — most renderers already open these in a new tab: + +```markdown +[GitHub Repository](https://github.com/Codecademy/gamut) +``` + +For more control over the link itself — for example, inside a component that needs an `Anchor` — pass `target="_blank"` together with `rel="noreferrer"` for security, but don't force that behavior unless it's actually needed; a reader can already choose to open a link in a new tab themselves. + +## Referencing code + +**Code in text** + +- Use backticks for inline code: props, CSS properties, component names, prop values (`onClick`, `flex-direction`, `Box`, `true`). +- Use backticks for file and package names too: `Button.tsx`, `package.json`, `@codecademy/gamut`. +- Refer to a component as "the `Box` component" on first mention, then "the component" afterward. +- Keep a component name singular even when referring to several instances — "these `Box` components," not "these `Boxes`." + +**Code samples** + +Include the necessary imports, use realistic and working examples, add comments for complex logic, keep each example focused on one concept, and use TypeScript types: + +```tsx +import { StrokeButton } from '@codecademy/gamut'; + +export const SimpleButtonExample: React.FC = () => ( + <StrokeButton variant="primary">Click me</StrokeButton> +); +``` + +**Command-line syntax** + +Use shell (`sh`) syntax highlighting, skip the prompt symbol (`$`), and put one command per block unless several are directly related: + +```bash +yarn add @codecademy/gamut-kit +``` + +**File paths** + +Use backticks for file paths (`packages/gamut/src/Button/index.tsx`); use a relative path when the context already makes it clear (`./types.ts`), and a workspace-root path when it doesn't. Say "in the `ComponentName.mdx` file" for a code location, rather than a bare path. + +**UI element references** + +- Bold a UI label: **Next**, **Back**, **Close**. +- Describe where an element is: "Click the **Theme Switcher** (paintbrush icon)." +- Use sentence case: "the **Show code** button." +- Prefer device-agnostic language — "click," not a touch- or mouse-specific verb. +- Avoid directional language like "the form on the right" or "the section above" — say "the following form" or "the previous section" instead. + +## Pull requests + +Fill out the pull request template, including links to the corresponding design file and JIRA ticket. + +:::tip +Use a [draft PR](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests) to run CI jobs without requesting review — that still deploys a Netlify preview and publishes alpha package versions to npm. +::: + +### Publishing updates with breaking changes + +If your PR has breaking changes affecting at least one downstream repository — for example, `codecademy-engineering/mono`: + +1. Before merging, open PRs in those downstream repositories using your PR's published alpha package versions. +2. Verify those PRs work as expected and get them signed off normally. +3. Merge your Gamut PR. +4. Once the new Gamut package publishes, update the downstream PRs to use it. +5. Merge and deploy those PRs as soon as possible. + +If a breaking change might affect other users beyond those you've already coordinated with, mention it in `#frontend` too. diff --git a/packages/starlight/src/content/docs/guides/creating-a-custom-theme.md b/packages/starlight/src/content/docs/guides/creating-a-custom-theme.md new file mode 100644 index 0000000000..da4dfb6fd9 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/creating-a-custom-theme.md @@ -0,0 +1,164 @@ +--- +title: Creating a custom theme +description: Build a custom theme with gamut-styles' createTheme builder. +--- + +## When to reach for this + +Most apps should [choose an existing theme](/guides/theming-your-app/) rather than build a new one — reach for a custom theme only for a genuinely new product surface that doesn't fit Core, Admin, LX Studio, Percipio, or Platform. + +## The `createTheme` builder + +Themes are built with `createTheme` from `@codecademy/variance`, giving you type-safe autocomplete, automatic CSS variable generation, built-in light/dark color mode support, and full compatibility with existing Gamut components: + +```tsx +import { createTheme } from '@codecademy/variance'; + +const myTheme = createTheme({ + ...baseTheme, + fontFamily: customFontFamily, +}) + .addColors(colorPalette) + .addColorModes('light', colorModeConfig) + .addScale('customProperty', scaleFunction) // only for new scales + .addName('my-theme') + .build(); +``` + +## 1. Start from `coreTheme` + +Extend `coreTheme` unless you're certain you have a full replacement for every token — see [Themes](/foundations/themes/) for what Core already provides: + +```tsx +import { createTheme } from '@codecademy/variance'; +import { coreTheme } from '@codecademy/gamut-styles'; + +const myCustomTheme = createTheme({ + ...coreTheme, + // your overrides here +}); +``` + +## 2. Define a color palette + +Build an object of your brand's raw colors — usually a `brand` object, plus a numbered `50`–`900` scale per hue: + +```tsx +const myColorPalette = { + brand: { + primary: '#004C54', + secondary: '#000000', + black: '#000000', + white: '#FFFFFF', + }, + kelly: { + 50: '#E6FFF7', + 500: '#00B388', + 900: '#004D3D', + // ...the rest of the scale + }, + success: '#10B981', + warning: '#F59E0B', + error: '#DC2626', +}; + +const myTheme = createTheme({ ...coreTheme }).addColors(myColorPalette); +``` + +## 3. Map colors to semantic tokens per color mode + +`.addColorModes` maps your palette onto the same semantic aliases Gamut components already read from — `text`, `background`, `primary`, `secondary`, `border`, and the rest — once per color mode. See [Color modes](/concepts/color-modes/) for what each alias means: + +```tsx +const myTheme = createTheme({ ...coreTheme }) + .addColors(myColorPalette) + .addColorModes('light', { + light: { + text: { _: 'brand-black', secondary: 'silver-700' }, + background: { _: 'brand-white', primary: 'kelly-50' }, + primary: { _: 'brand-primary', hover: 'kelly-700' }, + border: { primary: 'kelly-500', secondary: 'kelly-200' }, + // ... + }, + dark: { + text: { _: 'brand-white', secondary: 'silver-400' }, + background: { _: 'brand-black', primary: 'midnight-800' }, + primary: { _: 'brand-accent', hover: 'silver-400' }, + border: { primary: 'midnight-600', secondary: 'midnight-700' }, + // ... + }, + }); +``` + +## 4. Override or add scales + +Override an existing scale — `spacing`, `fontFamily`, `fontWeight`, and the rest — directly in the initial `createTheme()` object. Reach for `.addScale()` only for a genuinely new scale that doesn't already exist on `coreTheme`: + +```tsx +const myTheme = createTheme({ + ...coreTheme, + fontFamily: { ...coreTheme.fontFamily, base: '"Roboto", sans-serif' }, + spacing: { ...coreTheme.spacing, xs: 4, sm: 8, md: 16 }, +}) + .addColors(myColorPalette) + .addColorModes('light', colorModeConfig) + .addScale('shadows', ({ colors }) => ({ + sm: `0 1px 2px ${colors['shadow-primary']}`, + md: `0 4px 6px ${colors['shadow-secondary']}`, + })); +``` + +## 5. Convert scales to CSS variables + +`.createScaleVariables` turns a scale into real CSS custom properties, for better runtime performance: + +```tsx +const myTheme = createTheme({ ...coreTheme }) + .addColors(myColorPalette) + .addColorModes('light', colorModeConfig) + .addScale('shadows', shadowConfig) + .createScaleVariables('shadows') // creates --shadow-sm, --shadow-md, ... + .addName('my-custom-theme') + .build(); +``` + +## Using your theme + +Follow [Installation](/getting-started/installation/) to wire up `GamutProvider` with your new theme, the same way you would with any existing one. + +To preview it against real components in Storybook before shipping, register it in the theme switcher: + +```tsx +// packages/styleguide/.storybook/theming/GamutThemeProvider.tsx +const themeMap = { + core: coreTheme, + admin: adminTheme, + lxStudio: lxStudioTheme, + percipio: percipioTheme, + 'your-theme-here': yourCustomTheme, +} as const; +``` + +```tsx +// .storybook/preview.ts +theme: { + name: 'Theme', + defaultValue: 'percipio', + toolbar: { + icon: 'paintbrush', + items: [ + { value: 'core', title: 'Core' }, + { value: 'your-theme-here', title: 'Custom Theme' }, + ], + }, +}, +``` + +## Best practices + +- Replace every token, or extend from `coreTheme` — a partial theme breaks compatibility with existing components. +- Override an existing scale in `createTheme()`'s initial object; reach for `.addScale()` only for scales that don't already exist. +- Map your palette to semantic color names (`primary`, `secondary`, and the rest), not just raw brand colors. +- Provide both light and dark color modes. +- Convert frequently-used scales to CSS variables for performance. +- Test the theme against real Gamut components before shipping it. diff --git a/packages/starlight/src/content/docs/guides/index.md b/packages/starlight/src/content/docs/guides/index.md new file mode 100644 index 0000000000..7811e054a5 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/index.md @@ -0,0 +1,14 @@ +--- +title: Overview +description: Task-oriented recipes for things you already know you need to build. +--- + +How-to guides for cross-cutting tasks that don't have a home under a single component. + +- [Theming your app](/guides/theming-your-app/) +- [Creating a custom theme](/guides/creating-a-custom-theme/) +- [Building forms](/guides/building-forms/) +- [Supporting dark mode](/guides/supporting-dark-mode/) +- [Writing UX copy](/guides/writing-ux-copy/) +- [Migrating to logical properties](/guides/migrating-to-logical-properties/) +- [Contributing to Gamut](/guides/contributing-to-gamut/) diff --git a/packages/starlight/src/content/docs/guides/migrating-to-logical-properties.md b/packages/starlight/src/content/docs/guides/migrating-to-logical-properties.md new file mode 100644 index 0000000000..8db008005c --- /dev/null +++ b/packages/starlight/src/content/docs/guides/migrating-to-logical-properties.md @@ -0,0 +1,66 @@ +--- +title: Migrating to logical properties +description: Move from physical to logical CSS properties in an existing app. +--- + +## Physical vs. logical properties + +Physical properties reference the physical dimensions of the viewport — `margin-left`, `margin-right`, `padding-top`, `padding-bottom`, and the like. They work fine for left-to-right languages, but need manual overrides for right-to-left languages like Arabic or Hebrew. + +Logical properties reference the flow of content instead: + +- **Inline axis** (text direction) — `margin-inline-start`, `margin-inline-end` +- **Block axis** (reading direction) — `margin-block-start`, `margin-block-end` + +See [MDN: CSS logical properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values) for the full model. + +## Turning it on + +Gamut supports both modes through the `useLogicalProperties` prop on `GamutProvider`. It defaults to `false`, so system props map to physical CSS (`margin-left`, `width`, and so on). Set it to `true` to map the same props to logical CSS instead (`margin-inline-start`, `inline-size`, and so on): + +```tsx +<GamutProvider useLogicalProperties> + <App /> +</GamutProvider> +``` + +This affects both base properties (`marginLeft`, `width`) and shorthand props (`mx`, `py`) — a few examples, not the full list: + +| Prop | Physical CSS | Logical CSS | +| ------------ | -------------------------------- | ------------------------------------------- | +| `marginLeft` | `margin-left` | `margin-inline-start` | +| `mx` | `margin-left` / `margin-right` | `margin-inline-start` / `margin-inline-end` | +| `paddingTop` | `padding-top` | `padding-block-start` | +| `py` | `padding-top` / `padding-bottom` | `padding-block-start` / `padding-block-end` | +| `width` | `width` | `inline-size` | +| `height` | `height` | `block-size` | + +:::note +Props that set all four sides at once, like `m` and `p`, aren't affected by this setting — the CSS `margin`/`padding` shorthands work identically in both modes. +::: + +## Previewing before you migrate + +Toggle Storybook's **LogicalProps** toolbar button to preview logical CSS output without changing any application code. Storybook matches `GamutProvider`'s default (physical) until you turn it on. + +## Reading the flag in custom components + +If a custom component needs conditional styles or layout logic that has to mirror Gamut's physical-vs-logical output, read the same flag Gamut's own styled components use: + +```ts +import { useLogicalProperties } from '@codecademy/gamut-styles'; +``` + +This returns `theme.useLogicalProperties`, which `GamutProvider` merges into the theme object. If you mount a plain `ThemeProvider` without `GamutProvider`, the hook may return `undefined` unless you set that field on your theme yourself. + +## Reading text direction + +`useElementDir` resolves the effective text direction (`'rtl'` or `'ltr'`) for a DOM subtree: + +```ts +import { useElementDir } from '@codecademy/gamut-styles'; +``` + +Pass a ref to the element whose direction matters; if you omit it, the hook falls back to `document.documentElement`. It resolves direction from the element's `dir` attribute, then its computed `direction`, then the root element's `dir` — the last fallback helps in test environments where computed style can come back empty. The returned value updates automatically when `dir` changes anywhere under the document root. During SSR, or when `document` isn't available, it returns `'ltr'`. + +For non-React code, the same resolution logic is available as the imperative `elementDir(el)` export. diff --git a/packages/starlight/src/content/docs/guides/supporting-dark-mode.md b/packages/starlight/src/content/docs/guides/supporting-dark-mode.md new file mode 100644 index 0000000000..5398648b28 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/supporting-dark-mode.md @@ -0,0 +1,47 @@ +--- +title: Supporting dark mode +description: Make a page or component work correctly in both color modes. +--- + +Gamut components are built on semantic color aliases (`text`, `background`, `primary`, `secondary`, and more — see [Color modes](/concepts/color-modes/) for why), so most components support both color modes automatically, with no extra work. This guide covers the parts that need you to actually do something. + +## Setting a color mode + +Wrap a subtree in `<ColorMode>` to force a specific mode, or let it follow the reader's OS-level preference: + +```tsx +import { ColorMode } from '@codecademy/gamut-styles'; + +const Page = ({ children }) => <ColorMode mode="light">{children}</ColorMode>; +``` + +```tsx +// Follows the reader's OS-level light/dark preference automatically +const Page = ({ children }) => <ColorMode mode="system">{children}</ColorMode>; +``` + +## Setting a background without guessing the mode + +Use `<Background>` instead of picking a color mode by hand for a section's background — it checks the contrast between the background color you give it and the current mode's text color, and switches to an accessible mode automatically if the combination wouldn't pass: + +```tsx +import { Background } from '@codecademy/gamut-styles'; + +const Page = ({ children }) => <Background bg="hyper">{children}</Background>; +``` + +`<Background>` establishes a new color context for everything inside it, so you can safely nest one background inside another — a dark section can contain its own lighter card, for example, and each still resolves its own aliases correctly. + +The color a `<Background>` resolves to is exposed as the `background-current` theme variable — reach for it when a descendant needs to reference its ancestor's background directly, for example to simulate transparency against it. + +## Reading the current mode in JS + +Reach for these only when you need the mode value itself, not just correctly-themed styles: + +- `useColorModes()` — returns `[mode, modeColors, modes, getColorValue]`, for when you need every mode's values, not just the active one. +- `useCurrentMode(mode?)` — returns just the active mode key, or lets you override it. +- `usePrefersDarkMode()` — returns a boolean from the reader's OS-level `prefers-color-scheme: dark` media query, independent of whatever `<ColorMode>` is actually active. + +## Checking your work + +Toggle color mode with Storybook's Color Mode selector while reviewing a component or page — a component built entirely from semantic aliases and system props should need no other changes to read correctly in both modes. If something looks wrong in one mode only, that's usually a raw color value that should have been a semantic alias instead. diff --git a/packages/starlight/src/content/docs/guides/theming-your-app.md b/packages/starlight/src/content/docs/guides/theming-your-app.md new file mode 100644 index 0000000000..0332f93f10 --- /dev/null +++ b/packages/starlight/src/content/docs/guides/theming-your-app.md @@ -0,0 +1,42 @@ +--- +title: Theming your app +description: Choose and wire up a Gamut theme for your application. +--- + +Every Emotion component in Gamut has typed access to the current theme's tokens, without any extra imports — most directly through the `theme` prop every styled component receives, or through the `theme` object imported straight from `@codecademy/gamut-styles`. + +## 1. Choose a theme + +See [Themes](/foundations/themes/) for the available palettes (Core, Admin, LX Studio, Percipio, Platform) and what each one is for. Use Storybook's Theme Switcher (paintbrush icon in the toolbar) to preview your components across every theme before committing to one, paired with the Color Mode selector to check both light and dark variants. + +## 2. Wire up the theme + +`GamutProvider` (covered in [Installation](/getting-started/installation/)) already puts the right theme's values on Emotion's context for you. From there, reach a token: + +```tsx +import { css } from '@codecademy/gamut-styles'; +import styled from '@emotion/styled'; + +// Preferred: through a system prop +const Box = styled.div(css({ bg: 'navy-400', p: 4 })); + +// Preferred: through a ColorMode semantic alias +const OtherCoolThing = styled.div(css({ color: 'primary', p: 4 })); +``` + +```tsx +// Directly off the theme prop, when you're not using system props +import styled from '@emotion/styled'; + +const styles = styled.div` + color: ${({ theme }) => theme.colors.blue}; +`; +``` + +Prefer the first form — a [system prop](/foundations/system-props/) — whenever one exists for what you're styling; it's type-checked against the active theme's scales, so a typo or an out-of-range value is a compile error instead of a silent fallback. + +## 3. Check your work across themes + +Re-open the Theme Switcher and step through every theme your app might run under, in both color modes. A component built entirely from semantic aliases and system props should need no extra work to look right everywhere; if something looks off in one theme, that's usually a sign a raw value snuck in somewhere it should have been a token. + +See [Design tokens](/foundations/design-tokens/) for the full list of token categories, and [Color modes](/concepts/color-modes/) for how color mode composes with theme choice. diff --git a/packages/starlight/src/content/docs/index.mdx b/packages/starlight/src/content/docs/index.mdx new file mode 100644 index 0000000000..d407113d75 --- /dev/null +++ b/packages/starlight/src/content/docs/index.mdx @@ -0,0 +1,48 @@ +--- +title: Gamut +description: Codecademy's design system for the web. +template: splash +hero: + tagline: Codecademy's design system for the web — components, tokens, and guidance for building consistent, accessible product UI. + actions: + - text: Get started + link: /getting-started/installation/ + icon: right-arrow + - text: Browse components + link: /components/ + icon: right-arrow + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +<CardGrid> + <Card title="Getting started" icon="rocket"> + Install Gamut and build your first page, step by step. + + [Start the tutorial →](/getting-started/installation/) + + </Card> + <Card title="Guides" icon="open-book"> + Task-oriented recipes: theming an app, building a form, supporting dark + mode, and more. + + [Browse guides →](/guides/) + + </Card> + <Card title="Components" icon="puzzle"> + Every component, grouped by what it does — Actions, Containers, Inputs & + forms, Navigation, Feedback, Status, Overlays, Data display, Typography, + Media & assets, Utilities. + + [Browse components →](/components/) + + </Card> + <Card title="Concepts" icon="magnifier"> + The reasoning behind the system: architecture, theming, color modes, + brand, and voice. + + [Read concepts →](/concepts/) + + </Card> +</CardGrid> diff --git a/packages/starlight/src/styles/gamut-core-theme.css b/packages/starlight/src/styles/gamut-core-theme.css new file mode 100644 index 0000000000..50cc69e2ca --- /dev/null +++ b/packages/starlight/src/styles/gamut-core-theme.css @@ -0,0 +1,84 @@ +/** + * Custom Starlight color theme derived from Gamut's `coreTheme` + * (packages/gamut-styles/src/themes/core.ts). + * + * Starlight themes are just overrides of its CSS custom properties (see + * https://starlight.astro.build/guides/css-and-tailwind/#theming). Each + * variable below is paired with the coreTheme color-mode token it stands in + * for, so this file can be re-derived if coreTheme's palette changes. + * + * Only colors are wired up here — coreTheme's typography (fontFamily) relies + * on licensed, non-self-hosted fonts (Apercu/Suisse) that this site doesn't + * load, so Starlight's default font stack is left in place. + */ + +:root[data-theme="light"] { + /* gray-1..7: coreTheme's light-mode navy scale, most-to-least intense */ + --sl-color-gray-1: #0a0d1c; /* navy-900 — text.accent */ + --sl-color-gray-2: #10162f; /* navy-800 — text._ (body text) */ + --sl-color-gray-3: rgba(16, 22, 47, 0.86); /* navy-700 */ + --sl-color-gray-4: rgba(16, 22, 47, 0.75); /* navy-600 — border.secondary */ + --sl-color-gray-5: rgba( + 16, + 22, + 47, + 0.28 + ); /* navy-300 — border.tertiary (hairline) */ + --sl-color-gray-6: rgba( + 16, + 22, + 47, + 0.12 + ); /* navy-200 — subtle bg tint (inline code) */ + --sl-color-gray-7: rgba( + 16, + 22, + 47, + 0.04 + ); /* navy-100 — background.selected */ + + /* white/black are Starlight's text/bg endpoints, inverted per mode */ + --sl-color-white: #0a0d1c; /* navy-900 — heading text */ + --sl-color-black: #ffffff; /* background._ — page background */ + + /* accent: primary._ / primary.hover (hyper) */ + --sl-color-accent-low: color-mix(in srgb, #3a10e5 10%, white); + --sl-color-accent: #3a10e5; /* hyper-500 */ + --sl-color-accent-high: color-mix(in srgb, #3a10e5 65%, black); + + /* background.primary — a warm chrome tone behind the top nav, distinct + from the white content canvas (background._) */ + --sl-color-bg-nav: #fff0e5; /* beige */ +} + +:root[data-theme="dark"] { + /* gray-1..6: coreTheme's dark-mode white scale, most-to-least intense */ + --sl-color-gray-1: #ffffff; /* white — text._ */ + --sl-color-gray-2: rgba(255, 255, 255, 0.8); /* white-700 */ + --sl-color-gray-3: rgba(255, 255, 255, 0.65); /* white-600 — text.secondary */ + --sl-color-gray-4: rgba(255, 255, 255, 0.5); /* white-500 — border.disabled */ + --sl-color-gray-5: rgba( + 255, + 255, + 255, + 0.2 + ); /* white-300 — border.tertiary (hairline) */ + --sl-color-gray-6: rgba( + 255, + 255, + 255, + 0.09 + ); /* white-200 — subtle bg tint (inline code) */ + + --sl-color-white: #ffffff; /* text._ — heading text */ + --sl-color-black: #10162f; /* navy-800 — background._ (page background) */ + + /* accent: primary._ / primary.hover (yellow) */ + --sl-color-accent-low: color-mix(in srgb, #ffd300 20%, black); + --sl-color-accent: #ffd300; /* yellow-500 */ + --sl-color-accent-high: color-mix(in srgb, #ffd300 85%, white); + + /* background.primary — a deeper chrome tone behind the top nav, distinct + from the navy-800 content canvas (background._) */ + --sl-color-bg-nav: #0a0d1c; /* navy-900 */ +} diff --git a/packages/starlight/tsconfig.json b/packages/starlight/tsconfig.json new file mode 100644 index 0000000000..873e712d8c --- /dev/null +++ b/packages/starlight/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "~/*": ["src/*"] + } + } +} diff --git a/packages/styleguide/src/lib/About.mdx b/packages/styleguide/src/lib/About.mdx index e0236317e3..7782aa536e 100644 --- a/packages/styleguide/src/lib/About.mdx +++ b/packages/styleguide/src/lib/About.mdx @@ -14,7 +14,7 @@ import { parameters as uxWritingParameters } from './UX Writing/About.mdx'; export const parameters = { id: 'Gamut', title: 'Gamut', - subtitle: 'The design system for Codecademy ✨', + subtitle: 'The design system for Codecademy and Skillsoft ✨', }; <Meta title="Gamut" /> @@ -24,7 +24,7 @@ export const parameters = { Gamut components, styles, and utilities are the core of the Codecademy brand shared across its websites. They're most notably used on [codecademy.com](https://codecademy.com) but are also found on our internal websites and tools, as well as on our mobile application. -This storybook represents components found in the Codecademy [Client Modules](https://github.com/Codecademy/gamut) repo. +This storybook represents components found in the Codecademy [Gamut](https://github.com/Codecademy/gamut) repo. Its organization roughly aligns with variations of [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/). diff --git a/packages/styleguide/src/lib/Meta/Contributing.mdx b/packages/styleguide/src/lib/Meta/Contributing.mdx index ef7fc16097..99687b3fdd 100644 --- a/packages/styleguide/src/lib/Meta/Contributing.mdx +++ b/packages/styleguide/src/lib/Meta/Contributing.mdx @@ -17,7 +17,7 @@ We love working with Codecademy employees across all our teams.`, ## Prework -We track planned work for Gamut components in the [Gamut Board](https://skillsoftdev.atlassian.net/jira/software/projects/GM/boards/784) on JIRA. +We track planned work for Gamut components in the [Gamut Board](https://skillsoftdev.atlassian.net/jira/software/c/projects/GMT/boards/3655/backlog) on JIRA. - If there's a ticket there you want to take on, send a Slack to #gamut-team or come to Gamut Office hours and lets talk about it! - If the work you'd like to do isn't captured in a JIRA ticket, talk to us and we can work with you to create that ticket. diff --git a/yarn.lock b/yarn.lock index 6a0de90a5e..fb629e7364 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,6 +22,290 @@ __metadata: languageName: node linkType: hard +"@astrojs/compiler-binding-darwin-arm64@npm:0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-binding-darwin-arm64@npm:0.4.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@astrojs/compiler-binding-darwin-x64@npm:0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-binding-darwin-x64@npm:0.4.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@astrojs/compiler-binding-linux-arm64-gnu@npm:0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-binding-linux-arm64-gnu@npm:0.4.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@astrojs/compiler-binding-linux-arm64-musl@npm:0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-binding-linux-arm64-musl@npm:0.4.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@astrojs/compiler-binding-linux-x64-gnu@npm:0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-binding-linux-x64-gnu@npm:0.4.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@astrojs/compiler-binding-linux-x64-musl@npm:0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-binding-linux-x64-musl@npm:0.4.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@astrojs/compiler-binding-wasm32-wasi@npm:0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-binding-wasm32-wasi@npm:0.4.0" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.2.2" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@astrojs/compiler-binding-win32-arm64-msvc@npm:0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-binding-win32-arm64-msvc@npm:0.4.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@astrojs/compiler-binding-win32-x64-msvc@npm:0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-binding-win32-x64-msvc@npm:0.4.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@astrojs/compiler-binding@npm:0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-binding@npm:0.4.0" + dependencies: + "@astrojs/compiler-binding-darwin-arm64": "npm:0.4.0" + "@astrojs/compiler-binding-darwin-x64": "npm:0.4.0" + "@astrojs/compiler-binding-linux-arm64-gnu": "npm:0.4.0" + "@astrojs/compiler-binding-linux-arm64-musl": "npm:0.4.0" + "@astrojs/compiler-binding-linux-x64-gnu": "npm:0.4.0" + "@astrojs/compiler-binding-linux-x64-musl": "npm:0.4.0" + "@astrojs/compiler-binding-wasm32-wasi": "npm:0.4.0" + "@astrojs/compiler-binding-win32-arm64-msvc": "npm:0.4.0" + "@astrojs/compiler-binding-win32-x64-msvc": "npm:0.4.0" + dependenciesMeta: + "@astrojs/compiler-binding-darwin-arm64": + optional: true + "@astrojs/compiler-binding-darwin-x64": + optional: true + "@astrojs/compiler-binding-linux-arm64-gnu": + optional: true + "@astrojs/compiler-binding-linux-arm64-musl": + optional: true + "@astrojs/compiler-binding-linux-x64-gnu": + optional: true + "@astrojs/compiler-binding-linux-x64-musl": + optional: true + "@astrojs/compiler-binding-wasm32-wasi": + optional: true + "@astrojs/compiler-binding-win32-arm64-msvc": + optional: true + "@astrojs/compiler-binding-win32-x64-msvc": + optional: true + checksum: 10c0/bed8bd622549a3b60a867938a715bb826493c93ac24425c6ca19ecf9cd8b97abd8b6ce6a3f13d8a96538fffe4b019be010202a83120abb47f3c1322903e842c1 + languageName: node + linkType: hard + +"@astrojs/compiler-rs@npm:^0.4.0": + version: 0.4.0 + resolution: "@astrojs/compiler-rs@npm:0.4.0" + dependencies: + "@astrojs/compiler-binding": "npm:0.4.0" + checksum: 10c0/663810868196c5af67ed6d737e47fc1b626bee88e95d3e4ecf3997d90aa71afd4c63f0d7d0d32b1f3dda5d9ae8a791c1c9e7be08fd851d0dab5dddf43650f5a6 + languageName: node + linkType: hard + +"@astrojs/internal-helpers@npm:0.10.4": + version: 0.10.4 + resolution: "@astrojs/internal-helpers@npm:0.10.4" + dependencies: + "@types/hast": "npm:^3.0.4" + "@types/mdast": "npm:^4.0.4" + js-yaml: "npm:^4.3.0" + picomatch: "npm:^4.0.4" + retext-smartypants: "npm:^6.2.0" + shiki: "npm:^4.0.2" + smol-toml: "npm:^1.6.0" + unified: "npm:^11.0.5" + checksum: 10c0/2e789920c52597cfde72cb46524be92331b3a9b1d97a03d01e6d714fbbc905202b6949cfd9e5a33d241bf0b3275f5cfe7ffcbf3dd8652b3bc9345a8631b5af3c + languageName: node + linkType: hard + +"@astrojs/markdown-remark@npm:7.2.4": + version: 7.2.4 + resolution: "@astrojs/markdown-remark@npm:7.2.4" + dependencies: + "@astrojs/internal-helpers": "npm:0.10.4" + "@astrojs/prism": "npm:4.0.2" + github-slugger: "npm:^2.0.0" + hast-util-from-html: "npm:^2.0.3" + hast-util-to-text: "npm:^4.0.2" + mdast-util-definitions: "npm:^6.0.0" + rehype-raw: "npm:^7.0.0" + rehype-stringify: "npm:^10.0.1" + remark-gfm: "npm:^4.0.1" + remark-parse: "npm:^11.0.0" + remark-rehype: "npm:^11.1.2" + remark-smartypants: "npm:^3.0.2" + unified: "npm:^11.0.5" + unist-util-remove-position: "npm:^5.0.0" + unist-util-visit: "npm:^5.1.0" + unist-util-visit-parents: "npm:^6.0.2" + vfile: "npm:^6.0.3" + checksum: 10c0/8e82a79f9e19b759a9287db6cc0d40cbfd28bf908b6712b322a0d7de201583481951215b4fbfd1ce7a1c4b8f7c16e95de84d834b49a360a75c175a3009b172ae + languageName: node + linkType: hard + +"@astrojs/markdown-satteri@npm:0.3.8, @astrojs/markdown-satteri@npm:^0.3.5": + version: 0.3.8 + resolution: "@astrojs/markdown-satteri@npm:0.3.8" + dependencies: + "@astrojs/internal-helpers": "npm:0.10.4" + "@astrojs/prism": "npm:4.0.2" + github-slugger: "npm:^2.0.0" + satteri: "npm:^0.10.3" + checksum: 10c0/58dc4567a795d19a8cf94d3cdf98b42189b6d71de25e05bb5bc8ca8bf201a30920ee067ecf6b7999035f30f2ec0f7d1525346d5580ae03c8c29545d113ac311c + languageName: node + linkType: hard + +"@astrojs/mdx@npm:^7.0.5": + version: 7.0.8 + resolution: "@astrojs/mdx@npm:7.0.8" + dependencies: + "@astrojs/internal-helpers": "npm:0.10.4" + "@astrojs/markdown-remark": "npm:7.2.4" + "@mdx-js/mdx": "npm:^3.1.1" + acorn: "npm:^8.16.0" + es-module-lexer: "npm:^2.0.0" + estree-util-visit: "npm:^2.0.0" + hast-util-to-html: "npm:^9.0.5" + piccolore: "npm:^0.1.3" + rehype-raw: "npm:^7.0.0" + remark-gfm: "npm:^4.0.1" + remark-smartypants: "npm:^3.0.2" + source-map: "npm:^0.7.6" + unist-util-visit: "npm:^5.1.0" + vfile: "npm:^6.0.3" + peerDependencies: + "@astrojs/markdown-satteri": ^0.3.1 + astro: ^7.0.0 + peerDependenciesMeta: + "@astrojs/markdown-satteri": + optional: true + checksum: 10c0/9c01797ab1f1de2f3334e756f6dcb21c11d77f79288ab9c94f1ac31b192f4aa3f92f369f9a8bd9fa750f4b16d1c9f886f48e3ad0b6fd12532941093265f5209f + languageName: node + linkType: hard + +"@astrojs/prism@npm:4.0.2": + version: 4.0.2 + resolution: "@astrojs/prism@npm:4.0.2" + dependencies: + prismjs: "npm:^1.30.0" + checksum: 10c0/23a5bc096f966c08be6f8e89adfe9287fd6d08eeb9c4ec7de8172280aa638f2684dcefc5f848f55e211ff63369abc4f0661698daa5e2de6078c8f269215a7101 + languageName: node + linkType: hard + +"@astrojs/react@npm:^6.0.4": + version: 6.0.4 + resolution: "@astrojs/react@npm:6.0.4" + dependencies: + "@astrojs/internal-helpers": "npm:0.10.4" + "@vitejs/plugin-react": "npm:^5.2.0" + devalue: "npm:^5.8.1" + ultrahtml: "npm:^1.6.0" + vite: "npm:^8.0.13" + peerDependencies: + "@types/react": ^17.0.50 || ^18.0.21 || ^19.0.0 + "@types/react-dom": ^17.0.17 || ^18.0.6 || ^19.0.0 + react: ^17.0.2 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0 + checksum: 10c0/b619955c586de3395e786cf42e2bf52ad1275237b0b386837f202190352da3bd33e42a7d9a780f5bb579f65a3ce011ecce91adffac8547b2502ae01b3b3cdfac + languageName: node + linkType: hard + +"@astrojs/sitemap@npm:^3.7.3": + version: 3.7.3 + resolution: "@astrojs/sitemap@npm:3.7.3" + dependencies: + sitemap: "npm:^9.0.0" + stream-replace-string: "npm:^2.0.0" + zod: "npm:^4.3.6" + checksum: 10c0/99c0240586ea2ab1868903341a302ff4bd909b53c49c1c8be476921593fbee838623207c4fa88522264d79291f074ea536aef614d6b1fb286f29a1a5d244ec21 + languageName: node + linkType: hard + +"@astrojs/starlight@npm:^0.41.9": + version: 0.41.9 + resolution: "@astrojs/starlight@npm:0.41.9" + dependencies: + "@astrojs/markdown-satteri": "npm:^0.3.5" + "@astrojs/mdx": "npm:^7.0.5" + "@astrojs/sitemap": "npm:^3.7.3" + "@pagefind/default-ui": "npm:^1.3.0" + "@types/hast": "npm:^3.0.4" + "@types/js-yaml": "npm:^4.0.9" + "@types/mdast": "npm:^4.0.4" + astro-expressive-code: "npm:^0.44.0" + bcp-47: "npm:^2.1.0" + hast-util-from-html: "npm:^2.0.3" + hast-util-select: "npm:^6.0.4" + hast-util-to-string: "npm:^3.0.1" + hastscript: "npm:^9.0.1" + i18next: "npm:^26.0.7" + js-yaml: "npm:^4.1.1" + klona: "npm:^2.0.6" + magic-string: "npm:^0.30.21" + mdast-util-directive: "npm:^3.1.0" + mdast-util-to-markdown: "npm:^2.1.2" + mdast-util-to-string: "npm:^4.0.0" + pagefind: "npm:^1.5.2" + rehype: "npm:^13.0.2" + rehype-format: "npm:^5.0.1" + remark-directive: "npm:^4.0.0" + satteri: "npm:^0.9.1" + ultrahtml: "npm:^1.6.0" + unified: "npm:^11.0.5" + unist-util-visit: "npm:^5.1.0" + vfile: "npm:^6.0.3" + peerDependencies: + "@astrojs/markdown-remark": ^7.2.0 + astro: ^7.0.2 + peerDependenciesMeta: + "@astrojs/markdown-remark": + optional: true + checksum: 10c0/ce8e2aa28a3de4e1d8b9142e952376d9df1a34a17186079c4b5dd1f28eb8c1fb73d99e3e79f6c07f15b596528d522191603247a861c26dc2404f34c9ed5786e8 + languageName: node + linkType: hard + +"@astrojs/telemetry@npm:3.3.3": + version: 3.3.3 + resolution: "@astrojs/telemetry@npm:3.3.3" + dependencies: + ci-info: "npm:^4.4.0" + dset: "npm:^3.1.4" + is-docker: "npm:^4.0.0" + package-manager-detector: "npm:^1.6.0" + checksum: 10c0/d5373e0aa8cbfd684143027dce4e31f7fd45e90df799ce4d255fb750109173a3dd2fc3ef26532b298c715fe063d6e955984d238e323d96e1d8f6c0a03fbbbc73 + languageName: node + linkType: hard + "@babel/cli@npm:7.24.7": version: 7.24.7 resolution: "@babel/cli@npm:7.24.7" @@ -49,21 +333,21 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.24.7, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/code-frame@npm:7.29.0" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.24.7, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/code-frame@npm:7.29.7" dependencies: - "@babel/helper-validator-identifier": "npm:^7.28.5" + "@babel/helper-validator-identifier": "npm:^7.29.7" js-tokens: "npm:^4.0.0" picocolors: "npm:^1.1.1" - checksum: 10c0/d34cc504e7765dfb576a663d97067afb614525806b5cad1a5cc1a7183b916fec8ff57fa233585e3926fd5a9e6b31aae6df91aa81ae9775fb7a28f658d3346f0d + checksum: 10c0/169fc2080169a40c1760155eaaaf739bcb882df0bec76a83adbda5493645bc17270a3434b8848c494b1933e96fe1d147370001e3cda09a39f43ae30f08ef2069 languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.26.0, @babel/compat-data@npm:^7.28.6": - version: 7.29.0 - resolution: "@babel/compat-data@npm:7.29.0" - checksum: 10c0/08f348554989d23aa801bf1405aa34b15e841c0d52d79da7e524285c77a5f9d298e70e11d91cc578d8e2c9542efc586d50c5f5cf8e1915b254a9dcf786913a94 +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.26.0, @babel/compat-data@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/compat-data@npm:7.29.7" + checksum: 10c0/47913f05e08a45a1c9df38c02b4b49e391005085b489432647a1abe112e5d9c75e3be8ea5972b7f6da4ec5d1339922ceb9ea02b8a25d4ed1cb8636e5261f344e languageName: node linkType: hard @@ -90,39 +374,39 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.5, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.9, @babel/core@npm:^7.26.0, @babel/core@npm:^7.27.4, @babel/core@npm:^7.28.0, @babel/core@npm:^7.7.5": - version: 7.29.0 - resolution: "@babel/core@npm:7.29.0" +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.5, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.9, @babel/core@npm:^7.26.0, @babel/core@npm:^7.27.4, @babel/core@npm:^7.28.0, @babel/core@npm:^7.29.0, @babel/core@npm:^7.7.5": + version: 7.29.7 + resolution: "@babel/core@npm:7.29.7" dependencies: - "@babel/code-frame": "npm:^7.29.0" - "@babel/generator": "npm:^7.29.0" - "@babel/helper-compilation-targets": "npm:^7.28.6" - "@babel/helper-module-transforms": "npm:^7.28.6" - "@babel/helpers": "npm:^7.28.6" - "@babel/parser": "npm:^7.29.0" - "@babel/template": "npm:^7.28.6" - "@babel/traverse": "npm:^7.29.0" - "@babel/types": "npm:^7.29.0" + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.7" + "@babel/helper-compilation-targets": "npm:^7.29.7" + "@babel/helper-module-transforms": "npm:^7.29.7" + "@babel/helpers": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/template": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/5127d2e8e842ae409e11bcbb5c2dff9874abf5415e8026925af7308e903f4f43397341467a130490d1a39884f461bc2b67f3063bce0be44340db89687fd852aa + checksum: 10c0/112fb09c24de7a1de64d1de2c31fe65c4e6af4cb2fb6e6d99ea5373e6fc51e75b88581c0efae4c4c68f119a02a988c7106e95011a41530a2fb8ed793c7eaa07b languageName: node linkType: hard -"@babel/generator@npm:^7.22.5, @babel/generator@npm:^7.24.7, @babel/generator@npm:^7.27.5, @babel/generator@npm:^7.29.0, @babel/generator@npm:^7.7.2": - version: 7.29.1 - resolution: "@babel/generator@npm:7.29.1" +"@babel/generator@npm:^7.22.5, @babel/generator@npm:^7.24.7, @babel/generator@npm:^7.27.5, @babel/generator@npm:^7.29.7, @babel/generator@npm:^7.29.8, @babel/generator@npm:^7.7.2": + version: 7.29.8 + resolution: "@babel/generator@npm:7.29.8" dependencies: - "@babel/parser": "npm:^7.29.0" - "@babel/types": "npm:^7.29.0" + "@babel/parser": "npm:^7.29.8" + "@babel/types": "npm:^7.29.8" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/349086e6876258ef3fb2823030fee0f6c0eb9c3ebe35fc572e16997f8c030d765f636ddc6299edae63e760ea6658f8ee9a2edfa6d6b24c9a80c917916b973551 + checksum: 10c0/7b896696314a659652393b76d78276e236acd0f7fae40a9a1af7f01c76aeafc630dd0966aad6f9386d35d7c674a8e6e2d8e217c44d25fb11460e68afa9ba8441 languageName: node linkType: hard @@ -145,16 +429,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7, @babel/helper-compilation-targets@npm:^7.25.9, @babel/helper-compilation-targets@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-compilation-targets@npm:7.28.6" +"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7, @babel/helper-compilation-targets@npm:^7.25.9, @babel/helper-compilation-targets@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-compilation-targets@npm:7.29.7" dependencies: - "@babel/compat-data": "npm:^7.28.6" - "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/compat-data": "npm:^7.29.7" + "@babel/helper-validator-option": "npm:^7.29.7" browserslist: "npm:^4.24.0" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10c0/3fcdf3b1b857a1578e99d20508859dbd3f22f3c87b8a0f3dc540627b4be539bae7f6e61e49d931542fe5b557545347272bbdacd7f58a5c77025a18b745593a50 + checksum: 10c0/4c15fd4c69a0a7047799a28a88460c19cede0a0ee8af994ea169114986f4af48b92c7393a4a3fee0456c11a656eece3448a6ed06354453d6c27cccf17195453b languageName: node linkType: hard @@ -203,10 +487,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-globals@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/helper-globals@npm:7.28.0" - checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 +"@babel/helper-globals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-globals@npm:7.29.7" + checksum: 10c0/f38417c40b1129a1b2b519ca961b9040c8827d1444fd74068702286b91b77089431dc76b6b9d5c1496e5da2a4f3ad329c6946e688ba3fa0d1d0b3d2b4f34f36a languageName: node linkType: hard @@ -220,26 +504,26 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.18.6, @babel/helper-module-imports@npm:^7.25.9, @babel/helper-module-imports@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-module-imports@npm:7.28.6" +"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.18.6, @babel/helper-module-imports@npm:^7.25.9, @babel/helper-module-imports@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-imports@npm:7.29.7" dependencies: - "@babel/traverse": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" - checksum: 10c0/b49d8d8f204d9dbfd5ac70c54e533e5269afb3cea966a9d976722b13e9922cc773a653405f53c89acb247d5aebdae4681d631a3ae3df77ec046b58da76eda2ac + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/6adf60d97356027413342a092f818d9678c4f5caff716a33e3284b5ae14e47a9e88059d421dde4ee4894691260039a12602c0e7becadc175602194b40dfa345d languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.25.9, @babel/helper-module-transforms@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-module-transforms@npm:7.28.6" +"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.25.9, @babel/helper-module-transforms@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-transforms@npm:7.29.7" dependencies: - "@babel/helper-module-imports": "npm:^7.28.6" - "@babel/helper-validator-identifier": "npm:^7.28.5" - "@babel/traverse": "npm:^7.28.6" + "@babel/helper-module-imports": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10c0/6f03e14fc30b287ce0b839474b5f271e72837d0cafe6b172d759184d998fbee3903a035e81e07c2c596449e504f453463d58baa65b6f40a37ded5bec74620b2b + checksum: 10c0/ee5a2172c24a42be696836f4b0d947489c9729d8adf5821885cf77d1ad5333e3c447368e9a71f67df1099570490553dccf9f888ef0a92a48aa63cb086bd8c7e1 languageName: node linkType: hard @@ -252,10 +536,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.28.6 - resolution: "@babel/helper-plugin-utils@npm:7.28.6" - checksum: 10c0/3f5f8acc152fdbb69a84b8624145ff4f9b9f6e776cb989f9f968f8606eb7185c5c3cfcf3ba08534e37e1e0e1c118ac67080610333f56baa4f7376c99b5f1143d +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.29.7, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.29.7 + resolution: "@babel/helper-plugin-utils@npm:7.29.7" + checksum: 10c0/380477a06133274a2759f9355929cb60a95e8b8fee624a1ae1fa349e1d1645b89daca456f72833f6d1062bffa12ee4271c5bf0cc5a61c0166cdc24c7591e2408 languageName: node linkType: hard @@ -305,24 +589,24 @@ __metadata: languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-string-parser@npm:7.27.1" - checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.25.9, @babel/helper-validator-identifier@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-validator-identifier@npm:7.28.5" - checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 +"@babel/helper-validator-identifier@npm:^7.25.9, @babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.25.9, @babel/helper-validator-option@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-option@npm:7.27.1" - checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 +"@babel/helper-validator-option@npm:^7.25.9, @babel/helper-validator-option@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-option@npm:7.29.7" + checksum: 10c0/d2a06c6d0ac40ba4a2f219fc2cab249c7a94bacdb2686273b7f9598571c908809b48468ff588915a346e6cc7296f60b581023d1d498b747fed06f779d335c2cc languageName: node linkType: hard @@ -337,24 +621,24 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.24.7, @babel/helpers@npm:^7.28.6": - version: 7.29.2 - resolution: "@babel/helpers@npm:7.29.2" +"@babel/helpers@npm:^7.24.7, @babel/helpers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helpers@npm:7.29.7" dependencies: - "@babel/template": "npm:^7.28.6" - "@babel/types": "npm:^7.29.0" - checksum: 10c0/dab0e65b9318b2502a62c58bc0913572318595eec0482c31f0ad416b72636e6698a1d7c57cd2791d4528eb8c548bca88d338dc4d2a55a108dc1f6702f9bc5512 + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/218e8d10953647c9f44775f5a022b227a182674853b5ea8631889deb7e1a3e4bc870388aaecf59bb8bd92a87f9a96220ed3f70a35bffec6bcf9169ecb67891ac languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0": - version: 7.29.2 - resolution: "@babel/parser@npm:7.29.2" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.29.7, @babel/parser@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/parser@npm:7.29.8" dependencies: - "@babel/types": "npm:^7.29.0" + "@babel/types": "npm:^7.29.8" bin: parser: ./bin/babel-parser.js - checksum: 10c0/e5a4e69e3ac7acdde995f37cf299a68458cfe7009dff66bd0962fd04920bef287201169006af365af479c08ff216bfefbb595e331f87f6ae7283858aebbc3317 + checksum: 10c0/acc890c5e6a6dd40863a47b50bac111d7185ee6fbbe163ebe11d5214854ca2adb901462ad4d718a65090ef84bd2230e9e8ab45a2e0caccc685f1f57ab0bb1e28 languageName: node linkType: hard @@ -1144,6 +1428,28 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-jsx-self@npm:^7.27.1": + version: 7.29.7 + resolution: "@babel/plugin-transform-react-jsx-self@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/288995f0fd0d61ab740a315fb56c8255eb87dd4a4ac2ac7d0fdd4ce173c3878200141e80da2db0e598c7b2a71e74e604afdbb4c8e14ae6e0527ce0b6294c03da + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-source@npm:^7.27.1": + version: 7.29.7 + resolution: "@babel/plugin-transform-react-jsx-source@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/a121899631e6d99b9e1b276acf736dbb77948a31f8eeeae67b89c8a4ab0f05e51ba64544baa06c286a2b9944f227244e15aac464e2313d286d0511fe51e27975 + languageName: node + linkType: hard + "@babel/plugin-transform-react-jsx@npm:^7.25.9": version: 7.25.9 resolution: "@babel/plugin-transform-react-jsx@npm:7.25.9" @@ -1464,45 +1770,45 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.22.6, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": - version: 7.28.2 - resolution: "@babel/runtime@npm:7.28.2" - checksum: 10c0/c20afe253629d53a405a610b12a62ac74d341a2c1e0fb202bbef0c118f6b5c84f94bf16039f58fd0483dd256901259930a43976845bdeb180cab1f882c21b6e0 + version: 7.29.7 + resolution: "@babel/runtime@npm:7.29.7" + checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e languageName: node linkType: hard -"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.28.6, @babel/template@npm:^7.3.3": - version: 7.28.6 - resolution: "@babel/template@npm:7.28.6" +"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.29.7, @babel/template@npm:^7.3.3": + version: 7.29.7 + resolution: "@babel/template@npm:7.29.7" dependencies: - "@babel/code-frame": "npm:^7.28.6" - "@babel/parser": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" - checksum: 10c0/66d87225ed0bc77f888181ae2d97845021838c619944877f7c4398c6748bcf611f216dfd6be74d39016af502bca876e6ce6873db3c49e4ac354c56d34d57e9f5 + "@babel/code-frame": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/8bb7f900dcab0e9e1c5ffbc33ca10e0d26b7b2e2ca804becb73ee771b9c4ed6e2908a4ae4a14c08560febb45d2b6b9a173955e42ad404d05f8b04840a14d9c58 languageName: node linkType: hard -"@babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.6, @babel/traverse@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/traverse@npm:7.29.0" +"@babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.29.7": + version: 7.29.8 + resolution: "@babel/traverse@npm:7.29.8" dependencies: - "@babel/code-frame": "npm:^7.29.0" - "@babel/generator": "npm:^7.29.0" - "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.29.0" - "@babel/template": "npm:^7.28.6" - "@babel/types": "npm:^7.29.0" + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.8" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.8" + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.8" debug: "npm:^4.3.1" - checksum: 10c0/f63ef6e58d02a9fbf3c0e2e5f1c877da3e0bc57f91a19d2223d53e356a76859cbaf51171c9211c71816d94a0e69efa2732fd27ffc0e1bbc84b636e60932333eb + checksum: 10c0/87a28989c434add26d787776ac6d30f749b89cb030f2a605c89f671a516a6fa165ac0476f07e2b69feed70128b2734cae1cbbf41dfe95ccf22081bd8f8b91923 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": - version: 7.29.0 - resolution: "@babel/types@npm:7.29.0" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.29.7, @babel/types@npm:^7.29.8, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": + version: 7.29.8 + resolution: "@babel/types@npm:7.29.8" dependencies: - "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.28.5" - checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/be7c279f0abf2a086c633e21b49c7ca80275d05283cc5a268b67a708c9914bd0c944f1422b3eb3cb37682a2af5d560abf520ccf9b01b53ecbfe6b71fbc3fdde6 languageName: node linkType: hard @@ -1527,6 +1833,140 @@ __metadata: languageName: node linkType: hard +"@bruits/satteri-darwin-arm64@npm:0.10.5": + version: 0.10.5 + resolution: "@bruits/satteri-darwin-arm64@npm:0.10.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@bruits/satteri-darwin-arm64@npm:0.9.5": + version: 0.9.5 + resolution: "@bruits/satteri-darwin-arm64@npm:0.9.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@bruits/satteri-darwin-x64@npm:0.10.5": + version: 0.10.5 + resolution: "@bruits/satteri-darwin-x64@npm:0.10.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@bruits/satteri-darwin-x64@npm:0.9.5": + version: 0.9.5 + resolution: "@bruits/satteri-darwin-x64@npm:0.9.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@bruits/satteri-linux-arm64-gnu@npm:0.10.5": + version: 0.10.5 + resolution: "@bruits/satteri-linux-arm64-gnu@npm:0.10.5" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@bruits/satteri-linux-arm64-gnu@npm:0.9.5": + version: 0.9.5 + resolution: "@bruits/satteri-linux-arm64-gnu@npm:0.9.5" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@bruits/satteri-linux-arm64-musl@npm:0.10.5": + version: 0.10.5 + resolution: "@bruits/satteri-linux-arm64-musl@npm:0.10.5" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@bruits/satteri-linux-arm64-musl@npm:0.9.5": + version: 0.9.5 + resolution: "@bruits/satteri-linux-arm64-musl@npm:0.9.5" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@bruits/satteri-linux-x64-gnu@npm:0.10.5": + version: 0.10.5 + resolution: "@bruits/satteri-linux-x64-gnu@npm:0.10.5" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@bruits/satteri-linux-x64-gnu@npm:0.9.5": + version: 0.9.5 + resolution: "@bruits/satteri-linux-x64-gnu@npm:0.9.5" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@bruits/satteri-linux-x64-musl@npm:0.10.5": + version: 0.10.5 + resolution: "@bruits/satteri-linux-x64-musl@npm:0.10.5" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@bruits/satteri-linux-x64-musl@npm:0.9.5": + version: 0.9.5 + resolution: "@bruits/satteri-linux-x64-musl@npm:0.9.5" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@bruits/satteri-wasm32-wasi@npm:0.10.5": + version: 0.10.5 + resolution: "@bruits/satteri-wasm32-wasi@npm:0.10.5" + dependencies: + "@emnapi/core": "npm:1.11.1" + "@emnapi/runtime": "npm:1.11.1" + "@napi-rs/wasm-runtime": "npm:^1.2.3" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@bruits/satteri-wasm32-wasi@npm:0.9.5": + version: 0.9.5 + resolution: "@bruits/satteri-wasm32-wasi@npm:0.9.5" + dependencies: + "@emnapi/core": "npm:1.11.1" + "@emnapi/runtime": "npm:1.11.1" + "@napi-rs/wasm-runtime": "npm:^1.1.6" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@bruits/satteri-win32-arm64-msvc@npm:0.10.5": + version: 0.10.5 + resolution: "@bruits/satteri-win32-arm64-msvc@npm:0.10.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@bruits/satteri-win32-arm64-msvc@npm:0.9.5": + version: 0.9.5 + resolution: "@bruits/satteri-win32-arm64-msvc@npm:0.9.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@bruits/satteri-win32-x64-msvc@npm:0.10.5": + version: 0.10.5 + resolution: "@bruits/satteri-win32-x64-msvc@npm:0.10.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@bruits/satteri-win32-x64-msvc@npm:0.9.5": + version: 0.9.5 + resolution: "@bruits/satteri-win32-x64-msvc@npm:0.9.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@bufbuild/protobuf@npm:^2.0.0": version: 2.2.5 resolution: "@bufbuild/protobuf@npm:2.2.5" @@ -1534,6 +1974,37 @@ __metadata: languageName: node linkType: hard +"@capsizecss/unpack@npm:^4.0.0": + version: 4.0.1 + resolution: "@capsizecss/unpack@npm:4.0.1" + dependencies: + fontkitten: "npm:^1.0.3" + checksum: 10c0/72ce03871e137bae1c4925f9dc916a6250cf9b9630a8bb28d2ef7575d20fd7ef0e8966b361aaa3a54c0e8d5ff40b133490bd97a9beb6dc209529102947d392b1 + languageName: node + linkType: hard + +"@clack/core@npm:1.4.3": + version: 1.4.3 + resolution: "@clack/core@npm:1.4.3" + dependencies: + fast-wrap-ansi: "npm:^0.2.0" + sisteransi: "npm:^1.0.5" + checksum: 10c0/16cda430974bc02844cfa6e3f0e6e19695d97e915a580ab74603d34a1f00dcbdd8dc856adc3f89405e3555b5cf42568ae8687e748cb92c3434fe4d9fc7ebe664 + languageName: node + linkType: hard + +"@clack/prompts@npm:^1.1.0": + version: 1.7.0 + resolution: "@clack/prompts@npm:1.7.0" + dependencies: + "@clack/core": "npm:1.4.3" + fast-string-width: "npm:^3.0.2" + fast-wrap-ansi: "npm:^0.2.0" + sisteransi: "npm:^1.0.5" + checksum: 10c0/a11e4f8d4a03cfe2d9eb21c3263f0252648573977b96e495a7d4f159f7efa929fe775a6ac3fe7ae30d8acb72514d94418a2c4335855d05b575a41aa885c9ff26 + languageName: node + linkType: hard + "@codecademy/eslint-config@npm:8.2.0": version: 8.2.0 resolution: "@codecademy/eslint-config@npm:8.2.0" @@ -1559,7 +2030,28 @@ __metadata: languageName: node linkType: hard -"@codecademy/gamut-icons@npm:10.0.1, @codecademy/gamut-icons@workspace:packages/gamut-icons": +"@codecademy/gamut-docs@workspace:packages/starlight": + version: 0.0.0-use.local + resolution: "@codecademy/gamut-docs@workspace:packages/starlight" + dependencies: + "@astrojs/react": "npm:^6.0.4" + "@astrojs/starlight": "npm:^0.41.9" + "@codecademy/gamut": "workspace:*" + "@codecademy/gamut-icons": "workspace:*" + "@codecademy/gamut-styles": "workspace:*" + "@emotion/react": "npm:^11.14.0" + "@emotion/styled": "npm:^11.14.1" + "@types/react": "npm:^18.3.12" + "@types/react-dom": "npm:^18.3.1" + astro: "npm:^7.2.6" + astro-live-code: "npm:^0.0.6" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + starlight-sidebar-topics: "npm:^0.8.0" + languageName: unknown + linkType: soft + +"@codecademy/gamut-icons@npm:10.0.1, @codecademy/gamut-icons@workspace:*, @codecademy/gamut-icons@workspace:packages/gamut-icons": version: 0.0.0-use.local resolution: "@codecademy/gamut-icons@workspace:packages/gamut-icons" dependencies: @@ -1617,7 +2109,7 @@ __metadata: languageName: unknown linkType: soft -"@codecademy/gamut-styles@npm:21.1.0, @codecademy/gamut-styles@workspace:packages/gamut-styles": +"@codecademy/gamut-styles@npm:21.1.0, @codecademy/gamut-styles@workspace:*, @codecademy/gamut-styles@workspace:packages/gamut-styles": version: 0.0.0-use.local resolution: "@codecademy/gamut-styles@workspace:packages/gamut-styles" dependencies: @@ -1750,6 +2242,13 @@ __metadata: languageName: node linkType: hard +"@ctrl/tinycolor@npm:^4.0.4": + version: 4.2.0 + resolution: "@ctrl/tinycolor@npm:4.2.0" + checksum: 10c0/374034581953f6debd950e4b07da833d1d8f7af5aead5117c91545fbe1583c91b2407bc285e1625529839135d6465c2e7549e7a8ad70979cab4c207486798064 + languageName: node + linkType: hard + "@effect/data@npm:0.17.1": version: 0.17.1 resolution: "@effect/data@npm:0.17.1" @@ -1794,31 +2293,40 @@ __metadata: languageName: node linkType: hard -"@emnapi/core@npm:^1.1.0, @emnapi/core@npm:^1.4.3, @emnapi/core@npm:^1.5.0, @emnapi/core@npm:^1.7.1": - version: 1.9.1 - resolution: "@emnapi/core@npm:1.9.1" +"@emnapi/core@npm:1.11.1, @emnapi/core@npm:^1.1.0, @emnapi/core@npm:^1.4.3, @emnapi/core@npm:^1.5.0": + version: 1.11.1 + resolution: "@emnapi/core@npm:1.11.1" dependencies: - "@emnapi/wasi-threads": "npm:1.2.0" + "@emnapi/wasi-threads": "npm:1.2.2" tslib: "npm:^2.4.0" - checksum: 10c0/00e7a99a2bc3ad908ca8272ba861a934da87dffa8797a41316c4a3b571a1e4d2743e2fa14b1a0f131fa4a3c2018ddb601cd2a8cb7f574fa940af696df3c2fe8d + checksum: 10c0/2c6defdac2d1d26090384655d7d6c9614fa553853b1760597686749e9375dc2aa0dae80a2615b81c254600f5d531d07d8466cde0d331a8caae64b93f3ca5937e languageName: node linkType: hard -"@emnapi/runtime@npm:^1.1.0, @emnapi/runtime@npm:^1.4.3, @emnapi/runtime@npm:^1.5.0, @emnapi/runtime@npm:^1.7.1": - version: 1.9.1 - resolution: "@emnapi/runtime@npm:1.9.1" +"@emnapi/runtime@npm:1.11.1": + version: 1.11.1 + resolution: "@emnapi/runtime@npm:1.11.1" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/750edca117e0363ab2de10622f8ee60e57d8690c2f29c49704813da5cd627c641798d7f3cb0d953c62fdc71688e02e333ddbf2c1204f38b47e3e40657332a6f5 + checksum: 10c0/04332fb62076afc440aa23316c04bec42f584ca8b074e5507d08e2b33a47cbe0493b1aadb8f3c1057b64ae1e17f5bde1a7bc37f7facc9d0bc25c18197cbd366f languageName: node linkType: hard -"@emnapi/wasi-threads@npm:1.2.0": - version: 1.2.0 - resolution: "@emnapi/wasi-threads@npm:1.2.0" +"@emnapi/runtime@npm:^1.1.0, @emnapi/runtime@npm:^1.11.1, @emnapi/runtime@npm:^1.4.3, @emnapi/runtime@npm:^1.5.0": + version: 1.11.3 + resolution: "@emnapi/runtime@npm:1.11.3" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/a00f1020fefb9d4145c367f93a9fddb383a00da8ffd7871e20b19659890379b83aeecb9f84d7d0eda5456343f4a09eb05b0acb5153b0d3d889539dedb1ed87c3 + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.2.2": + version: 1.2.2 + resolution: "@emnapi/wasi-threads@npm:1.2.2" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/1e3724b5814b06c14782fda87eee9b9aa68af01576c81ffeaefdf621ddb74386e419d5b3b1027b6a8172397729d95a92f814fc4b8d3c224376428faa07a6a01a + checksum: 10c0/f0dc8269d6b20ae5a7c7b36e7a6a333452009d461038ef4febb29da2f3f78c1e2b1576d7e8970a5c5789ed3caedc1f80f5b0c2a5373bdaf8d03b20432bb55747 languageName: node linkType: hard @@ -1908,7 +2416,7 @@ __metadata: languageName: node linkType: hard -"@emotion/react@npm:11.14.0, @emotion/react@npm:^11.8.1": +"@emotion/react@npm:11.14.0, @emotion/react@npm:^11.14.0, @emotion/react@npm:^11.8.1": version: 11.14.0 resolution: "@emotion/react@npm:11.14.0" dependencies: @@ -1949,7 +2457,7 @@ __metadata: languageName: node linkType: hard -"@emotion/styled@npm:11.14.1": +"@emotion/styled@npm:11.14.1, @emotion/styled@npm:^11.14.1": version: 11.14.1 resolution: "@emotion/styled@npm:11.14.1" dependencies: @@ -1999,184 +2507,366 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/aix-ppc64@npm:0.27.4" +"@esbuild/aix-ppc64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/aix-ppc64@npm:0.27.7" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/android-arm64@npm:0.27.4" - conditions: os=android & cpu=arm64 +"@esbuild/aix-ppc64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/aix-ppc64@npm:0.28.2" + conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/android-arm@npm:0.27.4" - conditions: os=android & cpu=arm +"@esbuild/android-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/android-arm64@npm:0.27.7" + conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-x64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/android-x64@npm:0.27.4" - conditions: os=android & cpu=x64 +"@esbuild/android-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-arm64@npm:0.28.2" + conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/darwin-arm64@npm:0.27.4" - conditions: os=darwin & cpu=arm64 +"@esbuild/android-arm@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/android-arm@npm:0.27.7" + conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/darwin-x64@npm:0.27.4" - conditions: os=darwin & cpu=x64 +"@esbuild/android-arm@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-arm@npm:0.28.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/android-x64@npm:0.27.7" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-x64@npm:0.28.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/darwin-arm64@npm:0.27.7" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/darwin-arm64@npm:0.28.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/darwin-x64@npm:0.27.7" + conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/freebsd-arm64@npm:0.27.4" +"@esbuild/darwin-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/darwin-x64@npm:0.28.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/freebsd-arm64@npm:0.27.7" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/freebsd-x64@npm:0.27.4" +"@esbuild/freebsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/freebsd-arm64@npm:0.28.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/freebsd-x64@npm:0.27.7" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/linux-arm64@npm:0.27.4" +"@esbuild/freebsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/freebsd-x64@npm:0.28.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-arm64@npm:0.27.7" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-arm64@npm:0.28.2" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/linux-arm@npm:0.27.4" +"@esbuild/linux-arm@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-arm@npm:0.27.7" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/linux-ia32@npm:0.27.4" +"@esbuild/linux-arm@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-arm@npm:0.28.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-ia32@npm:0.27.7" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/linux-loong64@npm:0.27.4" +"@esbuild/linux-ia32@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-ia32@npm:0.28.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-loong64@npm:0.27.7" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-loong64@npm:0.28.2" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/linux-mips64el@npm:0.27.4" +"@esbuild/linux-mips64el@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-mips64el@npm:0.27.7" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/linux-ppc64@npm:0.27.4" +"@esbuild/linux-mips64el@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-mips64el@npm:0.28.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-ppc64@npm:0.27.7" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/linux-riscv64@npm:0.27.4" +"@esbuild/linux-ppc64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-ppc64@npm:0.28.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-riscv64@npm:0.27.7" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/linux-s390x@npm:0.27.4" +"@esbuild/linux-riscv64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-riscv64@npm:0.28.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-s390x@npm:0.27.7" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-s390x@npm:0.28.2" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/linux-x64@npm:0.27.4" +"@esbuild/linux-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/linux-x64@npm:0.27.7" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-x64@npm:0.28.2" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/netbsd-arm64@npm:0.27.4" +"@esbuild/netbsd-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/netbsd-arm64@npm:0.27.7" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/netbsd-x64@npm:0.27.4" +"@esbuild/netbsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/netbsd-arm64@npm:0.28.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/netbsd-x64@npm:0.27.7" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/openbsd-arm64@npm:0.27.4" +"@esbuild/netbsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/netbsd-x64@npm:0.28.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/openbsd-arm64@npm:0.27.7" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openbsd-arm64@npm:0.28.2" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/openbsd-x64@npm:0.27.4" +"@esbuild/openbsd-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/openbsd-x64@npm:0.27.7" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openbsd-x64@npm:0.28.2" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/openharmony-arm64@npm:0.27.4" +"@esbuild/openharmony-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/openharmony-arm64@npm:0.27.7" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openharmony-arm64@npm:0.28.2" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/sunos-x64@npm:0.27.4" +"@esbuild/sunos-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/sunos-x64@npm:0.27.7" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/win32-arm64@npm:0.27.4" +"@esbuild/sunos-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/sunos-x64@npm:0.28.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/win32-arm64@npm:0.27.7" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/win32-ia32@npm:0.27.4" +"@esbuild/win32-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-arm64@npm:0.28.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/win32-ia32@npm:0.27.7" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.27.4": - version: 0.27.4 - resolution: "@esbuild/win32-x64@npm:0.27.4" +"@esbuild/win32-ia32@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-ia32@npm:0.28.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.7": + version: 0.27.7 + resolution: "@esbuild/win32-x64@npm:0.27.7" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-x64@npm:0.28.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -2223,6 +2913,51 @@ __metadata: languageName: node linkType: hard +"@expressive-code/core@npm:^0.44.1": + version: 0.44.1 + resolution: "@expressive-code/core@npm:0.44.1" + dependencies: + "@ctrl/tinycolor": "npm:^4.0.4" + hast-util-select: "npm:^6.0.2" + hast-util-to-html: "npm:^9.0.1" + hast-util-to-text: "npm:^4.0.1" + hastscript: "npm:^9.0.0" + postcss: "npm:^8.4.38" + postcss-nested: "npm:^6.0.1" + unist-util-visit: "npm:^5.0.0" + unist-util-visit-parents: "npm:^6.0.1" + checksum: 10c0/0e2bc031ce475b290e107ae61b4f915daebf2c7672d293d5f3c12dcc0c88f8c9b82d8649a0a9f5da7fdb8bc2a620c8f08d84f536a59c8ea0fc1777758db735a1 + languageName: node + linkType: hard + +"@expressive-code/plugin-frames@npm:^0.44.1": + version: 0.44.1 + resolution: "@expressive-code/plugin-frames@npm:0.44.1" + dependencies: + "@expressive-code/core": "npm:^0.44.1" + checksum: 10c0/d32c6c4ae72439b4a5ac25688180d037df27ce3bc1a361e89732f487109afe8960b1b2a573c3d704bda2feb2e2a1e72828385abc6971d4c823ba3b06153bcd28 + languageName: node + linkType: hard + +"@expressive-code/plugin-shiki@npm:^0.44.1": + version: 0.44.1 + resolution: "@expressive-code/plugin-shiki@npm:0.44.1" + dependencies: + "@expressive-code/core": "npm:^0.44.1" + shiki: "npm:^4.0.2" + checksum: 10c0/3e9fac7ddc3ebf8cf24579bae9a6767b92f5c87d8de3eb47c0dac1ed138070ef2a4148732b3cd63d178c8fe9bc25f46933228a5b2b44615e5ab453eb7e064afb + languageName: node + linkType: hard + +"@expressive-code/plugin-text-markers@npm:^0.44.1": + version: 0.44.1 + resolution: "@expressive-code/plugin-text-markers@npm:0.44.1" + dependencies: + "@expressive-code/core": "npm:^0.44.1" + checksum: 10c0/78b8d2a98b931d78156830d3deeadbfd16fa1e221fe08a7ed8aca00c20663623f20496d1079529d70cea2660bf27c77783c4626797d77b8ce6932d02729096bd + languageName: node + linkType: hard + "@figspec/components@npm:^2.0.1": version: 2.1.0 resolution: "@figspec/components@npm:2.1.0" @@ -2468,6 +3203,251 @@ __metadata: languageName: node linkType: hard +"@img/colour@npm:^1.1.0": + version: 1.1.0 + resolution: "@img/colour@npm:1.1.0" + checksum: 10c0/2ebea2c0bbaee73b99badcefa04e1e71d83f36e5369337d3121dca841f4569533c4e2faddda6d62dd247f0d5cca143711f9446c59bcce81e427ba433a7a94a17 + languageName: node + linkType: hard + +"@img/sharp-darwin-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-darwin-arm64@npm:0.35.3" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-darwin-x64@npm:0.35.3" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-freebsd-wasm32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-freebsd-wasm32@npm:0.35.3" + dependencies: + "@img/sharp-wasm32": "npm:0.35.3" + conditions: os=freebsd + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.3.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.3.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.3.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-arm@npm:1.3.2" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.3.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-riscv64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.3.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.3.2" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linux-x64@npm:1.3.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.3.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.3.2": + version: 1.3.2 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.3.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-arm64@npm:0.35.3" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-arm@npm:0.35.3" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-ppc64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-ppc64@npm:0.35.3" + dependencies: + "@img/sharp-libvips-linux-ppc64": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-riscv64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-riscv64@npm:0.35.3" + dependencies: + "@img/sharp-libvips-linux-riscv64": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-linux-riscv64": + optional: true + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-s390x@npm:0.35.3" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linux-x64@npm:0.35.3" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.35.3" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-linuxmusl-x64@npm:0.35.3" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.3.2" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-wasm32@npm:0.35.3" + dependencies: + "@emnapi/runtime": "npm:^1.11.1" + checksum: 10c0/627125c330ab96e324c888d3c43fa72eb7c975e653277755121df14447373b7b47b011a1eae9c8c360a9535a7072bd6205b6498974006124b513c033a07e0e32 + languageName: node + linkType: hard + +"@img/sharp-webcontainers-wasm32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-webcontainers-wasm32@npm:0.35.3" + dependencies: + "@img/sharp-wasm32": "npm:0.35.3" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-win32-arm64@npm:0.35.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-win32-ia32@npm:0.35.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.35.3": + version: 0.35.3 + resolution: "@img/sharp-win32-x64@npm:0.35.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@internationalized/date@npm:^3.11.0, @internationalized/date@npm:^3.7.0": version: 3.11.0 resolution: "@internationalized/date@npm:3.11.0" @@ -3088,10 +4068,10 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0": - version: 1.5.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" - checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 languageName: node linkType: hard @@ -3179,6 +4159,39 @@ __metadata: languageName: node linkType: hard +"@mdx-js/mdx@npm:^3.1.1": + version: 3.1.1 + resolution: "@mdx-js/mdx@npm:3.1.1" + dependencies: + "@types/estree": "npm:^1.0.0" + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdx": "npm:^2.0.0" + acorn: "npm:^8.0.0" + collapse-white-space: "npm:^2.0.0" + devlop: "npm:^1.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + estree-util-scope: "npm:^1.0.0" + estree-walker: "npm:^3.0.0" + hast-util-to-jsx-runtime: "npm:^2.0.0" + markdown-extensions: "npm:^2.0.0" + recma-build-jsx: "npm:^1.0.0" + recma-jsx: "npm:^1.0.0" + recma-stringify: "npm:^1.0.0" + rehype-recma: "npm:^1.0.0" + remark-mdx: "npm:^3.0.0" + remark-parse: "npm:^11.0.0" + remark-rehype: "npm:^11.0.0" + source-map: "npm:^0.7.0" + unified: "npm:^11.0.0" + unist-util-position-from-estree: "npm:^2.0.0" + unist-util-stringify-position: "npm:^4.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/371ed95e2bee7731f30a7ce57db66383a0b7470e66c38139427174cb456d6a40bf7d259f3652716370c1de64acfba50a1ba27eb8c556e7a431dc7940b04cb1a1 + languageName: node + linkType: hard + "@mdx-js/react@npm:^3.0.0": version: 3.1.0 resolution: "@mdx-js/react@npm:3.1.0" @@ -3506,6 +4519,13 @@ __metadata: languageName: node linkType: hard +"@napi-rs/lzma-linux-x64-gnu@npm:1.5.1": + version: 1.5.1 + resolution: "@napi-rs/lzma-linux-x64-gnu@npm:1.5.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@napi-rs/wasm-runtime@npm:0.2.4": version: 0.2.4 resolution: "@napi-rs/wasm-runtime@npm:0.2.4" @@ -3539,14 +4559,15 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-runtime@npm:^1.1.1": - version: 1.1.1 - resolution: "@napi-rs/wasm-runtime@npm:1.1.1" +"@napi-rs/wasm-runtime@npm:^1.1.1, @napi-rs/wasm-runtime@npm:^1.1.6, @napi-rs/wasm-runtime@npm:^1.2.2, @napi-rs/wasm-runtime@npm:^1.2.3": + version: 1.2.3 + resolution: "@napi-rs/wasm-runtime@npm:1.2.3" dependencies: - "@emnapi/core": "npm:^1.7.1" - "@emnapi/runtime": "npm:^1.7.1" - "@tybys/wasm-util": "npm:^0.10.1" - checksum: 10c0/04d57b67e80736e41fe44674a011878db0a8ad893f4d44abb9d3608debb7c174224cba2796ed5b0c1d367368159f3ca6be45f1c59222f70e32ddc880f803d447 + "@tybys/wasm-util": "npm:^0.10.3" + peerDependencies: + "@emnapi/core": ^1.7.1 || ^2.0.0-alpha.4 + "@emnapi/runtime": ^1.7.1 || ^2.0.0-alpha.4 + checksum: 10c0/6da0a4bf9df79e9abfb3e3d462f6a5d42889be39426040038c9dd5925865585d7dbd06dd439c2ffc20f49ef2751412da5bd748cfb3c5b049142d72c0550f6f79 languageName: node linkType: hard @@ -4085,6 +5106,20 @@ __metadata: languageName: node linkType: hard +"@oslojs/encoding@npm:^1.1.0": + version: 1.1.0 + resolution: "@oslojs/encoding@npm:1.1.0" + checksum: 10c0/5553a0974dca60e1a8b247b7b97abcb141cc7ee4e22444f424a07921d6a5f76a43c316f3ee669222787fdef6549f8749cc6d68ff5a631e2542521c56fe36417f + languageName: node + linkType: hard + +"@oxc-project/types@npm:=0.146.0": + version: 0.146.0 + resolution: "@oxc-project/types@npm:0.146.0" + checksum: 10c0/15e99d1d4d9233244262779b6e3bbaf7f11a4e62b57c21ef3755acbec469cb90cd468548103fec282af649096e9c6389fd5221c28b268579061352c49b29f4c8 + languageName: node + linkType: hard + "@oxc-resolver/binding-android-arm-eabi@npm:11.19.1": version: 11.19.1 resolution: "@oxc-resolver/binding-android-arm-eabi@npm:11.19.1" @@ -4227,6 +5262,62 @@ __metadata: languageName: node linkType: hard +"@pagefind/darwin-arm64@npm:1.5.2": + version: 1.5.2 + resolution: "@pagefind/darwin-arm64@npm:1.5.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@pagefind/darwin-x64@npm:1.5.2": + version: 1.5.2 + resolution: "@pagefind/darwin-x64@npm:1.5.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@pagefind/default-ui@npm:^1.3.0": + version: 1.5.2 + resolution: "@pagefind/default-ui@npm:1.5.2" + checksum: 10c0/2e4add8f315b6ceb2355b635bf1bbc1fc84e4d3d1dadafc38696184aff1d459720a8efcb843d58ae798a30512859620960a671311ff8e197d0a67b43c47fdeef + languageName: node + linkType: hard + +"@pagefind/freebsd-x64@npm:1.5.2": + version: 1.5.2 + resolution: "@pagefind/freebsd-x64@npm:1.5.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@pagefind/linux-arm64@npm:1.5.2": + version: 1.5.2 + resolution: "@pagefind/linux-arm64@npm:1.5.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@pagefind/linux-x64@npm:1.5.2": + version: 1.5.2 + resolution: "@pagefind/linux-x64@npm:1.5.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@pagefind/windows-arm64@npm:1.5.2": + version: 1.5.2 + resolution: "@pagefind/windows-arm64@npm:1.5.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@pagefind/windows-x64@npm:1.5.2": + version: 1.5.2 + resolution: "@pagefind/windows-x64@npm:1.5.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@parcel/watcher-android-arm64@npm:2.4.1": version: 2.4.1 resolution: "@parcel/watcher-android-arm64@npm:2.4.1" @@ -6090,6 +7181,125 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-android-arm-eabi@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-android-arm-eabi@npm:1.2.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-android-arm64@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-android-arm64@npm:1.2.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-darwin-arm64@npm:1.2.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-darwin-x64@npm:1.2.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-freebsd-x64@npm:1.2.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.5" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.5" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-ppc64-gnu@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.5" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.5" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.5" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.5" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.5" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.2.5": + version: 1.2.5 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-rc.3": + version: 1.0.0-rc.3 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.3" + checksum: 10c0/3928b6282a30f307d1b075d2f217180ae173ea9e00638ce46ab65f089bd5f7a0b2c488ae1ce530f509387793c656a2910337c4cd68fa9d37d7e439365989e699 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:^1.0.0": + version: 1.0.1 + resolution: "@rolldown/pluginutils@npm:1.0.1" + checksum: 10c0/99d9b06d90196823e4d8c841f258db7a16e5dbba5824a2962b05d907b79f1ba929d56f22dd744fd530936e568c865ee56a719dc31e57e13bc0a8eb4764a8d8dd + languageName: node + linkType: hard + "@rollup/plugin-babel@npm:^6.0.4": version: 6.0.4 resolution: "@rollup/plugin-babel@npm:6.0.4" @@ -6205,8 +7415,8 @@ __metadata: linkType: hard "@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.1.0": - version: 5.2.0 - resolution: "@rollup/pluginutils@npm:5.2.0" + version: 5.4.0 + resolution: "@rollup/pluginutils@npm:5.4.0" dependencies: "@types/estree": "npm:^1.0.0" estree-walker: "npm:^2.0.2" @@ -6216,146 +7426,181 @@ __metadata: peerDependenciesMeta: rollup: optional: true - checksum: 10c0/794890d512751451bcc06aa112366ef47ea8f9125dac49b1abf72ff8b079518b09359de9c60a013b33266541634e765ae61839c749fae0edb59a463418665c55 + checksum: 10c0/ccc2cbd3a05df642df60ab05ffb81b2e564bd945e2a118bb8a474ea75b941033c8f44273133d4865643cca1492d0c80b14de1281f74779a64285a80fc3a194d8 languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.47.1" +"@rollup/rollup-android-arm-eabi@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.62.4" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-android-arm64@npm:4.47.1" +"@rollup/rollup-android-arm64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-android-arm64@npm:4.62.4" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-darwin-arm64@npm:4.47.1" +"@rollup/rollup-darwin-arm64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-darwin-arm64@npm:4.62.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-darwin-x64@npm:4.47.1" +"@rollup/rollup-darwin-x64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-darwin-x64@npm:4.62.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.47.1" +"@rollup/rollup-freebsd-arm64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.62.4" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-freebsd-x64@npm:4.47.1" +"@rollup/rollup-freebsd-x64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-freebsd-x64@npm:4.62.4" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.47.1" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.62.4" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.47.1" +"@rollup/rollup-linux-arm-musleabihf@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.62.4" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.47.1" +"@rollup/rollup-linux-arm64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.62.4" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.47.1" +"@rollup/rollup-linux-arm64-musl@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.62.4" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loongarch64-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.47.1" +"@rollup/rollup-linux-loong64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.62.4" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.47.1" - conditions: os=linux & cpu=ppc64 & libc=glibc +"@rollup/rollup-linux-loong64-musl@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.62.4" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.62.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.62.4" + conditions: os=linux & cpu=ppc64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.47.1" +"@rollup/rollup-linux-riscv64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.62.4" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.47.1" +"@rollup/rollup-linux-riscv64-musl@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.62.4" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.47.1" +"@rollup/rollup-linux-s390x-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.62.4" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.47.1" +"@rollup/rollup-linux-x64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.62.4" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.47.1" +"@rollup/rollup-linux-x64-musl@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.62.4" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.47.1" +"@rollup/rollup-openbsd-x64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-openbsd-x64@npm:4.62.4" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.62.4" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.62.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.47.1" +"@rollup/rollup-win32-ia32-msvc@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.62.4" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.47.1" +"@rollup/rollup-win32-x64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.62.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.62.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -6501,6 +7746,86 @@ __metadata: languageName: node linkType: hard +"@shikijs/core@npm:4.4.3": + version: 4.4.3 + resolution: "@shikijs/core@npm:4.4.3" + dependencies: + "@shikijs/primitive": "npm:4.4.3" + "@shikijs/types": "npm:4.4.3" + "@shikijs/vscode-textmate": "npm:^10.0.2" + "@types/hast": "npm:^3.0.5" + hast-util-to-html: "npm:^9.0.5" + checksum: 10c0/6ee90d70d9c2b98cadd11c1b9174851f8e761d5a47cf9723d7070546c936135e3faf1bccb9249d489e04963ffcadd07fad48385953057cbd40de49d7fd1f93a3 + languageName: node + linkType: hard + +"@shikijs/engine-javascript@npm:4.4.3": + version: 4.4.3 + resolution: "@shikijs/engine-javascript@npm:4.4.3" + dependencies: + "@shikijs/types": "npm:4.4.3" + "@shikijs/vscode-textmate": "npm:^10.0.2" + oniguruma-to-es: "npm:^4.3.6" + checksum: 10c0/22e611a783950dccb72c45c699d423897378d9bb85681fea6de2dda93e9fe238ade93c530ebe6b7c229ba23817fc64797ddecb314af4ea1e9e317f6167ee4d82 + languageName: node + linkType: hard + +"@shikijs/engine-oniguruma@npm:4.4.3": + version: 4.4.3 + resolution: "@shikijs/engine-oniguruma@npm:4.4.3" + dependencies: + "@shikijs/types": "npm:4.4.3" + "@shikijs/vscode-textmate": "npm:^10.0.2" + checksum: 10c0/119f5a7e0b660c5d3b5a15a1fb564ca8705e2e6b533e766dfd3124faf3bf3abd869c1fcf70be9b4b04865ae8d8bcb75d4185defacb2a5e1bfa66eea5d0a7d465 + languageName: node + linkType: hard + +"@shikijs/langs@npm:4.4.3": + version: 4.4.3 + resolution: "@shikijs/langs@npm:4.4.3" + dependencies: + "@shikijs/types": "npm:4.4.3" + checksum: 10c0/3150ca6b69440511bab56b089a28228206012003e105739509728964108ac7fdce36b3637f4927de401e6960bad88298a68699b92b6021e5afe8eca4102876e7 + languageName: node + linkType: hard + +"@shikijs/primitive@npm:4.4.3": + version: 4.4.3 + resolution: "@shikijs/primitive@npm:4.4.3" + dependencies: + "@shikijs/types": "npm:4.4.3" + "@shikijs/vscode-textmate": "npm:^10.0.2" + "@types/hast": "npm:^3.0.5" + checksum: 10c0/ecee74fca715882eb7dd9de0d7fd5816592285606aa21428abd87dcb5929a065638b2edf275c4199dc69191a4a719c337f522bec9c6dacfe0b63bba5fcc34a38 + languageName: node + linkType: hard + +"@shikijs/themes@npm:4.4.3": + version: 4.4.3 + resolution: "@shikijs/themes@npm:4.4.3" + dependencies: + "@shikijs/types": "npm:4.4.3" + checksum: 10c0/69b8e1f1125d493854361e9b31e89b6d7fa14df65600b07e41b581a20e2d24b5291fea4ca4ee4186186c99e8bd3d67cc4569bbf517e6372c9329d556e36ed004 + languageName: node + linkType: hard + +"@shikijs/types@npm:4.4.3": + version: 4.4.3 + resolution: "@shikijs/types@npm:4.4.3" + dependencies: + "@shikijs/vscode-textmate": "npm:^10.0.2" + "@types/hast": "npm:^3.0.5" + checksum: 10c0/ba8f0f38332d9bf44298e47acc5bf8229025464ed2dce38deddd1198d5d69a195792b29a6800bf2dfb89db67ae6d70720a48340b5ed5a4c33afebd364cca8ab7 + languageName: node + linkType: hard + +"@shikijs/vscode-textmate@npm:^10.0.2": + version: 10.0.2 + resolution: "@shikijs/vscode-textmate@npm:10.0.2" + checksum: 10c0/36b682d691088ec244de292dc8f91b808f95c89466af421cf84cbab92230f03c8348649c14b3251991b10ce632b0c715e416e992dd5f28ff3221dc2693fd9462 + languageName: node + linkType: hard + "@sideway/address@npm:^4.1.5": version: 4.1.5 resolution: "@sideway/address@npm:4.1.5" @@ -7514,12 +8839,12 @@ __metadata: languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.10.0, @tybys/wasm-util@npm:^0.10.1": - version: 0.10.1 - resolution: "@tybys/wasm-util@npm:0.10.1" +"@tybys/wasm-util@npm:^0.10.0, @tybys/wasm-util@npm:^0.10.1, @tybys/wasm-util@npm:^0.10.3": + version: 0.10.3 + resolution: "@tybys/wasm-util@npm:0.10.3" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/b255094f293794c6d2289300c5fbcafbb5532a3aed3a5ffd2f8dc1828e639b88d75f6a376dd8f94347a44813fd7a7149d8463477a9a49525c8b2dcaa38c2d1e8 + checksum: 10c0/fd2bd2a79c6cd8c79ed1cf7a0fa375c64589264c88a27acaf9756d556b453ea222b62a4f68dd2fbb8b3a78b6bab3b1f4fb2431b6afc6aeda8344b53a521a1cd3 languageName: node linkType: hard @@ -7696,7 +9021,7 @@ __metadata: languageName: node linkType: hard -"@types/estree-jsx@npm:^1.0.0": +"@types/estree-jsx@npm:^1.0.0, @types/estree-jsx@npm:^1.0.5": version: 1.0.5 resolution: "@types/estree-jsx@npm:1.0.5" dependencies: @@ -7705,10 +9030,10 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.8": - version: 1.0.8 - resolution: "@types/estree@npm:1.0.8" - checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 +"@types/estree@npm:*, @types/estree@npm:1.0.9, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.8": + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 languageName: node linkType: hard @@ -7769,12 +9094,12 @@ __metadata: languageName: node linkType: hard -"@types/hast@npm:^3.0.0": - version: 3.0.4 - resolution: "@types/hast@npm:3.0.4" +"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4, @types/hast@npm:^3.0.5": + version: 3.0.5 + resolution: "@types/hast@npm:3.0.5" dependencies: "@types/unist": "npm:*" - checksum: 10c0/3249781a511b38f1d330fd1e3344eed3c4e7ea8eff82e835d35da78e637480d36fad37a78be5a7aed8465d237ad0446abc1150859d0fde395354ea634decf9f7 + checksum: 10c0/f3af8594a6903a507ed191eda944af18099198d6708c29102ae17118c3e20779f6929e91ed37e033541fd28d58055d8a5910a4366dbdf976cf81b13a464741fb languageName: node linkType: hard @@ -7857,6 +9182,13 @@ __metadata: languageName: node linkType: hard +"@types/js-yaml@npm:^4.0.9": + version: 4.0.9 + resolution: "@types/js-yaml@npm:4.0.9" + checksum: 10c0/24de857aa8d61526bbfbbaa383aa538283ad17363fcd5bb5148e2c7f604547db36646440e739d78241ed008702a8920665d1add5618687b6743858fae00da211 + languageName: node + linkType: hard + "@types/jsdom@npm:^20.0.0": version: 20.0.1 resolution: "@types/jsdom@npm:20.0.1" @@ -7903,7 +9235,7 @@ __metadata: languageName: node linkType: hard -"@types/mdast@npm:^4.0.0": +"@types/mdast@npm:^4.0.0, @types/mdast@npm:^4.0.4": version: 4.0.4 resolution: "@types/mdast@npm:4.0.4" dependencies: @@ -7940,6 +9272,15 @@ __metadata: languageName: node linkType: hard +"@types/nlcst@npm:^2.0.0": + version: 2.0.3 + resolution: "@types/nlcst@npm:2.0.3" + dependencies: + "@types/unist": "npm:*" + checksum: 10c0/d83549aaee59681ae8fa2a78d8a1b968a41eb7c0422773dff12acbf3661e4b2b2859740c3effdad9d0cd12ea14a0ec33ca302da12106476b627e09d2a029d3c1 + languageName: node + linkType: hard + "@types/node-forge@npm:^1.3.0": version: 1.3.11 resolution: "@types/node-forge@npm:1.3.11" @@ -7949,7 +9290,16 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:^22.0.0": +"@types/node@npm:*, @types/node@npm:^24.9.2": + version: 24.13.3 + resolution: "@types/node@npm:24.13.3" + dependencies: + undici-types: "npm:~7.18.0" + checksum: 10c0/a5bc08f49b9581dcdca90e02cd77197a3c799840807664942e5cd5161a553d4d2ae8064f7e3294410d748d7d098b5c1848be7fa50c06f29c4193ab16be4e59eb + languageName: node + linkType: hard + +"@types/node@npm:^22.0.0": version: 22.19.15 resolution: "@types/node@npm:22.19.15" dependencies: @@ -8058,6 +9408,15 @@ __metadata: languageName: node linkType: hard +"@types/sax@npm:^1.2.1": + version: 1.2.7 + resolution: "@types/sax@npm:1.2.7" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/d077a761a0753b079bf8279b3993948030ca86ed9125437b9b29c1de40db9b2deb7fddc369f014b58861d450e8b8cc75f163aa29dc8cea81952efbfd859168cf + languageName: node + linkType: hard + "@types/semver@npm:7.5.8": version: 7.5.8 resolution: "@types/semver@npm:7.5.8" @@ -8148,7 +9507,7 @@ __metadata: languageName: node linkType: hard -"@types/unist@npm:*, @types/unist@npm:^3.0.0": +"@types/unist@npm:*, @types/unist@npm:^3.0.0, @types/unist@npm:^3.0.3": version: 3.0.3 resolution: "@types/unist@npm:3.0.3" checksum: 10c0/2b1e4adcab78388e088fcc3c0ae8700f76619dbcb4741d7d201f87e2cb346bfc29a89003cfea2d76c996e1061452e14fcd737e8b25aacf949c1f2d6b2bc3dd60 @@ -8331,10 +9690,10 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.2.0, @ungap/structured-clone@npm:^1.3.0": - version: 1.3.0 - resolution: "@ungap/structured-clone@npm:1.3.0" - checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a +"@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0, @ungap/structured-clone@npm:^1.3.0": + version: 1.3.3 + resolution: "@ungap/structured-clone@npm:1.3.3" + checksum: 10c0/b199e280ee06e9c447e0ccd38df60a65c2b2c13d3c77af50b1e6d77230aee1ea7da309d7b80c5a4850c8e22e4eee9e4b2813c6a33f8c360560d7f2e99a9f6e8f languageName: node linkType: hard @@ -8486,6 +9845,22 @@ __metadata: languageName: node linkType: hard +"@vitejs/plugin-react@npm:^5.2.0": + version: 5.2.0 + resolution: "@vitejs/plugin-react@npm:5.2.0" + dependencies: + "@babel/core": "npm:^7.29.0" + "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" + "@rolldown/pluginutils": "npm:1.0.0-rc.3" + "@types/babel__core": "npm:^7.20.5" + react-refresh: "npm:^0.18.0" + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/bac0a409e71eee954a05bc41580411c369bd5f9ef0586a1f9743fba76ad6603c437d93d407d230780015361f93d1592c55e53314813cded6369c36d3c1e8edbf + languageName: node + linkType: hard + "@vitest/expect@npm:3.2.4": version: 3.2.4 resolution: "@vitest/expect@npm:3.2.4" @@ -8913,6 +10288,17 @@ __metadata: languageName: node linkType: hard +"am-i-vibing@npm:^0.4.0": + version: 0.4.0 + resolution: "am-i-vibing@npm:0.4.0" + dependencies: + process-ancestry: "npm:^0.1.0" + bin: + am-i-vibing: dist/cli.mjs + checksum: 10c0/af511ee621c78957e7e9e86b15498d64b9d73c54689e571a03531fe123002d3a5f7bd2d7fadda9cc07511c8f7cfdcc888d897bebeb12d2bc8b725161e64fd3f5 + languageName: node + linkType: hard + "ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" @@ -9026,7 +10412,7 @@ __metadata: languageName: node linkType: hard -"arg@npm:^5.0.2": +"arg@npm:^5.0.0, arg@npm:^5.0.2": version: 5.0.2 resolution: "arg@npm:5.0.2" checksum: 10c0/ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e @@ -9105,6 +10491,13 @@ __metadata: languageName: node linkType: hard +"array-iterate@npm:^2.0.0": + version: 2.0.1 + resolution: "array-iterate@npm:2.0.1" + checksum: 10c0/756c08334f95e290f03ab2141b034514af1311ef7b62f15b0f5ea6f8f3033ee9cc6a8f1c3e9ff4803d4d723cf992aa61460acf5fce884936972db966b1da287d + languageName: node + linkType: hard + "array-union@npm:^1.0.1": version: 1.0.2 resolution: "array-union@npm:1.0.2" @@ -9267,6 +10660,110 @@ __metadata: languageName: node linkType: hard +"astring@npm:^1.8.0": + version: 1.9.0 + resolution: "astring@npm:1.9.0" + bin: + astring: bin/astring + checksum: 10c0/e7519544d9824494e80ef0e722bb3a0c543a31440d59691c13aeaceb75b14502af536b23f08db50aa6c632dafaade54caa25f0788aa7550b6b2d6e2df89e0830 + languageName: node + linkType: hard + +"astro-expressive-code@npm:^0.44.0": + version: 0.44.1 + resolution: "astro-expressive-code@npm:0.44.1" + dependencies: + rehype-expressive-code: "npm:^0.44.1" + url-extras: "npm:^0.1.0" + peerDependencies: + astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta || ^7.0.0 + checksum: 10c0/52d57b7d0b8b0001a51e55f37eb82ade6dea93c53ca1455930c21b89f6e245a3e2660da75fbe924ea019f403cc2648675772ac0352f3a92f5486564e79a54a41 + languageName: node + linkType: hard + +"astro-live-code@npm:^0.0.6": + version: 0.0.6 + resolution: "astro-live-code@npm:0.0.6" + dependencies: + estree-util-visit: "npm:^2.0.0" + magic-string: "npm:^0.30.5" + unist-util-visit-parents: "npm:^6.0.1" + checksum: 10c0/e3c9a9f6dc0447831ad2562d4c6817647d905920abe9f46944edc6667a9d21e67bcad758f51a50322397cf106f59fa58a807dd32b1086d08db25baed641e95aa + languageName: node + linkType: hard + +"astro@npm:^7.2.6": + version: 7.2.6 + resolution: "astro@npm:7.2.6" + dependencies: + "@astrojs/compiler-rs": "npm:^0.4.0" + "@astrojs/internal-helpers": "npm:0.10.4" + "@astrojs/markdown-satteri": "npm:0.3.8" + "@astrojs/telemetry": "npm:3.3.3" + "@capsizecss/unpack": "npm:^4.0.0" + "@clack/prompts": "npm:^1.1.0" + "@oslojs/encoding": "npm:^1.1.0" + am-i-vibing: "npm:^0.4.0" + aria-query: "npm:^5.3.2" + axobject-query: "npm:^4.1.0" + ci-info: "npm:^4.4.0" + clsx: "npm:^2.1.1" + common-ancestor-path: "npm:^2.0.0" + cookie: "npm:^2.0.1" + devalue: "npm:^5.8.1" + diff: "npm:^9.0.0" + dset: "npm:^3.1.4" + es-module-lexer: "npm:^2.0.0" + esbuild: "npm:^0.28.0" + find-process: "npm:^2.1.1" + flattie: "npm:^1.1.1" + fontace: "npm:~0.4.1" + get-tsconfig: "npm:5.0.0-beta.4" + github-slugger: "npm:^2.0.0" + html-escaper: "npm:3.0.3" + http-cache-semantics: "npm:^4.2.0" + js-yaml: "npm:^4.3.0" + jsonc-parser: "npm:^3.3.1" + magic-string: "npm:^1.0.0" + magicast: "npm:^0.5.2" + mrmime: "npm:^2.0.1" + neotraverse: "npm:^1.0.1" + obug: "npm:^2.1.1" + p-limit: "npm:^7.3.0" + p-queue: "npm:^9.1.0" + package-manager-detector: "npm:^1.6.0" + piccolore: "npm:^0.1.3" + picomatch: "npm:^4.0.4" + semver: "npm:^7.7.4" + sharp: "npm:^0.34.0 || ^0.35.0" + shiki: "npm:^4.0.2" + smol-toml: "npm:^1.6.0" + svgo: "npm:^4.0.1" + tinyclip: "npm:^0.1.12" + tinyexec: "npm:^1.0.4" + tinyglobby: "npm:^0.2.15" + ultrahtml: "npm:^1.6.0" + unifont: "npm:~0.7.5" + unstorage: "npm:^1.17.5" + vite: "npm:^8.0.13" + vitefu: "npm:^1.1.2" + xxhash-wasm: "npm:^1.1.0" + yargs-parser: "npm:^22.0.0" + zod: "npm:^4.3.6" + peerDependencies: + "@astrojs/markdown-remark": 7.2.4 + dependenciesMeta: + sharp: + optional: true + peerDependenciesMeta: + "@astrojs/markdown-remark": + optional: true + bin: + astro: ./bin/astro.mjs + checksum: 10c0/eaa42c42693ecca4eb4f377b9f037ff1f42cebfc218ef4922aec1b550f7031d7d2fb05ff83dc2717e01618799204ffc6a905da269c9e6c834f70042b169acd31 + languageName: node + linkType: hard + "async-function@npm:^1.0.0": version: 1.0.0 resolution: "async-function@npm:1.0.0" @@ -9663,6 +11160,24 @@ __metadata: languageName: node linkType: hard +"bcp-47-match@npm:^2.0.0": + version: 2.0.3 + resolution: "bcp-47-match@npm:2.0.3" + checksum: 10c0/ae5c202854df8a9ad4777dc3b49562578495a69164869f365a88c1a089837a9fbbce4c0c44f6f1a5e44c7841f47e91fe6fea00306ca49ce5ec95a7eb71f839c4 + languageName: node + linkType: hard + +"bcp-47@npm:^2.1.0": + version: 2.1.1 + resolution: "bcp-47@npm:2.1.1" + dependencies: + is-alphabetical: "npm:^2.0.0" + is-alphanumerical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + checksum: 10c0/086cb208c8bb0a2dad6ffcfc626ed9f81116037b92cecb63525410ccc943c1dfb78cb1b9ad41a8bcd239d98500933d1c4c44ae6903d5af428f738f2d743f8dec + languageName: node + linkType: hard + "big.js@npm:^5.2.2": version: 5.2.2 resolution: "big.js@npm:5.2.2" @@ -10125,6 +11640,15 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^5.0.0": + version: 5.0.0 + resolution: "chokidar@npm:5.0.0" + dependencies: + readdirp: "npm:^5.0.0" + checksum: 10c0/42fc907cb2a7ff5c9e220f84dae75380a77997f851c2a5e7865a2cf9ae45dd407a23557208cdcdbf3ac8c93341135a1748e4c48c31855f3bfa095e5159b6bdec + languageName: node + linkType: hard + "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -10153,7 +11677,7 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^4.0.0, ci-info@npm:^4.2.0": +"ci-info@npm:^4.0.0, ci-info@npm:^4.2.0, ci-info@npm:^4.4.0": version: 4.4.0 resolution: "ci-info@npm:4.4.0" checksum: 10c0/44156201545b8dde01aa8a09ee2fe9fc7a73b1bef9adbd4606c9f61c8caeeb73fb7a575c88b0443f7b4edb5ee45debaa59ed54ba5f99698339393ca01349eb3a @@ -10284,7 +11808,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^2.0.0": +"clsx@npm:^2.0.0, clsx@npm:^2.1.1": version: 2.1.1 resolution: "clsx@npm:2.1.1" checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 @@ -10309,6 +11833,13 @@ __metadata: languageName: node linkType: hard +"collapse-white-space@npm:^2.0.0": + version: 2.1.0 + resolution: "collapse-white-space@npm:2.1.0" + checksum: 10c0/b2e2800f4ab261e62eb27a1fbe853378296e3a726d6695117ed033e82d61fb6abeae4ffc1465d5454499e237005de9cfc52c9562dc7ca4ac759b9a222ef14453 + languageName: node + linkType: hard + "collect-v8-coverage@npm:^1.0.0, collect-v8-coverage@npm:^1.0.2": version: 1.0.2 resolution: "collect-v8-coverage@npm:1.0.2" @@ -10388,6 +11919,13 @@ __metadata: languageName: node linkType: hard +"comma-separated-tokens@npm:^2.0.0": + version: 2.0.3 + resolution: "comma-separated-tokens@npm:2.0.3" + checksum: 10c0/91f90f1aae320f1755d6957ef0b864fe4f54737f3313bd95e0802686ee2ca38bff1dd381964d00ae5db42912dd1f4ae5c2709644e82706ffc6f6842a813cdd67 + languageName: node + linkType: hard + "commander@npm:11.0.0": version: 11.0.0 resolution: "commander@npm:11.0.0" @@ -10451,6 +11989,13 @@ __metadata: languageName: node linkType: hard +"common-ancestor-path@npm:^2.0.0": + version: 2.0.0 + resolution: "common-ancestor-path@npm:2.0.0" + checksum: 10c0/fa0872dc8d5ffb2c0bb006d1f9e7ba4586773df4f0cf3dfa4b4c95710cedb8a78246fbbcc1392c71c882bd5428a2d003851bdd9033f549a445ac2c5deacb45ca + languageName: node + linkType: hard + "common-path-prefix@npm:^3.0.0": version: 3.0.0 resolution: "common-path-prefix@npm:3.0.0" @@ -10568,6 +12113,13 @@ __metadata: languageName: node linkType: hard +"cookie-es@npm:^1.2.3": + version: 1.2.3 + resolution: "cookie-es@npm:1.2.3" + checksum: 10c0/429eae6f5130a7380ea024d787d7e1ecc644ca84f9c43dfb70f18761a831d2ba591d28f837ce350892cfff0857d711f3a4ad93a082637bceb478823c339c1a97 + languageName: node + linkType: hard + "cookie-signature@npm:1.0.6": version: 1.0.6 resolution: "cookie-signature@npm:1.0.6" @@ -10575,10 +12127,10 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.7.1": - version: 0.7.1 - resolution: "cookie@npm:0.7.1" - checksum: 10c0/5de60c67a410e7c8dc8a46a4b72eb0fe925871d057c9a5d2c0e8145c4270a4f81076de83410c4d397179744b478e33cd80ccbcc457abf40a9409ad27dcd21dde +"cookie@npm:^2.0.1": + version: 2.0.1 + resolution: "cookie@npm:2.0.1" + checksum: 10c0/dcf3bd6ec9d0f4f32be7575e23242866cb53be55fc3d4cf36215638596661cec1327a3b4ca286ba15eba8f2abd443e422b8c5f708af4ea9f75b4d3dedc67ee4c languageName: node linkType: hard @@ -10771,6 +12323,15 @@ __metadata: languageName: node linkType: hard +"crossws@npm:^0.3.5": + version: 0.3.5 + resolution: "crossws@npm:0.3.5" + dependencies: + uncrypto: "npm:^0.1.3" + checksum: 10c0/9e873546f0806606c4f775219f6811768fc3b3b0765ca8230722e849058ad098318af006e1faa39a8008c03009c37c519f6bccad41b0d78586237585c75fb38b + languageName: node + linkType: hard + "css-declaration-sorter@npm:^7.2.0": version: 7.2.0 resolution: "css-declaration-sorter@npm:7.2.0" @@ -10911,6 +12472,13 @@ __metadata: languageName: node linkType: hard +"css-selector-parser@npm:^3.0.0": + version: 3.3.0 + resolution: "css-selector-parser@npm:3.3.0" + checksum: 10c0/7ec2c19800ce52591cf32d3d3745db5a8715b40dbd01057c9b799577c47b0ce5e29c19369a50bf3d6f8990fc3278544f1f73d5c03646c0fe752ce83330eff608 + languageName: node + linkType: hard + "css-tree@npm:1.0.0-alpha.37": version: 1.0.0-alpha.37 resolution: "css-tree@npm:1.0.0-alpha.37" @@ -10941,7 +12509,7 @@ __metadata: languageName: node linkType: hard -"css-tree@npm:^3.0.1": +"css-tree@npm:^3.0.1, css-tree@npm:^3.1.0": version: 3.2.1 resolution: "css-tree@npm:3.2.1" dependencies: @@ -11356,6 +12924,13 @@ __metadata: languageName: node linkType: hard +"defu@npm:^6.1.6": + version: 6.1.7 + resolution: "defu@npm:6.1.7" + checksum: 10c0/e6635388103c8be3c574ac31302f6930e5e6eeedba32cb1b30cf993c7d9fb571aec2485446dfa23bfa63e55e66156fe109027a9695db82a50f931e91e8d4bedb + languageName: node + linkType: hard + "delayed-stream@npm:~1.0.0": version: 1.0.0 resolution: "delayed-stream@npm:1.0.0" @@ -11384,6 +12959,13 @@ __metadata: languageName: node linkType: hard +"destr@npm:^2.0.5": + version: 2.0.5 + resolution: "destr@npm:2.0.5" + checksum: 10c0/efabffe7312a45ad90d79975376be958c50069f1156b94c181199763a7f971e113bd92227c26b94a169c71ca7dbc13583b7e96e5164743969fc79e1ff153e646 + languageName: node + linkType: hard + "destroy@npm:1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" @@ -11400,6 +12982,13 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^2.0.3, detect-libc@npm:^2.1.2": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + "detect-newline@npm:^3.0.0, detect-newline@npm:^3.1.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" @@ -11434,6 +13023,13 @@ __metadata: languageName: node linkType: hard +"devalue@npm:^5.8.1": + version: 5.9.1 + resolution: "devalue@npm:5.9.1" + checksum: 10c0/051254fbd21cd3ee90de325e038a76ea84d2102d27344043a0f861c7d5c8212dbda63979f053ac2d627d83cf33c42b62d301b0b82b4e6a4c6ea539d8feba87e7 + languageName: node + linkType: hard + "devlop@npm:^1.0.0, devlop@npm:^1.1.0": version: 1.1.0 resolution: "devlop@npm:1.1.0" @@ -11457,6 +13053,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^9.0.0": + version: 9.0.0 + resolution: "diff@npm:9.0.0" + checksum: 10c0/a971cc88f66071a33bd3942db2f51b57d484e9856265ae45cf44fb28ab2fde91f132d9c26d8be0fb6082d2be94d29e1295c1adb251b7461935d6a2dd304cd161 + languageName: node + linkType: hard + "diffable-html@npm:^4.1.0": version: 4.1.0 resolution: "diffable-html@npm:4.1.0" @@ -11475,6 +13078,15 @@ __metadata: languageName: node linkType: hard +"direction@npm:^2.0.0": + version: 2.0.1 + resolution: "direction@npm:2.0.1" + bin: + direction: cli.js + checksum: 10c0/dce809431cad978e0778769a3818ea797ebe0bd542c85032ad9ad98971e2021a146be62feb259d7ffe4b76739e07b23e861b29c3f184ac8d38cc6ba956d5c586 + languageName: node + linkType: hard + "dns-packet@npm:^5.2.2": version: 5.6.1 resolution: "dns-packet@npm:5.6.1" @@ -11682,6 +13294,13 @@ __metadata: languageName: node linkType: hard +"dset@npm:^3.1.4": + version: 3.1.4 + resolution: "dset@npm:3.1.4" + checksum: 10c0/b67bbd28dd8a539e90c15ffb61100eb64ef995c5270a124d4f99bbb53f4d82f55a051b731ba81f3215dd9dce2b4c8d69927dc20b3be1c5fc88bab159467aa438 + languageName: node + linkType: hard + "dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" @@ -12033,9 +13652,9 @@ __metadata: linkType: hard "es-module-lexer@npm:^1.5.0": - version: 1.5.4 - resolution: "es-module-lexer@npm:1.5.4" - checksum: 10c0/300a469488c2f22081df1e4c8398c78db92358496e639b0df7f89ac6455462aaf5d8893939087c1a1cbcbf20eed4610c70e0bcb8f3e4b0d80a5d2611c539408c + version: 1.7.0 + resolution: "es-module-lexer@npm:1.7.0" + checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b languageName: node linkType: hard @@ -12094,36 +13713,149 @@ __metadata: languageName: node linkType: hard +"esast-util-from-estree@npm:^2.0.0": + version: 2.0.0 + resolution: "esast-util-from-estree@npm:2.0.0" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + devlop: "npm:^1.0.0" + estree-util-visit: "npm:^2.0.0" + unist-util-position-from-estree: "npm:^2.0.0" + checksum: 10c0/6c619bc6963314f8f64b32e3b101b321bf121f659e62b11e70f425619c2db6f1d25f4c594a57fd00908da96c67d9bfbf876eb5172abf9e13f47a71796f6630ff + languageName: node + linkType: hard + +"esast-util-from-js@npm:^2.0.0": + version: 2.0.1 + resolution: "esast-util-from-js@npm:2.0.1" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + acorn: "npm:^8.0.0" + esast-util-from-estree: "npm:^2.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/3a446fb0b0d7bcd7e0157aa44b3b692802a08c93edbea81cc0f7fe4437bfdfb4b72e4563fe63b4e36d390086b71185dba4ac921f4180cc6349985c263cc74421 + languageName: node + linkType: hard + "esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0": - version: 0.27.4 - resolution: "esbuild@npm:0.27.4" - dependencies: - "@esbuild/aix-ppc64": "npm:0.27.4" - "@esbuild/android-arm": "npm:0.27.4" - "@esbuild/android-arm64": "npm:0.27.4" - "@esbuild/android-x64": "npm:0.27.4" - "@esbuild/darwin-arm64": "npm:0.27.4" - "@esbuild/darwin-x64": "npm:0.27.4" - "@esbuild/freebsd-arm64": "npm:0.27.4" - "@esbuild/freebsd-x64": "npm:0.27.4" - "@esbuild/linux-arm": "npm:0.27.4" - "@esbuild/linux-arm64": "npm:0.27.4" - "@esbuild/linux-ia32": "npm:0.27.4" - "@esbuild/linux-loong64": "npm:0.27.4" - "@esbuild/linux-mips64el": "npm:0.27.4" - "@esbuild/linux-ppc64": "npm:0.27.4" - "@esbuild/linux-riscv64": "npm:0.27.4" - "@esbuild/linux-s390x": "npm:0.27.4" - "@esbuild/linux-x64": "npm:0.27.4" - "@esbuild/netbsd-arm64": "npm:0.27.4" - "@esbuild/netbsd-x64": "npm:0.27.4" - "@esbuild/openbsd-arm64": "npm:0.27.4" - "@esbuild/openbsd-x64": "npm:0.27.4" - "@esbuild/openharmony-arm64": "npm:0.27.4" - "@esbuild/sunos-x64": "npm:0.27.4" - "@esbuild/win32-arm64": "npm:0.27.4" - "@esbuild/win32-ia32": "npm:0.27.4" - "@esbuild/win32-x64": "npm:0.27.4" + version: 0.27.7 + resolution: "esbuild@npm:0.27.7" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.7" + "@esbuild/android-arm": "npm:0.27.7" + "@esbuild/android-arm64": "npm:0.27.7" + "@esbuild/android-x64": "npm:0.27.7" + "@esbuild/darwin-arm64": "npm:0.27.7" + "@esbuild/darwin-x64": "npm:0.27.7" + "@esbuild/freebsd-arm64": "npm:0.27.7" + "@esbuild/freebsd-x64": "npm:0.27.7" + "@esbuild/linux-arm": "npm:0.27.7" + "@esbuild/linux-arm64": "npm:0.27.7" + "@esbuild/linux-ia32": "npm:0.27.7" + "@esbuild/linux-loong64": "npm:0.27.7" + "@esbuild/linux-mips64el": "npm:0.27.7" + "@esbuild/linux-ppc64": "npm:0.27.7" + "@esbuild/linux-riscv64": "npm:0.27.7" + "@esbuild/linux-s390x": "npm:0.27.7" + "@esbuild/linux-x64": "npm:0.27.7" + "@esbuild/netbsd-arm64": "npm:0.27.7" + "@esbuild/netbsd-x64": "npm:0.27.7" + "@esbuild/openbsd-arm64": "npm:0.27.7" + "@esbuild/openbsd-x64": "npm:0.27.7" + "@esbuild/openharmony-arm64": "npm:0.27.7" + "@esbuild/sunos-x64": "npm:0.27.7" + "@esbuild/win32-arm64": "npm:0.27.7" + "@esbuild/win32-ia32": "npm:0.27.7" + "@esbuild/win32-x64": "npm:0.27.7" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/ccd51f0555708bc9ff4ec9dc3ac92d3daacd45ecaac949ca8645984c5c323bf8cefe98c2df307418685e0b4ce37f9a3bdbfe8e3651fe632a0059a436195a17d4 + languageName: node + linkType: hard + +"esbuild@npm:^0.28.0": + version: 0.28.2 + resolution: "esbuild@npm:0.28.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.28.2" + "@esbuild/android-arm": "npm:0.28.2" + "@esbuild/android-arm64": "npm:0.28.2" + "@esbuild/android-x64": "npm:0.28.2" + "@esbuild/darwin-arm64": "npm:0.28.2" + "@esbuild/darwin-x64": "npm:0.28.2" + "@esbuild/freebsd-arm64": "npm:0.28.2" + "@esbuild/freebsd-x64": "npm:0.28.2" + "@esbuild/linux-arm": "npm:0.28.2" + "@esbuild/linux-arm64": "npm:0.28.2" + "@esbuild/linux-ia32": "npm:0.28.2" + "@esbuild/linux-loong64": "npm:0.28.2" + "@esbuild/linux-mips64el": "npm:0.28.2" + "@esbuild/linux-ppc64": "npm:0.28.2" + "@esbuild/linux-riscv64": "npm:0.28.2" + "@esbuild/linux-s390x": "npm:0.28.2" + "@esbuild/linux-x64": "npm:0.28.2" + "@esbuild/netbsd-arm64": "npm:0.28.2" + "@esbuild/netbsd-x64": "npm:0.28.2" + "@esbuild/openbsd-arm64": "npm:0.28.2" + "@esbuild/openbsd-x64": "npm:0.28.2" + "@esbuild/openharmony-arm64": "npm:0.28.2" + "@esbuild/sunos-x64": "npm:0.28.2" + "@esbuild/win32-arm64": "npm:0.28.2" + "@esbuild/win32-ia32": "npm:0.28.2" + "@esbuild/win32-x64": "npm:0.28.2" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -12179,7 +13911,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/2a1c2bcccda279f2afd72a7f8259860cb4483b32453d17878e1ecb4ac416b9e7c1001e7aa0a25ba4c29c1e250a3ceaae5d8bb72a119815bc8db4e9b5f5321490 + checksum: 10c0/9b19edb63bd7780fd2e8e65a1394a0e8a05a17d697f73f0647ffe3c134709d8dbe744ab3fd57b35c9470db268cae7678fafd3dcd3ce1f34fc590568c2433f5d2 languageName: node linkType: hard @@ -12197,7 +13929,7 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:5.0.0": +"escape-string-regexp@npm:5.0.0, escape-string-regexp@npm:^5.0.0": version: 5.0.0 resolution: "escape-string-regexp@npm:5.0.0" checksum: 10c0/6366f474c6f37a802800a435232395e04e9885919873e382b157ab7e8f0feb8fed71497f84a6f6a81a49aab41815522f5839112bd38026d203aea0c91622df95 @@ -12620,6 +14352,27 @@ __metadata: languageName: node linkType: hard +"estree-util-attach-comments@npm:^3.0.0": + version: 3.0.0 + resolution: "estree-util-attach-comments@npm:3.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/ee69bb5c45e2ad074725b90ed181c1c934b29d81bce4b0c7761431e83c4c6ab1b223a6a3d6a4fbeb92128bc5d5ee201d5dd36cf1770aa5e16a40b0cf36e8a1f1 + languageName: node + linkType: hard + +"estree-util-build-jsx@npm:^3.0.0": + version: 3.0.1 + resolution: "estree-util-build-jsx@npm:3.0.1" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + devlop: "npm:^1.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + estree-walker: "npm:^3.0.0" + checksum: 10c0/274c119817b8e7caa14a9778f1e497fea56cdd2b01df1a1ed037f843178992d3afe85e0d364d485e1e2e239255763553d1b647b15e4a7ba50851bcb43dc6bf80 + languageName: node + linkType: hard + "estree-util-is-identifier-name@npm:^3.0.0": version: 3.0.0 resolution: "estree-util-is-identifier-name@npm:3.0.0" @@ -12627,6 +14380,27 @@ __metadata: languageName: node linkType: hard +"estree-util-scope@npm:^1.0.0": + version: 1.0.0 + resolution: "estree-util-scope@npm:1.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + devlop: "npm:^1.0.0" + checksum: 10c0/ef8a573cc899277c613623a1722f630e2163abbc6e9e2f49e758c59b81b484e248b585df6df09a38c00fbfb6390117997cc80c1347b7a86bc1525d9e462b60d5 + languageName: node + linkType: hard + +"estree-util-to-js@npm:^2.0.0": + version: 2.0.0 + resolution: "estree-util-to-js@npm:2.0.0" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + astring: "npm:^1.8.0" + source-map: "npm:^0.7.0" + checksum: 10c0/ac88cb831401ef99e365f92f4af903755d56ae1ce0e0f0fb8ff66e678141f3d529194f0fb15f6c78cd7554c16fda36854df851d58f9e05cfab15bddf7a97cea0 + languageName: node + linkType: hard + "estree-util-visit@npm:^2.0.0": version: 2.0.0 resolution: "estree-util-visit@npm:2.0.0" @@ -12644,6 +14418,15 @@ __metadata: languageName: node linkType: hard +"estree-walker@npm:^3.0.0": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -12680,10 +14463,10 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^5.0.1": - version: 5.0.1 - resolution: "eventemitter3@npm:5.0.1" - checksum: 10c0/4ba5c00c506e6c786b4d6262cfbce90ddc14c10d4667e5c83ae993c9de88aa856033994dd2b35b83e8dc1170e224e66a319fa80adc4c32adcd2379bbc75da814 +"eventemitter3@npm:^5.0.1, eventemitter3@npm:^5.0.4": + version: 5.0.4 + resolution: "eventemitter3@npm:5.0.4" + checksum: 10c0/575b8cac8d709e1473da46f8f15ef311b57ff7609445a7c71af5cd42598583eee6f098fa7a593e30f27e94b8865642baa0689e8fa97c016f742abdb3b1bf6d9a languageName: node linkType: hard @@ -12823,6 +14606,18 @@ __metadata: languageName: node linkType: hard +"expressive-code@npm:^0.44.1": + version: 0.44.1 + resolution: "expressive-code@npm:0.44.1" + dependencies: + "@expressive-code/core": "npm:^0.44.1" + "@expressive-code/plugin-frames": "npm:^0.44.1" + "@expressive-code/plugin-shiki": "npm:^0.44.1" + "@expressive-code/plugin-text-markers": "npm:^0.44.1" + checksum: 10c0/ca09029718c687bfa494f095a0cc78b5981adc1e19d89a2740b2b7e07b326b205371018d894bfe8abe477cf842b015ef78367ecd575cbb8f2defd6d4910a7967 + languageName: node + linkType: hard + "extend@npm:^3.0.0": version: 3.0.2 resolution: "extend@npm:3.0.2" @@ -12887,6 +14682,22 @@ __metadata: languageName: node linkType: hard +"fast-string-truncated-width@npm:^3.0.2": + version: 3.0.3 + resolution: "fast-string-truncated-width@npm:3.0.3" + checksum: 10c0/043b8663397d14a3880ce4f3407bcda60b40db9bbeafe62863a35d1f9c69ea17c8da3fcd72de235553e6c9cd053128cde9e24ca0d4a7463208f48db3cd23d981 + languageName: node + linkType: hard + +"fast-string-width@npm:^3.0.2": + version: 3.0.2 + resolution: "fast-string-width@npm:3.0.2" + dependencies: + fast-string-truncated-width: "npm:^3.0.2" + checksum: 10c0/c8822d175315bb353ebe782b65214ac53b13e3bf704e03b132ea7bdfa8de6a636375b3ab7a4097545393d109381c37c4f387c72a462c90b61412dbc4632f39a7 + languageName: node + linkType: hard + "fast-uri@npm:^3.0.1": version: 3.0.3 resolution: "fast-uri@npm:3.0.3" @@ -12894,6 +14705,15 @@ __metadata: languageName: node linkType: hard +"fast-wrap-ansi@npm:^0.2.0": + version: 0.2.2 + resolution: "fast-wrap-ansi@npm:0.2.2" + dependencies: + fast-string-width: "npm:^3.0.2" + checksum: 10c0/1aa7be4f7cb86f4bdb14691cb6bcc0b8df8b3b89df142ade3ae1602332dcf6f990cd750a923cd581ca0847808cb4ec1aa5afaafa7a72f849e87a2a62c98fa370 + languageName: node + linkType: hard + "fastest-stable-stringify@npm:^2.0.2": version: 2.0.2 resolution: "fastest-stable-stringify@npm:2.0.2" @@ -13080,6 +14900,19 @@ __metadata: languageName: node linkType: hard +"find-process@npm:^2.1.1": + version: 2.1.1 + resolution: "find-process@npm:2.1.1" + dependencies: + chalk: "npm:~4.1.2" + commander: "npm:^14.0.3" + loglevel: "npm:^1.9.2" + bin: + find-process: dist/cjs/bin/find-process.js + checksum: 10c0/9ae76be1140c6ad676a43e6bbdb7133a0c79a5844393dec2db8e0b55f65edf1ff689a8f59c3532a5810b7517510a41ed31e186ebd23776ad6a24f82737817a30 + languageName: node + linkType: hard + "find-root@npm:^1.1.0": version: 1.1.0 resolution: "find-root@npm:1.1.0" @@ -13144,6 +14977,13 @@ __metadata: languageName: node linkType: hard +"flattie@npm:^1.1.1": + version: 1.1.1 + resolution: "flattie@npm:1.1.1" + checksum: 10c0/a8f8242c7af126cb2f1aa4a067af338fce609fc4c4df183c626fcc70a46c1878ce4aa88cd0dc8ef8f583ad4e7088a3b11ebeb6a62c9c97d75c0b1b0f08182ee3 + languageName: node + linkType: hard + "focus-lock@npm:^1.3.6": version: 1.3.6 resolution: "focus-lock@npm:1.3.6" @@ -13163,6 +15003,24 @@ __metadata: languageName: node linkType: hard +"fontace@npm:~0.4.1": + version: 0.4.1 + resolution: "fontace@npm:0.4.1" + dependencies: + fontkitten: "npm:^1.0.2" + checksum: 10c0/2b3b8bc60192b0e9d87c369f1d72bf5da6c5b0f6055d5eeb0c06693455c55395f9592844273035399289f410202306c1d7f7329312e6e42a2694912d39c70709 + languageName: node + linkType: hard + +"fontkitten@npm:^1.0.2, fontkitten@npm:^1.0.3": + version: 1.0.3 + resolution: "fontkitten@npm:1.0.3" + dependencies: + tiny-inflate: "npm:^1.0.3" + checksum: 10c0/edee7bde2f824a778cf401a2019ada63376fc8bcc5303d09a11e8a9245f35bd945934abea04aef1ca52e8dbb6ce3a17fe9ecdfa52cc0c92f56b25709fb865a76 + languageName: node + linkType: hard + "for-each@npm:^0.3.3, for-each@npm:^0.3.5": version: 0.3.5 resolution: "for-each@npm:0.3.5" @@ -13413,7 +15271,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2, fsevents@npm:^2.3.3, fsevents@npm:~2.3.2": +"fsevents@npm:^2.3.2, fsevents@npm:^2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -13432,7 +15290,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin<compat/fsevents>": +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin<compat/fsevents>": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1" dependencies: @@ -13663,6 +15521,15 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:5.0.0-beta.4": + version: 5.0.0-beta.4 + resolution: "get-tsconfig@npm:5.0.0-beta.4" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/6b908fc5d2abc937fca6938f5e9218238f49b11cd07362831383aa5815b25ce6f5fff36d4bcd70caa17778a54922b654745ce6c0b1bb19be8eac87da4e3fc087 + languageName: node + linkType: hard + "gh-pages@npm:^5.0.0": version: 5.0.0 resolution: "gh-pages@npm:5.0.0" @@ -13681,6 +15548,13 @@ __metadata: languageName: node linkType: hard +"github-slugger@npm:^2.0.0": + version: 2.0.0 + resolution: "github-slugger@npm:2.0.0" + checksum: 10c0/21b912b6b1e48f1e5a50b2292b48df0ff6abeeb0691b161b3d93d84f4ae6b1acd6ae23702e914af7ea5d441c096453cf0f621b72d57893946618d21dd1a1c486 + languageName: node + linkType: hard + "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -13859,132 +15733,434 @@ __metadata: languageName: node linkType: hard -"globby@npm:^6.1.0": - version: 6.1.0 - resolution: "globby@npm:6.1.0" +"globby@npm:^6.1.0": + version: 6.1.0 + resolution: "globby@npm:6.1.0" + dependencies: + array-union: "npm:^1.0.1" + glob: "npm:^7.0.3" + object-assign: "npm:^4.0.1" + pify: "npm:^2.0.0" + pinkie-promise: "npm:^2.0.0" + checksum: 10c0/656ad1f0d02c6ef378c07589519ed3ec27fe988ea177195c05b8aff280320f3d67b91fa0baa6f7e49288f9bf1f92fc84f783a79ac3ed66278f3fa082e627ed84 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 + languageName: node + linkType: hard + +"h3@npm:^1.15.10": + version: 1.15.11 + resolution: "h3@npm:1.15.11" + dependencies: + cookie-es: "npm:^1.2.3" + crossws: "npm:^0.3.5" + defu: "npm:^6.1.6" + destr: "npm:^2.0.5" + iron-webcrypto: "npm:^1.2.1" + node-mock-http: "npm:^1.0.4" + radix3: "npm:^1.1.2" + ufo: "npm:^1.6.3" + uncrypto: "npm:^0.1.3" + checksum: 10c0/6ccb421b9f92e02e6330c2b6697b18ef18e9550e4a1708f224ca517c40ecd201cd00967d0feb26e718595e86e985edec1755933cf8792d34fb8504f1c7cc261d + languageName: node + linkType: hard + +"handle-thing@npm:^2.0.0": + version: 2.0.1 + resolution: "handle-thing@npm:2.0.1" + checksum: 10c0/7ae34ba286a3434f1993ebd1cc9c9e6b6d8ea672182db28b1afc0a7119229552fa7031e3e5f3cd32a76430ece4e94b7da6f12af2eb39d6239a7693e4bd63a998 + languageName: node + linkType: hard + +"hard-rejection@npm:^2.1.0": + version: 2.1.0 + resolution: "hard-rejection@npm:2.1.0" + checksum: 10c0/febc3343a1ad575aedcc112580835b44a89a89e01f400b4eda6e8110869edfdab0b00cd1bd4c3bfec9475a57e79e0b355aecd5be46454b6a62b9a359af60e564 + languageName: node + linkType: hard + +"harmony-reflect@npm:^1.4.6": + version: 1.6.2 + resolution: "harmony-reflect@npm:1.6.2" + checksum: 10c0/fa5b251fbeff0e2d925f0bfb5ffe39e0627639e998c453562d6a39e41789c15499649dc022178c807cf99bfb97e7b974bbbc031ba82078a26be7b098b9bc2b1a + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.2": + version: 1.0.2 + resolution: "has-bigints@npm:1.0.2" + checksum: 10c0/724eb1485bfa3cdff6f18d95130aa190561f00b3fcf9f19dc640baf8176b5917c143b81ec2123f8cddb6c05164a198c94b13e1377c497705ccc8e1a80306e83b + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + languageName: node + linkType: hard + +"has-proto@npm:^1.2.0": + version: 1.2.0 + resolution: "has-proto@npm:1.2.0" + dependencies: + dunder-proto: "npm:^1.0.0" + checksum: 10c0/46538dddab297ec2f43923c3d35237df45d8c55a6fc1067031e04c13ed8a9a8f94954460632fd4da84c31a1721eefee16d901cbb1ae9602bab93bb6e08f93b95 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasha@npm:^5.0.0": + version: 5.2.2 + resolution: "hasha@npm:5.2.2" + dependencies: + is-stream: "npm:^2.0.0" + type-fest: "npm:^0.8.0" + checksum: 10c0/9d10d4e665a37beea6e18ba3a0c0399a05b26e505c5ff2fe9115b64fedb3ca95f68c89cf15b08ee4d09fd3064b5e1bfc8e8247353c7aa6b7388471d0f86dca74 + languageName: node + linkType: hard + +"hasown@npm:^2.0.0, hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"hast-util-embedded@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-embedded@npm:3.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + hast-util-is-element: "npm:^3.0.0" + checksum: 10c0/054c3d3b96fcd5c1d1c6f8d38ce1f7f33022ba6362129a022673d0b539f876acdcababbb9df29812fb927294f98ef7a2f44519a80d637fe3eea1819c9e69eeac + languageName: node + linkType: hard + +"hast-util-format@npm:^1.0.0": + version: 1.1.0 + resolution: "hast-util-format@npm:1.1.0" + dependencies: + "@types/hast": "npm:^3.0.0" + hast-util-embedded: "npm:^3.0.0" + hast-util-minify-whitespace: "npm:^1.0.0" + hast-util-phrasing: "npm:^3.0.0" + hast-util-whitespace: "npm:^3.0.0" + html-whitespace-sensitive-tag-names: "npm:^3.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10c0/6ab223cffe8a524ef4f2564d0385cab174a52551513b318496b3776e6882594eab4810b0f8d90f20e8291fa4e87fa068e03cba316d83c0836dab12dd140e98df + languageName: node + linkType: hard + +"hast-util-from-html@npm:^2.0.0, hast-util-from-html@npm:^2.0.3": + version: 2.0.3 + resolution: "hast-util-from-html@npm:2.0.3" + dependencies: + "@types/hast": "npm:^3.0.0" + devlop: "npm:^1.1.0" + hast-util-from-parse5: "npm:^8.0.0" + parse5: "npm:^7.0.0" + vfile: "npm:^6.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/993ef707c1a12474c8d4094fc9706a72826c660a7e308ea54c50ad893353d32e139b7cbc67510c2e82feac572b320e3b05aeb13d0f9c6302d61261f337b46764 + languageName: node + linkType: hard + +"hast-util-from-parse5@npm:^8.0.0": + version: 8.0.3 + resolution: "hast-util-from-parse5@npm:8.0.3" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + devlop: "npm:^1.0.0" + hastscript: "npm:^9.0.0" + property-information: "npm:^7.0.0" + vfile: "npm:^6.0.0" + vfile-location: "npm:^5.0.0" + web-namespaces: "npm:^2.0.0" + checksum: 10c0/40ace6c0ad43c26f721c7499fe408e639cde917b2350c9299635e6326559855896dae3c3ebf7440df54766b96c4276a7823e8f376a2b6a28b37b591f03412545 + languageName: node + linkType: hard + +"hast-util-has-property@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-has-property@npm:3.0.0" dependencies: - array-union: "npm:^1.0.1" - glob: "npm:^7.0.3" - object-assign: "npm:^4.0.1" - pify: "npm:^2.0.0" - pinkie-promise: "npm:^2.0.0" - checksum: 10c0/656ad1f0d02c6ef378c07589519ed3ec27fe988ea177195c05b8aff280320f3d67b91fa0baa6f7e49288f9bf1f92fc84f783a79ac3ed66278f3fa082e627ed84 + "@types/hast": "npm:^3.0.0" + checksum: 10c0/6e2c0e22ca893c6ebb60f8390e184c4deb041c36d09796756f02cd121c1789c0f5c862ed06caea8f1a80ea8c0ef6a7854dd57946c2eebb76488727bd4a1c952e languageName: node linkType: hard -"gopd@npm:^1.0.1, gopd@npm:^1.2.0": - version: 1.2.0 - resolution: "gopd@npm:1.2.0" - checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead +"hast-util-is-body-ok-link@npm:^3.0.0": + version: 3.0.1 + resolution: "hast-util-is-body-ok-link@npm:3.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10c0/c320cbd9a9a834b007a6f2f8c271e98b8331c0193adf06e0a7c5ea0acae664e97ce28eb4436e0658bc5cdb8f47390ec1c6cba7c4fe1ded10951fcdd1432f60bf languageName: node linkType: hard -"graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 +"hast-util-is-element@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-is-element@npm:3.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10c0/f5361e4c9859c587ca8eb0d8343492f3077ccaa0f58a44cd09f35d5038f94d65152288dcd0c19336ef2c9491ec4d4e45fde2176b05293437021570aa0bc3613b languageName: node linkType: hard -"graphemer@npm:^1.4.0": - version: 1.4.0 - resolution: "graphemer@npm:1.4.0" - checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 +"hast-util-minify-whitespace@npm:^1.0.0": + version: 1.0.1 + resolution: "hast-util-minify-whitespace@npm:1.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + hast-util-embedded: "npm:^3.0.0" + hast-util-is-element: "npm:^3.0.0" + hast-util-whitespace: "npm:^3.0.0" + unist-util-is: "npm:^6.0.0" + checksum: 10c0/20a7d64947e080463084f444ad09c7f28c40e7648ca2d9c6c036e42a67f8e945d352560ff599304c988257c1e477abcf6a1f508c0900211fa58ec1ba21b36533 languageName: node linkType: hard -"handle-thing@npm:^2.0.0": - version: 2.0.1 - resolution: "handle-thing@npm:2.0.1" - checksum: 10c0/7ae34ba286a3434f1993ebd1cc9c9e6b6d8ea672182db28b1afc0a7119229552fa7031e3e5f3cd32a76430ece4e94b7da6f12af2eb39d6239a7693e4bd63a998 +"hast-util-parse-selector@npm:^4.0.0": + version: 4.0.0 + resolution: "hast-util-parse-selector@npm:4.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10c0/5e98168cb44470dc274aabf1a28317e4feb09b1eaf7a48bbaa8c1de1b43a89cd195cb1284e535698e658e3ec26ad91bc5e52c9563c36feb75abbc68aaf68fb9f languageName: node linkType: hard -"hard-rejection@npm:^2.1.0": - version: 2.1.0 - resolution: "hard-rejection@npm:2.1.0" - checksum: 10c0/febc3343a1ad575aedcc112580835b44a89a89e01f400b4eda6e8110869edfdab0b00cd1bd4c3bfec9475a57e79e0b355aecd5be46454b6a62b9a359af60e564 +"hast-util-phrasing@npm:^3.0.0": + version: 3.0.1 + resolution: "hast-util-phrasing@npm:3.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + hast-util-embedded: "npm:^3.0.0" + hast-util-has-property: "npm:^3.0.0" + hast-util-is-body-ok-link: "npm:^3.0.0" + hast-util-is-element: "npm:^3.0.0" + checksum: 10c0/d77e186ea3d7d62f6db9c4a55c3e6d9f1f6affd5f40250e8de9d73f167ae19fcc02fafe1601dfbe36e90f76ed5013ac004f0b6b398aee3a04a7a81de12788600 languageName: node linkType: hard -"harmony-reflect@npm:^1.4.6": - version: 1.6.2 - resolution: "harmony-reflect@npm:1.6.2" - checksum: 10c0/fa5b251fbeff0e2d925f0bfb5ffe39e0627639e998c453562d6a39e41789c15499649dc022178c807cf99bfb97e7b974bbbc031ba82078a26be7b098b9bc2b1a +"hast-util-raw@npm:^9.0.0": + version: 9.1.0 + resolution: "hast-util-raw@npm:9.1.0" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + "@ungap/structured-clone": "npm:^1.0.0" + hast-util-from-parse5: "npm:^8.0.0" + hast-util-to-parse5: "npm:^8.0.0" + html-void-elements: "npm:^3.0.0" + mdast-util-to-hast: "npm:^13.0.0" + parse5: "npm:^7.0.0" + unist-util-position: "npm:^5.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" + web-namespaces: "npm:^2.0.0" + zwitch: "npm:^2.0.0" + checksum: 10c0/d0d909d2aedecef6a06f0005cfae410d6475e6e182d768bde30c3af9fcbbe4f9beb0522bdc21d0679cb3c243c0df40385797ed255148d68b3d3f12e82d12aacc languageName: node linkType: hard -"has-bigints@npm:^1.0.2": - version: 1.0.2 - resolution: "has-bigints@npm:1.0.2" - checksum: 10c0/724eb1485bfa3cdff6f18d95130aa190561f00b3fcf9f19dc640baf8176b5917c143b81ec2123f8cddb6c05164a198c94b13e1377c497705ccc8e1a80306e83b +"hast-util-select@npm:^6.0.2, hast-util-select@npm:^6.0.4": + version: 6.0.4 + resolution: "hast-util-select@npm:6.0.4" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + bcp-47-match: "npm:^2.0.0" + comma-separated-tokens: "npm:^2.0.0" + css-selector-parser: "npm:^3.0.0" + devlop: "npm:^1.0.0" + direction: "npm:^2.0.0" + hast-util-has-property: "npm:^3.0.0" + hast-util-to-string: "npm:^3.0.0" + hast-util-whitespace: "npm:^3.0.0" + nth-check: "npm:^2.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + unist-util-visit: "npm:^5.0.0" + zwitch: "npm:^2.0.0" + checksum: 10c0/d6829953f829c24ffe465c2b156f6a7cd352f7d9b4d601e0e6ca38b85cc4a720bb9f027d34881c3b2a05f4b55c9375e256dbf43ca88604230da784e1c9c7d03f languageName: node linkType: hard -"has-flag@npm:^3.0.0": - version: 3.0.0 - resolution: "has-flag@npm:3.0.0" - checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 +"hast-util-to-estree@npm:^3.0.0": + version: 3.1.3 + resolution: "hast-util-to-estree@npm:3.1.3" + dependencies: + "@types/estree": "npm:^1.0.0" + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + devlop: "npm:^1.0.0" + estree-util-attach-comments: "npm:^3.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + hast-util-whitespace: "npm:^3.0.0" + mdast-util-mdx-expression: "npm:^2.0.0" + mdast-util-mdx-jsx: "npm:^3.0.0" + mdast-util-mdxjs-esm: "npm:^2.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + style-to-js: "npm:^1.0.0" + unist-util-position: "npm:^5.0.0" + zwitch: "npm:^2.0.0" + checksum: 10c0/8e86c075319082c8a6304c5bcdf24ec02466074571e993f58bfa2cfd70850ef46d33b5c402208597a87fe0f02f1e620bda5958217efb1b7396c81c486373b75f languageName: node linkType: hard -"has-flag@npm:^4.0.0": - version: 4.0.0 - resolution: "has-flag@npm:4.0.0" - checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 +"hast-util-to-html@npm:^9.0.0, hast-util-to-html@npm:^9.0.1, hast-util-to-html@npm:^9.0.5": + version: 9.0.5 + resolution: "hast-util-to-html@npm:9.0.5" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + ccount: "npm:^2.0.0" + comma-separated-tokens: "npm:^2.0.0" + hast-util-whitespace: "npm:^3.0.0" + html-void-elements: "npm:^3.0.0" + mdast-util-to-hast: "npm:^13.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + stringify-entities: "npm:^4.0.0" + zwitch: "npm:^2.0.4" + checksum: 10c0/b7a08c30bab4371fc9b4a620965c40b270e5ae7a8e94cf885f43b21705179e28c8e43b39c72885d1647965fb3738654e6962eb8b58b0c2a84271655b4d748836 languageName: node linkType: hard -"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": - version: 1.0.2 - resolution: "has-property-descriptors@npm:1.0.2" +"hast-util-to-jsx-runtime@npm:^2.0.0": + version: 2.3.6 + resolution: "hast-util-to-jsx-runtime@npm:2.3.6" dependencies: - es-define-property: "npm:^1.0.0" - checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + "@types/estree": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + devlop: "npm:^1.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + hast-util-whitespace: "npm:^3.0.0" + mdast-util-mdx-expression: "npm:^2.0.0" + mdast-util-mdx-jsx: "npm:^3.0.0" + mdast-util-mdxjs-esm: "npm:^2.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + style-to-js: "npm:^1.0.0" + unist-util-position: "npm:^5.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/27297e02848fe37ef219be04a26ce708d17278a175a807689e94a821dcffc88aa506d62c3a85beed1f9a8544f7211bdcbcde0528b7b456a57c2e342c3fd11056 languageName: node linkType: hard -"has-proto@npm:^1.2.0": - version: 1.2.0 - resolution: "has-proto@npm:1.2.0" +"hast-util-to-parse5@npm:^8.0.0": + version: 8.0.1 + resolution: "hast-util-to-parse5@npm:8.0.1" dependencies: - dunder-proto: "npm:^1.0.0" - checksum: 10c0/46538dddab297ec2f43923c3d35237df45d8c55a6fc1067031e04c13ed8a9a8f94954460632fd4da84c31a1721eefee16d901cbb1ae9602bab93bb6e08f93b95 + "@types/hast": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + devlop: "npm:^1.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + web-namespaces: "npm:^2.0.0" + zwitch: "npm:^2.0.0" + checksum: 10c0/8e8a1817c7ff8906ac66e7201b1b8d19d9e1b705e695a6e71620270d498d982ec1ecc0e227bd517f723e91e7fdfb90ef75f9ae64d14b3b65239a7d5e1194d7dd languageName: node linkType: hard -"has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": - version: 1.1.0 - resolution: "has-symbols@npm:1.1.0" - checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e +"hast-util-to-string@npm:^3.0.0, hast-util-to-string@npm:^3.0.1": + version: 3.0.1 + resolution: "hast-util-to-string@npm:3.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10c0/b5fa1912a6ba6131affae52a0f4394406c4c0d23c2b0307f1d69988f1030c7bb830289303e67c5ad8f674f5f23a454c1dcd492c39e45a22c1f46d3c9bce5bd0c languageName: node linkType: hard -"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": - version: 1.0.2 - resolution: "has-tostringtag@npm:1.0.2" +"hast-util-to-text@npm:^4.0.1, hast-util-to-text@npm:^4.0.2": + version: 4.0.2 + resolution: "hast-util-to-text@npm:4.0.2" dependencies: - has-symbols: "npm:^1.0.3" - checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + hast-util-is-element: "npm:^3.0.0" + unist-util-find-after: "npm:^5.0.0" + checksum: 10c0/93ecc10e68fe5391c6e634140eb330942e71dea2724c8e0c647c73ed74a8ec930a4b77043b5081284808c96f73f2bee64ee416038ece75a63a467e8d14f09946 languageName: node linkType: hard -"hasha@npm:^5.0.0": - version: 5.2.2 - resolution: "hasha@npm:5.2.2" +"hast-util-whitespace@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-whitespace@npm:3.0.0" dependencies: - is-stream: "npm:^2.0.0" - type-fest: "npm:^0.8.0" - checksum: 10c0/9d10d4e665a37beea6e18ba3a0c0399a05b26e505c5ff2fe9115b64fedb3ca95f68c89cf15b08ee4d09fd3064b5e1bfc8e8247353c7aa6b7388471d0f86dca74 + "@types/hast": "npm:^3.0.0" + checksum: 10c0/b898bc9fe27884b272580d15260b6bbdabe239973a147e97fa98c45fa0ffec967a481aaa42291ec34fb56530dc2d484d473d7e2bae79f39c83f3762307edfea8 languageName: node linkType: hard -"hasown@npm:^2.0.0, hasown@npm:^2.0.2": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" +"hastscript@npm:^9.0.0, hastscript@npm:^9.0.1": + version: 9.0.1 + resolution: "hastscript@npm:9.0.1" dependencies: - function-bind: "npm:^1.1.2" - checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + "@types/hast": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + hast-util-parse-selector: "npm:^4.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + checksum: 10c0/18dc8064e5c3a7a2ae862978e626b97a254e1c8a67ee9d0c9f06d373bba155ed805fc5b5ce21b990fb7bc174624889e5e1ce1cade264f1b1d58b48f994bc85ce languageName: node linkType: hard @@ -14070,6 +16246,13 @@ __metadata: languageName: node linkType: hard +"html-escaper@npm:3.0.3": + version: 3.0.3 + resolution: "html-escaper@npm:3.0.3" + checksum: 10c0/a042fa4139127ff7546513e90ea39cc9161a1938ce90122dbc4260d4b7252c9aa8452f4509c0c2889901b8ae9a8699179150f1f99d3f80bcf7317573c5f08f4e + languageName: node + linkType: hard + "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -14107,6 +16290,13 @@ __metadata: languageName: node linkType: hard +"html-void-elements@npm:^3.0.0": + version: 3.0.0 + resolution: "html-void-elements@npm:3.0.0" + checksum: 10c0/a8b9ec5db23b7c8053876dad73a0336183e6162bf6d2677376d8b38d654fdc59ba74fdd12f8812688f7db6fad451210c91b300e472afc0909224e0a44c8610d2 + languageName: node + linkType: hard + "html-webpack-plugin@npm:^5.5.0": version: 5.6.3 resolution: "html-webpack-plugin@npm:5.6.3" @@ -14128,6 +16318,13 @@ __metadata: languageName: node linkType: hard +"html-whitespace-sensitive-tag-names@npm:^3.0.0": + version: 3.0.1 + resolution: "html-whitespace-sensitive-tag-names@npm:3.0.1" + checksum: 10c0/da06cad111f6a432edd85c6cd09f6b5abbb385872fba79f23f939bdd4626920ac2e62507f604ef94eb8449902033bda292774624e3283b4dea4ed1620a2be3b2 + languageName: node + linkType: hard + "htmlparser2@npm:^3.9.2": version: 3.10.1 resolution: "htmlparser2@npm:3.10.1" @@ -14166,10 +16363,10 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.1": - version: 4.1.1 - resolution: "http-cache-semantics@npm:4.1.1" - checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc +"http-cache-semantics@npm:^4.1.1, http-cache-semantics@npm:^4.2.0": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 languageName: node linkType: hard @@ -14349,6 +16546,18 @@ __metadata: languageName: node linkType: hard +"i18next@npm:^26.0.7": + version: 26.4.0 + resolution: "i18next@npm:26.4.0" + peerDependencies: + typescript: ^5 || ^6 || ^7 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/3edc8b329e61f627278b83668fae9c68d9d5d187fac9d96403b3e69f7afa42cfb7bce8d5bec345dde082483cb57528a837bb2ea3f2a9d377ff23c2b86d6afdcd + languageName: node + linkType: hard + "iconv-lite@npm:0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -14517,6 +16726,13 @@ __metadata: languageName: node linkType: hard +"inline-style-parser@npm:0.2.7": + version: 0.2.7 + resolution: "inline-style-parser@npm:0.2.7" + checksum: 10c0/d884d76f84959517430ae6c22f0bda59bb3f58f539f99aac75a8d786199ec594ed648c6ab4640531f9fc244b0ed5cd8c458078e592d016ef06de793beb1debff + languageName: node + linkType: hard + "inline-style-prefixer@npm:^7.0.1": version: 7.0.1 resolution: "inline-style-prefixer@npm:7.0.1" @@ -14579,6 +16795,13 @@ __metadata: languageName: node linkType: hard +"iron-webcrypto@npm:^1.2.1": + version: 1.2.1 + resolution: "iron-webcrypto@npm:1.2.1" + checksum: 10c0/5cf27c6e2bd3ef3b4970e486235fd82491ab8229e2ed0ac23307c28d6c80d721772a86ed4e9fe2a5cabadd710c2f024b706843b40561fb83f15afee58f809f66 + languageName: node + linkType: hard + "is-alphabetical@npm:^2.0.0": version: 2.0.1 resolution: "is-alphabetical@npm:2.0.1" @@ -14713,6 +16936,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^4.0.0": + version: 4.0.0 + resolution: "is-docker@npm:4.0.0" + bin: + is-docker: cli.js + checksum: 10c0/4ee05c305b545422b172cab17f42900b940a5fa77820380d3244784fde69279d6881305f249b3b7fa2e261c2294804100548f5638880842e5b43df72cec29087 + languageName: node + linkType: hard + "is-empty@npm:^1.0.0": version: 1.2.0 resolution: "is-empty@npm:1.2.0" @@ -16255,14 +18487,14 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" +"js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0, js-yaml@npm:^4.1.1, js-yaml@npm:^4.3.0": + version: 4.3.1 + resolution: "js-yaml@npm:4.3.1" dependencies: argparse: "npm:^2.0.1" bin: js-yaml: bin/js-yaml.js - checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + checksum: 10c0/13c500ca322e0c3f8c81686e6ecda96d2ea37b45247a420c17c7db36932d6965cc27391abc2d1a104501600e7f0d947a5f8b7be6db619c4fefa87901b3512807 languageName: node linkType: hard @@ -16383,7 +18615,7 @@ __metadata: languageName: node linkType: hard -"jsonc-parser@npm:^3.2.0": +"jsonc-parser@npm:^3.2.0, jsonc-parser@npm:^3.3.1": version: 3.3.1 resolution: "jsonc-parser@npm:3.3.1" checksum: 10c0/269c3ae0a0e4f907a914bf334306c384aabb9929bd8c99f909275ebd5c2d3bc70b9bcd119ad794f339dec9f24b6a4ee9cd5a8ab2e6435e730ad4075388fc2ab6 @@ -16457,7 +18689,7 @@ __metadata: languageName: node linkType: hard -"klona@npm:^2.0.5": +"klona@npm:^2.0.5, klona@npm:^2.0.6": version: 2.0.6 resolution: "klona@npm:2.0.6" checksum: 10c0/94eed2c6c2ce99f409df9186a96340558897b3e62a85afdc1ee39103954d2ebe1c1c4e9fe2b0952771771fa96d70055ede8b27962a7021406374fdb695fd4d01 @@ -16548,34 +18780,154 @@ __metadata: languageName: node linkType: hard -"leven@npm:^3.1.0": - version: 3.1.0 - resolution: "leven@npm:3.1.0" - checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df +"leven@npm:^3.1.0": + version: 3.1.0 + resolution: "leven@npm:3.1.0" + checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"license-webpack-plugin@npm:^4.0.2": + version: 4.0.2 + resolution: "license-webpack-plugin@npm:4.0.2" + dependencies: + webpack-sources: "npm:^3.0.0" + peerDependenciesMeta: + webpack: + optional: true + webpack-sources: + optional: true + checksum: 10c0/6014492b22c5f28a4d367057b5b2c1214b83c73785157fea130d5b877b50ed8820d8d8e73e96b3437c455b5b5c6817b36837da093239f95b534be43c0cdcfedc + languageName: node + linkType: hard + +"lightningcss-android-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-android-arm64@npm:1.33.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-arm64@npm:1.33.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-x64@npm:1.33.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-freebsd-x64@npm:1.33.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.33.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-musl@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"levn@npm:^0.4.1": - version: 0.4.1 - resolution: "levn@npm:0.4.1" - dependencies: - prelude-ls: "npm:^1.2.1" - type-check: "npm:~0.4.0" - checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e +"lightningcss-win32-arm64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"license-webpack-plugin@npm:^4.0.2": - version: 4.0.2 - resolution: "license-webpack-plugin@npm:4.0.2" +"lightningcss-win32-x64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.33.0": + version: 1.33.0 + resolution: "lightningcss@npm:1.33.0" dependencies: - webpack-sources: "npm:^3.0.0" - peerDependenciesMeta: - webpack: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.33.0" + lightningcss-darwin-arm64: "npm:1.33.0" + lightningcss-darwin-x64: "npm:1.33.0" + lightningcss-freebsd-x64: "npm:1.33.0" + lightningcss-linux-arm-gnueabihf: "npm:1.33.0" + lightningcss-linux-arm64-gnu: "npm:1.33.0" + lightningcss-linux-arm64-musl: "npm:1.33.0" + lightningcss-linux-x64-gnu: "npm:1.33.0" + lightningcss-linux-x64-musl: "npm:1.33.0" + lightningcss-win32-arm64-msvc: "npm:1.33.0" + lightningcss-win32-x64-msvc: "npm:1.33.0" + dependenciesMeta: + lightningcss-android-arm64: optional: true - webpack-sources: + lightningcss-darwin-arm64: optional: true - checksum: 10c0/6014492b22c5f28a4d367057b5b2c1214b83c73785157fea130d5b877b50ed8820d8d8e73e96b3437c455b5b5c6817b36837da093239f95b534be43c0cdcfedc + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/ce1f8279fbae636dbf37fa6e7385d5f98ed881d72af3362f24afbd4685e19c1fcdfecf17e5dd77f2ebee3d0c23ade276230d85842d07292229a2cffba8ff20a3 languageName: node linkType: hard @@ -16862,10 +19214,10 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": - version: 11.2.4 - resolution: "lru-cache@npm:11.2.4" - checksum: 10c0/4a24f9b17537619f9144d7b8e42cd5a225efdfd7076ebe7b5e7dc02b860a818455201e67fbf000765233fe7e339d3c8229fc815e9b58ee6ede511e07608c19b2 +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1, lru-cache@npm:^11.2.7": + version: 11.5.2 + resolution: "lru-cache@npm:11.5.2" + checksum: 10c0/ece1ad731f5b655e85d67047d04bfc13823dc77aa61c5454924a9869ba600a0104e39cc33d726021feef812bc347ca34a5608a5eb1972a5dd0870b7ecd42c3f2 languageName: node linkType: hard @@ -16910,12 +19262,32 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.3, magic-string@npm:^0.30.5": - version: 0.30.17 - resolution: "magic-string@npm:0.30.17" +"magic-string@npm:^0.30.21, magic-string@npm:^0.30.3, magic-string@npm:^0.30.5": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - checksum: 10c0/16826e415d04b88378f200fe022b53e638e3838b9e496edda6c0e086d7753a44a6ed187adc72d19f3623810589bf139af1a315541cd6a26ae0771a0193eaf7b8 + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"magic-string@npm:^1.0.0": + version: 1.2.2 + resolution: "magic-string@npm:1.2.2" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/79eddeaf22fa15a227145105c36dc507e67b9fa147750b7abfe35e533ee133085ce40a05c09dd0a49cccd3e8813dc01f1367589fe35e012295e5ffe94122389c + languageName: node + linkType: hard + +"magicast@npm:^0.5.2": + version: 0.5.4 + resolution: "magicast@npm:0.5.4" + dependencies: + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + source-map-js: "npm:^1.2.1" + checksum: 10c0/f6a3b33d1c994cace3999fc96876a9fd06429deff8266563ccdc1d731c9edc4e1fb59d724128d7d4f3382eb457cd5a39ae1ec7b1e1864e068297ee2a30c12a3d languageName: node linkType: hard @@ -17003,6 +19375,20 @@ __metadata: languageName: node linkType: hard +"markdown-extensions@npm:^2.0.0": + version: 2.0.0 + resolution: "markdown-extensions@npm:2.0.0" + checksum: 10c0/406139da2aa0d5ebad86195c8e8c02412f873c452b4c087ae7bc767af37956141be449998223bb379eea179b5fd38dfa610602b6f29c22ddab5d51e627a7e41d + languageName: node + linkType: hard + +"markdown-table@npm:^3.0.0": + version: 3.0.4 + resolution: "markdown-table@npm:3.0.4" + checksum: 10c0/1257b31827629a54c24a5030a3dac952256c559174c95ce3ef89bebd6bff0cb1444b1fd667b1a1bb53307f83278111505b3e26f0c4e7b731e0060d435d2d930b + languageName: node + linkType: hard + "marked@npm:^4.3.0": version: 4.3.0 resolution: "marked@npm:4.3.0" @@ -17019,6 +19405,46 @@ __metadata: languageName: node linkType: hard +"mdast-util-definitions@npm:^6.0.0": + version: 6.0.0 + resolution: "mdast-util-definitions@npm:6.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + unist-util-visit: "npm:^5.0.0" + checksum: 10c0/a2e0e51122a3eff4f35379de2c50ee3d8a89bea58488a390b1b40ada95727eb769f87d4bc885e5935d61820d19e0567bc047876db302a2139f3a29668b612b80 + languageName: node + linkType: hard + +"mdast-util-directive@npm:^3.0.0, mdast-util-directive@npm:^3.1.0": + version: 3.1.0 + resolution: "mdast-util-directive@npm:3.1.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + ccount: "npm:^2.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + parse-entities: "npm:^4.0.0" + stringify-entities: "npm:^4.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10c0/596b093b940197cf43af4d0de12e82a1d2b1eb5add73dd16077aa80e0d0e1f208ea642c420726e59ccd352c193d6ecd5c106d6fab769f252617c75333f91a314 + languageName: node + linkType: hard + +"mdast-util-find-and-replace@npm:^3.0.0": + version: 3.0.2 + resolution: "mdast-util-find-and-replace@npm:3.0.2" + dependencies: + "@types/mdast": "npm:^4.0.0" + escape-string-regexp: "npm:^5.0.0" + unist-util-is: "npm:^6.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10c0/c8417a35605d567772ff5c1aa08363ff3010b0d60c8ea68c53cba09bf25492e3dd261560425c1756535f3b7107f62e7ff3857cdd8fb1e62d1b2cc2ea6e074ca2 + languageName: node + linkType: hard + "mdast-util-from-markdown@npm:^2.0.0": version: 2.0.3 resolution: "mdast-util-from-markdown@npm:2.0.3" @@ -17039,6 +19465,83 @@ __metadata: languageName: node linkType: hard +"mdast-util-gfm-autolink-literal@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-gfm-autolink-literal@npm:2.0.1" + dependencies: + "@types/mdast": "npm:^4.0.0" + ccount: "npm:^2.0.0" + devlop: "npm:^1.0.0" + mdast-util-find-and-replace: "npm:^3.0.0" + micromark-util-character: "npm:^2.0.0" + checksum: 10c0/963cd22bd42aebdec7bdd0a527c9494d024d1ad0739c43dc040fee35bdfb5e29c22564330a7418a72b5eab51d47a6eff32bc0255ef3ccb5cebfe8970e91b81b6 + languageName: node + linkType: hard + +"mdast-util-gfm-footnote@npm:^2.0.0": + version: 2.1.0 + resolution: "mdast-util-gfm-footnote@npm:2.1.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.1.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + checksum: 10c0/8ab965ee6be3670d76ec0e95b2ba3101fc7444eec47564943ab483d96ac17d29da2a4e6146a2a288be30c21b48c4f3938a1e54b9a46fbdd321d49a5bc0077ed0 + languageName: node + linkType: hard + +"mdast-util-gfm-strikethrough@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-strikethrough@npm:2.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/b053e93d62c7545019bd914271ea9e5667ad3b3b57d16dbf68e56fea39a7e19b4a345e781312714eb3d43fdd069ff7ee22a3ca7f6149dfa774554f19ce3ac056 + languageName: node + linkType: hard + +"mdast-util-gfm-table@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-table@npm:2.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + markdown-table: "npm:^3.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/128af47c503a53bd1c79f20642561e54a510ad5e2db1e418d28fefaf1294ab839e6c838e341aef5d7e404f9170b9ca3d1d89605f234efafde93ee51174a6e31e + languageName: node + linkType: hard + +"mdast-util-gfm-task-list-item@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-task-list-item@npm:2.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/258d725288482b636c0a376c296431390c14b4f29588675297cb6580a8598ed311fc73ebc312acfca12cc8546f07a3a285a53a3b082712e2cbf5c190d677d834 + languageName: node + linkType: hard + +"mdast-util-gfm@npm:^3.0.0": + version: 3.1.0 + resolution: "mdast-util-gfm@npm:3.1.0" + dependencies: + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-gfm-autolink-literal: "npm:^2.0.0" + mdast-util-gfm-footnote: "npm:^2.0.0" + mdast-util-gfm-strikethrough: "npm:^2.0.0" + mdast-util-gfm-table: "npm:^2.0.0" + mdast-util-gfm-task-list-item: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/4bedcfb6a20e39901c8772f0d2bb2d7a64ae87a54c13cbd92eec062cf470fbb68c2ad754e149af5b30794e2de61c978ab1de1ace03c0c40f443ca9b9b8044f81 + languageName: node + linkType: hard + "mdast-util-mdx-expression@npm:^2.0.0": version: 2.0.1 resolution: "mdast-util-mdx-expression@npm:2.0.1" @@ -17110,7 +19613,24 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-markdown@npm:^2.0.0": +"mdast-util-to-hast@npm:^13.0.0": + version: 13.2.1 + resolution: "mdast-util-to-hast@npm:13.2.1" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + "@ungap/structured-clone": "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + trim-lines: "npm:^3.0.0" + unist-util-position: "npm:^5.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/3eeaf28a5e84e1e08e6d54a1a8a06c0fca88cb5d36f4cf8086f0177248d1ce6e4e751f4ad0da19a3dea1c6ea61bd80784acc3ae021e44ceeb21aa5413a375e43 + languageName: node + linkType: hard + +"mdast-util-to-markdown@npm:^2.0.0, mdast-util-to-markdown@npm:^2.1.2": version: 2.1.2 resolution: "mdast-util-to-markdown@npm:2.1.2" dependencies: @@ -17292,6 +19812,114 @@ __metadata: languageName: node linkType: hard +"micromark-extension-directive@npm:^4.0.0": + version: 4.0.0 + resolution: "micromark-extension-directive@npm:4.0.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-factory-whitespace: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + parse-entities: "npm:^4.0.0" + checksum: 10c0/b4aef0f44339543466ae186130a4514985837b6b12d0c155bd1162e740f631e58f0883a39d0c723206fa0ff53a9b579965c79116f902236f6f123c3340b5fefb + languageName: node + linkType: hard + +"micromark-extension-gfm-autolink-literal@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-autolink-literal@npm:2.1.0" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/84e6fbb84ea7c161dfa179665dc90d51116de4c28f3e958260c0423e5a745372b7dcbc87d3cde98213b532e6812f847eef5ae561c9397d7f7da1e59872ef3efe + languageName: node + linkType: hard + +"micromark-extension-gfm-footnote@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-footnote@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/d172e4218968b7371b9321af5cde8c77423f73b233b2b0fcf3ff6fd6f61d2e0d52c49123a9b7910612478bf1f0d5e88c75a3990dd68f70f3933fe812b9f77edc + languageName: node + linkType: hard + +"micromark-extension-gfm-strikethrough@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-strikethrough@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/ef4f248b865bdda71303b494671b7487808a340b25552b11ca6814dff3fcfaab9be8d294643060bbdb50f79313e4a686ab18b99cbe4d3ee8a4170fcd134234fb + languageName: node + linkType: hard + +"micromark-extension-gfm-table@npm:^2.0.0": + version: 2.1.1 + resolution: "micromark-extension-gfm-table@npm:2.1.1" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/04bc00e19b435fa0add62cd029d8b7eb6137522f77832186b1d5ef34544a9bd030c9cf85e92ddfcc5c31f6f0a58a43d4b96dba4fc21316037c734630ee12c912 + languageName: node + linkType: hard + +"micromark-extension-gfm-tagfilter@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-extension-gfm-tagfilter@npm:2.0.0" + dependencies: + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/995558843fff137ae4e46aecb878d8a4691cdf23527dcf1e2f0157d66786be9f7bea0109c52a8ef70e68e3f930af811828ba912239438e31a9cfb9981f44d34d + languageName: node + linkType: hard + +"micromark-extension-gfm-task-list-item@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-task-list-item@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/78aa537d929e9309f076ba41e5edc99f78d6decd754b6734519ccbbfca8abd52e1c62df68d41a6ae64d2a3fc1646cea955893c79680b0b4385ced4c52296181f + languageName: node + linkType: hard + +"micromark-extension-gfm@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-gfm@npm:3.0.0" + dependencies: + micromark-extension-gfm-autolink-literal: "npm:^2.0.0" + micromark-extension-gfm-footnote: "npm:^2.0.0" + micromark-extension-gfm-strikethrough: "npm:^2.0.0" + micromark-extension-gfm-table: "npm:^2.0.0" + micromark-extension-gfm-tagfilter: "npm:^2.0.0" + micromark-extension-gfm-task-list-item: "npm:^2.0.0" + micromark-util-combine-extensions: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/970e28df6ebdd7c7249f52a0dda56e0566fbfa9ae56c8eeeb2445d77b6b89d44096880cd57a1c01e7821b1f4e31009109fbaca4e89731bff7b83b8519690e5d9 + languageName: node + linkType: hard + "micromark-extension-mdx-expression@npm:^3.0.0": version: 3.0.1 resolution: "micromark-extension-mdx-expression@npm:3.0.1" @@ -17903,6 +20531,13 @@ __metadata: languageName: node linkType: hard +"mrmime@npm:^2.0.1": + version: 2.0.1 + resolution: "mrmime@npm:2.0.1" + checksum: 10c0/af05afd95af202fdd620422f976ad67dc18e6ee29beb03dd1ce950ea6ef664de378e44197246df4c7cdd73d47f2e7143a6e26e473084b9e4aa2095c0ad1e1761 + languageName: node + linkType: hard + "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -17955,12 +20590,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.11": - version: 3.3.11 - resolution: "nanoid@npm:3.3.11" +"nanoid@npm:^3.3.17": + version: 3.3.18 + resolution: "nanoid@npm:3.3.18" bin: nanoid: bin/nanoid.cjs - checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + checksum: 10c0/b994b4e396730f8be2520923284e2040d61eaee55cc6d4935ef6d38d34bafdc46133eda4d3faea5073bda545aa6079d82b886caeac5c731cf9ac18bcc1301425 languageName: node linkType: hard @@ -18013,6 +20648,13 @@ __metadata: languageName: node linkType: hard +"neotraverse@npm:^1.0.1": + version: 1.0.1 + resolution: "neotraverse@npm:1.0.1" + checksum: 10c0/98e5d2d1974f91d527851a9dcfceb62701a1d31970b324b9f35fbdd244d173edc7a379ea1773f64771a9dc282486e3d3b693b24e14b1e969291612b2ff6b5054 + languageName: node + linkType: hard + "nested-error-stacks@npm:^2.0.0, nested-error-stacks@npm:^2.1.0": version: 2.1.1 resolution: "nested-error-stacks@npm:2.1.1" @@ -18020,6 +20662,15 @@ __metadata: languageName: node linkType: hard +"nlcst-to-string@npm:^4.0.0": + version: 4.0.0 + resolution: "nlcst-to-string@npm:4.0.0" + dependencies: + "@types/nlcst": "npm:^2.0.0" + checksum: 10c0/a192c8b3365a7c076812004e72ae5b4a1734e582be2a6f3c062f3beecf18868a9fe2d1bad870bfead320fb39830f2c4f3752e5ae6574c4e59157126fd1ddba70 + languageName: node + linkType: hard + "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" @@ -18046,6 +20697,13 @@ __metadata: languageName: node linkType: hard +"node-fetch-native@npm:^1.6.7": + version: 1.6.7 + resolution: "node-fetch-native@npm:1.6.7" + checksum: 10c0/8b748300fb053d21ca4d3db9c3ff52593d5e8f8a2d9fe90cbfad159676e324b954fdaefab46aeca007b5b9edab3d150021c4846444e4e8ab1f4e44cd3807be87 + languageName: node + linkType: hard + "node-fetch@npm:2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" @@ -18101,6 +20759,13 @@ __metadata: languageName: node linkType: hard +"node-mock-http@npm:^1.0.4": + version: 1.0.5 + resolution: "node-mock-http@npm:1.0.5" + checksum: 10c0/14a36b28423953af95e4659aa1e07208b5a38daaa89c1aac28a193f5420744a2511eb288503019b2fc8de582c70612d2e475d0df1d1a0e44fe3dfd7fc8f5399c + languageName: node + linkType: hard + "node-preload@npm:^0.2.1": version: 0.2.1 resolution: "node-preload@npm:0.2.1" @@ -18257,7 +20922,7 @@ __metadata: languageName: node linkType: hard -"nth-check@npm:^2.0.1": +"nth-check@npm:^2.0.0, nth-check@npm:^2.0.1": version: 2.1.1 resolution: "nth-check@npm:2.1.1" dependencies: @@ -18528,6 +21193,31 @@ __metadata: languageName: node linkType: hard +"obug@npm:^2.1.1": + version: 2.1.4 + resolution: "obug@npm:2.1.4" + checksum: 10c0/34a0ee97cd88573cfd97d384c2a79f07118ae5680d7e45d1de6e99c74eddefe145e8ca27a2db02195a1ee5fded5aa22b924869c842728c201b9f109a27d0ef19 + languageName: node + linkType: hard + +"ofetch@npm:^1.5.1": + version: 1.5.1 + resolution: "ofetch@npm:1.5.1" + dependencies: + destr: "npm:^2.0.5" + node-fetch-native: "npm:^1.6.7" + ufo: "npm:^1.6.1" + checksum: 10c0/97ebc600512ea0ab401e97c73313218cc53c9b530b32ec8c995c347b0c68887129993168d1753f527761a64c6f93a5d823ce1378ccec95fc65a606f323a79a6c + languageName: node + linkType: hard + +"ohash@npm:^2.0.11": + version: 2.0.12 + resolution: "ohash@npm:2.0.12" + checksum: 10c0/29a47fa554bfc80d5fcea6ee347296de97cc900abd7d3f502ac3379ce7631770a66c6e942582e0f1ddc8fb2b1246521383d8d6f634fd4f9a0935e315af728b61 + languageName: node + linkType: hard + "on-finished@npm:2.4.1, on-finished@npm:^2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" @@ -18588,6 +21278,24 @@ __metadata: languageName: node linkType: hard +"oniguruma-parser@npm:^0.12.2": + version: 0.12.2 + resolution: "oniguruma-parser@npm:0.12.2" + checksum: 10c0/fe5255d2cd5a6b845d5a0abe1725898ef40cea5522290dba6ccc08cc388891e9e8007af4baa5059942b786ad44b2b677ef25948039c9ba3f057bd35d2c52076a + languageName: node + linkType: hard + +"oniguruma-to-es@npm:^4.3.6": + version: 4.3.6 + resolution: "oniguruma-to-es@npm:4.3.6" + dependencies: + oniguruma-parser: "npm:^0.12.2" + regex: "npm:^6.1.0" + regex-recursion: "npm:^6.0.2" + checksum: 10c0/044e08b98e706987c2882ccf228de2a671de4aff33e3bd370da137ba599c0d520549625ad26ecc8898502445c991e19f106f584e94382262ba2af02c908ca3cf + languageName: node + linkType: hard + "open@npm:^10.0.3, open@npm:^10.2.0": version: 10.2.0 resolution: "open@npm:10.2.0" @@ -18806,6 +21514,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^7.3.0": + version: 7.3.1 + resolution: "p-limit@npm:7.3.1" + dependencies: + yocto-queue: "npm:^1.2.1" + checksum: 10c0/49b7d7fcc244d4659fa3b260c7b4e632bc72333c60e76e4cd24c7befdeb9b8e22da644e5064e83df9e2ded75245e3e3b9df0e9cf8500b08db8ebf58ac18c6764 + languageName: node + linkType: hard + "p-locate@npm:^4.1.0": version: 4.1.0 resolution: "p-locate@npm:4.1.0" @@ -18858,6 +21575,16 @@ __metadata: languageName: node linkType: hard +"p-queue@npm:^9.1.0": + version: 9.3.3 + resolution: "p-queue@npm:9.3.3" + dependencies: + eventemitter3: "npm:^5.0.4" + p-timeout: "npm:^7.0.0" + checksum: 10c0/0d88a35cb3301bee2aac654c0a7018a2e40775fa597f3a06da66f33b0311d106bebbe9732c8e5968ae1e0e0b49f79c874333d2a7b0a57fad469189f236c066d7 + languageName: node + linkType: hard + "p-retry@npm:^6.2.0": version: 6.2.1 resolution: "p-retry@npm:6.2.1" @@ -18878,6 +21605,13 @@ __metadata: languageName: node linkType: hard +"p-timeout@npm:^7.0.0": + version: 7.0.1 + resolution: "p-timeout@npm:7.0.1" + checksum: 10c0/87d96529d1096d506607218dba6f9ec077c6dbedd0c2e2788c748e33bcd05faae8a81009fd9d22ec0b3c95fc83f4717306baba223f6e464737d8b99294c3e863 + languageName: node + linkType: hard + "p-try@npm:^2.0.0": version: 2.2.0 resolution: "p-try@npm:2.2.0" @@ -18904,6 +21638,45 @@ __metadata: languageName: node linkType: hard +"package-manager-detector@npm:^1.6.0": + version: 1.8.0 + resolution: "package-manager-detector@npm:1.8.0" + checksum: 10c0/4c8e2c47fdc874465d3b3b11934401db9d73470ccbdabeb092e46cb94abbc491d5befc8c271b5ea1ae9c6a881f44c2997e011873365bde9fd6c3dc001221ff7a + languageName: node + linkType: hard + +"pagefind@npm:^1.5.2": + version: 1.5.2 + resolution: "pagefind@npm:1.5.2" + dependencies: + "@pagefind/darwin-arm64": "npm:1.5.2" + "@pagefind/darwin-x64": "npm:1.5.2" + "@pagefind/freebsd-x64": "npm:1.5.2" + "@pagefind/linux-arm64": "npm:1.5.2" + "@pagefind/linux-x64": "npm:1.5.2" + "@pagefind/windows-arm64": "npm:1.5.2" + "@pagefind/windows-x64": "npm:1.5.2" + dependenciesMeta: + "@pagefind/darwin-arm64": + optional: true + "@pagefind/darwin-x64": + optional: true + "@pagefind/freebsd-x64": + optional: true + "@pagefind/linux-arm64": + optional: true + "@pagefind/linux-x64": + optional: true + "@pagefind/windows-arm64": + optional: true + "@pagefind/windows-x64": + optional: true + bin: + pagefind: lib/runner/bin.cjs + checksum: 10c0/bcc4f34f473bfeb09dc4cb464a3547bf958e11ee4dee4144ec7a364bd74b9f7c21f85be71e388b66d30923a9a453dcaed2f6b425f3e67631cadc9650ba741aab + languageName: node + linkType: hard + "param-case@npm:^3.0.4": version: 3.0.4 resolution: "param-case@npm:3.0.4" @@ -18963,6 +21736,20 @@ __metadata: languageName: node linkType: hard +"parse-latin@npm:^7.0.0": + version: 7.0.0 + resolution: "parse-latin@npm:7.0.0" + dependencies: + "@types/nlcst": "npm:^2.0.0" + "@types/unist": "npm:^3.0.0" + nlcst-to-string: "npm:^4.0.0" + unist-util-modify-children: "npm:^4.0.0" + unist-util-visit-children: "npm:^3.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/4232a464f98c41c6680575c54bc2c9b21ac4b82a1f796a871bfef5efa6eddaab9bccf734b08cde6b0a5504ef46a0a14041ddd0bc5d9cc70f73a507f93f610596 + languageName: node + linkType: hard + "parse-node-version@npm:^1.0.1": version: 1.0.1 resolution: "parse-node-version@npm:1.0.1" @@ -19095,6 +21882,13 @@ __metadata: languageName: node linkType: hard +"piccolore@npm:^0.1.3": + version: 0.1.3 + resolution: "piccolore@npm:0.1.3" + checksum: 10c0/999666bb32eccc96a26b0cf3b8afe72f9d4cd4ca0eab5802b404cc84c601d81db4485b5feee5c50dbc8436ba6e1bc6a2310f99c18cee6687e430cc0eb4a36470 + languageName: node + linkType: hard + "picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" @@ -19116,10 +21910,10 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": - version: 4.0.4 - resolution: "picomatch@npm:4.0.4" - checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 +"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3, picomatch@npm:^4.0.4, picomatch@npm:^4.0.5": + version: 4.0.7 + resolution: "picomatch@npm:4.0.7" + checksum: 10c0/beb6ae02c43ae44e84883b90830196d9046b1726ead292adcf7f57945e0bb0d992d68563d87e03b484b6f3c9a5c6defda7523477f047d7f0e663f126cc01787f languageName: node linkType: hard @@ -19469,6 +22263,17 @@ __metadata: languageName: node linkType: hard +"postcss-nested@npm:^6.0.1": + version: 6.2.0 + resolution: "postcss-nested@npm:6.2.0" + dependencies: + postcss-selector-parser: "npm:^6.1.1" + peerDependencies: + postcss: ^8.2.14 + checksum: 10c0/7f9c3f2d764191a39364cbdcec350f26a312431a569c9ef17408021424726b0d67995ff5288405e3724bb7152a4c92f73c027e580ec91e798800ed3c52e2bc6e + languageName: node + linkType: hard + "postcss-normalize-charset@npm:^7.0.1": version: 7.0.1 resolution: "postcss-normalize-charset@npm:7.0.1" @@ -19602,6 +22407,16 @@ __metadata: languageName: node linkType: hard +"postcss-selector-parser@npm:^6.1.1": + version: 6.1.4 + resolution: "postcss-selector-parser@npm:6.1.4" + dependencies: + cssesc: "npm:^3.0.0" + util-deprecate: "npm:^1.0.2" + checksum: 10c0/996f3290dee08ecb073d5f396d1134e619494cfd83140aec07a29618ee1e76d370769a8959d4c0cfefb24aa96dcffa4e7c4937dd881e03b735d50cba959ceb19 + languageName: node + linkType: hard + "postcss-selector-parser@npm:^7.0.0, postcss-selector-parser@npm:^7.1.1": version: 7.1.1 resolution: "postcss-selector-parser@npm:7.1.1" @@ -19642,14 +22457,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.33, postcss@npm:^8.4.38, postcss@npm:^8.4.40": - version: 8.5.8 - resolution: "postcss@npm:8.5.8" +"postcss@npm:^8.4.33, postcss@npm:^8.4.38, postcss@npm:^8.4.40, postcss@npm:^8.5.26": + version: 8.5.26 + resolution: "postcss@npm:8.5.26" dependencies: - nanoid: "npm:^3.3.11" + nanoid: "npm:^3.3.17" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10c0/dd918f7127ee7c60a0295bae2e72b3787892296e1d1c3c564d7a2a00c68d8df83cadc3178491259daa19ccc54804fb71ed8c937c6787e08d8bd4bedf8d17044c + checksum: 10c0/2bdafc00d96bd57b6649a52e458864a4bf58ee56cfdbe4aea1472b5cccc127e6c1ad653bd0bec50d211e650eb0b9270c80e1e72aff2e2fa40d9e7363234d6e43 languageName: node linkType: hard @@ -19712,6 +22527,13 @@ __metadata: languageName: node linkType: hard +"prismjs@npm:^1.30.0": + version: 1.30.0 + resolution: "prismjs@npm:1.30.0" + checksum: 10c0/f56205bfd58ef71ccfcbcb691fd0eb84adc96c6ff21b0b69fc6fdcf02be42d6ef972ba4aed60466310de3d67733f6a746f89f2fb79c00bf217406d465b3e8f23 + languageName: node + linkType: hard + "proc-log@npm:^3.0.0": version: 3.0.0 resolution: "proc-log@npm:3.0.0" @@ -19733,6 +22555,13 @@ __metadata: languageName: node linkType: hard +"process-ancestry@npm:^0.1.0": + version: 0.1.0 + resolution: "process-ancestry@npm:0.1.0" + checksum: 10c0/3b20263db7c2eeb567efcef21bf4b3f3363c4bf7041796943fd8a467f888d94eb093a022b8fcc49c8d843f3fc81d15047255d4c2b4990f2d5a4f851f3c5c9cd4 + languageName: node + linkType: hard + "process-nextick-args@npm:~2.0.0": version: 2.0.1 resolution: "process-nextick-args@npm:2.0.1" @@ -19787,6 +22616,13 @@ __metadata: languageName: node linkType: hard +"property-information@npm:^7.0.0": + version: 7.2.0 + resolution: "property-information@npm:7.2.0" + checksum: 10c0/03662c8f9e1544510914c5e594ae72963f67d261027a5fdc06c4134742584fe40dd49b959470d30e757e9fb70b297d8546ab1362a040364144029926ad4e07c4 + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -19889,6 +22725,13 @@ __metadata: languageName: node linkType: hard +"radix3@npm:^1.1.2": + version: 1.1.2 + resolution: "radix3@npm:1.1.2" + checksum: 10c0/d4a295547f71af079868d2c2ed3814a9296ee026c5488212d58c106e6b4797c6eaec1259b46c9728913622f2240c9a944bfc8e2b3b5f6e4a5045338b1609f1e4 + languageName: node + linkType: hard + "rambda@npm:^9.1.0": version: 9.4.2 resolution: "rambda@npm:9.4.2" @@ -20195,6 +23038,13 @@ __metadata: languageName: node linkType: hard +"react-refresh@npm:^0.18.0": + version: 0.18.0 + resolution: "react-refresh@npm:0.18.0" + checksum: 10c0/34a262f7fd803433a534f50deb27a148112a81adcae440c7d1cbae7ef14d21ea8f2b3d783e858cb7698968183b77755a38b4d4b5b1d79b4f4689c2f6d358fff2 + languageName: node + linkType: hard + "react-remove-scroll-bar@npm:^2.3.7": version: 2.3.8 resolution: "react-remove-scroll-bar@npm:2.3.8" @@ -20470,6 +23320,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^5.0.0": + version: 5.1.1 + resolution: "readdirp@npm:5.1.1" + checksum: 10c0/5e99755684b9104761e48801828f4d2614efa33b1698629929c5985184dc63a7f8233c827832c40dc7be16b00cc871d303cf9de396d2fb74b9465ee2aa2424a1 + languageName: node + linkType: hard + "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -20492,6 +23349,56 @@ __metadata: languageName: node linkType: hard +"recma-build-jsx@npm:^1.0.0": + version: 1.0.0 + resolution: "recma-build-jsx@npm:1.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + estree-util-build-jsx: "npm:^3.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/ca30f5163887b44c74682355da2625f7b49f33267699d22247913e513e043650cbdd6a7497cf13c60f09ad9e7bc2bd35bd20853672773c19188569814b56bb04 + languageName: node + linkType: hard + +"recma-jsx@npm:^1.0.0": + version: 1.0.1 + resolution: "recma-jsx@npm:1.0.1" + dependencies: + acorn-jsx: "npm:^5.0.0" + estree-util-to-js: "npm:^2.0.0" + recma-parse: "npm:^1.0.0" + recma-stringify: "npm:^1.0.0" + unified: "npm:^11.0.0" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/9921b1270581ff133b94678868e665ba0fb6285ee60a6936106bac4899196c2ffb02dde894d9bc088fbf3deacb3e2426a3452e72066bf1203cbefebd7809d93f + languageName: node + linkType: hard + +"recma-parse@npm:^1.0.0": + version: 1.0.0 + resolution: "recma-parse@npm:1.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + esast-util-from-js: "npm:^2.0.0" + unified: "npm:^11.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/37c0990859a562d082e02d475ca5f4c8ef0840d285270f6699fe888cbb06260f97eb098585eda4aae416182c207fd19cf05e4f0b2dcf55cbf81dde4406d95545 + languageName: node + linkType: hard + +"recma-stringify@npm:^1.0.0": + version: 1.0.0 + resolution: "recma-stringify@npm:1.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + estree-util-to-js: "npm:^2.0.0" + unified: "npm:^11.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/c2ed4c0e8cf8a09aedcd47c5d016d47f6e1ff6c2d4b220e2abaf1b77713bf404756af2ea3ea7999aec5862e8825aff035edceb370c7fd8603a7e9da03bd6987e + languageName: node + linkType: hard + "redent@npm:^3.0.0": version: 3.0.0 resolution: "redent@npm:3.0.0" @@ -20553,6 +23460,31 @@ __metadata: languageName: node linkType: hard +"regex-recursion@npm:^6.0.2": + version: 6.0.2 + resolution: "regex-recursion@npm:6.0.2" + dependencies: + regex-utilities: "npm:^2.3.0" + checksum: 10c0/68e8b6889680e904b75d7f26edaf70a1a4dc1087406bff53face4c2929d918fd77c72223843fe816ac8ed9964f96b4160650e8d5909e26a998c6e9de324dadb1 + languageName: node + linkType: hard + +"regex-utilities@npm:^2.3.0": + version: 2.3.0 + resolution: "regex-utilities@npm:2.3.0" + checksum: 10c0/78c550a80a0af75223244fff006743922591bd8f61d91fef7c86b9b56cf9bbf8ee5d7adb6d8991b5e304c57c90103fc4818cf1e357b11c6c669b782839bd7893 + languageName: node + linkType: hard + +"regex@npm:^6.1.0": + version: 6.1.0 + resolution: "regex@npm:6.1.0" + dependencies: + regex-utilities: "npm:^2.3.0" + checksum: 10c0/6e0ee2a1c17d5a66dc1120dfc51899dedf6677857e83a0df4d5a822ebb8645a54a079772efc1ade382b67aad35e4e22b5bd2d33c05ed28b0e000f8f57eb0aec1 + languageName: node + linkType: hard + "regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": version: 1.5.4 resolution: "regexp.prototype.flags@npm:1.5.4" @@ -20599,6 +23531,81 @@ __metadata: languageName: node linkType: hard +"rehype-expressive-code@npm:^0.44.1": + version: 0.44.1 + resolution: "rehype-expressive-code@npm:0.44.1" + dependencies: + expressive-code: "npm:^0.44.1" + checksum: 10c0/7139dcb93dfc479225612ac51d1d7ad9b16e295eadbae4fecd697ab5f6f497dfb2d03317dd7b52a4a71691a8a88b7f23a7d0e216195ca6065b761a4958ae33c9 + languageName: node + linkType: hard + +"rehype-format@npm:^5.0.1": + version: 5.0.1 + resolution: "rehype-format@npm:5.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + hast-util-format: "npm:^1.0.0" + checksum: 10c0/e87aac3e318ef96688785e108315b23762681f1a834ae52ac449b9787ff63e8435aceb70354fb629d2d733daca4f65889ddcdf0cd44b684ea4f481e8fac750e3 + languageName: node + linkType: hard + +"rehype-parse@npm:^9.0.0": + version: 9.0.1 + resolution: "rehype-parse@npm:9.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + hast-util-from-html: "npm:^2.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/efa9ca17673fe70e2d322a1d262796bbed5f6a89382f8f8393352bbd6f6bbf1d4d1d050984b86ff9cb6c0fa2535175ab0829e53c94b1e38fc3c158e6c0ad90bc + languageName: node + linkType: hard + +"rehype-raw@npm:^7.0.0": + version: 7.0.0 + resolution: "rehype-raw@npm:7.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + hast-util-raw: "npm:^9.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/1435b4b6640a5bc3abe3b2133885c4dbff5ef2190ef9cfe09d6a63f74dd7d7ffd0cede70603278560ccf1acbfb9da9faae4b68065a28bc5aa88ad18e40f32d52 + languageName: node + linkType: hard + +"rehype-recma@npm:^1.0.0": + version: 1.0.0 + resolution: "rehype-recma@npm:1.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + hast-util-to-estree: "npm:^3.0.0" + checksum: 10c0/be60d7433a7f788a14f41da3e93ba9d9272c908ddef47757026cc4bbcc912f6301d56810349adf876d294a8d048626a0dbf6988aaa574afbfc29eac1ddc1eb74 + languageName: node + linkType: hard + +"rehype-stringify@npm:^10.0.0, rehype-stringify@npm:^10.0.1": + version: 10.0.1 + resolution: "rehype-stringify@npm:10.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + hast-util-to-html: "npm:^9.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/c643ae3a4862465033e0f1e9f664433767279b4ee9296570746970a79940417ec1fb1997a513659aab97063cf971c5d97e0af8129f590719f01628c8aa480765 + languageName: node + linkType: hard + +"rehype@npm:^13.0.2": + version: 13.0.2 + resolution: "rehype@npm:13.0.2" + dependencies: + "@types/hast": "npm:^3.0.0" + rehype-parse: "npm:^9.0.0" + rehype-stringify: "npm:^10.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/13d82086b673b3ce1fddb54cc8d30be16bde83fb62f1507f0af06070c94b85d07c3780fa994357bad2c9d51b84e4108ff661677b71d187e4f2167cab22d84363 + languageName: node + linkType: hard + "relateurl@npm:^0.2.7": version: 0.2.7 resolution: "relateurl@npm:0.2.7" @@ -20615,7 +23622,33 @@ __metadata: languageName: node linkType: hard -"remark-mdx@npm:^3.1.0": +"remark-directive@npm:^4.0.0": + version: 4.0.0 + resolution: "remark-directive@npm:4.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-directive: "npm:^3.0.0" + micromark-extension-directive: "npm:^4.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/ab1f71ae220913f62833464d483f29de7b4af0dcf1ff626d33cdb5dd81eda1c553a11f779de9fd9b641802586c5f070175824d70f14d53e451a484850705d53a + languageName: node + linkType: hard + +"remark-gfm@npm:^4.0.1": + version: 4.0.1 + resolution: "remark-gfm@npm:4.0.1" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-gfm: "npm:^3.0.0" + micromark-extension-gfm: "npm:^3.0.0" + remark-parse: "npm:^11.0.0" + remark-stringify: "npm:^11.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/427ecc6af3e76222662061a5f670a3e4e33ec5fffe2cabf04034da6a3f9a1bda1fc023e838a636385ba314e66e2bebbf017ca61ebea357eb0f5200fe0625a4b7 + languageName: node + linkType: hard + +"remark-mdx@npm:^3.0.0, remark-mdx@npm:^3.1.0": version: 3.1.1 resolution: "remark-mdx@npm:3.1.1" dependencies: @@ -20637,6 +23670,31 @@ __metadata: languageName: node linkType: hard +"remark-rehype@npm:^11.0.0, remark-rehype@npm:^11.1.2": + version: 11.1.2 + resolution: "remark-rehype@npm:11.1.2" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + mdast-util-to-hast: "npm:^13.0.0" + unified: "npm:^11.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/f9eccacfb596d9605581dc05bfad28635d6ded5dd0a18e88af5fd4df0d3fcf9612e1501d4513bc2164d833cfe9636dab20400080b09e53f155c6e1442a1231fb + languageName: node + linkType: hard + +"remark-smartypants@npm:^3.0.2": + version: 3.0.3 + resolution: "remark-smartypants@npm:3.0.3" + dependencies: + retext: "npm:^9.0.0" + retext-smartypants: "npm:^6.0.0" + unified: "npm:^11.0.4" + unist-util-visit: "npm:^5.0.0" + checksum: 10c0/24f210e64d44b13559aa6c7423b29a0a3b7fb1b603c3b997c70ad923e1bf1cebff9f234db801c689c02ff5bebdfd5256481c6690928a2465c09cd54a03076514 + languageName: node + linkType: hard + "remark-stringify@npm:^11.0.0": version: 11.0.0 resolution: "remark-stringify@npm:11.0.0" @@ -20739,6 +23797,13 @@ __metadata: languageName: node linkType: hard +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + "resolve.exports@npm:2.0.3, resolve.exports@npm:^2.0.0": version: 2.0.3 resolution: "resolve.exports@npm:2.0.3" @@ -20844,6 +23909,51 @@ __metadata: languageName: node linkType: hard +"retext-latin@npm:^4.0.0": + version: 4.0.0 + resolution: "retext-latin@npm:4.0.0" + dependencies: + "@types/nlcst": "npm:^2.0.0" + parse-latin: "npm:^7.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/51530be66db9ef6ab8e9cda5dd0598377ff4321481d6a941bf70dac16fa6e9123ff7d8ff093a05c30a3e00e282e37094b845b6130a8005a3cb7186a961ab99cb + languageName: node + linkType: hard + +"retext-smartypants@npm:^6.0.0, retext-smartypants@npm:^6.2.0": + version: 6.2.0 + resolution: "retext-smartypants@npm:6.2.0" + dependencies: + "@types/nlcst": "npm:^2.0.0" + nlcst-to-string: "npm:^4.0.0" + unist-util-visit: "npm:^5.0.0" + checksum: 10c0/36f925353dd7f31df642bca2493524a8daee15f9b0e0dfe7fb8982462d23ccb12a99864989db22f0bacb6d7fea1f696ba96e031d3fbac4f013e1c95ef3fed881 + languageName: node + linkType: hard + +"retext-stringify@npm:^4.0.0": + version: 4.0.0 + resolution: "retext-stringify@npm:4.0.0" + dependencies: + "@types/nlcst": "npm:^2.0.0" + nlcst-to-string: "npm:^4.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/eb2930356c85999a8978092a5d6ba3695fea859c71f221dcdc485704552922641bc17e50fea2ae0599d665192eaad002e98bb4236ecac94a570b73581b99004d + languageName: node + linkType: hard + +"retext@npm:^9.0.0": + version: 9.0.0 + resolution: "retext@npm:9.0.0" + dependencies: + "@types/nlcst": "npm:^2.0.0" + retext-latin: "npm:^4.0.0" + retext-stringify: "npm:^4.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/eee9f66ff6fae5670a5eeccc0b5e2639112f868475273ce307d3079cfe7deb9d1b0f2b8fa28b4ab30abaf8538345185a44908f461a27bbf43c4f94feda90ecac + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -20883,6 +23993,64 @@ __metadata: languageName: node linkType: hard +"rolldown@npm:~1.2.4": + version: 1.2.5 + resolution: "rolldown@npm:1.2.5" + dependencies: + "@oxc-project/types": "npm:=0.146.0" + "@rolldown/binding-android-arm-eabi": "npm:1.2.5" + "@rolldown/binding-android-arm64": "npm:1.2.5" + "@rolldown/binding-darwin-arm64": "npm:1.2.5" + "@rolldown/binding-darwin-x64": "npm:1.2.5" + "@rolldown/binding-freebsd-x64": "npm:1.2.5" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.2.5" + "@rolldown/binding-linux-arm64-gnu": "npm:1.2.5" + "@rolldown/binding-linux-arm64-musl": "npm:1.2.5" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.2.5" + "@rolldown/binding-linux-s390x-gnu": "npm:1.2.5" + "@rolldown/binding-linux-x64-gnu": "npm:1.2.5" + "@rolldown/binding-linux-x64-musl": "npm:1.2.5" + "@rolldown/binding-openharmony-arm64": "npm:1.2.5" + "@rolldown/binding-win32-arm64-msvc": "npm:1.2.5" + "@rolldown/binding-win32-x64-msvc": "npm:1.2.5" + "@rolldown/pluginutils": "npm:^1.0.0" + dependenciesMeta: + "@rolldown/binding-android-arm-eabi": + optional: true + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: ./bin/cli.mjs + checksum: 10c0/f6b4840300dcf4bb1b1f901fbc55d7642caefe63fd68e3a804f99eece8f4abae39dd3cd481d395e27f73fe55111563ea2511fef758f62f4c19af7ce2b42088e9 + languageName: node + linkType: hard + "rollup-plugin-typescript2@npm:^0.36.0": version: 0.36.0 resolution: "rollup-plugin-typescript2@npm:0.36.0" @@ -20900,32 +24068,40 @@ __metadata: linkType: hard "rollup@npm:^4.14.0": - version: 4.47.1 - resolution: "rollup@npm:4.47.1" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.47.1" - "@rollup/rollup-android-arm64": "npm:4.47.1" - "@rollup/rollup-darwin-arm64": "npm:4.47.1" - "@rollup/rollup-darwin-x64": "npm:4.47.1" - "@rollup/rollup-freebsd-arm64": "npm:4.47.1" - "@rollup/rollup-freebsd-x64": "npm:4.47.1" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.47.1" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.47.1" - "@rollup/rollup-linux-arm64-gnu": "npm:4.47.1" - "@rollup/rollup-linux-arm64-musl": "npm:4.47.1" - "@rollup/rollup-linux-loongarch64-gnu": "npm:4.47.1" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.47.1" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.47.1" - "@rollup/rollup-linux-riscv64-musl": "npm:4.47.1" - "@rollup/rollup-linux-s390x-gnu": "npm:4.47.1" - "@rollup/rollup-linux-x64-gnu": "npm:4.47.1" - "@rollup/rollup-linux-x64-musl": "npm:4.47.1" - "@rollup/rollup-win32-arm64-msvc": "npm:4.47.1" - "@rollup/rollup-win32-ia32-msvc": "npm:4.47.1" - "@rollup/rollup-win32-x64-msvc": "npm:4.47.1" - "@types/estree": "npm:1.0.8" + version: 4.62.4 + resolution: "rollup@npm:4.62.4" + dependencies: + "@napi-rs/lzma-linux-x64-gnu": "npm:1.5.1" + "@rollup/rollup-android-arm-eabi": "npm:4.62.4" + "@rollup/rollup-android-arm64": "npm:4.62.4" + "@rollup/rollup-darwin-arm64": "npm:4.62.4" + "@rollup/rollup-darwin-x64": "npm:4.62.4" + "@rollup/rollup-freebsd-arm64": "npm:4.62.4" + "@rollup/rollup-freebsd-x64": "npm:4.62.4" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.62.4" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.62.4" + "@rollup/rollup-linux-arm64-gnu": "npm:4.62.4" + "@rollup/rollup-linux-arm64-musl": "npm:4.62.4" + "@rollup/rollup-linux-loong64-gnu": "npm:4.62.4" + "@rollup/rollup-linux-loong64-musl": "npm:4.62.4" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.62.4" + "@rollup/rollup-linux-ppc64-musl": "npm:4.62.4" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.62.4" + "@rollup/rollup-linux-riscv64-musl": "npm:4.62.4" + "@rollup/rollup-linux-s390x-gnu": "npm:4.62.4" + "@rollup/rollup-linux-x64-gnu": "npm:4.62.4" + "@rollup/rollup-linux-x64-musl": "npm:4.62.4" + "@rollup/rollup-openbsd-x64": "npm:4.62.4" + "@rollup/rollup-openharmony-arm64": "npm:4.62.4" + "@rollup/rollup-win32-arm64-msvc": "npm:4.62.4" + "@rollup/rollup-win32-ia32-msvc": "npm:4.62.4" + "@rollup/rollup-win32-x64-gnu": "npm:4.62.4" + "@rollup/rollup-win32-x64-msvc": "npm:4.62.4" + "@types/estree": "npm:1.0.9" fsevents: "npm:~2.3.2" dependenciesMeta: + "@napi-rs/lzma-linux-x64-gnu": + optional: true "@rollup/rollup-android-arm-eabi": optional: true "@rollup/rollup-android-arm64": @@ -20946,10 +24122,14 @@ __metadata: optional: true "@rollup/rollup-linux-arm64-musl": optional: true - "@rollup/rollup-linux-loongarch64-gnu": + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": optional: true "@rollup/rollup-linux-ppc64-gnu": optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true "@rollup/rollup-linux-riscv64-gnu": optional: true "@rollup/rollup-linux-riscv64-musl": @@ -20960,17 +24140,23 @@ __metadata: optional: true "@rollup/rollup-linux-x64-musl": optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true "@rollup/rollup-win32-arm64-msvc": optional: true "@rollup/rollup-win32-ia32-msvc": optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true "@rollup/rollup-win32-x64-msvc": optional: true fsevents: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/4d792f8a8bfb4408485bbc6c1f393a88422230c14d06b9de4d27bcf443fe3569f9fa4ed6111972bf6b8b515bd0133bfeb16ab66cdf32494ef0fbd2da41dc2855 + checksum: 10c0/d83bcc89f02db337963dcd0b0d16620129cee263f8437464c02d782dde4e1bb89a6b5b511cd230317ce2c5959fb858884305a9a1a33d1d3da4eef9fe6368414b languageName: node linkType: hard @@ -21334,10 +24520,90 @@ __metadata: languageName: node linkType: hard -"sax@npm:^1.2.4, sax@npm:^1.5.0": - version: 1.6.0 - resolution: "sax@npm:1.6.0" - checksum: 10c0/e5593f4a91eb25761a688c4d96902e4e95a0dd6017bc65146b6f21236e3d715cf893333b76bc758923c9574c2fb5a7a76c3a81e96ea15432f2624f906c027c1e +"satteri@npm:^0.10.3": + version: 0.10.5 + resolution: "satteri@npm:0.10.5" + dependencies: + "@bruits/satteri-darwin-arm64": "npm:0.10.5" + "@bruits/satteri-darwin-x64": "npm:0.10.5" + "@bruits/satteri-linux-arm64-gnu": "npm:0.10.5" + "@bruits/satteri-linux-arm64-musl": "npm:0.10.5" + "@bruits/satteri-linux-x64-gnu": "npm:0.10.5" + "@bruits/satteri-linux-x64-musl": "npm:0.10.5" + "@bruits/satteri-wasm32-wasi": "npm:0.10.5" + "@bruits/satteri-win32-arm64-msvc": "npm:0.10.5" + "@bruits/satteri-win32-x64-msvc": "npm:0.10.5" + "@types/estree-jsx": "npm:^1.0.5" + "@types/hast": "npm:^3.0.5" + "@types/mdast": "npm:^4.0.4" + "@types/unist": "npm:^3.0.3" + dependenciesMeta: + "@bruits/satteri-darwin-arm64": + optional: true + "@bruits/satteri-darwin-x64": + optional: true + "@bruits/satteri-linux-arm64-gnu": + optional: true + "@bruits/satteri-linux-arm64-musl": + optional: true + "@bruits/satteri-linux-x64-gnu": + optional: true + "@bruits/satteri-linux-x64-musl": + optional: true + "@bruits/satteri-wasm32-wasi": + optional: true + "@bruits/satteri-win32-arm64-msvc": + optional: true + "@bruits/satteri-win32-x64-msvc": + optional: true + checksum: 10c0/e1a9f6f9784c8ea79be59ddb803403999521632ef57a595579e1e310646172c9661a0ce41c2667ed9b82a55d697dd192f6470e9a6ee348d5b207172d89d0cba3 + languageName: node + linkType: hard + +"satteri@npm:^0.9.1": + version: 0.9.5 + resolution: "satteri@npm:0.9.5" + dependencies: + "@bruits/satteri-darwin-arm64": "npm:0.9.5" + "@bruits/satteri-darwin-x64": "npm:0.9.5" + "@bruits/satteri-linux-arm64-gnu": "npm:0.9.5" + "@bruits/satteri-linux-arm64-musl": "npm:0.9.5" + "@bruits/satteri-linux-x64-gnu": "npm:0.9.5" + "@bruits/satteri-linux-x64-musl": "npm:0.9.5" + "@bruits/satteri-wasm32-wasi": "npm:0.9.5" + "@bruits/satteri-win32-arm64-msvc": "npm:0.9.5" + "@bruits/satteri-win32-x64-msvc": "npm:0.9.5" + "@types/estree-jsx": "npm:^1.0.5" + "@types/hast": "npm:^3.0.4" + "@types/mdast": "npm:^4.0.4" + "@types/unist": "npm:^3.0.3" + dependenciesMeta: + "@bruits/satteri-darwin-arm64": + optional: true + "@bruits/satteri-darwin-x64": + optional: true + "@bruits/satteri-linux-arm64-gnu": + optional: true + "@bruits/satteri-linux-arm64-musl": + optional: true + "@bruits/satteri-linux-x64-gnu": + optional: true + "@bruits/satteri-linux-x64-musl": + optional: true + "@bruits/satteri-wasm32-wasi": + optional: true + "@bruits/satteri-win32-arm64-msvc": + optional: true + "@bruits/satteri-win32-x64-msvc": + optional: true + checksum: 10c0/4a204a57b7b84750cb0fc9a1ab153c0c99dd3c57cac20b785705f6b723c1a465263951ad624eb47808a9501418eb81fa526bbc71f57f8fd07472fdf114e73a9c + languageName: node + linkType: hard + +"sax@npm:^1.2.4, sax@npm:^1.4.1, sax@npm:^1.5.0": + version: 1.6.1 + resolution: "sax@npm:1.6.1" + checksum: 10c0/c91a71043d60da50fdf1e2cee936fc7ad4c9376afb8d1022aef5655f279433640859ec06b3b49abfcbe578fc7da2828cc1661e45aaedf835f5393496193437fa languageName: node linkType: hard @@ -21456,12 +24722,12 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.3, semver@npm:^7.7.2, semver@npm:^7.7.3": - version: 7.7.4 - resolution: "semver@npm:7.7.4" +"semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.3, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.7.4, semver@npm:^7.8.5": + version: 7.8.5 + resolution: "semver@npm:7.8.5" bin: semver: bin/semver.js - checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c languageName: node linkType: hard @@ -21592,6 +24858,96 @@ __metadata: languageName: node linkType: hard +"sharp@npm:^0.34.0 || ^0.35.0": + version: 0.35.3 + resolution: "sharp@npm:0.35.3" + dependencies: + "@img/colour": "npm:^1.1.0" + "@img/sharp-darwin-arm64": "npm:0.35.3" + "@img/sharp-darwin-x64": "npm:0.35.3" + "@img/sharp-freebsd-wasm32": "npm:0.35.3" + "@img/sharp-libvips-darwin-arm64": "npm:1.3.2" + "@img/sharp-libvips-darwin-x64": "npm:1.3.2" + "@img/sharp-libvips-linux-arm": "npm:1.3.2" + "@img/sharp-libvips-linux-arm64": "npm:1.3.2" + "@img/sharp-libvips-linux-ppc64": "npm:1.3.2" + "@img/sharp-libvips-linux-riscv64": "npm:1.3.2" + "@img/sharp-libvips-linux-s390x": "npm:1.3.2" + "@img/sharp-libvips-linux-x64": "npm:1.3.2" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.3.2" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.3.2" + "@img/sharp-linux-arm": "npm:0.35.3" + "@img/sharp-linux-arm64": "npm:0.35.3" + "@img/sharp-linux-ppc64": "npm:0.35.3" + "@img/sharp-linux-riscv64": "npm:0.35.3" + "@img/sharp-linux-s390x": "npm:0.35.3" + "@img/sharp-linux-x64": "npm:0.35.3" + "@img/sharp-linuxmusl-arm64": "npm:0.35.3" + "@img/sharp-linuxmusl-x64": "npm:0.35.3" + "@img/sharp-webcontainers-wasm32": "npm:0.35.3" + "@img/sharp-win32-arm64": "npm:0.35.3" + "@img/sharp-win32-ia32": "npm:0.35.3" + "@img/sharp-win32-x64": "npm:0.35.3" + detect-libc: "npm:^2.1.2" + semver: "npm:^7.8.5" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-freebsd-wasm32": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-riscv64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-ppc64": + optional: true + "@img/sharp-linux-riscv64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-webcontainers-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/d99d60fc716aacbf42fcb0eb6d129142e0912eb23310adf48bc94e6989e27c838c5b5f507b928c723c72d265f9ceea3d4b1207e8fd5929797b23005663ce1d07 + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -21615,6 +24971,22 @@ __metadata: languageName: node linkType: hard +"shiki@npm:^4.0.2": + version: 4.4.3 + resolution: "shiki@npm:4.4.3" + dependencies: + "@shikijs/core": "npm:4.4.3" + "@shikijs/engine-javascript": "npm:4.4.3" + "@shikijs/engine-oniguruma": "npm:4.4.3" + "@shikijs/langs": "npm:4.4.3" + "@shikijs/themes": "npm:4.4.3" + "@shikijs/types": "npm:4.4.3" + "@shikijs/vscode-textmate": "npm:^10.0.2" + "@types/hast": "npm:^3.0.5" + checksum: 10c0/deff32ff8069de44c8b0f11296088eafa9cece8e3aa0817b4a52637a31f291d4da08f1133f19bea1a976dda026386862cd131f37bfe6562c1b15932d25ee460e + languageName: node + linkType: hard + "side-channel-list@npm:^1.0.0": version: 1.0.0 resolution: "side-channel-list@npm:1.0.0" @@ -21684,6 +25056,20 @@ __metadata: languageName: node linkType: hard +"sitemap@npm:^9.0.0": + version: 9.0.1 + resolution: "sitemap@npm:9.0.1" + dependencies: + "@types/node": "npm:^24.9.2" + "@types/sax": "npm:^1.2.1" + arg: "npm:^5.0.0" + sax: "npm:^1.4.1" + bin: + sitemap: dist/esm/cli.js + checksum: 10c0/dddd3c9f30fd09655fa2c1bd9bb182ed860717b3ba681fdc1ac66ad25b5f399dafdd8b87d5ba4ee914ea14e7b11841aadc3af61e4a45242a14f28194efe12416 + languageName: node + linkType: hard + "slash@npm:^2.0.0": version: 2.0.0 resolution: "slash@npm:2.0.0" @@ -21739,6 +25125,13 @@ __metadata: languageName: node linkType: hard +"smol-toml@npm:^1.6.0": + version: 1.8.0 + resolution: "smol-toml@npm:1.8.0" + checksum: 10c0/4e01dbfb7c7af142176b037ebedaccccafb8229cc873feaa47c184ee31073d86e30b4b1b0fe9a5a6d083ed3ce21615eda9e81d1f5d779ad8b2e2e9459b900aea + languageName: node + linkType: hard + "snake-case@npm:^3.0.4": version: 3.0.4 resolution: "snake-case@npm:3.0.4" @@ -21858,10 +25251,17 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.7.4": - version: 0.7.4 - resolution: "source-map@npm:0.7.4" - checksum: 10c0/dc0cf3768fe23c345ea8760487f8c97ef6fca8a73c83cd7c9bf2fde8bc2c34adb9c0824d6feb14bc4f9e37fb522e18af621543f1289038a66ac7586da29aa7dc +"source-map@npm:^0.7.0, source-map@npm:^0.7.4, source-map@npm:^0.7.6": + version: 0.7.6 + resolution: "source-map@npm:0.7.6" + checksum: 10c0/59f6f05538539b274ba771d2e9e32f6c65451982510564438e048bc1352f019c6efcdc6dd07909b1968144941c14015c2c7d4369fb7c4d7d53ae769716dcc16c + languageName: node + linkType: hard + +"space-separated-tokens@npm:^2.0.0": + version: 2.0.2 + resolution: "space-separated-tokens@npm:2.0.2" + checksum: 10c0/6173e1d903dca41dcab6a2deed8b4caf61bd13b6d7af8374713500570aa929ff9414ae09a0519f4f8772df993300305a395d4871f35bc4ca72b6db57e1f30af8 languageName: node linkType: hard @@ -22039,6 +25439,17 @@ __metadata: languageName: node linkType: hard +"starlight-sidebar-topics@npm:^0.8.0": + version: 0.8.0 + resolution: "starlight-sidebar-topics@npm:0.8.0" + dependencies: + picomatch: "npm:^4.0.3" + peerDependencies: + "@astrojs/starlight": ">=0.38.0" + checksum: 10c0/42a6a4634c0c2a4360e69b3cadf60e5426a2009704bb24a3f0fd319d7130b262408ccbd4279ba4911269e75455cf373ecb8744ec6198b5e27c5cb331ecfe0241 + languageName: node + linkType: hard + "start-server-and-test@npm:^2.1.5": version: 2.1.5 resolution: "start-server-and-test@npm:2.1.5" @@ -22128,6 +25539,13 @@ __metadata: languageName: node linkType: hard +"stream-replace-string@npm:^2.0.0": + version: 2.0.0 + resolution: "stream-replace-string@npm:2.0.0" + checksum: 10c0/6cdf6108c57a869c1282dece0728bd7a8e314855bee71992436460192cdf46b3c976451e1e114716af209b2bfefa0e7e4581ca0eebc330d9dfcde341a72d50af + languageName: node + linkType: hard + "streamroller@npm:^3.1.5": version: 3.1.5 resolution: "streamroller@npm:3.1.5" @@ -22425,6 +25843,24 @@ __metadata: languageName: node linkType: hard +"style-to-js@npm:^1.0.0": + version: 1.1.21 + resolution: "style-to-js@npm:1.1.21" + dependencies: + style-to-object: "npm:1.0.14" + checksum: 10c0/94231aa80f58f442c3a5ae01a21d10701e5d62f96b4b3e52eab3499077ee52df203cc0df4a1a870707f5e99470859136ea8657b782a5f4ca7934e0ffe662a588 + languageName: node + linkType: hard + +"style-to-object@npm:1.0.14": + version: 1.0.14 + resolution: "style-to-object@npm:1.0.14" + dependencies: + inline-style-parser: "npm:0.2.7" + checksum: 10c0/854d9e9b77afc336e6d7b09348e7939f2617b34eb0895824b066d8cd1790284cb6d8b2ba36be88025b2595d715dba14b299ae76e4628a366541106f639e13679 + languageName: node + linkType: hard + "stylehacks@npm:^7.0.5": version: 7.0.8 resolution: "stylehacks@npm:7.0.8" @@ -22540,8 +25976,8 @@ __metadata: linkType: hard "svgo@npm:^4.0.1": - version: 4.0.1 - resolution: "svgo@npm:4.0.1" + version: 4.0.2 + resolution: "svgo@npm:4.0.2" dependencies: commander: "npm:^11.1.0" css-select: "npm:^5.1.0" @@ -22552,7 +25988,7 @@ __metadata: sax: "npm:^1.5.0" bin: svgo: ./bin/svgo.js - checksum: 10c0/f61f9957b42e0c97593b49a01f3b93cb239b2e818efccb056fcc866d622704d9021c4ef8bd0287264c3e4f33da60e4af14d841b4a624b87ff8888e3b896d13d5 + checksum: 10c0/d58c9446d2701dd57ef62a29b4299b157cfc32e2282f82fc68dd7cdca2e1efdf0d8bb0fb30ba0499f322d20b6304be277ffbc60a6b6a75489fa30c5300694b9a languageName: node linkType: hard @@ -22762,6 +26198,13 @@ __metadata: languageName: node linkType: hard +"tiny-inflate@npm:^1.0.3": + version: 1.0.3 + resolution: "tiny-inflate@npm:1.0.3" + checksum: 10c0/fab687537254f6ec44c9a2e880048fe70da3542aba28f73cda3e74c95cabf342a339372f2a6c032e322324f01accc03ca26c04ba2bad9b3eb8cf3ee99bba7f9b + languageName: node + linkType: hard + "tiny-invariant@npm:^1.3.3": version: 1.3.3 resolution: "tiny-invariant@npm:1.3.3" @@ -22769,20 +26212,27 @@ __metadata: languageName: node linkType: hard +"tinyclip@npm:^0.1.12": + version: 0.1.15 + resolution: "tinyclip@npm:0.1.15" + checksum: 10c0/8ebdcc60e0e9ba54edeae28158a7a96e94657ac9387cc03cd5ef6c51c36b92be2ebe10cf5e34c9382124ef766cf82784cca2c5db57cd7ab447b7d04880ee7579 + languageName: node + linkType: hard + "tinyexec@npm:^1.0.4": - version: 1.0.4 - resolution: "tinyexec@npm:1.0.4" - checksum: 10c0/d4a5bbcf6bdb23527a4b74c4aa566f41432167112fe76f420ec7e3a90a3ecfd3a7d944383e2719fc3987b69400f7b928daf08700d145fb527c2e80ec01e198bd + version: 1.3.0 + resolution: "tinyexec@npm:1.3.0" + checksum: 10c0/e9b89f97489d2aab2cef408da279e6b32547e738d1275032ccb8fd0028a006d93eb70fc51c6cffd9fc2f5aca6c2a273d8b6f73b52d46ee5116da6b94969ef958 languageName: node linkType: hard -"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15": - version: 0.2.15 - resolution: "tinyglobby@npm:0.2.15" +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.17": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" dependencies: fdir: "npm:^6.5.0" - picomatch: "npm:^4.0.3" - checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c languageName: node linkType: hard @@ -22883,6 +26333,13 @@ __metadata: languageName: node linkType: hard +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: 10c0/3a1611fa9e52aa56a94c69951a9ea15b8aaad760eaa26c56a65330dc8adf99cb282fc07cc9d94968b7d4d88003beba220a7278bbe2063328eb23fb56f9509e94 + languageName: node + linkType: hard + "trim-newlines@npm:^4.0.2": version: 4.1.1 resolution: "trim-newlines@npm:4.1.1" @@ -23229,6 +26686,20 @@ __metadata: languageName: node linkType: hard +"ufo@npm:^1.6.1, ufo@npm:^1.6.3": + version: 1.6.4 + resolution: "ufo@npm:1.6.4" + checksum: 10c0/3a2b29e7e3d772fbf6893d7d23bf442981457adb2fe122828abdbda89bedcb81aafd0dcc080e41b45f9a877db00cb42cbfee9639753a19d9b9bd39b5627039cf + languageName: node + linkType: hard + +"ultrahtml@npm:^1.6.0": + version: 1.7.0 + resolution: "ultrahtml@npm:1.7.0" + checksum: 10c0/a56907b3ea4b0c16d30c37cb814138f8ae47044892a9d1c5ecca0731e24c2c4cd1c9cfa53d57bf508b21bc3b993e3c02bf3f9c3bfc156d18ed7e12c62a140f75 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.1.0": version: 1.1.0 resolution: "unbox-primitive@npm:1.1.0" @@ -23241,6 +26712,13 @@ __metadata: languageName: node linkType: hard +"uncrypto@npm:^0.1.3": + version: 0.1.3 + resolution: "uncrypto@npm:0.1.3" + checksum: 10c0/74a29afefd76d5b77bedc983559ceb33f5bbc8dada84ff33755d1e3355da55a4e03a10e7ce717918c436b4dfafde1782e799ebaf2aadd775612b49f7b5b2998e + languageName: node + linkType: hard + "undici-types@npm:~6.21.0": version: 6.21.0 resolution: "undici-types@npm:6.21.0" @@ -23248,6 +26726,20 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~7.18.0": + version: 7.18.2 + resolution: "undici-types@npm:7.18.2" + checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d + languageName: node + linkType: hard + +"undici@npm:^8.0.0": + version: 8.10.0 + resolution: "undici@npm:8.10.0" + checksum: 10c0/37ae2b1db8f65c3a003504e2040e96887b99f9db16ba07dcf49c37ed8b7f8c72f4452f2d46f52f7c9e49518fe8325e3b5e7e02ac3a2424886bd67d4b62a0ecab + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.1 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1" @@ -23308,7 +26800,7 @@ __metadata: languageName: node linkType: hard -"unified@npm:^11.0.0, unified@npm:^11.0.5": +"unified@npm:^11.0.0, unified@npm:^11.0.4, unified@npm:^11.0.5": version: 11.0.5 resolution: "unified@npm:11.0.5" dependencies: @@ -23323,6 +26815,17 @@ __metadata: languageName: node linkType: hard +"unifont@npm:~0.7.5": + version: 0.7.5 + resolution: "unifont@npm:0.7.5" + dependencies: + css-tree: "npm:^3.1.0" + ohash: "npm:^2.0.11" + undici: "npm:^8.0.0" + checksum: 10c0/bf91799b2fe53c8d360421f48cde7f56f2f14a9573cf49d10d97c9013efe50094a69258f3618108d899a202b36a2c627676fd901f61c2d6310e8ef2feed66047 + languageName: node + linkType: hard + "union@npm:~0.5.0": version: 0.5.0 resolution: "union@npm:0.5.0" @@ -23350,6 +26853,16 @@ __metadata: languageName: node linkType: hard +"unist-util-find-after@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-find-after@npm:5.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-is: "npm:^6.0.0" + checksum: 10c0/a7cea473c4384df8de867c456b797ff1221b20f822e1af673ff5812ed505358b36f47f3b084ac14c3622cb879ed833b71b288e8aa71025352a2aab4c2925a6eb + languageName: node + linkType: hard + "unist-util-inspect@npm:^8.0.0": version: 8.1.0 resolution: "unist-util-inspect@npm:8.1.0" @@ -23368,6 +26881,16 @@ __metadata: languageName: node linkType: hard +"unist-util-modify-children@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-modify-children@npm:4.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + array-iterate: "npm:^2.0.0" + checksum: 10c0/63d44b09a2e4c674c72816d4328d668972e68cc965ea719fef1c642b66a3ebe3b102e284a3213b4920ebccff05e0f689b4eaae8a0e5c3dafcad117d1577496da + languageName: node + linkType: hard + "unist-util-position-from-estree@npm:^2.0.0": version: 2.0.0 resolution: "unist-util-position-from-estree@npm:2.0.0" @@ -23377,6 +26900,25 @@ __metadata: languageName: node linkType: hard +"unist-util-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-position@npm:5.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10c0/dde3b31e314c98f12b4dc6402f9722b2bf35e96a4f2d463233dd90d7cde2d4928074a7a11eff0a5eb1f4e200f27fc1557e0a64a7e8e4da6558542f251b1b7400 + languageName: node + linkType: hard + +"unist-util-remove-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-remove-position@npm:5.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-visit: "npm:^5.0.0" + checksum: 10c0/e8c76da4399446b3da2d1c84a97c607b37d03d1d92561e14838cbe4fdcb485bfc06c06cfadbb808ccb72105a80643976d0660d1fe222ca372203075be9d71105 + languageName: node + linkType: hard + "unist-util-stringify-position@npm:^4.0.0": version: 4.0.0 resolution: "unist-util-stringify-position@npm:4.0.0" @@ -23386,7 +26928,16 @@ __metadata: languageName: node linkType: hard -"unist-util-visit-parents@npm:^6.0.0": +"unist-util-visit-children@npm:^3.0.0": + version: 3.0.0 + resolution: "unist-util-visit-children@npm:3.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10c0/51e95f54fbf11d414952c011c761c3960864948ad3fd2abe3989eb18b18d96b8f48e7ea5ab6f23264d1a3f4f5a1ff76312dd8f2196c78b762098403505c3abb9 + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^6.0.0, unist-util-visit-parents@npm:^6.0.1, unist-util-visit-parents@npm:^6.0.2": version: 6.0.2 resolution: "unist-util-visit-parents@npm:6.0.2" dependencies: @@ -23396,7 +26947,7 @@ __metadata: languageName: node linkType: hard -"unist-util-visit@npm:^5.0.0": +"unist-util-visit@npm:^5.0.0, unist-util-visit@npm:^5.1.0": version: 5.1.0 resolution: "unist-util-visit@npm:5.1.0" dependencies: @@ -23521,6 +27072,81 @@ __metadata: languageName: node linkType: hard +"unstorage@npm:^1.17.5": + version: 1.17.5 + resolution: "unstorage@npm:1.17.5" + dependencies: + anymatch: "npm:^3.1.3" + chokidar: "npm:^5.0.0" + destr: "npm:^2.0.5" + h3: "npm:^1.15.10" + lru-cache: "npm:^11.2.7" + node-fetch-native: "npm:^1.6.7" + ofetch: "npm:^1.5.1" + ufo: "npm:^1.6.3" + peerDependencies: + "@azure/app-configuration": ^1.8.0 + "@azure/cosmos": ^4.2.0 + "@azure/data-tables": ^13.3.0 + "@azure/identity": ^4.6.0 + "@azure/keyvault-secrets": ^4.9.0 + "@azure/storage-blob": ^12.26.0 + "@capacitor/preferences": ^6 || ^7 || ^8 + "@deno/kv": ">=0.9.0" + "@netlify/blobs": ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + "@planetscale/database": ^1.19.0 + "@upstash/redis": ^1.34.3 + "@vercel/blob": ">=0.27.1" + "@vercel/functions": ^2.2.12 || ^3.0.0 + "@vercel/kv": ^1 || ^2 || ^3 + aws4fetch: ^1.0.20 + db0: ">=0.2.1" + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + "@azure/app-configuration": + optional: true + "@azure/cosmos": + optional: true + "@azure/data-tables": + optional: true + "@azure/identity": + optional: true + "@azure/keyvault-secrets": + optional: true + "@azure/storage-blob": + optional: true + "@capacitor/preferences": + optional: true + "@deno/kv": + optional: true + "@netlify/blobs": + optional: true + "@planetscale/database": + optional: true + "@upstash/redis": + optional: true + "@vercel/blob": + optional: true + "@vercel/functions": + optional: true + "@vercel/kv": + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + checksum: 10c0/52bc07d0951129f493cc2d2a5204f49df90c85e7ab3faac0019e3a31f22f507f7a8263fca7ca4d019950c14cbfed4d43e15c9afe4b5dcb307249ebb60d2e538e + languageName: node + linkType: hard + "upath@npm:2.0.1": version: 2.0.1 resolution: "upath@npm:2.0.1" @@ -23551,6 +27177,13 @@ __metadata: languageName: node linkType: hard +"url-extras@npm:^0.1.0": + version: 0.1.0 + resolution: "url-extras@npm:0.1.0" + checksum: 10c0/353ca8190921d793bedaf765b55f94c1d719d1fd696c9a5dafeaf1b4532c1de936649925a0d699aa9613555b8dac82078e51d0a65cb1e48b92817ed68a527b60 + languageName: node + linkType: hard + "url-join@npm:^4.0.1": version: 4.0.1 resolution: "url-join@npm:4.0.1" @@ -23711,6 +27344,16 @@ __metadata: languageName: node linkType: hard +"vfile-location@npm:^5.0.0": + version: 5.0.3 + resolution: "vfile-location@npm:5.0.3" + dependencies: + "@types/unist": "npm:^3.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/1711f67802a5bc175ea69750d59863343ed43d1b1bb25c0a9063e4c70595e673e53e2ed5cdbb6dcdc370059b31605144d95e8c061b9361bcc2b036b8f63a4966 + languageName: node + linkType: hard + "vfile-message@npm:^4.0.0": version: 4.0.3 resolution: "vfile-message@npm:4.0.3" @@ -23767,6 +27410,75 @@ __metadata: languageName: node linkType: hard +"vite@npm:^8.0.13": + version: 8.2.2 + resolution: "vite@npm:8.2.2" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.33.0" + picomatch: "npm:^4.0.5" + postcss: "npm:^8.5.26" + rolldown: "npm:~1.2.4" + tinyglobby: "npm:^0.2.17" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.4.0 || ^0.5.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/94cbbbdc38ad500dcb86b6202ddd14aa41d05c80739766cada9bbe250b410d1a27be433c9c491ed39744019471ac1e27a59908616a88c42f9787bdb6bdca49d2 + languageName: node + linkType: hard + +"vitefu@npm:^1.1.2": + version: 1.1.3 + resolution: "vitefu@npm:1.1.3" + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + checksum: 10c0/6cfcd52a17339f63f71ef08787a68850a17a9d9dd7a688e29e41067f963eb2cd5192bb65d38df87fa3a733a21e0229aaa6382201655c1599699e5d6669e3e094 + languageName: node + linkType: hard + "w3c-xmlserializer@npm:^4.0.0": version: 4.0.0 resolution: "w3c-xmlserializer@npm:4.0.0" @@ -23863,6 +27575,13 @@ __metadata: languageName: node linkType: hard +"web-namespaces@npm:^2.0.0": + version: 2.0.1 + resolution: "web-namespaces@npm:2.0.1" + checksum: 10c0/df245f466ad83bd5cd80bfffc1674c7f64b7b84d1de0e4d2c0934fb0782e0a599164e7197a4bce310ee3342fd61817b8047ff04f076a1ce12dd470584142a4bd + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -24360,6 +28079,13 @@ __metadata: languageName: node linkType: hard +"xxhash-wasm@npm:^1.1.0": + version: 1.1.0 + resolution: "xxhash-wasm@npm:1.1.0" + checksum: 10c0/35aa152fc7d775ae13364fe4fb20ebd89c6ac1f56cdb6060a6d2f1ed68d15180694467e63a4adb3d11936a4798ccd75a540979070e70d9b911e9981bbdd9cea6 + languageName: node + linkType: hard + "y18n@npm:^4.0.0": version: 4.0.3 resolution: "y18n@npm:4.0.3" @@ -24411,13 +28137,20 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:21.1.1, yargs-parser@npm:>=21.1.1, yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": +"yargs-parser@npm:21.1.1, yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 languageName: node linkType: hard +"yargs-parser@npm:>=21.1.1, yargs-parser@npm:^22.0.0": + version: 22.0.0 + resolution: "yargs-parser@npm:22.0.0" + checksum: 10c0/cb7ef81759c4271cb1d96b9351dbbc9a9ce35d3e1122d2b739bf6c432603824fa02c67cc12dcef6ea80283379d63495686e8f41cc7b06c6576e792aba4d33e1c + languageName: node + linkType: hard + "yargs-parser@npm:^18.1.2": version: 18.1.3 resolution: "yargs-parser@npm:18.1.3" @@ -24483,14 +28216,21 @@ __metadata: languageName: node linkType: hard -"yocto-queue@npm:^1.0.0": - version: 1.1.1 - resolution: "yocto-queue@npm:1.1.1" - checksum: 10c0/cb287fe5e6acfa82690acb43c283de34e945c571a78a939774f6eaba7c285bacdf6c90fbc16ce530060863984c906d2b4c6ceb069c94d1e0a06d5f2b458e2a92 +"yocto-queue@npm:^1.0.0, yocto-queue@npm:^1.2.1": + version: 1.2.2 + resolution: "yocto-queue@npm:1.2.2" + checksum: 10c0/36d4793e9cf7060f9da543baf67c55e354f4862c8d3d34de1a1b1d7c382d44171315cc54abf84d8900b8113d742b830108a1434f4898fb244f9b7e8426d4b8f5 + languageName: node + linkType: hard + +"zod@npm:^4.3.6": + version: 4.4.3 + resolution: "zod@npm:4.4.3" + checksum: 10c0/7ea31b558e88f9faf44f31dd185e2e1cbf51fed3081787fb96cc2534749b50c0acfc6da7f0922a7353ed092dd358c7d50c28ea96c94d04af64191bd33152eca3 languageName: node linkType: hard -"zwitch@npm:^2.0.0": +"zwitch@npm:^2.0.0, zwitch@npm:^2.0.4": version: 2.0.4 resolution: "zwitch@npm:2.0.4" checksum: 10c0/3c7830cdd3378667e058ffdb4cf2bb78ac5711214e2725900873accb23f3dfe5f9e7e5a06dcdc5f29605da976fc45c26d9a13ca334d6eea2245a15e77b8fc06e