From ee6454ef3cb4c8dc54a504fa6c1f585dbd56a6d4 Mon Sep 17 00:00:00 2001
From: Charles de Dreuille
Date: Mon, 30 Dec 2024 11:24:54 +0100
Subject: [PATCH] Improve CodeBlock
Signed-off-by: Charles de Dreuille
---
microsite-canon/app/globals.css | 17 +++++------
microsite-canon/app/layout.tsx | 13 +++++----
microsite-canon/app/providers.tsx | 13 +++++++++
.../components/CodeBlock/index.tsx | 28 +++++++++++++++++++
.../components/CodeBlock/styles.module.css | 26 +++++++++++++++++
microsite-canon/components/Global/index.tsx | 9 ++++++
.../components/Global/styles.module.css | 3 ++
.../components/Sidebar/Sidebar.module.css | 1 +
.../components/Tabs/Tabs.module.css | 2 ++
microsite-canon/components/Tabs/index.tsx | 19 +++++++------
microsite-canon/content/layout.mdx | 15 +++++++++-
microsite-canon/eslint.config.mjs | 8 +++---
microsite-canon/mdx-components.tsx | 28 ++++---------------
microsite-canon/package.json | 1 +
microsite-canon/yarn.lock | 11 ++++++++
15 files changed, 143 insertions(+), 51 deletions(-)
create mode 100644 microsite-canon/app/providers.tsx
create mode 100644 microsite-canon/components/CodeBlock/index.tsx
create mode 100644 microsite-canon/components/CodeBlock/styles.module.css
create mode 100644 microsite-canon/components/Global/index.tsx
create mode 100644 microsite-canon/components/Global/styles.module.css
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"