@charcuterie/ui 3.23.0 → 3.24.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/dist/Board/Board.d.ts +6 -3
- package/dist/Board/Board.d.ts.map +1 -1
- package/dist/Board/Board.js +19 -1
- package/dist/Board/Board.js.map +1 -1
- package/dist/Board/BoardCard.d.ts +42 -7
- package/dist/Board/BoardCard.d.ts.map +1 -1
- package/dist/Board/BoardCard.js +54 -8
- package/dist/Board/BoardCard.js.map +1 -1
- package/dist/Nav/Nav.d.ts +167 -0
- package/dist/Nav/Nav.d.ts.map +1 -0
- package/dist/Nav/Nav.js +165 -0
- package/dist/Nav/Nav.js.map +1 -0
- package/dist/Nav/NavLink.d.ts +43 -0
- package/dist/Nav/NavLink.d.ts.map +1 -0
- package/dist/Nav/NavLink.js +111 -0
- package/dist/Nav/NavLink.js.map +1 -0
- package/dist/Nav/navItems.d.ts +67 -0
- package/dist/Nav/navItems.d.ts.map +1 -0
- package/dist/Nav/navItems.js +104 -0
- package/dist/Nav/navItems.js.map +1 -0
- package/dist/Nav/useNavLayout.d.ts +86 -0
- package/dist/Nav/useNavLayout.d.ts.map +1 -0
- package/dist/Nav/useNavLayout.js +114 -0
- package/dist/Nav/useNavLayout.js.map +1 -0
- package/dist/NavBar/NavBar.d.ts +37 -111
- package/dist/NavBar/NavBar.d.ts.map +1 -1
- package/dist/NavBar/NavBar.js +30 -110
- package/dist/NavBar/NavBar.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Board/Board.mdx +25 -2
- package/src/Board/Board.stories.tsx +52 -1
- package/src/Board/Board.test.tsx +82 -0
- package/src/Board/Board.tsx +24 -4
- package/src/Board/BoardCard.tsx +103 -21
- package/src/Board/boardMoveHandleContainer.test.ts +54 -0
- package/src/Nav/Nav.mdx +169 -0
- package/src/Nav/Nav.stories.tsx +402 -0
- package/src/Nav/Nav.test.tsx +338 -0
- package/src/Nav/Nav.tsx +406 -0
- package/src/Nav/NavLink.tsx +220 -0
- package/src/Nav/navItems.test.ts +163 -0
- package/src/Nav/navItems.ts +175 -0
- package/src/Nav/useNavLayout.ts +197 -0
- package/src/NavBar/NavBar.mdx +26 -0
- package/src/NavBar/NavBar.tsx +54 -240
- package/src/index.ts +20 -1
- package/src/sourceRules.test.ts +13 -1
- package/dist/NavBar/NavBarLink.d.ts +0 -43
- package/dist/NavBar/NavBarLink.d.ts.map +0 -1
- package/dist/NavBar/NavBarLink.js +0 -67
- package/dist/NavBar/NavBarLink.js.map +0 -1
- package/dist/NavBar/navBarCurrent.d.ts +0 -37
- package/dist/NavBar/navBarCurrent.d.ts.map +0 -1
- package/dist/NavBar/navBarCurrent.js +0 -48
- package/dist/NavBar/navBarCurrent.js.map +0 -1
- package/src/NavBar/NavBarLink.tsx +0 -139
- package/src/NavBar/navBarCurrent.test.ts +0 -81
- package/src/NavBar/navBarCurrent.ts +0 -64
package/src/NavBar/NavBar.tsx
CHANGED
|
@@ -1,271 +1,85 @@
|
|
|
1
|
-
import { useVisibility } from "@charcuterie/logic"
|
|
2
1
|
import type { ControlSize } from "@charcuterie/tokens"
|
|
3
2
|
import type { Placement } from "@floating-ui/react"
|
|
4
3
|
import type { ReactNode } from "react"
|
|
5
4
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { usePanelItemSize } from "../Overlay/usePanelItemSize.ts"
|
|
9
|
-
import { Popover } from "../Popover/Popover.tsx"
|
|
10
|
-
import { useToolbarOverflow } from "../Toolbar/useToolbarOverflow.ts"
|
|
11
|
-
import { toClassName } from "../toClassName.ts"
|
|
12
|
-
import { NavBarLink } from "./NavBarLink.tsx"
|
|
13
|
-
import { getIsCurrentHref } from "./navBarCurrent.ts"
|
|
5
|
+
import { Nav } from "../Nav/Nav.tsx"
|
|
6
|
+
import type { NavItem } from "../Nav/navItems.ts"
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
href: string
|
|
21
|
-
/** Decoration beside the label. Never the accessible name. */
|
|
22
|
-
icon?: ReactNode
|
|
23
|
-
/** Defaults to `href`, which is already unique in a nav. */
|
|
24
|
-
key?: string
|
|
25
|
-
/** The visible text, and the accessible name. */
|
|
26
|
-
label: string
|
|
27
|
-
}
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Use `Nav`'s `NavItem`. Removed in the next major.
|
|
10
|
+
*/
|
|
11
|
+
export type NavBarItem = NavItem
|
|
28
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Use `NavProps` with `layout="bar"`. Removed in the
|
|
15
|
+
* next major.
|
|
16
|
+
*/
|
|
29
17
|
export type NavBarProps = {
|
|
30
18
|
className?: string
|
|
31
|
-
/**
|
|
32
|
-
* Where the reader is — `useLocation().pathname` in a
|
|
33
|
-
* react-router app. `aria-current="page"` is derived from it by
|
|
34
|
-
* `getIsCurrentHref`, which matches a parent path for its
|
|
35
|
-
* children and treats `/` as exact.
|
|
36
|
-
*
|
|
37
|
-
* Omitted means nothing is current, which is the honest answer on
|
|
38
|
-
* a route no destination owns.
|
|
39
|
-
*/
|
|
40
19
|
currentHref?: string
|
|
41
|
-
/**
|
|
42
|
-
* **Initial** only, and the bar owns it from then on — the same
|
|
43
|
-
* uncontrolled contract every other component here has. It exists
|
|
44
|
-
* so a story (and a screenshot) can show the menu open; an app
|
|
45
|
-
* almost never passes it.
|
|
46
|
-
*/
|
|
47
20
|
isMenuVisible?: boolean
|
|
48
21
|
items: readonly NavBarItem[]
|
|
49
|
-
/**
|
|
50
|
-
* Required, and it becomes the `<nav>`'s `aria-label`. Two unnamed
|
|
51
|
-
* navigation landmarks in one page is axe's `landmark-unique`, and
|
|
52
|
-
* more to the point it is a screen reader announcing "navigation"
|
|
53
|
-
* twice with no way to tell which is the main one.
|
|
54
|
-
*/
|
|
55
22
|
label: string
|
|
56
|
-
/**
|
|
57
|
-
* Where the trigger sits in the bar's track once the row has
|
|
58
|
-
* folded. `"end"` when the header puts its own actions at the
|
|
59
|
-
* far edge, so the fold control joins them instead of hugging
|
|
60
|
-
* the wordmark with the whole track empty after it.
|
|
61
|
-
*
|
|
62
|
-
* It reaches **only** the folded row, which holds exactly one
|
|
63
|
-
* item — so it cannot disturb where the links sit when the bar
|
|
64
|
-
* is whole, and there is deliberately no prop that can.
|
|
65
|
-
*/
|
|
66
23
|
menuAlign?: "end" | "start"
|
|
67
|
-
/**
|
|
68
|
-
* The collapsed trigger's glyph. **No default** — the library
|
|
69
|
-
* ships no icons, and `☰` renders as nothing where the font lacks
|
|
70
|
-
* it. With no icon the trigger is a text button reading
|
|
71
|
-
* `menuLabel`, which works everywhere.
|
|
72
|
-
*/
|
|
73
24
|
menuIcon?: ReactNode
|
|
74
|
-
/**
|
|
75
|
-
* Names the trigger and, through it, the panel it opens. A
|
|
76
|
-
* `role="dialog"` takes no accessible name from its content.
|
|
77
|
-
*/
|
|
78
25
|
menuLabel?: string
|
|
79
26
|
menuPlacement?: Placement
|
|
80
27
|
size?: ControlSize
|
|
81
28
|
}
|
|
82
29
|
|
|
83
30
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* ## Why this is not `Toolbar`
|
|
88
|
-
*
|
|
89
|
-
* `Toolbar` already solves collapse-into-an-overflow, and this
|
|
90
|
-
* component **reuses its measurement** (`useToolbarOverflow`) rather
|
|
91
|
-
* than growing a second one. What it cannot reuse is the rendering:
|
|
92
|
-
* a `ToolbarAction` is `{ label, onSelect }` and a collapsed one
|
|
93
|
-
* becomes a `MenuItem`, so a nav folded into a `Toolbar` is a row of
|
|
94
|
-
* `<button>`s. Navigation is an `<a href>` — see `NavBarLink`.
|
|
95
|
-
*
|
|
96
|
-
* The overflow is a `Popover` (`role="dialog"`) rather than a `Menu`
|
|
97
|
-
* for the same reason: `role="menu"` permits only the `menuitem`
|
|
98
|
-
* family, and a `menuitem` is something you *do*, not somewhere you
|
|
99
|
-
* *go*.
|
|
100
|
-
*
|
|
101
|
-
* ## It folds WHOLE, and that is the decision
|
|
31
|
+
* @deprecated Use `Nav` with `layout="bar"` — which is `Nav`'s
|
|
32
|
+
* default, so `<Nav activeHref={…} items={…} label="Main" />` is the
|
|
33
|
+
* whole migration. **Removed in the next major.**
|
|
102
34
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* reads as the pipeline: Triage → Backlog → Phases → Board →
|
|
108
|
-
* Archives), and a half-collapsed one splits that shape across two
|
|
109
|
-
* places with no rule the reader can learn — "some of the app is up
|
|
110
|
-
* here and the rest is behind a button" is worse than either whole
|
|
111
|
-
* answer. So the bar shows **every** destination or **none**, and
|
|
112
|
-
* the menu, when it opens, is the complete list in the product's own
|
|
113
|
-
* order.
|
|
35
|
+
* This is now a thin adapter over `Nav` and keeps every behaviour it
|
|
36
|
+
* shipped with: the measured whole-row fold, the real `<a href>` in
|
|
37
|
+
* both the bar and the panel, and the same props under the same
|
|
38
|
+
* names. Only two things differ, and both are fixes:
|
|
114
39
|
*
|
|
115
|
-
*
|
|
40
|
+
* 1. `currentHref` is `activeHref` on `Nav`.
|
|
41
|
+
* 2. **Two items can no longer both be current.** This component
|
|
42
|
+
* asked the question once per item, so a bar holding `/settings`
|
|
43
|
+
* and `/settings/labels` marked *both* while the reader was on
|
|
44
|
+
* the deeper one — against its own decision record, which says a
|
|
45
|
+
* nav with two current items has stopped saying anything. `Nav`
|
|
46
|
+
* takes the deepest match. If an app was relying on the parent
|
|
47
|
+
* also lighting up, it was relying on a defect.
|
|
116
48
|
*
|
|
117
|
-
*
|
|
118
|
-
* the width its links actually have and folds when their sum stops
|
|
119
|
-
* fitting, so a longer label, a tenth destination, a
|
|
120
|
-
* `data-density="kiosk"` or a reader at 175% browser zoom each move
|
|
121
|
-
* the fold on their own. A hardcoded pixel number is wrong the first
|
|
122
|
-
* time any of those changes — mux-magic collapses at `480px` and
|
|
123
|
-
* plex-channels at `760px`, and neither number has a reason.
|
|
49
|
+
* ## Why it went away
|
|
124
50
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* the
|
|
132
|
-
*
|
|
133
|
-
* ## Mounted exactly once
|
|
134
|
-
*
|
|
135
|
-
* A destination is in the bar or in the panel, never both. The
|
|
136
|
-
* fleet's habit is to render the whole nav twice and hide one copy
|
|
137
|
-
* with `hidden md:flex` — mux-magic's `PageHeader` across two
|
|
138
|
-
* ~55-line blocks, mail-sifter's `TriageQueue` again — which puts
|
|
139
|
-
* every link in the DOM at every width, so an agent driving the page
|
|
140
|
-
* finds two of each and cannot tell which one a human can see.
|
|
51
|
+
* It answered half the question. A horizontal row that folds is one
|
|
52
|
+
* shape an app's destinations take; a labelled side rail, a
|
|
53
|
+
* collapsed icon rail and a Narrow-View bottom strip are the others,
|
|
54
|
+
* and this component could not become any of them — so an app that
|
|
55
|
+
* wanted a rail was back to hand-rolling, which is the state this
|
|
56
|
+
* was built to end. `Nav` is the same list drawn in whichever of the
|
|
57
|
+
* five places fits, and `useNavLayout` is the width rule that says
|
|
58
|
+
* which.
|
|
141
59
|
*/
|
|
142
60
|
export const NavBar = ({
|
|
143
61
|
className,
|
|
144
62
|
currentHref,
|
|
145
|
-
isMenuVisible
|
|
63
|
+
isMenuVisible,
|
|
146
64
|
items,
|
|
147
65
|
label,
|
|
148
|
-
menuAlign
|
|
66
|
+
menuAlign,
|
|
149
67
|
menuIcon,
|
|
150
|
-
menuLabel
|
|
151
|
-
menuPlacement
|
|
152
|
-
size
|
|
153
|
-
}: NavBarProps): ReactNode =>
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
* progressive bar can ask "how many fit **beside a trigger**",
|
|
169
|
-
* which is a question this bar never asks: either every link fits
|
|
170
|
-
* with no trigger at all, or the row is one trigger and nothing
|
|
171
|
-
* else. `chooseVisibleCount` tests the whole-row case first and
|
|
172
|
-
* with the trigger costed at zero, so that branch is exact.
|
|
173
|
-
*/
|
|
174
|
-
const isCollapsed = visibleCount < items.length
|
|
175
|
-
|
|
176
|
-
const { hide, isVisible, toggle } = useVisibility({
|
|
177
|
-
isVisible: isMenuVisible,
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
const panelItemSize = usePanelItemSize("lg")
|
|
181
|
-
|
|
182
|
-
const renderLink = (
|
|
183
|
-
item: NavBarItem,
|
|
184
|
-
placement: "bar" | "panel",
|
|
185
|
-
) => (
|
|
186
|
-
<NavBarLink
|
|
187
|
-
isCurrent={getIsCurrentHref({
|
|
188
|
-
currentHref,
|
|
189
|
-
href: item.href,
|
|
190
|
-
})}
|
|
191
|
-
item={item}
|
|
192
|
-
key={item.key ?? item.href}
|
|
193
|
-
onNavigate={placement === "panel" ? hide : undefined}
|
|
194
|
-
placement={placement}
|
|
195
|
-
size={placement === "bar" ? size : panelItemSize}
|
|
196
|
-
trackElement={
|
|
197
|
-
placement === "bar" ? trackItem : undefined
|
|
198
|
-
}
|
|
199
|
-
/>
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
const trigger =
|
|
203
|
-
menuIcon === undefined ? (
|
|
204
|
-
<Button
|
|
205
|
-
appearance="ghost"
|
|
206
|
-
intent="neutral"
|
|
207
|
-
onClick={toggle}
|
|
208
|
-
size={size}
|
|
209
|
-
>
|
|
210
|
-
{menuLabel}
|
|
211
|
-
</Button>
|
|
212
|
-
) : (
|
|
213
|
-
<IconButton
|
|
214
|
-
appearance="ghost"
|
|
215
|
-
intent="neutral"
|
|
216
|
-
label={menuLabel}
|
|
217
|
-
onClick={toggle}
|
|
218
|
-
size={size}
|
|
219
|
-
>
|
|
220
|
-
{menuIcon}
|
|
221
|
-
</IconButton>
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
return (
|
|
225
|
-
<nav
|
|
226
|
-
aria-label={label}
|
|
227
|
-
className={toClassName(
|
|
228
|
-
"flex min-w-0 flex-1 items-center gap-1",
|
|
229
|
-
// Two written-out entries rather than one interpolated
|
|
230
|
-
// string: Tailwind's scanner cannot see a class that
|
|
231
|
-
// exists only at runtime.
|
|
232
|
-
isCollapsed && menuAlign === "end"
|
|
233
|
-
? "justify-end"
|
|
234
|
-
: "justify-start",
|
|
235
|
-
className,
|
|
236
|
-
)}
|
|
237
|
-
ref={containerRef}
|
|
238
|
-
>
|
|
239
|
-
{isCollapsed ? (
|
|
240
|
-
<Popover
|
|
241
|
-
// The default panel is `max-w-xs p-3` — sized for a
|
|
242
|
-
// paragraph of prose. This one holds full-bleed rows that
|
|
243
|
-
// bring their own inset, so the padding shrinks to the
|
|
244
|
-
// gutter around them and the width is left to the list.
|
|
245
|
-
className="max-w-none p-1"
|
|
246
|
-
heading={menuLabel}
|
|
247
|
-
isVisible={isVisible}
|
|
248
|
-
onDismiss={hide}
|
|
249
|
-
placement={menuPlacement}
|
|
250
|
-
trigger={trigger}
|
|
251
|
-
>
|
|
252
|
-
{/*
|
|
253
|
-
A list, so a screen reader says how many destinations
|
|
254
|
-
there are before the reader starts down them — the one
|
|
255
|
-
thing the bar's own row communicates by simply being
|
|
256
|
-
visible all at once.
|
|
257
|
-
*/}
|
|
258
|
-
<ul className="flex min-w-48 list-none flex-col gap-0.5">
|
|
259
|
-
{items.map((item) => (
|
|
260
|
-
<li key={item.key ?? item.href}>
|
|
261
|
-
{renderLink(item, "panel")}
|
|
262
|
-
</li>
|
|
263
|
-
))}
|
|
264
|
-
</ul>
|
|
265
|
-
</Popover>
|
|
266
|
-
) : (
|
|
267
|
-
items.map((item) => renderLink(item, "bar"))
|
|
268
|
-
)}
|
|
269
|
-
</nav>
|
|
270
|
-
)
|
|
271
|
-
}
|
|
68
|
+
menuLabel,
|
|
69
|
+
menuPlacement,
|
|
70
|
+
size,
|
|
71
|
+
}: NavBarProps): ReactNode => (
|
|
72
|
+
<Nav
|
|
73
|
+
activeHref={currentHref}
|
|
74
|
+
className={className}
|
|
75
|
+
isMenuVisible={isMenuVisible}
|
|
76
|
+
items={items}
|
|
77
|
+
label={label}
|
|
78
|
+
layout="bar"
|
|
79
|
+
menuAlign={menuAlign}
|
|
80
|
+
menuIcon={menuIcon}
|
|
81
|
+
menuLabel={menuLabel}
|
|
82
|
+
menuPlacement={menuPlacement}
|
|
83
|
+
size={size}
|
|
84
|
+
/>
|
|
85
|
+
)
|
package/src/index.ts
CHANGED
|
@@ -276,12 +276,31 @@ export type {
|
|
|
276
276
|
export { Menu } from "./Menu/Menu.tsx"
|
|
277
277
|
export type { ModalProps } from "./Modal/Modal.tsx"
|
|
278
278
|
export { Modal } from "./Modal/Modal.tsx"
|
|
279
|
+
export type {
|
|
280
|
+
NavLayout,
|
|
281
|
+
NavProps,
|
|
282
|
+
} from "./Nav/Nav.tsx"
|
|
283
|
+
export { Nav } from "./Nav/Nav.tsx"
|
|
284
|
+
export type {
|
|
285
|
+
NavItem,
|
|
286
|
+
NavRailItem,
|
|
287
|
+
} from "./Nav/navItems.ts"
|
|
288
|
+
export {
|
|
289
|
+
getIsCurrentHref,
|
|
290
|
+
resolveActiveKey,
|
|
291
|
+
} from "./Nav/navItems.ts"
|
|
292
|
+
export type {
|
|
293
|
+
NavLayoutMode,
|
|
294
|
+
NavLayoutState,
|
|
295
|
+
ResolvedNavLayout,
|
|
296
|
+
UseNavLayoutOptions,
|
|
297
|
+
} from "./Nav/useNavLayout.ts"
|
|
298
|
+
export { useNavLayout } from "./Nav/useNavLayout.ts"
|
|
279
299
|
export type {
|
|
280
300
|
NavBarItem,
|
|
281
301
|
NavBarProps,
|
|
282
302
|
} from "./NavBar/NavBar.tsx"
|
|
283
303
|
export { NavBar } from "./NavBar/NavBar.tsx"
|
|
284
|
-
export { getIsCurrentHref } from "./NavBar/navBarCurrent.ts"
|
|
285
304
|
export type {
|
|
286
305
|
PickerOption,
|
|
287
306
|
PickerProps,
|
package/src/sourceRules.test.ts
CHANGED
|
@@ -525,7 +525,19 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
525
525
|
// `VirtualizedGrid` calls `useAdaptiveColumns`. It renders no
|
|
526
526
|
// row, no handle and no buttons; `renderItem` is what lets one
|
|
527
527
|
// component serve three grids that have nothing in common.
|
|
528
|
-
|
|
528
|
+
//
|
|
529
|
+
// `Nav` — the destinations as a rail, a collapsed icon rail or a
|
|
530
|
+
// bottom strip — is +1 -> 60. It is the other half of `NavBar`,
|
|
531
|
+
// which folds a horizontal row and cannot become a vertical one:
|
|
532
|
+
// no media query moves an element across `Shell`'s grid, so the
|
|
533
|
+
// rail is a different corner of the page rather than a restyling
|
|
534
|
+
// of the same one. Two members stay out of this count by the
|
|
535
|
+
// `<Name>/<Name>.tsx` rule and are exported from the barrel
|
|
536
|
+
// anyway — `navItems.ts`, because `resolveActiveKey` is the
|
|
537
|
+
// active-path rule an app's tests want to assert directly, and
|
|
538
|
+
// `useNavLayout.ts`, because the app has to read the width rule to
|
|
539
|
+
// know which corner to render into.
|
|
540
|
+
expect(componentNames.length).toBe(60)
|
|
529
541
|
|
|
530
542
|
for (const name of componentNames) {
|
|
531
543
|
expect(barrel).toContain(`export { ${name} }`)
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { ControlSize } from "@charcuterie/tokens";
|
|
2
|
-
import type { ReactNode } from "react";
|
|
3
|
-
import type { NavBarItem } from "./NavBar.tsx";
|
|
4
|
-
export type NavBarLinkProps = {
|
|
5
|
-
isCurrent: boolean;
|
|
6
|
-
item: NavBarItem;
|
|
7
|
-
/**
|
|
8
|
-
* Fires when the reader activates this link **in a way that keeps
|
|
9
|
-
* them on the page** — a plain left click, Enter, Space. It is
|
|
10
|
-
* what closes the collapsed panel behind them.
|
|
11
|
-
*
|
|
12
|
-
* A modified click (ctrl, meta, shift, alt) opens a new tab and
|
|
13
|
-
* leaves this document exactly where it was, so it deliberately
|
|
14
|
-
* does not fire: shutting the menu the reader is still working
|
|
15
|
-
* through is the behaviour that makes people stop ctrl-clicking.
|
|
16
|
-
*/
|
|
17
|
-
onNavigate?: () => void;
|
|
18
|
-
/** Where it is drawn. A bar row and a panel row paint differently. */
|
|
19
|
-
placement: "bar" | "panel";
|
|
20
|
-
size: ControlSize;
|
|
21
|
-
/** Hands the bar row's box to the measuring hook. Bar only. */
|
|
22
|
-
trackElement?: (key: string, element: HTMLElement | null) => void;
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* One destination, drawn either in the bar or in the collapsed
|
|
26
|
-
* panel — and it is a real `<a href>` in both.
|
|
27
|
-
*
|
|
28
|
-
* That is the whole reason this component exists rather than the
|
|
29
|
-
* `Menu` a collapsed `Toolbar` opens: `MenuItem` is `{ label,
|
|
30
|
-
* onSelect }`, so a nav folded into one becomes a row of
|
|
31
|
-
* `<button>`s. It looks identical in a screenshot and it loses
|
|
32
|
-
* middle-click, ctrl-click, "open in a new tab", "copy link
|
|
33
|
-
* address", the status bar, and the browser's own visited styling.
|
|
34
|
-
* `BuildingAnApp.mdx` names `<button onClick={navigate}>` as the
|
|
35
|
-
* fleet's most common navigation defect; a collapse mechanism that
|
|
36
|
-
* introduced it at narrow widths would be that defect on a timer.
|
|
37
|
-
*
|
|
38
|
-
* The router seam is `ButtonLink`'s, unchanged: an external
|
|
39
|
-
* destination or a fragment falls back to a plain anchor rather than
|
|
40
|
-
* being pushed onto a history stack that has no route for it.
|
|
41
|
-
*/
|
|
42
|
-
export declare const NavBarLink: ({ isCurrent, item, onNavigate, placement, size, trackElement, }: NavBarLinkProps) => ReactNode;
|
|
43
|
-
//# sourceMappingURL=NavBarLink.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NavBarLink.d.ts","sourceRoot":"","sources":["../../src/NavBar/NavBarLink.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,OAAO,CAAA;AAUlD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,UAAU,CAAA;IAChB;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,sEAAsE;IACtE,SAAS,EAAE,KAAK,GAAG,OAAO,CAAA;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,CACb,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,WAAW,GAAG,IAAI,KACxB,IAAI,CAAA;CACV,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,GAAI,iEAOxB,eAAe,KAAG,SA4EpB,CAAA"}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { CONTROL_SIZE_CLASS, PANEL_ITEM_SIZE_CLASS, } from "../controlStyles.js";
|
|
3
|
-
import { FOCUS_RING_CLASS } from "../intentStyles.js";
|
|
4
|
-
import { useRouterLink } from "../RouterLink/RouterLinkProvider.js";
|
|
5
|
-
import { getIsRoutedHref } from "../RouterLink/routerLink.js";
|
|
6
|
-
import { toClassName } from "../toClassName.js";
|
|
7
|
-
/**
|
|
8
|
-
* One destination, drawn either in the bar or in the collapsed
|
|
9
|
-
* panel — and it is a real `<a href>` in both.
|
|
10
|
-
*
|
|
11
|
-
* That is the whole reason this component exists rather than the
|
|
12
|
-
* `Menu` a collapsed `Toolbar` opens: `MenuItem` is `{ label,
|
|
13
|
-
* onSelect }`, so a nav folded into one becomes a row of
|
|
14
|
-
* `<button>`s. It looks identical in a screenshot and it loses
|
|
15
|
-
* middle-click, ctrl-click, "open in a new tab", "copy link
|
|
16
|
-
* address", the status bar, and the browser's own visited styling.
|
|
17
|
-
* `BuildingAnApp.mdx` names `<button onClick={navigate}>` as the
|
|
18
|
-
* fleet's most common navigation defect; a collapse mechanism that
|
|
19
|
-
* introduced it at narrow widths would be that defect on a timer.
|
|
20
|
-
*
|
|
21
|
-
* The router seam is `ButtonLink`'s, unchanged: an external
|
|
22
|
-
* destination or a fragment falls back to a plain anchor rather than
|
|
23
|
-
* being pushed onto a history stack that has no route for it.
|
|
24
|
-
*/
|
|
25
|
-
export const NavBarLink = ({ isCurrent, item, onNavigate, placement, size, trackElement, }) => {
|
|
26
|
-
const RouterLink = useRouterLink();
|
|
27
|
-
const LinkElement = getIsRoutedHref(item.href)
|
|
28
|
-
? RouterLink
|
|
29
|
-
: "a";
|
|
30
|
-
const className = placement === "bar"
|
|
31
|
-
? toClassName("inline-flex shrink-0 items-center rounded-md border border-transparent whitespace-nowrap transition-colors duration-(--duration-fast) ease-standard", CONTROL_SIZE_CLASS[size],
|
|
32
|
-
// Two written-out entries rather than one interpolated
|
|
33
|
-
// string, the same rule `SegmentedOption` follows:
|
|
34
|
-
// Tailwind's scanner cannot see a class that exists only
|
|
35
|
-
// at runtime, and `tailwindCandidates.test.ts` rejects a
|
|
36
|
-
// template literal in a className outright.
|
|
37
|
-
isCurrent
|
|
38
|
-
? "bg-intent-neutral-surface font-medium text-content-primary"
|
|
39
|
-
: "text-content-secondary hover:bg-intent-neutral-surface hover:text-content-primary", FOCUS_RING_CLASS)
|
|
40
|
-
: toClassName("flex w-full items-center rounded-sm text-start transition-colors duration-(--duration-fast) ease-standard", PANEL_ITEM_SIZE_CLASS[size],
|
|
41
|
-
// `-hover`, not the plain surface: the panel is drawn on
|
|
42
|
-
// `surface-overlay`, which every dark scheme paints
|
|
43
|
-
// *lighter* than the base tint, so the plain one reads as
|
|
44
|
-
// no change at all. Same correction `MenuAction` carries.
|
|
45
|
-
isCurrent
|
|
46
|
-
? "bg-intent-neutral-surface-hover font-medium text-content-primary"
|
|
47
|
-
: "text-content-primary hover:bg-intent-neutral-surface-hover", FOCUS_RING_CLASS);
|
|
48
|
-
return (_jsxs(LinkElement
|
|
49
|
-
// The platform's own "you are here", and the reason the paint
|
|
50
|
-
// above is never the only signal: a highlight is invisible to
|
|
51
|
-
// a screen reader and to anyone who cannot tell the two
|
|
52
|
-
// surfaces apart.
|
|
53
|
-
, { "aria-current": isCurrent ? "page" : undefined, className: className, href: item.href, onClick: (event) => {
|
|
54
|
-
if (event.defaultPrevented ||
|
|
55
|
-
event.button !== 0 ||
|
|
56
|
-
event.altKey ||
|
|
57
|
-
event.ctrlKey ||
|
|
58
|
-
event.metaKey ||
|
|
59
|
-
event.shiftKey) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
onNavigate?.();
|
|
63
|
-
}, ref: (element) => {
|
|
64
|
-
trackElement?.(item.key ?? item.href, element);
|
|
65
|
-
}, children: [item.icon === undefined ? null : (_jsx("span", { "aria-hidden": "true", className: "shrink-0 text-content-secondary", children: item.icon })), item.label] }));
|
|
66
|
-
};
|
|
67
|
-
//# sourceMappingURL=NavBarLink.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NavBarLink.js","sourceRoot":"","sources":["../../src/NavBar/NavBarLink.tsx"],"names":[],"mappings":";AAGA,OAAO,EACL,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAsC,CAAA;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA2B/C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,SAAS,EACT,IAAI,EACJ,UAAU,EACV,SAAS,EACT,IAAI,EACJ,YAAY,GACI,EAAa,EAAE;IAC/B,MAAM,UAAU,GAAG,aAAa,EAAE,CAAA;IAElC,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5C,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,GAAG,CAAA;IAEP,MAAM,SAAS,GACb,SAAS,KAAK,KAAK;QACjB,CAAC,CAAC,WAAW,CACT,qJAAqJ,EACrJ,kBAAkB,CAAC,IAAI,CAAC;QACxB,uDAAuD;QACvD,mDAAmD;QACnD,yDAAyD;QACzD,yDAAyD;QACzD,4CAA4C;QAC5C,SAAS;YACP,CAAC,CAAC,4DAA4D;YAC9D,CAAC,CAAC,mFAAmF,EACvF,gBAAgB,CACjB;QACH,CAAC,CAAC,WAAW,CACT,2GAA2G,EAC3G,qBAAqB,CAAC,IAAI,CAAC;QAC3B,yDAAyD;QACzD,oDAAoD;QACpD,0DAA0D;QAC1D,0DAA0D;QAC1D,SAAS;YACP,CAAC,CAAC,kEAAkE;YACpE,CAAC,CAAC,4DAA4D,EAChE,gBAAgB,CACjB,CAAA;IAEP,OAAO,CACL,MAAC,WAAW;IACV,8DAA8D;IAC9D,8DAA8D;IAC9D,wDAAwD;IACxD,kBAAkB;wBACJ,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAC5C,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,IAAI,CAAC,IAAI,EACf,OAAO,EAAE,CAAC,KAAoC,EAAE,EAAE;YAChD,IACE,KAAK,CAAC,gBAAgB;gBACtB,KAAK,CAAC,MAAM,KAAK,CAAC;gBAClB,KAAK,CAAC,MAAM;gBACZ,KAAK,CAAC,OAAO;gBACb,KAAK,CAAC,OAAO;gBACb,KAAK,CAAC,QAAQ,EACd,CAAC;gBACD,OAAM;YACR,CAAC;YAED,UAAU,EAAE,EAAE,CAAA;QAChB,CAAC,EACD,GAAG,EAAE,CAAC,OAAiC,EAAE,EAAE;YACzC,YAAY,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAChD,CAAC,aAEA,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAGhC,8BACc,MAAM,EAClB,SAAS,EAAC,iCAAiC,YAE1C,IAAI,CAAC,IAAI,GACL,CACR,EAEA,IAAI,CAAC,KAAK,IACC,CACf,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Which destination the reader is on — the rule behind
|
|
3
|
-
* `aria-current="page"`, kept pure so it can be checked in Node.
|
|
4
|
-
*
|
|
5
|
-
* The library is router-agnostic (see `RouterLinkProvider`), so it
|
|
6
|
-
* cannot ask a router which link is active. The app hands it one
|
|
7
|
-
* string — `useLocation().pathname`, `window.location.pathname`,
|
|
8
|
-
* whatever it has — and this decides the rest.
|
|
9
|
-
*
|
|
10
|
-
* ## A parent path is current for its children
|
|
11
|
-
*
|
|
12
|
-
* `/tasks` is current while the reader is on `/tasks/41`, because a
|
|
13
|
-
* nav item names a **section** and a section does not stop being the
|
|
14
|
-
* one you are in when you open something inside it. The match is on
|
|
15
|
-
* whole segments: `/task` is *not* current for `/tasks`, which a bare
|
|
16
|
-
* `startsWith` would get wrong.
|
|
17
|
-
*
|
|
18
|
-
* ## `/` is exact, and that is deliberately not react-router
|
|
19
|
-
*
|
|
20
|
-
* react-router's `<NavLink to="/">` is active on **every** route
|
|
21
|
-
* unless the caller remembers `end`, which is the footgun behind
|
|
22
|
-
* every "why is Home always highlighted" bug in the fleet. The root
|
|
23
|
-
* matches only itself here, and there is no prop to change it: a nav
|
|
24
|
-
* with two current items is a nav that has stopped saying anything.
|
|
25
|
-
*
|
|
26
|
-
* ## The query string and the fragment are not part of the answer
|
|
27
|
-
*
|
|
28
|
-
* `/backlog?focus=search` is still the Backlog. Sorting a list does
|
|
29
|
-
* not move the reader to a different destination.
|
|
30
|
-
*/
|
|
31
|
-
export declare const getIsCurrentHref: ({ currentHref, href, }: {
|
|
32
|
-
/** Where the reader is. `undefined` means "nothing is current". */
|
|
33
|
-
currentHref: string | undefined;
|
|
34
|
-
/** Where this destination goes. */
|
|
35
|
-
href: string;
|
|
36
|
-
}) => boolean;
|
|
37
|
-
//# sourceMappingURL=navBarCurrent.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"navBarCurrent.d.ts","sourceRoot":"","sources":["../../src/NavBar/navBarCurrent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAUH,eAAO,MAAM,gBAAgB,GAAI,wBAG9B;IACD,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAA;CACb,KAAG,OAgBH,CAAA"}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Which destination the reader is on — the rule behind
|
|
3
|
-
* `aria-current="page"`, kept pure so it can be checked in Node.
|
|
4
|
-
*
|
|
5
|
-
* The library is router-agnostic (see `RouterLinkProvider`), so it
|
|
6
|
-
* cannot ask a router which link is active. The app hands it one
|
|
7
|
-
* string — `useLocation().pathname`, `window.location.pathname`,
|
|
8
|
-
* whatever it has — and this decides the rest.
|
|
9
|
-
*
|
|
10
|
-
* ## A parent path is current for its children
|
|
11
|
-
*
|
|
12
|
-
* `/tasks` is current while the reader is on `/tasks/41`, because a
|
|
13
|
-
* nav item names a **section** and a section does not stop being the
|
|
14
|
-
* one you are in when you open something inside it. The match is on
|
|
15
|
-
* whole segments: `/task` is *not* current for `/tasks`, which a bare
|
|
16
|
-
* `startsWith` would get wrong.
|
|
17
|
-
*
|
|
18
|
-
* ## `/` is exact, and that is deliberately not react-router
|
|
19
|
-
*
|
|
20
|
-
* react-router's `<NavLink to="/">` is active on **every** route
|
|
21
|
-
* unless the caller remembers `end`, which is the footgun behind
|
|
22
|
-
* every "why is Home always highlighted" bug in the fleet. The root
|
|
23
|
-
* matches only itself here, and there is no prop to change it: a nav
|
|
24
|
-
* with two current items is a nav that has stopped saying anything.
|
|
25
|
-
*
|
|
26
|
-
* ## The query string and the fragment are not part of the answer
|
|
27
|
-
*
|
|
28
|
-
* `/backlog?focus=search` is still the Backlog. Sorting a list does
|
|
29
|
-
* not move the reader to a different destination.
|
|
30
|
-
*/
|
|
31
|
-
const toComparablePath = (href) => {
|
|
32
|
-
const path = href.split(/[#?]/u)[0] ?? "";
|
|
33
|
-
// A trailing slash is the same place, so it must not change the
|
|
34
|
-
// answer — but `/` itself has to survive being trimmed.
|
|
35
|
-
return path.length > 1 ? path.replace(/\/+$/u, "") : path;
|
|
36
|
-
};
|
|
37
|
-
export const getIsCurrentHref = ({ currentHref, href, }) => {
|
|
38
|
-
if (currentHref === undefined) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
const current = toComparablePath(currentHref);
|
|
42
|
-
const target = toComparablePath(href);
|
|
43
|
-
if (target === "" || target === "/") {
|
|
44
|
-
return current === target;
|
|
45
|
-
}
|
|
46
|
-
return (current === target || current.startsWith(`${target}/`));
|
|
47
|
-
};
|
|
48
|
-
//# sourceMappingURL=navBarCurrent.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"navBarCurrent.js","sourceRoot":"","sources":["../../src/NavBar/navBarCurrent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAU,EAAE;IAChD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAEzC,gEAAgE;IAChE,wDAAwD;IACxD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAC3D,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAC/B,WAAW,EACX,IAAI,GAML,EAAW,EAAE;IACZ,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,OAAO,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAA;IAE7C,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAErC,IAAI,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACpC,OAAO,OAAO,KAAK,MAAM,CAAA;IAC3B,CAAC;IAED,OAAO,CACL,OAAO,KAAK,MAAM,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CACvD,CAAA;AACH,CAAC,CAAA"}
|