@thewhatmatters/wmds 0.4.9 → 0.4.28
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 +303 -0
- package/CONSUMING.md +62 -1
- package/README.md +1 -1
- package/bin/wmds-check.mjs +47 -0
- package/dist/colors.css +92 -0
- package/dist/components/atoms/Badge/Badge.d.ts +13 -5
- package/dist/components/atoms/Badge/Badge.d.ts.map +1 -1
- package/dist/components/atoms/Badge/Badge.js +56 -48
- package/dist/components/atoms/Badge/badgeStyles.d.ts +15 -0
- package/dist/components/atoms/Badge/badgeStyles.d.ts.map +1 -1
- package/dist/components/atoms/Badge/badgeStyles.js +42 -20
- package/dist/components/atoms/Band/Band.d.ts +27 -0
- package/dist/components/atoms/Band/Band.d.ts.map +1 -0
- package/dist/components/atoms/Band/Band.js +22 -0
- package/dist/components/atoms/Band/bandStyles.d.ts +20 -0
- package/dist/components/atoms/Band/bandStyles.d.ts.map +1 -0
- package/dist/components/atoms/Band/bandStyles.js +23 -0
- package/dist/components/atoms/Button/Button.d.ts +1 -1
- package/dist/components/atoms/Button/Button.d.ts.map +1 -1
- package/dist/components/atoms/Button/Button.js +67 -48
- package/dist/components/atoms/Button/ButtonStatusButton.d.ts.map +1 -1
- package/dist/components/atoms/Button/ButtonStatusButton.js +12 -5
- package/dist/components/atoms/Button/buttonStatusMotion.d.ts.map +1 -1
- package/dist/components/atoms/Button/buttonStatusMotion.js +6 -1
- package/dist/components/atoms/Button/buttonStyles.d.ts +2 -2
- package/dist/components/atoms/Button/buttonStyles.d.ts.map +1 -1
- package/dist/components/atoms/Button/buttonStyles.js +16 -14
- package/dist/components/atoms/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/atoms/Checkbox/Checkbox.js +69 -68
- package/dist/components/atoms/Checkbox/checkboxStyles.d.ts +1 -1
- package/dist/components/atoms/Checkbox/checkboxStyles.d.ts.map +1 -1
- package/dist/components/atoms/Checkbox/checkboxStyles.js +1 -1
- package/dist/components/atoms/IconButton/IconButton.d.ts.map +1 -1
- package/dist/components/atoms/IconButton/IconButton.js +13 -12
- package/dist/components/atoms/IconButton/iconButtonStyles.d.ts +2 -2
- package/dist/components/atoms/IconButton/iconButtonStyles.d.ts.map +1 -1
- package/dist/components/atoms/IconButton/iconButtonStyles.js +1 -1
- package/dist/components/atoms/InlinePill/InlinePill.d.ts +42 -0
- package/dist/components/atoms/InlinePill/InlinePill.d.ts.map +1 -0
- package/dist/components/atoms/InlinePill/InlinePill.js +48 -0
- package/dist/components/atoms/InlinePill/inlinePillStyles.d.ts +27 -0
- package/dist/components/atoms/InlinePill/inlinePillStyles.d.ts.map +1 -0
- package/dist/components/atoms/InlinePill/inlinePillStyles.js +19 -0
- package/dist/components/atoms/Input/Input.d.ts +9 -4
- package/dist/components/atoms/Input/Input.d.ts.map +1 -1
- package/dist/components/atoms/Input/Input.js +59 -59
- package/dist/components/atoms/Input/inputShellStyles.d.ts +10 -4
- package/dist/components/atoms/Input/inputShellStyles.d.ts.map +1 -1
- package/dist/components/atoms/Input/inputShellStyles.js +40 -40
- package/dist/components/atoms/RiveHand/RiveHand.d.ts.map +1 -1
- package/dist/components/atoms/RiveHand/RiveHand.js +83 -81
- package/dist/components/atoms/Skeleton/skeletonStyles.js +1 -1
- package/dist/components/atoms/Switch/Switch.d.ts.map +1 -1
- package/dist/components/atoms/Switch/Switch.js +45 -44
- package/dist/components/atoms/Switch/switchStyles.d.ts +1 -1
- package/dist/components/atoms/Switch/switchStyles.d.ts.map +1 -1
- package/dist/components/atoms/Switch/switchStyles.js +1 -1
- package/dist/components/atoms/Table/Table.d.ts +54 -0
- package/dist/components/atoms/Table/Table.d.ts.map +1 -0
- package/dist/components/atoms/Table/Table.js +66 -0
- package/dist/components/atoms/Table/tableStyles.d.ts +18 -0
- package/dist/components/atoms/Table/tableStyles.d.ts.map +1 -0
- package/dist/components/atoms/Table/tableStyles.js +14 -0
- package/dist/components/atoms/TextArea/TextArea.d.ts +6 -0
- package/dist/components/atoms/TextArea/TextArea.d.ts.map +1 -1
- package/dist/components/atoms/TextArea/TextArea.js +86 -78
- package/dist/components/atoms/TextArea/textareaShellStyles.d.ts +2 -2
- package/dist/components/atoms/TextArea/textareaShellStyles.d.ts.map +1 -1
- package/dist/components/atoms/TextArea/textareaShellStyles.js +5 -5
- package/dist/components/molecules/Accordion/accordionStyles.d.ts +1 -1
- package/dist/components/molecules/Accordion/accordionStyles.d.ts.map +1 -1
- package/dist/components/molecules/Accordion/accordionStyles.js +1 -1
- package/dist/components/molecules/Breadcrumb/Breadcrumb.d.ts.map +1 -1
- package/dist/components/molecules/Breadcrumb/Breadcrumb.js +80 -122
- package/dist/components/molecules/Card/Card.d.ts +28 -6
- package/dist/components/molecules/Card/Card.d.ts.map +1 -1
- package/dist/components/molecules/Card/Card.js +83 -42
- package/dist/components/molecules/Card/cardStyles.d.ts +16 -1
- package/dist/components/molecules/Card/cardStyles.d.ts.map +1 -1
- package/dist/components/molecules/Card/cardStyles.js +19 -5
- package/dist/components/molecules/ChatQa/chatQaStyles.d.ts +1 -1
- package/dist/components/molecules/ChatQa/chatQaStyles.d.ts.map +1 -1
- package/dist/components/molecules/ChatQa/chatQaStyles.js +1 -1
- package/dist/components/molecules/Chip/chipStyles.js +9 -9
- package/dist/components/molecules/CopyField/CopyField.d.ts +41 -0
- package/dist/components/molecules/CopyField/CopyField.d.ts.map +1 -0
- package/dist/components/molecules/CopyField/CopyField.js +120 -0
- package/dist/components/molecules/CopyField/copyFieldStyles.d.ts +28 -0
- package/dist/components/molecules/CopyField/copyFieldStyles.d.ts.map +1 -0
- package/dist/components/molecules/CopyField/copyFieldStyles.js +7 -0
- package/dist/components/molecules/DisplayControls/DisplayControls.d.ts +16 -7
- package/dist/components/molecules/DisplayControls/DisplayControls.d.ts.map +1 -1
- package/dist/components/molecules/DisplayControls/DisplayControls.js +41 -39
- package/dist/components/molecules/DisplayControls/displayControlsState.d.ts +21 -0
- package/dist/components/molecules/DisplayControls/displayControlsState.d.ts.map +1 -1
- package/dist/components/molecules/DisplayControls/displayControlsState.js +46 -4
- package/dist/components/molecules/Dropdown/Dropdown.d.ts +1 -1
- package/dist/components/molecules/Dropdown/DropdownItem.d.ts +2 -2
- package/dist/components/molecules/Dropdown/DropdownItem.d.ts.map +1 -1
- package/dist/components/molecules/Dropdown/DropdownItem.js +30 -27
- package/dist/components/molecules/Dropdown/dropdownStyles.js +1 -1
- package/dist/components/molecules/Dropdown/resolveOverflow.d.ts +14 -0
- package/dist/components/molecules/Dropdown/resolveOverflow.d.ts.map +1 -0
- package/dist/components/{organisms/Tab/tabOverflow.js → molecules/Dropdown/resolveOverflow.js} +2 -2
- package/dist/components/molecules/Dropdown/useDropdownMenu.d.ts +70 -0
- package/dist/components/molecules/Dropdown/useDropdownMenu.d.ts.map +1 -0
- package/dist/components/molecules/Dropdown/useDropdownMenu.js +155 -0
- package/dist/components/molecules/Dropdown/useOverflowMenu.d.ts +71 -0
- package/dist/components/molecules/Dropdown/useOverflowMenu.d.ts.map +1 -0
- package/dist/components/molecules/Dropdown/useOverflowMenu.js +61 -0
- package/dist/components/molecules/Field/Field.d.ts.map +1 -1
- package/dist/components/molecules/Field/Field.js +11 -10
- package/dist/components/molecules/LogView/LogView.d.ts +31 -0
- package/dist/components/molecules/LogView/LogView.d.ts.map +1 -0
- package/dist/components/molecules/LogView/LogView.js +109 -0
- package/dist/components/molecules/LogView/logViewStyles.d.ts +11 -0
- package/dist/components/molecules/LogView/logViewStyles.d.ts.map +1 -0
- package/dist/components/molecules/LogView/logViewStyles.js +6 -0
- package/dist/components/molecules/NavList/NavList.d.ts +23 -4
- package/dist/components/molecules/NavList/NavList.d.ts.map +1 -1
- package/dist/components/molecules/NavList/NavList.js +65 -52
- package/dist/components/molecules/NavList/navListStyles.d.ts +2 -0
- package/dist/components/molecules/NavList/navListStyles.d.ts.map +1 -1
- package/dist/components/molecules/NavList/navListStyles.js +3 -3
- package/dist/components/molecules/PageHeader/PageHeader.js +3 -3
- package/dist/components/molecules/PillGroup/pillGroupStyles.js +1 -1
- package/dist/components/molecules/Search/searchStyles.js +1 -1
- package/dist/components/molecules/SegmentedControl/segmentedControlStyles.d.ts +1 -1
- package/dist/components/molecules/SegmentedControl/segmentedControlStyles.d.ts.map +1 -1
- package/dist/components/molecules/SegmentedControl/segmentedControlStyles.js +8 -8
- package/dist/components/molecules/Select/Select.d.ts +3 -1
- package/dist/components/molecules/Select/Select.d.ts.map +1 -1
- package/dist/components/molecules/Select/Select.js +75 -119
- package/dist/components/molecules/Select/selectStyles.js +3 -3
- package/dist/components/molecules/Stat/Stat.d.ts +31 -5
- package/dist/components/molecules/Stat/Stat.d.ts.map +1 -1
- package/dist/components/molecules/Stat/Stat.js +83 -49
- package/dist/components/molecules/Stat/statStyles.d.ts +24 -4
- package/dist/components/molecules/Stat/statStyles.d.ts.map +1 -1
- package/dist/components/molecules/Stat/statStyles.js +30 -13
- package/dist/components/molecules/TaskRows/TaskRows.d.ts +13 -3
- package/dist/components/molecules/TaskRows/TaskRows.d.ts.map +1 -1
- package/dist/components/molecules/TaskRows/TaskRows.js +81 -57
- package/dist/components/molecules/TaskRows/TaskRowsTable.d.ts +54 -0
- package/dist/components/molecules/TaskRows/TaskRowsTable.d.ts.map +1 -0
- package/dist/components/molecules/TaskRows/TaskRowsTable.js +200 -0
- package/dist/components/molecules/TaskRows/taskRowsStyles.d.ts +19 -1
- package/dist/components/molecules/TaskRows/taskRowsStyles.d.ts.map +1 -1
- package/dist/components/molecules/TaskRows/taskRowsStyles.js +7 -3
- package/dist/components/molecules/TextSequence/TextSequence.d.ts.map +1 -1
- package/dist/components/molecules/TextSequence/TextSequence.js +35 -35
- package/dist/components/organisms/AppShell/AppShell.d.ts +84 -0
- package/dist/components/organisms/AppShell/AppShell.d.ts.map +1 -0
- package/dist/components/organisms/AppShell/AppShell.js +231 -0
- package/dist/components/organisms/AppShell/appShellStyles.d.ts +20 -0
- package/dist/components/organisms/AppShell/appShellStyles.d.ts.map +1 -0
- package/dist/components/organisms/AppShell/appShellStyles.js +8 -0
- package/dist/components/organisms/Carousel/Carousel.d.ts +9 -1
- package/dist/components/organisms/Carousel/Carousel.d.ts.map +1 -1
- package/dist/components/organisms/Carousel/Carousel.js +220 -182
- package/dist/components/organisms/Carousel/carouselMath.d.ts +5 -0
- package/dist/components/organisms/Carousel/carouselMath.d.ts.map +1 -1
- package/dist/components/organisms/Carousel/carouselMath.js +9 -1
- package/dist/components/organisms/Carousel/carouselStyles.d.ts +2 -0
- package/dist/components/organisms/Carousel/carouselStyles.d.ts.map +1 -1
- package/dist/components/organisms/Carousel/carouselStyles.js +8 -7
- package/dist/components/organisms/Chart/Chart.d.ts +5 -1
- package/dist/components/organisms/Chart/Chart.d.ts.map +1 -1
- package/dist/components/organisms/Chart/Chart.js +126 -62
- package/dist/components/organisms/Chart/ChartLegend.d.ts +8 -1
- package/dist/components/organisms/Chart/ChartLegend.d.ts.map +1 -1
- package/dist/components/organisms/Chart/ChartLegend.js +43 -13
- package/dist/components/organisms/Chart/ChartSegmentedRing.d.ts +52 -0
- package/dist/components/organisms/Chart/ChartSegmentedRing.d.ts.map +1 -0
- package/dist/components/organisms/Chart/ChartSegmentedRing.js +95 -0
- package/dist/components/organisms/Chart/chartStyles.d.ts +32 -0
- package/dist/components/organisms/Chart/chartStyles.d.ts.map +1 -1
- package/dist/components/organisms/Chart/chartStyles.js +15 -3
- package/dist/components/organisms/ChatDock/ChatDock.d.ts.map +1 -1
- package/dist/components/organisms/ChatDock/ChatDock.js +29 -25
- package/dist/components/organisms/Dialog/AlertDialog.d.ts +7 -1
- package/dist/components/organisms/Dialog/AlertDialog.d.ts.map +1 -1
- package/dist/components/organisms/Dialog/AlertDialog.js +54 -50
- package/dist/components/organisms/Dialog/Dialog.d.ts.map +1 -1
- package/dist/components/organisms/Dialog/Dialog.js +52 -71
- package/dist/components/organisms/Dialog/OverlayPanelHeader.d.ts.map +1 -1
- package/dist/components/organisms/Dialog/OverlayPanelHeader.js +1 -0
- package/dist/components/organisms/Dialog/dialogStyles.d.ts +14 -1
- package/dist/components/organisms/Dialog/dialogStyles.d.ts.map +1 -1
- package/dist/components/organisms/Dialog/dialogStyles.js +3 -3
- package/dist/components/organisms/FilterPanel/FilterPanel.d.ts.map +1 -1
- package/dist/components/organisms/FilterPanel/FilterPanel.js +52 -42
- package/dist/components/organisms/IntakeModal/IntakeModal.d.ts +6 -1
- package/dist/components/organisms/IntakeModal/IntakeModal.d.ts.map +1 -1
- package/dist/components/organisms/IntakeModal/IntakeModal.js +54 -66
- package/dist/components/organisms/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/components/organisms/MoreMenu/MoreMenu.js +45 -101
- package/dist/components/organisms/Panel/Panel.d.ts.map +1 -1
- package/dist/components/organisms/Panel/Panel.js +44 -53
- package/dist/components/organisms/Sheet/Sheet.d.ts +3 -1
- package/dist/components/organisms/Sheet/Sheet.d.ts.map +1 -1
- package/dist/components/organisms/Sheet/Sheet.js +66 -81
- package/dist/components/organisms/Sheet/sheetStyles.d.ts +8 -2
- package/dist/components/organisms/Sheet/sheetStyles.d.ts.map +1 -1
- package/dist/components/organisms/Sheet/sheetStyles.js +16 -11
- package/dist/components/organisms/SiteNav/SiteNav.d.ts +0 -9
- package/dist/components/organisms/SiteNav/SiteNav.d.ts.map +1 -1
- package/dist/components/organisms/SiteNav/SiteNav.js +236 -286
- package/dist/components/organisms/SiteNav/siteNavStyles.js +1 -1
- package/dist/components/organisms/Tab/Tab.d.ts.map +1 -1
- package/dist/components/organisms/Tab/Tab.js +77 -127
- package/dist/components/organisms/Tab/tabStyles.d.ts +2 -1
- package/dist/components/organisms/Tab/tabStyles.d.ts.map +1 -1
- package/dist/components/organisms/Tab/tabStyles.js +7 -7
- package/dist/fonts.css +10 -0
- package/dist/grid.css +38 -0
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +152 -139
- package/dist/lib/chartTheme.d.ts +11 -0
- package/dist/lib/chartTheme.d.ts.map +1 -1
- package/dist/lib/compositionAudit.d.ts.map +1 -1
- package/dist/lib/contrast.d.ts +11 -0
- package/dist/lib/contrast.d.ts.map +1 -0
- package/dist/lib/dialogOverlay.d.ts +34 -1
- package/dist/lib/dialogOverlay.d.ts.map +1 -1
- package/dist/lib/dialogOverlay.js +100 -23
- package/dist/lib/sheetParam/index.d.ts +3 -0
- package/dist/lib/sheetParam/index.d.ts.map +1 -0
- package/dist/lib/sheetParam/sheetParam.d.ts +15 -0
- package/dist/lib/sheetParam/sheetParam.d.ts.map +1 -0
- package/dist/lib/sheetParam/sheetParam.js +26 -0
- package/dist/lib/sheetParam/useSheetParam.d.ts +25 -0
- package/dist/lib/sheetParam/useSheetParam.d.ts.map +1 -0
- package/dist/lib/sheetParam/useSheetParam.js +29 -0
- package/dist/lib/typography.d.ts +1 -1
- package/dist/lib/typography.d.ts.map +1 -1
- package/dist/shape.css +24 -3
- package/dist/styles.css +1 -1
- package/dist/typography.css +55 -8
- package/docs/README.md +1 -1
- package/docs/component-contracts.md +101 -0
- package/docs/components.md +19 -12
- package/docs/exports.json +142 -15
- package/docs/patterns/components-accordion--flush.tsx +1 -1
- package/docs/patterns/components-appshell--portal-dashboard-pattern.tsx +176 -0
- package/docs/patterns/components-avatar--dropdown-row.tsx +1 -1
- package/docs/patterns/components-badge--outline-tags.tsx +1 -1
- package/docs/patterns/components-badge--status-pill.tsx +31 -0
- package/docs/patterns/components-badge--with-avatar.tsx +1 -1
- package/docs/patterns/components-band--page-in-bands.tsx +39 -0
- package/docs/patterns/components-breadcrumb--editorial.tsx +1 -1
- package/docs/patterns/components-breadcrumb--long-path.tsx +1 -1
- package/docs/patterns/components-breadcrumb--path.tsx +1 -1
- package/docs/patterns/components-breadcrumb--router-links.tsx +1 -1
- package/docs/patterns/components-button-button--copy-button.tsx +1 -1
- package/docs/patterns/components-button-button--destructive-action.tsx +1 -1
- package/docs/patterns/components-button-button--external-link.tsx +1 -1
- package/docs/patterns/components-button-button--ghost-action.tsx +1 -1
- package/docs/patterns/components-button-button--inverse-action.tsx +1 -1
- package/docs/patterns/components-button-button--link-render.tsx +1 -1
- package/docs/patterns/components-button-button--multi-state-badge.tsx +1 -1
- package/docs/patterns/components-button-button--outline-mono.tsx +1 -1
- package/docs/patterns/components-button-button--primary-action.tsx +1 -1
- package/docs/patterns/components-button-button--row-hug.tsx +1 -1
- package/docs/patterns/components-button-button--row-layout.tsx +1 -1
- package/docs/patterns/components-button-button--secondary-action.tsx +1 -1
- package/docs/patterns/components-button-button--suggestion-pills.tsx +1 -1
- package/docs/patterns/components-button-button--with-count.tsx +1 -1
- package/docs/patterns/components-button-button--with-icon.tsx +1 -1
- package/docs/patterns/components-button-floatingactionbutton--vertical-actions.tsx +1 -1
- package/docs/patterns/components-calembed--pattern-skip-in-footer.tsx +1 -1
- package/docs/patterns/components-card-card--body-slot-occupancy-history.tsx +1 -1
- package/docs/patterns/components-card-card--headerless-media.tsx +1 -1
- package/docs/patterns/components-card-card--outlined-layout.tsx +1 -1
- package/docs/patterns/components-card-card--pinned-height.tsx +1 -1
- package/docs/patterns/components-card-card--terminal-frame.tsx +48 -0
- package/docs/patterns/components-card-selectablecard--selectable-cards-pattern.tsx +1 -1
- package/docs/patterns/components-carousel--image-carousel.tsx +1 -1
- package/docs/patterns/components-chart--audience-activity-heatmap.tsx +1 -1
- package/docs/patterns/components-chart--cartesian-no-data-gaps.tsx +1 -1
- package/docs/patterns/components-chart--occupancy-history-in-card.tsx +1 -1
- package/docs/patterns/components-chart--occupancy-in-card.tsx +1 -1
- package/docs/patterns/components-chart--post-reach-distribution.tsx +1 -1
- package/docs/patterns/components-chart--ranked-audience-breakdown.tsx +1 -1
- package/docs/patterns/components-chart--revenue-by-line.tsx +68 -0
- package/docs/patterns/components-chart--segmented-ring-pattern.tsx +144 -0
- package/docs/patterns/components-chart--unit-grid-composition.tsx +1 -1
- package/docs/patterns/components-chatdock--pattern-chat-dock.tsx +1 -1
- package/docs/patterns/components-chatdock--pattern-start-project-gate.tsx +1 -1
- package/docs/patterns/components-chatqa--pattern-qa-with-reply.tsx +1 -1
- package/docs/patterns/components-checkbox-checkbox--default.tsx +1 -1
- package/docs/patterns/components-checkbox-checkbox--with-count.tsx +1 -1
- package/docs/patterns/components-checkbox-checkbox--with-description.tsx +1 -1
- package/docs/patterns/components-checkbox-checkboxgroup--default.tsx +1 -1
- package/docs/patterns/components-checkbox-checkboxgroup--option-counts.tsx +1 -1
- package/docs/patterns/components-copyfield--clipboard-blocked.tsx +9 -0
- package/docs/patterns/components-copyfield--copied.tsx +9 -0
- package/docs/patterns/components-copyfield--copy-key.tsx +9 -0
- package/docs/patterns/components-copyfield--on-navy.tsx +29 -0
- package/docs/patterns/components-descriptionlist--details.tsx +1 -1
- package/docs/patterns/components-descriptionlist--post-metadata.tsx +1 -1
- package/docs/patterns/components-dialog--alert-dialog-destructive.tsx +1 -1
- package/docs/patterns/components-dialog--alert-dialog-typed-confirmation.tsx +54 -0
- package/docs/patterns/components-dialog--default-dialog.tsx +1 -1
- package/docs/patterns/components-dialog--form-dialog.tsx +1 -1
- package/docs/patterns/components-displaycontrols--page-utilities.tsx +1 -1
- package/docs/patterns/components-displaycontrols--theme-switch.tsx +35 -0
- package/docs/patterns/components-field--horizontal-input.tsx +1 -1
- package/docs/patterns/components-field--horizontal-text-area.tsx +1 -1
- package/docs/patterns/components-field--multi-control.tsx +1 -1
- package/docs/patterns/components-filterpanel--panel.tsx +1 -1
- package/docs/patterns/components-footerreveal--marketing-hero-pattern.tsx +1 -1
- package/docs/patterns/components-footerreveal--marketing-hero-ruled-pattern.tsx +1 -1
- package/docs/patterns/components-footerreveal--marketing-hero-with-gallery-intro.tsx +1 -1
- package/docs/patterns/components-footerreveal--marketing-page-pattern.tsx +1 -1
- package/docs/patterns/components-footerreveal--ruled-grid-footer-pattern.tsx +1 -1
- package/docs/patterns/components-footerreveal--sequenced-marketing-hero.tsx +1 -1
- package/docs/patterns/components-herointro--two-line-intro.tsx +1 -1
- package/docs/patterns/components-herotilestack--marketing-hero-pattern.tsx +1 -1
- package/docs/patterns/components-herotilestack--marketing-hero-text-sequence.tsx +1 -1
- package/docs/patterns/components-herotilestack--marketing-hero-with-gallery-intro.tsx +1 -1
- package/docs/patterns/components-indexlist--post-index.tsx +1 -1
- package/docs/patterns/components-intakeconfirmation--booked-pattern.tsx +1 -1
- package/docs/patterns/components-intakeconfirmation--emailed-pattern.tsx +1 -1
- package/docs/patterns/components-intakeform--about-you-pattern.tsx +1 -1
- package/docs/patterns/components-intakeform--short-form-pattern.tsx +1 -1
- package/docs/patterns/components-intakemodal--intake-modal-pattern.tsx +1 -1
- package/docs/patterns/components-kbd--command-row.tsx +1 -1
- package/docs/patterns/components-kbd--numbered-choices.tsx +1 -1
- package/docs/patterns/components-kbd--shortcut.tsx +1 -1
- package/docs/patterns/components-linktile--resource-tile.tsx +1 -1
- package/docs/patterns/components-linktile--router-link.tsx +1 -1
- package/docs/patterns/components-logview--detail-pane.tsx +80 -0
- package/docs/patterns/components-logview--log-viewer.tsx +20 -0
- package/docs/patterns/components-moremenu--action-menu.tsx +1 -1
- package/docs/patterns/components-moremenu--in-card-header.tsx +1 -1
- package/docs/patterns/components-navlist--app-sidebar.tsx +118 -0
- package/docs/patterns/components-navlist--side-nav-settings.tsx +1 -1
- package/docs/patterns/components-pageheader--app-header-pattern.tsx +1 -1
- package/docs/patterns/components-pageheader--page-section-header.tsx +1 -1
- package/docs/patterns/components-pageheader--toolbar-header.tsx +1 -1
- package/docs/patterns/components-panel--end-detail-rail.tsx +1 -1
- package/docs/patterns/components-pillgroup--pill-group-pattern.tsx +1 -1
- package/docs/patterns/components-promptbar--pattern-prompt-bar.tsx +1 -1
- package/docs/patterns/components-prose--article-body.tsx +1 -1
- package/docs/patterns/components-radio-radiogroup--default.tsx +1 -1
- package/docs/patterns/components-scrollhorizontal--project-gallery-pattern.tsx +1 -1
- package/docs/patterns/components-scrollhorizontal--with-intro.tsx +1 -1
- package/docs/patterns/components-sectioncaption--column-caption.tsx +1 -1
- package/docs/patterns/components-sectioncaption--with-action.tsx +1 -1
- package/docs/patterns/components-sheet--bottom-sheet.tsx +1 -1
- package/docs/patterns/components-sheet--end-sheet.tsx +1 -1
- package/docs/patterns/components-sheet--side-sheet-header-actions.tsx +50 -0
- package/docs/patterns/components-sheet--side-sheet-url-deep-link.tsx +51 -0
- package/docs/patterns/components-sheet--side-sheet.tsx +38 -0
- package/docs/patterns/components-sitenav--brand-and-cta.tsx +1 -1
- package/docs/patterns/components-sitenav--centered-links.tsx +1 -1
- package/docs/patterns/components-sitenav--compact.tsx +1 -1
- package/docs/patterns/components-sitenav--marketing-header.tsx +1 -1
- package/docs/patterns/components-sitenav--mega-menu.tsx +1 -1
- package/docs/patterns/components-stat--hero-stat.tsx +54 -0
- package/docs/patterns/components-stat--insights-row.tsx +1 -1
- package/docs/patterns/components-stepprogress--step-progress-pattern.tsx +1 -1
- package/docs/patterns/components-switch--default.tsx +1 -1
- package/docs/patterns/components-switch--settings-row.tsx +1 -1
- package/docs/patterns/components-tab--settings-pages.tsx +1 -1
- package/docs/patterns/components-table--approvals-with-sheet.tsx +81 -0
- package/docs/patterns/components-table--approvals.tsx +223 -0
- package/docs/patterns/components-table--basic.tsx +60 -0
- package/docs/patterns/components-taskrows--action-details.tsx +1 -1
- package/docs/patterns/components-taskrows--capsules.tsx +1 -1
- package/docs/patterns/components-taskrows--card-with-status-rows.tsx +1 -1
- package/docs/patterns/components-taskrows--controlled-expand.tsx +1 -1
- package/docs/patterns/components-taskrows--custom-slots.tsx +1 -1
- package/docs/patterns/components-taskrows--needs-you-table.tsx +62 -0
- package/docs/patterns/components-taskrows--status-list.tsx +1 -1
- package/docs/patterns/components-taskrows--tag-chips.tsx +1 -1
- package/docs/patterns/components-textarea--save-status.tsx +32 -0
- package/docs/patterns/components-textlink--external-destination.tsx +1 -1
- package/docs/patterns/components-textlink--inline-prose.tsx +1 -1
- package/docs/patterns/components-textlink--quiet-title.tsx +1 -1
- package/docs/patterns/components-textlink--supporting-copy.tsx +1 -1
- package/docs/patterns/components-textsequence--default.tsx +1 -1
- package/docs/patterns/components-textsequence--reduced-motion.tsx +1 -1
- package/docs/patterns/components-textsequence--shapes.tsx +1 -1
- package/docs/patterns/components-tilegrid--masonry.tsx +1 -1
- package/docs/patterns/components-toast--stacked-notifications.tsx +1 -1
- package/docs/patterns/components-tooltip--controlled.tsx +1 -1
- package/docs/patterns/components-tooltip--disabled-control.tsx +1 -1
- package/docs/patterns/components-tooltip--icon-action.tsx +1 -1
- package/docs/patterns/components-tooltip--keyboard-shortcut.tsx +1 -1
- package/docs/patterns/components-tooltip--placement.tsx +1 -1
- package/docs/patterns/foundations-scroll-fade--horizontal-rail.tsx +1 -1
- package/docs/patterns/foundations-scroll-fade--overflowing-list.tsx +1 -1
- package/docs/patterns/guides-chart-explorations--comparison-gallery.tsx +1 -1
- package/docs/patterns/guides-filter-panel--filtered-grid.tsx +1 -1
- package/docs/patterns/guides-filter-panel--filtered-index.tsx +1 -1
- package/docs/patterns/guides-overlay-flows--notification-preferences.tsx +1 -1
- package/docs/patterns/guides-post-page--post-page.tsx +1 -1
- package/docs/patterns/guides-profile-typography--editorial-profile.tsx +1 -1
- package/docs/patterns/guides-resource-detail--detail.tsx +1 -1
- package/docs/patterns/index.json +193 -1
- package/package.json +1 -1
- package/dist/components/organisms/Tab/tabOverflow.d.ts +0 -14
- package/dist/components/organisms/Tab/tabOverflow.d.ts.map +0 -1
package/dist/typography.css
CHANGED
|
@@ -89,6 +89,22 @@
|
|
|
89
89
|
--text-display-3-weight: var(--font-weight-semibold);
|
|
90
90
|
--text-display-3-leading: 1.24;
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Hero — the one oversized moment on a marketing page (a poster headline). Fluid to 16vw:
|
|
94
|
+
* 62px at 390, 230px at 1440. A brand kit may set `--text-display-hero-size` for its face
|
|
95
|
+
* (a condensed face carries more vw). Tight leading; long words break instead of overflowing.
|
|
96
|
+
*/
|
|
97
|
+
--text-display-hero-size: clamp(3.5rem, 16vw, 16rem);
|
|
98
|
+
--text-display-hero-weight: var(--font-weight-bold);
|
|
99
|
+
--text-display-hero-leading: 0.92;
|
|
100
|
+
--text-display-hero-tracking: -0.04em;
|
|
101
|
+
|
|
102
|
+
/** XL — big statement rows under a hero (section titles, price rows): 44px → 136px. */
|
|
103
|
+
--text-display-xl-size: clamp(2.75rem, 9vw, 8.5rem);
|
|
104
|
+
--text-display-xl-weight: var(--font-weight-bold);
|
|
105
|
+
--text-display-xl-leading: 0.9;
|
|
106
|
+
--text-display-xl-tracking: -0.03em;
|
|
107
|
+
|
|
92
108
|
/* Body & UI */
|
|
93
109
|
--text-large-size: var(--font-size-lg);
|
|
94
110
|
--text-large-weight: var(--font-weight-semibold);
|
|
@@ -143,27 +159,58 @@
|
|
|
143
159
|
--text-control-leading: 1;
|
|
144
160
|
}
|
|
145
161
|
|
|
146
|
-
/*
|
|
162
|
+
/*
|
|
163
|
+
* Semantic type utilities — size + weight + leading from tokens; color via text-fg / text-muted.
|
|
164
|
+
* Display steps read the brand-kit knobs (fonts.css) with the step's own value as the fallback.
|
|
165
|
+
* With no fallback (display-2/3 tracking, case), an unset knob leaves the inherited value alone.
|
|
166
|
+
*/
|
|
167
|
+
@utility type-display-hero {
|
|
168
|
+
font-family: var(--font-display, var(--font-family-heading));
|
|
169
|
+
font-size: var(--text-display-hero-size);
|
|
170
|
+
font-weight: var(--font-display-weight, var(--text-display-hero-weight));
|
|
171
|
+
line-height: var(--text-display-hero-leading);
|
|
172
|
+
letter-spacing: var(--font-display-tracking, var(--text-display-hero-tracking));
|
|
173
|
+
text-transform: var(--font-display-case);
|
|
174
|
+
overflow-wrap: break-word;
|
|
175
|
+
text-wrap: balance;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@utility type-display-xl {
|
|
179
|
+
font-family: var(--font-display, var(--font-family-heading));
|
|
180
|
+
font-size: var(--text-display-xl-size);
|
|
181
|
+
font-weight: var(--font-display-weight, var(--text-display-xl-weight));
|
|
182
|
+
line-height: var(--text-display-xl-leading);
|
|
183
|
+
letter-spacing: var(--font-display-tracking, var(--text-display-xl-tracking));
|
|
184
|
+
text-transform: var(--font-display-case);
|
|
185
|
+
overflow-wrap: break-word;
|
|
186
|
+
text-wrap: balance;
|
|
187
|
+
}
|
|
188
|
+
|
|
147
189
|
@utility type-display-1 {
|
|
148
|
-
font-family: var(--font-family-heading);
|
|
190
|
+
font-family: var(--font-display, var(--font-family-heading));
|
|
149
191
|
font-size: var(--text-display-1-size);
|
|
150
|
-
font-weight: var(--text-display-1-weight);
|
|
192
|
+
font-weight: var(--font-display-weight, var(--text-display-1-weight));
|
|
151
193
|
line-height: var(--text-display-1-leading);
|
|
152
|
-
letter-spacing: var(--text-display-1-tracking);
|
|
194
|
+
letter-spacing: var(--font-display-tracking, var(--text-display-1-tracking));
|
|
195
|
+
text-transform: var(--font-display-case);
|
|
153
196
|
}
|
|
154
197
|
|
|
155
198
|
@utility type-display-2 {
|
|
156
|
-
font-family: var(--font-family-heading);
|
|
199
|
+
font-family: var(--font-display, var(--font-family-heading));
|
|
157
200
|
font-size: var(--text-display-2-size);
|
|
158
|
-
font-weight: var(--text-display-2-weight);
|
|
201
|
+
font-weight: var(--font-display-weight, var(--text-display-2-weight));
|
|
159
202
|
line-height: var(--text-display-2-leading);
|
|
203
|
+
letter-spacing: var(--font-display-tracking);
|
|
204
|
+
text-transform: var(--font-display-case);
|
|
160
205
|
}
|
|
161
206
|
|
|
162
207
|
@utility type-display-3 {
|
|
163
|
-
font-family: var(--font-family-heading);
|
|
208
|
+
font-family: var(--font-display, var(--font-family-heading));
|
|
164
209
|
font-size: var(--text-display-3-size);
|
|
165
|
-
font-weight: var(--text-display-3-weight);
|
|
210
|
+
font-weight: var(--font-display-weight, var(--text-display-3-weight));
|
|
166
211
|
line-height: var(--text-display-3-leading);
|
|
212
|
+
letter-spacing: var(--font-display-tracking);
|
|
213
|
+
text-transform: var(--font-display-case);
|
|
167
214
|
}
|
|
168
215
|
|
|
169
216
|
@utility type-heading-1 {
|
package/docs/README.md
CHANGED
|
@@ -6,7 +6,7 @@ These files ship in the npm package at `node_modules/@thewhatmatters/wmds/docs/`
|
|
|
6
6
|
|------|------------|
|
|
7
7
|
| `exports.json` | Every export with its tier, category, one-line summary, Storybook page, and the ids of its patterns. Also the CSS entries, peer dependencies, planned components, and lib helpers. |
|
|
8
8
|
| `components.md` | The component and token contracts: props, patterns, composition rules, and what not to do. |
|
|
9
|
-
| `component-contracts.md` | Long-form contracts for FooterReveal, HeroTileStack, TextSequence, ScrollHorizontal, RiveHand, PromptBar, and
|
|
9
|
+
| `component-contracts.md` | Long-form contracts for FooterReveal, HeroTileStack, TextSequence, ScrollHorizontal, RiveHand, PromptBar, ChatDock, and AppShell (sidebar footer, mobile toggle name, server rendering with a Next.js App Router example). |
|
|
10
10
|
| `patterns/index.json` | One row per pattern: id, Storybook title, pattern name, file. |
|
|
11
11
|
| `patterns/<id>.tsx` | A pattern's Show code, exactly as Storybook shows it, with a header naming the pattern and the package version. |
|
|
12
12
|
| `consumer-agents.md` | A block to paste into a consuming repo's `AGENTS.md` / `CLAUDE.md`. |
|
|
@@ -42,3 +42,104 @@ The site assistant. At rest, a **PromptBar** pinned to the bottom of the page gr
|
|
|
42
42
|
|
|
43
43
|
The app owns `messages`, `thinking`, `followUps`, the votes, the gate's open state, `onSend`, and `onSuggestionSelect`; ChatDock never fetches or parses Markdown (`content` is text or rendered Markdown). `placement="inline"` is for previews only. Copy **Components/ChatDock → Pattern — chat dock**. See **ADR-0039**.
|
|
44
44
|
|
|
45
|
+
## AppShell
|
|
46
|
+
|
|
47
|
+
signed-in page chrome — a sidebar on `bg-body`, the page as an inset raised surface with a title bar (**PageHeader** `variant="app"`), the phone **Sheet**, and the optional aside **Panel**. See **ADR-0047**.
|
|
48
|
+
|
|
49
|
+
**Sidebar footer.** `sidebarFooter` (or **AppShell.SidebarFooter**) is pinned to the bottom of the sidebar column and of the phone **Sheet**. The **NavList** above it scrolls on its own, so a **DisplayControls** `controls="theme"` switch stays visible at 1440x900 and on a phone. It takes a 16px inset (`px-4`, like the brand row) and `py-3`; the slot adds no fill, border, or type. A choice inside the footer does not close the phone menu — only a link or button inside the `nav` does. The slot is not rendered when empty or when there is no `sidebar`.
|
|
50
|
+
|
|
51
|
+
**Mobile toggle name.** Below `lg` the title-bar toggle is a ghost **IconButton** whose accessible name is **"Menu"** (`labels.menu`), with `aria-haspopup="dialog"` and `aria-expanded`. The **Sheet** it opens has the same title. The desktop toggle is named "Collapse sidebar" / "Expand sidebar" (`labels.collapse`, `labels.expand`) and is hidden below `lg`. Keep "Menu" as the phone name — tests and screen reader users find the control by it. Translate it through `labels.menu`; do not rename it to a page or product name.
|
|
52
|
+
|
|
53
|
+
## AppShell — server rendering
|
|
54
|
+
|
|
55
|
+
**Which parts render on the server.** All of **AppShell** server-renders when it is imported from a client module: the sidebar column (brand row, **NavList**, `sidebarFooter`), the title bar with both toggles, and `main` with the page. The desktop and phone toggles are two buttons, one hidden by CSS per breakpoint, so the server HTML needs no viewport query. The phone **Sheet** and the aside **Panel** render nothing until they open (they portal on the client), so no dialog is in the server HTML. `sidebarOpen` and the aside state start from `defaultSidebarOpen` / `defaultAsideOpen`. **DisplayControls** renders its `auto` state on the server; `useSyncExternalStore` with a server snapshot of `"auto"` switches to the saved choice after hydration without a mismatch.
|
|
56
|
+
|
|
57
|
+
**What needs `"use client"`.** Every module in `@thewhatmatters/wmds` ships with the `"use client"` directive, so a Server Component can render the components but cannot call hooks or pass event handlers into them, and a non-component export (such as `displayThemeNoFlashScript`) is a client reference there, not its value. **AppShell** takes `onSidebarOpenChange`, `onAsideOpenChange`, **NavList** `onSelect`, and a **DisplayControls** `onThemeChange` — all functions — so the file that renders it is a `"use client"` module. Page content passed as `children` can stay a Server Component: a client component renders the server-rendered `children` it is given.
|
|
58
|
+
|
|
59
|
+
**The no-flash script goes in the head.** `displayThemeNoFlashScript` sets or clears `data-theme` on `<html>` before first paint. Render it in `<head>` before the stylesheet, from a client module that renders the `<script>` (below), and put `suppressHydrationWarning` on `<html>` because the script changes its attributes.
|
|
60
|
+
|
|
61
|
+
**Next.js App Router.**
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// app/theme-script.tsx — a client module: the package's exports are client references on the server
|
|
65
|
+
"use client";
|
|
66
|
+
|
|
67
|
+
import { displayThemeNoFlashScript } from "@thewhatmatters/wmds";
|
|
68
|
+
|
|
69
|
+
export function ThemeScript() {
|
|
70
|
+
return <script dangerouslySetInnerHTML={{ __html: displayThemeNoFlashScript }} />;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
// app/layout.tsx — a Server Component
|
|
76
|
+
import "@thewhatmatters/wmds/styles.css";
|
|
77
|
+
import type { ReactNode } from "react";
|
|
78
|
+
import { ThemeScript } from "./theme-script";
|
|
79
|
+
|
|
80
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
81
|
+
return (
|
|
82
|
+
<html lang="en" suppressHydrationWarning>
|
|
83
|
+
<head>
|
|
84
|
+
<ThemeScript />
|
|
85
|
+
</head>
|
|
86
|
+
<body>{children}</body>
|
|
87
|
+
</html>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// app/(portal)/portal-shell.tsx — the client boundary
|
|
94
|
+
"use client";
|
|
95
|
+
|
|
96
|
+
import { usePathname } from "next/navigation";
|
|
97
|
+
import { useSyncExternalStore, type ReactNode } from "react";
|
|
98
|
+
import {
|
|
99
|
+
AppShell,
|
|
100
|
+
DisplayControls,
|
|
101
|
+
NavList,
|
|
102
|
+
commitDisplayTheme,
|
|
103
|
+
readDisplayTheme,
|
|
104
|
+
subscribeDisplayTheme,
|
|
105
|
+
type DisplayControlThemeMode,
|
|
106
|
+
} from "@thewhatmatters/wmds";
|
|
107
|
+
|
|
108
|
+
function themeServerSnapshot(): DisplayControlThemeMode {
|
|
109
|
+
return "auto";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function PortalShell({ children }: { children: ReactNode }) {
|
|
113
|
+
const pathname = usePathname();
|
|
114
|
+
const theme = useSyncExternalStore(subscribeDisplayTheme, readDisplayTheme, themeServerSnapshot);
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<AppShell
|
|
118
|
+
title="Dashboard"
|
|
119
|
+
sidebar={
|
|
120
|
+
<NavList
|
|
121
|
+
aria-label="Portal"
|
|
122
|
+
className="w-full"
|
|
123
|
+
activeId={pathname}
|
|
124
|
+
sections={[{ items: [{ id: "/", label: "Dashboard", href: "/" }] }]}
|
|
125
|
+
/>
|
|
126
|
+
}
|
|
127
|
+
sidebarFooter={
|
|
128
|
+
<DisplayControls controls="theme" aria-label="Appearance" theme={theme} onThemeChange={commitDisplayTheme} />
|
|
129
|
+
}
|
|
130
|
+
>
|
|
131
|
+
{children}
|
|
132
|
+
</AppShell>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
// app/(portal)/layout.tsx — a Server Component; its pages stay Server Components
|
|
139
|
+
import type { ReactNode } from "react";
|
|
140
|
+
import { PortalShell } from "./portal-shell";
|
|
141
|
+
|
|
142
|
+
export default function PortalLayout({ children }: { children: ReactNode }) {
|
|
143
|
+
return <PortalShell>{children}</PortalShell>;
|
|
144
|
+
}
|
|
145
|
+
```
|
package/docs/components.md
CHANGED
|
@@ -1,55 +1,61 @@
|
|
|
1
1
|
# @thewhatmatters/wmds — component contracts
|
|
2
2
|
|
|
3
|
-
Version 0.4.
|
|
3
|
+
Version 0.4.28. Generated from the WMDS repository's AGENTS.md (Storybook-first) by `scripts/generate-package-docs.mjs` — do not edit here.
|
|
4
4
|
|
|
5
5
|
Storybook paths such as **Components/Button/Button → Pattern — …** name a story; its Show code is in `patterns/` (see `patterns/index.json`). Longer contracts for FooterReveal, HeroTileStack, TextSequence, ScrollHorizontal, RiveHand, and PromptBar are in `component-contracts.md`. ADR numbers refer to decision records in the WMDS repository.
|
|
6
6
|
|
|
7
7
|
- **Theme** lives in `src/theme/` — `colors.css` (semantic roles) + `theme.css` (Tailwind `@theme` bridge). Storybook is the source of truth, not Paper.
|
|
8
8
|
- **Dark mode:** `[data-theme="dark"]` on `<html>` or any ancestor — same token names, swapped values in `colors.css`.
|
|
9
9
|
- **Consuming apps:** use exported components and typed props; `className` for layout only — not re-theming. Page layout utilities (`gap`, `grid`, `max-w`) are fine between components.
|
|
10
|
-
- **Theme tokens:** Semantic roles in **`src/theme/colors.css`**; typography in **`src/theme/typography.css`** (`type-*` utilities, geometric scale; **display-1/2/3** are fluid via `clamp` — display-1 hits **5rem** on desktop and scales down to **2.5rem** on mobile); motion in **`src/theme/motion.css`**; page spine in **`src/theme/grid.css`** (`--grid-*`, `grid-page` / `band`, `--leading-base`). **`--spacing` stays 4px** — do not re-scale; baseline is 8px. See **ADR-0007**, **ADR-0008**, **ADR-0009**, **ADR-0010**, **DESIGN.md → Grid**. Use **`typographyClass()`** — not raw `text-sm` in document copy. **`type-eyebrow`** (`typographyClass("eyebrow")`) is the small uppercase mono caption for editorial labels — **SectionCaption**, **IndexList** captions, **DescriptionList** `variant="mono"` names; write the text in sentence case — the "/" marker comes from the component, `aria-hidden`. Not for app UI section labels (`overline`) or secondary text (`caption`). **`type-caption`** (`typographyClass("caption-tight")`, 12px on a 16px line) is a single line directly under a label — **LinkTile**'s meta line only, for now; never wrapped text, which stays on `caption` (`type-supporting`, 12px on 20px). **`type-reading`** (`typographyClass("reading")`, 17px at a 28px line) is long-form article text — **Prose** sets it; UI copy stays on `type-body`. A dotted hairline is **DescriptionList** `rule="dotted"`. See **ADR-0040**, **ADR-0041**.
|
|
10
|
+
- **Theme tokens:** Semantic roles in **`src/theme/colors.css`**; typography in **`src/theme/typography.css`** (`type-*` utilities, geometric scale; **display-1/2/3** are fluid via `clamp` — display-1 hits **5rem** on desktop and scales down to **2.5rem** on mobile; above them **`type-display-hero`** (the one oversized moment, to 16vw) and **`type-display-xl`** (statement rows); every display step reads the brand-kit knobs `--font-display*`, CONSUMING.md → Brand kit); motion in **`src/theme/motion.css`**; page spine in **`src/theme/grid.css`** (`--grid-*`, `grid-page` / `band`, `--leading-base`). **`--spacing` stays 4px** — do not re-scale; baseline is 8px. See **ADR-0007**, **ADR-0008**, **ADR-0009**, **ADR-0010**, **DESIGN.md → Grid**. Use **`typographyClass()`** — not raw `text-sm` in document copy. **`type-eyebrow`** (`typographyClass("eyebrow")`) is the small uppercase mono caption for editorial labels — **SectionCaption**, **IndexList** captions, **DescriptionList** `variant="mono"` names; write the text in sentence case — the "/" marker comes from the component, `aria-hidden`. Not for app UI section labels (`overline`) or secondary text (`caption`). **`type-caption`** (`typographyClass("caption-tight")`, 12px on a 16px line) is a single line directly under a label — **LinkTile**'s meta line only, for now; never wrapped text, which stays on `caption` (`type-supporting`, 12px on 20px). **`type-reading`** (`typographyClass("reading")`, 17px at a 28px line) is long-form article text — **Prose** sets it; UI copy stays on `type-body`. A dotted hairline is **DescriptionList** `rule="dotted"`. See **ADR-0040**, **ADR-0041**.
|
|
11
11
|
- **Page layout:** Every Storybook **Guides** page and consuming product page starts on **`grid-page`**; use **`band`** + responsive column spans for content alignment. Constrain a specific experience by overriding **`--grid-max`** on that wrapper (for example, `960px`) rather than replacing the grid with an arbitrary centered container.
|
|
12
12
|
- **Scroll fade:** use **`scroll-fade-y`** / **`scroll-fade-x`** on the element that owns overflow to hint at clipped list or rail content. The mask is scroll-aware, disappears when no overflow exists in supporting browsers, and follows inline direction for RTL. Keep the surface fill/border on a wrapper; customize with `--scroll-fade-size` only when the default 12% / 40px cap does not fit. Copy **Foundations → Scroll fade → Pattern — …**.
|
|
13
13
|
- **Motion:** Duration tiers in **`src/theme/motion.css`** — **fast** (hover/focus), **medium** (panels/layout), **slow** (rare). Single **`--ease-standard`**. CSS via `motionTransition()`; Motion via `motionTransitionProp()`. Choreography tokens `--motion-stagger`, `--motion-beat`, `--motion-blur-reveal` through `motionStaggerSeconds()`, `motionBeatSeconds(n)`, `motionBlurReveal(reduce)`, and `readMotionDurationSeconds(tier)` — Show code never hard-codes a blur, stagger, or duration. See **ADR-0008**. Storybook wraps `<MotionConfig reducedMotion="user">`.
|
|
14
14
|
- **Icons:** **[Lucide](https://lucide.dev/icons/) only** until further notice — import from **`lucide-react`** in components, stories, and examples. Peer dependency, not bundled; use `stroke-current` / `ButtonIcon` for sizing. See **Foundations → Icons**.
|
|
15
|
-
- **Button:** pill by default; **`layout="row"`** for flat full-width detail/settings lines (**`width="hug"`** sizes a row to its content with a tight gap — inline disclosure triggers); **`align="start"`** left-aligns a pill label and **`emphasis="quiet"`** puts `role="outline"` on the `border-border` hairline (full-width suggestion pills — **Pattern — suggestion pills**); **`layout="nav"`** for **NavList** rows (`selected`).
|
|
15
|
+
- **Button:** pill by default; **`layout="row"`** for flat full-width detail/settings lines (**`width="hug"`** sizes a row to its content with a tight gap — inline disclosure triggers); **`align="start"`** left-aligns a pill label and **`emphasis="quiet"`** puts `role="outline"` on the `border-border` hairline (full-width suggestion pills — **Pattern — suggestion pills**); **`layout="nav"`** for **NavList** rows (`selected`; `render` makes the row a link, the same as a pill). Seven **roles** (`primary`, `secondary`, `ghost`, `destructive`, **`inverse`** — surface fill and brand text, for a brand-blue field, **`outline`** — hairline border and transparent fill, **`inverse-outline`** — on-brand hairline and transparent fill, for that same navy field). Patterns: action, **`row`**, **`nav`**, `icon`, `count`, `status` morph, **`mono`** + **`endIcon`** (accent arrow square) — mutually exclusive where documented. **`status` with `icon`**: the icon morphs into the status glyph — **Pattern — copy button** (`icon={<Copy />}`, `statusLabels`, hold with **`buttonStatusHoldMs`**; the pill's live region announces the result). **`external`** with `render={<a href />}`: new tab, safe `rel`, spoken "(opens in a new tab)", **TextLink**'s trailing icon (`externalIcon={false}` drops it) — **Pattern — external link**; never hand-write `target` or the new-tab text. The shell sets its own case, so a caps-styled parent does not capitalize a label. No slots. [Motion reference](https://motion.dev/examples/react-multi-state-badge).
|
|
16
16
|
- **Avatar:** circular identity — required `name`, optional `src`, initials → **User** fallback; sizes `xsm` / `sm` / `md` (default) / `lg` / `xl` (20–128px). Optional `presence={{ tone, label, pulsing? }}` — rim dot via **Status** dot tokens (not the **Status** component). **`avatarSizeForCluster()`** for header rows; **`size="sm"`** in **Dropdown.Item** `start`. Not clickable — wrap at call site. Agents: copy **Components/Avatar → Pattern — …**; see **ADR-0017**.
|
|
17
17
|
- **IconButton:** circular icon-only control. Patterns: toolbar (ghost), `fab`, `loading`, native `title`, **`inset`** (compact dismiss inside Chip), **`render={<a/>}`** (circular brand / icon links), **`pressed`** (toggle — `aria-pressed`, pressed fill and a filled glyph while on; votes, stars). Compose **Tooltip** for tokenized hover/focus labels and pass `title=""` to avoid duplicate native UI. **`aria-label` required** — use `Button` when text is needed.
|
|
18
18
|
- **Kbd:** semantic keyboard keycap — one physical key per **Kbd**; compose multiple keycaps for shortcuts. Sizes `sm` | `md`; add `aria-label` when symbolic labels need spoken expansion. Display-only — never interactive on the keycap itself. Numbered choice rows pair trailing **Kbd** digits with **`useKbdChoiceKeys`** so `1`–`9` select the same way a click does while focus is not in text entry (a focused checkbox or radio still takes digits); pass `scope` (a ref) to act only while focus is inside that element, as in a chat gate (see **Pattern — numbered choice keys**). Copy **Components/Kbd → Pattern — keyboard shortcut**.
|
|
19
19
|
- **TextLink:** inline anchor for navigation — `variant="prose"` (default) inside prose: canonical solid underline, stronger hover decoration, and focus ring. **`variant="quiet"`** for headline-size text and list titles: no underline at rest, an underline scaled to the text on hover and focus, the same focus ring; inherits size and weight from the heading around it — never inside running prose. `external` opens a new tab and appends Lucide **SquareArrowOutUpRight** with spoken context. `render={<Link href />}` in place of `href` composes the link onto the app's router link. Inherits surrounding type size; `className` is layout only. Use **Button** for actions. Copy **Components/TextLink → Pattern — inline prose link** or **Pattern — quiet title link**.
|
|
20
20
|
- **Tooltip:** short supplemental label — **`Tooltip.Provider`** shares hover delay; **`Tooltip.Trigger render={...}`** composes a ref/DOM-prop-forwarding control; **`Tooltip.Content`** portals with collision-aware physical or logical placement. Opens on hover/focus, closes with Escape, and does not open on touch. No essential or interactive content. Disabled native controls need the documented focusable wrapper. Copy **Components/Tooltip → Pattern — …**.
|
|
21
|
-
- **Badge:** solid, **muted**, and **outline** (hairline, transparent fill) semantic fills. Patterns: label, **`emphasis="muted"`**, **`emphasis="outline"`** + **`mono`** (mono caps, as **Button** `mono` — author and category tags), **`render={<a href />}`** (a tag that navigates: hover underline, focus ring, 44px-tall hit area — **Pattern — outline tags**), `count`, `icon`, **`avatar`**, **`iconOnly`** — mutually exclusive where documented. Actions stay on **Button**. **`avatar={{ src, alt }}`** renders **Avatar** at the badge size (`badgeAvatarSize`: sm → 20px, md → 24px). **`BadgeSegmentCount`** for filter/tab segment totals (Chip trailing count).
|
|
21
|
+
- **Badge:** solid, **muted**, and **outline** (hairline, transparent fill) semantic fills. Patterns: label, **`emphasis="muted"`**, **`emphasis="outline"`** + **`mono`** (mono caps, as **Button** `mono` — author and category tags), **`render={<a href />}`** (a tag that navigates: hover underline, focus ring, 44px-tall hit area — **Pattern — outline tags**), `count`, `icon`, **`avatar`**, **`iconOnly`**, **`status`** — mutually exclusive where documented. **`status`** (`active` | `idle` | `success` | `failed` | `warning`) is the pill for run state, cadence ("Every 2 days"), and freshness ("Last checked 1d ago"): cluster height (28 / 36px), tabular figures, muted fills. `active` is `bg-brand-tint` and `text-brand-outline` (navy in light, the lifted navy in dark). **`dot`** is an 8px mark on that pill and pulses when `active` (reduced motion holds still). **`icon`** leads cadence and freshness. Paste **Components/Badge → Pattern — status pill**. Actions stay on **Button**. **`avatar={{ src, alt }}`** renders **Avatar** at the badge size (`badgeAvatarSize`: sm → 20px, md → 24px). **`BadgeSegmentCount`** for filter/tab segment totals (Chip trailing count). Do not nest **Status** inside Badge — a list row uses **Status** `variant="dot"` beside the label; the pill paints its own dot. See **ADR-0033**.
|
|
22
22
|
- **Chip:** filter/toggle molecule — composes **`BadgeIcon`** / **`ButtonIcon`**, **`IconButton inset`** (remove), **`BadgeSegmentCount`** (count). Patterns: **multi-select filter** (`ChipFilterGroup` + `value`), single-select group, standalone `selected`, **removable** (`onRemove`), **read-only**. Sizes `sm` | `md` | `lg`. Optional `icon`, `count` — not with `onRemove`. No slots.
|
|
23
23
|
- **SegmentedControl:** connected **segmented control** — one track, Motion sliding thumb, mutually exclusive **`SegmentedControl.Item`** segments (`role="radiogroup"`). Patterns: **shape/settings toggle** (`layout="hug"`), **view switcher** (`layout="stretch"`). Sizes **`sm` | `md` | `lg`** — cluster tiers (28 / 36 / 44px), **1:1 with Chip**; pair **Button** via `buttonSizeForCluster()` and **IconButton** via `iconButtonSizeForCluster()`. Not multi-select — use **ChipFilterGroup**; not many options — use **Select**. **Badge** `sm`/`md` is inline label scale, not cluster header pairing.
|
|
24
|
-
- **DisplayControls:** compact page-utility molecule — controlled grid visibility + theme mode (`auto` | `light` | `dark`); composes **Button** + **Kbd**. Global **G** toggles grid and **T** cycles theme. When paired with **GridOverlay**, set `keyboardShortcut={false}` on the overlay. `className` is placement only. Copy **Components/DisplayControls → Pattern — page display controls**.
|
|
24
|
+
- **DisplayControls:** compact page-utility molecule — controlled grid visibility + theme mode (`auto` | `light` | `dark`); composes **Button** + **Kbd**. Global **G** toggles grid and **T** cycles theme. `controls="theme"` is the appearance switch (no grid button, and `G` is not captured). `commitDisplayTheme` saves the choice under `wmds-theme` and sets or clears `data-theme` (`light` clears it, `dark` sets it, `auto` follows `prefers-color-scheme`). Read it with `useSyncExternalStore(subscribeDisplayTheme, readDisplayTheme, …)`. Paste `displayThemeNoFlashScript` in `<head>` before the stylesheet so the first paint matches. When paired with **GridOverlay**, set `keyboardShortcut={false}` on the overlay. `className` is placement only. Copy **Components/DisplayControls → Pattern — page display controls** or **Pattern — theme switch**.
|
|
25
25
|
- **PageHeader:** horizontal page chrome — `start` | `title` | `end`; **`variant="app"`** (56px page band + hairline), **`page`** (section `h2`), **`toolbar`** (`role="toolbar"`, cluster row). Pair **SegmentedControl** / **Button** / **Avatar** at one cluster tier. Not **Card.Header**. See **ADR-0018**.
|
|
26
26
|
- **FooterReveal:** marketing page root — **FooterReveal.Content** covers a sticky **FooterReveal.Footer**, which sharpens from blur as the cover scrolls away. **FooterReveal.Brand** is the navy field (`footerRevealFieldClasses`); **FooterReveal.Ruled** is the cream ruled footer (`footerRevealRuledFieldClasses`). Neither ships content defaults: pass the wordmark, headline, CTA label, links, and email. Reduced motion renders it sharp, including SSR. Paste **Components/FooterReveal → Pattern — marketing page**, **Pattern — marketing hero**, or **Pattern — ruled grid footer**. See **ADR-0030**. Full contract: **docs/component-contracts.md → FooterReveal**.
|
|
27
27
|
- **HeroTileStack:** marketing hero image fan — `tiles`; a fine pointer pushes cards away from the pointer, a coarse pointer scatters once on tap, reduced motion keeps the resting fan. **HeroIntro** is the page `h1` on `type-display-2` and sits on `grid-page`. Paste **Components/HeroTileStack → Pattern — marketing hero**. See **ADR-0032**, **ADR-0034**, **ADR-0035**. Full contract: **docs/component-contracts.md → HeroTileStack**.
|
|
28
28
|
- **TextSequence:** masked word reveal — children mix text and **`TextSequence.Shape`**; `trigger` (`mount` \| `inView`), `emphasis`, `idle`, `lines`, `stagger`, `delay`. Parent owns the type step; `className` is layout only. The only component that imports GSAP (**ADR-0037**) — the rest of WMDS stays on `motion/react`. Reduced motion and the server render the rest state. Copy **Components/TextSequence**. Full contract: **docs/component-contracts.md → TextSequence**.
|
|
29
29
|
- **ScrollHorizontal:** marketing gallery directly under the hero — `items`, optional `heading`, `intro` (**`ScrollHorizontal.Intro`**), `expandLast`, `expanded`; `className` on the root only. A sticky window translates the row from the first card to the last; `expandLast` then grows the last tile to full bleed. Reduced motion is a native horizontal scroller. Paste **Components/ScrollHorizontal → Pattern — project gallery**. See **ADR-0036**. Full contract: **docs/component-contracts.md → ScrollHorizontal**.
|
|
30
|
-
- **Carousel:** a row of items to drag, scroll, or step through, with a progress scrubber under it — **Carousel.Item** children (any content; direct children, one per item) and a required `aria-label` or `aria-labelledby` (do not give a wrapping `section` the same name). The row is a native scroller: touch, trackpad, and shift-wheel move it; a mouse drags it, with a glide and a soft stop at each end; it does not loop. `itemWidth` is a percentage of the row — one number or `{ base, sm, md, lg }` (default `{ base: 80, sm: 55, lg: 40 }`; keep it under 100 so the next item shows); the gap is the grid gutter. `snap` (default on) rests on an item's leading edge. `bleed` runs the row to the page edges while items still start on the grid. `fade` (default on) fades clipped items the way `scroll-fade-x` does. `progress` is `center` (default) / `start` (a shorter track, full width on phones) / `full` / `none`: the filled part's place is the row's progress and its width the share of the row in view; drag it, or press the track; 44px hit area; it hides itself, keeping its space, while every item fits. Keyboard: the row is the tab stop — Left and Right move one item, Home and End go to the ends (arrows swap in RTL); Tab reaches links inside items and scrolls them into view. The scrubber is `aria-hidden` and not a tab stop. Each item is a group named by its place ("2 of 4"; `labels.item`). A drag never clicks a link inside an item. Reduced motion: no glide; moves are immediate. Server render: the row at its start, the scrubber at zero. No previous / next buttons. `className` is layout only. Not a gallery driven by page scroll (**ScrollHorizontal**). Paste **Components/Carousel → Pattern — image carousel**. See **ADR-0043**.
|
|
30
|
+
- **Carousel:** a row of items to drag, scroll, or step through, with a progress scrubber under it — **Carousel.Item** children (any content; direct children, one per item) and a required `aria-label` or `aria-labelledby` (do not give a wrapping `section` the same name). The row is a native scroller: touch, trackpad, and shift-wheel move it; a mouse drags it, with a glide and a soft stop at each end; it does not loop. `itemWidth` is a percentage of the row — one number or `{ base, sm, md, lg }` (default `{ base: 80, sm: 55, lg: 40 }`; keep it under 100 so the next item shows); the gap is the grid gutter. `snap` (default on) rests on an item's leading edge. `bleed` runs the row to the page edges while items still start on the grid. `fade` (default on) fades clipped items the way `scroll-fade-x` does. `progress` is `center` (default) / `start` (a shorter track, full width on phones) / `full` / `none`: the filled part's place is the row's progress and its width the share of the row in view; drag it, or press the track; 44px hit area; it hides itself, keeping its space, while every item fits. Keyboard: the row is the tab stop — Left and Right move one item, Home and End go to the ends (arrows swap in RTL); Tab reaches links inside items and scrolls them into view. The scrubber is `aria-hidden` and not a tab stop. Each item is a group named by its place ("2 of 4"; `labels.item`). A drag never clicks a link inside an item. Reduced motion: no glide; moves are immediate. Server render: the row at its start, the scrubber at zero. No previous / next buttons. **`counter`** (default off) adds a visible "1 of 6" (`labels.counter`; polite live region; hidden while every item fits); **`onIndexChange(index, count)`** (0-based, the resting item) feeds a count shown elsewhere; **`activeIndex`** moves the row to an item. `className` is layout only. Not a gallery driven by page scroll (**ScrollHorizontal**). Paste **Components/Carousel → Pattern — image carousel**. See **ADR-0043**.
|
|
31
31
|
- **RiveHand:** decorative hand on the marketing headline (`@rive-app/react-canvas`) — `hand` (`point` \| `rock`), `size`, `inline`, `active`, `idle`, `entrance`; `aria-hidden` by default. Apps serve two files beside each other: `/rive/interactive-icon-set.riv` and `/rive/rive.wasm` (`riveHandWasmSrc`, copied from `node_modules/@rive-app/canvas/rive.wasm`). Reduced motion draws one static frame. Paste **Components/HeroTileStack → Pattern — marketing hero**. See **ADR-0034**. Full contract: **docs/component-contracts.md → RiveHand**.
|
|
32
32
|
- **SiteNav:** marketing site header — `start` | `middle` | `end` slots; **page chrome** (`placement="fixed"`, optional `scrollContainer`) vs **specimen** (`placement="inline"`); expanded is **in flow** (scrolls away); after scroll past `collapseAt` (default **50% of scrollport**) a **narrower hug pill pins 1rem from the top** (`compactLayout="grid"` optional); mega panel stays **grid-wide** above a light dim; mega body: **SiteNav.Featured** / **ReadRow** / **MenuMedia** / **MenuLinkGrid** (paste **Pattern — mega menu**); below `md` mega sections stack; **SiteNav.Links** / **Link** / **Menu** / **MenuSection** / **MenuLink** (Base UI NavigationMenu) with Tab-style **More** overflow when the middle track is tight; `mobile` → **Sheet** `side="end"`. Compose **Button** `render={<a/>}` for anchors. Brand: circular **IconButton** via `icon` + `aria-label` (or wordmark **Button**). Not app chrome. See **ADR-0028**.
|
|
33
33
|
- **Breadcrumb:** the path to the current page — `items` (`{ label, href }` from the root; the last, without `href`, is the current page, `aria-current="page"`). Above `maxItems` (default 4, min 3) the first item and the last `maxItems - 2` stay and the middle folds into a "…" **IconButton** that opens a **Dropdown.Menu** of links — a disclosure (focus to the first link, arrows move, Tab leaves, Escape returns focus), not an ARIA menu; the same on phones. Long labels truncate (full label in the name and `title`). `separator` `chevron` (default) / `slash`; `variant` `sans` (body) / `mono` (eyebrow caps — editorial pages, beside **SectionCaption**). `renderLink={(item) => <Link href={item.href} />}` routes every crumb and menu link. `className` is layout only. Not main navigation (**SiteNav**) or tabs (**Tab**). Paste **Components/Breadcrumb → Pattern — path**, **Pattern — long path**, **Pattern — router links**, or **Pattern — editorial**. See **ADR-0042**.
|
|
34
|
-
- **NavList:** sectioned secondary nav — **`sections`** + **`activeId`** / **`onSelect`**, or **NavList.Section** + **NavList.Item**. Inset pills via **Button** `layout="nav"` + **ButtonIcon**; optional counts; **`labelAlign="brand"`** reserves a 56px first-section band. Not primary navigation. See **ADR-0019**.
|
|
34
|
+
- **NavList:** sectioned secondary nav — **`sections`** + **`activeId`** / **`onSelect`**, or **NavList.Section** + **NavList.Item**. Inset pills via **Button** `layout="nav"` + **ButtonIcon**; optional counts (tabular); **`labelAlign="brand"`** reserves a 56px first-section band. Item **`href`** or **`render={<Link href />}`** makes the row an anchor (`render` wins if both are set) so a server-rendered app navigates without an `onSelect` island. **`trailing`** is a **Status** dot or a **Badge** `status` pill. Section **`action`** is an **IconButton** (filter or more). Paste **Components/NavList → Pattern — app sidebar** for the brand row, **Search**, sections, footer slot, and the phone form (**PageHeader** `variant="app"` menu **IconButton** + **Sheet** `side="start"`). Not primary navigation. See **ADR-0019**.
|
|
35
|
+
- **AppShell:** signed-in page chrome — sidebar on `bg-body`, the page as an inset raised surface with a title bar. `sidebar` scrolls; **`sidebarFooter`** / **AppShell.SidebarFooter** is pinned under it in the column and the phone **Sheet** (a **DisplayControls** `controls="theme"` switch). The phone toggle's accessible name is **"Menu"** (`labels.menu`) — keep it. Render it from a `"use client"` module; it server-renders in full, the **Sheet** and **Panel** wait for open, and `displayThemeNoFlashScript` goes in `<head>` from a client module. Paste **Components/AppShell → Pattern — portal dashboard**. See **ADR-0047**. Full contract: **docs/component-contracts.md → AppShell**.
|
|
35
36
|
- **FloatingActionButton:** compact vertical action menu — `items` (2–4) + `onAction`; optional trigger/open icons, labels, and backdrop. Composes **IconButton** for trigger/actions; Motion `offsetPath` opens straight above the trigger; visible labels stay beside every icon. `className` is placement only. See **ADR-0024**.
|
|
36
37
|
- **Tab:** responsive peer-page/view navigation — **Tab.Group** + **Tab** (`tablist` / `tab`); inactive labels are regular-weight `text-muted`, active labels are medium-weight `text-fg` with the shared Motion bottom rule, and the muted pill fill is hover-only; optional `count`. Width overflow reserves a **More** Dropdown; selecting a hidden tab promotes it into the final visible slot before More. Not **SegmentedControl** (settings choice) or **Chip** (filter). See **ADR-0022**.
|
|
37
|
-
- **
|
|
38
|
-
- **
|
|
38
|
+
- **Table:** semantic table in a horizontal-scroll box — `Table` (a focusable `region` named by `aria-label` / `aria-labelledby`, around the `table`), `TableCaption`, `TableHeader`, `TableBody`, `TableFooter`, `TableRow`, `TableHead` (`scope="col"`; `scope="row"` for a row header), `TableCell`. `align` `start` / `center` / `end` (`end` sets tabular figures) on a head or cell. The table never squeezes below its content width; a phone scrolls it sideways. Status goes in a **Badge** `status` pill and actions in **Button** — the cells add no style. `className` is layout only. Not a list of facts (**DescriptionList**) or expandable task rows (**TaskRows**). Paste **Components/Table → Pattern — basic table** or **Pattern — approvals queue** (Approve `primary`, Deny `outline`; Deny swaps the row's actions for Confirm deny and Cancel in place — no dialog; focus follows, Escape cancels; a decided row shows a success or failed pill; every button's `aria-label` includes the row name). See **ADR-0048**.
|
|
39
|
+
- **Stat:** at-a-glance metric tile — required `label`, `value`; optional `trend` (muted **Badge**; `direction` `up` | `down` | `neutral` | `flat`), `context` (visible secondary line, tabular — "vs $14,850" or "of $1M goal"; `trend.label` stays a tooltip), `detail` (one tabular line under the value — "3 new"; hidden while loading or unavailable), `icon` (leading Lucide glyph in a brand-tint tile via **ButtonIcon**), `action` (**Button** `size="sm"` in the label row), `unavailable` (`{ reason, label? }` — replaces the figure with muted "Not connected" and the reason; hides `trend`, `context`, and `detail`), `start` / `end`, `loading`. **`Stat.Group`** — responsive grid (`columns` 2–4, 2-up mobile). **`size="sm"`** for insights rows; **`size="md"`** for a KPI above charts; **`size="lg"`** for the hero figure (`type-display-3`, tabular numbers, sentence-case label). Paste **Components/Stat → Pattern — hero stat**. Inline mono trend (`chartKpiTrend*`) stays on **Chart**-paired KPIs — not **Badge**. Section heading + actions → **PageHeader** `variant="page"`.
|
|
40
|
+
- **TaskRows:** expandable task rows — composes **Accordion** for disclosure; adds status (**Badge**, **Status**), **meta**, detail rails, and **TaskRows.Detail**. Patterns: **status rows**; **capsules**; **action details** (`detailsLayout="actions"` + `TaskRows.Detail variant="button"` → **Button** `size="xs"`); **tag chips** (`detailsLayout="chips"` + read-only **Chip** `size="sm"`); **detail lines** (`TaskRows.Detail` + `onPress` → **Button** `layout="row"`). **`variant="table"`** is the needs-you table: `columns` (`id` `title` | `waiting` | `metric`, `header`, `align`, `width`), **`TaskRows.Row`** (`title`, `href` or `render`, `status` dot + label, `waiting` pill + detail, `metric` tabular value). `source` `loading` | `error` | `unwired` (footer `note`). No rows shows `empty` ("Nothing waiting"). Below `md` a row is two lines. Optional **Card.Body**.
|
|
39
41
|
- **Accordion:** generic expand/collapse — **leading**, **label**, **trailing**, built-in chevron, `motion-collapse` panel. Variants: `list`, `capsule`, `plain`; **`flush`** (plain only) drops the inset so labels line up with surrounding text — filter side panels. A closed panel turns `visibility: hidden` once it folds, so Tab skips its controls. **TaskRows** is the task-status pattern on top.
|
|
40
42
|
- **DescriptionList:** a list of facts — `dl` > **DescriptionList.Item** (`div` > `dt` `name` + `dd` value). `layout` `inline` (value beside the name, 2 : 3 tracks, default) or `stacked` (value under it — tags, actions), per list or per item. `rule` `solid` (default) / `dotted` (quieter — beside caption rules) / `none`. `variant` `sans` (caption name, body value) or `mono` (eyebrow name, mono caps value, tabular figures — post metadata). Values take text, `<time>`, **Badge** tags, or **Button** actions; several wrap in a row. Not form fields (**Field**), metrics (**Stat**), or status rows (**TaskRows**). Paste **Components/DescriptionList → Pattern — post metadata** or **Pattern — details**. See **ADR-0041**.
|
|
41
43
|
- **SectionCaption:** the small uppercase mono caption over a page column ("/ Metadata", "/ Article", "/ Filters") — `type-eyebrow` on a `border-border-emphasized` rule; the "/" `marker` is `aria-hidden`. `as` `h2` (default — pair `id` with the region's `aria-labelledby`) or `p` (labels a column that has its own heading); `end` holds a **Button** `size="xs"` (Clear all) without growing the row; `rule` / `marker` can be turned off. Write `children` in sentence case. Copy **Components/SectionCaption → Pattern — column caption**. See **ADR-0041**.
|
|
44
|
+
- **Band:** a full-bleed page section in a brand-kit tone. `tone` `default` / `inverse` (the brand surface, `--color-brand-surface`; also `data-theme="dark"`) / `field` (the saturated section colour, `--color-field`) sets `data-tone`, which remaps the surface, text, border and fill roles so every component inside re-skins; never re-colour what's inside with `className`. It holds its own `grid-page` on the shared `--grid-*` tokens: standing alone it's a full-width section; inside a `grid-page` it spans every column, paints to the viewport edges (border-image outset, no scrollbar) and its page becomes a subgrid, so its columns stay the page's. `spacing` `md` (48px, default) / `lg` (96px) / `none`; `as` `section` (default) / `div` / `header` / `footer` / `aside`. Status colours aren't remapped on `field`. Paste **Components/Band → Pattern — page in bands**; the brand kit and its contrast check are **Foundations/Brand kit → Theme a client**.
|
|
45
|
+
- **InlinePill:** a key word inside running text (a display headline, a lead line) in a soft pill that scales with the text (em-sized), with an optional decorative `media` picture on its left. Pop the pictures in with `useInlinePillsPopIn()` spread on the heading. Not a control: use `Chip` for filters and tokens, `Badge` for status. Keep 2–3 pills of one or two words per sentence.
|
|
42
46
|
- **Prose:** long-form content set from plain HTML elements (rendered markdown) — headings, paragraphs, lists, quotes, inline code, code blocks, rules, images, figures, and tables, with the spacing between them. `size` `lg` (default — `type-reading`, headings a step up) or `md` (`type-body`, denser notes); `measure` `reading` (40rem, default) or `none`; `as` `div` / `article` / `section`. Wide tables scroll in their own box. Map markdown `#` to `h2` under a page `h1`, and links to **TextLink** (`external` for other sites). Never add classes to the elements inside. Paste **Components/Prose → Pattern — article body**; the full page is **Guides/Post page → Pattern — post page** (`metadataSide` `start` / `end` and `metadataColumns` 4 / 3 place the panel from `lg`; `metadataStack` `after` (default) / `before` places it below `lg`, where it stacks, and in the markup — `after` adds a date and reading-time line under the title below `lg`; pair `end` with `after` or `start` with `before` so tab order matches the layout at every width — never edit its classes). See **ADR-0041**.
|
|
43
|
-
- **IndexList:** editorial index for list pages (blog, case studies) — **IndexList.Item** rows with `meta` (a `<time>`), a linked `title` (`href`, or `render={<Link href />}` for the app's router; a quiet **TextLink** in an `h2` by default — `titleAs`), and an optional `preview` an **IconButton** disclosure expands under the title. Optional `captions={{ meta, title }}` in sentence case (`type-eyebrow`, the "/" marker and the **SectionCaption** rule, `aria-hidden`) share the rows' tracks. Two tracks (7.5rem meta, title) once the list is 32rem wide (container query); one column below, captions hidden. `size` `lg` (type-display-3, default) / `md` / `sm`. `empty` replaces the rows when there are none. `className` is layout only. Not tabular data — **Table
|
|
47
|
+
- **IndexList:** editorial index for list pages (blog, case studies) — **IndexList.Item** rows with `meta` (a `<time>`), a linked `title` (`href`, or `render={<Link href />}` for the app's router; a quiet **TextLink** in an `h2` by default — `titleAs`), and an optional `preview` an **IconButton** disclosure expands under the title. Optional `captions={{ meta, title }}` in sentence case (`type-eyebrow`, the "/" marker and the **SectionCaption** rule, `aria-hidden`) share the rows' tracks. Two tracks (7.5rem meta, title) once the list is 32rem wide (container query); one column below, captions hidden. `size` `lg` (type-display-3, default) / `md` / `sm`. `empty` replaces the rows when there are none. `className` is layout only. Not tabular data — **Table**. Paste **Components/IndexList → Pattern — post index**; with filters, **Guides/Filter panel → Pattern — filtered index** (a post's `facets` take one option or several per group; `defaultSelection` opens the page filtered, `selection` + `onSelectionChange` hold it in the URL). See **ADR-0040**.
|
|
44
48
|
- **LinkTile:** a resource tile that is one link — `title`, `href` (or `render={<Link href />}` for a router link), `media` (an `<img>` or `next/image` with its `width` and `height`; one image, not a video), `meta` (one line, for example the domain), optional `tag` (a **Badge**, over the image's top-start corner) and `source` (**Avatar** `size="xsm"` or a 20px favicon, before the title). The whole tile is the anchor: one hit area, one focus ring; never put a link or a button inside it. `external` opens a new tab with a safe `rel`, the trailing mark **TextLink** uses, and spoken "(opens in a new tab)" — never hand-write `target`. Screen readers hear the title, the meta, the tag, then the new-tab hint; the image's `alt` is not in the link's name. The image keeps its own ratio by default, so tiles vary in height and hold their space before the image loads; `ratio` (width / height, `4 / 3`) fixes the frame and crops the image. A hairline inside the frame keeps an edge on a light image in light mode and a dark image in dark mode; hover and focus darken it and underline the title. The caption is 12px under the image; the title (two lines at most) and the meta line (`type-caption`, 16px) have no gap between them. `loading` is a **Skeleton** placeholder of the same height, not a link. `className` is layout only. Not a **Card** (a surface with slots that can hold several controls). Paste **Components/LinkTile → Pattern — resource tile** or **Pattern — router link**. See **ADR-0044**.
|
|
45
49
|
- **TileGrid:** a grid of tiles on the grid gutter — **TileGrid.Item** children, each with a stable `key`, and `aria-label` or `aria-labelledby`. `layout` `masonry` (default — tiles keep their heights and pack; each next item goes to the shortest column) or `uniform` (rows of equal height; give the tiles a `ratio`). `columns` — one number or `{ base, sm, md, lg, xl }` (default `{ base: 1, sm: 2, lg: 3 }`). Reading order and tab order are the order of the items in both layouts — across the top row, then down the page; never use CSS columns or `order`. `empty` replaces the tiles when there are none; `busy` marks a grid of placeholders. Masonry packs after it measures: the server and the first paint show aligned rows, then the tiles close up (one column needs no packing); the space under a tile is the 2rem row gap plus less than 4px (32px to just under 36px — heights round up to a 4px track). On a server-rendered page that is one instant move at hydration — nothing slides or fades — on grids of two or more columns; `uniform` is CSS alone and does not move at all, so use it where that move is not acceptable. When the items change, the ones that stay move and the others fade; reduced motion changes at once. `className` is layout only. Not dated rows (**IndexList**) or a row to drag (**Carousel**). Paste **Components/TileGrid → Pattern — tile grid**; with filters, **Guides/Filter panel → Pattern — filtered grid**. See **ADR-0044**.
|
|
46
50
|
- **FilterPanel:** checkbox filters for an index page — **FilterPanel** (no element; holds `groups`, `selection` + `onSelectionChange`, `resultsLabel` such as "3 posts", optional `labels`) around the page, with **FilterPanel.Rail** (`aside`: **SectionCaption** "Filters" with Clear all, flush **Accordion** groups of **CheckboxGroup** with counts), **FilterPanel.Trigger** (**Button** `secondary` with the number of options on), and **FilterPanel.Sheet** (the same groups in a bottom **Sheet**; Clear all · Show N). Show the rail and the trigger at opposite breakpoints with `className` (`hidden md:col-span-3 md:block` / `md:hidden`). Helpers: `useFilterSelection({ defaultSelection, selection, onSelectionChange })`, `matchesFilterSelection(facets, selection)` (alternatives within a group, combined across groups), `countFilterOptions(groups, items, label)`, `filterSelectionCount(selection)`. Never write a count into an option label. Not a single choice (**Select**, **SegmentedControl**). Paste **Components/FilterPanel → Pattern — filter panel**; the full pages are **Guides/Filter panel → Pattern — filtered index** and **Pattern — filtered grid** (both take `renderLink` — the router's link for a post title or a tile; the grid also takes `renderImage` — the app's image component; both take `overlay` for page-level layers such as **GridOverlay**, as does **Guides/Post page → Pattern — post page**, which also takes `renderLink` for its breadcrumb and category tags — never add elements to a pasted pattern's markup). See **ADR-0044**.
|
|
47
|
-
- **Card:** `Card.Header`, `Card.Body`, `Card.Footer`. Layout cards (`padding="none"`): header/footer on the shell; **`Card.Header`** is two horizontal slots (`start` | `end`) at **16px** (`px-4`). **`end` is always trailing** (`ml-auto`) — overflow / **MoreMenu** stays on the right even when `start` is omitted. **`Card.Body` is a square composition slot** with a **2px** horizontal gutter — no inner pad, no radius, **no default fill**. The **occupant** dictates how the body region looks; use **`cardLayoutBodyOccupantInsetXClasses`** (`px-3.5` / 14px) + **`cardLayoutBodyOccupantPadYClasses`** (`py-[16px]` / literal 16px vertical) when body content should align with header/footer (2px + 14px = 16px horizontal). Shape config lives in **`src/theme/shape.css`**: shell **`--radius-card-shell: 16px`**, body occupant **`--radius-card-body: 14px`**. Radius-only media → **`cardLayoutBodyOccupantRadiusClasses`**; inset gray/chart wells → **`cardLayoutBodyOccupantWellClasses`** or **`cardLayoutBodyOccupantDotGridWellClasses`**. A headerless layout **Card.Body** densifies the shell top to 2px (`pt-[2px]`) — same density as footerless / `bodyTerminal` bottom; **`headerless`** is only an override for wrapped slot composition. A direct terminal **`Card.Body`** automatically fills stretched cards and gets the 2px bottom shell inset; **`bodyTerminal`** is only an override for wrapped slot composition. Pin a layout height on the root (`className`) with Header + Body + Footer — Header/Footer stay put; **Card.Body** scrolls (2px gutter unchanged; no new variant). See **Components/Card/Card → Pattern — pinned height**. `variant="surface"` is elevated; **`variant="outlined"`** uses **`shadow-soft-card`** with no stroke (same elevation as layout `surface`). Radius stays **`--radius-card-shell`**. Default **`shape="rounded"`**. **`shape="flush"`** only when a parent owns outer chrome. Simple cards: `padding="md"`.
|
|
51
|
+
- **Card:** `Card.Header`, `Card.Body`, `Card.Footer`. Layout cards (`padding="none"`): header/footer on the shell; **`Card.Header`** is two horizontal slots (`start` | `end`) at **16px** (`px-4`). **`end` is always trailing** (`ml-auto`) — overflow / **MoreMenu** stays on the right even when `start` is omitted. **`Card.Body` is a square composition slot** with a **2px** horizontal gutter — no inner pad, no radius, **no default fill**. The **occupant** dictates how the body region looks; use **`cardLayoutBodyOccupantInsetXClasses`** (`px-3.5` / 14px) + **`cardLayoutBodyOccupantPadYClasses`** (`py-[16px]` / literal 16px vertical) when body content should align with header/footer (2px + 14px = 16px horizontal). Shape config lives in **`src/theme/shape.css`**: shell **`--radius-card-shell: 16px`**, body occupant **`--radius-card-body: 14px`**. Radius-only media → **`cardLayoutBodyOccupantRadiusClasses`**; inset gray/chart wells → **`cardLayoutBodyOccupantWellClasses`** or **`cardLayoutBodyOccupantDotGridWellClasses`**. A headerless layout **Card.Body** densifies the shell top to 2px (`pt-[2px]`) — same density as footerless / `bodyTerminal` bottom; **`headerless`** is only an override for wrapped slot composition. A direct terminal **`Card.Body`** automatically fills stretched cards and gets the 2px bottom shell inset; **`bodyTerminal`** is only an override for wrapped slot composition. Pin a layout height on the root (`className`) with Header + Body + Footer — Header/Footer stay put; **Card.Body** scrolls (2px gutter unchanged; no new variant). See **Components/Card/Card → Pattern — pinned height**. `variant="surface"` is elevated; **`variant="outlined"`** uses **`shadow-soft-card`** with no stroke (same elevation as layout `surface`). Radius stays **`--radius-card-shell`**. Default **`shape="rounded"`**. **`shape="flush"`** only when a parent owns outer chrome. Simple cards: `padding="md"`. **`Card.Body variant="terminal"`** is a dark embed (`data-theme="dark"`, so it stays dark in both page themes): `src` or `srcDoc`, `title`, `height` (`sm` | `md` | `lg` | `fill`), `readOnly` (muted **Badge**), `state` (`ready` | `loading` | `disconnected`). That variant is not `bodyTerminal`, which only densifies a footerless shell. Copy **Components/Card/Card → Pattern — terminal frame**.
|
|
48
52
|
- **Input:** pill-only. Optional leading `icon`; trailing `endBadge` or status / `loading`. Hero search with inset button → **Search** molecule. See **ADR-0006**.
|
|
49
53
|
- **PromptBar:** wide prompt pill — inline **TextArea** (one line when empty, grows through 3 lines, then scrolls) and an inset **IconButton** `sm` send control. Enter sends; Shift+Enter inserts a newline; send stays disabled until the draft has text. Optional `start` (brand mark — **Avatar** `size="md"`) and `end` (extra inset **IconButton** `sm`) slots. No new Button role. Copy **Components/PromptBar → Pattern — prompt bar**. The site assistant on the marketing pages is **ChatDock**, not a hand-pinned PromptBar. Do not restyle **SiteNav**. Full contract: **docs/component-contracts.md → PromptBar**.
|
|
50
54
|
- **ChatDock:** the site assistant — a **PromptBar** pinned to the bottom of the page grid (`--grid-max: 40rem`, under **SiteNav**) with the brand `mark`; hovering shows `suggestions` that carry a `prompt` as **Button** `secondary` pills above it; focusing, clicking, or typing opens a non-modal chat window around the same composer (shared overlay header with `title` / `subtitle` / close, `greeting`, `messages` (visitor turns on `bg-brand-tint`, replies as plain text), `thinking` row, suggestion rows — **Button** `layout="row"` — until the first message (a row that sends hands focus to the composer as the rows leave), `disclaimer` under the composer). One **PromptBar** serves both states: the window grows up out of the bar and Escape or close folds it back; either reverses the other midway. Below `md` it fills the screen, rising from the bottom edge. Reduced motion crossfades. `followUps` (same items as `suggestions`; `icon` not shown — inline rows lead with **CornerDownRight**, composer pills are text only) show under the latest reply only — hidden while `thinking`, gone the moment the visitor sends; `followUpsPlacement` is `inline` (default, rows in the thread, 44px on phones) or `composer` (pills above the composer). Under each finished reply, a row: **Copy** (`copyText`), thumbs (`onMessageFeedback`, on replies that carry `feedback` — `null` until the visitor votes; **IconButton** `pressed`), and a muted score (`meta: { label, description? }`, e.g. "Match 99%") — each part on its own; shown on hover or focus where the pointer can hover, always on touch; it keeps its place so the thread never moves, and stays empty while the latest reply is still arriving. Never put actions inside `content`. `gate` puts a multi-step form in the conversation — **ChatDock.Gate**, under the latest message where a reply would go, set in as a well (hairline border at the card body radius, `bg-body`, `shadow-inset-well` — embedded, not raised): step title, subtitle, previous · "2 of 4" · next (no close of its own), the step, `pending` ("Sending…") / `error` lines, and a footer (start slot, Cancel, Next). Setting it opens the window and takes focus; the window keeps its header (`gateSubtitle` replaces the subtitle) and its close; the composer stays in place but is off (`labels.gatePlaceholder`); suggestion rows, follow-ups, and reply rows step aside. The window grows to fit the step, up to the viewport less 7rem; past that the conversation scrolls, and each step is brought into view. The window stays non-modal. Its close and Escape fold it and keep the gate with its progress; Cancel leaves the gate and the composer comes back with focus. Phones keep the window inside the area a keyboard leaves visible. Start a project runs as a gate, not a dialog — paste **Pattern — start a project gate** (its `onSubmit` returns a promise; a rejection keeps the answers and offers Try again) and return it from **Pattern — chat dock**'s `renderStartProject`. Never put a form inside `content`. The app owns `messages`, `followUps`, votes, the gate's open state, and `onSend`; suggestions without a `prompt` (for example **Start a project**) only call `onSuggestionSelect`. No full-page chat, no header send/expand arrow, no voice. `placement="inline"` is for previews only. Copy **Components/ChatDock → Pattern — chat dock** (it holds the follow-up state, the votes, and Start a project). See **ADR-0039**. Full contract: **docs/component-contracts.md → ChatDock**.
|
|
51
55
|
- **TextArea:** element-radius multiline — same optional `label`, `description`, `status`, `message`, and `loading` as **Input**; default `resize="vertical"`. See **ADR-0006**.
|
|
52
56
|
- **Field:** label/layout wrapper — `orientation="vertical" | "horizontal"`; never required. Validation stays on **Input** / **TextArea**. See **ADR-0006**.
|
|
57
|
+
- **CopyField:** read-only monospace value inside **PromptBar**'s pill, with **Button** `role="secondary"` `size="sm"` seated at the trailing end the way PromptBar seats send (Lucide **Copy**; the confirmation check is the status glyph). **Input** is `inline` `mono` `readOnly` (`type-code`); a long value truncates and does not run under the button. A click copies `value`. The label becomes "Copied" for `buttonStatusHoldMs` (2s), announced from the button's `aria-live="polite"` region, then returns. Enter and Space activate the button; focusing the value selects it, so Cmd/Ctrl+C copies from the field. If the Clipboard API is missing or blocked, the value is selected, `execCommand('copy')` is tried, and a polite hint reads "Press Cmd/Ctrl+C to copy." The value is never logged. The label and the blocked-clipboard hint inherit color (`type-label` / `type-supporting`, not `text-fg` or `text-muted`); the pill stays `bg-surface` so the key stays dark on white. On navy, wrap the section in `bg-brand text-on-brand` and use **Button** `role="inverse"` plus `role="inverse-outline"`. Do not set `data-theme="dark"` on that section — the dark surface is about 1:1 on `#011272`. `state` (`"copied"` | `"fallback"`) pins a specimen — omit it in the app. `className` is layout only. No new color. Paste **Components/CopyField → Pattern — copy key**, or **Pattern — on a navy field** for the unlock page. See **ADR-0045**, **ADR-0046**.
|
|
58
|
+
- **LogView:** a scrolling log — `lines`, `label`, line numbers, a tabular line count, **Copy** (**Button**), and **Follow** (`aria-pressed`, keeps the scroller on the last line). `loading` is a **Skeleton**. An empty log reads "No log lines". `className` is layout only. Paste **Components/LogView → Pattern — log viewer**. The dark run detail is **Pattern — detail pane**: prev/next **IconButton**, **Badge** tags, **Stat** `detail`, and **LogView**, wrapped in `data-theme="dark"`.
|
|
53
59
|
- **Checkbox:** boolean toggle — label row + optional `description`, `indeterminate`, `status` / `message`, and `loading`. **`count`** puts a muted number at the row's end (filter results, styled like **NavList** counts); screen readers hear it after the label in parentheses — "Guides (2)", or "Guides (2 posts)" with `countLabel`. Never write the count into `label`. See **ADR-0006**.
|
|
54
60
|
- **Radio:** single option circle — same label row rhythm as **Checkbox**; compose in **RadioGroup**. See **ADR-0006**.
|
|
55
61
|
- **RadioGroup:** mutually exclusive options — `value` / `onValueChange`, vertical or horizontal layout, group validation band.
|
|
@@ -64,8 +70,9 @@ Storybook paths such as **Components/Button/Button → Pattern — …** name a
|
|
|
64
70
|
- **IntakeModal:** full-screen Start a project shell — typographic **WM** + **WhatMatters** wordmark (no separate logo asset), **IconButton** close (Lucide **X**, name **Close**), **StepProgress** (navy segments, pill label **Step N of 4**), pinned **Back** / **Continue** footer. Focus trap, Escape, scroll lock, `aria-modal`. **SelectableCard** is the multi-select grid (2 columns mobile, 3 from `lg`) with a `toggle` slot for hugged **SegmentedControl** and a **Badge** `iconOnly` check. **PillGroup** is the wrapping radio pills; **Not sure yet** uses existing **Badge** neutral `emphasis="muted"` — no new Badge emphasis. **IntakeForm** is **Field** + **Input** / **TextArea** (optional Link URL); required fields show `status` + `message` on blur; company and link stay optional, and `company={false}` / `link={false}` leave them out (the short form — name, email, details — which **Pattern — start a project gate** uses; their values stay empty in `IntakeAboutValues`); the details counter is the field description. **CalEmbed** is the calendar frame plus **TextLink** skip; with no embed mounted it shows a visitor-facing empty state (`emptyTitle`, `emptyDescription`), never developer notes. **CalEmbed.Skip** is that same skip; in a **Card** gate put it in **Card.Footer** and pass `skip={false}` so the in-body link is omitted. **IntakeConfirmation** (`booked` | `emailed`) calls **`useConfettiOnMount`** with `intakeConfettiColors` (includes `--color-brand`); optional `bookingTime` / `videoHref` / `googleCalendarHref` / `icsHref` reuse **TextLink** and **Button**. Copy **Components/IntakeModal → Pattern — intake modal** and compose the steps in the app. See **ADR-0038**.
|
|
65
71
|
- **Sheet:** dismissible edge overlay — `side="bottom" | "end" | "start"` only (**no `top`** — not a product pattern); same scrim/focus/scroll lock as **Dialog**; bottom drawer or side detail rail; header/footer pinned, body scrolls; **hairline borders** on scroll regions. Mobile **16px inset on trailing edge only** (e.g. `end` → left gap for scrim; entry/top/bottom flush); tablet+ side sheets fully edge-flush. **`Sheet.Content`** — Card header rhythm, scrollable body, optional footer.
|
|
66
72
|
- **Panel:** persistent edge flyover — `side="end" | "start"` only; **non-blocking visual scrim** (clicks pass through), **no scroll lock**, **no focus trap**; `aria-modal="false"`; page stays interactive. Same width tokens and hairline scroll chrome as **Sheet** side panels. Use for peek/detail rails; use **Sheet** when the canvas should block.
|
|
73
|
+
- **Sheet:** `side="end"` is the detail rail — **full-screen below `md`** (no inset, no radius; header and footer pinned, body scrolls, safe-area footer), a rail over a light scrim from `md`; the page behind is inert. **`headerActions`** puts **IconButton** `size="sm"` controls before the close. **`useSheetParam(kind, { initialUrl? })`** keeps the open sheet in `?sheet=<kind>&id=<id>` (History API, no router; Back closes; `initialUrl` for the server), with pure **`readSheetParam`** / **`withSheetParam(url, id, kind)`** for `href`s. Paste **Components/Sheet → Pattern — side sheet**, **Pattern — side sheet header actions**, or **Pattern — side sheet URL deep link**; with a table, **Components/Table → Pattern — approvals with sheet**. See **ADR-0049**.
|
|
67
74
|
- **Status:** fixed-scale indicators — **`variant="ring"`** (24px task progress: `active`, `step`) or **`variant="dot"`** (8px semantic: `tone`, `pulsing`). `besideLabel` / `label` for a11y. Not inside Badge.
|
|
68
75
|
- **Skeleton:** layout placeholder blocks — Motion horizontal shimmer ([Motion skeleton shimmer](https://motion.dev/examples/react-skeleton-shimmer)); **`index`** staggers sweep. Compose to mirror resolved chrome (title, **Select**, well, footer). **`aria-busy`** on **Card** / page region; shapes **`aria-hidden`**. Not chart-mark state — swap region for **Chart.Loading** or live marks. See **Components/Skeleton**, **Components/Chart** Card pattern stories.
|
|
69
|
-
- **Chart:** composes **[@visx/visx](https://airbnb.io/visx/)** primitives — peer dep, not bundled. WMDS owns shell, tooltip, legend, and **`chartTheme.ts`** token maps. Chart-type selection: **`.cursor/skills/wmds-visx-charts/lieflat-mapping.md`** (Lieflat catalog → WMDS patterns + example data; Lieflat HTML is reference-only). **`Chart.SegmentedBar`** — capacity meter; **`Chart.RankedBars`** — ordered percentage comparisons; **`Chart.UnitGrid`** — exact part-to-whole composition; **`Chart.DistributionStrip`** — one measure across peers; **`Chart.Heatmap`** — categorical row × column intensity; **`Chart.Cartesian`** + **`Chart.Cartesian.Area`** — time series with crosshair tooltip + optional **`Chart.Legend`**. Pass **`null`** (not `0`) for missing points — in-series hatch + muted **Badge** (**Pattern — Cartesian no-data gaps**, ADR-0027); default gap when every plotted series key is null. Entirely missing / all-zero series → product Card.Body empty (not a full-plot hatch). Initial mark animation is opt-in via **`animate="initial"`**. **`Chart.Loading`** — spinner + copy for in-flight fetch (**Card.Body**; header stays mounted). Loading model: **Skeleton** (initial layout) → **Chart.Loading** (fetch) → live marks with enter animation. Series colors: semantic **`chartSeriesConfigFromTone`** or categorical **`chartSeriesConfigFromKeys`** / **`chartSeriesColor(index)`** (ADR-0013). Tooltip portal: visx **`unstyled`** — styling only on **`Chart.Tooltip.Content`**. Responsive chart hosts measure the full parent width and use explicit **`minHeight`**; give adjacent plots the same value so chart areas align. See **ADR-0012**, **ADR-0014**, **ADR-0015**, **ADR-0027**.
|
|
76
|
+
- **Chart:** composes **[@visx/visx](https://airbnb.io/visx/)** primitives — peer dep, not bundled. WMDS owns shell, tooltip, legend, and **`chartTheme.ts`** token maps. Chart-type selection: **`.cursor/skills/wmds-visx-charts/lieflat-mapping.md`** (Lieflat catalog → WMDS patterns + example data; Lieflat HTML is reference-only). **`Chart.SegmentedBar`** — capacity meter (CSS ticks in the server HTML, then the measured SVG); **`Chart.SegmentedRing`** — segmented radial meter (`value` / `max`, `centerValue`, `unavailable`); **`Chart.Legend` `variant="tabular"`** — swatch, label, mono value, percent, delta; **`Chart.RankedBars`** — ordered percentage comparisons; **`Chart.UnitGrid`** — exact part-to-whole composition; **`Chart.DistributionStrip`** — one measure across peers; **`Chart.Heatmap`** — categorical row × column intensity; **`Chart.Cartesian`** + **`Chart.Cartesian.Area`** — time series with crosshair tooltip + optional **`Chart.Legend`**. Pass **`null`** (not `0`) for missing points — in-series hatch + muted **Badge** (**Pattern — Cartesian no-data gaps**, ADR-0027); default gap when every plotted series key is null. Entirely missing / all-zero series → product Card.Body empty (not a full-plot hatch). Initial mark animation is opt-in via **`animate="initial"`**. **`Chart.Loading`** — spinner + copy for in-flight fetch (**Card.Body**; header stays mounted). Loading model: **Skeleton** (initial layout) → **Chart.Loading** (fetch) → live marks with enter animation. Series colors: semantic **`chartSeriesConfigFromTone`** or categorical **`chartSeriesConfigFromKeys`** / **`chartSeriesColor(index)`** (ADR-0013). Tooltip portal: visx **`unstyled`** — styling only on **`Chart.Tooltip.Content`**. Responsive chart hosts measure the full parent width and use explicit **`minHeight`**; give adjacent plots the same value so chart areas align. See **ADR-0012**, **ADR-0014**, **ADR-0015**, **ADR-0027**. **Pattern — revenue by line** places **Chart.Cartesian** beside a column of **Stat**. Until the source exists, the card says the line is not wired.
|
|
70
77
|
- **Responsive:** **Mobile-first** — unprefixed utilities = mobile; `sm:` / `md:` / `lg:` scale up. Review at Mobile (390px), Tablet (768px), Desktop (1280px). Grid columns step on that same scale (4 / 8 / 12). Atoms: min **44×44px** touch targets. No hover-only core actions. See **ADR-0003** and **Foundations → Grid**.
|
|
71
78
|
- **Cluster scale:** shared heights for controls in one row (Card headers, filter rails) — **sm / md / lg** → 28 / 36 / 44px. **Chip** `sm|md|lg` pairs with **IconButton** `xs|sm|md` and compact **Button** `xs|sm|md`. See **Foundations → Cluster** and **ADR-0011**. Use `iconButtonSizeForCluster()` when composing headers.
|