@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.
- package/AGENTS.md +29 -9
- package/CHANGELOG.md +86 -0
- package/README.md +10 -4
- package/design-system/01-how-claude-decides.md +5 -3
- package/design-system/03-colour.md +19 -1
- package/design-system/05-space-and-layout.md +2 -1
- package/design-system/08-choosing-components.md +20 -4
- package/design-system/09-charts.md +6 -3
- package/design-system/11-page-recipes.md +40 -1
- package/design-system/13-motion.md +1 -1
- package/design-system/15-ai-experiences.md +11 -0
- package/design-system/17-decisions.md +41 -0
- package/design-system/components/AppShell/README.md +89 -21
- package/design-system/components/AppShell/preview.html +148 -3
- package/design-system/components/AppShell/style.css +47 -0
- package/design-system/components/Avatar/README.md +6 -3
- package/design-system/components/Avatar/preview.html +2 -1
- package/design-system/components/Avatar/style.css +3 -0
- package/design-system/components/Banner/README.md +9 -5
- package/design-system/components/Banner/preview.html +9 -0
- package/design-system/components/Banner/style.css +3 -0
- package/design-system/components/Button/README.md +29 -2
- package/design-system/components/Button/preview.html +33 -1
- package/design-system/components/Button/style.css +21 -0
- package/design-system/components/Calendar/README.md +148 -0
- package/design-system/components/Calendar/preview.html +449 -0
- package/design-system/components/Calendar/style.css +61 -0
- package/design-system/components/Card/README.md +13 -0
- package/design-system/components/Card/preview.html +23 -0
- package/design-system/components/Card/style.css +19 -0
- package/design-system/components/ChatCard/README.md +74 -3
- package/design-system/components/ChatCard/preview.html +126 -25
- package/design-system/components/ChatCard/style.css +6 -0
- package/design-system/components/Chip/README.md +16 -7
- package/design-system/components/Chip/preview.html +25 -1
- package/design-system/components/Chip/style.css +5 -0
- package/design-system/components/Combobox/README.md +10 -4
- package/design-system/components/Combobox/preview.html +19 -4
- package/design-system/components/Combobox/style.css +7 -1
- package/design-system/components/CommentThread/README.md +138 -0
- package/design-system/components/CommentThread/preview.html +134 -0
- package/design-system/components/CommentThread/style.css +41 -0
- package/design-system/components/DateInput/README.md +38 -4
- package/design-system/components/DateInput/preview.html +50 -2
- package/design-system/components/DateInput/style.css +6 -0
- package/design-system/components/Dialog/README.md +26 -3
- package/design-system/components/Dialog/preview.html +80 -0
- package/design-system/components/Dialog/style.css +28 -3
- package/design-system/components/EmptyState/README.md +1 -1
- package/design-system/components/Fold/README.md +21 -0
- package/design-system/components/Fold/preview.html +44 -1
- package/design-system/components/Fold/style.css +30 -0
- package/design-system/components/InlineEdit/README.md +116 -0
- package/design-system/components/InlineEdit/preview.html +135 -0
- package/design-system/components/InlineEdit/style.css +34 -0
- package/design-system/components/Input/README.md +20 -1
- package/design-system/components/Input/preview.html +24 -0
- package/design-system/components/Input/style.css +13 -1
- package/design-system/components/KanbanBoard/README.md +68 -4
- package/design-system/components/KanbanBoard/preview.html +129 -0
- package/design-system/components/KanbanBoard/style.css +31 -0
- package/design-system/components/PlanTimeline/README.md +144 -0
- package/design-system/components/PlanTimeline/preview.html +109 -0
- package/design-system/components/PlanTimeline/style.css +64 -0
- package/design-system/components/README.md +11 -6
- package/design-system/components/Skeleton/README.md +66 -2
- package/design-system/components/Skeleton/preview.html +83 -1
- package/design-system/components/Starter/README.md +1 -1
- package/design-system/components/Starter/preview.html +1 -1
- package/design-system/components/Stepper/README.md +81 -5
- package/design-system/components/Stepper/preview.html +64 -1
- package/design-system/components/Stepper/style.css +29 -0
- package/design-system/components/StreakCalendar/README.md +1 -0
- package/design-system/components/Table/README.md +7 -3
- package/design-system/components/Table/preview.html +15 -0
- package/design-system/components/Table/style.css +2 -0
- package/design-system/components/TextDiff/README.md +96 -0
- package/design-system/components/TextDiff/preview.html +42 -0
- package/design-system/components/TextDiff/style.css +21 -0
- package/design-system/components/Timeline/README.md +3 -1
- package/design-system/components/TrendLine/README.md +38 -7
- package/design-system/components/TrendLine/preview.html +140 -3
- package/design-system/components/TrendLine/style.css +12 -2
- package/design-system/components/bundle.css +483 -7
- package/design-system/tokens.json +15 -1
- package/dist/fello-core.css +2 -2
- package/dist/fello.css +2 -2
- package/dist/fello.scoped.css +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/scoped.js +2 -2
- package/dist/tokens.d.ts +8 -0
- package/dist/tokens.js +8 -0
- package/package.json +7 -2
- package/scripts/check-drift.mjs +325 -0
- package/scripts/token-model.mjs +2 -1
- package/tokens/_dark.css +2 -0
- package/tokens/_product.css +2 -0
- package/tokens/palettes.ts +1 -1
- package/tokens/shadcn-compat.css +1 -1
- package/tokens/shadcn-compat.hsl.css +1 -1
- package/tokens/tokens.css +14 -1
- package/tokens/tokens.md +53 -46
- package/tokens/tokens.root.css +14 -1
- package/tokens/tokens.ts +9 -1
|
@@ -6,6 +6,8 @@ edge while typing.
|
|
|
6
6
|
- One line of text, a number, an email or a phone number needs typing: a lead's email, a listing's address.
|
|
7
7
|
- A page or panel needs a search box (the kit has no dedicated search field: `Input` with `type="search"` and its icon).
|
|
8
8
|
- A form field needs a label above it and, below it, a hint or an error.
|
|
9
|
+
- Someone sets a colour (an organisation's brand colours, a palette): the colour field, a swatch that opens the
|
|
10
|
+
browser's picker beside its hex.
|
|
9
11
|
|
|
10
12
|
## Not for
|
|
11
13
|
|
|
@@ -21,6 +23,9 @@ edge while typing.
|
|
|
21
23
|
- Label: 12px medium, `--color-text-body`, above the field.
|
|
22
24
|
- Words typed: 14 / 20, `--ws-field-ink`; placeholder `--color-text-placeholder` (#6E6E6E).
|
|
23
25
|
- Search (`ws-search`): 36 tall (40 on touch), the 16 px glass 8 px in at the left, the words 32 in.
|
|
26
|
+
- Colour field (`ws-colour-field`): a swatch (`__swatch`, a native `<input type="color">`) square at the field's height
|
|
27
|
+
(32, 40 on touch), 4 px corners, the 0.5 px card edge drawn on the swatch so a white or cream colour still shows its
|
|
28
|
+
edge; 8 apart (`--space-xs`), the hex in a plain `ws-input` beside it, tabular figures.
|
|
24
29
|
- Message below: 12px, `--color-text-muted` for a hint, `--ws-word-needs` for an error, with a warning icon before it.
|
|
25
30
|
|
|
26
31
|
## Variants and sizes
|
|
@@ -29,6 +34,7 @@ edge while typing.
|
|
|
29
34
|
| --- | --- | --- |
|
|
30
35
|
| Default | 32px, as above | every field on a card or a form |
|
|
31
36
|
| Search | 36px (`--ws-control-h-md`), 4px corners, a hairline, a search icon inside at the left | a page's or a panel's search box (the system's rule) |
|
|
37
|
+
| Colour | a swatch square beside the hex field | a colour someone sets (brand colours, a palette) |
|
|
32
38
|
|
|
33
39
|
## States
|
|
34
40
|
|
|
@@ -53,6 +59,12 @@ edge while typing.
|
|
|
53
59
|
|
|
54
60
|
- Keyboard: a native `<input>`; Tab moves to and from it like any field; typing and browser autofill work unmodified.
|
|
55
61
|
- Pointer / touch: clicking or tapping anywhere in the box focuses it; the touch floor is 40px.
|
|
62
|
+
- Colour field: the swatch opens the browser's own picker (keyboard and screen readers come with it); picking writes
|
|
63
|
+
the hex into the field, and typing a valid hex (`#` and six characters, any case) moves the swatch. An invalid hex
|
|
64
|
+
marks the field (`aria-invalid`, the error message under the pair, "Needs six characters after the #, like
|
|
65
|
+
#FEFBF9") and leaves the swatch on the last good colour. The swatch takes an `aria-label` naming the colour
|
|
66
|
+
("Primary colour, pick from the palette"); the visible label belongs to the hex field. The swatch's fill is the
|
|
67
|
+
chosen colour itself, the one colour on a kit screen no token sets.
|
|
56
68
|
- Motion: the edge over the hover beat (200 ms) on `--ws-ease-soft`; an error message rises in (`ws-arrive`); still
|
|
57
69
|
under reduced motion.
|
|
58
70
|
|
|
@@ -103,9 +115,16 @@ edge while typing.
|
|
|
103
115
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="7" cy="7" r="4.5"/><path d="M13 13l-2.8-2.8"/></svg>
|
|
104
116
|
<input class="ws-input ws-search" type="search" placeholder="Search" aria-label="Search leads">
|
|
105
117
|
</span>
|
|
118
|
+
<div class="ws-field">
|
|
119
|
+
<label class="ws-label" for="primary">Primary colour</label>
|
|
120
|
+
<span class="ws-colour-field">
|
|
121
|
+
<input class="ws-colour-field__swatch" type="color" value="#093555" aria-label="Primary colour, pick from the palette">
|
|
122
|
+
<input class="ws-input" id="primary" value="#093555" spellcheck="false" autocomplete="off" maxlength="7">
|
|
123
|
+
</span>
|
|
124
|
+
</div>
|
|
106
125
|
```
|
|
107
126
|
|
|
108
127
|
## Styles
|
|
109
128
|
|
|
110
129
|
`design-system/components/Input/style.css`: `ws-input` (and `--typing`, the focus drawn still), `ws-search-wrap`,
|
|
111
|
-
`ws-search`. `ws-field`, `ws-label` and `ws-field-msg` are the house's (`_house.css`, Fields).
|
|
130
|
+
`ws-search`, `ws-colour-field` and its `__swatch`. `ws-field`, `ws-label` and `ws-field-msg` are the house's (`_house.css`, Fields).
|
|
@@ -47,6 +47,30 @@
|
|
|
47
47
|
<input class="ws-input" id="i-dis" value="FEL-00412" disabled>
|
|
48
48
|
</div>
|
|
49
49
|
|
|
50
|
+
<div class="ws-field">
|
|
51
|
+
<span class="ws-muted">Colour field: the swatch opens the picker, the hex beside it</span>
|
|
52
|
+
<label class="ws-label" for="i-colour">Primary colour</label>
|
|
53
|
+
<span class="ws-colour-field">
|
|
54
|
+
<input class="ws-colour-field__swatch" type="color" value="#093555" aria-label="Primary colour, pick from the palette">
|
|
55
|
+
<input class="ws-input" id="i-colour" value="#093555" spellcheck="false" autocomplete="off" inputmode="text" maxlength="7">
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="ws-field">
|
|
60
|
+
<span class="ws-muted">Colour field, a light colour (the swatch keeps its edge) and an invalid hex</span>
|
|
61
|
+
<label class="ws-label" for="i-colour-bad">Page colour</label>
|
|
62
|
+
<span class="ws-colour-field">
|
|
63
|
+
<input class="ws-colour-field__swatch" type="color" value="#fefbf9" aria-label="Page colour, pick from the palette">
|
|
64
|
+
<input class="ws-input" id="i-colour-bad" value="#FEFBF" spellcheck="false" autocomplete="off" maxlength="7" aria-invalid="true" aria-describedby="i-colour-bad-msg">
|
|
65
|
+
</span>
|
|
66
|
+
<p class="ws-field-msg ws-field-msg--error" id="i-colour-bad-msg">
|
|
67
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
68
|
+
<path d="M8 2.3 14.6 13.5H1.4L8 2.3Z"/><path d="M8 6.4v3"/><path d="M8 11.5v.05"/>
|
|
69
|
+
</svg>
|
|
70
|
+
Needs six characters after the #, like #FEFBF9
|
|
71
|
+
</p>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
50
74
|
<div class="ws-field">
|
|
51
75
|
<span class="ws-muted">Search box (36px, 4px corners)</span>
|
|
52
76
|
<label class="ws-label" for="i-search">Search leads</label>
|
|
@@ -18,6 +18,18 @@
|
|
|
18
18
|
.ws-search-wrap { position: relative; display: flex; min-width: 0; }
|
|
19
19
|
.ws-search-wrap svg { position: absolute; left: var(--space-xs); top: 50%; width: var(--ws-icon); height: var(--ws-icon); transform: translateY(-50%); color: var(--color-text-muted); pointer-events: none; }
|
|
20
20
|
.ws-search { height: var(--ws-control-h-md); padding: 0 var(--space-sm) 0 var(--space-xl); }
|
|
21
|
+
/* The colour field (#29): a swatch that opens the browser's colour picker (a native <input type="color">) and the hex
|
|
22
|
+
field beside it, 8 apart. The swatch is a square the field's height, its 4 px corners and 0.5 px edge drawn inside, so
|
|
23
|
+
a white or cream colour still shows its edge; its fill is the input's own value, the one colour the kit paints that
|
|
24
|
+
no token sets. An invalid hex marks the field and leaves the swatch on the last good colour. */
|
|
25
|
+
.ws-colour-field { display: flex; align-items: center; gap: var(--space-xs); min-width: 0; }
|
|
26
|
+
.ws-colour-field__swatch { flex-shrink: 0; width: var(--ws-control-h); height: var(--ws-control-h); padding: 0; border: 0; border-radius: var(--ws-radius-control); background: none; cursor: pointer; }
|
|
27
|
+
.ws-colour-field__swatch::-webkit-color-swatch-wrapper { padding: 0; }
|
|
28
|
+
/* The edge sits on the swatch itself: the browser paints the colour over anything drawn on the input's own box. */
|
|
29
|
+
.ws-colour-field__swatch::-webkit-color-swatch { border: var(--ws-hairline) solid var(--ws-edge-card); border-radius: inherit; }
|
|
30
|
+
.ws-colour-field__swatch::-moz-color-swatch { border: var(--ws-hairline) solid var(--ws-edge-card); border-radius: inherit; }
|
|
31
|
+
.ws-colour-field__swatch:disabled { cursor: not-allowed; opacity: 0.5; }
|
|
32
|
+
.ws-colour-field .ws-input { font-variant-numeric: tabular-nums; }
|
|
21
33
|
/* Touch: 40 tall (the house's touch height, restated after the field's own so it still wins), the words 16 so a phone
|
|
22
34
|
does not zoom the page into the field when it takes focus (iOS zooms any field under 16 px). */
|
|
23
|
-
@media (pointer: coarse) { .ws-input, .ws-search { height: var(--ws-touch); font-size: var(--ws-text-value); } }
|
|
35
|
+
@media (pointer: coarse) { .ws-input, .ws-search { height: var(--ws-touch); font-size: var(--ws-text-value); } .ws-colour-field__swatch { width: var(--ws-touch); height: var(--ws-touch); } }
|
|
@@ -24,7 +24,29 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
24
24
|
- A column head: at least 32 tall, the stage in 13 / 18 medium navy (`__title`), its count beside it in muted figures
|
|
25
25
|
(`__count`), sentence case, flush with the cards' edge.
|
|
26
26
|
- A card: `ws-card` (a link or a button when it opens the thing), 12 inside, a 28 px `Avatar` and the name (14 / 20
|
|
27
|
-
medium navy) 8 apart, a 13 / 18 muted meta line 4 under.
|
|
27
|
+
medium navy) 8 apart, a 13 / 18 muted meta line 4 under. That is the person card: a lead, an applicant.
|
|
28
|
+
- The standard card (a ticket, an onboarding, a deal: anything with a kind, a figure, an owner and acts): one anatomy
|
|
29
|
+
for every board, its slots in this order, 4 apart (8 before the chips and the foot), each left out when it has nothing
|
|
30
|
+
to say, never shown blank and never reordered:
|
|
31
|
+
1. The head (`__card-head`, 18 tall): the kind and its figure on the left (`__kind`, 13 / 18 medium muted words, the
|
|
32
|
+
figure in a `strong`, semibold navy tabular: "Expansion **$28K**"), the time in stage on the right (`__age`, 13 / 18
|
|
33
|
+
muted tabular figures: "4d", "6h").
|
|
34
|
+
2. The title (`a.__open`, 14 / 20 medium navy, two lines at most, whole in its `title`): the card's link. Its cover
|
|
35
|
+
(`::after`) spans the card, so the whole card opens the thing and the focus ring is drawn round the card, 2 px out.
|
|
36
|
+
Never underlined, at rest, under the pointer or anywhere else.
|
|
37
|
+
3. The meta (`__meta`): whose it is and the last thing that happened, one short line ("Harbor Lane Realty, last
|
|
38
|
+
activity Nov 2").
|
|
39
|
+
4. The chips (`__chips`, `Chip`s 4 apart, three at most): a status ("Deferred", sunk), a milestone ("TTV1 15d", live),
|
|
40
|
+
a blocker that needs the person ("No meeting booked", needs).
|
|
41
|
+
5. The foot (`__card-foot`, 28 tall): the people on the left, a 28 px `Avatar` and the owner's name (`__name`, 13 / 18
|
|
42
|
+
body) or an `AvatarStack` for a team; on the right (`__card-end`), a tally (`__tally`: a 12 px icon and muted
|
|
43
|
+
tabular figures, "1/6", its meaning in words for a screen reader) and one `IconButton` `small`, the card's act
|
|
44
|
+
("Move to Implementing").
|
|
45
|
+
6. An act under it (`__card-act`): one `Button` `well` `small`, full width, only when the stage waits on the person
|
|
46
|
+
for an act the board cannot show by moving the card ("Hand over", "Approve"). Never navy: a board holds many
|
|
47
|
+
cards, and a page has one navy button.
|
|
48
|
+
The card is an `article` (it holds acts, so it cannot be a link): the acts sit above the title's cover, so a press on
|
|
49
|
+
one is the act, not the card.
|
|
28
50
|
- A held card (`__card--dragging`, with `aria-grabbed="true"`; the app sets both while a card is held): North's
|
|
29
51
|
floating shadow (`--ws-shadow-pop`, with the dark rim) and a 1 degree tilt; the only thing on the board that floats.
|
|
30
52
|
- The column under a held card (`__column--over`, set by the app while the card is over it): one step darker
|
|
@@ -37,6 +59,8 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
37
59
|
| Variant | What changes |
|
|
38
60
|
| --- | --- |
|
|
39
61
|
| Default | Columns as tall as their cards; the board as tall as its tallest column |
|
|
62
|
+
| Person card | `a.ws-card.__card`: a face, a name and a meta line; the whole card is the link |
|
|
63
|
+
| Standard card | `article.ws-card.__card` with the six slots above; the title is the link and covers the card. Every board in an app uses it, so a ticket board and an onboarding board read alike |
|
|
40
64
|
| `--fill` | The board takes the rest of a height-bound column (`flex: 1; min-height: 0`); every column runs the full height and scrolls on its own, its head stuck at the very top (over the column's 8 px padding, so no card shows above it; the title sits where the plain board's does), so the sideways bar sits at the window's foot (K16). A column that holds more than it shows fades its last 24 px into its ground, lifting at the end; a card reached by Tab scrolls clear of the stuck head |
|
|
41
65
|
| Phone | The same row of 280 columns, the next one peeking in, snapping a stage at a time |
|
|
42
66
|
|
|
@@ -49,10 +73,15 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
49
73
|
darkens a step. The app adds and removes both classes; the kit only draws them.
|
|
50
74
|
- **Dropped**: the card glides to its new place and the cards it leaves close up, never teleports (North's "List rows
|
|
51
75
|
reorder", `springs.reorder` .4 / 0): wrap the move in `reorder()` (below).
|
|
76
|
+
- **Stalled** (a standard card past its stage's time): the time in stage takes the needs words (`--ws-word-needs`,
|
|
77
|
+
medium) and the 12 px pause mark before it, with "stalled" in words for a screen reader and in its `title` ("54 days
|
|
78
|
+
in this stage, stalled"). Nothing else on the card changes: no coloured rail, no tinted card.
|
|
79
|
+
- **Needs you** (something blocks the card and it is the person's to fix): a `needs` chip saying what ("No meeting
|
|
80
|
+
booked", "Due Nov 12"), first in the chips. A card the stage waits on adds the well act under the foot.
|
|
52
81
|
- **Empty**: the column keeps its full width, its head and its count; a muted sentence in place of cards. It never
|
|
53
82
|
hides, which would reflow every column beside it mid-task.
|
|
54
83
|
- **Loading**: ghost cards the height of a card under the shimmer (`__ghost ws-shimmer`), the heads shown at
|
|
55
|
-
once; still under reduced motion.
|
|
84
|
+
once; still under reduced motion. A board of standard cards takes the taller ghost (`__ghost--card`).
|
|
56
85
|
- **Scrolled**: the far-edge fade while stages are hidden, gone at the end; in `--fill`, a column's foot fades while cards
|
|
57
86
|
are hidden below it, gone at its end.
|
|
58
87
|
- **Touch**: a card is one target (its whole box); dragging on touch needs a press-and-hold, not drawn here.
|
|
@@ -80,6 +109,10 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
80
109
|
- A stage name is the pipeline's own word for it, sentence case, one or two words ("Offer made", not "Stage 2").
|
|
81
110
|
- A card's meta line says the one thing worth knowing at a glance (time in stage, who is waited on), never the whole
|
|
82
111
|
record.
|
|
112
|
+
- A standard card's kind is one sentence-case word ("New", "Expansion", "Dispute"), never an uppercase eyebrow; its
|
|
113
|
+
figure is short ("$28K", "$1,321"). Time in stage is a bare figure and a unit ("54d", "6h"), its meaning in the
|
|
114
|
+
`title`.
|
|
115
|
+
- A chip is a fact, two or three words; a tally is a bare figure ("1/6") that says what it counts to a screen reader.
|
|
83
116
|
- No em dashes; the count is a bare number.
|
|
84
117
|
|
|
85
118
|
## Props
|
|
@@ -87,7 +120,8 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
87
120
|
| Name | Type | Meaning |
|
|
88
121
|
| --- | --- | --- |
|
|
89
122
|
| `columns` | `{ stage, label, count, cards: CardItem[] }[]` | The board's stages, in order |
|
|
90
|
-
| `CardItem` | `{ id, name, initials, meta, href? }` | One card's content |
|
|
123
|
+
| `CardItem` | `{ id, name, initials, meta, href? }` | One person card's content |
|
|
124
|
+
| `BoardCard` | `{ id, kind?, figure?, age?, stalled?, title, href, meta?, chips?: { label, tone }[], people: { name, initials }[], tally?: { value, label }, act?: { label, onClick }, stageAct?: { label, onClick } }` | One standard card: `act` is the foot's icon act, `stageAct` the well button under it |
|
|
91
125
|
| `onMove` | `(cardId, toStage) => void` | Called when a card changes column |
|
|
92
126
|
| `draggingId` | `string \| null` | Which card, if any, is currently lifted |
|
|
93
127
|
| `fill` | `boolean` | The board page form (`--fill`) |
|
|
@@ -101,6 +135,12 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
101
135
|
- **A board page shows its sideways bar** (K16): when the board is the page, use `--fill` so the bar is never below the
|
|
102
136
|
fold, and each stage scrolls on its own.
|
|
103
137
|
- **The head is sentence case** (D6): 13 / 18 medium, no uppercase eyebrow.
|
|
138
|
+
- **One card anatomy per app.** Every board with kinds, figures and owners uses the standard card, its slots in their
|
|
139
|
+
order; a board never draws its own card.
|
|
140
|
+
- **Never** a coloured rail down a card's edge, a hue per column or per stage (a stage's dot, a tinted column), an
|
|
141
|
+
underlined title, a navy button on a card, or more than one act in the foot. Urgency is words: the stalled time, a
|
|
142
|
+
needs chip. Not a salmon rail for critical and a grey one for the rest, not a purple dot on one stage, not one card's
|
|
143
|
+
title in mid blue with an underline, not a navy "Hand over" on every card in the column.
|
|
104
144
|
|
|
105
145
|
## Source
|
|
106
146
|
|
|
@@ -108,7 +148,9 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
108
148
|
flat-except-dragged rules; North's floating shadow family and its edge fade; the K16 audit (Revenue OS: a 6-stage
|
|
109
149
|
board 638 px tall with its sideways bar at y 1,107 in a 900 px window).
|
|
110
150
|
- **Precedent**: an intentional addition decided from the system, 26 Oct 2026, generalised from the first product's
|
|
111
|
-
pipeline board; `--fill` added in 0.5.0 (4 Nov 2026).
|
|
151
|
+
pipeline board; `--fill` added in 0.5.0 (4 Nov 2026). The standard card (0.5.2, 10 Nov 2026) from the field: an app's
|
|
152
|
+
ticket board and onboarding board each drew their own card (rails per column, a stage hue, one title underlined), so
|
|
153
|
+
the two boards did not match.
|
|
112
154
|
|
|
113
155
|
## Markup
|
|
114
156
|
|
|
@@ -132,6 +174,28 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
132
174
|
</div>
|
|
133
175
|
```
|
|
134
176
|
|
|
177
|
+
The standard card (in a column, in place of the person card):
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<article class="ws-card ws-kanban-board__card">
|
|
181
|
+
<div class="ws-kanban-board__card-head">
|
|
182
|
+
<span class="ws-kanban-board__kind">Expansion <strong>$28K</strong></span>
|
|
183
|
+
<span class="ws-kanban-board__age ws-kanban-board__age--stalled" title="40 days in this stage, stalled"><svg viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><rect x="3" y="2.5" width="2" height="7" rx="0.5"/><rect x="7" y="2.5" width="2" height="7" rx="0.5"/></svg>40d<span class="ws-sr"> in this stage, stalled</span></span>
|
|
184
|
+
</div>
|
|
185
|
+
<a class="ws-kanban-board__open" href="#mesa" title="Mesa Group, Felix expansion">Mesa Group, Felix expansion</a>
|
|
186
|
+
<span class="ws-kanban-board__meta">Signed Sep 11</span>
|
|
187
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--live"><span class="ws-chip__label">TTV1 15d</span></span></div>
|
|
188
|
+
<div class="ws-kanban-board__card-foot">
|
|
189
|
+
<span class="ws-avatar-stack" role="img" aria-label="Hal Dorn and Ella Sim"><span class="ws-avatar ws-avatar--sm">HD</span><span class="ws-avatar ws-avatar--sm">ES</span></span>
|
|
190
|
+
<span class="ws-kanban-board__card-end">
|
|
191
|
+
<span class="ws-kanban-board__tally" title="1 of 6 tasks done"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 6.5l2.25 2.25L9.5 3.5"/></svg>1/6<span class="ws-sr"> tasks done</span></span>
|
|
192
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to Graduated" title="Move to Graduated"><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="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
193
|
+
</span>
|
|
194
|
+
</div>
|
|
195
|
+
<button type="button" class="ws-btn ws-btn--well ws-btn--sm ws-kanban-board__card-act">Hand over</button>
|
|
196
|
+
</article>
|
|
197
|
+
```
|
|
198
|
+
|
|
135
199
|
## Styles
|
|
136
200
|
|
|
137
201
|
`design-system/components/KanbanBoard/style.css`, in `bundle.css` and `fello.css`. Dark and the navy ground come from
|
|
@@ -74,6 +74,135 @@
|
|
|
74
74
|
</div>
|
|
75
75
|
</div>
|
|
76
76
|
|
|
77
|
+
<div class="ws-stack">
|
|
78
|
+
<span class="ws-muted">The standard card, one anatomy for every board: the kind and its figure with the time in stage, the title (two lines, the card's link), the meta, the chips, the foot (people, a tally, one icon act), one well act under it when the card waits on the person. A stall is the needs words and the pause mark, never a coloured rail. Tickets</span>
|
|
79
|
+
<div class="ws-kanban-board" role="region" aria-label="Ticket stages" tabindex="0">
|
|
80
|
+
|
|
81
|
+
<section class="ws-kanban-board__column" aria-labelledby="kt-new">
|
|
82
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="kt-new">New <span class="ws-kanban-board__count">70</span></h3></div>
|
|
83
|
+
<article class="ws-card ws-kanban-board__card">
|
|
84
|
+
<div class="ws-kanban-board__card-head">
|
|
85
|
+
<span class="ws-kanban-board__kind">Dispute <strong>$1,321</strong></span>
|
|
86
|
+
<span class="ws-kanban-board__age ws-kanban-board__age--stalled" title="Quiet for 7 days"><svg viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><rect x="3" y="2.5" width="2" height="7" rx="0.5"/><rect x="7" y="2.5" width="2" height="7" rx="0.5"/></svg>7d<span class="ws-sr"> quiet, stalled</span></span>
|
|
87
|
+
</div>
|
|
88
|
+
<a class="ws-kanban-board__open" href="#t1" title="Dispute raised on the October invoice by the team's broker">Dispute raised on the October invoice by the team's broker</a>
|
|
89
|
+
<span class="ws-kanban-board__meta">Harbor Lane Realty, last activity Nov 2</span>
|
|
90
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--needs"><span class="ws-chip__label">Critical</span></span><span class="ws-chip ws-chip--needs"><span class="ws-chip__label">Due Nov 12</span></span></div>
|
|
91
|
+
<div class="ws-kanban-board__card-foot">
|
|
92
|
+
<span class="ws-kanban-board__who"><span class="ws-avatar ws-avatar--sm" aria-hidden="true">VC</span><span class="ws-kanban-board__name" title="Vasu Chandran">Vasu Chandran</span></span>
|
|
93
|
+
<span class="ws-kanban-board__card-end">
|
|
94
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to In progress" title="Move to In progress"><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="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
</article>
|
|
98
|
+
<article class="ws-card ws-kanban-board__card">
|
|
99
|
+
<div class="ws-kanban-board__card-head">
|
|
100
|
+
<span class="ws-kanban-board__kind">Churn <strong>$4.2K</strong></span>
|
|
101
|
+
<span class="ws-kanban-board__age" title="6 hours in this stage">6h</span>
|
|
102
|
+
</div>
|
|
103
|
+
<a class="ws-kanban-board__open" href="#t2" title="Wants to cancel after the price change">Wants to cancel after the price change</a>
|
|
104
|
+
<span class="ws-kanban-board__meta">Oakridge Homes Group, last activity today</span>
|
|
105
|
+
<div class="ws-kanban-board__card-foot">
|
|
106
|
+
<span class="ws-kanban-board__who"><span class="ws-avatar ws-avatar--sm" aria-hidden="true">AY</span><span class="ws-kanban-board__name" title="Ayan Roy">Ayan Roy</span></span>
|
|
107
|
+
<span class="ws-kanban-board__card-end">
|
|
108
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to In progress" title="Move to In progress"><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="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
109
|
+
</span>
|
|
110
|
+
</div>
|
|
111
|
+
</article>
|
|
112
|
+
</section>
|
|
113
|
+
|
|
114
|
+
<section class="ws-kanban-board__column" aria-labelledby="kt-progress">
|
|
115
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="kt-progress">In progress <span class="ws-kanban-board__count">339</span></h3></div>
|
|
116
|
+
<article class="ws-card ws-kanban-board__card">
|
|
117
|
+
<div class="ws-kanban-board__card-head">
|
|
118
|
+
<span class="ws-kanban-board__kind">Integration</span>
|
|
119
|
+
<span class="ws-kanban-board__age" title="4 days in this stage">4d</span>
|
|
120
|
+
</div>
|
|
121
|
+
<a class="ws-kanban-board__open" href="#t3" title="Leads stop syncing to the CRM after the second login">Leads stop syncing to the CRM after the second login</a>
|
|
122
|
+
<span class="ws-kanban-board__meta">The Connors Team, last activity Nov 5</span>
|
|
123
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--sunk"><span class="ws-chip__label">Deferred</span></span><span class="ws-chip"><span class="ws-chip__label">Integration setup</span></span></div>
|
|
124
|
+
<div class="ws-kanban-board__card-foot">
|
|
125
|
+
<span class="ws-kanban-board__who"><span class="ws-avatar ws-avatar--sm" aria-hidden="true">TM</span><span class="ws-kanban-board__name" title="Tamara Mendes-Okonkwo">Tamara Mendes-Okonkwo</span></span>
|
|
126
|
+
<span class="ws-kanban-board__card-end">
|
|
127
|
+
<span class="ws-kanban-board__tally" title="2 replies"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="1.5" y="2.5" width="9" height="7" rx="1"/><path d="M2 3.5l4 3 4-3"/></svg>2<span class="ws-sr"> replies</span></span>
|
|
128
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to Waiting on customer" title="Move to Waiting on customer"><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="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
</article>
|
|
132
|
+
</section>
|
|
133
|
+
|
|
134
|
+
<section class="ws-kanban-board__column" aria-labelledby="kt-wait" aria-busy="true">
|
|
135
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="kt-wait">Waiting on customer</h3></div>
|
|
136
|
+
<div class="ws-kanban-board__ghost ws-kanban-board__ghost--card ws-shimmer"></div>
|
|
137
|
+
</section>
|
|
138
|
+
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div class="ws-stack">
|
|
143
|
+
<span class="ws-muted">The same card on an onboarding board: the kind and the deal's figure, the stall, milestones as chips, the team as a stack, tasks as a tally, and the one act this stage waits on (Hand over) as a well button</span>
|
|
144
|
+
<div class="ws-kanban-board" role="region" aria-label="Onboarding stages" tabindex="0">
|
|
145
|
+
|
|
146
|
+
<section class="ws-kanban-board__column" aria-labelledby="ko-sched">
|
|
147
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="ko-sched">Scheduling <span class="ws-kanban-board__count">15</span></h3></div>
|
|
148
|
+
<article class="ws-card ws-kanban-board__card">
|
|
149
|
+
<div class="ws-kanban-board__card-head">
|
|
150
|
+
<span class="ws-kanban-board__kind">New <strong>$26K</strong></span>
|
|
151
|
+
<span class="ws-kanban-board__age ws-kanban-board__age--stalled" title="54 days in this stage, stalled"><svg viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><rect x="3" y="2.5" width="2" height="7" rx="0.5"/><rect x="7" y="2.5" width="2" height="7" rx="0.5"/></svg>54d<span class="ws-sr"> in this stage, stalled</span></span>
|
|
152
|
+
</div>
|
|
153
|
+
<a class="ws-kanban-board__open" href="#o1" title="Bayview Real Estate Team, Felix expansion with a long name that wraps">Bayview Real Estate Team, Felix expansion with a long name that wraps</a>
|
|
154
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--needs"><span class="ws-chip__label">No meeting booked</span></span></div>
|
|
155
|
+
<div class="ws-kanban-board__card-foot">
|
|
156
|
+
<span class="ws-avatar-stack" role="img" aria-label="Tara Vale and Josh Smith"><span class="ws-avatar ws-avatar--sm">TV</span><span class="ws-avatar ws-avatar--sm">JS</span></span>
|
|
157
|
+
<span class="ws-kanban-board__card-end">
|
|
158
|
+
<span class="ws-kanban-board__tally" title="0 of 1 messages sent"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="1.5" y="2.5" width="9" height="7" rx="1"/><path d="M2 3.5l4 3 4-3"/></svg>0/1<span class="ws-sr"> messages sent</span></span>
|
|
159
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to Implementing" title="Move to Implementing"><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="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
160
|
+
</span>
|
|
161
|
+
</div>
|
|
162
|
+
</article>
|
|
163
|
+
</section>
|
|
164
|
+
|
|
165
|
+
<section class="ws-kanban-board__column" aria-labelledby="ko-opt">
|
|
166
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="ko-opt">Optimizing <span class="ws-kanban-board__count">66</span></h3></div>
|
|
167
|
+
<article class="ws-card ws-kanban-board__card">
|
|
168
|
+
<div class="ws-kanban-board__card-head">
|
|
169
|
+
<span class="ws-kanban-board__kind">New <strong>$39K</strong></span>
|
|
170
|
+
<span class="ws-kanban-board__age" title="27 days in this stage">27d</span>
|
|
171
|
+
</div>
|
|
172
|
+
<a class="ws-kanban-board__open" href="#o2" title="Linden Indy Realty, onboarding">Linden Indy Realty, onboarding</a>
|
|
173
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--live"><span class="ws-chip__label">TTV1 15d</span></span></div>
|
|
174
|
+
<div class="ws-kanban-board__card-foot">
|
|
175
|
+
<span class="ws-avatar-stack" role="img" aria-label="Mia Lund and Ravi Bose"><span class="ws-avatar ws-avatar--sm">ML</span><span class="ws-avatar ws-avatar--sm">RB</span></span>
|
|
176
|
+
<span class="ws-kanban-board__card-end">
|
|
177
|
+
<span class="ws-kanban-board__tally" title="3 of 4 tasks done"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 6.5l2.25 2.25L9.5 3.5"/></svg>3/4<span class="ws-sr"> tasks done</span></span>
|
|
178
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to Review and handoff" title="Move to Review and handoff"><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="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
179
|
+
</span>
|
|
180
|
+
</div>
|
|
181
|
+
</article>
|
|
182
|
+
</section>
|
|
183
|
+
|
|
184
|
+
<section class="ws-kanban-board__column" aria-labelledby="ko-review">
|
|
185
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="ko-review">Review and handoff <span class="ws-kanban-board__count">9</span></h3></div>
|
|
186
|
+
<article class="ws-card ws-kanban-board__card">
|
|
187
|
+
<div class="ws-kanban-board__card-head">
|
|
188
|
+
<span class="ws-kanban-board__kind">Expansion <strong>$28K</strong></span>
|
|
189
|
+
<span class="ws-kanban-board__age ws-kanban-board__age--stalled" title="40 days in this stage, stalled"><svg viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><rect x="3" y="2.5" width="2" height="7" rx="0.5"/><rect x="7" y="2.5" width="2" height="7" rx="0.5"/></svg>40d<span class="ws-sr"> in this stage, stalled</span></span>
|
|
190
|
+
</div>
|
|
191
|
+
<a class="ws-kanban-board__open" href="#o3" title="Mesa Group, Felix expansion">Mesa Group, Felix expansion</a>
|
|
192
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--live"><span class="ws-chip__label">TTV1 15d</span></span><span class="ws-chip ws-chip--live"><span class="ws-chip__label">TTV2 26d</span></span></div>
|
|
193
|
+
<div class="ws-kanban-board__card-foot">
|
|
194
|
+
<span class="ws-avatar-stack" role="img" aria-label="Hal Dorn, Ella Sim and Josh Smith"><span class="ws-avatar ws-avatar--sm">HD</span><span class="ws-avatar ws-avatar--sm">ES</span><span class="ws-avatar ws-avatar--sm">JS</span></span>
|
|
195
|
+
<span class="ws-kanban-board__card-end">
|
|
196
|
+
<span class="ws-kanban-board__tally" title="1 of 12 tasks done"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 6.5l2.25 2.25L9.5 3.5"/></svg>1/12<span class="ws-sr"> tasks done</span></span>
|
|
197
|
+
</span>
|
|
198
|
+
</div>
|
|
199
|
+
<button type="button" class="ws-btn ws-btn--well ws-btn--sm ws-kanban-board__card-act">Hand over</button>
|
|
200
|
+
</article>
|
|
201
|
+
</section>
|
|
202
|
+
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
77
206
|
<div class="ws-stack">
|
|
78
207
|
<span class="ws-muted">--fill, a board page: the board takes the rest of a height-bound column, each stage scrolls on its own with its head stuck, so the sideways bar stays on screen (shown here in a 320 px frame)</span>
|
|
79
208
|
<div style="height: 320px; display: flex; flex-direction: column;">
|
|
@@ -29,9 +29,40 @@
|
|
|
29
29
|
.ws-kanban-board__card--dragging { box-shadow: var(--ws-shadow-pop); transform: rotate(-1deg); }
|
|
30
30
|
.ws-kanban-board__who { display: flex; align-items: center; gap: var(--space-xs); min-width: 0; }
|
|
31
31
|
.ws-kanban-board__meta { font-size: var(--ws-text-control); line-height: var(--ws-leading-control); color: var(--color-text-muted); }
|
|
32
|
+
/* The standard card: one anatomy for every board (a ticket, an onboarding, a deal), its slots in a fixed order: the head
|
|
33
|
+
(the kind and its figure, the time in stage), the title, the meta, the chips, the foot (people, a tally, one act), an
|
|
34
|
+
optional act under it. A card with acts is an article: its title is the link and covers the whole card (__open), the
|
|
35
|
+
acts sit above that cover. */
|
|
36
|
+
.ws-kanban-board__card { position: relative; }
|
|
37
|
+
.ws-kanban-board__card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-xs); min-width: 0; font-size: var(--ws-text-control); line-height: var(--ws-leading-control); }
|
|
38
|
+
.ws-kanban-board__kind { min-width: 0; overflow: hidden; font-weight: 500; color: var(--color-text-muted); white-space: nowrap; text-overflow: ellipsis; }
|
|
39
|
+
.ws-kanban-board__kind > strong { font-weight: 600; color: var(--color-text-heading); font-variant-numeric: tabular-nums; }
|
|
40
|
+
/* Time in stage, muted; a stall takes the needs words and the pause mark, never a coloured rail. */
|
|
41
|
+
.ws-kanban-board__age { display: inline-flex; flex: none; align-items: center; gap: var(--space-xxs); margin-left: auto; color: var(--color-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
42
|
+
.ws-kanban-board__age > svg { width: var(--ws-icon-sm); height: var(--ws-icon-sm); }
|
|
43
|
+
.ws-kanban-board__age--stalled { font-weight: 500; color: var(--ws-word-needs); }
|
|
44
|
+
/* The title: 14 / 20 medium navy, two lines at most (whole in its title), never underlined. Its cover is the card's
|
|
45
|
+
target and carries the focus ring, 2 px out as an a.ws-card's does. */
|
|
46
|
+
.ws-kanban-board__open { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; font-size: var(--ws-text-body); line-height: var(--ws-leading-row); font-weight: 500; color: var(--color-text-heading); text-decoration: none; overflow-wrap: anywhere; }
|
|
47
|
+
.ws-kanban-board__open::after { content: ""; position: absolute; inset: calc(var(--ws-rim) * -1); border-radius: var(--ws-radius-card); }
|
|
48
|
+
.ws-kanban-board__open:focus-visible { outline: 0; }
|
|
49
|
+
.ws-kanban-board__open:focus-visible::after { outline: var(--ws-focus-width) solid var(--ws-focus-color); outline-offset: var(--ws-focus-offset); }
|
|
50
|
+
.ws-kanban-board__chips { display: flex; flex-wrap: wrap; gap: var(--space-xxs); min-width: 0; margin-top: var(--space-xxs); }
|
|
51
|
+
.ws-kanban-board__chips > .ws-chip { max-width: 100%; }
|
|
52
|
+
.ws-kanban-board__card-foot { display: flex; align-items: center; gap: var(--space-xs); min-width: 0; min-height: var(--ws-control-h-sm); margin-top: var(--space-xxs); }
|
|
53
|
+
.ws-kanban-board__card-foot > .ws-kanban-board__who { flex: 1; }
|
|
54
|
+
.ws-kanban-board__name { min-width: 0; overflow: hidden; font-size: var(--ws-text-control); line-height: var(--ws-leading-control); color: var(--color-text-body); white-space: nowrap; text-overflow: ellipsis; }
|
|
55
|
+
.ws-kanban-board__card-end { display: flex; flex: none; align-items: center; gap: var(--space-xs); margin-left: auto; }
|
|
56
|
+
.ws-kanban-board__tally { display: inline-flex; align-items: center; gap: var(--space-xxs); font-size: var(--ws-text-control); line-height: var(--ws-leading-control); color: var(--color-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
57
|
+
.ws-kanban-board__tally > svg { width: var(--ws-icon-sm); height: var(--ws-icon-sm); }
|
|
58
|
+
/* Acts sit above the title's cover, so a press on one is the act, not the card. */
|
|
59
|
+
.ws-kanban-board__card :is(.ws-icon-btn, .ws-btn) { position: relative; z-index: 1; }
|
|
60
|
+
.ws-kanban-board__card-act { width: 100%; margin-top: var(--space-xxs); }
|
|
32
61
|
.ws-kanban-board__empty { padding: var(--space-md) var(--space-xs); text-align: center; font-size: var(--ws-text-control); line-height: var(--ws-leading-control); color: var(--color-text-muted); }
|
|
33
62
|
/* Loading: a ghost the height of a card under the shimmer (add ws-shimmer). */
|
|
34
63
|
.ws-kanban-board__ghost { display: block; height: calc(var(--ws-card-padding) * 2 + var(--ws-control-h-sm) + var(--space-xxs) + var(--ws-leading-control)); border-radius: var(--ws-radius-card); background: var(--ws-track); }
|
|
64
|
+
/* The standard card's ghost: the height of a head, two title lines, a meta line and the foot. */
|
|
65
|
+
.ws-kanban-board__ghost--card { height: calc(var(--ws-card-padding) * 2 + var(--ws-leading-control) * 2 + var(--ws-leading-row) * 2 + var(--ws-control-h-sm) + var(--space-xxs) * 4); }
|
|
35
66
|
.ws-kanban-board__ghost.ws-shimmer { background: var(--ws-shimmer); background-size: 300% 100%; }
|
|
36
67
|
/* A board page (K16): the board takes the rest of a height-bound column (the shell's body, a fill page), every column
|
|
37
68
|
runs its full height and scrolls on its own with its head stuck, so the sideways bar is always on screen. */
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
A plan's phases and tasks as bars across its dates: a row a phase (its tasks one step in), each bar from its start to
|
|
2
|
+
its end on one scale, the phase in hand navy, milestones as diamonds and today one line through every row.
|
|
3
|
+
|
|
4
|
+
## Use it when
|
|
5
|
+
|
|
6
|
+
- Work is planned over a window and the question is "what runs when, and where are we now": a campaign's phases, a
|
|
7
|
+
launch, a rollout with its gates.
|
|
8
|
+
- Phases overlap or run back to back and their lengths matter, not only their order.
|
|
9
|
+
|
|
10
|
+
## Not for
|
|
11
|
+
|
|
12
|
+
- Steps in order with no dates: use `Stepper` (`--across` for a thing's stages).
|
|
13
|
+
- One thing's dated milestones with the time between them: use `Stepper --milestones`.
|
|
14
|
+
- Things that fall on a day (posts, sends, touches): use `Calendar`.
|
|
15
|
+
- What happened, in order: use `Timeline`.
|
|
16
|
+
|
|
17
|
+
## Anatomy
|
|
18
|
+
|
|
19
|
+
- The region (`ws-plan-timeline`, `role="region"`, `tabindex="0"`, named for the plan and today): it sets `--days`, the
|
|
20
|
+
window's length in days, and scrolls sideways inside itself when the plan is wider than its box. 4 px of room above,
|
|
21
|
+
below and at the far end (pulled back by a margin) for a focus ring; none at the start, where the names stick.
|
|
22
|
+
- The plot (`__plot`): the names' column (160, 120 under 40rem of the region) and the window, never narrower than 8 px
|
|
23
|
+
a day, so a long plan scrolls rather than squeezing its bars.
|
|
24
|
+
- The scale (`__scale`, `aria-hidden`): 24 tall, a tick (`__tick`, `--at` its day) at each month's (or week's) first
|
|
25
|
+
day: a 0.5 px line and the name, 11 / 13 medium muted.
|
|
26
|
+
- A row (`__row`, in an `ol`): a 0.5 px line above it. Its name (`__name`), stuck at the left on the card's cream: the
|
|
27
|
+
phase (`__title`, 13 / 18 medium navy; semibold for the row in hand) and its dates under it (`__when`, 12 / 16
|
|
28
|
+
muted; `--late` in needs-you words, "Was due Nov 6"). Names and dates wrap rather than cut.
|
|
29
|
+
- A task row (`--task`): one step in (16), its name in the body words, its bar 16 tall.
|
|
30
|
+
- The track (`__track`): at least 40 tall. A bar (`__bar`, a link when it opens the phase): `--start` (days from the
|
|
31
|
+
window's first day) and `--span` (days), 24 tall, 4 px corners, at least 24 wide. Its state, in a `ws-sr` span too:
|
|
32
|
+
done (`--done`, teal), in hand (`aria-current="step"`, navy), late (`--late`, salmon), to come (`--wait`, the track
|
|
33
|
+
with a 0.5 px edge).
|
|
34
|
+
- A milestone (`__milestone`, `--at` its day, `role="img"` with its name and date in `aria-label` and `title`): a 10 px
|
|
35
|
+
diamond, cream inside a navy edge; reached (`--done`) teal. It opens nothing: its phase's bar does.
|
|
36
|
+
- Today (`__now`, `--at` its day, `aria-hidden`): "Today" over the window, 11 px semibold, and one 1 px line through every
|
|
37
|
+
row, in the salmon's AA step (`--ws-word-needs`). Left out when today is outside the window; the region's name
|
|
38
|
+
carries the date.
|
|
39
|
+
|
|
40
|
+
## Variants and sizes
|
|
41
|
+
|
|
42
|
+
| State | Class | Bar |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| Done | `__bar--done` | teal (`--ws-chart-finished`) |
|
|
45
|
+
| In hand | `aria-current="step"` | navy (`--color-text-heading`); its row's name semibold |
|
|
46
|
+
| Late | `__bar--late` | salmon (`--ws-chart-under-half`); say it under the name in `__when--late` |
|
|
47
|
+
| To come | `__bar--wait` | the track (`--ws-track`) on a 0.5 px edge |
|
|
48
|
+
|
|
49
|
+
A tone token passed as `--tone` on a bar wins (a category of work rather than a state); it follows the palette, never a
|
|
50
|
+
hex. A short plan (two to six weeks) takes a tick per week; a quarter, a tick per month.
|
|
51
|
+
|
|
52
|
+
## States
|
|
53
|
+
|
|
54
|
+
- **Hover** (a bar that opens): a wash over its fill; never a lift.
|
|
55
|
+
- **Focus**: the house ring round the bar; a bar reached by Tab scrolls clear of the stuck names
|
|
56
|
+
(`scroll-padding-inline-start`). The region's own ring is drawn inside, and the far-edge fade drops while it has focus.
|
|
57
|
+
- **Scrolling** (a phone): the plan opens on today (`scroll-initial-target` on `__now`), the names stay put, the far
|
|
58
|
+
edge fades while days are hidden past it.
|
|
59
|
+
- **Loading**: the scale drawn; `ws-skeleton ws-skeleton--line` in each name and `ws-shimmer` on the bars where they
|
|
60
|
+
will land.
|
|
61
|
+
- **Empty**: a plan with no phases is an `EmptyState` in the card, never an empty scale.
|
|
62
|
+
- **Touch**: a bar's target is 40 tall (its `::after`), a task's 16 px bar too.
|
|
63
|
+
- **Dark and the navy ground**: from the tokens.
|
|
64
|
+
|
|
65
|
+
## Behaviour
|
|
66
|
+
|
|
67
|
+
- No script in the kit: the app works out each bar's `--start` and `--span` and each tick's and milestone's `--at`
|
|
68
|
+
(days from the window's first day) and renders them.
|
|
69
|
+
- Keyboard: the region takes focus so the keyboard can scroll it; bars are links in document order, row after row.
|
|
70
|
+
- Screen reader: the region's name says the plan and today; a row reads its name and dates, then its bar's state
|
|
71
|
+
("Pre-launch, in hand"); a milestone reads its name, date and whether it was passed. The scale and today's line are
|
|
72
|
+
`aria-hidden`.
|
|
73
|
+
- The names' column narrows under 40rem of the region (a container query, `ws-plan-timeline`).
|
|
74
|
+
|
|
75
|
+
## Content
|
|
76
|
+
|
|
77
|
+
- A phase's name is the plan's own word ("Pre-launch", "Retro"), sentence case; dates short ("Oct 20 to Nov 16",
|
|
78
|
+
"Nov 2 to 12").
|
|
79
|
+
- A late phase says how in words under its name ("Was due Nov 6", "4 days late"), never with colour alone.
|
|
80
|
+
- A milestone's label names it and its date ("Gate 2, Oct 20, passed", "Launch day, Nov 17").
|
|
81
|
+
- No em dashes.
|
|
82
|
+
|
|
83
|
+
## Props
|
|
84
|
+
|
|
85
|
+
| Name | Type | Meaning |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| `start`, `end` | `string` | The window, `YYYY-MM-DD`; `--days` is the days between them |
|
|
88
|
+
| `rows` | `{ id, name, when, start, end, state: "done" \| "current" \| "late" \| "wait", task?, href?, lateNote?, tone? }[]` | Phases and their tasks, in order (a task straight after its phase) |
|
|
89
|
+
| `milestones` | `{ row, date, label, done? }[]` | Diamonds, each on its row |
|
|
90
|
+
| `ticks` | `{ date, label }[]` | Months, or weeks for a short plan |
|
|
91
|
+
| `today` | `string` | `YYYY-MM-DD`; the line is drawn only inside the window |
|
|
92
|
+
|
|
93
|
+
## Rules
|
|
94
|
+
|
|
95
|
+
- **One scale for every row**: a bar's place and length are its dates, never squeezed to fit a label.
|
|
96
|
+
- **The phase in hand is the one navy bar** (its tasks in hand may share it); done is teal, late salmon, to come the
|
|
97
|
+
track. A state is never colour alone: its words are in the bar's `ws-sr` and, when late, under the name.
|
|
98
|
+
- **Today is one fine line**, never a band or a wash over the window.
|
|
99
|
+
- **Names stay on screen**: the plan scrolls, its names do not.
|
|
100
|
+
- **Hover is a tint**; nothing lifts.
|
|
101
|
+
|
|
102
|
+
## Source
|
|
103
|
+
|
|
104
|
+
- **Derived from**: `Stepper`'s states (done teal, in hand navy, a stall in needs-you words), `KanbanBoard`'s sideways
|
|
105
|
+
scroll with its far-edge fade and stuck head, today's mark in the needs step (`DateInput`, `StreakCalendar`), Seth's
|
|
106
|
+
0.5 px lines.
|
|
107
|
+
- **Precedent**: 0.5.3 (Nov 2026), from the field: three Marketing OS screens (a campaign's phases, the overview's
|
|
108
|
+
launch timeline and the plays by date) drew a plan across its window by hand for want of one (issue #24).
|
|
109
|
+
|
|
110
|
+
## Markup
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<div class="ws-plan-timeline" style="--days: 91;" role="region" aria-label="Portal launch plan by date, today Nov 10" tabindex="0">
|
|
114
|
+
<div class="ws-plan-timeline__plot">
|
|
115
|
+
<div class="ws-plan-timeline__scale" aria-hidden="true">
|
|
116
|
+
<span class="ws-plan-timeline__tick" style="--at: 0;">Sep</span>
|
|
117
|
+
<span class="ws-plan-timeline__tick" style="--at: 30;">Oct</span>
|
|
118
|
+
<span class="ws-plan-timeline__tick" style="--at: 61;">Nov</span>
|
|
119
|
+
</div>
|
|
120
|
+
<ol class="ws-plan-timeline__rows">
|
|
121
|
+
<li class="ws-plan-timeline__row">
|
|
122
|
+
<span class="ws-plan-timeline__name"><span class="ws-plan-timeline__title">Pre-launch</span><span class="ws-plan-timeline__when">Oct 20 to Nov 16</span></span>
|
|
123
|
+
<span class="ws-plan-timeline__track">
|
|
124
|
+
<a class="ws-plan-timeline__bar" href="#" aria-current="step" style="--start: 49; --span: 28;"><span class="ws-sr">Pre-launch, in hand</span></a>
|
|
125
|
+
<span class="ws-plan-timeline__milestone" style="--at: 77;" role="img" aria-label="Launch day, Nov 17" title="Launch day, Nov 17"></span>
|
|
126
|
+
</span>
|
|
127
|
+
</li>
|
|
128
|
+
<li class="ws-plan-timeline__row ws-plan-timeline__row--task">
|
|
129
|
+
<span class="ws-plan-timeline__name"><span class="ws-plan-timeline__title">Landing page</span><span class="ws-plan-timeline__when ws-plan-timeline__when--late">Was due Nov 6</span></span>
|
|
130
|
+
<span class="ws-plan-timeline__track">
|
|
131
|
+
<a class="ws-plan-timeline__bar ws-plan-timeline__bar--late" href="#" style="--start: 56; --span: 11;"><span class="ws-sr">Landing page, 4 days late</span></a>
|
|
132
|
+
</span>
|
|
133
|
+
</li>
|
|
134
|
+
</ol>
|
|
135
|
+
<span class="ws-plan-timeline__now" style="--at: 70;" aria-hidden="true"><span>Today</span></span>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Styles
|
|
141
|
+
|
|
142
|
+
`design-system/components/PlanTimeline/style.css`, in `bundle.css` and `fello.css` as `.ws-plan-timeline`. Every value
|
|
143
|
+
is a token; the bar tones follow the palette. The far-edge fade uses a scroll-driven animation (`ws-scroll-edges`)
|
|
144
|
+
where the browser has one.
|