Improve CodeBlock
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }} />;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user