radiant-docs 0.1.41 → 0.1.43

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 (34) hide show
  1. package/package.json +1 -1
  2. package/template/astro.config.mjs +44 -40
  3. package/template/package-lock.json +7 -0
  4. package/template/package.json +1 -0
  5. package/template/src/components/Header.astro +150 -16
  6. package/template/src/components/MdxPage.astro +82 -23
  7. package/template/src/components/PagePagination.astro +44 -8
  8. package/template/src/components/Sidebar.astro +10 -1
  9. package/template/src/components/TableOfContents.astro +159 -53
  10. package/template/src/components/chat/AssistantDocsWidget.tsx +221 -8
  11. package/template/src/components/chat/AssistantEmbedPanel.tsx +1090 -104
  12. package/template/src/components/user/Accordion.astro +2 -2
  13. package/template/src/components/user/AccordionGroup.astro +1 -1
  14. package/template/src/components/user/Callout.astro +2 -2
  15. package/template/src/components/user/Card.astro +488 -0
  16. package/template/src/components/user/CardGradient.astro +964 -0
  17. package/template/src/components/user/CodeBlock.astro +1 -1
  18. package/template/src/components/user/CodeGroup.astro +1 -1
  19. package/template/src/components/user/Column.astro +25 -0
  20. package/template/src/components/user/Columns.astro +200 -0
  21. package/template/src/components/user/ComponentPreviewBlock.astro +11 -1
  22. package/template/src/components/user/Image.astro +1 -1
  23. package/template/src/components/user/Step.astro +1 -1
  24. package/template/src/components/user/Steps.astro +1 -1
  25. package/template/src/components/user/Tab.astro +1 -3
  26. package/template/src/components/user/Tabs.astro +2 -2
  27. package/template/src/layouts/Layout.astro +2 -4
  28. package/template/src/lib/assistant-chrome-defaults.ts +12 -0
  29. package/template/src/lib/assistant-embed-script.ts +209 -18
  30. package/template/src/lib/mdx/rehype-prefix-preview-heading-ids.ts +52 -0
  31. package/template/src/lib/mdx/remark-code-block-component.ts +14 -8
  32. package/template/src/lib/validation.ts +325 -75
  33. package/template/src/styles/global.css +81 -4
  34. package/template/src/components/chat/AskAiWidget.tsx +0 -2011
@@ -1,6 +1,11 @@
1
1
  import type { JSX } from "preact";
2
- import { useEffect, useState } from "preact/hooks";
3
- import AssistantEmbedPanel, { AssistantPanelIcon } from "./AssistantEmbedPanel";
2
+ import { navigate } from "astro:transitions/client";
3
+ import { useEffect, useRef, useState } from "preact/hooks";
4
+ import AssistantEmbedPanel, {
5
+ ASSISTANT_PANEL_STORAGE_KEY,
6
+ AssistantPanelIcon,
7
+ type AssistantPanelSize,
8
+ } from "./AssistantEmbedPanel";
4
9
  import {
5
10
  DEFAULT_ASSISTANT_CHROME_CONFIG,
6
11
  type AssistantChromeConfig,
@@ -30,8 +35,32 @@ type AssistantDocsWidgetProps = {
30
35
 
31
36
  type AssistantOpenRequestWindow = Window & {
32
37
  __assistantOpenRequested?: boolean;
38
+ __assistantToggleRequested?: boolean;
39
+ __assistantDocsLauncherEntered?: boolean;
33
40
  };
34
41
 
42
+ function normalizePanelSize(value: unknown): AssistantPanelSize {
43
+ return value === "expanded" ? "expanded" : "default";
44
+ }
45
+
46
+ function readPersistedPanelSize(): AssistantPanelSize {
47
+ if (typeof window === "undefined") {
48
+ return "default";
49
+ }
50
+
51
+ try {
52
+ const rawState = window.localStorage.getItem(ASSISTANT_PANEL_STORAGE_KEY);
53
+ if (!rawState) {
54
+ return "default";
55
+ }
56
+
57
+ const parsed = JSON.parse(rawState) as { panelSize?: unknown };
58
+ return normalizePanelSize(parsed.panelSize);
59
+ } catch {
60
+ return "default";
61
+ }
62
+ }
63
+
35
64
  export default function AssistantDocsWidget({
36
65
  apiPath,
37
66
  docsTitle,
@@ -48,7 +77,28 @@ export default function AssistantDocsWidget({
48
77
  chrome,
49
78
  }: AssistantDocsWidgetProps) {
50
79
  const [isOpen, setIsOpen] = useState(false);
80
+ const [panelSize, setPanelSize] = useState<AssistantPanelSize>(() =>
81
+ readPersistedPanelSize(),
82
+ );
83
+ const [panelSizeTransition, setPanelSizeTransition] = useState<
84
+ "expand" | "collapse" | ""
85
+ >("");
51
86
  const [openSignal, setOpenSignal] = useState(0);
87
+ const [isNavigationTransitionSuppressed, setIsNavigationTransitionSuppressed] =
88
+ useState(false);
89
+ const [hasLauncherEntered, setHasLauncherEntered] = useState(() => {
90
+ if (typeof window === "undefined") {
91
+ return false;
92
+ }
93
+
94
+ return Boolean(
95
+ (window as AssistantOpenRequestWindow).__assistantDocsLauncherEntered,
96
+ );
97
+ });
98
+ const widgetRef = useRef<HTMLDivElement | null>(null);
99
+ const navigationTransitionCleanupRef = useRef<(() => void) | null>(null);
100
+ const navigationTransitionTimeoutRef = useRef<number | null>(null);
101
+ const panelSizeTransitionTimeoutRef = useRef<number | null>(null);
52
102
  const chromeConfig = chrome ?? DEFAULT_ASSISTANT_CHROME_CONFIG;
53
103
  const numericZIndex =
54
104
  Number.parseInt(chromeConfig.zIndex, 10) ||
@@ -78,6 +128,9 @@ export default function AssistantDocsWidget({
78
128
  "--assistant-docs-launcher-shadow": chromeConfig.launcherShadow,
79
129
  "--assistant-docs-panel-width": chromeConfig.panelWidth,
80
130
  "--assistant-docs-panel-height": chromeConfig.panelHeight,
131
+ "--assistant-docs-panel-expanded-width": chromeConfig.panelExpandedWidth,
132
+ "--assistant-docs-panel-expanded-height": chromeConfig.panelExpandedHeight,
133
+ "--assistant-docs-panel-viewport-margin": chromeConfig.panelViewportMargin,
81
134
  "--assistant-docs-panel-gap": chromeConfig.panelGap,
82
135
  "--assistant-docs-panel-radius": chromeConfig.panelRadius,
83
136
  "--assistant-docs-panel-border": chromeConfig.panelBorder,
@@ -90,6 +143,8 @@ export default function AssistantDocsWidget({
90
143
  "--assistant-docs-panel-open-scale": chromeConfig.panelOpenScale,
91
144
  "--assistant-docs-panel-open-duration": chromeConfig.panelOpenDuration,
92
145
  "--assistant-docs-panel-open-timing": chromeConfig.panelOpenTiming,
146
+ "--assistant-docs-panel-size-duration": chromeConfig.panelSizeDuration,
147
+ "--assistant-docs-panel-size-timing": chromeConfig.panelSizeTiming,
93
148
  "--assistant-docs-panel-close-duration": chromeConfig.panelCloseDuration,
94
149
  "--assistant-docs-panel-close-timing": chromeConfig.panelCloseTiming,
95
150
  } as JSX.CSSProperties;
@@ -107,32 +162,150 @@ export default function AssistantDocsWidget({
107
162
  setIsOpen(false);
108
163
  };
109
164
 
165
+ const togglePanel = () => {
166
+ setIsOpen((wasOpen) => {
167
+ if (!wasOpen) {
168
+ setOpenSignal((previous) => previous + 1);
169
+ }
170
+ return !wasOpen;
171
+ });
172
+ };
173
+
174
+ const handlePanelSizeChange = (nextPanelSize: AssistantPanelSize) => {
175
+ setPanelSize((previousSize) => {
176
+ if (previousSize === nextPanelSize) {
177
+ return previousSize;
178
+ }
179
+
180
+ if (panelSizeTransitionTimeoutRef.current !== null) {
181
+ window.clearTimeout(panelSizeTransitionTimeoutRef.current);
182
+ }
183
+
184
+ setPanelSizeTransition(
185
+ nextPanelSize === "expanded" ? "expand" : "collapse",
186
+ );
187
+ panelSizeTransitionTimeoutRef.current = window.setTimeout(() => {
188
+ panelSizeTransitionTimeoutRef.current = null;
189
+ setPanelSizeTransition("");
190
+ }, 480);
191
+
192
+ return nextPanelSize;
193
+ });
194
+ };
195
+
196
+ const setPanelTransitionSuppressed = (isSuppressed: boolean) => {
197
+ setIsNavigationTransitionSuppressed(isSuppressed);
198
+ if (widgetRef.current) {
199
+ widgetRef.current.dataset.suppressTransition = String(isSuppressed);
200
+ }
201
+ };
202
+
203
+ const handleCurrentLinkNavigate = (href: string, sourceElement?: Element) => {
204
+ if (typeof window === "undefined") {
205
+ return;
206
+ }
207
+
208
+ navigationTransitionCleanupRef.current?.();
209
+ setPanelTransitionSuppressed(true);
210
+
211
+ const finishTransitionSuppression = () => {
212
+ document.removeEventListener(
213
+ "astro:after-swap",
214
+ finishTransitionSuppression,
215
+ );
216
+ if (navigationTransitionTimeoutRef.current !== null) {
217
+ window.clearTimeout(navigationTransitionTimeoutRef.current);
218
+ navigationTransitionTimeoutRef.current = null;
219
+ }
220
+ navigationTransitionCleanupRef.current = null;
221
+
222
+ window.requestAnimationFrame(() => {
223
+ setPanelTransitionSuppressed(false);
224
+ });
225
+ };
226
+
227
+ document.addEventListener("astro:after-swap", finishTransitionSuppression, {
228
+ once: true,
229
+ });
230
+ navigationTransitionTimeoutRef.current = window.setTimeout(
231
+ finishTransitionSuppression,
232
+ 1200,
233
+ );
234
+ navigationTransitionCleanupRef.current = finishTransitionSuppression;
235
+
236
+ void navigate(
237
+ href,
238
+ sourceElement ? { sourceElement } : undefined,
239
+ ).catch(() => {
240
+ finishTransitionSuppression();
241
+ window.location.assign(href);
242
+ });
243
+ };
244
+
110
245
  useEffect(() => {
111
246
  const handleOpen = () => {
112
247
  (window as AssistantOpenRequestWindow).__assistantOpenRequested = false;
113
248
  openPanel();
114
249
  };
250
+ const handleToggle = () => {
251
+ (window as AssistantOpenRequestWindow).__assistantToggleRequested = false;
252
+ togglePanel();
253
+ };
115
254
 
116
255
  window.addEventListener("ask-ai:open", handleOpen);
256
+ window.addEventListener("ask-ai:toggle", handleToggle);
117
257
  if ((window as AssistantOpenRequestWindow).__assistantOpenRequested) {
118
258
  handleOpen();
119
259
  }
260
+ if ((window as AssistantOpenRequestWindow).__assistantToggleRequested) {
261
+ handleToggle();
262
+ }
120
263
 
121
264
  return () => {
122
265
  window.removeEventListener("ask-ai:open", handleOpen);
266
+ window.removeEventListener("ask-ai:toggle", handleToggle);
123
267
  };
124
268
  }, []);
125
269
 
270
+ useEffect(() => {
271
+ return () => {
272
+ navigationTransitionCleanupRef.current?.();
273
+ if (panelSizeTransitionTimeoutRef.current !== null) {
274
+ window.clearTimeout(panelSizeTransitionTimeoutRef.current);
275
+ }
276
+ };
277
+ }, []);
278
+
279
+ useEffect(() => {
280
+ if (typeof window === "undefined" || hasLauncherEntered) {
281
+ return;
282
+ }
283
+
284
+ (window as AssistantOpenRequestWindow).__assistantDocsLauncherEntered = true;
285
+ const launcherEntranceTimeout = window.setTimeout(() => {
286
+ setHasLauncherEntered(true);
287
+ }, 520);
288
+
289
+ return () => {
290
+ window.clearTimeout(launcherEntranceTimeout);
291
+ };
292
+ }, [hasLauncherEntered]);
293
+
126
294
  return (
127
295
  <div
296
+ ref={widgetRef}
128
297
  data-pagefind-ignore
129
298
  className="assistant-docs-widget"
130
299
  data-side={chromeConfig.side}
300
+ data-launcher-entered={String(hasLauncherEntered)}
301
+ data-suppress-transition={String(isNavigationTransitionSuppressed)}
131
302
  style={rootStyle}
132
303
  >
133
304
  <div
134
305
  className="assistant-docs-panel-shell"
135
306
  data-state={isOpen ? "open" : "closed"}
307
+ data-size={panelSize}
308
+ data-size-transition={panelSizeTransition}
136
309
  >
137
310
  <AssistantEmbedPanel
138
311
  apiPath={apiPath}
@@ -151,7 +324,10 @@ export default function AssistantDocsWidget({
151
324
  linkTarget="current"
152
325
  allowApiPathQueryOverride={false}
153
326
  openSignal={openSignal}
327
+ onRequestOpen={openPanel}
154
328
  onRequestClose={closePanel}
329
+ onRequestPanelSizeToggle={handlePanelSizeChange}
330
+ onCurrentLinkNavigate={handleCurrentLinkNavigate}
155
331
  />
156
332
  </div>
157
333
 
@@ -230,11 +406,14 @@ export default function AssistantDocsWidget({
230
406
  );
231
407
  box-shadow: var(--assistant-docs-launcher-shadow);
232
408
  cursor: pointer;
233
- animation: assistant-docs-launcher-enter 460ms linear backwards;
234
409
  transition: transform 180ms ease, opacity 160ms ease;
235
410
  transform-origin: center center;
236
411
  }
237
412
 
413
+ .assistant-docs-widget[data-launcher-entered="false"] .assistant-docs-launcher {
414
+ animation: assistant-docs-launcher-enter 460ms linear backwards;
415
+ }
416
+
238
417
  html.dark .assistant-docs-launcher,
239
418
  html[data-theme="dark"] .assistant-docs-launcher {
240
419
  --assistant-docs-theme: var(--assistant-docs-theme-dark);
@@ -297,6 +476,13 @@ export default function AssistantDocsWidget({
297
476
  }
298
477
 
299
478
  .assistant-docs-panel-shell {
479
+ --assistant-docs-panel-available-height: calc(
480
+ 100dvh -
481
+ var(--assistant-docs-offset-y) -
482
+ var(--assistant-docs-launcher-size) -
483
+ var(--assistant-docs-panel-gap) -
484
+ var(--assistant-docs-panel-viewport-margin)
485
+ );
300
486
  position: fixed;
301
487
  bottom: calc(
302
488
  var(--assistant-docs-offset-y) +
@@ -306,7 +492,10 @@ export default function AssistantDocsWidget({
306
492
  z-index: var(--assistant-docs-z-index);
307
493
  box-sizing: border-box;
308
494
  width: min(var(--assistant-docs-panel-width), calc(100vw - 32px));
309
- height: min(var(--assistant-docs-panel-height), calc(100dvh - 32px));
495
+ height: min(
496
+ var(--assistant-docs-panel-height),
497
+ var(--assistant-docs-panel-available-height)
498
+ );
310
499
  overflow: hidden;
311
500
  border: var(--assistant-docs-panel-border);
312
501
  border-radius: var(--assistant-docs-panel-radius);
@@ -318,12 +507,30 @@ export default function AssistantDocsWidget({
318
507
  pointer-events: auto;
319
508
  transform: scale(1);
320
509
  transform-origin: bottom right;
321
- transition-property: transform, opacity, display;
322
- transition-duration: var(--assistant-docs-panel-open-duration);
323
- transition-timing-function: var(--assistant-docs-panel-open-timing);
510
+ transition-property: width, height, transform, opacity, display;
511
+ transition-duration:
512
+ var(--assistant-docs-panel-size-duration),
513
+ var(--assistant-docs-panel-size-duration),
514
+ var(--assistant-docs-panel-open-duration),
515
+ var(--assistant-docs-panel-open-duration),
516
+ var(--assistant-docs-panel-open-duration);
517
+ transition-timing-function:
518
+ var(--assistant-docs-panel-size-timing),
519
+ var(--assistant-docs-panel-size-timing),
520
+ var(--assistant-docs-panel-open-timing),
521
+ var(--assistant-docs-panel-open-timing),
522
+ var(--assistant-docs-panel-open-timing);
324
523
  transition-behavior: allow-discrete;
325
524
  }
326
525
 
526
+ .assistant-docs-panel-shell[data-size="expanded"] {
527
+ width: min(var(--assistant-docs-panel-expanded-width), calc(100vw - 32px));
528
+ height: min(
529
+ var(--assistant-docs-panel-expanded-height),
530
+ var(--assistant-docs-panel-available-height)
531
+ );
532
+ }
533
+
327
534
  .dark .assistant-docs-panel-shell {
328
535
  border: var(--assistant-docs-panel-dark-border);
329
536
  background: var(--assistant-docs-panel-dark-background);
@@ -358,7 +565,11 @@ export default function AssistantDocsWidget({
358
565
  transition-behavior: allow-discrete;
359
566
  }
360
567
 
361
- @media (max-width: ${chromeConfig.mobileBreakpoint}) {
568
+ .assistant-docs-widget[data-suppress-transition="true"] .assistant-docs-panel-shell {
569
+ transition: none !important;
570
+ }
571
+
572
+ @media (max-width: ${chromeConfig.mobileBreakpoint}), (max-height: ${chromeConfig.panelFullscreenHeightBreakpoint}) {
362
573
  .assistant-docs-panel-shell {
363
574
  inset: 0 !important;
364
575
  z-index: var(--assistant-docs-mobile-panel-z-index) !important;
@@ -371,7 +582,9 @@ export default function AssistantDocsWidget({
371
582
  .assistant-docs-panel-shell > div {
372
583
  border-radius: 0;
373
584
  }
585
+ }
374
586
 
587
+ @media (max-width: ${chromeConfig.mobileBreakpoint}) {
375
588
  .assistant-docs-launcher {
376
589
  bottom: var(--assistant-docs-mobile-offset-y) !important;
377
590
  }