@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,84 @@
|
|
|
1
|
+
import { defineCases, tweak } from '@awarebydefault/display-case'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { type SegmentedOption, SegmentedToggle } from './SegmentedToggle'
|
|
4
|
+
|
|
5
|
+
const two: SegmentedOption<string>[] = [
|
|
6
|
+
{ id: 'primer', label: 'Primer' },
|
|
7
|
+
{ id: 'library', label: 'Cases' },
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
const three: SegmentedOption<string>[] = [
|
|
11
|
+
{ id: 'light', label: 'Light' },
|
|
12
|
+
{ id: 'auto', label: 'Auto' },
|
|
13
|
+
{ id: 'dark', label: 'Dark' },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
const five: SegmentedOption<string>[] = [
|
|
17
|
+
{ id: 'xs', label: 'XS' },
|
|
18
|
+
{ id: 'sm', label: 'SM' },
|
|
19
|
+
{ id: 'md', label: 'MD' },
|
|
20
|
+
{ id: 'lg', label: 'LG' },
|
|
21
|
+
{ id: 'xl', label: 'XL' },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
// SegmentedToggle is controlled, so each specimen owns its selected value —
|
|
25
|
+
// click a segment in the stage and the thumb lerps to it, no tweaks needed.
|
|
26
|
+
function Demo({
|
|
27
|
+
label,
|
|
28
|
+
options,
|
|
29
|
+
initial,
|
|
30
|
+
}: {
|
|
31
|
+
label: string
|
|
32
|
+
options: SegmentedOption<string>[]
|
|
33
|
+
initial: string
|
|
34
|
+
}) {
|
|
35
|
+
const [value, setValue] = useState(initial)
|
|
36
|
+
return (
|
|
37
|
+
<SegmentedToggle
|
|
38
|
+
label={label}
|
|
39
|
+
options={options}
|
|
40
|
+
value={value}
|
|
41
|
+
onChange={setValue}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default defineCases(
|
|
47
|
+
'SegmentedToggle',
|
|
48
|
+
{
|
|
49
|
+
// The browse chrome swaps cases *in place* (one persistent render root), so a
|
|
50
|
+
// bare <Demo> would be reconciled as the same instance across cases and keep
|
|
51
|
+
// its stale `value` — which, between cases with different option ids, leaves
|
|
52
|
+
// no segment active. A distinct `key` per case forces a remount so each
|
|
53
|
+
// re-seeds from its own `initial`. (Same reason the Playground keys by count.)
|
|
54
|
+
Playground: {
|
|
55
|
+
tweaks: {
|
|
56
|
+
count: tweak.choice(['2', '3', '5'], '5'),
|
|
57
|
+
},
|
|
58
|
+
render: (t) => {
|
|
59
|
+
const byCount: Record<string, SegmentedOption<string>[]> = {
|
|
60
|
+
'2': two,
|
|
61
|
+
'3': three,
|
|
62
|
+
'5': five,
|
|
63
|
+
}
|
|
64
|
+
const options = byCount[t.count] ?? five
|
|
65
|
+
return (
|
|
66
|
+
<Demo
|
|
67
|
+
key={`pg-${t.count}`}
|
|
68
|
+
label="Size"
|
|
69
|
+
options={options}
|
|
70
|
+
initial={options[0].id}
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
Two: () => (
|
|
76
|
+
<Demo key="two" label="View mode" options={two} initial="library" />
|
|
77
|
+
),
|
|
78
|
+
Three: () => (
|
|
79
|
+
<Demo key="three" label="Theme" options={three} initial="auto" />
|
|
80
|
+
),
|
|
81
|
+
Five: () => <Demo key="five" label="Size" options={five} initial="lg" />,
|
|
82
|
+
},
|
|
83
|
+
{ level: 'molecule' },
|
|
84
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
.dcui-segmented {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: grid;
|
|
4
|
+
grid-template-columns: repeat(var(--seg-count), 1fr);
|
|
5
|
+
gap: var(--dc-space-1);
|
|
6
|
+
padding: var(--dc-space-1);
|
|
7
|
+
border: var(--dc-border-line);
|
|
8
|
+
border-radius: var(--dc-radius-sm);
|
|
9
|
+
background: var(--dc-bg);
|
|
10
|
+
}
|
|
11
|
+
/* The highlight box: one cell-wide thumb pinned inside the padding box. The gap
|
|
12
|
+
between cells (and the padding around them) is one --dc-space-1; its width is a
|
|
13
|
+
single grid cell — (track − the N+1 gaps) divided by N — so translating by its
|
|
14
|
+
own width plus one gap, times the active index, lands it exactly over any
|
|
15
|
+
segment. The transform transition does the linear lerp across the list. */
|
|
16
|
+
.dcui-segmented-thumb {
|
|
17
|
+
position: absolute;
|
|
18
|
+
top: var(--dc-space-1);
|
|
19
|
+
bottom: var(--dc-space-1);
|
|
20
|
+
left: var(--dc-space-1);
|
|
21
|
+
width: calc(
|
|
22
|
+
(100% - (var(--seg-count) + 1) * var(--dc-space-1)) /
|
|
23
|
+
var(--seg-count)
|
|
24
|
+
);
|
|
25
|
+
transform: translateX(calc((100% + var(--dc-space-1)) * var(--seg-index)));
|
|
26
|
+
border-radius: calc(var(--dc-radius-sm) - 1px);
|
|
27
|
+
background: var(--dc-brand);
|
|
28
|
+
transition: transform var(--dc-transition-base);
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
}
|
|
31
|
+
.dcui-segmented-seg {
|
|
32
|
+
position: relative;
|
|
33
|
+
z-index: 1;
|
|
34
|
+
appearance: none;
|
|
35
|
+
border: 0;
|
|
36
|
+
border-radius: calc(var(--dc-radius-sm) - 1px);
|
|
37
|
+
padding: var(--dc-space-2) var(--dc-space-4);
|
|
38
|
+
font-family: var(--dc-font-mono);
|
|
39
|
+
font-size: var(--dc-text-xs);
|
|
40
|
+
font-weight: var(--dc-weight-medium);
|
|
41
|
+
letter-spacing: var(--dc-tracking-label);
|
|
42
|
+
text-transform: uppercase;
|
|
43
|
+
color: var(--dc-fg-muted);
|
|
44
|
+
background: transparent;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
transition: color var(--dc-transition-base);
|
|
47
|
+
}
|
|
48
|
+
/* Only the inactive segments take a hover fill — the active one sits over the
|
|
49
|
+
thumb, which already carries the brand background. */
|
|
50
|
+
.dcui-segmented-seg:not([data-active="true"]):hover {
|
|
51
|
+
color: var(--dc-fg);
|
|
52
|
+
background: var(--dc-hover);
|
|
53
|
+
}
|
|
54
|
+
.dcui-segmented-seg[data-active="true"] {
|
|
55
|
+
color: var(--dc-brand-fg);
|
|
56
|
+
}
|
|
57
|
+
@media (prefers-reduced-motion: reduce) {
|
|
58
|
+
.dcui-segmented-thumb {
|
|
59
|
+
transition: none;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
**SegmentedToggle** — an isolated, multi-option segmented control; reach for it whenever you're switching between a small fixed set of mutually-exclusive views or modes (2, 3, 5 — any count) and want the selection to slide rather than blink.
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
<SegmentedToggle
|
|
5
|
+
label="View mode"
|
|
6
|
+
options={[
|
|
7
|
+
{ id: 'primer', label: 'Primer' },
|
|
8
|
+
{ id: 'library', label: 'Cases' },
|
|
9
|
+
]}
|
|
10
|
+
value={mode}
|
|
11
|
+
onChange={setMode}
|
|
12
|
+
/>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It's controlled: pass `value` (one option's `id`) and `onChange`. A single brand-filled thumb is sized to one cell and translated by the active index, so the highlight animates **linearly across the list** for any number of options — there is no per-count CSS. The geometry is driven by `--seg-count` / `--seg-index` set inline on the root; the transition respects `prefers-reduced-motion`.
|
|
16
|
+
|
|
17
|
+
`label` is the tablist's accessible name (the control is `role="tablist"`, each segment `role="tab"`). Options render in array order. An unknown `value` parks the thumb on the first cell.
|
|
18
|
+
|
|
19
|
+
Pass `testId(id)` for per-segment `data-testid`s (the sidebar mode switch uses `DcTestIds.modeSwitch`). Pass `className` for layout-context placement (e.g. the sidebar adds `dc-modeswitch` for pinning); the control's own appearance stays self-contained.
|
|
20
|
+
|
|
21
|
+
For a single pill, reach for `Chip`; for a stepper with Prev/Next, `FlowNav`.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { SegmentedToggle } from './SegmentedToggle'
|
|
4
|
+
|
|
5
|
+
const options = [
|
|
6
|
+
{ id: 'light', label: 'Light' },
|
|
7
|
+
{ id: 'dark', label: 'Dark' },
|
|
8
|
+
{ id: 'system', label: 'System' },
|
|
9
|
+
] as const
|
|
10
|
+
|
|
11
|
+
const noop = () => {}
|
|
12
|
+
|
|
13
|
+
describe('SegmentedToggle', () => {
|
|
14
|
+
test('renders a labelled tablist of tabs', () => {
|
|
15
|
+
const html = renderToStaticMarkup(
|
|
16
|
+
<SegmentedToggle
|
|
17
|
+
options={[...options]}
|
|
18
|
+
value="light"
|
|
19
|
+
onChange={noop}
|
|
20
|
+
label="Theme"
|
|
21
|
+
/>,
|
|
22
|
+
)
|
|
23
|
+
expect(html).toContain('role="tablist"')
|
|
24
|
+
expect(html).toContain('aria-label="Theme"')
|
|
25
|
+
expect((html.match(/role="tab"/g) ?? []).length).toBe(3)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('marks the selected option as the active, selected tab', () => {
|
|
29
|
+
const html = renderToStaticMarkup(
|
|
30
|
+
<SegmentedToggle
|
|
31
|
+
options={[...options]}
|
|
32
|
+
value="dark"
|
|
33
|
+
onChange={noop}
|
|
34
|
+
label="Theme"
|
|
35
|
+
/>,
|
|
36
|
+
)
|
|
37
|
+
// Exactly one tab is selected/active.
|
|
38
|
+
expect((html.match(/aria-selected="true"/g) ?? []).length).toBe(1)
|
|
39
|
+
expect((html.match(/data-active="true"/g) ?? []).length).toBe(1)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('drives the thumb geometry from --seg-count and --seg-index', () => {
|
|
43
|
+
const html = renderToStaticMarkup(
|
|
44
|
+
<SegmentedToggle
|
|
45
|
+
options={[...options]}
|
|
46
|
+
value="system"
|
|
47
|
+
onChange={noop}
|
|
48
|
+
label="Theme"
|
|
49
|
+
/>,
|
|
50
|
+
)
|
|
51
|
+
expect(html).toContain('--seg-count:3')
|
|
52
|
+
expect(html).toContain('--seg-index:2')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('clamps an unknown value to the first cell rather than a negative index', () => {
|
|
56
|
+
const html = renderToStaticMarkup(
|
|
57
|
+
<SegmentedToggle
|
|
58
|
+
options={[...options]}
|
|
59
|
+
value={'gone' as never}
|
|
60
|
+
onChange={noop}
|
|
61
|
+
label="Theme"
|
|
62
|
+
/>,
|
|
63
|
+
)
|
|
64
|
+
expect(html).toContain('--seg-index:0')
|
|
65
|
+
expect(html).not.toContain('aria-selected="true"')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('applies a per-segment test id factory when given', () => {
|
|
69
|
+
const html = renderToStaticMarkup(
|
|
70
|
+
<SegmentedToggle
|
|
71
|
+
options={[...options]}
|
|
72
|
+
value="light"
|
|
73
|
+
onChange={noop}
|
|
74
|
+
label="Theme"
|
|
75
|
+
testId={(id) => `seg-${id}`}
|
|
76
|
+
/>,
|
|
77
|
+
)
|
|
78
|
+
expect(html).toContain('data-testid="seg-light"')
|
|
79
|
+
expect(html).toContain('data-testid="seg-system"')
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Display Case — SegmentedToggle
|
|
5
|
+
* An isolated, multi-option segmented control. Takes any number of options and
|
|
6
|
+
* animates a single brand-filled thumb linearly from one to the next: the thumb
|
|
7
|
+
* is sized to one cell and translated by `index` cells, so the highlight lerps
|
|
8
|
+
* across the list however many options there are — no per-count CSS.
|
|
9
|
+
*
|
|
10
|
+
* The geometry is driven entirely by two custom properties set inline on the
|
|
11
|
+
* root (`--seg-count`, `--seg-index`); the CSS below is option-count agnostic.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface SegmentedOption<T extends string> {
|
|
15
|
+
id: T
|
|
16
|
+
label: ReactNode
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SegmentedToggleProps<T extends string> {
|
|
20
|
+
/** The options, in display order. The thumb slides across them by index. */
|
|
21
|
+
options: SegmentedOption<T>[]
|
|
22
|
+
/** The currently selected option id. */
|
|
23
|
+
value: T
|
|
24
|
+
/** Called with the chosen option id when a segment is activated. */
|
|
25
|
+
onChange: (id: T) => void
|
|
26
|
+
/** Accessible name for the tablist (e.g. "View mode"). */
|
|
27
|
+
label: string
|
|
28
|
+
/** Optional className applied to the root, for layout-context placement. */
|
|
29
|
+
className?: string
|
|
30
|
+
/** Optional per-segment `data-testid` factory. */
|
|
31
|
+
testId?: (id: T) => string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function SegmentedToggle<T extends string>({
|
|
35
|
+
options,
|
|
36
|
+
value,
|
|
37
|
+
onChange,
|
|
38
|
+
label,
|
|
39
|
+
className,
|
|
40
|
+
testId,
|
|
41
|
+
}: SegmentedToggleProps<T>) {
|
|
42
|
+
// Clamp to 0 so an unknown `value` parks the thumb on the first cell rather
|
|
43
|
+
// than translating it off the track by a negative index.
|
|
44
|
+
const activeIndex = Math.max(
|
|
45
|
+
0,
|
|
46
|
+
options.findIndex((o) => o.id === value),
|
|
47
|
+
)
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
className={className ? `dcui-segmented ${className}` : 'dcui-segmented'}
|
|
51
|
+
role="tablist"
|
|
52
|
+
aria-label={label}
|
|
53
|
+
style={
|
|
54
|
+
{
|
|
55
|
+
'--seg-count': options.length,
|
|
56
|
+
'--seg-index': activeIndex,
|
|
57
|
+
} as CSSProperties
|
|
58
|
+
}>
|
|
59
|
+
<span className="dcui-segmented-thumb" aria-hidden="true" />
|
|
60
|
+
{options.map((opt) => (
|
|
61
|
+
<button
|
|
62
|
+
key={opt.id}
|
|
63
|
+
type="button"
|
|
64
|
+
role="tab"
|
|
65
|
+
aria-selected={value === opt.id}
|
|
66
|
+
className="dcui-segmented-seg"
|
|
67
|
+
data-testid={testId?.(opt.id)}
|
|
68
|
+
data-active={value === opt.id ? 'true' : undefined}
|
|
69
|
+
onClick={() => onChange(opt.id)}>
|
|
70
|
+
{opt.label}
|
|
71
|
+
</button>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { defineCases, tweak } from '@awarebydefault/display-case'
|
|
2
|
+
import { Eyebrow } from './Eyebrow'
|
|
3
|
+
import { NavItem } from './NavItem'
|
|
4
|
+
import { Sidebar } from './Sidebar'
|
|
5
|
+
|
|
6
|
+
export default defineCases(
|
|
7
|
+
'Sidebar',
|
|
8
|
+
{
|
|
9
|
+
Playground: {
|
|
10
|
+
tweaks: {
|
|
11
|
+
label: tweak.text('Components'),
|
|
12
|
+
width: tweak.number(15),
|
|
13
|
+
maxHeight: tweak.number(24),
|
|
14
|
+
showEyebrow: tweak.boolean(true),
|
|
15
|
+
},
|
|
16
|
+
render: (t) => (
|
|
17
|
+
<Sidebar
|
|
18
|
+
label={t.label}
|
|
19
|
+
style={{ width: `${t.width}rem`, maxHeight: `${t.maxHeight}rem` }}>
|
|
20
|
+
{t.showEyebrow && (
|
|
21
|
+
<Eyebrow style={{ margin: '0 0 0.5rem 0.5rem' }}>Atoms</Eyebrow>
|
|
22
|
+
)}
|
|
23
|
+
<NavItem
|
|
24
|
+
kind="component"
|
|
25
|
+
label="Button"
|
|
26
|
+
count={4}
|
|
27
|
+
expanded
|
|
28
|
+
onToggle={() => {}}
|
|
29
|
+
onSelect={() => {}}
|
|
30
|
+
/>
|
|
31
|
+
<NavItem kind="case" label="Playground" onSelect={() => {}} />
|
|
32
|
+
<NavItem kind="case" label="Variants" current onSelect={() => {}} />
|
|
33
|
+
<NavItem kind="case" label="Sizes" onSelect={() => {}} />
|
|
34
|
+
<NavItem
|
|
35
|
+
kind="component"
|
|
36
|
+
label="Checkbox"
|
|
37
|
+
onToggle={() => {}}
|
|
38
|
+
onSelect={() => {}}
|
|
39
|
+
/>
|
|
40
|
+
</Sidebar>
|
|
41
|
+
),
|
|
42
|
+
},
|
|
43
|
+
Tree: () => (
|
|
44
|
+
<Sidebar style={{ width: '15rem', maxHeight: '24rem' }}>
|
|
45
|
+
<Eyebrow style={{ margin: '0 0 0.5rem 0.5rem' }}>Atoms</Eyebrow>
|
|
46
|
+
<NavItem
|
|
47
|
+
kind="component"
|
|
48
|
+
label="Button"
|
|
49
|
+
count={4}
|
|
50
|
+
expanded
|
|
51
|
+
onToggle={() => {}}
|
|
52
|
+
onSelect={() => {}}
|
|
53
|
+
/>
|
|
54
|
+
<NavItem kind="case" label="Playground" onSelect={() => {}} />
|
|
55
|
+
<NavItem kind="case" label="Variants" current onSelect={() => {}} />
|
|
56
|
+
<NavItem kind="case" label="Sizes" onSelect={() => {}} />
|
|
57
|
+
<NavItem
|
|
58
|
+
kind="component"
|
|
59
|
+
label="Checkbox"
|
|
60
|
+
onToggle={() => {}}
|
|
61
|
+
onSelect={() => {}}
|
|
62
|
+
/>
|
|
63
|
+
</Sidebar>
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
{ level: 'organism' },
|
|
67
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
**Sidebar** — the nav rail: a scrolling, hairline-bordered column on the subtle backdrop that holds the component tree; reach for it as the ground `NavItem` rows sit on (rows are transparent and only read correctly against this surface).
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
<Sidebar label="Components">
|
|
5
|
+
<NavItem kind="component" label="Button" count={4} expanded onToggle={toggle} />
|
|
6
|
+
<NavItem kind="case" label="Variants" current onSelect={select} />
|
|
7
|
+
</Sidebar>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Layout (grid placement, collapse) stays the chrome's; the surface is the component's.
|
|
11
|
+
|
|
12
|
+
- **label**: accessible name for the `<nav>` landmark; pass the contextual name (default `"Navigation"` is a generic fallback)
|
|
13
|
+
- **children**: `NavItem` rows (typically grouped by hierarchy level)
|
|
14
|
+
- spreads remaining props onto the `<nav>`
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { Sidebar } from './Sidebar'
|
|
4
|
+
|
|
5
|
+
describe('Sidebar', () => {
|
|
6
|
+
test('renders a nav landmark with a generic default label', () => {
|
|
7
|
+
const html = renderToStaticMarkup(
|
|
8
|
+
<Sidebar>
|
|
9
|
+
<span>rows</span>
|
|
10
|
+
</Sidebar>,
|
|
11
|
+
)
|
|
12
|
+
expect(html).toContain('<nav')
|
|
13
|
+
expect(html).toContain('class="dcui-sidebar"')
|
|
14
|
+
expect(html).toContain('aria-label="Navigation"')
|
|
15
|
+
expect(html).toContain('rows')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('uses a contextual landmark label when given', () => {
|
|
19
|
+
const html = renderToStaticMarkup(<Sidebar label="Components">x</Sidebar>)
|
|
20
|
+
expect(html).toContain('aria-label="Components"')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('forwards arbitrary attributes to the nav element', () => {
|
|
24
|
+
const html = renderToStaticMarkup(
|
|
25
|
+
<Sidebar id="rail" data-testid="sidebar">
|
|
26
|
+
x
|
|
27
|
+
</Sidebar>,
|
|
28
|
+
)
|
|
29
|
+
expect(html).toContain('id="rail"')
|
|
30
|
+
expect(html).toContain('data-testid="sidebar"')
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Display Case — Sidebar
|
|
5
|
+
* The nav rail: a scrolling, hairline-bordered column on the subtle backdrop
|
|
6
|
+
* that holds the component tree. It's the ground NavItem rows are designed to
|
|
7
|
+
* sit on — the rows are transparent and only read correctly against this
|
|
8
|
+
* `--dc-bg-subtle` surface. Layout (grid placement, collapse) is the chrome's;
|
|
9
|
+
* the surface is the component's.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface SidebarProps extends HTMLAttributes<HTMLElement> {
|
|
13
|
+
/** Accessible name for the nav landmark; consumers should pass the contextual
|
|
14
|
+
* name (the chrome uses "Components"). Defaults to a generic fallback so the
|
|
15
|
+
* landmark always has an accessible name. */
|
|
16
|
+
label?: string
|
|
17
|
+
children?: ReactNode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Sidebar({
|
|
21
|
+
label = 'Navigation',
|
|
22
|
+
children,
|
|
23
|
+
...rest
|
|
24
|
+
}: SidebarProps) {
|
|
25
|
+
return (
|
|
26
|
+
<nav className="dcui-sidebar" aria-label={label} {...rest}>
|
|
27
|
+
{children}
|
|
28
|
+
</nav>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { defineCases, tweak } from '@awarebydefault/display-case'
|
|
2
|
+
import { Button } from '../controls/Button'
|
|
3
|
+
import { Stage } from './Stage'
|
|
4
|
+
|
|
5
|
+
const box = { width: '24rem', height: '12rem' }
|
|
6
|
+
|
|
7
|
+
export default defineCases(
|
|
8
|
+
'Stage',
|
|
9
|
+
{
|
|
10
|
+
Playground: {
|
|
11
|
+
tweaks: {
|
|
12
|
+
caption: tweak.text('button / playground'),
|
|
13
|
+
meta: tweak.text('390 × 844'),
|
|
14
|
+
grid: tweak.boolean(true),
|
|
15
|
+
corners: tweak.boolean(false),
|
|
16
|
+
},
|
|
17
|
+
render: (t) => (
|
|
18
|
+
<Stage
|
|
19
|
+
caption={t.caption || undefined}
|
|
20
|
+
meta={t.meta || undefined}
|
|
21
|
+
frame="fill"
|
|
22
|
+
grid={t.grid}
|
|
23
|
+
corners={t.corners}
|
|
24
|
+
style={box}>
|
|
25
|
+
<Button>On the stage</Button>
|
|
26
|
+
</Stage>
|
|
27
|
+
),
|
|
28
|
+
},
|
|
29
|
+
Default: () => (
|
|
30
|
+
<Stage frame="fill" style={box}>
|
|
31
|
+
<Button>On the stage</Button>
|
|
32
|
+
</Stage>
|
|
33
|
+
),
|
|
34
|
+
Grid: () => (
|
|
35
|
+
<Stage frame="fill" grid style={box}>
|
|
36
|
+
<Button variant="accent">On the grid</Button>
|
|
37
|
+
</Stage>
|
|
38
|
+
),
|
|
39
|
+
Captioned: () => (
|
|
40
|
+
<Stage
|
|
41
|
+
caption="button / playground"
|
|
42
|
+
meta="390 × 844"
|
|
43
|
+
frame="fill"
|
|
44
|
+
grid
|
|
45
|
+
style={box}>
|
|
46
|
+
<Button>Captioned</Button>
|
|
47
|
+
</Stage>
|
|
48
|
+
),
|
|
49
|
+
},
|
|
50
|
+
{ level: 'molecule' },
|
|
51
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
.dcui-stage {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
border: 1px solid var(--dc-border);
|
|
6
|
+
border-radius: var(--dc-radius-md);
|
|
7
|
+
background: var(--dc-surface);
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
}
|
|
10
|
+
/* Live-frame sizing (the browse chrome's preview). */
|
|
11
|
+
.dcui-stage[data-frame="hug"] {
|
|
12
|
+
flex: 0 0 auto;
|
|
13
|
+
min-width: min(22rem, 100%);
|
|
14
|
+
min-height: min(11rem, 100%);
|
|
15
|
+
max-width: 100%;
|
|
16
|
+
max-height: 100%;
|
|
17
|
+
}
|
|
18
|
+
.dcui-stage[data-frame="fill"] {
|
|
19
|
+
align-self: stretch;
|
|
20
|
+
width: 100%;
|
|
21
|
+
}
|
|
22
|
+
.dcui-stage-caption {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: space-between;
|
|
26
|
+
gap: var(--dc-space-4);
|
|
27
|
+
padding: var(--dc-space-4) var(--dc-space-6);
|
|
28
|
+
border-bottom: 1px solid var(--dc-border);
|
|
29
|
+
background: var(--dc-bg-subtle);
|
|
30
|
+
}
|
|
31
|
+
.dcui-stage-caption-label {
|
|
32
|
+
font-family: var(--dc-font-mono);
|
|
33
|
+
font-size: var(--dc-text-xs);
|
|
34
|
+
font-weight: var(--dc-weight-medium);
|
|
35
|
+
letter-spacing: var(--dc-tracking-label);
|
|
36
|
+
text-transform: uppercase;
|
|
37
|
+
color: var(--dc-fg-muted);
|
|
38
|
+
}
|
|
39
|
+
.dcui-stage-caption-meta {
|
|
40
|
+
font-family: var(--dc-font-mono);
|
|
41
|
+
font-size: var(--dc-text-xs);
|
|
42
|
+
color: var(--dc-fg-subtle);
|
|
43
|
+
}
|
|
44
|
+
.dcui-stage-body {
|
|
45
|
+
position: relative;
|
|
46
|
+
flex: 1;
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
min-height: 0;
|
|
51
|
+
padding: 0;
|
|
52
|
+
}
|
|
53
|
+
/* hug-frame sets the body padding inline (the dynamic grid margin); a filled
|
|
54
|
+
frame stays edge-to-edge. */
|
|
55
|
+
.dcui-stage[data-grid="true"] .dcui-stage-body {
|
|
56
|
+
background-image: radial-gradient(var(--dc-border) 1px, transparent 1px);
|
|
57
|
+
background-size: 16px 16px;
|
|
58
|
+
background-position: -1px -1px;
|
|
59
|
+
}
|
|
60
|
+
.dcui-stage-corner {
|
|
61
|
+
position: absolute;
|
|
62
|
+
width: 9px;
|
|
63
|
+
height: 9px;
|
|
64
|
+
border: 1.5px solid var(--dc-border-strong);
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
z-index: 1;
|
|
67
|
+
}
|
|
68
|
+
.dcui-stage-corner[data-c="tl"] {
|
|
69
|
+
top: 8px;
|
|
70
|
+
left: 8px;
|
|
71
|
+
border-right: 0;
|
|
72
|
+
border-bottom: 0;
|
|
73
|
+
}
|
|
74
|
+
.dcui-stage-corner[data-c="tr"] {
|
|
75
|
+
top: 8px;
|
|
76
|
+
right: 8px;
|
|
77
|
+
border-left: 0;
|
|
78
|
+
border-bottom: 0;
|
|
79
|
+
}
|
|
80
|
+
.dcui-stage-corner[data-c="bl"] {
|
|
81
|
+
bottom: 8px;
|
|
82
|
+
left: 8px;
|
|
83
|
+
border-right: 0;
|
|
84
|
+
border-top: 0;
|
|
85
|
+
}
|
|
86
|
+
.dcui-stage-corner[data-c="br"] {
|
|
87
|
+
bottom: 8px;
|
|
88
|
+
right: 8px;
|
|
89
|
+
border-left: 0;
|
|
90
|
+
border-top: 0;
|
|
91
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
**Stage** — the vitrine: the framed surface a component is exhibited on (hairline border, soft corner ticks, optional dotted grid, optional mono caption); reach for it to present one component. Keep it quiet — the exhibit leads.
|
|
2
|
+
|
|
3
|
+
The browse chrome's preview stage, sized by `frame` rather than centering a card. `frame="hug"` shrinks to the exhibit with a minimum size and a dynamic grid margin (`padX`/`padY`, in px); `frame="fill"` stretches edge-to-edge for full pages.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
<Stage frame="hug" padX={48} padY={32} surface="var(--color-bg)">
|
|
7
|
+
<App />
|
|
8
|
+
</Stage>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- **frame** (required): `hug` (shrink to the exhibit) · `fill` (stretch edge-to-edge)
|
|
12
|
+
- **caption** / **meta**: mono caption strip (label left, meta right)
|
|
13
|
+
- **grid**: dotted graph-paper backdrop · **corners**: corner ticks (default on)
|
|
14
|
+
- **padX** / **padY**: dynamic grid-margin padding (px) for `frame="hug"`
|
|
15
|
+
- **surface**: override the body backdrop colour
|