@charcuterie/ui 3.21.0 → 3.22.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 +11 -0
- package/dist/DropRail/DropRail.d.ts +109 -0
- package/dist/DropRail/DropRail.d.ts.map +1 -0
- package/dist/DropRail/DropRail.js +277 -0
- package/dist/DropRail/DropRail.js.map +1 -0
- package/dist/NavBar/NavBar.d.ts +130 -0
- package/dist/NavBar/NavBar.d.ts.map +1 -0
- package/dist/NavBar/NavBar.js +114 -0
- package/dist/NavBar/NavBar.js.map +1 -0
- package/dist/NavBar/NavBarLink.d.ts +43 -0
- package/dist/NavBar/NavBarLink.d.ts.map +1 -0
- package/dist/NavBar/NavBarLink.js +67 -0
- package/dist/NavBar/NavBarLink.js.map +1 -0
- package/dist/NavBar/navBarCurrent.d.ts +37 -0
- package/dist/NavBar/navBarCurrent.d.ts.map +1 -0
- package/dist/NavBar/navBarCurrent.js +48 -0
- package/dist/NavBar/navBarCurrent.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/DropRail/DropRail.mdx +90 -0
- package/src/DropRail/DropRail.stories.tsx +328 -0
- package/src/DropRail/DropRail.test.tsx +154 -0
- package/src/DropRail/DropRail.tsx +496 -0
- package/src/NavBar/NavBar.mdx +157 -0
- package/src/NavBar/NavBar.stories.tsx +350 -0
- package/src/NavBar/NavBar.test.tsx +344 -0
- package/src/NavBar/NavBar.tsx +271 -0
- package/src/NavBar/NavBarLink.tsx +139 -0
- package/src/NavBar/navBarCurrent.test.ts +81 -0
- package/src/NavBar/navBarCurrent.ts +64 -0
- package/src/icons.storyHelpers.tsx +12 -0
- package/src/index.ts +11 -0
- package/src/sourceRules.test.ts +24 -1
package/README.md
CHANGED
|
@@ -48,6 +48,17 @@ Moving a card takes **no drag-and-drop dependency**: one handle per card, a `Men
|
|
|
48
48
|
other lanes as the primary path, Pointer-Event dragging at 1.4 KB gzip beside it
|
|
49
49
|
([decision](../../docs/decisions/2026-08-19-the-board-owns-the-move-and-takes-no-drag-and-drop-dependency.md)).
|
|
50
50
|
|
|
51
|
+
**`DropRail` is the move control for a page `Board`'s `Menu` cannot serve.** A menu of the
|
|
52
|
+
other lanes is right at three lanes; it is wrong at thirty-four groups, where it becomes a
|
|
53
|
+
scrolling list inside a popup and a drag becomes a scroll with the button held down. The
|
|
54
|
+
rail pins every destination to the top for the length of a move — no travel, nothing off
|
|
55
|
+
screen — as a `listbox` driven by pointer, tap **or** the arrow keys, all committing through
|
|
56
|
+
one `onPick`. It reads `event.buttons` to tell a drag from a tap, so a host says only
|
|
57
|
+
whether a move is in flight. It holds the same line on dependencies: `elementFromPoint` over
|
|
58
|
+
its own chips is the only geometry a rail needs. Not to be confused with `Rail`, the app
|
|
59
|
+
shell's side landmark — `FileDropZone` is the precedent for the prefix carrying the
|
|
60
|
+
distinction.
|
|
61
|
+
|
|
51
62
|
**`BadgeButton` is a `Badge` you can press**, and it came from a consumer rather than from a
|
|
52
63
|
plan: QueuePilot has six pill-shaped controls — per-entry setting tags, an Edit chip, two
|
|
53
64
|
start-point chips, a group chip, a pool's Exclude chip — that were hand-rolled
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type DropRailTarget = {
|
|
3
|
+
/**
|
|
4
|
+
* A number after the label — a lane's size, a project's open
|
|
5
|
+
* count. Optional, and absent is a real answer: a rail of
|
|
6
|
+
* destinations that have no size should not print a zero on every
|
|
7
|
+
* chip.
|
|
8
|
+
*/
|
|
9
|
+
count?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Where the thing being moved already is. It stays **visible and
|
|
12
|
+
* unofferable** rather than being filtered out, because a rail
|
|
13
|
+
* whose contents change depending on what you grabbed is a rail
|
|
14
|
+
* whose chips move under the pointer between one move and the
|
|
15
|
+
* next — and muscle memory is the only reason this control is
|
|
16
|
+
* faster than a menu.
|
|
17
|
+
*/
|
|
18
|
+
isCurrent?: boolean;
|
|
19
|
+
isDisabled?: boolean;
|
|
20
|
+
key: string;
|
|
21
|
+
label: string;
|
|
22
|
+
/** A leading glyph the app owns — an emoji, a dot, an icon. */
|
|
23
|
+
mark?: ReactNode;
|
|
24
|
+
};
|
|
25
|
+
export type DropRailProps = {
|
|
26
|
+
className?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the rail is showing. The host owns this, because only
|
|
29
|
+
* the host knows a move started — and it is the same boolean
|
|
30
|
+
* whether the move began as a pointer drag or as a tap on a
|
|
31
|
+
* handle.
|
|
32
|
+
*/
|
|
33
|
+
isOpen: boolean;
|
|
34
|
+
/** The rail's accessible name. Required — it is a listbox. */
|
|
35
|
+
label: string;
|
|
36
|
+
onDismiss?: () => void;
|
|
37
|
+
onPick: (targetKey: string) => void;
|
|
38
|
+
targets: readonly DropRailTarget[];
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Every destination at once, brought to the pointer instead of the
|
|
42
|
+
* pointer being carried to it.
|
|
43
|
+
*
|
|
44
|
+
* ### The measurement this exists for
|
|
45
|
+
*
|
|
46
|
+
* `Board` moves a card with a `Menu` on the card, and that is the
|
|
47
|
+
* right control for a board: three or four lanes, all on screen, and
|
|
48
|
+
* the menu opens where your hand already is. It stops being the
|
|
49
|
+
* right control at the size the fleet's real lists reach. Docket's
|
|
50
|
+
* Backlog draws **34 project groups down one page**, of which about
|
|
51
|
+
* four fit on a display. Dragging a card from the first to the
|
|
52
|
+
* thirty-fourth means holding a pointer down while the page scrolls,
|
|
53
|
+
* and a menu of 34 items is a scrolling list inside a popup — the
|
|
54
|
+
* same problem in a smaller box.
|
|
55
|
+
*
|
|
56
|
+
* A rail answers both. It pins to the top for the length of the
|
|
57
|
+
* move, so nothing is ever off screen, and there is no travel: every
|
|
58
|
+
* destination is the same short distance away.
|
|
59
|
+
*
|
|
60
|
+
* ### One control, three drivers, and that is the whole design
|
|
61
|
+
*
|
|
62
|
+
* WCAG 2.5.7 requires a single-pointer alternative to any dragging
|
|
63
|
+
* movement, and `FileDropZone` next door says the same thing about
|
|
64
|
+
* its own gesture: build the accessible path first and let the drag
|
|
65
|
+
* be the enhancement. So the rail is **not** a drag affordance with
|
|
66
|
+
* a fallback bolted on. It is a listbox that three things drive:
|
|
67
|
+
*
|
|
68
|
+
* 1. **Pointer.** While a button is held, the chip under the
|
|
69
|
+
* pointer is active and releasing over it commits.
|
|
70
|
+
* 2. **Tap.** Opened with no button held, it is a list you touch.
|
|
71
|
+
* Identical markup, identical commit.
|
|
72
|
+
* 3. **Keyboard.** Arrow keys walk it, Enter commits, Escape
|
|
73
|
+
* abandons. `useRovingFocus` owns which chip is tabbable, so
|
|
74
|
+
* there is no second keyboard model in this file to drift out of
|
|
75
|
+
* step with `Listbox`.
|
|
76
|
+
*
|
|
77
|
+
* The rail does not know which of the three opened it, and the host
|
|
78
|
+
* does not have to say. It reads `event.buttons` on the first
|
|
79
|
+
* pointer event it sees: a button already down means a drag is in
|
|
80
|
+
* flight, and nothing else needs threading through.
|
|
81
|
+
*
|
|
82
|
+
* ### It takes no drag-and-drop dependency, and it hit-tests chips
|
|
83
|
+
*
|
|
84
|
+
* The board's decision priced four libraries and took none of them,
|
|
85
|
+
* because a published library's dependency list is inherited by
|
|
86
|
+
* every consumer — including one with 60 KB gzipped to spend in
|
|
87
|
+
* total. Nothing here changes that arithmetic, and this needs far
|
|
88
|
+
* less than `Board` does: a chip is a box under the pointer, so
|
|
89
|
+
* `elementFromPoint` answers the only geometric question there is.
|
|
90
|
+
* No snapshot, no collision strategy, no sortable transform.
|
|
91
|
+
*
|
|
92
|
+
* ### It is not `Rail`, and the two must not be confused
|
|
93
|
+
*
|
|
94
|
+
* `Rail` is the app shell's side rail — a landmark, an `<aside>` or
|
|
95
|
+
* a `<nav>`, permanent furniture down one edge of a page. This is a
|
|
96
|
+
* horizontal strip of drop targets that exists only for the length
|
|
97
|
+
* of a move, and it is a `listbox`. They share four letters and
|
|
98
|
+
* nothing else. `FileDropZone` sets the precedent for the prefix
|
|
99
|
+
* carrying the distinction.
|
|
100
|
+
*
|
|
101
|
+
* ### What it deliberately does not do
|
|
102
|
+
*
|
|
103
|
+
* It does not render the thing being dragged, own the handle, or
|
|
104
|
+
* know what a move means. The host keeps all three, which is what
|
|
105
|
+
* lets the same rail sit above a board, a card grid, or a list of
|
|
106
|
+
* accordions without any of them being modelled here.
|
|
107
|
+
*/
|
|
108
|
+
export declare const DropRail: ({ className, isOpen, label, onDismiss, onPick, targets, }: DropRailProps) => ReactNode;
|
|
109
|
+
//# sourceMappingURL=DropRail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DropRail.d.ts","sourceRoot":"","sources":["../../src/DropRail/DropRail.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,+DAA+D;IAC/D,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,MAAM,EAAE,OAAO,CAAA;IACf,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAA;CACnC,CAAA;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AACH,eAAO,MAAM,QAAQ,GAAI,2DAOtB,aAAa,KAAG,SAuWlB,CAAA"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useLatestRef, useRovingFocus, useUniqueId, } from "@charcuterie/logic";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { toClassName } from "../toClassName.js";
|
|
5
|
+
import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
|
|
6
|
+
/**
|
|
7
|
+
* The attribute the pointer hit test reads. A `data-` attribute
|
|
8
|
+
* rather than a class, for the same reason `Board` marks its cards
|
|
9
|
+
* with one: a class is a style and may be renamed by a consumer's
|
|
10
|
+
* tooling, and this is a contract with a hit test.
|
|
11
|
+
*/
|
|
12
|
+
const TARGET_ATTRIBUTE = "data-drop-rail-target";
|
|
13
|
+
const TARGET_SELECTOR = "[data-drop-rail-target]";
|
|
14
|
+
/**
|
|
15
|
+
* Every destination at once, brought to the pointer instead of the
|
|
16
|
+
* pointer being carried to it.
|
|
17
|
+
*
|
|
18
|
+
* ### The measurement this exists for
|
|
19
|
+
*
|
|
20
|
+
* `Board` moves a card with a `Menu` on the card, and that is the
|
|
21
|
+
* right control for a board: three or four lanes, all on screen, and
|
|
22
|
+
* the menu opens where your hand already is. It stops being the
|
|
23
|
+
* right control at the size the fleet's real lists reach. Docket's
|
|
24
|
+
* Backlog draws **34 project groups down one page**, of which about
|
|
25
|
+
* four fit on a display. Dragging a card from the first to the
|
|
26
|
+
* thirty-fourth means holding a pointer down while the page scrolls,
|
|
27
|
+
* and a menu of 34 items is a scrolling list inside a popup — the
|
|
28
|
+
* same problem in a smaller box.
|
|
29
|
+
*
|
|
30
|
+
* A rail answers both. It pins to the top for the length of the
|
|
31
|
+
* move, so nothing is ever off screen, and there is no travel: every
|
|
32
|
+
* destination is the same short distance away.
|
|
33
|
+
*
|
|
34
|
+
* ### One control, three drivers, and that is the whole design
|
|
35
|
+
*
|
|
36
|
+
* WCAG 2.5.7 requires a single-pointer alternative to any dragging
|
|
37
|
+
* movement, and `FileDropZone` next door says the same thing about
|
|
38
|
+
* its own gesture: build the accessible path first and let the drag
|
|
39
|
+
* be the enhancement. So the rail is **not** a drag affordance with
|
|
40
|
+
* a fallback bolted on. It is a listbox that three things drive:
|
|
41
|
+
*
|
|
42
|
+
* 1. **Pointer.** While a button is held, the chip under the
|
|
43
|
+
* pointer is active and releasing over it commits.
|
|
44
|
+
* 2. **Tap.** Opened with no button held, it is a list you touch.
|
|
45
|
+
* Identical markup, identical commit.
|
|
46
|
+
* 3. **Keyboard.** Arrow keys walk it, Enter commits, Escape
|
|
47
|
+
* abandons. `useRovingFocus` owns which chip is tabbable, so
|
|
48
|
+
* there is no second keyboard model in this file to drift out of
|
|
49
|
+
* step with `Listbox`.
|
|
50
|
+
*
|
|
51
|
+
* The rail does not know which of the three opened it, and the host
|
|
52
|
+
* does not have to say. It reads `event.buttons` on the first
|
|
53
|
+
* pointer event it sees: a button already down means a drag is in
|
|
54
|
+
* flight, and nothing else needs threading through.
|
|
55
|
+
*
|
|
56
|
+
* ### It takes no drag-and-drop dependency, and it hit-tests chips
|
|
57
|
+
*
|
|
58
|
+
* The board's decision priced four libraries and took none of them,
|
|
59
|
+
* because a published library's dependency list is inherited by
|
|
60
|
+
* every consumer — including one with 60 KB gzipped to spend in
|
|
61
|
+
* total. Nothing here changes that arithmetic, and this needs far
|
|
62
|
+
* less than `Board` does: a chip is a box under the pointer, so
|
|
63
|
+
* `elementFromPoint` answers the only geometric question there is.
|
|
64
|
+
* No snapshot, no collision strategy, no sortable transform.
|
|
65
|
+
*
|
|
66
|
+
* ### It is not `Rail`, and the two must not be confused
|
|
67
|
+
*
|
|
68
|
+
* `Rail` is the app shell's side rail — a landmark, an `<aside>` or
|
|
69
|
+
* a `<nav>`, permanent furniture down one edge of a page. This is a
|
|
70
|
+
* horizontal strip of drop targets that exists only for the length
|
|
71
|
+
* of a move, and it is a `listbox`. They share four letters and
|
|
72
|
+
* nothing else. `FileDropZone` sets the precedent for the prefix
|
|
73
|
+
* carrying the distinction.
|
|
74
|
+
*
|
|
75
|
+
* ### What it deliberately does not do
|
|
76
|
+
*
|
|
77
|
+
* It does not render the thing being dragged, own the handle, or
|
|
78
|
+
* know what a move means. The host keeps all three, which is what
|
|
79
|
+
* lets the same rail sit above a board, a card grid, or a list of
|
|
80
|
+
* accordions without any of them being modelled here.
|
|
81
|
+
*/
|
|
82
|
+
export const DropRail = ({ className, isOpen, label, onDismiss, onPick, targets, }) => {
|
|
83
|
+
const baseId = useUniqueId();
|
|
84
|
+
const railRef = useRef(null);
|
|
85
|
+
/**
|
|
86
|
+
* A pointer button was already down when the rail opened, so this
|
|
87
|
+
* move is a drag and it commits on release. A tap-opened rail
|
|
88
|
+
* commits on click instead, and the two must not both fire — a
|
|
89
|
+
* released drag also produces a `click`.
|
|
90
|
+
*/
|
|
91
|
+
const isPointerDriven = useRef(false);
|
|
92
|
+
/**
|
|
93
|
+
* Focus is moved into the rail once per open, not once per render.
|
|
94
|
+
* Without the latch the effect below re-runs whenever the host
|
|
95
|
+
* re-renders and drags focus back to the first chip, which
|
|
96
|
+
* undoes every arrow key the moment anything above updates.
|
|
97
|
+
*/
|
|
98
|
+
const hasTakenFocus = useRef(false);
|
|
99
|
+
const [announcement, setAnnouncement] = useState("");
|
|
100
|
+
const offerableKeys = targets
|
|
101
|
+
.filter((target) => !target.isCurrent && !target.isDisabled)
|
|
102
|
+
.map((target) => target.key);
|
|
103
|
+
/**
|
|
104
|
+
* The offerable keys as one string, because the effects below
|
|
105
|
+
* depend on *which* destinations are offerable and none of them
|
|
106
|
+
* may depend on the array's identity.
|
|
107
|
+
*
|
|
108
|
+
* `targets` is almost always a literal in the host's render, so
|
|
109
|
+
* the array is new on every render. An effect keyed on it
|
|
110
|
+
* re-registers every chip each time, and re-registering loses the
|
|
111
|
+
* active one — which during a keyboard walk means the arrow keys
|
|
112
|
+
* stop moving for no reason a consumer could ever debug.
|
|
113
|
+
*/
|
|
114
|
+
const offerableKeysText = offerableKeys.join(" ");
|
|
115
|
+
const firstOfferableKey = offerableKeys[0];
|
|
116
|
+
const { activeValue, next, previous, register, setActiveValue, } = useRovingFocus({ isWrapping: true });
|
|
117
|
+
/**
|
|
118
|
+
* Read by the global listeners, which are registered once per
|
|
119
|
+
* open rather than once per render. Without them the pointer
|
|
120
|
+
* handler closes over a stale `activeValue`, and Enter commits
|
|
121
|
+
* whichever chip was active when the rail opened.
|
|
122
|
+
*/
|
|
123
|
+
const activeValueRef = useLatestRef(activeValue);
|
|
124
|
+
const offerableKeysRef = useLatestRef(offerableKeys);
|
|
125
|
+
const onDismissRef = useLatestRef(onDismiss);
|
|
126
|
+
const onPickRef = useLatestRef(onPick);
|
|
127
|
+
const targetsRef = useLatestRef(targets);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const releases = offerableKeysText
|
|
130
|
+
.split(" ")
|
|
131
|
+
.filter((key) => key !== "")
|
|
132
|
+
.map((key) => register(key));
|
|
133
|
+
return () => {
|
|
134
|
+
for (const release of releases) {
|
|
135
|
+
release();
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}, [offerableKeysText, register]);
|
|
139
|
+
/**
|
|
140
|
+
* Closing resets everything a reopen must not inherit. Without it
|
|
141
|
+
* a rail reopened for a different card starts on whatever the last
|
|
142
|
+
* move chose, and the first arrow key moves from a place the
|
|
143
|
+
* person never went.
|
|
144
|
+
*/
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (isOpen) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
setActiveValue(null);
|
|
150
|
+
isPointerDriven.current = false;
|
|
151
|
+
hasTakenFocus.current = false;
|
|
152
|
+
setAnnouncement("");
|
|
153
|
+
}, [isOpen, setActiveValue]);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!isOpen) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
const commit = (targetKey) => {
|
|
159
|
+
const target = targetsRef.current.find((one) => one.key === targetKey);
|
|
160
|
+
if (target) {
|
|
161
|
+
setAnnouncement(`Moved to ${target.label}.`);
|
|
162
|
+
}
|
|
163
|
+
onPickRef.current(targetKey);
|
|
164
|
+
};
|
|
165
|
+
const getKeyAt = (clientX, clientY) => {
|
|
166
|
+
const chip = globalThis.document
|
|
167
|
+
.elementFromPoint(clientX, clientY)
|
|
168
|
+
?.closest(TARGET_SELECTOR);
|
|
169
|
+
return chip?.getAttribute(TARGET_ATTRIBUTE) ?? null;
|
|
170
|
+
};
|
|
171
|
+
const getIsOfferable = (key) => key !== null && offerableKeysRef.current.includes(key);
|
|
172
|
+
const onPointerMove = (pointerEvent) => {
|
|
173
|
+
if (pointerEvent.buttons === 0) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
isPointerDriven.current = true;
|
|
177
|
+
const key = getKeyAt(pointerEvent.clientX, pointerEvent.clientY);
|
|
178
|
+
// `null` when the pointer is between chips or off the rail
|
|
179
|
+
// entirely, and that has to clear the highlight. A rail that
|
|
180
|
+
// keeps the last chip lit is telling you it will land
|
|
181
|
+
// somewhere it will not.
|
|
182
|
+
setActiveValue(getIsOfferable(key) ? key : null);
|
|
183
|
+
};
|
|
184
|
+
const onPointerUp = (pointerEvent) => {
|
|
185
|
+
if (!isPointerDriven.current) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const key = getKeyAt(pointerEvent.clientX, pointerEvent.clientY);
|
|
189
|
+
if (getIsOfferable(key) && key !== null) {
|
|
190
|
+
commit(key);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
onDismissRef.current?.();
|
|
194
|
+
};
|
|
195
|
+
const onKeyDown = (keyEvent) => {
|
|
196
|
+
if (keyEvent.key === "Escape") {
|
|
197
|
+
keyEvent.preventDefault();
|
|
198
|
+
onDismissRef.current?.();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (keyEvent.key === "ArrowRight" ||
|
|
202
|
+
keyEvent.key === "ArrowDown") {
|
|
203
|
+
keyEvent.preventDefault();
|
|
204
|
+
next();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (keyEvent.key === "ArrowLeft" ||
|
|
208
|
+
keyEvent.key === "ArrowUp") {
|
|
209
|
+
keyEvent.preventDefault();
|
|
210
|
+
previous();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const isCommitKey = keyEvent.key === "Enter" || keyEvent.key === " ";
|
|
214
|
+
if (isCommitKey && activeValueRef.current !== null) {
|
|
215
|
+
keyEvent.preventDefault();
|
|
216
|
+
commit(activeValueRef.current);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
globalThis.addEventListener("pointermove", onPointerMove);
|
|
220
|
+
globalThis.addEventListener("pointerup", onPointerUp);
|
|
221
|
+
globalThis.addEventListener("keydown", onKeyDown);
|
|
222
|
+
return () => {
|
|
223
|
+
globalThis.removeEventListener("pointermove", onPointerMove);
|
|
224
|
+
globalThis.removeEventListener("pointerup", onPointerUp);
|
|
225
|
+
globalThis.removeEventListener("keydown", onKeyDown);
|
|
226
|
+
};
|
|
227
|
+
}, [
|
|
228
|
+
activeValueRef,
|
|
229
|
+
isOpen,
|
|
230
|
+
next,
|
|
231
|
+
offerableKeysRef,
|
|
232
|
+
onDismissRef,
|
|
233
|
+
onPickRef,
|
|
234
|
+
previous,
|
|
235
|
+
setActiveValue,
|
|
236
|
+
targetsRef,
|
|
237
|
+
]);
|
|
238
|
+
/**
|
|
239
|
+
* Focus enters the rail only when a pointer is NOT driving it.
|
|
240
|
+
* Stealing focus mid-drag scrolls the page to the rail on some
|
|
241
|
+
* browsers, which is the one thing this control exists to avoid.
|
|
242
|
+
*/
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
if (!isOpen ||
|
|
245
|
+
isPointerDriven.current ||
|
|
246
|
+
hasTakenFocus.current ||
|
|
247
|
+
firstOfferableKey === undefined) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
hasTakenFocus.current = true;
|
|
251
|
+
setActiveValue(firstOfferableKey);
|
|
252
|
+
railRef.current
|
|
253
|
+
?.querySelector("[data-drop-rail-target]:not([aria-disabled='true'])")
|
|
254
|
+
?.focus();
|
|
255
|
+
}, [firstOfferableKey, isOpen, setActiveValue]);
|
|
256
|
+
if (!isOpen) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { "aria-label": label, className: toClassName("sticky top-0 z-20 flex flex-wrap items-center gap-2 rounded-md border border-border-default bg-surface-overlay p-2 shadow-(--elevation-medium)", className), id: baseId, ref: railRef, role: "listbox", children: targets.map((target) => {
|
|
260
|
+
const isOfferable = !target.isCurrent && !target.isDisabled;
|
|
261
|
+
const isActive = isOfferable && activeValue === target.key;
|
|
262
|
+
return (_jsxs("button", { "aria-disabled": isOfferable ? undefined : true, "aria-selected": isActive, className: toClassName("flex max-w-80 items-center gap-1.5 rounded-full border px-3 py-1 text-sm transition-colors duration-(--duration-fast) ease-standard", isOfferable
|
|
263
|
+
? "cursor-pointer border-border-default bg-surface-raised text-content-primary hover:bg-surface-sunken"
|
|
264
|
+
: "cursor-not-allowed border-border-subtle bg-surface-sunken text-content-disabled", isActive &&
|
|
265
|
+
"border-intent-accent-border bg-intent-accent-surface text-intent-accent-content outline-solid outline-(length:--focus-ring-width) outline-focus-ring"), "data-drop-rail-target": target.key, onClick: () => {
|
|
266
|
+
// A released drag also fires a click. The pointer path
|
|
267
|
+
// has already committed by then, so this guard is what
|
|
268
|
+
// stops a drag committing twice.
|
|
269
|
+
if (isPointerDriven.current ||
|
|
270
|
+
!isOfferable) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
onPick(target.key);
|
|
274
|
+
}, role: "option", tabIndex: isActive ? 0 : -1, type: "button", children: [target.mark === undefined ? null : (_jsx("span", { "aria-hidden": true, children: target.mark })), _jsx("span", { className: "truncate", children: target.label }), target.count === undefined ? null : (_jsx("span", { className: "text-content-secondary text-xs", children: target.count })), target.isCurrent ? (_jsx(VisuallyHidden, { children: "\u2014 where it is now" })) : null] }, target.key));
|
|
275
|
+
}) }), _jsx("output", { "aria-live": "polite", className: "sr-only", children: announcement })] }));
|
|
276
|
+
};
|
|
277
|
+
//# sourceMappingURL=DropRail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DropRail.js","sourceRoot":"","sources":["../../src/DropRail/DropRail.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { ControlSize } from "@charcuterie/tokens";
|
|
2
|
+
import type { Placement } from "@floating-ui/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
export type NavBarItem = {
|
|
5
|
+
/**
|
|
6
|
+
* Where it goes. Routed through the injected `RouterLink` when it
|
|
7
|
+
* is this app's own path, and left to the platform when it is not.
|
|
8
|
+
*/
|
|
9
|
+
href: string;
|
|
10
|
+
/** Decoration beside the label. Never the accessible name. */
|
|
11
|
+
icon?: ReactNode;
|
|
12
|
+
/** Defaults to `href`, which is already unique in a nav. */
|
|
13
|
+
key?: string;
|
|
14
|
+
/** The visible text, and the accessible name. */
|
|
15
|
+
label: string;
|
|
16
|
+
};
|
|
17
|
+
export type NavBarProps = {
|
|
18
|
+
className?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Where the reader is — `useLocation().pathname` in a
|
|
21
|
+
* react-router app. `aria-current="page"` is derived from it by
|
|
22
|
+
* `getIsCurrentHref`, which matches a parent path for its
|
|
23
|
+
* children and treats `/` as exact.
|
|
24
|
+
*
|
|
25
|
+
* Omitted means nothing is current, which is the honest answer on
|
|
26
|
+
* a route no destination owns.
|
|
27
|
+
*/
|
|
28
|
+
currentHref?: string;
|
|
29
|
+
/**
|
|
30
|
+
* **Initial** only, and the bar owns it from then on — the same
|
|
31
|
+
* uncontrolled contract every other component here has. It exists
|
|
32
|
+
* so a story (and a screenshot) can show the menu open; an app
|
|
33
|
+
* almost never passes it.
|
|
34
|
+
*/
|
|
35
|
+
isMenuVisible?: boolean;
|
|
36
|
+
items: readonly NavBarItem[];
|
|
37
|
+
/**
|
|
38
|
+
* Required, and it becomes the `<nav>`'s `aria-label`. Two unnamed
|
|
39
|
+
* navigation landmarks in one page is axe's `landmark-unique`, and
|
|
40
|
+
* more to the point it is a screen reader announcing "navigation"
|
|
41
|
+
* twice with no way to tell which is the main one.
|
|
42
|
+
*/
|
|
43
|
+
label: string;
|
|
44
|
+
/**
|
|
45
|
+
* Where the trigger sits in the bar's track once the row has
|
|
46
|
+
* folded. `"end"` when the header puts its own actions at the
|
|
47
|
+
* far edge, so the fold control joins them instead of hugging
|
|
48
|
+
* the wordmark with the whole track empty after it.
|
|
49
|
+
*
|
|
50
|
+
* It reaches **only** the folded row, which holds exactly one
|
|
51
|
+
* item — so it cannot disturb where the links sit when the bar
|
|
52
|
+
* is whole, and there is deliberately no prop that can.
|
|
53
|
+
*/
|
|
54
|
+
menuAlign?: "end" | "start";
|
|
55
|
+
/**
|
|
56
|
+
* The collapsed trigger's glyph. **No default** — the library
|
|
57
|
+
* ships no icons, and `☰` renders as nothing where the font lacks
|
|
58
|
+
* it. With no icon the trigger is a text button reading
|
|
59
|
+
* `menuLabel`, which works everywhere.
|
|
60
|
+
*/
|
|
61
|
+
menuIcon?: ReactNode;
|
|
62
|
+
/**
|
|
63
|
+
* Names the trigger and, through it, the panel it opens. A
|
|
64
|
+
* `role="dialog"` takes no accessible name from its content.
|
|
65
|
+
*/
|
|
66
|
+
menuLabel?: string;
|
|
67
|
+
menuPlacement?: Placement;
|
|
68
|
+
size?: ControlSize;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* The app's destinations as real links, in one line — folding into a
|
|
72
|
+
* single menu button, whole, when that line does not fit.
|
|
73
|
+
*
|
|
74
|
+
* ## Why this is not `Toolbar`
|
|
75
|
+
*
|
|
76
|
+
* `Toolbar` already solves collapse-into-an-overflow, and this
|
|
77
|
+
* component **reuses its measurement** (`useToolbarOverflow`) rather
|
|
78
|
+
* than growing a second one. What it cannot reuse is the rendering:
|
|
79
|
+
* a `ToolbarAction` is `{ label, onSelect }` and a collapsed one
|
|
80
|
+
* becomes a `MenuItem`, so a nav folded into a `Toolbar` is a row of
|
|
81
|
+
* `<button>`s. Navigation is an `<a href>` — see `NavBarLink`.
|
|
82
|
+
*
|
|
83
|
+
* The overflow is a `Popover` (`role="dialog"`) rather than a `Menu`
|
|
84
|
+
* for the same reason: `role="menu"` permits only the `menuitem`
|
|
85
|
+
* family, and a `menuitem` is something you *do*, not somewhere you
|
|
86
|
+
* *go*.
|
|
87
|
+
*
|
|
88
|
+
* ## It folds WHOLE, and that is the decision
|
|
89
|
+
*
|
|
90
|
+
* `Toolbar` collapses **progressively** — items drop off the end one
|
|
91
|
+
* at a time — because its items are ranked: the two things you do to
|
|
92
|
+
* a running job matter more than the two you do to the deck. A nav
|
|
93
|
+
* is not ranked. Its order is the shape of the product (Docket's
|
|
94
|
+
* reads as the pipeline: Triage → Backlog → Phases → Board →
|
|
95
|
+
* Archives), and a half-collapsed one splits that shape across two
|
|
96
|
+
* places with no rule the reader can learn — "some of the app is up
|
|
97
|
+
* here and the rest is behind a button" is worse than either whole
|
|
98
|
+
* answer. So the bar shows **every** destination or **none**, and
|
|
99
|
+
* the menu, when it opens, is the complete list in the product's own
|
|
100
|
+
* order.
|
|
101
|
+
*
|
|
102
|
+
* ## Measured, not breakpointed
|
|
103
|
+
*
|
|
104
|
+
* There is no `collapseAt` prop and no media query. The bar reads
|
|
105
|
+
* the width its links actually have and folds when their sum stops
|
|
106
|
+
* fitting, so a longer label, a tenth destination, a
|
|
107
|
+
* `data-density="kiosk"` or a reader at 175% browser zoom each move
|
|
108
|
+
* the fold on their own. A hardcoded pixel number is wrong the first
|
|
109
|
+
* time any of those changes — mux-magic collapses at `480px` and
|
|
110
|
+
* plex-channels at `760px`, and neither number has a reason.
|
|
111
|
+
*
|
|
112
|
+
* ⚠️ **The bar must not be sized by its own contents.** It measures
|
|
113
|
+
* its own box to decide what to draw in it, so a container that
|
|
114
|
+
* shrink-wraps makes the answer its own input: put it in a
|
|
115
|
+
* `minmax(0, 1fr)` grid track or a `flex-1 min-w-0` row. The root
|
|
116
|
+
* carries `min-w-0 flex-1` for the flex case; a grid track is the
|
|
117
|
+
* caller's to declare. Same trap `useAdaptiveColumns` documents from
|
|
118
|
+
* the other end.
|
|
119
|
+
*
|
|
120
|
+
* ## Mounted exactly once
|
|
121
|
+
*
|
|
122
|
+
* A destination is in the bar or in the panel, never both. The
|
|
123
|
+
* fleet's habit is to render the whole nav twice and hide one copy
|
|
124
|
+
* with `hidden md:flex` — mux-magic's `PageHeader` across two
|
|
125
|
+
* ~55-line blocks, mail-sifter's `TriageQueue` again — which puts
|
|
126
|
+
* every link in the DOM at every width, so an agent driving the page
|
|
127
|
+
* finds two of each and cannot tell which one a human can see.
|
|
128
|
+
*/
|
|
129
|
+
export declare const NavBar: ({ className, currentHref, isMenuVisible, items, label, menuAlign, menuIcon, menuLabel, menuPlacement, size, }: NavBarProps) => ReactNode;
|
|
130
|
+
//# sourceMappingURL=NavBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavBar.d.ts","sourceRoot":"","sources":["../../src/NavBar/NavBar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAWtC,MAAM,MAAM,UAAU,GAAG;IACvB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,4DAA4D;IAC5D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,KAAK,EAAE,SAAS,UAAU,EAAE,CAAA;IAC5B;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,OAAO,CAAA;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,SAAS,CAAA;IACzB,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,eAAO,MAAM,MAAM,GAAI,+GAWpB,WAAW,KAAG,SAsHhB,CAAA"}
|