diff --git a/microsite-canon/app/globals.css b/microsite-canon/app/globals.css
index adeee8bc02..797a7d73cd 100644
--- a/microsite-canon/app/globals.css
+++ b/microsite-canon/app/globals.css
@@ -3,6 +3,7 @@ body {
flex-direction: row;
background-color: var(--canon-background);
color: var(--canon-text-primary);
+ transition: background-color 0.2s ease-in-out;
}
iframe {
@@ -10,26 +11,22 @@ iframe {
width: 100%;
}
-pre {
- border-radius: 0.5rem;
- border: 1px solid var(--canon-border-base);
- position: relative;
- padding: 16px 20px;
- background: transparent;
- overflow-x: auto;
- font-family: var(--canon-font-monospace);
+.shiki {
+ margin: 0;
}
-pre code span {
+.shiki,
+.shiki span {
+ background-color: var(--canon-surface-1) !important;
font-family: var(--canon-font-monospace);
font-size: 0.875rem;
line-height: 1.7;
+ transition: background-color 0.2s ease-in-out;
}
[data-theme='dark'] .shiki,
[data-theme='dark'] .shiki span {
color: var(--shiki-dark) !important;
- background-color: var(--shiki-dark-bg) !important;
/* Optional, if you also want font styles */
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
diff --git a/microsite-canon/app/layout.tsx b/microsite-canon/app/layout.tsx
index 8fa3b306a6..4694e187dc 100644
--- a/microsite-canon/app/layout.tsx
+++ b/microsite-canon/app/layout.tsx
@@ -1,11 +1,12 @@
import type { Metadata } from 'next';
import { Sidebar } from '../components/Sidebar';
import './globals.css';
-import { CanonProvider } from '@backstage/canon';
import '../../packages/canon/src/css/core.css';
import '../../packages/canon/src/css/components.css';
import styles from './page.module.css';
+import { Global } from '@/components/Global';
+import { Providers } from './providers';
export const metadata: Metadata = {
title: 'Canon',
@@ -18,14 +19,14 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
+
-
- <>
+
+
{children}
- >
-
+
+
);
diff --git a/microsite-canon/app/providers.tsx b/microsite-canon/app/providers.tsx
new file mode 100644
index 0000000000..23229653ac
--- /dev/null
+++ b/microsite-canon/app/providers.tsx
@@ -0,0 +1,13 @@
+'use client';
+
+import { ReactNode } from 'react';
+import { CanonProvider } from '@backstage/canon';
+import { ThemeProvider } from 'next-themes';
+
+export const Providers = ({ children }: { children: ReactNode }) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/microsite-canon/components/CodeBlock/index.tsx b/microsite-canon/components/CodeBlock/index.tsx
new file mode 100644
index 0000000000..1ed25902b9
--- /dev/null
+++ b/microsite-canon/components/CodeBlock/index.tsx
@@ -0,0 +1,28 @@
+import { ReactNode } from 'react';
+import type { BundledLanguage } from 'shiki';
+import { codeToHtml } from 'shiki';
+import React from 'react';
+import styles from './styles.module.css';
+
+interface CodeBlockProps {
+ lang?: BundledLanguage;
+ title?: string;
+ code?: string;
+}
+
+export async function CodeBlock({ lang, title, code }: CodeBlockProps) {
+ const out = await codeToHtml(code || '', {
+ lang: lang || 'ts',
+ themes: {
+ light: 'vitesse-light',
+ dark: 'vitesse-dark',
+ },
+ });
+
+ return (
+
+ );
+}
diff --git a/microsite-canon/components/CodeBlock/styles.module.css b/microsite-canon/components/CodeBlock/styles.module.css
new file mode 100644
index 0000000000..6affa1b13d
--- /dev/null
+++ b/microsite-canon/components/CodeBlock/styles.module.css
@@ -0,0 +1,26 @@
+.codeBlock {
+ border-radius: 0.5rem;
+ border: 1px solid var(--canon-border-base);
+ position: relative;
+ background: transparent;
+ overflow-x: auto;
+ font-family: var(--canon-font-monospace);
+ background-color: #fff;
+ transition: background-color 0.2s ease-in-out;
+ margin-bottom: 1rem;
+}
+
+[data-theme='dark'] .codeBlock {
+ background-color: #121212;
+}
+
+.title {
+ border-bottom: 1px solid var(--canon-border-base);
+ padding: 12px 20px;
+ font-size: 0.875rem;
+ color: var(--canon-text-secondary);
+}
+
+.code {
+ padding: 20px;
+}
diff --git a/microsite-canon/components/Global/index.tsx b/microsite-canon/components/Global/index.tsx
new file mode 100644
index 0000000000..ab089eca25
--- /dev/null
+++ b/microsite-canon/components/Global/index.tsx
@@ -0,0 +1,9 @@
+'use client';
+
+import { useEffect } from 'react';
+import { useSearchParams } from 'next/navigation';
+import styles from './styles.module.css';
+
+export const Global = ({ children }: { children: React.ReactNode }) => {
+ return {children}
;
+};
diff --git a/microsite-canon/components/Global/styles.module.css b/microsite-canon/components/Global/styles.module.css
new file mode 100644
index 0000000000..5cbd2d4b5e
--- /dev/null
+++ b/microsite-canon/components/Global/styles.module.css
@@ -0,0 +1,3 @@
+.global {
+ width: 100%;
+}
diff --git a/microsite-canon/components/Sidebar/Sidebar.module.css b/microsite-canon/components/Sidebar/Sidebar.module.css
index 335fd0e3cd..52d1a1ed51 100644
--- a/microsite-canon/components/Sidebar/Sidebar.module.css
+++ b/microsite-canon/components/Sidebar/Sidebar.module.css
@@ -9,6 +9,7 @@
border-right: 1px solid var(--canon-border-base);
padding-left: 20px;
padding-right: 20px;
+ transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
.sidebar svg path {
diff --git a/microsite-canon/components/Tabs/Tabs.module.css b/microsite-canon/components/Tabs/Tabs.module.css
index 24e0ecc741..d70184dd58 100644
--- a/microsite-canon/components/Tabs/Tabs.module.css
+++ b/microsite-canon/components/Tabs/Tabs.module.css
@@ -3,6 +3,7 @@
flex: 1;
width: 100%;
background-color: var(--canon-surface-2);
+ transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
.list {
@@ -69,4 +70,5 @@
transition-property: translate, width;
transition-duration: 200ms;
transition-timing-function: ease-in-out;
+ transition: background-color 0.2s ease-in-out;
}
diff --git a/microsite-canon/components/Tabs/index.tsx b/microsite-canon/components/Tabs/index.tsx
index 777f9eae51..6d095c1ad7 100644
--- a/microsite-canon/components/Tabs/index.tsx
+++ b/microsite-canon/components/Tabs/index.tsx
@@ -5,6 +5,8 @@ import { Tabs } from '@base-ui-components/react/tabs';
import { Icon } from '@backstage/canon';
import { usePathname, useSearchParams } from 'next/navigation';
import { useRouter } from 'next/navigation';
+import { useTheme } from 'next-themes';
+import { useEffect, useState } from 'react';
export const TabsVersion = () => {
return (
@@ -23,21 +25,22 @@ export const TabsVersion = () => {
};
export const TabsTheme = () => {
- const searchParams = useSearchParams();
- const theme = searchParams.get('theme') === 'dark' ? 'dark' : 'light';
- const current = new URLSearchParams(Array.from(searchParams.entries()));
- const router = useRouter();
+ const { theme, setTheme } = useTheme();
const onValueChange = (value: string) => {
- current.set('theme', value);
- router.push(`/?${current.toString()}`);
- document.documentElement.setAttribute('data-theme', value);
+ setTheme(value);
};
+ const [isClient, setIsClient] = useState(false);
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
return (
diff --git a/microsite-canon/content/layout.mdx b/microsite-canon/content/layout.mdx
index 7fdbcbfc57..4b0622d92e 100644
--- a/microsite-canon/content/layout.mdx
+++ b/microsite-canon/content/layout.mdx
@@ -1,4 +1,5 @@
import { LayoutComponents } from '@/components/LayoutComponents';
+import { CodeBlock } from '@/components/CodeBlock';
# Layout
@@ -19,7 +20,19 @@ that will be consistent with the rest of your Backstage instance. These
components are opinionated and use TypeScript to ensure that the props you
provide are the ones coming from the theme.
-```tsx title=boom
+
+ Hello World
+
+ Project 1
+ Project 2
+
+
+`}
+/>
+
+```tsx
Hello World
diff --git a/microsite-canon/eslint.config.mjs b/microsite-canon/eslint.config.mjs
index c85fb67c46..7f86eca7f3 100644
--- a/microsite-canon/eslint.config.mjs
+++ b/microsite-canon/eslint.config.mjs
@@ -1,6 +1,6 @@
-import { dirname } from "path";
-import { fileURLToPath } from "url";
-import { FlatCompat } from "@eslint/eslintrc";
+import { dirname } from 'path';
+import { fileURLToPath } from 'url';
+import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -10,7 +10,7 @@ const compat = new FlatCompat({
});
const eslintConfig = [
- ...compat.extends("next/core-web-vitals", "next/typescript"),
+ ...compat.extends('next/core-web-vitals', 'next/typescript'),
];
export default eslintConfig;
diff --git a/microsite-canon/mdx-components.tsx b/microsite-canon/mdx-components.tsx
index 21479dc4e6..b0ae6de4f3 100644
--- a/microsite-canon/mdx-components.tsx
+++ b/microsite-canon/mdx-components.tsx
@@ -4,6 +4,7 @@ import { ReactNode, ReactElement } from 'react';
import type { BundledLanguage } from 'shiki';
import { codeToHtml } from 'shiki';
import React from 'react';
+import { CodeBlock } from '@/components/CodeBlock';
// This file allows you to provide custom React components
// to be used in MDX files. You can import and use any
@@ -47,8 +48,11 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
),
pre: ({ children }) => {
- console.log(children);
- return {children as ReactNode};
+ const codeContent = React.isValidElement(children)
+ ? (children.props as { children: string }).children
+ : '';
+
+ return ;
},
img: props => (
;
-}
diff --git a/microsite-canon/package.json b/microsite-canon/package.json
index 963f219ddf..856c1124de 100644
--- a/microsite-canon/package.json
+++ b/microsite-canon/package.json
@@ -19,6 +19,7 @@
"@next/mdx": "^15.1.3",
"@types/mdx": "^2.0.13",
"next": "15.1.3",
+ "next-themes": "^0.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
diff --git a/microsite-canon/yarn.lock b/microsite-canon/yarn.lock
index 2af0e436e7..25272cdc9e 100644
--- a/microsite-canon/yarn.lock
+++ b/microsite-canon/yarn.lock
@@ -1199,6 +1199,7 @@ __metadata:
eslint: ^9
eslint-config-next: 15.1.3
next: 15.1.3
+ next-themes: ^0.4.4
react: ^19.0.0
react-dom: ^19.0.0
shiki: ^1.24.4
@@ -3427,6 +3428,16 @@ __metadata:
languageName: node
linkType: hard
+"next-themes@npm:^0.4.4":
+ version: 0.4.4
+ resolution: "next-themes@npm:0.4.4"
+ peerDependencies:
+ react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ checksum: 9107fd68152eec5e4681e18c7ddbf17f8d4d65abc0ff3b17f6f5d8ee61d2b2106ee5f26d93942da52799214354eed3bb5d4a76a8bf2c7274e73ac09467441390
+ languageName: node
+ linkType: hard
+
"next@npm:15.1.3":
version: 15.1.3
resolution: "next@npm:15.1.3"