rift-ds 1.11.0 → 1.13.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 (133) hide show
  1. package/README.md +8 -2
  2. package/behaviors/LinkProvider.d.ts +36 -0
  3. package/behaviors/LinkProvider.js +15 -0
  4. package/behaviors/visually-hidden.css +24 -0
  5. package/bin/rift-ds.mjs +5 -2
  6. package/chat/ChatAgentPane.css +5 -1
  7. package/chat/ChatProvider.d.ts +5 -1
  8. package/chat/ChatProvider.js +11 -1
  9. package/chat/ChatWidget.css +0 -36
  10. package/chat/ResponseActions.js +2 -1
  11. package/components/Accordion/Accordion.css +10 -3
  12. package/components/AgentPlan/AgentPlan.css +0 -12
  13. package/components/AgentPlan/AgentPlan.js +2 -1
  14. package/components/AgentStatus/AgentStatus.css +1 -1
  15. package/components/AiButton/AiButton.d.ts +7 -0
  16. package/components/AiButton/AiButton.js +4 -1
  17. package/components/AnimatedNumber/AnimatedNumber.css +0 -12
  18. package/components/AnimatedNumber/AnimatedNumber.js +2 -1
  19. package/components/AppLayout/AppLayout.d.ts +7 -0
  20. package/components/AppLayout/AppLayout.js +3 -1
  21. package/components/AppSidebar/AppSidebar.css +2 -2
  22. package/components/AppSidebar/AppSidebar.d.ts +7 -0
  23. package/components/AppSidebar/AppSidebar.js +8 -3
  24. package/components/Attachment/Attachment.css +0 -17
  25. package/components/Attachment/AttachmentTile.js +3 -2
  26. package/components/BottomNav/BottomNav.css +0 -9
  27. package/components/BottomNav/BottomNav.d.ts +7 -0
  28. package/components/BottomNav/BottomNav.js +7 -3
  29. package/components/Breadcrumb/Breadcrumb.d.ts +8 -0
  30. package/components/Breadcrumb/Breadcrumb.js +3 -2
  31. package/components/Button/Button.d.ts +7 -0
  32. package/components/Button/Button.js +4 -1
  33. package/components/Card/Card.d.ts +7 -0
  34. package/components/Card/Card.js +4 -1
  35. package/components/CardStack/CardStack.css +0 -11
  36. package/components/CardStack/CardStack.js +2 -1
  37. package/components/ChatMessage/ChatMessage.css +0 -12
  38. package/components/ChatMessage/ChatMessage.js +2 -1
  39. package/components/Checkbox/Checkbox.css +34 -25
  40. package/components/Checkbox/Checkbox.d.ts +34 -0
  41. package/components/Checkbox/Checkbox.js +64 -36
  42. package/components/CircularButton/CircularButton.d.ts +8 -0
  43. package/components/CircularButton/CircularButton.js +3 -1
  44. package/components/CodeDiff/CodeDiff.css +0 -11
  45. package/components/CodeDiff/CodeDiff.js +3 -2
  46. package/components/ColourSwatch/ColourSwatch.css +1 -0
  47. package/components/Combobox/Combobox.css +1 -8
  48. package/components/Combobox/Combobox.js +2 -1
  49. package/components/CommandPalette/CommandPalette.css +2 -25
  50. package/components/CommandPalette/CommandPalette.js +8 -5
  51. package/components/Composer/Composer.css +0 -12
  52. package/components/Composer/Composer.js +3 -2
  53. package/components/ContactCard/ContactCard.css +1 -1
  54. package/components/ContactCard/ContactCard.d.ts +7 -0
  55. package/components/ContactCard/ContactCard.js +4 -1
  56. package/components/ContextMenu/ContextMenu.css +1 -9
  57. package/components/ContextMenu/ContextMenu.js +3 -2
  58. package/components/Dialog/Dialog.css +2 -26
  59. package/components/Dialog/Dialog.js +8 -12
  60. package/components/Drawer/Drawer.css +2 -26
  61. package/components/Drawer/Drawer.js +8 -12
  62. package/components/Dropdown/ChosenMark.css +19 -0
  63. package/components/Dropdown/Dropdown.css +0 -12
  64. package/components/Dropdown/Dropdown.d.ts +23 -0
  65. package/components/Dropdown/Dropdown.js +18 -17
  66. package/components/DropdownMenu/DropdownMenu.css +3 -9
  67. package/components/DropdownMenu/DropdownMenu.js +3 -2
  68. package/components/FilterBar/FilterBar.css +0 -8
  69. package/components/FilterBar/FilterBar.js +9 -6
  70. package/components/GanttChart/GanttChart.css +0 -13
  71. package/components/GanttChart/GanttChart.js +3 -2
  72. package/components/Inspector/InspectorToggleSwitch.js +5 -10
  73. package/components/InterruptCard/InterruptCard.css +0 -12
  74. package/components/InterruptCard/InterruptCard.js +2 -1
  75. package/components/LinkList/LinkList.d.ts +8 -0
  76. package/components/LinkList/LinkList.js +3 -2
  77. package/components/ModelPicker/ModelPicker.css +4 -45
  78. package/components/ModelPicker/ModelPicker.js +13 -50
  79. package/components/MotionSwatch/MotionSwatch.css +1 -1
  80. package/components/NavList/NavList.d.ts +7 -0
  81. package/components/NavList/NavList.js +2 -0
  82. package/components/NotificationCenter/NotificationCenter.css +1 -13
  83. package/components/NotificationCenter/NotificationCenter.js +2 -1
  84. package/components/PromptSuggestions/PromptSuggestions.css +0 -11
  85. package/components/PromptSuggestions/PromptSuggestions.js +3 -2
  86. package/components/QuestionCard/QuestionCard.css +13 -22
  87. package/components/QuestionCard/QuestionCard.js +8 -13
  88. package/components/RadioButton/RadioButton.css +27 -11
  89. package/components/RadioButton/RadioButton.d.ts +18 -0
  90. package/components/RadioButton/RadioButton.js +12 -2
  91. package/components/RichDropdown/RichDropdown.css +0 -9
  92. package/components/RichDropdown/RichDropdown.js +2 -8
  93. package/components/SegmentedControl/SegmentedControl.css +2 -14
  94. package/components/SegmentedControl/SegmentedControl.d.ts +7 -0
  95. package/components/SegmentedControl/SegmentedControl.js +4 -2
  96. package/components/SelectionCard/SelectionCard.css +29 -160
  97. package/components/SelectionCard/SelectionCard.js +5 -35
  98. package/components/SidebarPanel/SidebarPanel.css +1 -1
  99. package/components/SidebarPanel/SidebarPanel.d.ts +7 -0
  100. package/components/SidebarPanel/SidebarPanel.js +4 -1
  101. package/components/SourceChip/SourceChip.d.ts +7 -0
  102. package/components/SourceChip/SourceChip.js +5 -3
  103. package/components/SourceTrail/SourceTrail.css +0 -13
  104. package/components/SourceTrail/SourceTrail.d.ts +7 -0
  105. package/components/SourceTrail/SourceTrail.js +4 -1
  106. package/components/SpacingSwatch/SpacingSwatch.css +1 -1
  107. package/components/Stepper/Stepper.css +0 -13
  108. package/components/Stepper/Stepper.js +2 -1
  109. package/components/Table/Table.css +0 -22
  110. package/components/Table/Table.js +3 -2
  111. package/components/ThreadPanel/ThreadPanel.css +2 -16
  112. package/components/ThreadPanel/ThreadPanel.d.ts +8 -0
  113. package/components/ThreadPanel/ThreadPanel.js +12 -4
  114. package/components/ThreadTabs/ThreadTabs.css +1 -15
  115. package/components/ThreadTabs/ThreadTabs.js +2 -1
  116. package/components/TimePicker/TimePicker.css +0 -12
  117. package/components/TimePicker/TimePicker.js +2 -8
  118. package/components/ToggleGroup/ToggleGroup.css +9 -2
  119. package/components/ToggleSwitch/ToggleSwitch.css +33 -27
  120. package/components/ToggleSwitch/ToggleSwitch.d.ts +19 -0
  121. package/components/ToggleSwitch/ToggleSwitch.js +17 -10
  122. package/components/Tooltip/Tooltip.css +33 -36
  123. package/components/Tooltip/Tooltip.d.ts +10 -0
  124. package/components/Tooltip/Tooltip.js +72 -3
  125. package/components/TopAppBar/TopAppBar.d.ts +7 -0
  126. package/components/TopAppBar/TopAppBar.js +4 -1
  127. package/components/Waveform/Waveform.css +0 -12
  128. package/components/Waveform/Waveform.js +2 -1
  129. package/components/Widget/Widget.d.ts +8 -0
  130. package/components/Widget/Widget.js +3 -2
  131. package/index.js +10 -4
  132. package/package.json +5 -1
  133. package/tokens/presets/mono.css +168 -168
package/README.md CHANGED
@@ -62,7 +62,13 @@ Swap the scripted transport for `createFetchTransport('/api/chat')` and the pane
62
62
 
63
63
  ## Set up your agent
64
64
 
65
- One command teaches a coding agent the system: it installs the generated agent skills into your project and prints the MCP connect line. The skills cover the library's rules, moving existing screens onto the tokens and components, and auditing styles against them.
65
+ The quickest start is one file: save [AGENTS.md](https://rift-ds.com/AGENTS.md) at your project root, or paste it into your agent. It holds the install, the setup, the rules, and where to look things up.
66
+
67
+ ```bash
68
+ curl -o AGENTS.md https://rift-ds.com/AGENTS.md
69
+ ```
70
+
71
+ For the full setup, one command teaches a coding agent the system: it installs the generated agent skills into your project and prints the MCP connect line. The skills cover the library's rules, moving existing screens onto the tokens and components, and auditing styles against them.
66
72
 
67
73
  ```bash
68
74
  npx rift-ds init
@@ -74,7 +80,7 @@ The MCP endpoint serves the component catalogue, per-component prop APIs, the to
74
80
  claude mcp add --transport http rift-ds https://rift-ds.com/api/mcp
75
81
  ```
76
82
 
77
- Every component's prop contract is also plain markdown (append `.md` to its docs URL), and [llms.txt](https://rift-ds.com/llms.txt) indexes every machine surface. A theme made in the [playground](https://rift-ds.com/playground) exports as CSS, a THEME.md and a setup prompt, so the agent can read the look as well as the code. Then just ask: "Build a settings page with Rift components."
83
+ Every component's prop contract is also plain markdown (append `.md` to its docs URL), and [llms.txt](https://rift-ds.com/llms.txt) indexes every machine surface. A theme made in the [playground](https://rift-ds.com/playground) exports as one AGENTS.md that carries the setup, your theme in words and its CSS, so the agent can read the look as well as the code. Then just ask: "Build a settings page with Rift components."
78
84
 
79
85
  ## Theming
80
86
 
@@ -0,0 +1,36 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * The props a link component receives: every anchor attribute, the ref, and
4
+ * an `href` that is always a string. Next.js's `Link` takes these as they
5
+ * are; a router whose link takes `to` instead needs a one-line adapter:
6
+ * `({ href, ...props }) => <RouterLink to={href} {...props} />`.
7
+ */
8
+ export type LinkComponentProps = React.ComponentPropsWithRef<'a'> & {
9
+ href: string;
10
+ };
11
+ /** A component that renders a link in place of a plain `<a>`, such as a router's. */
12
+ export type LinkComponent = React.ElementType<LinkComponentProps>;
13
+ export interface LinkProviderProps {
14
+ /**
15
+ * The link component every rift-ds link under this provider renders, such
16
+ * as Next.js's `Link`. Named like the instance prop it stands in for.
17
+ */
18
+ linkComponent: LinkComponent;
19
+ /** The part of the app whose links it applies to, usually the whole app. */
20
+ children?: React.ReactNode;
21
+ }
22
+ /**
23
+ * Sets the link component for every client-rendered rift-ds component below
24
+ * it that takes an `href`, so their links navigate through the app's router
25
+ * instead of reloading the page. A component's own `linkComponent` prop
26
+ * wins over it. The link components that can render on the server cannot
27
+ * read a provider and take the prop alone (design.md's Components intro
28
+ * names them, and scripts/validate-component-contract.mjs keeps them off
29
+ * the provider).
30
+ */
31
+ export declare function LinkProvider({ linkComponent, children }: LinkProviderProps): React.JSX.Element;
32
+ /**
33
+ * The link component to render: the component's own `linkComponent` prop,
34
+ * else the nearest LinkProvider's, else a plain `<a>`.
35
+ */
36
+ export declare function useLinkComponent(override?: LinkComponent): LinkComponent;
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from "react";
4
+ const LinkContext = createContext(null);
5
+ function LinkProvider({ linkComponent, children }) {
6
+ return /* @__PURE__ */ jsx(LinkContext.Provider, { value: linkComponent, children });
7
+ }
8
+ function useLinkComponent(override) {
9
+ const provided = useContext(LinkContext);
10
+ return override ?? provided ?? "a";
11
+ }
12
+ export {
13
+ LinkProvider,
14
+ useLinkComponent
15
+ };
@@ -0,0 +1,24 @@
1
+ /* ============================================
2
+ VISUALLY HIDDEN
3
+ The one recipe for text that is read but not
4
+ seen: a status for a live region, a control's
5
+ spoken name, a marker beside a visual state.
6
+ Every component imports this sheet and uses
7
+ ds-visually-hidden rather than writing the
8
+ recipe again (scripts/validate-shared-primitives.mjs
9
+ fails a second copy). A hidden native input
10
+ that must stay focusable is not this: it
11
+ keeps its own rule, with the reason beside it.
12
+ ============================================ */
13
+
14
+ .ds-visually-hidden {
15
+ position: absolute;
16
+ width: 1px;
17
+ height: 1px;
18
+ padding: 0;
19
+ margin: -1px;
20
+ overflow: hidden;
21
+ clip: rect(0, 0, 0, 0);
22
+ white-space: nowrap;
23
+ border: 0;
24
+ }
package/bin/rift-ds.mjs CHANGED
@@ -28,7 +28,8 @@
28
28
  * Zero dependencies by design: global fetch plus node:fs, so npx never
29
29
  * installs anything beyond the package itself. SITE_ORIGIN, SKILL_NAME,
30
30
  * MCP_SERVER_NAME and PACKAGE_NAME are stamped by build-package.mjs from
31
- * scripts/brand.mjs (the one home for brand facts); running this file
31
+ * scripts/brand.mjs (the one home for brand facts), and CHAT_SLUG from
32
+ * the chat registry, the widget page's slug; running this file
32
33
  * straight from source fails fast rather than guessing a domain.
33
34
  * SKILL_NAME is the library skill, kept only for --out without --skill,
34
35
  * which has always meant that one.
@@ -41,6 +42,7 @@ const SITE_ORIGIN = 'https://rift-ds.com';
41
42
  const SKILL_NAME = 'rift-design-system';
42
43
  const MCP_SERVER_NAME = 'rift-ds';
43
44
  const PACKAGE_NAME = 'rift-ds';
45
+ const CHAT_SLUG = 'ai-chat';
44
46
  const MANIFEST_URL = `${SITE_ORIGIN}/skill/manifest.json`;
45
47
  const DEFAULT_DIR = join('.claude', 'skills');
46
48
  const TEMPLATES_DIR = join(dirname(fileURLToPath(import.meta.url)), 'templates', 'chat');
@@ -229,7 +231,8 @@ function initChat({ app, dryRun }) {
229
231
  console.log(
230
232
  ` 4. Your layout's viewport should set interactiveWidget: "resizes-content", so the phone takeover ends above the keyboard.`
231
233
  );
232
- console.log(`\nThe widget's props, protocol and server options: ${SITE_ORIGIN}/chat`);
234
+ console.log(`\nHow to add it, the protocol and the server options: ${SITE_ORIGIN}/docs/chat`);
235
+ console.log(`Every prop: ${SITE_ORIGIN}/components/${CHAT_SLUG}`);
233
236
  }
234
237
 
235
238
  const args = process.argv.slice(2);
@@ -31,12 +31,16 @@
31
31
  margin: 0;
32
32
  }
33
33
 
34
+ /* The chat's card surface: container fill behind a hairline, like every
35
+ card in the conversation. The radius stays a step under the rail's
36
+ --radius-600, concentric with the shell it nests in. */
34
37
  .ds-chat-agent-pane__card {
35
38
  display: flex;
36
39
  flex-direction: column;
37
40
  gap: var(--gap-200);
38
41
  padding: var(--padding-300);
39
- background-color: var(--color-bg-container-secondary);
42
+ background-color: var(--color-bg-container-primary);
43
+ border: var(--border-025) solid var(--color-bg-container-border);
40
44
  border-radius: var(--radius-400);
41
45
  }
42
46
 
@@ -36,7 +36,11 @@ export interface ChatProviderProps {
36
36
  renderers?: ChatCardRenderers;
37
37
  /** Draws an artifact's preview by kind, for artifacts that are live React content rather than a document or a URL. */
38
38
  artifactRenderers?: ChatArtifactRenderers;
39
- /** Renders a same-site link in an answer. The default is a plain anchor; a Next.js host passes its Link. */
39
+ /**
40
+ * Renders a same-site link in an answer. The default renders through the
41
+ * nearest LinkProvider's link component, else a plain anchor; passing
42
+ * this wins over both.
43
+ */
40
44
  renderLink?: ChatLinkRenderer;
41
45
  /** Which hrefs in an answer become links. Everything else renders as text. */
42
46
  isInternalHref?: (href: string) => boolean;
@@ -7,7 +7,17 @@ import { createFollowupsFetcher, followupTarget, DEFAULT_FALLBACK_FOLLOWUPS, pic
7
7
  import { useThreads } from "./threads.js";
8
8
  import { createFetchTransport } from "./transport.js";
9
9
  import { DEFAULT_PLACEHOLDER_COPY, useChat } from "./useChat.js";
10
- const defaultRenderLink = ({ href, children, onNavigate }) => /* @__PURE__ */ jsx("a", { href, onClick: onNavigate, children });
10
+ import { useLinkComponent } from "../behaviors/LinkProvider.js";
11
+ function DefaultChatLink({ href, children, onNavigate }) {
12
+ const Link = useLinkComponent();
13
+ return (
14
+ // The hook returns a stable reference from context (or 'a'), never a
15
+ // component created here, which is what the rule guards against.
16
+ // eslint-disable-next-line react-hooks/static-components
17
+ /* @__PURE__ */ jsx(Link, { href, onClick: onNavigate, children })
18
+ );
19
+ }
20
+ const defaultRenderLink = (props) => /* @__PURE__ */ jsx(DefaultChatLink, { ...props });
11
21
  function ChatProvider({
12
22
  transport,
13
23
  endpoint = "/api/chat",
@@ -61,29 +61,6 @@
61
61
  min-height: calc(40px + var(--padding-150) * 2);
62
62
  }
63
63
 
64
- /* The close button hugs the panel's clipped right edge, so its centred
65
- bottom tooltip would lose its right cap to overflow: hidden. End-align
66
- the last header tooltip to the button and walk the arrow back to the
67
- button's centre (20px in, minus half the 8px arrow). */
68
- .ds-chat-widget .ds-chat-header__actions .ds-tooltip:last-child .ds-tooltip__panel--bottom,
69
- .ds-chat-widget__rail-actions .ds-tooltip:last-child .ds-tooltip__panel--bottom {
70
- left: auto;
71
- right: 0;
72
- transform: translateY(-4px);
73
- }
74
-
75
- .ds-chat-widget .ds-chat-header__actions .ds-tooltip:last-child .ds-tooltip__panel--bottom.ds-tooltip__panel--visible,
76
- .ds-chat-widget__rail-actions .ds-tooltip:last-child .ds-tooltip__panel--bottom.ds-tooltip__panel--visible {
77
- transform: translateY(0);
78
- }
79
-
80
- .ds-chat-widget .ds-chat-header__actions .ds-tooltip:last-child .ds-tooltip__panel--bottom .ds-tooltip__arrow,
81
- .ds-chat-widget__rail-actions .ds-tooltip:last-child .ds-tooltip__panel--bottom .ds-tooltip__arrow {
82
- left: auto;
83
- right: 16px;
84
- margin-left: 0;
85
- }
86
-
87
64
  /* The placement menu's wrapper is only there to catch Escape; it must not
88
65
  become a box of its own in the header's action row. */
89
66
  .ds-chat-widget__placement-menu {
@@ -560,19 +537,6 @@
560
537
  }
561
538
  }
562
539
 
563
- /* Visually hidden, still announced. */
564
- .ds-chat-widget__sr-only {
565
- position: absolute;
566
- width: 1px;
567
- height: 1px;
568
- margin: -1px;
569
- padding: 0;
570
- overflow: hidden;
571
- clip: rect(0 0 0 0);
572
- white-space: nowrap;
573
- border: 0;
574
- }
575
-
576
540
  /* ============================================
577
541
  THE RAILS — the optional session-history rail (leading) and agent pane
578
542
  (trailing). Wide widgets seat them inline; narrow ones summon the history
@@ -3,6 +3,7 @@ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext, useState, useRef, useEffect } from "react";
4
4
  import { MOTION_FEEDBACK_RESET_MS } from "../tokens/motion.js";
5
5
  import { useChatWidget } from "./context.js";
6
+ import "../behaviors/visually-hidden.css";
6
7
  const ResponseActionsLabelsContext = createContext({
7
8
  copy: "Copy response",
8
9
  copied: "Copied",
@@ -79,7 +80,7 @@ function ResponseActions({ turn }) {
79
80
  }
80
81
  )
81
82
  ] }),
82
- /* @__PURE__ */ jsx("span", { className: "ds-chat-widget__sr-only", "aria-live": "polite", children: copied ? labels.copiedAnnouncement : "" })
83
+ /* @__PURE__ */ jsx("span", { className: "ds-visually-hidden", "aria-live": "polite", children: copied ? labels.copiedAnnouncement : "" })
83
84
  ] });
84
85
  }
85
86
  export {
@@ -3,12 +3,17 @@
3
3
  Collapsible content sections
4
4
  ============================================ */
5
5
 
6
+ /* The standard card surface (container fill, hairline border,
7
+ --radius-300), the one SelectionCard's and QuestionCard's option rows
8
+ share, so a collapsible section reads as a card on the page rather than
9
+ a hairline floating over it. */
6
10
  .ds-accordion {
7
11
  display: flex;
8
12
  flex-direction: column;
9
13
  width: 100%;
10
14
  border: var(--border-025) solid var(--color-bg-container-border);
11
- border-radius: var(--radius-200);
15
+ border-radius: var(--radius-300);
16
+ background-color: var(--color-bg-container-primary);
12
17
  overflow: hidden;
13
18
  }
14
19
 
@@ -42,14 +47,16 @@
42
47
  transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
43
48
  }
44
49
 
50
+ /* Hover steps the header to the secondary fill, the collapsible-section
51
+ convention the agent cards (AgentPlan, SourceTrail, ToolCall) set. */
45
52
  .ds-accordion__trigger:hover {
46
- background-color: var(--color-action-passive-bg-hover);
53
+ background-color: var(--color-bg-container-secondary);
47
54
  }
48
55
 
49
56
  .ds-accordion__trigger:focus-visible {
50
57
  outline: 2px solid var(--color-action-primary-bg);
51
58
  outline-offset: -2px;
52
- border-radius: var(--radius-200);
59
+ border-radius: var(--radius-300);
53
60
  }
54
61
 
55
62
  /* ============================================
@@ -229,18 +229,6 @@
229
229
  this span announces it.
230
230
  ============================================ */
231
231
 
232
- .ds-agent-plan__sr {
233
- position: absolute;
234
- width: 1px;
235
- height: 1px;
236
- padding: 0;
237
- margin: -1px;
238
- overflow: hidden;
239
- clip: rect(0, 0, 0, 0);
240
- white-space: nowrap;
241
- border: 0;
242
- }
243
-
244
232
  /* Icons take the colour of the text around them. Explicit, not just
245
233
  inherited by default: a consumer stylesheet that sets a colour on
246
234
  .material-symbols-rounded itself (a common global default) would
@@ -3,6 +3,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
3
3
  import React, { useId, useState, useCallback } from "react";
4
4
  import { Spinner } from "../Spinner/Spinner.js";
5
5
  import "./AgentPlan.css";
6
+ import "../../behaviors/visually-hidden.css";
6
7
  import "../../fonts/material-symbols.css";
7
8
  const STEP_ICONS = {
8
9
  pending: "circle",
@@ -88,7 +89,7 @@ const AgentPlan = React.forwardRef(
88
89
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: step.label }),
89
90
  step.detail && /* @__PURE__ */ jsx("span", { className: `${baseClass}__detail`, children: step.detail })
90
91
  ] }),
91
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__sr`, children: statusText[status] })
92
+ /* @__PURE__ */ jsx("span", { className: "ds-visually-hidden", children: statusText[status] })
92
93
  ]
93
94
  },
94
95
  index
@@ -219,7 +219,7 @@
219
219
  display: flex;
220
220
  width: 100%;
221
221
  padding: var(--padding-150) var(--padding-300);
222
- background-color: var(--color-bg-container-secondary);
222
+ background-color: var(--color-bg-container-primary);
223
223
  border: var(--border-025) solid var(--color-bg-container-border);
224
224
  border-radius: var(--radius-300);
225
225
  }
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  import { PromptSuggestion } from '../PromptSuggestions/PromptSuggestions';
3
4
  /** The content of AiButton's summary panel. */
4
5
  export interface AiButtonSummary {
@@ -59,6 +60,12 @@ type AiButtonOwnProps = {
59
60
  suggestionsLabel?: string;
60
61
  /** Additional CSS classes */
61
62
  className?: string;
63
+ /**
64
+ * The component the link renders with, such as Next.js's `Link`, so it
65
+ * navigates through the app's router. Wins over a LinkProvider's; a plain
66
+ * `<a>` when neither is set.
67
+ */
68
+ linkComponent?: LinkComponent;
62
69
  };
63
70
  export interface AiButtonProps extends AiButtonOwnProps, Omit<React.ComponentPropsWithoutRef<'button'>, keyof AiButtonOwnProps | 'type'> {
64
71
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import React, { useState, useRef, useEffect } from "react";
4
+ import { useLinkComponent } from "../../behaviors/LinkProvider.js";
4
5
  import { PromptSuggestions } from "../PromptSuggestions/PromptSuggestions.js";
5
6
  import { Skeleton } from "../Skeleton/Skeleton.js";
6
7
  import { StreamingText } from "../StreamingText/StreamingText.js";
@@ -25,9 +26,11 @@ const AiButton = React.forwardRef(
25
26
  dismissLabel = "Dismiss summary",
26
27
  suggestionsLabel = "Suggested prompts",
27
28
  className = "",
29
+ linkComponent,
28
30
  ...rest
29
31
  }, ref) => {
30
32
  const baseClass = "ds-ai-button";
33
+ const Link = useLinkComponent(linkComponent);
31
34
  const [hoverOpen, setHoverOpen] = useState(false);
32
35
  const [dismissed, setDismissed] = useState(false);
33
36
  const [phase, setPhase] = useState("skeleton");
@@ -91,7 +94,7 @@ const AiButton = React.forwardRef(
91
94
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__text`, children: label })
92
95
  ] });
93
96
  const control = href && !disabled ? /* @__PURE__ */ jsx(
94
- "a",
97
+ Link,
95
98
  {
96
99
  ...rest,
97
100
  ref,
@@ -17,15 +17,3 @@
17
17
  font-variant-numeric: tabular-nums;
18
18
  }
19
19
 
20
- /* Visually hidden settled value — what a screen reader gets instead of the
21
- rolling digits. */
22
- .ds-animated-number__sr {
23
- position: absolute;
24
- width: 1px;
25
- height: 1px;
26
- margin: -1px;
27
- padding: 0;
28
- overflow: hidden;
29
- clip-path: inset(50%);
30
- white-space: nowrap;
31
- }
@@ -2,6 +2,7 @@
2
2
  import { jsxs, jsx } from "react/jsx-runtime";
3
3
  import React, { useState, useRef, useEffect } from "react";
4
4
  import "./AnimatedNumber.css";
5
+ import "../../behaviors/visually-hidden.css";
5
6
  import { MOTION_COUNT_UP_MS } from "../../tokens/motion.js";
6
7
  const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
7
8
  const prefersReducedMotion = () => typeof window !== "undefined" && window.matchMedia(REDUCED_MOTION_QUERY).matches;
@@ -45,7 +46,7 @@ const AnimatedNumber = React.forwardRef(
45
46
  const classes = [baseClass, className].filter(Boolean).join(" ");
46
47
  return /* @__PURE__ */ jsxs("span", { ...rest, ref, className: classes, children: [
47
48
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__value`, "aria-hidden": "true", children: fmt(display) }),
48
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__sr`, children: fmt(value) })
49
+ /* @__PURE__ */ jsx("span", { className: "ds-visually-hidden", children: fmt(value) })
49
50
  ] });
50
51
  }
51
52
  );
@@ -1,5 +1,6 @@
1
1
  import { default as React } from 'react';
2
2
  import { AppSidebarSection, AppSidebarProfile } from '../AppSidebar/AppSidebar';
3
+ import { LinkComponent } from '../../behaviors/LinkProvider';
3
4
  /** Props owned by AppLayout itself — everything else falls through to the outer wrapper `<div>`. */
4
5
  type AppLayoutOwnProps = {
5
6
  /** Sidebar navigation sections */
@@ -28,6 +29,12 @@ type AppLayoutOwnProps = {
28
29
  * follows the surrounding data-theme like any other component.
29
30
  */
30
31
  theme?: 'dark' | 'inherit';
32
+ /**
33
+ * The component its links render with, such as Next.js's `Link`, so they
34
+ * navigate through the app's router; passed on to the AppSidebar rail. Wins over a
35
+ * LinkProvider's; a plain `<a>` when neither is set.
36
+ */
37
+ linkComponent?: LinkComponent;
31
38
  /** Additional CSS classes on outer wrapper */
32
39
  className?: string;
33
40
  };
@@ -16,6 +16,7 @@ const AppLayout = React.forwardRef(
16
16
  logo,
17
17
  children,
18
18
  theme = "dark",
19
+ linkComponent,
19
20
  className = "",
20
21
  ...rest
21
22
  }, ref) => {
@@ -49,7 +50,8 @@ const AppLayout = React.forwardRef(
49
50
  expanded,
50
51
  onExpandedChange: handleExpandedChange,
51
52
  logoText,
52
- logo
53
+ logo,
54
+ linkComponent
53
55
  }
54
56
  ),
55
57
  /* @__PURE__ */ jsx("main", { className: `${baseClass}__main`, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__content`, children }) })
@@ -211,7 +211,7 @@
211
211
  }
212
212
 
213
213
  .ds-app-sidebar__btn--active {
214
- background-color: var(--color-action-passive-bg-hover);
214
+ background-color: var(--color-action-passive-bg-active);
215
215
  }
216
216
 
217
217
  .ds-app-sidebar__btn-left {
@@ -357,7 +357,7 @@
357
357
  }
358
358
 
359
359
  .ds-app-sidebar__sub-btn--active {
360
- background-color: var(--color-action-passive-bg-hover);
360
+ background-color: var(--color-action-passive-bg-active);
361
361
  }
362
362
 
363
363
  .ds-app-sidebar__sub-label {
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  export interface AppSidebarSubItem {
3
4
  /** Unique key for this sub-item */
4
5
  key: string;
@@ -108,6 +109,12 @@ type AppSidebarOwnProps = {
108
109
  footerSlot?: React.ReactNode;
109
110
  /** Additional CSS classes */
110
111
  className?: string;
112
+ /**
113
+ * The component its links render with, such as Next.js's `Link`, so they
114
+ * navigate through the app's router. Wins over a LinkProvider's; a plain
115
+ * `<a>` when neither is set.
116
+ */
117
+ linkComponent?: LinkComponent;
111
118
  /** Logo element — defaults to the built-in brand mark */
112
119
  logo?: React.ReactNode;
113
120
  /** Text shown next to logo when expanded */
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsxs, Fragment, jsx } from "react/jsx-runtime";
3
3
  import React, { useState, useRef, useEffect, useCallback, useId } from "react";
4
+ import { useLinkComponent } from "../../behaviors/LinkProvider.js";
4
5
  import { useLayer } from "../../behaviors/useLayer.js";
5
6
  import { useFocusScope } from "../../behaviors/useFocusScope.js";
6
7
  import { useScrollLock } from "../../behaviors/useScrollLock.js";
@@ -56,9 +57,11 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
56
57
  collapseLabel = "Collapse sidebar",
57
58
  profileMoreLabel = "Profile options",
58
59
  menuLabel = "Menu",
60
+ linkComponent,
59
61
  ...rest
60
62
  }, ref) {
61
63
  const baseClass = "ds-app-sidebar";
64
+ const Link = useLinkComponent(linkComponent);
62
65
  const avatarSeed = (item) => typeof item.pixelAvatar === "string" ? item.pixelAvatar : item.label;
63
66
  const avatarItems = sections.flatMap(
64
67
  (section) => section.items.filter((item) => item.pixelAvatar)
@@ -279,6 +282,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
279
282
  mobileOpen && drilled && panelSource ? /* @__PURE__ */ jsx(
280
283
  SidebarPanel,
281
284
  {
285
+ linkComponent: Link,
282
286
  className: `${baseClass}__drill`,
283
287
  items: panelSource.items,
284
288
  title: panelSource.label,
@@ -327,8 +331,8 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
327
331
  )
328
332
  ] });
329
333
  return /* @__PURE__ */ jsxs(React.Fragment, { children: [
330
- isLink ? /* @__PURE__ */ jsx(
331
- "a",
334
+ isLink && item.href ? /* @__PURE__ */ jsx(
335
+ Link,
332
336
  {
333
337
  className: itemClasses,
334
338
  href: item.href,
@@ -408,7 +412,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
408
412
  children: subLabel
409
413
  }
410
414
  ) : sub.href ? /* @__PURE__ */ jsx(
411
- "a",
415
+ Link,
412
416
  {
413
417
  className: subClasses,
414
418
  href: sub.href,
@@ -467,6 +471,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
467
471
  panelSource && !mobileOpen && /* @__PURE__ */ jsx(
468
472
  SidebarPanel,
469
473
  {
474
+ linkComponent: Link,
470
475
  id: panelId,
471
476
  className: [
472
477
  `${baseClass}__panel`,
@@ -481,23 +481,6 @@ button.ds-attachment-tile__body:focus-visible,
481
481
  outline-offset: 2px;
482
482
  }
483
483
 
484
- /* ============================================
485
- VISUALLY HIDDEN
486
- ============================================ */
487
-
488
- .ds-attachment-tile__sr-only,
489
- .ds-attachment-group__sr-only {
490
- position: absolute;
491
- width: 1px;
492
- height: 1px;
493
- padding: 0;
494
- margin: -1px;
495
- overflow: hidden;
496
- clip: rect(0, 0, 0, 0);
497
- white-space: nowrap;
498
- border: 0;
499
- }
500
-
501
484
  /* ============================================
502
485
  REDUCED MOTION
503
486
  The global guard collapses the spin to a still arc, which still reads as
@@ -5,6 +5,7 @@ import { formatBytes, getFileExtension, getFileKind, getFileTypeLabel, matchesAc
5
5
  import { renderTypeMark } from "./typeMark.js";
6
6
  import "../../fonts/material-symbols.css";
7
7
  import "./Attachment.css";
8
+ import "../../behaviors/visually-hidden.css";
8
9
  const AttachmentTile = React.forwardRef(
9
10
  ({
10
11
  name,
@@ -97,7 +98,7 @@ const AttachmentTile = React.forwardRef(
97
98
  const stack = /* @__PURE__ */ jsxs("span", { className: `${baseClass}__stack`, children: [
98
99
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__mark`, children: mark }),
99
100
  /* @__PURE__ */ jsxs("span", { className: `${baseClass}__name`, title: name, children: [
100
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__sr-only`, children: name }),
101
+ /* @__PURE__ */ jsx("span", { className: "ds-visually-hidden", children: name }),
101
102
  head !== "" && /* @__PURE__ */ jsx("span", { className: `${baseClass}__name-head`, "aria-hidden": "true", children: head }),
102
103
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__name-tail`, "aria-hidden": "true", children: tail })
103
104
  ] }),
@@ -111,7 +112,7 @@ const AttachmentTile = React.forwardRef(
111
112
  ] });
112
113
  } else if (showExcerpt) {
113
114
  content = /* @__PURE__ */ jsxs(Fragment, { children: [
114
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__sr-only`, children: name }),
115
+ /* @__PURE__ */ jsx("span", { className: "ds-visually-hidden", children: name }),
115
116
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__excerpt`, children: excerpt }),
116
117
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__corner`, children: cornerMark })
117
118
  ] });
@@ -141,15 +141,6 @@
141
141
  padding: 0;
142
142
  }
143
143
 
144
- .ds-bottom-nav__sr {
145
- position: absolute;
146
- width: 1px;
147
- height: 1px;
148
- overflow: hidden;
149
- clip-path: inset(50%);
150
- white-space: nowrap;
151
- }
152
-
153
144
  .ds-bottom-nav__search {
154
145
  display: none;
155
146
  }
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  export interface BottomNavItem {
3
4
  /** Unique key for this destination */
4
5
  key: string;
@@ -67,6 +68,12 @@ type BottomNavOwnProps = {
67
68
  badgeLabel?: (badge: number | string | true) => string;
68
69
  /** Additional CSS classes */
69
70
  className?: string;
71
+ /**
72
+ * The component its links render with, such as Next.js's `Link`, so they
73
+ * navigate through the app's router. Wins over a LinkProvider's; a plain
74
+ * `<a>` when neither is set.
75
+ */
76
+ linkComponent?: LinkComponent;
70
77
  };
71
78
  export interface BottomNavProps extends BottomNavOwnProps, Omit<React.ComponentPropsWithoutRef<'nav'>, keyof BottomNavOwnProps> {
72
79
  }