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.
- package/LICENSE +1 -1
- package/README.md +11 -3
- package/components/AppLayout/AppLayout.css +13 -3
- package/components/AppLayout/AppLayout.d.ts +5 -1
- package/components/AppLayout/AppLayout.js +4 -0
- package/components/AppSidebar/AppSidebar.css +237 -0
- package/components/AppSidebar/AppSidebar.d.ts +32 -3
- package/components/AppSidebar/AppSidebar.js +187 -16
- package/components/BottomNav/BottomNav.css +417 -0
- package/components/BottomNav/BottomNav.d.ts +73 -0
- package/components/BottomNav/BottomNav.js +100 -0
- package/components/Chart/AreaChart.js +67 -56
- package/components/Chart/BarChart.js +67 -56
- package/components/Chart/ComboChart.js +93 -82
- package/components/Chart/LineChart.js +59 -48
- package/components/Chart/PieChart.js +56 -45
- package/components/Chart/RadarChart.js +60 -49
- package/components/Chart/RadialChart.js +70 -59
- package/components/Chart/ScatterChart.js +61 -50
- package/components/Chart/StackedBarChart.js +64 -53
- package/components/Chart/Treemap.js +34 -23
- package/components/Nav/Nav.d.ts +11 -3
- package/components/Nav/Nav.js +3 -2
- package/components/SidebarPanel/SidebarPanel.css +291 -0
- package/components/SidebarPanel/SidebarPanel.d.ts +45 -0
- package/components/SidebarPanel/SidebarPanel.js +166 -0
- package/components/Stepper/Stepper.d.ts +3 -1
- package/components/Stepper/Stepper.js +1 -1
- package/components/TopAppBar/TopAppBar.css +464 -0
- package/components/TopAppBar/TopAppBar.d.ts +80 -0
- package/components/TopAppBar/TopAppBar.js +138 -0
- package/components/registry.json +25 -1
- package/components/registry.json.d.ts +25 -1
- package/components/registry.json.js +1 -1
- package/index.d.ts +3 -0
- package/index.js +6 -0
- 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
|
-
*
|
|
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
|
+
}
|