@hraness/design-kit 0.36.1 → 0.36.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.
package/dist/stylex.css CHANGED
@@ -2203,6 +2203,14 @@
2203
2203
  filter: saturate(1.08);
2204
2204
  }
2205
2205
 
2206
+ .x1kfky9y {
2207
+ flex-basis: 10rem;
2208
+ }
2209
+
2210
+ .x1vhnul8 {
2211
+ flex-basis: 3.5rem;
2212
+ }
2213
+
2206
2214
  .xdl72j9 {
2207
2215
  flex-basis: auto;
2208
2216
  }
@@ -2535,10 +2543,6 @@
2535
2543
  grid-template-columns: 2.25rem minmax(0, 1fr);
2536
2544
  }
2537
2545
 
2538
- .xrdwe0 {
2539
- grid-template-columns: 3.5rem minmax(0, 1fr);
2540
- }
2541
-
2542
2546
  .xx9t13c {
2543
2547
  grid-template-columns: auto minmax(0, 1fr) auto;
2544
2548
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/design-kit",
3
- "version": "0.36.1",
3
+ "version": "0.36.2",
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",
@@ -6,7 +6,7 @@
6
6
  "name": "@hraness/design-kit",
7
7
  "path": ".",
8
8
  "visibility": "public",
9
- "version": "0.36.1"
9
+ "version": "0.36.2"
10
10
  }
11
11
  ],
12
12
  "contract": "hraness.portfolio-inventory/v1",
@@ -93,7 +93,7 @@ export function FitToWidth({ children, className, minWidth = 400 }: Readonly<{ c
93
93
 
94
94
  const scaled = fit !== null && fit.scale < 1;
95
95
  return (
96
- <div className={joinMockupClasses("hkm-fit", className)} ref={outer} style={scaled ? { height: fit.height } : undefined}>
96
+ <div className={joinMockupClasses("hkm-fit", className)} data-hkm-min-width={minWidth} ref={outer} style={scaled ? { height: fit.height } : undefined}>
97
97
  <div
98
98
  className="hkm-fit-inner"
99
99
  data-hkm-scaled={scaled ? "" : undefined}
@@ -350,10 +350,48 @@ export type ThroughStep = Readonly<{
350
350
  render: (state: Readonly<{ animated: boolean; theme: MockupTheme | undefined }>) => ReactNode;
351
351
  }>;
352
352
 
353
+ /** Fill owns the outer shell and its top-level frames, never nested width-fit graphics. */
354
+ function fillFrames(stage: HTMLElement): HTMLElement[] {
355
+ const frames: HTMLElement[] = [];
356
+ for (const panel of stage.querySelectorAll<HTMLElement>(":scope > .hkm-step-panel, :scope > .hkm-mode-surface")) {
357
+ const fit = panel.querySelector<HTMLElement>(":scope > .hkm-fit");
358
+ if (fit === null) continue;
359
+ for (const root of fit.querySelectorAll<HTMLElement>(".hkm-root")) {
360
+ const enclosingRoot = root.parentElement?.closest(".hkm-root");
361
+ if (root.closest(".hkm-fit") === fit && (enclosingRoot === null || enclosingRoot === undefined || !fit.contains(enclosingRoot))) frames.push(root);
362
+ }
363
+ }
364
+ return frames;
365
+ }
366
+
367
+ function presentationBodies(stage: HTMLElement): HTMLElement[] {
368
+ return fillFrames(stage).flatMap((frame) => [...frame.querySelectorAll<HTMLElement>(':scope > .hkm-window > [data-hkm-density="presentation"]')]);
369
+ }
370
+
371
+ /** Detached clones have no ResizeObserver. Recreate explicit graphic fits from the inside out. */
372
+ function measureNestedFits(stage: HTMLElement): void {
373
+ const fits = [...stage.querySelectorAll<HTMLElement>("[data-hkm-min-width]")].filter((fit) => fit.parentElement?.classList.contains("hkm-step-panel") !== true && fit.parentElement?.classList.contains("hkm-mode-surface") !== true);
374
+ for (const fit of fits) {
375
+ const inner = fit.querySelector<HTMLElement>(":scope > .hkm-fit-inner");
376
+ if (inner === null) continue;
377
+ fit.style.removeProperty("height");
378
+ inner.style.removeProperty("transform");
379
+ inner.style.width = `${Math.max(fit.clientWidth, Number(fit.dataset.hkmMinWidth))}px`;
380
+ }
381
+ for (const fit of fits.reverse()) {
382
+ const inner = fit.querySelector<HTMLElement>(":scope > .hkm-fit-inner");
383
+ if (inner === null || inner.offsetWidth <= 0) continue;
384
+ const scale = Math.min(1, fit.clientWidth / inner.offsetWidth);
385
+ fit.style.height = `${inner.offsetHeight * scale}px`;
386
+ inner.style.transform = `scale(${scale})`;
387
+ }
388
+ }
389
+
353
390
  /** Measure complete mounted slides at a readable baseline, separately from fitted paint. */
354
391
  function fitShowcaseStage(stage: HTMLDivElement, minimumHeight: number): void {
355
392
  const owner = stage.parentElement;
356
393
  if (owner === null || stage.clientWidth <= 0) return;
394
+ for (const frame of fillFrames(stage)) frame.setAttribute("data-hkm-fill-frame", "");
357
395
  const probe = stage.cloneNode(true) as HTMLDivElement;
358
396
  probe.setAttribute("aria-hidden", "true");
359
397
  probe.setAttribute("inert", "");
@@ -362,7 +400,7 @@ function fitShowcaseStage(stage: HTMLDivElement, minimumHeight: number): void {
362
400
  for (const sentinel of probe.querySelectorAll("[data-hkm-font-sentinel]")) sentinel.remove();
363
401
  for (const node of probe.querySelectorAll("[id]")) node.removeAttribute("id");
364
402
  for (const node of probe.querySelectorAll("[data-hkm-animated]")) node.removeAttribute("data-hkm-animated");
365
- for (const body of probe.querySelectorAll<HTMLElement>('[data-hkm-density="presentation"]')) body.style.removeProperty("--hkm-terminal-presentation-size");
403
+ for (const body of presentationBodies(probe)) body.style.removeProperty("--hkm-terminal-presentation-size");
366
404
  for (const fixture of probe.querySelectorAll<HTMLElement>("[data-hkm-measurement]")) fixture.hidden = false;
367
405
  // Even a reduced-motion reset can introduce tiny all-property transitions.
368
406
  // Synchronous measurements need final styles throughout the owned copy,
@@ -373,6 +411,7 @@ function fitShowcaseStage(stage: HTMLDivElement, minimumHeight: number): void {
373
411
  }
374
412
  owner.append(probe);
375
413
  try {
414
+ measureNestedFits(probe);
376
415
  const naturalHeight = Math.max(minimumHeight, Math.ceil(probe.getBoundingClientRect().height));
377
416
  // The tallest complete slide establishes the floor. Fitted type never feeds
378
417
  // back into it, and changing the active tab never requests a measurement.
@@ -380,8 +419,8 @@ function fitShowcaseStage(stage: HTMLDivElement, minimumHeight: number): void {
380
419
  for (const fixture of probe.querySelectorAll("[data-hkm-measurement]")) fixture.remove();
381
420
  probe.style.blockSize = `${naturalHeight}px`;
382
421
  probe.removeAttribute("data-hkm-measuring");
383
- const bodies = [...stage.querySelectorAll<HTMLElement>('[data-hkm-density="presentation"]')].filter((body) => body.closest("[data-hkm-measurement]") === null);
384
- const copies = [...probe.querySelectorAll<HTMLElement>('[data-hkm-density="presentation"]')];
422
+ const bodies = presentationBodies(stage).filter((body) => body.closest("[data-hkm-measurement]") === null);
423
+ const copies = presentationBodies(probe);
385
424
  for (const [index, copy] of copies.entries()) {
386
425
  const body = bodies[index];
387
426
  if (body === undefined) continue;
@@ -438,7 +477,8 @@ function useFittedShowcaseStage(fit: "natural" | "fill", source: unknown, variat
438
477
  fontSentinel.remove();
439
478
  node.style.removeProperty("--hkm-showcase-fill-height");
440
479
  node.removeAttribute("data-hkm-fitted");
441
- for (const body of node.querySelectorAll<HTMLElement>('[data-hkm-density="presentation"]')) body.style.removeProperty("--hkm-terminal-presentation-size");
480
+ for (const body of presentationBodies(node)) body.style.removeProperty("--hkm-terminal-presentation-size");
481
+ for (const frame of fillFrames(node)) frame.removeAttribute("data-hkm-fill-frame");
442
482
  };
443
483
  }, [fit, source, variation, minimumHeight]);
444
484
  return stage;
package/src/mockups.css CHANGED
@@ -2533,35 +2533,37 @@
2533
2533
  .hkm-showcase.hkm-steps .hkm-step-panel .hkm-root { border-start-start-radius: 0; border-start-end-radius: 0; box-shadow: none; }
2534
2534
  /* Fill uses the tallest complete slide as its natural height. It does not
2535
2535
  * scale text or ask fitted font sizes to determine the stage height. */
2536
- .hkm-showcase[data-hkm-fit="fill"] [data-hkm-measuring] .hkm-page { max-block-size: none; }
2536
+ .hkm-showcase[data-hkm-fit="fill"] [data-hkm-measuring] [data-hkm-fill-frame] > .hkm-window > .hkm-page { max-block-size: none; }
2537
2537
 
2538
2538
  .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) {
2539
2539
  block-size: var(--hkm-showcase-fill-height, auto);
2540
2540
  }
2541
2541
 
2542
- .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) :is(.hkm-step-panel, .hkm-mode-surface) {
2542
+ .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) > :is(.hkm-step-panel, .hkm-mode-surface) {
2543
2543
  align-self: stretch;
2544
2544
  min-block-size: 0;
2545
2545
  }
2546
2546
 
2547
- .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) :is(.hkm-fit, .hkm-fit-inner, .hkm-root, .hkm-root > .hkm-window) {
2547
+ .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) > :is(.hkm-step-panel, .hkm-mode-surface) > .hkm-fit,
2548
+ .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) > :is(.hkm-step-panel, .hkm-mode-surface) > .hkm-fit > .hkm-fit-inner,
2549
+ .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) :is([data-hkm-fill-frame], [data-hkm-fill-frame] > .hkm-window) {
2548
2550
  block-size: 100%;
2549
2551
  min-block-size: 0;
2550
2552
  }
2551
2553
 
2552
- .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) .hkm-window:has(> .hkm-page) {
2554
+ .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) [data-hkm-fill-frame] > .hkm-window:has(> .hkm-page) {
2553
2555
  display: flex;
2554
2556
  flex-direction: column;
2555
2557
  }
2556
2558
 
2557
- .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) .hkm-window > .hkm-page {
2559
+ .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) [data-hkm-fill-frame] > .hkm-window > .hkm-page {
2558
2560
  flex: 1 1 auto;
2559
2561
  block-size: auto;
2560
2562
  min-block-size: 0;
2561
2563
  max-block-size: none;
2562
2564
  }
2563
2565
 
2564
- .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) :is(.hkm-title-bar, .hkm-browser-bar) {
2566
+ .hkm-showcase[data-hkm-fit="fill"] :is(.hkm-step-stage, .hkm-mode-stage):not([data-hkm-measuring]) [data-hkm-fill-frame] > .hkm-window > :is(.hkm-title-bar, .hkm-browser-bar) {
2565
2567
  flex: none;
2566
2568
  }
2567
2569
 
@@ -1710,8 +1710,9 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
1710
1710
  }
1711
1711
 
1712
1712
  .hraness-marketing-card[data-layout="icon"] {
1713
- display: grid;
1714
- grid-template-columns: 3.5rem minmax(0, 1fr);
1713
+ display: flex;
1714
+ flex-direction: row;
1715
+ flex-wrap: wrap;
1715
1716
  align-items: center;
1716
1717
  gap: 1rem;
1717
1718
  padding: 1.125rem;
@@ -1724,6 +1725,7 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
1724
1725
 
1725
1726
  .hraness-marketing-card__icon {
1726
1727
  display: flex;
1728
+ flex: 0 0 3.5rem;
1727
1729
  align-items: center;
1728
1730
  justify-content: center;
1729
1731
  inline-size: 3.5rem;
@@ -1740,6 +1742,7 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
1740
1742
 
1741
1743
  .hraness-marketing-card__copy {
1742
1744
  display: grid;
1745
+ flex: 1 1 10rem;
1743
1746
  align-content: center;
1744
1747
  min-inline-size: 0;
1745
1748
  gap: 0.35rem;
@@ -2502,8 +2502,9 @@ export const marketingStyles = stylex.create({
2502
2502
  "text-decoration": "none"
2503
2503
  },
2504
2504
  "cardIcon": {
2505
- "display": "grid",
2506
- "grid-template-columns": "3.5rem minmax(0, 1fr)",
2505
+ "display": "flex",
2506
+ "flex-direction": "row",
2507
+ "flex-wrap": "wrap",
2507
2508
  "align-items": "center",
2508
2509
  "gap": "1rem",
2509
2510
  "padding": "1.125rem",
@@ -2518,6 +2519,9 @@ export const marketingStyles = stylex.create({
2518
2519
  },
2519
2520
  "card__icon": {
2520
2521
  "display": "flex",
2522
+ "flex-grow": "0",
2523
+ "flex-shrink": "0",
2524
+ "flex-basis": "3.5rem",
2521
2525
  "align-items": "center",
2522
2526
  "justify-content": "center",
2523
2527
  "inline-size": "3.5rem",
@@ -2527,6 +2531,9 @@ export const marketingStyles = stylex.create({
2527
2531
  },
2528
2532
  "card__copy": {
2529
2533
  "display": "grid",
2534
+ "flex-grow": "1",
2535
+ "flex-shrink": "1",
2536
+ "flex-basis": "10rem",
2530
2537
  "align-content": "center",
2531
2538
  "min-inline-size": "0",
2532
2539
  "gap": "0.35rem"