radiant-docs 0.1.40 → 0.1.41

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.
@@ -0,0 +1,95 @@
1
+ ---
2
+ import "../../styles/global.css";
3
+ import "../../styles/google-sans-flex.css";
4
+ import "../../styles/geist-mono.css";
5
+ import AssistantEmbedPanel from "./AssistantEmbedPanel";
6
+ import { getAssistantPanelRuntimeConfig } from "../../lib/assistant-panel-config";
7
+ import { getDocsThemeCss } from "../../lib/theme-css";
8
+ import { getConfig } from "../../lib/validation";
9
+
10
+ const config = await getConfig();
11
+ const themeCss = getDocsThemeCss(config.theme);
12
+ const assistantConfig = getAssistantPanelRuntimeConfig(config);
13
+ ---
14
+
15
+ <!doctype html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="utf-8" />
19
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
20
+ <meta name="robots" content="noindex" />
21
+ <style is:inline is:global set:html={themeCss}></style>
22
+ <script is:inline>
23
+ (() => {
24
+ const applyTheme = (theme) => {
25
+ if (theme !== "light" && theme !== "dark") {
26
+ return;
27
+ }
28
+
29
+ document.documentElement.dataset.theme = theme;
30
+ document.documentElement.classList.toggle("dark", theme === "dark");
31
+ };
32
+
33
+ const params = new URLSearchParams(window.location.search);
34
+ const forcedTheme = params.get("mode");
35
+ const localStorageTheme = localStorage.getItem("theme");
36
+ const prefersDark = window.matchMedia(
37
+ "(prefers-color-scheme: dark)",
38
+ ).matches;
39
+ const theme =
40
+ forcedTheme === "light" || forcedTheme === "dark"
41
+ ? forcedTheme
42
+ : localStorageTheme === "light" || localStorageTheme === "dark"
43
+ ? localStorageTheme
44
+ : prefersDark
45
+ ? "dark"
46
+ : "light";
47
+
48
+ applyTheme(theme);
49
+
50
+ window.addEventListener("message", (event) => {
51
+ if (
52
+ !event.data ||
53
+ typeof event.data !== "object" ||
54
+ event.data.type !== "assistant-embed:set-theme"
55
+ ) {
56
+ return;
57
+ }
58
+
59
+ applyTheme(event.data.theme);
60
+ });
61
+ })();
62
+ </script>
63
+ <style is:inline>
64
+ html,
65
+ body {
66
+ width: 100%;
67
+ height: 100%;
68
+ margin: 0;
69
+ overflow: hidden;
70
+ background: transparent;
71
+ }
72
+
73
+ body {
74
+ color-scheme: inherit;
75
+ }
76
+
77
+ @media (max-width: 640px) {
78
+ .assistant-panel-shell > div {
79
+ border-radius: 0;
80
+ border-left: 0;
81
+ border-right: 0;
82
+ }
83
+ }
84
+ </style>
85
+ </head>
86
+ <body data-pagefind-ignore>
87
+ <div class="assistant-panel-shell h-full">
88
+ <AssistantEmbedPanel
89
+ client:only="preact"
90
+ {...assistantConfig}
91
+ linkTarget="blank"
92
+ />
93
+ </div>
94
+ </body>
95
+ </html>
@@ -72,13 +72,13 @@ const resolvedTitle = title ?? defaults.title;
72
72
 
73
73
  <aside
74
74
  class:list={[
75
- "my-5 space-y-1 rounded-lg border border-neutral-200 bg-white/80 shadow-xs px-4 py-3.5 pl-6 relative dark:border-neutral-800 dark:bg-(--rd-code-surface)",
75
+ "relative my-5 space-y-1 rounded-xl border-[0.5px]. border-neutral-900/8 bg-white px-4 py-3.5 pl-6 dark:border-neutral-800 dark:bg-(--rd-code-surface) shadow-[0px_1px_3px_0px_rgba(0,0,0,0.04),0px_0px_0px_0.8px_rgba(0,0,0,0.06)_inset,0px_-1px_0px_0px_rgba(0,0,0,0.06)_inset]",
76
76
  ]}
77
77
  role="note"
78
78
  >
79
79
  <div
80
80
  class:list={[
81
- "absolute left-2 inset-y-2 w-[2.5px] rounded-full",
81
+ "absolute left-2.5 inset-y-2.5 w-[2px] rounded-full mb-0",
82
82
  defaults.color,
83
83
  ]}
84
84
  style={color && { backgroundColor: color }}
@@ -96,11 +96,17 @@ const resolvedTitle = title ?? defaults.title;
96
96
  <img src={defaults.icon} alt="" width="16" height="16" class="" />
97
97
  )
98
98
  }
99
- <h6 class:list={["font-semibold text-sm text-neutral-900 dark:text-neutral-100"]}>
99
+ <h6
100
+ class:list={[
101
+ "font-semibold text-sm text-neutral-900 dark:text-neutral-100",
102
+ ]}
103
+ >
100
104
  {resolvedTitle}
101
105
  </h6>
102
106
  </div>
103
- <div class="**:first:mt-0! **:last:mb-0! text-sm text-neutral-700 dark:text-neutral-300">
107
+ <div
108
+ class="**:first:mt-0! **:last:mb-0! text-sm text-neutral-700 dark:text-neutral-300"
109
+ >
104
110
  <slot />
105
111
  </div>
106
112
  </aside>
@@ -7,19 +7,15 @@ import googleSansLatinExtWoff2 from "../assets/fonts/google-sans-flex/latin-ext.
7
7
  import geistMonoLatinWoff2 from "../assets/fonts/geist-mono/latin.woff2?url";
8
8
  import geistMonoLatinExtWoff2 from "../assets/fonts/geist-mono/latin-ext.woff2?url";
9
9
  import Sidebar from "../components/Sidebar.astro";
10
- import colors from "tailwindcss/colors";
11
- import {
12
- DEFAULT_THEME_COLOR_DARK,
13
- DEFAULT_THEME_COLOR_LIGHT,
14
- getConfig,
15
- type BaseColorOption,
16
- } from "../lib/validation";
10
+ import { getConfig } from "../lib/validation";
17
11
  import Header from "../components/Header.astro";
18
12
  import Footer from "../components/Footer.astro";
19
- import AskAiWidget from "../components/chat/AskAiWidget";
13
+ import AssistantDocsWidget from "../components/chat/AssistantDocsWidget.astro";
20
14
  import { ClientRouter } from "astro:transitions";
21
- import { prependBasePath, stripBasePath, withBasePath } from "../lib/base-path";
15
+ import { prependBasePath, stripBasePath } from "../lib/base-path";
16
+ import { getFaviconConfig } from "../lib/favicon";
22
17
  import { resolveStaticAssetUrl } from "../lib/static-asset-url";
18
+ import { getDocsThemeCss } from "../lib/theme-css";
23
19
 
24
20
  interface Props {
25
21
  pageTitle?: string;
@@ -43,117 +39,8 @@ function routePathToOgImagePath(routePath: string): string {
43
39
 
44
40
  const { pageTitle, pageDescription } = Astro.props as Props;
45
41
  const config = await getConfig();
46
- const themeBaseColor = config.theme?.baseColor ?? "neutral";
47
- const themeThemeColor = config.theme?.themeColor;
48
- const lightBaseColor: BaseColorOption =
49
- typeof themeBaseColor === "string" ? themeBaseColor : themeBaseColor.light;
50
- const darkBaseColor: BaseColorOption =
51
- typeof themeBaseColor === "string" ? themeBaseColor : themeBaseColor.dark;
52
- const lightThemeColor =
53
- typeof themeThemeColor === "string"
54
- ? themeThemeColor
55
- : (themeThemeColor?.light ?? DEFAULT_THEME_COLOR_LIGHT);
56
- const darkThemeColor =
57
- typeof themeThemeColor === "string"
58
- ? themeThemeColor
59
- : (themeThemeColor?.dark ?? DEFAULT_THEME_COLOR_DARK);
60
- const neutralColorShades = [
61
- "50",
62
- "100",
63
- "200",
64
- "300",
65
- "400",
66
- "500",
67
- "600",
68
- "700",
69
- "800",
70
- "900",
71
- "950",
72
- ] as const;
73
- const paletteColors = colors as unknown as Record<
74
- string,
75
- Record<(typeof neutralColorShades)[number], string>
76
- >;
77
- const getNeutralPaletteVariables = (baseColor: BaseColorOption): string[] => {
78
- const palette = paletteColors[baseColor] ?? paletteColors.neutral;
79
- return [
80
- `--color-neutral: ${palette["500"]};`,
81
- ...neutralColorShades.map(
82
- (shade) => `--color-neutral-${shade}: ${palette[shade]};`,
83
- ),
84
- ];
85
- };
86
- function normalizeHexColorToRgb(
87
- hexColor: string,
88
- ): { r: number; g: number; b: number } | null {
89
- const normalized = hexColor.replace("#", "").trim();
90
- if (/^[a-fA-F0-9]{3,4}$/.test(normalized)) {
91
- const [r, g, b] = normalized.split("");
92
- if (!r || !g || !b) return null;
93
- return {
94
- r: Number.parseInt(`${r}${r}`, 16),
95
- g: Number.parseInt(`${g}${g}`, 16),
96
- b: Number.parseInt(`${b}${b}`, 16),
97
- };
98
- }
99
-
100
- if (/^[a-fA-F0-9]{6,8}$/.test(normalized)) {
101
- return {
102
- r: Number.parseInt(normalized.slice(0, 2), 16),
103
- g: Number.parseInt(normalized.slice(2, 4), 16),
104
- b: Number.parseInt(normalized.slice(4, 6), 16),
105
- };
106
- }
107
-
108
- return null;
109
- }
110
-
111
- function getThemeForegroundColor(hexColor: string): "#ffffff" | "#111827" {
112
- const rgb = normalizeHexColorToRgb(hexColor);
113
- if (!rgb) return "#ffffff";
114
-
115
- const toLinear = (channel: number): number => {
116
- const normalized = channel / 255;
117
- return normalized <= 0.03928
118
- ? normalized / 12.92
119
- : ((normalized + 0.055) / 1.055) ** 2.4;
120
- };
121
-
122
- const luminance =
123
- 0.2126 * toLinear(rgb.r) +
124
- 0.7152 * toLinear(rgb.g) +
125
- 0.0722 * toLinear(rgb.b);
126
- return luminance > 0.45 ? "#111827" : "#ffffff";
127
- }
128
-
129
- const getThemeColorVariables = (themeColor: string): string[] => {
130
- const foreground = getThemeForegroundColor(themeColor);
131
- const iconFilter = foreground === "#ffffff" ? "invert(1)" : "none";
132
-
133
- return [
134
- `--color-theme: ${themeColor};`,
135
- `--color-theme-foreground: ${foreground};`,
136
- `--color-theme-icon-filter: ${iconFilter};`,
137
- "--color-theme-top: color-mix(in oklab, var(--color-theme) 88%, white);",
138
- "--color-theme-bottom: color-mix(in oklab, var(--color-theme) 90%, black);",
139
- ];
140
- };
141
- const lightNeutralVariables = getNeutralPaletteVariables(lightBaseColor);
142
- const darkNeutralVariables = getNeutralPaletteVariables(darkBaseColor);
143
- const lightThemeColorVariables = getThemeColorVariables(lightThemeColor);
144
- const darkThemeColorVariables = getThemeColorVariables(darkThemeColor);
145
- const neutralPaletteCss = [
146
- "html[data-theme='light'], html:not(.dark):not([data-theme='dark']) {",
147
- ...[...lightNeutralVariables, ...lightThemeColorVariables].map(
148
- (declaration) => ` ${declaration.replace(/;$/, " !important;")}`,
149
- ),
150
- "}",
151
- "html.dark, html[data-theme='dark'] {",
152
- ...[...darkNeutralVariables, ...darkThemeColorVariables].map(
153
- (declaration) => ` ${declaration.replace(/;$/, " !important;")}`,
154
- ),
155
- "}",
156
- ].join("\n");
42
+ const favicon = getFaviconConfig();
43
+ const neutralPaletteCss = getDocsThemeCss(config.theme);
157
44
  const resolvedPageTitle = pageTitle?.trim();
158
45
  const resolvedPageDescription =
159
46
  typeof pageDescription === "string" && pageDescription.trim().length > 0
@@ -183,23 +70,7 @@ const parsedOrgTier = Number.parseInt(
183
70
  const orgTier =
184
71
  Number.isFinite(parsedOrgTier) && parsedOrgTier > 0 ? parsedOrgTier : 1;
185
72
  const isDev = import.meta.env.DEV;
186
- const askAiDevHost = (import.meta.env.ASK_AI_DEV_HOST ?? "s").toString().trim();
187
- const askAiDevProxySecret = (import.meta.env.ASK_AI_DEV_PROXY_SECRET ?? "s")
188
- .toString()
189
- .trim();
190
- const hasAskAiDevConfig =
191
- askAiDevHost.length > 0 && askAiDevProxySecret.length > 0;
192
73
  const askAiEnabled = isDev || orgTier >= 3;
193
- const askAiChatAvailable = isDev ? hasAskAiDevConfig : orgTier >= 3;
194
- let askAiApiPath = withBasePath("/_platform/ask-ai");
195
-
196
- if (isDev && hasAskAiDevConfig) {
197
- try {
198
- askAiApiPath = new URL("/_platform/ask-ai", askAiDevHost).toString();
199
- } catch {
200
- askAiApiPath = withBasePath("/_platform/ask-ai");
201
- }
202
- }
203
74
  ---
204
75
 
205
76
  <!doctype html>
@@ -357,8 +228,8 @@ if (isDev && hasAskAiDevConfig) {
357
228
  <meta name="viewport" content="width=device-width" />
358
229
  <link
359
230
  rel="icon"
360
- type="image/svg+xml"
361
- href={resolveStaticAssetUrl("/favicon.svg")}
231
+ type={favicon.type}
232
+ href={resolveStaticAssetUrl(favicon.href)}
362
233
  />
363
234
  <meta name="generator" content={Astro.generator} />
364
235
  <link rel="canonical" href={canonicalUrl} />
@@ -434,20 +305,8 @@ if (isDev && hasAskAiDevConfig) {
434
305
  >
435
306
  <slot />
436
307
  </main>
437
- <Footer askAiEnabled />
308
+ <Footer askAiEnabled={askAiEnabled} />
438
309
  </div>
439
- {
440
- askAiEnabled ? (
441
- <div transition:persist>
442
- <AskAiWidget
443
- client:only="preact"
444
- apiPath={askAiApiPath}
445
- isChatAvailable={askAiChatAvailable}
446
- devProxyToken={isDev ? askAiDevProxySecret : undefined}
447
- unavailableMessage="This feature is available on production sites for Pro tier organizations."
448
- />
449
- </div>
450
- ) : null
451
- }
310
+ {askAiEnabled ? <AssistantDocsWidget /> : null}
452
311
  </body>
453
312
  </html>
@@ -0,0 +1,74 @@
1
+ export type AssistantChromeConfig = {
2
+ zIndex: string;
3
+ side: "left" | "right";
4
+ offsetX: string;
5
+ offsetY: string;
6
+ mobileOffsetX: string;
7
+ mobileOffsetY: string;
8
+ launcherSize: string;
9
+ launcherIconSize: string;
10
+ launcherShadow: string;
11
+ panelWidth: string;
12
+ panelHeight: string;
13
+ panelGap: string;
14
+ panelRadius: string;
15
+ panelBorder: string;
16
+ panelBackground: string;
17
+ panelDarkBorder: string;
18
+ panelDarkBackground: string;
19
+ panelBackdropBlur: string;
20
+ panelShadow: string;
21
+ panelDarkShadow: string;
22
+ panelOpenScale: string;
23
+ panelOpenDuration: string;
24
+ panelOpenTiming: string;
25
+ panelCloseDuration: string;
26
+ panelCloseTiming: string;
27
+ mobileBreakpoint: string;
28
+ };
29
+
30
+ const ASSISTANT_PANEL_DARK_NEUTRAL_800_MIX = "20%";
31
+ const ASSISTANT_PANEL_DARK_NEUTRAL_900_MIX = "80%";
32
+ const ASSISTANT_PANEL_DARK_SURFACE_OPACITY = "92%";
33
+
34
+ export function getAssistantPanelDarkBackgroundCss({
35
+ neutral800,
36
+ neutral900,
37
+ }: {
38
+ neutral800: string;
39
+ neutral900: string;
40
+ }): string {
41
+ return `color-mix(in srgb, color-mix(in srgb, ${neutral800} ${ASSISTANT_PANEL_DARK_NEUTRAL_800_MIX}, ${neutral900} ${ASSISTANT_PANEL_DARK_NEUTRAL_900_MIX}) ${ASSISTANT_PANEL_DARK_SURFACE_OPACITY}, transparent)`;
42
+ }
43
+
44
+ export const DEFAULT_ASSISTANT_CHROME_CONFIG: AssistantChromeConfig = {
45
+ zIndex: "2147483000",
46
+ side: "right",
47
+ offsetX: "20px",
48
+ offsetY: "20px",
49
+ mobileOffsetX: "16px",
50
+ mobileOffsetY: "16px",
51
+ launcherSize: "48px",
52
+ launcherIconSize: "20px",
53
+ launcherShadow: "0 16px 32px -8px rgb(0 0 0 / 24%)",
54
+ panelWidth: "420px",
55
+ panelHeight: "640px",
56
+ panelGap: "12px",
57
+ panelRadius: "16px",
58
+ panelBorder: "1px solid rgb(9 14 21 / 8%)",
59
+ panelBackground: "rgb(255 255 255 / 90%)",
60
+ panelDarkBorder: "1px solid rgb(255 255 255 / 8%)",
61
+ panelDarkBackground: getAssistantPanelDarkBackgroundCss({
62
+ neutral800: "var(--color-neutral-800, oklch(26.9% 0 0))",
63
+ neutral900: "var(--color-neutral-900, oklch(20.5% 0 0))",
64
+ }),
65
+ panelBackdropBlur: "48px",
66
+ panelShadow: "rgba(9, 14, 21, 0.16) 0px 5px 40px 0px",
67
+ panelDarkShadow: "rgba(0, 0, 0, 0.36) 0px 8px 44px 0px",
68
+ panelOpenScale: ".9",
69
+ panelOpenDuration: ".5s",
70
+ panelOpenTiming: "cubic-bezier(.34, 1.56, .64, 1)",
71
+ panelCloseDuration: ".2s",
72
+ panelCloseTiming: "cubic-bezier(.25, 1, .5, 1)",
73
+ mobileBreakpoint: "640px",
74
+ };
@@ -0,0 +1,39 @@
1
+ import type { DocsConfig } from "./validation";
2
+ import { getDocsDarkBaseColorShade } from "./theme-css";
3
+ import {
4
+ DEFAULT_ASSISTANT_CHROME_CONFIG,
5
+ getAssistantPanelDarkBackgroundCss,
6
+ type AssistantChromeConfig,
7
+ } from "./assistant-chrome-defaults";
8
+
9
+ export {
10
+ DEFAULT_ASSISTANT_CHROME_CONFIG,
11
+ getAssistantPanelDarkBackgroundCss,
12
+ type AssistantChromeConfig,
13
+ } from "./assistant-chrome-defaults";
14
+
15
+ function getAssistantPanelDarkBackground(theme: DocsConfig["theme"]): string {
16
+ const neutral800 = getDocsDarkBaseColorShade(theme, "800");
17
+ const neutral900 = getDocsDarkBaseColorShade(theme, "900");
18
+
19
+ return getAssistantPanelDarkBackgroundCss({ neutral800, neutral900 });
20
+ }
21
+
22
+ export function getAssistantChromeConfig(
23
+ config: DocsConfig,
24
+ ): AssistantChromeConfig {
25
+ const chromeConfig = {
26
+ ...DEFAULT_ASSISTANT_CHROME_CONFIG,
27
+ panelDarkBackground: getAssistantPanelDarkBackground(config.theme),
28
+ };
29
+
30
+ if (config.assistant?.button?.size === "small") {
31
+ return {
32
+ ...chromeConfig,
33
+ launcherSize: "36px",
34
+ launcherIconSize: "16px",
35
+ };
36
+ }
37
+
38
+ return chromeConfig;
39
+ }