rift-ds 1.15.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ An open source React design system built for AI products and coding agents: comp
13
13
 
14
14
  ## Why Rift DS
15
15
 
16
- - **<!-- component-count -->159<!-- /component-count --> components.** Buttons, forms and tables through to charts, navigation and overlays, each typed and documented with live examples. Every one forwards a ref and your own attributes to its element, state you can own works controlled or uncontrolled (a modal opens when your app says so, by design), and every word a component writes has a prop you can localise. The build checks the first two on every component, and the words wherever they sit in markup. React and React DOM are the only required peer dependencies.
16
+ - **<!-- component-count -->162<!-- /component-count --> components.** Buttons, forms and tables through to charts, navigation and overlays, each typed and documented with live examples. Every one forwards a ref and your own attributes to its element, state you can own works controlled or uncontrolled (a modal opens when your app says so, by design), and every word a component writes has a prop you can localise. The build checks the first two on every component, and the words wherever they sit in markup. React and React DOM are the only required peer dependencies.
17
17
  - **AI patterns, the whole chat and templates, ready to use.** Composer, streaming chat thread, tool calls, reasoning and thread panel are in the box, the complete AI chat ships assembled as one widget that one command scaffolds, and the docs site has complete template screens built from them.
18
18
  - **Fully themeable.** The shipped themes are complete looks to start from, each in light and dark. One `data-brand` attribute applies a theme, and a few CSS overrides make your own.
19
19
  - **Ready for coding agents.** One command installs the agent skills, and a public MCP endpoint serves every component's props, so your agent builds with the library as it actually is. No key, no account.
@@ -107,13 +107,13 @@ import 'rift-ds/tokens/presets/presets.css';
107
107
 
108
108
  ## Components
109
109
 
110
- <!-- component-count -->159<!-- /component-count --> components, including a chat set for AI products (Chat thread, Composer, Tool call, Reasoning, Thread panel) and Shader field, a WebGL2 ambient background whose light sources read your colour tokens at runtime, so it re-themes with everything else. Details and live examples are on the [docs site](https://rift-ds.com/components).
110
+ <!-- component-count -->162<!-- /component-count --> components, including a chat set for AI products (Chat thread, Composer, Tool call, Reasoning, Thread panel) and Shader field, a WebGL2 ambient background whose light sources read your colour tokens at runtime, so it re-themes with everything else. Details and live examples are on the [docs site](https://rift-ds.com/components).
111
111
 
112
112
  <details>
113
113
  <summary>The full list</summary>
114
114
 
115
115
  <!-- component-list:start -->
116
- Accordion · Agent plan · Agent rail · Agent status · AI button · Alert · Alert dialog · Anchor nav · Animated number · App layout · App sidebar · Area chart · Artifact card · Artifact panel · Attachment drop zone · Attachment group · Attachment tile · Attachment viewer · Avatar · Avatar group · Badge · Banner · Bar chart · Bento tile · Bento tile art · Bottom nav · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Command palette trigger · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Filter bar · Funnel chart · Gantt chart · Gauge · Globe · Hover card · Image compare · Input · Inspector colour picker · Inspector dropdown · Inspector input · Inspector section · Inspector segments · Inspector slider · Inspector toggle switch · Instructions · Interrupt card · Kbd · Legend tile · Lightbox · Line chart · Link list · Map callout · Map legend · Mention · Message actions · Message card · Meter · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Pixel avatar · Popover · Progress bar · Prompt suggestions · Prose · Question card · Quote · Radar chart · Radial chart · Radio button · Rating · Reasoning · Rich dropdown · Scatter chart · Section title · Segmented control · Selection card · Shader field · Sidebar panel · Skeleton · Slider · Source chip · Source trail · Sparkline · Spinner · Split button · Split pane · Stacked bar chart · Stat · Status dot · Stepper · Streaming text · Swatch · Table · Table toolbar · Tabs · Tag input · Textarea · Theme picker · Theme toggle · Thread panel · Thread tabs · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Toolbar · Tooltip · Top app bar · Tree view · Treemap · Usage card · Waveform · Widget · World map
116
+ Accordion · Agent plan · Agent rail · Agent status · AI button · Alert · Alert dialog · Anchor nav · Animated number · App layout · App sidebar · Area chart · Artifact card · Artifact panel · Attachment drop zone · Attachment group · Attachment tile · Attachment viewer · Avatar · Avatar group · Badge · Banner · Bar chart · Bento tile · Bento tile art · Bottom nav · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Command palette trigger · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Filter bar · Funnel chart · Gantt chart · Gauge · Globe · Hover card · Image compare · Input · Inspector colour picker · Inspector dropdown · Inspector input · Inspector section · Inspector segments · Inspector slider · Inspector toggle switch · Instructions · Interrupt card · Kbd · Legend tile · Lightbox · Line chart · Link list · Map callout · Map legend · Mega menu · Mention · Message actions · Message card · Meter · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Pixel avatar · Popover · Progress bar · Progressive blur · Prompt suggestions · Prose · Question card · Quote · Radar chart · Radial chart · Radio button · Rating · Reasoning · Rich dropdown · Scatter chart · Section title · Segmented control · Selection card · Shader field · Sidebar panel · Site header · Skeleton · Slider · Source chip · Source trail · Sparkline · Spinner · Split button · Split pane · Stacked bar chart · Stat · Status dot · Stepper · Streaming text · Swatch · Table · Table toolbar · Tabs · Tag input · Textarea · Theme picker · Theme toggle · Thread panel · Thread tabs · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Toolbar · Tooltip · Top app bar · Tree view · Treemap · Usage card · Waveform · Widget · World map
117
117
  <!-- component-list:end -->
118
118
 
119
119
  </details>
@@ -28,8 +28,10 @@
28
28
  width: 24px;
29
29
  height: 24px;
30
30
  box-sizing: border-box;
31
- border: var(--border-050) solid var(--color-input-border-primary);
32
- border-radius: var(--radius-200); /* 8px */
31
+ border: var(--border-050) solid var(--color-input-indicator-border);
32
+ /* Always a rounded square, whatever the theme's radius: never sharp at a
33
+ zero radius, never rounding towards a radio's circle at a large one. */
34
+ border-radius: clamp(4px, var(--radius-200), 8px); /* ds-allow(spacing): the checkbox's shape is fixed, so its corner is bounded */
33
35
  background-color: var(--color-input-bg-primary);
34
36
  display: inline-flex;
35
37
  align-items: center;
@@ -12,6 +12,12 @@ type DrawerOwnProps = {
12
12
  title: string;
13
13
  /** Optional subtitle under the title */
14
14
  description?: string;
15
+ /**
16
+ * Content shown in place of the title text, such as a site's logo. The
17
+ * `title` still names the dialog, read by assistive tech from a visually
18
+ * hidden heading.
19
+ */
20
+ heading?: React.ReactNode;
15
21
  /** Drawer body content */
16
22
  children?: React.ReactNode;
17
23
  /** Optional footer slot — typically a row of Buttons */
@@ -5,6 +5,7 @@ import { CircularButton } from "../CircularButton/CircularButton.js";
5
5
  import ReactDOM from "react-dom";
6
6
  import "./Drawer.css";
7
7
  import "../../behaviors/direction.css";
8
+ import "../../behaviors/visually-hidden.css";
8
9
  import "../../fonts/material-symbols.css";
9
10
  import { useMounted } from "../../behaviors/useMounted.js";
10
11
  import { useLayer } from "../../behaviors/useLayer.js";
@@ -16,6 +17,7 @@ const Drawer = React.forwardRef(
16
17
  onOpenChange,
17
18
  title,
18
19
  description,
20
+ heading,
19
21
  children,
20
22
  footer,
21
23
  side = "right",
@@ -65,7 +67,15 @@ const Drawer = React.forwardRef(
65
67
  children: [
66
68
  /* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
67
69
  /* @__PURE__ */ jsxs("div", { className: `${baseClass}__heading`, children: [
68
- /* @__PURE__ */ jsx("h2", { className: `${baseClass}__title`, id: titleId, children: title }),
70
+ /* @__PURE__ */ jsx(
71
+ "h2",
72
+ {
73
+ className: heading ? "ds-visually-hidden" : `${baseClass}__title`,
74
+ id: titleId,
75
+ children: title
76
+ }
77
+ ),
78
+ heading,
69
79
  description && /* @__PURE__ */ jsx("p", { className: `${baseClass}__description`, id: descId, children: description })
70
80
  ] }),
71
81
  dismissible && /* @__PURE__ */ jsx(
@@ -0,0 +1,271 @@
1
+ /* ============================================
2
+ MEGA MENU
3
+ A wide navigation panel: icon rows plus an
4
+ optional featured card
5
+ ============================================ */
6
+
7
+ /* Base: the floating glass shell, CommandPalette's surface. The panel is a
8
+ shallow strip that lands on whatever the page opens with, display type
9
+ most of it, so its blur runs harder than the shell's 24px: at that
10
+ strength a heading that size still reads through the row labels. */
11
+ .ds-mega-menu {
12
+ box-sizing: border-box;
13
+ padding: var(--padding-1000);
14
+ background-color: var(--color-bg-glass);
15
+ backdrop-filter: blur(32px);
16
+ border: var(--border-025) solid var(--color-bg-container-border);
17
+ border-radius: var(--radius-400);
18
+ box-shadow: var(--shadow-floating);
19
+ /* The showcase card's height floor is sized against this box. */
20
+ container: ds-mega-menu / inline-size;
21
+
22
+ /* Closed: faded out, then hidden once the fade has run, so a reopen
23
+ mid-fade picks up where it was. */
24
+ opacity: 0;
25
+ visibility: hidden;
26
+ transition:
27
+ opacity var(--motion-duration-base) var(--motion-ease-standard),
28
+ visibility 0s linear var(--motion-duration-base);
29
+ }
30
+
31
+ /* States */
32
+ .ds-mega-menu--open {
33
+ opacity: 1;
34
+ visibility: visible;
35
+ transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
36
+ }
37
+
38
+ /* Layout: the link grid alone, or the grid in two thirds beside the
39
+ showcase in one. */
40
+ .ds-mega-menu__layout {
41
+ display: grid;
42
+ grid-template-columns: minmax(0, 1fr);
43
+ gap: var(--gap-1000);
44
+ }
45
+
46
+ .ds-mega-menu__layout--showcase {
47
+ grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
48
+ }
49
+
50
+ /* The link grid: four rows to a column, wrapping into further columns, so
51
+ a long menu grows sideways and never into a scroll. */
52
+ .ds-mega-menu__grid {
53
+ display: grid;
54
+ grid-template-rows: repeat(4, auto);
55
+ grid-auto-flow: column;
56
+ grid-auto-columns: minmax(0, 1fr);
57
+ gap: var(--gap-200) var(--gap-1000);
58
+ align-content: start;
59
+ }
60
+
61
+ /* Rows: the overlay row family. No fill at rest, the passive wash on hover
62
+ and focus at --radius-200, and the navigation current item one step past
63
+ it, so current and hovered never read the same. */
64
+ .ds-mega-menu__item {
65
+ display: flex;
66
+ align-items: flex-start;
67
+ gap: var(--gap-300);
68
+ min-width: 0;
69
+ padding: var(--padding-300) var(--padding-400);
70
+ border-radius: var(--radius-200);
71
+ color: inherit;
72
+ text-decoration: none;
73
+ transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
74
+ }
75
+
76
+ .ds-mega-menu__item:hover {
77
+ background-color: var(--color-action-passive-bg-hover);
78
+ }
79
+
80
+ .ds-mega-menu__item:focus-visible {
81
+ outline: 2px solid var(--color-action-primary-bg);
82
+ outline-offset: 2px;
83
+ background-color: var(--color-action-passive-bg-hover);
84
+ }
85
+
86
+ .ds-mega-menu__item--current {
87
+ background-color: var(--color-action-passive-bg-active);
88
+ }
89
+
90
+ /* The icon tile: a step of container fill around the glyph, holding that
91
+ step over the row's hover wash. Its corner is the row's own radius, since
92
+ the tile sits at the row's start with no padding between them on that
93
+ axis that would ask for a smaller one. */
94
+ .ds-mega-menu__icon {
95
+ flex-shrink: 0;
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ width: calc(var(--icon-size-600) + var(--padding-200) * 2);
100
+ height: calc(var(--icon-size-600) + var(--padding-200) * 2);
101
+ border-radius: var(--radius-200);
102
+ background-color: var(--color-bg-container-secondary);
103
+ color: var(--color-text-primary);
104
+ }
105
+
106
+ /* The tile keeps a step of contrast when the row behind it fills in. The
107
+ hover wash already differs from the tile's resting fill; the current
108
+ row's wash is that same fill, so there the tile takes the other step. */
109
+ .ds-mega-menu__item--current .ds-mega-menu__icon {
110
+ background-color: var(--color-bg-container-tertiary);
111
+ }
112
+
113
+ .ds-mega-menu__icon .material-symbols-rounded {
114
+ --icon-size: var(--icon-size-600);
115
+ line-height: 1;
116
+ }
117
+
118
+ .ds-mega-menu__logo {
119
+ display: block;
120
+ width: var(--icon-size-600);
121
+ height: var(--icon-size-600);
122
+ object-fit: contain;
123
+ }
124
+
125
+ .ds-mega-menu__text {
126
+ display: flex;
127
+ flex-direction: column;
128
+ gap: var(--gap-100);
129
+ min-width: 0;
130
+ }
131
+
132
+ .ds-mega-menu__label {
133
+ font-family: var(--font-title-body-family);
134
+ font-size: var(--font-title-body-size);
135
+ font-weight: var(--font-title-body-weight);
136
+ line-height: var(--font-title-body-line-height);
137
+ letter-spacing: var(--font-title-body-letter-spacing);
138
+ color: var(--color-text-primary);
139
+ }
140
+
141
+ /* Rows stay even: a description is at most two lines. The full sentence
142
+ lives on the page the row links to. */
143
+ .ds-mega-menu__description {
144
+ display: -webkit-box;
145
+ -webkit-line-clamp: 2;
146
+ -webkit-box-orient: vertical;
147
+ overflow: hidden;
148
+ font-family: var(--font-paragraph-sm-family);
149
+ font-size: var(--font-paragraph-sm-size);
150
+ font-weight: var(--font-paragraph-sm-weight);
151
+ line-height: var(--font-paragraph-sm-line-height);
152
+ color: var(--color-text-tertiary);
153
+ }
154
+
155
+ /* Showcase: the column carries the divider, the card fills it. */
156
+ .ds-mega-menu__showcase-column {
157
+ display: flex;
158
+ padding-inline-start: var(--padding-1000);
159
+ border-inline-start: var(--border-025) solid var(--color-bg-container-border);
160
+ }
161
+
162
+ /* One link that is the whole card. The cover fills it and absorbs whatever
163
+ height the link grid runs to, so the columns always bottom-align; the
164
+ floor keeps a short grid's card at about 4:3 so the drawing has room
165
+ above the copy. */
166
+ .ds-mega-menu__showcase {
167
+ position: relative;
168
+ flex: 1;
169
+ display: flex;
170
+ flex-direction: column;
171
+ justify-content: flex-end;
172
+ min-width: 0;
173
+ min-height: 25cqw;
174
+ overflow: hidden;
175
+ border: var(--border-025) solid var(--color-bg-container-border);
176
+ border-radius: var(--radius-300);
177
+ color: inherit;
178
+ text-decoration: none;
179
+ transition: border-color var(--motion-duration-fast) var(--motion-ease-standard);
180
+ }
181
+
182
+ .ds-mega-menu__showcase:hover {
183
+ border-color: var(--color-bg-container-tertiary);
184
+ }
185
+
186
+ .ds-mega-menu__showcase:focus-visible {
187
+ outline: 2px solid var(--color-action-primary-bg);
188
+ outline-offset: 4px;
189
+ }
190
+
191
+ /* The cover's gentle zoom on hover is the card acknowledging the pointer,
192
+ clipped by the frame. */
193
+ .ds-mega-menu__cover {
194
+ position: absolute;
195
+ inset: 0;
196
+ transition: scale var(--motion-duration-fast) var(--motion-ease-standard);
197
+ }
198
+
199
+ .ds-mega-menu__cover > * {
200
+ width: 100%;
201
+ height: 100%;
202
+ }
203
+
204
+ .ds-mega-menu__showcase:hover .ds-mega-menu__cover {
205
+ scale: 1.02;
206
+ }
207
+
208
+ /* The copy, kept legible on the drawing by a rise of page surface from the
209
+ card's foot. The tall top padding is the fade zone; the text never
210
+ reaches it. */
211
+ .ds-mega-menu__showcase-text {
212
+ position: relative;
213
+ display: flex;
214
+ flex-direction: column;
215
+ gap: var(--gap-100);
216
+ padding: var(--padding-1500) var(--padding-500) var(--padding-500);
217
+ /* ds-allow(color): the transparent end of the fade, not a palette colour */
218
+ background: linear-gradient(to top, var(--color-bg-page-primary) 55%, transparent);
219
+ }
220
+
221
+ .ds-mega-menu__overline {
222
+ font-family: var(--font-overline-family);
223
+ font-size: var(--font-overline-size);
224
+ font-weight: var(--font-overline-weight);
225
+ line-height: var(--font-overline-line-height);
226
+ letter-spacing: var(--font-overline-letter-spacing);
227
+ text-transform: uppercase;
228
+ color: var(--color-text-tertiary);
229
+ }
230
+
231
+ .ds-mega-menu__showcase-title {
232
+ display: flex;
233
+ align-items: center;
234
+ gap: var(--gap-100);
235
+ }
236
+
237
+ .ds-mega-menu__arrow {
238
+ --icon-size: var(--icon-size-500);
239
+ line-height: 1;
240
+ color: var(--color-action-passive-text);
241
+ transition: translate var(--motion-duration-fast) var(--motion-ease-standard);
242
+ }
243
+
244
+ .ds-mega-menu__showcase:hover .ds-mega-menu__arrow {
245
+ translate: calc(var(--gap-050) * var(--ds-inline-sign, 1)) 0;
246
+ }
247
+
248
+ /* Narrow: a panel without the room for two column groups stacks the
249
+ showcase under the grid and lets the grid run one column deep.
250
+ ds-allow(breakpoint): content-driven: two row columns and the showcase need about 720px of panel. */
251
+ @container ds-mega-menu (max-width: 720px) {
252
+ .ds-mega-menu__layout--showcase {
253
+ grid-template-columns: minmax(0, 1fr);
254
+ }
255
+
256
+ .ds-mega-menu__grid {
257
+ grid-template-rows: none;
258
+ grid-auto-flow: row;
259
+ }
260
+
261
+ .ds-mega-menu__showcase-column {
262
+ padding-inline-start: 0;
263
+ border-inline-start: none;
264
+ }
265
+
266
+ /* Full width now, so the floor that kept a third-width card at 4:3
267
+ becomes a wide band with the same room above the copy. */
268
+ .ds-mega-menu__showcase {
269
+ min-height: 50cqw;
270
+ }
271
+ }
@@ -0,0 +1,72 @@
1
+ import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
3
+ /** One link row in a mega menu: a page and the line that says what is on it. */
4
+ export interface MegaMenuItem {
5
+ /** Where the row links. */
6
+ href: string;
7
+ /** The page's name. */
8
+ label: string;
9
+ /** One line on what the page holds, clamped to two lines in the panel. */
10
+ description?: string;
11
+ /** A Material Symbols name drawn in the row's icon tile. */
12
+ icon?: string;
13
+ /** An image path drawn in the icon tile instead of `icon`, for a brand or product mark. */
14
+ logo?: string;
15
+ }
16
+ /** The featured card beside a mega menu's link grid: one link that is the whole card. */
17
+ export interface MegaMenuShowcase {
18
+ /** Where the card links. */
19
+ href: string;
20
+ /** A short kicker above the title, set in the overline face. */
21
+ overline?: string;
22
+ /** The card's title, followed by a forward arrow. */
23
+ label: string;
24
+ /** One or two lines under the title. */
25
+ description?: string;
26
+ /**
27
+ * A drawing that fills the card behind the copy. Decorative: the title and
28
+ * description carry the meaning, so mark it `aria-hidden`.
29
+ */
30
+ cover?: React.ReactNode;
31
+ }
32
+ /** Props owned by MegaMenu itself — everything else falls through to the panel `<div>`. */
33
+ type MegaMenuOwnProps = {
34
+ /**
35
+ * Whether the panel shows. Controlled by design: the host owns the
36
+ * triggers that open it, so it owns this state too. A closed panel fades
37
+ * out and is `inert`, so its links leave the tab order.
38
+ */
39
+ open: boolean;
40
+ /** The link rows, laid out four to a column and wrapping into further columns. */
41
+ items: MegaMenuItem[];
42
+ /** A featured card in its own column beside the link grid. */
43
+ showcase?: MegaMenuShowcase;
44
+ /**
45
+ * The path of the page being viewed. A row whose `href` is this path, or a
46
+ * parent of it, is marked as the current page.
47
+ */
48
+ currentHref?: string;
49
+ /** Called when any link in the panel is followed, so a host can close it. */
50
+ onNavigate?: () => void;
51
+ /**
52
+ * The component every link renders through, so a router's link (Next.js
53
+ * `Link`) navigates without a reload. The panel is server-renderable, so it
54
+ * cannot read a `LinkProvider`; a client host resolves one and passes it
55
+ * here. Defaults to `<a>`.
56
+ */
57
+ linkComponent?: LinkComponent;
58
+ /** Additional CSS classes on the panel. */
59
+ className?: string;
60
+ };
61
+ export interface MegaMenuProps extends MegaMenuOwnProps, Omit<React.ComponentPropsWithRef<'div'>, keyof MegaMenuOwnProps> {
62
+ }
63
+ /**
64
+ * A wide navigation panel: a grid of icon rows, each a page with a line of
65
+ * description, and an optional featured card in a column of its own. It is
66
+ * the panel only. The host places it (under a header bar, usually) and owns
67
+ * the triggers, the hover delay and the dismissal; SiteHeader is the
68
+ * reference host. The forwarded ref and any unrecognised props target the
69
+ * panel `<div>`.
70
+ */
71
+ export declare function MegaMenu({ open, items, showcase, currentHref, onNavigate, linkComponent, className, ref, ...rest }: MegaMenuProps): React.JSX.Element;
72
+ export {};
@@ -0,0 +1,71 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import "react";
3
+ import { mirrorClass } from "../../behaviors/direction.js";
4
+ import "./MegaMenu.css";
5
+ import "../../fonts/material-symbols.css";
6
+ import "../../behaviors/direction.css";
7
+ const isCurrent = (href, currentHref) => currentHref != null && (currentHref === href || currentHref.startsWith(`${href}/`));
8
+ function MegaMenu({
9
+ open,
10
+ items,
11
+ showcase,
12
+ currentHref,
13
+ onNavigate,
14
+ linkComponent,
15
+ className = "",
16
+ ref,
17
+ ...rest
18
+ }) {
19
+ const Link = linkComponent ?? "a";
20
+ const baseClass = "ds-mega-menu";
21
+ const classes = [baseClass, open && `${baseClass}--open`, className].filter(Boolean).join(" ");
22
+ return /* @__PURE__ */ jsx("div", { ...rest, ref, className: classes, inert: !open, children: /* @__PURE__ */ jsxs(
23
+ "div",
24
+ {
25
+ className: [`${baseClass}__layout`, showcase && `${baseClass}__layout--showcase`].filter(Boolean).join(" "),
26
+ children: [
27
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__grid`, children: items.map((item) => {
28
+ const current = isCurrent(item.href, currentHref);
29
+ return /* @__PURE__ */ jsxs(
30
+ Link,
31
+ {
32
+ href: item.href,
33
+ className: [`${baseClass}__item`, current && `${baseClass}__item--current`].filter(Boolean).join(" "),
34
+ "aria-current": current ? "page" : void 0,
35
+ onClick: onNavigate,
36
+ children: [
37
+ (item.logo || item.icon) && /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, "aria-hidden": "true", children: item.logo ? /* @__PURE__ */ jsx("img", { className: `${baseClass}__logo`, src: item.logo, alt: "" }) : /* @__PURE__ */ jsx("span", { className: `material-symbols-rounded ${mirrorClass(item.icon)}`, children: item.icon }) }),
38
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__text`, children: [
39
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: item.label }),
40
+ item.description && /* @__PURE__ */ jsx("span", { className: `${baseClass}__description`, children: item.description })
41
+ ] })
42
+ ]
43
+ },
44
+ item.href
45
+ );
46
+ }) }),
47
+ showcase && /* @__PURE__ */ jsx("div", { className: `${baseClass}__showcase-column`, children: /* @__PURE__ */ jsxs(Link, { href: showcase.href, className: `${baseClass}__showcase`, onClick: onNavigate, children: [
48
+ showcase.cover && /* @__PURE__ */ jsx("span", { className: `${baseClass}__cover`, children: showcase.cover }),
49
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__showcase-text`, children: [
50
+ showcase.overline && /* @__PURE__ */ jsx("span", { className: `${baseClass}__overline`, children: showcase.overline }),
51
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__label ${baseClass}__showcase-title`, children: [
52
+ showcase.label,
53
+ /* @__PURE__ */ jsx(
54
+ "span",
55
+ {
56
+ className: `material-symbols-rounded ds-mirror-rtl ${baseClass}__arrow`,
57
+ "aria-hidden": "true",
58
+ children: "arrow_forward"
59
+ }
60
+ )
61
+ ] }),
62
+ showcase.description && /* @__PURE__ */ jsx("span", { className: `${baseClass}__description`, children: showcase.description })
63
+ ] })
64
+ ] }) })
65
+ ]
66
+ }
67
+ ) });
68
+ }
69
+ export {
70
+ MegaMenu
71
+ };
@@ -16,12 +16,17 @@ type NavOwnProps = {
16
16
  export interface NavProps extends NavOwnProps, Omit<React.ComponentPropsWithoutRef<'nav'>, keyof NavOwnProps> {
17
17
  }
18
18
  /**
19
- * Nav component from Figma design system
20
- * Renders a horizontal navigation bar using ButtonGroup.
19
+ * Nav is the earlier plain top bar: a brand slot and a horizontal
20
+ * ButtonGroup of page links, with no menus and no mobile behaviour of its
21
+ * own. It keeps working for existing consumers, and new work uses
22
+ * SiteHeader, which adds section mega menus, the sticky progressive blur and
23
+ * the fold to a drawer.
21
24
  *
22
25
  * The landmark is labelled "Main" by default so assistive tech can tell it
23
26
  * apart from any other navigation on the page; pass `aria-label` to name it
24
27
  * differently. The forwarded ref and any unrecognised props target the `<nav>`.
28
+ *
29
+ * @deprecated Use `SiteHeader` instead.
25
30
  */
26
31
  export declare const Nav: React.ForwardRefExoticComponent<NavProps & React.RefAttributes<HTMLElement>>;
27
32
  export {};
@@ -0,0 +1,102 @@
1
+ /* ============================================
2
+ PROGRESSIVE BLUR
3
+ A backdrop blur that dissolves along one edge
4
+ ============================================ */
5
+
6
+ /* Why a stack, and why masks: a single backdrop-filter is uniform across its
7
+ whole box, so a tint that fades to transparent still ends in a hard line
8
+ where the blur stops. A mask-image multiplies both the layer's paint and
9
+ its filtered backdrop by its alpha, so the blur itself dissolves. Four
10
+ layers with rising blur strength and a shrinking mask band give a
11
+ progressive blur: frosted at the anchored edge, clear at the far one.
12
+
13
+ The blur sits on child layers, never on the root: a backdrop-filter on an
14
+ ancestor would make it a backdrop root, and any glass inside the host
15
+ (a menu panel, say) would then sample only that subtree instead of the
16
+ page. So the host keeps its own children above this element, and the
17
+ blur reads the page behind both.
18
+
19
+ No hand-written -webkit- twins for backdrop-filter: the build's CSS
20
+ pipeline adds the prefix, and a manual one beside it has made the
21
+ pipeline drop the property entirely. */
22
+
23
+ /* Base */
24
+ .ds-progressive-blur {
25
+ /* The tint's colour. An override hook: a host over a different surface
26
+ points it at that surface. */
27
+ --ds-progressive-blur-tint: var(--color-bg-page-primary);
28
+ position: absolute;
29
+ inset: 0;
30
+ /* A child's backdrop-filter is not reliably clipped by a rounded
31
+ overflow: hidden parent, so the frost would show square at the host's
32
+ corners. The root and every layer take the host's corner instead. */
33
+ border-radius: inherit;
34
+ pointer-events: none;
35
+ }
36
+
37
+ .ds-progressive-blur--top {
38
+ --ds-progressive-blur-toward: to bottom;
39
+ }
40
+
41
+ .ds-progressive-blur--bottom {
42
+ --ds-progressive-blur-toward: to top;
43
+ }
44
+
45
+ .ds-progressive-blur__layer {
46
+ position: absolute;
47
+ inset: 0;
48
+ display: block;
49
+ border-radius: inherit;
50
+ }
51
+
52
+ /* Layers: blur strength rises as the masked band shrinks, so the edge is
53
+ fully frosted and the far side is sharp. The radii are the effect's own
54
+ tuning, not a scale: a blur radius is not spacing. */
55
+
56
+ /* ds-allow(color): mask alpha keywords (black/transparent), not palette colours */
57
+
58
+ .ds-progressive-blur__layer:nth-child(1) {
59
+ backdrop-filter: blur(2px);
60
+ mask-image: linear-gradient(var(--ds-progressive-blur-toward), black 0%, black 55%, transparent 100%);
61
+ }
62
+
63
+ .ds-progressive-blur__layer:nth-child(2) {
64
+ backdrop-filter: blur(6px);
65
+ mask-image: linear-gradient(var(--ds-progressive-blur-toward), black 0%, black 40%, transparent 80%);
66
+ }
67
+
68
+ .ds-progressive-blur__layer:nth-child(3) {
69
+ backdrop-filter: blur(12px);
70
+ mask-image: linear-gradient(var(--ds-progressive-blur-toward), black 0%, black 25%, transparent 60%);
71
+ }
72
+
73
+ .ds-progressive-blur__layer:nth-child(4) {
74
+ backdrop-filter: blur(24px);
75
+ mask-image: linear-gradient(var(--ds-progressive-blur-toward), black 0%, black 10%, transparent 40%);
76
+ }
77
+
78
+ /* Tint: the page surface at the edge stepping to transparent, on its own
79
+ layer above the blur. Only this layer ever fades: transitioning opacity
80
+ on a backdrop-filter makes the browser re-rasterise the blur, which then
81
+ snaps in at the end of the fade, while a plain gradient fades smoothly. */
82
+ .ds-progressive-blur::after {
83
+ content: "";
84
+ position: absolute;
85
+ inset: 0;
86
+ border-radius: inherit;
87
+ background: linear-gradient(
88
+ var(--ds-progressive-blur-toward),
89
+ var(--ds-progressive-blur-tint) 0%,
90
+ color-mix(in srgb, var(--ds-progressive-blur-tint) 90%, transparent) 30%,
91
+ color-mix(in srgb, var(--ds-progressive-blur-tint) 40%, transparent) 65%,
92
+ transparent 100%
93
+ );
94
+ mask-image: linear-gradient(var(--ds-progressive-blur-toward), black 0%, black 55%, transparent 100%);
95
+ opacity: 0;
96
+ transition: opacity var(--motion-duration-slow) var(--motion-ease-standard);
97
+ }
98
+
99
+ /* States */
100
+ .ds-progressive-blur--tinted::after {
101
+ opacity: 1;
102
+ }
@@ -0,0 +1,30 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by ProgressiveBlur itself — everything else falls through to the root `<div>`. */
3
+ type ProgressiveBlurOwnProps = {
4
+ /**
5
+ * The side the blur is anchored to, as Drawer names its edge: fully
6
+ * frosted there, dissolving to clear toward the opposite side. `top` sits
7
+ * behind a header, `bottom` behind a toolbar or a footer bar.
8
+ */
9
+ side?: 'top' | 'bottom';
10
+ /**
11
+ * Whether the page-surface tint shows over the blur. It fades on change,
12
+ * so a header can bring it in once the page has scrolled; the blur
13
+ * itself never fades.
14
+ */
15
+ tinted?: boolean;
16
+ /** Additional CSS classes on the root. */
17
+ className?: string;
18
+ };
19
+ export interface ProgressiveBlurProps extends ProgressiveBlurOwnProps, Omit<React.ComponentPropsWithRef<'div'>, keyof ProgressiveBlurOwnProps> {
20
+ }
21
+ /**
22
+ * A backdrop blur that dissolves along one edge instead of ending in a hard
23
+ * line: four masked blur layers of rising strength, with an optional tint of
24
+ * the page surface on its own layer above them. It fills its positioned
25
+ * parent and is decorative, so it is hidden from assistive tech and lets the
26
+ * pointer through. The forwarded ref and any unrecognised props target the
27
+ * root `<div>`.
28
+ */
29
+ export declare function ProgressiveBlur({ side, tinted, className, ref, ...rest }: ProgressiveBlurProps): React.JSX.Element;
30
+ export {};