@specglass/theme-default 0.0.10 → 0.0.11

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 (45) hide show
  1. package/dist/__tests__/design-tokens.test.d.ts +1 -0
  2. package/dist/__tests__/design-tokens.test.js +107 -0
  3. package/dist/islands/CopyButton.js +2 -6
  4. package/dist/islands/LanguageToggle.d.ts +16 -0
  5. package/dist/islands/LanguageToggle.js +88 -0
  6. package/dist/islands/SearchPalette.js +57 -8
  7. package/dist/islands/ThemeToggle.js +1 -1
  8. package/dist/scripts/code-block-enhancer.js +6 -3
  9. package/dist/themes/notdiamond-dark.json +168 -0
  10. package/dist/themes/notdiamond-light.json +168 -0
  11. package/dist/ui/command.js +2 -2
  12. package/dist/ui/dialog.js +2 -2
  13. package/dist/utils/shiki.d.ts +1 -1
  14. package/dist/utils/shiki.js +5 -3
  15. package/package.json +5 -3
  16. package/src/components/ApiAuth.astro +31 -4
  17. package/src/components/ApiEndpoint.astro +67 -44
  18. package/src/components/ApiNavigation.astro +8 -11
  19. package/src/components/ApiParameters.astro +113 -162
  20. package/src/components/ApiResponse.astro +1 -1
  21. package/src/components/Callout.astro +59 -18
  22. package/src/components/Card.astro +4 -4
  23. package/src/components/CodeBlock.astro +7 -7
  24. package/src/components/CodeBlockGroup.astro +3 -3
  25. package/src/components/CodeExample.astro +183 -0
  26. package/src/components/EditLink.astro +53 -0
  27. package/src/components/Footer.astro +87 -25
  28. package/src/components/Header.astro +63 -7
  29. package/src/components/Sidebar.astro +43 -11
  30. package/src/components/TableOfContents.astro +5 -5
  31. package/src/components/Tabs.astro +51 -20
  32. package/src/islands/CopyButton.tsx +36 -34
  33. package/src/islands/LanguageToggle.tsx +214 -0
  34. package/src/islands/SearchPalette.tsx +121 -39
  35. package/src/islands/ThemeToggle.tsx +45 -48
  36. package/src/layouts/ApiReferencePage.astro +67 -56
  37. package/src/layouts/DocPage.astro +32 -27
  38. package/src/layouts/LandingPage.astro +348 -27
  39. package/src/scripts/code-block-enhancer.ts +8 -3
  40. package/src/styles/global.css +388 -59
  41. package/src/themes/notdiamond-dark.json +168 -0
  42. package/src/themes/notdiamond-light.json +168 -0
  43. package/src/ui/command.tsx +1 -2
  44. package/src/ui/dialog.tsx +8 -5
  45. package/src/utils/shiki.ts +5 -3
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,107 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { readFile } from "node:fs/promises";
3
+ import { resolve, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const GLOBAL_CSS_PATH = resolve(__dirname, "../styles/global.css");
7
+ /**
8
+ * Token regression tests — ensure design tokens exist in global.css
9
+ * and have not been accidentally removed or renamed.
10
+ *
11
+ * These tests read the raw CSS file to verify token declarations.
12
+ */
13
+ let css;
14
+ describe("design-tokens", () => {
15
+ it("loads global.css", async () => {
16
+ css = await readFile(GLOBAL_CSS_PATH, "utf-8");
17
+ expect(css).toBeTruthy();
18
+ });
19
+ describe("surface layer tokens", () => {
20
+ it.each([
21
+ "--color-surface-0",
22
+ "--color-surface-1",
23
+ "--color-surface-2",
24
+ "--color-surface-0-dark",
25
+ "--color-surface-1-dark",
26
+ "--color-surface-2-dark",
27
+ ])("declares %s", (token) => {
28
+ expect(css).toContain(`${token}:`);
29
+ });
30
+ });
31
+ describe("typography tokens", () => {
32
+ it.each([
33
+ "--font-size-h1",
34
+ "--font-size-h2",
35
+ "--font-size-h3",
36
+ "--font-size-h4",
37
+ ])("declares %s", (token) => {
38
+ expect(css).toContain(`${token}:`);
39
+ });
40
+ it("h1 size is 2rem", () => {
41
+ expect(css).toContain("--font-size-h1: 2rem");
42
+ });
43
+ it("h2 size is 1.5rem", () => {
44
+ expect(css).toContain("--font-size-h2: 1.5rem");
45
+ });
46
+ });
47
+ describe("letter-spacing tokens", () => {
48
+ it.each([
49
+ "--tracking-tight",
50
+ "--tracking-snug",
51
+ "--tracking-body",
52
+ "--tracking-wide",
53
+ ])("declares %s", (token) => {
54
+ expect(css).toContain(`${token}:`);
55
+ });
56
+ it("tight tracking is -0.04em", () => {
57
+ expect(css).toContain("--tracking-tight: -0.04em");
58
+ });
59
+ });
60
+ describe("spacing tokens", () => {
61
+ it.each([
62
+ "--spacing-page",
63
+ "--spacing-section",
64
+ "--spacing-subsection",
65
+ ])("declares %s", (token) => {
66
+ expect(css).toContain(`${token}:`);
67
+ });
68
+ it("section spacing is 3rem", () => {
69
+ expect(css).toContain("--spacing-section: 3rem");
70
+ });
71
+ it("subsection spacing is 2rem", () => {
72
+ expect(css).toContain("--spacing-subsection: 2rem");
73
+ });
74
+ });
75
+ describe("dark mode remapping", () => {
76
+ it("remaps surface-0 in .dark", () => {
77
+ expect(css).toContain("--color-surface-0: var(--color-surface-0-dark)");
78
+ });
79
+ it("remaps surface-1 in .dark", () => {
80
+ expect(css).toContain("--color-surface-1: var(--color-surface-1-dark)");
81
+ });
82
+ it("remaps surface-2 in .dark", () => {
83
+ expect(css).toContain("--color-surface-2: var(--color-surface-2-dark)");
84
+ });
85
+ });
86
+ describe("legacy backward compat", () => {
87
+ it("preserves --color-surface alias", () => {
88
+ expect(css).toContain("--color-surface:");
89
+ });
90
+ it("preserves --color-surface-dark alias", () => {
91
+ expect(css).toContain("--color-surface-dark:");
92
+ });
93
+ it("preserves surface-dark as backward compat", () => {
94
+ expect(css).toContain("--color-surface-dark:");
95
+ expect(css).toContain("--color-surface:");
96
+ });
97
+ });
98
+ describe("surface tokens use oklch", () => {
99
+ it("surface-0 uses oklch", () => {
100
+ expect(css).toMatch(/--color-surface-0:\s*oklch\(/);
101
+ });
102
+ it("surface-0-dark uses oklch with Zinc hue", () => {
103
+ // Hue should be ~286 (neutral Zinc)
104
+ expect(css).toMatch(/--color-surface-0-dark:\s*oklch\([\d.]+ [\d.]+ 285\.823\)/);
105
+ });
106
+ });
107
+ });
@@ -29,7 +29,7 @@ export function CopyButton({ code }) {
29
29
  console.warn("[CopyButton] Clipboard API unavailable");
30
30
  });
31
31
  }, [code]);
32
- return (_jsx("button", { type: "button", onClick: handleCopy, "aria-label": copied ? "Copied!" : "Copy code", title: copied ? "Copied!" : "Copy code", className: "sg-copy-btn", style: {
32
+ return (_jsxs("button", { type: "button", onClick: handleCopy, "aria-label": copied ? "Copied!" : "Copy code", title: copied ? "Copied!" : "Copy code", className: `sg-copy-btn${copied ? " sg-copy-btn-copied" : ""}`, style: {
33
33
  position: "absolute",
34
34
  top: "0.5rem",
35
35
  right: "0.5rem",
@@ -45,9 +45,5 @@ export function CopyButton({ code }) {
45
45
  cursor: "pointer",
46
46
  opacity: copied ? 1 : 0,
47
47
  transition: "opacity 150ms ease, background-color 150ms ease, color 150ms ease",
48
- }, children: copied ? (
49
- /* Checkmark icon */
50
- _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { color: "#22c55e" }, children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })) : (
51
- /* Copy icon */
52
- _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), _jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] })) }));
48
+ }, children: [_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "sg-copy-icon", "aria-hidden": "true", children: [_jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), _jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] }), _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "sg-copy-check", style: { color: "#22c55e" }, "aria-hidden": "true", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })] }));
53
49
  }
@@ -0,0 +1,16 @@
1
+ /** A selectable language option */
2
+ interface LanguageOption {
3
+ /** Display label (e.g., "Python") */
4
+ label: string;
5
+ /** Language key matching CodeTabs (e.g., "python") */
6
+ value: string;
7
+ }
8
+ interface LanguageToggleProps {
9
+ /** Available languages. Defaults to Python/Node.js/Go/cURL */
10
+ languages?: LanguageOption[];
11
+ /** Sync key — must match CodeTabs syncKey. Default: "sdk-language" */
12
+ syncKey?: string;
13
+ }
14
+ declare function LanguageToggle({ languages, syncKey, }: LanguageToggleProps): import("react/jsx-runtime").JSX.Element;
15
+ export { LanguageToggle };
16
+ export type { LanguageOption, LanguageToggleProps };
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback, useRef } from "react";
3
+ import { cn } from "../lib/utils";
4
+ /**
5
+ * LanguageToggle — Global language selector for code examples.
6
+ *
7
+ * Broadcasts the same `ndocs-language-change` CustomEvent that CodeTabs
8
+ * uses, so all code tab groups on the page (and future pages) sync to
9
+ * the selected language. Persists preference in localStorage.
10
+ *
11
+ * Designed for `client:idle` hydration in the site header.
12
+ */
13
+ // Must match CodeTabs constants
14
+ const LANGUAGE_CHANGE_EVENT = "ndocs-language-change";
15
+ const DEFAULT_SYNC_KEY = "sdk-language";
16
+ const STORAGE_KEY = `ndocs-preferred-${DEFAULT_SYNC_KEY}`;
17
+ const DEFAULT_LANGUAGES = [
18
+ { label: "Python", value: "python" },
19
+ { label: "Node.js", value: "nodejs" },
20
+ { label: "Go", value: "go" },
21
+ { label: "cURL", value: "curl" },
22
+ ];
23
+ function LanguageToggle({ languages = DEFAULT_LANGUAGES, syncKey = DEFAULT_SYNC_KEY, }) {
24
+ const storageKey = `ndocs-preferred-${syncKey}`;
25
+ const [selected, setSelected] = useState(languages[0]?.value ?? "");
26
+ const [isOpen, setIsOpen] = useState(false);
27
+ const dropdownRef = useRef(null);
28
+ // Restore stored preference after hydration
29
+ useEffect(() => {
30
+ try {
31
+ const stored = localStorage.getItem(storageKey);
32
+ if (stored && languages.some((l) => l.value === stored)) {
33
+ setSelected(stored);
34
+ }
35
+ }
36
+ catch { /* noop */ }
37
+ }, [storageKey, languages]);
38
+ // Listen for external language changes
39
+ useEffect(() => {
40
+ const handler = (e) => {
41
+ const detail = e.detail;
42
+ if (detail?.syncKey === syncKey && languages.some((l) => l.value === detail.language)) {
43
+ setSelected(detail.language);
44
+ }
45
+ };
46
+ window.addEventListener(LANGUAGE_CHANGE_EVENT, handler);
47
+ return () => window.removeEventListener(LANGUAGE_CHANGE_EVENT, handler);
48
+ }, [syncKey, languages]);
49
+ // Close dropdown on outside click
50
+ useEffect(() => {
51
+ if (!isOpen)
52
+ return;
53
+ const handler = (e) => {
54
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
55
+ setIsOpen(false);
56
+ }
57
+ };
58
+ document.addEventListener("mousedown", handler);
59
+ return () => document.removeEventListener("mousedown", handler);
60
+ }, [isOpen]);
61
+ // Close on Escape
62
+ useEffect(() => {
63
+ if (!isOpen)
64
+ return;
65
+ const handler = (e) => {
66
+ if (e.key === "Escape")
67
+ setIsOpen(false);
68
+ };
69
+ document.addEventListener("keydown", handler);
70
+ return () => document.removeEventListener("keydown", handler);
71
+ }, [isOpen]);
72
+ const handleSelect = useCallback((value) => {
73
+ setSelected(value);
74
+ setIsOpen(false);
75
+ try {
76
+ localStorage.setItem(storageKey, value);
77
+ }
78
+ catch { /* noop */ }
79
+ window.dispatchEvent(new CustomEvent(LANGUAGE_CHANGE_EVENT, {
80
+ detail: { syncKey, language: value },
81
+ }));
82
+ }, [storageKey, syncKey]);
83
+ const selectedLabel = languages.find((l) => l.value === selected)?.label ?? selected;
84
+ return (_jsxs("div", { ref: dropdownRef, className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setIsOpen(!isOpen), className: cn("inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium rounded-lg", "border border-border/50 bg-surface text-text-muted", "hover:bg-surface-2 hover:text-text transition-colors duration-150", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": `Language: ${selectedLabel}`, "aria-expanded": isOpen, "aria-haspopup": "listbox", children: [_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "shrink-0", "aria-hidden": "true", children: [_jsx("polyline", { points: "16 18 22 12 16 6" }), _jsx("polyline", { points: "8 6 2 12 8 18" })] }), _jsx("span", { children: selectedLabel }), _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", className: cn("shrink-0 transition-transform duration-150", isOpen && "rotate-180"), "aria-hidden": "true", children: _jsx("polyline", { points: "6 9 12 15 18 9" }) })] }), isOpen && (_jsx("div", { role: "listbox", "aria-label": "Select language", className: cn("absolute right-0 top-full mt-1.5 z-50 min-w-32", "rounded-xl border border-border/50 bg-surface shadow-xl shadow-black/20", "py-1 animate-in fade-in-0 zoom-in-95 duration-150"), children: languages.map((lang) => (_jsxs("button", { type: "button", role: "option", "aria-selected": lang.value === selected, className: cn("flex items-center w-full px-3 py-1.5 text-xs text-left rounded-md mx-auto", "hover:bg-surface-2 transition-colors duration-150", lang.value === selected
85
+ ? "text-text font-medium"
86
+ : "text-text-muted"), onClick: () => handleSelect(lang.value), children: [lang.value === selected && (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", className: "mr-2 shrink-0", "aria-hidden": "true", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })), lang.value !== selected && _jsx("span", { className: "w-[12px] mr-2" }), lang.label] }, lang.value))) }))] }));
87
+ }
88
+ export { LanguageToggle };
@@ -1,13 +1,50 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useCallback, useEffect, useRef } from "react";
3
3
  import { cn } from "../lib/utils";
4
- import { CommandDialog, CommandInput, CommandList, CommandEmpty, CommandItem, } from "../ui/command";
4
+ import { CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, } from "../ui/command";
5
5
  const DEBOUNCE_MS = 150;
6
6
  const MAX_RESULTS = 8;
7
+ const MAX_RECENT = 5;
8
+ const RECENT_STORAGE_KEY = "ndocs-recent-searches";
9
+ /** Load recent searches from localStorage */
10
+ function loadRecentSearches() {
11
+ try {
12
+ const stored = localStorage.getItem(RECENT_STORAGE_KEY);
13
+ if (stored)
14
+ return JSON.parse(stored);
15
+ }
16
+ catch { /* noop */ }
17
+ return [];
18
+ }
19
+ /** Save a query to recent searches */
20
+ function saveRecentSearch(query) {
21
+ try {
22
+ const recents = loadRecentSearches().filter((q) => q !== query);
23
+ recents.unshift(query);
24
+ localStorage.setItem(RECENT_STORAGE_KEY, JSON.stringify(recents.slice(0, MAX_RECENT)));
25
+ }
26
+ catch { /* noop */ }
27
+ }
28
+ /** Group results by section (first URL segment) */
29
+ function groupResults(results) {
30
+ const groups = new Map();
31
+ for (const result of results) {
32
+ // Extract section from URL: /docs/getting-started → "Docs"
33
+ const segments = result.url.replace(/^\//, "").split("/");
34
+ const section = segments.length > 1
35
+ ? segments[0].charAt(0).toUpperCase() + segments[0].slice(1).replace(/-/g, " ")
36
+ : "Pages";
37
+ if (!groups.has(section))
38
+ groups.set(section, []);
39
+ groups.get(section).push(result);
40
+ }
41
+ return groups;
42
+ }
7
43
  export function SearchPalette() {
8
44
  const [open, setOpen] = useState(false);
9
45
  const [query, setQuery] = useState("");
10
46
  const [results, setResults] = useState([]);
47
+ const [recentSearches, setRecentSearches] = useState([]);
11
48
  const [isLoading, setIsLoading] = useState(false);
12
49
  const [isUnavailable, setIsUnavailable] = useState(false);
13
50
  const pagefindRef = useRef(null);
@@ -80,6 +117,9 @@ export function SearchPalette() {
80
117
  // Reset state when closing
81
118
  const handleOpenChange = useCallback((isOpen) => {
82
119
  setOpen(isOpen);
120
+ if (isOpen) {
121
+ setRecentSearches(loadRecentSearches());
122
+ }
83
123
  if (!isOpen) {
84
124
  setTimeout(() => {
85
125
  setQuery("");
@@ -89,9 +129,17 @@ export function SearchPalette() {
89
129
  }, []);
90
130
  // Navigate to a search result
91
131
  const handleSelect = useCallback((url) => {
132
+ if (query.trim()) {
133
+ saveRecentSearch(query.trim());
134
+ }
92
135
  handleOpenChange(false);
93
136
  window.location.href = url;
94
- }, [handleOpenChange]);
137
+ }, [handleOpenChange, query]);
138
+ // Handle selecting a recent search
139
+ const handleRecentSelect = useCallback((recentQuery) => {
140
+ setQuery(recentQuery);
141
+ performSearch(recentQuery);
142
+ }, [performSearch]);
95
143
  // Cleanup debounce on unmount
96
144
  useEffect(() => {
97
145
  return () => {
@@ -100,9 +148,10 @@ export function SearchPalette() {
100
148
  }
101
149
  };
102
150
  }, []);
103
- return (_jsxs("div", { "data-pagefind-ignore": "", children: [_jsxs("button", { type: "button", onClick: () => setOpen(true), className: cn("inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md", "border border-border bg-surface text-text-muted", "hover:bg-hover-bg hover:text-text transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": "Search documentation (\u2318K)", children: [_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: "shrink-0", children: [_jsx("circle", { cx: "11", cy: "11", r: "8" }), _jsx("path", { d: "m21 21-4.3-4.3" })] }), _jsx("span", { className: "hidden sm:inline", children: "Search\u2026" }), _jsx("kbd", { className: cn("hidden sm:inline-flex items-center gap-0.5", "rounded border border-border bg-surface-raised px-1.5 py-0.5", "font-mono text-[10px] text-text-muted"), children: "\u2318K" })] }), _jsxs(CommandDialog, { open: open, onOpenChange: handleOpenChange, shouldFilter: false, dialogTitle: "Search documentation", children: [_jsx(CommandInput, { value: query, onValueChange: handleValueChange, placeholder: "Search documentation\u2026" }), _jsxs(CommandList, { children: [isUnavailable && (_jsxs("div", { className: "px-4 py-6 text-center text-sm text-text-muted", children: [_jsx("p", { className: "mb-1", children: "Search available after build" }), _jsxs("p", { className: "text-xs opacity-70", children: ["Run ", _jsx("code", { className: "bg-surface-raised px-1 rounded", children: "npx turbo build" }), " to generate the search index"] })] })), isLoading && !isUnavailable && (_jsx("div", { className: "px-4 py-6 text-center text-sm text-text-muted", children: "Searching\u2026" })), !isLoading && !isUnavailable && query.trim().length > 0 && (_jsxs(CommandEmpty, { children: ["No results found for \u201C", query, "\u201D"] })), !isUnavailable && !isLoading && query.trim().length === 0 && results.length === 0 && (_jsx("div", { className: "px-4 py-6 text-center text-sm text-text-muted", children: "Type to search documentation\u2026" })), !isUnavailable &&
104
- results.map((result) => (_jsxs(CommandItem, { value: result.id, onSelect: () => handleSelect(result.url), className: "flex flex-col items-start gap-1 px-3 py-2.5", children: [_jsx("div", { className: "font-medium text-text", children: result.meta?.title ?? "Untitled" }), result.sub_results?.[0]?.title &&
105
- result.sub_results[0].title !== result.meta?.title && (_jsx("div", { className: "text-xs text-text-muted", children: result.sub_results[0].title })), _jsx("div", { className: "text-xs text-text-muted line-clamp-2 [&_mark]:bg-primary/20 [&_mark]:text-text [&_mark]:rounded-sm [&_mark]:px-0.5", dangerouslySetInnerHTML: {
106
- __html: result.sub_results?.[0]?.excerpt ?? result.excerpt ?? "",
107
- } })] }, result.id)))] }), (results.length > 0 || query.trim().length > 0) && !isUnavailable && (_jsx("div", { className: "flex items-center justify-between border-t border-border px-3 py-2 text-xs text-text-muted", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("kbd", { className: "rounded border border-border bg-surface-raised px-1 py-0.5 font-mono text-[10px]", children: "\u2191\u2193" }), "navigate"] }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("kbd", { className: "rounded border border-border bg-surface-raised px-1 py-0.5 font-mono text-[10px]", children: "\u21B5" }), "open"] }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("kbd", { className: "rounded border border-border bg-surface-raised px-1 py-0.5 font-mono text-[10px]", children: "esc" }), "close"] })] }) }))] })] }));
151
+ return (_jsxs("div", { "data-pagefind-ignore": "", children: [_jsxs("button", { type: "button", onClick: () => setOpen(true), className: cn("inline-flex items-center gap-2 px-3 py-1.5 text-[0.8125rem] rounded-lg", "border border-border/50 bg-surface text-text-muted", "hover:bg-surface-2 hover:text-text transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": "Search documentation (\u2318K)", children: [_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: "shrink-0", children: [_jsx("circle", { cx: "11", cy: "11", r: "8" }), _jsx("path", { d: "m21 21-4.3-4.3" })] }), _jsx("span", { className: "hidden sm:inline", children: "Search\u2026" }), _jsx("kbd", { className: cn("hidden sm:inline-flex items-center gap-0.5", "rounded border border-border/50 bg-surface-2 px-1.5 py-0.5", "font-mono text-[0.625rem] text-text-muted"), children: "\u2318K" })] }), _jsxs(CommandDialog, { open: open, onOpenChange: handleOpenChange, shouldFilter: false, dialogTitle: "Search documentation", children: [_jsx(CommandInput, { value: query, onValueChange: handleValueChange, placeholder: "Search documentation\u2026" }), _jsxs(CommandList, { children: [isUnavailable && (_jsxs("div", { className: "px-4 py-6 text-center text-sm text-text-muted", children: [_jsx("p", { className: "mb-1", children: "Search available after build" }), _jsxs("p", { className: "text-xs opacity-70", children: ["Run ", _jsx("code", { className: "bg-surface-raised px-1 rounded", children: "npx turbo build" }), " to generate the search index"] })] })), isLoading && !isUnavailable && (_jsx("div", { className: "px-4 py-6 text-center text-sm text-text-muted", children: "Searching\u2026" })), !isLoading && !isUnavailable && query.trim().length > 0 && (_jsxs(CommandEmpty, { children: ["No results found for \u201C", query, "\u201D"] })), !isUnavailable && !isLoading && query.trim().length === 0 && results.length === 0 && (_jsx(_Fragment, { children: recentSearches.length > 0 ? (_jsx(CommandGroup, { heading: "Recent searches", children: recentSearches.map((recent) => (_jsxs(CommandItem, { value: `recent-${recent}`, onSelect: () => handleRecentSelect(recent), className: "flex items-center gap-2 px-3 py-2 text-sm", children: [_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-text-muted shrink-0", "aria-hidden": "true", children: [_jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }), _jsx("path", { d: "M3 3v5h5" }), _jsx("path", { d: "M12 7v5l4 2" })] }), _jsx("span", { className: "text-text-muted", children: recent })] }, recent))) })) : (_jsx("div", { className: "px-4 py-6 text-center text-sm text-text-muted", children: "Type to search documentation\u2026" })) })), !isUnavailable &&
152
+ results.length > 0 &&
153
+ Array.from(groupResults(results)).map(([section, sectionResults]) => (_jsx(CommandGroup, { heading: section, children: sectionResults.map((result) => (_jsxs(CommandItem, { value: result.id, onSelect: () => handleSelect(result.url), className: "flex flex-col items-start gap-1 px-3 py-2.5", children: [_jsx("div", { className: "font-medium text-text", children: result.meta?.title ?? "Untitled" }), result.sub_results?.[0]?.title &&
154
+ result.sub_results[0].title !== result.meta?.title && (_jsx("div", { className: "text-xs text-text-muted", children: result.sub_results[0].title })), _jsx("div", { className: "text-xs text-text-muted line-clamp-2 [&_mark]:bg-primary/20 [&_mark]:text-text [&_mark]:rounded-sm [&_mark]:px-0.5", dangerouslySetInnerHTML: {
155
+ __html: result.sub_results?.[0]?.excerpt ?? result.excerpt ?? "",
156
+ } })] }, result.id))) }, section)))] }), (results.length > 0 || query.trim().length > 0) && !isUnavailable && (_jsx("div", { className: "flex items-center justify-between border-t border-border px-3 py-2 text-xs text-text-muted", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("kbd", { className: "rounded border border-border bg-surface-raised px-1 py-0.5 font-mono text-[10px]", children: "\u2191\u2193" }), "navigate"] }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("kbd", { className: "rounded border border-border bg-surface-raised px-1 py-0.5 font-mono text-[10px]", children: "\u21B5" }), "open"] }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("kbd", { className: "rounded border border-border bg-surface-raised px-1 py-0.5 font-mono text-[10px]", children: "esc" }), "close"] })] }) }))] })] }));
108
157
  }
@@ -38,5 +38,5 @@ export function ThemeToggle() {
38
38
  setIsDark(newIsDark);
39
39
  applyTheme(newIsDark ? "dark" : "light");
40
40
  }, []);
41
- return (_jsx("button", { type: "button", onClick: toggleTheme, "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", className: "inline-flex items-center justify-center rounded-md p-2 text-text-muted hover:text-(--color-text) hover:bg-hover-bg focus:outline-none focus-visible:ring-2 focus-visible:ring-primary transition-colors", children: _jsxs("span", { className: "relative inline-flex items-center justify-center w-5 h-5", children: [_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: `absolute inset-0 transition-transform duration-300 ${isDark ? "rotate-0 scale-100" : "-rotate-90 scale-0"}`, children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2v2" }), _jsx("path", { d: "M12 20v2" }), _jsx("path", { d: "m4.93 4.93 1.41 1.41" }), _jsx("path", { d: "m17.66 17.66 1.41 1.41" }), _jsx("path", { d: "M2 12h2" }), _jsx("path", { d: "M20 12h2" }), _jsx("path", { d: "m6.34 17.66-1.41 1.41" }), _jsx("path", { d: "m19.07 4.93-1.41 1.41" })] }), _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: `absolute inset-0 transition-transform duration-300 ${!isDark ? "rotate-0 scale-100" : "rotate-90 scale-0"}`, children: _jsx("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }) })] }) }));
41
+ return (_jsxs("button", { type: "button", onClick: toggleTheme, "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", className: "relative inline-flex items-center justify-center w-8 h-8 rounded-lg text-text-muted hover:text-text hover:bg-surface-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary transition-colors", children: [_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: `absolute inset-0 m-auto transition-all duration-300 ${isDark ? "rotate-0 scale-100" : "-rotate-90 scale-0"}`, children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2v2" }), _jsx("path", { d: "M12 20v2" }), _jsx("path", { d: "m4.93 4.93 1.41 1.41" }), _jsx("path", { d: "m17.66 17.66 1.41 1.41" }), _jsx("path", { d: "M2 12h2" }), _jsx("path", { d: "M20 12h2" }), _jsx("path", { d: "m6.34 17.66-1.41 1.41" }), _jsx("path", { d: "m19.07 4.93-1.41 1.41" })] }), _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: `absolute inset-0 m-auto transition-all duration-300 ${!isDark ? "rotate-0 scale-100" : "rotate-90 scale-0"}`, children: _jsx("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }) })] }));
42
42
  }
@@ -14,6 +14,8 @@
14
14
  * until the user actually clicks copy.
15
15
  */
16
16
  function initCopyButtons() {
17
+ const COPY_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sg-copy-icon" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
18
+ const CHECK_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sg-copy-check" style="color:#22c55e" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>`;
17
19
  document
18
20
  .querySelectorAll(".code-block--fenced")
19
21
  .forEach((block) => {
@@ -26,14 +28,15 @@ function initCopyButtons() {
26
28
  btn.className = "sg-copy-btn-native sg-copy-btn";
27
29
  btn.setAttribute("aria-label", "Copy code");
28
30
  btn.title = "Copy code";
29
- btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
31
+ // Render both icons overlaid CSS controls visibility
32
+ btn.innerHTML = COPY_SVG + CHECK_SVG;
30
33
  btn.addEventListener("click", () => {
31
34
  navigator.clipboard.writeText(code).then(() => {
32
- btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:#22c55e"><polyline points="20 6 9 17 4 12"/></svg>`;
35
+ btn.classList.add("sg-copy-btn-copied");
33
36
  btn.setAttribute("aria-label", "Copied!");
34
37
  btn.title = "Copied!";
35
38
  setTimeout(() => {
36
- btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
39
+ btn.classList.remove("sg-copy-btn-copied");
37
40
  btn.setAttribute("aria-label", "Copy code");
38
41
  btn.title = "Copy code";
39
42
  }, 2000);
@@ -0,0 +1,168 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/shikijs/textmate-grammars-themes/main/packages/tm-themes/themes/dark-plus.json",
3
+ "name": "notdiamond-dark",
4
+ "displayName": "Not Diamond Dark",
5
+ "type": "dark",
6
+ "colors": {
7
+ "editor.background": "#0a0a0b",
8
+ "editor.foreground": "#e4e4e7",
9
+ "editorLineNumber.foreground": "#3f3f46",
10
+ "editorLineNumber.activeForeground": "#71717a",
11
+ "editor.selectionBackground": "#77e54430",
12
+ "editor.lineHighlightBackground": "#18181b"
13
+ },
14
+ "tokenColors": [
15
+ {
16
+ "scope": ["comment", "punctuation.definition.comment"],
17
+ "settings": {
18
+ "foreground": "#71717a",
19
+ "fontStyle": "italic"
20
+ }
21
+ },
22
+ {
23
+ "scope": ["keyword", "storage.type", "storage.modifier"],
24
+ "settings": {
25
+ "foreground": "#77e544"
26
+ }
27
+ },
28
+ {
29
+ "scope": ["keyword.operator", "keyword.operator.assignment"],
30
+ "settings": {
31
+ "foreground": "#a1a1aa"
32
+ }
33
+ },
34
+ {
35
+ "scope": ["keyword.control.import", "keyword.control.export", "keyword.control.from"],
36
+ "settings": {
37
+ "foreground": "#77e544"
38
+ }
39
+ },
40
+ {
41
+ "scope": ["string", "string.quoted"],
42
+ "settings": {
43
+ "foreground": "#f38b32"
44
+ }
45
+ },
46
+ {
47
+ "scope": ["string.template", "punctuation.definition.string.template"],
48
+ "settings": {
49
+ "foreground": "#f38b32"
50
+ }
51
+ },
52
+ {
53
+ "scope": ["constant.numeric"],
54
+ "settings": {
55
+ "foreground": "#a78bfa"
56
+ }
57
+ },
58
+ {
59
+ "scope": ["constant.language"],
60
+ "settings": {
61
+ "foreground": "#a78bfa"
62
+ }
63
+ },
64
+ {
65
+ "scope": ["variable", "variable.other"],
66
+ "settings": {
67
+ "foreground": "#e4e4e7"
68
+ }
69
+ },
70
+ {
71
+ "scope": ["variable.parameter"],
72
+ "settings": {
73
+ "foreground": "#fca5a5"
74
+ }
75
+ },
76
+ {
77
+ "scope": ["entity.name.function", "support.function"],
78
+ "settings": {
79
+ "foreground": "#38bdf8"
80
+ }
81
+ },
82
+ {
83
+ "scope": ["entity.name.type", "entity.name.class", "support.type", "support.class"],
84
+ "settings": {
85
+ "foreground": "#67e8f9"
86
+ }
87
+ },
88
+ {
89
+ "scope": ["entity.name.tag"],
90
+ "settings": {
91
+ "foreground": "#77e544"
92
+ }
93
+ },
94
+ {
95
+ "scope": ["entity.other.attribute-name"],
96
+ "settings": {
97
+ "foreground": "#f38b32"
98
+ }
99
+ },
100
+ {
101
+ "scope": ["punctuation"],
102
+ "settings": {
103
+ "foreground": "#a1a1aa"
104
+ }
105
+ },
106
+ {
107
+ "scope": ["meta.brace"],
108
+ "settings": {
109
+ "foreground": "#a1a1aa"
110
+ }
111
+ },
112
+ {
113
+ "scope": ["punctuation.definition.tag"],
114
+ "settings": {
115
+ "foreground": "#71717a"
116
+ }
117
+ },
118
+ {
119
+ "scope": ["support.type.property-name"],
120
+ "settings": {
121
+ "foreground": "#38bdf8"
122
+ }
123
+ },
124
+ {
125
+ "scope": ["meta.object-literal.key"],
126
+ "settings": {
127
+ "foreground": "#e4e4e7"
128
+ }
129
+ },
130
+ {
131
+ "scope": ["variable.other.property"],
132
+ "settings": {
133
+ "foreground": "#e4e4e7"
134
+ }
135
+ },
136
+ {
137
+ "scope": ["markup.heading"],
138
+ "settings": {
139
+ "foreground": "#77e544",
140
+ "fontStyle": "bold"
141
+ }
142
+ },
143
+ {
144
+ "scope": ["markup.bold"],
145
+ "settings": {
146
+ "fontStyle": "bold"
147
+ }
148
+ },
149
+ {
150
+ "scope": ["markup.italic"],
151
+ "settings": {
152
+ "fontStyle": "italic"
153
+ }
154
+ },
155
+ {
156
+ "scope": ["markup.inline.raw"],
157
+ "settings": {
158
+ "foreground": "#f38b32"
159
+ }
160
+ },
161
+ {
162
+ "scope": ["markup.list"],
163
+ "settings": {
164
+ "foreground": "#a1a1aa"
165
+ }
166
+ }
167
+ ]
168
+ }