@awarebydefault/display-case 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +309 -0
- package/display-case.prompt.md +64 -0
- package/docs/ai-agents.md +126 -0
- package/docs/cli.md +99 -0
- package/docs/configuration.md +410 -0
- package/docs/documentation-panel.md +50 -0
- package/docs/examples/README.md +14 -0
- package/docs/examples/multi-variant.case.tsx +30 -0
- package/docs/examples/plain.case.tsx +22 -0
- package/docs/examples/tweak-control.placard.md +80 -0
- package/docs/examples/tweaks.case.tsx +39 -0
- package/docs/hierarchy.md +59 -0
- package/docs/quick-start.md +78 -0
- package/docs/style-engines.md +180 -0
- package/docs/testing.md +245 -0
- package/docs/theming.md +97 -0
- package/docs/tweaks.md +75 -0
- package/docs/writing-cases.md +144 -0
- package/docs/writing-placard-docs.md +194 -0
- package/package.json +113 -0
- package/skills/display-case-author-case/README.md +20 -0
- package/skills/display-case-author-case/SKILL.md +40 -0
- package/skills/display-case-author-placard-doc/README.md +24 -0
- package/skills/display-case-author-placard-doc/SKILL.md +65 -0
- package/skills/display-case-review/README.md +19 -0
- package/skills/display-case-review/SKILL.md +30 -0
- package/skills/display-case-snapshot/README.md +20 -0
- package/skills/display-case-snapshot/SKILL.md +29 -0
- package/src/checks/a11y-scanner.test.ts +240 -0
- package/src/checks/a11y-scanner.ts +410 -0
- package/src/checks/check-text.test.ts +53 -0
- package/src/checks/check-text.ts +78 -0
- package/src/checks/check.test.ts +194 -0
- package/src/checks/check.ts +473 -0
- package/src/checks/providers/pixelmatch-diff.test.ts +79 -0
- package/src/checks/providers/pixelmatch-diff.ts +30 -0
- package/src/checks/providers/playwright-driver.ts +104 -0
- package/src/checks/ssr-check.test.ts +73 -0
- package/src/checks/ssr-check.ts +96 -0
- package/src/checks/structure-check.cross-package.test.ts +165 -0
- package/src/checks/structure-check.test.ts +651 -0
- package/src/checks/structure-check.ts +988 -0
- package/src/checks/tokens-check.test.ts +159 -0
- package/src/checks/tokens-check.ts +162 -0
- package/src/cli.ts +218 -0
- package/src/commands/agents.test.ts +24 -0
- package/src/commands/agents.ts +28 -0
- package/src/commands/init-run.test.ts +123 -0
- package/src/commands/init.test.ts +63 -0
- package/src/commands/init.ts +412 -0
- package/src/commands/publish.test.ts +210 -0
- package/src/commands/publish.ts +292 -0
- package/src/core/affected.test.ts +99 -0
- package/src/core/affected.ts +144 -0
- package/src/core/catalog.test.ts +152 -0
- package/src/core/catalog.ts +92 -0
- package/src/core/discovery.test.ts +184 -0
- package/src/core/discovery.ts +250 -0
- package/src/core/manifest.ts +41 -0
- package/src/core/mdx-lite/__fixtures__/box-stub.tsx +7 -0
- package/src/core/mdx-lite/index.ts +393 -0
- package/src/core/mdx-lite/mdx-lite.test.ts +345 -0
- package/src/core/mdx-plugin.test.ts +60 -0
- package/src/core/mdx-plugin.ts +30 -0
- package/src/flow-step.test-d.ts +39 -0
- package/src/index.test.ts +100 -0
- package/src/index.ts +564 -0
- package/src/render/collect-styles.emotion.test.tsx +114 -0
- package/src/render/collect-styles.test.tsx +72 -0
- package/src/render/collect-styles.ts +33 -0
- package/src/render/documents.test.ts +184 -0
- package/src/render/documents.ts +88 -0
- package/src/render/render-node.test.tsx +160 -0
- package/src/render/render-node.tsx +133 -0
- package/src/render/ssr-primer.test.tsx +25 -0
- package/src/render/ssr-primer.tsx +54 -0
- package/src/render/ssr-render.test.tsx +142 -0
- package/src/render/ssr-render.tsx +63 -0
- package/src/render/ssr-shell.test.tsx +57 -0
- package/src/render/ssr-shell.tsx +54 -0
- package/src/server/prod-server.ts +237 -0
- package/src/server/server.test.ts +117 -0
- package/src/server/server.ts +1039 -0
- package/src/style-engine.test-d.ts +37 -0
- package/src/testing/test-helpers.ts +27 -0
- package/src/types/pixelmatch.d.ts +12 -0
- package/src/ui/browser-entry.tsx +51 -0
- package/src/ui/chrome.css +485 -0
- package/src/ui/design-system/README.md +88 -0
- package/src/ui/design-system/components/controls/Button.case.tsx +52 -0
- package/src/ui/design-system/components/controls/Button.css +89 -0
- package/src/ui/design-system/components/controls/Button.placard.md +14 -0
- package/src/ui/design-system/components/controls/Button.test.tsx +45 -0
- package/src/ui/design-system/components/controls/Button.tsx +41 -0
- package/src/ui/design-system/components/controls/IconButton.case.tsx +52 -0
- package/src/ui/design-system/components/controls/IconButton.css +67 -0
- package/src/ui/design-system/components/controls/IconButton.placard.md +13 -0
- package/src/ui/design-system/components/controls/IconButton.test.tsx +39 -0
- package/src/ui/design-system/components/controls/IconButton.tsx +47 -0
- package/src/ui/design-system/components/controls/Input.case.tsx +50 -0
- package/src/ui/design-system/components/controls/Input.css +52 -0
- package/src/ui/design-system/components/controls/Input.placard.md +12 -0
- package/src/ui/design-system/components/controls/Input.test.tsx +43 -0
- package/src/ui/design-system/components/controls/Input.tsx +45 -0
- package/src/ui/design-system/components/controls/Select.case.tsx +48 -0
- package/src/ui/design-system/components/controls/Select.css +44 -0
- package/src/ui/design-system/components/controls/Select.placard.md +15 -0
- package/src/ui/design-system/components/controls/Select.test.tsx +57 -0
- package/src/ui/design-system/components/controls/Select.tsx +58 -0
- package/src/ui/design-system/components/controls/SelectMenu.case.tsx +100 -0
- package/src/ui/design-system/components/controls/SelectMenu.css +72 -0
- package/src/ui/design-system/components/controls/SelectMenu.placard.md +18 -0
- package/src/ui/design-system/components/controls/SelectMenu.test.tsx +66 -0
- package/src/ui/design-system/components/controls/SelectMenu.tsx +377 -0
- package/src/ui/design-system/components/index.ts +66 -0
- package/src/ui/design-system/components/primer-specimen/ColorRamp.case.tsx +44 -0
- package/src/ui/design-system/components/primer-specimen/ColorRamp.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/ColorRamp.tsx +51 -0
- package/src/ui/design-system/components/primer-specimen/DefinitionList.case.tsx +38 -0
- package/src/ui/design-system/components/primer-specimen/DefinitionList.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/DefinitionList.tsx +41 -0
- package/src/ui/design-system/components/primer-specimen/FontFamilies.case.tsx +24 -0
- package/src/ui/design-system/components/primer-specimen/FontFamilies.placard.md +12 -0
- package/src/ui/design-system/components/primer-specimen/FontFamilies.tsx +41 -0
- package/src/ui/design-system/components/primer-specimen/GlyphGrid.case.tsx +27 -0
- package/src/ui/design-system/components/primer-specimen/GlyphGrid.placard.md +13 -0
- package/src/ui/design-system/components/primer-specimen/GlyphGrid.tsx +34 -0
- package/src/ui/design-system/components/primer-specimen/LayoutMock.case.tsx +36 -0
- package/src/ui/design-system/components/primer-specimen/LayoutMock.placard.md +7 -0
- package/src/ui/design-system/components/primer-specimen/LayoutMock.tsx +36 -0
- package/src/ui/design-system/components/primer-specimen/SpacingScale.case.tsx +20 -0
- package/src/ui/design-system/components/primer-specimen/SpacingScale.placard.md +12 -0
- package/src/ui/design-system/components/primer-specimen/SpacingScale.tsx +33 -0
- package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.case.tsx +56 -0
- package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.placard.md +17 -0
- package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.tsx +45 -0
- package/src/ui/design-system/components/primer-specimen/StatusList.case.tsx +17 -0
- package/src/ui/design-system/components/primer-specimen/StatusList.placard.md +16 -0
- package/src/ui/design-system/components/primer-specimen/StatusList.tsx +39 -0
- package/src/ui/design-system/components/primer-specimen/SwatchGrid.case.tsx +26 -0
- package/src/ui/design-system/components/primer-specimen/SwatchGrid.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/SwatchGrid.tsx +42 -0
- package/src/ui/design-system/components/primer-specimen/TypeScale.case.tsx +23 -0
- package/src/ui/design-system/components/primer-specimen/TypeScale.placard.md +14 -0
- package/src/ui/design-system/components/primer-specimen/TypeScale.tsx +34 -0
- package/src/ui/design-system/components/primer-specimen/WeightSpecimen.case.tsx +28 -0
- package/src/ui/design-system/components/primer-specimen/WeightSpecimen.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/WeightSpecimen.tsx +46 -0
- package/src/ui/design-system/components/primer-specimen/index.ts +31 -0
- package/src/ui/design-system/components/primer-specimen/styles.css +476 -0
- package/src/ui/design-system/components/shell/A11yPage.case.tsx +237 -0
- package/src/ui/design-system/components/shell/A11yPage.placard.md +15 -0
- package/src/ui/design-system/components/shell/CaseTemplate.case.tsx +32 -0
- package/src/ui/design-system/components/shell/CaseTemplate.placard.md +5 -0
- package/src/ui/design-system/components/shell/CasesPage.case.tsx +141 -0
- package/src/ui/design-system/components/shell/CasesPage.placard.md +12 -0
- package/src/ui/design-system/components/shell/PrimerPage.case.tsx +22 -0
- package/src/ui/design-system/components/shell/PrimerPage.placard.md +3 -0
- package/src/ui/design-system/components/shell/PrimerTemplate.case.tsx +22 -0
- package/src/ui/design-system/components/shell/PrimerTemplate.placard.md +5 -0
- package/src/ui/design-system/components/shell/ShellView.case.tsx +57 -0
- package/src/ui/design-system/components/shell/ShellView.placard.md +5 -0
- package/src/ui/design-system/components/shell/ShellView.tsx +678 -0
- package/src/ui/design-system/components/shell/shell-fixtures.tsx +727 -0
- package/src/ui/design-system/components/showcase/A11yBadge.case.tsx +46 -0
- package/src/ui/design-system/components/showcase/A11yBadge.css +27 -0
- package/src/ui/design-system/components/showcase/A11yBadge.placard.md +11 -0
- package/src/ui/design-system/components/showcase/A11yBadge.test.tsx +31 -0
- package/src/ui/design-system/components/showcase/A11yBadge.tsx +41 -0
- package/src/ui/design-system/components/showcase/A11yPanel.case.tsx +121 -0
- package/src/ui/design-system/components/showcase/A11yPanel.css +198 -0
- package/src/ui/design-system/components/showcase/A11yPanel.placard.md +19 -0
- package/src/ui/design-system/components/showcase/A11yPanel.test.tsx +81 -0
- package/src/ui/design-system/components/showcase/A11yPanel.tsx +144 -0
- package/src/ui/design-system/components/showcase/Chip.case.tsx +48 -0
- package/src/ui/design-system/components/showcase/Chip.css +51 -0
- package/src/ui/design-system/components/showcase/Chip.placard.md +13 -0
- package/src/ui/design-system/components/showcase/Chip.test.tsx +46 -0
- package/src/ui/design-system/components/showcase/Chip.tsx +54 -0
- package/src/ui/design-system/components/showcase/Eyebrow.case.tsx +30 -0
- package/src/ui/design-system/components/showcase/Eyebrow.css +16 -0
- package/src/ui/design-system/components/showcase/Eyebrow.placard.md +10 -0
- package/src/ui/design-system/components/showcase/Eyebrow.test.tsx +38 -0
- package/src/ui/design-system/components/showcase/Eyebrow.tsx +29 -0
- package/src/ui/design-system/components/showcase/FlowNav.case.tsx +35 -0
- package/src/ui/design-system/components/showcase/FlowNav.css +29 -0
- package/src/ui/design-system/components/showcase/FlowNav.placard.md +13 -0
- package/src/ui/design-system/components/showcase/FlowNav.test.tsx +48 -0
- package/src/ui/design-system/components/showcase/FlowNav.tsx +58 -0
- package/src/ui/design-system/components/showcase/ImpactTag.case.tsx +19 -0
- package/src/ui/design-system/components/showcase/ImpactTag.css +36 -0
- package/src/ui/design-system/components/showcase/ImpactTag.placard.md +14 -0
- package/src/ui/design-system/components/showcase/ImpactTag.test.tsx +40 -0
- package/src/ui/design-system/components/showcase/ImpactTag.tsx +35 -0
- package/src/ui/design-system/components/showcase/NavItem.case.tsx +86 -0
- package/src/ui/design-system/components/showcase/NavItem.css +111 -0
- package/src/ui/design-system/components/showcase/NavItem.placard.md +13 -0
- package/src/ui/design-system/components/showcase/NavItem.test.tsx +65 -0
- package/src/ui/design-system/components/showcase/NavItem.tsx +95 -0
- package/src/ui/design-system/components/showcase/RenderAddress.case.tsx +21 -0
- package/src/ui/design-system/components/showcase/RenderAddress.css +35 -0
- package/src/ui/design-system/components/showcase/RenderAddress.placard.md +7 -0
- package/src/ui/design-system/components/showcase/RenderAddress.test.tsx +26 -0
- package/src/ui/design-system/components/showcase/RenderAddress.tsx +43 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.case.tsx +84 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.css +61 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.placard.md +21 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.test.tsx +81 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.tsx +75 -0
- package/src/ui/design-system/components/showcase/Sidebar.case.tsx +67 -0
- package/src/ui/design-system/components/showcase/Sidebar.css +6 -0
- package/src/ui/design-system/components/showcase/Sidebar.placard.md +14 -0
- package/src/ui/design-system/components/showcase/Sidebar.test.tsx +32 -0
- package/src/ui/design-system/components/showcase/Sidebar.tsx +30 -0
- package/src/ui/design-system/components/showcase/Stage.case.tsx +51 -0
- package/src/ui/design-system/components/showcase/Stage.css +91 -0
- package/src/ui/design-system/components/showcase/Stage.placard.md +15 -0
- package/src/ui/design-system/components/showcase/Stage.test.tsx +84 -0
- package/src/ui/design-system/components/showcase/Stage.tsx +97 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.case.tsx +81 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.css +169 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.placard.md +20 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.tsx +230 -0
- package/src/ui/design-system/components/showcase/Wordmark.case.tsx +42 -0
- package/src/ui/design-system/components/showcase/Wordmark.css +31 -0
- package/src/ui/design-system/components/showcase/Wordmark.placard.md +10 -0
- package/src/ui/design-system/components/showcase/Wordmark.test.tsx +22 -0
- package/src/ui/design-system/components/showcase/Wordmark.tsx +22 -0
- package/src/ui/design-system/primer-specimens/brand.tsx +26 -0
- package/src/ui/design-system/primer-specimens/colors.tsx +83 -0
- package/src/ui/design-system/primer-specimens/components.tsx +308 -0
- package/src/ui/design-system/primer-specimens/foundations.tsx +71 -0
- package/src/ui/design-system/primer-specimens/index.ts +25 -0
- package/src/ui/design-system/primer-specimens/showcase.tsx +68 -0
- package/src/ui/design-system/primer-specimens/spacing.tsx +101 -0
- package/src/ui/design-system/primer-specimens/type.tsx +75 -0
- package/src/ui/design-system/primer.mdx +236 -0
- package/src/ui/design-system/styles.css +14 -0
- package/src/ui/design-system/tokens/colors.css +172 -0
- package/src/ui/design-system/tokens/fonts.css +18 -0
- package/src/ui/design-system/tokens/spacing.css +48 -0
- package/src/ui/design-system/tokens/typography.css +49 -0
- package/src/ui/markdown.test.tsx +54 -0
- package/src/ui/markdown.tsx +19 -0
- package/src/ui/primer-mount.tsx +76 -0
- package/src/ui/primer.css +175 -0
- package/src/ui/primer.tsx +277 -0
- package/src/ui/render-mount.tsx +284 -0
- package/src/ui/shell-core.test.ts +340 -0
- package/src/ui/shell-core.ts +295 -0
- package/src/ui/shell.tsx +60 -0
- package/src/ui/test-ids.ts +53 -0
- package/src/ui/use-shell.ts +1230 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level test for the style-engine seam. Compiled by `tsc` (the `lint:types`
|
|
3
|
+
* check) but never executed — `const x: T =` assertions pin the public shapes,
|
|
4
|
+
* and `@ts-expect-error` asserts a compile error. A regression of the types
|
|
5
|
+
* fails the typecheck.
|
|
6
|
+
*/
|
|
7
|
+
import type { DisplayCaseConfig, StyleCollector, StyleEngine } from './index'
|
|
8
|
+
|
|
9
|
+
// A StyleEngine is a zero-arg factory returning a StyleCollector.
|
|
10
|
+
const engine: StyleEngine = () => ({
|
|
11
|
+
wrap: (node) => node,
|
|
12
|
+
collect: (html) => `<style>${html.length}</style>`,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// A collector's methods have the expected signatures.
|
|
16
|
+
const collector: StyleCollector = engine()
|
|
17
|
+
const _wrapped = collector.wrap(null)
|
|
18
|
+
const _css: string = collector.collect('<div/>')
|
|
19
|
+
void _wrapped
|
|
20
|
+
void _css
|
|
21
|
+
|
|
22
|
+
// `styleEngines` is an optional array of engines on the config.
|
|
23
|
+
const config: DisplayCaseConfig = {
|
|
24
|
+
title: 'T',
|
|
25
|
+
roots: [],
|
|
26
|
+
styleEngines: [engine],
|
|
27
|
+
}
|
|
28
|
+
void config
|
|
29
|
+
|
|
30
|
+
// collect must return a string — returning a non-string is a type error.
|
|
31
|
+
// @ts-expect-error — collect returns void, not string
|
|
32
|
+
const _bad: StyleCollector = { wrap: (n) => n, collect: () => {} }
|
|
33
|
+
void _bad
|
|
34
|
+
|
|
35
|
+
// @ts-expect-error — a StyleEngine takes no arguments
|
|
36
|
+
const _badEngine: StyleEngine = (_x: number) => engine()
|
|
37
|
+
void _badEngine
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { dirname, join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared scaffolding for the unit suites: build a throwaway package directory on
|
|
7
|
+
* disk, populate it from a `{ relativePath: contents }` map, and clean it up.
|
|
8
|
+
* Filesystem-backed because the modules under test (config resolution, case
|
|
9
|
+
* discovery, the token scanner, the init scaffolder) all read real files.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Make a unique temp directory; the caller is responsible for removing it. */
|
|
13
|
+
export async function makeTempDir(): Promise<string> {
|
|
14
|
+
return mkdtemp(join(tmpdir(), 'dc-test-'))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Write a map of package-relative paths → file contents, creating parents. */
|
|
18
|
+
export async function writeFiles(
|
|
19
|
+
dir: string,
|
|
20
|
+
files: Record<string, string>,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
23
|
+
const abs = join(dir, rel)
|
|
24
|
+
await mkdir(dirname(abs), { recursive: true })
|
|
25
|
+
await writeFile(abs, content)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// pixelmatch v6 ships no bundled type declarations; this is the minimal surface
|
|
2
|
+
// Display Case uses.
|
|
3
|
+
declare module 'pixelmatch' {
|
|
4
|
+
export default function pixelmatch(
|
|
5
|
+
img1: Uint8Array | Uint8ClampedArray,
|
|
6
|
+
img2: Uint8Array | Uint8ClampedArray,
|
|
7
|
+
output: Uint8Array | Uint8ClampedArray | null,
|
|
8
|
+
width: number,
|
|
9
|
+
height: number,
|
|
10
|
+
options?: { threshold?: number; includeAA?: boolean },
|
|
11
|
+
): number
|
|
12
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot, hydrateRoot } from 'react-dom/client'
|
|
3
|
+
import { Shell } from './shell'
|
|
4
|
+
import { parseRoute } from './shell-core'
|
|
5
|
+
import type { ShellSeed } from './use-shell'
|
|
6
|
+
|
|
7
|
+
// Entry for the browse chrome. The server pre-renders the shell into #root and
|
|
8
|
+
// inlines the seed it rendered from (`window.__dcSeed`: the manifest, theme,
|
|
9
|
+
// and a11y flag). The route comes from the live address — which equals the
|
|
10
|
+
// request path the server rendered — so server and client derive the same route
|
|
11
|
+
// and the seeded initial state matches. The client then adopts (hydrates) the
|
|
12
|
+
// markup; case modules are still bundled only into the render entry, never here.
|
|
13
|
+
const inlined = (
|
|
14
|
+
globalThis as {
|
|
15
|
+
__dcSeed?: {
|
|
16
|
+
manifest: ShellSeed['manifest']
|
|
17
|
+
theme: ShellSeed['theme']
|
|
18
|
+
a11y: boolean
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
).__dcSeed
|
|
22
|
+
|
|
23
|
+
const rootEl = document.getElementById('root') as HTMLElement
|
|
24
|
+
if (!inlined) throw new Error('Display Case: missing shell seed (__dcSeed)')
|
|
25
|
+
|
|
26
|
+
const seed: ShellSeed = {
|
|
27
|
+
manifest: inlined.manifest,
|
|
28
|
+
route: parseRoute(window.location.pathname, window.location.search),
|
|
29
|
+
theme: inlined.theme,
|
|
30
|
+
a11y: inlined.a11y,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const tree = (
|
|
34
|
+
<StrictMode>
|
|
35
|
+
<Shell seed={seed} />
|
|
36
|
+
</StrictMode>
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// Adopt the server-rendered shell when present (`data-ssr="1"`); mount fresh
|
|
40
|
+
// otherwise.
|
|
41
|
+
if (rootEl.dataset.ssr === '1') {
|
|
42
|
+
hydrateRoot(rootEl, tree, {
|
|
43
|
+
onRecoverableError: (err) =>
|
|
44
|
+
console.warn(
|
|
45
|
+
'[display-case] shell adopt mismatch; client re-rendered:',
|
|
46
|
+
err,
|
|
47
|
+
),
|
|
48
|
+
})
|
|
49
|
+
} else {
|
|
50
|
+
createRoot(rootEl).render(tree)
|
|
51
|
+
}
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/* Display Case browsing chrome — "The Vitrine".
|
|
2
|
+
Styled entirely from the package's own design system (see
|
|
3
|
+
./design-system/). Every value is a `--dc-*` token; nothing here
|
|
4
|
+
hard-codes a color, radius, or font. The chrome is warm, flat, and
|
|
5
|
+
recessive so the showcased component — the exhibit — owns the weight.
|
|
6
|
+
The server inlines the design-system tokens ahead of this file. */
|
|
7
|
+
|
|
8
|
+
.dc-app {
|
|
9
|
+
display: grid;
|
|
10
|
+
grid-template-columns: var(--dc-sidebar-w) 1fr;
|
|
11
|
+
grid-template-rows: auto 1fr;
|
|
12
|
+
grid-template-areas: "header header" "sidebar main";
|
|
13
|
+
height: 100vh;
|
|
14
|
+
font-family: var(--dc-font-sans);
|
|
15
|
+
font-size: var(--dc-text-base);
|
|
16
|
+
line-height: var(--dc-leading-normal);
|
|
17
|
+
color: var(--dc-fg);
|
|
18
|
+
background: var(--dc-bg);
|
|
19
|
+
-webkit-font-smoothing: antialiased;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Collapsed nav: drop the sidebar column and let main span full width.
|
|
23
|
+
(The `.dcui-sidebar` display:none override lives just after the base
|
|
24
|
+
`.dcui-sidebar` rule below, to keep selector specificity ascending.) */
|
|
25
|
+
.dc-app[data-nav="collapsed"] {
|
|
26
|
+
grid-template-columns: 1fr;
|
|
27
|
+
grid-template-areas: "header" "main";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ── Header ─────────────────────────────────────────────────────── */
|
|
31
|
+
.dc-header {
|
|
32
|
+
grid-area: header;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: space-between;
|
|
36
|
+
gap: var(--dc-space-8);
|
|
37
|
+
padding: var(--dc-space-5) var(--dc-space-8);
|
|
38
|
+
border-bottom: var(--dc-border-line);
|
|
39
|
+
background: var(--dc-bg-subtle);
|
|
40
|
+
}
|
|
41
|
+
.dc-header-left {
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
gap: var(--dc-space-5);
|
|
45
|
+
}
|
|
46
|
+
/* The bracketed wordmark is the Wordmark component (owns its own dcui-wordmark
|
|
47
|
+
styles); the chrome only places it in the header-left cluster. */
|
|
48
|
+
.dc-controls {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: var(--dc-space-4);
|
|
52
|
+
flex-wrap: wrap;
|
|
53
|
+
justify-content: flex-end;
|
|
54
|
+
}
|
|
55
|
+
/* The library-only controls live in a fading group (so they crossfade with the
|
|
56
|
+
mode switch) but must still read as the same inline cluster — same axis, gap,
|
|
57
|
+
and wrapping as their `.dc-controls` parent. */
|
|
58
|
+
.dc-controls-extra {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
gap: var(--dc-space-4);
|
|
62
|
+
flex-wrap: wrap;
|
|
63
|
+
justify-content: flex-end;
|
|
64
|
+
}
|
|
65
|
+
/* Size the screen-size <select> to its current value, not its widest option
|
|
66
|
+
(without this a native select reserves room for "iPhone Pro Max" even when
|
|
67
|
+
"Full" is showing). Dropdown still lists full labels. */
|
|
68
|
+
.dc-controls .dcui-select-el {
|
|
69
|
+
field-sizing: content;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Group related inputs into a single bordered cluster — never float a
|
|
73
|
+
lone control. The W×H + rotate fields live in one box. */
|
|
74
|
+
.dc-dims {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: var(--dc-space-1);
|
|
78
|
+
height: 26px;
|
|
79
|
+
border: var(--dc-border-line);
|
|
80
|
+
border-radius: var(--dc-radius-sm);
|
|
81
|
+
background: var(--dc-surface);
|
|
82
|
+
/* No right padding: the bare rotate button sits flush to the box edge. */
|
|
83
|
+
padding: 0 0 0 var(--dc-space-2);
|
|
84
|
+
}
|
|
85
|
+
.dc-dim {
|
|
86
|
+
/* Room for 4 digits (max sensible pixel dimension) plus a hair of breathing
|
|
87
|
+
space — `field-sizing: content` isn't supported everywhere, and the fallback
|
|
88
|
+
is the browser's very wide default <input> width. */
|
|
89
|
+
width: 4.5ch;
|
|
90
|
+
border: 0;
|
|
91
|
+
background: none;
|
|
92
|
+
padding: 0;
|
|
93
|
+
font: inherit;
|
|
94
|
+
font-size: var(--dc-text-sm);
|
|
95
|
+
font-variant-numeric: tabular-nums;
|
|
96
|
+
text-align: center;
|
|
97
|
+
color: var(--dc-fg);
|
|
98
|
+
/* Flat design: drop the native number spinners (they add width and chrome). */
|
|
99
|
+
appearance: textfield;
|
|
100
|
+
-moz-appearance: textfield;
|
|
101
|
+
}
|
|
102
|
+
.dc-dim::-webkit-outer-spin-button,
|
|
103
|
+
.dc-dim::-webkit-inner-spin-button {
|
|
104
|
+
-webkit-appearance: none;
|
|
105
|
+
margin: 0;
|
|
106
|
+
}
|
|
107
|
+
.dc-dim::placeholder {
|
|
108
|
+
color: var(--dc-fg-subtle);
|
|
109
|
+
}
|
|
110
|
+
.dc-dim:focus,
|
|
111
|
+
.dc-dim:focus-visible {
|
|
112
|
+
outline: none;
|
|
113
|
+
}
|
|
114
|
+
.dc-dim:disabled {
|
|
115
|
+
opacity: 0.5;
|
|
116
|
+
}
|
|
117
|
+
.dc-dim-x {
|
|
118
|
+
color: var(--dc-fg-subtle);
|
|
119
|
+
font-family: var(--dc-font-mono);
|
|
120
|
+
font-size: var(--dc-text-sm);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ── Sidebar nav ────────────────────────────────────────────────── */
|
|
124
|
+
/* The Sidebar component owns the surface (border, backdrop, padding); the chrome
|
|
125
|
+
re-tasks it as a non-scrolling column so the mode switch can pin to the top and
|
|
126
|
+
the scroll/fade live on an inner region — keeping the rail's border crisp (a
|
|
127
|
+
mask on the scroll region can't touch a border that isn't on it). */
|
|
128
|
+
.dcui-sidebar {
|
|
129
|
+
grid-area: sidebar;
|
|
130
|
+
}
|
|
131
|
+
.dc-app .dcui-sidebar {
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: column;
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
}
|
|
136
|
+
.dc-app[data-nav="collapsed"] .dcui-sidebar {
|
|
137
|
+
display: none;
|
|
138
|
+
}
|
|
139
|
+
/* The scroll region: fills the rail below the pinned switch and carries the
|
|
140
|
+
content's scroll. Its native scrollbar is hidden (it can't fade, so on a
|
|
141
|
+
crossfade it would pop in as a step change; only the long Cases nav overflows,
|
|
142
|
+
so a reserved track would sit empty in the short Primer view) — replaced by a
|
|
143
|
+
soft gradient fade at whichever edge has more content off-screen. `data-fade-*`
|
|
144
|
+
are toggled from the scroll position (see shell.tsx); the mask is fixed to this
|
|
145
|
+
region's edges, so it tracks the viewport, not the scrolled content, and no
|
|
146
|
+
edge fades when it's fully docked. */
|
|
147
|
+
.dc-nav-scroll {
|
|
148
|
+
--dc-nav-fade: var(--dc-space-12);
|
|
149
|
+
flex: 1 1 0;
|
|
150
|
+
min-height: 0;
|
|
151
|
+
overflow-y: auto;
|
|
152
|
+
scrollbar-width: none; /* Firefox */
|
|
153
|
+
}
|
|
154
|
+
.dc-nav-scroll::-webkit-scrollbar {
|
|
155
|
+
display: none; /* WebKit/Blink */
|
|
156
|
+
}
|
|
157
|
+
.dc-nav-scroll[data-fade-top="true"][data-fade-bottom="true"] {
|
|
158
|
+
-webkit-mask-image: linear-gradient(
|
|
159
|
+
to bottom,
|
|
160
|
+
transparent 0,
|
|
161
|
+
#000 var(--dc-nav-fade),
|
|
162
|
+
#000 calc(100% - var(--dc-nav-fade)),
|
|
163
|
+
transparent 100%
|
|
164
|
+
);
|
|
165
|
+
mask-image: linear-gradient(
|
|
166
|
+
to bottom,
|
|
167
|
+
transparent 0,
|
|
168
|
+
#000 var(--dc-nav-fade),
|
|
169
|
+
#000 calc(100% - var(--dc-nav-fade)),
|
|
170
|
+
transparent 100%
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
.dc-nav-scroll[data-fade-top="true"]:not([data-fade-bottom="true"]) {
|
|
174
|
+
-webkit-mask-image: linear-gradient(
|
|
175
|
+
to bottom,
|
|
176
|
+
transparent 0,
|
|
177
|
+
#000 var(--dc-nav-fade)
|
|
178
|
+
);
|
|
179
|
+
mask-image: linear-gradient(
|
|
180
|
+
to bottom,
|
|
181
|
+
transparent 0,
|
|
182
|
+
#000 var(--dc-nav-fade)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
.dc-nav-scroll:not([data-fade-top="true"])[data-fade-bottom="true"] {
|
|
186
|
+
-webkit-mask-image: linear-gradient(
|
|
187
|
+
to bottom,
|
|
188
|
+
#000 calc(100% - var(--dc-nav-fade)),
|
|
189
|
+
transparent 100%
|
|
190
|
+
);
|
|
191
|
+
mask-image: linear-gradient(
|
|
192
|
+
to bottom,
|
|
193
|
+
#000 calc(100% - var(--dc-nav-fade)),
|
|
194
|
+
transparent 100%
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
.dc-group {
|
|
198
|
+
margin-bottom: var(--dc-space-8);
|
|
199
|
+
}
|
|
200
|
+
/* Primer TOC groups are far more numerous than the component-tree levels, so
|
|
201
|
+
they sit closer together — enough to read the `##` heading as a group break,
|
|
202
|
+
not so much it fragments the nav. */
|
|
203
|
+
.dc-primer-group {
|
|
204
|
+
margin-bottom: var(--dc-space-3);
|
|
205
|
+
}
|
|
206
|
+
/* Eyebrow styling is the Eyebrow component's; this only positions the label. */
|
|
207
|
+
.dc-group-label {
|
|
208
|
+
margin: 0 0 var(--dc-space-3) var(--dc-space-3);
|
|
209
|
+
}
|
|
210
|
+
/* Nav rows are the NavItem component; this only spaces the groups. */
|
|
211
|
+
.dc-nav-component {
|
|
212
|
+
margin-bottom: var(--dc-space-1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ── Main + stage ───────────────────────────────────────────────── */
|
|
216
|
+
.dc-main {
|
|
217
|
+
grid-area: main;
|
|
218
|
+
overflow-y: auto;
|
|
219
|
+
padding: var(--dc-space-10);
|
|
220
|
+
display: flex;
|
|
221
|
+
flex-direction: column;
|
|
222
|
+
min-height: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* The stage lays the content column beside the (optional) doc panel, filling
|
|
226
|
+
the main area's height so the preview can expand fully. */
|
|
227
|
+
.dc-stage {
|
|
228
|
+
display: flex;
|
|
229
|
+
align-items: stretch;
|
|
230
|
+
gap: var(--dc-space-8);
|
|
231
|
+
/* basis 0 (not auto) so height is a share of the bounded grid cell, never
|
|
232
|
+
content-driven — otherwise the measured preview height feeds back. */
|
|
233
|
+
flex: 1 1 0;
|
|
234
|
+
min-height: 0;
|
|
235
|
+
}
|
|
236
|
+
.dc-content {
|
|
237
|
+
flex: 1;
|
|
238
|
+
min-width: 0;
|
|
239
|
+
/* min-height: 0 lets the column shrink below its content; overflow-y keeps the
|
|
240
|
+
Tweaks + Accessibility panels reachable when the stage's min-height plus the
|
|
241
|
+
panels exceed the available height (e.g. a short viewport, or the a11y panel
|
|
242
|
+
loading its violations) instead of pushing them off the bottom edge. */
|
|
243
|
+
min-height: 0;
|
|
244
|
+
overflow-y: auto;
|
|
245
|
+
display: flex;
|
|
246
|
+
flex-direction: column;
|
|
247
|
+
gap: var(--dc-space-8);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.dc-doc-panel {
|
|
251
|
+
flex: 0 0 var(--dc-doc-w, var(--dc-doc-panel-w));
|
|
252
|
+
align-self: stretch;
|
|
253
|
+
border: var(--dc-border-line);
|
|
254
|
+
border-radius: var(--dc-radius-md);
|
|
255
|
+
background: var(--dc-surface);
|
|
256
|
+
position: sticky;
|
|
257
|
+
top: 0;
|
|
258
|
+
/* Positioning context for the edge handle; clips the (scrolling) content to
|
|
259
|
+
the rounded corners. The inner wrapper does the scrolling so the handle
|
|
260
|
+
stays pinned to the left edge. */
|
|
261
|
+
overflow: hidden;
|
|
262
|
+
}
|
|
263
|
+
.dc-doc-scroll {
|
|
264
|
+
height: 100%;
|
|
265
|
+
overflow-y: auto;
|
|
266
|
+
padding: var(--dc-space-6) var(--dc-space-8);
|
|
267
|
+
}
|
|
268
|
+
/* The resize grip IS the panel's left edge: a full-height strip pinned to the
|
|
269
|
+
left that brightens to marigold on hover; drag (or arrow keys) to resize. */
|
|
270
|
+
.dc-doc-resize {
|
|
271
|
+
position: absolute;
|
|
272
|
+
left: 0;
|
|
273
|
+
top: 0;
|
|
274
|
+
bottom: 0;
|
|
275
|
+
width: 10px;
|
|
276
|
+
z-index: 1;
|
|
277
|
+
cursor: col-resize;
|
|
278
|
+
touch-action: none;
|
|
279
|
+
}
|
|
280
|
+
.dc-doc-resize::after {
|
|
281
|
+
content: "";
|
|
282
|
+
position: absolute;
|
|
283
|
+
left: 0;
|
|
284
|
+
top: 0;
|
|
285
|
+
bottom: 0;
|
|
286
|
+
width: 1px;
|
|
287
|
+
background: transparent;
|
|
288
|
+
transition: background var(--dc-transition-fast);
|
|
289
|
+
}
|
|
290
|
+
.dc-doc-resize:hover::after,
|
|
291
|
+
.dc-doc-resize:focus-visible::after,
|
|
292
|
+
.dc-doc-resize:active::after {
|
|
293
|
+
background: var(--dc-brand);
|
|
294
|
+
}
|
|
295
|
+
.dc-doc-resize:focus-visible {
|
|
296
|
+
outline: none;
|
|
297
|
+
}
|
|
298
|
+
.dc-doc-head {
|
|
299
|
+
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
justify-content: space-between;
|
|
302
|
+
gap: var(--dc-space-4);
|
|
303
|
+
margin-bottom: var(--dc-space-4);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@media (max-width: 900px) {
|
|
307
|
+
.dc-stage {
|
|
308
|
+
flex-wrap: wrap;
|
|
309
|
+
}
|
|
310
|
+
.dc-doc-panel {
|
|
311
|
+
flex-basis: 100%;
|
|
312
|
+
position: static;
|
|
313
|
+
}
|
|
314
|
+
/* Stacked layout: the horizontal drag handle is meaningless. */
|
|
315
|
+
.dc-doc-resize {
|
|
316
|
+
display: none;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* The Accessibility panel, its severity tags, and the nav-rail a11y markers are
|
|
321
|
+
self-contained components that inject their own styles (showcase/A11yPanel,
|
|
322
|
+
showcase/ImpactTag, showcase/A11yBadge). */
|
|
323
|
+
|
|
324
|
+
/* The preview is the stable centering viewport that fills the content column.
|
|
325
|
+
It measures the available area (for responsive sizing) and centers the stage
|
|
326
|
+
frame inside it, scrolling to every edge when the (possibly scaled or zoomed)
|
|
327
|
+
frame is larger than the panel. `safe center` keeps the start reachable
|
|
328
|
+
instead of clipping it on overflow. */
|
|
329
|
+
.dc-preview {
|
|
330
|
+
flex: 1 1 0;
|
|
331
|
+
min-height: 12rem;
|
|
332
|
+
display: flex;
|
|
333
|
+
justify-content: safe center;
|
|
334
|
+
align-items: safe center;
|
|
335
|
+
overflow: auto;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* The vitrine frame (border, rounded surface, grid + corner ticks, hug/fill
|
|
339
|
+
sizing) is the Stage component (dcui-stage); see
|
|
340
|
+
./design-system/components/showcase/Stage.tsx. The shell only positions it
|
|
341
|
+
(centered in `.dc-preview`) and supplies the scaled frame-box below. */
|
|
342
|
+
|
|
343
|
+
/* Occupies the *scaled* footprint so centering + scrolling are correct; the
|
|
344
|
+
iframe is scaled from its top-left corner within. */
|
|
345
|
+
.dc-frame-box {
|
|
346
|
+
position: relative;
|
|
347
|
+
flex: 0 0 auto;
|
|
348
|
+
/* Clips the iframe to the visible (content) height: in Responsive mode the
|
|
349
|
+
iframe stays panel-tall so the component's viewport is stable, and the box
|
|
350
|
+
reveals only its measured height — the rest is grid, not blank surface. */
|
|
351
|
+
overflow: hidden;
|
|
352
|
+
}
|
|
353
|
+
.dc-frame {
|
|
354
|
+
position: absolute;
|
|
355
|
+
top: 0;
|
|
356
|
+
left: 0;
|
|
357
|
+
border: 0;
|
|
358
|
+
/* Transparent so the stage's surface + grid show through wherever the render
|
|
359
|
+
document is itself transparent (decorated components). Pages/flows paint
|
|
360
|
+
their own opaque body background over this. */
|
|
361
|
+
background: transparent;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Flow nav, the tweaks panel, and the button / icon-button / select / input
|
|
365
|
+
controls are now the design-system components (dcui-*); see
|
|
366
|
+
./design-system/components/. Only shell *layout* lives in this file. */
|
|
367
|
+
|
|
368
|
+
/* ── Zoom cluster ───────────────────────────────────────────────── */
|
|
369
|
+
.dc-zoom {
|
|
370
|
+
display: flex;
|
|
371
|
+
align-items: center;
|
|
372
|
+
gap: var(--dc-space-1);
|
|
373
|
+
height: 26px;
|
|
374
|
+
border: var(--dc-border-line);
|
|
375
|
+
border-radius: var(--dc-radius-sm);
|
|
376
|
+
background: var(--dc-surface);
|
|
377
|
+
}
|
|
378
|
+
.dc-zoom-level {
|
|
379
|
+
font: inherit;
|
|
380
|
+
font-family: var(--dc-font-mono);
|
|
381
|
+
font-size: var(--dc-text-sm);
|
|
382
|
+
font-variant-numeric: tabular-nums;
|
|
383
|
+
min-width: 2.75rem;
|
|
384
|
+
text-align: center;
|
|
385
|
+
border: 0;
|
|
386
|
+
background: none;
|
|
387
|
+
color: var(--dc-fg);
|
|
388
|
+
cursor: pointer;
|
|
389
|
+
padding: var(--dc-space-2);
|
|
390
|
+
border-radius: var(--dc-radius-sm);
|
|
391
|
+
}
|
|
392
|
+
.dc-zoom-level:hover {
|
|
393
|
+
background: var(--dc-hover);
|
|
394
|
+
}
|
|
395
|
+
.dc-zoom-fit {
|
|
396
|
+
cursor: default;
|
|
397
|
+
color: var(--dc-fg-muted);
|
|
398
|
+
}
|
|
399
|
+
.dc-zoom-fit:hover {
|
|
400
|
+
background: none;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* ── Documentation markdown ─────────────────────────────────────── */
|
|
404
|
+
.dc-doc-md {
|
|
405
|
+
margin-top: var(--dc-space-6);
|
|
406
|
+
line-height: var(--dc-leading-relaxed);
|
|
407
|
+
}
|
|
408
|
+
.dc-doc-md pre {
|
|
409
|
+
background: var(--dc-bg-subtle);
|
|
410
|
+
border: var(--dc-border-line);
|
|
411
|
+
padding: var(--dc-space-6);
|
|
412
|
+
border-radius: var(--dc-radius-sm);
|
|
413
|
+
overflow-x: auto;
|
|
414
|
+
}
|
|
415
|
+
.dc-doc-md code {
|
|
416
|
+
font-family: var(--dc-font-mono);
|
|
417
|
+
font-size: 0.85em;
|
|
418
|
+
}
|
|
419
|
+
.dc-doc-md table {
|
|
420
|
+
border-collapse: collapse;
|
|
421
|
+
}
|
|
422
|
+
.dc-doc-md th,
|
|
423
|
+
.dc-doc-md td {
|
|
424
|
+
border: var(--dc-border-line);
|
|
425
|
+
padding: var(--dc-space-2) var(--dc-space-4);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* ── Empty / loading / error states ─────────────────────────────── */
|
|
429
|
+
.dc-loading,
|
|
430
|
+
.dc-empty {
|
|
431
|
+
display: flex;
|
|
432
|
+
flex-direction: column;
|
|
433
|
+
align-items: center;
|
|
434
|
+
justify-content: center;
|
|
435
|
+
height: 100vh;
|
|
436
|
+
color: var(--dc-fg-muted);
|
|
437
|
+
background: var(--dc-bg);
|
|
438
|
+
gap: var(--dc-space-2);
|
|
439
|
+
}
|
|
440
|
+
.dc-empty-hint {
|
|
441
|
+
font-size: var(--dc-text-sm);
|
|
442
|
+
color: var(--dc-fg-subtle);
|
|
443
|
+
}
|
|
444
|
+
.dc-empty code,
|
|
445
|
+
.dc-empty-hint code {
|
|
446
|
+
font-family: var(--dc-font-mono);
|
|
447
|
+
font-size: 0.9em;
|
|
448
|
+
color: var(--dc-fg-muted);
|
|
449
|
+
}
|
|
450
|
+
.dc-render-missing {
|
|
451
|
+
padding: var(--dc-space-16);
|
|
452
|
+
font-family: var(--dc-font-sans);
|
|
453
|
+
color: var(--dc-danger);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/* ── Primer ────────────────────────────────────────────────────── */
|
|
457
|
+
/* Mode switch placement: the SegmentedToggle (primer vs cases) pinned above the
|
|
458
|
+
sidebar scroll region — never shrinks, so nav items scroll and fade beneath
|
|
459
|
+
it. The control's own appearance lives with the SegmentedToggle component. */
|
|
460
|
+
.dc-modeswitch {
|
|
461
|
+
flex: 0 0 auto;
|
|
462
|
+
margin-bottom: var(--dc-space-8);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/* The Primer host shares the `main` grid area with the library stage; the
|
|
466
|
+
inactive one is `hidden`. The reading page owns its own scroll inside the
|
|
467
|
+
isolated iframe, so the host is just an unpadded, edge-to-edge frame. */
|
|
468
|
+
.dc-primer-host {
|
|
469
|
+
grid-area: main;
|
|
470
|
+
display: flex;
|
|
471
|
+
min-height: 0;
|
|
472
|
+
}
|
|
473
|
+
.dc-primer-frame {
|
|
474
|
+
flex: 1;
|
|
475
|
+
width: 100%;
|
|
476
|
+
border: 0;
|
|
477
|
+
}
|
|
478
|
+
/* `.dc-main`/`.dc-primer-host` set `display`, which outranks the UA `[hidden]`
|
|
479
|
+
rule — so the inactive view would still paint (and bleed through). Re-assert
|
|
480
|
+
the hide explicitly. The library stays in the DOM (its render iframe keeps its
|
|
481
|
+
handshake); it's just not displayed. */
|
|
482
|
+
.dc-main[hidden],
|
|
483
|
+
.dc-primer-host[hidden] {
|
|
484
|
+
display: none;
|
|
485
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Display Case Design System — "The Vitrine"
|
|
2
|
+
|
|
3
|
+
The self-contained visual identity for Display Case. It lives **inside** the
|
|
4
|
+
package so Display Case dogfoods it: the browse chrome (`../chrome.css`) is
|
|
5
|
+
styled entirely from these `--dc-*` tokens — no host-app library, no
|
|
6
|
+
ui tokens.
|
|
7
|
+
|
|
8
|
+
## Identity in one breath
|
|
9
|
+
|
|
10
|
+
Warm paper neutrals, a warm-ink primary, and a single **marigold** accent.
|
|
11
|
+
Flat and border-led so the showcased component is the exhibit. Has charm
|
|
12
|
+
(bracketed wordmark, stage corner ticks) but never decorates.
|
|
13
|
+
|
|
14
|
+
- **Type** — Hanken Grotesk (UI) + JetBrains Mono (labels / values / code /
|
|
15
|
+
wordmark). Dense 14px base. Eyebrow labels are UPPERCASE mono, `0.08em`.
|
|
16
|
+
- **Icons** — Unicode glyphs only. No icon font, no SVG, no emoji.
|
|
17
|
+
- **Themes** — light + dark via `data-theme` on any scope. Dark is warm
|
|
18
|
+
charcoal, never pure black.
|
|
19
|
+
|
|
20
|
+
## Files
|
|
21
|
+
|
|
22
|
+
| Path | What |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `styles.css` | Entry point. `@import`s the four token files below. |
|
|
25
|
+
| `tokens/fonts.css` | Webfont declaration (Hanken Grotesk + JetBrains Mono). |
|
|
26
|
+
| `tokens/colors.css` | Paper ramp, marigold ramp, status hues, light + dark aliases. |
|
|
27
|
+
| `tokens/typography.css` | Families, size scale, weights, tracking, the eyebrow role token. |
|
|
28
|
+
| `tokens/spacing.css` | Spacing scale, radii, borders, elevation, motion. |
|
|
29
|
+
|
|
30
|
+
## Components
|
|
31
|
+
|
|
32
|
+
`components/` holds the library as **pure React components** — each keeps its
|
|
33
|
+
`dcui-*` CSS in a **co-located `.css` file** (`Button.tsx` → `Button.css`) and
|
|
34
|
+
consumes only the `--dc-*` tokens. The server reads every component `.css`, the
|
|
35
|
+
shell `chrome.css`, and the primer `primer.css`, concatenates them into one
|
|
36
|
+
**Vitrine stylesheet**, and inlines it into every document `<style>` before
|
|
37
|
+
scripts run (`readVitrineCss` in `../../server/server.ts`). There is **no
|
|
38
|
+
runtime style injection** — the components no longer touch `document` to paint.
|
|
39
|
+
|
|
40
|
+
| Group | Components |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `components/controls/` | **Button**, **IconButton**, **Input**, **Select** |
|
|
43
|
+
| `components/showcase/` | **Eyebrow**, **Chip**, **NavItem**, **Sidebar**, **Stage**, **FlowNav**, **TweaksPanel**, **RenderAddress**, **Wordmark** |
|
|
44
|
+
| `components/primer-specimen/` | **ColorRamp**, **SwatchGrid**, **StatusList**, **GlyphGrid**, **DefinitionList**, **LayoutMock**, **TypeScale**, **FontFamilies**, **WeightSpecimen**, **SpacingScale**, **SpecimenBoxRow** |
|
|
45
|
+
|
|
46
|
+
The `components/primer-specimen/` group is **generic, prop-driven foundation
|
|
47
|
+
specimens** for building a Primer — feed them your own ramps, swatches, type
|
|
48
|
+
scale, spacing steps, and glyphs. Display Case's own Primer (the
|
|
49
|
+
`primer-specimens/` wrappers) is the worked example: each wrapper supplies
|
|
50
|
+
Display-Case-specific data to one of these primitives.
|
|
51
|
+
|
|
52
|
+
The browse chrome (`../shell.tsx`) is built from these, and Display Case
|
|
53
|
+
**dogfoods them as its own showcased components**: each has a colocated
|
|
54
|
+
`*.case.tsx` + `*.placard.md`, surfaced by the repo-root `display-case.config.ts`.
|
|
55
|
+
Browse them with:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bun run display-case # or: bun src/cli.ts .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`chrome.css` keeps only shell *layout* (grid regions, the stage/preview sizing,
|
|
62
|
+
the doc panel); the component-level styling lives with the components.
|
|
63
|
+
|
|
64
|
+
### The chrome as a pure component (`components/shell/`)
|
|
65
|
+
|
|
66
|
+
The browse chrome is split so it is itself a pure, exhibitable component:
|
|
67
|
+
`ui/use-shell.ts` (the `useShell()` state machine) → `components/shell/ShellView.tsx`
|
|
68
|
+
(a pure function of the `ShellViewModel`, with the live iframes passed in as
|
|
69
|
+
`renderFrame` / `primerFrame` slots) → `ui/shell.tsx` (a thin container). That
|
|
70
|
+
lets Display Case **dogfood its own layout**: `components/shell/` adds `template`
|
|
71
|
+
cases (Case/Primer templates, placeholder slots), `page` cases (Button,
|
|
72
|
+
RenderAddress, Sidebar, Primer, Case-template pages — real content slotted in),
|
|
73
|
+
and a `flow` (`ShellView.case.tsx`, Primer → Cases). They share
|
|
74
|
+
`shell-fixtures.tsx` (a `// display-case: no-case` helper). `ShellView` paints
|
|
75
|
+
inside the isolated `/render` doc because the server inlines the whole Vitrine
|
|
76
|
+
stylesheet — `chrome.css` included — into *every* document (the shell pages use
|
|
77
|
+
its `.dc-*` layout). See `../../../contributing/NOTES.md` for the gotchas
|
|
78
|
+
(render-doc CSS, snapshot determinism, primer skeleton widths).
|
|
79
|
+
|
|
80
|
+
## How the chrome consumes the tokens
|
|
81
|
+
|
|
82
|
+
`startDisplayCase` (in `../../server/server.ts`) inlines the token CSS into the
|
|
83
|
+
browse-shell `<style>` ahead of `chrome.css`, and injects the webfont
|
|
84
|
+
`<link>`s into the document head. Everything references `var(--dc-*)`; nothing
|
|
85
|
+
hard-codes a color, radius, or font.
|
|
86
|
+
|
|
87
|
+
Source of truth: the **Display Case Design System** project on
|
|
88
|
+
claude.ai/design, kept in sync via `/design-sync`.
|