@hraness/design-kit 0.36.5 → 0.39.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/MARKETING_COPY.md +2 -0
- package/README.md +77 -11
- package/dist/{chunk-xa28y4fg.js → chunk-1dgm85ta.js} +191 -55
- package/dist/chunk-6mx403v5.js +286 -0
- package/dist/{chunk-thv5t11w.js → chunk-dc8v05zh.js} +6 -137
- package/dist/{chunk-evb02bc1.js → chunk-fcs5qekr.js} +48 -1
- package/dist/chunk-fxwed3e5.js +775 -0
- package/dist/chunk-vy0pt84a.js +1807 -0
- package/dist/{chunk-sabcr66p.js → chunk-w1y1m3m7.js} +300 -1
- package/dist/index.js +24 -12
- package/dist/mockups/client.js +6 -654
- package/dist/mockups/index.js +29 -1797
- package/dist/provider-marks.js +1 -1
- package/dist/react/index.js +190 -5
- package/dist/react/server.js +5 -3
- package/dist/stylex-manifest.json +1 -1
- package/package.json +9 -5
- package/portfolio-inventory.json +1 -1
- package/src/compiler-foundation.css +2 -0
- package/src/design-gallery.css +12 -0
- package/src/index.ts +1 -0
- package/src/marketing-marquee.css +281 -0
- package/src/marketing-marquee.ts +206 -0
- package/src/mockups/client.tsx +157 -66
- package/src/mockups/core.tsx +30 -0
- package/src/mockups.css +158 -23
- package/src/provider-marks.generated.ts +92 -0
- package/src/provider-marks.ts +29 -4
- package/src/react/design-gallery.tsx +53 -0
- package/src/react/index.ts +1 -0
- package/src/react/marketing-marquee.tsx +71 -0
- package/src/react/server.ts +1 -0
- package/src/styles.css +2 -0
- package/vendor/provider-marks/BOOTSTRAP-ICONS-LICENSE +21 -0
- package/vendor/provider-marks/UPSTREAM.md +28 -0
- package/vendor/provider-marks/bootstrap-icons/calendar-event-fill.svg +3 -0
- package/vendor/provider-marks/bootstrap-icons/filetype-csv.svg +3 -0
- package/vendor/provider-marks/bootstrap-icons/globe2.svg +3 -0
- package/vendor/provider-marks/bootstrap-icons/linkedin.svg +3 -0
- package/vendor/provider-marks/bootstrap-icons/microsoft.svg +3 -0
- package/vendor/provider-marks/bootstrap-icons/person-vcard-fill.svg +3 -0
- package/vendor/provider-marks/bootstrap-icons/shop.svg +3 -0
- package/vendor/provider-marks/simple-icons/apple.svg +1 -0
- package/vendor/provider-marks/simple-icons/bluesky.svg +1 -0
- package/vendor/provider-marks/simple-icons/facebook.svg +1 -0
- package/vendor/provider-marks/simple-icons/github.svg +1 -0
- package/vendor/provider-marks/simple-icons/gmail.svg +1 -0
- package/vendor/provider-marks/simple-icons/google.svg +1 -0
- package/vendor/provider-marks/simple-icons/instagram.svg +1 -0
- package/vendor/provider-marks/simple-icons/reddit.svg +1 -0
- package/vendor/provider-marks/simple-icons/substack.svg +1 -0
- package/vendor/provider-marks/simple-icons/telegram.svg +1 -0
- package/vendor/provider-marks/simple-icons/threads.svg +1 -0
- package/vendor/provider-marks/simple-icons/tiktok.svg +1 -0
- package/vendor/provider-marks/simple-icons/twitch.svg +1 -0
- package/vendor/provider-marks/simple-icons/x.svg +1 -0
- package/vendor/provider-marks/simple-icons/ycombinator.svg +1 -0
- package/vendor/provider-marks/simple-icons/youtube.svg +1 -0
- package/dist/chunk-gmea6p0d.js +0 -13
package/src/mockups/client.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
useMemo,
|
|
10
10
|
useRef,
|
|
11
11
|
useState,
|
|
12
|
+
version,
|
|
12
13
|
} from "react";
|
|
13
14
|
|
|
14
15
|
import { joinMockupClasses, type MockupTheme } from "./core.js";
|
|
@@ -23,6 +24,18 @@ import { joinMockupClasses, type MockupTheme } from "./core.js";
|
|
|
23
24
|
// measure only in the browser.
|
|
24
25
|
const useIsomorphicLayoutEffect: typeof useLayoutEffect = typeof document === "undefined" ? () => undefined : useLayoutEffect;
|
|
25
26
|
|
|
27
|
+
// React 18 drops unknown boolean attributes; React 19 recognizes inert as
|
|
28
|
+
// boolean. Keep the actual HTML attribute in both supported runtimes. The
|
|
29
|
+
// record describes compatibility attributes without claiming React 18's
|
|
30
|
+
// HTMLAttributes (or React 19's boolean-only inert prop) accepts both forms.
|
|
31
|
+
const inertAttribute: Readonly<Record<string, true | "">> = {
|
|
32
|
+
inert: Number.parseInt(version, 10) >= 19 ? true : "",
|
|
33
|
+
};
|
|
34
|
+
const noInertAttribute: Readonly<Record<string, true | "">> = {};
|
|
35
|
+
function inertAttributes(inactive: boolean): Readonly<Record<string, true | "">> {
|
|
36
|
+
return inactive ? inertAttribute : noInertAttribute;
|
|
37
|
+
}
|
|
38
|
+
|
|
26
39
|
function assertUniqueIds(items: readonly Readonly<{ id: string }>[], what: string): void {
|
|
27
40
|
if (items.length === 0) throw new RangeError(`${what} needs at least one entry.`);
|
|
28
41
|
const seen = new Set<string>();
|
|
@@ -128,6 +141,8 @@ export type ShowcaseState<M extends string, O extends string = string> = Readonl
|
|
|
128
141
|
export type ShowcaseSurface<S extends string, M extends string, O extends string = string> = Readonly<{
|
|
129
142
|
id: S;
|
|
130
143
|
label: string;
|
|
144
|
+
/** Optional explanation of what this surface illustrates. */
|
|
145
|
+
hint?: string;
|
|
131
146
|
render: (state: ShowcaseState<M, O>) => ReactNode;
|
|
132
147
|
}>;
|
|
133
148
|
|
|
@@ -208,7 +223,7 @@ export function ModeShowcase<S extends string, M extends string, O extends strin
|
|
|
208
223
|
// Pure, nonanimated authored states reserve one maximum before any choice.
|
|
209
224
|
// They stay hidden and inert; only a detached measurement clone reveals them.
|
|
210
225
|
const measurements = useMemo(() => fit !== "fill" ? null : surfaces.flatMap((entry) => modes.flatMap((choice) => (options ?? [{ id: undefined }]).map((optionChoice) => (
|
|
211
|
-
<div aria-hidden="true" className="hkm-mode-surface" data-hkm-measurement="" hidden
|
|
226
|
+
<div aria-hidden="true" className="hkm-mode-surface" data-hkm-measurement="" hidden {...inertAttributes(true)} key={JSON.stringify([entry.id, choice.id, optionChoice.id])}>
|
|
212
227
|
<div className="hkm-fit"><div className="hkm-fit-inner">
|
|
213
228
|
{entry.render({ animated: false, mode: choice.id, option: optionChoice.id, previousMode: choice.id, theme })}
|
|
214
229
|
</div></div>
|
|
@@ -238,10 +253,12 @@ export function ModeShowcase<S extends string, M extends string, O extends strin
|
|
|
238
253
|
};
|
|
239
254
|
|
|
240
255
|
const statusNode = status?.({ mode, option, surface: surface.id });
|
|
241
|
-
const
|
|
242
|
-
.map((text) => optionalText(text, "ModeShowcase hint"))
|
|
243
|
-
|
|
244
|
-
|
|
256
|
+
const describeChoice = (surfaceHint: string | undefined, modeHint: string | undefined, optionHint: string | undefined) =>
|
|
257
|
+
[surfaceHint, modeHint, optionHint].map((text) => optionalText(text, "ModeShowcase hint")).filter((text) => text !== undefined).join(" ");
|
|
258
|
+
const hint = describeChoice(surface.hint, modeChoice.hint, optionInactive ? undefined : optionChoice?.hint);
|
|
259
|
+
const hints = [...new Set(surfaces.flatMap((entry) => modes.flatMap((choice) =>
|
|
260
|
+
(optionInactiveModes.includes(choice.id) ? [undefined] : (options ?? [undefined])).map((optionEntry) =>
|
|
261
|
+
describeChoice(entry.hint, choice.hint, optionEntry?.hint)))))].filter((text) => text !== "");
|
|
245
262
|
const hasStatus = statusNode !== undefined && statusNode !== null && statusNode !== false && statusNode !== "";
|
|
246
263
|
return (
|
|
247
264
|
<figure
|
|
@@ -307,7 +324,12 @@ export function ModeShowcase<S extends string, M extends string, O extends strin
|
|
|
307
324
|
</div>
|
|
308
325
|
)}
|
|
309
326
|
</div>
|
|
310
|
-
{
|
|
327
|
+
{hints.length === 0 ? null : (
|
|
328
|
+
<div className="hkm-showcase-hints">
|
|
329
|
+
<p className="hkm-showcase-hint">{hint}</p>
|
|
330
|
+
{hints.filter((text) => text !== hint).map((text) => <p aria-hidden="true" className="hkm-showcase-hint" data-hkm-reserving="" {...inertAttributes(true)} key={text}>{text}</p>)}
|
|
331
|
+
</div>
|
|
332
|
+
)}
|
|
311
333
|
</div>
|
|
312
334
|
<div className="hkm-mode-stage" ref={stage}>
|
|
313
335
|
{measurements}
|
|
@@ -317,7 +339,7 @@ export function ModeShowcase<S extends string, M extends string, O extends strin
|
|
|
317
339
|
className="hkm-mode-surface"
|
|
318
340
|
data-hkm-animated={entry.id === surface.id && animated ? "" : undefined}
|
|
319
341
|
data-hkm-from={entry.id === surface.id && animated ? previousMode : undefined}
|
|
320
|
-
|
|
342
|
+
{...inertAttributes(entry.id !== surface.id)}
|
|
321
343
|
key={entry.id}
|
|
322
344
|
>
|
|
323
345
|
<FitToWidth minWidth={fit === "fill" ? 1 : minWidth}>
|
|
@@ -345,7 +367,7 @@ export function ModeShowcase<S extends string, M extends string, O extends strin
|
|
|
345
367
|
export type ThroughStep = Readonly<{
|
|
346
368
|
id: string;
|
|
347
369
|
label: string;
|
|
348
|
-
/** Optional
|
|
370
|
+
/** Optional explanation beside the label, and below the preview on compact screens. */
|
|
349
371
|
hint?: string;
|
|
350
372
|
render: (state: Readonly<{ animated: boolean; theme: MockupTheme | undefined }>) => ReactNode;
|
|
351
373
|
}>;
|
|
@@ -416,14 +438,14 @@ function fitShowcaseStage(stage: HTMLDivElement, minimumHeight: number): void {
|
|
|
416
438
|
// The tallest complete slide establishes the floor. Fitted type never feeds
|
|
417
439
|
// back into it, and changing the active tab never requests a measurement.
|
|
418
440
|
stage.style.setProperty("--hkm-showcase-fill-height", `${naturalHeight}px`);
|
|
419
|
-
for (const fixture of probe.querySelectorAll("[data-hkm-measurement]")) fixture.remove();
|
|
420
441
|
probe.style.blockSize = `${naturalHeight}px`;
|
|
421
442
|
probe.removeAttribute("data-hkm-measuring");
|
|
422
443
|
const bodies = presentationBodies(stage).filter((body) => body.closest("[data-hkm-measurement]") === null);
|
|
423
444
|
const copies = presentationBodies(probe);
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
445
|
+
// Every authored state uses one readable size. A short terminal must not
|
|
446
|
+
// become a different typographic scale when the next tab has more lines.
|
|
447
|
+
let sharedSize = 4;
|
|
448
|
+
for (const copy of copies) {
|
|
427
449
|
const lines = [...copy.querySelectorAll<HTMLElement>(".hkm-terminal-line")];
|
|
428
450
|
const wrappedRows = (line: HTMLElement) => Math.ceil((line.getBoundingClientRect().height - 0.5) / Number.parseFloat(getComputedStyle(line).lineHeight));
|
|
429
451
|
const baselineRows = lines.map(wrappedRows);
|
|
@@ -435,8 +457,9 @@ function fitShowcaseStage(stage: HTMLDivElement, minimumHeight: number): void {
|
|
|
435
457
|
if (copy.scrollHeight <= copy.clientHeight + 1 && copy.scrollWidth <= copy.clientWidth + 1 && lines.every((line, index) => { const baseline = baselineRows[index]; return baseline !== undefined && wrappedRows(line) <= baseline; })) low = candidate;
|
|
436
458
|
else high = candidate;
|
|
437
459
|
}
|
|
438
|
-
|
|
460
|
+
sharedSize = Math.min(sharedSize, low);
|
|
439
461
|
}
|
|
462
|
+
for (const body of bodies) body.style.setProperty("--hkm-terminal-presentation-size", `${Math.floor(sharedSize * 1000) / 1000}rem`);
|
|
440
463
|
stage.setAttribute("data-hkm-fitted", "");
|
|
441
464
|
} finally {
|
|
442
465
|
probe.remove();
|
|
@@ -485,9 +508,12 @@ function useFittedShowcaseStage(fit: "natural" | "fill", source: unknown, variat
|
|
|
485
508
|
}
|
|
486
509
|
|
|
487
510
|
/**
|
|
488
|
-
*
|
|
489
|
-
* to
|
|
490
|
-
*
|
|
511
|
+
* A tabbed walkthrough: the steps and the preview share one edge. Compact
|
|
512
|
+
* layouts attach a tab strip to the preview's top edge between Back and Next;
|
|
513
|
+
* at 720px and wider the steps stack on its start edge with their
|
|
514
|
+
* explanations. All render functions stay mounted to reserve the tallest
|
|
515
|
+
* panel; inactive panels are inert and visually hidden. The keyboard model
|
|
516
|
+
* matches `ModeShowcase`.
|
|
491
517
|
*/
|
|
492
518
|
export function StepThrough({
|
|
493
519
|
caption,
|
|
@@ -500,7 +526,7 @@ export function StepThrough({
|
|
|
500
526
|
theme,
|
|
501
527
|
}: Readonly<{
|
|
502
528
|
steps: readonly ThroughStep[];
|
|
503
|
-
/** Fill the tallest natural slide and fit presentation terminals
|
|
529
|
+
/** Fill the tallest natural slide and fit presentation terminals at one shared size. */
|
|
504
530
|
fit?: "natural" | "fill";
|
|
505
531
|
caption?: string;
|
|
506
532
|
initial?: string;
|
|
@@ -516,17 +542,68 @@ export function StepThrough({
|
|
|
516
542
|
const id = useId();
|
|
517
543
|
const [index, setIndex] = useState(() => Math.max(0, steps.findIndex((step) => step.id === initial)));
|
|
518
544
|
const [animated, setAnimated] = useState(false);
|
|
545
|
+
const [vertical, setVertical] = useState(false);
|
|
546
|
+
const layout = useRef<HTMLDivElement>(null);
|
|
547
|
+
const tablist = useRef<HTMLDivElement>(null);
|
|
519
548
|
const tabs = useRef(new Map<number, HTMLButtonElement>());
|
|
520
549
|
const current = Math.min(index, steps.length - 1);
|
|
521
|
-
const
|
|
550
|
+
const hints = steps.map((entry) => optionalText(entry.hint, "StepThrough hint"));
|
|
551
|
+
const currentHint = hints[current];
|
|
552
|
+
|
|
553
|
+
useIsomorphicLayoutEffect(() => {
|
|
554
|
+
const node = layout.current;
|
|
555
|
+
if (node === null || typeof ResizeObserver === "undefined") return undefined;
|
|
556
|
+
const measure = () => setVertical(node.clientWidth >= 720);
|
|
557
|
+
measure();
|
|
558
|
+
const observer = new ResizeObserver(measure);
|
|
559
|
+
observer.observe(node);
|
|
560
|
+
return () => observer.disconnect();
|
|
561
|
+
}, []);
|
|
562
|
+
|
|
563
|
+
// A strip that cannot show every label in full keeps only the selected
|
|
564
|
+
// label visible; the rest show their numbers and keep their accessible names.
|
|
565
|
+
useIsomorphicLayoutEffect(() => {
|
|
566
|
+
const list = tablist.current;
|
|
567
|
+
if (list === null || typeof ResizeObserver === "undefined") return undefined;
|
|
568
|
+
let disposed = false;
|
|
569
|
+
const measure = () => {
|
|
570
|
+
if (disposed) return;
|
|
571
|
+
list.removeAttribute("data-hkm-compact");
|
|
572
|
+
if (vertical) return;
|
|
573
|
+
const truncated = [...list.querySelectorAll<HTMLElement>(".hkm-step-label")].some((label) => label.scrollWidth > label.clientWidth + 1);
|
|
574
|
+
if (truncated) list.setAttribute("data-hkm-compact", "");
|
|
575
|
+
};
|
|
576
|
+
measure();
|
|
577
|
+
const observer = new ResizeObserver(measure);
|
|
578
|
+
observer.observe(list);
|
|
579
|
+
void document.fonts.ready.then(measure);
|
|
580
|
+
return () => {
|
|
581
|
+
disposed = true;
|
|
582
|
+
observer.disconnect();
|
|
583
|
+
list.removeAttribute("data-hkm-compact");
|
|
584
|
+
};
|
|
585
|
+
}, [current, vertical]);
|
|
586
|
+
|
|
587
|
+
useIsomorphicLayoutEffect(() => {
|
|
588
|
+
const list = tablist.current;
|
|
589
|
+
const selected = tabs.current.get(current);
|
|
590
|
+
if (vertical || list === null || selected === undefined) return;
|
|
591
|
+
const bounds = list.getBoundingClientRect();
|
|
592
|
+
const active = selected.getBoundingClientRect();
|
|
593
|
+
// Scroll only the owned strip, without moving the surrounding page.
|
|
594
|
+
if (active.left < bounds.left) list.scrollLeft += active.left - bounds.left;
|
|
595
|
+
else if (active.right > bounds.right) list.scrollLeft += active.right - bounds.right;
|
|
596
|
+
}, [current, vertical]);
|
|
522
597
|
|
|
523
598
|
const go = (next: number, focus = false) => {
|
|
524
599
|
if (next < 0 || next >= steps.length || next === current) return;
|
|
525
600
|
setIndex(next);
|
|
526
601
|
setAnimated(true);
|
|
527
|
-
if (focus) tabs.current.get(next)?.focus();
|
|
602
|
+
if (focus) tabs.current.get(next)?.focus({ preventScroll: true });
|
|
528
603
|
};
|
|
529
604
|
const onTabKey = (event: KeyboardEvent<HTMLButtonElement>) => {
|
|
605
|
+
if (vertical && (event.key === "ArrowLeft" || event.key === "ArrowRight")) return;
|
|
606
|
+
if (!vertical && (event.key === "ArrowUp" || event.key === "ArrowDown")) return;
|
|
530
607
|
const next = nextTabIndex(event.key, current, steps.length);
|
|
531
608
|
if (next === undefined) return;
|
|
532
609
|
event.preventDefault();
|
|
@@ -535,61 +612,75 @@ export function StepThrough({
|
|
|
535
612
|
|
|
536
613
|
return (
|
|
537
614
|
<figure aria-label={`Illustration: ${label.toLowerCase()}`} className={joinMockupClasses("hkm-showcase", "hkm-steps", className)} data-hkm-fit={fit === "fill" ? fit : undefined} data-hkm-theme={theme} data-nosnippet="">
|
|
538
|
-
<div className="hkm-
|
|
539
|
-
<div
|
|
615
|
+
<div className="hkm-step-layout" ref={layout}>
|
|
616
|
+
<div className="hkm-showcase-controls hkm-step-controls">
|
|
617
|
+
<div className="hkm-step-nav">
|
|
618
|
+
<button aria-label="Back" className="hkm-step-button" disabled={current === 0} onClick={() => go(current - 1)} type="button">
|
|
619
|
+
<svg aria-hidden="true" focusable="false" height="20" viewBox="0 0 24 24" width="20"><path d="m14.5 5-7 7 7 7" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.75" /></svg>
|
|
620
|
+
</button>
|
|
621
|
+
<span aria-hidden="true" className="hkm-step-count">{current + 1} / {steps.length}</span>
|
|
622
|
+
<div aria-label={label} aria-orientation={vertical ? "vertical" : "horizontal"} className="hkm-tabs hkm-step-tabs" ref={tablist} role="tablist">
|
|
623
|
+
{steps.map((entry, position) => (
|
|
624
|
+
<button
|
|
625
|
+
aria-controls={`${id}-panel-${entry.id}`}
|
|
626
|
+
aria-describedby={hints[position] === undefined ? undefined : `${id}-hint-${entry.id}`}
|
|
627
|
+
aria-label={entry.label}
|
|
628
|
+
aria-selected={position === current}
|
|
629
|
+
className="hkm-tab"
|
|
630
|
+
data-hkm-done={position < current ? "" : undefined}
|
|
631
|
+
id={`${id}-tab-${entry.id}`}
|
|
632
|
+
key={entry.id}
|
|
633
|
+
onClick={() => go(position)}
|
|
634
|
+
onKeyDown={onTabKey}
|
|
635
|
+
ref={(element) => {
|
|
636
|
+
if (element === null) tabs.current.delete(position);
|
|
637
|
+
else tabs.current.set(position, element);
|
|
638
|
+
}}
|
|
639
|
+
role="tab"
|
|
640
|
+
tabIndex={position === current ? 0 : -1}
|
|
641
|
+
type="button"
|
|
642
|
+
>
|
|
643
|
+
<span aria-hidden="true" className="hkm-step-number">{position + 1}</span>
|
|
644
|
+
<span className="hkm-step-copy">
|
|
645
|
+
<span className="hkm-step-label">{entry.label}</span>
|
|
646
|
+
{hints[position] === undefined ? null : <span className="hkm-step-hint" id={`${id}-hint-${entry.id}`}>{hints[position]}</span>}
|
|
647
|
+
</span>
|
|
648
|
+
</button>
|
|
649
|
+
))}
|
|
650
|
+
</div>
|
|
651
|
+
<button aria-label="Next" className="hkm-step-button" disabled={current === steps.length - 1} onClick={() => go(current + 1)} type="button">
|
|
652
|
+
<svg aria-hidden="true" focusable="false" height="20" viewBox="0 0 24 24" width="20"><path d="m9.5 5 7 7-7 7" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.75" /></svg>
|
|
653
|
+
</button>
|
|
654
|
+
</div>
|
|
655
|
+
</div>
|
|
656
|
+
<div className="hkm-showcase-stage hkm-step-stage" ref={stage}>
|
|
540
657
|
{steps.map((entry, position) => (
|
|
541
|
-
<
|
|
542
|
-
aria-
|
|
543
|
-
aria-
|
|
544
|
-
className="hkm-
|
|
545
|
-
data-hkm-
|
|
546
|
-
id={`${id}-
|
|
658
|
+
<div
|
|
659
|
+
aria-hidden={position !== current}
|
|
660
|
+
aria-labelledby={`${id}-tab-${entry.id}`}
|
|
661
|
+
className="hkm-step-panel"
|
|
662
|
+
data-hkm-animated={position === current && animated ? "" : undefined}
|
|
663
|
+
id={`${id}-panel-${entry.id}`}
|
|
664
|
+
{...inertAttributes(position !== current)}
|
|
547
665
|
key={entry.id}
|
|
548
|
-
|
|
549
|
-
onKeyDown={onTabKey}
|
|
550
|
-
ref={(element) => {
|
|
551
|
-
if (element === null) tabs.current.delete(position);
|
|
552
|
-
else tabs.current.set(position, element);
|
|
553
|
-
}}
|
|
554
|
-
role="tab"
|
|
666
|
+
role="tabpanel"
|
|
555
667
|
tabIndex={position === current ? 0 : -1}
|
|
556
|
-
type="button"
|
|
557
668
|
>
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
669
|
+
{fit === "fill"
|
|
670
|
+
? <div className="hkm-fit"><div className="hkm-fit-inner">{entry.render({ animated: position === current && animated, theme })}</div></div>
|
|
671
|
+
: <FitToWidth minWidth={minWidth}>{entry.render({ animated: position === current && animated, theme })}</FitToWidth>}
|
|
672
|
+
</div>
|
|
561
673
|
))}
|
|
562
674
|
</div>
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
<div
|
|
567
|
-
aria-hidden={position !== current}
|
|
568
|
-
aria-labelledby={`${id}-tab-${entry.id}`}
|
|
569
|
-
className="hkm-step-panel"
|
|
570
|
-
data-hkm-animated={position === current && animated ? "" : undefined}
|
|
571
|
-
id={`${id}-panel-${entry.id}`}
|
|
572
|
-
inert={position !== current}
|
|
573
|
-
key={entry.id}
|
|
574
|
-
role="tabpanel"
|
|
575
|
-
>
|
|
576
|
-
{fit === "fill"
|
|
577
|
-
? <div className="hkm-fit"><div className="hkm-fit-inner">{entry.render({ animated: position === current && animated, theme })}</div></div>
|
|
578
|
-
: <FitToWidth minWidth={minWidth}>{entry.render({ animated: position === current && animated, theme })}</FitToWidth>}
|
|
675
|
+
{hints.some((hint) => hint !== undefined) ? (
|
|
676
|
+
<div aria-hidden="true" className="hkm-step-descriptions">
|
|
677
|
+
{steps.map((entry, position) => <p className="hkm-step-description" data-hkm-active={position === current ? "" : undefined} key={entry.id}>{hints[position]}</p>)}
|
|
579
678
|
</div>
|
|
580
|
-
)
|
|
581
|
-
</div>
|
|
582
|
-
<div className="hkm-step-nav">
|
|
583
|
-
<button aria-label="Back" className="hkm-step-button" disabled={current === 0} onClick={() => go(current - 1)} type="button">
|
|
584
|
-
<svg aria-hidden="true" focusable="false" height="28" viewBox="0 0 24 24" width="28"><path d="m14.5 5-7 7 7 7" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.75" /></svg>
|
|
585
|
-
</button>
|
|
586
|
-
<span aria-live="polite" className="hkm-showcase-status hkm-step-announcement">
|
|
587
|
-
Step {current + 1} of {steps.length}{step.hint === undefined ? "" : `. ${step.hint}`}
|
|
588
|
-
</span>
|
|
589
|
-
<button aria-label="Next" className="hkm-step-button" data-hkm-primary="" disabled={current === steps.length - 1} onClick={() => go(current + 1)} type="button">
|
|
590
|
-
<svg aria-hidden="true" focusable="false" height="28" viewBox="0 0 24 24" width="28"><path d="m9.5 5 7 7-7 7" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.75" /></svg>
|
|
591
|
-
</button>
|
|
679
|
+
) : null}
|
|
592
680
|
</div>
|
|
681
|
+
<span aria-live="polite" className="hkm-showcase-status hkm-step-announcement">
|
|
682
|
+
Step {current + 1} of {steps.length}{currentHint === undefined ? "" : `. ${currentHint}`}
|
|
683
|
+
</span>
|
|
593
684
|
{captionText === undefined ? null : (
|
|
594
685
|
<figcaption className="hkm-showcase-caption"><span>{captionText}</span></figcaption>
|
|
595
686
|
)}
|
package/src/mockups/core.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode } from "react";
|
|
2
2
|
|
|
3
|
+
import { providerMark, providerMarkMonogram, providerMarkOnAccent } from "../provider-marks.js";
|
|
4
|
+
|
|
3
5
|
/*
|
|
4
6
|
* Shared pieces for the code-built mockups. Server-safe plain React: no hooks,
|
|
5
7
|
* no context, no StyleX, and no runtime dependency other than React, so a film
|
|
@@ -306,6 +308,34 @@ export function MockupGlyph({
|
|
|
306
308
|
);
|
|
307
309
|
}
|
|
308
310
|
|
|
311
|
+
/**
|
|
312
|
+
* A product's brand mark from the shared provider-mark registry, such as
|
|
313
|
+
* "Apple Contacts", "LinkedIn", or "iMessage". `tile` draws the vendor glyph
|
|
314
|
+
* in readable ink on the brand color, like an app icon; `glyph` follows the
|
|
315
|
+
* surrounding text color. Names without a registered mark get a neutral
|
|
316
|
+
* monogram tile, never an invented logo. Decorative: always aria-hidden, so
|
|
317
|
+
* name the product in nearby text.
|
|
318
|
+
*/
|
|
319
|
+
export function MockupBrandMark({
|
|
320
|
+
className,
|
|
321
|
+
name,
|
|
322
|
+
size = 32,
|
|
323
|
+
variant = "tile",
|
|
324
|
+
}: Readonly<{ name: string; size?: number; variant?: "tile" | "glyph"; className?: string }>) {
|
|
325
|
+
if (!(size > 0)) throw new RangeError("MockupBrandMark size must be positive.");
|
|
326
|
+
const mark = providerMark(name);
|
|
327
|
+
const style = { "--hkm-mark-size": `${String(size)}px`, ...(mark === undefined || variant !== "tile" ? {} : { "--hkm-mark-accent": mark.accent, "--hkm-mark-ink": providerMarkOnAccent(mark) }) } as CSSProperties;
|
|
328
|
+
if (mark === undefined) {
|
|
329
|
+
return <span aria-hidden="true" className={joinMockupClasses("hkm-brand-mark", className)} data-hkm-monogram="" style={style}>{providerMarkMonogram(name)}</span>;
|
|
330
|
+
}
|
|
331
|
+
return (
|
|
332
|
+
<span aria-hidden="true" className={joinMockupClasses("hkm-brand-mark", className)} data-hkm-mark={mark.id} data-hkm-variant={variant} style={style}>
|
|
333
|
+
{/* Generated from vendored, sanitized artwork in provider-marks.generated.ts. */}
|
|
334
|
+
<svg dangerouslySetInnerHTML={{ __html: mark.glyph.body }} fill="currentColor" focusable="false" viewBox={mark.glyph.viewBox} />
|
|
335
|
+
</span>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
309
339
|
/** The three window buttons at the start of a title bar. Decorative. */
|
|
310
340
|
export function WindowLights() {
|
|
311
341
|
return (
|
package/src/mockups.css
CHANGED
|
@@ -107,6 +107,39 @@
|
|
|
107
107
|
flex: none;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/* Brand marks are app-icon tiles: the vendor glyph in readable ink on the
|
|
111
|
+
* vendor color. The glyph variant follows the surrounding text instead. */
|
|
112
|
+
.hkm-root .hkm-brand-mark {
|
|
113
|
+
display: inline-grid;
|
|
114
|
+
flex: none;
|
|
115
|
+
place-items: center;
|
|
116
|
+
inline-size: var(--hkm-mark-size, 32px);
|
|
117
|
+
block-size: var(--hkm-mark-size, 32px);
|
|
118
|
+
border-radius: calc(var(--hkm-mark-size, 32px) * 0.26);
|
|
119
|
+
background: var(--hkm-mark-accent, var(--hkm-field));
|
|
120
|
+
color: var(--hkm-mark-ink, var(--hkm-muted));
|
|
121
|
+
box-shadow: inset 0 0 0 1px light-dark(rgb(0 0 0 / 0.06), rgb(255 255 255 / 0.1));
|
|
122
|
+
font: 650 calc(var(--hkm-mark-size, 32px) * 0.36)/1 var(--hkm-font);
|
|
123
|
+
letter-spacing: 0.01em;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.hkm-root .hkm-brand-mark svg {
|
|
127
|
+
display: block;
|
|
128
|
+
inline-size: 56%;
|
|
129
|
+
block-size: 56%;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.hkm-root .hkm-brand-mark[data-hkm-variant="glyph"] {
|
|
133
|
+
background: none;
|
|
134
|
+
box-shadow: none;
|
|
135
|
+
color: inherit;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.hkm-root .hkm-brand-mark[data-hkm-variant="glyph"] svg {
|
|
139
|
+
inline-size: 100%;
|
|
140
|
+
block-size: 100%;
|
|
141
|
+
}
|
|
142
|
+
|
|
110
143
|
.hkm-root .hkm-avatar {
|
|
111
144
|
display: inline-grid;
|
|
112
145
|
flex: none;
|
|
@@ -2329,22 +2362,13 @@
|
|
|
2329
2362
|
}
|
|
2330
2363
|
|
|
2331
2364
|
.hkm-showcase .hkm-step-number {
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
border: 1px solid currentColor;
|
|
2337
|
-
border-radius: 50%;
|
|
2338
|
-
font-size: 11px;
|
|
2365
|
+
flex: none;
|
|
2366
|
+
color: var(--hkm-sc-muted);
|
|
2367
|
+
font-size: 0.875rem;
|
|
2368
|
+
line-height: 1.5;
|
|
2339
2369
|
font-variant-numeric: tabular-nums;
|
|
2340
2370
|
}
|
|
2341
2371
|
|
|
2342
|
-
.hkm-showcase .hkm-tab[data-hkm-done] .hkm-step-number {
|
|
2343
|
-
border-color: var(--hkm-accent);
|
|
2344
|
-
background: var(--hkm-accent);
|
|
2345
|
-
color: var(--hkm-accent-foreground);
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
2372
|
.hkm-showcase .hkm-showcase-settings {
|
|
2349
2373
|
display: flex;
|
|
2350
2374
|
flex-wrap: wrap;
|
|
@@ -2412,11 +2436,26 @@
|
|
|
2412
2436
|
outline-color: var(--hkm-accent-foreground);
|
|
2413
2437
|
}
|
|
2414
2438
|
|
|
2439
|
+
.hkm-showcase .hkm-showcase-hints {
|
|
2440
|
+
display: grid;
|
|
2441
|
+
min-inline-size: 0;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
.hkm-showcase .hkm-showcase-hints > .hkm-showcase-hint {
|
|
2445
|
+
grid-area: 1 / 1;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
.hkm-showcase .hkm-showcase-hint[data-hkm-reserving] {
|
|
2449
|
+
visibility: hidden;
|
|
2450
|
+
pointer-events: none;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2415
2453
|
.hkm-showcase .hkm-showcase-hint {
|
|
2416
2454
|
min-block-size: 2.9em;
|
|
2417
2455
|
margin: 0;
|
|
2418
2456
|
color: var(--hkm-sc-muted);
|
|
2419
2457
|
font-size: 13px;
|
|
2458
|
+
overflow-wrap: anywhere;
|
|
2420
2459
|
}
|
|
2421
2460
|
|
|
2422
2461
|
.hkm-showcase .hkm-showcase-stage {
|
|
@@ -2512,14 +2551,14 @@
|
|
|
2512
2551
|
}
|
|
2513
2552
|
}
|
|
2514
2553
|
|
|
2515
|
-
/*
|
|
2554
|
+
/* Mode selectors retain a shared edge with their preview panel. */
|
|
2516
2555
|
.hkm-showcase:is(.hkm-steps, .hkm-modes) { gap: 0; }
|
|
2517
2556
|
.hkm-showcase.hkm-steps .hkm-showcase-controls { padding: 0; border: 0; border-radius: 0; background: transparent; }
|
|
2518
2557
|
.hkm-showcase .hkm-folder-tabs { gap: 6px; margin: 0; padding: 0; border: 0; position: relative; z-index: 1; }
|
|
2519
2558
|
.hkm-showcase .hkm-folder-tabs .hkm-tab { min-block-size: 48px; padding: 12px 18px; border: 1px solid var(--hkm-sc-line); border-block-end: 0; border-radius: 12px 12px 0 0; background: var(--hkm-sc-field); color: var(--hkm-sc-muted); }
|
|
2520
2559
|
.hkm-showcase .hkm-folder-tabs .hkm-tab[aria-selected="true"] { background: var(--hkm-sc-bg); color: var(--hkm-sc-fg); }
|
|
2521
2560
|
.hkm-showcase .hkm-folder-tabs .hkm-tab:hover { color: var(--hkm-sc-fg); }
|
|
2522
|
-
.hkm-showcase.hkm-steps .hkm-step-stage { display: grid; border: 1px solid var(--hkm-sc-line); border-radius:
|
|
2561
|
+
.hkm-showcase.hkm-steps .hkm-step-stage { display: grid; min-inline-size: 0; border: 1px solid var(--hkm-sc-line); border-radius: var(--hkm-step-radius, 12px); background: var(--hkm-sc-bg); overflow: clip; }
|
|
2523
2562
|
.hkm-showcase.hkm-modes .hkm-mode-panel { border: 1px solid var(--hkm-sc-line); border-radius: 12px; background: var(--hkm-sc-bg); overflow: clip; }
|
|
2524
2563
|
.hkm-showcase.hkm-modes .hkm-folder-tabs + .hkm-mode-panel { border-start-start-radius: 0; margin-block-start: -1px; }
|
|
2525
2564
|
.hkm-showcase.hkm-modes .hkm-showcase-controls { border: 0; border-block-end: 1px solid var(--hkm-sc-line); border-radius: 0; }
|
|
@@ -2527,10 +2566,13 @@
|
|
|
2527
2566
|
.hkm-mode-surface { grid-area: 1 / 1; min-inline-size: 0; align-self: start; }
|
|
2528
2567
|
.hkm-mode-surface[aria-hidden="true"] { visibility: hidden; pointer-events: none; }
|
|
2529
2568
|
.hkm-showcase.hkm-modes .hkm-mode-surface > .hkm-fit > .hkm-fit-inner > .hkm-root,
|
|
2530
|
-
.hkm-showcase.hkm-modes .hkm-mode-surface > .hkm-fit > .hkm-fit-inner > .hkm-root > .hkm-window
|
|
2569
|
+
.hkm-showcase.hkm-modes .hkm-mode-surface > .hkm-fit > .hkm-fit-inner > .hkm-root > .hkm-window,
|
|
2570
|
+
.hkm-showcase.hkm-steps .hkm-step-panel > .hkm-fit > .hkm-fit-inner > .hkm-root,
|
|
2571
|
+
.hkm-showcase.hkm-steps .hkm-step-panel > .hkm-fit > .hkm-fit-inner > .hkm-root > .hkm-window { border: 0; border-radius: 0; box-shadow: none; }
|
|
2531
2572
|
.hkm-step-panel { grid-area: 1 / 1; min-inline-size: 0; align-self: start; }
|
|
2532
2573
|
.hkm-step-panel[aria-hidden="true"] { visibility: hidden; pointer-events: none; }
|
|
2533
|
-
|
|
2574
|
+
/* Browser and app titles use the same chrome geometry inside a sequence. */
|
|
2575
|
+
.hkm-showcase .hkm-root :is(.hkm-title-bar, .hkm-browser-bar) { block-size: 44px; font-size: 13px; }
|
|
2534
2576
|
/* Fill uses the tallest complete slide as its natural height. It does not
|
|
2535
2577
|
* scale text or ask fitted font sizes to determine the stage height. */
|
|
2536
2578
|
.hkm-showcase[data-hkm-fit="fill"] [data-hkm-measuring] [data-hkm-fill-frame] > .hkm-window > .hkm-page { max-block-size: none; }
|
|
@@ -2567,11 +2609,75 @@
|
|
|
2567
2609
|
flex: none;
|
|
2568
2610
|
}
|
|
2569
2611
|
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2612
|
+
/* A walkthrough is one tabbed selector: the steps and the preview share an
|
|
2613
|
+
* edge. Compact layouts attach a tab strip to the preview's top edge between
|
|
2614
|
+
* Back and Next. Wide layouts stack the steps on its start edge, flush with
|
|
2615
|
+
* a square corner, and put Back and Next under them. The selected tab takes
|
|
2616
|
+
* the window-chrome color so it reads as part of the frame below it. */
|
|
2617
|
+
.hkm-showcase.hkm-steps {
|
|
2618
|
+
--hkm-step-radius: 12px;
|
|
2619
|
+
--hkm-step-tab-radius: 10px;
|
|
2620
|
+
--hkm-step-tab-selected: var(--hkm-chrome);
|
|
2621
|
+
--hkm-step-tab-hover: color-mix(in srgb, var(--hkm-chrome) 55%, transparent);
|
|
2622
|
+
font-size: 1rem;
|
|
2623
|
+
line-height: 1.5;
|
|
2624
|
+
}
|
|
2625
|
+
.hkm-showcase .hkm-step-layout { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-areas: "controls" "stage" "descriptions"; align-items: start; min-inline-size: 0; }
|
|
2626
|
+
.hkm-showcase.hkm-steps .hkm-step-controls { grid-area: controls; position: relative; z-index: 1; min-inline-size: 0; margin-block-end: -1px; }
|
|
2627
|
+
.hkm-showcase.hkm-steps .hkm-step-stage { grid-area: stage; }
|
|
2628
|
+
.hkm-showcase.hkm-steps .hkm-step-nav { display: grid; grid-template-columns: 44px minmax(0, 1fr) 44px; grid-template-areas: "back tabs next"; align-items: end; gap: 6px; min-inline-size: 0; padding: 0; }
|
|
2629
|
+
.hkm-showcase.hkm-steps .hkm-step-button { display: inline-grid; place-items: center; inline-size: 44px; min-inline-size: 44px; min-block-size: 44px; margin-block-end: 1px; padding: 0; border: 0; border-radius: var(--hkm-step-tab-radius); background: transparent; color: var(--hkm-sc-fg); }
|
|
2630
|
+
.hkm-showcase.hkm-steps .hkm-step-button:first-child { grid-area: back; }
|
|
2631
|
+
.hkm-showcase.hkm-steps .hkm-step-button:last-child { grid-area: next; }
|
|
2632
|
+
.hkm-showcase.hkm-steps .hkm-step-button:hover:enabled { background: var(--hkm-step-tab-hover); }
|
|
2633
|
+
.hkm-showcase.hkm-steps .hkm-step-button:disabled { background: transparent; color: var(--hkm-sc-muted); cursor: default; }
|
|
2634
|
+
.hkm-showcase .hkm-step-count { display: none; grid-area: count; color: var(--hkm-sc-muted); font: 500 0.8125rem/1 var(--hkm-mono); font-variant-numeric: tabular-nums; text-align: center; }
|
|
2635
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs { grid-area: tabs; display: flex; gap: 2px; min-inline-size: 0; margin: 0; padding: 0; overflow-x: auto; overflow-y: hidden; border: 0; scrollbar-width: none; }
|
|
2636
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs::-webkit-scrollbar { display: none; }
|
|
2637
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs .hkm-tab { display: inline-flex; flex: 0 1 auto; align-items: center; gap: 10px; min-inline-size: 44px; min-block-size: 44px; padding: 10px 16px; border: 1px solid transparent; border-block-end: 0; border-radius: var(--hkm-step-tab-radius) var(--hkm-step-tab-radius) 0 0; background: transparent; color: var(--hkm-sc-muted); font-size: 0.9375rem; line-height: 1.4; font-weight: 550; text-align: start; white-space: nowrap; }
|
|
2638
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs .hkm-tab:hover { background: var(--hkm-step-tab-hover); color: var(--hkm-sc-fg); }
|
|
2639
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs .hkm-tab[aria-selected="true"] { border-color: var(--hkm-sc-line); background: var(--hkm-step-tab-selected); color: var(--hkm-sc-fg); }
|
|
2640
|
+
/* A preview pinned to its own theme lends that theme to the selected tab,
|
|
2641
|
+
* so a dark window on a light page still reads as one frame. */
|
|
2642
|
+
.hkm-showcase.hkm-steps:has(.hkm-step-panel[aria-hidden="false"] .hkm-root[data-hkm-theme="dark"]) .hkm-step-tabs .hkm-tab[aria-selected="true"] { color-scheme: dark; }
|
|
2643
|
+
.hkm-showcase.hkm-steps:has(.hkm-step-panel[aria-hidden="false"] .hkm-root[data-hkm-theme="light"]) .hkm-step-tabs .hkm-tab[aria-selected="true"] { color-scheme: light; }
|
|
2644
|
+
.hkm-showcase.hkm-steps:has(.hkm-step-panel[aria-hidden="false"] .hkm-root[data-hkm-theme]) .hkm-step-tabs .hkm-tab[aria-selected="true"] { color: var(--hkm-fg); }
|
|
2645
|
+
.hkm-showcase.hkm-steps:has(.hkm-step-panel[aria-hidden="false"] .hkm-root[data-hkm-theme]) .hkm-step-tabs .hkm-tab[aria-selected="true"] :is(.hkm-step-number, .hkm-step-label) { color: var(--hkm-fg); }
|
|
2646
|
+
.hkm-showcase.hkm-steps:has(.hkm-step-panel[aria-hidden="false"] .hkm-root[data-hkm-theme]) .hkm-step-tabs .hkm-tab[aria-selected="true"] .hkm-step-hint { color: var(--hkm-muted); }
|
|
2647
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs .hkm-tab:focus-visible { outline-offset: -3px; }
|
|
2648
|
+
.hkm-showcase.hkm-steps .hkm-step-number { color: var(--hkm-sc-muted); font: 500 0.8125rem/1.4 var(--hkm-mono); }
|
|
2649
|
+
.hkm-showcase.hkm-steps .hkm-tab[aria-selected="true"] .hkm-step-number { color: var(--hkm-sc-fg); }
|
|
2650
|
+
.hkm-showcase .hkm-step-copy { display: flex; flex-direction: column; gap: 4px; min-inline-size: 0; }
|
|
2651
|
+
.hkm-showcase .hkm-step-label { overflow: hidden; font: inherit; text-overflow: ellipsis; }
|
|
2652
|
+
.hkm-showcase .hkm-step-hint { display: none; color: var(--hkm-sc-muted); font-size: 0.875rem; line-height: 1.45; font-weight: 400; }
|
|
2653
|
+
.hkm-showcase .hkm-step-descriptions { grid-area: descriptions; display: grid; padding: 12px 4px 0; }
|
|
2654
|
+
.hkm-showcase .hkm-step-description { grid-area: 1 / 1; margin: 0; color: var(--hkm-sc-muted); font-size: 0.875rem; line-height: 1.5; }
|
|
2655
|
+
.hkm-showcase .hkm-step-description:not([data-hkm-active]) { visibility: hidden; }
|
|
2656
|
+
.hkm-showcase .hkm-step-panel:focus-visible { outline: 2px solid var(--hkm-accent); outline-offset: -4px; }
|
|
2657
|
+
/* A strip too narrow for every label keeps the selected label and shows
|
|
2658
|
+
* numbers for the rest; each label stays the tab's accessible name. */
|
|
2659
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs[data-hkm-compact] .hkm-tab:not([aria-selected="true"]) { flex: none; justify-content: center; padding-inline: 12px; }
|
|
2660
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs[data-hkm-compact] .hkm-tab:not([aria-selected="true"]) .hkm-step-copy { display: none; }
|
|
2661
|
+
@container hkm-showcase (max-width: 519px) {
|
|
2662
|
+
.hkm-showcase.hkm-steps .hkm-step-nav { grid-template-columns: 40px minmax(0, 1fr) 40px; gap: 4px; }
|
|
2663
|
+
.hkm-showcase.hkm-steps .hkm-step-button { inline-size: 40px; min-inline-size: 40px; }
|
|
2664
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs .hkm-tab { gap: 8px; padding-inline: 12px; font-size: 0.875rem; }
|
|
2665
|
+
}
|
|
2666
|
+
@container hkm-showcase (min-width: 720px) {
|
|
2667
|
+
.hkm-showcase .hkm-step-layout { grid-template-columns: minmax(200px, 0.72fr) minmax(0, 1.6fr); grid-template-areas: "controls stage"; align-items: stretch; }
|
|
2668
|
+
.hkm-showcase.hkm-steps .hkm-step-controls { margin-block-end: 0; margin-inline-end: -1px; }
|
|
2669
|
+
.hkm-showcase.hkm-steps .hkm-step-stage { align-self: stretch; border-start-start-radius: 0; }
|
|
2670
|
+
.hkm-showcase.hkm-steps .hkm-step-nav { grid-template-areas: "tabs tabs tabs" "back count next"; align-items: center; row-gap: 14px; column-gap: 6px; }
|
|
2671
|
+
.hkm-showcase.hkm-steps .hkm-step-button { margin: 0; }
|
|
2672
|
+
.hkm-showcase .hkm-step-count { display: block; }
|
|
2673
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs { flex-direction: column; gap: 2px; overflow: visible; }
|
|
2674
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs .hkm-tab { display: grid; flex: none; grid-template-columns: 2.5ch minmax(0, 1fr); align-items: start; gap: 12px; padding: 14px 16px; border: 1px solid transparent; border-inline-end: 0; border-radius: var(--hkm-step-tab-radius) 0 0 var(--hkm-step-tab-radius); white-space: normal; }
|
|
2675
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs .hkm-tab[aria-selected="true"] { border-color: var(--hkm-sc-line); }
|
|
2676
|
+
.hkm-showcase.hkm-steps .hkm-step-number { padding-block-start: 0.1rem; }
|
|
2677
|
+
.hkm-showcase .hkm-step-label { overflow: visible; }
|
|
2678
|
+
.hkm-showcase .hkm-step-hint { display: block; }
|
|
2679
|
+
.hkm-showcase .hkm-step-descriptions { display: none; }
|
|
2680
|
+
}
|
|
2575
2681
|
.hkm-showcase.hkm-steps .hkm-step-announcement { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
2576
2682
|
.hkm-showcase:is(.hkm-steps, .hkm-modes) .hkm-showcase-caption { margin-block-start: 1rem; }
|
|
2577
2683
|
|
|
@@ -2679,7 +2785,8 @@
|
|
|
2679
2785
|
|
|
2680
2786
|
.hkm-showcase .hkm-tab[aria-selected="true"],
|
|
2681
2787
|
.hkm-showcase .hkm-segmented button[aria-pressed="true"],
|
|
2682
|
-
.hkm-showcase .hkm-tab[
|
|
2788
|
+
.hkm-showcase.hkm-steps .hkm-tab[aria-selected="true"],
|
|
2789
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs .hkm-tab[aria-selected="true"],
|
|
2683
2790
|
.hkm-root .hkm-popover-item[data-hkm-selected],
|
|
2684
2791
|
.hkm-root .hkm-work-mark {
|
|
2685
2792
|
forced-color-adjust: none;
|
|
@@ -2687,6 +2794,19 @@
|
|
|
2687
2794
|
color: HighlightText;
|
|
2688
2795
|
}
|
|
2689
2796
|
|
|
2797
|
+
.hkm-showcase.hkm-steps .hkm-tab[aria-selected="true"] :is(.hkm-step-number, .hkm-step-hint) {
|
|
2798
|
+
color: HighlightText;
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
/* Fill stages reserve their authored field for the live preview. Keep the
|
|
2802
|
+
* selected tab in the system pair even when that fill rule is authored by
|
|
2803
|
+
* a consumer layer with a stronger declaration. */
|
|
2804
|
+
.hkm-showcase[data-hkm-fit="fill"].hkm-steps .hkm-tab[aria-selected="true"] {
|
|
2805
|
+
forced-color-adjust: none;
|
|
2806
|
+
background: Highlight !important;
|
|
2807
|
+
color: HighlightText !important;
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2690
2810
|
.hkm-showcase .hkm-segmented button[aria-pressed="true"]:disabled {
|
|
2691
2811
|
background: ButtonFace;
|
|
2692
2812
|
color: GrayText;
|
|
@@ -2708,3 +2828,18 @@
|
|
|
2708
2828
|
}
|
|
2709
2829
|
}
|
|
2710
2830
|
}
|
|
2831
|
+
|
|
2832
|
+
/* Consumer layers and palette variable chains can add stronger declarations
|
|
2833
|
+
* after the legacy component layer. Keep the system selection pair as the
|
|
2834
|
+
* final forced-colors contract. */
|
|
2835
|
+
@media (forced-colors: active) {
|
|
2836
|
+
.hkm-showcase.hkm-steps .hkm-step-tabs .hkm-tab[aria-selected="true"] {
|
|
2837
|
+
forced-color-adjust: none;
|
|
2838
|
+
background: Highlight !important;
|
|
2839
|
+
color: HighlightText !important;
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
.hkm-showcase.hkm-steps .hkm-tab[aria-selected="true"] :is(.hkm-step-number, .hkm-step-hint) {
|
|
2843
|
+
color: HighlightText !important;
|
|
2844
|
+
}
|
|
2845
|
+
}
|