@felloinnovations/brand-kit 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +198 -24
- package/CHANGELOG.md +197 -0
- package/README.md +34 -7
- package/design-system/01-how-claude-decides.md +26 -17
- package/design-system/02-principles.md +11 -6
- package/design-system/03-colour.md +134 -47
- package/design-system/04-type.md +45 -23
- package/design-system/05-space-and-layout.md +175 -52
- package/design-system/06-sizing-profiles.md +4 -4
- package/design-system/07-scrolling.md +66 -20
- package/design-system/08-choosing-components.md +50 -22
- package/design-system/09-charts.md +71 -28
- package/design-system/10-patterns.md +33 -10
- package/design-system/11-page-recipes.md +110 -23
- package/design-system/12-content.md +2 -1
- package/design-system/13-motion.md +330 -47
- package/design-system/14-accessibility.md +47 -24
- package/design-system/15-ai-experiences.md +11 -6
- package/design-system/16-creativity.md +1 -1
- package/design-system/17-decisions.md +357 -1
- package/design-system/18-checklist.md +79 -16
- package/design-system/19-public-pages.md +6 -4
- package/design-system/20-palettes.md +275 -0
- package/design-system/README.md +30 -17
- package/design-system/components/ActionList/README.md +96 -10
- package/design-system/components/ActionList/preview.html +52 -7
- package/design-system/components/ActionList/style.css +64 -0
- package/design-system/components/ActivityGrid/README.md +38 -2
- package/design-system/components/ActivityGrid/preview.html +1 -1
- package/design-system/components/ActivityGrid/style.css +35 -0
- package/design-system/components/AppShell/README.md +205 -75
- package/design-system/components/AppShell/preview.html +339 -58
- package/design-system/components/AppShell/style.css +154 -0
- package/design-system/components/Avatar/README.md +37 -10
- package/design-system/components/Avatar/preview.html +13 -4
- package/design-system/components/Avatar/style.css +14 -0
- package/design-system/components/Badge/README.md +41 -19
- package/design-system/components/Badge/preview.html +13 -10
- package/design-system/components/Badge/style.css +23 -0
- package/design-system/components/Banner/README.md +30 -9
- package/design-system/components/Banner/preview.html +1 -1
- package/design-system/components/Banner/style.css +19 -0
- package/design-system/components/Breadcrumb/README.md +58 -46
- package/design-system/components/Breadcrumb/preview.html +16 -16
- package/design-system/components/Breadcrumb/style.css +17 -0
- package/design-system/components/Button/README.md +113 -55
- package/design-system/components/Button/preview.html +98 -25
- package/design-system/components/Button/style.css +86 -0
- package/design-system/components/Card/README.md +158 -49
- package/design-system/components/Card/preview.html +141 -19
- package/design-system/components/Card/style.css +115 -0
- package/design-system/components/CardBoard/README.md +58 -37
- package/design-system/components/CardBoard/preview.html +26 -29
- package/design-system/components/ChatCard/README.md +115 -29
- package/design-system/components/ChatCard/preview.html +149 -32
- package/design-system/components/ChatCard/style.css +49 -0
- package/design-system/components/ChatShell/README.md +116 -0
- package/design-system/components/ChatShell/preview.html +201 -0
- package/design-system/components/ChatShell/style.css +86 -0
- package/design-system/components/ChatTurn/README.md +78 -35
- package/design-system/components/ChatTurn/preview.html +60 -28
- package/design-system/components/ChatTurn/style.css +50 -0
- package/design-system/components/CheckRow/README.md +141 -0
- package/design-system/components/CheckRow/preview.html +140 -0
- package/design-system/components/CheckRow/style.css +88 -0
- package/design-system/components/Checkbox/README.md +58 -22
- package/design-system/components/Checkbox/preview.html +112 -50
- package/design-system/components/Checkbox/style.css +45 -0
- package/design-system/components/Chip/README.md +78 -47
- package/design-system/components/Chip/preview.html +29 -4
- package/design-system/components/Chip/style.css +17 -0
- package/design-system/components/Combobox/README.md +44 -11
- package/design-system/components/Combobox/preview.html +35 -12
- package/design-system/components/Combobox/style.css +54 -0
- package/design-system/components/Composer/README.md +137 -35
- package/design-system/components/Composer/preview.html +115 -40
- package/design-system/components/Composer/style.css +81 -0
- package/design-system/components/ConfirmDialog/README.md +73 -52
- package/design-system/components/ConfirmDialog/preview.html +70 -26
- package/design-system/components/ConfirmDialog/style.css +5 -0
- package/design-system/components/Cover/preview.html +3 -3
- package/design-system/components/Cover/style.css +19 -0
- package/design-system/components/DateInput/README.md +77 -12
- package/design-system/components/DateInput/preview.html +27 -12
- package/design-system/components/DateInput/style.css +38 -0
- package/design-system/components/Dialog/README.md +112 -44
- package/design-system/components/Dialog/preview.html +141 -18
- package/design-system/components/Dialog/style.css +74 -0
- package/design-system/components/EmptyState/README.md +42 -12
- package/design-system/components/EmptyState/preview.html +11 -11
- package/design-system/components/EmptyState/style.css +9 -0
- package/design-system/components/EntryShell/README.md +73 -48
- package/design-system/components/EntryShell/preview.html +60 -27
- package/design-system/components/EntryShell/style.css +19 -0
- package/design-system/components/FeatureGrid/README.md +61 -39
- package/design-system/components/FeatureGrid/preview.html +2 -2
- package/design-system/components/FeatureGrid/style.css +20 -0
- package/design-system/components/FileUpload/README.md +33 -3
- package/design-system/components/FileUpload/preview.html +18 -4
- package/design-system/components/FileUpload/style.css +23 -0
- package/design-system/components/FilterBar/README.md +114 -0
- package/design-system/components/FilterBar/preview.html +82 -0
- package/design-system/components/FilterBar/style.css +22 -0
- package/design-system/components/Fold/README.md +84 -26
- package/design-system/components/Fold/preview.html +77 -34
- package/design-system/components/Fold/style.css +48 -0
- package/design-system/components/Footer/README.md +62 -39
- package/design-system/components/Footer/preview.html +5 -5
- package/design-system/components/Footer/style.css +30 -0
- package/design-system/components/FunnelBars/README.md +24 -3
- package/design-system/components/FunnelBars/preview.html +5 -5
- package/design-system/components/FunnelBars/style.css +13 -0
- package/design-system/components/GoalBars/README.md +54 -10
- package/design-system/components/GoalBars/preview.html +69 -28
- package/design-system/components/GoalBars/style.css +51 -0
- package/design-system/components/GoalTicks/README.md +74 -58
- package/design-system/components/GoalTicks/preview.html +41 -118
- package/design-system/components/GoalTicks/style.css +37 -0
- package/design-system/components/GoalTubes/README.md +29 -3
- package/design-system/components/GoalTubes/preview.html +6 -6
- package/design-system/components/GoalTubes/style.css +23 -0
- package/design-system/components/Heatmap/README.md +133 -0
- package/design-system/components/Heatmap/preview.html +168 -0
- package/design-system/components/Heatmap/style.css +119 -0
- package/design-system/components/Hero/README.md +58 -40
- package/design-system/components/Hero/preview.html +1 -1
- package/design-system/components/Hero/style.css +19 -0
- package/design-system/components/HoverCard/README.md +114 -0
- package/design-system/components/HoverCard/preview.html +107 -0
- package/design-system/components/HoverCard/style.css +59 -0
- package/design-system/components/IconButton/README.md +69 -38
- package/design-system/components/IconButton/preview.html +77 -43
- package/design-system/components/IconButton/style.css +45 -0
- package/design-system/components/Input/README.md +36 -11
- package/design-system/components/Input/preview.html +1 -1
- package/design-system/components/Input/style.css +23 -0
- package/design-system/components/KanbanBoard/README.md +93 -45
- package/design-system/components/KanbanBoard/preview.html +159 -43
- package/design-system/components/KanbanBoard/style.css +53 -0
- package/design-system/components/Kbd/README.md +25 -9
- package/design-system/components/Kbd/preview.html +6 -2
- package/design-system/components/Kbd/style.css +9 -0
- package/design-system/components/KitButton/README.md +81 -47
- package/design-system/components/KitButton/preview.html +35 -13
- package/design-system/components/KitButton/style.css +46 -0
- package/design-system/components/KitCard/README.md +75 -44
- package/design-system/components/KitCard/preview.html +18 -10
- package/design-system/components/KitCard/style.css +15 -0
- package/design-system/components/KitInput/README.md +54 -39
- package/design-system/components/KitInput/preview.html +7 -7
- package/design-system/components/KitInput/style.css +23 -0
- package/design-system/components/Leaderboard/README.md +76 -49
- package/design-system/components/Leaderboard/preview.html +61 -26
- package/design-system/components/Leaderboard/style.css +44 -0
- package/design-system/components/ListDetail/README.md +222 -0
- package/design-system/components/ListDetail/preview.html +213 -0
- package/design-system/components/ListDetail/style.css +148 -0
- package/design-system/components/LoadBar/README.md +79 -0
- package/design-system/components/LoadBar/preview.html +40 -0
- package/design-system/components/LoadBar/style.css +17 -0
- package/design-system/components/MarketingNav/README.md +76 -42
- package/design-system/components/MarketingNav/preview.html +37 -8
- package/design-system/components/MarketingNav/style.css +45 -0
- package/design-system/components/Matrix/README.md +129 -0
- package/design-system/components/Matrix/preview.html +99 -0
- package/design-system/components/Matrix/style.css +84 -0
- package/design-system/components/Menu/README.md +117 -41
- package/design-system/components/Menu/preview.html +85 -23
- package/design-system/components/Menu/style.css +61 -0
- package/design-system/components/Meter/README.md +39 -4
- package/design-system/components/Meter/preview.html +18 -1
- package/design-system/components/Meter/style.css +23 -0
- package/design-system/components/MetricTile/README.md +237 -0
- package/design-system/components/MetricTile/preview.html +241 -0
- package/design-system/components/MetricTile/style.css +129 -0
- package/design-system/components/Morph/README.md +106 -0
- package/design-system/components/Morph/preview.html +103 -0
- package/design-system/components/Morph/style.css +31 -0
- package/design-system/components/Motion/README.md +164 -0
- package/design-system/components/Motion/preview.html +2537 -0
- package/design-system/components/NumberInput/README.md +27 -8
- package/design-system/components/NumberInput/preview.html +21 -5
- package/design-system/components/NumberInput/style.css +34 -0
- package/design-system/components/PaceBar/README.md +75 -0
- package/design-system/components/PaceBar/preview.html +67 -0
- package/design-system/components/PaceBar/style.css +25 -0
- package/design-system/components/Page/README.md +57 -18
- package/design-system/components/Page/preview.html +15 -17
- package/design-system/components/Page/style.css +7 -0
- package/design-system/components/PageBar/README.md +90 -34
- package/design-system/components/PageBar/preview.html +60 -19
- package/design-system/components/PageBar/style.css +35 -0
- package/design-system/components/PageHero/README.md +87 -51
- package/design-system/components/PageHero/preview.html +35 -6
- package/design-system/components/PageHero/style.css +31 -0
- package/design-system/components/Pagination/README.md +70 -49
- package/design-system/components/Pagination/preview.html +24 -15
- package/design-system/components/Pagination/style.css +12 -0
- package/design-system/components/Panel/README.md +109 -51
- package/design-system/components/Panel/preview.html +118 -20
- package/design-system/components/Panel/style.css +51 -0
- package/design-system/components/PasswordInput/README.md +50 -18
- package/design-system/components/PasswordInput/preview.html +71 -21
- package/design-system/components/PasswordInput/style.css +31 -0
- package/design-system/components/Plate/README.md +75 -17
- package/design-system/components/Plate/preview.html +40 -7
- package/design-system/components/Plate/style.css +37 -0
- package/design-system/components/Popover/README.md +70 -49
- package/design-system/components/Popover/preview.html +44 -22
- package/design-system/components/Popover/style.css +41 -0
- package/design-system/components/PricingTable/README.md +69 -49
- package/design-system/components/PricingTable/preview.html +21 -21
- package/design-system/components/PricingTable/style.css +21 -0
- package/design-system/components/ProgressPetals/README.md +31 -3
- package/design-system/components/ProgressPetals/preview.html +14 -15
- package/design-system/components/ProgressPetals/style.css +27 -0
- package/design-system/components/README.md +151 -0
- package/design-system/components/Radio/README.md +40 -17
- package/design-system/components/Radio/preview.html +16 -1
- package/design-system/components/Radio/style.css +21 -0
- package/design-system/components/RankBar/README.md +29 -2
- package/design-system/components/RankBar/preview.html +14 -12
- package/design-system/components/RankBar/style.css +18 -0
- package/design-system/components/Ring/README.md +61 -11
- package/design-system/components/Ring/preview.html +55 -10
- package/design-system/components/Ring/style.css +49 -0
- package/design-system/components/ScoreHero/README.md +91 -0
- package/design-system/components/ScoreHero/preview.html +104 -0
- package/design-system/components/ScoreHero/style.css +32 -0
- package/design-system/components/Section/README.md +72 -33
- package/design-system/components/Section/preview.html +46 -16
- package/design-system/components/Section/style.css +18 -0
- package/design-system/components/Segmented/README.md +100 -55
- package/design-system/components/Segmented/preview.html +107 -26
- package/design-system/components/Segmented/style.css +94 -0
- package/design-system/components/SegmentedProgress/README.md +64 -0
- package/design-system/components/SegmentedProgress/preview.html +69 -0
- package/design-system/components/SegmentedProgress/style.css +11 -0
- package/design-system/components/Select/README.md +47 -12
- package/design-system/components/Select/preview.html +28 -2
- package/design-system/components/Select/style.css +22 -0
- package/design-system/components/ShareBar/README.md +69 -0
- package/design-system/components/ShareBar/preview.html +72 -0
- package/design-system/components/ShareBar/style.css +25 -0
- package/design-system/components/SideList/README.md +147 -0
- package/design-system/components/SideList/preview.html +139 -0
- package/design-system/components/SideList/style.css +94 -0
- package/design-system/components/Skeleton/README.md +52 -16
- package/design-system/components/Skeleton/preview.html +35 -23
- package/design-system/components/Skeleton/style.css +22 -0
- package/design-system/components/Slider/README.md +95 -0
- package/design-system/components/Slider/preview.html +49 -0
- package/design-system/components/Slider/style.css +13 -0
- package/design-system/components/Split/README.md +112 -0
- package/design-system/components/Split/preview.html +105 -0
- package/design-system/components/Split/style.css +30 -0
- package/design-system/components/Starter/README.md +21 -9
- package/design-system/components/Starter/preview.html +5 -5
- package/design-system/components/Stat/README.md +64 -43
- package/design-system/components/Stat/preview.html +3 -3
- package/design-system/components/Stat/style.css +15 -0
- package/design-system/components/StatTiles/README.md +88 -44
- package/design-system/components/StatTiles/preview.html +142 -71
- package/design-system/components/StatTiles/style.css +64 -0
- package/design-system/components/StatusLine/README.md +85 -0
- package/design-system/components/StatusLine/preview.html +49 -0
- package/design-system/components/StatusLine/style.css +14 -0
- package/design-system/components/StatusScreen/README.md +49 -35
- package/design-system/components/StatusScreen/preview.html +20 -5
- package/design-system/components/StatusScreen/style.css +9 -0
- package/design-system/components/StepLine/README.md +97 -0
- package/design-system/components/StepLine/preview.html +108 -0
- package/design-system/components/StepLine/style.css +62 -0
- package/design-system/components/Stepper/README.md +35 -13
- package/design-system/components/Stepper/style.css +13 -0
- package/design-system/components/StreakCalendar/README.md +22 -1
- package/design-system/components/StreakCalendar/preview.html +10 -10
- package/design-system/components/StreakCalendar/style.css +12 -0
- package/design-system/components/SuggestionChip/README.md +87 -0
- package/design-system/components/SuggestionChip/preview.html +42 -0
- package/design-system/components/SuggestionChip/style.css +20 -0
- package/design-system/components/Switch/README.md +44 -24
- package/design-system/components/Switch/preview.html +23 -8
- package/design-system/components/Switch/style.css +31 -0
- package/design-system/components/Table/README.md +129 -58
- package/design-system/components/Table/preview.html +106 -72
- package/design-system/components/Table/style.css +104 -0
- package/design-system/components/Tabs/README.md +71 -46
- package/design-system/components/Tabs/preview.html +56 -24
- package/design-system/components/Tabs/style.css +44 -0
- package/design-system/components/Testimonial/README.md +48 -32
- package/design-system/components/Testimonial/preview.html +14 -14
- package/design-system/components/Testimonial/style.css +12 -0
- package/design-system/components/TextArea/README.md +27 -11
- package/design-system/components/TextArea/preview.html +8 -2
- package/design-system/components/TextArea/style.css +12 -0
- package/design-system/components/ThemeSwitch/README.md +116 -0
- package/design-system/components/ThemeSwitch/preview.html +57 -0
- package/design-system/components/ThemeSwitch/style.css +6 -0
- package/design-system/components/TickRow/README.md +52 -36
- package/design-system/components/TickRow/preview.html +64 -41
- package/design-system/components/TickRow/style.css +23 -0
- package/design-system/components/TickStrip/README.md +95 -0
- package/design-system/components/TickStrip/preview.html +88 -0
- package/design-system/components/TickStrip/style.css +42 -0
- package/design-system/components/Tile/README.md +79 -18
- package/design-system/components/Tile/preview.html +91 -40
- package/design-system/components/Tile/style.css +36 -0
- package/design-system/components/Timeline/README.md +68 -48
- package/design-system/components/Timeline/preview.html +27 -15
- package/design-system/components/Timeline/style.css +25 -0
- package/design-system/components/Toast/README.md +70 -38
- package/design-system/components/Toast/preview.html +43 -8
- package/design-system/components/Toast/style.css +25 -0
- package/design-system/components/Tooltip/README.md +93 -35
- package/design-system/components/Tooltip/preview.html +69 -15
- package/design-system/components/Tooltip/style.css +78 -0
- package/design-system/components/TopBar/README.md +67 -53
- package/design-system/components/TopBar/preview.html +14 -27
- package/design-system/components/TopBar/style.css +35 -0
- package/design-system/components/TopicRail/README.md +108 -0
- package/design-system/components/TopicRail/preview.html +58 -0
- package/design-system/components/TopicRail/style.css +48 -0
- package/design-system/components/TrendBars/README.md +52 -14
- package/design-system/components/TrendBars/preview.html +65 -32
- package/design-system/components/TrendBars/style.css +25 -0
- package/design-system/components/TrendLine/README.md +164 -0
- package/design-system/components/TrendLine/preview.html +373 -0
- package/design-system/components/TrendLine/style.css +155 -0
- package/design-system/components/Well/README.md +64 -22
- package/design-system/components/Well/preview.html +14 -14
- package/design-system/components/Well/style.css +22 -0
- package/design-system/components/_house.css +324 -0
- package/design-system/components/bundle.css +4091 -744
- package/design-system/tokens.json +1314 -124
- package/dist/fello-core.css +2 -2
- package/dist/fello.css +6 -1369
- package/dist/fello.scoped.css +7 -0
- package/dist/index.d.ts +110 -11
- package/dist/index.js +781 -96
- package/dist/palettes.d.ts +464 -0
- package/dist/palettes.js +497 -0
- package/dist/scoped.d.ts +3 -0
- package/dist/scoped.js +16 -0
- package/dist/toast.d.ts +14 -0
- package/dist/toast.js +33 -29
- package/dist/tokens.d.ts +1686 -62
- package/dist/tokens.js +1668 -61
- package/package.json +20 -4
- package/scripts/check-palette.mjs +295 -0
- package/scripts/token-model.mjs +552 -0
- package/tokens/_dark.css +174 -0
- package/tokens/_palettes.css +46 -0
- package/tokens/_product.css +266 -53
- package/tokens/_source.hubl.css +39 -19
- package/tokens/design-tokens.json +319 -0
- package/tokens/palettes.ts +504 -0
- package/tokens/shadcn-compat.css +19 -19
- package/tokens/shadcn-compat.hsl.css +76 -0
- package/tokens/tokens.css +1292 -62
- package/tokens/tokens.md +588 -61
- package/tokens/tokens.root.css +1298 -62
- package/tokens/tokens.ts +1677 -64
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* ---- Table: the wide-table pattern. Records in a card: a sticky head and first column (a shade on its edge once the rows
|
|
2
|
+
slide under it), a fade on the side that hides columns, outer cells on the card's 20 px edge, 8 px above and below every
|
|
3
|
+
cell (40 the row's minimum), numbers right-aligned in fixed columns, totals in the foot, columns that fold by priority
|
|
4
|
+
and a phone fold. The card is a size container: what folds follows the table's width, not the window's. ---- */
|
|
5
|
+
.ws-table { --ws-table-ground: var(--color-bg-page); --ws-table-shade: color-mix(in srgb, var(--ws-shade) 7.8431372549%, transparent); /* the 8-bit step of 8% (20 of 255), as rgba() painted it in this gradient */ --ws-table-num-w: var(--space-4xl); --ws-table-check-w: calc(var(--ws-card-inset) + var(--space-lg) + var(--space-sm)); container: ws-table / inline-size; }
|
|
6
|
+
/* Not clipped (K1): the scroller inside holds the columns and takes the card's corners. */
|
|
7
|
+
.ws-table > :first-child { border-top-left-radius: inherit; border-top-right-radius: inherit; }
|
|
8
|
+
.ws-table > :last-child { border-bottom-left-radius: inherit; border-bottom-right-radius: inherit; }
|
|
9
|
+
/* The scroller: sideways always; up and down once it is given a height (--ws-table-max-h), so the head and the totals stay. */
|
|
10
|
+
.ws-table__scroll { overflow: auto; max-height: var(--ws-table-max-h, none); overscroll-behavior-x: contain; scrollbar-width: thin; }
|
|
11
|
+
/* Its ring would sit under the stuck cells, so a focused scroller rings its card instead, 2 px out. */
|
|
12
|
+
.ws-table:has(> .ws-table__scroll:focus-visible) { outline: var(--ws-focus-width) solid var(--ws-focus-color); outline-offset: var(--ws-focus-offset); }
|
|
13
|
+
@supports selector(:has(*)) { .ws-table > .ws-table__scroll:focus-visible { outline: none; } }
|
|
14
|
+
/* Separate borders: a collapsed border belongs to the table and stays behind when its sticky cell moves. */
|
|
15
|
+
.ws-table table { width: 100%; border-collapse: separate; border-spacing: 0; }
|
|
16
|
+
/* Every cell: one line (columns scroll, rows never squash to a word a line), 8 px above and below, 12 at the sides, 20 at
|
|
17
|
+
the card's edge. A cell that may wrap says so (__wrap). */
|
|
18
|
+
.ws-table th, .ws-table td { padding: var(--space-xs) var(--space-sm); background-color: var(--ws-table-ground); text-align: left; vertical-align: middle; white-space: nowrap; }
|
|
19
|
+
.ws-table tr > :first-child { padding-left: var(--ws-card-inset); }
|
|
20
|
+
.ws-table tr > :last-child { padding-right: var(--ws-card-inset); }
|
|
21
|
+
.ws-table .ws-table__wrap { white-space: normal; min-width: var(--space-9xl); }
|
|
22
|
+
/* Head: 13 / 18 medium muted labels, stuck to the top of the scroller. */
|
|
23
|
+
.ws-table thead th { position: sticky; top: 0; z-index: 2; height: var(--ws-row-h-dense); font-size: var(--ws-text-control); line-height: var(--ws-leading-control); font-weight: 500; color: var(--color-text-muted); border-bottom: var(--ws-divider); }
|
|
24
|
+
.ws-table thead th[aria-sort="ascending"], .ws-table thead th[aria-sort="descending"] { color: var(--color-text-heading); }
|
|
25
|
+
.ws-table__sort { display: inline-flex; align-items: center; gap: var(--space-xxs); border: 0; background: none; padding: 0; font: inherit; color: inherit; cursor: pointer; }
|
|
26
|
+
.ws-table__sort svg { width: var(--ws-icon-sm); height: var(--ws-icon-sm); flex-shrink: 0; }
|
|
27
|
+
.ws-table th[aria-sort="ascending"] .ws-table__sort svg { transform: rotate(180deg); }
|
|
28
|
+
@media (hover: hover) { .ws-table__sort:hover { color: var(--color-text-heading); } }
|
|
29
|
+
/* An unsorted column's chevron only says it sorts: dimmed (--ws-dim), so two columns never both read as sorted; it comes
|
|
30
|
+
up on hover and focus. */
|
|
31
|
+
.ws-table th:not([aria-sort="ascending"], [aria-sort="descending"]) .ws-table__sort svg { opacity: var(--ws-dim); }
|
|
32
|
+
.ws-table th .ws-table__sort:focus-visible svg { opacity: 1; }
|
|
33
|
+
@media (hover: hover) { .ws-table th .ws-table__sort:hover svg { opacity: 1; } }
|
|
34
|
+
/* Body: 40 px rows, a 0.5 px line between them. */
|
|
35
|
+
.ws-table tbody tr, .ws-table tfoot tr { height: var(--ws-row-h-dense); }
|
|
36
|
+
.ws-table tbody th, .ws-table tbody td { font-size: var(--ws-text-body); line-height: calc(var(--space-md) + var(--space-xxs)); font-weight: 400; color: var(--color-text-body); }
|
|
37
|
+
.ws-table tbody tr + tr > * { border-top: var(--ws-divider); }
|
|
38
|
+
.ws-table .ws-table__name { font-weight: 500; color: var(--color-text-heading); }
|
|
39
|
+
/* A second line in a cell (a figure's base, a name's role). */
|
|
40
|
+
.ws-table__sub { display: block; font-size: var(--ws-text-meta); line-height: var(--space-md); font-weight: 400; color: var(--color-text-muted); }
|
|
41
|
+
/* Numbers: right-aligned, tabular, in a fixed column (64, 96 wide) so every row's digits stack; a wider number widens
|
|
42
|
+
the column, it never truncates. */
|
|
43
|
+
.ws-table .ws-table__num { width: var(--ws-table-num-w); text-align: right; font-variant-numeric: tabular-nums; }
|
|
44
|
+
.ws-table .ws-table__num--wide { --ws-table-num-w: var(--space-6xl); }
|
|
45
|
+
/* Totals: the body's rhythm, a line above, 600 navy; they stay at the foot of a scroller that has a height. */
|
|
46
|
+
.ws-table tfoot th, .ws-table tfoot td { position: sticky; bottom: 0; z-index: 1; border-top: var(--ws-divider); font-size: var(--ws-text-body); line-height: calc(var(--space-md) + var(--space-xxs)); font-weight: 600; color: var(--color-text-heading); }
|
|
47
|
+
/* The first column stays while the rest slide under it (and the check column with it). */
|
|
48
|
+
.ws-table tr > :first-child, .ws-table .ws-table__check + * { position: sticky; left: 0; z-index: 1; }
|
|
49
|
+
.ws-table .ws-table__check + * { left: var(--ws-table-check-w); }
|
|
50
|
+
.ws-table thead tr > :first-child, .ws-table thead .ws-table__check + * { z-index: 3; }
|
|
51
|
+
.ws-table tfoot tr > :first-child, .ws-table tfoot .ws-table__check + * { z-index: 2; }
|
|
52
|
+
/* The check column: a fixed width (the sticky name sits after it), the skinned Checkbox on a 24 px target, 40 on touch. */
|
|
53
|
+
.ws-table .ws-table__check { width: var(--ws-table-check-w); padding-right: var(--space-sm); }
|
|
54
|
+
.ws-table__check .ws-check { position: relative; align-items: center; justify-content: center; width: var(--space-lg); height: var(--space-lg); padding: 0; }
|
|
55
|
+
.ws-table__check .ws-check__box { margin-top: 0; }
|
|
56
|
+
@media (pointer: coarse) { .ws-table__check .ws-check::after { content: ""; position: absolute; inset: calc((100% - var(--ws-touch)) / 2); } }
|
|
57
|
+
/* Row acts: small ghost buttons, always in the DOM. The More act stands in for them on a phone. */
|
|
58
|
+
.ws-table .ws-table__acts { text-align: right; }
|
|
59
|
+
.ws-table__acts .ws-btn + .ws-btn { margin-left: var(--space-xxs); }
|
|
60
|
+
.ws-table .ws-table__more { display: none; }
|
|
61
|
+
/* Hover: North's faint navy wash (a colour change, nothing moves); selected: the sunk cream and a 2 px navy mark on the
|
|
62
|
+
row's left edge, so the state is never colour alone. */
|
|
63
|
+
.ws-table tbody tr > * { transition: background-color var(--ws-duration-hover) var(--ws-ease-soft); }
|
|
64
|
+
@media (hover: hover) { .ws-table tbody tr:hover > * { background-image: linear-gradient(var(--ws-hover-faint), var(--ws-hover-faint)); } }
|
|
65
|
+
.ws-table .ws-table__row--selected > *, .ws-table tr[aria-selected="true"] > * { background-color: var(--ws-surface-sunk); }
|
|
66
|
+
.ws-table .ws-table__row--selected > :first-child, .ws-table tr[aria-selected="true"] > :first-child { box-shadow: inset var(--ws-ring) 0 0 var(--color-text-heading); }
|
|
67
|
+
/* Edge cues. The sticky column's shade: the last sticky cell of each row throws a soft shade over the rows sliding under
|
|
68
|
+
it, only once they have moved (a scroll-driven fade where the browser has one; a hairline that is always there where it
|
|
69
|
+
has not). The far edge: while columns are hidden past it the rows fade out there, and the fade lifts at the end. */
|
|
70
|
+
.ws-table tr > :first-child:not(.ws-table__check)::after, .ws-table .ws-table__check + *::after { content: ""; position: absolute; top: 0; bottom: 0; left: 100%; width: var(--space-xs); background: linear-gradient(to right, var(--ws-table-shade), transparent); opacity: 0; pointer-events: none; }
|
|
71
|
+
/* In dark a navy or black shade vanishes into the dark ground (8 % black moved it 1 step), so the edge reads the dark
|
|
72
|
+
theme's way, as a lit rim: the card edge fading out. */
|
|
73
|
+
[data-theme="dark"] .ws-table { --ws-table-shade: var(--ws-edge-card); }
|
|
74
|
+
@media (prefers-color-scheme: dark) { [data-theme="system"] .ws-table { --ws-table-shade: var(--ws-edge-card); } }
|
|
75
|
+
@supports (animation-timeline: scroll()) {
|
|
76
|
+
.ws-table__scroll { scroll-timeline: --ws-table-x inline; mask-image: linear-gradient(to left, transparent, var(--ws-mask) var(--space-lg)); mask-size: calc(100% + var(--space-lg)) 100%; mask-repeat: no-repeat; animation: ws-table-end linear both; animation-timeline: scroll(self inline); animation-range: calc(100% - var(--space-lg)) 100%; }
|
|
77
|
+
.ws-table__scroll tr > :first-child:not(.ws-table__check)::after, .ws-table__scroll .ws-table__check + *::after { animation: ws-table-shade linear both; animation-timeline: --ws-table-x; animation-range: 0 var(--space-lg); }
|
|
78
|
+
}
|
|
79
|
+
@supports not (animation-timeline: scroll()) {
|
|
80
|
+
.ws-table__scroll tr > :first-child:not(.ws-table__check), .ws-table__scroll .ws-table__check + * { box-shadow: inset calc(var(--ws-hairline) * -1) 0 0 var(--ws-edge); }
|
|
81
|
+
/* The hairline outranks the selected mark there; a selected row's stuck name carries both. */
|
|
82
|
+
.ws-table__scroll .ws-table__row--selected > :first-child:not(.ws-table__check), .ws-table__scroll tr[aria-selected="true"] > :first-child:not(.ws-table__check) { box-shadow: inset var(--ws-ring) 0 0 var(--color-text-heading), inset calc(var(--ws-hairline) * -1) 0 0 var(--ws-edge); }
|
|
83
|
+
}
|
|
84
|
+
@keyframes ws-table-shade { from { opacity: 0; } to { opacity: 1; } }
|
|
85
|
+
@keyframes ws-table-end { from { mask-position: calc(var(--space-lg) * -1) 0; } to { mask-position: 0 0; } }
|
|
86
|
+
/* Column priority and the phone fold. A low-priority column (__low) folds away when the table is narrower than 48rem;
|
|
87
|
+
the row's fold line (__fold, under its name) then says what it held. */
|
|
88
|
+
.ws-table__fold { display: none; font-size: var(--ws-text-meta); line-height: var(--space-md); font-weight: 400; color: var(--color-text-muted); }
|
|
89
|
+
@container ws-table (max-width: 48rem) {
|
|
90
|
+
.ws-table .ws-table__low { display: none; }
|
|
91
|
+
.ws-table .ws-table__fold { display: block; }
|
|
92
|
+
}
|
|
93
|
+
/* Under 30rem the row acts fold into one More act, and a name that would push the rest off the card wraps at 40 % of
|
|
94
|
+
the table, so the stuck columns never leave the sliding ones a sliver (a name that fits keeps its one line). */
|
|
95
|
+
@container ws-table (max-width: 30rem) {
|
|
96
|
+
.ws-table .ws-table__acts > :not(.ws-table__more) { display: none; }
|
|
97
|
+
.ws-table .ws-table__more { display: inline-flex; }
|
|
98
|
+
.ws-table .ws-table__name { min-width: 40cqi; white-space: normal; }
|
|
99
|
+
}
|
|
100
|
+
/* Loading: a ghost bar in each cell, in the chart colours' sweep (add ws-shimmer), the head shown at once. */
|
|
101
|
+
.ws-table__placeholder { height: calc(var(--space-xs) + var(--space-3xs)); border-radius: var(--ws-radius-bar); background: var(--ws-track); }
|
|
102
|
+
.ws-table__placeholder.ws-shimmer { background: var(--ws-shimmer); background-size: 300% 100%; }
|
|
103
|
+
/* Empty: the same card, a sentence and the act that clears the filter. */
|
|
104
|
+
.ws-table__empty { display: flex; flex-direction: column; align-items: center; gap: var(--space-sm); padding: var(--space-lg) var(--ws-card-inset); text-align: center; }
|
|
@@ -8,81 +8,106 @@ The underline tab row: five or more sections of a settings or detail page, read
|
|
|
8
8
|
|
|
9
9
|
## Not for
|
|
10
10
|
|
|
11
|
-
- Two to four views of the same data: use `Segmented
|
|
11
|
+
- Two to four views of the same data: use `Segmented`.
|
|
12
12
|
- Moving between whole pages of a product: use `AppShell`'s nav or `TopBar`'s links.
|
|
13
13
|
- A one-off disclosure that opens and closes in place: use `Fold`.
|
|
14
14
|
|
|
15
15
|
## Anatomy
|
|
16
16
|
|
|
17
|
-
- Row:
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- A count: a small `ws-chip`
|
|
23
|
-
|
|
17
|
+
- Row: tabs 32 px tall (`--ws-control-h`, 40 on touch), 14 / 20 medium words, 24 px apart, a 1 px hairline
|
|
18
|
+
(`--ws-edge-slot`) under the whole row at the column's width.
|
|
19
|
+
- The first tab's words sit on the content edge, the same edge as the card's head and body (K7). Each tab's box bleeds
|
|
20
|
+
6 px past its words, and the row 6 px past the column, so the focus ring has room and no word moves.
|
|
21
|
+
- Rest: `--color-text-body`. Current: navy words and a 2 px navy underline under the words, over the hairline.
|
|
22
|
+
- A count: a small `ws-chip` after the label (18 tall, 11 px).
|
|
23
|
+
- Overflow: the row scrolls sideways with no scrollbar and fades 32 px at the edge that hides tabs (nothing fades when
|
|
24
|
+
every tab fits).
|
|
24
25
|
- The whole row is `role="tablist"`; each item `role="tab"` with `aria-selected` and a matching `role="tabpanel"`.
|
|
25
26
|
|
|
26
27
|
## Variants and sizes
|
|
27
28
|
|
|
28
29
|
| Variant | Where |
|
|
29
30
|
| --- | --- |
|
|
30
|
-
|
|
|
31
|
-
| `
|
|
31
|
+
| The row (this component) | five or more sections of one thing |
|
|
32
|
+
| `ws-tabs-fold` | the row on a desktop and a `Select` of the same sections on a phone (under 40rem): for a row too long to scroll comfortably on a phone |
|
|
32
33
|
|
|
33
34
|
## States
|
|
34
35
|
|
|
35
|
-
- **Default
|
|
36
|
-
- **Hover
|
|
37
|
-
- **Focus
|
|
38
|
-
- **Selected
|
|
39
|
-
- **With a count
|
|
40
|
-
- **
|
|
41
|
-
|
|
42
|
-
- **
|
|
36
|
+
- **Default:** grey words, no mark.
|
|
37
|
+
- **Hover (a mouse only):** the words turn navy (no underline until selected).
|
|
38
|
+
- **Focus:** the kit's ring, drawn inside the tab's box (the row scrolls, so a ring outside it would be cut).
|
|
39
|
+
- **Selected:** navy words, the 2 px underline.
|
|
40
|
+
- **With a count:** the chip sits after the label, never replacing it.
|
|
41
|
+
- **Disabled** (a section not yet available): 40%, `aria-disabled="true"`, skipped by click and arrow keys.
|
|
42
|
+
- **Long row:** it scrolls sideways and fades at the hidden edge; the page brings the chosen tab into view.
|
|
43
|
+
- **Narrow:** as long; on a phone, `ws-tabs-fold` shows the `Select` instead.
|
|
44
|
+
- **Touch:** tabs 40 tall; the row swipes.
|
|
45
|
+
- **Dark:** the hairline, the words and the underline take their dark values.
|
|
43
46
|
|
|
44
47
|
## Behaviour
|
|
45
48
|
|
|
46
|
-
- Keyboard:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
- Keyboard: left and right arrows move and choose, Home and End jump to the ends; only the chosen tab is in the Tab order
|
|
50
|
+
(`tabindex="-1"` on the rest). Activation follows focus.
|
|
51
|
+
- On a choice the page calls `tab.scrollIntoView({ inline: 'nearest', block: 'nearest' })`, so the chosen tab is never
|
|
52
|
+
off screen (K7); `scroll-padding-inline` keeps it clear of the fade.
|
|
53
|
+
- With `ws-tabs-fold`, the page keeps the row and the `Select` on the same section.
|
|
54
|
+
- Motion: the underline slides to the chosen tab and takes its width (North's tab spring, .4 / .12, the department
|
|
55
|
+
card underline), plain CSS with no script and no new markup. The chosen tab is a CSS anchor; the row's `::after` is
|
|
56
|
+
the travelling underline, its two ends easing on motion's own `spring(.4, .12)` written out as `linear()` (500 ms;
|
|
57
|
+
`--ws-spring-tab-duration` and `--ws-spring-tab`). At rest the chosen tab draws its own underline (the 0.4 look); while
|
|
58
|
+
the row is pointed at or holds focus the travelling one takes its place, so a choice made then slides. Without CSS
|
|
59
|
+
anchor positioning, or under reduced motion, it lands at once. The edge fade follows the scroll and is not motion.
|
|
60
|
+
Try it in the preview: click a tab.
|
|
54
61
|
|
|
55
62
|
## Content
|
|
56
63
|
|
|
57
|
-
- Section names: sentence case, one or two words, naming the section
|
|
58
|
-
"View the showings").
|
|
64
|
+
- Section names: sentence case, one or two words, naming the section ("Overview", "Showings").
|
|
59
65
|
- No em dashes.
|
|
60
|
-
- A count chip is a bare number (
|
|
66
|
+
- A count chip is a bare number (or a short word, "1 new"), never a sentence.
|
|
61
67
|
|
|
62
68
|
## Props
|
|
63
69
|
|
|
64
70
|
| Name | Type | Meaning |
|
|
65
71
|
| --- | --- | --- |
|
|
66
|
-
| `items` | `{ id, label, count?, disabled? }[]` |
|
|
67
|
-
| `value` | `string` |
|
|
68
|
-
| `onChange` | `(id: string) => void` |
|
|
69
|
-
| `panels` | `Record<string, ReactNode>` |
|
|
72
|
+
| `items` | `{ id, label, count?, disabled? }[]` | the tabs, in order |
|
|
73
|
+
| `value` | `string` | the chosen tab's id |
|
|
74
|
+
| `onChange` | `(id: string) => void` | called when the choice moves |
|
|
75
|
+
| `panels` | `Record<string, ReactNode>` | each tab's content |
|
|
76
|
+
| `foldOnPhone` | `boolean` | renders `ws-tabs-fold` with its `Select` |
|
|
70
77
|
|
|
71
78
|
## Rules
|
|
72
79
|
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- **Source**: Intentional addition, decided from the system (26 Oct 2026): five-plus sections of a settings or detail
|
|
80
|
-
page need a row `Segmented` was never meant to hold. Derived from the kit's own underline Tabs variant (Radix-based,
|
|
81
|
-
matching this product's look exactly) and the card's foot hairline tone for the row's underline.
|
|
80
|
+
- Underline tabs for five-plus sections; `Segmented` for two to four views.
|
|
81
|
+
- No tab row of its own styles: every tab row is this component.
|
|
82
|
+
- The first tab's words sit on the content edge (K7: the 0.4.0 row put them 4 px in).
|
|
83
|
+
- A tab row never hides its overflow silently: it fades at the hidden edge and the chosen tab is always in view; more
|
|
84
|
+
than four tabs on a phone fold into a `Select`.
|
|
85
|
+
- The hairline under the row is `--ws-edge-slot`, a content line, not a surface edge.
|
|
82
86
|
|
|
83
87
|
## Source
|
|
84
88
|
|
|
85
|
-
**Derived from**: the kit's
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
**Derived from**: the kit's underline Tabs (26 Oct 2026), the Revenue OS audit's K7 (the row hid its overflow and its
|
|
90
|
+
chosen tab, and its first tab sat 4 px in), North's tab underline spring (app.js) and its focus ring.
|
|
91
|
+
|
|
92
|
+
## Markup
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<div role="tablist" aria-label="Lead" class="ws-tabs">
|
|
96
|
+
<button type="button" role="tab" id="t-overview" aria-selected="true" aria-controls="p-overview" class="ws-tabs__tab">Overview</button>
|
|
97
|
+
<button type="button" role="tab" id="t-showings" aria-selected="false" aria-controls="p-showings" tabindex="-1" class="ws-tabs__tab">Showings <span class="ws-chip ws-chip--sunk">1</span></button>
|
|
98
|
+
</div>
|
|
99
|
+
<div id="p-overview" role="tabpanel" aria-labelledby="t-overview">...</div>
|
|
100
|
+
|
|
101
|
+
<div class="ws-tabs-fold">
|
|
102
|
+
<div role="tablist" aria-label="Account" class="ws-tabs">...</div>
|
|
103
|
+
<span class="ws-select-wrap ws-tabs-fold__select">
|
|
104
|
+
<select class="ws-select" aria-label="Section"><option selected>Overview</option><option>Deals</option></select>
|
|
105
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6l4 4 4-4"/></svg>
|
|
106
|
+
</span>
|
|
107
|
+
</div>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Styles
|
|
111
|
+
|
|
112
|
+
`design-system/components/Tabs/style.css`: `ws-tabs`, `ws-tabs__tab`, `ws-tabs-fold` and `__select`. The edge fade is a
|
|
113
|
+
scroll-driven animation (`ws-scroll-edges`, shared with `Segmented`); where a browser has none, the row scrolls without it.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- @dsCard group="Navigation" height=
|
|
1
|
+
<!-- @dsCard group="Navigation" height=420 -->
|
|
2
2
|
<!doctype html>
|
|
3
3
|
<html>
|
|
4
4
|
<head>
|
|
@@ -8,37 +8,69 @@
|
|
|
8
8
|
<link rel="stylesheet" href="../bundle.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
|
-
<div class="ws-stage ws-stage--
|
|
11
|
+
<div class="ws-stage ws-stage--column">
|
|
12
12
|
|
|
13
13
|
<div class="ws-stack">
|
|
14
|
-
<span class="ws-muted">
|
|
15
|
-
<div
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
<span class="ws-muted">Six sections, one with a count, one not ready yet; the first tab's words on the content edge. More than four on a phone fold into a Select. Click a tab: the underline slides to it on North's tab spring</span>
|
|
15
|
+
<div class="ws-card ws-card--inset">
|
|
16
|
+
<div class="ws-tabs-fold">
|
|
17
|
+
<div role="tablist" aria-label="Lead" class="ws-tabs">
|
|
18
|
+
<button type="button" role="tab" id="t-overview" aria-selected="true" aria-controls="p-overview" class="ws-tabs__tab">Overview</button>
|
|
19
|
+
<button type="button" role="tab" id="t-activity" aria-selected="false" aria-controls="p-overview" class="ws-tabs__tab" tabindex="-1">Activity</button>
|
|
20
|
+
<button type="button" role="tab" id="t-showings" aria-selected="false" aria-controls="p-overview" class="ws-tabs__tab" tabindex="-1">Showings <span class="ws-chip ws-chip--sunk">1</span></button>
|
|
21
|
+
<button type="button" role="tab" id="t-calls" aria-selected="false" aria-controls="p-overview" class="ws-tabs__tab" tabindex="-1">Calls</button>
|
|
22
|
+
<button type="button" role="tab" id="t-notes" aria-selected="false" aria-controls="p-overview" class="ws-tabs__tab" tabindex="-1">Notes</button>
|
|
23
|
+
<button type="button" role="tab" id="t-offer" aria-selected="false" aria-disabled="true" aria-controls="p-overview" class="ws-tabs__tab" tabindex="-1">Offer</button>
|
|
24
|
+
</div>
|
|
25
|
+
<span class="ws-select-wrap ws-tabs-fold__select">
|
|
26
|
+
<select class="ws-select" aria-label="Section" aria-controls="p-overview">
|
|
27
|
+
<option selected>Overview</option><option>Activity</option><option>Showings (1)</option><option>Calls</option><option>Notes</option><option disabled>Offer</option>
|
|
28
|
+
</select>
|
|
29
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6l4 4 4-4"/></svg>
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
<p id="p-overview" role="tabpanel" aria-labelledby="t-overview" class="ws-body" style="padding-top: var(--space-sm);">New lead 6 days ago, referred by Dana Alvarez.</p>
|
|
24
33
|
</div>
|
|
25
|
-
<div id="p-overview" role="tabpanel" aria-labelledby="t-overview" class="ws-body" style="padding-top: var(--space-sm);">New lead 6 days ago, referred by Dana Alvarez.</div>
|
|
26
34
|
</div>
|
|
27
35
|
|
|
28
|
-
<div class="ws-stack" style="width:
|
|
29
|
-
<span class="ws-muted">
|
|
30
|
-
<div role="tablist" aria-label="
|
|
31
|
-
<button type="button" role="tab" aria-selected="
|
|
32
|
-
<button type="button" role="tab" aria-selected="
|
|
33
|
-
<button type="button" role="tab" aria-selected="false" class="ws-tabs__tab" tabindex="-1">
|
|
34
|
-
|
|
35
|
-
</button>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
<div class="ws-stack" style="width: 300px; max-width: 100%;">
|
|
37
|
+
<span class="ws-muted">Too wide for its column: it scrolls sideways and fades at the edge that hides tabs</span>
|
|
38
|
+
<div role="tablist" aria-label="Pipeline stage" class="ws-tabs">
|
|
39
|
+
<button type="button" role="tab" aria-selected="false" class="ws-tabs__tab" tabindex="-1">Upcoming demos</button>
|
|
40
|
+
<button type="button" role="tab" aria-selected="true" class="ws-tabs__tab">Quote sent</button>
|
|
41
|
+
<button type="button" role="tab" aria-selected="false" class="ws-tabs__tab" tabindex="-1">Signed</button>
|
|
42
|
+
<button type="button" role="tab" aria-selected="false" class="ws-tabs__tab" tabindex="-1">Closed won</button>
|
|
43
|
+
<button type="button" role="tab" aria-selected="false" class="ws-tabs__tab" tabindex="-1">Closed lost</button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="ws-stack">
|
|
48
|
+
<span class="ws-muted">The fold: the row on a desktop, a Select of the same sections on a phone</span>
|
|
49
|
+
<div class="ws-tabs-fold">
|
|
50
|
+
<div role="tablist" aria-label="Account" class="ws-tabs">
|
|
51
|
+
<button type="button" role="tab" aria-selected="true" class="ws-tabs__tab">Overview</button>
|
|
52
|
+
<button type="button" role="tab" aria-selected="false" class="ws-tabs__tab" tabindex="-1">Deals</button>
|
|
53
|
+
<button type="button" role="tab" aria-selected="false" class="ws-tabs__tab" tabindex="-1">Contacts</button>
|
|
54
|
+
<button type="button" role="tab" aria-selected="false" class="ws-tabs__tab" tabindex="-1">Usage</button>
|
|
55
|
+
<button type="button" role="tab" aria-selected="false" class="ws-tabs__tab" tabindex="-1">Billing</button>
|
|
56
|
+
</div>
|
|
57
|
+
<span class="ws-select-wrap ws-tabs-fold__select">
|
|
58
|
+
<select class="ws-select" aria-label="Section">
|
|
59
|
+
<option selected>Overview</option><option>Deals</option><option>Contacts</option><option>Usage</option><option>Billing</option>
|
|
60
|
+
</select>
|
|
61
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6l4 4 4-4"/></svg>
|
|
62
|
+
</span>
|
|
39
63
|
</div>
|
|
40
64
|
</div>
|
|
41
65
|
|
|
42
66
|
</div>
|
|
67
|
+
<script>
|
|
68
|
+
/* The demo's wiring: a click chooses the tab (an app keeps the choice; the underline slides on its own). */
|
|
69
|
+
document.addEventListener('click', (e) => {
|
|
70
|
+
const tab = e.target.closest('.ws-tabs__tab');
|
|
71
|
+
if (!tab || tab.getAttribute('aria-disabled') === 'true') return;
|
|
72
|
+
for (const t of tab.closest('.ws-tabs').querySelectorAll('.ws-tabs__tab')) { t.setAttribute('aria-selected', String(t === tab)); t.tabIndex = t === tab ? 0 : -1; }
|
|
73
|
+
});
|
|
74
|
+
</script>
|
|
43
75
|
</body>
|
|
44
76
|
</html>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* ---- Tabs: a page's own sections, words on a hairline, the current one navy with a 2 px underline under its words.
|
|
2
|
+
The first tab's words sit on the content edge (K7); each tab's box bleeds 6 px past its words (and the row 6 px past
|
|
3
|
+
the column) so the inset focus ring has room without moving a word. The row scrolls sideways when it is too wide,
|
|
4
|
+
fading at the edge that hides tabs (a scroll-driven fade: nothing shows when it all fits), and a page brings the
|
|
5
|
+
chosen tab into view (scrollIntoView, inline: nearest). On a phone a long row can fold into a Select (.ws-tabs-fold). ---- */
|
|
6
|
+
.ws-tabs { display: flex; align-items: stretch; gap: var(--space-lg); margin-inline: calc(var(--space-xxs-plus) * -1); padding-inline: var(--space-xxs-plus); overflow-x: auto; scrollbar-width: none; scroll-padding-inline: var(--space-xl); background: linear-gradient(in srgb, var(--ws-edge-slot), var(--ws-edge-slot)) bottom / 100% var(--ws-line) no-repeat content-box; }
|
|
7
|
+
.ws-tabs::-webkit-scrollbar { display: none; }
|
|
8
|
+
@supports (animation-timeline: scroll()) {
|
|
9
|
+
.ws-tabs { mask-image: linear-gradient(90deg, transparent, currentColor var(--ws-fade-start), currentColor calc(100% - var(--ws-fade-end)), transparent); animation: ws-scroll-edges linear; animation-timeline: scroll(self inline); }
|
|
10
|
+
}
|
|
11
|
+
.ws-tabs__tab { position: relative; display: inline-flex; flex-shrink: 0; align-items: center; gap: var(--space-xxs-plus); height: var(--ws-control-h); margin-inline: calc(var(--space-xxs-plus) * -1); padding: 0 var(--space-xxs-plus); border: 0; border-radius: var(--ws-radius-control); background: none; font-size: var(--ws-text-body); line-height: calc(var(--space-md) + var(--space-xxs)); font-weight: 500; color: var(--color-text-body); white-space: nowrap; cursor: pointer; }
|
|
12
|
+
.ws-tabs__tab:focus-visible { outline-offset: calc(var(--ws-focus-width) * -1); }
|
|
13
|
+
@media (hover: hover) { .ws-tabs__tab:hover { color: var(--color-text-heading); } }
|
|
14
|
+
.ws-tabs__tab[aria-selected="true"] { color: var(--color-text-heading); }
|
|
15
|
+
.ws-tabs__tab[aria-selected="true"]::after { content: ""; position: absolute; left: var(--space-xxs-plus); right: var(--space-xxs-plus); bottom: 0; height: var(--ws-ring); background: var(--color-text-heading); border-radius: var(--ws-radius-bar); }
|
|
16
|
+
.ws-tabs__tab[aria-disabled="true"] { opacity: 0.4; cursor: default; }
|
|
17
|
+
.ws-tabs__tab .ws-chip { height: calc(var(--space-md) + var(--space-3xs)); padding: 0 var(--space-xxs-plus); font-size: var(--ws-text-micro); line-height: var(--ws-leading-micro); }
|
|
18
|
+
/* The fold: on a phone the row gives way to a Select of the same sections (the page keeps the two in step). */
|
|
19
|
+
.ws-tabs-fold__select { display: none; }
|
|
20
|
+
@media (max-width: 40rem) {
|
|
21
|
+
.ws-tabs-fold > .ws-tabs { display: none; }
|
|
22
|
+
.ws-tabs-fold > .ws-tabs-fold__select { display: flex; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* ---- Motion: the underline slides to the chosen tab on North's tab spring (.4 / .12, the department card underline,
|
|
26
|
+
app.js@754407; north-motion 3) instead of jumping. The chosen tab is the anchor (CSS anchor positioning) and the
|
|
27
|
+
row's ::after is a travelling underline, under the tab's words (6 in from its box), its two ends easing on motion's
|
|
28
|
+
own spring(.4, .12) written out as linear() (500 ms). At rest the chosen tab draws its own underline, so a resting row
|
|
29
|
+
is the 0.4 row pixel for pixel; while the row is pointed at or holds focus the travelling one takes its place (the
|
|
30
|
+
same bar, at once), so a choice made then slides. Only a real choice moves it; a resize or a late font, away from
|
|
31
|
+
the pointer, lands it at once. No new markup. Without anchor positioning, or under reduced motion, the underline
|
|
32
|
+
moves at once. The spring is the global --ws-spring-tab over --ws-spring-tab-duration. ---- */
|
|
33
|
+
@supports (anchor-name: --a) {
|
|
34
|
+
.ws-tabs { position: relative; anchor-scope: --ws-tabs-current; }
|
|
35
|
+
.ws-tabs::after { content: ""; position: absolute; position-anchor: --ws-tabs-current; left: calc(anchor(left) + var(--space-xxs-plus)); right: calc(anchor(right) + var(--space-xxs-plus)); bottom: anchor(bottom); height: var(--ws-ring); border-radius: var(--ws-radius-bar); background: var(--color-text-heading); opacity: 0; pointer-events: none; }
|
|
36
|
+
.ws-tabs__tab[aria-selected="true"] { anchor-name: --ws-tabs-current; }
|
|
37
|
+
}
|
|
38
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
39
|
+
.ws-tabs__tab { transition: color var(--ws-duration-hover) var(--ws-ease-soft); }
|
|
40
|
+
@supports (anchor-name: --a) {
|
|
41
|
+
.ws-tabs:is(:hover, :focus-within)::after { opacity: 1; transition: left var(--ws-spring-tab-duration) var(--ws-spring-tab), right var(--ws-spring-tab-duration) var(--ws-spring-tab); }
|
|
42
|
+
.ws-tabs:is(:hover, :focus-within) .ws-tabs__tab[aria-selected="true"]::after { opacity: 0; }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
The kit's marketing piece: for a public page, never a product screen.
|
|
2
2
|
|
|
3
|
-
A quoted customer, their initials and their role
|
|
3
|
+
A quoted customer, their initials and their role: a figure on the kit's card, built to carry proof rather than
|
|
4
|
+
product state.
|
|
4
5
|
|
|
5
6
|
## Use it when
|
|
6
7
|
|
|
@@ -15,34 +16,33 @@ A quoted customer, their initials and their role, a `KitCard` built to carry pro
|
|
|
15
16
|
|
|
16
17
|
## Anatomy
|
|
17
18
|
|
|
18
|
-
- Card
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
19
|
+
- Card (`.ws-testimonial`, a `<figure>`): 4px corners, a 1px `--color-border-default` edge, `--color-surface`,
|
|
20
|
+
`--space-lg` (24px) padding, a column.
|
|
21
|
+
- Quote (`__quote`, a `<blockquote>`): 16/24 `--color-text-primary`, no quotation marks drawn by the component.
|
|
22
|
+
- Person (`__who`, a `<figcaption>`): on the card's foot, at least `--space-md` (16px) under the quote; the avatar and
|
|
23
|
+
the words `--space-sm` (12px) apart, centred on each other.
|
|
24
|
+
- Avatar (`__avatar`): a 36px disc (`--ws-control-h-md`), `--ws-surface-sunk` fill, 12px semibold initials in
|
|
25
|
+
`--color-text-heading`.
|
|
26
|
+
- Name (`__name`): 14/20 medium `--color-text-heading`. Role (`__role`): 12/16 `--color-text-muted`, under the name.
|
|
27
|
+
- Row (`.ws-testimonial-row`): a grid of columns at least 280px wide, `--layout-col-gap` (24px) apart, the cards equal
|
|
28
|
+
height in each row; a third card that wraps takes the next row's first column at the same width, never the full row.
|
|
25
29
|
|
|
26
30
|
## Variants and sizes
|
|
27
31
|
|
|
28
|
-
One layout.
|
|
29
|
-
In fello.css: `.ws-testimonial` (`__quote`, `__who`, `__avatar`, `__name`, `__role`); two or three side by side sit in
|
|
30
|
-
`.ws-testimonial-row`, which wraps them to one column when they no longer fit.
|
|
32
|
+
One layout. The card has no fixed width of its own; it takes the column the row gives it.
|
|
31
33
|
|
|
32
34
|
## States
|
|
33
35
|
|
|
34
36
|
- **Default:** as above.
|
|
35
|
-
- **Hover / focus / selected / pressed:** not applicable; a testimonial is read-only
|
|
36
|
-
- **Pending / disabled / error:** not applicable.
|
|
37
|
+
- **Hover / focus / selected / pressed / pending / disabled / error:** not applicable; a testimonial is read-only.
|
|
37
38
|
- **Empty:** not applicable; a testimonial without a real quote and a real name is not shown.
|
|
38
|
-
- **Long label:** a longer quote wraps
|
|
39
|
-
|
|
40
|
-
- **Narrow:** the avatar and name wrap to a tidy row down to a fairly narrow column before the role would need to
|
|
41
|
-
wrap under the name as well.
|
|
39
|
+
- **Long label:** a longer quote wraps; the person stays on the card's foot, level with the person on the next card.
|
|
40
|
+
- **Narrow:** the cards stack; the avatar and the words stay in a row, and a long role wraps under the name.
|
|
42
41
|
|
|
43
42
|
## Behaviour
|
|
44
43
|
|
|
45
44
|
- Keyboard / pointer / touch: nothing interactive; the card is static content.
|
|
45
|
+
- Screen readers: the figure, blockquote and figcaption tie the words to the person who said them.
|
|
46
46
|
- Motion: none.
|
|
47
47
|
|
|
48
48
|
## Content
|
|
@@ -53,7 +53,7 @@ In fello.css: `.ws-testimonial` (`__quote`, `__who`, `__avatar`, `__name`, `__ro
|
|
|
53
53
|
|
|
54
54
|
## Props
|
|
55
55
|
|
|
56
|
-
Not a kit export
|
|
56
|
+
Not a kit export. A reusable version would take:
|
|
57
57
|
|
|
58
58
|
| Prop | Type | Meaning |
|
|
59
59
|
| --- | --- | --- |
|
|
@@ -64,20 +64,36 @@ Not a kit export; composed from `KitCard`. A reusable version would take:
|
|
|
64
64
|
|
|
65
65
|
## Rules
|
|
66
66
|
|
|
67
|
-
- **
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
token for).
|
|
67
|
+
- **The person sits on the card's foot** (4 Nov 2026): side by side, a short quote left its person higher than the
|
|
68
|
+
next card's.
|
|
69
|
+
- **A wrapped card keeps its column** (4 Nov 2026): the row was a wrapping flex row, so a third card on a tablet ran
|
|
70
|
+
the full width under the other two. It is a grid now.
|
|
71
|
+
- **A figure, not a div** (4 Nov 2026): `<figure>`, `<blockquote>` and `<figcaption>`, so the quote and its source are
|
|
72
|
+
one thing to a screen reader.
|
|
73
|
+
- **The avatar is the house disc**: `--ws-surface-sunk` with heading-colour initials, the system's "neutral discs, navy
|
|
74
|
+
initials", in both themes (it was `--color-gray-1`, which stayed near white in dark).
|
|
75
|
+
- The card stays at the kit's 4px radius ceiling, the testimonial card's own "tighter than feature cards" corner.
|
|
77
76
|
|
|
78
77
|
## Source
|
|
79
78
|
|
|
80
|
-
**Derived from**: the kit's own
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
**Derived from**: the kit's own card, its 4px testimonial radius, the house neutral disc and the dark-safe surface
|
|
80
|
+
names (D12). **Precedent**: an intentional addition decided from the system, 26 Oct 2026.
|
|
81
|
+
|
|
82
|
+
## Markup
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<div class="ws-testimonial-row">
|
|
86
|
+
<figure class="ws-testimonial">
|
|
87
|
+
<blockquote class="ws-testimonial__quote">Our team closes a cold call in under six minutes now.</blockquote>
|
|
88
|
+
<figcaption class="ws-testimonial__who">
|
|
89
|
+
<span class="ws-testimonial__avatar" aria-hidden="true">PN</span>
|
|
90
|
+
<div class="ws-testimonial__person">
|
|
91
|
+
<div class="ws-testimonial__name">Priya Nair</div>
|
|
92
|
+
<div class="ws-testimonial__role">Team lead, Nair Realty Group</div>
|
|
93
|
+
</div>
|
|
94
|
+
</figcaption>
|
|
95
|
+
</figure>
|
|
96
|
+
</div>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Styles: the `Testimonial` block (`components/Testimonial/style.css`). Nothing else.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<!-- @dsCard group="Marketing" height=
|
|
1
|
+
<!-- @dsCard group="Marketing" height=198 -->
|
|
2
2
|
<!doctype html>
|
|
3
|
-
<html>
|
|
3
|
+
<html lang="en">
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="utf-8">
|
|
6
6
|
<title>Testimonial</title>
|
|
@@ -11,27 +11,27 @@
|
|
|
11
11
|
<div class="ws-stage ws-stage--grey">
|
|
12
12
|
<div class="ws-testimonial-row">
|
|
13
13
|
|
|
14
|
-
<
|
|
15
|
-
<
|
|
16
|
-
<
|
|
14
|
+
<figure class="ws-testimonial">
|
|
15
|
+
<blockquote class="ws-testimonial__quote">Our team closes a cold call in under six minutes now. We book more appointments with fewer calls, not more of them.</blockquote>
|
|
16
|
+
<figcaption class="ws-testimonial__who">
|
|
17
17
|
<span class="ws-testimonial__avatar" aria-hidden="true">PN</span>
|
|
18
|
-
<div>
|
|
18
|
+
<div class="ws-testimonial__person">
|
|
19
19
|
<div class="ws-testimonial__name">Priya Nair</div>
|
|
20
20
|
<div class="ws-testimonial__role">Team lead, Nair Realty Group</div>
|
|
21
21
|
</div>
|
|
22
|
-
</
|
|
23
|
-
</
|
|
22
|
+
</figcaption>
|
|
23
|
+
</figure>
|
|
24
24
|
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
<
|
|
25
|
+
<figure class="ws-testimonial">
|
|
26
|
+
<blockquote class="ws-testimonial__quote">It drafts our weekly posts from our own wins. I approve one in under a minute instead of starting from a blank page every Friday, and the team actually reads them.</blockquote>
|
|
27
|
+
<figcaption class="ws-testimonial__who">
|
|
28
28
|
<span class="ws-testimonial__avatar" aria-hidden="true">DS</span>
|
|
29
|
-
<div>
|
|
29
|
+
<div class="ws-testimonial__person">
|
|
30
30
|
<div class="ws-testimonial__name">Dana Soto</div>
|
|
31
31
|
<div class="ws-testimonial__role">Broker, Soto Home Group</div>
|
|
32
32
|
</div>
|
|
33
|
-
</
|
|
34
|
-
</
|
|
33
|
+
</figcaption>
|
|
34
|
+
</figure>
|
|
35
35
|
|
|
36
36
|
</div>
|
|
37
37
|
</div>
|