Merge pull request #28442 from backstage/canon-docs-updates

Canon docs updates
This commit is contained in:
Charles de Dreuille
2025-01-12 09:10:18 +00:00
committed by GitHub
17 changed files with 509 additions and 172 deletions
+4
View File
@@ -4,6 +4,10 @@ body {
background-color: var(--canon-background);
color: var(--canon-text-primary);
transition: background-color 0.2s ease-in-out;
--docs-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}
iframe {
+6 -6
View File
@@ -1,8 +1,9 @@
import type { Metadata } from 'next';
import { Sidebar } from '../components/Sidebar';
import { Toolbar } from '@/components/Toolbar';
import { Providers } from './providers';
import styles from './page.module.css';
import { Providers } from './providers';
import './globals.css';
import '/public/core.css';
@@ -19,10 +20,6 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
// const cookieStore = await cookies();
// const theme = cookieStore.get('theme')?.value || 'light';
// const themeName = cookieStore.get('theme-name')?.value || 'default';
return (
<html
lang="en"
@@ -34,7 +31,10 @@ export default function RootLayout({
<Providers>
<div className={styles.global}>
<Sidebar />
<div className={styles.container}>{children}</div>
<div className={styles.container}>
<Toolbar />
{children}
</div>
</div>
</Providers>
</body>
Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

+6 -1
View File
@@ -3,10 +3,15 @@
}
.container {
padding-left: 300px;
width: 100%;
}
@media (min-width: 768px) {
.container {
padding-left: 300px;
}
}
.page {
flex: 1;
}
@@ -1,34 +1,34 @@
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 300px;
height: 100vh;
color: var(--canon-text-primary);
background-color: var(--canon-surface-1);
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;
overflow: hidden;
display: none;
}
@media (min-width: 768px) {
.sidebar {
display: block;
position: fixed;
top: 0;
left: 0;
width: 300px;
height: 100vh;
color: var(--canon-text-primary);
background-color: var(--canon-surface-1);
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;
overflow: hidden;
}
}
.logoContainer {
padding-left: 6px;
padding-top: 32px;
}
.logo path {
fill: var(--canon-text-primary);
}
.content {
padding-top: 20px;
}
.actions {
display: flex;
flex-direction: row;
gap: 12px;
margin-top: 20px;
margin-bottom: 12px;
}
.menu {
display: flex;
flex-direction: row;
@@ -46,6 +46,13 @@
width: 100%;
}
.sectionTitle {
font-family: var(--docs-font);
font-size: var(--canon-font-size-body);
font-weight: var(--canon-font-weight-bold);
padding: 12px 0;
}
.line {
width: 100%;
display: flex;
@@ -65,3 +72,16 @@
.line.active {
background-color: var(--canon-surface-2);
}
.lineTitle {
font-family: var(--docs-font);
font-size: var(--canon-font-size-body);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-text-primary);
}
.lineStatus {
font-family: var(--docs-font);
font-size: var(--canon-font-size-body);
color: var(--canon-text-secondary);
}
+4 -9
View File
@@ -55,9 +55,7 @@ export const Docs = () => {
return (
<Fragment key={section.title}>
<Box marginTop="lg" marginBottom="2xs">
<Text variant="body" weight="bold">
{section.title}
</Text>
<div className={styles.sectionTitle}>{section.title}</div>
</Box>
{section.content.map(item => {
const isActive = pathname === `${section.url}/${item.slug}`;
@@ -68,17 +66,14 @@ export const Docs = () => {
key={item.slug}
className={`${styles.line} ${isActive ? styles.active : ''}`}
>
<Text variant="body">{item.title}</Text>
<Text
variant="body"
style={{ color: 'var(--canon-text-secondary)' }}
>
<div className={styles.lineTitle}>{item.title}</div>
<div className={styles.lineStatus}>
{item.status === 'alpha' && 'Alpha'}
{item.status === 'beta' && 'Beta'}
{item.status === 'inProgress' && 'In Progress'}
{item.status === 'stable' && 'Stable'}
{item.status === 'deprecated' && 'Deprecated'}
</Text>
</div>
</Link>
);
})}
+5 -11
View File
@@ -1,5 +1,4 @@
import styles from './Sidebar.module.css';
import { TabsVersion, TabsPages, TabsTheme } from './tabs';
import { Docs } from './docs';
import { Playground } from './playground';
import Link from 'next/link';
@@ -7,7 +6,7 @@ import Link from 'next/link';
export const Sidebar = () => {
return (
<div className={styles.sidebar}>
<div className={styles.content}>
<div className={styles.logoContainer}>
<Link href="/">
<svg
width="89"
@@ -20,15 +19,10 @@ 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>
</Link>
<div className={styles.actions}>
<TabsVersion />
<TabsTheme />
</div>
<TabsPages />
<div className={styles.menu}>
<Docs />
<Playground />
</div>
</div>
<div className={styles.menu}>
<Docs />
<Playground />
</div>
</div>
);
-103
View File
@@ -1,103 +0,0 @@
'use client';
import styles from './Tabs.module.css';
import { Tabs } from '@base-ui-components/react/tabs';
import { Icon } from '../../../../packages/canon/src/components/Icon';
import { Text } from '../../../../packages/canon/src/components/Text';
import { usePathname } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { usePlayground } from '@/utils/playground-context';
export const TabsVersion = () => {
const { selectedThemeName, setSelectedThemeName } = usePlayground();
return (
<Tabs.Root
className={styles.tabs}
onValueChange={setSelectedThemeName}
value={selectedThemeName}
>
<Tabs.List className={styles.list}>
<Tabs.Tab className={styles.tab} value="legacy">
<Text variant="caption" weight="bold">
Theme 1
</Text>
</Tabs.Tab>
<Tabs.Tab className={styles.tab} value="default">
<Text variant="caption" weight="bold">
Theme 2
</Text>
</Tabs.Tab>
<Tabs.Indicator className={styles.indicator} />
</Tabs.List>
</Tabs.Root>
);
};
export const TabsTheme = () => {
const { selectedTheme, setSelectedTheme } = usePlayground();
return (
<Tabs.Root
className={styles.tabsTheme}
onValueChange={setSelectedTheme}
value={selectedTheme}
>
<Tabs.List className={styles.list}>
<Tabs.Tab className={styles.tab} value="light">
<Icon name="sun" />
</Tabs.Tab>
<Tabs.Tab className={styles.tab} value="dark">
<Icon name="moon" />
</Tabs.Tab>
<Tabs.Indicator className={styles.indicator} />
</Tabs.List>
</Tabs.Root>
);
};
export const TabsPages = () => {
const pathname = usePathname();
const router = useRouter();
const onValueChange = (value: string) => {
if (value === 'docs') {
router.push('/');
} else {
router.push('/playground');
}
};
return (
<Tabs.Root
className={styles.tabs}
value={pathname.includes('playground') ? 'playground' : 'docs'}
onValueChange={onValueChange}
>
<Tabs.List className={styles.list}>
<Tabs.Tab
className={styles.tab}
value="docs"
onClick={() => {
router.push('/');
}}
>
<Text variant="caption" weight="bold">
Documentation
</Text>
</Tabs.Tab>
<Tabs.Tab
className={styles.tab}
value="playground"
onClick={() => {
router.push('/playground');
}}
>
<Text variant="caption" weight="bold">
Playground
</Text>
</Tabs.Tab>
<Tabs.Indicator className={styles.indicator} />
</Tabs.List>
</Tabs.Root>
);
};
@@ -0,0 +1,18 @@
import { ThemeSelector } from './theme';
import { ThemeNameSelector } from './theme-name';
import styles from './styles.module.css';
import { Nav } from './nav';
export const Toolbar = () => {
return (
<div className={styles.toolbar}>
<div>
<Nav />
</div>
<div className={styles.actions}>
<ThemeNameSelector />
<ThemeSelector />
</div>
</div>
);
};
@@ -0,0 +1,79 @@
.tabs {
display: none;
}
@media (min-width: 768px) {
.tabs {
display: block;
height: 60px;
}
}
.tabsTheme {
width: 142px;
border-radius: 0.375rem;
background-color: var(--canon-surface-2);
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
.list {
display: flex;
position: relative;
z-index: 0;
gap: var(--canon-spacing-lg);
}
.tab {
all: unset;
height: 60px;
font-family: var(--docs-font);
color: var(--canon-text-secondary);
font-size: var(--canon-font-size-body);
font-weight: var(--canon-font-weight-bold);
cursor: pointer;
transition: color 0.2s ease-in-out;
&:hover {
color: var(--canon-text-primary);
}
&[data-selected] {
color: var(--canon-text-primary);
& p {
color: var(--canon-text-primary);
}
}
&:focus-visible {
position: relative;
&::before {
content: '';
position: absolute;
inset: 0.25rem 0;
border-radius: 0.25rem;
outline: 2px solid var(--canon-surface-1);
outline-offset: -1px;
}
}
}
.tab p {
color: var(--canon-text-secondary) !important;
}
.indicator {
position: absolute;
z-index: -1;
left: 0;
bottom: -1px;
translate: var(--active-tab-left) -50%;
width: var(--active-tab-width);
height: 1px;
border-radius: 0.25rem;
background-color: var(--canon-text-primary);
transition-property: translate, width, background-color;
transition-duration: 200ms;
transition-timing-function: ease-in-out;
}
+49
View File
@@ -0,0 +1,49 @@
'use client';
import { Tabs } from '@base-ui-components/react/tabs';
import { usePathname } from 'next/navigation';
import { useRouter } from 'next/navigation';
import styles from './nav.module.css';
export const Nav = () => {
const pathname = usePathname();
const router = useRouter();
const onValueChange = (value: string) => {
if (value === 'docs') {
router.push('/');
} else {
router.push('/playground');
}
};
return (
<Tabs.Root
className={styles.tabs}
value={pathname.includes('playground') ? 'playground' : 'docs'}
onValueChange={onValueChange}
>
<Tabs.List className={styles.list}>
<Tabs.Tab
className={styles.tab}
value="docs"
onClick={() => {
router.push('/');
}}
>
Documentation
</Tabs.Tab>
<Tabs.Tab
className={styles.tab}
value="playground"
onClick={() => {
router.push('/playground');
}}
>
Playground
</Tabs.Tab>
<Tabs.Indicator className={styles.indicator} />
</Tabs.List>
</Tabs.Root>
);
};
@@ -0,0 +1,21 @@
.toolbar {
position: sticky;
top: 0;
left: 0;
right: 0;
z-index: 10;
background-color: var(--canon-surface-1);
border-bottom: 1px solid var(--canon-border-base);
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 var(--canon-spacing-md);
transition: background-color 0.2s ease-in-out;
}
.actions {
display: flex;
align-items: center;
gap: var(--canon-spacing-sm);
}
@@ -0,0 +1,137 @@
.Select {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
height: 2rem;
padding-left: 0.875rem;
padding-right: 0.75rem;
margin: 0;
outline: 0;
border: 1px solid var(--color-gray-200);
border-radius: 0.375rem;
font-family: inherit;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
cursor: pointer;
user-select: none;
background-color: var(--canon-surface-2);
transition: background-color 0.2s ease-in-out;
&:focus-visible {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
}
.SelectIcon {
display: flex;
}
.SelectValue {
font-size: var(--canon-font-size-body);
font-weight: var(--canon-font-weight-regular);
}
.Positioner {
z-index: 20;
}
.Popup {
box-sizing: border-box;
padding-block: 0.25rem;
border-radius: 0.375rem;
background-color: canvas;
color: var(--color-gray-900);
border: 1px solid var(--canon-border-base);
padding-inline: 0.25rem;
transform-origin: var(--transform-origin);
transition: transform 150ms, opacity 150ms;
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
transform: scale(0.9);
}
&[data-side='none'] {
transition: none;
transform: none;
opacity: 1;
}
@media (prefers-color-scheme: light) {
outline: 1px solid var(--color-gray-200);
box-shadow: 0px 10px 15px -3px var(--color-gray-200),
0px 4px 6px -4px var(--color-gray-200);
}
@media (prefers-color-scheme: dark) {
outline: 1px solid var(--color-gray-300);
outline-offset: -1px;
}
}
.Item {
box-sizing: border-box;
outline: 0;
font-size: 0.875rem;
line-height: 1rem;
padding-block: 0.5rem;
padding-left: 0.625rem;
padding-right: 1rem;
font-size: var(--canon-font-size-body);
font-weight: var(--canon-font-weight-bold);
display: grid;
gap: 0.5rem;
align-items: center;
grid-template-columns: 0.75rem 1fr;
cursor: default;
user-select: none;
border-radius: 0.25rem;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
[data-side='none'] & {
font-size: 1rem;
padding-right: 3rem;
min-width: calc(var(--anchor-width) + 1rem);
}
&[data-highlighted] {
z-index: 0;
position: relative;
color: var(--color-gray-50);
background-color: var(--canon-surface-2);
}
&[data-highlighted]::before {
content: '';
z-index: -1;
position: absolute;
inset-block: 0;
inset-inline: 0.25rem;
border-radius: 0.25rem;
background-color: var(--color-gray-900);
}
}
.ItemIndicator {
grid-column-start: 1;
width: 1rem;
height: 1rem;
display: flex;
align-items: center;
}
.ItemIndicatorIcon {
display: block;
width: 0.75rem;
height: 0.75rem;
}
.ItemText {
grid-column-start: 2;
}
@@ -0,0 +1,48 @@
'use client';
import { Select } from '@base-ui-components/react/select';
import styles from './theme-name.module.css';
import { Icon } from '@backstage/canon';
import { usePlayground } from '@/utils/playground-context';
const themes = [
{ name: 'Backstage Legacy', value: 'legacy' },
{ name: 'Backstage Default', value: 'default' },
];
export const ThemeNameSelector = () => {
const { selectedThemeName, setSelectedThemeName } = usePlayground();
return (
<Select.Root
value={selectedThemeName || 'default'}
onValueChange={setSelectedThemeName}
>
<Select.Trigger className={styles.Select}>
<Select.Value
className={styles.SelectValue}
placeholder="Select a theme"
/>
<Select.Icon className={styles.SelectIcon}>
<Icon name="chevronDown" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Positioner className={styles.Positioner} sideOffset={8}>
<Select.Popup className={styles.Popup}>
{themes.map(({ name, value }) => (
<Select.Item className={styles.Item} value={value}>
<Select.ItemIndicator className={styles.ItemIndicator}>
<Icon name="check" />
</Select.ItemIndicator>
<Select.ItemText className={styles.ItemText}>
{name}
</Select.ItemText>
</Select.Item>
))}
</Select.Popup>
</Select.Positioner>
</Select.Portal>
</Select.Root>
);
};
@@ -6,7 +6,7 @@
}
.tabsTheme {
width: 142px;
width: 100px;
border-radius: 0.375rem;
background-color: var(--canon-surface-2);
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
@@ -29,21 +29,17 @@
outline: 0;
background: none;
appearance: none;
color: var(--canon-text-secondary) !important;
font-family: inherit;
font-size: 0.75rem;
line-height: 1.25rem;
font-weight: 500;
color: var(--canon-text-secondary);
user-select: none;
height: 2.25rem;
height: 2rem;
flex: 1;
cursor: pointer;
&[data-selected] {
color: var(--canon-text-primary) !important;
color: var(--canon-text-primary);
& p {
color: var(--canon-text-primary) !important;
color: var(--canon-text-primary);
}
}
@@ -78,7 +74,7 @@
top: 50%;
translate: var(--active-tab-left) -50%;
width: var(--active-tab-width);
height: 1.8rem;
height: 1.5rem;
border-radius: 0.25rem;
background-color: var(--canon-surface-1);
transition-property: translate, width, background-color;
@@ -0,0 +1,28 @@
'use client';
import { Tabs } from '@base-ui-components/react/tabs';
import { Icon } from '@backstage/canon';
import { usePlayground } from '@/utils/playground-context';
import styles from './theme.module.css';
export const ThemeSelector = () => {
const { selectedTheme, setSelectedTheme } = usePlayground();
return (
<Tabs.Root
className={styles.tabsTheme}
onValueChange={setSelectedTheme}
value={selectedTheme}
>
<Tabs.List className={styles.list}>
<Tabs.Tab className={styles.tab} value="light">
<Icon name="sun" />
</Tabs.Tab>
<Tabs.Tab className={styles.tab} value="dark">
<Icon name="moon" />
</Tabs.Tab>
<Tabs.Indicator className={styles.indicator} />
</Tabs.List>
</Tabs.Root>
);
};
+54 -8
View File
@@ -1,33 +1,64 @@
import React, { ReactNode } from 'react';
import type { MDXComponents } from 'mdx/types';
import Image, { ImageProps } from 'next/image';
import { ReactNode } from 'react';
import React from 'react';
import { CodeBlock } from '@/components/CodeBlock';
import { Heading } from '../../packages/canon/src/components/Heading';
import { Text } from '../../packages/canon/src/components/Text';
import { Box } from '../../packages/canon/src/components/Box';
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
// Allows customizing built-in components, e.g. to add styling.
h1: ({ children }) => (
<Box marginBottom="md" style={{ marginTop: '4rem' }}>
<Heading variant="title2">{children as ReactNode}</Heading>
<Box style={{ marginTop: '4rem' }}>
<h1
style={{
fontFamily: 'var(--docs-font)',
fontSize: '3rem',
fontWeight: 'var(--canon-font-weight-bold)',
margin: 0,
}}
>
{children as ReactNode}
</h1>
</Box>
),
h2: ({ children }) => (
<Box marginTop="2xl" marginBottom="md">
<Heading variant="title4">{children as ReactNode}</Heading>
<h2
style={{
fontFamily: 'var(--docs-font)',
fontSize: '1.5rem',
fontWeight: 'var(--canon-font-weight-bold)',
}}
>
{children as ReactNode}
</h2>
</Box>
),
h3: ({ children }) => (
<Box marginTop="xl" marginBottom="xs">
<Heading variant="title5">{children as ReactNode}</Heading>
<h3
style={{
fontFamily: 'var(--docs-font)',
fontSize: '1.25rem',
fontWeight: 'var(--canon-font-weight-bold)',
}}
>
{children as ReactNode}
</h3>
</Box>
),
p: ({ children }) => (
<Box marginBottom="sm">
<Text variant="subtitle">{children as ReactNode}</Text>
<p
style={{
fontFamily: 'var(--docs-font)',
fontSize: '1rem',
lineHeight: '1.5',
}}
>
{children as ReactNode}
</p>
</Box>
),
a: ({ children, href }) => (
@@ -42,6 +73,21 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
return <CodeBlock lang="tsx" code={codeContent} />;
},
code: ({ children }) => (
<code
style={{
fontFamily: 'var(--canon-font-monospace)',
backgroundColor: 'var(--canon-surface-1)',
padding: '0.2rem 0.375rem',
borderRadius: '0.25rem',
color: 'var(--canon-text-secondary)',
border: '1px solid var(--canon-border-base)',
fontSize: '0.875rem',
}}
>
{children}
</code>
),
img: props => (
<Image
sizes="100vw"