@jfrader/storybook-docs 0.8.0 → 0.9.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/dist/index.css CHANGED
@@ -115,11 +115,13 @@
115
115
  }
116
116
  .toc-track {
117
117
  display: block;
118
+ min-width: 0;
118
119
  grid-column: 3;
119
120
  grid-row: 2;
120
121
  }
121
122
  .toc {
122
123
  display: block;
124
+ min-width: 0;
123
125
  padding: 34px 18px 40px 6px;
124
126
  font-size: 12.5px;
125
127
  }
@@ -140,9 +142,12 @@
140
142
  border-left: 1px solid var(--sd-border);
141
143
  }
142
144
  .toc-link {
143
- display: flex;
144
- align-items: center;
145
+ display: block;
145
146
  width: 100%;
147
+ min-width: 0;
148
+ overflow: hidden;
149
+ white-space: nowrap;
150
+ text-overflow: ellipsis;
146
151
  min-block-size: 24px;
147
152
  padding: 4px 8px;
148
153
  font: inherit;
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { createRoot } from "react-dom/client";
5
5
  import { site as site3, themeCss, theme as theme2 } from "virtual:storybook-docs/site";
6
6
 
7
7
  // src/app.tsx
8
- import { useEffect as useEffect8, useRef as useRef5, useState as useState9 } from "react";
8
+ import { useEffect as useEffect8, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState9 } from "react";
9
9
  import { catalog as catalog3, groups as groups2 } from "virtual:storybook-docs/catalog";
10
10
  import { pages, site as site2, theme } from "virtual:storybook-docs/site";
11
11
 
@@ -522,11 +522,12 @@ function LazySection({ children, placeholder = "\u2026" }) {
522
522
  }
523
523
 
524
524
  // src/toc.tsx
525
- import { useCallback, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
525
+ import { useCallback, useEffect as useEffect3, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
526
526
 
527
527
  // src/rail.ts
528
528
  var RAIL_MEDIA = "(min-width: 1180px)";
529
529
  var RAIL_GAP = 20;
530
+ var RAIL_SETTLE_MS = 120;
530
531
  function followRail(rail, header) {
531
532
  const track = rail.parentElement;
532
533
  if (!track) return () => {
@@ -535,6 +536,8 @@ function followRail(rail, header) {
535
536
  let lastY = window.scrollY;
536
537
  let direction = "down";
537
538
  let spacer = 0;
539
+ let scrolling = false;
540
+ let settleTimer;
538
541
  const geometry = () => {
539
542
  const top = header.offsetHeight + RAIL_GAP;
540
543
  const viewportBottom = window.innerHeight - RAIL_GAP;
@@ -544,8 +547,8 @@ function followRail(rail, header) {
544
547
  viewportBottom,
545
548
  height,
546
549
  fits: top + height <= viewportBottom,
547
- trackTop: track.getBoundingClientRect().top + window.scrollY,
548
- maxOffset: Math.max(0, track.clientHeight - height)
550
+ trackTop: track.getBoundingClientRect().top + window.scrollY + RAIL_GAP,
551
+ maxOffset: Math.max(0, track.clientHeight - height - RAIL_GAP)
549
552
  };
550
553
  };
551
554
  const clear = () => {
@@ -558,17 +561,28 @@ function followRail(rail, header) {
558
561
  return;
559
562
  }
560
563
  spacer = g.fits ? 0 : Math.min(spacer, g.maxOffset);
564
+ track.style.setProperty("--sd-rail-start", `${RAIL_GAP}px`);
561
565
  track.style.setProperty("--sd-rail-offset", `${spacer}px`);
562
566
  rail.style.position = "sticky";
563
- rail.style.top = g.fits ? `${g.top}px` : direction === "down" ? `${g.viewportBottom - g.height}px` : "auto";
567
+ const parkedTop = g.fits ? g.top : g.viewportBottom - g.height;
568
+ const armedTop = Number.parseFloat(rail.style.top);
569
+ const stableTop = scrolling && Number.isFinite(armedTop) ? Math.min(armedTop, parkedTop) : parkedTop;
570
+ rail.style.top = g.fits || direction === "down" ? `${stableTop}px` : "auto";
564
571
  rail.style.bottom = g.fits || direction === "down" ? "auto" : `${window.innerHeight - g.top - g.height}px`;
565
572
  };
566
573
  const onScroll = () => {
567
574
  const y = window.scrollY;
575
+ scrolling = true;
576
+ window.clearTimeout(settleTimer);
577
+ settleTimer = window.setTimeout(() => {
578
+ scrolling = false;
579
+ arm();
580
+ }, RAIL_SETTLE_MS);
568
581
  const next = y > lastY ? "down" : y < lastY ? "up" : direction;
569
582
  if (next !== direction && media.matches) {
570
583
  const g = geometry();
571
- spacer = direction === "down" ? Math.min(Math.max(spacer, lastY + g.viewportBottom - g.height - g.trackTop), g.maxOffset) : Math.max(Math.min(spacer, lastY + g.top - g.trackTop), 0);
584
+ const armedTop = Number.parseFloat(rail.style.top);
585
+ spacer = direction === "down" ? Math.min(Math.max(spacer, lastY + (Number.isFinite(armedTop) ? armedTop : g.viewportBottom - g.height) - g.trackTop), g.maxOffset) : Math.max(Math.min(spacer, lastY + g.top - g.trackTop), 0);
572
586
  direction = next;
573
587
  arm(g);
574
588
  }
@@ -586,6 +600,7 @@ function followRail(rail, header) {
586
600
  arm();
587
601
  return () => {
588
602
  observer.disconnect();
603
+ window.clearTimeout(settleTimer);
589
604
  window.removeEventListener("scroll", onScroll);
590
605
  window.removeEventListener("resize", relayout);
591
606
  media.removeEventListener("change", relayout);
@@ -615,13 +630,13 @@ function storyLink(location, story) {
615
630
  return `${base}${hashPath(location.hash)}?${new URLSearchParams({ [STORY_PARAM]: story })}`;
616
631
  }
617
632
  var slugify = (text) => text.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
618
- function useRailFollow(ref, ready = true) {
619
- useEffect3(() => {
633
+ function useRailFollow(ref, ready = true, pageKey) {
634
+ useLayoutEffect(() => {
620
635
  const element = ref.current;
621
636
  const header = element?.closest(".app")?.querySelector(".top");
622
- if (!element || !header) return;
637
+ if (!ready || !element || !header) return;
623
638
  return followRail(element, header);
624
- }, [ready, ref]);
639
+ }, [ready, ref, pageKey]);
625
640
  }
626
641
  function scanPage(root, pageKey) {
627
642
  const used = /* @__PURE__ */ new Set();
@@ -677,11 +692,14 @@ function scanPage(root, pageKey) {
677
692
  var signature = (scan) => `${scan.pageKey}|${scan.sections.map((s) => `${s.id}(${s.subs.map((sub) => sub.id).join("+")}):${s.stories.map((st) => `${st.id}:${st.name}`).join(",")}`).join(";")}`;
678
693
  var prefersReducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
679
694
  function scrollToElement(element) {
695
+ const hash = window.location.hash;
680
696
  element.scrollIntoView({ behavior: prefersReducedMotion() ? "auto" : "smooth", block: "start" });
681
697
  let ticks = SETTLE_TICKS;
682
698
  let lastY = window.scrollY;
699
+ let timer;
700
+ let cancelled = false;
683
701
  const settle = () => {
684
- if (!element.isConnected || ticks === 0) return;
702
+ if (cancelled || window.location.hash !== hash || !element.isConnected || ticks === 0) return;
685
703
  ticks -= 1;
686
704
  const moving = window.scrollY !== lastY;
687
705
  lastY = window.scrollY;
@@ -689,9 +707,13 @@ function scrollToElement(element) {
689
707
  element.scrollIntoView({ behavior: "auto", block: "start" });
690
708
  lastY = window.scrollY;
691
709
  }
692
- window.setTimeout(settle, SETTLE_TICK_MS);
710
+ timer = window.setTimeout(settle, SETTLE_TICK_MS);
711
+ };
712
+ timer = window.setTimeout(settle, SETTLE_TICK_MS);
713
+ return () => {
714
+ cancelled = true;
715
+ window.clearTimeout(timer);
693
716
  };
694
- window.setTimeout(settle, SETTLE_TICK_MS);
695
717
  }
696
718
  function useActiveIds(scan) {
697
719
  const [active, setActive] = useState3({ section: null, story: null });
@@ -721,6 +743,12 @@ function PageRail({ pageKey }) {
721
743
  pageKeyRef.current = pageKey;
722
744
  const [target, setTarget] = useState3(() => storyFromHash(window.location.hash));
723
745
  const flashed = useRef2(null);
746
+ const cancelScroll = useRef2(null);
747
+ const go = useCallback((element) => {
748
+ cancelScroll.current?.();
749
+ cancelScroll.current = scrollToElement(element);
750
+ }, []);
751
+ useEffect3(() => () => cancelScroll.current?.(), []);
724
752
  useEffect3(() => {
725
753
  const main = document.querySelector(".main");
726
754
  if (!main) return;
@@ -752,17 +780,16 @@ function PageRail({ pageKey }) {
752
780
  if (!target || !scan || scan.pageKey !== pageKey) return;
753
781
  const match = scan.frames.find((frame) => frame.story === target) ?? scan.frames.find((frame) => frame.story.toLowerCase() === target.toLowerCase()) ?? scan.frames.find((frame) => frame.name === target) ?? scan.frames.find((frame) => frame.name.toLowerCase() === target.toLowerCase());
754
782
  if (!match) return;
755
- scrollToElement(match.element);
783
+ go(match.element);
756
784
  flashed.current?.classList.remove("toc-flash");
757
785
  match.element.classList.add("toc-flash");
758
786
  flashed.current = match.element;
759
787
  window.setTimeout(() => match.element.classList.remove("toc-flash"), FLASH_MS);
760
788
  setTarget(null);
761
- }, [target, scan, pageKey]);
789
+ }, [target, scan, pageKey, go]);
762
790
  const railRef = useRef2(null);
763
791
  useRailFollow(railRef, scan !== null);
764
792
  const active = useActiveIds(scan);
765
- const go = useCallback((element) => scrollToElement(element), []);
766
793
  if (!scan) return null;
767
794
  return /* @__PURE__ */ jsx5(Fragment, { children: scan.sections.length > 0 ? /* @__PURE__ */ jsx5("div", { className: "rail-track toc-track", children: /* @__PURE__ */ jsxs3("aside", { className: "toc", "aria-label": "On this page", ref: railRef, children: [
768
795
  /* @__PURE__ */ jsx5("div", { className: "toc-title", children: "On this page" }),
@@ -772,17 +799,19 @@ function PageRail({ pageKey }) {
772
799
  {
773
800
  type: "button",
774
801
  className: "toc-link",
802
+ title: section.title,
775
803
  "aria-current": active.section === section.id ? "location" : void 0,
776
804
  onClick: () => go(section.heading),
777
805
  children: section.title
778
806
  }
779
807
  ),
780
- section.subs.length > 0 ? /* @__PURE__ */ jsx5("ul", { children: section.subs.map((sub) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5("button", { type: "button", className: "toc-link toc-sub", onClick: () => go(sub.element), children: sub.title }) }, sub.id)) }) : null,
808
+ section.subs.length > 0 ? /* @__PURE__ */ jsx5("ul", { children: section.subs.map((sub) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5("button", { type: "button", className: "toc-link toc-sub", title: sub.title, onClick: () => go(sub.element), children: sub.title }) }, sub.id)) }) : null,
781
809
  section.stories.length > 0 ? /* @__PURE__ */ jsx5("ul", { children: section.stories.map((story) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(
782
810
  "button",
783
811
  {
784
812
  type: "button",
785
813
  className: "toc-link toc-story",
814
+ title: story.name,
786
815
  "aria-current": active.story === story.id ? "location" : void 0,
787
816
  onClick: () => go(story.element),
788
817
  children: story.name
@@ -1885,9 +1914,20 @@ function DocsApp() {
1885
1914
  const [mode, setMode] = useState9(theme.values?.[0] ?? "dark");
1886
1915
  const [navOpen, setNavOpen] = useState9(false);
1887
1916
  const navRef = useRef5(null);
1888
- useRailFollow(navRef);
1917
+ const pageKey = route.kind === "component" ? `components/${route.slug}` : route.kind;
1889
1918
  const headerRef = useRef5(null);
1890
1919
  const mainRef = useRef5(null);
1920
+ const renderedPage = useRef5(pageKey);
1921
+ useLayoutEffect2(() => {
1922
+ if (renderedPage.current !== pageKey) {
1923
+ const reset = () => window.scrollTo({ top: 0, left: 0, behavior: "instant" });
1924
+ reset();
1925
+ const frame = requestAnimationFrame(reset);
1926
+ renderedPage.current = pageKey;
1927
+ return () => cancelAnimationFrame(frame);
1928
+ }
1929
+ }, [pageKey]);
1930
+ useRailFollow(navRef, true, pageKey);
1891
1931
  useEffect8(() => {
1892
1932
  const header = headerRef.current;
1893
1933
  if (!header) return;
@@ -1907,7 +1947,6 @@ function DocsApp() {
1907
1947
  const onHash = () => {
1908
1948
  setRoute(parseRoute(window.location.hash));
1909
1949
  setNavOpen(false);
1910
- window.scrollTo(0, 0);
1911
1950
  mainRef.current?.focus({ preventScroll: true });
1912
1951
  };
1913
1952
  window.addEventListener("hashchange", onHash);
@@ -2019,8 +2058,8 @@ function DocsApp() {
2019
2058
  )) }, `${group}-${activeSlug ?? route.kind}`);
2020
2059
  })
2021
2060
  ] }) }),
2022
- /* @__PURE__ */ jsx16("main", { id: "main", className: "main", tabIndex: -1, ref: mainRef, children: route.kind === "component" ? /* @__PURE__ */ jsx16(ComponentPage, { entry: catalog3.find((entry) => entry.slug === route.slug) }) : route.kind === "color" ? /* @__PURE__ */ jsx16(ColorPage, {}) : route.kind === "typography" ? /* @__PURE__ */ jsx16(TypePage, {}) : route.kind === "spacing" ? /* @__PURE__ */ jsx16(SpacingPage, {}) : route.kind === "effects" ? /* @__PURE__ */ jsx16(EffectsPage, {}) : route.kind === "releases" ? /* @__PURE__ */ jsx16(ReleasesPage, {}) : /* @__PURE__ */ jsx16(OverviewPage, {}) }),
2023
- route.kind !== "overview" ? /* @__PURE__ */ jsx16(PageRail, { pageKey: activeSlug ?? route.kind }) : null
2061
+ /* @__PURE__ */ jsx16("main", { id: "main", className: "main", tabIndex: -1, ref: mainRef, children: route.kind === "component" ? /* @__PURE__ */ jsx16(ComponentPage, { entry: catalog3.find((entry) => entry.slug === route.slug) }, pageKey) : route.kind === "color" ? /* @__PURE__ */ jsx16(ColorPage, {}) : route.kind === "typography" ? /* @__PURE__ */ jsx16(TypePage, {}) : route.kind === "spacing" ? /* @__PURE__ */ jsx16(SpacingPage, {}) : route.kind === "effects" ? /* @__PURE__ */ jsx16(EffectsPage, {}) : route.kind === "releases" ? /* @__PURE__ */ jsx16(ReleasesPage, {}) : /* @__PURE__ */ jsx16(OverviewPage, {}) }),
2062
+ route.kind !== "overview" ? /* @__PURE__ */ jsx16(PageRail, { pageKey }, pageKey) : null
2024
2063
  ] });
2025
2064
  }
2026
2065
 
package/dist/styles.css CHANGED
@@ -194,7 +194,7 @@ body {
194
194
  .rail-track::before {
195
195
  content: '';
196
196
  display: block;
197
- height: var(--sd-rail-offset, 0px);
197
+ height: calc(var(--sd-rail-start, 0px) + var(--sd-rail-offset, 0px));
198
198
  }
199
199
  }
200
200
  .nav {
package/dist/toc.d.ts CHANGED
@@ -6,7 +6,7 @@ export declare function hashPath(hash: string): string;
6
6
  /** The `?story=` deep-link target of a hash, if any. */
7
7
  export declare function storyFromHash(hash: string): string | null;
8
8
  export declare function storyLink(location: Location, story: string): string;
9
- export declare function useRailFollow(ref: RefObject<HTMLElement | null>, ready?: boolean): void;
9
+ export declare function useRailFollow(ref: RefObject<HTMLElement | null>, ready?: boolean, pageKey?: string): void;
10
10
  /** The "On this page" rail and the `?story=` deep-link landing. */
11
11
  export declare function PageRail({ pageKey }: {
12
12
  pageKey: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jfrader/storybook-docs",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "A documentation front for any design system that has Storybook: navigation, live examples and API tables are generated from the story files themselves.",
6
6
  "exports": {