rift-ds 1.18.0 → 1.19.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/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/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 +3 -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 +130 -1
- package/components/TableToolbar/TableToolbar.d.ts +52 -0
- package/components/TableToolbar/TableToolbar.js +232 -32
- 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/tokens-light.css +4 -3
|
@@ -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. */
|
|
@@ -371,8 +382,68 @@
|
|
|
371
382
|
min-width: 0;
|
|
372
383
|
}
|
|
373
384
|
|
|
385
|
+
/* A row that takes over the bar: the bar's controls fade out, then the row
|
|
386
|
+
comes in. Coming back is the reverse. The bar stays laid out throughout, so the
|
|
387
|
+
row never changes height. */
|
|
388
|
+
.ds-table-toolbar__start > *,
|
|
389
|
+
.ds-table-toolbar__end {
|
|
390
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard) var(--motion-duration-fast);
|
|
391
|
+
}
|
|
392
|
+
|
|
374
393
|
.ds-table-toolbar__main[data-covered] {
|
|
375
394
|
visibility: hidden;
|
|
395
|
+
transition: visibility 0s linear calc(var(--motion-duration-fast) + var(--motion-duration-base));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.ds-table-toolbar__main[data-covered] .ds-table-toolbar__start > *,
|
|
399
|
+
.ds-table-toolbar__main[data-covered] .ds-table-toolbar__end {
|
|
400
|
+
opacity: 0;
|
|
401
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/* The AI button collapses to the circle the row's close button takes over,
|
|
405
|
+
clipped from its far end. Only while it does: a clip at rest would make the
|
|
406
|
+
button a stacking context and paint its glow over its fill. */
|
|
407
|
+
.ds-table-toolbar__start > .ds-table-toolbar__command {
|
|
408
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard) var(--motion-duration-fast);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.ds-table-toolbar__main[data-covered='command'] .ds-table-toolbar__start > .ds-table-toolbar__command {
|
|
412
|
+
--ds-table-toolbar-circle: var(--control-height-default);
|
|
413
|
+
/* ds-allow(physical): inset() has no logical form; the rtl rule below mirrors it */
|
|
414
|
+
--ds-table-toolbar-collapsed: inset(0 calc(100% - var(--ds-table-toolbar-circle)) 0 0 round var(--radius-pill));
|
|
415
|
+
opacity: 0;
|
|
416
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard) var(--motion-duration-base);
|
|
417
|
+
animation: ds-table-toolbar-collapse var(--motion-duration-base) var(--motion-ease-standard) forwards;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.ds-table-toolbar__main[data-covered='command'] .ds-table-toolbar__start > .ds-table-toolbar__command.ds-ai-button--compact {
|
|
421
|
+
--ds-table-toolbar-circle: var(--control-height-compact);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.ds-table-toolbar__main[data-covered='command'] .ds-table-toolbar__start > .ds-table-toolbar__command:dir(rtl) {
|
|
425
|
+
/* ds-allow(physical): the mirror of the rule above */
|
|
426
|
+
--ds-table-toolbar-collapsed: inset(0 0 0 calc(100% - var(--ds-table-toolbar-circle)) round var(--radius-pill));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
@keyframes ds-table-toolbar-collapse {
|
|
430
|
+
from {
|
|
431
|
+
clip-path: inset(calc(var(--gap-400) * -1) calc(var(--gap-400) * -1) round var(--radius-pill));
|
|
432
|
+
}
|
|
433
|
+
to {
|
|
434
|
+
clip-path: var(--ds-table-toolbar-collapsed);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* The bulk bar fades in as the bar finishes fading out. */
|
|
439
|
+
.ds-table-toolbar__bulk:not(.ds-table-toolbar__bulk--floating) {
|
|
440
|
+
animation: ds-table-toolbar-row-in var(--motion-duration-base) var(--motion-ease-entrance) var(--motion-duration-fast) backwards;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
@keyframes ds-table-toolbar-row-in {
|
|
444
|
+
from {
|
|
445
|
+
opacity: 0;
|
|
446
|
+
}
|
|
376
447
|
}
|
|
377
448
|
|
|
378
449
|
.ds-table-toolbar__bulk {
|
|
@@ -498,3 +569,61 @@
|
|
|
498
569
|
display: inline-flex;
|
|
499
570
|
flex-shrink: 0;
|
|
500
571
|
}
|
|
572
|
+
|
|
573
|
+
/* The AI command's row: it covers the bar as the bulk bar does, the composer
|
|
574
|
+
taking all of it after the close button. Shut, it stays laid out and out of
|
|
575
|
+
sight. Opening, the close button arrives as the AI button finishes collapsing
|
|
576
|
+
and the composer opens out from beside it; closing, both leave first. */
|
|
577
|
+
.ds-table-toolbar__command-row {
|
|
578
|
+
position: absolute;
|
|
579
|
+
inset: 0;
|
|
580
|
+
z-index: 1;
|
|
581
|
+
display: flex;
|
|
582
|
+
align-items: center;
|
|
583
|
+
gap: var(--gap-200);
|
|
584
|
+
visibility: hidden;
|
|
585
|
+
pointer-events: none;
|
|
586
|
+
transition: visibility 0s linear var(--motion-duration-fast);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.ds-table-toolbar__command-row[data-open] {
|
|
590
|
+
visibility: visible;
|
|
591
|
+
pointer-events: auto;
|
|
592
|
+
transition: visibility 0s;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.ds-table-toolbar__command-row .ds-composer {
|
|
596
|
+
flex: 1;
|
|
597
|
+
min-width: 0;
|
|
598
|
+
opacity: 0;
|
|
599
|
+
/* ds-allow(physical): inset() has no logical form; the rtl rule below mirrors it */
|
|
600
|
+
clip-path: inset(0 100% 0 0 round var(--radius-300));
|
|
601
|
+
transition:
|
|
602
|
+
opacity var(--motion-duration-fast) var(--motion-ease-standard),
|
|
603
|
+
clip-path var(--motion-duration-fast) var(--motion-ease-standard),
|
|
604
|
+
border-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.ds-table-toolbar__command-row .ds-composer:dir(rtl) {
|
|
608
|
+
/* ds-allow(physical): the mirror of the rule above */
|
|
609
|
+
clip-path: inset(0 0 0 100% round var(--radius-300));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.ds-table-toolbar__command-row[data-open] .ds-composer {
|
|
613
|
+
opacity: 1;
|
|
614
|
+
clip-path: inset(0 0 0 0 round var(--radius-300));
|
|
615
|
+
transition:
|
|
616
|
+
opacity var(--motion-duration-base) var(--motion-ease-entrance) var(--motion-duration-base),
|
|
617
|
+
clip-path var(--motion-duration-base) var(--motion-ease-entrance) var(--motion-duration-base),
|
|
618
|
+
border-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.ds-table-toolbar__command-close {
|
|
622
|
+
opacity: 0;
|
|
623
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.ds-table-toolbar__command-row[data-open] .ds-table-toolbar__command-close {
|
|
627
|
+
opacity: 1;
|
|
628
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard) var(--motion-duration-base);
|
|
629
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { FilterKind } from '../DataTable/filterModel';
|
|
3
3
|
import { DataTableFilter as Filter, DataTableFilterLabels as FilterLabels, DataTableSort as Sort, DataTableView } from '../DataTable/DataTable';
|
|
4
|
+
import { ToolbarColumn } from '../DataTable/tableContext';
|
|
5
|
+
import { TableView } from '../DataTable/views';
|
|
4
6
|
/** One column as a TableToolbar over some other table is told about it. */
|
|
5
7
|
export interface TableToolbarColumn {
|
|
6
8
|
/** The column's key. */
|
|
@@ -156,6 +158,22 @@ export interface TableToolbarLabels {
|
|
|
156
158
|
selected: (count: number) => string;
|
|
157
159
|
/** The bulk bar's count once every matching row is selected, given how many. */
|
|
158
160
|
allSelected: (count: number) => string;
|
|
161
|
+
/** The command button's label. */
|
|
162
|
+
command: string;
|
|
163
|
+
/** Closes the command's row and brings the bar back. */
|
|
164
|
+
commandClose: string;
|
|
165
|
+
/** The command composer's placeholder. */
|
|
166
|
+
commandPlaceholder: string;
|
|
167
|
+
/** The command composer's send button. */
|
|
168
|
+
commandSend: string;
|
|
169
|
+
/** Shown when a command changes the table without a message of its own. */
|
|
170
|
+
commandApplied: string;
|
|
171
|
+
/** Shown when a command fails without a message of its own. */
|
|
172
|
+
commandFailed: string;
|
|
173
|
+
/** Puts the table back as it was before the last command. */
|
|
174
|
+
commandUndo: string;
|
|
175
|
+
/** Closes a command's toast in the bar's own stack; inside a ToastProvider, its `dismissLabel` applies instead. */
|
|
176
|
+
commandDismiss: string;
|
|
159
177
|
/** The bulk bar's menu of the actions that do not fit. */
|
|
160
178
|
moreBulkActions: string;
|
|
161
179
|
/** The bulk bar's button that selects every matching row, given how many match. */
|
|
@@ -260,6 +278,17 @@ type TableToolbarOwnProps = {
|
|
|
260
278
|
secondaryActions?: TableToolbarAction[];
|
|
261
279
|
/** Any other controls at the bar's end, after the actions. */
|
|
262
280
|
actions?: React.ReactNode;
|
|
281
|
+
/**
|
|
282
|
+
* The AI command: a button at the bar's start that turns the whole row into a
|
|
283
|
+
* one-off composer, as selecting rows turns it into the bulk bar, whose
|
|
284
|
+
* text your handler turns into a whole table setup. It reaches everything a
|
|
285
|
+
* view holds (filters, sorts, search, columns, pinning, grouping, density),
|
|
286
|
+
* so it can do in a sentence what the bar's other controls do one at a time.
|
|
287
|
+
* The library calls no model; your handler does. A change or an error lands in
|
|
288
|
+
* a toast, in the surrounding ToastProvider's stack if there is one, else in the
|
|
289
|
+
* bar's own at the bottom-right. Shows only inside a DataTable.
|
|
290
|
+
*/
|
|
291
|
+
command?: TableToolbarCommand;
|
|
263
292
|
/** The bar's words, merged over their English defaults. */
|
|
264
293
|
labels?: Partial<TableToolbarLabels>;
|
|
265
294
|
/** The filters' words, merged over their English defaults (inside a DataTable, over the table's). */
|
|
@@ -267,6 +296,29 @@ type TableToolbarOwnProps = {
|
|
|
267
296
|
/** Additional CSS classes */
|
|
268
297
|
className?: string;
|
|
269
298
|
};
|
|
299
|
+
/** What a TableToolbar's command handler is given. */
|
|
300
|
+
export interface TableToolbarCommandRequest {
|
|
301
|
+
/** What the reader typed. */
|
|
302
|
+
text: string;
|
|
303
|
+
/** The table's setup right now, so a command can refine it. */
|
|
304
|
+
view: TableView;
|
|
305
|
+
/** The columns, as the bar knows them: keys, labels, filter kinds and choices. */
|
|
306
|
+
columns: ToolbarColumn[];
|
|
307
|
+
/** Aborts when the table leaves the page while the command runs. */
|
|
308
|
+
signal: AbortSignal;
|
|
309
|
+
}
|
|
310
|
+
/** What a TableToolbar's command handler answers with. */
|
|
311
|
+
export interface TableToolbarCommandResult {
|
|
312
|
+
/** The whole setup to apply; applying replaces the setup, and leaving it out changes nothing. */
|
|
313
|
+
view?: TableView;
|
|
314
|
+
/** What was done or found. A toast carries it, with an Undo when the setup changed; a setup that changed with no message takes the `commandApplied` label. */
|
|
315
|
+
message?: string;
|
|
316
|
+
}
|
|
317
|
+
/** The AI command in a TableToolbar. */
|
|
318
|
+
export interface TableToolbarCommand {
|
|
319
|
+
/** Turns the reader's text into a setup. A rejection shows as an error toast, using the error's message, and leaves the table alone. */
|
|
320
|
+
onSubmit: (request: TableToolbarCommandRequest) => Promise<TableToolbarCommandResult>;
|
|
321
|
+
}
|
|
270
322
|
export interface TableToolbarProps extends TableToolbarOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof TableToolbarOwnProps> {
|
|
271
323
|
}
|
|
272
324
|
/**
|