@nanisoft/prism-ui 1.2.0 → 1.3.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 (29) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/blocks/site-footer/site-footer.d.ts +23 -8
  3. package/dist/blocks/site-footer/site-footer.d.ts.map +1 -1
  4. package/dist/blocks/site-footer/site-footer.js +10 -4
  5. package/dist/blocks/site-navbar/index.d.ts +1 -1
  6. package/dist/blocks/site-navbar/index.d.ts.map +1 -1
  7. package/dist/blocks/site-navbar/mobile-nav.d.ts +25 -4
  8. package/dist/blocks/site-navbar/mobile-nav.d.ts.map +1 -1
  9. package/dist/blocks/site-navbar/mobile-nav.js +11 -4
  10. package/dist/blocks/site-navbar/nav-menu.d.ts +50 -0
  11. package/dist/blocks/site-navbar/nav-menu.d.ts.map +1 -0
  12. package/dist/blocks/site-navbar/nav-menu.js +44 -0
  13. package/dist/blocks/site-navbar/navbar-controls.d.ts +9 -3
  14. package/dist/blocks/site-navbar/navbar-controls.d.ts.map +1 -1
  15. package/dist/blocks/site-navbar/site-navbar.d.ts +54 -13
  16. package/dist/blocks/site-navbar/site-navbar.d.ts.map +1 -1
  17. package/dist/blocks/site-navbar/site-navbar.js +45 -10
  18. package/dist/components/ui/calendar.d.ts.map +1 -1
  19. package/dist/components/ui/calendar.js +7 -2
  20. package/dist/components/ui/mini-calendar.js +1 -1
  21. package/dist/components/ui/product-mark.d.ts +26 -7
  22. package/dist/components/ui/product-mark.d.ts.map +1 -1
  23. package/dist/components/ui/product-mark.js +6 -4
  24. package/dist/components/ui/pulse-series.d.ts.map +1 -1
  25. package/dist/components/ui/pulse-series.js +1 -1
  26. package/dist/components/ui/sparkline.d.ts.map +1 -1
  27. package/dist/components/ui/sparkline.js +1 -2
  28. package/dist/styles.css +13 -0
  29. package/package.json +1 -1
@@ -66,12 +66,26 @@ export type SiteFooterSocial = {
66
66
  */
67
67
  export type SiteFooterProps = {
68
68
  /**
69
- * The product this footer belongs to: the id, name and pack its `ProductMark`
70
- * is drawn from. The footer renders the mark rather than taking a slot,
71
- * because the brand lockup is the one part of a footer that is never
72
- * product-specific and every consumer would otherwise reimplement it.
69
+ * The product this footer belongs to: the id, name, pack and drawn mark its
70
+ * `ProductMark` is drawn from. The footer renders the mark rather than taking
71
+ * a slot, because the brand lockup is the one part of a footer that is never
72
+ * product-specific and every consumer would otherwise reimplement it; a
73
+ * product whose identity is a drawing carries it here as data, beside the
74
+ * name, exactly as the bar's lockup does.
73
75
  */
74
- product: Pick<ProductMarkProps, 'id' | 'name' | 'pack'>;
76
+ product: Pick<ProductMarkProps, 'id' | 'name' | 'pack' | 'mark'>;
77
+ /**
78
+ * A slot for the sentence or two a footer carries under its mark: what this
79
+ * site is, who publishes it, what it is built with.
80
+ *
81
+ * A slot rather than a string, because the same position holds one paragraph
82
+ * on one site and two labelled ones on another, and any headings are the
83
+ * caller's document outline rather than this Block's to level. It renders
84
+ * under the mark at the measure, with no colour of its own: muted text is the
85
+ * caller's decision about its own copy, and a Block that coloured a node it
86
+ * did not render would be styling it could not see.
87
+ */
88
+ about?: ReactNode;
75
89
  /**
76
90
  * The groups of destinations: the sections of this site, its documentation, and
77
91
  * anywhere else the site sends a reader. Omit it for a footer that carries only
@@ -109,8 +123,9 @@ export type SiteFooterProps = {
109
123
  className?: string;
110
124
  };
111
125
  /**
112
- * The footer of a product site: a brand lockup, grouped destinations, the links
113
- * that leave the site, and an optional slot for the legal line.
126
+ * The footer of a product site: a brand lockup, an optional word from the site
127
+ * under it, grouped destinations, the links that leave the site, and an optional
128
+ * slot for the legal line.
114
129
  *
115
130
  * It is a Block and not a Component because it is a region of a page: it
116
131
  * composes a `ProductMark` and two sets of links and owns the block they sit in.
@@ -163,6 +178,6 @@ export type SiteFooterProps = {
163
178
  * imports no router: a consumer renders it from a server file and the links are
164
179
  * plain anchors the consumer's own router can intercept.
165
180
  */
166
- export declare function SiteFooter({ product, columns, social, legal, headingLevel, className, }: SiteFooterProps): import("react").JSX.Element;
181
+ export declare function SiteFooter({ product, about, columns, social, legal, headingLevel, className, }: SiteFooterProps): import("react").JSX.Element;
167
182
  export default SiteFooter;
168
183
  //# sourceMappingURL=site-footer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"site-footer.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-footer/site-footer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AACrF,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAG/D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;CACjC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;OAKG;IACH,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC,CAAA;IACvD;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACrC;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACpC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,YAAmB,EACnB,SAAS,GACV,EAAE,eAAe,+BA+DjB;AAED,eAAe,UAAU,CAAA"}
1
+ {"version":3,"file":"site-footer.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-footer/site-footer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AACrF,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAG/D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;CACjC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;;;OAOG;IACH,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,CAAA;IAChE;;;;;;;;;;OAUG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACrC;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACpC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,KAAK,EACL,OAAO,EACP,MAAM,EACN,KAAK,EACL,YAAmB,EACnB,SAAS,GACV,EAAE,eAAe,+BA+EjB;AAED,eAAe,UAAU,CAAA"}
@@ -2,8 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ProductMark } from '../../components/ui/product-mark.js';
3
3
  import { cn } from '../../lib/utils.js';
4
4
  /**
5
- * The footer of a product site: a brand lockup, grouped destinations, the links
6
- * that leave the site, and an optional slot for the legal line.
5
+ * The footer of a product site: a brand lockup, an optional word from the site
6
+ * under it, grouped destinations, the links that leave the site, and an optional
7
+ * slot for the legal line.
7
8
  *
8
9
  * It is a Block and not a Component because it is a region of a page: it
9
10
  * composes a `ProductMark` and two sets of links and owns the block they sit in.
@@ -56,8 +57,13 @@ import { cn } from '../../lib/utils.js';
56
57
  * imports no router: a consumer renders it from a server file and the links are
57
58
  * plain anchors the consumer's own router can intercept.
58
59
  */
59
- export function SiteFooter({ product, columns, social, legal, headingLevel = 'h2', className, }) {
60
+ export function SiteFooter({ product, about, columns, social, legal, headingLevel = 'h2', className, }) {
61
+ /*
62
+ * The lockup is the same `ProductMark` the bar draws, so a product's drawn
63
+ * mark, when it has one, is the one drawing in both places and a reader who
64
+ * scrolled from the bar meets the same sign at the foot.
65
+ */
60
66
  const ColumnTitle = headingLevel;
61
- return (_jsx("footer", { "data-slot": "site-footer", className: cn('border-border bg-background w-full border-t', className), children: _jsxs("div", { className: "mx-auto flex w-full max-w-page flex-col gap-10 px-6 py-12 lg:px-8", children: [_jsxs("div", { className: "flex flex-col gap-10 md:flex-row md:justify-between", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(ProductMark, { id: product.id, name: product.name, pack: product.pack, size: "lg" }), social && social.length > 0 ? (_jsx("ul", { className: "flex flex-wrap items-center gap-4", children: social.map((link) => (_jsx("li", { children: _jsxs("a", { href: link.href, rel: link.newTab === false ? undefined : 'noopener noreferrer', target: link.newTab === false ? undefined : '_blank', className: "text-muted-foreground hover:text-foreground flex min-h-6 items-center gap-2 rounded-sm text-sm font-medium transition-colors duration-fast ease-out", children: [link.icon ? (_jsx("span", { "aria-hidden": true, className: "inline-flex items-center", children: link.icon })) : null, link.label] }) }, link.href))) })) : null] }), columns && columns.length > 0 ? (_jsx("div", { className: "grid gap-8 sm:grid-cols-2 lg:grid-cols-3", children: columns.map((column) => (_jsxs("nav", { "aria-label": column.title, className: "flex flex-col gap-3", children: [_jsx(ColumnTitle, { className: "text-sm font-semibold", children: column.title }), _jsx("ul", { className: "flex flex-col gap-2", children: column.links.map((link) => (_jsx("li", { children: _jsx("a", { href: link.href, rel: link.newTab ? 'noopener noreferrer' : undefined, target: link.newTab ? '_blank' : undefined, className: "text-muted-foreground hover:text-foreground flex min-h-6 items-center rounded-sm text-sm transition-colors duration-fast ease-out", children: link.label }) }, link.href))) })] }, column.title))) })) : null] }), legal ? _jsx("div", { className: "text-muted-foreground text-sm", children: legal }) : null] }) }));
67
+ return (_jsx("footer", { "data-slot": "site-footer", className: cn('border-border bg-background w-full border-t', className), children: _jsxs("div", { className: "mx-auto flex w-full max-w-page flex-col gap-10 px-6 py-12 lg:px-8", children: [_jsxs("div", { className: "flex flex-col gap-10 md:flex-row md:justify-between", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(ProductMark, { id: product.id, name: product.name, pack: product.pack, mark: product.mark, size: "lg" }), about ? (_jsx("div", { "data-slot": "site-footer-about", className: "max-w-measure flex flex-col gap-2", children: about })) : null, social && social.length > 0 ? (_jsx("ul", { className: "flex flex-wrap items-center gap-4", children: social.map((link) => (_jsx("li", { children: _jsxs("a", { href: link.href, rel: link.newTab === false ? undefined : 'noopener noreferrer', target: link.newTab === false ? undefined : '_blank', className: "text-muted-foreground hover:text-foreground flex min-h-6 items-center gap-2 rounded-sm text-sm font-medium transition-colors duration-fast ease-out", children: [link.icon ? (_jsx("span", { "aria-hidden": true, className: "inline-flex items-center", children: link.icon })) : null, link.label] }) }, link.href))) })) : null] }), columns && columns.length > 0 ? (_jsx("div", { className: "grid gap-8 sm:grid-cols-2 lg:grid-cols-3", children: columns.map((column) => (_jsxs("nav", { "aria-label": column.title, className: "flex flex-col gap-3", children: [_jsx(ColumnTitle, { className: "text-sm font-semibold", children: column.title }), _jsx("ul", { className: "flex flex-col gap-2", children: column.links.map((link) => (_jsx("li", { children: _jsx("a", { href: link.href, rel: link.newTab ? 'noopener noreferrer' : undefined, target: link.newTab ? '_blank' : undefined, className: "text-muted-foreground hover:text-foreground flex min-h-6 items-center rounded-sm text-sm transition-colors duration-fast ease-out", children: link.label }) }, link.href))) })] }, column.title))) })) : null] }), legal ? _jsx("div", { className: "text-muted-foreground text-sm", children: legal }) : null] }) }));
62
68
  }
63
69
  export default SiteFooter;
@@ -1,3 +1,3 @@
1
1
  export { SiteNavbar, default } from './site-navbar.js';
2
- export type { SiteNavbarProps, SiteNavLink, SiteNavSearch, SiteNavTheme, SiteNavMode, SiteNavMobileLabels, } from './site-navbar.js';
2
+ export type { SiteNavbarProps, SiteNavLink, SiteNavGroup, SiteNavEntry, SiteNavSearch, SiteNavTheme, SiteNavMode, SiteNavMobileLabels, } from './site-navbar.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-navbar/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACnD,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,YAAY,EACZ,WAAW,EACX,mBAAmB,GACpB,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-navbar/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACnD,YAAY,EACV,eAAe,EACf,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,mBAAmB,GACpB,MAAM,eAAe,CAAA"}
@@ -5,15 +5,33 @@ export type MobileNavLink = {
5
5
  label: string;
6
6
  /** Where the link goes. */
7
7
  href: string;
8
- /** Marks this link as the page the reader is on. */
8
+ /** Marks the link as the page the reader is on. */
9
9
  current?: boolean;
10
10
  };
11
+ /**
12
+ * A group of destinations in the mobile panel, as a label above its links.
13
+ *
14
+ * The row renders a group as a menu because a row is short of room and a group
15
+ * is one control there; this panel is not short of room, so the same group is a
16
+ * named run of the list rather than a second thing to open. A reader below the
17
+ * row's threshold therefore meets every destination flat: no route is behind
18
+ * two disclosures, and the group's name still tells them which destinations
19
+ * belong together.
20
+ */
21
+ export type MobileNavGroup = {
22
+ /** The group's own name, which is a label and not a link. */
23
+ label: string;
24
+ /** The destinations under the label, in the order a reader should meet them. */
25
+ children: readonly MobileNavLink[];
26
+ };
27
+ /** One entry in the mobile panel: a link, or a group of them. */
28
+ export type MobileNavEntry = MobileNavLink | MobileNavGroup;
11
29
  /** The props the mobile navigation takes. */
12
30
  export type MobileNavProps = {
13
31
  /** The product this navigation belongs to, drawn as the mark above the list. */
14
- product: Pick<ProductMarkProps, 'id' | 'name' | 'pack'>;
32
+ product: Pick<ProductMarkProps, 'id' | 'name' | 'pack' | 'mark'>;
15
33
  /** Every destination, in the order a reader should meet them. */
16
- links: readonly MobileNavLink[];
34
+ links: readonly MobileNavEntry[];
17
35
  /** The accessible name of the panel, which is also the trigger's name. */
18
36
  label: string;
19
37
  /** The word on the trigger while the panel is closed. */
@@ -42,7 +60,10 @@ export type MobileNavProps = {
42
60
  *
43
61
  * The links are full width with a 44 pixel minimum height, because below the row's
44
62
  * threshold this is a touch target and not a line of text, and the coarse-pointer
45
- * floor in the design system asks for exactly that.
63
+ * floor in the design system asks for exactly that. A group's label is a
64
+ * `role="group"` named by the label, so a reader who navigates the panel by
65
+ * landmark or by list is told which links belong together rather than meeting a
66
+ * longer list with a break in it.
46
67
  */
47
68
  export declare function MobileNav({ product, links, label, openLabel, closeLabel, }: MobileNavProps): import("react").JSX.Element;
48
69
  //# sourceMappingURL=mobile-nav.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-nav.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-navbar/mobile-nav.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AAExE,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG;IAC1B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,6CAA6C;AAC7C,MAAM,MAAM,cAAc,GAAG;IAC3B,gFAAgF;IAChF,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC,CAAA;IACvD,iEAAiE;IACjE,KAAK,EAAE,SAAS,aAAa,EAAE,CAAA;IAC/B,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,KAAK,EACL,KAAK,EACL,SAAS,EACT,UAAU,GACX,EAAE,cAAc,+BAsChB"}
1
+ {"version":3,"file":"mobile-nav.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-navbar/mobile-nav.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AAExE,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG;IAC1B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,mDAAmD;IACnD,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb,gFAAgF;IAChF,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAA;CACnC,CAAA;AAED,iEAAiE;AACjE,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,cAAc,CAAA;AAE3D,6CAA6C;AAC7C,MAAM,MAAM,cAAc,GAAG;IAC3B,gFAAgF;IAChF,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,CAAA;IAChE,iEAAiE;IACjE,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;IAChC,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,KAAK,EACL,KAAK,EACL,SAAS,EACT,UAAU,GACX,EAAE,cAAc,+BA0DhB"}
@@ -25,11 +25,18 @@ import { ProductMark } from '../../components/ui/product-mark.js';
25
25
  *
26
26
  * The links are full width with a 44 pixel minimum height, because below the row's
27
27
  * threshold this is a touch target and not a line of text, and the coarse-pointer
28
- * floor in the design system asks for exactly that.
28
+ * floor in the design system asks for exactly that. A group's label is a
29
+ * `role="group"` named by the label, so a reader who navigates the panel by
30
+ * landmark or by list is told which links belong together rather than meeting a
31
+ * longer list with a break in it.
29
32
  */
30
33
  export function MobileNav({ product, links, label, openLabel, closeLabel, }) {
31
34
  const [open, setOpen] = useState(false);
32
- return (_jsxs(Sheet, { open: open, onOpenChange: setOpen, children: [_jsx(SheetTrigger, { "data-slot": "site-navbar-mobile-trigger", "aria-label": open ? closeLabel : openLabel, className: "border-border bg-card text-foreground hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring flex size-11 shrink-0 items-center justify-center rounded-full border transition-colors focus-visible:ring-[3px] focus-visible:outline-none lg:hidden", children: open ? _jsx(X, { "aria-hidden": true, className: "size-5" }) : _jsx(Menu, { "aria-hidden": true, className: "size-5" }) }), _jsxs(SheetContent, { side: "right", className: "w-80", children: [_jsx(SheetTitle, { className: "sr-only", children: label }), _jsxs("nav", { "aria-label": label, className: "flex flex-col gap-1", children: [_jsx("div", { className: "mb-4 flex items-center", children: _jsx(ProductMark, { id: product.id, name: product.name, pack: product.pack, size: "md" }) }), links.map((link) => (_jsx("a", { href: link.href, "aria-current": link.current ? 'page' : undefined, onClick: () => setOpen(false), className: link.current
33
- ? 'bg-accent text-accent-foreground focus-visible:border-ring focus-visible:ring-ring flex min-h-11 items-center rounded-lg px-3 text-sm font-medium transition-colors focus-visible:ring-[3px] focus-visible:outline-none'
34
- : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring flex min-h-11 items-center rounded-lg px-3 text-sm font-medium transition-colors focus-visible:ring-[3px] focus-visible:outline-none', children: link.label }, link.href)))] })] })] }));
35
+ return (_jsxs(Sheet, { open: open, onOpenChange: setOpen, children: [_jsx(SheetTrigger, { "data-slot": "site-navbar-mobile-trigger", "aria-label": open ? closeLabel : openLabel, className: "border-border bg-card text-foreground hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring flex size-11 shrink-0 items-center justify-center rounded-full border transition-colors focus-visible:ring-[3px] focus-visible:outline-none lg:hidden", children: open ? _jsx(X, { "aria-hidden": true, className: "size-5" }) : _jsx(Menu, { "aria-hidden": true, className: "size-5" }) }), _jsxs(SheetContent, { side: "right", className: "w-80", children: [_jsx(SheetTitle, { className: "sr-only", children: label }), _jsxs("nav", { "aria-label": label, className: "flex flex-col gap-1", children: [_jsx("div", { className: "mb-4 flex items-center", children: _jsx(ProductMark, { id: product.id, name: product.name, pack: product.pack, mark: product.mark, size: "md" }) }), links.map((entry) => 'children' in entry ? (_jsxs("div", { role: "group", "aria-label": entry.label, className: "mt-5 flex flex-col gap-1", children: [_jsx("span", { className: "text-muted-foreground px-3 pb-2 text-sm font-semibold tracking-tight", children: entry.label }), entry.children.map((link) => (_jsx(PanelLink, { link: link, onNavigate: () => setOpen(false) }, link.href)))] }, entry.label)) : (_jsx(PanelLink, { link: entry, onNavigate: () => setOpen(false) }, entry.href)))] })] })] }));
36
+ }
37
+ /** One link in the panel, extracted because the group and the root render the same one. */
38
+ function PanelLink({ link, onNavigate }) {
39
+ return (_jsx("a", { href: link.href, "aria-current": link.current ? 'page' : undefined, onClick: onNavigate, className: link.current
40
+ ? 'bg-accent text-accent-foreground focus-visible:border-ring focus-visible:ring-ring flex min-h-11 items-center rounded-lg px-3 text-sm font-medium transition-colors focus-visible:ring-[3px] focus-visible:outline-none'
41
+ : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring flex min-h-11 items-center rounded-lg px-3 text-sm font-medium transition-colors focus-visible:ring-[3px] focus-visible:outline-none', children: link.label }));
35
42
  }
@@ -0,0 +1,50 @@
1
+ import type { SiteNavLink } from './site-navbar.js';
2
+ /** One destination inside the menu, with the current flag already resolved. */
3
+ export type NavMenuLink = SiteNavLink & {
4
+ /** Marks this destination as the page the reader is on. */
5
+ current: boolean;
6
+ };
7
+ /** The props the navigation menu takes. */
8
+ export type NavMenuProps = {
9
+ /** The word on the trigger, which is the group's own name. */
10
+ label: string;
11
+ /** The group's destinations, in the order a reader should meet them. */
12
+ links: readonly NavMenuLink[];
13
+ /**
14
+ * Marks the group as holding the page the reader is on, which the bar
15
+ * resolves against its own `currentPath` before it crosses the client
16
+ * boundary. The trigger is filled rather than merely tinted, so a reader who
17
+ * cannot distinguish the fill still finds the page named inside the menu.
18
+ */
19
+ current: boolean;
20
+ };
21
+ /**
22
+ * One group of the site's own destinations, as a menu.
23
+ *
24
+ * A bar whose navigation is a row of links clubs into groups the moment a site
25
+ * outgrows the row: five destinations that are all "the parts of the library"
26
+ * beside four that are each a body of knowledge is a row a reader scans
27
+ * word by word rather than recognises at a glance. This is the second half of
28
+ * that row, and it is the one control in the row, because a group is not a
29
+ * destination: it is a place other destinations live behind.
30
+ *
31
+ * **The items are links, not commands, for the reason the sites menu states.**
32
+ * A menu item that navigated would be a command wearing a link's clothes, and
33
+ * a reader who middle-clicks a row that only looks like a link gets no new tab.
34
+ * Each item is rendered as the anchor itself rather than wrapping one, so the
35
+ * row is one tab stop and one target.
36
+ *
37
+ * **The menu is a closed group, and a closed group shows nothing.** A reader
38
+ * with scripting off sees the trigger and cannot open it, which is why the bar
39
+ * renders a group as a menu only on the row above the panel threshold: below
40
+ * it, the sheet lists every destination flat, and the bar's caller keeps
41
+ * whatever routes it cannot afford to hide out of a group. The trigger is
42
+ * filled when the reader is inside the group, so the place they are is on the
43
+ * row even with the menu shut.
44
+ *
45
+ * **The chevron rotates on the open state and on nothing else.** Something
46
+ * happened and the reader can see that it did, at the authored duration, and
47
+ * the rotation is the only motion here.
48
+ */
49
+ export declare function NavMenu({ label, links, current }: NavMenuProps): import("react").JSX.Element;
50
+ //# sourceMappingURL=nav-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav-menu.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-navbar/nav-menu.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG;IACtC,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,2CAA2C;AAC3C,MAAM,MAAM,YAAY,GAAG;IACzB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAA;IACb,wEAAwE;IACxE,KAAK,EAAE,SAAS,WAAW,EAAE,CAAA;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,YAAY,+BA6C9D"}
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ChevronDown } from 'lucide-react';
4
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '../../components/ui/dropdown-menu.js';
5
+ /**
6
+ * One group of the site's own destinations, as a menu.
7
+ *
8
+ * A bar whose navigation is a row of links clubs into groups the moment a site
9
+ * outgrows the row: five destinations that are all "the parts of the library"
10
+ * beside four that are each a body of knowledge is a row a reader scans
11
+ * word by word rather than recognises at a glance. This is the second half of
12
+ * that row, and it is the one control in the row, because a group is not a
13
+ * destination: it is a place other destinations live behind.
14
+ *
15
+ * **The items are links, not commands, for the reason the sites menu states.**
16
+ * A menu item that navigated would be a command wearing a link's clothes, and
17
+ * a reader who middle-clicks a row that only looks like a link gets no new tab.
18
+ * Each item is rendered as the anchor itself rather than wrapping one, so the
19
+ * row is one tab stop and one target.
20
+ *
21
+ * **The menu is a closed group, and a closed group shows nothing.** A reader
22
+ * with scripting off sees the trigger and cannot open it, which is why the bar
23
+ * renders a group as a menu only on the row above the panel threshold: below
24
+ * it, the sheet lists every destination flat, and the bar's caller keeps
25
+ * whatever routes it cannot afford to hide out of a group. The trigger is
26
+ * filled when the reader is inside the group, so the place they are is on the
27
+ * row even with the menu shut.
28
+ *
29
+ * **The chevron rotates on the open state and on nothing else.** Something
30
+ * happened and the reader can see that it did, at the authored duration, and
31
+ * the rotation is the only motion here.
32
+ */
33
+ export function NavMenu({ label, links, current }) {
34
+ return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { "data-slot": "site-navbar-nav-trigger", "data-current": current ? 'true' : undefined, className: current
35
+ ? 'group data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground bg-accent text-accent-foreground focus-visible:border-ring focus-visible:ring-ring gap-1 rounded-md px-2 py-1.5 text-sm font-medium transition-colors focus-visible:ring-[3px] focus-visible:outline-none'
36
+ : 'group data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring gap-1 rounded-md px-2 py-1.5 text-sm font-medium transition-colors focus-visible:ring-[3px] focus-visible:outline-none', children: [label, _jsx(ChevronDown, { "aria-hidden": true, className: "group-data-[popup-open]:rotate-180 size-4 transition-transform duration-base ease-out" })] }), _jsx(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-44", children: links.map((link) => (_jsx(DropdownMenuItem, {
37
+ /*
38
+ * Rendered AS the link rather than wrapping one. A focusable menu
39
+ * item with an anchor inside it is two tab stops for one row, and a
40
+ * reader who middle-clicks a row that only looks like a link gets no
41
+ * new tab and no way to tell that the row was a link at all.
42
+ */
43
+ render: _jsx("a", { href: link.href, target: link.newTab ? '_blank' : undefined, rel: link.newTab ? 'noopener noreferrer' : undefined, "aria-current": link.current ? 'page' : undefined, "data-current": link.current ? 'true' : undefined }), className: "data-[current]:bg-accent data-[current]:text-accent-foreground data-[current]:font-medium", children: link.label }, link.href))) })] }));
44
+ }
@@ -1,18 +1,24 @@
1
+ import type { ReactNode } from 'react';
1
2
  import type { Mode, PackId } from '../../theming/index.js';
2
3
  import type { SearchDialogMessages } from '../../components/ui/search-dialog.js';
3
4
  import { type ThemePack } from './theme-menu.js';
4
5
  import type { SwitcherProduct } from '../../components/ui/product-switcher.js';
5
- import type { MobileNavLink } from './mobile-nav.js';
6
+ import type { MobileNavEntry } from './mobile-nav.js';
6
7
  /** What the control cluster was told to render. Every field is optional. */
7
8
  export type NavbarControlsProps = {
8
- /** The product whose mark heads the mobile panel. */
9
+ /**
10
+ * The product whose mark heads the mobile panel. The drawn `mark` crosses
11
+ * this client boundary as rendered content, the same way a slot does, so a
12
+ * consumer's drawing stays server-drawn while the panel around it is not.
13
+ */
9
14
  product: {
10
15
  id: string;
11
16
  name: string;
12
17
  pack?: PackId | null;
18
+ mark?: ReactNode;
13
19
  };
14
20
  /** The site's own destinations, for the panel below the row's threshold. */
15
- nav: readonly MobileNavLink[];
21
+ nav: readonly MobileNavEntry[];
16
22
  /** The accessible name of the panel and of its trigger. */
17
23
  navLabel: string;
18
24
  /** The set of sites, or nothing for a site that is the only one. */
@@ -1 +1 @@
1
- {"version":3,"file":"navbar-controls.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-navbar/navbar-controls.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAA;AAI7E,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAA;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAIjD,4EAA4E;AAC5E,MAAM,MAAM,mBAAmB,GAAG;IAChC,qDAAqD;IACrD,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;IAC3D,4EAA4E;IAC5E,GAAG,EAAE,SAAS,aAAa,EAAE,CAAA;IAC7B,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IAClC,6CAA6C;IAC7C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uEAAuE;IACvE,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,QAAQ,EAAE,oBAAoB,CAAA;KAC/B,CAAA;IACD,+DAA+D;IAC/D,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;KAAE,CAAA;IACvD,6EAA6E;IAC7E,IAAI,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAChD,2CAA2C;IAC3C,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,oDAAoD;IACpD,WAAW,EAAE,IAAI,CAAA;IACjB,mEAAmE;IACnE,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,sCAsDxD"}
1
+ {"version":3,"file":"navbar-controls.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-navbar/navbar-controls.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAA;AAI7E,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAA;AAC3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAIlD,4EAA4E;AAC5E,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;OAIG;IACH,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,SAAS,CAAA;KAAE,CAAA;IAC7E,4EAA4E;IAC5E,GAAG,EAAE,SAAS,cAAc,EAAE,CAAA;IAC9B,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IAClC,6CAA6C;IAC7C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uEAAuE;IACvE,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,QAAQ,EAAE,oBAAoB,CAAA;KAC/B,CAAA;IACD,+DAA+D;IAC/D,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;KAAE,CAAA;IACvD,6EAA6E;IAC7E,IAAI,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAChD,2CAA2C;IAC3C,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,oDAAoD;IACpD,WAAW,EAAE,IAAI,CAAA;IACjB,mEAAmE;IACnE,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,sCAsDxD"}
@@ -31,7 +31,41 @@ export type SiteNavLink = {
31
31
  * external: a cross-origin destination is not by itself a reason to open a tab.
32
32
  */
33
33
  newTab?: boolean;
34
+ /** Forbidden: a destination carrying children is a `SiteNavGroup`, not a link. */
35
+ children?: never;
34
36
  };
37
+ /**
38
+ * A group of destinations in the bar's own navigation, rendered as one trigger
39
+ * that opens a menu of them.
40
+ *
41
+ * The group is a place in the row rather than a route: it carries no `href`, and
42
+ * a caller whose group has an index page of its own lists that page among the
43
+ * `children` rather than on the group, so nothing is inventing a route. That is
44
+ * the same shape the site's own sidebar holds a folder to, and for the same
45
+ * reason: a group is where destinations live, not one.
46
+ *
47
+ * **A group hides its destinations until it is opened**, on the row above the
48
+ * panel threshold and nowhere else. The bar's narrow-viewport panel lists every
49
+ * destination flat, so a reader below the threshold meets no group at all, and a
50
+ * caller who cannot afford to hide a route behind a click leaves it out of a
51
+ * group: the search index, the sitemap and the page's own links are outside the
52
+ * bar and unaffected either way.
53
+ */
54
+ export type SiteNavGroup = {
55
+ /** The word on the trigger, which is the group's own name. */
56
+ label: string;
57
+ /** The destinations behind the trigger, in the order a reader should meet them. */
58
+ children: readonly SiteNavLink[];
59
+ /**
60
+ * Marks the group as holding the page the reader is on, for a caller that
61
+ * composes the bar per page and marks each entry itself. A caller that passes
62
+ * `currentPath` leaves this off and the bar resolves it against the group's
63
+ * children.
64
+ */
65
+ current?: boolean;
66
+ };
67
+ /** One entry in the bar's own navigation: a link, or a group of them. */
68
+ export type SiteNavEntry = SiteNavLink | SiteNavGroup;
35
69
  /** The search the bar offers, which is a URL rather than an index. */
36
70
  export type SiteNavSearch = {
37
71
  /**
@@ -79,12 +113,12 @@ export type SiteNavMobileLabels = {
79
113
  /** The props a SiteNavbar takes. */
80
114
  export type SiteNavbarProps = {
81
115
  /**
82
- * The product this bar belongs to: the id, name and pack its `ProductMark` is
83
- * drawn from, and the mark that heads the mobile panel. The bar renders the mark
84
- * itself rather than a slot, because a bar whose brand lockup is a slot is a bar
85
- * every consumer has to reimplement.
116
+ * The product this bar belongs to: the id, name, pack and drawn mark its
117
+ * `ProductMark` is drawn from, and the mark that heads the mobile panel. The
118
+ * bar renders the mark itself rather than a slot, because a bar whose brand
119
+ * lockup is a slot is a bar every consumer has to reimplement.
86
120
  */
87
- product: Pick<ProductMarkProps, 'id' | 'name' | 'pack'>;
121
+ product: Pick<ProductMarkProps, 'id' | 'name' | 'pack' | 'mark'>;
88
122
  /** Where the brand lockup goes. @defaultValue '/' */
89
123
  homeHref?: string;
90
124
  /**
@@ -99,8 +133,13 @@ export type SiteNavbarProps = {
99
133
  * control whose icon is a guess until it is pressed once.
100
134
  */
101
135
  defaultMode: Mode;
102
- /** This site's own destinations. Omit for a bar that carries only the lockup. */
103
- nav?: readonly SiteNavLink[];
136
+ /**
137
+ * This site's own destinations: links, groups of links, or both in the order
138
+ * a reader should meet them. A link renders as itself; a group renders as one
139
+ * trigger opening its children as a menu. Omit for a bar that carries only
140
+ * the lockup.
141
+ */
142
+ nav?: readonly SiteNavEntry[];
104
143
  /**
105
144
  * The accessible name of the site's navigation. Required, and required rather
106
145
  * than defaulted because it is a word a reader hears: a Block that ships no copy
@@ -234,12 +273,14 @@ export type SiteNavbarProps = {
234
273
  * with no release. This package ships no `will-change` anywhere, and the bar is not
235
274
  * the place to start one.
236
275
  *
237
- * **It is a server Component with one client island inside it.** The lockup and the
238
- * navigation are rendered on the server and ship no JavaScript; the controls are a
239
- * single boundary, because a control's state is a reader's state. A site that
240
- * passes no `search`, `sites`, `theme`, `mode` or `nav` renders the whole bar on the
241
- * server and ships none, which is the arrangement a static site wants and the reason
242
- * those props are optional rather than required.
276
+ * **It is a server Component whose client islands are the ones a reader opens.** The
277
+ * lockup and the row's links are rendered on the server and ship no JavaScript; the
278
+ * controls are a single boundary, because a control's state is a reader's state, and
279
+ * a group in the navigation adds one island of its own for the same reason. A site
280
+ * that passes no `search`, `sites`, `theme`, `mode` or `nav`, and groups none of
281
+ * its destinations, renders the whole bar on the server and ships none, which is
282
+ * the arrangement a static site wants and the reason those props are optional
283
+ * rather than required.
243
284
  */
244
285
  export declare function SiteNavbar({ product, homeHref, defaultPack, defaultMode, nav, navLabel, currentPath, sites, currentSiteId, sitesLabel, search, theme, mode, mobileLabels, actions, sticky, className, }: SiteNavbarProps): import("react").JSX.Element;
245
286
  export default SiteNavbar;
@@ -1 +1 @@
1
- {"version":3,"file":"site-navbar.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-navbar/site-navbar.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"site-navbar.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-navbar/site-navbar.tsx"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ProductMark } from '../../components/ui/product-mark.js';
3
3
  import { cn } from '../../lib/utils.js';
4
4
  import { NavbarControls } from './navbar-controls.js';
5
+ import { NavMenu } from './nav-menu.js';
5
6
  /**
6
7
  * The bar at the top of a product site, with the controls a reader needs on every
7
8
  * page of it.
@@ -73,18 +74,52 @@ import { NavbarControls } from './navbar-controls.js';
73
74
  * with no release. This package ships no `will-change` anywhere, and the bar is not
74
75
  * the place to start one.
75
76
  *
76
- * **It is a server Component with one client island inside it.** The lockup and the
77
- * navigation are rendered on the server and ship no JavaScript; the controls are a
78
- * single boundary, because a control's state is a reader's state. A site that
79
- * passes no `search`, `sites`, `theme`, `mode` or `nav` renders the whole bar on the
80
- * server and ships none, which is the arrangement a static site wants and the reason
81
- * those props are optional rather than required.
77
+ * **It is a server Component whose client islands are the ones a reader opens.** The
78
+ * lockup and the row's links are rendered on the server and ship no JavaScript; the
79
+ * controls are a single boundary, because a control's state is a reader's state, and
80
+ * a group in the navigation adds one island of its own for the same reason. A site
81
+ * that passes no `search`, `sites`, `theme`, `mode` or `nav`, and groups none of
82
+ * its destinations, renders the whole bar on the server and ships none, which is
83
+ * the arrangement a static site wants and the reason those props are optional
84
+ * rather than required.
82
85
  */
83
86
  export function SiteNavbar({ product, homeHref = '/', defaultPack, defaultMode, nav, navLabel, currentPath, sites, currentSiteId, sitesLabel, search, theme, mode, mobileLabels, actions, sticky = true, className, }) {
84
- const links = nav ?? [];
85
- const isCurrent = (link) => link.current ?? (currentPath !== undefined && link.href === currentPath);
86
- return (_jsx("header", { "data-slot": "site-navbar", className: cn('border-border bg-background w-full border-b', sticky && 'sticky top-0 z-20', className), children: _jsxs("div", { className: "mx-auto flex h-14 w-full max-w-page items-center gap-2 px-6", children: [_jsx("a", { href: homeHref, className: "focus-visible:border-ring focus-visible:ring-ring shrink-0 rounded-sm focus-visible:ring-[3px] focus-visible:outline-none", children: _jsx(ProductMark, { id: product.id, name: product.name, pack: product.pack, size: "sm" }) }), links.length > 0 ? (_jsx("nav", { "aria-label": navLabel, className: "hidden items-center lg:flex", children: links.map((link) => (_jsx("a", { href: link.href, "aria-current": isCurrent(link) ? 'page' : undefined, rel: link.newTab ? 'noopener noreferrer' : undefined, target: link.newTab ? '_blank' : undefined, className: isCurrent(link)
87
+ const entries = nav ?? [];
88
+ /*
89
+ * The current mark reads the route as a place rather than as a string, so a
90
+ * reader on a page inside a destination is still on it: `/components/button`
91
+ * marks Components on the row, which is the question a reader asking "where
92
+ * am I" is asking. The boundary is the slash, so a Section whose segment is a
93
+ * prefix of another's (`/content` beside a hypothetical `/content-x`) never
94
+ * claims the other's pages.
95
+ */
96
+ const under = (href) => currentPath !== undefined && (currentPath === href || currentPath.startsWith(`${href}/`));
97
+ const leafCurrent = (link) => link.current ?? under(link.href);
98
+ /**
99
+ * A group is current when the reader is on it or inside any of its children.
100
+ * A group is told apart from a link by `href`, which a link must carry and a
101
+ * group must not, so the narrowing is on the one key the two never share.
102
+ */
103
+ const entryCurrent = (entry) => entry.current ??
104
+ ('href' in entry
105
+ ? under(entry.href)
106
+ : entry.children.some((child) => leafCurrent(child)));
107
+ return (_jsx("header", { "data-slot": "site-navbar", className: cn('border-border bg-background w-full border-b', sticky && 'sticky top-0 z-20', className), children: _jsxs("div", { className: "mx-auto flex h-14 w-full max-w-page items-center gap-2 px-6", children: [_jsx("a", { href: homeHref, className: "focus-visible:border-ring focus-visible:ring-ring shrink-0 rounded-sm focus-visible:ring-[3px] focus-visible:outline-none", children: _jsx(ProductMark, { id: product.id, name: product.name, pack: product.pack, mark: product.mark, size: "sm" }) }), entries.length > 0 ? (_jsx("nav", { "aria-label": navLabel, className: "hidden items-center lg:flex", children: entries.map((entry) =>
108
+ /*
109
+ * A group is the one control in the row, because it is the one
110
+ * entry that is a place rather than a destination, and its menu is
111
+ * a client island for the same reason the controls cluster is:
112
+ * whether it is open is a reader's state. The links around it stay
113
+ * server-rendered anchors, so a bar whose row is all links still
114
+ * ships a navigation with no JavaScript in it.
115
+ */
116
+ 'href' in entry ? (_jsx("a", { href: entry.href, "aria-current": entryCurrent(entry) ? 'page' : undefined, rel: entry.newTab ? 'noopener noreferrer' : undefined, target: entry.newTab ? '_blank' : undefined, className: entryCurrent(entry)
87
117
  ? 'bg-accent text-accent-foreground focus-visible:border-ring focus-visible:ring-ring rounded-md px-2 py-1.5 text-sm font-medium transition-colors focus-visible:ring-[3px] focus-visible:outline-none'
88
- : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring rounded-md px-2 py-1.5 text-sm font-medium transition-colors focus-visible:ring-[3px] focus-visible:outline-none', children: link.label }, link.href))) })) : null, _jsx(NavbarControls, { product: product, nav: links.map((link) => ({ ...link, current: isCurrent(link) })), navLabel: navLabel, sites: sites, currentSiteId: currentSiteId, sitesLabel: sitesLabel, search: search, theme: theme, mode: mode, mobileLabels: mobileLabels, defaultPack: defaultPack, defaultMode: defaultMode, actions: actions })] }) }));
118
+ : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring rounded-md px-2 py-1.5 text-sm font-medium transition-colors focus-visible:ring-[3px] focus-visible:outline-none', children: entry.label }, entry.href)) : (_jsx(NavMenu, { label: entry.label, current: entryCurrent(entry), links: entry.children.map((child) => ({ ...child, current: leafCurrent(child) })) }, entry.label))) })) : null, _jsx(NavbarControls, { product: product, nav: entries.map((entry) => 'href' in entry
119
+ ? { ...entry, current: entryCurrent(entry) }
120
+ : {
121
+ label: entry.label,
122
+ children: entry.children.map((child) => ({ ...child, current: leafCurrent(child) })),
123
+ }), navLabel: navLabel, sites: sites, currentSiteId: currentSiteId, sitesLabel: sitesLabel, search: search, theme: theme, mode: mode, mobileLabels: mobileLabels, defaultPack: defaultPack, defaultMode: defaultMode, actions: actions })] }) }));
89
124
  }
90
125
  export default SiteNavbar;
@@ -1 +1 @@
1
- {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../../src/components/ui/calendar.tsx"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS;IACtC,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;CACP,CAAA;AAED,sCAAsC;AACtC,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;OAQG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,gEAAgE;IAChE,aAAa,EAAE,gBAAgB,CAAA;IAC/B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxC,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAA;IACrB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;;OAOG;IACH,KAAK,EAAE,IAAI,CAAA;IACX,mFAAmF;IACnF,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,IAAI,CAAA;IACpC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IACnB,kFAAkF;IAClF,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACpC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAA;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IACjC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,mBAAmB;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAwED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,iBAAS,QAAQ,CAAC,EAChB,KAAK,EACL,UAAU,EACV,aAAa,EACb,YAAgB,EAChB,aAAa,EACb,SAAS,EACT,KAAK,EACL,aAAa,EACb,KAAY,EACZ,aAAa,EACb,cAAc,EACd,QAAQ,EACR,KAAK,EACL,QAAgB,EAChB,IAAI,EACJ,IAAI,EACJ,SAAS,GACV,EAAE,aAAa,+BAyPf;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../../src/components/ui/calendar.tsx"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS;IACtC,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;CACP,CAAA;AAED,sCAAsC;AACtC,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;OAQG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,gEAAgE;IAChE,aAAa,EAAE,gBAAgB,CAAA;IAC/B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxC,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAA;IACrB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;;OAOG;IACH,KAAK,EAAE,IAAI,CAAA;IACX,mFAAmF;IACnF,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,IAAI,CAAA;IACpC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IACnB,kFAAkF;IAClF,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACpC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAA;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IACjC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,mBAAmB;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA6ED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,iBAAS,QAAQ,CAAC,EAChB,KAAK,EACL,UAAU,EACV,aAAa,EACb,YAAgB,EAChB,aAAa,EACb,SAAS,EACT,KAAK,EACL,aAAa,EACb,KAAY,EACZ,aAAa,EACb,cAAc,EACd,QAAQ,EACR,KAAK,EACL,QAAgB,EAChB,IAAI,EACJ,IAAI,EACJ,SAAS,GACV,EAAE,aAAa,+BAyPf;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
@@ -35,7 +35,12 @@ const DAY_CELL =
35
35
  // was added to it, and the fuller grid kept the 36px cell that is the same cell at
36
36
  // a different size. A month grid is the densest control in the package and the one
37
37
  // most worked with a thumb, so it is the last place the floor can be argued about.
38
- 'text-foreground hover:bg-accent hover:text-accent-foreground flex h-9 w-full cursor-pointer items-center justify-center rounded-md text-sm tabular-nums outline-none transition-colors duration-fast ease-out pointer-coarse:h-11 focus-visible:ring-ring focus-visible:ring-[3px]';
38
+ //
39
+ // The cell is centred by the table, not by a `flex` utility: a `display: flex` on
40
+ // a `<th>` or a `<td>` takes the cell out of the table's column layout, and the
41
+ // month drew as one stacked column at four times its height before the utility
42
+ // came off.
43
+ 'text-foreground hover:bg-accent hover:text-accent-foreground h-9 cursor-pointer rounded-md p-0 align-middle text-center text-sm tabular-nums outline-none transition-colors duration-fast ease-out pointer-coarse:h-11 focus-visible:ring-ring focus-visible:ring-[3px]';
39
44
  /**
40
45
  * The two paging controls.
41
46
  *
@@ -226,7 +231,7 @@ function Calendar({ label, monthLabel, weekdayLabels, firstWeekday = 0, previous
226
231
  const focusedIso = iso(focused);
227
232
  const chosenIso = value === null ? null : iso(value);
228
233
  const todayIso = today === undefined ? null : iso(today);
229
- return (_jsxs("div", { "data-slot": "calendar", className: cn('bg-popover text-popover-foreground w-64 rounded-md border p-3 shadow-md', className), children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-1", children: [_jsx("button", { type: "button", "data-slot": "calendar-previous", "aria-label": previousLabel, disabled: disabled, onClick: () => onMonthChange(shiftMonth(month, -1)), className: cn(NAV_CONTROL, 'disabled:pointer-events-none disabled:opacity-50'), children: _jsx(ChevronLeftIcon, { className: "size-4" }) }), _jsx("div", { "data-slot": "calendar-caption", "aria-live": "polite", className: "text-foreground text-sm font-medium tabular-nums", children: monthLabel }), _jsx("button", { type: "button", "data-slot": "calendar-next", "aria-label": nextLabel, disabled: disabled, onClick: () => onMonthChange(shiftMonth(month, 1)), className: cn(NAV_CONTROL, 'disabled:pointer-events-none disabled:opacity-50'), children: _jsx(ChevronRightIcon, { className: "size-4" }) })] }), _jsx("div", { ref: gridRef, children: _jsxs("table", { "data-slot": "calendar-grid", role: "grid", "aria-label": label, className: "w-full border-collapse tabular-nums", children: [_jsx("thead", { children: _jsx("tr", { children: weekdayLabels.map((weekday, column) => (_jsx("th", { scope: "col", "data-slot": "calendar-weekday", className: "text-muted-foreground flex h-8 items-center justify-center text-xs font-medium", children: weekday }, `${weekday}-${column}`))) }) }), _jsx("tbody", { children: Array.from({ length: 6 }, (_, row) => (_jsx("tr", { children: cells.slice(row * 7, row * 7 + 7).map((date) => {
234
+ return (_jsxs("div", { "data-slot": "calendar", className: cn('bg-popover text-popover-foreground w-64 rounded-md border p-3 shadow-md', className), children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-1", children: [_jsx("button", { type: "button", "data-slot": "calendar-previous", "aria-label": previousLabel, disabled: disabled, onClick: () => onMonthChange(shiftMonth(month, -1)), className: cn(NAV_CONTROL, 'disabled:pointer-events-none disabled:opacity-50'), children: _jsx(ChevronLeftIcon, { className: "size-4" }) }), _jsx("div", { "data-slot": "calendar-caption", "aria-live": "polite", className: "text-foreground text-sm font-medium tabular-nums", children: monthLabel }), _jsx("button", { type: "button", "data-slot": "calendar-next", "aria-label": nextLabel, disabled: disabled, onClick: () => onMonthChange(shiftMonth(month, 1)), className: cn(NAV_CONTROL, 'disabled:pointer-events-none disabled:opacity-50'), children: _jsx(ChevronRightIcon, { className: "size-4" }) })] }), _jsx("div", { ref: gridRef, children: _jsxs("table", { "data-slot": "calendar-grid", role: "grid", "aria-label": label, className: "w-full border-collapse tabular-nums", children: [_jsx("thead", { children: _jsx("tr", { children: weekdayLabels.map((weekday, column) => (_jsx("th", { scope: "col", "data-slot": "calendar-weekday", className: "text-muted-foreground h-8 p-0 align-middle text-center text-xs font-medium", children: weekday }, `${weekday}-${column}`))) }) }), _jsx("tbody", { children: Array.from({ length: 6 }, (_, row) => (_jsx("tr", { children: cells.slice(row * 7, row * 7 + 7).map((date) => {
230
235
  const key = iso(date);
231
236
  if (!sameMonth(date, month)) {
232
237
  // The cells that pad the month out. They are hidden from the
@@ -243,7 +243,7 @@ function MiniCalendar({ selected, onSelect, month, defaultMonth, onMonthChange,
243
243
  default:
244
244
  }
245
245
  };
246
- return (_jsx("div", { ref: gridRef, "data-slot": "mini-calendar", className: cn('bg-card text-card-foreground w-64 rounded-md border p-3 shadow-sm', className), children: _jsxs("table", { "data-slot": "mini-calendar-grid", role: "grid", "aria-label": label, className: "w-full border-collapse tabular-nums", children: [_jsx("caption", { "data-slot": "mini-calendar-caption", className: "mb-2", children: _jsxs("span", { className: "flex items-center justify-between gap-1", children: [_jsx("button", { type: "button", "data-slot": "mini-calendar-previous", "aria-label": previousLabel, onClick: () => move(shiftMonth(shown, -1)), className: NAV_CONTROL, children: _jsx(ChevronLeftIcon, { className: "size-4", "aria-hidden": "true" }) }), _jsx("span", { "data-slot": "mini-calendar-month", "aria-live": "polite", className: "text-foreground text-sm font-medium", children: monthText }), _jsx("button", { type: "button", "data-slot": "mini-calendar-next", "aria-label": nextLabel, onClick: () => move(shiftMonth(shown, 1)), className: NAV_CONTROL, children: _jsx(ChevronRightIcon, { className: "size-4", "aria-hidden": "true" }) })] }) }), _jsx("thead", { children: _jsx("tr", { children: headings.map((heading) => (_jsx("th", { scope: "col", abbr: heading.long, "data-slot": "mini-calendar-weekday", className: "text-muted-foreground flex h-8 items-center justify-center text-xs font-medium", children: heading.short }, heading.long))) }) }), _jsx("tbody", { children: Array.from({ length: 6 }, (_, row) => (_jsx("tr", { children: cells.slice(row * 7, row * 7 + 7).map((date) => {
246
+ return (_jsx("div", { ref: gridRef, "data-slot": "mini-calendar", className: cn('bg-card text-card-foreground w-64 rounded-md border p-3 shadow-sm', className), children: _jsxs("table", { "data-slot": "mini-calendar-grid", role: "grid", "aria-label": label, className: "w-full border-collapse tabular-nums", children: [_jsx("caption", { "data-slot": "mini-calendar-caption", className: "mb-2", children: _jsxs("span", { className: "flex items-center justify-between gap-1", children: [_jsx("button", { type: "button", "data-slot": "mini-calendar-previous", "aria-label": previousLabel, onClick: () => move(shiftMonth(shown, -1)), className: NAV_CONTROL, children: _jsx(ChevronLeftIcon, { className: "size-4", "aria-hidden": "true" }) }), _jsx("span", { "data-slot": "mini-calendar-month", "aria-live": "polite", className: "text-foreground text-sm font-medium", children: monthText }), _jsx("button", { type: "button", "data-slot": "mini-calendar-next", "aria-label": nextLabel, onClick: () => move(shiftMonth(shown, 1)), className: NAV_CONTROL, children: _jsx(ChevronRightIcon, { className: "size-4", "aria-hidden": "true" }) })] }) }), _jsx("thead", { children: _jsx("tr", { children: headings.map((heading) => (_jsx("th", { scope: "col", abbr: heading.long, "data-slot": "mini-calendar-weekday", className: "text-muted-foreground h-8 p-0 align-middle text-center text-xs font-medium", children: heading.short }, heading.long))) }) }), _jsx("tbody", { children: Array.from({ length: 6 }, (_, row) => (_jsx("tr", { children: cells.slice(row * 7, row * 7 + 7).map((date) => {
247
247
  const key = iso(date);
248
248
  if (!sameMonth(date, shown)) {
249
249
  // The cells that pad the month out. Hidden from the accessibility