radiant-docs 0.1.38 → 0.1.40

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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/template/astro.config.mjs +38 -7
  3. package/template/package-lock.json +19 -7
  4. package/template/package.json +1 -1
  5. package/template/scripts/generate-robots-txt.mjs +29 -1
  6. package/template/scripts/stamp-image-versions.mjs +59 -33
  7. package/template/src/components/Footer.astro +2 -1
  8. package/template/src/components/Header.astro +8 -6
  9. package/template/src/components/LogoLink.astro +2 -1
  10. package/template/src/components/MdxPage.astro +15 -4
  11. package/template/src/components/PagePagination.astro +61 -0
  12. package/template/src/components/SidebarDropdown.astro +12 -8
  13. package/template/src/components/SidebarGroup.astro +1 -1
  14. package/template/src/components/SidebarMenu.astro +1 -1
  15. package/template/src/components/SidebarSegmented.astro +6 -5
  16. package/template/src/components/TableOfContents.astro +4 -13
  17. package/template/src/components/chat/AskAiWidget.tsx +274 -39
  18. package/template/src/components/endpoint/PlaygroundForm.astro +2 -1
  19. package/template/src/components/user/CodeBlock.astro +8 -5
  20. package/template/src/components/user/CodeGroup.astro +262 -14
  21. package/template/src/components/user/ComponentPreviewBlock.astro +4 -3
  22. package/template/src/components/user/Image.astro +43 -53
  23. package/template/src/components/user/Tabs.astro +128 -23
  24. package/template/src/layouts/Layout.astro +217 -7
  25. package/template/src/lib/base-path.ts +98 -0
  26. package/template/src/lib/component-error.ts +49 -10
  27. package/template/src/lib/mdx/remark-resolve-internal-links.ts +128 -18
  28. package/template/src/lib/pagefind.ts +62 -14
  29. package/template/src/lib/routes.ts +49 -1
  30. package/template/src/lib/static-asset-url.ts +3 -1
  31. package/template/src/lib/utils.ts +12 -4
  32. package/template/src/lib/validation.ts +376 -36
  33. package/template/src/pages/404.astro +2 -1
  34. package/template/src/pages/[...slug].astro +68 -6
  35. package/template/src/styles/global.css +85 -1
@@ -4,7 +4,7 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
4
4
  ---
5
5
 
6
6
  <div
7
- class="group/prose-code-group not-prose relative my-6 w-full max-w-full min-w-0"
7
+ class="group/prose-code-group not-prose relative my-6 w-full max-w-full min-w-0 shadow-xs rounded-xl"
8
8
  data-rd-code-group-root="true"
9
9
  >
10
10
  <div
@@ -19,7 +19,7 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
19
19
  <div
20
20
  data-rd-code-group-pill
21
21
  aria-hidden="true"
22
- class="pointer-events-none absolute top-1/2 z-0 h-[28px] -translate-y-1/2 rounded-[9px] border-[0.5px] border-neutral-200 bg-white shadow-xs opacity-0 transition-[left,width,opacity] duration-200 ease-out dark:border-neutral-700/70 dark:bg-(--rd-code-surface)"
22
+ class="pointer-events-none absolute top-1/2 z-0 h-[28px] -translate-y-1/2 rounded-[9px] border-[0.5px] border-neutral-200 bg-white opacity-0 transition-[left,width,opacity] duration-200 ease-out dark:border-neutral-700/70 dark:bg-(--rd-code-surface)"
23
23
  >
24
24
  </div>
25
25
  </div>
@@ -58,10 +58,13 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
58
58
  </div>
59
59
  </div>
60
60
 
61
- <div data-rd-code-group-content class="min-w-0">
61
+ <div
62
+ data-rd-code-group-content
63
+ class="relative min-w-0 overflow-hidden transition-[height] duration-300 ease-in-out"
64
+ >
62
65
  <slot />
63
66
  </div>
64
- <script is:inline>
67
+ <script is:inline data-astro-rerun>
65
68
  (() => {
66
69
  const script = document.currentScript;
67
70
  if (!(script instanceof HTMLScriptElement)) return;
@@ -74,14 +77,27 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
74
77
  const pillElement = root.querySelector("[data-rd-code-group-pill]");
75
78
  const copyButton = root.querySelector("[data-rd-copy-trigger='true']");
76
79
 
77
- if (!contentElement || !tabsElement || !pillElement) return;
80
+ if (
81
+ !(contentElement instanceof HTMLElement) ||
82
+ !(tabsElement instanceof HTMLElement) ||
83
+ !(pillElement instanceof HTMLElement)
84
+ ) {
85
+ return;
86
+ }
78
87
 
79
88
  const codeItems = Array.from(
80
89
  contentElement.querySelectorAll("[data-rd-code-group-item='true']"),
81
- );
90
+ ).filter((item) => item instanceof HTMLElement);
82
91
  if (codeItems.length === 0) return;
83
92
 
93
+ const prefersReducedMotion =
94
+ typeof window.matchMedia === "function" &&
95
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches;
96
+ const TRANSITION_DURATION_MS = prefersReducedMotion ? 0 : 300;
97
+
84
98
  let activeIndex = 0;
99
+ let transitionTimeoutId = null;
100
+ let activeItemResizeObserver = null;
85
101
 
86
102
  const setCopiedState = (button, copied) => {
87
103
  const copyIcon = button.querySelector("[data-rd-copy-icon]");
@@ -146,14 +162,28 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
146
162
  tabWrapper.appendChild(tabButton);
147
163
  tabsElement.appendChild(tabWrapper);
148
164
 
165
+ const panelCandidate = itemElement.querySelector(
166
+ "[data-rd-code-group-panel='true']",
167
+ );
168
+ const panelElement =
169
+ panelCandidate instanceof HTMLElement ? panelCandidate : itemElement;
170
+ const frameElement =
171
+ itemElement.firstElementChild instanceof HTMLElement
172
+ ? itemElement.firstElementChild
173
+ : null;
174
+
149
175
  return {
150
176
  itemElement,
177
+ panelElement,
178
+ frameElement,
151
179
  tabWrapper,
152
180
  tabButton,
153
181
  iconContainer,
154
182
  };
155
183
  });
156
184
 
185
+ contentElement.style.transitionDuration = `${TRANSITION_DURATION_MS}ms`;
186
+
157
187
  const syncPill = () => {
158
188
  const activeTab = tabs[activeIndex]?.tabWrapper;
159
189
  if (!activeTab) {
@@ -172,10 +202,9 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
172
202
  pillElement.classList.add("opacity-100");
173
203
  };
174
204
 
175
- const syncActiveTab = () => {
176
- tabs.forEach(({ itemElement, tabButton, iconContainer }, index) => {
205
+ const updateTabButtonStates = () => {
206
+ tabs.forEach(({ tabButton, iconContainer }, index) => {
177
207
  const isActive = index === activeIndex;
178
- itemElement.style.display = isActive ? "" : "none";
179
208
  tabButton.classList.toggle("text-foreground", isActive);
180
209
  tabButton.classList.toggle("text-muted-foreground", !isActive);
181
210
 
@@ -186,11 +215,163 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
186
215
  syncPill();
187
216
  };
188
217
 
218
+ const getItemOuterHeight = (itemElement) => {
219
+ const frameElement =
220
+ itemElement.firstElementChild instanceof HTMLElement
221
+ ? itemElement.firstElementChild
222
+ : null;
223
+ if (frameElement) {
224
+ const frameRectHeight = frameElement.getBoundingClientRect().height;
225
+ if (Number.isFinite(frameRectHeight) && frameRectHeight > 0) {
226
+ return Math.ceil(frameRectHeight);
227
+ }
228
+
229
+ const frameOffsetHeight = frameElement.offsetHeight;
230
+ if (Number.isFinite(frameOffsetHeight) && frameOffsetHeight > 0) {
231
+ return frameOffsetHeight;
232
+ }
233
+ }
234
+
235
+ const rectHeight = itemElement.getBoundingClientRect().height;
236
+ if (Number.isFinite(rectHeight) && rectHeight > 0) {
237
+ return Math.ceil(rectHeight);
238
+ }
239
+
240
+ const offsetHeight = itemElement.offsetHeight;
241
+ if (Number.isFinite(offsetHeight) && offsetHeight > 0) {
242
+ return offsetHeight;
243
+ }
244
+
245
+ return itemElement.scrollHeight;
246
+ };
247
+
248
+ const syncContentHeight = () => {
249
+ const activeItem = tabs[activeIndex]?.itemElement;
250
+ if (!activeItem) return;
251
+ contentElement.style.height = `${getItemOuterHeight(activeItem)}px`;
252
+ };
253
+
254
+ const observeActiveItemHeight = () => {
255
+ if (activeItemResizeObserver) {
256
+ activeItemResizeObserver.disconnect();
257
+ activeItemResizeObserver = null;
258
+ }
259
+
260
+ if (typeof ResizeObserver === "undefined") return;
261
+
262
+ const activeItem = tabs[activeIndex]?.itemElement;
263
+ if (!activeItem) return;
264
+
265
+ activeItemResizeObserver = new ResizeObserver(() => {
266
+ if (transitionTimeoutId !== null) return;
267
+ syncContentHeight();
268
+ });
269
+ activeItemResizeObserver.observe(activeItem);
270
+ };
271
+
272
+ const setPanelsToRestState = () => {
273
+ tabs.forEach(({ itemElement, panelElement, frameElement }, index) => {
274
+ const isActive = index === activeIndex;
275
+ itemElement.style.position = isActive ? "relative" : "absolute";
276
+ itemElement.style.inset = isActive ? "" : "0";
277
+ itemElement.style.opacity = isActive ? "1" : "0";
278
+ itemElement.style.visibility = isActive ? "visible" : "hidden";
279
+ itemElement.style.pointerEvents = isActive ? "auto" : "none";
280
+ itemElement.style.zIndex = isActive ? "1" : "0";
281
+ panelElement.style.transform = "translateX(0)";
282
+ panelElement.style.animation = "none";
283
+ panelElement.style.willChange = "auto";
284
+ if (frameElement) {
285
+ frameElement.style.height = "";
286
+ }
287
+ });
288
+ };
289
+
290
+ const normalizeToCurrentState = () => {
291
+ setPanelsToRestState();
292
+ syncContentHeight();
293
+ observeActiveItemHeight();
294
+ };
295
+
296
+ const transitionToIndex = (nextIndex) => {
297
+ if (nextIndex === activeIndex) return;
298
+
299
+ if (transitionTimeoutId !== null) {
300
+ window.clearTimeout(transitionTimeoutId);
301
+ transitionTimeoutId = null;
302
+ normalizeToCurrentState();
303
+ }
304
+
305
+ const previousIndex = activeIndex;
306
+ const direction = nextIndex > previousIndex ? 1 : -1;
307
+ activeIndex = nextIndex;
308
+ updateTabButtonStates();
309
+
310
+ const previousTab = tabs[previousIndex];
311
+ const nextTab = tabs[nextIndex];
312
+ const previousItem = previousTab?.itemElement;
313
+ const nextItem = nextTab?.itemElement;
314
+ const previousPanel = previousTab?.panelElement;
315
+ const nextPanel = nextTab?.panelElement;
316
+ const previousFrame = previousTab?.frameElement;
317
+ const nextFrame = nextTab?.frameElement;
318
+ if (!previousItem || !nextItem || !previousPanel || !nextPanel) {
319
+ normalizeToCurrentState();
320
+ return;
321
+ }
322
+
323
+ const previousHeight = getItemOuterHeight(previousItem);
324
+ const nextHeight = getItemOuterHeight(nextItem);
325
+ contentElement.style.height = `${previousHeight}px`;
326
+ if (TRANSITION_DURATION_MS === 0) {
327
+ contentElement.style.height = `${nextHeight}px`;
328
+ } else {
329
+ requestAnimationFrame(() => {
330
+ contentElement.style.height = `${nextHeight}px`;
331
+ });
332
+ }
333
+
334
+ previousItem.style.position = "absolute";
335
+ previousItem.style.inset = "0";
336
+ previousItem.style.opacity = "1";
337
+ previousItem.style.visibility = "visible";
338
+ previousItem.style.pointerEvents = "none";
339
+ previousItem.style.zIndex = "1";
340
+ previousPanel.style.transform = "translateX(0)";
341
+ previousPanel.style.willChange = "transform";
342
+ if (previousFrame) {
343
+ previousFrame.style.height = "100%";
344
+ }
345
+ previousPanel.style.animation =
346
+ direction === 1
347
+ ? `rd-code-group-slide-out-to-left ${TRANSITION_DURATION_MS}ms ease-in-out both`
348
+ : `rd-code-group-slide-out-to-right ${TRANSITION_DURATION_MS}ms ease-in-out both`;
349
+
350
+ nextItem.style.position = "absolute";
351
+ nextItem.style.inset = "0";
352
+ nextItem.style.opacity = "1";
353
+ nextItem.style.visibility = "visible";
354
+ nextItem.style.pointerEvents = "auto";
355
+ nextItem.style.zIndex = "2";
356
+ nextPanel.style.transform = "translateX(0)";
357
+ nextPanel.style.willChange = "transform";
358
+ if (nextFrame) {
359
+ nextFrame.style.height = "100%";
360
+ }
361
+ nextPanel.style.animation =
362
+ direction === 1
363
+ ? `rd-code-group-slide-in-from-right ${TRANSITION_DURATION_MS}ms ease-in-out both`
364
+ : `rd-code-group-slide-in-from-left ${TRANSITION_DURATION_MS}ms ease-in-out both`;
365
+
366
+ transitionTimeoutId = window.setTimeout(() => {
367
+ transitionTimeoutId = null;
368
+ normalizeToCurrentState();
369
+ }, TRANSITION_DURATION_MS);
370
+ };
371
+
189
372
  tabs.forEach(({ tabButton }, index) => {
190
373
  tabButton.addEventListener("click", () => {
191
- if (activeIndex === index) return;
192
- activeIndex = index;
193
- syncActiveTab();
374
+ transitionToIndex(index);
194
375
  });
195
376
  });
196
377
 
@@ -226,9 +407,76 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
226
407
  }
227
408
 
228
409
  tabsElement.addEventListener("scroll", syncPill, { passive: true });
229
- window.addEventListener("resize", syncPill);
230
- syncActiveTab();
410
+ window.addEventListener("resize", () => {
411
+ syncPill();
412
+ if (transitionTimeoutId === null) {
413
+ syncContentHeight();
414
+ }
415
+ });
416
+ updateTabButtonStates();
417
+ normalizeToCurrentState();
231
418
  requestAnimationFrame(syncPill);
232
419
  })();
233
420
  </script>
234
421
  </div>
422
+
423
+ <style>
424
+ [data-rd-code-group-content] > [data-rd-code-group-item="true"] {
425
+ position: relative;
426
+ z-index: 1;
427
+ }
428
+
429
+ [data-rd-code-group-content]
430
+ > [data-rd-code-group-item="true"]:not(:first-child) {
431
+ position: absolute;
432
+ inset: 0;
433
+ opacity: 0;
434
+ visibility: hidden;
435
+ pointer-events: none;
436
+ z-index: 0;
437
+ }
438
+
439
+ @keyframes rd-code-group-slide-in-from-right {
440
+ from {
441
+ transform: translateX(100%);
442
+ opacity: 0;
443
+ }
444
+ to {
445
+ transform: translateX(0);
446
+ opacity: 1;
447
+ }
448
+ }
449
+
450
+ @keyframes rd-code-group-slide-in-from-left {
451
+ from {
452
+ transform: translateX(-100%);
453
+ opacity: 0;
454
+ }
455
+ to {
456
+ transform: translateX(0);
457
+ opacity: 1;
458
+ }
459
+ }
460
+
461
+ @keyframes rd-code-group-slide-out-to-left {
462
+ from {
463
+ transform: translateX(0);
464
+ opacity: 1;
465
+ }
466
+ to {
467
+ transform: translateX(-100%);
468
+ opacity: 0;
469
+ }
470
+ }
471
+
472
+ @keyframes rd-code-group-slide-out-to-right {
473
+ from {
474
+ transform: translateX(0);
475
+ opacity: 1;
476
+ }
477
+ to {
478
+ transform: translateX(100%);
479
+ opacity: 0;
480
+ }
481
+ }
482
+ </style>
@@ -69,7 +69,7 @@ const isInitiallyExpanded = shouldShowAllCode || totalLineCount <= visibleLines;
69
69
  <slot />
70
70
  </div>
71
71
  <div
72
- class="rd-component-preview__code not-prose relative overflow-hidden rounded-b-xl bg-white dark:bg-(--rd-code-surface)"
72
+ class="rd-component-preview__code not-prose relative overflow-hidden rounded-b-xl bg-neutral-50 dark:bg-(--rd-code-surface)"
73
73
  data-rd-preview-expanded={isInitiallyExpanded ? "true" : "false"}
74
74
  style={{ "--rd-preview-visible-lines": String(visibleLines) }}
75
75
  >
@@ -142,7 +142,7 @@ const isInitiallyExpanded = shouldShowAllCode || totalLineCount <= visibleLines;
142
142
  }
143
143
 
144
144
  .rd-component-preview__code :global(.group\/prose-code > div) {
145
- background-color: #fff !important;
145
+ background-color: var(--color-neutral-50) !important;
146
146
  border-top-left-radius: 0 !important;
147
147
  border-top-right-radius: 0 !important;
148
148
  }
@@ -153,7 +153,7 @@ const isInitiallyExpanded = shouldShowAllCode || totalLineCount <= visibleLines;
153
153
 
154
154
  .rd-component-preview__code :global(.group\/prose-code pre),
155
155
  .rd-component-preview__code :global(.group\/prose-code code) {
156
- background-color: #fff !important;
156
+ background-color: var(--color-neutral-50) !important;
157
157
  }
158
158
 
159
159
  :global(.dark) .rd-component-preview__code :global(.group\/prose-code pre),
@@ -199,6 +199,7 @@ const isInitiallyExpanded = shouldShowAllCode || totalLineCount <= visibleLines;
199
199
 
200
200
  <script
201
201
  is:inline
202
+ data-astro-rerun
202
203
  define:vars={{
203
204
  visibleLines,
204
205
  totalLineCount,
@@ -1,28 +1,54 @@
1
1
  ---
2
- import type { HTMLAttributes } from "astro/types";
3
- import { validateProps } from "../../lib/component-error";
2
+ import { validateNoUnknownProps, validateProps } from "../../lib/component-error";
4
3
  import { renderMarkdown } from "../../lib/utils";
5
4
  import { resolveStaticAssetUrl } from "../../lib/static-asset-url";
6
5
 
7
- interface Props extends HTMLAttributes<"img"> {
6
+ interface Props {
8
7
  src: string;
8
+ alt?: string;
9
+ title?: string;
10
+ width?: number | string;
9
11
  zoom?: boolean;
10
12
  }
11
13
 
12
- const { title, zoom = true, ...attrs } = Astro.props as Props;
14
+ const imageProps = Astro.props as Record<string, unknown>;
15
+
16
+ validateNoUnknownProps(
17
+ "Image",
18
+ imageProps,
19
+ ["src", "alt", "title", "width", "zoom"],
20
+ Astro.url.pathname,
21
+ );
22
+
23
+ validateProps(
24
+ "Image",
25
+ imageProps,
26
+ {
27
+ src: { required: true, type: "string" },
28
+ alt: { type: "string" },
29
+ title: { type: "string" },
30
+ width: { type: ["number", "string"] },
31
+ zoom: { type: "boolean" },
32
+ },
33
+ Astro.url.pathname,
34
+ );
35
+
36
+ const { src, alt, title, width, zoom = true } = imageProps as Props;
13
37
  const zoomEnabled = zoom !== false;
14
- const attrsRecord = attrs as Record<string, unknown>;
15
- if (typeof attrsRecord.src === "string") {
16
- attrsRecord.src = resolveStaticAssetUrl(attrsRecord.src);
38
+ const resolvedSrc = resolveStaticAssetUrl(src);
39
+ const rawWidth = width;
40
+ const imageAttrs: Record<string, unknown> = { src: resolvedSrc };
41
+ if (typeof alt === "string") {
42
+ imageAttrs.alt = alt;
43
+ }
44
+ if (width !== undefined) {
45
+ imageAttrs.width = width;
46
+ }
47
+
48
+ const zoomAttrs: Record<string, unknown> = { src: resolvedSrc };
49
+ if (typeof alt === "string") {
50
+ zoomAttrs.alt = alt;
17
51
  }
18
- const rawStyle = attrsRecord.style;
19
- const rawWidth = attrsRecord.width;
20
- const zoomAttrs: Record<string, unknown> = { ...attrsRecord };
21
- delete zoomAttrs.style;
22
- delete zoomAttrs.width;
23
- delete zoomAttrs.height;
24
- delete zoomAttrs.class;
25
- delete zoomAttrs.className;
26
52
 
27
53
  function isConstrainedWidthValue(value: unknown): boolean {
28
54
  if (typeof value === "number") {
@@ -55,48 +81,12 @@ function isConstrainedWidthValue(value: unknown): boolean {
55
81
  return true;
56
82
  }
57
83
 
58
- function hasStyleWidthConstraint(styleValue: unknown): boolean {
59
- if (typeof styleValue === "string") {
60
- const widthMatch = styleValue.match(/(?:^|;)\s*width\s*:\s*([^;]+)/i);
61
- const maxWidthMatch = styleValue.match(
62
- /(?:^|;)\s*max-width\s*:\s*([^;]+)/i,
63
- );
64
-
65
- return (
66
- isConstrainedWidthValue(widthMatch?.[1]) ||
67
- isConstrainedWidthValue(maxWidthMatch?.[1])
68
- );
69
- }
70
-
71
- if (!styleValue || typeof styleValue !== "object") return false;
72
- const styleObject = styleValue as Record<string, unknown>;
73
-
74
- return (
75
- isConstrainedWidthValue(styleObject.width) ||
76
- isConstrainedWidthValue(styleObject.maxWidth) ||
77
- isConstrainedWidthValue(styleObject["max-width"])
78
- );
79
- }
80
-
81
- const hasCustomImageWidth =
82
- isConstrainedWidthValue(rawWidth) || hasStyleWidthConstraint(rawStyle);
84
+ const hasCustomImageWidth = isConstrainedWidthValue(rawWidth);
83
85
 
84
86
  const captionHtml =
85
87
  typeof title === "string" && title.trim().length > 0
86
88
  ? (await renderMarkdown(title)).replace(/^<p>([\s\S]*)<\/p>\n?$/, "$1")
87
89
  : "";
88
-
89
- validateProps(
90
- "Image",
91
- Astro.props as Record<string, any>,
92
- {
93
- src: { required: true, type: "string" },
94
- alt: { type: "string" },
95
- title: { type: "string" },
96
- zoom: { type: "boolean" },
97
- },
98
- Astro.url.pathname,
99
- );
100
90
  ---
101
91
 
102
92
  <figure
@@ -207,7 +197,7 @@ validateProps(
207
197
  class="overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800 bg-neutral-100 dark:bg-(--rd-code-surface)"
208
198
  >
209
199
  <img
210
- {...attrs}
200
+ {...imageAttrs}
211
201
  x-ref="img"
212
202
  title={title}
213
203
  class:list={[