rift-ds 1.8.0 → 1.9.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 (51) hide show
  1. package/README.md +3 -3
  2. package/chat/ChatAgentPane.d.ts +8 -5
  3. package/chat/ChatAgentPane.js +4 -1
  4. package/chat/ChatArtifactCard.d.ts +13 -0
  5. package/chat/ChatArtifactCard.js +21 -0
  6. package/chat/ChatArtifactPane.d.ts +18 -0
  7. package/chat/ChatArtifactPane.js +43 -0
  8. package/chat/ChatProvider.d.ts +4 -2
  9. package/chat/ChatProvider.js +15 -0
  10. package/chat/ChatWidget.css +208 -21
  11. package/chat/ChatWidget.d.ts +3 -1
  12. package/chat/ChatWidget.js +287 -73
  13. package/chat/TurnContent.d.ts +3 -1
  14. package/chat/TurnContent.js +9 -2
  15. package/chat/artifacts.d.ts +5 -0
  16. package/chat/artifacts.js +14 -0
  17. package/chat/context.d.ts +18 -1
  18. package/chat/context.js +2 -0
  19. package/chat/demo-artifacts.d.ts +6 -0
  20. package/chat/demo-artifacts.js +80 -0
  21. package/chat/demo-card-components.d.ts +2 -0
  22. package/chat/demo-card-components.js +7 -0
  23. package/chat/demo-script.js +24 -2
  24. package/chat/demo.css +6 -0
  25. package/chat/demo.d.ts +1 -0
  26. package/chat/demo.js +4 -0
  27. package/chat/index.d.ts +6 -3
  28. package/chat/index.js +12 -2
  29. package/chat/queries.d.ts +10 -1
  30. package/chat/queries.js +5 -1
  31. package/chat/registry.json.d.ts +6 -1
  32. package/chat/registry.json.js +1 -1
  33. package/chat/types.d.ts +36 -0
  34. package/chat/types.js +2 -0
  35. package/components/AgentRail/AgentRail.css +14 -5
  36. package/components/AgentRail/AgentRail.d.ts +2 -0
  37. package/components/AgentRail/AgentRail.js +3 -2
  38. package/components/ArtifactCard/ArtifactCard.css +106 -0
  39. package/components/ArtifactCard/ArtifactCard.d.ts +27 -0
  40. package/components/ArtifactCard/ArtifactCard.js +29 -0
  41. package/components/ArtifactPanel/ArtifactPanel.css +159 -0
  42. package/components/ArtifactPanel/ArtifactPanel.d.ts +70 -0
  43. package/components/ArtifactPanel/ArtifactPanel.js +131 -0
  44. package/components/ThreadPanel/ThreadPanel.css +87 -33
  45. package/components/ThreadPanel/ThreadPanel.js +21 -10
  46. package/components/registry.json +16 -0
  47. package/components/registry.json.d.ts +16 -0
  48. package/components/registry.json.js +1 -1
  49. package/index.d.ts +2 -0
  50. package/index.js +4 -0
  51. package/package.json +1 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ An open source React design system built for AI products and coding agents: comp
13
13
 
14
14
  ## Why Rift DS
15
15
 
16
- - **<!-- component-count -->150<!-- /component-count --> components.** Buttons, forms and tables through to charts, navigation and overlays, each typed and documented with live examples. React is the only required peer dependency.
16
+ - **<!-- component-count -->152<!-- /component-count --> components.** Buttons, forms and tables through to charts, navigation and overlays, each typed and documented with live examples. React is the only required peer dependency.
17
17
  - **AI patterns, the whole chat and templates, ready to use.** Composer, streaming chat thread, tool calls, reasoning and thread panel are in the box, the complete AI chat ships assembled as one widget that one command scaffolds, and the docs site has complete template screens built from them.
18
18
  - **Fully themeable.** The shipped themes are complete looks to start from, each in light and dark. One `data-brand` attribute applies a theme, and a few CSS overrides make your own.
19
19
  - **Ready for coding agents.** One command installs the agent skills, and a public MCP endpoint serves every component's props, so your agent builds with the library as it actually is. No key, no account.
@@ -94,13 +94,13 @@ import 'rift-ds/tokens/presets/presets.css';
94
94
 
95
95
  ## Components
96
96
 
97
- <!-- component-count -->150<!-- /component-count --> components, including a chat set for AI products (Chat thread, Composer, Tool call, Reasoning, Thread panel) and Shader field, a WebGL2 ambient background whose light sources read your colour tokens at runtime, so it re-themes with everything else. Details and live examples are on the [docs site](https://rift-ds.com/components).
97
+ <!-- component-count -->152<!-- /component-count --> components, including a chat set for AI products (Chat thread, Composer, Tool call, Reasoning, Thread panel) and Shader field, a WebGL2 ambient background whose light sources read your colour tokens at runtime, so it re-themes with everything else. Details and live examples are on the [docs site](https://rift-ds.com/components).
98
98
 
99
99
  <details>
100
100
  <summary>The full list</summary>
101
101
 
102
102
  <!-- component-list:start -->
103
- Accordion · Agent plan · Agent rail · Agent status · AI button · Alert · Alert dialog · Anchor nav · Animated number · App layout · App sidebar · Area chart · Attachment drop zone · Attachment group · Attachment tile · Attachment viewer · Avatar · Avatar group · Badge · Banner · Bar chart · Bottom nav · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Filter bar · Funnel chart · Gantt chart · Gauge · Globe · Hover card · Image compare · Input · Inspector colour picker · Inspector dropdown · Inspector input · Inspector section · Inspector segments · Inspector slider · Inspector toggle switch · Instructions · Interrupt card · Kbd · Legend tile · Lightbox · Line chart · Link list · Map callout · Map legend · Mention · Message actions · Message card · Meter · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Pixel avatar · Popover · Progress bar · Prompt suggestions · Prose · Quote · Radar chart · Radial chart · Radio button · Rating · Reasoning · Rich dropdown · Scatter chart · Section title · Segmented control · Selection card · Shader field · Sidebar panel · Skeleton · Slider · Source chip · Source trail · Sparkline · Spinner · Split button · Split pane · Stacked bar chart · Stat · Status dot · Stepper · Streaming text · Swatch · Table · Tabs · Tag input · Textarea · Thread panel · Thread tabs · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Toolbar · Tooltip · Top app bar · Tree view · Treemap · Usage card · Waveform · Widget · World map
103
+ Accordion · Agent plan · Agent rail · Agent status · AI button · Alert · Alert dialog · Anchor nav · Animated number · App layout · App sidebar · Area chart · Artifact card · Artifact panel · Attachment drop zone · Attachment group · Attachment tile · Attachment viewer · Avatar · Avatar group · Badge · Banner · Bar chart · Bottom nav · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Filter bar · Funnel chart · Gantt chart · Gauge · Globe · Hover card · Image compare · Input · Inspector colour picker · Inspector dropdown · Inspector input · Inspector section · Inspector segments · Inspector slider · Inspector toggle switch · Instructions · Interrupt card · Kbd · Legend tile · Lightbox · Line chart · Link list · Map callout · Map legend · Mention · Message actions · Message card · Meter · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Pixel avatar · Popover · Progress bar · Prompt suggestions · Prose · Quote · Radar chart · Radial chart · Radio button · Rating · Reasoning · Rich dropdown · Scatter chart · Section title · Segmented control · Selection card · Shader field · Sidebar panel · Skeleton · Slider · Source chip · Source trail · Sparkline · Spinner · Split button · Split pane · Stacked bar chart · Stat · Status dot · Stepper · Streaming text · Swatch · Table · Tabs · Tag input · Textarea · Thread panel · Thread tabs · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Toolbar · Tooltip · Top app bar · Tree view · Treemap · Usage card · Waveform · Widget · World map
104
104
  <!-- component-list:end -->
105
105
 
106
106
  </details>
@@ -10,12 +10,15 @@ export interface ChatAgentPaneProps {
10
10
  onTabChange?: (id: string) => void;
11
11
  /** The widget seated the pane as a bottom sheet, where it fills the width instead of holding its own column. */
12
12
  overlay: boolean;
13
+ /** Fires from the pane's own close button, a cross in its top trailing corner. Omit for a pane with no close of its own. */
14
+ onClose?: () => void;
13
15
  }
14
16
  /**
15
17
  * The built-in agent pane: an AgentRail the widget seats on the
16
- * conversation's trailing edge (inline at 860px and up, a bottom sheet
17
- * below). A host hands it the agent's profile and panes; the widget owns
18
- * whether it is up, so `onCollapse` is deliberately unwired here: the
19
- * header's agent toggle is the one control, both ways.
18
+ * conversation's trailing edge (inline from ASIDE_RAIL_MIN_WIDTH, a bottom sheet
19
+ * below, the artifact panel's own rule). A host hands it the agent's profile and panes; the widget owns
20
+ * whether it is up. The header's agent toggle opens and closes it, and the
21
+ * pane carries a close of its own in its top corner, where a panel's way
22
+ * out is looked for.
20
23
  */
21
- export declare function ChatAgentPane({ profile, tabs, activeTab, onTabChange, overlay }: ChatAgentPaneProps): import("react").JSX.Element;
24
+ export declare function ChatAgentPane({ profile, tabs, activeTab, onTabChange, overlay, onClose }: ChatAgentPaneProps): import("react").JSX.Element;
@@ -3,7 +3,7 @@ import { jsx } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { AgentRail } from "../components/AgentRail/AgentRail.js";
5
5
  import "./ChatAgentPane.css";
6
- function ChatAgentPane({ profile, tabs, activeTab, onTabChange, overlay }) {
6
+ function ChatAgentPane({ profile, tabs, activeTab, onTabChange, overlay, onClose }) {
7
7
  const [ownTab, setOwnTab] = useState(tabs[0]?.id ?? "");
8
8
  const current = activeTab ?? ownTab;
9
9
  return /* @__PURE__ */ jsx(
@@ -13,6 +13,9 @@ function ChatAgentPane({ profile, tabs, activeTab, onTabChange, overlay }) {
13
13
  profile,
14
14
  tabs,
15
15
  activeTab: current,
16
+ onCollapse: onClose,
17
+ collapseVariant: "close",
18
+ collapseLabel: "Close the agent panel",
16
19
  onTabChange: (id) => {
17
20
  setOwnTab(id);
18
21
  onTabChange?.(id);
@@ -0,0 +1,13 @@
1
+ import { ChatArtifact } from './types';
2
+ export interface ChatArtifactCardProps {
3
+ /** The artifact this tile stands for. */
4
+ artifact: ChatArtifact;
5
+ }
6
+ /**
7
+ * An artifact's place in the thread: an ArtifactCard wired to the provider,
8
+ * which opens its artifact in the artifact panel. Pressing the tile of the
9
+ * artifact already on stage closes the panel again, so the tile is one
10
+ * control both ways. The widget draws it for every `artifact` card; a host
11
+ * building its own thread renders it the same way.
12
+ */
13
+ export declare function ChatArtifactCard({ artifact }: ChatArtifactCardProps): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { ArtifactCard } from "../components/ArtifactCard/ArtifactCard.js";
4
+ import { useChatWidget } from "./context.js";
5
+ function ChatArtifactCard({ artifact }) {
6
+ const { artifact: onStage, openArtifact, closeArtifact } = useChatWidget();
7
+ const open = onStage?.id === artifact.id;
8
+ return /* @__PURE__ */ jsx(
9
+ ArtifactCard,
10
+ {
11
+ title: artifact.title,
12
+ meta: artifact.meta,
13
+ icon: artifact.icon,
14
+ open,
15
+ onClick: () => open ? closeArtifact() : openArtifact(artifact)
16
+ }
17
+ );
18
+ }
19
+ export {
20
+ ChatArtifactCard
21
+ };
@@ -0,0 +1,18 @@
1
+ import { ChatArtifact } from './types';
2
+ export interface ChatArtifactPaneProps {
3
+ /** The artifact on stage, or null for the empty state: the panel opened before the conversation has made anything. */
4
+ artifact: ChatArtifact | null;
5
+ /** Whether the widget is showing the pane at its expanded size. */
6
+ expanded?: boolean;
7
+ /** Fires with the next state from the expand toggle; omit where the seat has one size. */
8
+ onExpandedChange?: (expanded: boolean) => void;
9
+ /** Fires from the pane's close button; omit where another control closes the seat. */
10
+ onClose?: () => void;
11
+ }
12
+ /**
13
+ * The built-in artifact pane: an ArtifactPanel drawn from a `ChatArtifact`.
14
+ * A kind the provider's `artifactRenderers` knows is live React content;
15
+ * otherwise the artifact's document, URL or source decides the preview. The
16
+ * widget owns the seat (beside the conversation, or a sheet over it).
17
+ */
18
+ export declare function ChatArtifactPane({ artifact, expanded, onExpandedChange, onClose }: ChatArtifactPaneProps): import("react").JSX.Element;
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { ArtifactPanel } from "../components/ArtifactPanel/ArtifactPanel.js";
4
+ import { EmptyState } from "../components/EmptyState/EmptyState.js";
5
+ import { useChatWidget } from "./context.js";
6
+ const FRAMEABLE_URL = /^(?:https?:\/\/|\/(?!\/))/i;
7
+ function ChatArtifactPane({ artifact, expanded, onExpandedChange, onClose }) {
8
+ const { artifactRenderers } = useChatWidget();
9
+ if (!artifact) {
10
+ return /* @__PURE__ */ jsx(ArtifactPanel, { title: "Artifacts", icon: "space_dashboard", onClose, children: /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__artifact-empty", children: /* @__PURE__ */ jsx(
11
+ EmptyState,
12
+ {
13
+ size: "compact",
14
+ icon: "draft",
15
+ title: "Nothing here yet",
16
+ description: "What the chat makes for you, like a dashboard or a page, opens in this panel."
17
+ }
18
+ ) }) });
19
+ }
20
+ const custom = Object.hasOwn(artifactRenderers, artifact.kind);
21
+ const render = artifactRenderers[artifact.kind];
22
+ const src = artifact.url && FRAMEABLE_URL.test(artifact.url) ? artifact.url : void 0;
23
+ return /* @__PURE__ */ jsx(
24
+ ArtifactPanel,
25
+ {
26
+ title: artifact.title,
27
+ meta: artifact.meta,
28
+ icon: artifact.icon,
29
+ html: custom ? void 0 : artifact.html,
30
+ src: custom ? void 0 : src,
31
+ code: artifact.code ?? (custom ? void 0 : artifact.html),
32
+ language: artifact.language ?? (artifact.code === void 0 && artifact.html !== void 0 ? "html" : void 0),
33
+ expanded,
34
+ onExpandedChange,
35
+ onClose,
36
+ children: custom ? render(artifact) : void 0
37
+ },
38
+ artifact.id
39
+ );
40
+ }
41
+ export {
42
+ ChatArtifactPane
43
+ };
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { ChatCardRenderers, ChatLinkRenderer, FeedbackSink } from './context';
2
+ import { ChatArtifactRenderers, ChatCardRenderers, ChatLinkRenderer, FeedbackSink } from './context';
3
3
  import { FollowupsSource } from './followups';
4
4
  import { ChatPlacement } from './placement';
5
5
  import { ThreadsOptions } from './threads';
@@ -33,6 +33,8 @@ export interface ChatProviderProps {
33
33
  feedback?: FeedbackSink | string | false;
34
34
  /** Draws `card` events by kind. */
35
35
  renderers?: ChatCardRenderers;
36
+ /** Draws an artifact's preview by kind, for artifacts that are live React content rather than a document or a URL. */
37
+ artifactRenderers?: ChatArtifactRenderers;
36
38
  /** Renders a same-site link in an answer. The default is a plain anchor; a Next.js host passes its Link. */
37
39
  renderLink?: ChatLinkRenderer;
38
40
  /** Which hrefs in an answer become links. Everything else renders as text. */
@@ -50,4 +52,4 @@ export interface ChatProviderProps {
50
52
  * clicking between pages for free. A hard reload starts clean by design:
51
53
  * no storage, no persistence.
52
54
  */
53
- export declare function ChatProvider({ transport, endpoint, defaultPlacement, followups, fallbackFollowups, feedback, renderers, renderLink, isInternalHref, threads: threadsOptions, placeholders, children, }: ChatProviderProps): import("react").JSX.Element;
55
+ export declare function ChatProvider({ transport, endpoint, defaultPlacement, followups, fallbackFollowups, feedback, renderers, artifactRenderers, renderLink, isInternalHref, threads: threadsOptions, placeholders, children, }: ChatProviderProps): import("react").JSX.Element;
@@ -16,6 +16,7 @@ function ChatProvider({
16
16
  fallbackFollowups = DEFAULT_FALLBACK_FOLLOWUPS,
17
17
  feedback = "/api/chat/feedback",
18
18
  renderers,
19
+ artifactRenderers,
19
20
  renderLink = defaultRenderLink,
20
21
  isInternalHref = isInternalPath,
21
22
  threads: threadsOptions,
@@ -70,9 +71,14 @@ function ChatProvider({
70
71
  );
71
72
  const [starterRevealKey, setStarterRevealKey] = useState(null);
72
73
  const [starterSeed, setStarterSeed] = useState(() => Math.random());
74
+ const [artifact, setArtifact] = useState(null);
75
+ const openArtifact = useCallback((next) => setArtifact(next), []);
76
+ const closeArtifact = useCallback(() => setArtifact(null), []);
77
+ if (artifact && chat.turns.length === 0 && !chat.live) setArtifact(null);
73
78
  const { reset: resetChat } = chat;
74
79
  const reset = useCallback(() => {
75
80
  resetChat();
81
+ setArtifact(null);
76
82
  setStarterSeed(Math.random());
77
83
  setStarterRevealKey(null);
78
84
  }, [resetChat]);
@@ -121,6 +127,7 @@ function ChatProvider({
121
127
  const returnFocusRef = useRef(null);
122
128
  const toggleOpen = useCallback(() => setOpen(!open), [open, setOpen]);
123
129
  const renderersValue = useMemo(() => renderers ?? {}, [renderers]);
130
+ const artifactRenderersValue = useMemo(() => artifactRenderers ?? {}, [artifactRenderers]);
124
131
  const value = useMemo(
125
132
  () => ({
126
133
  ...chat,
@@ -146,6 +153,10 @@ function ChatProvider({
146
153
  returnFocusRef,
147
154
  threads,
148
155
  renderers: renderersValue,
156
+ artifact,
157
+ openArtifact,
158
+ closeArtifact,
159
+ artifactRenderers: artifactRenderersValue,
149
160
  renderLink,
150
161
  isInternalHref,
151
162
  submitFeedback
@@ -169,6 +180,10 @@ function ChatProvider({
169
180
  draft,
170
181
  threads,
171
182
  renderersValue,
183
+ artifact,
184
+ openArtifact,
185
+ closeArtifact,
186
+ artifactRenderersValue,
172
187
  renderLink,
173
188
  isInternalHref,
174
189
  submitFeedback
@@ -52,8 +52,9 @@
52
52
  padding-inline: var(--ds-chat-widget-inset);
53
53
  }
54
54
 
55
- /* With the controls lifted to the card's edge, the header row has nothing
56
- 40px tall left in it and would collapse to the title's own line. The row
55
+ /* With a trailing panel seated inline, the controls stand over the panel's
56
+ top inset and the header row has nothing 40px tall left in it, so it
57
+ would collapse to the title's own line. The row
57
58
  holds the height its buttons were giving it: the control plus the
58
59
  header's own block padding either side. */
59
60
  .ds-chat-widget[data-lifted-actions] .ds-chat-header {
@@ -657,54 +658,240 @@
657
658
  own rounded card, inset on three sides. The top margin is the header
658
659
  row's exact height (the chat column's top padding, plus the header's own
659
660
  block padding either side of a 40px control), so the panel begins where
660
- that row ends and the lifted controls sit in the clear above it. */
661
+ that row ends and the widget's controls sit in the clear above it. */
661
662
  .ds-chat-widget__aside-rail {
663
+ display: flex;
662
664
  flex: none;
665
+ /* The open and close glide, the artifact panel's own: the rail's width
666
+ runs from nothing to the pane's, and the card is the rail at every
667
+ frame, a whole rounded card growing from the trailing edge with its
668
+ content revealed behind the leading border. The target is the pane's
669
+ natural width, which the widget measures and hands over as
670
+ --ds-chat-aside-width (a rail holds a width of its own, so there is no
671
+ share of the row to animate as the artifact does). Closed, the rail
672
+ keeps no width, no inset and no border, and goes invisible once the
673
+ collapse has finished (the visibility delay), so nothing of it can be
674
+ seen, hovered or reached. The reduced-motion guard in tokens-motion.css
675
+ zeroes every duration here. */
676
+ width: 0;
677
+ min-width: 0;
663
678
  min-height: 0;
664
679
  margin: calc(var(--padding-200) + var(--padding-150) * 2 + 40px) 0 var(--padding-500) 0;
665
- /* The open and close glide, the threads rail's own: a one-column grid
666
- whose track runs 0fr to 1fr, so the pane's width animates without the
667
- widget knowing it, while the pane's own card fades with it. Closed, the
668
- rail keeps no width, no inset and no border, and goes invisible once
669
- the collapse has finished (the visibility delay), so nothing of it can
670
- be seen, hovered or reached. The reduced-motion guard in
671
- tokens-motion.css zeroes every duration here. */
672
- display: grid;
673
- grid-template-columns: 0fr;
674
680
  opacity: 0;
675
681
  visibility: hidden;
682
+ overflow: hidden;
676
683
  transition:
677
- grid-template-columns var(--motion-duration-slow) var(--motion-ease-emphasized),
684
+ width var(--motion-duration-slow) var(--motion-ease-emphasized),
678
685
  margin var(--motion-duration-slow) var(--motion-ease-emphasized),
679
686
  opacity var(--motion-duration-base) var(--motion-ease-standard),
680
687
  visibility 0s linear var(--motion-duration-slow);
681
688
  }
682
689
 
683
690
  .ds-chat-widget__aside-rail--open {
684
- grid-template-columns: 1fr;
691
+ /* Before the first measurement there is no number to glide to, so the
692
+ rail simply takes its content's width. */
693
+ width: var(--ds-chat-aside-width, max-content);
685
694
  margin-right: var(--padding-500);
686
695
  opacity: 1;
687
696
  visibility: visible;
688
697
  transition:
689
- grid-template-columns var(--motion-duration-slow) var(--motion-ease-emphasized),
698
+ width var(--motion-duration-slow) var(--motion-ease-emphasized),
690
699
  margin var(--motion-duration-slow) var(--motion-ease-emphasized),
691
700
  opacity var(--motion-duration-base) var(--motion-ease-standard) calc(var(--motion-duration-slow) - var(--motion-duration-base)),
692
701
  visibility 0s linear 0s;
693
702
  }
694
703
 
695
- /* The pane's card: the border and radius live here, inside the animated
696
- track, so the collapsing rail never shows a sliver of frame. */
704
+ /* The pane's card fills the rail, so the two are one shape through the
705
+ glide; the pane inside keeps its own width and is clipped by the card. A
706
+ trailing panel is a solid surface, the page's own ground, not the
707
+ widget's glass: it is a place to read and work in, and what shows through
708
+ glass (the host's page, an ambient background) would sit under its
709
+ content. */
697
710
  .ds-chat-widget__aside-rail-inner {
711
+ box-sizing: border-box;
698
712
  display: flex;
713
+ flex: 1 0 auto;
714
+ width: 100%;
699
715
  min-width: 0;
700
716
  min-height: 0;
701
717
  border: var(--border-025) solid var(--color-bg-container-border);
702
718
  border-radius: var(--radius-600);
719
+ background-color: var(--color-bg-page-primary);
703
720
  overflow: hidden;
704
721
  }
705
722
 
706
- /* The widget's controls, lifted out of the chat header to the card's own
707
- right edge while the rail is seated inline. */
723
+ /* The pane never squeezes with the card around it. */
724
+ .ds-chat-widget__aside-rail-inner > * {
725
+ flex: none;
726
+ }
727
+
728
+ /* ============================================
729
+ THE ARTIFACT PANEL — the trailing seat's other tenant. Wide widgets seat
730
+ it beside the conversation as a second column; narrow ones raise it as a
731
+ tall bottom sheet. Unlike the agent rail it has no width of its own: it
732
+ shares the row with the conversation, a little the larger of the two.
733
+ ============================================ */
734
+
735
+ /* Any child of the row that is the conversation takes the room the rails
736
+ leave, and can shrink under an artifact. */
737
+ .ds-chat-widget__rails > .ds-chat-widget {
738
+ flex: 1 1 0;
739
+ min-width: 0;
740
+ }
741
+
742
+ /* The artifact's card floats exactly as the agent rail's does: inset from
743
+ the widget's edges, beginning under the header row (the same top margin,
744
+ the row's own height), with the widget's lifted controls in the clear
745
+ above it. The glide is the flex share running from nothing, the card
746
+ fading in behind it; closed, the rail keeps no width and no inset and
747
+ goes invisible once the collapse has finished. The reduced-motion guard
748
+ in tokens-motion.css zeroes every duration here. */
749
+ .ds-chat-widget__artifact-rail {
750
+ display: flex;
751
+ flex: 0 1 0;
752
+ min-width: 0;
753
+ min-height: 0;
754
+ margin: calc(var(--padding-200) + var(--padding-150) * 2 + 40px) 0 var(--padding-500) 0;
755
+ opacity: 0;
756
+ visibility: hidden;
757
+ overflow: hidden;
758
+ transition:
759
+ flex-grow var(--motion-duration-slow) var(--motion-ease-emphasized),
760
+ margin var(--motion-duration-slow) var(--motion-ease-emphasized),
761
+ opacity var(--motion-duration-base) var(--motion-ease-standard),
762
+ visibility 0s linear var(--motion-duration-slow);
763
+ }
764
+
765
+ .ds-chat-widget__artifact-rail--open {
766
+ flex-grow: 1.2;
767
+ margin-right: var(--padding-500);
768
+ opacity: 1;
769
+ visibility: visible;
770
+ transition:
771
+ flex-grow var(--motion-duration-slow) var(--motion-ease-emphasized),
772
+ margin var(--motion-duration-slow) var(--motion-ease-emphasized),
773
+ opacity var(--motion-duration-base) var(--motion-ease-standard) calc(var(--motion-duration-slow) - var(--motion-duration-base)),
774
+ visibility 0s linear 0s;
775
+ }
776
+
777
+ .ds-chat-widget__artifact-rail-inner {
778
+ display: flex;
779
+ flex: 1;
780
+ min-width: 0;
781
+ min-height: 0;
782
+ border: var(--border-025) solid var(--color-bg-container-border);
783
+ border-radius: var(--radius-600);
784
+ /* Solid, the agent pane's ground: see the aside rail's card. */
785
+ background-color: var(--color-bg-page-primary);
786
+ overflow: hidden;
787
+ }
788
+
789
+ /* The panel opened before the conversation has made anything. */
790
+ .ds-chat-widget__artifact-empty {
791
+ display: grid;
792
+ place-items: center;
793
+ min-height: 100%;
794
+ padding: var(--padding-400);
795
+ }
796
+
797
+ /* Sized by the grip: the panel holds the width the visitor gave it, and
798
+ gives it back before the conversation drops under its floor (the 320px
799
+ mirrors ARTIFACT_MIN_COLUMN_WIDTH in queries.ts). */
800
+ .ds-chat-widget__rails[data-artifact-sized] > .ds-chat-widget__artifact-rail--open {
801
+ flex: 0 1 var(--ds-chat-artifact-width);
802
+ }
803
+
804
+ .ds-chat-widget__rails[data-artifact-sized] > .ds-chat-widget {
805
+ flex: 1 0 320px;
806
+ }
807
+
808
+ /* The pointer drives the width frame by frame, so the glides stand down
809
+ and nothing under the drag selects. */
810
+ .ds-chat-widget__rails[data-artifact-resizing] {
811
+ user-select: none;
812
+ }
813
+
814
+ .ds-chat-widget__rails[data-artifact-resizing] > .ds-chat-widget__artifact-rail {
815
+ transition: none;
816
+ }
817
+
818
+ /* The grip: the dock's widen grip, restated for the card's leading edge. A
819
+ 20px grab zone straddling the border, with a hover-revealed 4×48px pill.
820
+ The seat itself has no width, so the row's layout never knows it is
821
+ there; the zone spans the card's own height. */
822
+ .ds-chat-widget__artifact-grip {
823
+ position: relative;
824
+ z-index: 4;
825
+ flex: none;
826
+ width: 0;
827
+ margin: calc(var(--padding-200) + var(--padding-150) * 2 + 40px) 0 var(--padding-500) 0;
828
+ cursor: ew-resize;
829
+ touch-action: none;
830
+ }
831
+
832
+ .ds-chat-widget__artifact-grip::before {
833
+ content: "";
834
+ position: absolute;
835
+ top: 0;
836
+ bottom: 0;
837
+ left: -10px;
838
+ width: 20px;
839
+ }
840
+
841
+ .ds-chat-widget__artifact-grip::after {
842
+ content: "";
843
+ position: absolute;
844
+ top: calc(50% - 24px);
845
+ left: -2px;
846
+ width: 4px;
847
+ height: 48px;
848
+ border-radius: var(--radius-pill);
849
+ background: transparent;
850
+ transition: background var(--motion-duration-fast) var(--motion-ease-standard);
851
+ }
852
+
853
+ .ds-chat-widget__artifact-grip:hover::after,
854
+ .ds-chat-widget__artifact-grip--resizing::after {
855
+ background: var(--color-text-tertiary);
856
+ }
857
+
858
+ .ds-chat-widget__artifact-grip:active::after {
859
+ background: var(--color-text-secondary);
860
+ }
861
+
862
+ /* Expanded, the artifact has the widget: the conversation and the history
863
+ leave the row (and with them the tab order), and the card takes an inset
864
+ on its leading edge too. */
865
+ .ds-chat-widget__rails[data-artifact-expanded] > .ds-chat-widget,
866
+ .ds-chat-widget__rails[data-artifact-expanded] > .ds-chat-widget__threads-rail {
867
+ display: none;
868
+ }
869
+
870
+ .ds-chat-widget__rails[data-artifact-expanded] > .ds-chat-widget__artifact-rail--open {
871
+ flex: 1 1 0;
872
+ margin-left: var(--padding-500);
873
+ }
874
+
875
+ /* The sheet runs nearly the widget's height: a preview has no natural
876
+ height to hug, and a short frame is no preview. The strip left above it
877
+ keeps the header, and so the way back, in view. */
878
+ .ds-chat-widget__aside-sheet--artifact {
879
+ height: 92%;
880
+ max-height: none;
881
+ }
882
+
883
+ /* The room the floating controls take in the header row while no panel is
884
+ up: as many 40px controls as the cluster holds, a gap between each. */
885
+ .ds-chat-widget__actions-spacer {
886
+ flex: none;
887
+ width: calc(
888
+ var(--ds-chat-action-count) * 40px + (var(--ds-chat-action-count) - 1) * var(--gap-100)
889
+ );
890
+ height: 40px;
891
+ }
892
+
893
+ /* The widget's controls at the card's own top trailing corner, their one
894
+ home wherever a trailing panel can seat inline. */
708
895
  .ds-chat-widget__rail-actions {
709
896
  position: absolute;
710
897
  top: calc(var(--padding-200) + var(--padding-150));
@@ -726,8 +913,8 @@
726
913
  display: flex;
727
914
  flex-direction: column;
728
915
  max-height: 82%;
729
- background-color: var(--color-bg-glass);
730
- backdrop-filter: blur(24px);
916
+ /* The same solid ground the panel has inline: one panel, two seats. */
917
+ background-color: var(--color-bg-page-primary);
731
918
  border-top: var(--border-025) solid var(--color-bg-container-border);
732
919
  border-radius: var(--radius-600) var(--radius-600) 0 0;
733
920
  box-shadow: var(--shadow-floating);
@@ -84,6 +84,8 @@ export interface ChatWidgetProps {
84
84
  }) => ReactNode;
85
85
  /** The built-in agent pane: who the agent is and what it has been doing. Seated like `aside`. */
86
86
  agent?: ChatAgentOptions;
87
+ /** Seat the artifact panel: a reply that carries an artifact draws a tile in the thread, and the tile (or the header's artifact button) opens the artifact beside the conversation on a wide host and as a bottom sheet on a narrow one. The trailing seat holds one panel at a time, so an open artifact stands in for the agent pane. Off, none of it exists: no header button, no tile, no panel. */
88
+ artifacts?: boolean;
87
89
  /** The composer's context note, naming what the chat is looking at. Omit for none. */
88
90
  contextLabel?: string | null;
89
91
  /** A widget slot on the welcome screen, under the composer and its
@@ -110,4 +112,4 @@ export interface ChatWidgetProps {
110
112
  * a stage card, a whole page). It reads all of its state from ChatProvider,
111
113
  * so the conversation is the same object wherever it shows.
112
114
  */
113
- export declare function ChatWidget({ fullscreenEnabled, closeEnabled, placementEnabled, compact, phone, title, placeholder, showStarters, logo, tagline, starters: startersInput, startersKey, composerActions, models, defaultModel, lockedModelDescription, fileAttachments, maxAttachments, maxAttachmentBytes, mentions, threads, threadsProps, tabs, aside, agent, contextLabel, widgets, widgetsLayout, widgetsWidth, disclaimer, }: ChatWidgetProps): import("react").JSX.Element;
115
+ export declare function ChatWidget({ fullscreenEnabled, closeEnabled, placementEnabled, compact, phone, title, placeholder, showStarters, logo, tagline, starters: startersInput, startersKey, composerActions, models, defaultModel, lockedModelDescription, fileAttachments, maxAttachments, maxAttachmentBytes, mentions, threads, threadsProps, tabs, aside, agent, artifacts, contextLabel, widgets, widgetsLayout, widgetsWidth, disclaimer, }: ChatWidgetProps): import("react").JSX.Element;