rift-ds 1.18.0 → 1.19.1
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/behaviors/useAnchoredPosition.d.ts +3 -1
- package/behaviors/useAnchoredPosition.js +1 -0
- package/behaviors/useTopLayer.d.ts +35 -0
- package/behaviors/useTopLayer.js +22 -0
- package/chat/ChatLauncher.js +23 -0
- package/chat/ChatWidget.css +8 -3
- package/components/AiButton/AiButton.css +11 -0
- package/components/AiButton/AiButton.d.ts +2 -0
- package/components/AiButton/AiButton.js +3 -1
- package/components/AppSidebar/AppSidebar.css +59 -9
- package/components/AppSidebar/AppSidebar.d.ts +2 -2
- package/components/AppSidebar/AppSidebar.js +15 -9
- package/components/Attachment/AttachmentViewer.js +2 -0
- package/components/BentoTile/BentoTile.js +2 -0
- package/components/Chart/BarChart.js +1 -0
- package/components/Chart/Chart.css +6 -0
- package/components/Chart/ComboChart.js +1 -0
- package/components/Chart/StackedBarChart.js +1 -0
- package/components/CommandPaletteTrigger/CommandPaletteTrigger.css +13 -1
- package/components/Composer/Composer.css +72 -0
- package/components/Composer/Composer.d.ts +5 -1
- package/components/Composer/Composer.js +8 -1
- package/components/DataTable/DataTable.css +7 -3
- package/components/Popover/Popover.d.ts +2 -0
- package/components/Popover/Popover.js +2 -1
- package/components/SegmentedControl/SegmentedControl.css +9 -1
- package/components/SegmentedControl/SegmentedControl.js +2 -0
- package/components/SidebarPanel/SidebarPanel.css +10 -2
- package/components/SidebarPanel/SidebarPanel.d.ts +1 -1
- package/components/SidebarPanel/SidebarPanel.js +5 -2
- package/components/SiteHeader/SiteHeader.css +63 -5
- package/components/SiteHeader/SiteHeader.d.ts +11 -3
- package/components/SiteHeader/SiteHeader.js +51 -16
- package/components/SourceChip/SourceChip.css +5 -1
- package/components/SourceChip/SourceChip.js +3 -0
- package/components/TableToolbar/TableToolbar.css +132 -2
- package/components/TableToolbar/TableToolbar.d.ts +52 -0
- package/components/TableToolbar/TableToolbar.js +239 -39
- package/components/ThemeToggle/ThemeToggle.css +26 -0
- package/components/ThemeToggle/ThemeToggle.d.ts +11 -1
- package/components/ThemeToggle/ThemeToggle.js +36 -5
- package/components/Toast/Toast.css +4 -0
- package/components/Toast/Toast.d.ts +9 -0
- package/components/Toast/Toast.js +9 -2
- package/components/Tooltip/Tooltip.css +9 -4
- package/components/Tooltip/Tooltip.d.ts +4 -2
- package/components/Tooltip/Tooltip.js +16 -4
- package/index.js +2 -1
- package/package.json +1 -1
- package/tokens/motion.d.ts +2 -0
- package/tokens/motion.js +2 -0
- package/tokens/presets/mono.css +14 -0
- package/tokens/presets/zest.css +2 -0
- package/tokens/tokens-dark.css +1 -1
- package/tokens/tokens-light.css +19 -17
|
@@ -14,6 +14,7 @@ const Popover = React.forwardRef(
|
|
|
14
14
|
size = "default",
|
|
15
15
|
surface = "panel",
|
|
16
16
|
position = "bottom",
|
|
17
|
+
align = "start",
|
|
17
18
|
trigger = "click",
|
|
18
19
|
open: controlledOpen,
|
|
19
20
|
defaultOpen = false,
|
|
@@ -77,7 +78,7 @@ const Popover = React.forwardRef(
|
|
|
77
78
|
anchorRef: triggerRef,
|
|
78
79
|
panelRef,
|
|
79
80
|
side: position,
|
|
80
|
-
align
|
|
81
|
+
align,
|
|
81
82
|
gapProperty: "--gap-050"
|
|
82
83
|
});
|
|
83
84
|
const baseClass = "ds-popover";
|
|
@@ -247,7 +247,9 @@
|
|
|
247
247
|
============================================ */
|
|
248
248
|
|
|
249
249
|
.ds-segmented-control__label--tip {
|
|
250
|
-
/* Out of flow at rest; the behaviour makes it fixed once it shows
|
|
250
|
+
/* Out of flow at rest; the behaviour makes it fixed once it shows, and
|
|
251
|
+
lifts it into the top layer (src/behaviors/useTopLayer), above every
|
|
252
|
+
z-index on the page. */
|
|
251
253
|
position: absolute;
|
|
252
254
|
z-index: var(--layer-floating);
|
|
253
255
|
padding: var(--padding-150) var(--padding-200);
|
|
@@ -270,6 +272,12 @@
|
|
|
270
272
|
transform var(--motion-duration-fast) var(--motion-ease-standard);
|
|
271
273
|
}
|
|
272
274
|
|
|
275
|
+
/* The tip sits on the page surface, not the selected segment's fill, so it
|
|
276
|
+
keeps its own ink when the active-label rules above would recolour it. */
|
|
277
|
+
.ds-segmented-control .ds-segmented-control__segment .ds-segmented-control__label--tip {
|
|
278
|
+
color: var(--color-text-primary);
|
|
279
|
+
}
|
|
280
|
+
|
|
273
281
|
/* --motion-duration-slow matches MOTION_HOVER_SHOW_DELAY_MS, the hover-intent
|
|
274
282
|
delay Tooltip uses in JS, so the label does not flash while the pointer
|
|
275
283
|
passes through. Hiding takes no delay, like a tooltip leaving. */
|
|
@@ -6,6 +6,7 @@ import "../../behaviors/visually-hidden.css";
|
|
|
6
6
|
import "../../fonts/material-symbols.css";
|
|
7
7
|
import { mirrorClass, logicalArrowKey } from "../../behaviors/direction.js";
|
|
8
8
|
import { useAnchoredPosition } from "../../behaviors/useAnchoredPosition.js";
|
|
9
|
+
import { useTopLayer } from "../../behaviors/useTopLayer.js";
|
|
9
10
|
const SegmentedControl = React.forwardRef(
|
|
10
11
|
({
|
|
11
12
|
segments,
|
|
@@ -197,6 +198,7 @@ const SegmentedControl = React.forwardRef(
|
|
|
197
198
|
tipLabelRef.current = label;
|
|
198
199
|
setTip([...strip.querySelectorAll(`.${baseClass}__segment`)].indexOf(segment));
|
|
199
200
|
};
|
|
201
|
+
useTopLayer({ open: tip !== null, panelRef: tipLabelRef, target: tip });
|
|
200
202
|
useAnchoredPosition({
|
|
201
203
|
open: tip !== null,
|
|
202
204
|
target: tip,
|
|
@@ -223,11 +223,19 @@
|
|
|
223
223
|
|
|
224
224
|
/* On a filled row the pill steps back to the page ground, or it would
|
|
225
225
|
share the row's fill and dissolve into it. */
|
|
226
|
-
.ds-sidebar-panel__row--active .ds-sidebar-panel__badge,
|
|
227
|
-
.ds-sidebar-panel__row:hover .ds-sidebar-panel__badge {
|
|
226
|
+
.ds-sidebar-panel__row--active .ds-sidebar-panel__badge:not(.ds-sidebar-panel__badge--count),
|
|
227
|
+
.ds-sidebar-panel__row:hover .ds-sidebar-panel__badge:not(.ds-sidebar-panel__badge--count) {
|
|
228
228
|
background-color: var(--color-bg-page-primary);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/* A count: a solid pill on the count badge pair, the "how many" mark the
|
|
232
|
+
rail beside it, BottomNav and TableToolbar share, which reads on any
|
|
233
|
+
row fill. A word such as "New" keeps the quiet pill above. */
|
|
234
|
+
.ds-sidebar-panel__badge--count {
|
|
235
|
+
color: var(--color-count-badge-text);
|
|
236
|
+
background-color: var(--color-count-badge-bg);
|
|
237
|
+
}
|
|
238
|
+
|
|
231
239
|
.ds-sidebar-panel__chevron {
|
|
232
240
|
--icon-size: var(--icon-size-500);
|
|
233
241
|
flex-shrink: 0;
|
|
@@ -9,7 +9,7 @@ export interface SidebarPanelItem {
|
|
|
9
9
|
href?: string;
|
|
10
10
|
/** Click handler — also fires on a link item, so a consumer can route client-side */
|
|
11
11
|
onClick?: () => void;
|
|
12
|
-
/** Trailing
|
|
12
|
+
/** Trailing pill, e.g. unread items. A count takes the count badge colours; a word stays a quiet pill */
|
|
13
13
|
badge?: string | number;
|
|
14
14
|
/** Group heading this item sits under; consecutive items sharing a group render beneath one heading */
|
|
15
15
|
group?: string;
|
|
@@ -4,6 +4,9 @@ import React, { useId, useState, useCallback } from "react";
|
|
|
4
4
|
import { useLinkComponent } from "../../behaviors/LinkProvider.js";
|
|
5
5
|
import "./SidebarPanel.css";
|
|
6
6
|
import "../../fonts/material-symbols.css";
|
|
7
|
+
function isCount(badge) {
|
|
8
|
+
return typeof badge === "number" || /^\d+\+?$/.test(badge.trim());
|
|
9
|
+
}
|
|
7
10
|
function groupRuns(items) {
|
|
8
11
|
const runs = [];
|
|
9
12
|
for (const item of items) {
|
|
@@ -56,7 +59,7 @@ const SidebarPanel = React.forwardRef(
|
|
|
56
59
|
].filter(Boolean).join(" ");
|
|
57
60
|
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
58
61
|
/* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: item.label }),
|
|
59
|
-
item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__badge`, children: item.badge })
|
|
62
|
+
item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__badge${isCount(item.badge) ? ` ${baseClass}__badge--count` : ""}`, children: item.badge })
|
|
60
63
|
] });
|
|
61
64
|
return item.href ? /* @__PURE__ */ jsx(
|
|
62
65
|
Link,
|
|
@@ -104,7 +107,7 @@ const SidebarPanel = React.forwardRef(
|
|
|
104
107
|
},
|
|
105
108
|
children: [
|
|
106
109
|
/* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: item.label }),
|
|
107
|
-
item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__badge`, children: item.badge }),
|
|
110
|
+
item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__badge${isCount(item.badge) ? ` ${baseClass}__badge--count` : ""}`, children: item.badge }),
|
|
108
111
|
/* @__PURE__ */ jsx(
|
|
109
112
|
"span",
|
|
110
113
|
{
|
|
@@ -159,11 +159,31 @@
|
|
|
159
159
|
rotate: 180deg;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
/* The menu button only exists once the bar folds.
|
|
162
|
+
/* The menu button only exists once the bar folds. It wears the track
|
|
163
|
+
recipe the CommandPaletteTrigger, the ThemePicker's trigger and the
|
|
164
|
+
ThemeToggle's cycle circle wear (container fill, hairline border, the
|
|
165
|
+
track's curve), so the circles at the bar's end read as one set, and it
|
|
166
|
+
hovers as they do: the fill steps up, the glyph holds its colour. */
|
|
163
167
|
.ds-site-header__menu-button {
|
|
164
168
|
display: none;
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
.ds-site-header .ds-site-header__menu-button {
|
|
172
|
+
background-color: var(--color-bg-container-primary);
|
|
173
|
+
border: var(--border-025) solid var(--color-bg-container-border);
|
|
174
|
+
border-radius: calc(var(--radius-pill) + var(--padding-100));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.ds-site-header .ds-site-header__menu-button:not(.ds-circular-button--disabled):hover {
|
|
178
|
+
background-color: var(--color-bg-container-secondary);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.ds-site-header .ds-site-header__menu-button .ds-circular-button__icon,
|
|
182
|
+
.ds-site-header .ds-site-header__menu-button:not(.ds-circular-button--disabled):hover .ds-circular-button__icon,
|
|
183
|
+
.ds-site-header .ds-site-header__menu-button:not(.ds-circular-button--disabled):active .ds-circular-button__icon {
|
|
184
|
+
color: var(--color-action-icon-default);
|
|
185
|
+
}
|
|
186
|
+
|
|
167
187
|
/* While the drawer is open its close button stands in this exact spot, on
|
|
168
188
|
the top layer; the button underneath would only show through the scrim. */
|
|
169
189
|
.ds-site-header__menu-button--hidden {
|
|
@@ -188,6 +208,16 @@
|
|
|
188
208
|
pointer-events: auto;
|
|
189
209
|
}
|
|
190
210
|
|
|
211
|
+
/* Condensed: `condensedActions` take the place of `actions`. Whichever set
|
|
212
|
+
is not showing leaves the row but stays measurable (hidden, out of flow,
|
|
213
|
+
inert), as the folded pills do. */
|
|
214
|
+
.ds-site-header--condensed .ds-site-header__actions:not(.ds-site-header__actions--condensed),
|
|
215
|
+
.ds-site-header:not(.ds-site-header--condensed) .ds-site-header__actions--condensed {
|
|
216
|
+
position: absolute;
|
|
217
|
+
visibility: hidden;
|
|
218
|
+
pointer-events: none;
|
|
219
|
+
}
|
|
220
|
+
|
|
191
221
|
/* Folded: the pills and the wide actions leave the row but stay measurable
|
|
192
222
|
(hidden, out of flow, inert), and the menu button takes their place. */
|
|
193
223
|
.ds-site-header--folded .ds-site-header__nav,
|
|
@@ -207,8 +237,8 @@
|
|
|
207
237
|
|
|
208
238
|
/* Before the bar has measured itself (the first frame, which a
|
|
209
239
|
server-rendered page shows before its script runs), it folds by CSS below
|
|
210
|
-
the `initialFold` step,
|
|
211
|
-
actions. The first measurement replaces this in the same commit as
|
|
240
|
+
the `initialFold` step, with `condensedActions` in place of `actions`, so
|
|
241
|
+
a phone never sees the pills spill past the actions. The first measurement replaces this in the same commit as
|
|
212
242
|
hydration. */
|
|
213
243
|
@media (max-width: 959px) {
|
|
214
244
|
.ds-site-header--prefold-tablet .ds-site-header__nav,
|
|
@@ -221,6 +251,18 @@
|
|
|
221
251
|
.ds-site-header--prefold-tablet .ds-site-header__menu-button {
|
|
222
252
|
display: inline-flex;
|
|
223
253
|
}
|
|
254
|
+
|
|
255
|
+
.ds-site-header--prefold-tablet .ds-site-header__actions:not(.ds-site-header__actions--condensed) {
|
|
256
|
+
position: absolute;
|
|
257
|
+
visibility: hidden;
|
|
258
|
+
pointer-events: none;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.ds-site-header.ds-site-header--prefold-tablet .ds-site-header__actions--condensed {
|
|
262
|
+
position: static;
|
|
263
|
+
visibility: visible;
|
|
264
|
+
pointer-events: auto;
|
|
265
|
+
}
|
|
224
266
|
}
|
|
225
267
|
|
|
226
268
|
@media (max-width: 1279px) {
|
|
@@ -234,6 +276,18 @@
|
|
|
234
276
|
.ds-site-header--prefold-mid .ds-site-header__menu-button {
|
|
235
277
|
display: inline-flex;
|
|
236
278
|
}
|
|
279
|
+
|
|
280
|
+
.ds-site-header--prefold-mid .ds-site-header__actions:not(.ds-site-header__actions--condensed) {
|
|
281
|
+
position: absolute;
|
|
282
|
+
visibility: hidden;
|
|
283
|
+
pointer-events: none;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.ds-site-header.ds-site-header--prefold-mid .ds-site-header__actions--condensed {
|
|
287
|
+
position: static;
|
|
288
|
+
visibility: visible;
|
|
289
|
+
pointer-events: auto;
|
|
290
|
+
}
|
|
237
291
|
}
|
|
238
292
|
|
|
239
293
|
/* The drawer: its brand row lands on the bar's own brand. SiteHeader
|
|
@@ -261,8 +315,9 @@
|
|
|
261
315
|
position: absolute;
|
|
262
316
|
inset-block-start: var(--ds-site-header-close-top, var(--padding-500));
|
|
263
317
|
inset-inline-start: var(--ds-site-header-close-start, auto);
|
|
264
|
-
|
|
265
|
-
|
|
318
|
+
/* The menu button's compact box, so the close lands exactly on it. */
|
|
319
|
+
width: var(--control-height-compact);
|
|
320
|
+
height: var(--control-height-compact);
|
|
266
321
|
/* ds-allow(color): on-scrim furniture, as Lightbox's: the scrim is dark in both themes, so the glyph is white in both. */
|
|
267
322
|
color: #ffffff;
|
|
268
323
|
opacity: 0;
|
|
@@ -273,6 +328,9 @@
|
|
|
273
328
|
turning into another. */
|
|
274
329
|
.ds-site-header__drawer .ds-drawer__close .material-symbols-rounded {
|
|
275
330
|
--icon-size: var(--icon-size-600);
|
|
331
|
+
/* The glyph takes the close's on-scrim white, not CircularButton's
|
|
332
|
+
icon colour, which is dark in light mode and vanished on the scrim. */
|
|
333
|
+
color: inherit;
|
|
276
334
|
}
|
|
277
335
|
|
|
278
336
|
.ds-site-header__drawer .ds-drawer__panel[data-state="open"] .ds-drawer__close {
|
|
@@ -46,11 +46,19 @@ type SiteHeaderOwnProps = {
|
|
|
46
46
|
sections: SiteHeaderSection[];
|
|
47
47
|
/** The path of the page being viewed, used to mark the current section, row and drawer item. */
|
|
48
48
|
currentHref?: string;
|
|
49
|
-
/** Controls at the bar's end that stay at every width, such as a search button. */
|
|
49
|
+
/** Controls at the bar's end that stay at every width, such as a search button; `condensedActions`, when given, stand in for them on the condensed step. */
|
|
50
50
|
actions?: React.ReactNode;
|
|
51
|
+
/**
|
|
52
|
+
* The same controls drawn narrower, such as an icon-only CommandPaletteTrigger and a
|
|
53
|
+
* `cycle` ThemeToggle. When the full row stops fitting, these take the place of
|
|
54
|
+
* `actions` (the header adds `ds-site-header--condensed`), and the pills fold to the
|
|
55
|
+
* menu button only when the condensed row does not fit either. Without it the
|
|
56
|
+
* header folds as soon as the full row stops fitting.
|
|
57
|
+
*/
|
|
58
|
+
condensedActions?: React.ReactNode;
|
|
51
59
|
/**
|
|
52
60
|
* Controls at the bar's end that show only while the pills fit, such as a
|
|
53
|
-
*
|
|
61
|
+
* Get started button. Once the bar folds to its menu button they leave it; put
|
|
54
62
|
* their drawer twin in `drawerFooter`.
|
|
55
63
|
*/
|
|
56
64
|
wideActions?: React.ReactNode;
|
|
@@ -113,5 +121,5 @@ export interface SiteHeaderProps extends SiteHeaderOwnProps, Omit<React.Componen
|
|
|
113
121
|
* same sections. The forwarded ref and any unrecognised props target the
|
|
114
122
|
* `<header>`.
|
|
115
123
|
*/
|
|
116
|
-
export declare function SiteHeader({ brand, sections, currentHref, actions, wideActions, drawerFooter, drawerOpen: drawerOpenProp, defaultDrawerOpen, onDrawerOpenChange, initialFold, sticky, tintAfter, linkComponent, navLabel, drawerOpenLabel, drawerTitle, drawerCloseLabel, className, ref, ...rest }: SiteHeaderProps): React.JSX.Element;
|
|
124
|
+
export declare function SiteHeader({ brand, sections, currentHref, actions, condensedActions, wideActions, drawerFooter, drawerOpen: drawerOpenProp, defaultDrawerOpen, onDrawerOpenChange, initialFold, sticky, tintAfter, linkComponent, navLabel, drawerOpenLabel, drawerTitle, drawerCloseLabel, className, ref, ...rest }: SiteHeaderProps): React.JSX.Element;
|
|
117
125
|
export {};
|
|
@@ -19,6 +19,7 @@ function SiteHeader({
|
|
|
19
19
|
sections,
|
|
20
20
|
currentHref,
|
|
21
21
|
actions,
|
|
22
|
+
condensedActions,
|
|
22
23
|
wideActions,
|
|
23
24
|
drawerFooter,
|
|
24
25
|
drawerOpen: drawerOpenProp,
|
|
@@ -45,12 +46,15 @@ function SiteHeader({
|
|
|
45
46
|
const navRef = useRef(null);
|
|
46
47
|
const wideRef = useRef(null);
|
|
47
48
|
const actionsRef = useRef(null);
|
|
49
|
+
const condensedActionsRef = useRef(null);
|
|
48
50
|
const panelRef = useRef(null);
|
|
49
51
|
const menuButtonRef = useRef(null);
|
|
50
52
|
const closeTimer = useRef(void 0);
|
|
51
53
|
const returningFocus = useRef(false);
|
|
54
|
+
const lockedGutter = useRef(0);
|
|
52
55
|
const [openId, setOpenId] = useState(null);
|
|
53
56
|
const [shownId, setShownId] = useState(null);
|
|
57
|
+
const [condensed, setCondensed] = useState(false);
|
|
54
58
|
const [folded, setFolded] = useState(false);
|
|
55
59
|
const [measured, setMeasured] = useState(false);
|
|
56
60
|
const [drawerOffset, setDrawerOffset] = useState({});
|
|
@@ -59,6 +63,7 @@ function SiteHeader({
|
|
|
59
63
|
const drawerOpen = drawerOpenProp ?? drawerOpenState;
|
|
60
64
|
const setDrawerOpen = useCallback(
|
|
61
65
|
(next) => {
|
|
66
|
+
lockedGutter.current = next ? window.innerWidth - document.documentElement.clientWidth : 0;
|
|
62
67
|
if (drawerOpenProp === void 0) setDrawerOpenState(next);
|
|
63
68
|
onDrawerOpenChange?.(next);
|
|
64
69
|
},
|
|
@@ -73,23 +78,33 @@ function SiteHeader({
|
|
|
73
78
|
const bar = barRef.current;
|
|
74
79
|
if (!bar) return;
|
|
75
80
|
const measure = () => {
|
|
76
|
-
const
|
|
77
|
-
const gap = parseFloat(style.columnGap) || 0;
|
|
81
|
+
const gap = parseFloat(getComputedStyle(bar).columnGap) || 0;
|
|
78
82
|
const navStart = navRef.current ? parseFloat(getComputedStyle(navRef.current).marginInlineStart) || 0 : 0;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
const rowWidth = (actionsEl) => {
|
|
84
|
+
const parts = [brandRef.current, navRef.current, wideRef.current, actionsEl];
|
|
85
|
+
const widths = parts.map((el) => el ? el.scrollWidth : 0);
|
|
86
|
+
const present = widths.filter((w) => w > 0).length;
|
|
87
|
+
return widths.reduce((sum, w) => sum + w, 0) + navStart + gap * Math.max(present - 1, 0);
|
|
88
|
+
};
|
|
89
|
+
const room = bar.clientWidth - lockedGutter.current;
|
|
90
|
+
const full = rowWidth(actionsRef.current);
|
|
91
|
+
const tight = condensedActionsRef.current ? rowWidth(condensedActionsRef.current) : full;
|
|
92
|
+
setCondensed(condensedActionsRef.current != null && full > room);
|
|
93
|
+
setFolded(tight > room);
|
|
84
94
|
setMeasured(true);
|
|
85
95
|
};
|
|
86
96
|
measure();
|
|
87
97
|
const observer = new ResizeObserver(measure);
|
|
88
|
-
[
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
[
|
|
99
|
+
bar,
|
|
100
|
+
brandRef.current,
|
|
101
|
+
navRef.current,
|
|
102
|
+
wideRef.current,
|
|
103
|
+
actionsRef.current,
|
|
104
|
+
condensedActionsRef.current
|
|
105
|
+
].forEach((el) => el && observer.observe(el));
|
|
91
106
|
return () => observer.disconnect();
|
|
92
|
-
}, [sections, actions, wideActions, brand]);
|
|
107
|
+
}, [sections, actions, condensedActions, wideActions, brand]);
|
|
93
108
|
useEffect(() => {
|
|
94
109
|
if (!sticky) return;
|
|
95
110
|
let frame = 0;
|
|
@@ -108,12 +123,11 @@ function SiteHeader({
|
|
|
108
123
|
};
|
|
109
124
|
}, [sticky, tintAfter]);
|
|
110
125
|
useEffect(() => () => clearTimeout(closeTimer.current), []);
|
|
111
|
-
|
|
112
|
-
if (!drawerOpen) return;
|
|
126
|
+
const placeDrawer = useCallback(() => {
|
|
113
127
|
const bar = barRef.current;
|
|
114
128
|
if (!bar) return;
|
|
115
129
|
const rtl = isRtl(bar);
|
|
116
|
-
const start = (rect) => rtl ?
|
|
130
|
+
const start = (rect) => rtl ? document.documentElement.clientWidth - rect.right : rect.left;
|
|
117
131
|
const barRect = bar.getBoundingClientRect();
|
|
118
132
|
const offsets = {
|
|
119
133
|
"--ds-site-header-height": `${bar.offsetHeight}px`,
|
|
@@ -126,7 +140,17 @@ function SiteHeader({
|
|
|
126
140
|
offsets["--ds-site-header-close-start"] = `${start(button)}px`;
|
|
127
141
|
}
|
|
128
142
|
setDrawerOffset(offsets);
|
|
129
|
-
}, [
|
|
143
|
+
}, []);
|
|
144
|
+
useLayoutEffect(() => {
|
|
145
|
+
if (!drawerOpen) {
|
|
146
|
+
lockedGutter.current = 0;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
placeDrawer();
|
|
150
|
+
}, [drawerOpen, placeDrawer]);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (drawerOpen) placeDrawer();
|
|
153
|
+
}, [drawerOpen, placeDrawer]);
|
|
130
154
|
const openSection = useCallback((id) => {
|
|
131
155
|
clearTimeout(closeTimer.current);
|
|
132
156
|
setOpenId(id);
|
|
@@ -173,6 +197,7 @@ function SiteHeader({
|
|
|
173
197
|
const classes = [
|
|
174
198
|
baseClass,
|
|
175
199
|
sticky && `${baseClass}--sticky`,
|
|
200
|
+
condensed && `${baseClass}--condensed`,
|
|
176
201
|
folded && `${baseClass}--folded`,
|
|
177
202
|
!measured && initialFold !== "never" && `${baseClass}--prefold-${initialFold}`,
|
|
178
203
|
className
|
|
@@ -247,7 +272,16 @@ function SiteHeader({
|
|
|
247
272
|
);
|
|
248
273
|
}) }),
|
|
249
274
|
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__end`, children: [
|
|
250
|
-
actions && /* @__PURE__ */ jsx("div", { ref: actionsRef, className: `${baseClass}__actions`, children: actions }),
|
|
275
|
+
actions && /* @__PURE__ */ jsx("div", { ref: actionsRef, className: `${baseClass}__actions`, inert: condensed, children: actions }),
|
|
276
|
+
condensedActions && /* @__PURE__ */ jsx(
|
|
277
|
+
"div",
|
|
278
|
+
{
|
|
279
|
+
ref: condensedActionsRef,
|
|
280
|
+
className: `${baseClass}__actions ${baseClass}__actions--condensed`,
|
|
281
|
+
inert: !condensed,
|
|
282
|
+
children: condensedActions
|
|
283
|
+
}
|
|
284
|
+
),
|
|
251
285
|
wideActions && /* @__PURE__ */ jsx("div", { ref: wideRef, className: `${baseClass}__wide-actions`, inert: folded, children: wideActions }),
|
|
252
286
|
/* @__PURE__ */ jsx(
|
|
253
287
|
CircularButton,
|
|
@@ -255,6 +289,7 @@ function SiteHeader({
|
|
|
255
289
|
ref: menuButtonRef,
|
|
256
290
|
className: [`${baseClass}__menu-button`, drawerOpen && `${baseClass}__menu-button--hidden`].filter(Boolean).join(" "),
|
|
257
291
|
icon: "menu",
|
|
292
|
+
size: "compact",
|
|
258
293
|
variant: "secondary",
|
|
259
294
|
tone: "neutral",
|
|
260
295
|
ariaLabel: drawerOpenLabel,
|
|
@@ -188,7 +188,11 @@ button.ds-source-chip {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
.ds-source-chip__preview {
|
|
191
|
+
/* A manual popover in the top layer while visible
|
|
192
|
+
(src/behaviors/useTopLayer); the floating layer only ranks it in a
|
|
193
|
+
browser without the Popover API. */
|
|
191
194
|
z-index: var(--layer-floating);
|
|
195
|
+
overflow: visible;
|
|
192
196
|
width: max-content;
|
|
193
197
|
max-width: min(320px, calc(100vw - var(--padding-400)));
|
|
194
198
|
padding: var(--padding-300) var(--padding-400);
|
|
@@ -216,7 +220,7 @@ button.ds-source-chip {
|
|
|
216
220
|
display: none;
|
|
217
221
|
opacity: 0;
|
|
218
222
|
transform: translateY(4px);
|
|
219
|
-
transition-property: opacity, transform, display;
|
|
223
|
+
transition-property: opacity, transform, display, overlay;
|
|
220
224
|
transition-duration: var(--motion-duration-fast);
|
|
221
225
|
transition-timing-function: var(--motion-ease-standard);
|
|
222
226
|
transition-behavior: allow-discrete;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useRef, useId, useEffect } from "react";
|
|
4
4
|
import { useAnchoredPosition } from "../../behaviors/useAnchoredPosition.js";
|
|
5
|
+
import { useTopLayer } from "../../behaviors/useTopLayer.js";
|
|
5
6
|
import { useLinkComponent } from "../../behaviors/LinkProvider.js";
|
|
6
7
|
import { useLayer } from "../../behaviors/useLayer.js";
|
|
7
8
|
import "./SourceChip.css";
|
|
@@ -61,6 +62,7 @@ const SourceChip = React.forwardRef(({ title, index, icon = "language", href, ex
|
|
|
61
62
|
if (closeOpenPreview === closeThis) closeOpenPreview = null;
|
|
62
63
|
};
|
|
63
64
|
}, [visible]);
|
|
65
|
+
useTopLayer({ open: visible, panelRef });
|
|
64
66
|
useAnchoredPosition({
|
|
65
67
|
open: visible,
|
|
66
68
|
anchorRef: hostRef,
|
|
@@ -135,6 +137,7 @@ const SourceChip = React.forwardRef(({ title, index, icon = "language", href, ex
|
|
|
135
137
|
ref: panelRef,
|
|
136
138
|
id: panelId,
|
|
137
139
|
role: "tooltip",
|
|
140
|
+
popover: "manual",
|
|
138
141
|
className: `${baseClass}__preview${visible ? ` ${baseClass}__preview--visible` : ""}`,
|
|
139
142
|
children: [
|
|
140
143
|
(logo || source || meta || href) && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__preview-top`, children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* ============================================
|
|
2
2
|
TABLE TOOLBAR
|
|
3
3
|
The control bar over a table: search, filter chips, the More panel's columns,
|
|
4
|
-
the density switch, the count and
|
|
4
|
+
the density switch, the count, the actions and the AI command, in one line.
|
|
5
5
|
============================================ */
|
|
6
6
|
|
|
7
7
|
.ds-table-toolbar {
|
|
@@ -63,6 +63,17 @@
|
|
|
63
63
|
flex-shrink: 0;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/* Folded, the start cluster holds only the AI command, at its own size. */
|
|
67
|
+
.ds-table-toolbar--narrow .ds-table-toolbar__start {
|
|
68
|
+
flex: 0 0 auto;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Folded, search moves into the end cluster and the start cluster is empty:
|
|
72
|
+
out of the row, so its gap cannot push the controls off the table's edge. */
|
|
73
|
+
.ds-table-toolbar__start:empty {
|
|
74
|
+
display: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
/* The search field flexes between a floor and a ceiling: the line wraps
|
|
67
78
|
against its basis, so chips take the room it would otherwise hold, and it
|
|
68
79
|
grows into room nothing else uses. */
|
|
@@ -109,6 +120,7 @@
|
|
|
109
120
|
chip's editor) holds its pressed fill until the panel closes, so the
|
|
110
121
|
bar says which control the open panel belongs to. */
|
|
111
122
|
.ds-table-toolbar .ds-circular-button--tertiary[aria-expanded='true'],
|
|
123
|
+
.ds-table-toolbar .ds-circular-button--secondary-neutral[aria-expanded='true'],
|
|
112
124
|
.ds-table-toolbar .ds-chip[aria-expanded='true'],
|
|
113
125
|
.ds-table-toolbar .ds-button[aria-expanded='true'] {
|
|
114
126
|
background-color: var(--color-action-passive-bg-active);
|
|
@@ -291,7 +303,7 @@
|
|
|
291
303
|
|
|
292
304
|
/* A count on an icon's top-end corner, drawn as BottomNav's count badge:
|
|
293
305
|
a solid pill on the count badge pair, so it reads at a glance on the
|
|
294
|
-
|
|
306
|
+
outlined icon buttons in both modes. */
|
|
295
307
|
.ds-table-toolbar__badge {
|
|
296
308
|
position: absolute;
|
|
297
309
|
inset-block-start: calc(-1 * var(--padding-100));
|
|
@@ -371,8 +383,68 @@
|
|
|
371
383
|
min-width: 0;
|
|
372
384
|
}
|
|
373
385
|
|
|
386
|
+
/* A row that takes over the bar: the bar's controls fade out, then the row
|
|
387
|
+
comes in. Coming back is the reverse. The bar stays laid out throughout, so the
|
|
388
|
+
row never changes height. */
|
|
389
|
+
.ds-table-toolbar__start > *,
|
|
390
|
+
.ds-table-toolbar__end {
|
|
391
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard) var(--motion-duration-fast);
|
|
392
|
+
}
|
|
393
|
+
|
|
374
394
|
.ds-table-toolbar__main[data-covered] {
|
|
375
395
|
visibility: hidden;
|
|
396
|
+
transition: visibility 0s linear calc(var(--motion-duration-fast) + var(--motion-duration-base));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.ds-table-toolbar__main[data-covered] .ds-table-toolbar__start > *,
|
|
400
|
+
.ds-table-toolbar__main[data-covered] .ds-table-toolbar__end {
|
|
401
|
+
opacity: 0;
|
|
402
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* The AI button collapses to the circle the row's close button takes over,
|
|
406
|
+
clipped from its far end. Only while it does: a clip at rest would make the
|
|
407
|
+
button a stacking context and paint its glow over its fill. */
|
|
408
|
+
.ds-table-toolbar__start > .ds-table-toolbar__command {
|
|
409
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard) var(--motion-duration-fast);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.ds-table-toolbar__main[data-covered='command'] .ds-table-toolbar__start > .ds-table-toolbar__command {
|
|
413
|
+
--ds-table-toolbar-circle: var(--control-height-default);
|
|
414
|
+
/* ds-allow(physical): inset() has no logical form; the rtl rule below mirrors it */
|
|
415
|
+
--ds-table-toolbar-collapsed: inset(0 calc(100% - var(--ds-table-toolbar-circle)) 0 0 round var(--radius-pill));
|
|
416
|
+
opacity: 0;
|
|
417
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard) var(--motion-duration-base);
|
|
418
|
+
animation: ds-table-toolbar-collapse var(--motion-duration-base) var(--motion-ease-standard) forwards;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.ds-table-toolbar__main[data-covered='command'] .ds-table-toolbar__start > .ds-table-toolbar__command.ds-ai-button--compact {
|
|
422
|
+
--ds-table-toolbar-circle: var(--control-height-compact);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.ds-table-toolbar__main[data-covered='command'] .ds-table-toolbar__start > .ds-table-toolbar__command:dir(rtl) {
|
|
426
|
+
/* ds-allow(physical): the mirror of the rule above */
|
|
427
|
+
--ds-table-toolbar-collapsed: inset(0 0 0 calc(100% - var(--ds-table-toolbar-circle)) round var(--radius-pill));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
@keyframes ds-table-toolbar-collapse {
|
|
431
|
+
from {
|
|
432
|
+
clip-path: inset(calc(var(--gap-400) * -1) calc(var(--gap-400) * -1) round var(--radius-pill));
|
|
433
|
+
}
|
|
434
|
+
to {
|
|
435
|
+
clip-path: var(--ds-table-toolbar-collapsed);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* The bulk bar fades in as the bar finishes fading out. */
|
|
440
|
+
.ds-table-toolbar__bulk:not(.ds-table-toolbar__bulk--floating) {
|
|
441
|
+
animation: ds-table-toolbar-row-in var(--motion-duration-base) var(--motion-ease-entrance) var(--motion-duration-fast) backwards;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
@keyframes ds-table-toolbar-row-in {
|
|
445
|
+
from {
|
|
446
|
+
opacity: 0;
|
|
447
|
+
}
|
|
376
448
|
}
|
|
377
449
|
|
|
378
450
|
.ds-table-toolbar__bulk {
|
|
@@ -498,3 +570,61 @@
|
|
|
498
570
|
display: inline-flex;
|
|
499
571
|
flex-shrink: 0;
|
|
500
572
|
}
|
|
573
|
+
|
|
574
|
+
/* The AI command's row: it covers the bar as the bulk bar does, the composer
|
|
575
|
+
taking all of it after the close button. Shut, it stays laid out and out of
|
|
576
|
+
sight. Opening, the close button arrives as the AI button finishes collapsing
|
|
577
|
+
and the composer opens out from beside it; closing, both leave first. */
|
|
578
|
+
.ds-table-toolbar__command-row {
|
|
579
|
+
position: absolute;
|
|
580
|
+
inset: 0;
|
|
581
|
+
z-index: 1;
|
|
582
|
+
display: flex;
|
|
583
|
+
align-items: center;
|
|
584
|
+
gap: var(--gap-200);
|
|
585
|
+
visibility: hidden;
|
|
586
|
+
pointer-events: none;
|
|
587
|
+
transition: visibility 0s linear var(--motion-duration-fast);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.ds-table-toolbar__command-row[data-open] {
|
|
591
|
+
visibility: visible;
|
|
592
|
+
pointer-events: auto;
|
|
593
|
+
transition: visibility 0s;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.ds-table-toolbar__command-row .ds-composer {
|
|
597
|
+
flex: 1;
|
|
598
|
+
min-width: 0;
|
|
599
|
+
opacity: 0;
|
|
600
|
+
/* ds-allow(physical): inset() has no logical form; the rtl rule below mirrors it */
|
|
601
|
+
clip-path: inset(0 100% 0 0 round var(--radius-300));
|
|
602
|
+
transition:
|
|
603
|
+
opacity var(--motion-duration-fast) var(--motion-ease-standard),
|
|
604
|
+
clip-path var(--motion-duration-fast) var(--motion-ease-standard),
|
|
605
|
+
border-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.ds-table-toolbar__command-row .ds-composer:dir(rtl) {
|
|
609
|
+
/* ds-allow(physical): the mirror of the rule above */
|
|
610
|
+
clip-path: inset(0 0 0 100% round var(--radius-300));
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.ds-table-toolbar__command-row[data-open] .ds-composer {
|
|
614
|
+
opacity: 1;
|
|
615
|
+
clip-path: inset(0 0 0 0 round var(--radius-300));
|
|
616
|
+
transition:
|
|
617
|
+
opacity var(--motion-duration-base) var(--motion-ease-entrance) var(--motion-duration-base),
|
|
618
|
+
clip-path var(--motion-duration-base) var(--motion-ease-entrance) var(--motion-duration-base),
|
|
619
|
+
border-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.ds-table-toolbar__command-close {
|
|
623
|
+
opacity: 0;
|
|
624
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.ds-table-toolbar__command-row[data-open] .ds-table-toolbar__command-close {
|
|
628
|
+
opacity: 1;
|
|
629
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard) var(--motion-duration-base);
|
|
630
|
+
}
|