Fix theme switcher

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-01-02 12:09:09 +01:00
parent 1bb0eceea4
commit a54d14754d
14 changed files with 167 additions and 244 deletions
+2 -2
View File
@@ -22,10 +22,10 @@ export default async function RootLayout({
}>) {
const cookieStore = await cookies();
const theme = cookieStore.get('theme')?.value || 'light';
const version = cookieStore.get('version')?.value || 'v2';
const themeName = cookieStore.get('theme-name')?.value || 'default';
return (
<html lang="en" data-theme={theme} data-version={version}>
<html lang="en" data-theme={theme} data-theme-name={themeName}>
<body>
<Providers>
<div className={styles.global}>
@@ -10,9 +10,9 @@ interface CodeBlockProps {
code?: string;
}
export async function CodeBlock({ lang, title, code }: CodeBlockProps) {
export async function CodeBlock({ lang = 'tsx', title, code }: CodeBlockProps) {
const out = await codeToHtml(code || '', {
lang: lang || 'tsx',
lang: lang,
themes: {
light: 'min-light',
dark: 'min-dark',
@@ -1,99 +0,0 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import styles from './styles.module.css';
export const ComponentStatus = () => {
return (
<div className={styles.container}>
<Component
name="Box"
status="alpha"
link="/?path=/docs/components-box--docs"
/>
<Component
name="Button"
status="inProgress"
link="/?path=/docs/components-button--docs"
/>
<Component name="Checkbox" status="inProgress" />
<Component
name="Container"
status="alpha"
link="/?path=/docs/components-container--docs"
/>
<Component
name="Grid"
status="alpha"
link="/?path=/story/components-grid--docs"
/>
<Component name="Header" status="notStarted" />
<Component
name="Icon"
status="alpha"
link="/?path=/docs/components-icon--docs"
/>
<Component
name="Inline"
status="alpha"
link="/?path=/story/components-inline--default"
/>
<Component name="Input" status="inProgress" />
<Component name="Radio" status="notStarted" />
<Component name="Select" status="notStarted" />
<Component
name="Stack"
status="alpha"
link="/?path=/docs/components-stack--default"
/>
<Component name="Switch" status="notStarted" />
<Component name="Tabs" status="notStarted" />
<Component name="Tooltip" status="notStarted" />
</div>
);
};
export const Component = ({
name,
status = 'notStarted',
style,
link,
}: {
name: string;
status: 'notStarted' | 'inProgress' | 'alpha' | 'beta' | 'stable';
style?: React.CSSProperties;
link?: string;
}) => {
return (
<div className={styles.componentStatus} style={style}>
{link ? (
<a href={link} className={styles.title}>
{name}
</a>
) : (
<span className={styles.title}>{name}</span>
)}
<span className={`${styles.pill} ${styles[status]}`}>
{status === 'notStarted' && 'Not Started'}
{status === 'inProgress' && 'In Progress'}
{status === 'alpha' && 'Alpha'}
{status === 'beta' && 'Beta'}
{status === 'stable' && 'Stable'}
</span>
</div>
);
};
@@ -1,16 +0,0 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ComponentStatus } from './ComponentStatus';
@@ -1,79 +0,0 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0px 64px;
}
.componentStatus {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
}
.title {
color: #3b59ff;
font-size: 16px;
font-weight: 400;
text-decoration: none;
}
& .pill {
display: inline-flex;
align-items: center;
color: #000;
border-radius: 40px;
padding: 0px 8px;
height: 24px;
font-size: 12px;
font-weight: 600;
border-style: solid;
border-width: 1px;
margin-left: 8px;
&.notStarted {
background-color: #f2f2f2;
border-color: #cdcdcd;
color: #888888;
}
&.inProgress {
background-color: #fff2b9;
border-color: #ffd000;
color: #d79927;
}
&.alpha {
background-color: #d7f9d7;
border-color: #4ed14a;
color: #3a9837;
}
&.beta {
background-color: #d7f9d7;
border-color: #4ed14a;
color: #3a9837;
}
&.stable {
background-color: #d7f9d7;
border-color: #4ed14a;
color: #3a9837;
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ import { cookies } from 'next/headers';
export const Sidebar = async () => {
const cookieStore = await cookies();
const theme = cookieStore.get('theme');
const version = cookieStore.get('version');
const themeName = cookieStore.get('theme-name');
return (
<div className={styles.sidebar}>
@@ -23,7 +23,7 @@ export const Sidebar = async () => {
<path d="M14.037 16.729a4.237 4.237 0 0 0 4.234-4.24 4.237 4.237 0 0 0-4.234-4.242 4.237 4.237 0 0 0-4.234 4.241 4.237 4.237 0 0 0 4.234 4.24Z" />
</svg>
<div className={styles.actions}>
<TabsVersion version={version} />
<TabsVersion themeName={themeName} />
<TabsTheme theme={theme} />
</div>
<TabsPages />
+5 -5
View File
@@ -13,13 +13,13 @@ export async function setThemeCookie() {
}
}
export async function setVersionCookie() {
export async function setThemeNameCookie() {
const cookieStore = await cookies();
const version = cookieStore.get('version');
const themeName = cookieStore.get('theme-name');
if (version?.value === 'v1') {
cookieStore.set('version', 'v2');
if (themeName?.value === 'legacy') {
cookieStore.set('theme-name', 'default');
} else {
cookieStore.set('version', 'v1');
cookieStore.set('theme-name', 'legacy');
}
}
+7 -7
View File
@@ -5,26 +5,26 @@ import { Tabs } from '@base-ui-components/react/tabs';
import { Icon, Text } from '@backstage/canon';
import { usePathname } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { setThemeCookie, setVersionCookie } from './actions';
import { setThemeCookie, setThemeNameCookie } from './actions';
export const TabsVersion = ({
version,
themeName,
}: {
version?: { value: string; name: string };
themeName?: { value: string; name: string };
}) => {
return (
<Tabs.Root
className={styles.tabs}
onValueChange={setVersionCookie}
value={version?.value || 'v2'}
onValueChange={setThemeNameCookie}
value={themeName?.value || 'default'}
>
<Tabs.List className={styles.list}>
<Tabs.Tab className={styles.tab} value="v1">
<Tabs.Tab className={styles.tab} value="legacy">
<Text variant="caption" weight="bold">
Theme 1
</Text>
</Tabs.Tab>
<Tabs.Tab className={styles.tab} value="v2">
<Tabs.Tab className={styles.tab} value="default">
<Text variant="caption" weight="bold">
Theme 2
</Text>
+49 -12
View File
@@ -1,5 +1,4 @@
import { Banner } from '@/components/Banner';
import { ComponentStatus } from '@/components/ComponentStatus';
import { CodeBlock } from '../components/CodeBlock';
<img src="header.png" style={{ width: '100%', marginBottom: '32px' }} />
@@ -9,16 +8,54 @@ the API as we go. We are aiming to improve the general UI of Backstage and
plugins across Backstage. This new library will take time to build but we are
building it incrementally with not conflict with the existing theming system.
<Banner
variant="warning"
text="This library is still under heavy construction. The API will change until we reach a stable release."
/>
## Installation
## Component Status
### 1. Install canon
We are still in the process of documenting the API and building the
components. You can use the statuses below to see what is ready and what is
coming soon. If there is a component missing that you need, please let us know
by opening an issue on GitHub.
Install Canon using a package manager.
<ComponentStatus />
<CodeBlock lang="shell" code={`npm install @backstage/canon`} />
### 2. Import the css files
Import the global CSS file at the root of your application.
```tsx
import '@backstage/canon/css/core.css';
import '@backstage/canon/css/components.css';
```
### 3. Add the theme provider
Add the theme provider to your application.
```tsx
import { ThemeProvider } from '@backstage/canon';
<ThemeProvider>
<App />
</ThemeProvider>;
```
### 4. Start building ✨
Now you can start building your plugin using the new design system.
```tsx
import { Stack, Button } from '@backstage/canon';
<Stack>
<Text>Hello World</Text>
<Button>Click me</Button>
</Stack>;
```
## Roadmap
You can check our roadmap [on GitHub](https://github.com/orgs/backstage/projects/10). We'll do our best to keep you updated on the progress.
## Next steps
Now that you have the basics down, you can start building your plugin using the new design system.
Please familiarise yourself first with our theming principles. This will help you understand the core concepts of the design system.
If you have any questions, please reach out to us on [Discord](https://discord.gg/MUpMjP2).
+7 -7
View File
@@ -14,17 +14,17 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
</Box>
),
h2: ({ children }) => (
<Box marginTop="xl" marginBottom="md">
<Heading variant="title3">{children as ReactNode}</Heading>
</Box>
),
h3: ({ children }) => (
<Box marginTop="lg" marginBottom="xs">
<Box marginTop="2xl" marginBottom="md">
<Heading variant="title4">{children as ReactNode}</Heading>
</Box>
),
h3: ({ children }) => (
<Box marginTop="xl" marginBottom="xs">
<Heading variant="title5">{children as ReactNode}</Heading>
</Box>
),
p: ({ children }) => (
<Box marginBottom="md">
<Box marginBottom="sm">
<Text variant="subtitle">{children as ReactNode}</Text>
</Box>
),
+3 -3
View File
@@ -1,4 +1,4 @@
:root {
[data-theme-name="legacy"][data-theme="light"], [data-theme-name="legacy"][data-theme="dark"] {
--canon-font-regular: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif;
& .cn-button {
@@ -45,7 +45,7 @@
}
}
[data-theme="backstage-light"] {
[data-theme-name="legacy"][data-theme="light"] {
--canon-accent: #2e77d0;
--canon-background: #f8f8f8;
--canon-surface-1: #fff;
@@ -55,7 +55,7 @@
--canon-text-secondary: #646464;
}
[data-theme="backstage-dark"] {
[data-theme-name="legacy"][data-theme="dark"] {
--canon-accent: #fff;
--canon-background: #000;
--canon-surface-1: #121212;
+35 -4
View File
@@ -5656,7 +5656,7 @@
}
}
:root {
[data-theme="light"] {
--canon-font-regular: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--canon-font-monospace: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace;
--canon-font-weight-regular: 400;
@@ -5691,9 +5691,6 @@
--canon-border-radius-2xl: 1.5rem;
--canon-container-max-width: 1200px;
--canon-container-padding: 1rem;
}
[data-theme="light"] {
--canon-accent: #000;
--canon-background: #f8f8f8;
--canon-surface-1: #fff;
@@ -5711,6 +5708,40 @@
}
[data-theme="dark"] {
--canon-font-regular: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--canon-font-monospace: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace;
--canon-font-weight-regular: 400;
--canon-font-weight-bold: 600;
--canon-font-size-label: .625rem;
--canon-font-size-caption: .75rem;
--canon-font-size-body: .875rem;
--canon-font-size-subtitle: 1rem;
--canon-font-size-title5: 1.25rem;
--canon-font-size-title4: 1.5rem;
--canon-font-size-title3: 2rem;
--canon-font-size-title2: 3rem;
--canon-font-size-title1: 4rem;
--canon-font-size-display: 5.75rem;
--canon-spacing-5xs: .125rem;
--canon-spacing-4xs: .25rem;
--canon-spacing-3xs: .375rem;
--canon-spacing-2xs: .5rem;
--canon-spacing-xs: .75rem;
--canon-spacing-sm: 1rem;
--canon-spacing-md: 1.5rem;
--canon-spacing-lg: 2rem;
--canon-spacing-xl: 2.5rem;
--canon-spacing-2xl: 3rem;
--canon-spacing-3xl: 3.5rem;
--canon-border-radius-2xs: .125rem;
--canon-border-radius-xs: .25rem;
--canon-border-radius-sm: .5rem;
--canon-border-radius-md: .75rem;
--canon-border-radius-lg: 1rem;
--canon-border-radius-xl: 1.25rem;
--canon-border-radius-2xl: 1.5rem;
--canon-container-max-width: 1200px;
--canon-container-padding: 1rem;
--canon-accent: #fff;
--canon-background: #000;
--canon-surface-1: #121212;
@@ -1,4 +1,5 @@
:root {
[data-theme-name='legacy'][data-theme='light'],
[data-theme-name='legacy'][data-theme='dark'] {
--canon-font-regular: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;
.cn-button {
@@ -51,7 +52,7 @@
}
}
[data-theme='backstage-light'] {
[data-theme-name='legacy'][data-theme='light'] {
/* Colors */
--canon-accent: #2e77d0;
--canon-background: #f8f8f8;
@@ -64,7 +65,7 @@
--canon-text-secondary: #646464;
}
[data-theme='backstage-dark'] {
[data-theme-name='legacy'][data-theme='dark'] {
/* Colors */
--canon-accent: #fff;
--canon-background: #000;
+51 -3
View File
@@ -19,7 +19,8 @@
@import './utilities.css';
/* Light theme tokens */
:root {
[data-theme='light'] {
/* Font families */
--canon-font-regular: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
@@ -68,9 +69,7 @@
/* Container */
--canon-container-max-width: 1200px;
--canon-container-padding: 1rem;
}
[data-theme='light'] {
/* Colors */
--canon-accent: #000;
--canon-background: #f8f8f8;
@@ -96,6 +95,55 @@
/* Dark theme tokens */
[data-theme='dark'] {
/* Font families */
--canon-font-regular: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
--canon-font-monospace: ui-monospace, 'Menlo', 'Monaco', 'Consolas',
'Liberation Mono', 'Courier New', monospace;
/* Font weights */
--canon-font-weight-regular: 400;
--canon-font-weight-bold: 600;
/* Font sizes */
--canon-font-size-label: 0.625rem; /* 10px */
--canon-font-size-caption: 0.75rem; /* 12px */
--canon-font-size-body: 0.875rem; /* 14px */
--canon-font-size-subtitle: 1rem; /* 16px */
--canon-font-size-title5: 1.25rem; /* 20px */
--canon-font-size-title4: 1.5rem; /* 24px */
--canon-font-size-title3: 2rem; /* 32px */
--canon-font-size-title2: 3rem; /* 48px */
--canon-font-size-title1: 4rem; /* 64px */
--canon-font-size-display: 5.75rem; /* 92px */
/* Spacing */
--canon-spacing-5xs: 0.125rem; /* 2px */
--canon-spacing-4xs: 0.25rem; /* 4px */
--canon-spacing-3xs: 0.375rem; /* 6px */
--canon-spacing-2xs: 0.5rem; /* 8px */
--canon-spacing-xs: 0.75rem; /* 12px */
--canon-spacing-sm: 1rem; /* 16px */
--canon-spacing-md: 1.5rem; /* 24px */
--canon-spacing-lg: 2rem; /* 32px */
--canon-spacing-xl: 2.5rem; /* 40px */
--canon-spacing-2xl: 3rem; /* 48px */
--canon-spacing-3xl: 3.5rem; /* 56px */
/* Border radius */
--canon-border-radius-2xs: 0.125rem; /* 2px */
--canon-border-radius-xs: 0.25rem; /* 4px */
--canon-border-radius-sm: 0.5rem; /* 8px */
--canon-border-radius-md: 0.75rem; /* 12px */
--canon-border-radius-lg: 1rem; /* 16px */
--canon-border-radius-xl: 1.25rem; /* 20px */
--canon-border-radius-2xl: 1.5rem; /* 24px */
/* Container */
--canon-container-max-width: 1200px;
--canon-container-padding: 1rem;
/* Colors */
--canon-accent: #fff;
--canon-background: #000;