@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.
Files changed (62) hide show
  1. package/dist/Board/Board.d.ts +6 -3
  2. package/dist/Board/Board.d.ts.map +1 -1
  3. package/dist/Board/Board.js +19 -1
  4. package/dist/Board/Board.js.map +1 -1
  5. package/dist/Board/BoardCard.d.ts +42 -7
  6. package/dist/Board/BoardCard.d.ts.map +1 -1
  7. package/dist/Board/BoardCard.js +54 -8
  8. package/dist/Board/BoardCard.js.map +1 -1
  9. package/dist/Nav/Nav.d.ts +167 -0
  10. package/dist/Nav/Nav.d.ts.map +1 -0
  11. package/dist/Nav/Nav.js +165 -0
  12. package/dist/Nav/Nav.js.map +1 -0
  13. package/dist/Nav/NavLink.d.ts +43 -0
  14. package/dist/Nav/NavLink.d.ts.map +1 -0
  15. package/dist/Nav/NavLink.js +111 -0
  16. package/dist/Nav/NavLink.js.map +1 -0
  17. package/dist/Nav/navItems.d.ts +67 -0
  18. package/dist/Nav/navItems.d.ts.map +1 -0
  19. package/dist/Nav/navItems.js +104 -0
  20. package/dist/Nav/navItems.js.map +1 -0
  21. package/dist/Nav/useNavLayout.d.ts +86 -0
  22. package/dist/Nav/useNavLayout.d.ts.map +1 -0
  23. package/dist/Nav/useNavLayout.js +114 -0
  24. package/dist/Nav/useNavLayout.js.map +1 -0
  25. package/dist/NavBar/NavBar.d.ts +37 -111
  26. package/dist/NavBar/NavBar.d.ts.map +1 -1
  27. package/dist/NavBar/NavBar.js +30 -110
  28. package/dist/NavBar/NavBar.js.map +1 -1
  29. package/dist/index.d.ts +6 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +3 -1
  32. package/dist/index.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/Board/Board.mdx +25 -2
  35. package/src/Board/Board.stories.tsx +52 -1
  36. package/src/Board/Board.test.tsx +82 -0
  37. package/src/Board/Board.tsx +24 -4
  38. package/src/Board/BoardCard.tsx +103 -21
  39. package/src/Board/boardMoveHandleContainer.test.ts +54 -0
  40. package/src/Nav/Nav.mdx +169 -0
  41. package/src/Nav/Nav.stories.tsx +402 -0
  42. package/src/Nav/Nav.test.tsx +338 -0
  43. package/src/Nav/Nav.tsx +406 -0
  44. package/src/Nav/NavLink.tsx +220 -0
  45. package/src/Nav/navItems.test.ts +163 -0
  46. package/src/Nav/navItems.ts +175 -0
  47. package/src/Nav/useNavLayout.ts +197 -0
  48. package/src/NavBar/NavBar.mdx +26 -0
  49. package/src/NavBar/NavBar.tsx +54 -240
  50. package/src/index.ts +20 -1
  51. package/src/sourceRules.test.ts +13 -1
  52. package/dist/NavBar/NavBarLink.d.ts +0 -43
  53. package/dist/NavBar/NavBarLink.d.ts.map +0 -1
  54. package/dist/NavBar/NavBarLink.js +0 -67
  55. package/dist/NavBar/NavBarLink.js.map +0 -1
  56. package/dist/NavBar/navBarCurrent.d.ts +0 -37
  57. package/dist/NavBar/navBarCurrent.d.ts.map +0 -1
  58. package/dist/NavBar/navBarCurrent.js +0 -48
  59. package/dist/NavBar/navBarCurrent.js.map +0 -1
  60. package/src/NavBar/NavBarLink.tsx +0 -139
  61. package/src/NavBar/navBarCurrent.test.ts +0 -81
  62. package/src/NavBar/navBarCurrent.ts +0 -64
@@ -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 { Button } from "../Button/Button.tsx"
7
- import { IconButton } from "../IconButton/IconButton.tsx"
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
- export type NavBarItem = {
16
- /**
17
- * Where it goes. Routed through the injected `RouterLink` when it
18
- * is this app's own path, and left to the platform when it is not.
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
- * The app's destinations as real links, in one line — folding into a
85
- * single menu button, whole, when that line does not fit.
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
- * `Toolbar` collapses **progressively** — items drop off the end one
104
- * at a time — because its items are ranked: the two things you do to
105
- * a running job matter more than the two you do to the deck. A nav
106
- * is not ranked. Its order is the shape of the product (Docket's
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
- * ## Measured, not breakpointed
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
- * There is no `collapseAt` prop and no media query. The bar reads
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
- * ⚠️ **The bar must not be sized by its own contents.** It measures
126
- * its own box to decide what to draw in it, so a container that
127
- * shrink-wraps makes the answer its own input: put it in a
128
- * `minmax(0, 1fr)` grid track or a `flex-1 min-w-0` row. The root
129
- * carries `min-w-0 flex-1` for the flex case; a grid track is the
130
- * caller's to declare. Same trap `useAdaptiveColumns` documents from
131
- * the other end.
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 = false,
63
+ isMenuVisible,
146
64
  items,
147
65
  label,
148
- menuAlign = "start",
66
+ menuAlign,
149
67
  menuIcon,
150
- menuLabel = "Menu",
151
- menuPlacement = "bottom-start",
152
- size = "sm",
153
- }: NavBarProps): ReactNode => {
154
- const itemKeys = items.map(
155
- (item) => item.key ?? item.href,
156
- )
157
-
158
- const { containerRef, trackItem, visibleCount } =
159
- useToolbarOverflow({ itemKeys })
160
-
161
- /**
162
- * All or nothing. `visibleCount` is the progressive answer and
163
- * this is the only thing read off it: the moment one link stops
164
- * fitting, every link goes into the menu together.
165
- *
166
- * The hook's `trackTrigger` is deliberately not wired up, and the
167
- * missing measurement costs nothing here. It exists so a
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,
@@ -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
- expect(componentNames.length).toBe(59)
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"}