@felloinnovations/brand-kit 0.5.1 → 0.5.3

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.
Files changed (104) hide show
  1. package/AGENTS.md +29 -9
  2. package/CHANGELOG.md +86 -0
  3. package/README.md +10 -4
  4. package/design-system/01-how-claude-decides.md +5 -3
  5. package/design-system/03-colour.md +19 -1
  6. package/design-system/05-space-and-layout.md +2 -1
  7. package/design-system/08-choosing-components.md +20 -4
  8. package/design-system/09-charts.md +6 -3
  9. package/design-system/11-page-recipes.md +40 -1
  10. package/design-system/13-motion.md +1 -1
  11. package/design-system/15-ai-experiences.md +11 -0
  12. package/design-system/17-decisions.md +41 -0
  13. package/design-system/components/AppShell/README.md +89 -21
  14. package/design-system/components/AppShell/preview.html +148 -3
  15. package/design-system/components/AppShell/style.css +47 -0
  16. package/design-system/components/Avatar/README.md +6 -3
  17. package/design-system/components/Avatar/preview.html +2 -1
  18. package/design-system/components/Avatar/style.css +3 -0
  19. package/design-system/components/Banner/README.md +9 -5
  20. package/design-system/components/Banner/preview.html +9 -0
  21. package/design-system/components/Banner/style.css +3 -0
  22. package/design-system/components/Button/README.md +29 -2
  23. package/design-system/components/Button/preview.html +33 -1
  24. package/design-system/components/Button/style.css +21 -0
  25. package/design-system/components/Calendar/README.md +148 -0
  26. package/design-system/components/Calendar/preview.html +449 -0
  27. package/design-system/components/Calendar/style.css +61 -0
  28. package/design-system/components/Card/README.md +13 -0
  29. package/design-system/components/Card/preview.html +23 -0
  30. package/design-system/components/Card/style.css +19 -0
  31. package/design-system/components/ChatCard/README.md +74 -3
  32. package/design-system/components/ChatCard/preview.html +126 -25
  33. package/design-system/components/ChatCard/style.css +6 -0
  34. package/design-system/components/Chip/README.md +16 -7
  35. package/design-system/components/Chip/preview.html +25 -1
  36. package/design-system/components/Chip/style.css +5 -0
  37. package/design-system/components/Combobox/README.md +10 -4
  38. package/design-system/components/Combobox/preview.html +19 -4
  39. package/design-system/components/Combobox/style.css +7 -1
  40. package/design-system/components/CommentThread/README.md +138 -0
  41. package/design-system/components/CommentThread/preview.html +134 -0
  42. package/design-system/components/CommentThread/style.css +41 -0
  43. package/design-system/components/DateInput/README.md +38 -4
  44. package/design-system/components/DateInput/preview.html +50 -2
  45. package/design-system/components/DateInput/style.css +6 -0
  46. package/design-system/components/Dialog/README.md +26 -3
  47. package/design-system/components/Dialog/preview.html +80 -0
  48. package/design-system/components/Dialog/style.css +28 -3
  49. package/design-system/components/EmptyState/README.md +1 -1
  50. package/design-system/components/Fold/README.md +21 -0
  51. package/design-system/components/Fold/preview.html +44 -1
  52. package/design-system/components/Fold/style.css +30 -0
  53. package/design-system/components/InlineEdit/README.md +116 -0
  54. package/design-system/components/InlineEdit/preview.html +135 -0
  55. package/design-system/components/InlineEdit/style.css +34 -0
  56. package/design-system/components/Input/README.md +20 -1
  57. package/design-system/components/Input/preview.html +24 -0
  58. package/design-system/components/Input/style.css +13 -1
  59. package/design-system/components/KanbanBoard/README.md +68 -4
  60. package/design-system/components/KanbanBoard/preview.html +129 -0
  61. package/design-system/components/KanbanBoard/style.css +31 -0
  62. package/design-system/components/PlanTimeline/README.md +144 -0
  63. package/design-system/components/PlanTimeline/preview.html +109 -0
  64. package/design-system/components/PlanTimeline/style.css +64 -0
  65. package/design-system/components/README.md +11 -6
  66. package/design-system/components/Skeleton/README.md +66 -2
  67. package/design-system/components/Skeleton/preview.html +83 -1
  68. package/design-system/components/Starter/README.md +1 -1
  69. package/design-system/components/Starter/preview.html +1 -1
  70. package/design-system/components/Stepper/README.md +81 -5
  71. package/design-system/components/Stepper/preview.html +64 -1
  72. package/design-system/components/Stepper/style.css +29 -0
  73. package/design-system/components/StreakCalendar/README.md +1 -0
  74. package/design-system/components/Table/README.md +7 -3
  75. package/design-system/components/Table/preview.html +15 -0
  76. package/design-system/components/Table/style.css +2 -0
  77. package/design-system/components/TextDiff/README.md +96 -0
  78. package/design-system/components/TextDiff/preview.html +42 -0
  79. package/design-system/components/TextDiff/style.css +21 -0
  80. package/design-system/components/Timeline/README.md +3 -1
  81. package/design-system/components/TrendLine/README.md +38 -7
  82. package/design-system/components/TrendLine/preview.html +140 -3
  83. package/design-system/components/TrendLine/style.css +12 -2
  84. package/design-system/components/bundle.css +483 -7
  85. package/design-system/tokens.json +15 -1
  86. package/dist/fello-core.css +2 -2
  87. package/dist/fello.css +2 -2
  88. package/dist/fello.scoped.css +2 -2
  89. package/dist/index.d.ts +1 -1
  90. package/dist/scoped.js +2 -2
  91. package/dist/tokens.d.ts +8 -0
  92. package/dist/tokens.js +8 -0
  93. package/package.json +7 -2
  94. package/scripts/check-drift.mjs +325 -0
  95. package/scripts/token-model.mjs +2 -1
  96. package/tokens/_dark.css +2 -0
  97. package/tokens/_product.css +2 -0
  98. package/tokens/palettes.ts +1 -1
  99. package/tokens/shadcn-compat.css +1 -1
  100. package/tokens/shadcn-compat.hsl.css +1 -1
  101. package/tokens/tokens.css +14 -1
  102. package/tokens/tokens.md +53 -46
  103. package/tokens/tokens.root.css +14 -1
  104. package/tokens/tokens.ts +9 -1
@@ -47,6 +47,13 @@ A split pair (`ws-btn-split`): two acts in equal halves across the width, the pr
47
47
  half would be under 160 px, or narrower than either label on its one line, they stack, the primary at the bottom, under
48
48
  the thumb.
49
49
 
50
+ An act with a menu of its other forms (`ws-btn-join`, #22): the act and a caret joined into one shape, the caret
51
+ opening a `Menu` of the same act done another way ("Approve", and behind the caret "Approve and post now", "Approve and
52
+ schedule"). The inner corners are square; a hairline in the words' colour at 35 % parts the halves, 6 in from the top
53
+ and bottom (two `secondary` halves share one edge instead); the caret is a square of the button's height with a 16 px
54
+ chevron (28 at small and medium, 40 large and on touch). Any look, any size; both halves wear the same classes. The
55
+ pair gives to a press as one. Not `ws-btn-split`, which is two different acts side by side.
56
+
50
57
  `--wrap`: a label that may be wider than its column wraps in balanced lines (a button's words never wrap otherwise).
51
58
 
52
59
  ## States
@@ -75,7 +82,10 @@ the thumb.
75
82
  - **Pending under the pointer:** keeps its resting look (no brighter navy, no light, no wash): it refuses the press a
76
83
  hover would invite.
77
84
  - **Long label:** one line; with `ws-btn--wrap`, wider than its column, it wraps in balanced lines.
78
- - **Narrow:** the split pair stacks; a lone large button may take the width.
85
+ - **Narrow:** the split pair stacks; a lone large button may take the width; a joined act never splits, its act's
86
+ words cut before the caret goes.
87
+ - **Joined, open:** the caret has `aria-expanded="true"` and its menu hangs under it, right edges flush (`Menu`'s
88
+ placement); Escape closes it and returns focus to the caret.
79
89
  - **Touch:** boxed sizes at least 40; `link` and `text` 24 tall.
80
90
  - **Dark:** the act is the light ink with dark words; its hover eases a step toward its words and draws no light
81
91
  (derived: North has no dark values).
@@ -112,6 +122,11 @@ the thumb.
112
122
 
113
123
  ## Rules
114
124
 
125
+ - **A joined act holds the act's other forms, never other acts.** "Approve" with "Approve and post now" behind its
126
+ caret; not "Approve" with "Delete" (that is a `Menu` from a three-dots `IconButton`). The plain act is the one most
127
+ people press. A navy joined act is the surface's one navy act. The caret is named for what it holds ("Other ways to
128
+ approve"), never "More".
129
+
115
130
  - Every button is `Button` or `ButtonLink` (`SubmitButton` in a form), never a styled `<button>` (24 Oct 2026, "One way to
116
131
  use buttons").
117
132
  - One navy (`primary`) button per card, and one for the page; everything else is secondary, well, ghost or words.
@@ -150,6 +165,18 @@ North's 34 px height is not taken: the Fia frames' 32 / 40 sit on the control la
150
165
  <button type="button" class="ws-btn ws-btn--lg ws-btn--primary">Review Grow's rewrite</button>
151
166
  </div>
152
167
 
168
+ <!-- an act with a menu of its other forms: the caret opens a Menu -->
169
+ <div class="ws-btn-join">
170
+ <button type="button" class="ws-btn ws-btn--primary">Approve</button>
171
+ <button type="button" class="ws-btn ws-btn--primary" aria-label="Other ways to approve" aria-haspopup="menu" aria-expanded="false" aria-controls="approve-menu">
172
+ <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>
173
+ </button>
174
+ </div>
175
+ <div class="ws-menu" role="menu" id="approve-menu" popover>
176
+ <button type="button" class="ws-menu-item" role="menuitem">Approve and post now</button>
177
+ <button type="button" class="ws-menu-item" role="menuitem">Approve and schedule</button>
178
+ </div>
179
+
153
180
  <a href="/leads/priya" class="ws-card ws-card--inset" aria-busy="true" style="position: relative; display: block;">
154
181
  Priya Nair <span class="ws-link-pending" aria-hidden="true"></span>
155
182
  </a>
@@ -159,6 +186,6 @@ North's 34 px height is not taken: the Fia frames' 32 / 40 sit on the control la
159
186
 
160
187
  `design-system/components/Button/style.css` (in `bundle.css` and `fello.css`): `ws-btn` with `--sm`, `--lg`, `--primary`,
161
188
  `--secondary`, `--well`, `--ghost`, `--link`, `--text`, `--wrap`, and the pictured states `is-hover`, `is-pressed`,
162
- `is-focus`; `ws-btn-split`; `ws-link-pending`. It also scopes the shared hover values `--ws-edge-hover`,
189
+ `is-focus`; `ws-btn-split`; `ws-btn-join`; `ws-link-pending`. It also scopes the shared hover values `--ws-edge-hover`,
163
190
  `--ws-act-hover` and `--ws-act-light` for every outlined or navy control (the switch's track too). Touch sizes come from
164
191
  the house's touch block; the working dot is the house's `ws-dot`.
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Actions" height=1060 -->
1
+ <!-- @dsCard group="Actions" height=1200 -->
2
2
  <!doctype html>
3
3
  <html>
4
4
  <head>
@@ -56,6 +56,38 @@
56
56
  </div>
57
57
  </div>
58
58
 
59
+ <div class="ws-stack">
60
+ <span class="ws-muted">An act with a menu of its other forms: the act and its caret joined, then the menu open</span>
61
+ <div class="ws-cluster ws-cluster--start">
62
+ <div class="ws-btn-join">
63
+ <button type="button" class="ws-btn ws-btn--primary">Approve</button>
64
+ <button type="button" class="ws-btn ws-btn--primary" aria-label="Other ways to approve" aria-haspopup="menu" aria-expanded="false"><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></button>
65
+ </div>
66
+ <div class="ws-btn-join">
67
+ <button type="button" class="ws-btn ws-btn--well">Save draft</button>
68
+ <button type="button" class="ws-btn ws-btn--well" aria-label="Other ways to save" aria-haspopup="menu" aria-expanded="false"><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></button>
69
+ </div>
70
+ <div class="ws-btn-join">
71
+ <button type="button" class="ws-btn ws-btn--secondary ws-btn--sm">Export</button>
72
+ <button type="button" class="ws-btn ws-btn--secondary ws-btn--sm" aria-label="Other ways to export" aria-haspopup="menu" aria-expanded="false"><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></button>
73
+ </div>
74
+ <div class="ws-btn-join">
75
+ <button type="button" class="ws-btn ws-btn--primary ws-btn--lg">Publish</button>
76
+ <button type="button" class="ws-btn ws-btn--primary ws-btn--lg" aria-label="Other ways to publish" aria-haspopup="menu" aria-expanded="false"><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></button>
77
+ </div>
78
+ </div>
79
+ <div class="ws-stack" style="align-items: flex-start;">
80
+ <div class="ws-btn-join">
81
+ <button type="button" class="ws-btn ws-btn--primary">Approve</button>
82
+ <button type="button" class="ws-btn ws-btn--primary" aria-label="Other ways to approve" aria-haspopup="menu" aria-expanded="true" aria-controls="join-menu"><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></button>
83
+ </div>
84
+ <div class="ws-menu" role="menu" id="join-menu" aria-label="Other ways to approve">
85
+ <button type="button" class="ws-menu-item" role="menuitem">Approve and post now</button>
86
+ <button type="button" class="ws-menu-item" role="menuitem">Approve and schedule</button>
87
+ </div>
88
+ </div>
89
+ </div>
90
+
59
91
  <div class="ws-stack">
60
92
  <span class="ws-muted">Words that act: link (goes somewhere) and text (acts in place), 24 px targets on touch</span>
61
93
  <p class="ws-body">Two leads came in overnight. <button type="button" class="ws-btn ws-btn--link">See them on the board</button></p>
@@ -76,6 +76,27 @@
76
76
  .ws-btn-split { display: flex; flex-wrap: wrap; gap: var(--space-xs); width: 100%; }
77
77
  .ws-btn-split > .ws-btn { flex: 1 1 var(--space-8xl); min-width: fit-content; }
78
78
 
79
+ /* An act with a menu of its other forms (#22): the act and a caret joined into one shape, the caret opening a Menu of
80
+ the same act done another way ("Approve and post now", "Approve and schedule"). Not ws-btn-split (two different acts
81
+ side by side). The inner corners go square; a hairline in the words' colour at 35 % parts the halves, inset 6 from the
82
+ top and bottom (on navy, the white words' line; two outlined halves share one edge instead); the caret is a square of
83
+ its height with a 16 px chevron (28 at small and medium, 40 large). The pair gives to a press as one: neither half
84
+ moves alone, so the join never opens. Each half takes its own focus ring. */
85
+ .ws-btn-join { display: inline-flex; flex-shrink: 0; max-width: 100%; }
86
+ .ws-btn-join > .ws-btn { --ws-press: 1; }
87
+ .ws-btn-join > .ws-btn:first-child { min-width: 0; border-start-end-radius: 0; border-end-end-radius: 0; }
88
+ .ws-btn-join > .ws-btn:last-child { --ws-btn-pad: var(--space-xxs-plus); border-start-start-radius: 0; border-end-start-radius: 0; }
89
+ .ws-btn-join > .ws-btn--lg:last-child { --ws-btn-pad: var(--space-sm); }
90
+ @media (pointer: coarse) { .ws-btn-join > .ws-btn:last-child { --ws-btn-pad: var(--space-sm); } }
91
+ .ws-btn-join > .ws-btn:last-child > svg { width: var(--ws-icon); height: var(--ws-icon); }
92
+ .ws-btn-join > .ws-btn + .ws-btn::after { content: ""; position: absolute; inset-block: var(--space-xxs-plus); inset-inline-start: 0; width: var(--ws-hairline); background: currentColor; opacity: 0.35; pointer-events: none; }
93
+ .ws-btn-join > .ws-btn--secondary + .ws-btn--secondary { margin-inline-start: calc(var(--ws-hairline) * -1); }
94
+ .ws-btn-join > .ws-btn--secondary + .ws-btn--secondary::after { content: none; }
95
+ @media (prefers-reduced-motion: no-preference) {
96
+ .ws-btn-join { transition: scale var(--ws-spring-press-duration) var(--ws-spring-press); }
97
+ .ws-btn-join:active { scale: var(--ws-press-scale); }
98
+ }
99
+
79
100
  /* LinkPending: a link card or a ButtonLink whose page is on its way. Its 1 px navy edge arrives after a beat (a quick
80
101
  answer shows nothing) and breathes until the page answers; it leaves at once. The link holds it (position: relative). */
81
102
  .ws-link-pending { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; opacity: 0; }
@@ -0,0 +1,148 @@
1
+ Things on dates: a month (or a week) of days, Monday first, each day holding its items as rows that open, the steps and
2
+ the view switch over it, today ringed; in a narrow column the same markup reads as an agenda.
3
+
4
+ ## Use it when
5
+
6
+ - Work is laid out by day and the day is the point: scheduled posts, campaign tasks, touches, sends, events.
7
+ - A person moves through time (this month, the next, back to today) and opens an item from the day it falls on.
8
+
9
+ ## Not for
10
+
11
+ - Choosing one day or a range: use `DateInput`.
12
+ - Whether a day happened (a streak of logged activity): use `StreakCalendar`.
13
+ - A plan's phases and tasks with their start and end: use `PlanTimeline`.
14
+ - One thing's dated milestones: use `Stepper --milestones`. What happened, in order: use `Timeline`.
15
+
16
+ ## Anatomy
17
+
18
+ - The bar (`__bar`): the period's name (`ws-section`, "November 2026", "Nov 9 to 15, 2026") on the left; at the right
19
+ the steps (`__steps`: a 28 px previous `IconButton`, a small secondary `Button` "Today", a 28 px next) and the view
20
+ switch (`Segmented`, "Month" and "Week"). It wraps on a narrow column, the switch under the title.
21
+ - The key (`__key`, optional): what each tone means, a mark (`__item-mark` with the item's tone class) and its words in
22
+ the meta words, on fixed 160 px columns so a wrapped key keeps shared edges; the lit period's swatch is `__key-lit`.
23
+ - The weekday names (`__weekdays`): once over the grid, 11 / 13 medium muted, centred over their days, `aria-hidden`.
24
+ - The days (`__days`, an `ol`): seven equal columns, the days abutting on 0.5 px lines (`--ws-edge-card`), 4 px corners
25
+ outside. A day (`__day`): at least 120 tall, 4 inside; its head (`__day-head`) centres the number (`__date`, a
26
+ `time`): 24 px round, 12 / 16 medium navy, figures tabular. A day outside the month (`--outside`): its number muted.
27
+ - Today: `aria-current="date"` on its `time`, a 1 px ring and the number in the salmon's AA step (`--ws-word-needs`,
28
+ semibold), as `DateInput`'s today is.
29
+ - A lit day (`--lit`): a day inside the period the view is about (the launch week, the phase in hand), one quiet tint
30
+ (`--ws-tint-accent`), named in the key.
31
+ - The items (`__items`, a `ul`): rows 2 apart. An item (`__item`, a link or a button with a `title`): 24 tall, 6 inside,
32
+ 4 px corners, on the sunk cream; a 6 px dot in its tone, 6 before its words, 12 / 16 navy, one line, whole in its
33
+ `title`. A milestone (`--milestone`): the dot a diamond, the words medium.
34
+ - More (`__more`, a `ws-btn ws-btn--text`): "2 more" under the third item, opening the day (a popover or the week).
35
+ - An empty day in the week view: `__empty`, "Nothing due" in the meta words.
36
+
37
+ ## Variants and sizes
38
+
39
+ | Variant | Class | What changes |
40
+ | --- | --- | --- |
41
+ | Month | `ws-calendar` | Six weeks of seven days; three items a day, then "N more" |
42
+ | Week | `ws-calendar--week` | One row of seven days at least 200 tall; every item shown |
43
+ | Agenda | (automatic, under 40rem of its own width) | A row a day that holds something (and today): the weekday and number in a 40 px column, the items beside; outside days and empty days hidden, the weekday names and the tint gone |
44
+
45
+ | Tone | Class | Mark |
46
+ | --- | --- | --- |
47
+ | First (default) | `__item` | navy (`--color-text-heading`) |
48
+ | Second | `__item--2` | teal (`--ws-chart-finished`) |
49
+ | Third | `__item--3` | purple (`--ws-chart-past-half`) |
50
+ | Waits on the person | `__item--needs` | salmon (`--ws-chart-under-half`) |
51
+ | A milestone | `__item--milestone` | a diamond in the item's tone |
52
+
53
+ A tone token passed as `--tone` on an item wins over its class (a fourth category, a palette's own). The tones follow
54
+ the palette: never a hex.
55
+
56
+ ## States
57
+
58
+ - **Today**: ringed in every view; in the agenda it stays even with nothing on it.
59
+ - **Hover** (an item that opens): North's wash over the sunk cream, stronger on press; never a lift.
60
+ - **Focus**: the house ring, drawn inside the item so a day's edge never cuts it.
61
+ - **Loading**: the grid drawn, a `ws-skeleton ws-skeleton--line` or two in each day where items will land.
62
+ - **Empty**: a month with nothing in it is still a grid; say why in one line under the bar ("Nothing scheduled in
63
+ November"). A view with no data at all is an `EmptyState` in the card instead.
64
+ - **Touch**: an item is 40 tall (a boxed control, K10); the steps' icon buttons keep a 40 px hit area.
65
+ - **Dark and the navy ground**: from the tokens; the lines, the sunk cream and the tint all turn.
66
+
67
+ ## Behaviour
68
+
69
+ - No script in the kit: the app renders the days and items and wires the steps, "Today", the switch and "N more".
70
+ - Keyboard: items are links or buttons in document order, day after day; the steps and the switch are buttons. The
71
+ title carries `aria-live="polite"`, so a step reads the new period aloud.
72
+ - Screen reader: the days are an ordered list named for the period (`aria-label="November 2026"`); each day's number
73
+ carries its full date in a `ws-sr` span ("Tuesday, November 10"), the weekday names are `aria-hidden`.
74
+ - The agenda is a container query on the calendar's own width (`container: ws-calendar / inline-size`), so a calendar
75
+ in a side panel on a wide screen becomes an agenda too.
76
+
77
+ ## Content
78
+
79
+ - The period in the title: "November 2026", "Nov 9 to 15, 2026". Weekday names short ("Mon").
80
+ - An item's words are what happens, in the app's own words ("Email 2: the how-to", "Webinar: the new portal"), never
81
+ its id or a status code; a gate or a launch is a milestone.
82
+ - "N more", never "+N" or "...".
83
+ - No em dashes.
84
+
85
+ ## Props
86
+
87
+ | Name | Type | Meaning |
88
+ | --- | --- | --- |
89
+ | `view` | `"month" \| "week"` | Which grid |
90
+ | `days` | `{ date, outside?, lit?, items: { id, label, tone?, milestone?, href? }[] }[]` | The days in order, Monday first; 42 for a month, 7 for a week |
91
+ | `today` | `string` | `YYYY-MM-DD`, ringed |
92
+ | `perDay` | `number` | Items shown in a month day before "N more" (3) |
93
+ | `onStep` | `(direction: -1 \| 1 \| 0) => void` | Previous, next, or back to today |
94
+ | `onView` | `(view) => void` | The switch |
95
+
96
+ ## Rules
97
+
98
+ - **Monday first**, seven equal columns, every week the same height in a month.
99
+ - **Today is the one salmon mark**: its ring and number in the needs step. An item that waits on the person may take the
100
+ needs tone; nothing else is salmon.
101
+ - **Items are words**: never a block of colour with no label, never a hue on the day itself beyond the one lit period.
102
+ - **A month shows three items a day**, then "N more"; the week shows them all.
103
+ - **Hover is a tint**; nothing lifts, nothing grows.
104
+
105
+ ## Source
106
+
107
+ - **Derived from**: `DateInput`'s month grid (Monday first, today's ring in the needs step), `StreakCalendar`'s
108
+ weekday letters, the house item and wash rules, Seth's 0.5 px lines.
109
+ - **Precedent**: 0.5.3 (Nov 2026), from the field: three Marketing OS screens (campaign tasks, the content calendar and
110
+ the touch calendar) laid out work on a hand-built month and week grid for want of one (issue #23).
111
+
112
+ ## Markup
113
+
114
+ ```html
115
+ <section class="ws-card ws-card--inset ws-calendar" aria-labelledby="cal-title">
116
+ <div class="ws-calendar__bar">
117
+ <h3 class="ws-section ws-calendar__title" id="cal-title" aria-live="polite">November 2026</h3>
118
+ <div class="ws-calendar__steps">
119
+ <button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Previous month"><svg>...</svg></button>
120
+ <button type="button" class="ws-btn ws-btn--secondary ws-btn--sm">Today</button>
121
+ <button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Next month"><svg>...</svg></button>
122
+ </div>
123
+ <div class="ws-segmented" role="group" aria-label="Calendar view">
124
+ <button type="button" class="ws-segmented__item" aria-pressed="true">Month</button>
125
+ <button type="button" class="ws-segmented__item" aria-pressed="false">Week</button>
126
+ </div>
127
+ </div>
128
+ <div class="ws-calendar__weekdays" aria-hidden="true"><span>Mon</span><!-- Tue to Sun --></div>
129
+ <ol class="ws-calendar__days" aria-label="November 2026">
130
+ <li class="ws-calendar__day ws-calendar__day--outside">
131
+ <div class="ws-calendar__day-head"><span class="ws-calendar__weekday" aria-hidden="true">Mon</span><time class="ws-calendar__date" datetime="2026-10-26"><span class="ws-sr">Monday, October </span>26</time></div>
132
+ </li>
133
+ <li class="ws-calendar__day">
134
+ <div class="ws-calendar__day-head"><span class="ws-calendar__weekday" aria-hidden="true">Tue</span><time class="ws-calendar__date" datetime="2026-11-10" aria-current="date"><span class="ws-sr">Tuesday, November </span>10</time></div>
135
+ <ul class="ws-calendar__items">
136
+ <li><a class="ws-calendar__item ws-calendar__item--needs" href="#" title="Walk Stephen through the demo"><span>Walk Stephen through the demo</span></a></li>
137
+ <li><a class="ws-calendar__item ws-calendar__item--milestone" href="#" title="Gate 2: copy signed off"><span>Gate 2: copy signed off</span></a></li>
138
+ </ul>
139
+ <button type="button" class="ws-btn ws-btn--text ws-calendar__more">2 more</button>
140
+ </li>
141
+ </ol>
142
+ </section>
143
+ ```
144
+
145
+ ## Styles
146
+
147
+ `design-system/components/Calendar/style.css`, in `bundle.css` and `fello.css` as `.ws-calendar`. Every value is a
148
+ token; the tones follow the palette. The agenda is a container query (`ws-calendar`, under 40rem).