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
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* hover cards, tooltips).
|
|
5
5
|
*
|
|
6
6
|
* The panel stays where it is in the DOM, so it keeps the trigger's stacking
|
|
7
|
-
* context
|
|
7
|
+
* context (a tooltip excepted, which paints in the top layer through
|
|
8
|
+
* src/behaviors/useTopLayer) and any theme scoped above it, but is
|
|
9
|
+
* `position: fixed`, placed
|
|
8
10
|
* from the anchor's rectangle. A fixed panel is not clipped by an ancestor's
|
|
9
11
|
* `overflow`, which is what lets a menu inside a table's scroll wrapper, a
|
|
10
12
|
* rail or a card show whole. Portals would escape the same way but would
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useRef, useLayoutEffect } from "react";
|
|
3
3
|
import { isRtl } from "./direction.js";
|
|
4
4
|
function fixedContainingBlock(element) {
|
|
5
|
+
if (element.matches(":popover-open")) return null;
|
|
5
6
|
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
6
7
|
const style = getComputedStyle(node);
|
|
7
8
|
const read = (property) => style.getPropertyValue(property).trim();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Lifts a panel into the browser's top layer while `open`, so it paints
|
|
4
|
+
* above everything on the page: every z-index, every stacking context, every
|
|
5
|
+
* `overflow` clip. Tooltips always sit here; no layer token can rank above
|
|
6
|
+
* the top layer, so nothing a page or a host does can cover one.
|
|
7
|
+
*
|
|
8
|
+
* The panel renders with `popover="manual"` and this hook calls
|
|
9
|
+
* `showPopover()` / `hidePopover()`. A panel that must stay in the
|
|
10
|
+
* accessibility tree at rest (SegmentedControl's segment labels, which are
|
|
11
|
+
* the segments' names) cannot carry the attribute, since a closed popover is
|
|
12
|
+
* `display: none`; the hook adds it for as long as the panel is shown and
|
|
13
|
+
* takes it off again. A manual popover has no light dismiss
|
|
14
|
+
* and closes no other popover, so dismissal stays with the shared layer
|
|
15
|
+
* stack (src/behaviors/useLayer). The panel keeps its place in the DOM, so
|
|
16
|
+
* it still inherits the custom properties of any theme scoped above it; only
|
|
17
|
+
* where it paints changes.
|
|
18
|
+
*
|
|
19
|
+
* Call it before useAnchoredPosition in the component: layout effects run in
|
|
20
|
+
* order, and the anchored hook must see the panel already in the top layer,
|
|
21
|
+
* where the viewport is its containing block whatever its ancestors
|
|
22
|
+
* transform (`fixedContainingBlock` returns null for an open popover).
|
|
23
|
+
*
|
|
24
|
+
* The panel's CSS adds `overlay` to its transition list with
|
|
25
|
+
* `allow-discrete`, so the exit fade still plays in the top layer, and sets
|
|
26
|
+
* `overflow: visible` over the popover default so an arrow outside the box
|
|
27
|
+
* still draws. A browser without the Popover API leaves the panel where it
|
|
28
|
+
* was, stacking at its layer token.
|
|
29
|
+
*/
|
|
30
|
+
export declare function useTopLayer({ open, panelRef, target, }: {
|
|
31
|
+
open: boolean;
|
|
32
|
+
panelRef: RefObject<HTMLElement | null>;
|
|
33
|
+
/** Changes when the ref is pointed at a different panel, as useAnchoredPosition's `target`. */
|
|
34
|
+
target?: unknown;
|
|
35
|
+
}): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useLayoutEffect } from "react";
|
|
3
|
+
function useTopLayer({
|
|
4
|
+
open,
|
|
5
|
+
panelRef,
|
|
6
|
+
target
|
|
7
|
+
}) {
|
|
8
|
+
useLayoutEffect(() => {
|
|
9
|
+
const panel = panelRef.current;
|
|
10
|
+
if (!open || !panel || typeof panel.showPopover !== "function") return;
|
|
11
|
+
const added = !panel.hasAttribute("popover");
|
|
12
|
+
if (added) panel.setAttribute("popover", "manual");
|
|
13
|
+
if (!panel.matches(":popover-open")) panel.showPopover();
|
|
14
|
+
return () => {
|
|
15
|
+
if (panel.isConnected && panel.matches(":popover-open")) panel.hidePopover();
|
|
16
|
+
if (added) panel.removeAttribute("popover");
|
|
17
|
+
};
|
|
18
|
+
}, [open, panelRef, target]);
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
useTopLayer
|
|
22
|
+
};
|
package/chat/ChatLauncher.js
CHANGED
|
@@ -82,6 +82,29 @@ function ChatLauncher({
|
|
|
82
82
|
}, [showPanel, isDocked, side, takeover, hostAttribute]);
|
|
83
83
|
useScrollLock(modal && !takeover);
|
|
84
84
|
const panelRef = useRef(null);
|
|
85
|
+
useLayoutEffect(() => {
|
|
86
|
+
const panel = panelRef.current;
|
|
87
|
+
const vv = window.visualViewport;
|
|
88
|
+
if (!showPanel || !takeover || !panel || !vv) return;
|
|
89
|
+
let frame = 0;
|
|
90
|
+
const sync = () => {
|
|
91
|
+
cancelAnimationFrame(frame);
|
|
92
|
+
frame = requestAnimationFrame(() => {
|
|
93
|
+
panel.style.setProperty("--ds-chat-viewport-height", `${vv.height}px`);
|
|
94
|
+
panel.style.setProperty("--ds-chat-viewport-top", `${vv.offsetTop}px`);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
sync();
|
|
98
|
+
vv.addEventListener("resize", sync);
|
|
99
|
+
vv.addEventListener("scroll", sync);
|
|
100
|
+
return () => {
|
|
101
|
+
cancelAnimationFrame(frame);
|
|
102
|
+
vv.removeEventListener("resize", sync);
|
|
103
|
+
vv.removeEventListener("scroll", sync);
|
|
104
|
+
panel.style.removeProperty("--ds-chat-viewport-height");
|
|
105
|
+
panel.style.removeProperty("--ds-chat-viewport-top");
|
|
106
|
+
};
|
|
107
|
+
}, [showPanel, takeover]);
|
|
85
108
|
const wasOpen = useRef(false);
|
|
86
109
|
useEffect(() => {
|
|
87
110
|
if (wasOpen.current && !open) {
|
package/chat/ChatWidget.css
CHANGED
|
@@ -1207,13 +1207,18 @@ html[data-chat-resizing] .ds-chat-launcher__panel--floating .ds-chat-header {
|
|
|
1207
1207
|
way the browser handles any page with a field at the bottom. The launcher
|
|
1208
1208
|
reads the same threshold as TAKEOVER_QUERY; this rule covers the first
|
|
1209
1209
|
render, before a media query has been read, so the two must stay in step.
|
|
1210
|
-
No transition: there is no card form to glide from.
|
|
1210
|
+
No transition: there is no card form to glide from. Once open, the
|
|
1211
|
+
launcher writes the visual viewport's height and pan here, so the
|
|
1212
|
+
composer ends at the keyboard and the header stays on screen; 100dvh is
|
|
1213
|
+
the first paint's height and the fallback without visualViewport. */
|
|
1211
1214
|
@media (max-width: 719px) {
|
|
1212
1215
|
.ds-chat-launcher__panel {
|
|
1213
1216
|
position: relative;
|
|
1214
|
-
|
|
1217
|
+
/* A relative offset, not a transform: a transform would become the
|
|
1218
|
+
containing block of every fixed popup inside the panel. */
|
|
1219
|
+
top: var(--ds-chat-viewport-top, 0px);
|
|
1215
1220
|
left: auto;
|
|
1216
|
-
height: 100dvh;
|
|
1221
|
+
height: var(--ds-chat-viewport-height, 100dvh);
|
|
1217
1222
|
width: 100%;
|
|
1218
1223
|
max-width: none;
|
|
1219
1224
|
border: none;
|
|
@@ -279,6 +279,7 @@
|
|
|
279
279
|
focus outline is its signal, as on Button, and a widened ring inside it
|
|
280
280
|
would read as two concentric strokes. */
|
|
281
281
|
.ds-ai-button:hover::before,
|
|
282
|
+
.ds-ai-button[aria-expanded='true']::before,
|
|
282
283
|
.ds-ai-button--busy::before,
|
|
283
284
|
.ds-ai-button--done::before,
|
|
284
285
|
.ds-ai-button--error::before {
|
|
@@ -287,6 +288,7 @@
|
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
.ds-ai-button:hover::after,
|
|
291
|
+
.ds-ai-button[aria-expanded='true']::after,
|
|
290
292
|
.ds-ai-button--busy::after,
|
|
291
293
|
.ds-ai-button--done::after,
|
|
292
294
|
.ds-ai-button--error::after {
|
|
@@ -339,6 +341,15 @@
|
|
|
339
341
|
--icon-size: var(--icon-size-500);
|
|
340
342
|
}
|
|
341
343
|
|
|
344
|
+
.ds-ai-button--icon-only {
|
|
345
|
+
width: var(--control-height-default);
|
|
346
|
+
padding: 0;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.ds-ai-button--compact.ds-ai-button--icon-only {
|
|
350
|
+
width: var(--control-height-compact);
|
|
351
|
+
}
|
|
352
|
+
|
|
342
353
|
/* Reduced motion: the ring and glow stay, the turning stops. */
|
|
343
354
|
/* ds-allow(motion): parks a looping animation, which the collapsed duration would strobe rather than stop (the ring's turn and the busy spin), and drops the status icon's entrance. */
|
|
344
355
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -21,6 +21,8 @@ type AiButtonOwnProps = {
|
|
|
21
21
|
label?: string;
|
|
22
22
|
/** Leading icon — Material Symbol name (string) or custom element (ReactNode) */
|
|
23
23
|
icon?: string | React.ReactNode;
|
|
24
|
+
/** Draws the button as a circle holding only its icon, for a tight bar. The label stays as its accessible name. */
|
|
25
|
+
iconOnly?: boolean;
|
|
24
26
|
/** Button size */
|
|
25
27
|
size?: 'default' | 'compact';
|
|
26
28
|
/** Whether the button is disabled */
|
|
@@ -26,6 +26,7 @@ const AiButton = React.forwardRef(
|
|
|
26
26
|
({
|
|
27
27
|
label = "Ask AI",
|
|
28
28
|
icon = "auto_awesome",
|
|
29
|
+
iconOnly = false,
|
|
29
30
|
size = "default",
|
|
30
31
|
disabled,
|
|
31
32
|
state: status,
|
|
@@ -164,6 +165,7 @@ const AiButton = React.forwardRef(
|
|
|
164
165
|
}
|
|
165
166
|
};
|
|
166
167
|
const classes = [
|
|
168
|
+
iconOnly ? `${baseClass}--icon-only` : "",
|
|
167
169
|
baseClass,
|
|
168
170
|
`${baseClass}--${size}`,
|
|
169
171
|
disabled && `${baseClass}--disabled`,
|
|
@@ -183,7 +185,7 @@ const AiButton = React.forwardRef(
|
|
|
183
185
|
},
|
|
184
186
|
shownStatus
|
|
185
187
|
) }),
|
|
186
|
-
/* @__PURE__ */ jsx("span", { className: `${baseClass}__text`, children: label })
|
|
188
|
+
/* @__PURE__ */ jsx("span", { className: iconOnly ? "ds-visually-hidden" : `${baseClass}__text`, children: label })
|
|
187
189
|
] });
|
|
188
190
|
const control = href && !disabled ? /* @__PURE__ */ jsx(
|
|
189
191
|
Link,
|
|
@@ -268,6 +268,9 @@
|
|
|
268
268
|
opacity: 1;
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
/* The trailing pill holds a beat off the row's end, so it sits as far in
|
|
272
|
+
from the rail's edge as the icon does at the start. A word is a quiet
|
|
273
|
+
pill; a count takes the count badge pair (the --count rule below). */
|
|
271
274
|
.ds-app-sidebar__btn-badge {
|
|
272
275
|
font-family: var(--font-caption-family);
|
|
273
276
|
font-size: var(--font-caption-size);
|
|
@@ -277,6 +280,7 @@
|
|
|
277
280
|
background-color: var(--color-bg-container-tertiary);
|
|
278
281
|
border-radius: var(--radius-pill);
|
|
279
282
|
padding: var(--padding-050) var(--padding-200);
|
|
283
|
+
margin-inline-end: var(--padding-100);
|
|
280
284
|
min-width: 0;
|
|
281
285
|
flex-shrink: 1;
|
|
282
286
|
overflow: hidden;
|
|
@@ -288,6 +292,61 @@
|
|
|
288
292
|
opacity: 1;
|
|
289
293
|
}
|
|
290
294
|
|
|
295
|
+
/* On a filled row the quiet pill steps back to the page ground, or it
|
|
296
|
+
would share the row's fill and dissolve into it. */
|
|
297
|
+
.ds-app-sidebar__btn--active .ds-app-sidebar__btn-badge:not(.ds-app-sidebar__btn-badge--count),
|
|
298
|
+
.ds-app-sidebar__btn:hover .ds-app-sidebar__btn-badge:not(.ds-app-sidebar__btn-badge--count),
|
|
299
|
+
.ds-app-sidebar__sub-btn--active .ds-app-sidebar__btn-badge:not(.ds-app-sidebar__btn-badge--count),
|
|
300
|
+
.ds-app-sidebar__sub-btn:hover .ds-app-sidebar__btn-badge:not(.ds-app-sidebar__btn-badge--count) {
|
|
301
|
+
background-color: var(--color-bg-page-primary);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* A count: a solid pill on the count badge pair, the "how many" mark
|
|
305
|
+
BottomNav and TableToolbar share, which reads on any row fill. */
|
|
306
|
+
.ds-app-sidebar__btn-badge--count {
|
|
307
|
+
color: var(--color-count-badge-text);
|
|
308
|
+
background-color: var(--color-count-badge-bg);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* The icon's seat: the same box as the icon, there to anchor the corner
|
|
312
|
+
copy of the count. */
|
|
313
|
+
.ds-app-sidebar__btn-seat {
|
|
314
|
+
position: relative;
|
|
315
|
+
display: inline-flex;
|
|
316
|
+
flex-shrink: 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* The collapsed rail's count rides the icon's top-end corner, BottomNav's
|
|
320
|
+
badge in size and pair. Its end edge sits on the row's padding edge, so
|
|
321
|
+
it stays inside the collapsed row's clip, and it grows back over the
|
|
322
|
+
icon; it fades out as the trailing pill fades in. */
|
|
323
|
+
.ds-app-sidebar__btn-corner-badge {
|
|
324
|
+
position: absolute;
|
|
325
|
+
top: calc(var(--padding-100) * -1);
|
|
326
|
+
inset-inline-end: calc(var(--padding-200) * -1);
|
|
327
|
+
box-sizing: border-box;
|
|
328
|
+
min-width: 16px;
|
|
329
|
+
height: 16px;
|
|
330
|
+
padding: 0 var(--padding-100);
|
|
331
|
+
border-radius: var(--radius-pill);
|
|
332
|
+
background-color: var(--color-count-badge-bg);
|
|
333
|
+
color: var(--color-count-badge-text);
|
|
334
|
+
font-family: var(--font-caption-family);
|
|
335
|
+
font-size: var(--font-caption-size);
|
|
336
|
+
font-weight: var(--font-paragraph-sm-emphasis-weight);
|
|
337
|
+
line-height: var(--font-caption-line-height);
|
|
338
|
+
text-align: center;
|
|
339
|
+
white-space: nowrap;
|
|
340
|
+
pointer-events: none;
|
|
341
|
+
opacity: 1;
|
|
342
|
+
transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.ds-app-sidebar--expanded .ds-app-sidebar__btn-corner-badge {
|
|
346
|
+
opacity: 0;
|
|
347
|
+
transition-duration: var(--motion-duration-fast);
|
|
348
|
+
}
|
|
349
|
+
|
|
291
350
|
.ds-app-sidebar__btn-chevron {
|
|
292
351
|
--icon-size: var(--icon-size-600);
|
|
293
352
|
color: var(--color-icon-secondary);
|
|
@@ -400,15 +459,6 @@
|
|
|
400
459
|
flex-shrink: 0;
|
|
401
460
|
}
|
|
402
461
|
|
|
403
|
-
/* On a filled row the pill steps back to the page ground, or it would
|
|
404
|
-
share the row's fill and dissolve into it. */
|
|
405
|
-
.ds-app-sidebar__btn--active .ds-app-sidebar__btn-badge,
|
|
406
|
-
.ds-app-sidebar__btn:hover .ds-app-sidebar__btn-badge,
|
|
407
|
-
.ds-app-sidebar__sub-btn--active .ds-app-sidebar__btn-badge,
|
|
408
|
-
.ds-app-sidebar__sub-btn:hover .ds-app-sidebar__btn-badge {
|
|
409
|
-
background-color: var(--color-bg-page-primary);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
462
|
.ds-app-sidebar__sub-chevron {
|
|
413
463
|
--icon-size: var(--icon-size-500);
|
|
414
464
|
flex-shrink: 0;
|
|
@@ -9,7 +9,7 @@ export interface AppSidebarSubItem {
|
|
|
9
9
|
onClick?: () => void;
|
|
10
10
|
/** Renders the sub-item as a real link to this URL instead of a button. Ignored when the sub-item has children, whose row opens them */
|
|
11
11
|
href?: string;
|
|
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 entry sits under when it is listed in the panel (see `subNav`); ignored in the accordion */
|
|
15
15
|
group?: string;
|
|
@@ -42,7 +42,7 @@ export interface AppSidebarItem {
|
|
|
42
42
|
onClick?: () => void;
|
|
43
43
|
/** Renders the item as a real link to this URL instead of a button. Ignored when the item has children, whose row is the accordion toggle */
|
|
44
44
|
href?: string;
|
|
45
|
-
/** Trailing
|
|
45
|
+
/** Trailing pill, e.g. unread items. A count takes the count badge colours and, while the rail is collapsed, rides the icon's corner; a word stays a quiet pill, hidden while collapsed */
|
|
46
46
|
badge?: string | number;
|
|
47
47
|
/** Sub-items: an accordion under the row, or the side panel's contents when `subNav="panel"` */
|
|
48
48
|
children?: AppSidebarSubItem[];
|
|
@@ -14,6 +14,9 @@ import "../../behaviors/direction.css";
|
|
|
14
14
|
import "../../fonts/material-symbols.css";
|
|
15
15
|
import { mirrorClass } from "../../behaviors/direction.js";
|
|
16
16
|
import { distinctPixelInks } from "../PixelAvatar/pixelInks.js";
|
|
17
|
+
function isCount(badge) {
|
|
18
|
+
return typeof badge === "number" || /^\d+\+?$/.test(badge.trim());
|
|
19
|
+
}
|
|
17
20
|
function DefaultLogo() {
|
|
18
21
|
return /* @__PURE__ */ jsxs(
|
|
19
22
|
"svg",
|
|
@@ -315,17 +318,20 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
|
|
|
315
318
|
].filter(Boolean).join(" ");
|
|
316
319
|
const itemContent = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
317
320
|
/* @__PURE__ */ jsxs("span", { className: `${baseClass}__btn-left`, children: [
|
|
318
|
-
|
|
319
|
-
"span",
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
321
|
+
/* @__PURE__ */ jsxs("span", { className: `${baseClass}__btn-seat`, children: [
|
|
322
|
+
item.pixelAvatar ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-icon ${baseClass}__btn-icon--avatar`, children: /* @__PURE__ */ jsx(PixelAvatar, { name: avatarSeed(item), ink: avatarInks.get(item.key) }) }) : /* @__PURE__ */ jsx(
|
|
323
|
+
"span",
|
|
324
|
+
{
|
|
325
|
+
className: `${baseClass}__btn-icon${typeof item.icon === "string" ? ` material-symbols-rounded ${mirrorClass(item.icon)}` : ""}`,
|
|
326
|
+
children: item.icon
|
|
327
|
+
}
|
|
328
|
+
),
|
|
329
|
+
item.badge !== void 0 && isCount(item.badge) && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-corner-badge`, "aria-hidden": "true", children: item.badge })
|
|
330
|
+
] }),
|
|
325
331
|
/* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-label`, children: item.label })
|
|
326
332
|
] }),
|
|
327
333
|
isPanelMode && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-caption`, "aria-hidden": "true", children: item.label }),
|
|
328
|
-
item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-badge`, children: item.badge }),
|
|
334
|
+
item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-badge${isCount(item.badge) ? ` ${baseClass}__btn-badge--count` : ""}`, children: item.badge }),
|
|
329
335
|
hasChildren && /* @__PURE__ */ jsx(
|
|
330
336
|
"span",
|
|
331
337
|
{
|
|
@@ -391,7 +397,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
|
|
|
391
397
|
].filter(Boolean).join(" ");
|
|
392
398
|
const subLabel = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
393
399
|
/* @__PURE__ */ jsx("span", { className: `${baseClass}__sub-label`, children: sub.label }),
|
|
394
|
-
sub.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-badge`, children: sub.badge }),
|
|
400
|
+
sub.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-badge${isCount(sub.badge) ? ` ${baseClass}__btn-badge--count` : ""}`, children: sub.badge }),
|
|
395
401
|
subHasChildren && /* @__PURE__ */ jsx(
|
|
396
402
|
"span",
|
|
397
403
|
{
|
|
@@ -100,6 +100,12 @@
|
|
|
100
100
|
position: relative;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/* A bar is a large filled mass, so it takes a wash of its series colour, the
|
|
104
|
+
way an area fill does; thin marks (lines, dots, swatches) keep the full token. */
|
|
105
|
+
.ds-chart__bar .recharts-rectangle {
|
|
106
|
+
fill-opacity: 0.45;
|
|
107
|
+
}
|
|
108
|
+
|
|
103
109
|
/* ============================================
|
|
104
110
|
BARE MODE — the chart sits inside another
|
|
105
111
|
panel that supplies the surface, so its own
|
|
@@ -70,12 +70,24 @@
|
|
|
70
70
|
padding: 0 var(--padding-200);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/* The compact field's keycaps step down to caption type with no block
|
|
74
|
+
padding, so they sit inside the shorter row with air around them
|
|
75
|
+
instead of filling it. */
|
|
76
|
+
.ds-command-palette-trigger--compact .ds-kbd--compact {
|
|
77
|
+
padding-block: 0;
|
|
78
|
+
font-size: var(--font-caption-size);
|
|
79
|
+
line-height: var(--font-caption-line-height);
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
/* Folded to the icon: a circle the row height across, hovering like the
|
|
74
|
-
theme picker's trigger rather than like a field.
|
|
83
|
+
theme picker's trigger rather than like a field. With no prompt beside it
|
|
84
|
+
the glyph is the control, so it takes the icon colour the family's other
|
|
85
|
+
circles wear rather than the field's placeholder tone. */
|
|
75
86
|
.ds-command-palette-trigger--icon-only {
|
|
76
87
|
justify-content: center;
|
|
77
88
|
width: var(--control-height-default);
|
|
78
89
|
padding: 0;
|
|
90
|
+
color: var(--color-action-icon-default);
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
.ds-command-palette-trigger--compact.ds-command-palette-trigger--icon-only {
|
|
@@ -633,3 +633,75 @@
|
|
|
633
633
|
.ds-composer--disabled.ds-composer--mentions .ds-composer__textarea {
|
|
634
634
|
color: transparent;
|
|
635
635
|
}
|
|
636
|
+
|
|
637
|
+
/* ============================================
|
|
638
|
+
COMPACT: the same shell at the compact density
|
|
639
|
+
============================================ */
|
|
640
|
+
.ds-composer--compact {
|
|
641
|
+
--ds-composer-send-size: var(--control-height-compact);
|
|
642
|
+
--ds-composer-text-inset: var(--padding-300);
|
|
643
|
+
/* Concentric on this density's tighter ring. */
|
|
644
|
+
--ds-composer-radius: calc(
|
|
645
|
+
min(var(--radius-pill), var(--ds-composer-send-size) / 2) + var(--padding-100) + var(--border-025)
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.ds-composer--compact .ds-composer__content {
|
|
650
|
+
padding-block: var(--padding-200) var(--padding-100);
|
|
651
|
+
height: calc(var(--ds-composer-text-height, var(--font-paragraph-sm-line-height)) + var(--padding-200) + var(--padding-100));
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.ds-composer--compact .ds-composer__textarea {
|
|
655
|
+
font-size: var(--font-paragraph-sm-size);
|
|
656
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
657
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
658
|
+
max-height: calc(var(--ds-composer-max-rows, 8) * var(--font-paragraph-sm-line-height));
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.ds-composer--compact .ds-composer__footer {
|
|
662
|
+
padding: var(--padding-100);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/* ============================================
|
|
666
|
+
INLINE: one row at the control height, the send button on the text's line.
|
|
667
|
+
The shell is an input here, so it does not float, and the button sits
|
|
668
|
+
inside the height, concentric on a thinner ring.
|
|
669
|
+
============================================ */
|
|
670
|
+
.ds-composer--inline {
|
|
671
|
+
--ds-composer-control: var(--control-height-default);
|
|
672
|
+
--ds-composer-line: var(--font-paragraph-line-height);
|
|
673
|
+
--ds-composer-send-size: calc(var(--ds-composer-control) - var(--padding-050) * 2 - var(--border-025) * 2);
|
|
674
|
+
--ds-composer-radius: calc(
|
|
675
|
+
min(var(--radius-pill), var(--ds-composer-send-size) / 2) + var(--padding-050) + var(--border-025)
|
|
676
|
+
);
|
|
677
|
+
flex-direction: row;
|
|
678
|
+
align-items: center;
|
|
679
|
+
box-shadow: none;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.ds-composer--inline.ds-composer--compact {
|
|
683
|
+
--ds-composer-control: var(--control-height-compact);
|
|
684
|
+
--ds-composer-line: var(--font-paragraph-sm-line-height);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.ds-composer--inline .ds-composer__content,
|
|
688
|
+
.ds-composer--inline.ds-composer--compact .ds-composer__content {
|
|
689
|
+
--ds-composer-text-block: calc((var(--ds-composer-control) - var(--ds-composer-line)) / 2 - var(--border-025));
|
|
690
|
+
flex: 1;
|
|
691
|
+
min-width: 0;
|
|
692
|
+
width: auto;
|
|
693
|
+
padding-block: var(--ds-composer-text-block);
|
|
694
|
+
padding-inline: var(--ds-composer-text-inset) var(--padding-100);
|
|
695
|
+
height: calc(var(--ds-composer-text-height, var(--ds-composer-line)) + var(--ds-composer-text-block) * 2);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.ds-composer--inline .ds-composer__footer,
|
|
699
|
+
.ds-composer--inline.ds-composer--compact .ds-composer__footer {
|
|
700
|
+
flex-shrink: 0;
|
|
701
|
+
padding: var(--padding-050);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.ds-composer--inline .ds-composer__footer .ds-circular-button {
|
|
705
|
+
width: var(--ds-composer-send-size);
|
|
706
|
+
height: var(--ds-composer-send-size);
|
|
707
|
+
}
|
|
@@ -32,7 +32,11 @@ type ComposerOwnProps = {
|
|
|
32
32
|
streaming?: boolean;
|
|
33
33
|
/** Fires when the stop button is pressed while `streaming`. */
|
|
34
34
|
onStop?: () => void;
|
|
35
|
-
/**
|
|
35
|
+
/** The density, matching a table's or a bar's: `compact` tightens the padding and takes the smaller text and buttons. */
|
|
36
|
+
size?: 'default' | 'compact';
|
|
37
|
+
/** One row with the send button on the text's line, the shell exactly `size`'s control height, so it lines up with an Input or a table's toolbar. For a popover, a sheet or a tight bar. `maxRows` defaults to 1. */
|
|
38
|
+
inline?: boolean;
|
|
39
|
+
/** Growth cap in text rows before the textarea scrolls internally. Defaults to 8, or 1 when `inline`. */
|
|
36
40
|
maxRows?: number;
|
|
37
41
|
/**
|
|
38
42
|
* While focused, the shell wears AiButton's slowly rotating gradient ring
|
|
@@ -34,7 +34,9 @@ const Composer = React.forwardRef(
|
|
|
34
34
|
onSubmit,
|
|
35
35
|
streaming = false,
|
|
36
36
|
onStop,
|
|
37
|
-
|
|
37
|
+
size = "default",
|
|
38
|
+
inline = false,
|
|
39
|
+
maxRows = inline ? 1 : 8,
|
|
38
40
|
aiGlow = false,
|
|
39
41
|
context,
|
|
40
42
|
contextIcon,
|
|
@@ -362,6 +364,8 @@ const Composer = React.forwardRef(
|
|
|
362
364
|
const classes = [
|
|
363
365
|
baseClass,
|
|
364
366
|
aiGlow ? `${baseClass}--ai-glow` : "",
|
|
367
|
+
size === "compact" ? `${baseClass}--compact` : "",
|
|
368
|
+
inline ? `${baseClass}--inline` : "",
|
|
365
369
|
streaming ? `${baseClass}--streaming` : "",
|
|
366
370
|
disabled ? `${baseClass}--disabled` : "",
|
|
367
371
|
dragging ? `${baseClass}--dragging` : "",
|
|
@@ -469,6 +473,7 @@ const Composer = React.forwardRef(
|
|
|
469
473
|
{
|
|
470
474
|
icon: "add",
|
|
471
475
|
variant: "tertiary",
|
|
476
|
+
size,
|
|
472
477
|
ariaLabel: attachLabel,
|
|
473
478
|
disabled,
|
|
474
479
|
onClick: () => fileInputRef.current?.click()
|
|
@@ -483,6 +488,7 @@ const Composer = React.forwardRef(
|
|
|
483
488
|
{
|
|
484
489
|
icon: "stop",
|
|
485
490
|
variant: "primary",
|
|
491
|
+
size,
|
|
486
492
|
ariaLabel: stopLabel,
|
|
487
493
|
disabled,
|
|
488
494
|
onClick: onStop
|
|
@@ -492,6 +498,7 @@ const Composer = React.forwardRef(
|
|
|
492
498
|
{
|
|
493
499
|
icon: "arrow_upward",
|
|
494
500
|
variant: "primary",
|
|
501
|
+
size,
|
|
495
502
|
ariaLabel: sendLabel,
|
|
496
503
|
disabled: !canSend,
|
|
497
504
|
onClick: submit
|
|
@@ -848,7 +848,10 @@
|
|
|
848
848
|
read, from a visually hidden thead.
|
|
849
849
|
============================================ */
|
|
850
850
|
|
|
851
|
+
/* The wrapper qualifier outranks Table's phone rule, which holds a
|
|
852
|
+
column table at max-content so it scrolls: cards fit the frame. */
|
|
851
853
|
.ds-data-table__grid--stacked,
|
|
854
|
+
.ds-table__wrapper .ds-data-table__grid--stacked,
|
|
852
855
|
.ds-data-table__grid--stacked > tbody {
|
|
853
856
|
display: block;
|
|
854
857
|
width: 100%;
|
|
@@ -901,8 +904,10 @@
|
|
|
901
904
|
width: auto;
|
|
902
905
|
}
|
|
903
906
|
|
|
907
|
+
/* The actions close the title line, at its end, with or without a row header beside them. */
|
|
904
908
|
.ds-data-table__grid--stacked .ds-data-table__actions {
|
|
905
909
|
order: 1;
|
|
910
|
+
margin-inline-start: auto;
|
|
906
911
|
}
|
|
907
912
|
|
|
908
913
|
/* A row has no drag on a card, and no filler column. */
|
|
@@ -922,11 +927,10 @@
|
|
|
922
927
|
gap: var(--gap-200);
|
|
923
928
|
}
|
|
924
929
|
|
|
930
|
+
/* A long header wraps in its quarter rather than losing its end. */
|
|
925
931
|
.ds-data-table__stack-label {
|
|
926
932
|
min-width: 0;
|
|
927
|
-
overflow:
|
|
928
|
-
text-overflow: ellipsis;
|
|
929
|
-
white-space: nowrap;
|
|
933
|
+
overflow-wrap: break-word;
|
|
930
934
|
font-family: var(--font-caption-family);
|
|
931
935
|
font-size: var(--font-caption-size);
|
|
932
936
|
font-weight: var(--font-caption-weight);
|
|
@@ -19,6 +19,8 @@ type PopoverOwnProps = {
|
|
|
19
19
|
surface?: 'panel' | 'menu';
|
|
20
20
|
/** Preferred position. `left` and `right` are screen sides; `start` and `end` follow the reading direction, so `end` opens to the right in English and to the left in Arabic. */
|
|
21
21
|
position?: 'top' | 'bottom' | 'left' | 'right' | 'start' | 'end';
|
|
22
|
+
/** Which edge of the trigger the panel lines up with, on the top and bottom sides. `start` and `end` follow the reading direction. Defaults to `start`. */
|
|
23
|
+
align?: 'start' | 'end';
|
|
22
24
|
/** Trigger mode */
|
|
23
25
|
trigger?: 'click' | 'hover';
|
|
24
26
|
/** Whether the popover is open (controlled mode) */
|
|
@@ -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);
|
|
@@ -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,
|