Improve CodeBlock

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2024-12-30 11:24:54 +01:00
parent ee0e3fca93
commit ee6454ef3c
15 changed files with 143 additions and 51 deletions
+7 -10
View File
@@ -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;
+7 -6
View File
@@ -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 (
<html lang="en" data-theme="light">
<html lang="en" suppressHydrationWarning>
<body>
<CanonProvider>
<>
<Providers>
<Global>
<Sidebar />
<div className={styles.container}>{children}</div>
</>
</CanonProvider>
</Global>
</Providers>
</body>
</html>
);
+13
View File
@@ -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 (
<CanonProvider>
<ThemeProvider defaultTheme="light">{children}</ThemeProvider>
</CanonProvider>
);
};
@@ -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 (
<div className={styles.codeBlock}>
{title && <div className={styles.title}>{title}</div>}
<div dangerouslySetInnerHTML={{ __html: out }} className={styles.code} />
</div>
);
}
@@ -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;
}
@@ -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 <div className={styles.global}>{children}</div>;
};
@@ -0,0 +1,3 @@
.global {
width: 100%;
}
@@ -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 {
@@ -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;
}
+11 -8
View File
@@ -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 (
<Tabs.Root
className={styles.tabs}
value={theme}
value={isClient ? theme : 'light'}
onValueChange={onValueChange}
>
<Tabs.List className={styles.list}>
+14 -1
View File
@@ -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
<CodeBlock
title="Layout components"
code={`<Stack direction="column" gap="md">
<Box>Hello World</Box>
<Inline gap="sm">
<Box>Project 1</Box>
<Box>Project 2</Box>
</Inline>
</Stack>
`}
/>
```tsx
<Stack direction="column" gap="md">
<Box>Hello World</Box>
<Inline gap="sm">
+4 -4
View File
@@ -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;
+6 -22
View File
@@ -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 {
</p>
),
pre: ({ children }) => {
console.log(children);
return <CodeBlock lang="ts">{children as ReactNode}</CodeBlock>;
const codeContent = React.isValidElement(children)
? (children.props as { children: string }).children
: '';
return <CodeBlock lang="ts" code={codeContent} />;
},
img: props => (
<Image
@@ -60,23 +64,3 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
...components,
};
}
interface CodeBlockProps {
children: ReactNode;
lang: BundledLanguage;
}
async function CodeBlock({ children, lang }: CodeBlockProps) {
const codeContent = React.isValidElement(children)
? (children.props as { children: string }).children
: '';
const out = await codeToHtml(codeContent, {
lang: lang,
themes: {
light: 'vitesse-light',
dark: 'vitesse-dark',
},
});
return <div dangerouslySetInnerHTML={{ __html: out }} />;
}
+1
View File
@@ -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"
},
+11
View File
@@ -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"