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.
- package/README.md +3 -3
- package/chat/ChatAgentPane.d.ts +8 -5
- package/chat/ChatAgentPane.js +4 -1
- package/chat/ChatArtifactCard.d.ts +13 -0
- package/chat/ChatArtifactCard.js +21 -0
- package/chat/ChatArtifactPane.d.ts +18 -0
- package/chat/ChatArtifactPane.js +43 -0
- package/chat/ChatProvider.d.ts +4 -2
- package/chat/ChatProvider.js +15 -0
- package/chat/ChatWidget.css +208 -21
- package/chat/ChatWidget.d.ts +3 -1
- package/chat/ChatWidget.js +287 -73
- package/chat/TurnContent.d.ts +3 -1
- package/chat/TurnContent.js +9 -2
- package/chat/artifacts.d.ts +5 -0
- package/chat/artifacts.js +14 -0
- package/chat/context.d.ts +18 -1
- package/chat/context.js +2 -0
- package/chat/demo-artifacts.d.ts +6 -0
- package/chat/demo-artifacts.js +80 -0
- package/chat/demo-card-components.d.ts +2 -0
- package/chat/demo-card-components.js +7 -0
- package/chat/demo-script.js +24 -2
- package/chat/demo.css +6 -0
- package/chat/demo.d.ts +1 -0
- package/chat/demo.js +4 -0
- package/chat/index.d.ts +6 -3
- package/chat/index.js +12 -2
- package/chat/queries.d.ts +10 -1
- package/chat/queries.js +5 -1
- package/chat/registry.json.d.ts +6 -1
- package/chat/registry.json.js +1 -1
- package/chat/types.d.ts +36 -0
- package/chat/types.js +2 -0
- package/components/AgentRail/AgentRail.css +14 -5
- package/components/AgentRail/AgentRail.d.ts +2 -0
- package/components/AgentRail/AgentRail.js +3 -2
- package/components/ArtifactCard/ArtifactCard.css +106 -0
- package/components/ArtifactCard/ArtifactCard.d.ts +27 -0
- package/components/ArtifactCard/ArtifactCard.js +29 -0
- package/components/ArtifactPanel/ArtifactPanel.css +159 -0
- package/components/ArtifactPanel/ArtifactPanel.d.ts +70 -0
- package/components/ArtifactPanel/ArtifactPanel.js +131 -0
- package/components/ThreadPanel/ThreadPanel.css +87 -33
- package/components/ThreadPanel/ThreadPanel.js +21 -10
- package/components/registry.json +16 -0
- package/components/registry.json.d.ts +16 -0
- package/components/registry.json.js +1 -1
- package/index.d.ts +2 -0
- package/index.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
ARTIFACT PANEL COMPONENT
|
|
3
|
+
The surface a chat opens beside the conversation to show something it
|
|
4
|
+
made. A header names the artifact and carries its controls; the body is
|
|
5
|
+
the preview (live content, or a sandboxed frame) or the source.
|
|
6
|
+
Deliberately surface-less, like AgentRail and ThreadPanel — the host
|
|
7
|
+
paints the card it sits in, and decides where that card goes.
|
|
8
|
+
|
|
9
|
+
One scroll region only: the body. The header stays put, so the controls
|
|
10
|
+
never leave with the content.
|
|
11
|
+
============================================ */
|
|
12
|
+
|
|
13
|
+
/* Base */
|
|
14
|
+
|
|
15
|
+
.ds-artifact-panel {
|
|
16
|
+
/* Width, height and padding combine on the root, so the component declares
|
|
17
|
+
its own sizing model rather than leaning on a host reset. */
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: 100%;
|
|
23
|
+
min-width: 0;
|
|
24
|
+
min-height: 0;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ============================================
|
|
29
|
+
HEADER — the artifact's name, then its controls
|
|
30
|
+
============================================ */
|
|
31
|
+
|
|
32
|
+
/* The controls stand --padding-300 in from the top and trailing edges, the
|
|
33
|
+
inset AgentRail gives its own corner control, so the two panels that
|
|
34
|
+
share a chat's trailing seat put their close in the same place and a
|
|
35
|
+
rounded host card has room to turn its corner around it. */
|
|
36
|
+
.ds-artifact-panel__header {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex: none;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: space-between;
|
|
41
|
+
gap: var(--gap-300);
|
|
42
|
+
min-width: 0;
|
|
43
|
+
padding: var(--padding-300) var(--padding-300) var(--padding-300)
|
|
44
|
+
var(--padding-400);
|
|
45
|
+
border-bottom: var(--border-025) solid var(--color-bg-container-border);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.ds-artifact-panel__heading {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex: 1;
|
|
51
|
+
align-items: baseline;
|
|
52
|
+
gap: var(--gap-200);
|
|
53
|
+
min-width: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* The mark sits on the row's centre line while the name and its meta share
|
|
57
|
+
a baseline. */
|
|
58
|
+
.ds-artifact-panel__icon {
|
|
59
|
+
--icon-size: var(--icon-size-500);
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
flex: none;
|
|
62
|
+
align-self: center;
|
|
63
|
+
color: var(--color-icon-secondary);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ChatHeader's title face, so the two headers read as one line of type.
|
|
67
|
+
The name outranks its meta when the row runs short: the meta carries a
|
|
68
|
+
shrink factor large enough to give way entirely before the name loses a
|
|
69
|
+
letter. */
|
|
70
|
+
.ds-artifact-panel__title {
|
|
71
|
+
flex: 0 1 auto;
|
|
72
|
+
min-width: 0;
|
|
73
|
+
margin: 0;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
text-overflow: ellipsis;
|
|
76
|
+
white-space: nowrap;
|
|
77
|
+
font-family: var(--font-title-body-family);
|
|
78
|
+
font-size: var(--font-title-body-size);
|
|
79
|
+
font-weight: var(--font-title-body-weight);
|
|
80
|
+
line-height: var(--font-title-body-line-height);
|
|
81
|
+
color: var(--color-text-primary);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ds-artifact-panel__meta {
|
|
85
|
+
flex: 0 1000 auto;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
text-overflow: ellipsis;
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
font-family: var(--font-caption-family);
|
|
91
|
+
font-size: var(--font-caption-size);
|
|
92
|
+
font-weight: var(--font-caption-weight);
|
|
93
|
+
line-height: var(--font-caption-line-height);
|
|
94
|
+
color: var(--color-text-tertiary);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.ds-artifact-panel__controls {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex: none;
|
|
100
|
+
align-items: center;
|
|
101
|
+
/* The row is the buttons' height whatever it holds: the view switch
|
|
102
|
+
stands a hair taller than a button, and a header that grew with it
|
|
103
|
+
would move every control when an artifact with source took the stage. */
|
|
104
|
+
height: 40px;
|
|
105
|
+
gap: var(--gap-100);
|
|
106
|
+
min-width: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* The view switch stands a beat clear of the icon buttons it leads. */
|
|
110
|
+
.ds-artifact-panel__controls > .ds-segmented-control {
|
|
111
|
+
margin-inline-end: var(--gap-100);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ============================================
|
|
115
|
+
BODY — the preview or the source
|
|
116
|
+
============================================ */
|
|
117
|
+
|
|
118
|
+
.ds-artifact-panel__body {
|
|
119
|
+
display: flex;
|
|
120
|
+
flex: 1;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
min-width: 0;
|
|
123
|
+
min-height: 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Live content scrolls inside the body and brings its own padding: a
|
|
127
|
+
dashboard wants an inset, a full-bleed page does not. */
|
|
128
|
+
.ds-artifact-panel__content,
|
|
129
|
+
.ds-artifact-panel__code {
|
|
130
|
+
flex: 1;
|
|
131
|
+
min-width: 0;
|
|
132
|
+
min-height: 0;
|
|
133
|
+
overflow: auto;
|
|
134
|
+
/* ThreadPanel's tucked-away thin scrollbar: no track fill, the thumb
|
|
135
|
+
showing only while the region is hovered or scrolled from the keyboard. */
|
|
136
|
+
scrollbar-width: thin;
|
|
137
|
+
scrollbar-color: transparent transparent;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.ds-artifact-panel__content:hover,
|
|
141
|
+
.ds-artifact-panel__content:focus-within,
|
|
142
|
+
.ds-artifact-panel__code:hover,
|
|
143
|
+
.ds-artifact-panel__code:focus-within {
|
|
144
|
+
scrollbar-color: var(--color-bg-container-border) transparent;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.ds-artifact-panel__code {
|
|
148
|
+
padding: var(--padding-400);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* A framed document is its own page: it fills the body edge to edge and
|
|
152
|
+
owns its own scrolling. */
|
|
153
|
+
.ds-artifact-panel__frame {
|
|
154
|
+
display: block;
|
|
155
|
+
flex: 1;
|
|
156
|
+
width: 100%;
|
|
157
|
+
min-height: 0;
|
|
158
|
+
border: none;
|
|
159
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** Which face of the artifact is on stage. */
|
|
3
|
+
export type ArtifactPanelView = 'preview' | 'code';
|
|
4
|
+
/** Props owned by ArtifactPanel itself — everything else falls through to the root `<section>`. */
|
|
5
|
+
type ArtifactPanelOwnProps = {
|
|
6
|
+
/** The artifact's name, shown in the header and used as the panel's accessible name. Collides with the native `title` tooltip attribute on purpose: a panel has a heading, not a tooltip. */
|
|
7
|
+
title: React.ReactNode;
|
|
8
|
+
/** A quiet line beside the name: what kind of thing this is, or which version. */
|
|
9
|
+
meta?: React.ReactNode;
|
|
10
|
+
/** Leading mark in the header — Material Symbol name (string) or custom element (ReactNode). */
|
|
11
|
+
icon?: string | React.ReactNode;
|
|
12
|
+
/** The preview, as live React content. Wins over `html` and `src`. */
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
/** The preview, as a complete HTML document drawn in a sandboxed frame. Wins over `src`. A document that honours `prefers-color-scheme` follows the `color-scheme` of the page around the panel, so set that with your theme and the preview turns light or dark with it. */
|
|
15
|
+
html?: string;
|
|
16
|
+
/** The preview, as a URL drawn in a sandboxed frame: a deployed page, a site. */
|
|
17
|
+
src?: string;
|
|
18
|
+
/** The frame's `sandbox` tokens for `html` and `src`. The default lets a document run its own scripts and forms in an origin of its own, with no reach into the host page; widen it only for a source you trust. */
|
|
19
|
+
sandbox?: string;
|
|
20
|
+
/** Accessible name for the preview frame. Defaults to the title when that is a string. */
|
|
21
|
+
frameTitle?: string;
|
|
22
|
+
/** The artifact's source. Given, the header offers a Code view beside the preview (or shows the code alone when there is no preview). */
|
|
23
|
+
code?: string;
|
|
24
|
+
/** The source's language, shown on the code view. */
|
|
25
|
+
language?: string;
|
|
26
|
+
/** The view on stage, when the host owns the state. */
|
|
27
|
+
view?: ArtifactPanelView;
|
|
28
|
+
/** The view the panel opens on when it keeps its own state. */
|
|
29
|
+
defaultView?: ArtifactPanelView;
|
|
30
|
+
/** Fires with the chosen view. */
|
|
31
|
+
onViewChange?: (view: ArtifactPanelView) => void;
|
|
32
|
+
/** Whether the host is showing the panel at its expanded size. The panel never sizes itself: this only picks the toggle's icon and name. */
|
|
33
|
+
expanded?: boolean;
|
|
34
|
+
/** Fires with the next state when the expand toggle is pressed. The toggle renders only when this is given. */
|
|
35
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
36
|
+
/** Fires when the close button is pressed. The button renders only when this is given. */
|
|
37
|
+
onClose?: () => void;
|
|
38
|
+
/** Extra header controls, seated ahead of the expand and close buttons: a download, a share, a version menu. */
|
|
39
|
+
actions?: React.ReactNode;
|
|
40
|
+
/** Label for the preview segment of the view switch. */
|
|
41
|
+
previewLabel?: string;
|
|
42
|
+
/** Label for the code segment of the view switch. */
|
|
43
|
+
codeLabel?: string;
|
|
44
|
+
/** Accessible name for the view switch. */
|
|
45
|
+
viewLabel?: string;
|
|
46
|
+
/** Accessible name for the expand toggle while the panel is at its resting size. */
|
|
47
|
+
expandLabel?: string;
|
|
48
|
+
/** Accessible name for the expand toggle while the panel is expanded. */
|
|
49
|
+
collapseLabel?: string;
|
|
50
|
+
/** Accessible name for the close button. */
|
|
51
|
+
closeLabel?: string;
|
|
52
|
+
/** Additional CSS classes */
|
|
53
|
+
className?: string;
|
|
54
|
+
};
|
|
55
|
+
export interface ArtifactPanelProps extends ArtifactPanelOwnProps, Omit<React.ComponentPropsWithoutRef<'section'>, keyof ArtifactPanelOwnProps> {
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* ArtifactPanel is the surface a chat opens beside the conversation to show
|
|
59
|
+
* something it made: a dashboard, a document, a page, a site. A header names
|
|
60
|
+
* the artifact and carries its controls; the body is the preview, drawn from
|
|
61
|
+
* live React content, a sandboxed HTML document or a URL, with the source one
|
|
62
|
+
* switch away when there is any.
|
|
63
|
+
*
|
|
64
|
+
* Surface-less like AgentRail and ThreadPanel: it fills the box the host
|
|
65
|
+
* gives it and the host paints the card around it. Where it sits and how
|
|
66
|
+
* large it runs are the host's too, so `expanded` and `onClose` only report
|
|
67
|
+
* intent.
|
|
68
|
+
*/
|
|
69
|
+
export declare const ArtifactPanel: React.ForwardRefExoticComponent<ArtifactPanelProps & React.RefAttributes<HTMLElement>>;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import React, { useId, useState } from "react";
|
|
4
|
+
import { CircularButton } from "../CircularButton/CircularButton.js";
|
|
5
|
+
import { CodeBlock } from "../CodeBlock/CodeBlock.js";
|
|
6
|
+
import { SegmentedControl } from "../SegmentedControl/SegmentedControl.js";
|
|
7
|
+
import "./ArtifactPanel.css";
|
|
8
|
+
import "../../fonts/material-symbols.css";
|
|
9
|
+
const baseClass = "ds-artifact-panel";
|
|
10
|
+
const ArtifactPanel = React.forwardRef(
|
|
11
|
+
({
|
|
12
|
+
title,
|
|
13
|
+
meta,
|
|
14
|
+
icon,
|
|
15
|
+
children,
|
|
16
|
+
html,
|
|
17
|
+
src,
|
|
18
|
+
sandbox = "allow-scripts allow-forms",
|
|
19
|
+
frameTitle,
|
|
20
|
+
code,
|
|
21
|
+
language,
|
|
22
|
+
view,
|
|
23
|
+
defaultView,
|
|
24
|
+
onViewChange,
|
|
25
|
+
expanded = false,
|
|
26
|
+
onExpandedChange,
|
|
27
|
+
onClose,
|
|
28
|
+
actions,
|
|
29
|
+
previewLabel = "Preview",
|
|
30
|
+
codeLabel = "Code",
|
|
31
|
+
viewLabel = "Artifact view",
|
|
32
|
+
expandLabel = "Expand the artifact",
|
|
33
|
+
collapseLabel = "Collapse the artifact",
|
|
34
|
+
closeLabel = "Close the artifact",
|
|
35
|
+
className = "",
|
|
36
|
+
...rest
|
|
37
|
+
}, ref) => {
|
|
38
|
+
const titleId = useId();
|
|
39
|
+
const hasPreview = Boolean(children) || html !== void 0 || Boolean(src);
|
|
40
|
+
const hasCode = code !== void 0;
|
|
41
|
+
const [ownView, setOwnView] = useState(defaultView ?? "preview");
|
|
42
|
+
const wanted = view ?? ownView;
|
|
43
|
+
const current = wanted === "code" ? hasCode ? "code" : "preview" : hasPreview ? "preview" : "code";
|
|
44
|
+
const changeView = (next) => {
|
|
45
|
+
const chosen = next === "code" ? "code" : "preview";
|
|
46
|
+
setOwnView(chosen);
|
|
47
|
+
onViewChange?.(chosen);
|
|
48
|
+
};
|
|
49
|
+
const frameName = frameTitle ?? (typeof title === "string" ? title : previewLabel);
|
|
50
|
+
return /* @__PURE__ */ jsxs(
|
|
51
|
+
"section",
|
|
52
|
+
{
|
|
53
|
+
ref,
|
|
54
|
+
...rest,
|
|
55
|
+
className: [baseClass, className].filter(Boolean).join(" "),
|
|
56
|
+
"aria-labelledby": titleId,
|
|
57
|
+
children: [
|
|
58
|
+
/* @__PURE__ */ jsxs("header", { className: `${baseClass}__header`, children: [
|
|
59
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__heading`, children: [
|
|
60
|
+
icon !== void 0 && (typeof icon === "string" ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: icon }) : /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, children: icon })),
|
|
61
|
+
/* @__PURE__ */ jsx("h2", { className: `${baseClass}__title`, id: titleId, children: title }),
|
|
62
|
+
meta && /* @__PURE__ */ jsx("span", { className: `${baseClass}__meta`, children: meta })
|
|
63
|
+
] }),
|
|
64
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__controls`, children: [
|
|
65
|
+
hasPreview && hasCode && /* @__PURE__ */ jsx(
|
|
66
|
+
SegmentedControl,
|
|
67
|
+
{
|
|
68
|
+
size: "compact",
|
|
69
|
+
variant: "neutral",
|
|
70
|
+
ariaLabel: viewLabel,
|
|
71
|
+
segments: [
|
|
72
|
+
{ value: "preview", label: previewLabel },
|
|
73
|
+
{ value: "code", label: codeLabel }
|
|
74
|
+
],
|
|
75
|
+
activeSegment: current,
|
|
76
|
+
onSegmentChange: changeView
|
|
77
|
+
}
|
|
78
|
+
),
|
|
79
|
+
actions,
|
|
80
|
+
onExpandedChange && /* @__PURE__ */ jsx(
|
|
81
|
+
CircularButton,
|
|
82
|
+
{
|
|
83
|
+
icon: expanded ? "collapse_content" : "expand_content",
|
|
84
|
+
variant: "tertiary",
|
|
85
|
+
ariaLabel: expanded ? collapseLabel : expandLabel,
|
|
86
|
+
tooltipPosition: "bottom",
|
|
87
|
+
"aria-pressed": expanded,
|
|
88
|
+
onClick: () => onExpandedChange(!expanded)
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
onClose && /* @__PURE__ */ jsx(
|
|
92
|
+
CircularButton,
|
|
93
|
+
{
|
|
94
|
+
icon: "close",
|
|
95
|
+
variant: "tertiary",
|
|
96
|
+
ariaLabel: closeLabel,
|
|
97
|
+
tooltipPosition: "bottom",
|
|
98
|
+
onClick: onClose
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
] })
|
|
102
|
+
] }),
|
|
103
|
+
/* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, "data-view": current, children: current === "code" && hasCode ? /* @__PURE__ */ jsx("div", { className: `${baseClass}__code`, children: /* @__PURE__ */ jsx(CodeBlock, { code: code ?? "", language }) }) : children ? /* @__PURE__ */ jsx("div", { className: `${baseClass}__content`, children }) : html !== void 0 ? /* @__PURE__ */ jsx(
|
|
104
|
+
"iframe",
|
|
105
|
+
{
|
|
106
|
+
className: `${baseClass}__frame`,
|
|
107
|
+
title: frameName,
|
|
108
|
+
srcDoc: html,
|
|
109
|
+
sandbox,
|
|
110
|
+
referrerPolicy: "no-referrer"
|
|
111
|
+
}
|
|
112
|
+
) : src ? /* @__PURE__ */ jsx(
|
|
113
|
+
"iframe",
|
|
114
|
+
{
|
|
115
|
+
className: `${baseClass}__frame`,
|
|
116
|
+
title: frameName,
|
|
117
|
+
src,
|
|
118
|
+
sandbox,
|
|
119
|
+
referrerPolicy: "no-referrer",
|
|
120
|
+
loading: "lazy"
|
|
121
|
+
}
|
|
122
|
+
) : null })
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
ArtifactPanel.displayName = "ArtifactPanel";
|
|
129
|
+
export {
|
|
130
|
+
ArtifactPanel
|
|
131
|
+
};
|
|
@@ -51,10 +51,9 @@
|
|
|
51
51
|
HEADER — brand row and the collapse toggle
|
|
52
52
|
============================================ */
|
|
53
53
|
|
|
54
|
-
/*
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
margin sits the brand row evenly between the panel's top edge and the
|
|
54
|
+
/* --padding-200 centres the 24px mark on the rail's axis (12 + 8 = the
|
|
55
|
+
rows' icon seat) and holds it there expanded, so the logo never moves
|
|
56
|
+
as the rail sweeps. The margin sits the brand row evenly between the panel's top edge and the
|
|
58
57
|
new-thread row: 6 + 12 + the panel's 4px gap below mirrors the 16px
|
|
59
58
|
panel padding + 6 above. */
|
|
60
59
|
.ds-thread-panel__header {
|
|
@@ -64,11 +63,6 @@
|
|
|
64
63
|
min-width: 0;
|
|
65
64
|
padding: var(--padding-150) var(--padding-200);
|
|
66
65
|
margin-bottom: var(--gap-300);
|
|
67
|
-
transition: padding var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.ds-thread-panel--expanded .ds-thread-panel__header {
|
|
71
|
-
padding: var(--padding-150) var(--padding-400);
|
|
72
66
|
}
|
|
73
67
|
|
|
74
68
|
/* A logo-less header collapsed to the rail: the empty brand slot would
|
|
@@ -151,11 +145,57 @@
|
|
|
151
145
|
overflow: hidden;
|
|
152
146
|
opacity: 0;
|
|
153
147
|
transition: opacity var(--motion-duration-base) var(--motion-ease-standard),
|
|
154
|
-
width var(--motion-duration-slow) var(--motion-ease-emphasized)
|
|
148
|
+
width var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
149
|
+
margin var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* The hover wash is a circle drawn around the 24px mark rather than a
|
|
153
|
+
bigger button, so the glyph keeps its place on the trailing rail. The
|
|
154
|
+
toggle clips while it folds, so the wash only exists once it is open. */
|
|
155
|
+
.ds-thread-panel__toggle::before {
|
|
156
|
+
content: '';
|
|
157
|
+
position: absolute;
|
|
158
|
+
inset: calc(-1 * var(--gap-100));
|
|
159
|
+
z-index: -1;
|
|
160
|
+
border-radius: var(--radius-pill);
|
|
161
|
+
background-color: transparent;
|
|
162
|
+
transition: background-color var(--motion-duration-fast)
|
|
163
|
+
var(--motion-ease-standard);
|
|
155
164
|
}
|
|
156
165
|
|
|
166
|
+
.ds-thread-panel--expanded .ds-thread-panel__toggle,
|
|
167
|
+
.ds-thread-panel__toggle--standalone {
|
|
168
|
+
position: relative;
|
|
169
|
+
z-index: 0;
|
|
170
|
+
overflow: visible;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.ds-thread-panel__toggle:hover::before {
|
|
174
|
+
background-color: var(--color-action-passive-bg-hover);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.ds-thread-panel__toggle:active::before {
|
|
178
|
+
background-color: var(--color-action-passive-bg-active);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Collapsed, the rail is too narrow for a tooltip and clips it; the
|
|
182
|
+
native title carries the name there instead. */
|
|
183
|
+
.ds-thread-panel__toggle-tip {
|
|
184
|
+
flex: none;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.ds-thread-panel:not(.ds-thread-panel--expanded)
|
|
188
|
+
.ds-thread-panel__toggle-tip
|
|
189
|
+
.ds-tooltip__panel {
|
|
190
|
+
display: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* The glyph's ink stops a bearing short of its 24px box, so the box is
|
|
194
|
+
pulled out by that much: the mark's right edge lands on the rail the
|
|
195
|
+
rows' meta captions and the profile's caption end on. */
|
|
157
196
|
.ds-thread-panel--expanded .ds-thread-panel__toggle {
|
|
158
197
|
width: 24px;
|
|
198
|
+
margin-inline-end: calc(-1 * var(--gap-100));
|
|
159
199
|
opacity: 1;
|
|
160
200
|
}
|
|
161
201
|
|
|
@@ -199,20 +239,13 @@
|
|
|
199
239
|
text-align: left;
|
|
200
240
|
text-decoration: none;
|
|
201
241
|
transition: background-color var(--motion-duration-fast)
|
|
202
|
-
|
|
203
|
-
padding var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
242
|
+
var(--motion-ease-standard);
|
|
204
243
|
}
|
|
205
244
|
|
|
206
|
-
/*
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
.ds-thread-panel--expanded .ds-thread-panel__new,
|
|
211
|
-
.ds-thread-panel--expanded .ds-thread-panel__control,
|
|
212
|
-
.ds-thread-panel--expanded .ds-thread-panel__project,
|
|
213
|
-
.ds-thread-panel--expanded .ds-thread-panel__thread {
|
|
214
|
-
padding: var(--padding-200) var(--padding-400);
|
|
215
|
-
}
|
|
245
|
+
/* One seat in both states: the 20px inset (12 + 8) the collapsed circle's
|
|
246
|
+
math needs is also where the expanded panel's marks sit, so no mark
|
|
247
|
+
shifts as the rail sweeps. Text-led content sits a bearing deeper — see
|
|
248
|
+
TEXT SEAT at the end of the file. */
|
|
216
249
|
|
|
217
250
|
.ds-thread-panel__new:hover,
|
|
218
251
|
.ds-thread-panel__control:hover,
|
|
@@ -335,8 +368,7 @@
|
|
|
335
368
|
align-items: center;
|
|
336
369
|
gap: var(--gap-200);
|
|
337
370
|
min-width: 0;
|
|
338
|
-
padding: var(--padding-150) var(--padding-200)
|
|
339
|
-
var(--padding-400);
|
|
371
|
+
padding: var(--padding-150) var(--padding-200);
|
|
340
372
|
opacity: 0;
|
|
341
373
|
visibility: hidden;
|
|
342
374
|
transition: opacity var(--motion-duration-base) var(--motion-ease-standard),
|
|
@@ -356,7 +388,7 @@
|
|
|
356
388
|
overflow: hidden;
|
|
357
389
|
white-space: nowrap;
|
|
358
390
|
font-family: var(--font-overline-family);
|
|
359
|
-
font-size: var(--font-
|
|
391
|
+
font-size: var(--font-caption-size);
|
|
360
392
|
font-weight: var(--font-overline-weight);
|
|
361
393
|
line-height: var(--font-overline-line-height);
|
|
362
394
|
letter-spacing: var(--font-overline-letter-spacing);
|
|
@@ -477,16 +509,19 @@
|
|
|
477
509
|
gap: var(--gap-050);
|
|
478
510
|
}
|
|
479
511
|
|
|
480
|
-
/*
|
|
512
|
+
/* The overline face set one step down, at the caption's size: the labels
|
|
513
|
+
sit among caption-sized meta and second lines, and the full overline
|
|
514
|
+
outweighed the thread titles it files. The overline's own line-height
|
|
515
|
+
stays, so the rhythm below is unchanged. Aligned with the text seat. The vertical padding
|
|
481
516
|
is the rows' own inset restated — as if the label sat inside a row —
|
|
482
517
|
so the overline keeps the pills' rhythm instead of hugging the
|
|
483
518
|
new-thread row above and the first thread below. */
|
|
484
519
|
.ds-thread-panel__group-label {
|
|
485
|
-
padding: var(--padding-200)
|
|
520
|
+
padding: var(--padding-200);
|
|
486
521
|
overflow: hidden;
|
|
487
522
|
white-space: nowrap;
|
|
488
523
|
font-family: var(--font-overline-family);
|
|
489
|
-
font-size: var(--font-
|
|
524
|
+
font-size: var(--font-caption-size);
|
|
490
525
|
font-weight: var(--font-overline-weight);
|
|
491
526
|
line-height: var(--font-overline-line-height);
|
|
492
527
|
letter-spacing: var(--font-overline-letter-spacing);
|
|
@@ -760,7 +795,7 @@
|
|
|
760
795
|
width: 100%;
|
|
761
796
|
height: 40px;
|
|
762
797
|
min-width: 0;
|
|
763
|
-
padding: 0 var(--padding-
|
|
798
|
+
padding: 0 var(--padding-200);
|
|
764
799
|
border: var(--border-025) solid var(--color-action-primary-bg);
|
|
765
800
|
border-radius: var(--radius-pill);
|
|
766
801
|
background-color: var(--color-bg-container-primary);
|
|
@@ -778,7 +813,7 @@
|
|
|
778
813
|
|
|
779
814
|
.ds-thread-panel__more {
|
|
780
815
|
align-self: flex-start;
|
|
781
|
-
padding: var(--padding-150) var(--padding-
|
|
816
|
+
padding: var(--padding-150) var(--padding-200);
|
|
782
817
|
border: none;
|
|
783
818
|
border-radius: var(--radius-pill);
|
|
784
819
|
background: transparent;
|
|
@@ -826,8 +861,9 @@
|
|
|
826
861
|
pointer-events: auto;
|
|
827
862
|
}
|
|
828
863
|
|
|
829
|
-
/*
|
|
830
|
-
|
|
864
|
+
/* --padding-100 centres the 32px avatar on the rail's axis (12 + 4) in
|
|
865
|
+
both states, so it holds still like the header's mark; expanded, only
|
|
866
|
+
the trailing inset deepens, landing the caption on the rows' meta rail. */
|
|
831
867
|
.ds-thread-panel__profile {
|
|
832
868
|
display: flex;
|
|
833
869
|
align-items: center;
|
|
@@ -838,7 +874,8 @@
|
|
|
838
874
|
}
|
|
839
875
|
|
|
840
876
|
.ds-thread-panel--expanded .ds-thread-panel__profile {
|
|
841
|
-
padding: var(--padding-100) var(--padding-
|
|
877
|
+
padding: var(--padding-100) var(--padding-200) var(--padding-100)
|
|
878
|
+
var(--padding-100);
|
|
842
879
|
}
|
|
843
880
|
|
|
844
881
|
.ds-thread-panel__profile-name {
|
|
@@ -875,3 +912,20 @@
|
|
|
875
912
|
.ds-thread-panel--expanded .ds-thread-panel__profile-meta {
|
|
876
913
|
opacity: 1;
|
|
877
914
|
}
|
|
915
|
+
|
|
916
|
+
/* ============================================
|
|
917
|
+
TEXT SEAT — last, so it wins over each rule's padding shorthand
|
|
918
|
+
============================================ */
|
|
919
|
+
|
|
920
|
+
/* Text-led content — group labels, thread rows, the more row, the rename
|
|
921
|
+
field — sits a bearing deeper than the icon seat: a glyph's ink starts
|
|
922
|
+
that far inside its 24px box, so this is what lands the labels and status
|
|
923
|
+
dots under the pen's visible edge while the glyph itself stays centred
|
|
924
|
+
under the logo. None of it shows collapsed, so nothing moves. */
|
|
925
|
+
.ds-thread-panel__thread,
|
|
926
|
+
.ds-thread-panel__group-label,
|
|
927
|
+
.ds-thread-panel__projects-header,
|
|
928
|
+
.ds-thread-panel__more,
|
|
929
|
+
.ds-thread-panel__rename {
|
|
930
|
+
padding-inline-start: var(--padding-300);
|
|
931
|
+
}
|
|
@@ -5,6 +5,7 @@ import { DropdownMenu } from "../DropdownMenu/DropdownMenu.js";
|
|
|
5
5
|
import { Kbd } from "../Kbd/Kbd.js";
|
|
6
6
|
import { PixelAvatar } from "../PixelAvatar/PixelAvatar.js";
|
|
7
7
|
import { StatusDot } from "../StatusDot/StatusDot.js";
|
|
8
|
+
import { Tooltip } from "../Tooltip/Tooltip.js";
|
|
8
9
|
import "./ThreadPanel.css";
|
|
9
10
|
import "../../fonts/material-symbols.css";
|
|
10
11
|
import { distinctPixelInks } from "../PixelAvatar/pixelInks.js";
|
|
@@ -163,18 +164,28 @@ const ThreadPanel = React.forwardRef(
|
|
|
163
164
|
const showNew = Boolean(newThreadLabel || onNewThread || newThreadHref);
|
|
164
165
|
const showFooter = Boolean(footerSlot || profile);
|
|
165
166
|
const logoExpands = Boolean(logo && !expanded && onExpandedChange);
|
|
167
|
+
const toggleLabel = expanded ? collapseLabel : expandLabel;
|
|
166
168
|
const toggle = onExpandedChange && /* @__PURE__ */ jsx(
|
|
167
|
-
|
|
169
|
+
Tooltip,
|
|
168
170
|
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
171
|
+
content: toggleLabel,
|
|
172
|
+
position: "left",
|
|
173
|
+
className: `${baseClass}__toggle-tip`,
|
|
174
|
+
children: /* @__PURE__ */ jsx(
|
|
175
|
+
"button",
|
|
176
|
+
{
|
|
177
|
+
type: "button",
|
|
178
|
+
className: [
|
|
179
|
+
`${baseClass}__toggle`,
|
|
180
|
+
!logo && `${baseClass}__toggle--standalone`
|
|
181
|
+
].filter(Boolean).join(" "),
|
|
182
|
+
"aria-label": toggleLabel,
|
|
183
|
+
title: expanded ? void 0 : toggleLabel,
|
|
184
|
+
"aria-expanded": expanded,
|
|
185
|
+
onClick: () => onExpandedChange(!expanded),
|
|
186
|
+
children: /* @__PURE__ */ jsx("span", { className: iconClass, "aria-hidden": "true", children: expanded ? "left_panel_close" : "left_panel_open" })
|
|
187
|
+
}
|
|
188
|
+
)
|
|
178
189
|
}
|
|
179
190
|
);
|
|
180
191
|
const expandOnClick = !expanded && onExpandedChange ? (e) => {
|
package/components/registry.json
CHANGED
|
@@ -161,6 +161,22 @@
|
|
|
161
161
|
"folder": "Chart",
|
|
162
162
|
"recharts": true
|
|
163
163
|
},
|
|
164
|
+
{
|
|
165
|
+
"name": "ArtifactCard",
|
|
166
|
+
"label": "Artifact card",
|
|
167
|
+
"slug": "artifact-card",
|
|
168
|
+
"description": "The tile a chat leaves in the thread for something it made, a toggle that opens the artifact in its panel.",
|
|
169
|
+
"category": "ai",
|
|
170
|
+
"client": false
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "ArtifactPanel",
|
|
174
|
+
"label": "Artifact panel",
|
|
175
|
+
"slug": "artifact-panel",
|
|
176
|
+
"description": "The panel a chat opens beside the conversation to preview what it made: live content, a sandboxed page or a site, with the source a switch away.",
|
|
177
|
+
"category": "ai",
|
|
178
|
+
"client": true
|
|
179
|
+
},
|
|
164
180
|
{
|
|
165
181
|
"name": "AttachmentDropZone",
|
|
166
182
|
"label": "Attachment drop zone",
|
|
@@ -161,6 +161,22 @@ declare const _default: {
|
|
|
161
161
|
"folder": "Chart",
|
|
162
162
|
"recharts": true
|
|
163
163
|
},
|
|
164
|
+
{
|
|
165
|
+
"name": "ArtifactCard",
|
|
166
|
+
"label": "Artifact card",
|
|
167
|
+
"slug": "artifact-card",
|
|
168
|
+
"description": "The tile a chat leaves in the thread for something it made, a toggle that opens the artifact in its panel.",
|
|
169
|
+
"category": "ai",
|
|
170
|
+
"client": false
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "ArtifactPanel",
|
|
174
|
+
"label": "Artifact panel",
|
|
175
|
+
"slug": "artifact-panel",
|
|
176
|
+
"description": "The panel a chat opens beside the conversation to preview what it made: live content, a sandboxed page or a site, with the source a switch away.",
|
|
177
|
+
"category": "ai",
|
|
178
|
+
"client": true
|
|
179
|
+
},
|
|
164
180
|
{
|
|
165
181
|
"name": "AttachmentDropZone",
|
|
166
182
|
"label": "Attachment drop zone",
|