@parallelworks/ui 0.80.0 → 0.82.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 +7 -1
- package/dist/ai/adapter/types.d.ts +1 -0
- package/dist/ai/ai.css +3 -92
- package/dist/ai/components/agent/AgentMessageParts.js +1 -0
- package/dist/ai/core/ChatProvider.js +10 -5
- package/dist/ai/strings.d.ts +3 -0
- package/dist/ai/strings.js +2 -0
- package/dist/ai/types.d.ts +5 -0
- package/dist/components/Avatar.d.ts +5 -3
- package/dist/components/Avatar.js +6 -2
- package/dist/components/Card.d.ts +16 -1
- package/dist/components/Card.js +13 -2
- package/dist/components/CodeBlock.d.ts +40 -0
- package/dist/components/CodeBlock.js +159 -0
- package/dist/components/CopyButton.d.ts +11 -4
- package/dist/components/CopyButton.js +86 -11
- package/dist/components/CreateModal.d.ts +3 -1
- package/dist/components/CreateModal.js +31 -11
- package/dist/components/DescriptionList.js +1 -1
- package/dist/components/Drawer.js +1 -1
- package/dist/components/Input.d.ts +5 -0
- package/dist/components/Input.js +4 -3
- package/dist/components/Listbox.d.ts +12 -3
- package/dist/components/Listbox.js +38 -15
- package/dist/components/MarkdownImpl.js +89 -2
- package/dist/components/MenuEntries.d.ts +3 -0
- package/dist/components/MenuEntries.js +19 -6
- package/dist/components/Modal.d.ts +1 -1
- package/dist/components/Modal.js +6 -5
- package/dist/components/PageHeader.d.ts +7 -1
- package/dist/components/PageHeader.js +11 -5
- package/dist/components/Provider.d.ts +3 -0
- package/dist/components/Provider.js +1 -0
- package/dist/components/Select.js +1 -1
- package/dist/components/SettingsCard.d.ts +7 -2
- package/dist/components/SettingsCard.js +42 -7
- package/dist/components/ToggleGroup.d.ts +3 -1
- package/dist/components/ToggleGroup.js +2 -2
- package/dist/components/Tooltip.js +35 -2
- package/dist/components/codeHighlight.d.ts +9 -0
- package/dist/components/codeHighlight.js +29 -0
- package/dist/components/pageHeaderStore.d.ts +4 -0
- package/dist/components/pageHeaderStore.js +5 -2
- package/dist/file-explorer/FileExplorer.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/list/ListFilterBar.js +6 -5
- package/dist/list/filterModel.d.ts +4 -0
- package/dist/list/filterModel.js +7 -1
- package/dist/styles/theme.css +1 -0
- package/dist/styles.css +1 -1
- package/dist/theme/derive.d.ts +1 -1
- package/dist/theme/derive.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -131,7 +131,8 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
131
131
|
| A live relative time or duration | `RelativeTime` (with a `fallback`, an `exact` style for its tooltip, or `show="absolute"`), `Elapsed` |
|
|
132
132
|
| Fetching again on demand | `RefreshButton`; `variant="inline"` in a line of details; `loading` on an `ActionToolbar` action |
|
|
133
133
|
| Rendered Markdown | `Markdown` |
|
|
134
|
-
| Code or YAML to read
|
|
134
|
+
| Code or YAML to read, such as a run's resolved workflow | `CodeBlock`: highlighted, numbered, with copy and, given a `filename`, download; `fill` for a view that is the code |
|
|
135
|
+
| Code or YAML to edit | `Editor` from `./editor`; `EditorField` as a form field, `FormCodePanel` for a form's values as JSON |
|
|
135
136
|
| A run's jobs, steps, logs, annotations and warnings | `DependencyGraph` from `./graph`, as a graph or a list |
|
|
136
137
|
| A workflow's jobs drawn from its YAML, or edited by hand | `DependencyGraphPreview` from `./graph`, editable with an `editor`; `InputsFormEditor` for its run form |
|
|
137
138
|
| A value that survives a reload | `usePersistedState` |
|
|
@@ -168,6 +169,11 @@ Some pairs look alike and are split on purpose:
|
|
|
168
169
|
its tone, for anything. A `RunStatusIcon` shows the status of something that
|
|
169
170
|
ran as an icon beside its name, and turns while it runs; `runStatusTone` gives
|
|
170
171
|
a badge for the same status the same tone.
|
|
172
|
+
- **CodeBlock or CopyCodeBlock.** A `CodeBlock` is code to read, such as a
|
|
173
|
+
file or a workflow: a header names it, its lines are highlighted and
|
|
174
|
+
numbered, and it can fill a pane. A `CopyCodeBlock` is a command or a value
|
|
175
|
+
to copy, such as an install line or a new API key, with its copy button over
|
|
176
|
+
it and `secret` to mask it.
|
|
171
177
|
- **Select or Combobox.** A `Select` picks from a short list it shows in full.
|
|
172
178
|
A `Combobox` filters as you type, for a list long enough to search, and is the
|
|
173
179
|
one that takes several values, new ones, or free text (`Autocomplete`).
|
package/dist/ai/ai.css
CHANGED
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
padding: 0;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/* Inline code
|
|
50
|
-
* no border: a hard outline turns every identifier into a keycap. */
|
|
49
|
+
/* Inline code; a fenced block is a CodeBlock, which styles itself. A tint of
|
|
50
|
+
* the ink and no border: a hard outline turns every identifier into a keycap. */
|
|
51
51
|
.markdown code {
|
|
52
52
|
/* rem, not em: a table cell or list already sized down would size it twice. */
|
|
53
53
|
font-size: 0.875rem;
|
|
@@ -226,95 +226,6 @@
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
/* Streamdown code blocks: one surface, not a frame around an inset panel.
|
|
230
|
-
* The theme's code surface, which the syntax colors are solved against, with a
|
|
231
|
-
* hairline edge holds the language on its top edge,
|
|
232
|
-
* the copy and download controls quiet beside it, and the code on the same
|
|
233
|
-
* ground, so the block reads as part of the answer. */
|
|
234
|
-
.markdown [data-streamdown="code-block"] {
|
|
235
|
-
gap: 0;
|
|
236
|
-
margin: 0.875rem 0;
|
|
237
|
-
padding: 0;
|
|
238
|
-
border: 0;
|
|
239
|
-
border-radius: 0.75rem;
|
|
240
|
-
overflow: hidden;
|
|
241
|
-
background: var(--theme-code-bg, #f3f4f6);
|
|
242
|
-
box-shadow: 0 0 0 1px color-mix(in oklab, var(--theme-app, #000) 8%, transparent);
|
|
243
|
-
}
|
|
244
|
-
.markdown [data-streamdown="code-block-header"] {
|
|
245
|
-
height: 2.25rem;
|
|
246
|
-
padding: 0 0.75rem 0 0.75rem;
|
|
247
|
-
font-size: 0.75rem;
|
|
248
|
-
color: var(--theme-muted-text-color, #6a7282);
|
|
249
|
-
}
|
|
250
|
-
.markdown [data-streamdown="code-block-actions"] {
|
|
251
|
-
gap: 0.125rem;
|
|
252
|
-
padding: 0;
|
|
253
|
-
border: 0;
|
|
254
|
-
background: transparent;
|
|
255
|
-
backdrop-filter: none;
|
|
256
|
-
}
|
|
257
|
-
.markdown [data-streamdown="code-block-actions"] button {
|
|
258
|
-
padding: 0.3125rem;
|
|
259
|
-
border-radius: 0.375rem;
|
|
260
|
-
color: var(--theme-muted-text-color, #6a7282);
|
|
261
|
-
transition:
|
|
262
|
-
background-color 150ms ease-out,
|
|
263
|
-
color 150ms ease-out;
|
|
264
|
-
}
|
|
265
|
-
.markdown [data-streamdown="code-block-actions"] button:hover {
|
|
266
|
-
color: var(--theme-app, #000);
|
|
267
|
-
background: color-mix(in oklab, var(--theme-app, #000) 7%, transparent);
|
|
268
|
-
}
|
|
269
|
-
.markdown [data-streamdown="code-block-body"] {
|
|
270
|
-
border: 0;
|
|
271
|
-
border-radius: 0;
|
|
272
|
-
padding: 0;
|
|
273
|
-
background: transparent;
|
|
274
|
-
}
|
|
275
|
-
.markdown [data-streamdown="code-block-body"] pre {
|
|
276
|
-
margin: 0;
|
|
277
|
-
padding: 0.125rem 1rem 0.875rem;
|
|
278
|
-
overflow-x: auto;
|
|
279
|
-
/* Code reads looser than prose; Streamdown's text-sm line is too tight. */
|
|
280
|
-
line-height: 1.6;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/* Shiki dual-theme color overrides (standard Shiki approach).
|
|
284
|
-
Streamdown uses Tailwind dark: arbitrary-value classes for theme switching,
|
|
285
|
-
but Tailwind v4's source scanner can misparse them from minified JS —
|
|
286
|
-
generating broken selectors that apply dark bg unconditionally while
|
|
287
|
-
missing the text color classes entirely. Override both light and dark
|
|
288
|
-
explicitly to ensure correct colors in all modes.
|
|
289
|
-
See: https://shiki.style/guide/dual-themes#class-based-dark-mode */
|
|
290
|
-
|
|
291
|
-
/* Light mode: the primary Shiki theme's colors (--sdm-c) on the block's own
|
|
292
|
-
surface, which stands in for the theme's background. */
|
|
293
|
-
.markdown [data-streamdown="code-block-body"] pre {
|
|
294
|
-
/* biome-ignore lint/complexity/noImportantStyles: the highlighter sets the background as an inline style */
|
|
295
|
-
background-color: transparent !important;
|
|
296
|
-
color: var(--sdm-fg, inherit);
|
|
297
|
-
}
|
|
298
|
-
.markdown [data-streamdown="code-block-body"] pre code > span > span {
|
|
299
|
-
color: var(--sdm-c, inherit);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/* Dark mode: the --shiki-dark colors, again on the block's surface. */
|
|
303
|
-
.dark .markdown [data-streamdown="code-block-body"] pre {
|
|
304
|
-
/* biome-ignore lint/complexity/noImportantStyles: the highlighter sets the background as an inline style */
|
|
305
|
-
background-color: transparent !important;
|
|
306
|
-
color: var(--shiki-dark, var(--sdm-fg, inherit));
|
|
307
|
-
}
|
|
308
|
-
.dark .markdown [data-streamdown="code-block-body"] pre code > span > span {
|
|
309
|
-
color: var(--shiki-dark, inherit);
|
|
310
|
-
font-style: var(--shiki-dark-font-style, inherit);
|
|
311
|
-
font-weight: var(--shiki-dark-font-weight, inherit);
|
|
312
|
-
text-decoration: var(--shiki-dark-text-decoration, inherit);
|
|
313
|
-
}
|
|
314
|
-
.dark .markdown [data-streamdown="code-block-body"] pre code > span > span[style*="--sdm-tbg"] {
|
|
315
|
-
background-color: var(--shiki-dark-bg, var(--sdm-tbg));
|
|
316
|
-
}
|
|
317
|
-
|
|
318
229
|
/* Remove prose backtick pseudo-elements on inline code */
|
|
319
230
|
.markdown code::before,
|
|
320
231
|
.markdown code::after {
|
|
@@ -531,7 +442,7 @@
|
|
|
531
442
|
/* A paste in the reader's bubble is a code block like the assistant's, set on
|
|
532
443
|
* the panel so it stands off the bubble's tint, with the bubble's spacing
|
|
533
444
|
* rather than the answer's. */
|
|
534
|
-
.chat-paste-code .markdown [data-
|
|
445
|
+
.chat-paste-code .markdown [data-code-block] {
|
|
535
446
|
margin: 0;
|
|
536
447
|
background: var(--theme-panel-bg, #fff);
|
|
537
448
|
white-space: normal;
|
|
@@ -302,6 +302,7 @@ function decisionLabel(d, t) {
|
|
|
302
302
|
switch (d.status) {
|
|
303
303
|
case "approved":
|
|
304
304
|
if (d.by === "auto-reviewer") return t.allowedByReviewer;
|
|
305
|
+
if (d.edited) return d.name ? t.allowedWithChangesBy(d.name) : t.allowedWithChanges;
|
|
305
306
|
return d.name ? t.allowedBy(d.name) : t.allowed;
|
|
306
307
|
case "denied":
|
|
307
308
|
if (d.by === "auto-reviewer") return t.deniedByReviewer;
|
|
@@ -627,6 +627,9 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
627
627
|
id: approvalId,
|
|
628
628
|
answer: answer.allowed ? t.approvalAllowed : t.approvalDenied
|
|
629
629
|
});
|
|
630
|
+
const instead = answer.allowed ? answer.instead : void 0;
|
|
631
|
+
const asked = pausedParts.find((p) => p.kind === "tool_call" && p.id === approvalId);
|
|
632
|
+
const args = answer.allowed && answer.args !== asked?.args ? answer.args : void 0;
|
|
630
633
|
applyToPaused({
|
|
631
634
|
type: "tool_decision",
|
|
632
635
|
id: approvalId,
|
|
@@ -634,14 +637,15 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
634
637
|
status: answer.allowed ? "approved" : "denied",
|
|
635
638
|
by: "user",
|
|
636
639
|
name: currentUser.name || currentUser.username,
|
|
637
|
-
at: DateTime.now().toUTC().toISO()
|
|
640
|
+
at: DateTime.now().toUTC().toISO(),
|
|
641
|
+
...args !== void 0 ? { edited: true } : {}
|
|
638
642
|
}
|
|
639
643
|
});
|
|
640
|
-
|
|
641
|
-
if (instead) {
|
|
644
|
+
if (instead || args !== void 0) {
|
|
642
645
|
pausedParts = pausedParts.map((p) => p.kind === "tool_call" && p.id === approvalId ? {
|
|
643
646
|
...p,
|
|
644
|
-
name: instead
|
|
647
|
+
...instead ? { name: instead } : {},
|
|
648
|
+
...args !== void 0 ? { args } : {}
|
|
645
649
|
} : p);
|
|
646
650
|
updatePaused({ parts: pausedParts });
|
|
647
651
|
}
|
|
@@ -661,7 +665,8 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
661
665
|
toolCallId: approvalId,
|
|
662
666
|
allowed: answer.allowed,
|
|
663
667
|
...answer.denyMessage ? { feedback: answer.denyMessage } : {},
|
|
664
|
-
...instead ? { tool: instead } : {}
|
|
668
|
+
...instead ? { tool: instead } : {},
|
|
669
|
+
...args !== void 0 ? { args } : {}
|
|
665
670
|
}
|
|
666
671
|
}, paused.id, (message) => updatePaused({ error: message }), (delta) => {
|
|
667
672
|
if (delta.type !== "tool_end" || delta.id !== approvalId) return false;
|
package/dist/ai/strings.d.ts
CHANGED
|
@@ -245,8 +245,11 @@ export interface ChatStrings {
|
|
|
245
245
|
answered: string;
|
|
246
246
|
/** Under a call someone answered: who allowed or denied it. */
|
|
247
247
|
allowedBy: (name: string) => string;
|
|
248
|
+
/** Allowed after the reader changed the call's arguments. */
|
|
249
|
+
allowedWithChangesBy: (name: string) => string;
|
|
248
250
|
deniedBy: (name: string) => string;
|
|
249
251
|
allowed: string;
|
|
252
|
+
allowedWithChanges: string;
|
|
250
253
|
denied: string;
|
|
251
254
|
allowedByReviewer: string;
|
|
252
255
|
deniedByReviewer: string;
|
package/dist/ai/strings.js
CHANGED
|
@@ -258,8 +258,10 @@ var defaultChatStrings = {
|
|
|
258
258
|
feedbackPlaceholder: "Tell the agent what to do instead…",
|
|
259
259
|
answered: "Answered",
|
|
260
260
|
allowedBy: (name) => `Allowed by ${name}`,
|
|
261
|
+
allowedWithChangesBy: (name) => `Allowed with changes by ${name}`,
|
|
261
262
|
deniedBy: (name) => `Denied by ${name}`,
|
|
262
263
|
allowed: "Allowed",
|
|
264
|
+
allowedWithChanges: "Allowed with changes",
|
|
263
265
|
denied: "Denied",
|
|
264
266
|
allowedByReviewer: "Allowed by the auto-reviewer",
|
|
265
267
|
deniedByReviewer: "Denied by the auto-reviewer",
|
package/dist/ai/types.d.ts
CHANGED
|
@@ -67,6 +67,8 @@ export interface ToolDecision {
|
|
|
67
67
|
note?: string;
|
|
68
68
|
/** Why it was settled this way, such as the auto-reviewer's rationale. */
|
|
69
69
|
reason?: string;
|
|
70
|
+
/** The reader changed the call's arguments before allowing it. */
|
|
71
|
+
edited?: boolean;
|
|
70
72
|
}
|
|
71
73
|
export interface ToolCallPart {
|
|
72
74
|
kind: 'tool_call';
|
|
@@ -103,6 +105,9 @@ export type ApprovalAnswerValue = {
|
|
|
103
105
|
/** A sibling tool to run in place of the one asked for, with the same
|
|
104
106
|
* arguments, such as destroying a cluster the model asked to stop. */
|
|
105
107
|
instead?: string;
|
|
108
|
+
/** The call's arguments as the reader changed them, as JSON text, to
|
|
109
|
+
* run in place of the model's suggestion. */
|
|
110
|
+
args?: string;
|
|
106
111
|
} | {
|
|
107
112
|
kind: 'ask';
|
|
108
113
|
text: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
1
|
+
import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
2
|
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
3
3
|
export type AvatarStatus = 'online' | 'offline' | 'busy' | 'away';
|
|
4
4
|
/** Who an avatar stands for. An agent's avatar is a rounded square, so its shape, and not only
|
|
@@ -51,7 +51,9 @@ export declare function avatarInitials(name: string | undefined, { twoLetters }?
|
|
|
51
51
|
* `--avatar-person-text`) and agents `--avatar-agent` (with `--avatar-agent-text`, and the
|
|
52
52
|
* edge `--avatar-agent-ring`). */
|
|
53
53
|
export declare function Avatar({ src, name, kind, shape, colorKey, colorClassName, size: sizeProp, status, decorative, tooltip, className, }: AvatarProps): import("react").JSX.Element;
|
|
54
|
-
|
|
54
|
+
/** Any other span attribute and the ref reach the stack's element, so the group can be a
|
|
55
|
+
* Tooltip's or a popover's direct child. */
|
|
56
|
+
export interface AvatarGroupProps extends Omit<ComponentPropsWithRef<'span'>, 'children' | 'className'> {
|
|
55
57
|
/** Avatars, each possibly wrapped, such as in a hover card. Each child counts as one. */
|
|
56
58
|
children: ReactNode;
|
|
57
59
|
/** Shows at most this many; the rest become a "+N" chip. */
|
|
@@ -77,4 +79,4 @@ export interface AvatarGroupProps {
|
|
|
77
79
|
/** Overlapping avatars, such as a task's assignees or who is here, with a "+N" chip for the
|
|
78
80
|
* rest. Each avatar inside takes the group's size and a ring in the color behind it, which
|
|
79
81
|
* follows its shape, round or square. */
|
|
80
|
-
export declare function AvatarGroup({ children, max, total, showOverflow, size, ring, ringColor, label, className, }: AvatarGroupProps): import("react").JSX.Element;
|
|
82
|
+
export declare function AvatarGroup({ children, max, total, showOverflow, size, ring, ringColor, label, className, style, ...spanProps }: AvatarGroupProps): import("react").JSX.Element;
|
|
@@ -144,7 +144,7 @@ function formatNumber(n, locale) {
|
|
|
144
144
|
/** Overlapping avatars, such as a task's assignees or who is here, with a "+N" chip for the
|
|
145
145
|
* rest. Each avatar inside takes the group's size and a ring in the color behind it, which
|
|
146
146
|
* follows its shape, round or square. */
|
|
147
|
-
function AvatarGroup({ children, max, total, showOverflow = true, size = "sm", ring = true, ringColor, label, className }) {
|
|
147
|
+
function AvatarGroup({ children, max, total, showOverflow = true, size = "sm", ring = true, ringColor, label, className, style, ...spanProps }) {
|
|
148
148
|
const { common } = useStrings();
|
|
149
149
|
const locale = useLocale();
|
|
150
150
|
const items = Children.toArray(children);
|
|
@@ -157,11 +157,15 @@ function AvatarGroup({ children, max, total, showOverflow = true, size = "sm", r
|
|
|
157
157
|
return /* @__PURE__ */ jsx(AvatarGroupContext, {
|
|
158
158
|
value: context,
|
|
159
159
|
children: /* @__PURE__ */ jsxs("span", {
|
|
160
|
+
...spanProps,
|
|
160
161
|
...label ? {
|
|
161
162
|
role: "group",
|
|
162
163
|
"aria-label": label
|
|
163
164
|
} : {},
|
|
164
|
-
style: {
|
|
165
|
+
style: {
|
|
166
|
+
"--avatar-ring": ringColor ?? "var(--theme-panel-bg)",
|
|
167
|
+
...style
|
|
168
|
+
},
|
|
165
169
|
className: cx("inline-flex shrink-0 items-center", OVERLAP_CLASSES[size], className),
|
|
166
170
|
children: [shown, showOverflow && rest > 0 && /* @__PURE__ */ jsxs("span", {
|
|
167
171
|
className: cx("relative inline-flex shrink-0 items-center justify-center rounded-full bg-(--theme-muted-panel-bg) font-medium text-(--theme-muted-text-color) tabular-nums select-none", CHIP_SIZE_CLASSES[size], ring && "ring-2 ring-(--avatar-ring)"),
|
|
@@ -12,6 +12,15 @@ declare const paddingClasses: {
|
|
|
12
12
|
md: string;
|
|
13
13
|
lg: string;
|
|
14
14
|
};
|
|
15
|
+
declare const toneColors: {
|
|
16
|
+
readonly accent: 'var(--theme-accent)';
|
|
17
|
+
readonly success: 'var(--theme-success)';
|
|
18
|
+
readonly warning: 'var(--theme-warning)';
|
|
19
|
+
readonly error: 'var(--theme-error)';
|
|
20
|
+
readonly info: 'var(--theme-info)';
|
|
21
|
+
};
|
|
22
|
+
/** A status's tone, or any CSS color a host picks, such as `var(--agent-color)`. */
|
|
23
|
+
export type CardTone = keyof typeof toneColors | (string & {});
|
|
15
24
|
export type CardSize = keyof typeof widthClasses;
|
|
16
25
|
export type CardPadding = keyof typeof paddingClasses;
|
|
17
26
|
export interface CardProps extends LinkTarget {
|
|
@@ -32,6 +41,12 @@ export interface CardProps extends LinkTarget {
|
|
|
32
41
|
/** Tints the border while a field inside has focus, for a card that frames an
|
|
33
42
|
* input, such as a composer. */
|
|
34
43
|
focusWithin?: boolean | undefined;
|
|
44
|
+
/** Tints the border in a color, setting the card apart by what it is, such as the work of
|
|
45
|
+
* one kind of author: a status's tone, or any CSS color, such as `var(--agent-color)`.
|
|
46
|
+
* It is `--card-tone` inside the card, for its own content to use. `highlight` deepens it. */
|
|
47
|
+
tone?: CardTone | undefined;
|
|
48
|
+
/** With `tone`, a faint fill of it too, for a card that should stand out at a glance. */
|
|
49
|
+
tinted?: boolean | undefined;
|
|
35
50
|
/** Renders the card as this element, such as a button, a list or a section. */
|
|
36
51
|
render?: ReactElement | undefined;
|
|
37
52
|
/** The whole card presses: it lifts on hover and shows focus. On by default for
|
|
@@ -43,7 +58,7 @@ export interface CardProps extends LinkTarget {
|
|
|
43
58
|
/** A surface holding one task, such as a sign-in form or a choice among a few
|
|
44
59
|
* options, raised over the page; or, with `size="fill"`, a flat panel among the
|
|
45
60
|
* page's content. */
|
|
46
|
-
export declare function Card({ children, size, padding, divided, elevated, highlight, focusWithin, render: renderProp, interactive, href, reloadDocument, target, className, }: CardProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
61
|
+
export declare function Card({ children, size, padding, divided, elevated, highlight, focusWithin, tone, tinted, render: renderProp, interactive, href, reloadDocument, target, className, }: CardProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
47
62
|
export interface CardHeaderProps {
|
|
48
63
|
badge?: ReactNode;
|
|
49
64
|
title: string;
|
package/dist/components/Card.js
CHANGED
|
@@ -21,10 +21,17 @@ var sizePadding = {
|
|
|
21
21
|
lg: "p-6 sm:p-8",
|
|
22
22
|
fill: paddingClasses.md
|
|
23
23
|
};
|
|
24
|
+
var toneColors = {
|
|
25
|
+
accent: "var(--theme-accent)",
|
|
26
|
+
success: "var(--theme-success)",
|
|
27
|
+
warning: "var(--theme-warning)",
|
|
28
|
+
error: "var(--theme-error)",
|
|
29
|
+
info: "var(--theme-info)"
|
|
30
|
+
};
|
|
24
31
|
/** A surface holding one task, such as a sign-in form or a choice among a few
|
|
25
32
|
* options, raised over the page; or, with `size="fill"`, a flat panel among the
|
|
26
33
|
* page's content. */
|
|
27
|
-
function Card({ children, size = "md", padding, divided = false, elevated = false, highlight = false, focusWithin = false, render: renderProp, interactive, href, reloadDocument, target, className }) {
|
|
34
|
+
function Card({ children, size = "md", padding, divided = false, elevated = false, highlight = false, focusWithin = false, tone, tinted = false, render: renderProp, interactive, href, reloadDocument, target, className }) {
|
|
28
35
|
const render = href === void 0 ? renderProp : /* @__PURE__ */ jsx(AppLink, {
|
|
29
36
|
href,
|
|
30
37
|
reloadDocument,
|
|
@@ -32,10 +39,14 @@ function Card({ children, size = "md", padding, divided = false, elevated = fals
|
|
|
32
39
|
});
|
|
33
40
|
const fill = size === "fill";
|
|
34
41
|
const pressable = interactive ?? render !== void 0;
|
|
42
|
+
const toned = tone !== void 0;
|
|
43
|
+
const toneColor = toned ? toneColors[tone] ?? tone : void 0;
|
|
44
|
+
const fillTint = toned && tinted;
|
|
35
45
|
return useRender({
|
|
36
46
|
render: render ?? /* @__PURE__ */ jsx("div", {}),
|
|
37
47
|
props: {
|
|
38
|
-
|
|
48
|
+
style: toneColor ? { "--card-tone": toneColor } : void 0,
|
|
49
|
+
className: cx("block w-full rounded-xl border", fillTint ? "bg-[color-mix(in_oklab,var(--card-tone)_7%,var(--theme-panel-bg))]" : "bg-(--theme-panel-bg)", toned ? highlight ? "border-(--card-tone)/80" : "border-(--card-tone)/45" : highlight ? "border-(--theme-accent)/30" : "border-(--theme-border)", focusWithin && "focus-within:border-(--theme-focus)/60", !fill && "backdrop-blur-xl", !fill && (elevated ? "shadow-(--theme-shadow-xl)" : "shadow-(--theme-shadow)"), widthClasses[size], padding === void 0 ? sizePadding[size] : paddingClasses[padding], divided && "divide-y divide-(--theme-border)", pressable && "cursor-pointer text-left text-inherit no-underline transition-[background-color,box-shadow,border-color] duration-150 hover:shadow-(--theme-shadow-lg) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--theme-link) motion-reduce:transition-none", pressable && (fillTint ? "hover:bg-[color-mix(in_oklab,var(--card-tone)_11%,var(--theme-panel-hover))]" : "hover:bg-(--theme-panel-hover)"), pressable && toned && "hover:border-(--card-tone)/70", !pressable && focusWithin && "transition-[border-color] duration-150 motion-reduce:transition-none", className),
|
|
39
50
|
children
|
|
40
51
|
}
|
|
41
52
|
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface CodeBlockProps {
|
|
3
|
+
/** The code, as it is copied and downloaded. */
|
|
4
|
+
code: string;
|
|
5
|
+
/** The language to highlight it as, such as `yaml`; the header names it
|
|
6
|
+
* unless `title` says otherwise. Without it the code reads as plain text. */
|
|
7
|
+
language?: string | undefined;
|
|
8
|
+
/** The header's text in place of the language, such as a file's name. */
|
|
9
|
+
title?: ReactNode;
|
|
10
|
+
/** Names the code for assistive technology where `title` is not plain text;
|
|
11
|
+
* by default the title, or the language ("yaml code"). */
|
|
12
|
+
label?: string | undefined;
|
|
13
|
+
/** Adds a download button that saves the code under this name. */
|
|
14
|
+
filename?: string | undefined;
|
|
15
|
+
/** Controls of the host's own, before download and copy. */
|
|
16
|
+
headerActions?: ReactNode;
|
|
17
|
+
/** The copy button; on by default. */
|
|
18
|
+
copy?: boolean | undefined;
|
|
19
|
+
/** Numbers the lines; on by default when there is more than one. */
|
|
20
|
+
lineNumbers?: boolean | undefined;
|
|
21
|
+
/** The first line's number, for an excerpt. */
|
|
22
|
+
startLine?: number | undefined;
|
|
23
|
+
/** Breaks long lines instead of scrolling them sideways. */
|
|
24
|
+
wrap?: boolean | undefined;
|
|
25
|
+
/** Grows to the height of its container and scrolls inside it, for a view
|
|
26
|
+
* that is the code, such as a tab. Without it the block is as tall as its
|
|
27
|
+
* code, up to `maxHeight`. */
|
|
28
|
+
fill?: boolean | undefined;
|
|
29
|
+
/** The tallest it grows before it scrolls, as a CSS length or in pixels. */
|
|
30
|
+
maxHeight?: number | string | undefined;
|
|
31
|
+
/** The code is still arriving: highlighting is not cached, and the block
|
|
32
|
+
* keeps its end in view while it is scrolled there. */
|
|
33
|
+
streaming?: boolean | undefined;
|
|
34
|
+
className?: string | undefined;
|
|
35
|
+
}
|
|
36
|
+
/** Code to read on a page, such as a workflow's YAML: highlighted in the
|
|
37
|
+
* theme's code colors, with numbered lines, a copy button and, given a
|
|
38
|
+
* `filename`, a download button. Markdown's fenced code renders as one, so
|
|
39
|
+
* both read the same. */
|
|
40
|
+
export declare function CodeBlock({ code, language, title, label, filename, headerActions, copy, lineNumbers, startLine, wrap, fill, maxHeight, streaming, className, }: CodeBlockProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { cx } from "../cx.js";
|
|
2
|
+
import { DownloadIcon } from "../icons.js";
|
|
3
|
+
import { useStrings } from "./Provider.js";
|
|
4
|
+
import { IconButton } from "./IconButton.js";
|
|
5
|
+
import { CopyButton } from "./CopyButton.js";
|
|
6
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/components/CodeBlock.tsx
|
|
9
|
+
function useHighlighted(source, language, streaming) {
|
|
10
|
+
const [state, setState] = useState(null);
|
|
11
|
+
const latest = useRef({
|
|
12
|
+
source,
|
|
13
|
+
language
|
|
14
|
+
});
|
|
15
|
+
latest.current = {
|
|
16
|
+
source,
|
|
17
|
+
language
|
|
18
|
+
};
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
let mounted = true;
|
|
21
|
+
const accept = (lines) => {
|
|
22
|
+
const now = latest.current;
|
|
23
|
+
if (mounted && now.language === language && now.source.startsWith(source)) setState({
|
|
24
|
+
source,
|
|
25
|
+
language,
|
|
26
|
+
lines
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
import("./codeHighlight.js").then(({ highlightCode }) => {
|
|
30
|
+
const ready = highlightCode(source, language, streaming, accept);
|
|
31
|
+
if (ready) accept(ready);
|
|
32
|
+
}).catch(() => {});
|
|
33
|
+
return () => {
|
|
34
|
+
mounted = false;
|
|
35
|
+
};
|
|
36
|
+
}, [
|
|
37
|
+
source,
|
|
38
|
+
language,
|
|
39
|
+
streaming
|
|
40
|
+
]);
|
|
41
|
+
return state;
|
|
42
|
+
}
|
|
43
|
+
function tokensFor(state, source, language, count) {
|
|
44
|
+
const out = new Array(count).fill(null);
|
|
45
|
+
if (!state || state.language !== language || !source.startsWith(state.source)) return out;
|
|
46
|
+
if (state.source === source) {
|
|
47
|
+
if (state.lines.length === count) return state.lines;
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
const settled = Math.min(state.source.split("\n").length - 1, state.lines.length, count);
|
|
51
|
+
for (let i = 0; i < settled; i++) out[i] = state.lines[i] ?? null;
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function isScrollable(el) {
|
|
55
|
+
return el.scrollHeight > el.clientHeight + 1 || el.scrollWidth > el.clientWidth + 1;
|
|
56
|
+
}
|
|
57
|
+
function downloadText(text, name) {
|
|
58
|
+
const url = URL.createObjectURL(new Blob([text], { type: "text/plain" }));
|
|
59
|
+
const a = document.createElement("a");
|
|
60
|
+
a.href = url;
|
|
61
|
+
a.download = name;
|
|
62
|
+
a.click();
|
|
63
|
+
setTimeout(() => URL.revokeObjectURL(url));
|
|
64
|
+
}
|
|
65
|
+
/** Code to read on a page, such as a workflow's YAML: highlighted in the
|
|
66
|
+
* theme's code colors, with numbered lines, a copy button and, given a
|
|
67
|
+
* `filename`, a download button. Markdown's fenced code renders as one, so
|
|
68
|
+
* both read the same. */
|
|
69
|
+
function CodeBlock({ code, language = "", title, label, filename, headerActions, copy = true, lineNumbers, startLine = 1, wrap = false, fill = false, maxHeight, streaming = false, className }) {
|
|
70
|
+
const { common: t } = useStrings();
|
|
71
|
+
const source = code.endsWith("\n") ? code.slice(0, -1) : code;
|
|
72
|
+
const lines = useMemo(() => source.split("\n"), [source]);
|
|
73
|
+
const tokens = tokensFor(useHighlighted(source, language, streaming), source, language, lines.length);
|
|
74
|
+
const numbered = lineNumbers ?? lines.length > 1;
|
|
75
|
+
const lastLine = startLine + lines.length - 1;
|
|
76
|
+
const body = useRef(null);
|
|
77
|
+
const [scrollable, setScrollable] = useState(false);
|
|
78
|
+
useLayoutEffect(() => {
|
|
79
|
+
const el = body.current;
|
|
80
|
+
if (!el) return;
|
|
81
|
+
setScrollable(isScrollable(el));
|
|
82
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
83
|
+
const observer = new ResizeObserver(() => setScrollable(isScrollable(el)));
|
|
84
|
+
observer.observe(el);
|
|
85
|
+
if (el.firstElementChild) observer.observe(el.firstElementChild);
|
|
86
|
+
return () => observer.disconnect();
|
|
87
|
+
}, []);
|
|
88
|
+
const atEnd = useRef(true);
|
|
89
|
+
useLayoutEffect(() => {
|
|
90
|
+
const el = body.current;
|
|
91
|
+
if (streaming && el && atEnd.current && source) el.scrollTop = el.scrollHeight;
|
|
92
|
+
}, [source, streaming]);
|
|
93
|
+
const name = label ?? (typeof title === "string" ? title : t.codeOf(language));
|
|
94
|
+
const heading = title ?? language;
|
|
95
|
+
const bodyStyle = {};
|
|
96
|
+
if (!fill && maxHeight !== void 0) bodyStyle.maxHeight = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
|
|
97
|
+
const codeStyle = {
|
|
98
|
+
counterReset: `line ${startLine - 1}`,
|
|
99
|
+
"--code-gutter": `max(1.5rem, ${String(lastLine).length}ch)`
|
|
100
|
+
};
|
|
101
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
102
|
+
"data-code-block": "",
|
|
103
|
+
className: cx("not-prose flex flex-col overflow-hidden rounded-xl bg-(--theme-code-bg) text-(--theme-code-text) shadow-[0_0_0_1px_color-mix(in_oklab,var(--theme-app)_8%,transparent)]", fill && "h-full min-h-0", className),
|
|
104
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
105
|
+
className: "flex h-9 shrink-0 items-center gap-2 pr-1.5 pl-4",
|
|
106
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
107
|
+
className: cx("min-w-0 flex-1 truncate", title === void 0 ? "text-xs text-(--theme-muted-text-color)" : "text-[13px] font-medium text-(--theme-app)"),
|
|
108
|
+
children: heading
|
|
109
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
110
|
+
className: "flex shrink-0 items-center gap-0.5",
|
|
111
|
+
children: [
|
|
112
|
+
headerActions,
|
|
113
|
+
filename && /* @__PURE__ */ jsx(IconButton, {
|
|
114
|
+
label: t.download,
|
|
115
|
+
icon: /* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
116
|
+
onClick: () => downloadText(code, filename)
|
|
117
|
+
}),
|
|
118
|
+
copy && /* @__PURE__ */ jsx(CopyButton, { text: code })
|
|
119
|
+
]
|
|
120
|
+
})]
|
|
121
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
122
|
+
ref: body,
|
|
123
|
+
dir: "ltr",
|
|
124
|
+
className: cx("overflow-auto focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--theme-focus)", fill && "min-h-0 flex-1"),
|
|
125
|
+
style: bodyStyle,
|
|
126
|
+
onScroll: (e) => {
|
|
127
|
+
const el = e.currentTarget;
|
|
128
|
+
atEnd.current = el.scrollTop + el.clientHeight >= el.scrollHeight - 4;
|
|
129
|
+
},
|
|
130
|
+
...scrollable ? {
|
|
131
|
+
tabIndex: 0,
|
|
132
|
+
role: "region",
|
|
133
|
+
"aria-label": name
|
|
134
|
+
} : {},
|
|
135
|
+
children: /* @__PURE__ */ jsx("pre", {
|
|
136
|
+
className: cx("m-0 px-4 pt-0.5 pb-3.5 font-mono text-sm leading-[1.6]", wrap ? "w-full" : "w-max min-w-full"),
|
|
137
|
+
children: /* @__PURE__ */ jsx("code", {
|
|
138
|
+
className: "block",
|
|
139
|
+
style: codeStyle,
|
|
140
|
+
children: lines.map((line, i) => /* @__PURE__ */ jsx("span", {
|
|
141
|
+
className: cx("flex min-h-[1lh] items-baseline", numbered && "before:mr-4 before:w-(--code-gutter) before:shrink-0 before:text-right before:font-mono before:text-[0.8125rem] before:text-(--theme-muted-text-color)/60 before:content-[counter(line)] before:[counter-increment:line]"),
|
|
142
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
143
|
+
className: cx("min-w-0", wrap ? "whitespace-pre-wrap break-words" : "whitespace-pre"),
|
|
144
|
+
children: tokens[i] ? tokens[i].map((token, j) => /* @__PURE__ */ jsx("span", {
|
|
145
|
+
style: token.color || token.fontStyle ? {
|
|
146
|
+
color: token.color,
|
|
147
|
+
fontStyle: token.fontStyle
|
|
148
|
+
} : void 0,
|
|
149
|
+
children: token.content
|
|
150
|
+
}, j)) : line
|
|
151
|
+
})
|
|
152
|
+
}, i))
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
})]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
export { CodeBlock };
|
|
@@ -40,12 +40,19 @@ export interface CopyCodeBlockProps {
|
|
|
40
40
|
revealed?: boolean | undefined;
|
|
41
41
|
defaultRevealed?: boolean | undefined;
|
|
42
42
|
onRevealedChange?: ((revealed: boolean) => void) | undefined;
|
|
43
|
-
/** Breaks a long value across lines instead of scrolling it, such as a token.
|
|
43
|
+
/** Breaks a long value across lines instead of scrolling it, such as a token in a block.
|
|
44
|
+
* Default false: an inline token, key or ID stays on one line and scrolls inside. */
|
|
44
45
|
wrap?: boolean | undefined;
|
|
46
|
+
/** A compact value in the line, such as a token shown once in a Callout: as wide as the
|
|
47
|
+
* value, up to its container, where a longer one scrolls sideways on its one line. A quiet
|
|
48
|
+
* surface reads on a panel and a tinted callout alike, and the buttons stay right after the
|
|
49
|
+
* value, always shown. The copy confirms with its check alone, as the change shows there. */
|
|
50
|
+
inline?: boolean | undefined;
|
|
45
51
|
className?: string | undefined;
|
|
46
52
|
}
|
|
47
53
|
/** A block of code, such as a command to run, with a button that copies it. The
|
|
48
54
|
* buttons show while the pointer is over the block or focus is in it, and always
|
|
49
|
-
* where there is no hover to reveal them. With `
|
|
50
|
-
* such as a
|
|
51
|
-
|
|
55
|
+
* where there is no hover to reveal them. With `inline` it is a compact value in the line
|
|
56
|
+
* instead, such as a token shown once, its buttons always beside it. With `secret` it
|
|
57
|
+
* masks the value until the eye shows it, and copies it either way. */
|
|
58
|
+
export declare function CopyCodeBlock({ text, label, children, secret, revealed: revealedProp, defaultRevealed, onRevealedChange, inline, wrap, className, }: CopyCodeBlockProps): import("react").JSX.Element;
|