Improve tabs to work with cookies
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export async function setDefaultThemeCookie() {
|
||||
const cookieStore = await cookies();
|
||||
const theme = cookieStore.get('theme');
|
||||
|
||||
if (theme === undefined) {
|
||||
cookieStore.set('theme', 'light');
|
||||
}
|
||||
}
|
||||
|
||||
export async function setDefaultVersionCookie() {
|
||||
const cookieStore = await cookies();
|
||||
const version = cookieStore.get('version');
|
||||
|
||||
if (version === undefined) {
|
||||
cookieStore.set('version', 'v2');
|
||||
}
|
||||
}
|
||||
@@ -7,19 +7,31 @@ import '../../packages/canon/src/css/components.css';
|
||||
import styles from './page.module.css';
|
||||
import { Global } from '@/components/Global';
|
||||
import { Providers } from './providers';
|
||||
import { cookies } from 'next/headers';
|
||||
import { setDefaultThemeCookie, setDefaultVersionCookie } from './actions';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Canon',
|
||||
description: 'UI library for Backstage',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const cookieStore = await cookies();
|
||||
const theme = cookieStore.get('theme')?.value || 'light';
|
||||
const version = cookieStore.get('version')?.value || 'v1';
|
||||
|
||||
let dataTheme = 'light';
|
||||
if (version === 'v1' && theme === 'light') dataTheme = 'backstage-light';
|
||||
if (version === 'v1' && theme === 'dark') dataTheme = 'backstage-dark';
|
||||
if (version === 'v2' && theme === 'light') dataTheme = 'light';
|
||||
if (version === 'v2' && theme === 'dark') dataTheme = 'dark';
|
||||
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<html lang="en" data-theme={dataTheme}>
|
||||
<body>
|
||||
<Providers>
|
||||
<Global>
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { CanonProvider } from '@backstage/canon';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import { PlaygroundProvider } from '@/utils/playground-context';
|
||||
|
||||
export const Providers = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<CanonProvider>
|
||||
<ThemeProvider defaultTheme="light">
|
||||
<PlaygroundProvider>{children}</PlaygroundProvider>
|
||||
</ThemeProvider>
|
||||
<PlaygroundProvider>{children}</PlaygroundProvider>
|
||||
</CanonProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export const Global = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import styles from './Sidebar.module.css';
|
||||
import { TabsVersion, TabsTheme, TabsPages } from '../Tabs';
|
||||
import { TabsVersion, TabsPages, TabsTheme } from '../Tabs';
|
||||
import { Docs } from './docs';
|
||||
import { Playground } from './playground';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export const Sidebar = async () => {
|
||||
const cookieStore = await cookies();
|
||||
const theme = cookieStore.get('theme');
|
||||
const version = cookieStore.get('version');
|
||||
|
||||
export const Sidebar = () => {
|
||||
return (
|
||||
<div className={styles.sidebar}>
|
||||
<div className={styles.content}>
|
||||
@@ -18,8 +23,8 @@ export const Sidebar = () => {
|
||||
<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 />
|
||||
<TabsTheme />
|
||||
<TabsVersion version={version} />
|
||||
<TabsTheme theme={theme} />
|
||||
</div>
|
||||
<TabsPages />
|
||||
<div className={styles.menu}>
|
||||
|
||||
@@ -36,8 +36,6 @@ export const Playground = () => {
|
||||
}
|
||||
};
|
||||
|
||||
console.log(selectedComponents);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={styles.section}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
import IframeResizer from '@iframe-resizer/react';
|
||||
|
||||
interface FrameProps {
|
||||
url: string;
|
||||
border: boolean;
|
||||
}
|
||||
|
||||
export const Frame = ({ url, border }: FrameProps) => {
|
||||
return (
|
||||
<IframeResizer
|
||||
src={url}
|
||||
license="GPLv3"
|
||||
checkOrigin={false}
|
||||
className={styles.container}
|
||||
style={{
|
||||
border: border ? '1px solid var(--canon-border-base)' : 'none',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,33 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import { useTheme } from 'next-themes';
|
||||
import styles from './styles.module.css';
|
||||
import IframeResizer from '@iframe-resizer/react';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Frame } from './frame';
|
||||
|
||||
interface StoryProps {
|
||||
id: string;
|
||||
border?: boolean;
|
||||
}
|
||||
|
||||
export const Story = ({ id, border = true }: StoryProps) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const localTheme = theme === 'dark' ? 'Dark' : 'Light';
|
||||
export const Story = async ({ id, border = true }: StoryProps) => {
|
||||
const cookieStore = await cookies();
|
||||
const theme = cookieStore.get('theme');
|
||||
const localTheme = theme?.value === 'dark' ? 'Dark' : 'Light';
|
||||
const chromaticId = '67584b7e8c2eb09c0422c27e-dmfbzicnkw';
|
||||
const chromaticUrl = `https://${chromaticId}.chromatic.com/iframe.html`;
|
||||
const localUrl = 'http://localhost:6006/iframe.html';
|
||||
const url = process.env.NODE_ENV === 'development' ? localUrl : chromaticUrl;
|
||||
const iframeUrl = `${url}?globals=theme%3A${localTheme}&args=&id=${id}`;
|
||||
|
||||
return (
|
||||
<IframeResizer
|
||||
src={iframeUrl}
|
||||
license="GPLv3"
|
||||
checkOrigin={false}
|
||||
className={styles.container}
|
||||
style={{
|
||||
border: border ? '1px solid var(--canon-border-base)' : 'none',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return <Frame url={iframeUrl} border={border} />;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export async function setThemeCookie() {
|
||||
const cookieStore = await cookies();
|
||||
const theme = cookieStore.get('theme');
|
||||
|
||||
if (theme?.value === 'dark') {
|
||||
cookieStore.set('theme', 'light');
|
||||
} else {
|
||||
cookieStore.set('theme', 'dark');
|
||||
}
|
||||
}
|
||||
|
||||
export async function setVersionCookie() {
|
||||
const cookieStore = await cookies();
|
||||
const version = cookieStore.get('version');
|
||||
|
||||
if (version?.value === 'v1') {
|
||||
cookieStore.set('version', 'v2');
|
||||
} else {
|
||||
cookieStore.set('version', 'v1');
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,21 @@
|
||||
import styles from './Tabs.module.css';
|
||||
import { Tabs } from '@base-ui-components/react/tabs';
|
||||
import { Icon, Text } from '@backstage/canon';
|
||||
import { usePathname, useSearchParams } from 'next/navigation';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { setThemeCookie, setVersionCookie } from './actions';
|
||||
|
||||
export const TabsVersion = () => {
|
||||
export const TabsVersion = ({
|
||||
version,
|
||||
}: {
|
||||
version?: { value: string; name: string };
|
||||
}) => {
|
||||
return (
|
||||
<Tabs.Root className={styles.tabs} defaultValue="v1">
|
||||
<Tabs.Root
|
||||
className={styles.tabs}
|
||||
onValueChange={setVersionCookie}
|
||||
value={version?.value || 'v2'}
|
||||
>
|
||||
<Tabs.List className={styles.list}>
|
||||
<Tabs.Tab className={styles.tab} value="v1">
|
||||
<Text variant="caption" weight="bold">
|
||||
@@ -28,24 +35,16 @@ export const TabsVersion = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const TabsTheme = () => {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
const onValueChange = (value: string) => {
|
||||
setTheme(value);
|
||||
};
|
||||
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
export const TabsTheme = ({
|
||||
theme,
|
||||
}: {
|
||||
theme?: { value: string; name: string };
|
||||
}) => {
|
||||
return (
|
||||
<Tabs.Root
|
||||
className={styles.tabs}
|
||||
value={isClient ? theme : 'light'}
|
||||
onValueChange={onValueChange}
|
||||
onValueChange={setThemeCookie}
|
||||
value={theme?.value || 'light'}
|
||||
>
|
||||
<Tabs.List className={styles.list}>
|
||||
<Tabs.Tab className={styles.tab} value="light">
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"@types/mdx": "^2.0.13",
|
||||
"motion": "^11.15.0",
|
||||
"next": "15.1.3",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-frame-component": "^5.2.7"
|
||||
|
||||
@@ -1 +1 @@
|
||||
.cn-button{all:unset;user-select:none;font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-bold);font-size:var(--canon-font-size-body);cursor:pointer;border-radius:8px;justify-content:center;align-items:center;padding:0;transition:all .15s;display:inline-flex}.cn-button-primary{background-color:var(--canon-accent);color:var(--canon-text-primary-on-accent);&:hover{box-shadow:inset 0 0 0 1px var(--canon-outline-focus);color:var(--canon-text-primary);background-color:#0000}}.cn-button-secondary{box-shadow:inset 0 0 0 1px var(--canon-outline);color:var(--canon-text-primary);background-color:#0000;&:hover{box-shadow:inset 0 0 0 1px var(--canon-outline-hover)}}.cn-button-tertiary{color:var(--canon-text-primary);background-color:#0000;&:hover{color:var(--canon-text-secondary)}}.cn-button-small{height:32px;padding-left:6px;padding-right:6px}.cn-button-medium{height:40px;padding-left:8px;padding-right:8px}.cn-button-content{align-items:center;gap:var(--canon-spacing-xs);font-weight:var(--canon-font-weight-bold);display:flex}.cn-button-content-icon-both{flex:1}.canon-stack{flex-direction:column;display:flex}.canon-inline{flex-wrap:wrap;display:flex}.canon-grid{display:grid}.canon-container{max-width:var(--canon-container-max-width);padding:0 var(--canon-container-padding);margin:0 auto}.icon-12{width:12px;height:12px}.icon-16{width:16px;height:16px}.icon-24{width:24px;height:24px}.checkbox-label{justify-content:center;align-items:center;gap:var(--canon-spacing-xs);font-size:var(--canon-font-size-xs);font-family:var(--canon-font-regular);color:var(--canon-text-primary);user-select:none;flex-direction:row;display:flex;&:hover{& .checkbox{box-shadow:inset 0 0 0 1px var(--canon-border-hover)}}}.checkbox{all:unset;width:1rem;height:1rem;box-shadow:inset 0 0 0 1px var(--canon-border-base);cursor:pointer;border-radius:2px;justify-content:center;align-items:center;transition:all .2s ease-in-out;display:flex;&[data-checked]{background-color:var(--canon-accent)}}.checkbox-indicator{color:var(--canon-text-primary-on-accent);justify-content:center;align-items:center;display:flex}.table{background-color:var(--canon-surface-1);border-radius:var(--canon-border-radius-xs);width:100%;padding-bottom:var(--canon-spacing-5xs);padding-top:var(--canon-spacing-5xs);font-size:var(--canon-font-size-body);position:relative;overflow:auto;& table{caption-side:bottom;width:100%;font-size:var(--canon-font-size-sm);border-collapse:collapse}}.table-head{text-align:left;padding:var(--canon-spacing-xs)}.table-body{& tr:last-child{border-bottom:none}}.table-row{transition:color .2s ease-in-out;&:hover td{background-color:var(--canon-surface-3)}& .table-cell:first-child{border-top-left-radius:var(--canon-border-radius-xs);border-bottom-left-radius:var(--canon-border-radius-xs);box-shadow:inset 4px 2px 0 0 var(--canon-surface-1),inset 4px -2px 0 0 var(--canon-surface-1);padding-left:var(--canon-spacing-xs)}& .table-cell:last-child{border-top-right-radius:var(--canon-border-radius-xs);border-bottom-right-radius:var(--canon-border-radius-xs);box-shadow:inset -4px 2px 0 0 var(--canon-surface-1),inset -4px -2px 0 0 var(--canon-surface-1);padding-right:var(--canon-spacing-xs)}}.table-cell{padding:var(--canon-spacing-xs);background-color:var(--canon-surface-2);box-shadow:inset 0px 2px 0 0 var(--canon-surface-1),inset 0px -2px 0 0 var(--canon-surface-1)}.text{font-family:var(--canon-font-regular);color:var(--canon-text-primary);margin:0;padding:0;&.text-body{font-size:var(--canon-font-size-body);line-height:140%}&.text-subtitle{font-size:var(--canon-font-size-subtitle);line-height:140%}&.text-caption{font-size:var(--canon-font-size-caption);line-height:140%}&.text-label{font-size:var(--canon-font-size-label);line-height:140%}&.text-regular{font-weight:var(--canon-font-weight-regular)}&.text-bold{font-weight:var(--canon-font-weight-bold)}}.text{font-family:var(--canon-font-regular);color:var(--canon-text-primary);margin:0;padding:0;&.text-display{font-size:var(--canon-font-size-display);line-height:100%;font-weight:var(--canon-font-weight-bold)}&.text-title1{font-size:var(--canon-font-size-title1);line-height:100%;font-weight:var(--canon-font-weight-bold)}&.text-title2{font-size:var(--canon-font-size-title2);line-height:100%;font-weight:var(--canon-font-weight-bold)}&.text-title3{font-size:var(--canon-font-size-title3);line-height:100%;font-weight:var(--canon-font-weight-bold)}&.text-title4{font-size:var(--canon-font-size-title4);line-height:100%;font-weight:var(--canon-font-weight-bold)}&.text-title5{font-size:var(--canon-font-size-title5);line-height:100%;font-weight:var(--canon-font-weight-bold)}}
|
||||
.cn-button{all:unset;user-select:none;font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-bold);font-size:var(--canon-font-size-body);cursor:pointer;border-radius:8px;justify-content:center;align-items:center;padding:0;transition:all .15s;display:inline-flex}.cn-button-primary{background-color:var(--canon-accent);color:var(--canon-text-primary-on-accent);&:hover{box-shadow:inset 0 0 0 1px var(--canon-border-focus);color:var(--canon-text-primary);background-color:#0000}}.cn-button-secondary{box-shadow:inset 0 0 0 1px var(--canon-border-base);color:var(--canon-text-primary);background-color:#0000;&:hover{box-shadow:inset 0 0 0 1px var(--canon-border-hover)}}.cn-button-tertiary{color:var(--canon-text-primary);background-color:#0000;&:hover{color:var(--canon-text-secondary)}}.cn-button-small{height:32px;padding-left:6px;padding-right:6px}.cn-button-medium{height:40px;padding-left:8px;padding-right:8px}.cn-button-content{align-items:center;gap:var(--canon-spacing-xs);font-weight:var(--canon-font-weight-bold);display:flex}.cn-button-content-icon-both{flex:1}.canon-stack{flex-direction:column;display:flex}.canon-inline{flex-wrap:wrap;display:flex}.canon-grid{display:grid}.canon-container{max-width:var(--canon-container-max-width);padding:0 var(--canon-container-padding);margin:0 auto}.icon-12{width:12px;height:12px}.icon-16{width:16px;height:16px}.icon-24{width:24px;height:24px}.checkbox-label{justify-content:center;align-items:center;gap:var(--canon-spacing-xs);font-size:var(--canon-font-size-xs);font-family:var(--canon-font-regular);color:var(--canon-text-primary);user-select:none;flex-direction:row;display:flex;&:hover{& .checkbox{box-shadow:inset 0 0 0 1px var(--canon-border-hover)}}}.checkbox{all:unset;width:1rem;height:1rem;box-shadow:inset 0 0 0 1px var(--canon-border-base);cursor:pointer;border-radius:2px;justify-content:center;align-items:center;transition:all .2s ease-in-out;display:flex;&[data-checked]{background-color:var(--canon-accent)}}.checkbox-indicator{color:var(--canon-text-primary-on-accent);justify-content:center;align-items:center;display:flex}.table{background-color:var(--canon-surface-1);border-radius:var(--canon-border-radius-xs);width:100%;padding-bottom:var(--canon-spacing-5xs);padding-top:var(--canon-spacing-5xs);font-size:var(--canon-font-size-body);position:relative;overflow:auto;& table{caption-side:bottom;width:100%;font-size:var(--canon-font-size-sm);border-collapse:collapse}}.table-head{text-align:left;padding:var(--canon-spacing-xs)}.table-body{& tr:last-child{border-bottom:none}}.table-row{transition:color .2s ease-in-out;&:hover td{background-color:var(--canon-surface-3)}& .table-cell:first-child{border-top-left-radius:var(--canon-border-radius-xs);border-bottom-left-radius:var(--canon-border-radius-xs);box-shadow:inset 4px 2px 0 0 var(--canon-surface-1),inset 4px -2px 0 0 var(--canon-surface-1);padding-left:var(--canon-spacing-xs)}& .table-cell:last-child{border-top-right-radius:var(--canon-border-radius-xs);border-bottom-right-radius:var(--canon-border-radius-xs);box-shadow:inset -4px 2px 0 0 var(--canon-surface-1),inset -4px -2px 0 0 var(--canon-surface-1);padding-right:var(--canon-spacing-xs)}}.table-cell{padding:var(--canon-spacing-xs);background-color:var(--canon-surface-2);box-shadow:inset 0px 2px 0 0 var(--canon-surface-1),inset 0px -2px 0 0 var(--canon-surface-1)}.text{font-family:var(--canon-font-regular);color:var(--canon-text-primary);margin:0;padding:0;&.text-body{font-size:var(--canon-font-size-body);line-height:140%}&.text-subtitle{font-size:var(--canon-font-size-subtitle);line-height:140%}&.text-caption{font-size:var(--canon-font-size-caption);line-height:140%}&.text-label{font-size:var(--canon-font-size-label);line-height:140%}&.text-regular{font-weight:var(--canon-font-weight-regular)}&.text-bold{font-weight:var(--canon-font-weight-bold)}}.text{font-family:var(--canon-font-regular);color:var(--canon-text-primary);margin:0;padding:0;&.text-display{font-size:var(--canon-font-size-display);line-height:100%;font-weight:var(--canon-font-weight-bold)}&.text-title1{font-size:var(--canon-font-size-title1);line-height:100%;font-weight:var(--canon-font-weight-bold)}&.text-title2{font-size:var(--canon-font-size-title2);line-height:100%;font-weight:var(--canon-font-weight-bold)}&.text-title3{font-size:var(--canon-font-size-title3);line-height:100%;font-weight:var(--canon-font-weight-bold)}&.text-title4{font-size:var(--canon-font-size-title4);line-height:100%;font-weight:var(--canon-font-weight-bold)}&.text-title5{font-size:var(--canon-font-size-title5);line-height:100%;font-weight:var(--canon-font-weight-bold)}}
|
||||
@@ -24570,7 +24570,6 @@ __metadata:
|
||||
lightningcss: ^1.28.2
|
||||
motion: ^11.15.0
|
||||
next: 15.1.3
|
||||
next-themes: ^0.4.4
|
||||
react: ^19.0.0
|
||||
react-dom: ^19.0.0
|
||||
react-frame-component: ^5.2.7
|
||||
@@ -38282,16 +38281,6 @@ __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-tick@npm:1, next-tick@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "next-tick@npm:1.1.0"
|
||||
|
||||
Reference in New Issue
Block a user