rift-ds 1.0.0 → 1.1.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 (37) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -3
  3. package/components/AppLayout/AppLayout.css +13 -3
  4. package/components/AppLayout/AppLayout.d.ts +5 -1
  5. package/components/AppLayout/AppLayout.js +4 -0
  6. package/components/AppSidebar/AppSidebar.css +237 -0
  7. package/components/AppSidebar/AppSidebar.d.ts +32 -3
  8. package/components/AppSidebar/AppSidebar.js +187 -16
  9. package/components/BottomNav/BottomNav.css +417 -0
  10. package/components/BottomNav/BottomNav.d.ts +73 -0
  11. package/components/BottomNav/BottomNav.js +100 -0
  12. package/components/Chart/AreaChart.js +67 -56
  13. package/components/Chart/BarChart.js +67 -56
  14. package/components/Chart/ComboChart.js +93 -82
  15. package/components/Chart/LineChart.js +59 -48
  16. package/components/Chart/PieChart.js +56 -45
  17. package/components/Chart/RadarChart.js +60 -49
  18. package/components/Chart/RadialChart.js +70 -59
  19. package/components/Chart/ScatterChart.js +61 -50
  20. package/components/Chart/StackedBarChart.js +64 -53
  21. package/components/Chart/Treemap.js +34 -23
  22. package/components/Nav/Nav.d.ts +11 -3
  23. package/components/Nav/Nav.js +3 -2
  24. package/components/SidebarPanel/SidebarPanel.css +291 -0
  25. package/components/SidebarPanel/SidebarPanel.d.ts +45 -0
  26. package/components/SidebarPanel/SidebarPanel.js +166 -0
  27. package/components/Stepper/Stepper.d.ts +3 -1
  28. package/components/Stepper/Stepper.js +1 -1
  29. package/components/TopAppBar/TopAppBar.css +464 -0
  30. package/components/TopAppBar/TopAppBar.d.ts +80 -0
  31. package/components/TopAppBar/TopAppBar.js +138 -0
  32. package/components/registry.json +25 -1
  33. package/components/registry.json.d.ts +25 -1
  34. package/components/registry.json.js +1 -1
  35. package/index.d.ts +3 -0
  36. package/index.js +6 -0
  37. package/package.json +5 -1
@@ -0,0 +1,166 @@
1
+ "use client";
2
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
3
+ import React, { useId, useState, useCallback } from "react";
4
+ import "./SidebarPanel.css";
5
+ import "../../fonts/material-symbols.css";
6
+ function groupRuns(items) {
7
+ const runs = [];
8
+ for (const item of items) {
9
+ const last = runs[runs.length - 1];
10
+ if (last && last.group === item.group) last.items.push(item);
11
+ else runs.push({ group: item.group, items: [item] });
12
+ }
13
+ return runs;
14
+ }
15
+ const SidebarPanel = React.forwardRef(
16
+ ({
17
+ items,
18
+ title,
19
+ activeKey,
20
+ defaultOpenKeys,
21
+ onCollapse,
22
+ onBack,
23
+ backLabel = "Back",
24
+ className = "",
25
+ ...rest
26
+ }, ref) => {
27
+ const baseClass = "ds-sidebar-panel";
28
+ const uid = useId();
29
+ const [openKeys, setOpenKeys] = useState(() => {
30
+ const initial = new Set(defaultOpenKeys ?? []);
31
+ if (activeKey) {
32
+ for (const item of items) {
33
+ if (item.children?.some((child) => child.key === activeKey)) initial.add(item.key);
34
+ }
35
+ }
36
+ return initial;
37
+ });
38
+ const toggle = useCallback((key) => {
39
+ setOpenKeys((prev) => {
40
+ const next = new Set(prev);
41
+ if (next.has(key)) next.delete(key);
42
+ else next.add(key);
43
+ return next;
44
+ });
45
+ }, []);
46
+ const renderLeaf = (item, nested) => {
47
+ const isActive = item.key === activeKey;
48
+ const classes = [
49
+ `${baseClass}__row`,
50
+ nested ? `${baseClass}__row--nested` : "",
51
+ isActive ? `${baseClass}__row--active` : ""
52
+ ].filter(Boolean).join(" ");
53
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
54
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: item.label }),
55
+ item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__badge`, children: item.badge })
56
+ ] });
57
+ return item.href ? /* @__PURE__ */ jsx(
58
+ "a",
59
+ {
60
+ className: classes,
61
+ href: item.href,
62
+ onClick: () => item.onClick?.(),
63
+ "aria-current": isActive ? "page" : void 0,
64
+ children: content
65
+ }
66
+ ) : /* @__PURE__ */ jsx(
67
+ "button",
68
+ {
69
+ type: "button",
70
+ className: classes,
71
+ onClick: () => item.onClick?.(),
72
+ "aria-current": isActive ? "page" : void 0,
73
+ children: content
74
+ }
75
+ );
76
+ };
77
+ const renderItem = (item) => {
78
+ const children = item.children ?? [];
79
+ if (children.length === 0) {
80
+ return /* @__PURE__ */ jsx("li", { children: renderLeaf(item, false) }, item.key);
81
+ }
82
+ const isOpen = openKeys.has(item.key);
83
+ const holdsActive = children.some((child) => child.key === activeKey);
84
+ const regionId = `${uid}-${item.key.replace(/\s+/g, "-")}`;
85
+ return /* @__PURE__ */ jsxs("li", { children: [
86
+ /* @__PURE__ */ jsxs(
87
+ "button",
88
+ {
89
+ type: "button",
90
+ className: [
91
+ `${baseClass}__row`,
92
+ `${baseClass}__row--parent`,
93
+ holdsActive && !isOpen ? `${baseClass}__row--holds-active` : ""
94
+ ].filter(Boolean).join(" "),
95
+ "aria-expanded": isOpen,
96
+ "aria-controls": regionId,
97
+ onClick: () => {
98
+ toggle(item.key);
99
+ item.onClick?.();
100
+ },
101
+ children: [
102
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: item.label }),
103
+ item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__badge`, children: item.badge }),
104
+ /* @__PURE__ */ jsx(
105
+ "span",
106
+ {
107
+ className: [
108
+ `${baseClass}__chevron material-symbols-rounded`,
109
+ isOpen ? `${baseClass}__chevron--open` : ""
110
+ ].filter(Boolean).join(" "),
111
+ "aria-hidden": "true",
112
+ children: "expand_more"
113
+ }
114
+ )
115
+ ]
116
+ }
117
+ ),
118
+ /* @__PURE__ */ jsx(
119
+ "div",
120
+ {
121
+ id: regionId,
122
+ className: [`${baseClass}__nested`, isOpen ? `${baseClass}__nested--open` : ""].filter(Boolean).join(" "),
123
+ inert: !isOpen,
124
+ children: /* @__PURE__ */ jsx("ul", { className: `${baseClass}__nested-list`, children: children.map((child) => /* @__PURE__ */ jsx("li", { children: renderLeaf(child, true) }, child.key)) })
125
+ }
126
+ )
127
+ ] }, item.key);
128
+ };
129
+ return /* @__PURE__ */ jsxs(
130
+ "nav",
131
+ {
132
+ ...rest,
133
+ ref,
134
+ className: [baseClass, className].filter(Boolean).join(" "),
135
+ "aria-label": rest["aria-label"] ?? title ?? "Section navigation",
136
+ children: [
137
+ onBack && /* @__PURE__ */ jsxs("button", { type: "button", className: `${baseClass}__back`, onClick: onBack, children: [
138
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__back-icon material-symbols-rounded`, "aria-hidden": "true", children: "chevron_left" }),
139
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: backLabel })
140
+ ] }),
141
+ (title || onCollapse) && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
142
+ title && /* @__PURE__ */ jsx("h2", { className: `${baseClass}__title`, children: title }),
143
+ onCollapse && /* @__PURE__ */ jsx(
144
+ "button",
145
+ {
146
+ type: "button",
147
+ className: `${baseClass}__collapse`,
148
+ onClick: onCollapse,
149
+ "aria-label": title ? `Collapse ${title}` : "Collapse panel",
150
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "left_panel_close" })
151
+ }
152
+ )
153
+ ] }),
154
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, children: groupRuns(items).map((run, i) => /* @__PURE__ */ jsxs("div", { className: `${baseClass}__group`, children: [
155
+ run.group && /* @__PURE__ */ jsx("div", { className: `${baseClass}__group-label`, children: run.group }),
156
+ /* @__PURE__ */ jsx("ul", { className: `${baseClass}__list`, children: run.items.map(renderItem) })
157
+ ] }, `${run.group ?? ""}-${i}`)) })
158
+ ]
159
+ }
160
+ );
161
+ }
162
+ );
163
+ SidebarPanel.displayName = "SidebarPanel";
164
+ export {
165
+ SidebarPanel
166
+ };
@@ -35,7 +35,9 @@ export interface StepperProps extends StepperOwnProps, Omit<React.ComponentProps
35
35
  * connectors: completed steps show a check, the active step carries
36
36
  * `aria-current="step"`, and upcoming steps show their number.
37
37
  *
38
- * Forwards a ref to the `<ol>` and spreads unrecognised props onto it.
38
+ * The list is labelled "Progress" by default; pass `aria-label` to name it
39
+ * for its flow. Forwards a ref to the `<ol>` and spreads unrecognised props
40
+ * onto it.
39
41
  */
40
42
  export declare const Stepper: React.ForwardRefExoticComponent<StepperProps & React.RefAttributes<HTMLOListElement>>;
41
43
  export {};
@@ -18,7 +18,7 @@ const Stepper = React.forwardRef(
18
18
  orientation === "vertical" && `${baseClass}--vertical`,
19
19
  className
20
20
  ].filter(Boolean).join(" ");
21
- return /* @__PURE__ */ jsx("ol", { ...rest, ref, className: classes, children: steps.map((step, index) => {
21
+ return /* @__PURE__ */ jsx("ol", { "aria-label": "Progress", ...rest, ref, className: classes, children: steps.map((step, index) => {
22
22
  const status = index < activeStep ? "complete" : index === activeStep ? "active" : "upcoming";
23
23
  const interactive = Boolean(onStepClick) && status !== "upcoming";
24
24
  const indicator = /* @__PURE__ */ jsx("span", { className: `${baseClass}__indicator`, "aria-hidden": "true", children: status === "complete" ? /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", children: "check" }) : index + 1 });
@@ -0,0 +1,464 @@
1
+ /* ============================================
2
+ TOP APP BAR — the header of a mobile screen
3
+ Three platforms share one markup:
4
+ default the Rift bar, fully on-token
5
+ ios the iOS 26 navigation bar
6
+ android the Material 3 Expressive top app bar
7
+ A row (leading control, title, trailing
8
+ actions) and, for medium and large sizes, an
9
+ expanded title that collapses on scroll.
10
+ ============================================ */
11
+
12
+ /* ============================================
13
+ BASE (shared by every platform)
14
+ ============================================ */
15
+ .ds-top-app-bar {
16
+ position: relative;
17
+ box-sizing: border-box;
18
+ width: 100%;
19
+ /* The status-bar area on notched phones; zero elsewhere. */
20
+ padding-top: env(safe-area-inset-top, 0px);
21
+ color: var(--color-text-primary);
22
+ transition: background-color var(--motion-duration-base) var(--motion-ease-standard);
23
+ }
24
+
25
+ .ds-top-app-bar--sticky {
26
+ position: sticky;
27
+ top: 0;
28
+ z-index: 10;
29
+ }
30
+
31
+ .ds-top-app-bar__row {
32
+ position: relative;
33
+ display: grid;
34
+ grid-template-columns: auto minmax(0, 1fr) auto;
35
+ align-items: center;
36
+ box-sizing: border-box;
37
+ }
38
+
39
+ /* Centred titles hold the middle column however wide either side is. */
40
+ .ds-top-app-bar--align-center .ds-top-app-bar__row {
41
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
42
+ }
43
+
44
+ .ds-top-app-bar__leading,
45
+ .ds-top-app-bar__trailing {
46
+ display: flex;
47
+ align-items: center;
48
+ min-width: 0;
49
+ }
50
+
51
+ .ds-top-app-bar__trailing {
52
+ justify-content: flex-end;
53
+ }
54
+
55
+ .ds-top-app-bar__group {
56
+ display: flex;
57
+ align-items: center;
58
+ }
59
+
60
+ /* The bar's title, and the expanded title under it. */
61
+ .ds-top-app-bar__title,
62
+ .ds-top-app-bar__large-title {
63
+ display: flex;
64
+ flex-direction: column;
65
+ min-width: 0;
66
+ margin: 0;
67
+ font: inherit;
68
+ transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
69
+ }
70
+
71
+ .ds-top-app-bar--align-center .ds-top-app-bar__title {
72
+ align-items: center;
73
+ text-align: center;
74
+ }
75
+
76
+ .ds-top-app-bar__title--hidden {
77
+ opacity: 0;
78
+ }
79
+
80
+ .ds-top-app-bar__title-text,
81
+ .ds-top-app-bar__subtitle {
82
+ max-width: 100%;
83
+ overflow: hidden;
84
+ text-overflow: ellipsis;
85
+ white-space: nowrap;
86
+ }
87
+
88
+ .ds-top-app-bar__subtitle {
89
+ color: var(--color-text-secondary);
90
+ }
91
+
92
+ /* Expanded title: the grid-row trick collapses it to nothing without a
93
+ height guess, and the fade leads the fold. */
94
+ .ds-top-app-bar__expanded {
95
+ display: grid;
96
+ grid-template-rows: 1fr;
97
+ transition:
98
+ grid-template-rows var(--motion-duration-slow) var(--motion-ease-emphasized),
99
+ opacity var(--motion-duration-base) var(--motion-ease-standard);
100
+ }
101
+
102
+ .ds-top-app-bar--scrolled .ds-top-app-bar__expanded {
103
+ grid-template-rows: 0fr;
104
+ opacity: 0;
105
+ }
106
+
107
+ .ds-top-app-bar__expanded-inner {
108
+ min-height: 0;
109
+ overflow: hidden;
110
+ }
111
+
112
+ /* Icon buttons. */
113
+ .ds-top-app-bar__button {
114
+ position: relative;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ flex-shrink: 0;
119
+ padding: 0;
120
+ border: none;
121
+ background: transparent;
122
+ color: inherit;
123
+ text-decoration: none;
124
+ cursor: pointer;
125
+ -webkit-tap-highlight-color: transparent;
126
+ transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
127
+ }
128
+
129
+ .ds-top-app-bar__button:focus-visible {
130
+ outline: 2px solid var(--color-action-primary-bg);
131
+ outline-offset: -2px;
132
+ }
133
+
134
+ .ds-top-app-bar__icon {
135
+ --icon-size: var(--icon-size-600);
136
+ display: flex;
137
+ color: inherit;
138
+ }
139
+
140
+ .ds-top-app-bar__badge {
141
+ position: absolute;
142
+ top: 25%;
143
+ right: 25%;
144
+ width: 6px;
145
+ height: 6px;
146
+ border-radius: var(--radius-pill);
147
+ background-color: var(--color-status-error-icon);
148
+ }
149
+
150
+ /* The overflow menu's own wrapper takes no room of its own. */
151
+ .ds-top-app-bar__overflow {
152
+ display: flex;
153
+ }
154
+
155
+ .ds-top-app-bar .material-symbols-rounded {
156
+ color: inherit;
157
+ }
158
+
159
+ /* ============================================
160
+ DEFAULT — the Rift bar
161
+ ============================================ */
162
+ .ds-top-app-bar--default {
163
+ background-color: var(--color-bg-page-primary);
164
+ /* The hairline is present from the start, transparent until content
165
+ slides under the bar, so the scrolled state moves no geometry. */
166
+ border-bottom: var(--border-025) solid transparent;
167
+ transition:
168
+ background-color var(--motion-duration-base) var(--motion-ease-standard),
169
+ border-color var(--motion-duration-base) var(--motion-ease-standard);
170
+ }
171
+
172
+ .ds-top-app-bar--default.ds-top-app-bar--scrolled {
173
+ border-bottom-color: var(--color-bg-container-border);
174
+ }
175
+
176
+ .ds-top-app-bar--default .ds-top-app-bar__row {
177
+ min-height: 56px;
178
+ gap: var(--gap-100);
179
+ padding: 0 var(--padding-200);
180
+ }
181
+
182
+ .ds-top-app-bar--default .ds-top-app-bar__leading:empty {
183
+ width: var(--padding-200);
184
+ }
185
+
186
+ .ds-top-app-bar--default .ds-top-app-bar__button {
187
+ width: 40px;
188
+ height: 40px;
189
+ border-radius: var(--radius-pill);
190
+ }
191
+
192
+ .ds-top-app-bar--default .ds-top-app-bar__button:hover {
193
+ background-color: var(--color-action-passive-bg-hover);
194
+ }
195
+
196
+ .ds-top-app-bar--default .ds-top-app-bar__title {
197
+ padding: 0 var(--padding-100);
198
+ }
199
+
200
+ .ds-top-app-bar--default .ds-top-app-bar__title-text {
201
+ font-family: var(--font-title-body-family);
202
+ font-size: var(--font-title-body-size);
203
+ font-weight: var(--font-title-body-weight);
204
+ line-height: var(--font-title-body-line-height);
205
+ letter-spacing: var(--font-title-body-letter-spacing);
206
+ }
207
+
208
+ .ds-top-app-bar--default .ds-top-app-bar__subtitle {
209
+ font-family: var(--font-paragraph-sm-family);
210
+ font-size: var(--font-paragraph-sm-size);
211
+ font-weight: var(--font-paragraph-sm-weight);
212
+ line-height: var(--font-paragraph-sm-line-height);
213
+ }
214
+
215
+ .ds-top-app-bar--default .ds-top-app-bar__large-title {
216
+ gap: var(--gap-100);
217
+ padding: var(--padding-100) var(--padding-400) var(--padding-400);
218
+ }
219
+
220
+ .ds-top-app-bar--default.ds-top-app-bar--medium .ds-top-app-bar__large-title .ds-top-app-bar__title-text {
221
+ font-family: var(--font-heading-2-family);
222
+ font-size: var(--font-heading-2-size);
223
+ font-weight: var(--font-heading-2-weight);
224
+ line-height: var(--font-heading-2-line-height);
225
+ letter-spacing: var(--font-heading-2-letter-spacing);
226
+ }
227
+
228
+ .ds-top-app-bar--default.ds-top-app-bar--large .ds-top-app-bar__large-title .ds-top-app-bar__title-text {
229
+ font-family: var(--font-heading-1-family);
230
+ font-size: var(--font-heading-1-size);
231
+ font-weight: var(--font-heading-1-weight);
232
+ line-height: var(--font-heading-1-line-height);
233
+ letter-spacing: var(--font-heading-1-letter-spacing);
234
+ }
235
+
236
+ /* ============================================
237
+ iOS — the iOS 26 navigation bar
238
+ ds-allow(platform): iOS 26 navigation-bar geometry and type — the 44pt
239
+ row with 16pt margins, 44pt glass circle buttons and the grouped glass
240
+ capsule, the 17pt semibold inline title and 34pt bold large title in
241
+ the system face, the glass fill, blur, saturation and white rim, and
242
+ the blurred scroll edge — restated from the platform so the mock reads
243
+ as system chrome.
244
+ ============================================ */
245
+ .ds-top-app-bar--ios {
246
+ --ds-top-app-bar-glass: color-mix(in srgb, var(--color-bg-container-primary) 58%, transparent);
247
+ --ds-top-app-bar-glass-filter: blur(16px) saturate(180%);
248
+ --ds-top-app-bar-glass-rim:
249
+ inset 0 1px 0 0 rgb(255 255 255 / 0.55),
250
+ inset 0 0 0 0.5px rgb(255 255 255 / 0.28),
251
+ 0 6px 24px rgb(0 0 0 / 0.12),
252
+ 0 1px 3px rgb(0 0 0 / 0.08);
253
+ background: transparent;
254
+ font-family: system-ui, -apple-system, 'SF Pro Text', sans-serif;
255
+ }
256
+
257
+ /* The scroll edge: no bar fill and no hairline, just the content under
258
+ the bar blurring and fading out toward the top edge. */
259
+ .ds-top-app-bar--ios::before {
260
+ content: '';
261
+ position: absolute;
262
+ inset: 0 0 -16px;
263
+ z-index: -1;
264
+ background: linear-gradient(
265
+ to bottom,
266
+ color-mix(in srgb, var(--color-bg-page-primary) 80%, transparent),
267
+ transparent
268
+ );
269
+ -webkit-backdrop-filter: blur(6px);
270
+ backdrop-filter: blur(6px);
271
+ -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent);
272
+ mask-image: linear-gradient(to bottom, #000 55%, transparent);
273
+ opacity: 0;
274
+ pointer-events: none;
275
+ transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
276
+ }
277
+
278
+ .ds-top-app-bar--ios.ds-top-app-bar--scrolled::before {
279
+ opacity: 1;
280
+ }
281
+
282
+ .ds-top-app-bar--ios .ds-top-app-bar__row {
283
+ min-height: 44px;
284
+ gap: 8px;
285
+ padding: 6px 16px;
286
+ }
287
+
288
+ .ds-top-app-bar--ios .ds-top-app-bar__button {
289
+ width: 44px;
290
+ height: 44px;
291
+ border-radius: 999px;
292
+ }
293
+
294
+ /* A lone leading button and a lone trailing button are their own glass
295
+ circles; two or more trailing buttons share one capsule. */
296
+ .ds-top-app-bar--ios .ds-top-app-bar__button--nav,
297
+ .ds-top-app-bar--ios .ds-top-app-bar__group {
298
+ border-radius: 999px;
299
+ background-color: var(--ds-top-app-bar-glass);
300
+ box-shadow: var(--ds-top-app-bar-glass-rim);
301
+ -webkit-backdrop-filter: var(--ds-top-app-bar-glass-filter);
302
+ backdrop-filter: var(--ds-top-app-bar-glass-filter);
303
+ }
304
+
305
+ .ds-top-app-bar--ios .ds-top-app-bar__group {
306
+ padding: 0 2px;
307
+ }
308
+
309
+ .ds-top-app-bar--ios .ds-top-app-bar__group .ds-top-app-bar__button {
310
+ width: 40px;
311
+ }
312
+
313
+ .ds-top-app-bar--ios .ds-top-app-bar__group:has(> :only-child) {
314
+ padding: 0;
315
+ }
316
+
317
+ .ds-top-app-bar--ios .ds-top-app-bar__group:has(> :only-child) .ds-top-app-bar__button {
318
+ width: 44px;
319
+ }
320
+
321
+ /* SF Symbols in a bar draw at a medium weight; the icon font's default
322
+ 400 read spindly inside the glass circles. */
323
+ .ds-top-app-bar--ios .ds-top-app-bar__icon {
324
+ font-variation-settings: 'wght' 500;
325
+ }
326
+
327
+ .ds-top-app-bar--ios .ds-top-app-bar__button--nav .ds-top-app-bar__icon {
328
+ /* The chevron's glyph sits right of centre in its box; a nudge
329
+ centres it optically in the circle. */
330
+ transform: translateX(-1px);
331
+ }
332
+
333
+ .ds-top-app-bar--ios .ds-top-app-bar__title-text {
334
+ font-size: 17px;
335
+ font-weight: 600;
336
+ line-height: 22px;
337
+ letter-spacing: -0.02em;
338
+ }
339
+
340
+ .ds-top-app-bar--ios .ds-top-app-bar__subtitle {
341
+ font-size: 12px;
342
+ font-weight: 400;
343
+ line-height: 16px;
344
+ }
345
+
346
+ .ds-top-app-bar--ios .ds-top-app-bar__large-title {
347
+ padding: 2px 16px 8px;
348
+ }
349
+
350
+ .ds-top-app-bar--ios .ds-top-app-bar__large-title .ds-top-app-bar__title-text {
351
+ font-size: 34px;
352
+ font-weight: 700;
353
+ line-height: 41px;
354
+ letter-spacing: 0.01em;
355
+ }
356
+
357
+ .ds-top-app-bar--ios .ds-top-app-bar__large-title .ds-top-app-bar__subtitle {
358
+ font-size: 15px;
359
+ line-height: 20px;
360
+ }
361
+
362
+ /* ============================================
363
+ ANDROID — Material 3 Expressive top app bar
364
+ ds-allow(platform): the M3 Expressive app bar — a 64dp row with 4dp
365
+ edge padding, 48dp touch targets around 40dp icon-button containers,
366
+ the 22/28 title and 12/16 subtitle, the 28/36 medium and 36/44 large
367
+ flexible titles, and the platform face (Google Sans Flex, then Roboto)
368
+ — restated from the Material token set so the mock reads as system
369
+ chrome. The surface roles map onto the page and container tokens.
370
+ ============================================ */
371
+ .ds-top-app-bar--android {
372
+ background-color: var(--color-bg-page-primary);
373
+ font-family: 'Google Sans Flex', Roboto, system-ui, sans-serif;
374
+ }
375
+
376
+ /* On scroll the surface lifts to the container role. */
377
+ .ds-top-app-bar--android.ds-top-app-bar--scrolled {
378
+ background-color: var(--color-bg-container-secondary);
379
+ }
380
+
381
+ .ds-top-app-bar--android .ds-top-app-bar__row {
382
+ min-height: 64px;
383
+ padding: 0 4px;
384
+ }
385
+
386
+ .ds-top-app-bar--android .ds-top-app-bar__leading:empty {
387
+ width: 12px;
388
+ }
389
+
390
+ .ds-top-app-bar--android .ds-top-app-bar__button {
391
+ width: 48px;
392
+ height: 48px;
393
+ color: var(--color-text-secondary);
394
+ }
395
+
396
+ .ds-top-app-bar--android .ds-top-app-bar__button--nav {
397
+ color: var(--color-text-primary);
398
+ }
399
+
400
+ /* The 40dp state layer inside the 48dp target. */
401
+ .ds-top-app-bar--android .ds-top-app-bar__button::before {
402
+ content: '';
403
+ position: absolute;
404
+ inset: 4px;
405
+ border-radius: 999px;
406
+ background-color: var(--color-text-primary);
407
+ opacity: 0;
408
+ transition: opacity var(--motion-duration-fast) var(--motion-ease-standard);
409
+ }
410
+
411
+ .ds-top-app-bar--android .ds-top-app-bar__button:hover::before {
412
+ opacity: 0.08;
413
+ }
414
+
415
+ .ds-top-app-bar--android .ds-top-app-bar__button:active::before {
416
+ opacity: 0.12;
417
+ }
418
+
419
+ .ds-top-app-bar--android .ds-top-app-bar__title {
420
+ padding: 0 4px;
421
+ }
422
+
423
+ .ds-top-app-bar--android .ds-top-app-bar__title-text {
424
+ font-size: 22px;
425
+ font-weight: 400;
426
+ line-height: 28px;
427
+ }
428
+
429
+ .ds-top-app-bar--android .ds-top-app-bar__subtitle {
430
+ font-size: 12px;
431
+ font-weight: 500;
432
+ line-height: 16px;
433
+ letter-spacing: 0.5px;
434
+ }
435
+
436
+ .ds-top-app-bar--android .ds-top-app-bar__large-title {
437
+ padding: 0 16px 12px;
438
+ }
439
+
440
+ .ds-top-app-bar--android.ds-top-app-bar--medium .ds-top-app-bar__large-title .ds-top-app-bar__title-text {
441
+ font-size: 28px;
442
+ line-height: 36px;
443
+ }
444
+
445
+ .ds-top-app-bar--android.ds-top-app-bar--medium .ds-top-app-bar__large-title .ds-top-app-bar__subtitle {
446
+ font-size: 14px;
447
+ line-height: 20px;
448
+ letter-spacing: 0.1px;
449
+ }
450
+
451
+ .ds-top-app-bar--android.ds-top-app-bar--large .ds-top-app-bar__large-title {
452
+ padding-top: 4px;
453
+ }
454
+
455
+ .ds-top-app-bar--android.ds-top-app-bar--large .ds-top-app-bar__large-title .ds-top-app-bar__title-text {
456
+ font-size: 36px;
457
+ line-height: 44px;
458
+ }
459
+
460
+ .ds-top-app-bar--android.ds-top-app-bar--large .ds-top-app-bar__large-title .ds-top-app-bar__subtitle {
461
+ font-size: 16px;
462
+ line-height: 24px;
463
+ letter-spacing: 0.15px;
464
+ }