veslx 0.1.56 → 0.1.58

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.
@@ -25,15 +25,27 @@ function usePreventSwipeNavigation(ref) {
25
25
  }, [ref]);
26
26
  }
27
27
  function getImageLabel(path) {
28
- const filename = path.split("/").pop() || path;
28
+ const cleanPath = path.split(/[?#]/)[0];
29
+ const filename = cleanPath.split("/").pop() || cleanPath;
29
30
  return filename.replace(/\.(png|jpg|jpeg|gif|svg|webp)$/i, "").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim();
30
31
  }
31
- function getImageUrl(path) {
32
+ function isAbsoluteUrl(path) {
33
+ return /^https?:\/\//i.test(path) || path.startsWith("//");
34
+ }
35
+ function joinUrl(baseUrl, path) {
36
+ const trimmedBase = baseUrl.replace(/\/+$/, "");
37
+ const trimmedPath = path.replace(/^\/+/, "");
38
+ return `${trimmedBase}/${trimmedPath}`;
39
+ }
40
+ function getImageUrl(path, baseUrl) {
41
+ if (isAbsoluteUrl(path)) return path;
42
+ if (baseUrl) return joinUrl(baseUrl, path);
32
43
  return `/raw/${path}`;
33
44
  }
34
45
  function Gallery({
35
46
  path,
36
47
  globs = null,
48
+ baseUrl,
37
49
  caption,
38
50
  captionLabel,
39
51
  title,
@@ -53,8 +65,8 @@ function Gallery({
53
65
  const scrollRef = useRef(null);
54
66
  usePreventSwipeNavigation(scrollRef);
55
67
  const images = useMemo(
56
- () => paths.map((p) => ({ src: getImageUrl(p), label: getImageLabel(p) })),
57
- [paths]
68
+ () => paths.map((p) => ({ src: getImageUrl(p, baseUrl), label: getImageLabel(p) })),
69
+ [paths, baseUrl]
58
70
  );
59
71
  if (isLoading) {
60
72
  return /* @__PURE__ */ jsx("figure", { className: "not-prose py-6 md:py-8", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx(
@@ -101,7 +113,7 @@ function Gallery({
101
113
  index
102
114
  );
103
115
  return /* @__PURE__ */ jsxs(Fragment, { children: [
104
- /* @__PURE__ */ jsxs("figure", { className: `not-prose relative py-6 md:py-8 ${shouldBreakOut ? isTwo ? "gallery-breakout w-[75vw] max-w-[var(--gallery-width)]" : isCompact ? "gallery-breakout w-[96vw] max-w-[var(--gallery-width)]" : "gallery-breakout w-[var(--gallery-width)]" : ""}`, children: [
116
+ /* @__PURE__ */ jsxs("figure", { className: `not-prose relative py-6 md:py-8 ${shouldBreakOut ? isTwo ? "gallery-breakout w-[75vw] max-w-[var(--gallery-width)]" : isCompact ? "gallery-breakout w-[96vw] max-w-[var(--gallery-width)]" : "gallery-breakout w-screen" : ""}`, children: [
105
117
  !isSingleWithChildren && !isSingle && /* @__PURE__ */ jsx("div", { className: "max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]", children: /* @__PURE__ */ jsx(FigureHeader, { title, subtitle }) }),
106
118
  isSingleWithChildren ? /* @__PURE__ */ jsxs("div", { className: `grid items-start gap-12 md:gap-16 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] ${childAlign === "left" ? "" : "md:[&>div:first-child]:order-2"}`, children: [
107
119
  /* @__PURE__ */ jsx("div", { className: "min-w-0 self-center text-sm leading-relaxed text-foreground/90 space-y-3 [&>ul]:space-y-1.5 [&>ul]:list-disc [&>ul]:pl-5 [&>ol]:space-y-1.5 [&>ol]:list-decimal [&>ol]:pl-5", children }),
@@ -114,11 +126,11 @@ function Gallery({
114
126
  /* @__PURE__ */ jsx(FigureHeader, { title, subtitle }),
115
127
  imageElement(0, images[0]),
116
128
  /* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
117
- ] }) : isCompact ? /* @__PURE__ */ jsx("div", { className: "flex gap-3", children: images.map((img, index) => imageElement(index, img, "flex-1")) }) : /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "flex gap-3 overflow-x-auto overscroll-x-contain pb-4", children: images.map((img, index) => /* @__PURE__ */ jsx(
129
+ ] }) : isCompact ? /* @__PURE__ */ jsx("div", { className: "flex gap-3", children: images.map((img, index) => imageElement(index, img, "flex-1")) }) : /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "gallery-scroll-row flex gap-3 overflow-x-auto overscroll-x-contain pb-4", children: images.map((img, index) => /* @__PURE__ */ jsx(
118
130
  "div",
119
131
  {
120
132
  title: img.label,
121
- className: "flex-none w-[30%] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group",
133
+ className: "flex-none w-[calc(var(--gallery-width)*0.3)] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group",
122
134
  onClick: () => lightbox.open(index),
123
135
  children: /* @__PURE__ */ jsx(
124
136
  LoadingImage,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/components/gallery/index.tsx"],"sourcesContent":["import { useMemo, useRef, useEffect } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { Lightbox, LightboxImage } from \"@/components/gallery/components/lightbox\";\nimport { useGalleryImages } from \"./hooks/use-gallery-images\";\nimport { useLightbox } from \"./hooks/use-lightbox\";\nimport { LoadingImage } from \"./components/loading-image\";\nimport { FigureHeader } from \"./components/figure-header\";\nimport { FigureCaption } from \"./components/figure-caption\";\n\n/**\n * Hook to prevent horizontal scroll from triggering browser back/forward gestures.\n * Captures wheel events and prevents default when at scroll boundaries.\n */\nfunction usePreventSwipeNavigation(ref: React.RefObject<HTMLElement | null>) {\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const handleWheel = (e: WheelEvent) => {\n // Only handle horizontal scrolling\n if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;\n\n const { scrollLeft, scrollWidth, clientWidth } = el;\n const atLeftEdge = scrollLeft <= 0;\n const atRightEdge = scrollLeft + clientWidth >= scrollWidth - 1;\n\n // Prevent default if trying to scroll past boundaries\n if ((atLeftEdge && e.deltaX < 0) || (atRightEdge && e.deltaX > 0)) {\n e.preventDefault();\n }\n };\n\n el.addEventListener('wheel', handleWheel, { passive: false });\n return () => el.removeEventListener('wheel', handleWheel);\n }, [ref]);\n}\n\nfunction getImageLabel(path: string): string {\n const filename = path.split('/').pop() || path;\n return filename\n .replace(/\\.(png|jpg|jpeg|gif|svg|webp)$/i, '')\n .replace(/[_-]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction getImageUrl(path: string): string {\n return `/raw/${path}`;\n}\n\nexport default function Gallery({\n path,\n globs = null,\n caption,\n captionLabel,\n title,\n subtitle,\n limit = null,\n page = 0,\n children,\n childAlign = \"right\",\n}: {\n path?: string;\n globs?: string[] | null;\n caption?: string;\n captionLabel?: string;\n title?: string;\n subtitle?: string;\n limit?: number | null;\n page?: number;\n children?: React.ReactNode;\n childAlign?: \"left\" | \"right\";\n}) {\n const { paths, isLoading, isEmpty } = useGalleryImages({\n path,\n globs,\n limit,\n page: page,\n });\n\n const lightbox = useLightbox(paths.length);\n const scrollRef = useRef<HTMLDivElement>(null);\n usePreventSwipeNavigation(scrollRef);\n\n const images: LightboxImage[] = useMemo(() =>\n paths.map(p => ({ src: getImageUrl(p), label: getImageLabel(p) })),\n [paths]\n );\n\n if (isLoading) {\n return (\n <figure className=\"not-prose py-6 md:py-8\">\n <div className=\"grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"aspect-square rounded-sm bg-muted/20 relative overflow-hidden\"\n >\n <div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer\"\n style={{ animationDelay: `${i * 150}ms` }}\n />\n </div>\n ))}\n </div>\n </figure>\n );\n }\n\n if (isEmpty) {\n return (\n <figure className=\"not-prose py-12 text-center\">\n <div className=\"inline-flex items-center gap-2.5 text-muted-foreground/40\">\n <Image className=\"h-3.5 w-3.5\" strokeWidth={1.5} />\n <span className=\"font-mono text-xs uppercase tracking-widest\">No images</span>\n </div>\n </figure>\n );\n }\n\n const isSingle = images.length === 1;\n const isTwo = images.length === 2;\n const isCompact = images.length <= 3;\n const isSingleWithChildren = images.length === 1 && children;\n // 2-3 images should break out of the content width\n const shouldBreakOut = images.length >= 2;\n\n const imageElement = (index: number, img: LightboxImage, className?: string) => (\n <div\n key={index}\n title={img.label}\n className={`aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ''}`}\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n );\n\n return (\n <>\n <figure className={`not-prose relative py-6 md:py-8 ${shouldBreakOut ? (isTwo ? 'gallery-breakout w-[75vw] max-w-[var(--gallery-width)]' : isCompact ? 'gallery-breakout w-[96vw] max-w-[var(--gallery-width)]' : 'gallery-breakout w-[var(--gallery-width)]') : ''}`}>\n {!isSingleWithChildren && !isSingle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureHeader title={title} subtitle={subtitle} />\n </div>\n )}\n\n {isSingleWithChildren ? (\n <div className={`grid items-start gap-12 md:gap-16 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] ${childAlign === 'left' ? '' : 'md:[&>div:first-child]:order-2'}`}>\n <div className=\"min-w-0 self-center text-sm leading-relaxed text-foreground/90 space-y-3 [&>ul]:space-y-1.5 [&>ul]:list-disc [&>ul]:pl-5 [&>ol]:space-y-1.5 [&>ol]:list-decimal [&>ol]:pl-5\">\n {children}\n </div>\n <div className=\"min-w-0\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n </div>\n ) : isSingle ? (\n <div className=\"max-w-[70%] mx-auto\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n ) : isCompact ? (\n <div className=\"flex gap-3\">\n {images.map((img, index) => imageElement(index, img, 'flex-1'))}\n </div>\n ) : (\n <div ref={scrollRef} className=\"flex gap-3 overflow-x-auto overscroll-x-contain pb-4\">\n {images.map((img, index) => (\n <div\n key={index}\n title={img.label}\n className=\"flex-none w-[30%] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group\"\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n ))}\n </div>\n )}\n\n {!isSingleWithChildren && !isSingle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n )}\n </figure>\n\n {lightbox.isOpen && lightbox.selectedIndex !== null && (\n <Lightbox\n images={images}\n selectedIndex={lightbox.selectedIndex}\n onClose={lightbox.close}\n onPrevious={lightbox.goToPrevious}\n onNext={lightbox.goToNext}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAaA,SAAS,0BAA0B,KAA0C;AAC3E,YAAU,MAAM;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AAET,UAAM,cAAc,CAAC,MAAkB;AAErC,UAAI,KAAK,IAAI,EAAE,MAAM,KAAK,KAAK,IAAI,EAAE,MAAM,EAAG;AAE9C,YAAM,EAAE,YAAY,aAAa,YAAA,IAAgB;AACjD,YAAM,aAAa,cAAc;AACjC,YAAM,cAAc,aAAa,eAAe,cAAc;AAG9D,UAAK,cAAc,EAAE,SAAS,KAAO,eAAe,EAAE,SAAS,GAAI;AACjE,UAAE,eAAA;AAAA,MACJ;AAAA,IACF;AAEA,OAAG,iBAAiB,SAAS,aAAa,EAAE,SAAS,OAAO;AAC5D,WAAO,MAAM,GAAG,oBAAoB,SAAS,WAAW;AAAA,EAC1D,GAAG,CAAC,GAAG,CAAC;AACV;AAEA,SAAS,cAAc,MAAsB;AAC3C,QAAM,WAAW,KAAK,MAAM,GAAG,EAAE,SAAS;AAC1C,SAAO,SACJ,QAAQ,mCAAmC,EAAE,EAC7C,QAAQ,SAAS,GAAG,EACpB,QAAQ,QAAQ,GAAG,EACnB,KAAA;AACL;AAEA,SAAS,YAAY,MAAsB;AACzC,SAAO,QAAQ,IAAI;AACrB;AAEA,SAAwB,QAAQ;AAAA,EAC9B;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,OAAO;AAAA,EACP;AAAA,EACA,aAAa;AACf,GAWG;AACD,QAAM,EAAE,OAAO,WAAW,QAAA,IAAY,iBAAiB;AAAA,IACrD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AAED,QAAM,WAAW,YAAY,MAAM,MAAM;AACzC,QAAM,YAAY,OAAuB,IAAI;AAC7C,4BAA0B,SAAS;AAEnC,QAAM,SAA0B;AAAA,IAAQ,MACtC,MAAM,IAAI,CAAA,OAAM,EAAE,KAAK,YAAY,CAAC,GAAG,OAAO,cAAc,CAAC,IAAI;AAAA,IACjE,CAAC,KAAK;AAAA,EAAA;AAGR,MAAI,WAAW;AACb,+BACG,UAAA,EAAO,WAAU,0BAChB,UAAA,oBAAC,SAAI,WAAU,+DACZ,UAAA,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,MACrB;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAU;AAAA,QAEV,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,KAAA;AAAA,UAAK;AAAA,QAAA;AAAA,MAC1C;AAAA,MANK;AAAA,IAAA,CAQR,GACH,EAAA,CACF;AAAA,EAEJ;AAEA,MAAI,SAAS;AACX,+BACG,UAAA,EAAO,WAAU,+BAChB,UAAA,qBAAC,OAAA,EAAI,WAAU,6DACb,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAM,WAAU,eAAc,aAAa,KAAK;AAAA,MACjD,oBAAC,QAAA,EAAK,WAAU,+CAA8C,UAAA,YAAA,CAAS;AAAA,IAAA,EAAA,CACzE,EAAA,CACF;AAAA,EAEJ;AAEA,QAAM,WAAW,OAAO,WAAW;AACnC,QAAM,QAAQ,OAAO,WAAW;AAChC,QAAM,YAAY,OAAO,UAAU;AACnC,QAAM,uBAAuB,OAAO,WAAW,KAAK;AAEpD,QAAM,iBAAiB,OAAO,UAAU;AAExC,QAAM,eAAe,CAAC,OAAe,KAAoB,cACvD;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,OAAO,IAAI;AAAA,MACX,WAAW,6EAA6E,aAAa,EAAE;AAAA,MACvG,SAAS,MAAM,SAAS,KAAK,KAAK;AAAA,MAElC,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAK,IAAI;AAAA,UACT,KAAK,IAAI;AAAA,UACT,WAAU;AAAA,QAAA;AAAA,MAAA;AAAA,IACZ;AAAA,IATK;AAAA,EAAA;AAaT,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA,qBAAC,UAAA,EAAO,WAAW,mCAAmC,iBAAkB,QAAQ,2DAA2D,YAAY,2DAA2D,8CAA+C,EAAE,IAChQ,UAAA;AAAA,MAAA,CAAC,wBAAwB,CAAC,YACzB,oBAAC,OAAA,EAAI,WAAU,iEACb,UAAA,oBAAC,cAAA,EAAa,OAAc,SAAA,CAAoB,EAAA,CAClD;AAAA,MAGD,4CACE,OAAA,EAAI,WAAW,gFAAgF,eAAe,SAAS,KAAK,gCAAgC,IAC3J,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAI,WAAU,+KACZ,SAAA,CACH;AAAA,QACA,qBAAC,OAAA,EAAI,WAAU,WACb,UAAA;AAAA,UAAA,oBAAC,cAAA,EAAa,OAAc,SAAA,CAAoB;AAAA,UAC/C,aAAa,GAAG,OAAO,CAAC,CAAC;AAAA,UAC1B,oBAAC,eAAA,EAAc,SAAkB,OAAO,aAAA,CAAc;AAAA,QAAA,EAAA,CACxD;AAAA,MAAA,EAAA,CACF,IACE,WACF,qBAAC,OAAA,EAAI,WAAU,uBACb,UAAA;AAAA,QAAA,oBAAC,cAAA,EAAa,OAAc,SAAA,CAAoB;AAAA,QAC/C,aAAa,GAAG,OAAO,CAAC,CAAC;AAAA,QAC1B,oBAAC,eAAA,EAAc,SAAkB,OAAO,aAAA,CAAc;AAAA,MAAA,EAAA,CACxD,IACE,YACF,oBAAC,OAAA,EAAI,WAAU,cACZ,UAAA,OAAO,IAAI,CAAC,KAAK,UAAU,aAAa,OAAO,KAAK,QAAQ,CAAC,EAAA,CAChE,IAEA,oBAAC,OAAA,EAAI,KAAK,WAAW,WAAU,wDAC5B,UAAA,OAAO,IAAI,CAAC,KAAK,UAChB;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,OAAO,IAAI;AAAA,UACX,WAAU;AAAA,UACV,SAAS,MAAM,SAAS,KAAK,KAAK;AAAA,UAElC,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,KAAK,IAAI;AAAA,cACT,KAAK,IAAI;AAAA,cACT,WAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACZ;AAAA,QATK;AAAA,MAAA,CAWR,GACH;AAAA,MAGD,CAAC,wBAAwB,CAAC,YACzB,oBAAC,OAAA,EAAI,WAAU,iEACb,UAAA,oBAAC,eAAA,EAAc,SAAkB,OAAO,cAAc,EAAA,CACxD;AAAA,IAAA,GAEJ;AAAA,IAEC,SAAS,UAAU,SAAS,kBAAkB,QAC7C;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAe,SAAS;AAAA,QACxB,SAAS,SAAS;AAAA,QAClB,YAAY,SAAS;AAAA,QACrB,QAAQ,SAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EACnB,GAEJ;AAEJ;"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/gallery/index.tsx"],"sourcesContent":["import { useMemo, useRef, useEffect } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { Lightbox, LightboxImage } from \"@/components/gallery/components/lightbox\";\nimport { useGalleryImages } from \"./hooks/use-gallery-images\";\nimport { useLightbox } from \"./hooks/use-lightbox\";\nimport { LoadingImage } from \"./components/loading-image\";\nimport { FigureHeader } from \"./components/figure-header\";\nimport { FigureCaption } from \"./components/figure-caption\";\n\n/**\n * Hook to prevent horizontal scroll from triggering browser back/forward gestures.\n * Captures wheel events and prevents default when at scroll boundaries.\n */\nfunction usePreventSwipeNavigation(ref: React.RefObject<HTMLElement | null>) {\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const handleWheel = (e: WheelEvent) => {\n // Only handle horizontal scrolling\n if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;\n\n const { scrollLeft, scrollWidth, clientWidth } = el;\n const atLeftEdge = scrollLeft <= 0;\n const atRightEdge = scrollLeft + clientWidth >= scrollWidth - 1;\n\n // Prevent default if trying to scroll past boundaries\n if ((atLeftEdge && e.deltaX < 0) || (atRightEdge && e.deltaX > 0)) {\n e.preventDefault();\n }\n };\n\n el.addEventListener('wheel', handleWheel, { passive: false });\n return () => el.removeEventListener('wheel', handleWheel);\n }, [ref]);\n}\n\nfunction getImageLabel(path: string): string {\n const cleanPath = path.split(/[?#]/)[0];\n const filename = cleanPath.split('/').pop() || cleanPath;\n return filename\n .replace(/\\.(png|jpg|jpeg|gif|svg|webp)$/i, '')\n .replace(/[_-]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction isAbsoluteUrl(path: string): boolean {\n return /^https?:\\/\\//i.test(path) || path.startsWith('//');\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n const trimmedBase = baseUrl.replace(/\\/+$/, '');\n const trimmedPath = path.replace(/^\\/+/, '');\n return `${trimmedBase}/${trimmedPath}`;\n}\n\nfunction getImageUrl(path: string, baseUrl?: string): string {\n if (isAbsoluteUrl(path)) return path;\n if (baseUrl) return joinUrl(baseUrl, path);\n return `/raw/${path}`;\n}\n\nexport default function Gallery({\n path,\n globs = null,\n baseUrl,\n caption,\n captionLabel,\n title,\n subtitle,\n limit = null,\n page = 0,\n children,\n childAlign = \"right\",\n}: {\n path?: string;\n globs?: string[] | null;\n baseUrl?: string;\n caption?: string;\n captionLabel?: string;\n title?: string;\n subtitle?: string;\n limit?: number | null;\n page?: number;\n children?: React.ReactNode;\n childAlign?: \"left\" | \"right\";\n}) {\n const { paths, isLoading, isEmpty } = useGalleryImages({\n path,\n globs,\n limit,\n page: page,\n });\n\n const lightbox = useLightbox(paths.length);\n const scrollRef = useRef<HTMLDivElement>(null);\n usePreventSwipeNavigation(scrollRef);\n\n const images: LightboxImage[] = useMemo(() =>\n paths.map(p => ({ src: getImageUrl(p, baseUrl), label: getImageLabel(p) })),\n [paths, baseUrl]\n );\n\n if (isLoading) {\n return (\n <figure className=\"not-prose py-6 md:py-8\">\n <div className=\"grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"aspect-square rounded-sm bg-muted/20 relative overflow-hidden\"\n >\n <div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer\"\n style={{ animationDelay: `${i * 150}ms` }}\n />\n </div>\n ))}\n </div>\n </figure>\n );\n }\n\n if (isEmpty) {\n return (\n <figure className=\"not-prose py-12 text-center\">\n <div className=\"inline-flex items-center gap-2.5 text-muted-foreground/40\">\n <Image className=\"h-3.5 w-3.5\" strokeWidth={1.5} />\n <span className=\"font-mono text-xs uppercase tracking-widest\">No images</span>\n </div>\n </figure>\n );\n }\n\n const isSingle = images.length === 1;\n const isTwo = images.length === 2;\n const isCompact = images.length <= 3;\n const isSingleWithChildren = images.length === 1 && children;\n // 2-3 images should break out of the content width\n const shouldBreakOut = images.length >= 2;\n\n const imageElement = (index: number, img: LightboxImage, className?: string) => (\n <div\n key={index}\n title={img.label}\n className={`aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ''}`}\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n );\n\n return (\n <>\n <figure className={`not-prose relative py-6 md:py-8 ${shouldBreakOut ? (isTwo ? 'gallery-breakout w-[75vw] max-w-[var(--gallery-width)]' : isCompact ? 'gallery-breakout w-[96vw] max-w-[var(--gallery-width)]' : 'gallery-breakout w-screen') : ''}`}>\n {!isSingleWithChildren && !isSingle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureHeader title={title} subtitle={subtitle} />\n </div>\n )}\n\n {isSingleWithChildren ? (\n <div className={`grid items-start gap-12 md:gap-16 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] ${childAlign === 'left' ? '' : 'md:[&>div:first-child]:order-2'}`}>\n <div className=\"min-w-0 self-center text-sm leading-relaxed text-foreground/90 space-y-3 [&>ul]:space-y-1.5 [&>ul]:list-disc [&>ul]:pl-5 [&>ol]:space-y-1.5 [&>ol]:list-decimal [&>ol]:pl-5\">\n {children}\n </div>\n <div className=\"min-w-0\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n </div>\n ) : isSingle ? (\n <div className=\"max-w-[70%] mx-auto\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n ) : isCompact ? (\n <div className=\"flex gap-3\">\n {images.map((img, index) => imageElement(index, img, 'flex-1'))}\n </div>\n ) : (\n <div ref={scrollRef} className=\"gallery-scroll-row flex gap-3 overflow-x-auto overscroll-x-contain pb-4\">\n {images.map((img, index) => (\n <div\n key={index}\n title={img.label}\n className=\"flex-none w-[calc(var(--gallery-width)*0.3)] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group\"\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n ))}\n </div>\n )}\n\n {!isSingleWithChildren && !isSingle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n )}\n </figure>\n\n {lightbox.isOpen && lightbox.selectedIndex !== null && (\n <Lightbox\n images={images}\n selectedIndex={lightbox.selectedIndex}\n onClose={lightbox.close}\n onPrevious={lightbox.goToPrevious}\n onNext={lightbox.goToNext}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -70,33 +70,32 @@ function filterData(data, keys) {
70
70
  }
71
71
  function ParameterGrid({ entries }) {
72
72
  if (entries.length === 0) return null;
73
- return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-x-6 gap-y-px", children: entries.map(([key, value]) => {
74
- const type = getValueType(value);
75
- return /* @__PURE__ */ jsxs(
76
- "div",
77
- {
78
- className: "flex items-baseline justify-between gap-2 py-1 group hover:bg-muted/30 -mx-1.5 px-1.5 rounded-sm transition-colors",
79
- children: [
80
- /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground font-mono truncate", children: key }),
81
- /* @__PURE__ */ jsx(
82
- "span",
83
- {
84
- className: cn(
85
- "text-[11px] font-mono tabular-nums font-medium truncate max-w-[120px]",
86
- type === "number" && "text-foreground",
87
- type === "string" && "text-amber-600 dark:text-amber-500",
88
- type === "boolean" && "text-cyan-600 dark:text-cyan-500",
89
- type === "null" && "text-muted-foreground/50"
90
- ),
91
- title: type === "string" ? `"${formatValue(value)}"` : formatValue(value),
92
- children: type === "string" ? `"${formatValue(value)}"` : formatValue(value)
93
- }
94
- )
95
- ]
96
- },
97
- key
98
- );
99
- }) });
73
+ return /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-[11px] font-mono", children: [
74
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground/70 uppercase tracking-widest", children: [
75
+ /* @__PURE__ */ jsx("th", { className: "text-left font-semibold py-1.5 px-1 border-b border-border/50", children: "Parameter" }),
76
+ /* @__PURE__ */ jsx("th", { className: "text-right font-semibold py-1.5 px-1 border-b border-border/50", children: "Value" })
77
+ ] }) }),
78
+ /* @__PURE__ */ jsx("tbody", { children: entries.map(([key, value]) => {
79
+ const type = getValueType(value);
80
+ return /* @__PURE__ */ jsxs("tr", { className: "border-b border-border/30 last:border-b-0", children: [
81
+ /* @__PURE__ */ jsx("td", { className: "py-1.5 px-1 text-muted-foreground truncate", title: key, children: key }),
82
+ /* @__PURE__ */ jsx(
83
+ "td",
84
+ {
85
+ className: cn(
86
+ "py-1.5 px-1 text-right tabular-nums font-medium truncate",
87
+ type === "number" && "text-foreground",
88
+ type === "string" && "text-amber-600 dark:text-amber-500",
89
+ type === "boolean" && "text-cyan-600 dark:text-cyan-500",
90
+ type === "null" && "text-muted-foreground/50"
91
+ ),
92
+ title: type === "string" ? `"${formatValue(value)}"` : formatValue(value),
93
+ children: type === "string" ? `"${formatValue(value)}"` : formatValue(value)
94
+ }
95
+ )
96
+ ] }, key);
97
+ }) })
98
+ ] }) });
100
99
  }
101
100
  function ParameterSection({
102
101
  name,
@@ -113,143 +112,86 @@ function ParameterSection({
113
112
  const t = getValueType(v);
114
113
  return t === "object" || t === "array";
115
114
  });
116
- return /* @__PURE__ */ jsxs("div", { className: cn(depth === 0 && "mb-4 last:mb-0"), children: [
117
- /* @__PURE__ */ jsxs(
118
- "button",
119
- {
120
- onClick: () => setIsCollapsed(!isCollapsed),
121
- className: cn(
122
- "flex items-center gap-2 w-full text-left group mb-1.5",
123
- depth === 0 && "pb-1 border-b border-border/50"
124
- ),
125
- children: [
126
- /* @__PURE__ */ jsx("span", { className: cn(
127
- "text-[10px] text-muted-foreground/60 transition-transform duration-150 select-none",
128
- isCollapsed && "-rotate-90"
129
- ), children: isCollapsed ? "+" : "-" }),
130
- /* @__PURE__ */ jsx("span", { className: cn(
131
- "font-mono text-[11px] uppercase tracking-widest",
132
- depth === 0 ? "text-foreground/80 font-semibold" : "text-muted-foreground/70"
133
- ), children: name.replace(/_/g, " ") }),
134
- /* @__PURE__ */ jsx("span", { className: "text-[9px] font-mono text-muted-foreground/40 ml-auto", children: entries.length })
135
- ]
136
- }
137
- ),
138
- !isCollapsed && /* @__PURE__ */ jsxs("div", { className: cn(
139
- depth > 0 && "pl-3 ml-1 border-l border-border/40"
140
- ), children: [
141
- leafEntries.length > 0 && /* @__PURE__ */ jsx("div", { className: cn(nestedEntries.length > 0 && "mb-3"), children: /* @__PURE__ */ jsx(ParameterGrid, { entries: leafEntries }) }),
142
- nestedEntries.map(([key, value]) => {
143
- const type = getValueType(value);
144
- if (type === "array") {
145
- const arr = value;
146
- return /* @__PURE__ */ jsxs("div", { className: "mb-2 last:mb-0", children: [
147
- /* @__PURE__ */ jsxs("div", { className: "text-[10px] font-mono text-muted-foreground/60 uppercase tracking-wider mb-1", children: [
148
- key,
149
- " [",
150
- arr.length,
151
- "]"
152
- ] }),
153
- /* @__PURE__ */ jsx("div", { className: "pl-3 ml-1 border-l border-border/40", children: arr.map((item, i) => {
154
- const itemType = getValueType(item);
155
- if (itemType === "object") {
156
- return /* @__PURE__ */ jsx(
157
- ParameterSection,
158
- {
159
- name: `${i}`,
160
- data: item,
161
- depth: depth + 1
162
- },
163
- i
164
- );
165
- }
166
- return /* @__PURE__ */ jsxs("div", { className: "text-[11px] font-mono text-foreground py-0.5", children: [
167
- "[",
168
- i,
169
- "] ",
170
- formatValue(item)
171
- ] }, i);
172
- }) })
173
- ] }, key);
174
- }
175
- return /* @__PURE__ */ jsx(
176
- ParameterSection,
115
+ const isTopLevel = depth === 0;
116
+ return /* @__PURE__ */ jsxs(
117
+ "div",
118
+ {
119
+ className: cn(
120
+ isTopLevel && "mb-4 last:mb-0 rounded-md border border-border/40 bg-card/30 p-2",
121
+ !isTopLevel && "mb-3 last:mb-0"
122
+ ),
123
+ children: [
124
+ /* @__PURE__ */ jsxs(
125
+ "button",
177
126
  {
178
- name: key,
179
- data: value,
180
- depth: depth + 1
181
- },
182
- key
183
- );
184
- })
185
- ] })
186
- ] });
187
- }
188
- function estimateHeight(data, depth = 0) {
189
- const entries = Object.entries(data);
190
- let height = 0;
191
- for (const [, value] of entries) {
192
- const type = getValueType(value);
193
- if (type === "object") {
194
- height += 28 + estimateHeight(value, depth + 1);
195
- } else if (type === "array") {
196
- const arr = value;
197
- height += 28;
198
- for (const item of arr) {
199
- if (getValueType(item) === "object") {
200
- height += 24 + estimateHeight(item, depth + 1);
201
- } else {
202
- height += 24;
203
- }
204
- }
205
- } else {
206
- height += 24;
207
- }
208
- }
209
- return height;
210
- }
211
- function splitIntoColumns(entries, numColumns) {
212
- if (numColumns <= 1) return [entries];
213
- const entryHeights = entries.map(([, value]) => {
214
- const type = getValueType(value);
215
- if (type === "object") {
216
- return 28 + estimateHeight(value);
217
- } else if (type === "array") {
218
- const arr = value;
219
- let h = 28;
220
- for (const item of arr) {
221
- if (getValueType(item) === "object") {
222
- h += 24 + estimateHeight(item);
223
- } else {
224
- h += 24;
225
- }
226
- }
227
- return h;
228
- }
229
- return 24;
230
- });
231
- const totalHeight = entryHeights.reduce((a, b) => a + b, 0);
232
- const targetPerColumn = totalHeight / numColumns;
233
- const columns = [];
234
- let currentColumn = [];
235
- let currentHeight = 0;
236
- for (let i = 0; i < entries.length; i++) {
237
- const entry = entries[i];
238
- const entryHeight = entryHeights[i];
239
- if (currentHeight >= targetPerColumn && columns.length < numColumns - 1 && currentColumn.length > 0) {
240
- columns.push(currentColumn);
241
- currentColumn = [];
242
- currentHeight = 0;
127
+ onClick: () => setIsCollapsed(!isCollapsed),
128
+ className: cn(
129
+ "flex items-center gap-2 w-full text-left group mb-1.5 px-1",
130
+ isTopLevel && "pb-1 border-b border-border/50"
131
+ ),
132
+ children: [
133
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/60 select-none", children: isCollapsed ? "▸" : "▾" }),
134
+ /* @__PURE__ */ jsx("span", { className: cn(
135
+ "font-mono text-[11px] uppercase tracking-widest",
136
+ isTopLevel ? "text-foreground/80 font-semibold" : "text-muted-foreground/70"
137
+ ), children: name.replace(/_/g, " ") }),
138
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] font-mono text-muted-foreground/40 ml-auto", children: entries.length })
139
+ ]
140
+ }
141
+ ),
142
+ !isCollapsed && /* @__PURE__ */ jsxs("div", { className: cn(
143
+ depth > 0 && "pl-3 ml-1 border-l border-border/40"
144
+ ), children: [
145
+ leafEntries.length > 0 && /* @__PURE__ */ jsx("div", { className: cn(nestedEntries.length > 0 && "mb-3"), children: /* @__PURE__ */ jsx(ParameterGrid, { entries: leafEntries }) }),
146
+ nestedEntries.map(([key, value]) => {
147
+ const type = getValueType(value);
148
+ if (type === "array") {
149
+ const arr = value;
150
+ return /* @__PURE__ */ jsxs("div", { className: "mb-2 last:mb-0", children: [
151
+ /* @__PURE__ */ jsxs("div", { className: "text-[10px] font-mono text-muted-foreground/60 uppercase tracking-wider mb-1", children: [
152
+ key,
153
+ " [",
154
+ arr.length,
155
+ "]"
156
+ ] }),
157
+ /* @__PURE__ */ jsx("div", { className: "pl-3 ml-1 border-l border-border/40", children: arr.map((item, i) => {
158
+ const itemType = getValueType(item);
159
+ if (itemType === "object") {
160
+ return /* @__PURE__ */ jsx(
161
+ ParameterSection,
162
+ {
163
+ name: `${i}`,
164
+ data: item,
165
+ depth: depth + 1
166
+ },
167
+ i
168
+ );
169
+ }
170
+ return /* @__PURE__ */ jsxs("div", { className: "text-[11px] font-mono text-foreground py-0.5", children: [
171
+ "[",
172
+ i,
173
+ "] ",
174
+ formatValue(item)
175
+ ] }, i);
176
+ }) })
177
+ ] }, key);
178
+ }
179
+ return /* @__PURE__ */ jsx(
180
+ ParameterSection,
181
+ {
182
+ name: key,
183
+ data: value,
184
+ depth: depth + 1
185
+ },
186
+ key
187
+ );
188
+ })
189
+ ] })
190
+ ]
243
191
  }
244
- currentColumn.push(entry);
245
- currentHeight += entryHeight;
246
- }
247
- if (currentColumn.length > 0) {
248
- columns.push(currentColumn);
249
- }
250
- return columns;
192
+ );
251
193
  }
252
- function SingleParameterTable({ path, keys, label, withMargin = true }) {
194
+ function SingleParameterTable({ path, keys, pairs, label, withMargin = true }) {
253
195
  const { content, loading, error } = useFileContent(path);
254
196
  const { parsed, parseError } = useMemo(() => {
255
197
  if (!content) return { parsed: null, parseError: "no content" };
@@ -263,6 +205,9 @@ function SingleParameterTable({ path, keys, label, withMargin = true }) {
263
205
  }
264
206
  return { parsed: null, parseError: `invalid ${path.split(".").pop()} syntax` };
265
207
  }
208
+ if (pairs && pairs.length > 0) {
209
+ return { parsed: data, parseError: null };
210
+ }
266
211
  if (keys && keys.length > 0) {
267
212
  const filtered = filterData(data, keys);
268
213
  if (Object.keys(filtered).length === 0) {
@@ -299,18 +244,40 @@ function SingleParameterTable({ path, keys, label, withMargin = true }) {
299
244
  const t = getValueType(v);
300
245
  return t === "object" || t === "array";
301
246
  });
302
- const estHeight = estimateHeight(parsed);
303
- const HEIGHT_THRESHOLD = 500;
304
- const numColumns = estHeight > HEIGHT_THRESHOLD ? Math.min(Math.ceil(estHeight / HEIGHT_THRESHOLD), 3) : 1;
305
- const useColumns = numColumns > 1 && topNested.length > 1;
306
- const columns = useColumns ? splitIntoColumns(topNested, numColumns) : [topNested];
307
247
  path.split("/").pop() || path;
248
+ const renderPairsTable = () => {
249
+ if (!pairs || pairs.length === 0) return null;
250
+ return /* @__PURE__ */ jsx("div", { className: "not-prose my-6 overflow-x-auto border border-border rounded-md", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm border-collapse", children: [
251
+ /* @__PURE__ */ jsx("thead", { className: "bg-muted/50", children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border last:border-b-0", children: [
252
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider", children: "Parameter" }),
253
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider", children: "Value" })
254
+ ] }) }),
255
+ /* @__PURE__ */ jsx("tbody", { children: pairs.map(({ key, label: rowLabel }) => {
256
+ const value = extractPath(parsed, key);
257
+ const type = value === void 0 ? "missing" : getValueType(value);
258
+ const displayValue = value === void 0 ? "—" : type === "string" ? `"${formatValue(value)}"` : formatValue(value);
259
+ return /* @__PURE__ */ jsxs("tr", { className: "border-b border-border last:border-b-0", children: [
260
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3 align-top", children: rowLabel || key }),
261
+ /* @__PURE__ */ jsx(
262
+ "td",
263
+ {
264
+ className: cn(
265
+ "px-4 py-3 align-top",
266
+ type === "missing" && "text-muted-foreground"
267
+ ),
268
+ children: displayValue
269
+ }
270
+ )
271
+ ] }, key);
272
+ }) })
273
+ ] }) });
274
+ };
308
275
  const renderNestedEntry = ([key, value]) => {
309
276
  const type = getValueType(value);
310
277
  if (type === "array") {
311
278
  const arr = value;
312
- return /* @__PURE__ */ jsxs("div", { className: "mb-4 last:mb-0", children: [
313
- /* @__PURE__ */ jsxs("div", { className: "text-[11px] font-mono text-foreground/80 uppercase tracking-widest font-semibold mb-1.5 pb-1 border-b border-border/50", children: [
279
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 last:mb-0 rounded-md border border-border/40 bg-card/30 p-2", children: [
280
+ /* @__PURE__ */ jsxs("div", { className: "text-[11px] font-mono text-foreground/80 uppercase tracking-widest font-semibold mb-1.5 pb-1 border-b border-border/50 px-1", children: [
314
281
  key.replace(/_/g, " "),
315
282
  " [",
316
283
  arr.length,
@@ -350,22 +317,13 @@ function SingleParameterTable({ path, keys, label, withMargin = true }) {
350
317
  };
351
318
  return /* @__PURE__ */ jsxs("div", { className: cn("not-prose", withMargin && "my-6"), children: [
352
319
  label && /* @__PURE__ */ jsx("div", { className: "text-[11px] font-mono text-muted-foreground mb-1.5 truncate", title: label, children: label }),
353
- /* @__PURE__ */ jsxs("div", { className: "rounded border border-border/60 bg-card/20 p-3 overflow-hidden max-w-full", children: [
320
+ pairs && pairs.length > 0 ? renderPairsTable() : /* @__PURE__ */ jsxs("div", { className: "rounded border border-border/60 bg-card/20 p-3 overflow-hidden max-w-full", children: [
354
321
  topLeaves.length > 0 && /* @__PURE__ */ jsx("div", { className: cn(topNested.length > 0 && "mb-4 pb-3 border-b border-border/30"), children: /* @__PURE__ */ jsx(ParameterGrid, { entries: topLeaves }) }),
355
- useColumns ? /* @__PURE__ */ jsx(
356
- "div",
357
- {
358
- className: "grid gap-6",
359
- style: { gridTemplateColumns: `repeat(${columns.length}, 1fr)` },
360
- children: columns.map((columnEntries, colIndex) => /* @__PURE__ */ jsx("div", { className: cn(
361
- colIndex > 0 && "border-l border-border/30 pl-6"
362
- ), children: columnEntries.map(renderNestedEntry) }, colIndex))
363
- }
364
- ) : topNested.map(renderNestedEntry)
322
+ topNested.map(renderNestedEntry)
365
323
  ] })
366
324
  ] });
367
325
  }
368
- function ParameterTable({ path, keys }) {
326
+ function ParameterTable({ path, keys, pairs }) {
369
327
  const { "*": routePath = "" } = useParams();
370
328
  const currentDir = routePath.replace(/\/?[^/]+\.mdx$/i, "").replace(/\/$/, "") || ".";
371
329
  let resolvedPath = path;
@@ -392,7 +350,7 @@ function ParameterTable({ path, keys }) {
392
350
  return paths;
393
351
  }, [hasGlob, directory, resolvedPath, path, baseDir]);
394
352
  if (!hasGlob) {
395
- return /* @__PURE__ */ jsx(SingleParameterTable, { path: resolvedPath, keys });
353
+ return /* @__PURE__ */ jsx(SingleParameterTable, { path: resolvedPath, keys, pairs });
396
354
  }
397
355
  if (!directory) {
398
356
  return /* @__PURE__ */ jsx("div", { className: "my-6 p-4 rounded border border-border/50 bg-card/30", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground/60", children: [
@@ -423,6 +381,7 @@ function ParameterTable({ path, keys }) {
423
381
  {
424
382
  path: filePath,
425
383
  keys,
384
+ pairs,
426
385
  label: filePath.split("/").pop() || filePath,
427
386
  withMargin: false
428
387
  }
@@ -1 +1 @@
1
- {"version":3,"file":"parameter-table.js","sources":["../../../src/components/parameter-table.tsx"],"sourcesContent":["import { useFileContent, useDirectory } from \"../../plugin/src/client\";\nimport { useMemo, useState, useRef, useEffect } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { cn } from \"@/lib/utils\";\nimport { minimatch } from \"minimatch\";\nimport {\n type ParameterValue,\n extractPath,\n getValueType,\n formatValue,\n parseConfigFile,\n} from \"@/lib/parameter-utils\";\nimport { FileEntry, DirectoryEntry } from \"../../plugin/src/lib\";\n\n/**\n * Hook to prevent horizontal scroll from triggering browser back/forward gestures.\n */\nfunction usePreventSwipeNavigation(ref: React.RefObject<HTMLElement | null>) {\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const handleWheel = (e: WheelEvent) => {\n if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;\n\n const { scrollLeft, scrollWidth, clientWidth } = el;\n const atLeftEdge = scrollLeft <= 0;\n const atRightEdge = scrollLeft + clientWidth >= scrollWidth - 1;\n\n if ((atLeftEdge && e.deltaX < 0) || (atRightEdge && e.deltaX > 0)) {\n e.preventDefault();\n }\n };\n\n el.addEventListener('wheel', handleWheel, { passive: false });\n return () => el.removeEventListener('wheel', handleWheel);\n }, [ref]);\n}\n\n// Check if a path contains glob patterns\nfunction isGlobPattern(path: string): boolean {\n return path.includes('*') || path.includes('?') || path.includes('[');\n}\n\n// Recursively collect all config files from a directory tree\nfunction collectAllConfigFiles(entry: DirectoryEntry | FileEntry): FileEntry[] {\n if (entry.type === \"file\") {\n if (entry.name.match(/\\.(yaml|yml|json)$/i)) {\n return [entry];\n }\n return [];\n }\n const files: FileEntry[] = [];\n for (const child of entry.children || []) {\n files.push(...collectAllConfigFiles(child));\n }\n return files;\n}\n\n// Sort paths numerically\nfunction sortPathsNumerically(paths: string[]): void {\n paths.sort((a, b) => {\n const nums = (s: string) => (s.match(/\\d+/g) || []).map(Number);\n const na = nums(a);\n const nb = nums(b);\n const len = Math.max(na.length, nb.length);\n for (let i = 0; i < len; i++) {\n const diff = (na[i] ?? 0) - (nb[i] ?? 0);\n if (diff !== 0) return diff;\n }\n return a.localeCompare(b);\n });\n}\n\n/**\n * Build a filtered data object from an array of jq-like paths.\n * Each path extracts data and places it in the result under the final key name.\n */\nfunction filterData(\n data: Record<string, ParameterValue>,\n keys: string[]\n): Record<string, ParameterValue> {\n const result: Record<string, ParameterValue> = {};\n\n for (const keyPath of keys) {\n const extracted = extractPath(data, keyPath);\n if (extracted === undefined) continue;\n\n const cleanPath = keyPath.startsWith(\".\") ? keyPath.slice(1) : keyPath;\n\n // For simple paths like .base.N_E, use \"N_E\" as key\n // For paths with [], preserve more context\n let keyName: string;\n if (cleanPath.includes(\"[\")) {\n keyName = cleanPath.replace(/\\[\\]/g, \"\").replace(/\\[(\\d+)\\]/g, \"_$1\");\n } else {\n const parts = cleanPath.split(\".\");\n keyName = parts[parts.length - 1];\n }\n\n result[keyName] = extracted;\n }\n\n return result;\n}\n\n// Renders a flat section of key-value pairs in a dense grid\nfunction ParameterGrid({ entries }: { entries: [string, ParameterValue][] }) {\n if (entries.length === 0) return null;\n\n return (\n <div className=\"grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-x-6 gap-y-px\">\n {entries.map(([key, value]) => {\n const type = getValueType(value);\n return (\n <div\n key={key}\n className=\"flex items-baseline justify-between gap-2 py-1 group hover:bg-muted/30 -mx-1.5 px-1.5 rounded-sm transition-colors\"\n >\n <span className=\"text-[11px] text-muted-foreground font-mono truncate\">\n {key}\n </span>\n <span\n className={cn(\n \"text-[11px] font-mono tabular-nums font-medium truncate max-w-[120px]\",\n type === \"number\" && \"text-foreground\",\n type === \"string\" && \"text-amber-600 dark:text-amber-500\",\n type === \"boolean\" && \"text-cyan-600 dark:text-cyan-500\",\n type === \"null\" && \"text-muted-foreground/50\"\n )}\n title={type === \"string\" ? `\"${formatValue(value)}\"` : formatValue(value)}\n >\n {type === \"string\" ? `\"${formatValue(value)}\"` : formatValue(value)}\n </span>\n </div>\n );\n })}\n </div>\n );\n}\n\n// Renders a nested section with its own header\nfunction ParameterSection({\n name,\n data,\n depth = 0\n}: {\n name: string;\n data: Record<string, ParameterValue>;\n depth?: number;\n}) {\n const [isCollapsed, setIsCollapsed] = useState(false);\n\n const entries = Object.entries(data);\n const leafEntries = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t !== \"object\" && t !== \"array\";\n });\n const nestedEntries = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t === \"object\" || t === \"array\";\n });\n\n return (\n <div className={cn(depth === 0 && \"mb-4 last:mb-0\")}>\n <button\n onClick={() => setIsCollapsed(!isCollapsed)}\n className={cn(\n \"flex items-center gap-2 w-full text-left group mb-1.5\",\n depth === 0 && \"pb-1 border-b border-border/50\"\n )}\n >\n <span className={cn(\n \"text-[10px] text-muted-foreground/60 transition-transform duration-150 select-none\",\n isCollapsed && \"-rotate-90\"\n )}>\n {isCollapsed ? \"+\" : \"-\"}\n </span>\n\n <span className={cn(\n \"font-mono text-[11px] uppercase tracking-widest\",\n depth === 0\n ? \"text-foreground/80 font-semibold\"\n : \"text-muted-foreground/70\"\n )}>\n {name.replace(/_/g, \" \")}\n </span>\n\n <span className=\"text-[9px] font-mono text-muted-foreground/40 ml-auto\">\n {entries.length}\n </span>\n </button>\n\n {!isCollapsed && (\n <div className={cn(\n depth > 0 && \"pl-3 ml-1 border-l border-border/40\"\n )}>\n {leafEntries.length > 0 && (\n <div className={cn(nestedEntries.length > 0 && \"mb-3\")}>\n <ParameterGrid entries={leafEntries} />\n </div>\n )}\n\n {nestedEntries.map(([key, value]) => {\n const type = getValueType(value);\n if (type === \"array\") {\n const arr = value as ParameterValue[];\n return (\n <div key={key} className=\"mb-2 last:mb-0\">\n <div className=\"text-[10px] font-mono text-muted-foreground/60 uppercase tracking-wider mb-1\">\n {key} [{arr.length}]\n </div>\n <div className=\"pl-3 ml-1 border-l border-border/40\">\n {arr.map((item, i) => {\n const itemType = getValueType(item);\n if (itemType === \"object\") {\n return (\n <ParameterSection\n key={i}\n name={`${i}`}\n data={item as Record<string, ParameterValue>}\n depth={depth + 1}\n />\n );\n }\n return (\n <div key={i} className=\"text-[11px] font-mono text-foreground py-0.5\">\n [{i}] {formatValue(item)}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n return (\n <ParameterSection\n key={key}\n name={key}\n data={value as Record<string, ParameterValue>}\n depth={depth + 1}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\ninterface SingleParameterTableProps {\n /** Path to the YAML or JSON file */\n path: string;\n /**\n * Optional array of jq-like paths to filter which parameters to show.\n * Examples:\n * - [\".base.N_E\", \".base.N_I\"] → show only N_E and N_I from base\n * - [\".base\"] → show entire base section\n * - [\".default_inputs\", \".base.dt\"] → show default_inputs section and dt from base\n */\n keys?: string[];\n /** Optional label to show above the table */\n label?: string;\n /** Whether to include vertical margin (default true) */\n withMargin?: boolean;\n}\n\ninterface ParameterTableProps {\n /** Path to the YAML or JSON file, supports glob patterns like \"*.yaml\" */\n path: string;\n /**\n * Optional array of jq-like paths to filter which parameters to show.\n */\n keys?: string[];\n}\n\n/**\n * Estimate the height contribution of a data structure.\n */\nfunction estimateHeight(data: Record<string, ParameterValue>, depth = 0): number {\n const entries = Object.entries(data);\n let height = 0;\n\n for (const [, value] of entries) {\n const type = getValueType(value);\n if (type === \"object\") {\n height += 28 + estimateHeight(value as Record<string, ParameterValue>, depth + 1);\n } else if (type === \"array\") {\n const arr = value as ParameterValue[];\n height += 28;\n for (const item of arr) {\n if (getValueType(item) === \"object\") {\n height += 24 + estimateHeight(item as Record<string, ParameterValue>, depth + 1);\n } else {\n height += 24;\n }\n }\n } else {\n height += 24;\n }\n }\n\n return height;\n}\n\n/**\n * Split entries into balanced columns based on estimated height.\n */\nfunction splitIntoColumns<T extends [string, ParameterValue]>(\n entries: T[],\n numColumns: number\n): T[][] {\n if (numColumns <= 1) return [entries];\n\n const entryHeights = entries.map(([, value]) => {\n const type = getValueType(value);\n if (type === \"object\") {\n return 28 + estimateHeight(value as Record<string, ParameterValue>);\n } else if (type === \"array\") {\n const arr = value as ParameterValue[];\n let h = 28;\n for (const item of arr) {\n if (getValueType(item) === \"object\") {\n h += 24 + estimateHeight(item as Record<string, ParameterValue>);\n } else {\n h += 24;\n }\n }\n return h;\n }\n return 24;\n });\n\n const totalHeight = entryHeights.reduce((a, b) => a + b, 0);\n const targetPerColumn = totalHeight / numColumns;\n\n const columns: T[][] = [];\n let currentColumn: T[] = [];\n let currentHeight = 0;\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const entryHeight = entryHeights[i];\n\n if (currentHeight >= targetPerColumn && columns.length < numColumns - 1 && currentColumn.length > 0) {\n columns.push(currentColumn);\n currentColumn = [];\n currentHeight = 0;\n }\n\n currentColumn.push(entry);\n currentHeight += entryHeight;\n }\n\n if (currentColumn.length > 0) {\n columns.push(currentColumn);\n }\n\n return columns;\n}\n\nfunction SingleParameterTable({ path, keys, label, withMargin = true }: SingleParameterTableProps) {\n const { content, loading, error } = useFileContent(path);\n\n const { parsed, parseError } = useMemo(() => {\n if (!content) return { parsed: null, parseError: 'no content' };\n\n const data = parseConfigFile(content, path);\n if (!data) {\n // Check why parsing failed\n if (!path.match(/\\.(yaml|yml|json)$/i)) {\n return { parsed: null, parseError: `unsupported file type` };\n }\n // Check if content looks like HTML (404 page)\n if (content.trim().startsWith('<!') || content.trim().startsWith('<html')) {\n return { parsed: null, parseError: `file not found` };\n }\n return { parsed: null, parseError: `invalid ${path.split('.').pop()} syntax` };\n }\n\n if (keys && keys.length > 0) {\n const filtered = filterData(data, keys);\n if (Object.keys(filtered).length === 0) {\n return { parsed: null, parseError: `keys not found: ${keys.join(', ')}` };\n }\n return { parsed: filtered, parseError: null };\n }\n\n return { parsed: data, parseError: null };\n }, [content, path, keys]);\n\n if (loading) {\n return (\n <div className=\"my-6 p-4 rounded border border-border/50 bg-card/30\">\n <div className=\"flex items-center gap-2 text-muted-foreground/60\">\n <div className=\"w-3 h-3 border border-current border-t-transparent rounded-full animate-spin\" />\n <span className=\"text-[11px] font-mono\">loading parameters...</span>\n </div>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"my-6 p-3 rounded border border-destructive/30 bg-destructive/5\">\n <p className=\"text-[11px] font-mono text-destructive\">{error}</p>\n </div>\n );\n }\n\n if (!parsed) {\n return (\n <div className={cn(\"p-3 rounded border border-border/50 bg-card/30\", withMargin && \"my-6\")}>\n <p className=\"text-[11px] font-mono text-muted-foreground\">\n {label && <span className=\"text-foreground/60\">{label}: </span>}\n {parseError || 'unable to parse'}\n </p>\n </div>\n );\n }\n\n const entries = Object.entries(parsed);\n\n const topLeaves = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t !== \"object\" && t !== \"array\";\n });\n const topNested = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t === \"object\" || t === \"array\";\n });\n\n const estHeight = estimateHeight(parsed);\n const HEIGHT_THRESHOLD = 500;\n const numColumns = estHeight > HEIGHT_THRESHOLD ? Math.min(Math.ceil(estHeight / HEIGHT_THRESHOLD), 3) : 1;\n const useColumns = numColumns > 1 && topNested.length > 1;\n\n const columns = useColumns\n ? splitIntoColumns(topNested as [string, ParameterValue][], numColumns)\n : [topNested];\n\n const filename = path.split(\"/\").pop() || path;\n\n const renderNestedEntry = ([key, value]: [string, ParameterValue]) => {\n const type = getValueType(value);\n if (type === \"array\") {\n const arr = value as ParameterValue[];\n return (\n <div key={key} className=\"mb-4 last:mb-0\">\n <div className=\"text-[11px] font-mono text-foreground/80 uppercase tracking-widest font-semibold mb-1.5 pb-1 border-b border-border/50\">\n {key.replace(/_/g, \" \")} [{arr.length}]\n </div>\n <div className=\"pl-3 ml-1 border-l border-border/40\">\n {arr.map((item, i) => {\n const itemType = getValueType(item);\n if (itemType === \"object\") {\n return (\n <ParameterSection\n key={i}\n name={`${i}`}\n data={item as Record<string, ParameterValue>}\n depth={1}\n />\n );\n }\n return (\n <div key={i} className=\"text-[11px] font-mono text-foreground py-0.5\">\n [{i}] {formatValue(item)}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n return (\n <ParameterSection\n key={key}\n name={key}\n data={value as Record<string, ParameterValue>}\n depth={0}\n />\n );\n };\n\n return (\n <div className={cn(\"not-prose\", withMargin && \"my-6\")}>\n {label && (\n <div className=\"text-[11px] font-mono text-muted-foreground mb-1.5 truncate\" title={label}>\n {label}\n </div>\n )}\n <div className=\"rounded border border-border/60 bg-card/20 p-3 overflow-hidden max-w-full\">\n {topLeaves.length > 0 && (\n <div className={cn(topNested.length > 0 && \"mb-4 pb-3 border-b border-border/30\")}>\n <ParameterGrid entries={topLeaves} />\n </div>\n )}\n\n {useColumns ? (\n <div\n className=\"grid gap-6\"\n style={{ gridTemplateColumns: `repeat(${columns.length}, 1fr)` }}\n >\n {columns.map((columnEntries, colIndex) => (\n <div key={colIndex} className={cn(\n colIndex > 0 && \"border-l border-border/30 pl-6\"\n )}>\n {columnEntries.map(renderNestedEntry)}\n </div>\n ))}\n </div>\n ) : (\n topNested.map(renderNestedEntry)\n )}\n </div>\n </div>\n );\n}\n\n/**\n * ParameterTable component that displays YAML/JSON config files.\n * Supports glob patterns in the path prop to show multiple files.\n */\nexport function ParameterTable({ path, keys }: ParameterTableProps) {\n const { \"*\": routePath = \"\" } = useParams();\n\n // Get current directory from route\n const currentDir = routePath\n .replace(/\\/?[^/]+\\.mdx$/i, \"\")\n .replace(/\\/$/, \"\")\n || \".\";\n\n // Resolve relative paths\n let resolvedPath = path;\n if (path?.startsWith(\"./\")) {\n const relativePart = path.slice(2);\n resolvedPath = currentDir === \".\" ? relativePart : `${currentDir}/${relativePart}`;\n } else if (path && !path.startsWith(\"/\") && !path.includes(\"/\") && !isGlobPattern(path)) {\n resolvedPath = currentDir === \".\" ? path : `${currentDir}/${path}`;\n }\n\n // Check if this is a glob pattern\n const hasGlob = isGlobPattern(resolvedPath);\n\n // For glob patterns, get the base directory (directory containing the glob pattern)\n const baseDir = useMemo(() => {\n if (!hasGlob) return null;\n // Get everything before the first glob character\n const beforeGlob = resolvedPath.split(/[*?\\[]/, 1)[0];\n // Extract directory portion (everything up to the last slash)\n const lastSlash = beforeGlob.lastIndexOf('/');\n if (lastSlash === -1) return \".\";\n return beforeGlob.slice(0, lastSlash) || \".\";\n }, [hasGlob, resolvedPath]);\n\n const { directory } = useDirectory(baseDir || \".\");\n\n // Find matching files for glob patterns\n const matchingPaths = useMemo(() => {\n if (!hasGlob || !directory) return [];\n\n const allFiles = collectAllConfigFiles(directory);\n const paths = allFiles\n .map(f => f.path)\n .filter(p => minimatch(p, resolvedPath, { matchBase: true }));\n\n sortPathsNumerically(paths);\n\n return paths;\n }, [hasGlob, directory, resolvedPath, path, baseDir]);\n\n // If not a glob pattern, just render the single table\n if (!hasGlob) {\n return <SingleParameterTable path={resolvedPath} keys={keys} />;\n }\n\n // Loading state for glob patterns\n if (!directory) {\n return (\n <div className=\"my-6 p-4 rounded border border-border/50 bg-card/30\">\n <div className=\"flex items-center gap-2 text-muted-foreground/60\">\n <div className=\"w-3 h-3 border border-current border-t-transparent rounded-full animate-spin\" />\n <span className=\"text-[11px] font-mono\">loading parameters...</span>\n </div>\n </div>\n );\n }\n\n // No matches\n if (matchingPaths.length === 0) {\n return (\n <div className=\"my-6 p-3 rounded border border-border/50 bg-card/30\">\n <p className=\"text-[11px] font-mono text-muted-foreground\">\n no files matching: {resolvedPath}\n <br />\n <span className=\"text-muted-foreground/50\">(base dir: {baseDir}, original: {path})</span>\n </p>\n </div>\n );\n }\n\n const scrollRef = useRef<HTMLDivElement>(null);\n usePreventSwipeNavigation(scrollRef);\n\n // Breakout width based on count\n const count = matchingPaths.length;\n const breakoutClass = count >= 4\n ? 'w-[96vw] ml-[calc(-48vw+50%)]'\n : count >= 2\n ? 'w-[75vw] ml-[calc(-37.5vw+50%)]'\n : '';\n\n return (\n <div className={`my-6 ${breakoutClass}`}>\n <div ref={scrollRef} className=\"flex gap-4 overflow-x-auto overscroll-x-contain pb-2\">\n {matchingPaths.map((filePath) => (\n <div key={filePath} className=\"flex-none w-[280px]\">\n <SingleParameterTable\n path={filePath}\n keys={keys}\n label={filePath.split('/').pop() || filePath}\n withMargin={false}\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parameter-table.js","sources":["../../../src/components/parameter-table.tsx"],"sourcesContent":["import { useFileContent, useDirectory } from \"../../plugin/src/client\";\nimport { useMemo, useState, useRef, useEffect } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { cn } from \"@/lib/utils\";\nimport { minimatch } from \"minimatch\";\nimport {\n type ParameterValue,\n extractPath,\n getValueType,\n formatValue,\n parseConfigFile,\n} from \"@/lib/parameter-utils\";\nimport { FileEntry, DirectoryEntry } from \"../../plugin/src/lib\";\n\n/**\n * Hook to prevent horizontal scroll from triggering browser back/forward gestures.\n */\nfunction usePreventSwipeNavigation(ref: React.RefObject<HTMLElement | null>) {\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const handleWheel = (e: WheelEvent) => {\n if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;\n\n const { scrollLeft, scrollWidth, clientWidth } = el;\n const atLeftEdge = scrollLeft <= 0;\n const atRightEdge = scrollLeft + clientWidth >= scrollWidth - 1;\n\n if ((atLeftEdge && e.deltaX < 0) || (atRightEdge && e.deltaX > 0)) {\n e.preventDefault();\n }\n };\n\n el.addEventListener('wheel', handleWheel, { passive: false });\n return () => el.removeEventListener('wheel', handleWheel);\n }, [ref]);\n}\n\n// Check if a path contains glob patterns\nfunction isGlobPattern(path: string): boolean {\n return path.includes('*') || path.includes('?') || path.includes('[');\n}\n\n// Recursively collect all config files from a directory tree\nfunction collectAllConfigFiles(entry: DirectoryEntry | FileEntry): FileEntry[] {\n if (entry.type === \"file\") {\n if (entry.name.match(/\\.(yaml|yml|json)$/i)) {\n return [entry];\n }\n return [];\n }\n const files: FileEntry[] = [];\n for (const child of entry.children || []) {\n files.push(...collectAllConfigFiles(child));\n }\n return files;\n}\n\n// Sort paths numerically\nfunction sortPathsNumerically(paths: string[]): void {\n paths.sort((a, b) => {\n const nums = (s: string) => (s.match(/\\d+/g) || []).map(Number);\n const na = nums(a);\n const nb = nums(b);\n const len = Math.max(na.length, nb.length);\n for (let i = 0; i < len; i++) {\n const diff = (na[i] ?? 0) - (nb[i] ?? 0);\n if (diff !== 0) return diff;\n }\n return a.localeCompare(b);\n });\n}\n\n/**\n * Build a filtered data object from an array of jq-like paths.\n * Each path extracts data and places it in the result under the final key name.\n */\nfunction filterData(\n data: Record<string, ParameterValue>,\n keys: string[]\n): Record<string, ParameterValue> {\n const result: Record<string, ParameterValue> = {};\n\n for (const keyPath of keys) {\n const extracted = extractPath(data, keyPath);\n if (extracted === undefined) continue;\n\n const cleanPath = keyPath.startsWith(\".\") ? keyPath.slice(1) : keyPath;\n\n // For simple paths like .base.N_E, use \"N_E\" as key\n // For paths with [], preserve more context\n let keyName: string;\n if (cleanPath.includes(\"[\")) {\n keyName = cleanPath.replace(/\\[\\]/g, \"\").replace(/\\[(\\d+)\\]/g, \"_$1\");\n } else {\n const parts = cleanPath.split(\".\");\n keyName = parts[parts.length - 1];\n }\n\n result[keyName] = extracted;\n }\n\n return result;\n}\n\n// Renders a flat section of key-value pairs in a dense grid\nfunction ParameterGrid({ entries }: { entries: [string, ParameterValue][] }) {\n if (entries.length === 0) return null;\n\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full border-collapse text-[11px] font-mono\">\n <thead>\n <tr className=\"text-muted-foreground/70 uppercase tracking-widest\">\n <th className=\"text-left font-semibold py-1.5 px-1 border-b border-border/50\">\n Parameter\n </th>\n <th className=\"text-right font-semibold py-1.5 px-1 border-b border-border/50\">\n Value\n </th>\n </tr>\n </thead>\n <tbody>\n {entries.map(([key, value]) => {\n const type = getValueType(value);\n return (\n <tr key={key} className=\"border-b border-border/30 last:border-b-0\">\n <td className=\"py-1.5 px-1 text-muted-foreground truncate\" title={key}>\n {key}\n </td>\n <td\n className={cn(\n \"py-1.5 px-1 text-right tabular-nums font-medium truncate\",\n type === \"number\" && \"text-foreground\",\n type === \"string\" && \"text-amber-600 dark:text-amber-500\",\n type === \"boolean\" && \"text-cyan-600 dark:text-cyan-500\",\n type === \"null\" && \"text-muted-foreground/50\"\n )}\n title={type === \"string\" ? `\"${formatValue(value)}\"` : formatValue(value)}\n >\n {type === \"string\" ? `\"${formatValue(value)}\"` : formatValue(value)}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n\n// Renders a nested section with its own header\nfunction ParameterSection({\n name,\n data,\n depth = 0\n}: {\n name: string;\n data: Record<string, ParameterValue>;\n depth?: number;\n}) {\n const [isCollapsed, setIsCollapsed] = useState(false);\n\n const entries = Object.entries(data);\n const leafEntries = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t !== \"object\" && t !== \"array\";\n });\n const nestedEntries = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t === \"object\" || t === \"array\";\n });\n\n const isTopLevel = depth === 0;\n\n return (\n <div\n className={cn(\n isTopLevel && \"mb-4 last:mb-0 rounded-md border border-border/40 bg-card/30 p-2\",\n !isTopLevel && \"mb-3 last:mb-0\"\n )}\n >\n <button\n onClick={() => setIsCollapsed(!isCollapsed)}\n className={cn(\n \"flex items-center gap-2 w-full text-left group mb-1.5 px-1\",\n isTopLevel && \"pb-1 border-b border-border/50\"\n )}\n >\n <span className=\"text-[10px] text-muted-foreground/60 select-none\">\n {isCollapsed ? \"▸\" : \"▾\"}\n </span>\n\n <span className={cn(\n \"font-mono text-[11px] uppercase tracking-widest\",\n isTopLevel\n ? \"text-foreground/80 font-semibold\"\n : \"text-muted-foreground/70\"\n )}>\n {name.replace(/_/g, \" \")}\n </span>\n\n <span className=\"text-[9px] font-mono text-muted-foreground/40 ml-auto\">\n {entries.length}\n </span>\n </button>\n\n {!isCollapsed && (\n <div className={cn(\n depth > 0 && \"pl-3 ml-1 border-l border-border/40\"\n )}>\n {leafEntries.length > 0 && (\n <div className={cn(nestedEntries.length > 0 && \"mb-3\")}>\n <ParameterGrid entries={leafEntries} />\n </div>\n )}\n\n {nestedEntries.map(([key, value]) => {\n const type = getValueType(value);\n if (type === \"array\") {\n const arr = value as ParameterValue[];\n return (\n <div key={key} className=\"mb-2 last:mb-0\">\n <div className=\"text-[10px] font-mono text-muted-foreground/60 uppercase tracking-wider mb-1\">\n {key} [{arr.length}]\n </div>\n <div className=\"pl-3 ml-1 border-l border-border/40\">\n {arr.map((item, i) => {\n const itemType = getValueType(item);\n if (itemType === \"object\") {\n return (\n <ParameterSection\n key={i}\n name={`${i}`}\n data={item as Record<string, ParameterValue>}\n depth={depth + 1}\n />\n );\n }\n return (\n <div key={i} className=\"text-[11px] font-mono text-foreground py-0.5\">\n [{i}] {formatValue(item)}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n return (\n <ParameterSection\n key={key}\n name={key}\n data={value as Record<string, ParameterValue>}\n depth={depth + 1}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\ninterface SingleParameterTableProps {\n /** Path to the YAML or JSON file */\n path: string;\n /**\n * Optional array of jq-like paths to filter which parameters to show.\n * Examples:\n * - [\".base.N_E\", \".base.N_I\"] → show only N_E and N_I from base\n * - [\".base\"] → show entire base section\n * - [\".default_inputs\", \".base.dt\"] → show default_inputs section and dt from base\n */\n keys?: string[];\n /**\n * Optional array of key/label pairs to render as a markdown-style table.\n * Each key is a jq-like path (e.g., \".base.dt\").\n */\n pairs?: Array<{ key: string; label?: string }>;\n /** Optional label to show above the table */\n label?: string;\n /** Whether to include vertical margin (default true) */\n withMargin?: boolean;\n}\n\ninterface ParameterTableProps {\n /** Path to the YAML or JSON file, supports glob patterns like \"*.yaml\" */\n path: string;\n /**\n * Optional array of jq-like paths to filter which parameters to show.\n */\n keys?: string[];\n /**\n * Optional array of key/label pairs to render as a markdown-style table.\n */\n pairs?: Array<{ key: string; label?: string }>;\n}\n\n/**\n * Estimate the height contribution of a data structure.\n */\nfunction estimateHeight(data: Record<string, ParameterValue>, depth = 0): number {\n const entries = Object.entries(data);\n let height = 0;\n\n for (const [, value] of entries) {\n const type = getValueType(value);\n if (type === \"object\") {\n height += 28 + estimateHeight(value as Record<string, ParameterValue>, depth + 1);\n } else if (type === \"array\") {\n const arr = value as ParameterValue[];\n height += 28;\n for (const item of arr) {\n if (getValueType(item) === \"object\") {\n height += 24 + estimateHeight(item as Record<string, ParameterValue>, depth + 1);\n } else {\n height += 24;\n }\n }\n } else {\n height += 24;\n }\n }\n\n return height;\n}\n\n/**\n * Split entries into balanced columns based on estimated height.\n */\nfunction splitIntoColumns<T extends [string, ParameterValue]>(\n entries: T[],\n numColumns: number\n): T[][] {\n if (numColumns <= 1) return [entries];\n\n const entryHeights = entries.map(([, value]) => {\n const type = getValueType(value);\n if (type === \"object\") {\n return 28 + estimateHeight(value as Record<string, ParameterValue>);\n } else if (type === \"array\") {\n const arr = value as ParameterValue[];\n let h = 28;\n for (const item of arr) {\n if (getValueType(item) === \"object\") {\n h += 24 + estimateHeight(item as Record<string, ParameterValue>);\n } else {\n h += 24;\n }\n }\n return h;\n }\n return 24;\n });\n\n const totalHeight = entryHeights.reduce((a, b) => a + b, 0);\n const targetPerColumn = totalHeight / numColumns;\n\n const columns: T[][] = [];\n let currentColumn: T[] = [];\n let currentHeight = 0;\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const entryHeight = entryHeights[i];\n\n if (currentHeight >= targetPerColumn && columns.length < numColumns - 1 && currentColumn.length > 0) {\n columns.push(currentColumn);\n currentColumn = [];\n currentHeight = 0;\n }\n\n currentColumn.push(entry);\n currentHeight += entryHeight;\n }\n\n if (currentColumn.length > 0) {\n columns.push(currentColumn);\n }\n\n return columns;\n}\n\nfunction SingleParameterTable({ path, keys, pairs, label, withMargin = true }: SingleParameterTableProps) {\n const { content, loading, error } = useFileContent(path);\n\n const { parsed, parseError } = useMemo(() => {\n if (!content) return { parsed: null, parseError: 'no content' };\n\n const data = parseConfigFile(content, path);\n if (!data) {\n // Check why parsing failed\n if (!path.match(/\\.(yaml|yml|json)$/i)) {\n return { parsed: null, parseError: `unsupported file type` };\n }\n // Check if content looks like HTML (404 page)\n if (content.trim().startsWith('<!') || content.trim().startsWith('<html')) {\n return { parsed: null, parseError: `file not found` };\n }\n return { parsed: null, parseError: `invalid ${path.split('.').pop()} syntax` };\n }\n\n if (pairs && pairs.length > 0) {\n return { parsed: data, parseError: null };\n }\n\n if (keys && keys.length > 0) {\n const filtered = filterData(data, keys);\n if (Object.keys(filtered).length === 0) {\n return { parsed: null, parseError: `keys not found: ${keys.join(', ')}` };\n }\n return { parsed: filtered, parseError: null };\n }\n\n return { parsed: data, parseError: null };\n }, [content, path, keys]);\n\n if (loading) {\n return (\n <div className=\"my-6 p-4 rounded border border-border/50 bg-card/30\">\n <div className=\"flex items-center gap-2 text-muted-foreground/60\">\n <div className=\"w-3 h-3 border border-current border-t-transparent rounded-full animate-spin\" />\n <span className=\"text-[11px] font-mono\">loading parameters...</span>\n </div>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"my-6 p-3 rounded border border-destructive/30 bg-destructive/5\">\n <p className=\"text-[11px] font-mono text-destructive\">{error}</p>\n </div>\n );\n }\n\n if (!parsed) {\n return (\n <div className={cn(\"p-3 rounded border border-border/50 bg-card/30\", withMargin && \"my-6\")}>\n <p className=\"text-[11px] font-mono text-muted-foreground\">\n {label && <span className=\"text-foreground/60\">{label}: </span>}\n {parseError || 'unable to parse'}\n </p>\n </div>\n );\n }\n\n const entries = Object.entries(parsed);\n\n const topLeaves = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t !== \"object\" && t !== \"array\";\n });\n const topNested = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t === \"object\" || t === \"array\";\n });\n\n const useColumns = false;\n\n const columns = [topNested];\n\n const filename = path.split(\"/\").pop() || path;\n\n const renderPairsTable = () => {\n if (!pairs || pairs.length === 0) return null;\n\n return (\n <div className=\"not-prose my-6 overflow-x-auto border border-border rounded-md\">\n <table className=\"w-full text-sm border-collapse\">\n <thead className=\"bg-muted/50\">\n <tr className=\"border-b border-border last:border-b-0\">\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n Parameter\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n Value\n </th>\n </tr>\n </thead>\n <tbody>\n {pairs.map(({ key, label: rowLabel }) => {\n const value = extractPath(parsed, key);\n const type = value === undefined ? \"missing\" : getValueType(value);\n const displayValue = value === undefined\n ? \"—\"\n : type === \"string\"\n ? `\"${formatValue(value)}\"`\n : formatValue(value);\n\n return (\n <tr key={key} className=\"border-b border-border last:border-b-0\">\n <td className=\"px-4 py-3 align-top\">{rowLabel || key}</td>\n <td\n className={cn(\n \"px-4 py-3 align-top\",\n type === \"missing\" && \"text-muted-foreground\"\n )}\n >\n {displayValue}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n };\n\n const renderNestedEntry = ([key, value]: [string, ParameterValue]) => {\n const type = getValueType(value);\n if (type === \"array\") {\n const arr = value as ParameterValue[];\n return (\n <div key={key} className=\"mb-4 last:mb-0 rounded-md border border-border/40 bg-card/30 p-2\">\n <div className=\"text-[11px] font-mono text-foreground/80 uppercase tracking-widest font-semibold mb-1.5 pb-1 border-b border-border/50 px-1\">\n {key.replace(/_/g, \" \")} [{arr.length}]\n </div>\n <div className=\"pl-3 ml-1 border-l border-border/40\">\n {arr.map((item, i) => {\n const itemType = getValueType(item);\n if (itemType === \"object\") {\n return (\n <ParameterSection\n key={i}\n name={`${i}`}\n data={item as Record<string, ParameterValue>}\n depth={1}\n />\n );\n }\n return (\n <div key={i} className=\"text-[11px] font-mono text-foreground py-0.5\">\n [{i}] {formatValue(item)}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n return (\n <ParameterSection\n key={key}\n name={key}\n data={value as Record<string, ParameterValue>}\n depth={0}\n />\n );\n };\n\n return (\n <div className={cn(\"not-prose\", withMargin && \"my-6\")}>\n {label && (\n <div className=\"text-[11px] font-mono text-muted-foreground mb-1.5 truncate\" title={label}>\n {label}\n </div>\n )}\n {pairs && pairs.length > 0 ? (\n renderPairsTable()\n ) : (\n <div className=\"rounded border border-border/60 bg-card/20 p-3 overflow-hidden max-w-full\">\n {topLeaves.length > 0 && (\n <div className={cn(topNested.length > 0 && \"mb-4 pb-3 border-b border-border/30\")}>\n <ParameterGrid entries={topLeaves} />\n </div>\n )}\n\n {useColumns ? (\n <div\n className=\"grid gap-6\"\n style={{ gridTemplateColumns: `repeat(${columns.length}, 1fr)` }}\n >\n {columns.map((columnEntries, colIndex) => (\n <div key={colIndex} className={cn(\n colIndex > 0 && \"border-l border-border/30 pl-6\"\n )}>\n {columnEntries.map(renderNestedEntry)}\n </div>\n ))}\n </div>\n ) : (\n topNested.map(renderNestedEntry)\n )}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * ParameterTable component that displays YAML/JSON config files.\n * Supports glob patterns in the path prop to show multiple files.\n */\nexport function ParameterTable({ path, keys, pairs }: ParameterTableProps) {\n const { \"*\": routePath = \"\" } = useParams();\n\n // Get current directory from route\n const currentDir = routePath\n .replace(/\\/?[^/]+\\.mdx$/i, \"\")\n .replace(/\\/$/, \"\")\n || \".\";\n\n // Resolve relative paths\n let resolvedPath = path;\n if (path?.startsWith(\"./\")) {\n const relativePart = path.slice(2);\n resolvedPath = currentDir === \".\" ? relativePart : `${currentDir}/${relativePart}`;\n } else if (path && !path.startsWith(\"/\") && !path.includes(\"/\") && !isGlobPattern(path)) {\n resolvedPath = currentDir === \".\" ? path : `${currentDir}/${path}`;\n }\n\n // Check if this is a glob pattern\n const hasGlob = isGlobPattern(resolvedPath);\n\n // For glob patterns, get the base directory (directory containing the glob pattern)\n const baseDir = useMemo(() => {\n if (!hasGlob) return null;\n // Get everything before the first glob character\n const beforeGlob = resolvedPath.split(/[*?\\[]/, 1)[0];\n // Extract directory portion (everything up to the last slash)\n const lastSlash = beforeGlob.lastIndexOf('/');\n if (lastSlash === -1) return \".\";\n return beforeGlob.slice(0, lastSlash) || \".\";\n }, [hasGlob, resolvedPath]);\n\n const { directory } = useDirectory(baseDir || \".\");\n\n // Find matching files for glob patterns\n const matchingPaths = useMemo(() => {\n if (!hasGlob || !directory) return [];\n\n const allFiles = collectAllConfigFiles(directory);\n const paths = allFiles\n .map(f => f.path)\n .filter(p => minimatch(p, resolvedPath, { matchBase: true }));\n\n sortPathsNumerically(paths);\n\n return paths;\n }, [hasGlob, directory, resolvedPath, path, baseDir]);\n\n // If not a glob pattern, just render the single table\n if (!hasGlob) {\n return <SingleParameterTable path={resolvedPath} keys={keys} pairs={pairs} />;\n }\n\n // Loading state for glob patterns\n if (!directory) {\n return (\n <div className=\"my-6 p-4 rounded border border-border/50 bg-card/30\">\n <div className=\"flex items-center gap-2 text-muted-foreground/60\">\n <div className=\"w-3 h-3 border border-current border-t-transparent rounded-full animate-spin\" />\n <span className=\"text-[11px] font-mono\">loading parameters...</span>\n </div>\n </div>\n );\n }\n\n // No matches\n if (matchingPaths.length === 0) {\n return (\n <div className=\"my-6 p-3 rounded border border-border/50 bg-card/30\">\n <p className=\"text-[11px] font-mono text-muted-foreground\">\n no files matching: {resolvedPath}\n <br />\n <span className=\"text-muted-foreground/50\">(base dir: {baseDir}, original: {path})</span>\n </p>\n </div>\n );\n }\n\n const scrollRef = useRef<HTMLDivElement>(null);\n usePreventSwipeNavigation(scrollRef);\n\n // Breakout width based on count\n const count = matchingPaths.length;\n const breakoutClass = count >= 4\n ? 'w-[96vw] ml-[calc(-48vw+50%)]'\n : count >= 2\n ? 'w-[75vw] ml-[calc(-37.5vw+50%)]'\n : '';\n\n return (\n <div className={`my-6 ${breakoutClass}`}>\n <div ref={scrollRef} className=\"flex gap-4 overflow-x-auto overscroll-x-contain pb-2\">\n {matchingPaths.map((filePath) => (\n <div key={filePath} className=\"flex-none w-[280px]\">\n <SingleParameterTable\n path={filePath}\n keys={keys}\n pairs={pairs}\n label={filePath.split('/').pop() || filePath}\n withMargin={false}\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -91,6 +91,13 @@
91
91
  margin-right: 0;
92
92
  }
93
93
 
94
+ .gallery-scroll-row {
95
+ padding-left: var(--page-padding);
96
+ padding-right: var(--page-padding);
97
+ scroll-padding-left: var(--page-padding);
98
+ scroll-padding-right: var(--page-padding);
99
+ }
100
+
94
101
  .dark {
95
102
  /* Dark theme - zinc neutrals, no blue tint */
96
103
  --background: 0 0% 7%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veslx",
3
- "version": "0.1.56",
3
+ "version": "0.1.58",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,7 +36,8 @@ function usePreventSwipeNavigation(ref: React.RefObject<HTMLElement | null>) {
36
36
  }
37
37
 
38
38
  function getImageLabel(path: string): string {
39
- const filename = path.split('/').pop() || path;
39
+ const cleanPath = path.split(/[?#]/)[0];
40
+ const filename = cleanPath.split('/').pop() || cleanPath;
40
41
  return filename
41
42
  .replace(/\.(png|jpg|jpeg|gif|svg|webp)$/i, '')
42
43
  .replace(/[_-]/g, ' ')
@@ -44,13 +45,26 @@ function getImageLabel(path: string): string {
44
45
  .trim();
45
46
  }
46
47
 
47
- function getImageUrl(path: string): string {
48
+ function isAbsoluteUrl(path: string): boolean {
49
+ return /^https?:\/\//i.test(path) || path.startsWith('//');
50
+ }
51
+
52
+ function joinUrl(baseUrl: string, path: string): string {
53
+ const trimmedBase = baseUrl.replace(/\/+$/, '');
54
+ const trimmedPath = path.replace(/^\/+/, '');
55
+ return `${trimmedBase}/${trimmedPath}`;
56
+ }
57
+
58
+ function getImageUrl(path: string, baseUrl?: string): string {
59
+ if (isAbsoluteUrl(path)) return path;
60
+ if (baseUrl) return joinUrl(baseUrl, path);
48
61
  return `/raw/${path}`;
49
62
  }
50
63
 
51
64
  export default function Gallery({
52
65
  path,
53
66
  globs = null,
67
+ baseUrl,
54
68
  caption,
55
69
  captionLabel,
56
70
  title,
@@ -62,6 +76,7 @@ export default function Gallery({
62
76
  }: {
63
77
  path?: string;
64
78
  globs?: string[] | null;
79
+ baseUrl?: string;
65
80
  caption?: string;
66
81
  captionLabel?: string;
67
82
  title?: string;
@@ -83,8 +98,8 @@ export default function Gallery({
83
98
  usePreventSwipeNavigation(scrollRef);
84
99
 
85
100
  const images: LightboxImage[] = useMemo(() =>
86
- paths.map(p => ({ src: getImageUrl(p), label: getImageLabel(p) })),
87
- [paths]
101
+ paths.map(p => ({ src: getImageUrl(p, baseUrl), label: getImageLabel(p) })),
102
+ [paths, baseUrl]
88
103
  );
89
104
 
90
105
  if (isLoading) {
@@ -142,7 +157,7 @@ export default function Gallery({
142
157
 
143
158
  return (
144
159
  <>
145
- <figure className={`not-prose relative py-6 md:py-8 ${shouldBreakOut ? (isTwo ? 'gallery-breakout w-[75vw] max-w-[var(--gallery-width)]' : isCompact ? 'gallery-breakout w-[96vw] max-w-[var(--gallery-width)]' : 'gallery-breakout w-[var(--gallery-width)]') : ''}`}>
160
+ <figure className={`not-prose relative py-6 md:py-8 ${shouldBreakOut ? (isTwo ? 'gallery-breakout w-[75vw] max-w-[var(--gallery-width)]' : isCompact ? 'gallery-breakout w-[96vw] max-w-[var(--gallery-width)]' : 'gallery-breakout w-screen') : ''}`}>
146
161
  {!isSingleWithChildren && !isSingle && (
147
162
  <div className="max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]">
148
163
  <FigureHeader title={title} subtitle={subtitle} />
@@ -171,12 +186,12 @@ export default function Gallery({
171
186
  {images.map((img, index) => imageElement(index, img, 'flex-1'))}
172
187
  </div>
173
188
  ) : (
174
- <div ref={scrollRef} className="flex gap-3 overflow-x-auto overscroll-x-contain pb-4">
189
+ <div ref={scrollRef} className="gallery-scroll-row flex gap-3 overflow-x-auto overscroll-x-contain pb-4">
175
190
  {images.map((img, index) => (
176
191
  <div
177
192
  key={index}
178
193
  title={img.label}
179
- className="flex-none w-[30%] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group"
194
+ className="flex-none w-[calc(var(--gallery-width)*0.3)] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group"
180
195
  onClick={() => lightbox.open(index)}
181
196
  >
182
197
  <LoadingImage
@@ -109,32 +109,43 @@ function ParameterGrid({ entries }: { entries: [string, ParameterValue][] }) {
109
109
  if (entries.length === 0) return null;
110
110
 
111
111
  return (
112
- <div className="grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-x-6 gap-y-px">
113
- {entries.map(([key, value]) => {
114
- const type = getValueType(value);
115
- return (
116
- <div
117
- key={key}
118
- className="flex items-baseline justify-between gap-2 py-1 group hover:bg-muted/30 -mx-1.5 px-1.5 rounded-sm transition-colors"
119
- >
120
- <span className="text-[11px] text-muted-foreground font-mono truncate">
121
- {key}
122
- </span>
123
- <span
124
- className={cn(
125
- "text-[11px] font-mono tabular-nums font-medium truncate max-w-[120px]",
126
- type === "number" && "text-foreground",
127
- type === "string" && "text-amber-600 dark:text-amber-500",
128
- type === "boolean" && "text-cyan-600 dark:text-cyan-500",
129
- type === "null" && "text-muted-foreground/50"
130
- )}
131
- title={type === "string" ? `"${formatValue(value)}"` : formatValue(value)}
132
- >
133
- {type === "string" ? `"${formatValue(value)}"` : formatValue(value)}
134
- </span>
135
- </div>
136
- );
137
- })}
112
+ <div className="overflow-x-auto">
113
+ <table className="w-full border-collapse text-[11px] font-mono">
114
+ <thead>
115
+ <tr className="text-muted-foreground/70 uppercase tracking-widest">
116
+ <th className="text-left font-semibold py-1.5 px-1 border-b border-border/50">
117
+ Parameter
118
+ </th>
119
+ <th className="text-right font-semibold py-1.5 px-1 border-b border-border/50">
120
+ Value
121
+ </th>
122
+ </tr>
123
+ </thead>
124
+ <tbody>
125
+ {entries.map(([key, value]) => {
126
+ const type = getValueType(value);
127
+ return (
128
+ <tr key={key} className="border-b border-border/30 last:border-b-0">
129
+ <td className="py-1.5 px-1 text-muted-foreground truncate" title={key}>
130
+ {key}
131
+ </td>
132
+ <td
133
+ className={cn(
134
+ "py-1.5 px-1 text-right tabular-nums font-medium truncate",
135
+ type === "number" && "text-foreground",
136
+ type === "string" && "text-amber-600 dark:text-amber-500",
137
+ type === "boolean" && "text-cyan-600 dark:text-cyan-500",
138
+ type === "null" && "text-muted-foreground/50"
139
+ )}
140
+ title={type === "string" ? `"${formatValue(value)}"` : formatValue(value)}
141
+ >
142
+ {type === "string" ? `"${formatValue(value)}"` : formatValue(value)}
143
+ </td>
144
+ </tr>
145
+ );
146
+ })}
147
+ </tbody>
148
+ </table>
138
149
  </div>
139
150
  );
140
151
  }
@@ -161,25 +172,29 @@ function ParameterSection({
161
172
  return t === "object" || t === "array";
162
173
  });
163
174
 
175
+ const isTopLevel = depth === 0;
176
+
164
177
  return (
165
- <div className={cn(depth === 0 && "mb-4 last:mb-0")}>
178
+ <div
179
+ className={cn(
180
+ isTopLevel && "mb-4 last:mb-0 rounded-md border border-border/40 bg-card/30 p-2",
181
+ !isTopLevel && "mb-3 last:mb-0"
182
+ )}
183
+ >
166
184
  <button
167
185
  onClick={() => setIsCollapsed(!isCollapsed)}
168
186
  className={cn(
169
- "flex items-center gap-2 w-full text-left group mb-1.5",
170
- depth === 0 && "pb-1 border-b border-border/50"
187
+ "flex items-center gap-2 w-full text-left group mb-1.5 px-1",
188
+ isTopLevel && "pb-1 border-b border-border/50"
171
189
  )}
172
190
  >
173
- <span className={cn(
174
- "text-[10px] text-muted-foreground/60 transition-transform duration-150 select-none",
175
- isCollapsed && "-rotate-90"
176
- )}>
177
- {isCollapsed ? "+" : "-"}
191
+ <span className="text-[10px] text-muted-foreground/60 select-none">
192
+ {isCollapsed ? "▸" : "▾"}
178
193
  </span>
179
194
 
180
195
  <span className={cn(
181
196
  "font-mono text-[11px] uppercase tracking-widest",
182
- depth === 0
197
+ isTopLevel
183
198
  ? "text-foreground/80 font-semibold"
184
199
  : "text-muted-foreground/70"
185
200
  )}>
@@ -259,6 +274,11 @@ interface SingleParameterTableProps {
259
274
  * - [".default_inputs", ".base.dt"] → show default_inputs section and dt from base
260
275
  */
261
276
  keys?: string[];
277
+ /**
278
+ * Optional array of key/label pairs to render as a markdown-style table.
279
+ * Each key is a jq-like path (e.g., ".base.dt").
280
+ */
281
+ pairs?: Array<{ key: string; label?: string }>;
262
282
  /** Optional label to show above the table */
263
283
  label?: string;
264
284
  /** Whether to include vertical margin (default true) */
@@ -272,6 +292,10 @@ interface ParameterTableProps {
272
292
  * Optional array of jq-like paths to filter which parameters to show.
273
293
  */
274
294
  keys?: string[];
295
+ /**
296
+ * Optional array of key/label pairs to render as a markdown-style table.
297
+ */
298
+ pairs?: Array<{ key: string; label?: string }>;
275
299
  }
276
300
 
277
301
  /**
@@ -359,7 +383,7 @@ function splitIntoColumns<T extends [string, ParameterValue]>(
359
383
  return columns;
360
384
  }
361
385
 
362
- function SingleParameterTable({ path, keys, label, withMargin = true }: SingleParameterTableProps) {
386
+ function SingleParameterTable({ path, keys, pairs, label, withMargin = true }: SingleParameterTableProps) {
363
387
  const { content, loading, error } = useFileContent(path);
364
388
 
365
389
  const { parsed, parseError } = useMemo(() => {
@@ -378,6 +402,10 @@ function SingleParameterTable({ path, keys, label, withMargin = true }: SinglePa
378
402
  return { parsed: null, parseError: `invalid ${path.split('.').pop()} syntax` };
379
403
  }
380
404
 
405
+ if (pairs && pairs.length > 0) {
406
+ return { parsed: data, parseError: null };
407
+ }
408
+
381
409
  if (keys && keys.length > 0) {
382
410
  const filtered = filterData(data, keys);
383
411
  if (Object.keys(filtered).length === 0) {
@@ -430,24 +458,65 @@ function SingleParameterTable({ path, keys, label, withMargin = true }: SinglePa
430
458
  return t === "object" || t === "array";
431
459
  });
432
460
 
433
- const estHeight = estimateHeight(parsed);
434
- const HEIGHT_THRESHOLD = 500;
435
- const numColumns = estHeight > HEIGHT_THRESHOLD ? Math.min(Math.ceil(estHeight / HEIGHT_THRESHOLD), 3) : 1;
436
- const useColumns = numColumns > 1 && topNested.length > 1;
461
+ const useColumns = false;
437
462
 
438
- const columns = useColumns
439
- ? splitIntoColumns(topNested as [string, ParameterValue][], numColumns)
440
- : [topNested];
463
+ const columns = [topNested];
441
464
 
442
465
  const filename = path.split("/").pop() || path;
443
466
 
467
+ const renderPairsTable = () => {
468
+ if (!pairs || pairs.length === 0) return null;
469
+
470
+ return (
471
+ <div className="not-prose my-6 overflow-x-auto border border-border rounded-md">
472
+ <table className="w-full text-sm border-collapse">
473
+ <thead className="bg-muted/50">
474
+ <tr className="border-b border-border last:border-b-0">
475
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
476
+ Parameter
477
+ </th>
478
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
479
+ Value
480
+ </th>
481
+ </tr>
482
+ </thead>
483
+ <tbody>
484
+ {pairs.map(({ key, label: rowLabel }) => {
485
+ const value = extractPath(parsed, key);
486
+ const type = value === undefined ? "missing" : getValueType(value);
487
+ const displayValue = value === undefined
488
+ ? "—"
489
+ : type === "string"
490
+ ? `"${formatValue(value)}"`
491
+ : formatValue(value);
492
+
493
+ return (
494
+ <tr key={key} className="border-b border-border last:border-b-0">
495
+ <td className="px-4 py-3 align-top">{rowLabel || key}</td>
496
+ <td
497
+ className={cn(
498
+ "px-4 py-3 align-top",
499
+ type === "missing" && "text-muted-foreground"
500
+ )}
501
+ >
502
+ {displayValue}
503
+ </td>
504
+ </tr>
505
+ );
506
+ })}
507
+ </tbody>
508
+ </table>
509
+ </div>
510
+ );
511
+ };
512
+
444
513
  const renderNestedEntry = ([key, value]: [string, ParameterValue]) => {
445
514
  const type = getValueType(value);
446
515
  if (type === "array") {
447
516
  const arr = value as ParameterValue[];
448
517
  return (
449
- <div key={key} className="mb-4 last:mb-0">
450
- <div className="text-[11px] font-mono text-foreground/80 uppercase tracking-widest font-semibold mb-1.5 pb-1 border-b border-border/50">
518
+ <div key={key} className="mb-4 last:mb-0 rounded-md border border-border/40 bg-card/30 p-2">
519
+ <div className="text-[11px] font-mono text-foreground/80 uppercase tracking-widest font-semibold mb-1.5 pb-1 border-b border-border/50 px-1">
451
520
  {key.replace(/_/g, " ")} [{arr.length}]
452
521
  </div>
453
522
  <div className="pl-3 ml-1 border-l border-border/40">
@@ -490,30 +559,34 @@ function SingleParameterTable({ path, keys, label, withMargin = true }: SinglePa
490
559
  {label}
491
560
  </div>
492
561
  )}
493
- <div className="rounded border border-border/60 bg-card/20 p-3 overflow-hidden max-w-full">
494
- {topLeaves.length > 0 && (
495
- <div className={cn(topNested.length > 0 && "mb-4 pb-3 border-b border-border/30")}>
496
- <ParameterGrid entries={topLeaves} />
497
- </div>
498
- )}
562
+ {pairs && pairs.length > 0 ? (
563
+ renderPairsTable()
564
+ ) : (
565
+ <div className="rounded border border-border/60 bg-card/20 p-3 overflow-hidden max-w-full">
566
+ {topLeaves.length > 0 && (
567
+ <div className={cn(topNested.length > 0 && "mb-4 pb-3 border-b border-border/30")}>
568
+ <ParameterGrid entries={topLeaves} />
569
+ </div>
570
+ )}
499
571
 
500
- {useColumns ? (
501
- <div
502
- className="grid gap-6"
503
- style={{ gridTemplateColumns: `repeat(${columns.length}, 1fr)` }}
504
- >
505
- {columns.map((columnEntries, colIndex) => (
506
- <div key={colIndex} className={cn(
507
- colIndex > 0 && "border-l border-border/30 pl-6"
508
- )}>
509
- {columnEntries.map(renderNestedEntry)}
510
- </div>
511
- ))}
512
- </div>
513
- ) : (
514
- topNested.map(renderNestedEntry)
515
- )}
516
- </div>
572
+ {useColumns ? (
573
+ <div
574
+ className="grid gap-6"
575
+ style={{ gridTemplateColumns: `repeat(${columns.length}, 1fr)` }}
576
+ >
577
+ {columns.map((columnEntries, colIndex) => (
578
+ <div key={colIndex} className={cn(
579
+ colIndex > 0 && "border-l border-border/30 pl-6"
580
+ )}>
581
+ {columnEntries.map(renderNestedEntry)}
582
+ </div>
583
+ ))}
584
+ </div>
585
+ ) : (
586
+ topNested.map(renderNestedEntry)
587
+ )}
588
+ </div>
589
+ )}
517
590
  </div>
518
591
  );
519
592
  }
@@ -522,7 +595,7 @@ function SingleParameterTable({ path, keys, label, withMargin = true }: SinglePa
522
595
  * ParameterTable component that displays YAML/JSON config files.
523
596
  * Supports glob patterns in the path prop to show multiple files.
524
597
  */
525
- export function ParameterTable({ path, keys }: ParameterTableProps) {
598
+ export function ParameterTable({ path, keys, pairs }: ParameterTableProps) {
526
599
  const { "*": routePath = "" } = useParams();
527
600
 
528
601
  // Get current directory from route
@@ -572,7 +645,7 @@ export function ParameterTable({ path, keys }: ParameterTableProps) {
572
645
 
573
646
  // If not a glob pattern, just render the single table
574
647
  if (!hasGlob) {
575
- return <SingleParameterTable path={resolvedPath} keys={keys} />;
648
+ return <SingleParameterTable path={resolvedPath} keys={keys} pairs={pairs} />;
576
649
  }
577
650
 
578
651
  // Loading state for glob patterns
@@ -619,6 +692,7 @@ export function ParameterTable({ path, keys }: ParameterTableProps) {
619
692
  <SingleParameterTable
620
693
  path={filePath}
621
694
  keys={keys}
695
+ pairs={pairs}
622
696
  label={filePath.split('/').pop() || filePath}
623
697
  withMargin={false}
624
698
  />
package/src/index.css CHANGED
@@ -91,6 +91,13 @@
91
91
  margin-right: 0;
92
92
  }
93
93
 
94
+ .gallery-scroll-row {
95
+ padding-left: var(--page-padding);
96
+ padding-right: var(--page-padding);
97
+ scroll-padding-left: var(--page-padding);
98
+ scroll-padding-right: var(--page-padding);
99
+ }
100
+
94
101
  .dark {
95
102
  /* Dark theme - zinc neutrals, no blue tint */
96
103
  --background: 0 0% 7%;