@repobit/dex-system-design 0.25.12 → 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/CHANGELOG.md +204 -0
- package/README.md +131 -8
- package/package.json +96 -13
- package/schemas/component-manifest.json +3294 -405
- package/schemas/manifest-omissions.json +173 -0
- package/src/blog/src/breakpoints.js +6 -6
- package/src/blog/src/content/blog-atoms-showcase.js +1 -1
- package/src/blog/src/content/blog-page-layout.js +2 -2
- package/src/blog/src/lib/article-toc.js +7 -7
- package/src/blog/src/shell/blog-shell.js +1 -1
- package/src/blog/src/tokens/blog.js +2 -2
- package/src/blog/src/tokens/core.js +34 -35
- package/src/blog/src/tokens/legacy.js +0 -9
- package/src/blog/tokens/blog.js +2 -2
- package/src/blog/tokens/core.js +34 -35
- package/src/blog/tokens/legacy.js +0 -9
- package/src/blog/tokens.css +36 -46
- package/src/components/accordion/accordion-bg.css.js +83 -59
- package/src/components/accordion/accordion-bg.js +29 -10
- package/src/components/accordion/accordion-bg.stories.js +67 -67
- package/src/components/accordion/accordion-keyboard.js +80 -0
- package/src/components/accordion/accordion.css.js +142 -80
- package/src/components/accordion/accordion.js +37 -10
- package/src/components/accordion/accordion.stories.js +7 -7
- package/src/components/agent-endpoints/agent-endpoints.css.js +36 -39
- package/src/components/agent-endpoints/agent-endpoints.js +30 -20
- package/src/components/agent-endpoints/agent-endpoints.stories.js +1 -1
- package/src/components/ai-guardian-demo.stories.js +91 -91
- package/src/components/alert/alert.css.js +198 -0
- package/src/components/alert/alert.js +103 -0
- package/src/components/alert/alert.stories.js +206 -0
- package/src/components/anchor/anchor-nav.css.js +213 -101
- package/src/components/anchor/anchor-nav.js +26 -40
- package/src/components/anchor/anchor.stories.js +22 -12
- package/src/components/annotation/annotation.css.js +58 -0
- package/src/components/annotation/annotation.js +67 -0
- package/src/components/annotation/annotation.stories.js +63 -0
- package/src/components/architecture.stories.js +285 -0
- package/src/components/atoms.stories.js +342 -0
- package/src/components/avatar/avatar.css.js +67 -0
- package/src/components/avatar/avatar.js +42 -0
- package/src/components/avatar/avatar.stories.js +95 -0
- package/src/components/awards/awards-flex.css.js +209 -23
- package/src/components/awards/awards-flex.js +66 -7
- package/src/components/awards/awards-flex.stories.js +1 -1
- package/src/components/awards/{awards-light.stories.js → awards-summary.stories.js} +54 -64
- package/src/components/awards/awards.css.js +116 -58
- package/src/components/awards/awards.js +102 -28
- package/src/components/awards/awards.stories.js +1 -1
- package/src/components/back/back.css.js +51 -22
- package/src/components/back/back.js +28 -7
- package/src/components/back/back.stories.js +9 -3
- package/src/components/badge/badge.css.js +158 -46
- package/src/components/badge/badge.js +62 -74
- package/src/components/badge/badge.stories.js +176 -248
- package/src/components/bento-grid/bento-grid.css.js +21 -16
- package/src/components/bento-grid/bento-grid.js +16 -4
- package/src/components/bento-grid/bento-grid.stories.js +19 -18
- package/src/components/bento-grid/bento-item.css.js +52 -28
- package/src/components/bento-grid/bento-item.js +2 -2
- package/src/components/blockquote/blockquote.css.js +47 -0
- package/src/components/blockquote/blockquote.js +29 -0
- package/src/components/blockquote/blockquote.stories.js +62 -0
- package/src/components/breadcrumb/breadcrumb.css.js +92 -22
- package/src/components/breadcrumb/breadcrumb.js +56 -11
- package/src/components/button/button.css.js +531 -161
- package/src/components/button/button.js +206 -214
- package/src/components/button/button.stories.js +322 -174
- package/src/components/caption/caption.css.js +21 -0
- package/src/components/caption/caption.js +31 -0
- package/src/components/caption/caption.stories.js +62 -0
- package/src/components/card-features/card-features.css.js +14 -22
- package/src/components/card-features/card-features.js +68 -9
- package/src/components/card-features/card-features.stories.js +11 -11
- package/src/components/cards/card.css.js +60 -81
- package/src/components/cards/card.js +56 -25
- package/src/components/cards/card.stories.js +39 -39
- package/src/components/carousel/carousel.css.js +93 -99
- package/src/components/carousel/carousel.js +32 -4
- package/src/components/checkbox/checkbox.css.js +111 -93
- package/src/components/checkbox/checkbox.js +64 -90
- package/src/components/checkbox/checkbox.stories.js +96 -29
- package/src/components/code/code.css.js +70 -0
- package/src/components/code/code.js +40 -0
- package/src/components/code/code.stories.js +73 -0
- package/src/components/compare/compare.css.js +179 -43
- package/src/components/compare/compare.js +52 -25
- package/src/components/compare/compare.stories.js +61 -4
- package/src/components/comparison-panel/comparison-panel.css.js +38 -51
- package/src/components/comparison-panel/comparison-panel.js +5 -4
- package/src/components/comparison-panel/comparison-panel.stories.js +1 -1
- package/src/components/compatibility-table/compatibility-table.css.js +180 -80
- package/src/components/compatibility-table/compatibility-table.js +37 -9
- package/src/components/control-alignment.stories.js +393 -0
- package/src/components/cta-banner/cta-banner.css.js +41 -13
- package/src/components/cta-banner/cta-banner.js +6 -5
- package/src/components/cta-banner/cta-banner.stories.js +43 -98
- package/src/components/display/display.css.js +25 -1
- package/src/components/display/display.js +46 -13
- package/src/components/display/display.stories.js +270 -40
- package/src/components/divider/divider-horizontal.js +21 -5
- package/src/components/divider/divider-vertical.js +15 -7
- package/src/components/divider/divider.css.js +2 -2
- package/src/components/divider/divider.stories.js +38 -34
- package/src/components/dropdown/dropdown.css.js +93 -126
- package/src/components/dropdown/dropdown.js +114 -34
- package/src/components/dropdown/dropdown.stories.js +56 -24
- package/src/components/features/features.css.js +24 -11
- package/src/components/features/features.js +7 -3
- package/src/components/features/features.stories.js +37 -37
- package/src/components/features-tab/features-tab.css.js +19 -8
- package/src/components/features-tab/features-tab.js +6 -5
- package/src/components/features-tab/features-tab.stories.js +23 -23
- package/src/components/fieldset/fieldset.css.js +30 -0
- package/src/components/fieldset/fieldset.js +28 -0
- package/src/components/fieldset/fieldset.stories.js +84 -0
- package/src/components/footer/footer-links-group.css.js +10 -14
- package/src/components/footer/footer-links-group.js +4 -3
- package/src/components/footer/footer-lp.css.js +59 -30
- package/src/components/footer/footer-lp.js +17 -12
- package/src/components/footer/footer-lp.stories.js +63 -75
- package/src/components/footer/footer-nav-menu.css.js +26 -4
- package/src/components/footer/footer-nav-menu.js +3 -2
- package/src/components/footer/footer.css.js +146 -264
- package/src/components/footer/footer.js +176 -106
- package/src/components/footer/footer.stories.js +9 -9
- package/src/components/footer/localeMap.js +1 -1
- package/src/components/grid/grid.css.js +1 -1
- package/src/components/grid/grid.js +49 -7
- package/src/components/grid/grid.stories.js +111 -0
- package/src/components/header/header-code.css.js +61 -51
- package/src/components/header/header-code.js +28 -14
- package/src/components/header/header-code.stories.js +5 -5
- package/src/components/header/header.css.js +655 -176
- package/src/components/header/header.js +269 -36
- package/src/components/header/header.stories.js +841 -18
- package/src/components/heading/heading.css.js +14 -17
- package/src/components/heading/heading.js +48 -33
- package/src/components/heading/heading.stories.js +27 -25
- package/src/components/help-text/help-text.css.js +21 -0
- package/src/components/help-text/help-text.js +25 -0
- package/src/components/help-text/help-text.stories.js +66 -0
- package/src/components/highlight/highlight.css.js +86 -64
- package/src/components/highlight/highlight.js +18 -27
- package/src/components/highlight/highlight.stories.js +14 -3
- package/src/components/icons/analysis-icon.js +6 -16
- package/src/components/icons/arrow-down-icon.js +6 -19
- package/src/components/icons/arrow-icon.js +44 -0
- package/src/components/icons/arrow-right-icon.js +30 -23
- package/src/components/icons/arrow-up-icon.js +19 -18
- package/src/components/icons/bd-ai-icon.js +1 -1
- package/src/components/icons/brand-marks.stories.js +279 -0
- package/src/components/icons/brand-signature-icon.js +42 -0
- package/src/components/icons/check-icon.js +27 -0
- package/src/components/icons/circle-info-icon.js +33 -0
- package/src/components/icons/close-icon.js +6 -18
- package/src/components/icons/family-icon.js +8 -20
- package/src/components/icons/globe-icon.js +6 -18
- package/src/components/icons/guide-icon.js +6 -18
- package/src/components/icons/icon-element.js +99 -0
- package/src/components/icons/icons.js +36 -0
- package/src/components/icons/icons.stories.js +184 -52
- package/src/components/icons/individual-icon.js +6 -18
- package/src/components/icons/laurel-icon.js +9 -19
- package/src/components/icons/machine-learning-icon.js +6 -17
- package/src/components/icons/marks.js +41 -0
- package/src/components/icons/minus-icon.js +7 -21
- package/src/components/icons/missing-icon.js +52 -0
- package/src/components/icons/plus-icon.js +7 -21
- package/src/components/icons/quote-icon.js +19 -19
- package/src/components/image/image.css.js +72 -25
- package/src/components/image/image.js +11 -6
- package/src/components/image/image.stories.js +2 -2
- package/src/components/input/custom-form.stories.js +214 -62
- package/src/components/input/input-clipboard.css.js +51 -144
- package/src/components/input/input-clipboard.js +31 -15
- package/src/components/input/input.css.js +8 -146
- package/src/components/input/input.js +93 -38
- package/src/components/install-options/install-options.css.js +84 -77
- package/src/components/install-options/install-options.js +33 -30
- package/src/components/install-options/install-options.stories.js +3 -3
- package/src/components/label/label.css.js +35 -0
- package/src/components/label/label.js +38 -0
- package/src/components/label/label.stories.js +82 -0
- package/src/components/light-carousel/light-carousel.css.js +111 -63
- package/src/components/light-carousel/light-carousel.js +85 -11
- package/src/components/light-carousel/light-carousel.stories.js +126 -82
- package/src/components/link/link.css.js +150 -99
- package/src/components/link/link.js +86 -11
- package/src/components/link/link.stories.js +124 -30
- package/src/components/list/list-item/list-item.css.js +74 -22
- package/src/components/list/list-item/list-item.js +32 -10
- package/src/components/list/list-item/list-item.stories.js +23 -23
- package/src/components/list/list.css.js +8 -2
- package/src/components/list/list.js +3 -2
- package/src/components/list/list.stories.js +222 -0
- package/src/components/modal/modal.css.js +114 -24
- package/src/components/modal/modal.js +83 -7
- package/src/components/modal/modal.stories.js +9 -9
- package/src/components/overview/overview.css.js +19 -14
- package/src/components/overview/overview.js +6 -5
- package/src/components/overview/overview.stories.js +10 -10
- package/src/components/page-switcher/page-switcher.css.js +97 -40
- package/src/components/page-switcher/page-switcher.js +28 -5
- package/src/components/page-switcher/page-switcher.stories.js +95 -0
- package/src/components/paragraph/paragraph.css.js +15 -10
- package/src/components/paragraph/paragraph.js +41 -44
- package/src/components/paragraph/paragraph.stories.js +56 -21
- package/src/components/picture/picture.css.js +103 -44
- package/src/components/picture/picture.js +100 -30
- package/src/components/picture/picture.stories.js +1 -1
- package/src/components/pricing-cards/new-pricing-card.js +3 -3
- package/src/components/pricing-cards/new-pricing.css.js +24 -15
- package/src/components/pricing-cards/pricing-card-actions.css.js +1 -1
- package/src/components/pricing-cards/pricing-card-actions.js +3 -4
- package/src/components/pricing-cards/pricing-card-container.css.js +3 -3
- package/src/components/pricing-cards/pricing-card-container.js +3 -2
- package/src/components/pricing-cards/pricing-card-header.css.js +10 -8
- package/src/components/pricing-cards/pricing-card-header.js +6 -7
- package/src/components/pricing-cards/pricing-card-pricing.css.js +16 -9
- package/src/components/pricing-cards/pricing-card-pricing.js +8 -6
- package/src/components/pricing-cards/pricing-card-show-more.css.js +11 -3
- package/src/components/pricing-cards/pricing-card-show-more.js +8 -3
- package/src/components/pricing-cards/pricing-card.css.js +244 -190
- package/src/components/pricing-cards/pricing-card.js +54 -70
- package/src/components/pricing-cards/pricing-feature-item.css.js +25 -8
- package/src/components/pricing-cards/pricing-feature-item.js +8 -3
- package/src/components/process-steps/process-steps.css.js +26 -14
- package/src/components/process-steps/process-steps.js +23 -6
- package/src/components/process-steps/process-steps.stories.js +1 -1
- package/src/components/progress/progress.css.js +80 -0
- package/src/components/progress/progress.js +42 -0
- package/src/components/progress/progress.stories.js +79 -0
- package/src/components/quick-answer/quick-answer.css.js +75 -13
- package/src/components/quick-answer/quick-answer.js +8 -3
- package/src/components/radio/radio.css.js +92 -128
- package/src/components/radio/radio.js +106 -127
- package/src/components/radio/radio.stories.js +54 -19
- package/src/components/review/review-grid.css.js +5 -6
- package/src/components/review/review-grid.js +14 -11
- package/src/components/review/review.css.js +11 -6
- package/src/components/review/review.js +3 -8
- package/src/components/shared/ensemble.css.js +93 -0
- package/src/components/shared/form-field.css.js +206 -0
- package/src/components/shared/prop-vocabulary.js +48 -0
- package/src/components/shared/token-scales.js +306 -0
- package/src/components/skeleton/skeleton.css.js +34 -0
- package/src/components/skeleton/skeleton.js +32 -0
- package/src/components/skeleton/skeleton.stories.js +70 -0
- package/src/components/spinner/spinner.css.js +62 -0
- package/src/components/spinner/spinner.js +38 -0
- package/src/components/spinner/spinner.stories.js +92 -0
- package/src/components/spotlight-section/spotlight-section.css.js +14 -13
- package/src/components/spotlight-section/spotlight-section.js +42 -39
- package/src/components/switch/switch.css.js +103 -0
- package/src/components/switch/switch.js +44 -0
- package/src/components/switch/switch.stories.js +86 -0
- package/src/components/tab-list/content-switcher.stories.js +189 -0
- package/src/components/tab-list/tab-list.css.js +250 -0
- package/src/components/tab-list/tab-list.js +184 -0
- package/src/components/tab-list/tab-list.stories.js +221 -0
- package/src/components/tabs/tabs.css.js +14 -123
- package/src/components/tabs/tabs.js +16 -19
- package/src/components/tabs/tabs.stories.js +95 -95
- package/src/components/tag/tag.css.js +54 -0
- package/src/components/tag/tag.js +32 -0
- package/src/components/tag/tag.stories.js +126 -0
- package/src/components/terminal/terminal-message.css.js +164 -60
- package/src/components/terminal/terminal-message.js +5 -5
- package/src/components/terminal/terminal-message.stories.js +70 -1
- package/src/components/termsOfUse/terms.css.js +25 -20
- package/src/components/termsOfUse/terms.js +13 -9
- package/src/components/termsOfUse/terms.stories.js +1 -1
- package/src/components/textarea/textarea.css.js +51 -0
- package/src/components/textarea/textarea.js +136 -0
- package/src/components/textarea/textarea.stories.js +191 -0
- package/src/components/tooltip/tooltip.css.js +75 -0
- package/src/components/tooltip/tooltip.js +156 -0
- package/src/components/tooltip/tooltip.stories.js +161 -0
- package/src/components/trust-panel/trust-panel.css.js +85 -33
- package/src/components/trust-panel/trust-panel.js +18 -10
- package/src/components/trust-panel/trust-panel.stories.js +1 -1
- package/src/components/ultimate-security-demo.stories.js +431 -0
- package/src/components/video/video.css.js +17 -0
- package/src/components/video/video.js +40 -0
- package/src/components/video/video.stories.js +106 -0
- package/src/components/video-player/video-player.css.js +67 -11
- package/src/components/video-player/video-player.js +35 -4
- package/src/components/video-player/video-player.stories.js +1 -1
- package/src/components/visual-alignment.stories.js +411 -0
- package/src/components/visually-hidden/visually-hidden.css.js +24 -0
- package/src/components/visually-hidden/visually-hidden.js +16 -0
- package/src/components/visually-hidden/visually-hidden.stories.js +56 -0
- package/src/stories/Button.stories.js +17 -17
- package/src/stories/Header.stories.js +10 -10
- package/src/stories/Page.js +1 -1
- package/src/stories/Page.stories.js +6 -6
- package/src/tokens/borders.stories.js +339 -0
- package/src/tokens/color-responsibilities.stories.js +227 -0
- package/src/tokens/dark-derivation.js +309 -0
- package/src/tokens/dark-findings.js +197 -0
- package/src/tokens/dark-mode.stories.js +207 -0
- package/src/tokens/dimension.stories.js +251 -0
- package/src/tokens/elevation.stories.js +143 -0
- package/src/tokens/focus.stories.js +276 -0
- package/src/tokens/fonts.css +20 -20
- package/src/tokens/fonts.stories.js +354 -374
- package/src/tokens/layout.css +25 -11
- package/src/tokens/motion.stories.js +231 -0
- package/src/tokens/shape.stories.js +272 -0
- package/src/tokens/spacing-rhythm.stories.js +359 -0
- package/src/tokens/spacing.stories.js +208 -703
- package/src/tokens/story-chrome.js +503 -0
- package/src/tokens/surfaces.stories.js +224 -0
- package/src/tokens/text-on-surface.stories.js +164 -0
- package/src/tokens/token-map.js +220 -0
- package/src/tokens/token-map.stories.js +257 -0
- package/src/tokens/token-purposes.js +266 -0
- package/src/tokens/tokens-grid.stories.js +388 -900
- package/src/tokens/tokens.css +1633 -1045
- package/src/tokens/tokens.js +1130 -492
- package/src/tokens/tokens.stories.js +137 -47
- package/src/tokens/type-ceiling-words.js +299 -0
- package/src/tokens/typography.stories.js +1003 -965
- package/src/tokens/vertical-rhythm.stories.js +224 -0
- package/src/components/awards/awards-icon.js +0 -56
- package/src/components/awards/awards-light.js +0 -240
- package/src/components/banner/banner.js +0 -60
- package/src/components/banner/banner.stories.js +0 -51
- package/src/components/button/icons.js +0 -24
- package/src/components/input/index.js +0 -0
- package/src/components/light-carousel/light-carousel-simple.css.js +0 -183
- package/src/components/light-carousel/light-carousel-simple.js +0 -74
- package/src/tokens/colors.js +0 -121
- package/src/tokens/typography.css.js +0 -207
- package/src/tokens/typography.js +0 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import { docsPage, refSection, resolve, toHex } from './story-chrome.js';
|
|
3
|
+
/* The role text lives in token-purposes.js, so the Figma variable
|
|
4
|
+
descriptions are generated from the same sentences this page shows. */
|
|
5
|
+
import { BORDERS, SURFACES } from './token-purposes.js';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Foundations/Surfaces',
|
|
9
|
+
tags : ['autodocs']
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const swatch = (token) => {
|
|
13
|
+
const value = resolve(token);
|
|
14
|
+
return html`<span
|
|
15
|
+
style="display:inline-block;width:var(--spacing-40);height:var(--spacing-24);
|
|
16
|
+
border-radius:var(--radius-sm);vertical-align:middle;
|
|
17
|
+
border:var(--border-width-1) solid var(--color-border-subtle);
|
|
18
|
+
background:${value}"
|
|
19
|
+
title="${toHex(value) || value}"
|
|
20
|
+
></span>`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const roleTable = (rows, kind, caption) => html`
|
|
24
|
+
<div class="table-scroll">
|
|
25
|
+
<table>
|
|
26
|
+
<caption>${caption}</caption>
|
|
27
|
+
<thead>
|
|
28
|
+
<tr>
|
|
29
|
+
<th scope="col">Role</th>
|
|
30
|
+
<th scope="col">Preview</th>
|
|
31
|
+
<th scope="col">Purpose</th>
|
|
32
|
+
<th scope="col">Use when</th>
|
|
33
|
+
<th scope="col">Do not use when</th>
|
|
34
|
+
${kind === 'surface'
|
|
35
|
+
? html`<th scope="col">Relationship</th><th scope="col">Theme expectation</th>`
|
|
36
|
+
: ''}
|
|
37
|
+
</tr>
|
|
38
|
+
</thead>
|
|
39
|
+
<tbody>
|
|
40
|
+
${rows.map(
|
|
41
|
+
(r) => html`
|
|
42
|
+
<tr>
|
|
43
|
+
<td><code>${r.token}</code></td>
|
|
44
|
+
<td>${swatch(r.token)}</td>
|
|
45
|
+
<td>${r.purpose}</td>
|
|
46
|
+
<td>${r.use}</td>
|
|
47
|
+
<td>${r.avoid}</td>
|
|
48
|
+
${kind === 'surface'
|
|
49
|
+
? html`<td>${r.relation}</td><td>${r.theme}</td>`
|
|
50
|
+
: ''}
|
|
51
|
+
</tr>`
|
|
52
|
+
)}
|
|
53
|
+
</tbody>
|
|
54
|
+
</table>
|
|
55
|
+
</div>
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
export const Surfaces = () =>
|
|
59
|
+
docsPage({
|
|
60
|
+
title: 'Surfaces',
|
|
61
|
+
lede : `A surface role says what a background is for. It does not say what colour it is
|
|
62
|
+
today, and choosing one by finding the nearest current value is how the wrong role
|
|
63
|
+
gets picked. Identify the job first; the role then decides the value, and the theme
|
|
64
|
+
decides it again.`,
|
|
65
|
+
note: html`<b>Same value is not same meaning.</b> Several roles resolve to the same
|
|
66
|
+
primitive right now — muted and disabled, for instance. They are different
|
|
67
|
+
questions and will not necessarily stay equal.`,
|
|
68
|
+
children: html`
|
|
69
|
+
${refSection({
|
|
70
|
+
name : 'Choosing a background',
|
|
71
|
+
meta : 'in order',
|
|
72
|
+
children: html`
|
|
73
|
+
<ol class="docs-list">
|
|
74
|
+
<li>Is it the page, section or component ground? <code>--color-surface-default</code></li>
|
|
75
|
+
<li>Is it a self-contained object — a card, tile or panel? Ask what it sits on first.
|
|
76
|
+
On the page ground, <code>--color-surface-container</code> and no edge. On a
|
|
77
|
+
tinted section, <code>--color-surface-default</code> with a
|
|
78
|
+
<code>--border-width-1 solid --color-border-container</code> edge, because no
|
|
79
|
+
tonal step is available against a tint. See below.</li>
|
|
80
|
+
<li>Is it a low-emphasis wash grouping several things? <code>--color-surface-subtle</code></li>
|
|
81
|
+
<li>Is it a state — pressed, inactive? <code>--color-surface-muted</code>. Held down, where muted is already the hover? <code>--color-surface-pressed</code>. Disabled? <code>--color-surface-disabled</code></li>
|
|
82
|
+
<li>Does content on it need inverse text? <code>--color-surface-inverse</code></li>
|
|
83
|
+
<li>Is it making a brand statement? <code>--color-surface-brand</code></li>
|
|
84
|
+
<li>Does it carry a message? The matching status surface.</li>
|
|
85
|
+
</ol>
|
|
86
|
+
<p>
|
|
87
|
+
If two answers seem to fit, the earlier one usually wins: the ladder runs from the
|
|
88
|
+
page outward, and most surfaces are nearer the page than authors expect.
|
|
89
|
+
</p>`
|
|
90
|
+
})}
|
|
91
|
+
|
|
92
|
+
${refSection({
|
|
93
|
+
name : 'Surface roles',
|
|
94
|
+
meta : `${SURFACES.length} roles`,
|
|
95
|
+
children: roleTable(SURFACES, 'surface', 'Surface roles, what each is for, and how it should behave under a theme')
|
|
96
|
+
})}
|
|
97
|
+
|
|
98
|
+
${refSection({
|
|
99
|
+
name : 'Default, container and subtle',
|
|
100
|
+
meta : 'the distinction authors get wrong',
|
|
101
|
+
children: html`
|
|
102
|
+
<p>
|
|
103
|
+
These three are the ones worth being precise about, because they are adjacent and
|
|
104
|
+
the difference is about role rather than appearance.
|
|
105
|
+
</p>
|
|
106
|
+
<ul class="docs-list">
|
|
107
|
+
<li><b>Default</b> is the ground. Things sit <i>on</i> it.</li>
|
|
108
|
+
<li><b>Container</b> is the ground of a thing that sits on the page. A card uses it so
|
|
109
|
+
that it reads as an object without needing a border or a shadow to say so — as
|
|
110
|
+
long as the page is what it sits on.</li>
|
|
111
|
+
<li><b>Subtle</b> is a wash <i>behind</i> a group. It recedes; it is not the ground of
|
|
112
|
+
any single object.</li>
|
|
113
|
+
</ul>
|
|
114
|
+
<p>
|
|
115
|
+
An author who can answer <q>is this an object, or the space behind several objects?</q>
|
|
116
|
+
never needs to look at the values.
|
|
117
|
+
</p>
|
|
118
|
+
<h3 style="font-size: var(--typography-fontSize-base); margin: var(--spacing-32) 0 var(--spacing-8)">
|
|
119
|
+
A card's ground and its edge
|
|
120
|
+
</h3>
|
|
121
|
+
<p>
|
|
122
|
+
Two answers, and which applies depends on what the card sits on. <b>On the page
|
|
123
|
+
ground</b> — <code>--color-surface-container</code>, and no edge: the tonal step is
|
|
124
|
+
the separation, which is what the role is for, and a border there is redundant with
|
|
125
|
+
the role's own purpose. <b>On a tinted section</b> —
|
|
126
|
+
<code>--color-surface-default</code> plus a <code>--border-width-1 solid
|
|
127
|
+
--color-border-container</code> edge, because there is no tonal step available
|
|
128
|
+
against a tint and the edge earns its keep.
|
|
129
|
+
</p>
|
|
130
|
+
<p>
|
|
131
|
+
This was read off the components rather than decided in advance, and it is checkable:
|
|
132
|
+
of the eleven card-shaped objects — the ones
|
|
133
|
+
<code>scripts/card-shapes.json</code> lists, which is now what makes the count
|
|
134
|
+
checkable — six sit on the page ground with no edge, four sit on a
|
|
135
|
+
tinted or inverted ground and draw one, and exactly one does neither. Bento and the
|
|
136
|
+
Carousel item were the two that disagreed, and they were changed rather than the rule.
|
|
137
|
+
The Awards feature card moved the other way when its section gained a blue-50
|
|
138
|
+
band: on the page ground its tonal step was the separation, and on a tint it
|
|
139
|
+
was not — 1.034:1, across a hue change rather than a lightness step — so it
|
|
140
|
+
took the second clause and now reads 1.08:1 with a 1.192:1 edge.
|
|
141
|
+
</p>
|
|
142
|
+
<p>
|
|
143
|
+
<b>The one exception is recorded.</b> The new-pricing card keeps
|
|
144
|
+
<code>--color-surface-container</code> and draws an edge on it, because it has no
|
|
145
|
+
wrapper of its own to carry a band — its container belongs to the page — so inverting
|
|
146
|
+
to white would put a white card on a white page. The edge is the half of the rule that
|
|
147
|
+
is safe on its own, and it triples the delta to 1.233:1. That is the shape an exception
|
|
148
|
+
should have: the rule named, the reason measured, the alternative said out loud.
|
|
149
|
+
</p>
|
|
150
|
+
<p>
|
|
151
|
+
<b>Two qualifications, both measured, and both found by the Pricing card.</b>
|
|
152
|
+
</p>
|
|
153
|
+
<p>
|
|
154
|
+
<b>The step is quieter than the sentence above implies.</b>
|
|
155
|
+
<code>--color-surface-default</code>, <code>-container</code> and <code>-subtle</code>
|
|
156
|
+
measure 1.000, 1.044 and 1.121 against the page — three named grounds inside a 12%
|
|
157
|
+
band. On the page ground the step is real but faint, so an edge there is redundant
|
|
158
|
+
rather than useless. Where a card must be <i>sharp</i> — adjacent cards in a
|
|
159
|
+
comparison set, say — the step alone will not do it, and the answer is the second
|
|
160
|
+
clause, a tinted band with edges, not a stronger border on the first.
|
|
161
|
+
</p>
|
|
162
|
+
<p>
|
|
163
|
+
<b>The second clause breaks on one tint.</b> <code>--color-border-container</code>
|
|
164
|
+
<i>is</i> <code>--color-neutral-100</code>, and so is
|
|
165
|
+
<code>--color-surface-muted</code>. On a muted band the rule's own edge is the same
|
|
166
|
+
colour as the band and disappears at 1.000:1. The card is then defined by its tonal
|
|
167
|
+
step alone, which on that band is the strongest of the three at 1.287:1 — so the
|
|
168
|
+
result is acceptable, but the edge is decoration. On a <code>-container</code>,
|
|
169
|
+
<code>-subtle</code> or status-tinted band the edge reads normally. Pick the band
|
|
170
|
+
before picking the edge.
|
|
171
|
+
</p>
|
|
172
|
+
<p>
|
|
173
|
+
<b>Revisit this when a card needs an edge for a reason the tonal step cannot
|
|
174
|
+
carry</b> — a selected or focused card, a card over an image, a card that must read
|
|
175
|
+
on both grounds. That is a new need rather than a violation, and the rule should grow
|
|
176
|
+
to name it rather than be worked around.
|
|
177
|
+
</p>
|
|
178
|
+
<p>
|
|
179
|
+
<b>Container means a visual container for content.</b> It has nothing to do with CSS
|
|
180
|
+
container queries or any layout API.
|
|
181
|
+
</p>`
|
|
182
|
+
})}
|
|
183
|
+
|
|
184
|
+
${refSection({
|
|
185
|
+
name : 'Borders, dividers and focus',
|
|
186
|
+
meta : 'three different jobs',
|
|
187
|
+
children: html`
|
|
188
|
+
<ul class="docs-list">
|
|
189
|
+
<li><b>A border</b> bounds an object. It says where a thing ends.</li>
|
|
190
|
+
<li><b>A divider</b> separates regions of content. Reach for
|
|
191
|
+
<code>bd-divider-horizontal</code> when the line is a separator between regions.
|
|
192
|
+
A layout border-top that participates in a grid row is a boundary, not a
|
|
193
|
+
separator, and native CSS is the right tool for it — do not convert one into a
|
|
194
|
+
divider for the sake of reuse.</li>
|
|
195
|
+
<li><b>A focus indicator</b> communicates keyboard focus. It has its own tokens and is
|
|
196
|
+
never a border role. Do not express focus by swapping a border colour.</li>
|
|
197
|
+
<li><b>A status border</b> communicates state and belongs with the status family.</li>
|
|
198
|
+
</ul>
|
|
199
|
+
${roleTable(BORDERS, 'border', 'Border roles and what each one bounds')}`
|
|
200
|
+
})}
|
|
201
|
+
|
|
202
|
+
${refSection({
|
|
203
|
+
name : 'Elevation',
|
|
204
|
+
meta : 'purpose, not appearance',
|
|
205
|
+
children: html`
|
|
206
|
+
<p>
|
|
207
|
+
Pick a shadow because of what it means, not because it looks close. The shadow scale
|
|
208
|
+
is a set of effect levels; the meanings below are what should drive the choice.
|
|
209
|
+
</p>
|
|
210
|
+
<ul class="docs-list">
|
|
211
|
+
<li><b>Resting elevation</b> — a surface raised at rest. Rare here: no audited card
|
|
212
|
+
uses one. A container surface is usually the better way to make an object read as
|
|
213
|
+
an object.</li>
|
|
214
|
+
<li><b>Interaction lift</b> — a shadow that appears on hover or drag. It belongs with a
|
|
215
|
+
transform and must be reducible under prefers-reduced-motion.</li>
|
|
216
|
+
<li><b>Overlay</b> — modals, popovers, dropdowns and tooltips, which sit above the page
|
|
217
|
+
and need to look like it.</li>
|
|
218
|
+
<li><b>Inset border effects</b> — an inset shadow used to draw a hairline without
|
|
219
|
+
affecting layout. This is a border technique, not elevation, and most of the
|
|
220
|
+
inset shadows in this package are exactly that.</li>
|
|
221
|
+
</ul>`
|
|
222
|
+
})}
|
|
223
|
+
`
|
|
224
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import { contrast, docsPage, levelBadge, refSection, resolve, toHex } from './story-chrome.js';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Foundations/Text on a surface',
|
|
6
|
+
tags : ['autodocs']
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/* The grounds text actually lands on in this package. Two families name some of
|
|
10
|
+
the same values -- the surface roles and the status roles -- which is exactly
|
|
11
|
+
why a pairing can look considered and still have been measured against
|
|
12
|
+
nothing. */
|
|
13
|
+
const GROUNDS = [
|
|
14
|
+
{ token: '--color-surface-default', note: 'the page ground' },
|
|
15
|
+
{ token: '--color-surface-container', note: 'a card or tile' },
|
|
16
|
+
{ token: '--color-surface-subtle', note: 'a wash behind a group' },
|
|
17
|
+
{ token: '--status-brand-surface', note: 'the brand tint, and the same value as surface-info' },
|
|
18
|
+
{ token: '--status-success-surface', note: 'the success tint' },
|
|
19
|
+
{ token: '--status-warning-surface', note: 'the warning tint' },
|
|
20
|
+
{ token: '--status-error-surface', note: 'the error tint' }
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/* Roles that are legitimately used for text somewhere in the package. */
|
|
24
|
+
const INKS = [
|
|
25
|
+
'--color-text-primary',
|
|
26
|
+
'--color-text-secondary',
|
|
27
|
+
'--color-text-muted',
|
|
28
|
+
'--color-text-subtle',
|
|
29
|
+
'--color-text-brand',
|
|
30
|
+
'--status-brand-text',
|
|
31
|
+
'--color-action-accent'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const cell = (ink, ground) => {
|
|
35
|
+
const ratio = contrast(resolve(ink), resolve(ground));
|
|
36
|
+
const rounded = Math.round(ratio * 100) / 100;
|
|
37
|
+
return html`
|
|
38
|
+
<td style="background:${resolve(ground)};color:${resolve(ink)};padding:var(--spacing-8)">
|
|
39
|
+
<span style="font-size:var(--typography-fontSize-xs)">Aa ${rounded}</span>
|
|
40
|
+
${levelBadge(ratio)}
|
|
41
|
+
</td>`;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const TextOnSurface = () =>
|
|
45
|
+
docsPage({
|
|
46
|
+
title: 'Text on a surface',
|
|
47
|
+
lede : `A text role is only legible against a particular ground, and the ground is not
|
|
48
|
+
always the one it was measured on. Every pairing below is measured live from
|
|
49
|
+
the tokens, so this page cannot drift from them.`,
|
|
50
|
+
note: html`<b>A pass on white is not a pass anywhere.</b>
|
|
51
|
+
<code>--color-text-subtle</code> reaches 4.5:1 on the page ground and falls
|
|
52
|
+
under it on every tint in this table. That is the same trap
|
|
53
|
+
<code>--color-blue-500</code> already carries a warning for, and it is how
|
|
54
|
+
three components ended up below AA with tokens that looked deliberate.`,
|
|
55
|
+
children: html`
|
|
56
|
+
${refSection({
|
|
57
|
+
name : 'Every role against every ground',
|
|
58
|
+
meta : `${INKS.length} roles x ${GROUNDS.length} grounds`,
|
|
59
|
+
children: html`
|
|
60
|
+
<p>
|
|
61
|
+
The badge is the level the pair reaches for <b>normal-size body text</b>.
|
|
62
|
+
<code>18pt</code> means it only clears the lower bar that large text and
|
|
63
|
+
non-text UI are held to — at body size it fails.
|
|
64
|
+
</p>
|
|
65
|
+
<div class="table-scroll">
|
|
66
|
+
<table>
|
|
67
|
+
<caption>Contrast of each text role against each surface it may sit on</caption>
|
|
68
|
+
<thead>
|
|
69
|
+
<tr>
|
|
70
|
+
<th scope="col">Text role</th>
|
|
71
|
+
${GROUNDS.map(g => html`<th scope="col"><code>${g.token.replace('--color-', '').replace('--status-', '')}</code></th>`)}
|
|
72
|
+
</tr>
|
|
73
|
+
</thead>
|
|
74
|
+
<tbody>
|
|
75
|
+
${INKS.map(ink => html`
|
|
76
|
+
<tr>
|
|
77
|
+
<td><code>${ink}</code></td>
|
|
78
|
+
${GROUNDS.map(g => cell(ink, g.token))}
|
|
79
|
+
</tr>`)}
|
|
80
|
+
</tbody>
|
|
81
|
+
</table>
|
|
82
|
+
</div>`
|
|
83
|
+
})}
|
|
84
|
+
|
|
85
|
+
${refSection({
|
|
86
|
+
name : 'The grounds',
|
|
87
|
+
meta : 'what each one is for',
|
|
88
|
+
children: html`
|
|
89
|
+
<div class="table-scroll">
|
|
90
|
+
<table>
|
|
91
|
+
<caption>Surface tokens used as a text ground, and their current values</caption>
|
|
92
|
+
<thead>
|
|
93
|
+
<tr><th scope="col">Token</th><th scope="col">Value</th><th scope="col">What it is</th></tr>
|
|
94
|
+
</thead>
|
|
95
|
+
<tbody>
|
|
96
|
+
${GROUNDS.map(g => html`
|
|
97
|
+
<tr>
|
|
98
|
+
<td><code>${g.token}</code></td>
|
|
99
|
+
<td class="num">${toHex(resolve(g.token))}</td>
|
|
100
|
+
<td>${g.note}</td>
|
|
101
|
+
</tr>`)}
|
|
102
|
+
</tbody>
|
|
103
|
+
</table>
|
|
104
|
+
</div>`
|
|
105
|
+
})}
|
|
106
|
+
|
|
107
|
+
${refSection({
|
|
108
|
+
name : 'Choosing',
|
|
109
|
+
meta : 'in order',
|
|
110
|
+
children: html`
|
|
111
|
+
<ol class="docs-list">
|
|
112
|
+
<li>Decide what the text <i>is</i> — body, supporting, a label, a link, a status
|
|
113
|
+
message. That picks the family.</li>
|
|
114
|
+
<li>Find the ground it will actually sit on, which is often a tint rather than
|
|
115
|
+
the page.</li>
|
|
116
|
+
<li>Read the pair off this table. If it does not reach
|
|
117
|
+
<code>AA</code> at the size you are using, take the next step up in the
|
|
118
|
+
same family rather than a colour from another one.</li>
|
|
119
|
+
</ol>
|
|
120
|
+
<p>
|
|
121
|
+
Supporting metadata that failed on a tint went from
|
|
122
|
+
<code>--color-text-subtle</code> to <code>--color-text-muted</code>, and a
|
|
123
|
+
brand-coloured eyebrow went from a lighter blue to
|
|
124
|
+
<code>--color-text-brand</code>. Both are one step inside the family that
|
|
125
|
+
already described the text; neither needed a new token.
|
|
126
|
+
</p>`
|
|
127
|
+
})}
|
|
128
|
+
${refSection({
|
|
129
|
+
name : 'The inverse side',
|
|
130
|
+
meta : 'two steps, not a mirror of the light hierarchy',
|
|
131
|
+
children: html`
|
|
132
|
+
<p>
|
|
133
|
+
The table above is the light side. On an inverse surface the same hierarchy does
|
|
134
|
+
not exist and cannot be borrowed: <code>--color-text-secondary</code>,
|
|
135
|
+
<code>-muted</code> and <code>-subtle</code> measure 1.91, 2.73 and 3.85 against
|
|
136
|
+
<code>--color-surface-inverse</code>, so every one of them is unusable there.
|
|
137
|
+
</p>
|
|
138
|
+
<p>
|
|
139
|
+
There are exactly two inverse text roles.
|
|
140
|
+
<code>--color-text-inverse</code> is high emphasis, and
|
|
141
|
+
<code>--color-text-muted-inverse</code> is everything subordinate to it. Two
|
|
142
|
+
components had built the second one separately -- one with a white alpha, one with
|
|
143
|
+
a neutral step -- before it was named.
|
|
144
|
+
</p>
|
|
145
|
+
<div style="background: var(--color-surface-inverse); padding: var(--spacing-24); border-radius: var(--radius-lg); max-width: 52ch">
|
|
146
|
+
<p style="margin: 0 0 var(--spacing-8); color: var(--color-text-inverse)">
|
|
147
|
+
High emphasis on an inverse surface — 17.97:1
|
|
148
|
+
</p>
|
|
149
|
+
<p style="margin: 0; color: var(--color-text-muted-inverse); font-size: var(--typography-fontSize-sm)">
|
|
150
|
+
Subordinate on an inverse surface — 7.92:1
|
|
151
|
+
</p>
|
|
152
|
+
</div>
|
|
153
|
+
<p>
|
|
154
|
+
<b>An inverse ground is not always flat, and arithmetic is not measurement.</b> A
|
|
155
|
+
gradient or an image has a lightest point, and that point is the one that decides —
|
|
156
|
+
but which point the text actually sits on is a question about layout, not about the
|
|
157
|
+
stops. bd-cta-banner sets white at 60% opacity over a blue gradient, and calculating
|
|
158
|
+
against its lightest stop predicts 2.13:1. Sampling the pixels behind the rendered
|
|
159
|
+
text gives 4.46:1, because the radial highlight sits at <code>78% -10%</code> and is
|
|
160
|
+
mostly outside the banner. Sample the ground; do not infer it.
|
|
161
|
+
</p>`
|
|
162
|
+
})}
|
|
163
|
+
`
|
|
164
|
+
});
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The analysis behind Foundations/Token Map, kept free of Lit and the DOM so
|
|
3
|
+
* the same functions run in Storybook and under Node: the page renders what
|
|
4
|
+
* this returns, and nothing on the page is transcribed by hand.
|
|
5
|
+
*
|
|
6
|
+
* Inputs are text, not live objects: the generated tokens.css, the tier
|
|
7
|
+
* registry in scripts/token-tiers.json, and the raw source of every file that
|
|
8
|
+
* might read a token. Component CSS sits inside shadow roots, which is why the
|
|
9
|
+
* other Foundations pages can only count token-to-token references; reading the
|
|
10
|
+
* source is what lets this page say which component reads a token.
|
|
11
|
+
*
|
|
12
|
+
* Not a story file: Storybook only globs *.stories.js.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Blank comments, keeping offsets, so commented-out CSS is never read. */
|
|
16
|
+
export const blankComments = text =>
|
|
17
|
+
text.replace(/\/\*[\s\S]*?\*\//g, c => c.replace(/[^\n]/g, ' '));
|
|
18
|
+
|
|
19
|
+
/** name -> raw declared value, in declaration order. */
|
|
20
|
+
export function declarationsFrom(cssText) {
|
|
21
|
+
const out = new Map();
|
|
22
|
+
for (const m of blankComments(cssText).matchAll(/(?:^|[\s;{])(--[A-Za-z0-9-]+)\s*:\s*([^;]*);/g)) {
|
|
23
|
+
if (!out.has(m[1])) out.set(m[1], m[2].replace(/\s+/g, ' ').trim());
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Every custom property a value refers to. */
|
|
29
|
+
export const refsOf = value => [...value.matchAll(/var\(\s*(--[A-Za-z0-9-]+)/g)].map(m => m[1]);
|
|
30
|
+
|
|
31
|
+
/** The family a token belongs to: the longest matching registry prefix. */
|
|
32
|
+
export function familyOf(token, families) {
|
|
33
|
+
let best = null;
|
|
34
|
+
for (const f of families) {
|
|
35
|
+
if (token.startsWith(f.prefix) && (!best || f.prefix.length > best.prefix.length)) best = f;
|
|
36
|
+
}
|
|
37
|
+
return best;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The alias chain from a token down to its value. Follows the reference while
|
|
42
|
+
* the value is exactly one var(); a value that combines references
|
|
43
|
+
* (color-mix, a shorthand, a clamp) ends the chain and is reported as compound.
|
|
44
|
+
*/
|
|
45
|
+
export function chainOf(token, decls) {
|
|
46
|
+
const hops = [token];
|
|
47
|
+
let value = decls.get(token);
|
|
48
|
+
while (value !== undefined) {
|
|
49
|
+
const only = value.match(/^var\(\s*(--[A-Za-z0-9-]+)\s*\)$/);
|
|
50
|
+
if (!only || !decls.has(only[1]) || hops.includes(only[1])) break;
|
|
51
|
+
hops.push(only[1]);
|
|
52
|
+
value = decls.get(only[1]);
|
|
53
|
+
}
|
|
54
|
+
const compound = value !== undefined && refsOf(value).length > 0;
|
|
55
|
+
return { hops, value: value ?? '', compound };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Who reads each token, from raw source text keyed by path.
|
|
60
|
+
*
|
|
61
|
+
* Three ways a token is read, and all three are real in this package:
|
|
62
|
+
* var(--x) the ordinary case
|
|
63
|
+
* '--x' a name held as data -- token-scales.js maps
|
|
64
|
+
* color="muted" to '--color-text-muted' this way
|
|
65
|
+
* '--spacing-' a prefix the scale helpers complete at runtime, so a
|
|
66
|
+
* step may be read without its full name appearing
|
|
67
|
+
* The third is reported separately: it says a token can be reached, not that
|
|
68
|
+
* anything reaches it.
|
|
69
|
+
*/
|
|
70
|
+
export function consumersFrom(sources, ownerOf) {
|
|
71
|
+
const readers = new Map();
|
|
72
|
+
const prefixes = new Map();
|
|
73
|
+
const add = (map, key, owner) => {
|
|
74
|
+
if (!map.has(key)) map.set(key, new Set());
|
|
75
|
+
map.get(key).add(owner);
|
|
76
|
+
};
|
|
77
|
+
for (const [path, raw] of Object.entries(sources)) {
|
|
78
|
+
const text = blankComments(raw);
|
|
79
|
+
const owner = ownerOf(path);
|
|
80
|
+
for (const m of text.matchAll(/var\(\s*(--[A-Za-z0-9-]+)/g)) add(readers, m[1], owner);
|
|
81
|
+
for (const m of text.matchAll(/['"`](--[A-Za-z0-9-]*[A-Za-z0-9])['"`]/g)) add(readers, m[1], owner);
|
|
82
|
+
/* documentation looks tokens up by prefix to list them, which is not a
|
|
83
|
+
product being able to reach one */
|
|
84
|
+
if (owner === DOCS) continue;
|
|
85
|
+
for (const m of text.matchAll(/['"`](--[A-Za-z0-9-]+-)['"`$]/g)) add(prefixes, m[1], owner);
|
|
86
|
+
for (const m of text.matchAll(/var\(\s*(--[A-Za-z0-9-]+-)\$\{/g)) add(prefixes, m[1], owner);
|
|
87
|
+
}
|
|
88
|
+
return { readers, prefixes };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const TIER_RANK = { primitive: 0, semantic: 1, component: 2 };
|
|
92
|
+
|
|
93
|
+
/** The owner name ownerOf() should return for documentation sources. */
|
|
94
|
+
export const DOCS = 'stories';
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Everything the page shows. `sources` is { path: text }; `ownerOf` turns a
|
|
98
|
+
* path into the name shown in "read by".
|
|
99
|
+
*/
|
|
100
|
+
export function analyse({ cssText, registry, sources, ownerOf }) {
|
|
101
|
+
const decls = declarationsFrom(cssText);
|
|
102
|
+
const families = registry.families;
|
|
103
|
+
const { readers, prefixes } = consumersFrom(sources, ownerOf);
|
|
104
|
+
|
|
105
|
+
const byToken = new Map();
|
|
106
|
+
for (const [name, value] of decls) {
|
|
107
|
+
const family = familyOf(name, families);
|
|
108
|
+
const chain = chainOf(name, decls);
|
|
109
|
+
const tokenReaders = new Set();
|
|
110
|
+
for (const [other, v] of decls) if (other !== name && refsOf(v).includes(name)) tokenReaders.add(other);
|
|
111
|
+
const reachable = new Set();
|
|
112
|
+
for (const [prefix, owners] of prefixes) if (name.startsWith(prefix)) owners.forEach(o => reachable.add(o));
|
|
113
|
+
byToken.set(name, {
|
|
114
|
+
name,
|
|
115
|
+
value,
|
|
116
|
+
family,
|
|
117
|
+
tier : family?.tier ?? 'unassigned',
|
|
118
|
+
chain,
|
|
119
|
+
tokenReaders,
|
|
120
|
+
sourceReaders: readers.get(name) ?? new Set(),
|
|
121
|
+
reachable
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const rows = [...byToken.values()];
|
|
126
|
+
const roleOf = name => name.replace(/-[A-Za-z]+$/, '');
|
|
127
|
+
|
|
128
|
+
/* Tier skips: a component token whose first hop is a primitive. Grouped by
|
|
129
|
+
the primitive family reached, because the answer differs by family: a
|
|
130
|
+
colour primitive has semantic roles above it that the token could have
|
|
131
|
+
asked for, and --spacing-* has none -- its roles were deleted on purpose.
|
|
132
|
+
Inversions: a token whose first hop is a HIGHER tier than itself. */
|
|
133
|
+
const tierSkips = new Map();
|
|
134
|
+
const inversions = [];
|
|
135
|
+
for (const r of rows) {
|
|
136
|
+
const firstHop = r.chain.hops[1] ?? refsOf(r.value)[0];
|
|
137
|
+
const target = firstHop && byToken.get(firstHop);
|
|
138
|
+
if (!target) continue;
|
|
139
|
+
if (r.tier === 'component' && target.tier === 'primitive') {
|
|
140
|
+
const key = target.family.prefix;
|
|
141
|
+
if (!tierSkips.has(key)) tierSkips.set(key, []);
|
|
142
|
+
tierSkips.get(key).push({ row: r, target });
|
|
143
|
+
}
|
|
144
|
+
if (TIER_RANK[target.tier] > TIER_RANK[r.tier]) inversions.push({ row: r, target });
|
|
145
|
+
}
|
|
146
|
+
/* For each skipped-to family, the semantic tokens that do sit above it --
|
|
147
|
+
the roles a skipping token could have asked for instead. A count, not a
|
|
148
|
+
verdict: --spacing-* has two (the card roles), and that is not a spacing
|
|
149
|
+
vocabulary. */
|
|
150
|
+
const rolesAbove = prefix => rows.filter(r => r.tier === 'semantic' &&
|
|
151
|
+
r.chain.hops.slice(1).some(h => byToken.get(h)?.family?.prefix === prefix));
|
|
152
|
+
const skips = [...tierSkips].map(([prefix, list]) => ({ prefix, list, rolesAbove: rolesAbove(prefix) }))
|
|
153
|
+
.sort((a, b) => b.list.length - a.list.length);
|
|
154
|
+
|
|
155
|
+
/* Unread, split by what it means:
|
|
156
|
+
unread semantic or component, and nothing reads it -- the
|
|
157
|
+
case that retired display-hero and two status tokens
|
|
158
|
+
docsOnly read by a story and nothing else
|
|
159
|
+
unreadAxes one axis of a typography role whose other axes are
|
|
160
|
+
read; a role declares five and a consumer may want two
|
|
161
|
+
reachableOnly nothing names it, but a scale helper can build its name
|
|
162
|
+
unreadSteps a primitive step nothing reads; a scale is allowed to
|
|
163
|
+
be complete */
|
|
164
|
+
const unread = [];
|
|
165
|
+
const docsOnly = [];
|
|
166
|
+
const unreadAxes = [];
|
|
167
|
+
const reachableOnly = [];
|
|
168
|
+
const unreadSteps = [];
|
|
169
|
+
const productRead = r => r.tokenReaders.size > 0 || [...r.sourceReaders].some(o => o !== DOCS);
|
|
170
|
+
for (const r of rows) {
|
|
171
|
+
if (productRead(r)) continue;
|
|
172
|
+
if (r.reachable.size) { reachableOnly.push(r); continue; }
|
|
173
|
+
if (r.sourceReaders.has(DOCS)) { docsOnly.push(r); continue; }
|
|
174
|
+
if (r.tier === 'primitive') { unreadSteps.push(r); continue; }
|
|
175
|
+
const isRoleAxis = r.tier === 'semantic' && r.name.startsWith('--typography-');
|
|
176
|
+
const siblingRead = isRoleAxis && rows.some(o => o !== r && roleOf(o.name) === roleOf(r.name) && productRead(o));
|
|
177
|
+
(siblingRead ? unreadAxes : unread).push(r);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* semantic tokens declared as a literal: no primitive beneath them */
|
|
181
|
+
const literalSemantics = rows.filter(r => r.tier === 'semantic' && refsOf(r.value).length === 0);
|
|
182
|
+
|
|
183
|
+
/* Same colour, two semantic names in one family, differing in exactly one
|
|
184
|
+
segment. Split by WHICH segment differs:
|
|
185
|
+
sameSlot a middle segment -- --status-brand-surface and
|
|
186
|
+
--status-info-surface: one slot, two tones, one colour. This
|
|
187
|
+
is the kind that collided twice.
|
|
188
|
+
sameRole the last segment -- --color-surface-muted and -disabled: two
|
|
189
|
+
jobs that happen to share a value, which CLAUDE.md says is
|
|
190
|
+
expected and must not drive a choice. Listed, not flagged. */
|
|
191
|
+
const slots = new Map();
|
|
192
|
+
const sameRole = [];
|
|
193
|
+
const colour = rows.filter(r => r.tier === 'semantic' && !r.chain.compound &&
|
|
194
|
+
byToken.get(r.chain.hops.at(-1))?.family?.prefix?.startsWith('--color-') &&
|
|
195
|
+
byToken.get(r.chain.hops.at(-1))?.tier === 'primitive');
|
|
196
|
+
const segs = n => n.slice(2).split('-');
|
|
197
|
+
for (let i = 0; i < colour.length; i++) {
|
|
198
|
+
for (let j = i + 1; j < colour.length; j++) {
|
|
199
|
+
const a = colour[i], b = colour[j];
|
|
200
|
+
if (a.family !== b.family || a.chain.hops.at(-1) !== b.chain.hops.at(-1)) continue;
|
|
201
|
+
const sa = segs(a.name), sb = segs(b.name);
|
|
202
|
+
if (sa.length !== sb.length) continue;
|
|
203
|
+
const diff = sa.map((s, k) => s !== sb[k]);
|
|
204
|
+
if (diff.filter(Boolean).length !== 1) continue;
|
|
205
|
+
if (diff.at(-1)) { sameRole.push({ a, b, primitive: a.chain.hops.at(-1) }); continue; }
|
|
206
|
+
/* one line per slot: --status-*-surface -> blue-50: brand, info */
|
|
207
|
+
const k = diff.indexOf(true);
|
|
208
|
+
const pattern = `--${sa.map((s, n) => (n === k ? '*' : s)).join('-')}`;
|
|
209
|
+
const key = `${pattern}|${a.chain.hops.at(-1)}`;
|
|
210
|
+
if (!slots.has(key)) slots.set(key, { pattern, primitive: a.chain.hops.at(-1), members: new Set() });
|
|
211
|
+
slots.get(key).members.add(sa[k]).add(sb[k]);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const sameSlot = [...slots.values()].map(g => ({ ...g, members: [...g.members] }));
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
rows, decls,
|
|
218
|
+
flags: { tierSkips: skips, inversions, unread, docsOnly, unreadAxes, reachableOnly, unreadSteps, literalSemantics, sameSlot, sameRole }
|
|
219
|
+
};
|
|
220
|
+
}
|