@hraness/design-kit 0.35.0 → 0.35.2

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.
@@ -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 under the command, such as "Apple silicon" or "x86_64 and ARM64, glibc 2.34+". */
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
- <button
203
- className={cx(["copy"])}
204
- data-copy-state={state}
205
- onClick={() => onCopy(copyKey, command, subject, preRef.current)}
206
- type="button"
207
- >
208
- <CopyGlyph copied={state === "copied"} />
209
- <span>{state === "copied" ? "Copied" : state === "failed" ? "Select to copy" : "Copy"}</span>
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
+ }