@hraness/design-kit 0.35.0 → 0.35.1
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/AGENT_SETUP.md +4 -0
- package/ARTICLE_COPY.md +12 -6
- package/README.md +6 -2
- package/dist/{chunk-c9mhn3yb.js → chunk-6bfr0g23.js} +1 -1
- package/dist/{chunk-fjf81bww.js → chunk-6ts4955n.js} +8 -34
- package/dist/{chunk-8xfssb9c.js → chunk-tzd7177a.js} +138 -81
- package/dist/react/index.js +35 -95
- package/dist/react/platform-install.js +2 -2
- package/dist/react/server.js +2 -2
- package/dist/stylex-manifest.json +1 -1
- package/dist/stylex.css +28 -28
- package/package.json +3 -1
- package/portfolio-inventory.json +1 -1
- package/src/react/agent-setup-prompt.stylex.ts +5 -25
- package/src/react/agent-setup-prompt.tsx +9 -18
- package/src/react/design-gallery.tsx +1 -0
- package/src/react/platform-install.stylex.ts +7 -36
- package/src/react/platform-install.tsx +15 -23
- package/src/react/setup-copy-button.stylex.ts +39 -0
- package/src/react/setup-copy-button.tsx +20 -0
package/dist/stylex.css
CHANGED
|
@@ -712,10 +712,6 @@
|
|
|
712
712
|
padding-block: .25rem;
|
|
713
713
|
}
|
|
714
714
|
|
|
715
|
-
.x1267ecw {
|
|
716
|
-
padding-block: .35rem;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
715
|
.x1bilvtl {
|
|
720
716
|
padding-block: .375rem;
|
|
721
717
|
}
|
|
@@ -728,6 +724,10 @@
|
|
|
728
724
|
padding-block: .5rem;
|
|
729
725
|
}
|
|
730
726
|
|
|
727
|
+
.x8fiyw4 {
|
|
728
|
+
padding-block: .65rem;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
731
|
.xo0yzjp {
|
|
732
732
|
padding-block: .75rem;
|
|
733
733
|
}
|
|
@@ -816,10 +816,6 @@
|
|
|
816
816
|
padding-inline: .625rem;
|
|
817
817
|
}
|
|
818
818
|
|
|
819
|
-
.x1f6otoe {
|
|
820
|
-
padding-inline: .65rem;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
819
|
.x1ryrjj2 {
|
|
824
820
|
padding-inline: .75rem;
|
|
825
821
|
}
|
|
@@ -1283,10 +1279,6 @@
|
|
|
1283
1279
|
background-color: color-mix(in srgb,var(--foreground) 8%,transparent);
|
|
1284
1280
|
}
|
|
1285
1281
|
|
|
1286
|
-
.xf7cb96 {
|
|
1287
|
-
background-color: color-mix(in srgb,var(--hraness-marketing-ink, var(--foreground, CanvasText)) 4%,var(--hraness-marketing-surface, var(--surface, Canvas)));
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
1282
|
.xy1scu2 {
|
|
1291
1283
|
background-color: color-mix(in srgb,var(--hraness-marketing-ink, var(--foreground, CanvasText)) 5%,var(--hraness-marketing-background, var(--background, Canvas)));
|
|
1292
1284
|
}
|
|
@@ -2587,6 +2579,10 @@
|
|
|
2587
2579
|
grid-template-columns: repeat(auto-fit,minmax(min(100%,var(--_hraness-marketing-grid-track, 18rem)),1fr));
|
|
2588
2580
|
}
|
|
2589
2581
|
|
|
2582
|
+
.xe92ajh {
|
|
2583
|
+
grid-template-columns: repeat(var(--_hraness-agent-target-wide-columns, var(--_hraness-agent-target-columns, 1)),minmax(0,1fr));
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2590
2586
|
.x3g07o8 {
|
|
2591
2587
|
grid-template-columns: repeat(var(--hraness-marketing-fact-columns, 4),minmax(0,1fr));
|
|
2592
2588
|
}
|
|
@@ -3769,10 +3765,6 @@
|
|
|
3769
3765
|
background-color: color-mix(in srgb,var(--hraness-marketing-ink, var(--foreground, CanvasText)) 6%,transparent);
|
|
3770
3766
|
}
|
|
3771
3767
|
|
|
3772
|
-
.x3oli7v:hover {
|
|
3773
|
-
background-color: color-mix(in srgb,var(--hraness-marketing-ink, var(--foreground, CanvasText)) 9%,var(--hraness-marketing-surface, var(--surface, Canvas)));
|
|
3774
|
-
}
|
|
3775
|
-
|
|
3776
3768
|
.xjoriyk:hover {
|
|
3777
3769
|
background-color: color-mix(in srgb,var(--hraness-marketing-related-tone, var(--hraness-marketing-ink)) 6%,transparent);
|
|
3778
3770
|
}
|
|
@@ -4376,6 +4368,10 @@
|
|
|
4376
4368
|
border-right-width: 2px;
|
|
4377
4369
|
}
|
|
4378
4370
|
|
|
4371
|
+
.x1h3am7x {
|
|
4372
|
+
border-top-color: color-mix(in srgb,var(--hraness-marketing-ink, var(--foreground, CanvasText)) 12%,transparent);
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4379
4375
|
.x1v8p93f {
|
|
4380
4376
|
border-top-color: #0000;
|
|
4381
4377
|
}
|
|
@@ -4392,6 +4388,10 @@
|
|
|
4392
4388
|
border-top-style: solid;
|
|
4393
4389
|
}
|
|
4394
4390
|
|
|
4391
|
+
.x178xt8z {
|
|
4392
|
+
border-top-width: 1px;
|
|
4393
|
+
}
|
|
4394
|
+
|
|
4395
4395
|
.xamhcws {
|
|
4396
4396
|
border-top-width: 2px;
|
|
4397
4397
|
}
|
|
@@ -4404,10 +4404,6 @@
|
|
|
4404
4404
|
bottom: 0;
|
|
4405
4405
|
}
|
|
4406
4406
|
|
|
4407
|
-
.x1at03k5 {
|
|
4408
|
-
height: .95rem;
|
|
4409
|
-
}
|
|
4410
|
-
|
|
4411
4407
|
.xqtp20y {
|
|
4412
4408
|
height: 0;
|
|
4413
4409
|
}
|
|
@@ -4540,10 +4536,6 @@
|
|
|
4540
4536
|
min-height: 250px;
|
|
4541
4537
|
}
|
|
4542
4538
|
|
|
4543
|
-
.x1in32aq {
|
|
4544
|
-
min-height: 2rem;
|
|
4545
|
-
}
|
|
4546
|
-
|
|
4547
4539
|
.x13gjtz1 {
|
|
4548
4540
|
min-height: 3.5rem;
|
|
4549
4541
|
}
|
|
@@ -4628,10 +4620,6 @@
|
|
|
4628
4620
|
top: var(--hraness-design-procedural-ripple-y);
|
|
4629
4621
|
}
|
|
4630
4622
|
|
|
4631
|
-
.x2tfi8i {
|
|
4632
|
-
width: .95rem;
|
|
4633
|
-
}
|
|
4634
|
-
|
|
4635
4623
|
.xnalus7 {
|
|
4636
4624
|
width: 0;
|
|
4637
4625
|
}
|
|
@@ -5863,6 +5851,18 @@
|
|
|
5863
5851
|
}
|
|
5864
5852
|
}
|
|
5865
5853
|
|
|
5854
|
+
@container hraness-agent-setup (width >= 20rem) {
|
|
5855
|
+
.x6fuqh4.x6fuqh4 {
|
|
5856
|
+
--_hraness-agent-target-columns: 2;
|
|
5857
|
+
}
|
|
5858
|
+
}
|
|
5859
|
+
|
|
5860
|
+
@container hraness-agent-setup (width >= 34rem) {
|
|
5861
|
+
.x1ii36b5.x1ii36b5 {
|
|
5862
|
+
--_hraness-agent-target-wide-columns: 3;
|
|
5863
|
+
}
|
|
5864
|
+
}
|
|
5865
|
+
|
|
5866
5866
|
@container hraness-platform-install (width <= 30rem) {
|
|
5867
5867
|
.x1rp0mr4.x1rp0mr4 {
|
|
5868
5868
|
--_hraness-platform-install-narrow-tab-font-size: .8125rem;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hraness/design-kit",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"description": "React components and CSS for Hraness apps and product sites: application shells, marketing sections, charts, themes, effects, and syntax highlighting. Built on @hraness/ui.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -300,6 +300,8 @@
|
|
|
300
300
|
"src/agent-setup.ts",
|
|
301
301
|
"src/react/agent-setup-prompt.tsx",
|
|
302
302
|
"src/react/agent-setup-prompt.stylex.ts",
|
|
303
|
+
"src/react/setup-copy-button.tsx",
|
|
304
|
+
"src/react/setup-copy-button.stylex.ts",
|
|
303
305
|
"AGENT_SETUP.md",
|
|
304
306
|
"src/react/marketing-comparison.tsx",
|
|
305
307
|
"src/react/marketing-comparison.stylex.ts",
|
package/portfolio-inventory.json
CHANGED
|
@@ -89,39 +89,19 @@ export const agentSetupStyles = stylex.create({
|
|
|
89
89
|
":hover": { color: ink },
|
|
90
90
|
"::marker": { content: '""' },
|
|
91
91
|
},
|
|
92
|
-
copy: {
|
|
93
|
-
alignItems: "center",
|
|
94
|
-
appearance: "none",
|
|
95
|
-
backgroundColor: { default: background, ":hover": surface, [forced]: "ButtonFace" },
|
|
96
|
-
borderColor: { default: line, [forced]: "ButtonText" },
|
|
97
|
-
borderRadius: "0.5rem",
|
|
98
|
-
borderStyle: "solid",
|
|
99
|
-
borderWidth: "1px",
|
|
100
|
-
boxSizing: "border-box",
|
|
101
|
-
color: { default: ink, [forced]: "ButtonText" },
|
|
102
|
-
cursor: "pointer",
|
|
103
|
-
display: { default: "inline-flex", [noScript]: "none" },
|
|
104
|
-
flexShrink: 0,
|
|
105
|
-
fontFamily: "inherit",
|
|
106
|
-
fontSize: "0.8125rem",
|
|
107
|
-
gap: "0.4rem",
|
|
108
|
-
justifyContent: "center",
|
|
109
|
-
lineHeight: 1,
|
|
110
|
-
margin: 0,
|
|
111
|
-
minBlockSize: "2.75rem",
|
|
112
|
-
outline: { default: "none", ":focus-visible": `2px solid ${focus}` },
|
|
113
|
-
outlineOffset: "2px",
|
|
114
|
-
padding: "0.65rem 0.8rem",
|
|
115
|
-
},
|
|
116
92
|
copyOverlay: {
|
|
117
93
|
insetBlockEnd: "0.5rem",
|
|
118
94
|
insetInlineEnd: "0.5rem",
|
|
119
95
|
position: "absolute",
|
|
120
96
|
},
|
|
121
|
-
copyIcon: { blockSize: "1rem", flexShrink: 0, inlineSize: "1rem" },
|
|
122
97
|
targets: { display: "grid", gap: "0.6rem", minInlineSize: 0 },
|
|
123
98
|
targetsLabel: { color: { default: muted, [forced]: "CanvasText" }, fontSize: "0.8125rem", fontWeight: 500, margin: 0 },
|
|
124
99
|
targetList: { display: "grid", gap: "0.45rem", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 10rem), 1fr))", listStyle: "none", margin: 0, padding: 0 },
|
|
100
|
+
targetGrid: {
|
|
101
|
+
gridTemplateColumns: "repeat(var(--_hraness-agent-target-wide-columns, var(--_hraness-agent-target-columns, 1)), minmax(0, 1fr))",
|
|
102
|
+
"--_hraness-agent-target-columns": { default: null, "@container hraness-agent-setup (min-width: 20rem)": "2" },
|
|
103
|
+
"--_hraness-agent-target-wide-columns": { default: null, "@container hraness-agent-setup (min-width: 34rem)": "3" },
|
|
104
|
+
},
|
|
125
105
|
target: {
|
|
126
106
|
alignItems: "center",
|
|
127
107
|
backgroundColor: { default: surface, ":hover": background, [forced]: "ButtonFace" },
|
|
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useId, useRef, useState, type KeyboardEvent, ty
|
|
|
5
5
|
import type { ProviderMarkDescriptor } from "../provider-marks.js";
|
|
6
6
|
import { agentSetupClassName as cx } from "./agent-setup-prompt.stylex.js";
|
|
7
7
|
import { ProviderMark } from "./provider-mark.js";
|
|
8
|
+
import { SetupCopyButton } from "./setup-copy-button.js";
|
|
8
9
|
import { SyntaxCode } from "./syntax-code.js";
|
|
9
10
|
|
|
10
11
|
export interface AgentSetupTarget {
|
|
@@ -20,6 +21,8 @@ export interface AgentSetupPromptProps {
|
|
|
20
21
|
readonly prompt: string;
|
|
21
22
|
readonly label?: string;
|
|
22
23
|
readonly targets?: readonly AgentSetupTarget[];
|
|
24
|
+
/** Keep provider actions below the prompt, or place them beside it in wide containers. */
|
|
25
|
+
readonly targetsPlacement?: "responsive" | "below";
|
|
23
26
|
readonly className?: string;
|
|
24
27
|
/** Runs once after a successful copy; callback failures do not change copy feedback. */
|
|
25
28
|
readonly onCopied?: () => void;
|
|
@@ -186,21 +189,8 @@ function useCopy(text: string, subject: string, fallback: () => HTMLElement | nu
|
|
|
186
189
|
return { state, message, copy };
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
function CopyGlyph({ copied }: Readonly<{ copied: boolean }>) {
|
|
190
|
-
return (
|
|
191
|
-
<svg aria-hidden="true" className={cx(["copyIcon"])} fill="none" focusable="false" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} viewBox="0 0 24 24">
|
|
192
|
-
{copied ? <path d="M5 12.5l4.5 4.5L19 7.5" /> : <><rect height="12" rx="2" width="12" x="8" y="8" /><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2" /></>}
|
|
193
|
-
</svg>
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
192
|
function CopyButton({ copy, overlay = false, state, subject }: Readonly<{ copy: () => Promise<boolean>; overlay?: boolean; state: CopyState; subject: string }>) {
|
|
198
|
-
return (
|
|
199
|
-
<button aria-busy={state === "copying" || undefined} aria-label={`${state === "copied" ? "Copied" : "Copy"} ${subject}`} className={cx(["copy", overlay && "copyOverlay"])} data-copy-state={state} disabled={state === "copying"} onClick={() => { void copy(); }} type="button">
|
|
200
|
-
<CopyGlyph copied={state === "copied"} />
|
|
201
|
-
<span>{state === "copied" ? "Copied" : state === "copying" ? "Copying" : state === "failed" ? "Copy failed" : "Copy"}</span>
|
|
202
|
-
</button>
|
|
203
|
-
);
|
|
193
|
+
return <SetupCopyButton className={cx([overlay && "copyOverlay"], "hraness-agent-setup__copy")} iconClassName="hraness-agent-setup__copyIcon" onCopy={() => { void copy(); }} state={state} subject={subject} />;
|
|
204
194
|
}
|
|
205
195
|
|
|
206
196
|
function CopyStatus({ children }: Readonly<{ children: ReactNode }>) {
|
|
@@ -231,10 +221,11 @@ function reserveTargetTab(owner: Window | null): Window | null {
|
|
|
231
221
|
}
|
|
232
222
|
|
|
233
223
|
/** A quiet prompt preview with the complete source behind a native disclosure. */
|
|
234
|
-
export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [], className, onCopied }: AgentSetupPromptProps) {
|
|
224
|
+
export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [], targetsPlacement = "responsive", className, onCopied }: AgentSetupPromptProps) {
|
|
235
225
|
assertText(prompt, "Agent setup prompt");
|
|
236
226
|
assertText(label, "Agent setup label");
|
|
237
227
|
assertEntries(targets, "Agent setup target");
|
|
228
|
+
if (targetsPlacement !== "responsive" && targetsPlacement !== "below") throw new RangeError("Unsupported agent setup targets placement.");
|
|
238
229
|
for (const target of targets) {
|
|
239
230
|
assertHref(target.href);
|
|
240
231
|
if (target.mode !== undefined && target.mode !== "prefill" && target.mode !== "copy-and-open") throw new RangeError("Unsupported agent setup target mode.");
|
|
@@ -313,8 +304,8 @@ export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [],
|
|
|
313
304
|
}, []);
|
|
314
305
|
|
|
315
306
|
return (
|
|
316
|
-
<section aria-label={label} className={cx(["root"], className)} data-hraness-agent-setup-prompt="">
|
|
317
|
-
<div className={cx(["layout", targets.length > 0 && "withTargets"])}>
|
|
307
|
+
<section aria-label={label} className={cx(["root"], className)} data-hraness-agent-setup-prompt="" data-targets-placement={targetsPlacement}>
|
|
308
|
+
<div className={cx(["layout", targets.length > 0 && targetsPlacement === "responsive" && "withTargets"])}>
|
|
318
309
|
<div className={cx(["frame"])} data-copy-state={state}>
|
|
319
310
|
<div className={cx(["preview"])} hidden={expanded}>
|
|
320
311
|
<pre aria-label={`${label} preview`} className={cx(["pre", "previewText"])}>{prompt}</pre>
|
|
@@ -330,7 +321,7 @@ export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [],
|
|
|
330
321
|
{targets.length === 0 ? null : (
|
|
331
322
|
<aside aria-labelledby={`${id}-targets`} className={cx(["targets"])}>
|
|
332
323
|
<p className={cx(["targetsLabel"])} id={`${id}-targets`}>Open in</p>
|
|
333
|
-
<ul className={cx(["targetList"])}>
|
|
324
|
+
<ul className={cx(["targetList", targetsPlacement === "below" && "targetGrid"])}>
|
|
334
325
|
{targets.map((target) => (
|
|
335
326
|
<li key={target.id}>
|
|
336
327
|
<a aria-busy={target.mode === "copy-and-open" && state === "copying" || undefined} aria-disabled={target.mode === "copy-and-open" && state === "copying" || undefined} aria-label={target.mode === "copy-and-open" ? `Copy prompt and open ${target.label}` : undefined} className={cx(["target"])} data-agent-target={target.id} data-agent-target-mode={target.mode ?? "prefill"} href={target.href} onAuxClick={target.mode === "copy-and-open" ? (event) => { copyAndOpen(event, target); } : undefined} onClick={target.mode === "copy-and-open" ? (event) => { copyAndOpen(event, target); } : undefined} rel="noopener noreferrer" target="_blank">
|
|
@@ -401,6 +401,7 @@ export function DesignSystemGallery({
|
|
|
401
401
|
<MarketingSection heading="Ask your agent to set it up." headingId="gallery-agent-setup" headingLevel={3} label="Agent setup">
|
|
402
402
|
<AgentSetupPrompt
|
|
403
403
|
label="Relay agent setup"
|
|
404
|
+
targetsPlacement="below"
|
|
404
405
|
prompt={"Install Relay from https://relay.example/install.sh.\nRead the project instructions before making changes.\nRun relay init in this workspace and create a job named sample.\nRun the job, then show me its log."}
|
|
405
406
|
targets={[
|
|
406
407
|
{ href: "https://claude.ai/new", id: "claude", label: "Claude", mark: "claudecode", mode: "copy-and-open" },
|
|
@@ -221,40 +221,6 @@ export const platformInstallStyles = stylex.create({
|
|
|
221
221
|
fontSize: "inherit",
|
|
222
222
|
padding: 0,
|
|
223
223
|
},
|
|
224
|
-
copy: {
|
|
225
|
-
alignItems: "center",
|
|
226
|
-
appearance: "none",
|
|
227
|
-
backgroundColor: {
|
|
228
|
-
default: `color-mix(in srgb, ${ink} 4%, ${surface})`,
|
|
229
|
-
":hover": `color-mix(in srgb, ${ink} 9%, ${surface})`,
|
|
230
|
-
[forced]: "ButtonFace",
|
|
231
|
-
},
|
|
232
|
-
borderColor: { default: line, [forced]: "ButtonText" },
|
|
233
|
-
borderRadius: "0.5rem",
|
|
234
|
-
borderStyle: "solid",
|
|
235
|
-
borderWidth: "1px",
|
|
236
|
-
boxSizing: "border-box",
|
|
237
|
-
color: { default: ink, [forced]: "ButtonText" },
|
|
238
|
-
cursor: "pointer",
|
|
239
|
-
display: { default: "inline-flex", [noScript]: "none" },
|
|
240
|
-
flexShrink: 0,
|
|
241
|
-
fontFamily: textFont,
|
|
242
|
-
fontSize: "0.8125rem",
|
|
243
|
-
fontWeight: 500,
|
|
244
|
-
gap: "0.35rem",
|
|
245
|
-
lineHeight: 1,
|
|
246
|
-
margin: 0,
|
|
247
|
-
minBlockSize: { default: "2rem", "@media (pointer: coarse)": "2.75rem" },
|
|
248
|
-
outline: { default: "none", ":focus-visible": `2px solid ${focusRing}` },
|
|
249
|
-
outlineOffset: "1px",
|
|
250
|
-
paddingBlock: "0.35rem",
|
|
251
|
-
paddingInline: "0.65rem",
|
|
252
|
-
},
|
|
253
|
-
copyIcon: {
|
|
254
|
-
blockSize: "0.95rem",
|
|
255
|
-
flexShrink: 0,
|
|
256
|
-
inlineSize: "0.95rem",
|
|
257
|
-
},
|
|
258
224
|
alternatives: {
|
|
259
225
|
display: "grid",
|
|
260
226
|
gap: "0.6rem",
|
|
@@ -269,6 +235,13 @@ export const platformInstallStyles = stylex.create({
|
|
|
269
235
|
lineHeight: 1.5,
|
|
270
236
|
margin: 0,
|
|
271
237
|
},
|
|
238
|
+
commandNote: {
|
|
239
|
+
borderBlockStartColor: { default: line, [forced]: "CanvasText" },
|
|
240
|
+
borderBlockStartStyle: "solid",
|
|
241
|
+
borderBlockStartWidth: "1px",
|
|
242
|
+
paddingBlock: "0.65rem",
|
|
243
|
+
paddingInline: "0.95rem",
|
|
244
|
+
},
|
|
272
245
|
unavailable: {
|
|
273
246
|
borderColor: { default: line, [forced]: "CanvasText" },
|
|
274
247
|
borderRadius: "0.75rem",
|
|
@@ -359,8 +332,6 @@ const hooks: Partial<Record<Part, string>> = {
|
|
|
359
332
|
code: "hraness-platform-install__code",
|
|
360
333
|
command: "hraness-platform-install__command",
|
|
361
334
|
commandBar: "hraness-platform-install__command-bar",
|
|
362
|
-
copy: "hraness-platform-install__copy",
|
|
363
|
-
copyIcon: "hraness-platform-install__copy-icon",
|
|
364
335
|
icon: "hraness-platform-icon",
|
|
365
336
|
markSymbols: "hraness-platform-install__marks",
|
|
366
337
|
note: "hraness-platform-install__note",
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
platformMark,
|
|
12
12
|
type PlatformId,
|
|
13
13
|
} from "../platforms.js";
|
|
14
|
+
import { SetupCopyButton } from "./setup-copy-button.js";
|
|
14
15
|
import { SyntaxCode } from "./syntax-code.js";
|
|
15
16
|
import { platformInstallClassName as cx } from "./platform-install.stylex.js";
|
|
16
17
|
|
|
@@ -29,7 +30,7 @@ interface PlatformInstallTargetBase {
|
|
|
29
30
|
readonly label?: string;
|
|
30
31
|
/** Where the command runs, such as "Terminal" or "PowerShell". */
|
|
31
32
|
readonly shell?: string;
|
|
32
|
-
/** A short qualifier
|
|
33
|
+
/** A short qualifier in the primary command footer, such as "Apple silicon" or "Requires Bun 1.3.14+". */
|
|
33
34
|
readonly note?: ReactNode;
|
|
34
35
|
readonly alternatives?: readonly PlatformInstallAlternative[];
|
|
35
36
|
}
|
|
@@ -167,22 +168,12 @@ function PlatformMarkUse({ platform, symbolId }: Readonly<{ platform: PlatformId
|
|
|
167
168
|
);
|
|
168
169
|
}
|
|
169
170
|
|
|
170
|
-
function CopyGlyph({ copied }: Readonly<{ copied: boolean }>) {
|
|
171
|
-
return (
|
|
172
|
-
<svg aria-hidden="true" className={cx(["copyIcon"])} fill="none" focusable="false" stroke="currentColor"
|
|
173
|
-
strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} viewBox="0 0 24 24">
|
|
174
|
-
{copied
|
|
175
|
-
? <path d="M5 12.5l4.5 4.5L19 7.5" />
|
|
176
|
-
: <><rect height="12" rx="2" width="12" x="8" y="8" /><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2" /></>}
|
|
177
|
-
</svg>
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
171
|
function CommandBlock({
|
|
182
172
|
caption,
|
|
183
173
|
command,
|
|
184
174
|
copyKey,
|
|
185
175
|
onCopy,
|
|
176
|
+
note,
|
|
186
177
|
status,
|
|
187
178
|
subject,
|
|
188
179
|
}: Readonly<{
|
|
@@ -190,6 +181,7 @@ function CommandBlock({
|
|
|
190
181
|
command: string;
|
|
191
182
|
copyKey: string;
|
|
192
183
|
onCopy: (key: string, command: string, subject: string, pre: HTMLElement | null) => void;
|
|
184
|
+
note?: ReactNode;
|
|
193
185
|
status: CopyStatus | null;
|
|
194
186
|
subject: string;
|
|
195
187
|
}>) {
|
|
@@ -199,21 +191,20 @@ function CommandBlock({
|
|
|
199
191
|
<div className={cx(["command"])} data-copy-state={state}>
|
|
200
192
|
<div className={cx(["commandBar", (caption === undefined || caption === "") && "commandBarEmpty"])}>
|
|
201
193
|
<span className={cx(["shell"])}>{caption}</span>
|
|
202
|
-
<
|
|
203
|
-
className=
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
<span className={cx(["status"])}> {subject}</span>
|
|
211
|
-
</button>
|
|
194
|
+
<SetupCopyButton
|
|
195
|
+
className="hraness-platform-install__copy"
|
|
196
|
+
iconClassName="hraness-platform-install__copy-icon"
|
|
197
|
+
failedLabel="Select to copy"
|
|
198
|
+
onCopy={() => onCopy(copyKey, command, subject, preRef.current)}
|
|
199
|
+
state={state}
|
|
200
|
+
subject={subject}
|
|
201
|
+
/>
|
|
212
202
|
</div>
|
|
213
203
|
{/* Focusable so keyboard users can scroll a long command sideways. */}
|
|
214
204
|
<pre aria-label={subject} className={cx(["pre"])} ref={preRef} tabIndex={0}>
|
|
215
205
|
<SyntaxCode className={cx(["code"])} code={command} language="shell" styles="classes" />
|
|
216
206
|
</pre>
|
|
207
|
+
{note === undefined ? null : <div className={cx(["note", "commandNote"])}>{note}</div>}
|
|
217
208
|
</div>
|
|
218
209
|
);
|
|
219
210
|
}
|
|
@@ -357,6 +348,7 @@ export function PlatformInstall({
|
|
|
357
348
|
command={target.command}
|
|
358
349
|
copyKey={`${target.id}:primary`}
|
|
359
350
|
onCopy={copy}
|
|
351
|
+
note={target.note}
|
|
360
352
|
status={status}
|
|
361
353
|
subject={`${name} install command`}
|
|
362
354
|
/>
|
|
@@ -377,7 +369,7 @@ export function PlatformInstall({
|
|
|
377
369
|
))}
|
|
378
370
|
</ul>
|
|
379
371
|
)}
|
|
380
|
-
{target.note === undefined ? null : <div className={cx(["note"])}>{target.note}</div>}
|
|
372
|
+
{target.command !== undefined || target.note === undefined ? null : <div className={cx(["note"])}>{target.note}</div>}
|
|
381
373
|
</div>
|
|
382
374
|
</div>
|
|
383
375
|
);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as stylex from "@stylexjs/stylex";
|
|
2
|
+
|
|
3
|
+
const ink = "var(--foreground, CanvasText)";
|
|
4
|
+
const surface = "var(--surface, Canvas)";
|
|
5
|
+
const background = "var(--background, Canvas)";
|
|
6
|
+
const forced = "@media (forced-colors: active)";
|
|
7
|
+
|
|
8
|
+
export const setupCopyButtonStyles = stylex.create({
|
|
9
|
+
button: {
|
|
10
|
+
alignItems: "center",
|
|
11
|
+
appearance: "none",
|
|
12
|
+
backgroundColor: { default: background, ":hover": surface, [forced]: "ButtonFace" },
|
|
13
|
+
borderColor: { default: `color-mix(in srgb, ${ink} 15%, transparent)`, [forced]: "ButtonText" },
|
|
14
|
+
borderRadius: "0.5rem",
|
|
15
|
+
borderStyle: "solid",
|
|
16
|
+
borderWidth: "1px",
|
|
17
|
+
boxSizing: "border-box",
|
|
18
|
+
color: { default: ink, [forced]: "ButtonText" },
|
|
19
|
+
cursor: "pointer",
|
|
20
|
+
display: { default: "inline-flex", "@media (scripting: none)": "none" },
|
|
21
|
+
flexShrink: 0,
|
|
22
|
+
fontFamily: "var(--font-text, ui-sans-serif, system-ui, sans-serif)",
|
|
23
|
+
fontSize: "0.8125rem",
|
|
24
|
+
fontWeight: 500,
|
|
25
|
+
gap: "0.4rem",
|
|
26
|
+
justifyContent: "center",
|
|
27
|
+
lineHeight: 1,
|
|
28
|
+
margin: 0,
|
|
29
|
+
minBlockSize: "2.75rem",
|
|
30
|
+
outline: { default: "none", ":focus-visible": "2px solid var(--focus, Highlight)" },
|
|
31
|
+
outlineOffset: "2px",
|
|
32
|
+
padding: "0.65rem 0.8rem",
|
|
33
|
+
},
|
|
34
|
+
icon: { blockSize: "1rem", flexShrink: 0, inlineSize: "1rem" },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export function setupCopyButtonClassName(part: keyof typeof setupCopyButtonStyles, caller?: string): string {
|
|
38
|
+
return [`hraness-setup-copy__${part}`, stylex.props(setupCopyButtonStyles[part]).className, caller].filter(Boolean).join(" ");
|
|
39
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { setupCopyButtonClassName as cx } from "./setup-copy-button.stylex.js";
|
|
2
|
+
|
|
3
|
+
/** Presentation only: each setup composition retains its clipboard and recovery behavior. */
|
|
4
|
+
export function SetupCopyButton({ className, iconClassName, onCopy, state, subject, failedLabel = "Copy failed" }: Readonly<{
|
|
5
|
+
className?: string;
|
|
6
|
+
iconClassName?: string;
|
|
7
|
+
onCopy: () => void;
|
|
8
|
+
state: "idle" | "copying" | "copied" | "failed";
|
|
9
|
+
subject: string;
|
|
10
|
+
failedLabel?: string;
|
|
11
|
+
}>) {
|
|
12
|
+
return (
|
|
13
|
+
<button aria-busy={state === "copying" || undefined} aria-label={`${state === "copied" ? "Copied" : "Copy"} ${subject}`} className={cx("button", className)} data-copy-state={state} disabled={state === "copying"} onClick={onCopy} type="button">
|
|
14
|
+
<svg aria-hidden="true" className={cx("icon", iconClassName)} fill="none" focusable="false" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} viewBox="0 0 24 24">
|
|
15
|
+
{state === "copied" ? <path d="M5 12.5l4.5 4.5L19 7.5" /> : <><rect height="12" rx="2" width="12" x="8" y="8" /><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2" /></>}
|
|
16
|
+
</svg>
|
|
17
|
+
<span>{state === "copied" ? "Copied" : state === "copying" ? "Copying" : state === "failed" ? failedLabel : "Copy"}</span>
|
|
18
|
+
</button>
|
|
19
|
+
);
|
|
20
|
+
}
|