@jfrader/storybook-docs 0.5.0 → 0.6.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
@@ -106,16 +106,20 @@
106
106
  .toc {
107
107
  display: none;
108
108
  }
109
+ .toc-track {
110
+ display: none;
111
+ }
109
112
  @media (min-width: 1180px) {
110
113
  .app.has-toc {
111
114
  grid-template-columns: 276px minmax(0, 1fr) 224px;
112
115
  }
113
- .toc {
116
+ .toc-track {
114
117
  display: block;
115
118
  grid-column: 3;
116
119
  grid-row: 2;
117
- align-self: start;
118
- transform: translateY(var(--sd-rail-follow, 0px));
120
+ }
121
+ .toc {
122
+ display: block;
119
123
  padding: 34px 18px 40px 6px;
120
124
  font-size: 12.5px;
121
125
  }
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 { Children, useEffect as useEffect8, 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
 
@@ -511,6 +511,77 @@ function LazySection({ children, placeholder = "\u2026" }) {
511
511
 
512
512
  // src/toc.tsx
513
513
  import { useCallback, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
514
+
515
+ // src/rail.ts
516
+ var RAIL_MEDIA = "(min-width: 1180px)";
517
+ var RAIL_GAP = 20;
518
+ function followRail(rail, header) {
519
+ const track = rail.parentElement;
520
+ if (!track) return () => {
521
+ };
522
+ const media = window.matchMedia(RAIL_MEDIA);
523
+ let lastY = window.scrollY;
524
+ let direction = "down";
525
+ let spacer = 0;
526
+ const geometry = () => {
527
+ const top = header.offsetHeight + RAIL_GAP;
528
+ const viewportBottom = window.innerHeight - RAIL_GAP;
529
+ const height = rail.offsetHeight;
530
+ return {
531
+ top,
532
+ viewportBottom,
533
+ height,
534
+ fits: top + height <= viewportBottom,
535
+ trackTop: track.getBoundingClientRect().top + window.scrollY,
536
+ maxOffset: Math.max(0, track.clientHeight - height)
537
+ };
538
+ };
539
+ const clear = () => {
540
+ rail.removeAttribute("style");
541
+ track.removeAttribute("style");
542
+ };
543
+ const arm = (g = geometry()) => {
544
+ if (!media.matches) {
545
+ clear();
546
+ return;
547
+ }
548
+ spacer = g.fits ? 0 : Math.min(spacer, g.maxOffset);
549
+ track.style.setProperty("--sd-rail-offset", `${spacer}px`);
550
+ rail.style.position = "sticky";
551
+ rail.style.top = g.fits ? `${g.top}px` : direction === "down" ? `${g.viewportBottom - g.height}px` : "auto";
552
+ rail.style.bottom = g.fits || direction === "down" ? "auto" : `${window.innerHeight - g.top - g.height}px`;
553
+ };
554
+ const onScroll = () => {
555
+ const y = window.scrollY;
556
+ const next = y > lastY ? "down" : y < lastY ? "up" : direction;
557
+ if (next !== direction && media.matches) {
558
+ const g = geometry();
559
+ 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);
560
+ direction = next;
561
+ arm(g);
562
+ }
563
+ direction = next;
564
+ lastY = y;
565
+ };
566
+ const relayout = () => arm();
567
+ const observer = new ResizeObserver(relayout);
568
+ observer.observe(rail);
569
+ observer.observe(track);
570
+ observer.observe(header);
571
+ window.addEventListener("scroll", onScroll, { passive: true });
572
+ window.addEventListener("resize", relayout);
573
+ media.addEventListener("change", relayout);
574
+ arm();
575
+ return () => {
576
+ observer.disconnect();
577
+ window.removeEventListener("scroll", onScroll);
578
+ window.removeEventListener("resize", relayout);
579
+ media.removeEventListener("change", relayout);
580
+ clear();
581
+ };
582
+ }
583
+
584
+ // src/toc.tsx
514
585
  import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
515
586
  var STORY_PARAM = "story";
516
587
  var SCROLL_OFFSET_PX = 76;
@@ -535,34 +606,9 @@ var slugify = (text) => text.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCa
535
606
  function useRailFollow(ref, ready = true) {
536
607
  useEffect3(() => {
537
608
  const element = ref.current;
538
- if (!element) return;
539
- let frame = 0;
540
- let applied = 0;
541
- const limit = () => window.innerHeight - (Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--sd-header-height")) || 56) - 20;
542
- const apply = () => {
543
- frame = 0;
544
- const bottomInDocument = element.getBoundingClientRect().bottom + window.scrollY - applied;
545
- const crossing = bottomInDocument - limit();
546
- const next = Math.max(0, window.scrollY - crossing);
547
- if (next !== applied) {
548
- applied = next;
549
- element.style.setProperty("--sd-rail-follow", `${next}px`);
550
- }
551
- };
552
- const schedule = () => {
553
- if (!frame) frame = requestAnimationFrame(apply);
554
- };
555
- apply();
556
- window.addEventListener("scroll", schedule, { passive: true });
557
- window.addEventListener("resize", schedule);
558
- const observer = new ResizeObserver(schedule);
559
- observer.observe(element);
560
- return () => {
561
- window.removeEventListener("scroll", schedule);
562
- window.removeEventListener("resize", schedule);
563
- observer.disconnect();
564
- if (frame) cancelAnimationFrame(frame);
565
- };
609
+ const header = element?.closest(".app")?.querySelector(".top");
610
+ if (!element || !header) return;
611
+ return followRail(element, header);
566
612
  }, [ready, ref]);
567
613
  }
568
614
  function scanPage(root, pageKey) {
@@ -706,7 +752,7 @@ function PageRail({ pageKey }) {
706
752
  const active = useActiveIds(scan);
707
753
  const go = useCallback((element) => scrollToElement(element), []);
708
754
  if (!scan) return null;
709
- return /* @__PURE__ */ jsx5(Fragment, { children: scan.sections.length > 0 ? /* @__PURE__ */ jsxs3("aside", { className: "toc", "aria-label": "On this page", ref: railRef, children: [
755
+ 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: [
710
756
  /* @__PURE__ */ jsx5("div", { className: "toc-title", children: "On this page" }),
711
757
  /* @__PURE__ */ jsx5("ul", { children: scan.sections.map((section) => /* @__PURE__ */ jsxs3("li", { children: [
712
758
  /* @__PURE__ */ jsx5(
@@ -731,7 +777,7 @@ function PageRail({ pageKey }) {
731
777
  }
732
778
  ) }, story.id)) }) : null
733
779
  ] }, section.id)) })
734
- ] }) : null });
780
+ ] }) }) : null });
735
781
  }
736
782
 
737
783
  // src/component-demo.tsx
@@ -1801,6 +1847,23 @@ function SearchBox() {
1801
1847
  import { releases as releases2 } from "virtual:storybook-docs/releases";
1802
1848
  import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1803
1849
  var routeHref = (path) => `#${hrefFor(path)}`;
1850
+ var NAV_GROUP_WINDOW = 12;
1851
+ function NavGroup({ id, title, active, activeIndex = -1, children }) {
1852
+ const [open, setOpen] = useState9(false);
1853
+ const [showAll, setShowAll] = useState9(false);
1854
+ const expanded = active || open;
1855
+ const links = Children.toArray(children);
1856
+ const capped = active && links.length > NAV_GROUP_WINDOW;
1857
+ const start2 = Math.min(Math.max(activeIndex - NAV_GROUP_WINDOW / 2, 0), Math.max(0, links.length - NAV_GROUP_WINDOW));
1858
+ const visibleLinks = capped && !showAll ? links.slice(start2, start2 + NAV_GROUP_WINDOW) : links;
1859
+ return /* @__PURE__ */ jsxs13("div", { role: "group", "aria-labelledby": id, children: [
1860
+ /* @__PURE__ */ jsx16("button", { type: "button", className: "grp", id, "aria-expanded": expanded, "aria-controls": `${id}-links`, disabled: active, onClick: () => setOpen(!open), children: title }),
1861
+ /* @__PURE__ */ jsxs13("div", { id: `${id}-links`, hidden: !expanded, children: [
1862
+ /* @__PURE__ */ jsx16("div", { id: `${id}-window`, children: visibleLinks }),
1863
+ capped ? /* @__PURE__ */ jsx16("button", { type: "button", className: "nav-more", "aria-expanded": showAll, "aria-controls": `${id}-window`, onClick: () => setShowAll(!showAll), children: showAll ? "Show less" : `Show all ${links.length}` }) : null
1864
+ ] })
1865
+ ] });
1866
+ }
1804
1867
  function parseRoute(hash) {
1805
1868
  const path = hashPath(hash).replace(new RegExp(`^#?\\/?${STORYBOOK_DOCS_BASE}`), "").replace(/^\/+/, "");
1806
1869
  if (path.startsWith("components/")) {
@@ -1909,7 +1972,7 @@ function DocsApp() {
1909
1972
  /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", children: "\u2197" })
1910
1973
  ] }, link.href))
1911
1974
  ] }),
1912
- /* @__PURE__ */ jsxs13("nav", { id: "docs-nav", className: navOpen ? "nav open" : "nav", "aria-label": "Documentation", ref: navRef, children: [
1975
+ /* @__PURE__ */ jsx16("div", { className: "rail-track nav-track", children: /* @__PURE__ */ jsxs13("nav", { id: "docs-nav", className: navOpen ? "nav open" : "nav", "aria-label": "Documentation", ref: navRef, children: [
1913
1976
  /* @__PURE__ */ jsx16("a", { href: routeHref("/"), className: route.kind === "overview" ? "on" : void 0, "aria-current": route.kind === "overview" ? "page" : void 0, children: "Overview" }),
1914
1977
  releases2.changelog ? /* @__PURE__ */ jsxs13(
1915
1978
  "a",
@@ -1926,22 +1989,20 @@ function DocsApp() {
1926
1989
  ]
1927
1990
  }
1928
1991
  ) : null,
1929
- foundations.length > 0 ? /* @__PURE__ */ jsxs13("div", { role: "group", "aria-labelledby": "nav-grp-foundations", children: [
1930
- /* @__PURE__ */ jsx16("div", { className: "grp", id: "nav-grp-foundations", children: "Foundations" }),
1931
- foundations.map((item) => /* @__PURE__ */ jsx16(
1932
- "a",
1933
- {
1934
- href: item.href,
1935
- className: route.kind === item.kind ? "on" : void 0,
1936
- "aria-current": route.kind === item.kind ? "page" : void 0,
1937
- children: item.label
1938
- },
1939
- item.href
1940
- ))
1941
- ] }) : null,
1942
- groups2.map((group, index) => /* @__PURE__ */ jsxs13("div", { role: "group", "aria-labelledby": `nav-grp-${index}`, children: [
1943
- /* @__PURE__ */ jsx16("div", { className: "grp", id: `nav-grp-${index}`, children: group }),
1944
- catalog3.filter((entry) => entry.group === group).map((entry) => /* @__PURE__ */ jsx16(
1992
+ foundations.length > 0 ? /* @__PURE__ */ jsx16(NavGroup, { id: "nav-grp-foundations", title: "Foundations", active: foundations.some((item) => route.kind === item.kind), children: foundations.map((item) => /* @__PURE__ */ jsx16(
1993
+ "a",
1994
+ {
1995
+ href: item.href,
1996
+ className: route.kind === item.kind ? "on" : void 0,
1997
+ "aria-current": route.kind === item.kind ? "page" : void 0,
1998
+ children: item.label
1999
+ },
2000
+ item.href
2001
+ )) }, `foundations-${activeSlug ?? route.kind}`) : null,
2002
+ groups2.map((group, index) => {
2003
+ const entries = catalog3.filter((entry) => entry.group === group);
2004
+ const activeIndex = entries.findIndex((entry) => entry.slug === activeSlug);
2005
+ return /* @__PURE__ */ jsx16(NavGroup, { id: `nav-grp-${index}`, title: group, active: activeIndex !== -1, activeIndex, children: entries.map((entry) => /* @__PURE__ */ jsx16(
1945
2006
  "a",
1946
2007
  {
1947
2008
  href: routeHref(`/components/${entry.slug}`),
@@ -1952,9 +2013,9 @@ function DocsApp() {
1952
2013
  children: entry.name
1953
2014
  },
1954
2015
  entry.slug
1955
- ))
1956
- ] }, group))
1957
- ] }),
2016
+ )) }, `${group}-${activeSlug ?? route.kind}`);
2017
+ })
2018
+ ] }) }),
1958
2019
  /* @__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, {}) }),
1959
2020
  route.kind !== "overview" ? /* @__PURE__ */ jsx16(PageRail, { pageKey: activeSlug ?? route.kind }) : null
1960
2021
  ] });
package/dist/rail.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function followRail(rail: HTMLElement, header: HTMLElement): () => void;
package/dist/styles.css CHANGED
@@ -186,23 +186,44 @@ body {
186
186
  outline-offset: 2px;
187
187
  }
188
188
 
189
+ .rail-track {
190
+ overflow-anchor: none;
191
+ min-width: 0;
192
+ }
193
+ @media (min-width: 1180px) {
194
+ .rail-track::before {
195
+ content: '';
196
+ display: block;
197
+ height: var(--sd-rail-offset, 0px);
198
+ }
199
+ }
189
200
  .nav {
190
201
  border-right: 0;
191
202
  background: var(--sd-surface);
192
- padding: 14px 12px 80px;
193
- /* Follows the document like the right rail, parking when its own end is visible. */
194
- align-self: start;
195
- transform: translateY(var(--sd-rail-follow, 0px));
203
+ padding: 14px 12px 12px;
196
204
  }
197
- .nav a {
198
- display: block;
199
- padding: 6px 10px;
205
+ .nav a,
206
+ .nav-more {
207
+ display: flex;
208
+ align-items: center;
209
+ box-sizing: border-box;
210
+ min-height: 28px;
211
+ padding: 4px 10px;
200
212
  border-radius: 8px;
201
213
  color: var(--sd-text-muted);
202
214
  font-size: 13.5px;
203
215
  text-decoration: none;
204
216
  }
205
- .nav a:hover {
217
+ .nav-more {
218
+ width: 100%;
219
+ background: none;
220
+ border: 0;
221
+ font-family: inherit;
222
+ cursor: pointer;
223
+ text-align: left;
224
+ }
225
+ .nav a:hover,
226
+ .nav-more:hover {
206
227
  background: var(--sd-surface-hover);
207
228
  color: var(--sd-text);
208
229
  }
@@ -212,13 +233,26 @@ body {
212
233
  font-weight: 600;
213
234
  }
214
235
  .grp {
215
- margin: 16px 0 4px;
236
+ margin: 3px 0 2px;
216
237
  padding: 0 10px;
217
238
  font-size: 10.5px;
218
239
  letter-spacing: 0.09em;
219
240
  text-transform: uppercase;
220
241
  color: var(--sd-text-muted);
221
242
  font-weight: 700;
243
+ display: block;
244
+ width: 100%;
245
+ text-align: left;
246
+ font-family: inherit;
247
+ background: none;
248
+ border: 0;
249
+ cursor: pointer;
250
+ min-height: 28px;
251
+ }
252
+ .grp:focus-visible,
253
+ .nav-more:focus-visible {
254
+ outline: 2px solid var(--sd-focus-ring);
255
+ outline-offset: 2px;
222
256
  }
223
257
 
224
258
  .main {
@@ -618,6 +652,9 @@ code.type {
618
652
  .nav-toggle {
619
653
  display: inline-block;
620
654
  }
655
+ .nav-track {
656
+ display: contents;
657
+ }
621
658
  .search {
622
659
  order: 10;
623
660
  flex: 1 1 100%;
package/dist/toc.d.ts CHANGED
@@ -6,12 +6,6 @@ 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
- /**
10
- * A rail travels with the document until its own end would pass the fold, then it parks there —
11
- * the reader keeps seeing where it continues, and scrolling up lets it fall back into flow. This is
12
- * the scroll-linked translation docs sites use, because `position: sticky` cannot move an element
13
- * taller than the viewport.
14
- */
15
9
  export declare function useRailFollow(ref: RefObject<HTMLElement | null>, ready?: boolean): void;
16
10
  /** The "On this page" rail and the `?story=` deep-link landing. */
17
11
  export declare function PageRail({ pageKey }: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jfrader/storybook-docs",
3
- "version": "0.5.0",
3
+ "version": "0.6.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": {