diff --git a/src/components/LibraryLayout.tsx b/src/components/LibraryLayout.tsx
index 09d56c174..58285cb95 100644
--- a/src/components/LibraryLayout.tsx
+++ b/src/components/LibraryLayout.tsx
@@ -830,6 +830,10 @@ export function LibraryLayout({
d.routeId.startsWith('/_library/charts/catalog'),
)
+ const isThemeEditor = matches.some((d) =>
+ d.routeId.startsWith('/_library/highlight/$version/theme-editor'),
+ )
+
const isNpmStats = matches.some((d) => d.pathname.includes('/docs/npm-stats'))
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false)
@@ -860,30 +864,58 @@ export function LibraryLayout({
const tabbedMenuConfig = React.useMemo(() => {
const tabs = getTabbedMenuConfig(menuConfig)
- return libraryId === 'charts'
- ? tabs.map((tab) =>
- tab.id === 'examples'
- ? {
- ...tab,
- firstItem: {
- label: 'Examples',
- to: '/charts/catalog',
- tab: 'examples',
- },
- }
- : tab,
- )
- : tabs
+ if (libraryId === 'charts') {
+ return tabs.map((tab) =>
+ tab.id === 'examples'
+ ? {
+ ...tab,
+ firstItem: {
+ label: 'Examples',
+ to: '/charts/catalog',
+ tab: 'examples',
+ },
+ }
+ : tab,
+ )
+ }
+
+ if (libraryId === 'highlight') {
+ return [
+ ...tabs,
+ {
+ id: 'theme-editor' as const,
+ label: 'Theme Editor',
+ groups: [],
+ firstItem: {
+ label: 'Theme Editor',
+ to: '/highlight/$version/theme-editor',
+ tab: 'theme-editor' as const,
+ },
+ },
+ ]
+ }
+
+ return tabs
}, [libraryId, menuConfig])
const activeTabId = React.useMemo(() => {
+ if (isThemeEditor) {
+ return 'theme-editor' as const
+ }
+
return getActiveDocsNavTabId({
isExample,
menuConfig,
pathname: lastMatch.pathname,
relativePathname,
})
- }, [isExample, lastMatch.pathname, menuConfig, relativePathname])
+ }, [
+ isExample,
+ isThemeEditor,
+ lastMatch.pathname,
+ menuConfig,
+ relativePathname,
+ ])
const visibleMenuConfig = React.useMemo(() => {
return (
@@ -1187,23 +1219,29 @@ export function LibraryLayout({
return null
}
- const linkOptions = getLibraryTabLinkOptions({
- libraryId,
- version,
- to: target.to,
- })
+ const isCustomToolRoute =
+ target.to === '/charts/catalog' ||
+ target.to === '/highlight/$version/theme-editor'
+ const linkParams =
+ !target.to.startsWith('/') ||
+ target.to.includes('/$libraryId') ||
+ target.to.includes('/$version')
+ ? ({ libraryId, version } as never)
+ : undefined
const isActive = tab.id === activeTabId
return (
(
+ 'highlight-theme-editor-draft',
+ defaultDraft,
+ )
+ const { notify } = useToast()
+
+ const { lang, presetId, theme } = draft
+
+ const updateTheme = (updates: Partial) => {
+ setDraft((prev) => ({ ...prev, theme: { ...prev.theme, ...updates } }))
+ }
+
+ const updateToken = (token: HighlightThemeToken, value: string) => {
+ setDraft((prev) => ({
+ ...prev,
+ theme: {
+ ...prev.theme,
+ tokens: { ...prev.theme.tokens, [token]: value },
+ },
+ }))
+ }
+
+ const selectPreset = (preset: ThemePreset) => {
+ setDraft((prev) => ({ ...prev, presetId: preset.id, theme: preset.theme }))
+ }
+
+ const previewCss = React.useMemo(
+ () =>
+ `${createThemeBaseCss()}\n\n${createThemeRule(PREVIEW_SELECTOR, theme)}`,
+ [theme],
+ )
+
+ const codeBlock = React.useMemo(
+ () =>
+ defaultHighlighter.renderCodeBlockData({
+ code: themeEditorSnippets[lang],
+ lang,
+ lineNumbers: true,
+ }),
+ [lang],
+ )
+
+ const copyThemeObject = async () => {
+ try {
+ await copyTextToClipboard(buildThemeObjectSnippet(theme))
+ notify('Copied theme object to clipboard', { id: 'theme-copy' })
+ } catch {
+ notify('Failed to copy theme object', { id: 'theme-copy' })
+ }
+ }
+
+ const copyAgentPrompt = async () => {
+ try {
+ await copyTextToClipboard(buildAgentPrompt(theme))
+ notify('Copied AI prompt to clipboard', { id: 'theme-copy-prompt' })
+ } catch {
+ notify('Failed to copy AI prompt', { id: 'theme-copy-prompt' })
+ }
+ }
+
+ return (
+
+
+
+
Highlight Theme Editor
+
+
+ Pick a language, tune every token, then copy the result out.
+
+
+
+
+
+
+
+
+
+
+
+ {tokenGroups.map((group) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function Field({
+ children,
+ label,
+}: {
+ children: React.ReactNode
+ label: string
+}) {
+ return (
+
+ )
+}
+
+function ColorField({
+ label,
+ onChange,
+ value,
+}: {
+ label: string
+ onChange: (next: string) => void
+ value: string
+}) {
+ const [draft, setDraft] = React.useState(value)
+
+ React.useEffect(() => {
+ setDraft(value)
+ }, [value])
+
+ const isValidDraft = HEX_PATTERN.test(draft)
+ const swatchValue = isValidDraft ? normalizeHex(draft) : normalizeHex(value)
+
+ return (
+
+
{
+ setDraft(event.target.value)
+ onChange(event.target.value)
+ }}
+ className="h-9 w-9 shrink-0 cursor-pointer rounded-md border border-border-default bg-transparent p-0.5"
+ />
+
+ {label}
+ setDraft(event.target.value)}
+ onBlur={() => {
+ if (isValidDraft) onChange(normalizeHex(draft))
+ else setDraft(value)
+ }}
+ className="h-7 px-2 py-0 font-mono text-xs"
+ spellCheck={false}
+ />
+
+
+ )
+}
+
+function normalizeHex(value: string) {
+ if (!HEX_PATTERN.test(value)) return '#000000'
+ if (value.length === 4) {
+ const [, r, g, b] = value
+ return `#${r}${r}${g}${g}${b}${b}`
+ }
+ return value
+}
+
+function buildThemeObjectSnippet(theme: HighlightTheme) {
+ const identifier = toThemeIdentifier(theme.name)
+ const tokenLines = themeTokenClasses
+ .map((token) => ` '${token}': ${JSON.stringify(theme.tokens[token])},`)
+ .join('\n')
+
+ return `import type { HighlightTheme } from '@tanstack/highlight/theme'
+
+export const ${identifier} = {
+ name: ${JSON.stringify(theme.name)},
+ type: ${JSON.stringify(theme.type)},
+ background: ${JSON.stringify(theme.background)},
+ foreground: ${JSON.stringify(theme.foreground)},
+ tokens: {
+${tokenLines}
+ },
+} satisfies HighlightTheme
+`
+}
+
+function buildAgentPrompt(theme: HighlightTheme) {
+ return `Add this @tanstack/highlight theme to my project.
+
+Find where this project configures @tanstack/highlight (look for a call to \`createThemeCss\` from '@tanstack/highlight/theme', usually near wherever the site's global styles are set up), and register the theme below there — as a new light/dark pair, or as an additional entry in a \`themes: [...]\` list, matching however the existing config is structured. Don't rewrite the surrounding setup beyond wiring this one in.
+
+${buildThemeObjectSnippet(theme)}`
+}
+
+function toThemeIdentifier(name: string) {
+ const words = name
+ .trim()
+ .split(/[^a-zA-Z0-9]+/)
+ .filter(Boolean)
+ const camel = words
+ .map((word, index) =>
+ index === 0
+ ? word.toLowerCase()
+ : word[0].toUpperCase() + word.slice(1).toLowerCase(),
+ )
+ .join('')
+ return `${camel || 'custom'}Theme`
+}
diff --git a/src/components/highlight-theme-editor/snippets.ts b/src/components/highlight-theme-editor/snippets.ts
new file mode 100644
index 000000000..81a04398a
--- /dev/null
+++ b/src/components/highlight-theme-editor/snippets.ts
@@ -0,0 +1,311 @@
+import type { HighlightLanguage } from '@tanstack/highlight'
+
+export const languageOptions: Array<{
+ value: HighlightLanguage
+ label: string
+}> = [
+ { value: 'ts', label: 'TypeScript' },
+ { value: 'tsx', label: 'TSX' },
+ { value: 'js', label: 'JavaScript' },
+ { value: 'jsx', label: 'JSX' },
+ { value: 'tsrx', label: 'TSRX (Octane)' },
+ { value: 'json', label: 'JSON' },
+ { value: 'html', label: 'HTML' },
+ { value: 'css', label: 'CSS' },
+ { value: 'vue', label: 'Vue' },
+ { value: 'svelte', label: 'Svelte' },
+ { value: 'markdown', label: 'Markdown' },
+ { value: 'mermaid', label: 'Mermaid' },
+ { value: 'yaml', label: 'YAML' },
+ { value: 'toml', label: 'TOML' },
+ { value: 'sql', label: 'SQL' },
+ { value: 'python', label: 'Python' },
+ { value: 'scheme', label: 'Scheme' },
+ { value: 'shell', label: 'Shell' },
+ { value: 'dockerfile', label: 'Dockerfile' },
+ { value: 'nginx', label: 'Nginx' },
+ { value: 'apache', label: 'Apache' },
+ { value: 'http', label: 'HTTP' },
+ { value: 'env', label: 'Env' },
+ { value: 'ejs', label: 'EJS' },
+ { value: 'diff', label: 'Diff' },
+ { value: 'plaintext', label: 'Plain Text' },
+]
+
+export const themeEditorSnippets: Record = {
+ ts: `import { createThemeCss } from '@tanstack/highlight/theme'
+import type { HighlightTheme } from '@tanstack/highlight/theme'
+
+/** Merge overrides onto a complete base theme. */
+export function extendTheme(
+ base: HighlightTheme,
+ overrides: Partial,
+): HighlightTheme {
+ const tokens = { ...base.tokens, ...overrides }
+ const isValid = Object.keys(tokens).length > 0
+
+ return isValid ? { ...base, tokens } : base
+}
+
+export const css = createThemeCss({ light: extendTheme(base, { keyword: '#ff79c6' }) })
+`,
+ tsx: `import * as React from 'react'
+
+type SwatchProps = {
+ label: string
+ value: string
+ onChange: (next: string) => void
+}
+
+export function Swatch({ label, value, onChange }: SwatchProps) {
+ const [isFocused, setIsFocused] = React.useState(false)
+
+ return (
+
+ )
+}
+`,
+ js: `export function debounce(fn, delayMs = 250) {
+ let timeoutId = null
+
+ return function debounced(...args) {
+ if (timeoutId !== null) clearTimeout(timeoutId)
+ console.log('scheduling', fn.name)
+ timeoutId = setTimeout(() => fn.apply(this, args), delayMs)
+ }
+}
+
+// Only fires once the user stops typing for 250ms
+const onSearchInput = debounce((query) => fetchResults(query))
+`,
+ jsx: `function Avatar({ src, name }) {
+ const initials = name
+ .split(' ')
+ .map((part) => part[0])
+ .join('')
+
+ return src ? (
+
+ ) : (
+ {initials}
+ )
+}
+`,
+ tsrx: `import { Markdown } from '@tanstack/markdown/octane'
+
+@for (post of posts) {
+
+ @{post.title}
+ @if (post.description) {
+ {post.description}
+ } @else {
+ No description yet.
+ }
+
+}
+`,
+ json: `{
+ "name": "@tanstack/highlight",
+ "version": "0.0.10",
+ "private": false,
+ "sideEffects": false,
+ "keywords": ["syntax-highlighting", "documentation"],
+ "engines": {
+ "node": ">=18"
+ }
+}
+`,
+ html: `
+
+ Live preview
+ Pick a language, then tune every token below.
+
+
+`,
+ css: `/* Scoped to the preview panel only */
+[data-theme-editor-preview] {
+ --preview-radius: 0.75rem;
+}
+
+.th-code {
+ border-radius: var(--preview-radius);
+ font-size: 0.875rem;
+}
+
+.th-code:hover {
+ outline: 2px solid var(--th-keyword);
+}
+`,
+ vue: `
+
+
+
+
+`,
+ svelte: `
+
+
+
+
+`,
+ markdown: `# Theme Editor
+
+Pick a **language** on the left, then adjust any token below.
+
+> [!TIP]
+> Use \`createThemeCss\` to turn your picks into real CSS.
+
+See the [themes guide](https://github.com/TanStack/highlight) for more.
+`,
+ mermaid: `sequenceDiagram
+ participant Editor
+ participant Highlighter
+ participant Preview
+
+ Editor->>Highlighter: highlightToHtml(snippet)
+ Highlighter->>Preview: htmlMarkup
+ Preview-->>Editor: rendered tokens
+`,
+ yaml: `# theme-editor.yaml
+name: theme-editor
+version: 1
+enabled: true
+tokens:
+ keyword: "#cf222e"
+ string: "#0a7f64"
+ count: 20
+`,
+ toml: `# railway.toml
+[deploy]
+startCommand = "pnpm workflow:sweep"
+cronSchedule = "*/5 * * * *"
+restartPolicyType = "NEVER"
+retries = 3
+enabled = true
+`,
+ sql: `CREATE TABLE themes (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ is_dark INTEGER NOT NULL DEFAULT 0,
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+SELECT name, is_dark FROM themes WHERE is_dark = 1 ORDER BY created_at DESC;
+`,
+ python: `from dataclasses import dataclass
+
+@dataclass
+class Theme:
+ name: str
+ background: str
+ is_dark: bool = False
+
+def contrast_ratio(theme: Theme) -> float:
+ # Placeholder — real formula lives in colorimetry.py
+ return 4.5 if theme.is_dark else 7.0
+`,
+ scheme: `; sandbox policy for theme exports
+(version 1)
+(allow default)
+(deny file-write* (subpath "/"))
+(allow file-write* (subpath "/tmp/theme-editor"))
+(define (clamp x lo hi) (max lo (min x hi)))
+`,
+ shell: `# Install the highlighter and preview it locally
+npm install @tanstack/highlight
+
+# Regenerate the compare report after editing themes
+pnpm run report:compare -- --theme=$THEME_NAME
+`,
+ dockerfile: `# Build stage
+FROM node:18-alpine AS build
+WORKDIR /app
+COPY package*.json ./
+RUN npm ci
+COPY . .
+RUN npm run build
+
+# Production stage
+FROM nginx:alpine
+COPY --from=build /app/dist /usr/share/nginx/html
+EXPOSE 80
+`,
+ nginx: `server {
+ listen 80;
+ server_name theme-editor.example.com;
+ root /var/www/theme-editor;
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+}
+`,
+ apache: `
+ RewriteEngine On
+ RewriteBase /
+ RewriteRule ^index\\.html$ - [L]
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule . /index.html [L]
+
+`,
+ http: `HTTP/1.1 200 OK
+Content-Type: application/json
+Cache-Control: no-cache
+X-Theme-Id: github-light
+`,
+ env: `# .env.local
+VITE_HIGHLIGHT_DEFAULT_THEME=github-light
+VITE_HIGHLIGHT_DEFAULT_LANG=ts
+`,
+ ejs: `
+<% if (theme.isDark) { %>
+
+<% } else { %>
+
+<% } %>
+`,
+ diff: `theme({
+- keyword: '#cf222e',
++ keyword: '#ff79c6',
+- background: '#ffffff',
++ background: '#282a36',
+ comment: '#6e7781',
+})
+`,
+ plaintext: `This language has no syntax rules, so every
+character renders with the default "token" color
+and background only — a good way to check contrast
+before tuning the rest of the palette.
+`,
+}
diff --git a/src/components/highlight-theme-editor/tokenGroups.ts b/src/components/highlight-theme-editor/tokenGroups.ts
new file mode 100644
index 000000000..de984c6b7
--- /dev/null
+++ b/src/components/highlight-theme-editor/tokenGroups.ts
@@ -0,0 +1,96 @@
+import { auroraXTheme } from '@tanstack/highlight/themes/aurora-x'
+import { draculaTheme } from '@tanstack/highlight/themes/dracula'
+import { githubDarkTheme } from '@tanstack/highlight/themes/github-dark'
+import { githubLightTheme } from '@tanstack/highlight/themes/github-light'
+import { monokaiTheme } from '@tanstack/highlight/themes/monokai'
+import { nordTheme } from '@tanstack/highlight/themes/nord'
+import { oneDarkProTheme } from '@tanstack/highlight/themes/one-dark-pro'
+import { solarizedDarkTheme } from '@tanstack/highlight/themes/solarized-dark'
+import { solarizedLightTheme } from '@tanstack/highlight/themes/solarized-light'
+import type {
+ HighlightTheme,
+ HighlightThemeToken,
+} from '@tanstack/highlight/theme'
+
+export type TokenGroup = {
+ id: string
+ label: string
+ tokens: ReadonlyArray
+}
+
+export const tokenGroups: ReadonlyArray = [
+ {
+ id: 'syntax',
+ label: 'Syntax',
+ tokens: ['keyword', 'string', 'number', 'literal', 'operator', 'comment'],
+ },
+ {
+ id: 'structure',
+ label: 'Structure',
+ tokens: ['function', 'type', 'property', 'variable'],
+ },
+ {
+ id: 'markup',
+ label: 'Markup',
+ tokens: ['tag', 'attr', 'selector'],
+ },
+ {
+ id: 'docs-and-diff',
+ label: 'Docs & Diff',
+ tokens: [
+ 'heading',
+ 'link',
+ 'code-inline',
+ 'inserted',
+ 'deleted',
+ 'meta',
+ 'command',
+ ],
+ },
+]
+
+export const tokenLabels: Record = {
+ token: 'Default text',
+ attr: 'Attribute',
+ 'code-inline': 'Inline code',
+ command: 'Command',
+ comment: 'Comment',
+ deleted: 'Deleted line',
+ function: 'Function',
+ heading: 'Heading',
+ inserted: 'Inserted line',
+ keyword: 'Keyword',
+ link: 'Link',
+ literal: 'Literal',
+ meta: 'Meta',
+ number: 'Number',
+ operator: 'Operator',
+ property: 'Property',
+ selector: 'Selector',
+ string: 'String',
+ tag: 'Tag',
+ type: 'Type',
+ variable: 'Variable',
+}
+
+export type ThemePreset = {
+ id: string
+ label: string
+ theme: HighlightTheme
+}
+
+export const themePresets: ReadonlyArray = [
+ { id: 'github-light', label: 'GitHub Light', theme: githubLightTheme },
+ { id: 'github-dark', label: 'GitHub Dark', theme: githubDarkTheme },
+ { id: 'dracula', label: 'Dracula', theme: draculaTheme },
+ { id: 'nord', label: 'Nord', theme: nordTheme },
+ { id: 'monokai', label: 'Monokai', theme: monokaiTheme },
+ { id: 'one-dark-pro', label: 'One Dark Pro', theme: oneDarkProTheme },
+ { id: 'aurora-x', label: 'Aurora X', theme: auroraXTheme },
+ {
+ id: 'solarized-light',
+ label: 'Solarized Light',
+ theme: solarizedLightTheme,
+ },
+ { id: 'solarized-dark', label: 'Solarized Dark', theme: solarizedDarkTheme },
+]
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index 1ad9cc6e9..6fa8754ff 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -189,6 +189,7 @@ import { Route as ApiBuilderDeployGithubRouteImport } from './routes/api/builder
import { Route as ApiBuilderDeployCheckNameRouteImport } from './routes/api/builder/deploy/check-name'
import { Route as ApiAuthCliCreateTicketRouteImport } from './routes/api/auth/cli/create-ticket'
import { Route as ApiAuthCallbackProviderRouteImport } from './routes/api/auth/callback/$provider'
+import { Route as LibraryHighlightVersionThemeEditorRouteImport } from './routes/_library/highlight.$version.theme-editor'
import { Route as LibraryChartsCatalogAllRouteImport } from './routes/_library/charts.catalog.all'
import { Route as LibraryLibraryIdVersionLlmsDottxtRouteImport } from './routes/_library/$libraryId/$version.llms[.]txt'
import { Route as LibraryLibraryIdVersionDocsRouteImport } from './routes/_library/$libraryId/$version.docs'
@@ -1144,6 +1145,12 @@ const ApiAuthCallbackProviderRoute = ApiAuthCallbackProviderRouteImport.update({
path: '/api/auth/callback/$provider',
getParentRoute: () => rootRouteImport,
} as any)
+const LibraryHighlightVersionThemeEditorRoute =
+ LibraryHighlightVersionThemeEditorRouteImport.update({
+ id: '/highlight/$version/theme-editor',
+ path: '/highlight/$version/theme-editor',
+ getParentRoute: () => LibraryRoute,
+ } as any)
const LibraryChartsCatalogAllRoute = LibraryChartsCatalogAllRouteImport.update({
id: '/all',
path: '/all',
@@ -1424,6 +1431,7 @@ export interface FileRoutesByFullPath {
'/$libraryId/$version/docs': typeof LibraryLibraryIdVersionDocsRouteWithChildren
'/$libraryId/$version/llms.txt': typeof LibraryLibraryIdVersionLlmsDottxtRoute
'/charts/catalog/all': typeof LibraryChartsCatalogAllRoute
+ '/highlight/$version/theme-editor': typeof LibraryHighlightVersionThemeEditorRoute
'/api/auth/callback/$provider': typeof ApiAuthCallbackProviderRoute
'/api/auth/cli/create-ticket': typeof ApiAuthCliCreateTicketRoute
'/api/builder/deploy/check-name': typeof ApiBuilderDeployCheckNameRoute
@@ -1614,6 +1622,7 @@ export interface FileRoutesByTo {
'/stats/npm': typeof StatsNpmIndexRoute
'/$libraryId/$version/llms.txt': typeof LibraryLibraryIdVersionLlmsDottxtRoute
'/charts/catalog/all': typeof LibraryChartsCatalogAllRoute
+ '/highlight/$version/theme-editor': typeof LibraryHighlightVersionThemeEditorRoute
'/api/auth/callback/$provider': typeof ApiAuthCallbackProviderRoute
'/api/auth/cli/create-ticket': typeof ApiAuthCliCreateTicketRoute
'/api/builder/deploy/check-name': typeof ApiBuilderDeployCheckNameRoute
@@ -1818,6 +1827,7 @@ export interface FileRoutesById {
'/_library/$libraryId/$version/docs': typeof LibraryLibraryIdVersionDocsRouteWithChildren
'/_library/$libraryId/$version/llms.txt': typeof LibraryLibraryIdVersionLlmsDottxtRoute
'/_library/charts/catalog/all': typeof LibraryChartsCatalogAllRoute
+ '/_library/highlight/$version/theme-editor': typeof LibraryHighlightVersionThemeEditorRoute
'/api/auth/callback/$provider': typeof ApiAuthCallbackProviderRoute
'/api/auth/cli/create-ticket': typeof ApiAuthCliCreateTicketRoute
'/api/builder/deploy/check-name': typeof ApiBuilderDeployCheckNameRoute
@@ -2022,6 +2032,7 @@ export interface FileRouteTypes {
| '/$libraryId/$version/docs'
| '/$libraryId/$version/llms.txt'
| '/charts/catalog/all'
+ | '/highlight/$version/theme-editor'
| '/api/auth/callback/$provider'
| '/api/auth/cli/create-ticket'
| '/api/builder/deploy/check-name'
@@ -2212,6 +2223,7 @@ export interface FileRouteTypes {
| '/stats/npm'
| '/$libraryId/$version/llms.txt'
| '/charts/catalog/all'
+ | '/highlight/$version/theme-editor'
| '/api/auth/callback/$provider'
| '/api/auth/cli/create-ticket'
| '/api/builder/deploy/check-name'
@@ -2415,6 +2427,7 @@ export interface FileRouteTypes {
| '/_library/$libraryId/$version/docs'
| '/_library/$libraryId/$version/llms.txt'
| '/_library/charts/catalog/all'
+ | '/_library/highlight/$version/theme-editor'
| '/api/auth/callback/$provider'
| '/api/auth/cli/create-ticket'
| '/api/builder/deploy/check-name'
@@ -3823,6 +3836,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiAuthCallbackProviderRouteImport
parentRoute: typeof rootRouteImport
}
+ '/_library/highlight/$version/theme-editor': {
+ id: '/_library/highlight/$version/theme-editor'
+ path: '/highlight/$version/theme-editor'
+ fullPath: '/highlight/$version/theme-editor'
+ preLoaderRoute: typeof LibraryHighlightVersionThemeEditorRouteImport
+ parentRoute: typeof LibraryRoute
+ }
'/_library/charts/catalog/all': {
id: '/_library/charts/catalog/all'
path: '/all'
@@ -4131,6 +4151,7 @@ const LibraryChartsCatalogRouteWithChildren =
interface LibraryRouteChildren {
LibraryLibraryIdRouteRoute: typeof LibraryLibraryIdRouteRouteWithChildren
LibraryChartsCatalogRoute: typeof LibraryChartsCatalogRouteWithChildren
+ LibraryHighlightVersionThemeEditorRoute: typeof LibraryHighlightVersionThemeEditorRoute
LibraryAiVersionIndexRoute: typeof LibraryAiVersionIndexRoute
LibraryChartsVersionIndexRoute: typeof LibraryChartsVersionIndexRoute
LibraryCliVersionIndexRoute: typeof LibraryCliVersionIndexRoute
@@ -4156,6 +4177,8 @@ interface LibraryRouteChildren {
const LibraryRouteChildren: LibraryRouteChildren = {
LibraryLibraryIdRouteRoute: LibraryLibraryIdRouteRouteWithChildren,
LibraryChartsCatalogRoute: LibraryChartsCatalogRouteWithChildren,
+ LibraryHighlightVersionThemeEditorRoute:
+ LibraryHighlightVersionThemeEditorRoute,
LibraryAiVersionIndexRoute: LibraryAiVersionIndexRoute,
LibraryChartsVersionIndexRoute: LibraryChartsVersionIndexRoute,
LibraryCliVersionIndexRoute: LibraryCliVersionIndexRoute,
diff --git a/src/routes/_library/highlight.$version.theme-editor.tsx b/src/routes/_library/highlight.$version.theme-editor.tsx
new file mode 100644
index 000000000..2ceb3e9a4
--- /dev/null
+++ b/src/routes/_library/highlight.$version.theme-editor.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { docsConfigQueryOptions } from '~/queries/docsConfig'
+import { ThemeEditorPage } from '~/components/highlight-theme-editor/ThemeEditorPage'
+
+export const Route = createFileRoute(
+ '/_library/highlight/$version/theme-editor',
+)({
+ loader: async ({ params, context: { queryClient } }) => {
+ return {
+ config: await queryClient.ensureQueryData(
+ docsConfigQueryOptions('highlight', params.version),
+ ),
+ }
+ },
+ component: ThemeEditorPage,
+ ssr: false,
+})
diff --git a/src/utils/docsNavTabs.ts b/src/utils/docsNavTabs.ts
index 600f549db..4c08df264 100644
--- a/src/utils/docsNavTabs.ts
+++ b/src/utils/docsNavTabs.ts
@@ -8,6 +8,7 @@ export const docsNavTabIds = [
'guides',
'api',
'examples',
+ 'theme-editor',
] as const
export type DocsNavTabId = (typeof docsNavTabIds)[number]