@charcuterie/ui 4.6.0 → 4.7.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/README.md +12 -0
- package/dist/LaneTimeline/LaneTimeline.d.ts +132 -0
- package/dist/LaneTimeline/LaneTimeline.d.ts.map +1 -0
- package/dist/LaneTimeline/LaneTimeline.js +165 -0
- package/dist/LaneTimeline/LaneTimeline.js.map +1 -0
- package/dist/LaneTimeline/LaneTimelineBar.d.ts +97 -0
- package/dist/LaneTimeline/LaneTimelineBar.d.ts.map +1 -0
- package/dist/LaneTimeline/LaneTimelineBar.js +69 -0
- package/dist/LaneTimeline/LaneTimelineBar.js.map +1 -0
- package/dist/LaneTimeline/LaneTimelineLane.d.ts +84 -0
- package/dist/LaneTimeline/LaneTimelineLane.d.ts.map +1 -0
- package/dist/LaneTimeline/LaneTimelineLane.js +109 -0
- package/dist/LaneTimeline/LaneTimelineLane.js.map +1 -0
- package/dist/LaneTimeline/laneTimelineGeometry.d.ts +268 -0
- package/dist/LaneTimeline/laneTimelineGeometry.d.ts.map +1 -0
- package/dist/LaneTimeline/laneTimelineGeometry.js +277 -0
- package/dist/LaneTimeline/laneTimelineGeometry.js.map +1 -0
- package/dist/LaneTimeline/useTrackMeasurements.d.ts +69 -0
- package/dist/LaneTimeline/useTrackMeasurements.d.ts.map +1 -0
- package/dist/LaneTimeline/useTrackMeasurements.js +134 -0
- package/dist/LaneTimeline/useTrackMeasurements.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/LaneTimeline/LaneTimeline.mdx +191 -0
- package/src/LaneTimeline/LaneTimeline.stories.tsx +561 -0
- package/src/LaneTimeline/LaneTimeline.test.tsx +330 -0
- package/src/LaneTimeline/LaneTimeline.tsx +381 -0
- package/src/LaneTimeline/LaneTimelineBar.tsx +237 -0
- package/src/LaneTimeline/LaneTimelineLane.tsx +332 -0
- package/src/LaneTimeline/laneTimelineGeometry.test.ts +429 -0
- package/src/LaneTimeline/laneTimelineGeometry.ts +476 -0
- package/src/LaneTimeline/useTrackMeasurements.ts +165 -0
- package/src/index.ts +13 -0
- package/src/sourceRules.test.ts +22 -1
package/README.md
CHANGED
|
@@ -51,6 +51,18 @@ Moving a card takes **no drag-and-drop dependency**: one handle per card, a `Men
|
|
|
51
51
|
other lanes as the primary path, Pointer-Event dragging at 1.4 KB gzip beside it
|
|
52
52
|
([decision](../../docs/decisions/2026-08-19-the-board-owns-the-move-and-takes-no-drag-and-drop-dependency.md)).
|
|
53
53
|
|
|
54
|
+
**`LaneTimeline` is the first component that folds on a measurement of its own box.**
|
|
55
|
+
One horizontal lane per group across a date axis, with anything lasting more than a day drawn
|
|
56
|
+
as a **bar** — the shape a month grid cannot show, and the only one that makes load asymmetry
|
|
57
|
+
visible, because overlapping items pack into stacked rows and a busy lane is literally taller.
|
|
58
|
+
`end` is **inclusive**, said four times over, because an off-by-one on a date range is
|
|
59
|
+
invisible in both directions. The Narrow View **abandons** the axis rather than panning or
|
|
60
|
+
squeezing it. And the fold is measured rather than queried for a reason no other component
|
|
61
|
+
here has: the question is not how wide the box is but how wide one *day* is, which is the
|
|
62
|
+
width divided by a number that lives in the data — 30 columns in 900px is a readable month
|
|
63
|
+
and 365 columns in the same 900px is a smear
|
|
64
|
+
([decision](../../docs/decisions/2026-09-18-a-timeline-span-ends-on-its-last-day-and-the-axis-folds-on-measurement.md)).
|
|
65
|
+
|
|
54
66
|
**`DropRail` is the move control for a page `Board`'s `Menu` cannot serve.** A menu of the
|
|
55
67
|
other lanes is right at three lanes; it is wrong at thirty-four groups, where it becomes a
|
|
56
68
|
scrolling list inside a popup and a drag becomes a scroll with the button held down. The
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { TimelineLane } from "./LaneTimelineLane.tsx";
|
|
3
|
+
import type { TimelineSpan } from "./laneTimelineGeometry.ts";
|
|
4
|
+
export type LaneTimelineProps = {
|
|
5
|
+
className?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Shown when there are no lanes, or when `range` is not a pair of
|
|
8
|
+
* calendar dates. Defaults to a real `EmptyState` — *"real empty
|
|
9
|
+
* states"* is a standing requirement, and an empty timeline is
|
|
10
|
+
* what a household sees on a good week.
|
|
11
|
+
*/
|
|
12
|
+
emptyState?: ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* What level the lane headings are. The caller's, because only
|
|
15
|
+
* the caller knows what the timeline sits under: one directly
|
|
16
|
+
* below a page `<h1>` wants `2`. Getting it wrong is an axe
|
|
17
|
+
* `heading-order` failure and a hole in the screen-reader
|
|
18
|
+
* outline.
|
|
19
|
+
*/
|
|
20
|
+
headingLevel?: 2 | 3 | 4 | 5;
|
|
21
|
+
/** The timeline's accessible name. Required — it is a landmark. */
|
|
22
|
+
label: string;
|
|
23
|
+
lanes: readonly TimelineLane[];
|
|
24
|
+
/**
|
|
25
|
+
* BCP-47, for the dates only. Omitted, `Intl` uses the runtime's
|
|
26
|
+
* own — which is right on a personal machine and wrong on a
|
|
27
|
+
* server, so a server render should pass one rather than let two
|
|
28
|
+
* environments disagree about what "6 Apr" looks like.
|
|
29
|
+
*/
|
|
30
|
+
locale?: string;
|
|
31
|
+
/** How many bars one lane may stack. Default 6. */
|
|
32
|
+
maxRowCount?: number;
|
|
33
|
+
/**
|
|
34
|
+
* The narrowest a day column may get before the axis is abandoned
|
|
35
|
+
* for the Narrow View, as a multiple of the timeline's **own font
|
|
36
|
+
* size**. Default 1.75.
|
|
37
|
+
*
|
|
38
|
+
* In em rather than pixels because every length in this token set
|
|
39
|
+
* moves with the density axis: a floor pinned at 20px is generous
|
|
40
|
+
* on a desktop and clips a two-digit date on the kiosk. Same
|
|
41
|
+
* argument `Avatar` makes about a chip that stays 20px while the
|
|
42
|
+
* prose around it scales.
|
|
43
|
+
*/
|
|
44
|
+
minColumnSize?: number;
|
|
45
|
+
/**
|
|
46
|
+
* The days on screen. **Both ends inclusive**, like every other
|
|
47
|
+
* span in this component — a Monday-to-Sunday week is
|
|
48
|
+
* `2026-04-06` to `2026-04-12`, which is seven columns, not eight.
|
|
49
|
+
*/
|
|
50
|
+
range: TimelineSpan;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* One horizontal lane per group, across a date axis, with anything
|
|
54
|
+
* that lasts more than a day drawn as a **bar**.
|
|
55
|
+
*
|
|
56
|
+
* ## What it is for
|
|
57
|
+
*
|
|
58
|
+
* A month grid gives every day the same box, so a thing that runs
|
|
59
|
+
* Tuesday to Friday is four separate entries and the fact that it is
|
|
60
|
+
* *one* thing is lost. Worse, every cell is the same size no matter
|
|
61
|
+
* how much is in it, so a grid cannot show that one person is
|
|
62
|
+
* carrying most of the week. A lane can: bars keep their real
|
|
63
|
+
* shape, and a lane with overlapping work stacks into rows and gets
|
|
64
|
+
* **taller**. The asymmetry then reads from across the room, before
|
|
65
|
+
* anybody counts anything.
|
|
66
|
+
*
|
|
67
|
+
* It is not a calendar. There is no event, no attendee and no
|
|
68
|
+
* recurrence in this API, because Docket wants the same shape for
|
|
69
|
+
* phases and Horizon wants it for a household — the library owns the
|
|
70
|
+
* **shape** and the app owns the **data**. What a lane holds is a
|
|
71
|
+
* `key`, a `label` and items with a `start`, an `end` and a `title`.
|
|
72
|
+
*
|
|
73
|
+
* ## `end` is INCLUSIVE, and that is the decision to know
|
|
74
|
+
*
|
|
75
|
+
* An off-by-one on a date range is invisible in both directions: a
|
|
76
|
+
* bar one day short and a bar one day long are both bars. So it is
|
|
77
|
+
* stated loudly here and again on the type — **`end` is the last day
|
|
78
|
+
* the item covers**. `2026-04-06` to `2026-04-06` is one day, and a
|
|
79
|
+
* seven-day week ends on its seventh day, not on the eighth. The
|
|
80
|
+
* alternative (a half-open interval, the way `Date` ranges and
|
|
81
|
+
* iCalendar's `DTEND` work) is the better model for *instants* and
|
|
82
|
+
* the wrong one for a human writing down when the roofers are
|
|
83
|
+
* coming: nobody says "Monday to Saturday" about a working week, so
|
|
84
|
+
* an exclusive end would make every consumer add a day on the way in
|
|
85
|
+
* and forget to on one of the paths.
|
|
86
|
+
* [Decision](../../../docs/decisions/2026-09-18-a-timeline-span-ends-on-its-last-day-and-the-axis-folds-on-measurement.md).
|
|
87
|
+
*
|
|
88
|
+
* ## The Narrow View is a LIST, not a pan surface
|
|
89
|
+
*
|
|
90
|
+
* Thirty columns have no narrow layout, and the two obvious answers
|
|
91
|
+
* are both wrong. Horizontal scrolling hides lanes behind a gesture
|
|
92
|
+
* with no affordance and fights the trackpad's own back-navigation
|
|
93
|
+
* — the same objection `Board` settled with *"no horizontal scroll,
|
|
94
|
+
* ever"*. Squeezing thirty columns into 390px gives each day 13
|
|
95
|
+
* pixels, which is not a timeline, it is a texture.
|
|
96
|
+
*
|
|
97
|
+
* So below the threshold the axis is **abandoned** rather than
|
|
98
|
+
* shrunk: each lane becomes a one-column list of its items, each one
|
|
99
|
+
* a line of text with its span written out in words. Nothing is
|
|
100
|
+
* hidden, nothing overflows, and the reading order is the one the
|
|
101
|
+
* fleet's text-heavy lists already use.
|
|
102
|
+
*
|
|
103
|
+
* ## It folds on a MEASUREMENT, not a breakpoint
|
|
104
|
+
*
|
|
105
|
+
* There is no `@media` and no `cq-` in this component, and neither
|
|
106
|
+
* could do the job: the question is not *how wide is the box* but
|
|
107
|
+
* **how wide is one day**, which is the width divided by a number
|
|
108
|
+
* that lives in the data. Thirty columns in 900px is a readable
|
|
109
|
+
* month and three hundred and sixty five columns in the same 900px
|
|
110
|
+
* is a grey smear, and no query can tell those apart. `Nav` folds
|
|
111
|
+
* its bar on a measured width for the weaker version of this
|
|
112
|
+
* reason; `useToolbarOverflow` writes the argument down.
|
|
113
|
+
*
|
|
114
|
+
* ⚠️ **It must not be sized by its own contents.** The component
|
|
115
|
+
* reads its own box to decide what to draw in it, so a shrink-
|
|
116
|
+
* wrapping parent makes the answer its own input. The root declares
|
|
117
|
+
* `container-type: inline-size` — not to query it, the same reason
|
|
118
|
+
* `Main` declares one — which forbids the element from being sized
|
|
119
|
+
* by what is inside it and makes that loop unformable. In a story
|
|
120
|
+
* this is what `align="stretch"` on the `StoryCell` is for.
|
|
121
|
+
*
|
|
122
|
+
* ## What it deliberately does not do
|
|
123
|
+
*
|
|
124
|
+
* No time of day: this is a **calendar-date** axis, so two things on
|
|
125
|
+
* one Tuesday stack rather than sit at 09:00 and 14:00. No "now"
|
|
126
|
+
* marker, no zoom, no drag to reschedule, no data. A range control
|
|
127
|
+
* belongs to the page — `DatePicker` already is one — and a bar you
|
|
128
|
+
* can drag is a write operation, which is a different component with
|
|
129
|
+
* a different decision behind it.
|
|
130
|
+
*/
|
|
131
|
+
export declare const LaneTimeline: ({ className, emptyState, headingLevel, label, lanes, locale, maxRowCount, minColumnSize, range, }: LaneTimelineProps) => ReactNode;
|
|
132
|
+
//# sourceMappingURL=LaneTimeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LaneTimeline.d.ts","sourceRoot":"","sources":["../../src/LaneTimeline/LaneTimeline.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAE1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AA+C7D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC5B,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,KAAK,EAAE,YAAY,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,eAAO,MAAM,YAAY,GAAI,mGAU1B,iBAAiB,KAAG,SA4LtB,CAAA"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { getPlainDateFromDayNumber } from "../DatePicker/plainDate.js";
|
|
3
|
+
import { EmptyState } from "../EmptyState/EmptyState.js";
|
|
4
|
+
import { toClassName } from "../toClassName.js";
|
|
5
|
+
import { LaneTimelineLane } from "./LaneTimelineLane.js";
|
|
6
|
+
import { chooseAxisTickStep, formatAxisDate, getIsAxisReadable, toTimelineAxis, } from "./laneTimelineGeometry.js";
|
|
7
|
+
import { useTrackMeasurements } from "./useTrackMeasurements.js";
|
|
8
|
+
/**
|
|
9
|
+
* Room one tick label needs, as a multiple of the timeline's own
|
|
10
|
+
* font size.
|
|
11
|
+
*
|
|
12
|
+
* `Mon 12 Apr` is about 4.5 of them, and the number is bigger than
|
|
13
|
+
* the label looks because the two are measured against **different**
|
|
14
|
+
* sizes: the label renders at `text-xs` while this multiplies the
|
|
15
|
+
* container's own font size, which is the one the probe can read. A
|
|
16
|
+
* constant rather than a prop, because it is a fact about the label
|
|
17
|
+
* this component prints rather than a preference anybody has.
|
|
18
|
+
*/
|
|
19
|
+
const TICK_LABEL_SIZE = 4.5;
|
|
20
|
+
/**
|
|
21
|
+
* The narrowest a day column may get before the axis stops being
|
|
22
|
+
* worth drawing, in em.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ **The one number in here that is a taste call**, and it was
|
|
25
|
+
* moved once already. 1.25em drew a seven-day axis into a 24rem
|
|
26
|
+
* sidebar, which is 25 pixels a day: the bars were real shapes and
|
|
27
|
+
* every one of their titles was `Mi…`, so the picture was worse than
|
|
28
|
+
* the list it was chosen over. 1.75em is roughly the narrowest
|
|
29
|
+
* column that still holds a short word, which puts a month at about
|
|
30
|
+
* 1030px of component and a week in anything past a phone.
|
|
31
|
+
*
|
|
32
|
+
* The case still worth arguing about is a quarter. Ninety days wants
|
|
33
|
+
* more than 2500px and therefore falls to the Narrow View on every
|
|
34
|
+
* screen here — and 6px a day does still show a *shape*, so somebody
|
|
35
|
+
* may prefer the smear to the list. It is a prop for that reason.
|
|
36
|
+
*/
|
|
37
|
+
const DEFAULT_MIN_COLUMN_SIZE = 1.75;
|
|
38
|
+
/**
|
|
39
|
+
* How many bars a lane may stack before the rest are counted rather
|
|
40
|
+
* than drawn. Six is roughly a screen's worth of one lane.
|
|
41
|
+
*/
|
|
42
|
+
const DEFAULT_MAX_ROW_COUNT = 6;
|
|
43
|
+
/**
|
|
44
|
+
* One horizontal lane per group, across a date axis, with anything
|
|
45
|
+
* that lasts more than a day drawn as a **bar**.
|
|
46
|
+
*
|
|
47
|
+
* ## What it is for
|
|
48
|
+
*
|
|
49
|
+
* A month grid gives every day the same box, so a thing that runs
|
|
50
|
+
* Tuesday to Friday is four separate entries and the fact that it is
|
|
51
|
+
* *one* thing is lost. Worse, every cell is the same size no matter
|
|
52
|
+
* how much is in it, so a grid cannot show that one person is
|
|
53
|
+
* carrying most of the week. A lane can: bars keep their real
|
|
54
|
+
* shape, and a lane with overlapping work stacks into rows and gets
|
|
55
|
+
* **taller**. The asymmetry then reads from across the room, before
|
|
56
|
+
* anybody counts anything.
|
|
57
|
+
*
|
|
58
|
+
* It is not a calendar. There is no event, no attendee and no
|
|
59
|
+
* recurrence in this API, because Docket wants the same shape for
|
|
60
|
+
* phases and Horizon wants it for a household — the library owns the
|
|
61
|
+
* **shape** and the app owns the **data**. What a lane holds is a
|
|
62
|
+
* `key`, a `label` and items with a `start`, an `end` and a `title`.
|
|
63
|
+
*
|
|
64
|
+
* ## `end` is INCLUSIVE, and that is the decision to know
|
|
65
|
+
*
|
|
66
|
+
* An off-by-one on a date range is invisible in both directions: a
|
|
67
|
+
* bar one day short and a bar one day long are both bars. So it is
|
|
68
|
+
* stated loudly here and again on the type — **`end` is the last day
|
|
69
|
+
* the item covers**. `2026-04-06` to `2026-04-06` is one day, and a
|
|
70
|
+
* seven-day week ends on its seventh day, not on the eighth. The
|
|
71
|
+
* alternative (a half-open interval, the way `Date` ranges and
|
|
72
|
+
* iCalendar's `DTEND` work) is the better model for *instants* and
|
|
73
|
+
* the wrong one for a human writing down when the roofers are
|
|
74
|
+
* coming: nobody says "Monday to Saturday" about a working week, so
|
|
75
|
+
* an exclusive end would make every consumer add a day on the way in
|
|
76
|
+
* and forget to on one of the paths.
|
|
77
|
+
* [Decision](../../../docs/decisions/2026-09-18-a-timeline-span-ends-on-its-last-day-and-the-axis-folds-on-measurement.md).
|
|
78
|
+
*
|
|
79
|
+
* ## The Narrow View is a LIST, not a pan surface
|
|
80
|
+
*
|
|
81
|
+
* Thirty columns have no narrow layout, and the two obvious answers
|
|
82
|
+
* are both wrong. Horizontal scrolling hides lanes behind a gesture
|
|
83
|
+
* with no affordance and fights the trackpad's own back-navigation
|
|
84
|
+
* — the same objection `Board` settled with *"no horizontal scroll,
|
|
85
|
+
* ever"*. Squeezing thirty columns into 390px gives each day 13
|
|
86
|
+
* pixels, which is not a timeline, it is a texture.
|
|
87
|
+
*
|
|
88
|
+
* So below the threshold the axis is **abandoned** rather than
|
|
89
|
+
* shrunk: each lane becomes a one-column list of its items, each one
|
|
90
|
+
* a line of text with its span written out in words. Nothing is
|
|
91
|
+
* hidden, nothing overflows, and the reading order is the one the
|
|
92
|
+
* fleet's text-heavy lists already use.
|
|
93
|
+
*
|
|
94
|
+
* ## It folds on a MEASUREMENT, not a breakpoint
|
|
95
|
+
*
|
|
96
|
+
* There is no `@media` and no `cq-` in this component, and neither
|
|
97
|
+
* could do the job: the question is not *how wide is the box* but
|
|
98
|
+
* **how wide is one day**, which is the width divided by a number
|
|
99
|
+
* that lives in the data. Thirty columns in 900px is a readable
|
|
100
|
+
* month and three hundred and sixty five columns in the same 900px
|
|
101
|
+
* is a grey smear, and no query can tell those apart. `Nav` folds
|
|
102
|
+
* its bar on a measured width for the weaker version of this
|
|
103
|
+
* reason; `useToolbarOverflow` writes the argument down.
|
|
104
|
+
*
|
|
105
|
+
* ⚠️ **It must not be sized by its own contents.** The component
|
|
106
|
+
* reads its own box to decide what to draw in it, so a shrink-
|
|
107
|
+
* wrapping parent makes the answer its own input. The root declares
|
|
108
|
+
* `container-type: inline-size` — not to query it, the same reason
|
|
109
|
+
* `Main` declares one — which forbids the element from being sized
|
|
110
|
+
* by what is inside it and makes that loop unformable. In a story
|
|
111
|
+
* this is what `align="stretch"` on the `StoryCell` is for.
|
|
112
|
+
*
|
|
113
|
+
* ## What it deliberately does not do
|
|
114
|
+
*
|
|
115
|
+
* No time of day: this is a **calendar-date** axis, so two things on
|
|
116
|
+
* one Tuesday stack rather than sit at 09:00 and 14:00. No "now"
|
|
117
|
+
* marker, no zoom, no drag to reschedule, no data. A range control
|
|
118
|
+
* belongs to the page — `DatePicker` already is one — and a bar you
|
|
119
|
+
* can drag is a write operation, which is a different component with
|
|
120
|
+
* a different decision behind it.
|
|
121
|
+
*/
|
|
122
|
+
export const LaneTimeline = ({ className, emptyState, headingLevel = 3, label, lanes, locale, maxRowCount = DEFAULT_MAX_ROW_COUNT, minColumnSize = DEFAULT_MIN_COLUMN_SIZE, range, }) => {
|
|
123
|
+
const { fontSize, inlineSize, isMeasured, trackRef } = useTrackMeasurements();
|
|
124
|
+
const axis = toTimelineAxis(range);
|
|
125
|
+
const columnCount = axis?.columnCount ?? 0;
|
|
126
|
+
const isAxisReadable = getIsAxisReadable({
|
|
127
|
+
columnCount,
|
|
128
|
+
inlineSize,
|
|
129
|
+
isMeasured,
|
|
130
|
+
minColumnInlineSize: minColumnSize * fontSize,
|
|
131
|
+
});
|
|
132
|
+
const tickStep = chooseAxisTickStep({
|
|
133
|
+
columnCount,
|
|
134
|
+
inlineSize,
|
|
135
|
+
minTickInlineSize: TICK_LABEL_SIZE * fontSize,
|
|
136
|
+
});
|
|
137
|
+
/*
|
|
138
|
+
* An inline style, not a class. Tailwind scans source text for
|
|
139
|
+
* complete class strings, so a `grid-cols-[repeat(${n},…)]` is
|
|
140
|
+
* generated **never** — the grid silently falls back to one column
|
|
141
|
+
* and every bar lands on top of every other one, with nothing
|
|
142
|
+
* failing. `AdaptiveGrid` carries the same note about the same
|
|
143
|
+
* trap.
|
|
144
|
+
*/
|
|
145
|
+
const columnTemplate = `repeat(${Math.max(columnCount, 1)}, minmax(0, 1fr))`;
|
|
146
|
+
const rangeCaption = axis &&
|
|
147
|
+
`${formatAxisDate({
|
|
148
|
+
date: getPlainDateFromDayNumber(axis.startDay),
|
|
149
|
+
locale,
|
|
150
|
+
})} to ${formatAxisDate({
|
|
151
|
+
date: getPlainDateFromDayNumber(axis.endDay),
|
|
152
|
+
locale,
|
|
153
|
+
})}`;
|
|
154
|
+
return (_jsxs("section", { "aria-label": label, className: toClassName("@container flex flex-col", className), children: [_jsxs("div", { "aria-hidden": "true", className: "grid grid-cols-[12rem_minmax(0,1fr)] gap-x-3 overflow-hidden", children: [_jsx("div", {}), _jsx("div", { className: "h-0", ref: trackRef })] }), axis === null ? ((emptyState ?? (_jsx(EmptyState, { description: "This timeline has no visible date range.", heading: "Nothing to show", headingLevel: headingLevel, size: "sm" })))) : lanes.length === 0 ? ((emptyState ?? (_jsx(EmptyState, { description: "Nothing has been added to this timeline yet.", heading: "No lanes yet", headingLevel: headingLevel, size: "sm" })))) : isAxisReadable ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid grid-cols-[12rem_minmax(0,1fr)] items-end gap-x-3 pb-1", children: [_jsx("span", { className: "min-w-0 truncate text-content-muted text-xs", title: rangeCaption ?? undefined, children: rangeCaption }), _jsx("div", { "aria-hidden": "true", className: "grid", style: {
|
|
155
|
+
gridTemplateColumns: columnTemplate,
|
|
156
|
+
}, children: Array.from({ length: columnCount }, (_unused, index) => index)
|
|
157
|
+
.filter((index) => index % tickStep === 0)
|
|
158
|
+
.map((index) => (_jsx("span", { className: "min-w-0 truncate text-content-muted text-xs", style: {
|
|
159
|
+
gridColumn: `${index + 1} / span ${Math.min(tickStep, columnCount - index)}`,
|
|
160
|
+
}, children: formatAxisDate({
|
|
161
|
+
date: getPlainDateFromDayNumber(axis.startDay + index),
|
|
162
|
+
locale,
|
|
163
|
+
}) }, axis.startDay + index))) })] }), lanes.map((lane, position) => (_jsx(LaneTimelineLane, { axis: axis, columnTemplate: columnTemplate, headingLevel: headingLevel, lane: lane, locale: locale, maxRowCount: maxRowCount, position: position, shape: "axis", tickStep: tickStep }, lane.key)))] })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "pb-1 text-content-muted text-xs", children: rangeCaption }), lanes.map((lane, position) => (_jsx(LaneTimelineLane, { axis: axis, columnTemplate: columnTemplate, headingLevel: headingLevel, lane: lane, locale: locale, maxRowCount: maxRowCount, position: position, shape: "list", tickStep: tickStep }, lane.key)))] }))] }));
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=LaneTimeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LaneTimeline.js","sourceRoot":"","sources":["../../src/LaneTimeline/LaneTimeline.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA8B,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAwB,CAAA;AAEzD,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,cAAc,GACf,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAEhE;;;;;;;;;;GAUG;AACH,MAAM,eAAe,GAAG,GAAG,CAAA;AAE3B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,uBAAuB,GAAG,IAAI,CAAA;AAEpC;;;GAGG;AACH,MAAM,qBAAqB,GAAG,CAAC,CAAA;AAmD/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,EAC3B,SAAS,EACT,UAAU,EACV,YAAY,GAAG,CAAC,EAChB,KAAK,EACL,KAAK,EACL,MAAM,EACN,WAAW,GAAG,qBAAqB,EACnC,aAAa,GAAG,uBAAuB,EACvC,KAAK,GACa,EAAa,EAAE;IACjC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,GAClD,oBAAoB,EAAE,CAAA;IAExB,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;IAElC,MAAM,WAAW,GAAG,IAAI,EAAE,WAAW,IAAI,CAAC,CAAA;IAE1C,MAAM,cAAc,GAAG,iBAAiB,CAAC;QACvC,WAAW;QACX,UAAU;QACV,UAAU;QACV,mBAAmB,EAAE,aAAa,GAAG,QAAQ;KAC9C,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,kBAAkB,CAAC;QAClC,WAAW;QACX,UAAU;QACV,iBAAiB,EAAE,eAAe,GAAG,QAAQ;KAC9C,CAAC,CAAA;IAEF;;;;;;;OAOG;IACH,MAAM,cAAc,GAAG,UAAU,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,mBAAmB,CAAA;IAE5E,MAAM,YAAY,GAChB,IAAI;QACJ,GAAG,cAAc,CAAC;YAChB,IAAI,EAAE,yBAAyB,CAAC,IAAI,CAAC,QAAQ,CAAC;YAC9C,MAAM;SACP,CAAC,OAAO,cAAc,CAAC;YACtB,IAAI,EAAE,yBAAyB,CAAC,IAAI,CAAC,MAAM,CAAC;YAC5C,MAAM;SACP,CAAC,EAAE,CAAA;IAEN,OAAO,CACL,iCACc,KAAK,EACjB,SAAS,EAAE,WAAW,CACpB,0BAA0B,EAC1B,SAAS,CACV,aAgBD,8BACc,MAAM,EAClB,SAAS,EAAC,8DAA8D,aAExE,eAAO,EAEP,cAAK,SAAS,EAAC,KAAK,EAAC,GAAG,EAAE,QAAQ,GAAI,IAClC,EAEL,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CACf,CAAC,UAAU,IAAI,CACb,KAAC,UAAU,IACT,WAAW,EAAC,0CAA0C,EACtD,OAAO,EAAC,iBAAiB,EACzB,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAC,IAAI,GACT,CACH,CAAC,CACH,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACvB,CAAC,UAAU,IAAI,CACb,KAAC,UAAU,IACT,WAAW,EAAC,8CAA8C,EAC1D,OAAO,EAAC,cAAc,EACtB,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAC,IAAI,GACT,CACH,CAAC,CACH,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CACnB,8BACE,eAAK,SAAS,EAAC,6DAA6D,aAC1E,eACE,SAAS,EAAC,6CAA6C,EACvD,KAAK,EAAE,YAAY,IAAI,SAAS,YAE/B,YAAY,GACR,EAWP,6BACc,MAAM,EAClB,SAAS,EAAC,MAAM,EAChB,KAAK,EAAE;oCACL,mBAAmB,EAAE,cAAc;iCACpC,YAEA,KAAK,CAAC,IAAI,CACT,EAAE,MAAM,EAAE,WAAW,EAAE,EACvB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAC1B;qCACE,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,QAAQ,KAAK,CAAC,CAAC;qCACzC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACd,eACE,SAAS,EAAC,6CAA6C,EAEvD,KAAK,EAAE;wCACL,UAAU,EAAE,GAAG,KAAK,GAAG,CAAC,WAAW,IAAI,CAAC,GAAG,CACzC,QAAQ,EACR,WAAW,GAAG,KAAK,CACpB,EAAE;qCACJ,YAEA,cAAc,CAAC;wCACd,IAAI,EAAE,yBAAyB,CAC7B,IAAI,CAAC,QAAQ,GAAG,KAAK,CACtB;wCACD,MAAM;qCACP,CAAC,IAbG,IAAI,CAAC,QAAQ,GAAG,KAAK,CAcrB,CACR,CAAC,GACA,IACF,EAEL,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAC7B,KAAC,gBAAgB,IACf,IAAI,EAAE,IAAI,EACV,cAAc,EAAE,cAAc,EAC9B,YAAY,EAAE,YAAY,EAE1B,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAC,MAAM,EACZ,QAAQ,EAAE,QAAQ,IANb,IAAI,CAAC,GAAG,CAOb,CACH,CAAC,IACD,CACJ,CAAC,CAAC,CAAC,CACF,8BAOE,YAAG,SAAS,EAAC,iCAAiC,YAC3C,YAAY,GACX,EAEH,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAC7B,KAAC,gBAAgB,IACf,IAAI,EAAE,IAAI,EACV,cAAc,EAAE,cAAc,EAC9B,YAAY,EAAE,YAAY,EAE1B,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAC,MAAM,EACZ,QAAQ,EAAE,QAAQ,IANb,IAAI,CAAC,GAAG,CAOb,CACH,CAAC,IACD,CACJ,IACO,CACX,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { CategoricalIndex } from "@charcuterie/tokens";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { TimelineSpan } from "./laneTimelineGeometry.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Who owns the item's activation — nobody, a router, or a handler.
|
|
6
|
+
*
|
|
7
|
+
* A union rather than two loose optionals, the same call
|
|
8
|
+
* `BoardItemTitleLink` makes and for the same reason: `href` renders
|
|
9
|
+
* an `<a>` and `onSelect` renders a `<button>`, so a bar carrying
|
|
10
|
+
* both is one of them with the other silently dropped. On screen the
|
|
11
|
+
* two are identical.
|
|
12
|
+
*/
|
|
13
|
+
type TimelineItemAction = {
|
|
14
|
+
/**
|
|
15
|
+
* Where the bar navigates. Routed through
|
|
16
|
+
* `RouterLinkProvider` like every other `href` in this
|
|
17
|
+
* library.
|
|
18
|
+
*/
|
|
19
|
+
href?: string;
|
|
20
|
+
onSelect?: never;
|
|
21
|
+
} | {
|
|
22
|
+
href?: never;
|
|
23
|
+
/** For a timeline that opens a panel rather than a route. */
|
|
24
|
+
onSelect?: () => void;
|
|
25
|
+
};
|
|
26
|
+
export type TimelineItem = TimelineSpan & {
|
|
27
|
+
/**
|
|
28
|
+
* The bar's colour, when somebody has chosen one. Omitted, it is
|
|
29
|
+
* the **lane's**, because on this component colour means *which
|
|
30
|
+
* lane* — an item that picked its own hue out of the ten would
|
|
31
|
+
* read as belonging somewhere else. Pass this only when the item
|
|
32
|
+
* genuinely carries its own identity, and then put that identity
|
|
33
|
+
* in words too.
|
|
34
|
+
*/
|
|
35
|
+
categorical?: CategoricalIndex;
|
|
36
|
+
key: string;
|
|
37
|
+
/**
|
|
38
|
+
* The words. A **string**, not a `ReactNode`, for the reason
|
|
39
|
+
* `BoardLane.label` is one: the same value goes into the bar's
|
|
40
|
+
* accessible name, the `title` a hovering reader gets, and the
|
|
41
|
+
* Narrow View's row — none of which can hold a node.
|
|
42
|
+
*/
|
|
43
|
+
title: string;
|
|
44
|
+
} & TimelineItemAction;
|
|
45
|
+
export type LaneTimelineBarProps = {
|
|
46
|
+
className?: string;
|
|
47
|
+
item: TimelineItem;
|
|
48
|
+
/** The lane's hue, used when the item picked none. */
|
|
49
|
+
laneCategorical: CategoricalIndex;
|
|
50
|
+
/** What the bar says out loud: title, then the span in words. */
|
|
51
|
+
description: string;
|
|
52
|
+
/** Square the leading edge — the item began before this range. */
|
|
53
|
+
hasEarlierStart: boolean;
|
|
54
|
+
/** Square the trailing edge — it runs past this range. */
|
|
55
|
+
hasLaterEnd: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* `bar` is the block on the axis. `row` is the Narrow View's
|
|
58
|
+
* full-width line, where the same item has room for two lines of
|
|
59
|
+
* text and no position to carry meaning.
|
|
60
|
+
*/
|
|
61
|
+
shape: "bar" | "row";
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* One item, in either of the two shapes this component draws it in.
|
|
65
|
+
*
|
|
66
|
+
* ### The name is the whole of the accessibility argument
|
|
67
|
+
*
|
|
68
|
+
* A bar means something by **where it sits and how long it is**, and
|
|
69
|
+
* neither of those reaches a screen reader. Nor does the hue: ten
|
|
70
|
+
* categorical colours carry *which lane*, and colour alone failing
|
|
71
|
+
* WCAG 1.4.1 is the least of it — the lane is already a named group,
|
|
72
|
+
* so the colour is reinforcement rather than information.
|
|
73
|
+
*
|
|
74
|
+
* What is genuinely only in the geometry is the **span**, so every
|
|
75
|
+
* bar carries it as text: `description` is appended to the title in
|
|
76
|
+
* a `VisuallyHidden` and repeated in the `title` attribute. That
|
|
77
|
+
* second one is not an accessibility feature, it is for the sighted
|
|
78
|
+
* reader: a one-day bar is about twenty pixels wide and cannot show
|
|
79
|
+
* its own name at all.
|
|
80
|
+
*
|
|
81
|
+
* A native `title` rather than `Tooltip`, deliberately. A household
|
|
82
|
+
* week is two hundred bars, and two hundred floating-ui instances to
|
|
83
|
+
* describe boxes that are already described is a cost with nothing
|
|
84
|
+
* on the other side of it.
|
|
85
|
+
*
|
|
86
|
+
* ### The clipped edge is square, and it is also in words
|
|
87
|
+
*
|
|
88
|
+
* An item running out of the visible range loses the radius on that
|
|
89
|
+
* edge, which is the convention every Gantt chart uses. It is **not**
|
|
90
|
+
* the only signal:
|
|
91
|
+
* `description` already says "starts before this range", because a
|
|
92
|
+
* missing corner is not something anybody perceives without the
|
|
93
|
+
* unclipped bar beside it to compare against.
|
|
94
|
+
*/
|
|
95
|
+
export declare const LaneTimelineBar: ({ className, description, hasEarlierStart, hasLaterEnd, item, laneCategorical, shape, }: LaneTimelineBarProps) => ReactNode;
|
|
96
|
+
export {};
|
|
97
|
+
//# sourceMappingURL=LaneTimelineBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LaneTimelineBar.d.ts","sourceRoot":"","sources":["../../src/LaneTimeline/LaneTimelineBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAUtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;;;;;;GAQG;AACH,KAAK,kBAAkB,GACnB;IACE;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB,GACD;IACE,IAAI,CAAC,EAAE,KAAK,CAAA;IACZ,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAEL,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG;IACxC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAA;CACd,GAAG,kBAAkB,CAAA;AAEtB,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,YAAY,CAAA;IAClB,sDAAsD;IACtD,eAAe,EAAE,gBAAgB,CAAA;IACjC,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAA;IACnB,kEAAkE;IAClE,eAAe,EAAE,OAAO,CAAA;IACxB,0DAA0D;IAC1D,WAAW,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,KAAK,EAAE,KAAK,GAAG,KAAK,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,eAAe,GAAI,yFAQ7B,oBAAoB,KAAG,SAuHzB,CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CATEGORICAL_APPEARANCE_CLASS, CATEGORICAL_HOVER_CLASS, } from "../categoricalStyles.js";
|
|
3
|
+
import { FOCUS_RING_CLASS } from "../intentStyles.js";
|
|
4
|
+
import { UnstyledLink } from "../RouterLink/UnstyledLink.js";
|
|
5
|
+
import { toClassName } from "../toClassName.js";
|
|
6
|
+
import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
|
|
7
|
+
/**
|
|
8
|
+
* One item, in either of the two shapes this component draws it in.
|
|
9
|
+
*
|
|
10
|
+
* ### The name is the whole of the accessibility argument
|
|
11
|
+
*
|
|
12
|
+
* A bar means something by **where it sits and how long it is**, and
|
|
13
|
+
* neither of those reaches a screen reader. Nor does the hue: ten
|
|
14
|
+
* categorical colours carry *which lane*, and colour alone failing
|
|
15
|
+
* WCAG 1.4.1 is the least of it — the lane is already a named group,
|
|
16
|
+
* so the colour is reinforcement rather than information.
|
|
17
|
+
*
|
|
18
|
+
* What is genuinely only in the geometry is the **span**, so every
|
|
19
|
+
* bar carries it as text: `description` is appended to the title in
|
|
20
|
+
* a `VisuallyHidden` and repeated in the `title` attribute. That
|
|
21
|
+
* second one is not an accessibility feature, it is for the sighted
|
|
22
|
+
* reader: a one-day bar is about twenty pixels wide and cannot show
|
|
23
|
+
* its own name at all.
|
|
24
|
+
*
|
|
25
|
+
* A native `title` rather than `Tooltip`, deliberately. A household
|
|
26
|
+
* week is two hundred bars, and two hundred floating-ui instances to
|
|
27
|
+
* describe boxes that are already described is a cost with nothing
|
|
28
|
+
* on the other side of it.
|
|
29
|
+
*
|
|
30
|
+
* ### The clipped edge is square, and it is also in words
|
|
31
|
+
*
|
|
32
|
+
* An item running out of the visible range loses the radius on that
|
|
33
|
+
* edge, which is the convention every Gantt chart uses. It is **not**
|
|
34
|
+
* the only signal:
|
|
35
|
+
* `description` already says "starts before this range", because a
|
|
36
|
+
* missing corner is not something anybody perceives without the
|
|
37
|
+
* unclipped bar beside it to compare against.
|
|
38
|
+
*/
|
|
39
|
+
export const LaneTimelineBar = ({ className, description, hasEarlierStart, hasLaterEnd, item, laneCategorical, shape, }) => {
|
|
40
|
+
const categorical = item.categorical ?? laneCategorical;
|
|
41
|
+
const isInteractive = Boolean(item.href ?? item.onSelect);
|
|
42
|
+
const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: toClassName("min-w-0 flex-1", shape === "bar"
|
|
43
|
+
? "truncate"
|
|
44
|
+
: "wrap-anywhere text-sm"), children: item.title }), shape === "row" ? (_jsx("span", { className: "text-content-muted text-xs", children: description })) : isInteractive ? null : (_jsx(VisuallyHidden, { children: description }))] }));
|
|
45
|
+
/**
|
|
46
|
+
* Only the bar shape gets one, and it carries the **title** as
|
|
47
|
+
* well as the span. The Narrow View prints both already, and a
|
|
48
|
+
* tooltip repeating text that is on screen is noise a pointer
|
|
49
|
+
* user cannot dismiss.
|
|
50
|
+
*/
|
|
51
|
+
const sentence = `${item.title}, ${description}`;
|
|
52
|
+
const hoverText = shape === "bar" ? sentence : undefined;
|
|
53
|
+
const shellClassName = toClassName("w-full min-w-0 border px-1.5 text-xs leading-tight", shape === "bar"
|
|
54
|
+
? "flex min-h-5 items-center rounded-sm"
|
|
55
|
+
: "flex flex-col gap-0.5 rounded-md py-1", CATEGORICAL_APPEARANCE_CLASS[categorical].soft,
|
|
56
|
+
// A square edge where the item leaves the range. `rounded-*-none`
|
|
57
|
+
// rather than a different component, so a clipped bar and a whole
|
|
58
|
+
// one are the same box with one corner rule changed.
|
|
59
|
+
hasEarlierStart && shape === "bar" && "rounded-s-none", hasLaterEnd && shape === "bar" && "rounded-e-none", isInteractive && "cursor-pointer", isInteractive &&
|
|
60
|
+
CATEGORICAL_HOVER_CLASS[categorical].soft, isInteractive && FOCUS_RING_CLASS, className);
|
|
61
|
+
if (item.href) {
|
|
62
|
+
return (_jsx(UnstyledLink, { "aria-label": sentence, className: shellClassName, href: item.href, title: hoverText, children: content }));
|
|
63
|
+
}
|
|
64
|
+
if (item.onSelect) {
|
|
65
|
+
return (_jsx("button", { "aria-label": sentence, className: toClassName(shellClassName, "text-start"), onClick: item.onSelect, title: hoverText, type: "button", children: content }));
|
|
66
|
+
}
|
|
67
|
+
return (_jsx("span", { className: shellClassName, title: hoverText, children: content }));
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=LaneTimelineBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LaneTimelineBar.js","sourceRoot":"","sources":["../../src/LaneTimeline/LaneTimelineBar.tsx"],"names":[],"mappings":";AAGA,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,GACxB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAAgC,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAsC,CAAA;AAmErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAC9B,SAAS,EACT,WAAW,EACX,eAAe,EACf,WAAW,EACX,IAAI,EACJ,eAAe,EACf,KAAK,GACgB,EAAa,EAAE;IACpC,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,eAAe,CAAA;IAEvD,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAA;IAEzD,MAAM,OAAO,GAAG,CACd,8BASE,eACE,SAAS,EAAE,WAAW,CACpB,gBAAgB,EAChB,KAAK,KAAK,KAAK;oBACb,CAAC,CAAC,UAAU;oBACZ,CAAC,CAAC,uBAAuB,CAC5B,YAEA,IAAI,CAAC,KAAK,GACN,EAsBN,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,CACjB,eAAM,SAAS,EAAC,4BAA4B,YACzC,WAAW,GACP,CACR,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACzB,KAAC,cAAc,cAAE,WAAW,GAAkB,CAC/C,IACA,CACJ,CAAA;IAED;;;;;OAKG;IACH,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,KAAK,KAAK,WAAW,EAAE,CAAA;IAEhD,MAAM,SAAS,GAAG,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;IAExD,MAAM,cAAc,GAAG,WAAW,CAChC,oDAAoD,EACpD,KAAK,KAAK,KAAK;QACb,CAAC,CAAC,sCAAsC;QACxC,CAAC,CAAC,uCAAuC,EAC3C,4BAA4B,CAAC,WAAW,CAAC,CAAC,IAAI;IAC9C,kEAAkE;IAClE,kEAAkE;IAClE,qDAAqD;IACrD,eAAe,IAAI,KAAK,KAAK,KAAK,IAAI,gBAAgB,EACtD,WAAW,IAAI,KAAK,KAAK,KAAK,IAAI,gBAAgB,EAClD,aAAa,IAAI,gBAAgB,EACjC,aAAa;QACX,uBAAuB,CAAC,WAAW,CAAC,CAAC,IAAI,EAC3C,aAAa,IAAI,gBAAgB,EACjC,SAAS,CACV,CAAA;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CACL,KAAC,YAAY,kBACC,QAAQ,EACpB,SAAS,EAAE,cAAc,EACzB,IAAI,EAAE,IAAI,CAAC,IAAI,EACf,KAAK,EAAE,SAAS,YAEf,OAAO,GACK,CAChB,CAAA;IACH,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,OAAO,CACL,+BACc,QAAQ,EACpB,SAAS,EAAE,WAAW,CACpB,cAAc,EACd,YAAY,CACb,EACD,OAAO,EAAE,IAAI,CAAC,QAAQ,EACtB,KAAK,EAAE,SAAS,EAChB,IAAI,EAAC,QAAQ,YAEZ,OAAO,GACD,CACV,CAAA;IACH,CAAC;IAED,OAAO,CACL,eAAM,SAAS,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,YAC9C,OAAO,GACH,CACR,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CategoricalIndex } from "@charcuterie/tokens";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { TimelineItem } from "./LaneTimelineBar.tsx";
|
|
4
|
+
import type { TimelineAxis } from "./laneTimelineGeometry.ts";
|
|
5
|
+
export type TimelineLane = {
|
|
6
|
+
/**
|
|
7
|
+
* Which of the ten hues this lane wears, overriding the one its
|
|
8
|
+
* **position** would have given it.
|
|
9
|
+
*
|
|
10
|
+
* Reach for it when a lane's colour means something outside this
|
|
11
|
+
* set — a person who is the same colour on every screen of the
|
|
12
|
+
* app, a pair that must not drift apart when a third lane is
|
|
13
|
+
* inserted between them. An overridden lane keeps its hue and the
|
|
14
|
+
* rest go on walking `CATEGORICAL_SEQUENCE` from their own
|
|
15
|
+
* positions, so naming one does not force naming all of them.
|
|
16
|
+
*/
|
|
17
|
+
categorical?: CategoricalIndex;
|
|
18
|
+
/**
|
|
19
|
+
* Where the lane's heading navigates — the "show me only this
|
|
20
|
+
* lane" route. A link **inside** the heading, never a heading
|
|
21
|
+
* replaced by a link, exactly as `BoardLane.href` is: the heading
|
|
22
|
+
* keeps its level, its id, and its job of naming the group.
|
|
23
|
+
*/
|
|
24
|
+
href?: string;
|
|
25
|
+
items: readonly TimelineItem[];
|
|
26
|
+
key: string;
|
|
27
|
+
/**
|
|
28
|
+
* The lane's name. A **string**: it names the group, and a group
|
|
29
|
+
* with a `ReactNode` name is a group with no name.
|
|
30
|
+
*/
|
|
31
|
+
label: string;
|
|
32
|
+
};
|
|
33
|
+
export type LaneTimelineLaneProps = {
|
|
34
|
+
axis: TimelineAxis;
|
|
35
|
+
/** The CSS track list, built once by the parent and shared. */
|
|
36
|
+
columnTemplate: string;
|
|
37
|
+
headingLevel: 2 | 3 | 4 | 5;
|
|
38
|
+
lane: TimelineLane;
|
|
39
|
+
locale?: string;
|
|
40
|
+
maxRowCount: number;
|
|
41
|
+
/** Where this lane sits in the set, for its default hue. */
|
|
42
|
+
position: number;
|
|
43
|
+
/**
|
|
44
|
+
* `axis` is the bars across the date axis. `list` is the Narrow
|
|
45
|
+
* View, where there is no axis and each item is a line of text.
|
|
46
|
+
*/
|
|
47
|
+
shape: "axis" | "list";
|
|
48
|
+
/** Draw a separator every this many columns. */
|
|
49
|
+
tickStep: number;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* One lane: its name, how much it is carrying, and either a track of
|
|
53
|
+
* bars or a list of lines.
|
|
54
|
+
*
|
|
55
|
+
* ### A lane is a `group`, not a landmark
|
|
56
|
+
*
|
|
57
|
+
* The same call `BoardLaneList` makes, and it was a real axe failure
|
|
58
|
+
* there before it changed. A named `<section>` is a `region`, so a
|
|
59
|
+
* timeline of eight lanes would put eight landmarks on a page that
|
|
60
|
+
* has one job, and two timelines on one page would put two landmarks
|
|
61
|
+
* with the same name in the document — which is `landmark-unique`.
|
|
62
|
+
* It is a `<div role="group">` rather than a `<section role="group">`
|
|
63
|
+
* because axe matches `section[aria-labelledby]` on the **element**
|
|
64
|
+
* and audits it as a landmark even after the role is overridden.
|
|
65
|
+
*
|
|
66
|
+
* ### The count is what the lane carries IN THIS RANGE
|
|
67
|
+
*
|
|
68
|
+
* Not `items.length`. A consumer hands over a month and shows a
|
|
69
|
+
* week; counting the array would print "31" beside a lane with two
|
|
70
|
+
* bars on it. `inRangeCount` includes anything the row limit hid, so
|
|
71
|
+
* a lane cannot look lighter by being more loaded.
|
|
72
|
+
*
|
|
73
|
+
* ### A lane gets TALLER when it is busy, and that is the design
|
|
74
|
+
*
|
|
75
|
+
* Overlapping items stack into rows (`toPackedLane`), so a lane
|
|
76
|
+
* carrying three things at once is three bars tall while one
|
|
77
|
+
* carrying them in sequence is one. Load asymmetry then reads as
|
|
78
|
+
* height from across the room, before anybody counts anything —
|
|
79
|
+
* which is the whole reason this component exists rather than a
|
|
80
|
+
* month grid, where every cell is the same size no matter what is
|
|
81
|
+
* in it.
|
|
82
|
+
*/
|
|
83
|
+
export declare const LaneTimelineLane: ({ axis, columnTemplate, headingLevel, lane, locale, maxRowCount, position, shape, tickStep, }: LaneTimelineLaneProps) => ReactNode;
|
|
84
|
+
//# sourceMappingURL=LaneTimelineLane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LaneTimelineLane.d.ts","sourceRoot":"","sources":["../../src/LaneTimeline/LaneTimelineLane.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAK3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAM7D,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,YAAY,CAAA;IAClB,+DAA+D;IAC/D,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC3B,IAAI,EAAE,YAAY,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,gBAAgB,GAAI,+FAU9B,qBAAqB,KAAG,SA6N1B,CAAA"}
|