rift-ds 1.17.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.
Files changed (59) hide show
  1. package/behaviors/useAnchoredPosition.d.ts +3 -1
  2. package/behaviors/useAnchoredPosition.js +1 -0
  3. package/behaviors/useTopLayer.d.ts +35 -0
  4. package/behaviors/useTopLayer.js +22 -0
  5. package/bin/templates/blocks/app/dashboard/page.module.css +4 -3
  6. package/bin/templates/blocks/app/dashboard/page.tsx +1 -1
  7. package/bin/templates/blocks/components/blocks/AppShell.module.css +50 -0
  8. package/bin/templates/blocks/components/blocks/AppShell.tsx +25 -6
  9. package/chat/ChatLauncher.js +23 -0
  10. package/chat/ChatThreads.d.ts +3 -1
  11. package/chat/ChatThreads.js +3 -1
  12. package/chat/ChatWidget.css +8 -3
  13. package/components/AiButton/AiButton.css +11 -0
  14. package/components/AiButton/AiButton.d.ts +2 -0
  15. package/components/AiButton/AiButton.js +3 -1
  16. package/components/AppSidebar/AppSidebar.css +142 -52
  17. package/components/AppSidebar/AppSidebar.d.ts +2 -2
  18. package/components/AppSidebar/AppSidebar.js +16 -9
  19. package/components/Chart/BarChart.js +1 -0
  20. package/components/Chart/Chart.css +6 -0
  21. package/components/Chart/ComboChart.js +1 -0
  22. package/components/Chart/StackedBarChart.js +1 -0
  23. package/components/CommandPaletteTrigger/CommandPaletteTrigger.css +13 -1
  24. package/components/Composer/Composer.css +72 -0
  25. package/components/Composer/Composer.d.ts +5 -1
  26. package/components/Composer/Composer.js +8 -1
  27. package/components/DataTable/DataTable.css +7 -3
  28. package/components/Popover/Popover.d.ts +2 -0
  29. package/components/Popover/Popover.js +2 -1
  30. package/components/SegmentedControl/SegmentedControl.css +3 -1
  31. package/components/SegmentedControl/SegmentedControl.js +2 -0
  32. package/components/SidebarPanel/SidebarPanel.css +10 -2
  33. package/components/SidebarPanel/SidebarPanel.d.ts +1 -1
  34. package/components/SidebarPanel/SidebarPanel.js +5 -2
  35. package/components/SiteHeader/SiteHeader.css +63 -5
  36. package/components/SiteHeader/SiteHeader.d.ts +11 -3
  37. package/components/SiteHeader/SiteHeader.js +51 -16
  38. package/components/SourceChip/SourceChip.css +5 -1
  39. package/components/SourceChip/SourceChip.js +3 -0
  40. package/components/Stat/Stat.css +57 -0
  41. package/components/Stat/Stat.d.ts +8 -2
  42. package/components/Stat/Stat.js +4 -2
  43. package/components/TableToolbar/TableToolbar.css +130 -1
  44. package/components/TableToolbar/TableToolbar.d.ts +52 -0
  45. package/components/TableToolbar/TableToolbar.js +232 -32
  46. package/components/ThemeToggle/ThemeToggle.css +26 -0
  47. package/components/ThemeToggle/ThemeToggle.d.ts +11 -1
  48. package/components/ThemeToggle/ThemeToggle.js +36 -5
  49. package/components/Toast/Toast.css +4 -0
  50. package/components/Toast/Toast.d.ts +9 -0
  51. package/components/Toast/Toast.js +9 -2
  52. package/components/Tooltip/Tooltip.css +9 -4
  53. package/components/Tooltip/Tooltip.d.ts +4 -2
  54. package/components/Tooltip/Tooltip.js +16 -4
  55. package/index.js +2 -1
  56. package/package.json +1 -1
  57. package/tokens/motion.d.ts +2 -0
  58. package/tokens/motion.js +2 -0
  59. package/tokens/tokens-light.css +4 -3
@@ -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;
@@ -621,13 +671,12 @@
621
671
  inset-inline-start: calc(var(--ds-sidebar-float-inset, 20px) + 280px + var(--gap-300));
622
672
  }
623
673
 
624
- /* Inset: the side panel joins the rail on the shell plane, with no borders
625
- between them, so rail and panel read as one column of navigation and the
626
- inset content surface starts beyond both (AppLayout reserves the panel's
627
- width while it shows). */
674
+ /* Inset: the side panel joins the rail on the shell plane, one hairline
675
+ divider between the two levels, and the inset content surface starts
676
+ beyond both (AppLayout reserves the panel's width while it shows). The
677
+ trailing edge needs no border: the surface's own edge ends the panel. */
628
678
  .ds-app-sidebar__panel--inset {
629
679
  background-color: var(--color-bg-app-shell);
630
- border-inline-start-color: transparent;
631
680
  border-inline-end-color: transparent;
632
681
  }
633
682
 
@@ -638,85 +687,126 @@
638
687
  widens to 80px and names every destination:
639
688
  a pill indicator behind the icon, the label
640
689
  beneath (the navigation-rail pattern).
690
+
691
+ Expand/collapse holds one seat, as ThreadPanel
692
+ does: the logo, every icon and the avatar sit
693
+ at the same x and y in both states, so only the
694
+ rail's width, the rows' explicit heights and
695
+ the labels' opacity move. The caption under the
696
+ icon fades out as the inline label fades in.
641
697
  ============================================ */
642
698
  .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) {
643
699
  width: 80px;
644
700
  }
645
701
 
646
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__logo {
647
- /* Centres the 24px mark in the 80px rail: (80 - 24) / 2. */
648
- padding: 0 calc(var(--padding-500) + var(--padding-200));
702
+ /* The seat: 12px row margin + 16px row padding puts every 24px icon's
703
+ centre on the 80px rail's centre line (28 + 12 = 40), and the logo,
704
+ the category labels and the profile row share that leading edge. */
705
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__logo {
706
+ padding-inline-start: calc(var(--padding-500) + var(--padding-200));
707
+ }
708
+
709
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__category {
710
+ padding-inline-start: calc(var(--padding-500) + var(--padding-200));
649
711
  }
650
712
 
651
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__nav {
713
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__nav {
652
714
  gap: 0;
715
+ transition: gap var(--motion-duration-slow) var(--motion-ease-emphasized);
653
716
  }
654
717
 
655
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn {
656
- height: auto;
657
- margin: 0 var(--padding-100);
658
- /* Room between the content and the focus ring; the rows' spacing lives
659
- here rather than in the nav gap, so the rhythm is unchanged. */
660
- padding: var(--padding-100) 0;
661
- justify-content: center;
662
- border-radius: var(--radius-300);
718
+ .ds-app-sidebar--panel-mode.ds-app-sidebar--expanded .ds-app-sidebar__nav {
719
+ gap: var(--gap-100);
720
+ }
721
+
722
+ /* The icon is pinned to the row's top in both states, so it holds its
723
+ y while the row's height glides between its two explicit values. */
724
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__btn {
725
+ position: relative;
726
+ isolation: isolate;
727
+ align-items: flex-start;
728
+ /* Collapsed: the 32px indicator, the caption beneath it, a 4px beat
729
+ above, between and below. */
730
+ height: calc(32px + var(--font-caption-line-height) + var(--padding-100) * 3);
731
+ margin: 0 var(--padding-300);
732
+ padding: var(--padding-200) var(--padding-200) var(--padding-200) var(--padding-400);
663
733
  overflow: visible;
734
+ transition:
735
+ background-color var(--motion-duration-fast) var(--motion-ease-standard),
736
+ height var(--motion-duration-slow) var(--motion-ease-emphasized);
664
737
  }
665
738
 
666
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn:hover,
667
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active {
668
- background-color: transparent;
739
+ .ds-app-sidebar--panel-mode.ds-app-sidebar--expanded .ds-app-sidebar__btn {
740
+ height: 40px;
669
741
  }
670
742
 
671
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn-left {
672
- flex-direction: column;
673
- gap: var(--gap-100);
674
- width: 100%;
743
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__btn-left {
744
+ height: 24px;
675
745
  }
676
746
 
677
- /* The trailing chevron and count pill are faded out on any collapsed
678
- rail, but faded still takes width: on this centred column it pushed
679
- the icon and label off centre. Here they leave the layout outright. */
680
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn-chevron,
681
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn-badge {
682
- display: none;
747
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__btn-chevron {
748
+ flex-shrink: 0;
683
749
  }
684
750
 
685
751
  /* The indicator: a 56 x 32 pill behind the icon carries hover and the
686
- selection, so the label stays legible on the page ground. */
687
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn-icon {
688
- display: flex;
689
- align-items: center;
690
- justify-content: center;
752
+ selection while collapsed, so the caption stays legible on the page
753
+ ground; expanded, the row's own pill takes over and the two crossfade. */
754
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__btn::before {
755
+ content: '';
756
+ position: absolute;
757
+ top: var(--padding-100);
758
+ inset-inline-start: 0;
759
+ z-index: -1;
691
760
  width: 56px;
692
761
  height: 32px;
693
762
  border-radius: var(--radius-pill);
763
+ background-color: transparent;
694
764
  transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
695
765
  }
696
766
 
697
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn:hover .ds-app-sidebar__btn-icon,
698
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active .ds-app-sidebar__btn-icon {
767
+ .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn:hover,
768
+ .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active {
769
+ background-color: transparent;
770
+ }
771
+
772
+ .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn:hover::before,
773
+ .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active::before {
699
774
  background-color: var(--color-action-passive-bg-hover);
700
775
  }
701
776
 
702
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn-label {
703
- opacity: 1;
704
- max-width: 100%;
777
+ /* The caption: centred under the indicator, the rail's 72px wide
778
+ (80 less a 4px inset either side), fading out as the rail opens. */
779
+ .ds-app-sidebar__btn-caption {
780
+ position: absolute;
781
+ top: calc(32px + var(--padding-100) * 2);
782
+ inset-inline-start: calc(var(--padding-200) * -1);
783
+ width: 72px;
705
784
  font-family: var(--font-caption-family);
706
785
  font-size: var(--font-caption-size);
707
786
  font-weight: var(--font-caption-weight);
708
787
  line-height: var(--font-caption-line-height);
709
788
  letter-spacing: var(--font-caption-letter-spacing);
789
+ color: var(--color-text-primary);
710
790
  text-align: center;
791
+ white-space: nowrap;
792
+ overflow: hidden;
711
793
  text-overflow: ellipsis;
794
+ pointer-events: none;
795
+ opacity: 1;
796
+ transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
797
+ }
798
+
799
+ .ds-app-sidebar--expanded .ds-app-sidebar__btn-caption {
800
+ opacity: 0;
801
+ transition-duration: var(--motion-duration-fast);
712
802
  }
713
803
 
714
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active .ds-app-sidebar__btn-label {
804
+ .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active .ds-app-sidebar__btn-caption {
715
805
  font-weight: var(--font-paragraph-sm-emphasis-weight);
716
806
  }
717
807
 
718
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__profile {
719
- /* Centres the 40px avatar in the 80px rail. */
808
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__profile {
809
+ /* Centres the 40px avatar in the 80px rail, and holds it there open. */
720
810
  padding: 0 var(--padding-500);
721
811
  }
722
812
 
@@ -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 count pill, e.g. unread items */
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 count pill, e.g. unread items; hidden while the rail is collapsed */
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,16 +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
- 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(
319
- "span",
320
- {
321
- className: `${baseClass}__btn-icon${typeof item.icon === "string" ? ` material-symbols-rounded ${mirrorClass(item.icon)}` : ""}`,
322
- children: item.icon
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
- item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-badge`, children: item.badge }),
333
+ isPanelMode && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-caption`, "aria-hidden": "true", children: item.label }),
334
+ item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-badge${isCount(item.badge) ? ` ${baseClass}__btn-badge--count` : ""}`, children: item.badge }),
328
335
  hasChildren && /* @__PURE__ */ jsx(
329
336
  "span",
330
337
  {
@@ -390,7 +397,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
390
397
  ].filter(Boolean).join(" ");
391
398
  const subLabel = /* @__PURE__ */ jsxs(Fragment, { children: [
392
399
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__sub-label`, children: sub.label }),
393
- 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 }),
394
401
  subHasChildren && /* @__PURE__ */ jsx(
395
402
  "span",
396
403
  {
@@ -121,6 +121,7 @@ const BarChart = React.forwardRef(({
121
121
  Bar,
122
122
  {
123
123
  ...animation,
124
+ className: `${baseClass}__bar`,
124
125
  dataKey: yKey,
125
126
  fill: resolvedBarColor,
126
127
  radius: [3, 3, 0, 0],
@@ -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
@@ -138,6 +138,7 @@ const ComboChart = React.forwardRef(({
138
138
  Bar,
139
139
  {
140
140
  ...animation,
141
+ className: `${baseClass}__bar`,
141
142
  yAxisId: "bar",
142
143
  dataKey: barKey,
143
144
  name: resolvedBarLabel,
@@ -106,6 +106,7 @@ const StackedBarChart = React.forwardRef(({
106
106
  Bar,
107
107
  {
108
108
  ...animation,
109
+ className: `${baseClass}__bar`,
109
110
  dataKey: s.dataKey,
110
111
  name: s.label,
111
112
  fill: s.color || defaultColors[i % defaultColors.length],
@@ -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
- /** Growth cap in text rows before the textarea scrolls internally. */
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
- maxRows = 8,
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: hidden;
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: "start",
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,
@@ -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;