Move canon-docs to docs-ui
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals", "next/typescript"],
|
||||
"rules": {
|
||||
"notice/notice": "off",
|
||||
"react/forbid-elements": "off",
|
||||
"jsx-a11y/alt-text": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# next.js
|
||||
/.next/
|
||||
/dist/
|
||||
next-env.d.ts
|
||||
|
||||
# css
|
||||
/public/core.css
|
||||
/public/components.css
|
||||
/public/backstage.css
|
||||
@@ -0,0 +1,15 @@
|
||||
# Canon Docs
|
||||
|
||||
Canon is our internal UI library built for Backstage. We built this website to document the library and its components. You can view this website [here](https://canon.backstage.io).
|
||||
|
||||
## How to run locally
|
||||
|
||||
This website is built with Next.js and it is hosted on Github pages. To run it locally, you can run the following command:
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
Deployments are done automatically when a PR is merged into the `master` branch. We host the website using Github pages.
|
||||
@@ -0,0 +1,20 @@
|
||||
import createMDX from '@next/mdx';
|
||||
|
||||
const nextConfig = {
|
||||
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
|
||||
output: 'export',
|
||||
assetPrefix: '/',
|
||||
distDir: 'dist',
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
typescript: {
|
||||
// Ignore TypeScript errors during build - safe for React 18/19 compatibility issues
|
||||
// These are type-level conflicts that don't affect runtime behavior
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
};
|
||||
|
||||
const withMDX = createMDX({});
|
||||
|
||||
export default withMDX(nextConfig);
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "canon-docs",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"prebuild": "yarn sync:css",
|
||||
"build": "next build",
|
||||
"lint": "next lint",
|
||||
"prestart": "yarn sync:css",
|
||||
"start": "concurrently \"yarn sync:css:watch\" \"next dev\"",
|
||||
"sync:css": "node scripts/sync-css.js",
|
||||
"sync:css:watch": "node scripts/sync-css.js --watch"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-sass": "^6.0.2",
|
||||
"@codemirror/view": "^6.34.4",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@mdx-js/loader": "^3.1.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@next/mdx": "15.3.4",
|
||||
"@storybook/react": "^8.6.8",
|
||||
"@uiw/codemirror-themes": "^4.23.7",
|
||||
"@uiw/react-codemirror": "^4.23.7",
|
||||
"html-react-parser": "^5.2.5",
|
||||
"motion": "^12.4.1",
|
||||
"next": "15.3.4",
|
||||
"next-mdx-remote-client": "^2.1.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-frame-component": "^5.2.7",
|
||||
"shiki": "^1.26.1",
|
||||
"storybook": "^8.6.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"chokidar": "^3.6.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.3.4",
|
||||
"lightningcss": "^1.28.2",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ui.backstage.io
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,149 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { bundle } = require('lightningcss');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
// Configuration
|
||||
const config = {
|
||||
UIPath: '../../packages/ui',
|
||||
publicPath: '../public',
|
||||
files: [
|
||||
{
|
||||
source: 'css/styles.css',
|
||||
destination: 'theme-backstage.css',
|
||||
name: 'Main Styles',
|
||||
},
|
||||
{
|
||||
source: '.storybook/themes/spotify.css',
|
||||
destination: 'theme-spotify.css',
|
||||
name: 'Spotify Theme',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
class CSSSync {
|
||||
constructor() {
|
||||
this.UIPath = path.resolve(__dirname, config.UIPath);
|
||||
this.publicPath = path.resolve(__dirname, config.publicPath);
|
||||
this.isWatching = process.argv.includes('--watch');
|
||||
}
|
||||
|
||||
async syncFile(fileConfig) {
|
||||
const sourcePath = path.join(this.UIPath, fileConfig.source);
|
||||
const destPath = path.join(this.publicPath, fileConfig.destination);
|
||||
|
||||
try {
|
||||
// Check if source file exists
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
console.warn(`⚠️ Source file not found: ${sourcePath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
||||
|
||||
// Bundle and optimize CSS
|
||||
const result = await bundle({
|
||||
filename: sourcePath,
|
||||
minify: true,
|
||||
});
|
||||
|
||||
// Write to destination
|
||||
fs.writeFileSync(destPath, result.code);
|
||||
|
||||
console.log(
|
||||
`✅ ${fileConfig.name}: ${fileConfig.source} → ${fileConfig.destination}`,
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ Error syncing ${fileConfig.name}:`, error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async syncAll() {
|
||||
console.log('🔄 Syncing CSS files...\n');
|
||||
|
||||
let successCount = 0;
|
||||
for (const fileConfig of config.files) {
|
||||
if (await this.syncFile(fileConfig)) {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`\n✨ Synced ${successCount}/${config.files.length} CSS files successfully!`,
|
||||
);
|
||||
|
||||
if (successCount > 0) {
|
||||
console.log('\n📁 Available CSS files in public/:');
|
||||
config.files.forEach(file => {
|
||||
const destPath = path.join(this.publicPath, file.destination);
|
||||
if (fs.existsSync(destPath)) {
|
||||
const stats = fs.statSync(destPath);
|
||||
const size = (stats.size / 1024).toFixed(2);
|
||||
console.log(` • ${file.destination} (${size} KB)`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
startWatching() {
|
||||
console.log('👀 Watching for CSS changes...\n');
|
||||
|
||||
// Watch all source files
|
||||
const watchPaths = config.files.map(file =>
|
||||
path.join(this.UIPath, file.source),
|
||||
);
|
||||
|
||||
const watcher = chokidar.watch(watchPaths, {
|
||||
ignored: /node_modules/,
|
||||
persistent: true,
|
||||
});
|
||||
|
||||
watcher.on('change', async filePath => {
|
||||
console.log(
|
||||
`\n🔄 Change detected: ${path.relative(this.UIPath, filePath)}`,
|
||||
);
|
||||
|
||||
// Find which file changed and sync it
|
||||
const fileConfig = config.files.find(file =>
|
||||
filePath.endsWith(file.source.replace(/\//g, path.sep)),
|
||||
);
|
||||
|
||||
if (fileConfig) {
|
||||
await this.syncFile(fileConfig);
|
||||
}
|
||||
});
|
||||
|
||||
watcher.on('error', error => console.error('❌ Watch error:', error));
|
||||
|
||||
// Handle process termination
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n👋 Stopping CSS sync...');
|
||||
watcher.close();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
async run() {
|
||||
console.log('🎨 BUI CSS Sync Tool\n');
|
||||
console.log(`📂 BUI path: ${this.UIPath}`);
|
||||
console.log(`📂 Public path: ${this.publicPath}\n`);
|
||||
|
||||
// Initial sync
|
||||
await this.syncAll();
|
||||
|
||||
// Watch for changes if requested
|
||||
if (this.isWatching) {
|
||||
this.startWatching();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the sync tool
|
||||
const cssSync = new CSSSync();
|
||||
cssSync.run().catch(error => {
|
||||
console.error('❌ CSS Sync failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
# About Canon
|
||||
|
||||
Canon is a design system created specifically for Backstage, built with React, TypeScript, and vanilla CSS.
|
||||
This open-source library is hosted in the Backstage monorepo. While it can be used in other projects, Canon
|
||||
is designed to deliver a consistent, accessible, and extensible experience tailored to Backstage users.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Backstage empowers product teams to build software faster and with greater quality. Its extensibility,
|
||||
however, required us to rethink how to deliver a consistent and accessible user experience. Our goal is
|
||||
to enable plugin creators to design plugins that seamlessly integrate with Backstage's look and feel while
|
||||
still allowing customization to match individual brands.
|
||||
|
||||
Instead of reinventing the wheel, we chose to focus on layout and styling while leveraging existing headless
|
||||
component libraries for functionality. This approach allows us to dedicate our efforts to creating a cohesive
|
||||
and flexible theming system.
|
||||
|
||||
## Team
|
||||
|
||||
Canon is designed and maintained primarily by Spotify's Backstage team, leveraging Spotify's expertise in
|
||||
crafting high-quality design and technology. Drawing from our experience in building reliable and intuitive
|
||||
user experiences for the music industry, we've created a design system that looks great and works seamlessly.
|
||||
|
||||
## Community
|
||||
|
||||
Canon is an open-source project and we welcome contributions from the community. If you are interested in
|
||||
contributing to Canon, please read our [contributing guide](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md)
|
||||
and our [code of conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
## License
|
||||
|
||||
Canon is licensed under the Apache 2.0 license. See the [LICENSE](https://github.com/backstage/backstage/blob/master/LICENSE) file for more details.
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Changelog } from '@/components/Changelog';
|
||||
|
||||
# Changelog
|
||||
|
||||
<Changelog />
|
||||
|
||||
## Version 0.1.0
|
||||
|
||||
We're excited to share the initial release of Canon 💚 In this first alpha version,
|
||||
you'll find the foundation of our design system: a set of versatile layout components
|
||||
and a handful of essential atomic elements to help you get started. While Canon is
|
||||
still in its early stages, it's ready for exploration and we'd love for you to give
|
||||
it a try and share your feedback.
|
||||
@@ -0,0 +1,25 @@
|
||||
import { components, layoutComponents } from '@/utils/data';
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
|
||||
const { default: Component } = await import(
|
||||
`@/content/components/${slug}.mdx`
|
||||
);
|
||||
|
||||
return <Component />;
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
const list = [...components, ...layoutComponents];
|
||||
|
||||
return list.map(component => ({
|
||||
slug: component.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export const dynamicParams = false;
|
||||
@@ -0,0 +1,6 @@
|
||||
.pageContainer {
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px 64px;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import styles from './layout.module.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Canon',
|
||||
description: 'UI library for Backstage',
|
||||
};
|
||||
|
||||
export default function DocsLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return <div className={styles.pageContainer}>{children}</div>;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
|
||||
<img
|
||||
src="header.png"
|
||||
style={{ width: '100%', marginBottom: '32px', marginTop: '64px' }}
|
||||
/>
|
||||
|
||||
Welcome to the Canon, the new design library for Backstage plugins. This
|
||||
project is still under active development but we will make sure to document
|
||||
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.
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Install canon
|
||||
|
||||
Install Canon using a package manager.
|
||||
|
||||
<CodeBlock lang="shell" code={`yarn add @backstage/canon`} />
|
||||
|
||||
### 2. Import the css files
|
||||
|
||||
Import the global CSS file at the root of your application.
|
||||
|
||||
```tsx
|
||||
import '@backstage/canon/css/styles.css';
|
||||
```
|
||||
|
||||
### 3. Start building ✨
|
||||
|
||||
Now you can start building your plugin using the new design system.
|
||||
|
||||
```tsx
|
||||
import { Flex, Button, Text } from '@backstage/canon';
|
||||
|
||||
<Flex>
|
||||
<Text>Hello World</Text>
|
||||
<Button>Click me</Button>
|
||||
</Flex>;
|
||||
```
|
||||
|
||||
## 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).
|
||||
@@ -0,0 +1,24 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { IconLibrary } from '@/components/IconLibrary';
|
||||
|
||||
# Iconography
|
||||
|
||||
All our default icons are provided by [Remix Icon](https://remixicon.com/). We
|
||||
don't import all icons to reduce the bundle size but we cherry pick a nice
|
||||
selection for you to use in your application. The list of names is set down
|
||||
below. To use an icon, you can use the `Icon` component and pass the name of
|
||||
the icon you want to use.
|
||||
|
||||
<CodeBlock code={`<Icon name="heart" />`} />
|
||||
|
||||
## Icon overrides
|
||||
|
||||
You can override any icons in our library by using the `IconProvider` at the root of your application.
|
||||
|
||||
<CodeBlock
|
||||
code={`<IconProvider overrides={{ heart: () => <div>Custom Icon</div> }} />`}
|
||||
/>
|
||||
|
||||
## Icon library
|
||||
|
||||
<IconLibrary />
|
||||
@@ -0,0 +1,42 @@
|
||||
import { LayoutComponents } from '@/components/LayoutComponents';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
|
||||
# Layout
|
||||
|
||||
Canon is made for extensibility. We built this library to make it easy for any
|
||||
Backstage plugin creator to be able to build their ideas at speed ensuring
|
||||
consistency across the rest of your ecosystem. Each component is designed to
|
||||
be editable to match your need but sometimes you want to have more control
|
||||
over the layout of your page. To help you with that, we created a set of
|
||||
layout components that you can use to build your own layouts. All of these
|
||||
components are built to extend on our theming system, making it easy for you
|
||||
to build your own layouts. Sometimes these components are not enough so we
|
||||
created a set of helpers to be used with any CSS-in-JS library.
|
||||
|
||||
## Layout Components
|
||||
|
||||
We built a couple of layout components to help you build responsive elements
|
||||
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.
|
||||
|
||||
<CodeBlock
|
||||
title="Layout components"
|
||||
code={`<Flex direction="column" gap="4">
|
||||
<Box>Hello World</Box>
|
||||
<Inline gap="sm">
|
||||
<Box>Project 1</Box>
|
||||
<Box>Project 2</Box>
|
||||
</Inline>
|
||||
</Flex>
|
||||
`}
|
||||
/>
|
||||
|
||||
<LayoutComponents />
|
||||
|
||||
## Layout Helpers
|
||||
|
||||
Sometimes you want to use global tokens dynamically outside of React
|
||||
components. To help you with that we would like to provide a set of helpers
|
||||
that you can use in your code. These helpers are not available just yet but we
|
||||
are working on it.
|
||||
@@ -0,0 +1,107 @@
|
||||
import * as Table from '@/components/Table';
|
||||
import { Chip } from '@/components/Chip';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
|
||||
# Responsive
|
||||
|
||||
Canon is built on a responsive design system, meaning that the components are
|
||||
designed to adapt to different screen sizes. By default we offer a set of
|
||||
breakpoints that you can use to create responsive components.
|
||||
|
||||
## Breakpoints
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Breakpoint prefix</Table.HeaderCell>
|
||||
<Table.HeaderCell>Minimum width</Table.HeaderCell>
|
||||
<Table.HeaderCell>CSS</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>xs</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>0px</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>{`{ ... }`}</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>sm</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>640px</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>{`@media (min-width: 640px) { ... }`}</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>md</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>768px</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>{`@media (min-width: 768px) { ... }`}</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>lg</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>1024px</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>{`@media (min-width: 1024px) { ... }`}</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>xl</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>1280px</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>{`@media (min-width: 1280px) { ... }`}</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>2xl</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>1536px</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>{`@media (min-width: 1536px) { ... }`}</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
## Responsive components
|
||||
|
||||
Canon components are designed to be responsive, meaning that they will adapt
|
||||
to different screen sizes. Not every component is responsive, but the ones
|
||||
that are will have a prop to control the responsive behavior.
|
||||
|
||||
The behaviour is the same for each component. For each prop, instead of adding
|
||||
the value, you add an object with the value and the breakpoint prefix.
|
||||
|
||||
<CodeBlock
|
||||
code={`// Fixed value
|
||||
|
||||
<Button size="small">Button</Button>
|
||||
|
||||
// Responsive value
|
||||
|
||||
<Button size={{ xs: 'small', md: 'medium' }}>Button</Button>`} />
|
||||
@@ -0,0 +1,693 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import * as Table from '@/components/Table';
|
||||
import { Chip } from '@/components/Chip';
|
||||
import { customTheme } from '@/snippets/code-snippets';
|
||||
|
||||
# Theming
|
||||
|
||||
Canon's theming is built entirely on CSS, without relying on any CSS-in-JS libraries.
|
||||
At its core, it provides a solid default theme that is easily customizable using a
|
||||
comprehensive set of CSS variables. Additionally, it enables anyone to adapt the design
|
||||
to their specific needs. Each component comes with fixed class names, making customization
|
||||
even more straightforward.
|
||||
|
||||
## Light & Dark modes
|
||||
|
||||
By default, Canon supports both light and dark modes using the `data-theme` attribute.
|
||||
The light theme is applied by default if no `data-theme` attribute is specified. To create
|
||||
a custom theme, you'll need to define both light and dark modes as outlined below. If
|
||||
only one mode is defined, the other will fall back to the default theme.
|
||||
|
||||
## How to create your own theme
|
||||
|
||||
In our [started guide](/), we ask you to import two css files. The `core.css` file includes
|
||||
the default set of variables. We recommend to keep this file in place and add your own theme
|
||||
on top of it. `core.css` also include an opinionated reset. If you decided to remove `core.css`
|
||||
you will have to provide your own reset css.
|
||||
|
||||
Here's an example of how your theme.css file should look like:
|
||||
|
||||
<CodeBlock lang="css" code={customTheme} />
|
||||
|
||||
## CSS class name structure
|
||||
|
||||
All Canon components come with a set of CSS classes that you can use to style them. To make it
|
||||
easier to identify the class name you can use, we use a specific structure for the class names.
|
||||
|
||||
<img
|
||||
src="/css-classname-structure.png"
|
||||
style={{ width: '100%', marginBottom: '32px', marginTop: '16px' }}
|
||||
/>
|
||||
|
||||
Every component has a unique prefix `.canon-` followed by the component name. Component props
|
||||
are represented using the `data-` attribute. That way, class names are easily identifiable.
|
||||
|
||||
## Available CSS variables
|
||||
|
||||
### Base colors
|
||||
|
||||
These colors are used for special purposes like ring, scrollbar, ...
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-black</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
Pure black color. This one should be the same in light and dark themes.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-white</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
Pure white color. This one should be the same in light and dark themes.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-gray-1</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-gray-2</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-gray-3</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-gray-4</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-gray-5</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-gray-6</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-gray-7</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-gray-8</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
### Core background colors
|
||||
|
||||
These colors are used for the background of your application. We are mostly using for now a
|
||||
single elevated background for panels. `--canon-bg` should mostly use as the main background
|
||||
color of your app.
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>The background color of your Backstage instance.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-surface-1</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Use for any panels or elevated surfaces.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-surface-2</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Use for any panels or elevated surfaces.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-solid</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used for solid background colors.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-solid-hover</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used for solid background colors when hovered.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-solid-pressed</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used for solid background colors when pressed.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-solid-disabled</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used for solid background colors when disabled.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-tint</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used for tint background colors.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-tint-hover</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used for tint background colors when hovered.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-tint-focus</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used for tint background colors when active.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-tint-disabled</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used for tint background colors when disabled.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-danger</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used to show errors information.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-warning</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used to show warnings information.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-success</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used to show success information.</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
### Foreground colors
|
||||
|
||||
Foreground colours are meant to work in pair with a background colours. Typeically this would work
|
||||
for icons, texts, shapes, ... Use a matching name to know what foreground color to use. These colors
|
||||
are prefixed with `fg` to make it easier to identify.
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-primary</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of main background surfaces.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-secondary</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of main background surfaces.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-link</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of main background surfaces.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-link-hover</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of main background surfaces.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-disabled</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of main background surfaces.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-solid</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of solid background colors.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-tint</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of tint background colors.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-tint-disabled</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of tint background colors when disabled.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-danger</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of danger background colors.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-warning</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of warning background colors.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-success</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of success background colors.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
### Border colors
|
||||
|
||||
These border colors are mostly meant to be used as borders on top of any components with
|
||||
low contrast to help as a separator with the different background colors.
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-border</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of `--canon-bg-surface-1`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-border-hover</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
Used when the component is interactive and hovered.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-border-pressed</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
Used when the component is interactive and focused.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-border-disabled</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Used when the component is disabled.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-border-danger</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>It should be used on top of `--canon-bg-danger`.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-border-warning</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>It should be used on top of `--canon-bg-warning`.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-border-success</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>It should be used on top of `--canon-bg-success`.</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
### Special colors
|
||||
|
||||
These colors are used for special purposes like ring, scrollbar, ...
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-ring</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>The color of the ring.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-scrollbar</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>The color of the scrollbar.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-scrollbar-thumb</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>The color of the scrollbar thumb.</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
### Font families
|
||||
|
||||
We have two fonts that we use across Canon. The first one is the sans-serif
|
||||
font that we use for the body of the application. The second one is the
|
||||
monospace font that we use for code blocks and tables.
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-font-regular</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>The sans-serif font for the theme.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-font-mono</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>The monospace font for the theme.</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
### Font weights
|
||||
|
||||
We have two font weights that we use across Canon. Regular or Bold.
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-font-weight-regular</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>The regular font weight for the theme.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-font-weight-bold</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>The bold font weight for the theme.</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
### Spacing
|
||||
|
||||
We built a spacing system based on a single value `--canon-space`. This value is
|
||||
used to calculate the spacing for all the components. By default if you would like to
|
||||
increase or decrease the spacing between your components you can do it simply by updating
|
||||
`--canon-space` and it will apply to all spacing values.
|
||||
|
||||
`--canon-space` is not used directly in any components but serve as an easy way to
|
||||
calculate the other values.
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
The base unit for the spacing system. Default value is `0.25rem`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
Below is the list of all spacing values you can use in your application. We use these
|
||||
tokens for pretty much each spacing properties like padding, margin, gaps, ...
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-0_5</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 0.5.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-1</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`).</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-1_5</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 1.5.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-2</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 2.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-3</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 3.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-4</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 4.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-5</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 5.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-6</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 6.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-7</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 7.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-8</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 8.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-9</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 9.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-10</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 10.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-11</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 11.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-12</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 12.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-13</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 13.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-space-14</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Base unit (`--canon-space`) times 14.</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
### Radius
|
||||
|
||||
We use a radius system to make sure that the components have a consistent look and feel.
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-radius-1</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
The radius of the component. Default value is `0.125rem`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-radius-2</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
The radius of the component. Default value is `0.25rem`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-radius-3</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
The radius of the component. Default value is `0.5rem`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-radius-4</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
The radius of the component. Default value is `0.75rem`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-radius-5</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
The radius of the component. Default value is `1rem`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-radius-6</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
The radius of the component. Default value is `1.25rem`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-radius-full</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
The radius of the component. Default value is `9999px`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
@@ -0,0 +1,65 @@
|
||||
import { HeadingSnippet, TextSnippet } from '@/snippets/stories-snippets';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
|
||||
# Typography
|
||||
|
||||
Canon offers a suite of typography components designed to seamlessly align
|
||||
with the rest of your Backstage instance. While you can customize their
|
||||
appearance to match your brand, the underlying API remains consistent and
|
||||
unchanged. Each component is built on a responsive structure, allowing you to
|
||||
define different typography values for various breakpoints.
|
||||
|
||||
## Headings
|
||||
|
||||
Headings are used to structure the content of your page. They are used to
|
||||
create a hierarchy of information and to make the content more readable. The
|
||||
best way to use add these headings to your page is to import the [Heading
|
||||
component](?path=/docs/components-heading--docs).
|
||||
|
||||
<Snippet
|
||||
py={2}
|
||||
open
|
||||
preview={<HeadingSnippet story="AllVariants" />}
|
||||
code={`<Flex direction="column" gap="4">
|
||||
<Heading variant="display">Display</Heading>
|
||||
<Heading variant="title1">Title 1</Heading>
|
||||
<Heading variant="title2">Title 2</Heading>
|
||||
<Heading variant="title3">Title 3</Heading>
|
||||
<Heading variant="title4">Title 4</Heading>
|
||||
</Flex>`}
|
||||
/>
|
||||
|
||||
## Text
|
||||
|
||||
Canon provides four distinct text variants, each offering different font sizes
|
||||
carefully designed to cover the majority of use cases. These variants are
|
||||
versatile and can be paired with regular and bold of font weights. You can use
|
||||
the [Text component](?path=/docs/components-text--docs) to add text to your
|
||||
page.
|
||||
|
||||
<Snippet
|
||||
open
|
||||
preview={<TextSnippet story="AllVariants" />}
|
||||
code={`<Flex direction="column" gap="4">
|
||||
<Text variant="subtitle" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
have none, but that man's father is my father's son.” Who is
|
||||
in the painting?
|
||||
</Text>
|
||||
<Text variant="body" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
have none, but that man's father is my father's son.” Who is
|
||||
in the painting?
|
||||
</Text>
|
||||
<Text variant="caption" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
have none, but that man's father is my father's son.” Who is
|
||||
in the painting?
|
||||
</Text>
|
||||
<Text variant="label" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
have none, but that man's father is my father's son.” Who is
|
||||
in the painting?
|
||||
</Text>
|
||||
</Flex>`}
|
||||
/>
|
||||
@@ -0,0 +1,87 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { Grid, Flex, Text } from '../../../../../packages/canon';
|
||||
import { screenSizes } from '@/utils/data';
|
||||
import { Frame } from '@/components/Frame';
|
||||
import { usePlayground } from '@/utils/playground-context';
|
||||
import {
|
||||
ButtonSnippet,
|
||||
CheckboxSnippet,
|
||||
HeadingSnippet,
|
||||
TextSnippet,
|
||||
} from '@/snippets/stories-snippets';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export default function PlaygroundPage() {
|
||||
const { selectedScreenSizes } = usePlayground();
|
||||
|
||||
const filteredScreenSizes = screenSizes.filter(item =>
|
||||
selectedScreenSizes.includes(item.slug),
|
||||
);
|
||||
|
||||
if (filteredScreenSizes.length === 0) {
|
||||
return (
|
||||
<div className={styles.containerEmpty}>
|
||||
<Content />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{filteredScreenSizes.map(screenSize => (
|
||||
<div
|
||||
className={styles.breakpointContainer}
|
||||
style={{ width: screenSize.width }}
|
||||
key={screenSize.slug}
|
||||
>
|
||||
<Text>
|
||||
{screenSize.title} - {screenSize.width}px
|
||||
</Text>
|
||||
<div className={styles.breakpointContent}>
|
||||
<Frame>
|
||||
<Content />
|
||||
</Frame>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Content = () => {
|
||||
const { selectedComponents } = usePlayground();
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
{selectedComponents.find(c => c === 'button') && (
|
||||
<Line content={<ButtonSnippet story="Playground" />} title="Button" />
|
||||
)}
|
||||
{selectedComponents.find(c => c === 'checkbox') && (
|
||||
<Line
|
||||
content={<CheckboxSnippet story="Playground" />}
|
||||
title="Checkbox"
|
||||
/>
|
||||
)}
|
||||
{selectedComponents.find(c => c === 'heading') && (
|
||||
<Line content={<HeadingSnippet story="Playground" />} title="Heading" />
|
||||
)}
|
||||
{selectedComponents.find(c => c === 'text') && (
|
||||
<Line content={<TextSnippet story="Playground" />} title="Text" />
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const Line = ({ content, title }: { content: ReactNode; title: string }) => {
|
||||
return (
|
||||
<Grid.Root gap={{ xs: '2', md: '4' }}>
|
||||
<Grid.Item colSpan="2">
|
||||
<Text>{title}</Text>
|
||||
</Grid.Item>
|
||||
<Grid.Item colSpan="10">{content}</Grid.Item>
|
||||
</Grid.Root>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
.container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 24px;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.containerEmpty {
|
||||
padding: 78px 40px;
|
||||
}
|
||||
|
||||
.breakpointContainer {
|
||||
flex-shrink: 0;
|
||||
margin-top: 40px;
|
||||
height: calc(100vh - 86px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 48px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.breakpointContent {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--bg);
|
||||
padding: 16px;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="640" height="640" viewBox="0 0 640 640" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M370.766 153.765C377.563 166.716 371.236 182.114 359.357 190.647C323.005 216.758 299.326 259.411 299.326 307.597C299.326 365.997 334.107 416.27 384.077 438.817C395.235 443.852 403.478 454.368 403.478 466.609V640H382.864C224.433 640 96 511.517 96 353.025L96 20.6143C96 9.22934 105.229 0 116.614 0V0C226.982 3.14552e-06 322.792 62.3523 370.766 153.765Z" fill="black"/>
|
||||
<path d="M438.772 411.19C496.335 411.19 543 364.454 543 306.803C543 249.152 496.335 202.417 438.772 202.417C381.208 202.417 334.543 249.152 334.543 306.803C334.543 364.454 381.208 411.19 438.772 411.19Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 702 B |
@@ -0,0 +1,44 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Sidebar } from '../components/Sidebar';
|
||||
import { Toolbar } from '@/components/Toolbar';
|
||||
import { Providers } from './providers';
|
||||
import { CustomTheme } from '@/components/CustomTheme';
|
||||
import styles from '../css/page.module.css';
|
||||
|
||||
import '../css/globals.css';
|
||||
import '/public/theme-backstage.css';
|
||||
import '/public/theme-spotify.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Canon',
|
||||
description: 'UI library for Backstage',
|
||||
metadataBase: new URL('https://canon.backstage.io'),
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
data-theme="light"
|
||||
data-theme-name="default"
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<body>
|
||||
<Providers>
|
||||
<div className={styles.global}>
|
||||
<Sidebar />
|
||||
<div className={styles.container}>
|
||||
<Toolbar />
|
||||
{children}
|
||||
</div>
|
||||
<CustomTheme />
|
||||
</div>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 350 KiB |
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { PlaygroundProvider } from '@/utils/playground-context';
|
||||
|
||||
export const Providers = ({ children }: { children: ReactNode }) => {
|
||||
return <PlaygroundProvider>{children}</PlaygroundProvider>;
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export const Banner = ({
|
||||
variant = 'info',
|
||||
text,
|
||||
}: {
|
||||
variant?: 'info' | 'warning';
|
||||
text?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className={`${styles.banner} ${styles[variant]}`}>
|
||||
<div className={styles.icon}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
height="18"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M4.00001 20V14C4.00001 9.58172 7.58173 6 12 6C16.4183 6 20 9.58172 20 14V20H21V22H3.00001V20H4.00001ZM6.00001 20H18V14C18 10.6863 15.3137 8 12 8C8.6863 8 6.00001 10.6863 6.00001 14V20ZM11 2H13V5H11V2ZM19.7782 4.80761L21.1924 6.22183L19.0711 8.34315L17.6569 6.92893L19.7782 4.80761ZM2.80762 6.22183L4.22183 4.80761L6.34315 6.92893L4.92894 8.34315L2.80762 6.22183ZM7.00001 14C7.00001 11.2386 9.23858 9 12 9V11C10.3432 11 9.00001 12.3431 9.00001 14H7.00001Z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div className={styles.text}>{text}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Banner } from './Banner';
|
||||
@@ -0,0 +1,41 @@
|
||||
.banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
line-height: 28px;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #e0e0e0;
|
||||
|
||||
& > p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
background-color: #f2f2f2;
|
||||
border-color: #cdcdcd;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: #fff2b9;
|
||||
border-color: #ffd000;
|
||||
color: #d79927;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: rgba(215, 153, 39, 0.2);
|
||||
border-radius: 6px;
|
||||
margin-right: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.text {
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { changelog } from '@/utils/changelog';
|
||||
import { MDXRemote } from 'next-mdx-remote-client/rsc';
|
||||
import { formattedMDXComponents } from '@/mdx-components';
|
||||
|
||||
export function Changelog() {
|
||||
// Group changelog entries by version
|
||||
const groupedChangelog = changelog.reduce((acc, entry) => {
|
||||
if (!acc[entry.version]) {
|
||||
acc[entry.version] = [];
|
||||
}
|
||||
acc[entry.version].push(entry);
|
||||
return acc;
|
||||
}, {} as Record<string, typeof changelog>);
|
||||
|
||||
// Sort versions in descending order
|
||||
const sortedVersions = Object.keys(groupedChangelog).sort((a, b) =>
|
||||
b.localeCompare(a),
|
||||
);
|
||||
|
||||
const content = sortedVersions
|
||||
.map(version => {
|
||||
const entries = groupedChangelog[version];
|
||||
|
||||
return `## Version ${version}
|
||||
|
||||
${entries
|
||||
.map(e => {
|
||||
const prs =
|
||||
e.prs.length > 0 &&
|
||||
e.prs
|
||||
.map(
|
||||
pr =>
|
||||
`[#${pr}](https://github.com/backstage/backstage/pull/${pr})`,
|
||||
)
|
||||
.join(', ');
|
||||
return `- ${e.description} ${prs}`;
|
||||
})
|
||||
.join('\n')}`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return <MDXRemote components={formattedMDXComponents} source={content} />;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export const Chip = ({
|
||||
children,
|
||||
head = false,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
head?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<span className={`${styles.chip} ${head ? styles.head : ''}`}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Chip } from './Chip';
|
||||
@@ -0,0 +1,26 @@
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
border-radius: 6px;
|
||||
padding: 0px 8px;
|
||||
height: 24px;
|
||||
margin-right: 4px;
|
||||
background-color: #f0f0f0;
|
||||
color: #5d5d5d;
|
||||
}
|
||||
|
||||
.head {
|
||||
background-color: #eaf2fd;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .chip {
|
||||
background-color: #2c2c2c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .chip.head {
|
||||
background-color: #33405b;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import { CodeBlockProps } from '.';
|
||||
import { Text } from '@backstage/canon';
|
||||
import styles from './styles.module.css';
|
||||
import parse from 'html-react-parser';
|
||||
|
||||
export const CodeBlockClient = ({
|
||||
out,
|
||||
title,
|
||||
}: {
|
||||
out: string;
|
||||
title?: CodeBlockProps['title'];
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.codeBlock}>
|
||||
{title && (
|
||||
<div className={styles.title}>
|
||||
<Text variant="body">{title}</Text>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.code}>{parse(out)}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { BundledLanguage } from 'shiki';
|
||||
import { codeToHtml } from 'shiki';
|
||||
import { CodeBlockClient } from './client';
|
||||
|
||||
export interface CodeBlockProps {
|
||||
lang?: BundledLanguage;
|
||||
title?: string;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export async function CodeBlock({ lang = 'tsx', title, code }: CodeBlockProps) {
|
||||
const out = await codeToHtml(code || '', {
|
||||
lang: lang,
|
||||
themes: {
|
||||
light: 'min-light',
|
||||
dark: 'min-dark',
|
||||
},
|
||||
});
|
||||
|
||||
return <CodeBlockClient out={out} title={title} />;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
.codeBlock {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
position: relative;
|
||||
background: transparent;
|
||||
overflow-x: auto;
|
||||
font-family: var(--font-mono);
|
||||
background-color: #fff;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .codeBlock {
|
||||
background-color: #121212;
|
||||
}
|
||||
|
||||
.title {
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 12px 20px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.code {
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ReactNode, CSSProperties } from 'react';
|
||||
|
||||
export const Columns = ({
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
style?: CSSProperties;
|
||||
}) => {
|
||||
return (
|
||||
<div className="columns" style={style}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Columns } from './Columns';
|
||||
@@ -0,0 +1,6 @@
|
||||
.columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
row-gap: 20px;
|
||||
column-gap: 80px;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Tabs } from '@/components/Tabs';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import type { ComponentInfosProps } from './types';
|
||||
import styles from '@/css/mdx.module.css';
|
||||
import Link from 'next/link';
|
||||
import { changelog } from '@/utils/changelog';
|
||||
import { MDXRemote } from 'next-mdx-remote-client/rsc';
|
||||
import { formattedMDXComponents } from '@/mdx-components';
|
||||
|
||||
export const ComponentInfos = ({
|
||||
usageCode,
|
||||
component,
|
||||
classNames,
|
||||
}: ComponentInfosProps) => {
|
||||
const componentChangelog = changelog.filter(c =>
|
||||
c.components.includes(component),
|
||||
);
|
||||
|
||||
return (
|
||||
<Tabs.Root>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab>Usage</Tabs.Tab>
|
||||
{classNames && classNames.length > 0 && <Tabs.Tab>Theming</Tabs.Tab>}
|
||||
<Tabs.Tab>Changelog</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel>
|
||||
<CodeBlock code={usageCode} />
|
||||
</Tabs.Panel>
|
||||
{classNames && classNames.length > 0 && (
|
||||
<Tabs.Panel>
|
||||
<p className={styles.p}>
|
||||
We recommend starting with our{' '}
|
||||
<Link className={styles.a} href="/theme/theming">
|
||||
global tokens
|
||||
</Link>{' '}
|
||||
to customize the library and align it with your brand. For
|
||||
additional flexibility, you can use the provided class names for
|
||||
each element listed below.
|
||||
</p>
|
||||
<MDXRemote
|
||||
components={formattedMDXComponents}
|
||||
source={`${classNames
|
||||
?.map(className => `- \`${className}\``)
|
||||
.join('\n')}`}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
)}
|
||||
<Tabs.Panel>
|
||||
<MDXRemote
|
||||
components={formattedMDXComponents}
|
||||
source={`${componentChangelog
|
||||
?.map(change => {
|
||||
const prs =
|
||||
change.prs.length > 0 &&
|
||||
change.prs
|
||||
.map(
|
||||
pr =>
|
||||
`[#${pr}](https://github.com/backstage/backstage/pull/${pr})`,
|
||||
)
|
||||
.join(', ');
|
||||
return `- \`${change.version}\` - ${change.description} ${prs}`;
|
||||
})
|
||||
.join('\n')}`}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
</Tabs.Root>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { ComponentInfos } from './ComponentInfos';
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Component } from '@/utils/changelog';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface ComponentInfosProps {
|
||||
usageCode?: string;
|
||||
classNames?: string[];
|
||||
component: Component;
|
||||
children: ReactNode;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { sass } from '@codemirror/lang-sass';
|
||||
import styles from './styles.module.css';
|
||||
import { usePlayground } from '@/utils/playground-context';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import { Icon } from '../../../../packages/canon';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
|
||||
const defaultTheme = `:root {
|
||||
--canon-bg-solid: #000;
|
||||
}`;
|
||||
|
||||
const myTheme = createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: 'var(--surface-1)',
|
||||
backgroundImage: '',
|
||||
foreground: '#6182B8',
|
||||
caret: '#5d00ff',
|
||||
selection: '#036dd626',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#8a91991a',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#8a919966',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.comment, color: '#787b8099' },
|
||||
{ tag: t.variableName, color: '#0080ff' },
|
||||
{ tag: [t.string, t.special(t.brace)], color: '#6182B8' },
|
||||
{ tag: t.number, color: '#6182B8' },
|
||||
{ tag: t.bool, color: '#6182B8' },
|
||||
{ tag: t.null, color: '#6182B8' },
|
||||
{ tag: t.keyword, color: '#6182B8' },
|
||||
{ tag: t.operator, color: '#6182B8' },
|
||||
{ tag: t.className, color: '#6182B8' },
|
||||
{ tag: t.definition(t.typeName), color: '#6182B8' },
|
||||
{ tag: t.typeName, color: '#6182B8' },
|
||||
{ tag: t.angleBracket, color: '#6182B8' },
|
||||
{ tag: t.tagName, color: '#6182B8' },
|
||||
{ tag: t.attributeName, color: '#6182B8' },
|
||||
],
|
||||
});
|
||||
|
||||
export const CustomTheme = () => {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
const [open, setOpen] = useState(true);
|
||||
const [customTheme, setCustomTheme] = useState<string | undefined>(undefined);
|
||||
const { selectedThemeName } = usePlayground();
|
||||
const [savedMessage, setSavedMessage] = useState<string>('Save');
|
||||
|
||||
const updateStyleElement = (theme: string) => {
|
||||
let styleElement = document.getElementById(
|
||||
'custom-theme-style',
|
||||
) as HTMLStyleElement;
|
||||
|
||||
if (!styleElement) {
|
||||
styleElement = document.createElement('style');
|
||||
styleElement.id = 'custom-theme-style';
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
||||
|
||||
styleElement.textContent = theme;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedThemeName === 'custom') {
|
||||
let storedTheme = localStorage.getItem('customThemeCss');
|
||||
if (!storedTheme) {
|
||||
storedTheme = defaultTheme;
|
||||
localStorage.setItem('customThemeCss', storedTheme);
|
||||
}
|
||||
setCustomTheme(storedTheme);
|
||||
updateStyleElement(storedTheme);
|
||||
} else {
|
||||
const styleElement = document.getElementById(
|
||||
'custom-theme-style',
|
||||
) as HTMLStyleElement;
|
||||
if (styleElement) {
|
||||
styleElement.remove();
|
||||
}
|
||||
}
|
||||
}, [selectedThemeName]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
const handleSave = () => {
|
||||
if (customTheme) {
|
||||
localStorage.setItem('customThemeCss', customTheme);
|
||||
updateStyleElement(customTheme);
|
||||
setSavedMessage('Saved!');
|
||||
setTimeout(() => setSavedMessage('Save'), 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = useCallback((val: string) => {
|
||||
setCustomTheme(val);
|
||||
}, []);
|
||||
|
||||
if (isClient === false) return null;
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{selectedThemeName === 'custom' && (
|
||||
<motion.div
|
||||
className={`${styles.container} ${open ? styles.open : ''}`}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 10 }}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.headerLeft}>Custom Theme</div>
|
||||
<div className={styles.headerRight}>
|
||||
{open && (
|
||||
<button className={styles.buttonSave} onClick={handleSave}>
|
||||
{savedMessage}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className={styles.buttonClose}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<Icon name={open ? 'chevron-down' : 'chevron-up'} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.editorContainer}>
|
||||
<CodeMirror
|
||||
value={customTheme}
|
||||
height="300px"
|
||||
extensions={[sass()]}
|
||||
onChange={handleChange}
|
||||
className={styles.editor}
|
||||
basicSetup={{ foldGutter: false }}
|
||||
theme={myTheme}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { CustomTheme } from './customTheme';
|
||||
@@ -0,0 +1,77 @@
|
||||
.container {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
width: 240px;
|
||||
height: 47px;
|
||||
background-color: var(--panel);
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transition-property: background-color, border-color, height, width;
|
||||
transition-duration: 0.2s;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.open {
|
||||
width: 36%;
|
||||
height: 348px;
|
||||
}
|
||||
|
||||
.editor {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.editorContainer {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 46px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background-color: var(--panel);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 12px 0 16px;
|
||||
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.headerLeft {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.headerRight {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.buttonSave {
|
||||
all: unset;
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.buttonClose {
|
||||
all: unset;
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
color: #fff;
|
||||
background-color: var(--bg);
|
||||
color: var(--primary);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export const DecorativeBox = () => {
|
||||
return <div className={styles.box} />;
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
.box {
|
||||
min-width: 64px;
|
||||
min-height: 64px;
|
||||
background-color: #eaf2fd;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 0 1px #2563eb;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%232563eb' fill-opacity='0.3' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import ReactFrame from 'react-frame-component';
|
||||
|
||||
export const Frame = ({ children }: { children: React.ReactNode }) => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setShow(true);
|
||||
}, []);
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<ReactFrame
|
||||
loading="lazy"
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
initialContent={`<!DOCTYPE html><html data-theme="light"><head></head><body><div class="frame-root"></div></body></html>`}
|
||||
mountTarget=".frame-root"
|
||||
head={
|
||||
<>
|
||||
<link rel="stylesheet" href="/core.css" />
|
||||
<link rel="stylesheet" href="/components.css" />
|
||||
<link rel="stylesheet" href="/backstage.css" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</ReactFrame>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { Icon, Text } from '../../../../packages/canon';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
interface BaseUIProps {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export const BaseUI = ({ href }: BaseUIProps) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<svg
|
||||
width="17"
|
||||
height="30"
|
||||
viewBox="0 0 17 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={styles.icon}
|
||||
>
|
||||
<path d="M8 12.8V15V26C3.58172 26 0 22.0601 0 17.2V15V4C4.41828 4 8 7.93989 8 12.8Z" />
|
||||
<path d="M9.5001 10.0154C9.2245 9.99843 9 10.2239 9 10.5V26.0001C13.4183 26.0001 17 22.4184 17 18.0001C17 13.7498 13.6854 10.2736 9.5001 10.0154Z" />
|
||||
</svg>
|
||||
<div className={styles.content}>
|
||||
<Text variant="subtitle" weight="bold">
|
||||
Base UI
|
||||
</Text>
|
||||
<div className={styles.description}>
|
||||
<Text variant="subtitle">
|
||||
This component is using Base UI under the hood. While most of the
|
||||
original props are available, we have made some changes to the API
|
||||
to fit our needs.
|
||||
</Text>
|
||||
</div>
|
||||
{href && (
|
||||
<a className={styles.button} href={href} target="_blank">
|
||||
Discover more
|
||||
<Icon name="external-link" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
.container {
|
||||
display: flex;
|
||||
background-color: var(--panel);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid var(--border);
|
||||
margin-bottom: 1.5rem;
|
||||
gap: 1.5rem;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.icon path {
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.description {
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.button {
|
||||
all: unset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
background-color: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.25rem;
|
||||
padding: 0 0.75rem;
|
||||
height: 28px;
|
||||
border-radius: 100px;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--primary);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import { Text, Icon, icons } from '../../../../packages/canon';
|
||||
import type { IconNames } from '../../../../packages/canon';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export const IconLibrary = () => {
|
||||
const list = Object.keys(icons);
|
||||
|
||||
return (
|
||||
<div className={styles.library}>
|
||||
{list.map(icon => (
|
||||
<div key={icon} className={styles.item}>
|
||||
<div className={styles.icon}>
|
||||
<Icon name={icon as IconNames} />
|
||||
</div>
|
||||
<Text variant="body">{icon}</Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { IconLibrary } from './IconLibrary';
|
||||
@@ -0,0 +1,28 @@
|
||||
.library {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 80px;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--panel);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
.layoutComponents {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 2rem;
|
||||
|
||||
& svg rect {
|
||||
transition: fill 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
& .box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(50% - 0.5rem);
|
||||
margin-bottom: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
& .content {
|
||||
flex: none;
|
||||
background-color: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
}
|
||||
|
||||
& .title {
|
||||
font-size: 16px;
|
||||
transition: color 0.2s ease-in-out;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
& .description {
|
||||
font-size: 16px;
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { BoxSvg } from './svgs/box';
|
||||
import { FlexSvg } from './svgs/flex';
|
||||
import { GridSvg } from './svgs/grid';
|
||||
import { ContainerSvg } from './svgs/container';
|
||||
import styles from './LayoutComponents.module.css';
|
||||
import Link from 'next/link';
|
||||
|
||||
export const LayoutComponents = () => {
|
||||
return (
|
||||
<div className={styles.layoutComponents}>
|
||||
<div className={styles.box}>
|
||||
<Link className={styles.content} href="/components/box">
|
||||
<BoxSvg />
|
||||
</Link>
|
||||
<div className={styles.title}>Box</div>
|
||||
<div className={styles.description}>
|
||||
The most basic layout component
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.box}>
|
||||
<Link className={styles.content} href="/components/flex">
|
||||
<FlexSvg />
|
||||
</Link>
|
||||
<div className={styles.title}>Flex</div>
|
||||
<div className={styles.description}>
|
||||
Arrange your components vertically
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.box}>
|
||||
<Link className={styles.content} href="/components/grid">
|
||||
<GridSvg />
|
||||
</Link>
|
||||
<div className={styles.title}>Grid</div>
|
||||
<div className={styles.description}>
|
||||
Arrange your components in a grid
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.box}>
|
||||
<Link className={styles.content} href="/components/container">
|
||||
<ContainerSvg />
|
||||
</Link>
|
||||
<div className={styles.title}>Container</div>
|
||||
<div className={styles.description}>
|
||||
A container for your components
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { LayoutComponents } from './LayoutComponents';
|
||||
@@ -0,0 +1,37 @@
|
||||
export const BoxSvg = () => {
|
||||
return (
|
||||
<svg
|
||||
width="100"
|
||||
height="60"
|
||||
viewBox="0 0 100 60"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="100" height="60" rx="4" fill="var(--surface-1)" />
|
||||
<path
|
||||
d="M94.5 0.5H96C97.933 0.5 99.5 2.067 99.5 4V5.5"
|
||||
stroke="#4765FF"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M99.5 54.5L99.5 56C99.5 57.933 97.933 59.5 96 59.5L94.5 59.5"
|
||||
stroke="#4765FF"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.5 59.5L4 59.5C2.067 59.5 0.5 57.933 0.5 56L0.5 54.5"
|
||||
stroke="#4765FF"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M0.5 5.5L0.5 4C0.5 2.067 2.067 0.5 4 0.5L5.5 0.500001"
|
||||
stroke="#4765FF"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,354 @@
|
||||
export const ContainerSvg = () => {
|
||||
return (
|
||||
<svg
|
||||
width="126"
|
||||
height="111"
|
||||
viewBox="0 0 126 111"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<mask
|
||||
id="mask0_1922_1559"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="13"
|
||||
height="111"
|
||||
>
|
||||
<rect width="13" height="111" fill="url(#paint0_linear_1922_1559)" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_1922_1559)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 44.7563L13.0259 51.6995L12.5259 52.5655L0.5 45.6224L1 44.7563Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 50.5655L13.0259 57.5087L12.5259 58.3747L0.5 51.4316L1 50.5655Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 56.3747L13.0259 63.3179L12.5259 64.1839L0.5 57.2408L1 56.3747Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 62.1839L13.0259 69.1271L12.5259 69.9931L0.5 63.05L1 62.1839Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 67.9931L13.0259 74.9363L12.5259 75.8023L0.5 68.8592L1 67.9931Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 73.8023L13.0259 80.7455L12.5259 81.6116L0.5 74.6684L1 73.8023Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 79.6115L13.0259 86.5547L12.5259 87.4208L0.5 80.4776L1 79.6115Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 85.4208L13.0259 92.3639L12.5259 93.23L0.5 86.2868L1 85.4208Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 91.23L13.0259 98.1731L12.5259 99.0392L0.5 92.096L1 91.23Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 97.0392L13.0259 103.982L12.5259 104.848L0.5 97.9052L1 97.0392Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 102.848L13.0259 109.792L12.5259 110.658L0.5 103.714L1 102.848Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 108.658L13.0259 115.601L12.5259 116.467L0.5 109.524L1 108.658Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 38.9471L13.0259 45.8903L12.5259 46.7563L0.5 39.8131L1 38.9471Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 33.1379L13.0259 40.0811L12.5259 40.9471L0.5 34.0039L1 33.1379Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 27.3287L13.0259 34.2719L12.5259 35.1379L0.5 28.1947L1 27.3287Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 21.5195L13.0259 28.4627L12.5259 29.3287L0.5 22.3855L1 21.5195Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 15.7103L13.0259 22.6535L12.5259 23.5195L0.5 16.5763L1 15.7103Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 9.90109L13.0259 16.8443L12.5259 17.7103L0.5 10.7671L1 9.90109Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 4.09188L13.0259 11.0351L12.5259 11.9011L0.5 4.95791L1 4.09188Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 -1.71732L13.0259 5.22586L12.5259 6.09188L0.5 -0.851298L1 -1.71732Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 -7.52653L13.0259 -0.583351L12.5259 0.282675L0.5 -6.66051L1 -7.52653Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0 131L0 -17H1L1 131H0Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 131L12 -17H13L13 131H12Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="13"
|
||||
y="25.5"
|
||||
width="100"
|
||||
height="60"
|
||||
rx="4"
|
||||
fill="var(--surface-1)"
|
||||
/>
|
||||
<mask
|
||||
id="mask1_1922_1559"
|
||||
// style="mask-type:alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="113"
|
||||
y="0"
|
||||
width="13"
|
||||
height="111"
|
||||
>
|
||||
<rect
|
||||
x="113"
|
||||
width="13"
|
||||
height="111"
|
||||
fill="url(#paint1_linear_1922_1559)"
|
||||
/>
|
||||
</mask>
|
||||
<g mask="url(#mask1_1922_1559)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 44.7563L126.026 51.6995L125.526 52.5655L113.5 45.6224L114 44.7563Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 50.5655L126.026 57.5087L125.526 58.3747L113.5 51.4316L114 50.5655Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 56.3747L126.026 63.3179L125.526 64.1839L113.5 57.2408L114 56.3747Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 62.1839L126.026 69.1271L125.526 69.9931L113.5 63.05L114 62.1839Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 67.9931L126.026 74.9363L125.526 75.8023L113.5 68.8592L114 67.9931Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 73.8023L126.026 80.7455L125.526 81.6116L113.5 74.6684L114 73.8023Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 79.6115L126.026 86.5547L125.526 87.4208L113.5 80.4776L114 79.6115Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 85.4208L126.026 92.3639L125.526 93.23L113.5 86.2868L114 85.4208Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 91.23L126.026 98.1731L125.526 99.0392L113.5 92.096L114 91.23Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 97.0392L126.026 103.982L125.526 104.848L113.5 97.9052L114 97.0392Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 102.848L126.026 109.792L125.526 110.658L113.5 103.714L114 102.848Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 108.658L126.026 115.601L125.526 116.467L113.5 109.524L114 108.658Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 38.9471L126.026 45.8903L125.526 46.7563L113.5 39.8131L114 38.9471Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 33.1379L126.026 40.0811L125.526 40.9471L113.5 34.0039L114 33.1379Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 27.3287L126.026 34.2719L125.526 35.1379L113.5 28.1947L114 27.3287Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 21.5195L126.026 28.4627L125.526 29.3287L113.5 22.3855L114 21.5195Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 15.7103L126.026 22.6535L125.526 23.5195L113.5 16.5763L114 15.7103Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 9.90109L126.026 16.8443L125.526 17.7103L113.5 10.7671L114 9.90109Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 4.09188L126.026 11.0351L125.526 11.9011L113.5 4.95791L114 4.09188Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 -1.71732L126.026 5.22586L125.526 6.09188L113.5 -0.851298L114 -1.71732Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M114 -7.52653L126.026 -0.583351L125.526 0.282675L113.5 -6.66051L114 -7.52653Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M113 131L113 -17H114L114 131H113Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M125 131L125 -17H126L126 131H125Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_1922_1559"
|
||||
x1="6.5"
|
||||
y1="0"
|
||||
x2="6.5"
|
||||
y2="111"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.05" stopColor="white" stopOpacity="0" />
|
||||
<stop offset="0.2" stopColor="white" />
|
||||
<stop offset="0.8" stopColor="white" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_1922_1559"
|
||||
x1="119.5"
|
||||
y1="0"
|
||||
x2="119.5"
|
||||
y2="111"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.05" stopColor="white" stopOpacity="0" />
|
||||
<stop offset="0.2" stopColor="white" />
|
||||
<stop offset="0.8" stopColor="white" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
export const FlexSvg = () => {
|
||||
return (
|
||||
<svg
|
||||
width="100"
|
||||
height="96"
|
||||
viewBox="0 0 100 96"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="100" height="24" rx="4" fill="var(--surface-1)" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M49.5 36L49.5 25L50.5 25L50.5 36L49.5 36Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M44 25C43.7239 25 43.5 24.7761 43.5 24.5V24.5C43.5 24.2239 43.7239 24 44 24L56 24C56.2761 24 56.5 24.2239 56.5 24.5V24.5C56.5 24.7761 56.2761 25 56 25L44 25Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M44 36C43.7239 36 43.5 35.7761 43.5 35.5V35.5C43.5 35.2239 43.7239 35 44 35L56 35C56.2761 35 56.5 35.2239 56.5 35.5V35.5C56.5 35.7761 56.2761 36 56 36L44 36Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<rect y="36" width="100" height="24" rx="4" fill="var(--surface-1)" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M49.5 72L49.5 61L50.5 61L50.5 72L49.5 72Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M44 61C43.7239 61 43.5 60.7761 43.5 60.5V60.5C43.5 60.2239 43.7239 60 44 60L56 60C56.2761 60 56.5 60.2239 56.5 60.5V60.5C56.5 60.7761 56.2761 61 56 61L44 61Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M44 72C43.7239 72 43.5 71.7761 43.5 71.5V71.5C43.5 71.2239 43.7239 71 44 71L56 71C56.2761 71 56.5 71.2239 56.5 71.5V71.5C56.5 71.7761 56.2761 72 56 72L44 72Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<rect y="72" width="100" height="24" rx="4" fill="var(--surface-1)" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
export const GridSvg = () => {
|
||||
return (
|
||||
<svg
|
||||
width="166"
|
||||
height="61"
|
||||
viewBox="0 0 166 61"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="0.5" width="52" height="61" rx="4" fill="var(--surface-1)" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M53.5 30L64.5 30V31L53.5 31L53.5 30Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M64.5 24.5C64.5 24.2239 64.7239 24 65 24C65.2761 24 65.5 24.2239 65.5 24.5L65.5 36.5C65.5 36.7761 65.2761 37 65 37C64.7239 37 64.5 36.7761 64.5 36.5L64.5 24.5Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M52.5 24.5C52.5 24.2239 52.7239 24 53 24C53.2761 24 53.5 24.2239 53.5 24.5L53.5 36.5C53.5 36.7761 53.2761 37 53 37C52.7239 37 52.5 36.7761 52.5 36.5L52.5 24.5Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<rect
|
||||
x="65.5"
|
||||
y="0.5"
|
||||
width="100"
|
||||
height="24"
|
||||
rx="4"
|
||||
fill="var(--surface-1)"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M115 36.5L115 25.5L116 25.5L116 36.5L115 36.5Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M109.5 25.5C109.224 25.5 109 25.2761 109 25V25C109 24.7239 109.224 24.5 109.5 24.5L121.5 24.5C121.776 24.5 122 24.7239 122 25V25C122 25.2761 121.776 25.5 121.5 25.5L109.5 25.5Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M109.5 36.5C109.224 36.5 109 36.2761 109 36V36C109 35.7239 109.224 35.5 109.5 35.5L121.5 35.5C121.776 35.5 122 35.7239 122 36V36C122 36.2761 121.776 36.5 121.5 36.5L109.5 36.5Z"
|
||||
fill="#4765FF"
|
||||
/>
|
||||
<rect
|
||||
x="65.5"
|
||||
y="36.5"
|
||||
width="100"
|
||||
height="24"
|
||||
rx="4"
|
||||
fill="var(--surface-1)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
'use client';
|
||||
|
||||
import * as Table from '../Table';
|
||||
import { Chip } from '../Chip';
|
||||
import { icons } from '../../../../packages/canon';
|
||||
|
||||
// Define a more specific type for the data object
|
||||
type PropData = {
|
||||
values?: string | string[];
|
||||
responsive?: boolean;
|
||||
default?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
// Modify the PropsTable component to use the new type
|
||||
export const PropsTable = <T extends Record<string, PropData>>({
|
||||
data,
|
||||
}: {
|
||||
data: T;
|
||||
}) => {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell style={{ width: '16%' }}>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell style={{ width: '50%' }}>Type</Table.HeaderCell>
|
||||
<Table.HeaderCell style={{ width: '20%' }}>Default</Table.HeaderCell>
|
||||
<Table.HeaderCell style={{ width: '14%' }}>
|
||||
Responsive
|
||||
</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{Object.keys(data).map(n => {
|
||||
const enumValues =
|
||||
data[n].values === 'icon'
|
||||
? Object.keys(icons).map(icon => <Chip key={icon}>{icon}</Chip>)
|
||||
: Array.isArray(data[n].values) &&
|
||||
data[n].values.map(t => <Chip key={t}>{t}</Chip>);
|
||||
|
||||
return (
|
||||
<Table.Row key={n}>
|
||||
<Table.Cell style={{ width: '16%' }}>
|
||||
<Chip head>{n}</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell style={{ width: '50%' }}>
|
||||
<div
|
||||
style={{ display: 'flex', flexWrap: 'wrap', gap: '0.375rem' }}
|
||||
>
|
||||
{data[n].type === 'string' && <Chip>string</Chip>}
|
||||
{data[n].type === 'number' && <Chip>number</Chip>}
|
||||
{data[n].type === 'boolean' && <Chip>boolean</Chip>}
|
||||
{data[n].type === 'enum' && enumValues}
|
||||
{data[n].type === 'spacing' && (
|
||||
<>
|
||||
<Chip>0.5, 1, 1.5, 2, 3, ..., 14</Chip>
|
||||
<Chip>string</Chip>
|
||||
</>
|
||||
)}
|
||||
{data[n].type === 'enum | string' && (
|
||||
<>
|
||||
{enumValues}
|
||||
<Chip>string</Chip>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
<Table.Cell style={{ width: '20%' }}>
|
||||
<Chip>{data[n].default ? data[n].default : '-'}</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell style={{ width: '14%' }}>
|
||||
<Chip>{data[n].responsive ? 'Yes' : 'No'}</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { PropsTable } from './PropsTable';
|
||||
@@ -0,0 +1,37 @@
|
||||
import { RoadmapItem } from './list';
|
||||
|
||||
export const Roadmap = ({ list }: { list: RoadmapItem[] }) => {
|
||||
const orderList = ['inProgress', 'notStarted', 'completed'];
|
||||
return (
|
||||
<div className="roadmap">
|
||||
{list
|
||||
.sort(
|
||||
(a, b) => orderList.indexOf(a.status) - orderList.indexOf(b.status),
|
||||
)
|
||||
.map(Item)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({
|
||||
title,
|
||||
status = 'notStarted',
|
||||
}: {
|
||||
title: string;
|
||||
status: 'notStarted' | 'inProgress' | 'inReview' | 'completed';
|
||||
}) => {
|
||||
return (
|
||||
<div className={['roadmap-item', status].join(' ')}>
|
||||
<div className="left">
|
||||
<div className="dot" />
|
||||
<div className="title">{title}</div>
|
||||
</div>
|
||||
<span className="pill">
|
||||
{status === 'notStarted' && 'Not Started'}
|
||||
{status === 'inProgress' && 'In Progress'}
|
||||
{status === 'inReview' && 'Ready for Review'}
|
||||
{status === 'completed' && 'Completed'}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Roadmap } from './Roadmap';
|
||||
@@ -0,0 +1,47 @@
|
||||
export type RoadmapItem = {
|
||||
title: string;
|
||||
status: 'notStarted' | 'inProgress' | 'inReview' | 'completed';
|
||||
};
|
||||
|
||||
export const list: RoadmapItem[] = [
|
||||
{
|
||||
title: 'Remove Vanilla Extract and use pure CSS instead',
|
||||
status: 'inProgress',
|
||||
},
|
||||
{
|
||||
title: 'Add collapsing across breakpoints for the Inline component',
|
||||
status: 'notStarted',
|
||||
},
|
||||
{
|
||||
title: 'Add reversing the order for the Inline component',
|
||||
status: 'notStarted',
|
||||
},
|
||||
{
|
||||
title: 'Set up Storybook',
|
||||
status: 'completed',
|
||||
},
|
||||
{
|
||||
title: 'Set up iconography',
|
||||
status: 'completed',
|
||||
},
|
||||
{
|
||||
title: 'Set up global tokens',
|
||||
status: 'inProgress',
|
||||
},
|
||||
{
|
||||
title: 'Set up theming system',
|
||||
status: 'inProgress',
|
||||
},
|
||||
{
|
||||
title: 'Create first pass at box component',
|
||||
status: 'completed',
|
||||
},
|
||||
{
|
||||
title: 'Create first pass at stack component',
|
||||
status: 'completed',
|
||||
},
|
||||
{
|
||||
title: 'Create first pass at inline component',
|
||||
status: 'completed',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,100 @@
|
||||
.roadmap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding: 8px 0px;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item .left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item .dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.notStarted {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.inProgress {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.inReview {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.completed .title {
|
||||
color: #a2a2a2;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.notStarted .dot {
|
||||
background-color: #d1d1d1;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.inProgress .dot {
|
||||
background-color: #ffd000;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.inReview .dot {
|
||||
background-color: #4ed14a;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.completed .dot {
|
||||
background-color: #4ed14a;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item .title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item .pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 40px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-left: 8px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.notStarted .pill {
|
||||
background-color: #f2f2f2;
|
||||
border-color: #cdcdcd;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.inProgress .pill {
|
||||
background-color: #fff2b9;
|
||||
border-color: #ffd000;
|
||||
color: #d79927;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.inReview .pill {
|
||||
background-color: #d7f9d7;
|
||||
border-color: #4ed14a;
|
||||
color: #3a9837;
|
||||
}
|
||||
|
||||
.roadmap .roadmap-item.completed .pill {
|
||||
background-color: #d7f9d7;
|
||||
border-color: #4ed14a;
|
||||
color: #3a9837;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
border-radius: 8px;
|
||||
width: 300px;
|
||||
height: calc(100vh - 32px);
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.025);
|
||||
color: var(--primary);
|
||||
background-color: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.viewport {
|
||||
overflow: scroll;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
|
||||
.logoContainer {
|
||||
padding-left: 6px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.logo path {
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
padding: 12px 0;
|
||||
color: var(--primary);
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.line {
|
||||
text-decoration: none;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 26px;
|
||||
padding: 0 12px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-1);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.line.active {
|
||||
background-color: var(--surface-1);
|
||||
}
|
||||
|
||||
.line.active .lineTitle {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.lineTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.lineStatus {
|
||||
font-size: 14px;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
width: 0.25rem;
|
||||
border-radius: 0.375rem;
|
||||
margin: 0.5rem;
|
||||
opacity: 0;
|
||||
transition: opacity 150ms 300ms;
|
||||
right: -20px;
|
||||
|
||||
&[data-hovering],
|
||||
&[data-scrolling] {
|
||||
opacity: 1;
|
||||
transition-duration: 75ms;
|
||||
transition-delay: 0ms;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1.25rem;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.thumb {
|
||||
width: 100%;
|
||||
border-radius: inherit;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { components, overview, layoutComponents, theme } from '@/utils/data';
|
||||
import { motion } from 'motion/react';
|
||||
import styles from './Sidebar.module.css';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
const data = [
|
||||
{
|
||||
title: 'Overview',
|
||||
content: overview,
|
||||
url: '',
|
||||
},
|
||||
{
|
||||
title: 'Theme',
|
||||
content: theme,
|
||||
url: '/theme',
|
||||
},
|
||||
{
|
||||
title: 'Layout Components',
|
||||
content: layoutComponents,
|
||||
url: '/components',
|
||||
},
|
||||
{
|
||||
title: 'Components',
|
||||
content: components,
|
||||
url: '/components',
|
||||
},
|
||||
];
|
||||
|
||||
export const Docs = () => {
|
||||
const pathname = usePathname();
|
||||
const isPlayground = pathname.includes('/playground');
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={styles.section}
|
||||
animate={{
|
||||
x: isPlayground ? -10 : 0,
|
||||
opacity: isPlayground ? 0 : 1,
|
||||
visibility: isPlayground ? 'hidden' : 'visible',
|
||||
}}
|
||||
initial={{
|
||||
x: isPlayground ? -10 : 0,
|
||||
opacity: isPlayground ? 0 : 1,
|
||||
visibility: isPlayground ? 'hidden' : 'visible',
|
||||
}}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
{data.map(section => {
|
||||
return (
|
||||
<Fragment key={section.title}>
|
||||
<div className={styles.sectionTitle}>{section.title}</div>
|
||||
|
||||
{section.content.map(item => {
|
||||
const isActive = pathname === `${section.url}/${item.slug}`;
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`${section.url}/${item.slug}`}
|
||||
key={item.slug}
|
||||
className={`${styles.line} ${isActive ? styles.active : ''}`}
|
||||
>
|
||||
<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'}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import styles from './Sidebar.module.css';
|
||||
import { Docs } from './docs';
|
||||
import { Playground } from './playground';
|
||||
import Link from 'next/link';
|
||||
import { ScrollArea } from '@base-ui-components/react/scroll-area';
|
||||
|
||||
export const Sidebar = () => {
|
||||
return (
|
||||
<div className={styles.sidebar}>
|
||||
<ScrollArea.Root className={styles.root}>
|
||||
<ScrollArea.Viewport className={styles.viewport}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.logoContainer}>
|
||||
<Link href="/">
|
||||
<svg
|
||||
width="89"
|
||||
height="27"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={styles.logo}
|
||||
>
|
||||
<path d="M77.414 9.71h3.036v2.992l-.264-.022c.279-.88.748-1.65 1.408-2.31.675-.66 1.533-.99 2.574-.99 1.086 0 1.9.352 2.442 1.056.558.689.836 1.598.836 2.728v7.502H84.41v-6.6c0-.66-.117-1.159-.352-1.496-.234-.338-.623-.506-1.166-.506-.601 0-1.122.271-1.562.814a4.321 4.321 0 0 0-.88 1.848v5.94h-3.036V9.71ZM69.868 21.04c-1.13 0-2.142-.257-3.036-.77a5.547 5.547 0 0 1-2.068-2.09c-.484-.895-.726-1.892-.726-2.992 0-1.115.242-2.112.726-2.992a5.503 5.503 0 0 1 2.068-2.112c.88-.514 1.892-.77 3.036-.77 1.144 0 2.156.256 3.036.77a5.347 5.347 0 0 1 2.046 2.112c.498.88.748 1.87.748 2.97 0 1.114-.25 2.12-.748 3.014a5.343 5.343 0 0 1-2.068 2.09c-.88.513-1.885.77-3.014.77Zm0-2.618c.557 0 1.048-.132 1.474-.396a2.79 2.79 0 0 0 .99-1.144c.234-.499.352-1.064.352-1.694 0-.63-.118-1.188-.352-1.672a2.764 2.764 0 0 0-.99-1.166c-.426-.279-.917-.418-1.474-.418-.558 0-1.05.14-1.474.418a2.764 2.764 0 0 0-.99 1.166c-.22.484-.33 1.041-.33 1.672 0 .63.11 1.195.33 1.694.234.484.564.865.99 1.144.425.264.916.396 1.474.396ZM52.385 9.71h3.036v2.992l-.264-.022c.279-.88.748-1.65 1.408-2.31.675-.66 1.533-.99 2.574-.99 1.085 0 1.9.352 2.442 1.056.557.689.836 1.598.836 2.728v7.502h-3.036v-6.6c0-.66-.117-1.159-.352-1.496-.235-.338-.623-.506-1.166-.506-.601 0-1.122.271-1.562.814a4.321 4.321 0 0 0-.88 1.848v5.94h-3.036V9.71ZM43.904 20.952c-1.026 0-1.87-.308-2.53-.924-.66-.616-.99-1.416-.99-2.398 0-1.115.418-2.01 1.254-2.684.85-.69 2.105-1.034 3.762-1.034h2.75l-.924.352v-.462c0-.572-.176-1.02-.528-1.342-.352-.338-.931-.506-1.738-.506-.69 0-1.386.154-2.09.462-.704.308-1.29.69-1.76 1.144v-2.882c.47-.352 1.1-.66 1.892-.924a7.942 7.942 0 0 1 2.486-.396c1.599 0 2.78.41 3.542 1.232.778.806 1.166 1.914 1.166 3.322v5.742c0 .176.008.352.022.528.03.161.066.322.11.484h-3.014v-3.3l.044 1.386a4.044 4.044 0 0 1-1.32 1.606c-.572.396-1.283.594-2.134.594Zm.858-2.156c.558 0 1.049-.206 1.474-.616.44-.426.77-.954.99-1.584v-.792h-1.562c-.762 0-1.334.146-1.716.44-.381.278-.572.674-.572 1.188 0 .41.125.74.374.99.264.25.602.374 1.012.374ZM34.66 21.04c-1.525 0-2.874-.345-4.047-1.034-1.174-.704-2.09-1.68-2.75-2.926-.646-1.247-.968-2.662-.968-4.246 0-1.584.322-2.992.968-4.224.66-1.232 1.576-2.193 2.75-2.882 1.173-.704 2.522-1.056 4.048-1.056.968 0 1.818.11 2.552.33.748.22 1.356.506 1.826.858v3.498c-.396-.484-.96-.895-1.694-1.232-.719-.338-1.51-.506-2.376-.506-.939 0-1.775.22-2.508.66-.719.44-1.284 1.056-1.694 1.848-.396.792-.594 1.694-.594 2.706 0 1.026.198 1.943.594 2.75.41.792.975 1.408 1.694 1.848.733.44 1.57.66 2.508.66.88 0 1.68-.162 2.398-.484.718-.323 1.276-.726 1.672-1.21v3.41a5.686 5.686 0 0 1-1.87.902c-.704.22-1.54.33-2.508.33ZM11.275 6.271c.276.526.019 1.152-.464 1.498a5.846 5.846 0 0 0 1.004 10.082c.454.205.789.632.789 1.13v6.206a.837.837 0 0 1-.838.837C5.33 26.024.112 20.804.112 14.366V.862C.112.399.487.024.95.024c4.483 0 8.376 2.533 10.325 6.247Z" />
|
||||
<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>
|
||||
<div className={styles.menu}>
|
||||
<Docs />
|
||||
<Playground />
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea.Viewport>
|
||||
<ScrollArea.Scrollbar className={styles.scrollbar}>
|
||||
<ScrollArea.Thumb className={styles.thumb} />
|
||||
</ScrollArea.Scrollbar>
|
||||
</ScrollArea.Root>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
'use client';
|
||||
|
||||
import { components } from '@/utils/data';
|
||||
import { Checkbox } from '@backstage/canon';
|
||||
import { motion } from 'motion/react';
|
||||
import styles from './Sidebar.module.css';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { screenSizes } from '@/utils/data';
|
||||
import { usePlayground } from '@/utils/playground-context';
|
||||
|
||||
export const Playground = () => {
|
||||
const pathname = usePathname();
|
||||
const isPlayground = pathname.includes('/playground');
|
||||
const {
|
||||
selectedScreenSizes,
|
||||
setSelectedScreenSizes,
|
||||
selectedComponents,
|
||||
setSelectedComponents,
|
||||
} = usePlayground();
|
||||
|
||||
const handleComponentCheckboxChange = (slug: string) => {
|
||||
if (selectedComponents.find(item => item === slug)) {
|
||||
const res = selectedComponents.filter(item => item !== slug);
|
||||
setSelectedComponents(res);
|
||||
} else {
|
||||
setSelectedComponents([...selectedComponents, slug]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (slug: string) => {
|
||||
if (selectedScreenSizes.find(item => item === slug)) {
|
||||
const res = selectedScreenSizes.filter(item => item !== slug);
|
||||
setSelectedScreenSizes(res);
|
||||
} else {
|
||||
setSelectedScreenSizes([...selectedScreenSizes, slug]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={styles.section}
|
||||
animate={{
|
||||
opacity: isPlayground ? 1 : 0,
|
||||
x: isPlayground ? 0 : 20,
|
||||
visibility: isPlayground ? 'visible' : 'hidden',
|
||||
}}
|
||||
initial={{
|
||||
opacity: isPlayground ? 1 : 0,
|
||||
x: isPlayground ? 0 : 20,
|
||||
visibility: isPlayground ? 'visible' : 'hidden',
|
||||
}}
|
||||
transition={{ duration: 0.2 }}
|
||||
style={{ position: 'absolute' }}
|
||||
>
|
||||
<div className={styles.sectionTitle}>Components</div>
|
||||
{components.map(({ slug, title }) => (
|
||||
<div className={styles.line} key={slug}>
|
||||
<div className={styles.lineTitle}>{title}</div>
|
||||
<Checkbox
|
||||
checked={selectedComponents.includes(slug)}
|
||||
onChange={() => handleComponentCheckboxChange(slug)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className={styles.sectionTitle}> Screen sizes</div>
|
||||
{screenSizes.map(({ slug, title }) => (
|
||||
<div className={styles.line} key={slug}>
|
||||
<div className={styles.lineTitle}>{title}</div>
|
||||
<Checkbox
|
||||
checked={selectedScreenSizes.includes(slug)}
|
||||
onChange={() => handleCheckboxChange(slug)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Collapsible } from '@base-ui-components/react/collapsible';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
interface SnippetProps {
|
||||
preview: JSX.Element;
|
||||
codeContent: JSX.Element;
|
||||
align?: 'left' | 'center';
|
||||
px?: number;
|
||||
py?: number;
|
||||
open?: boolean;
|
||||
height?: string | number;
|
||||
}
|
||||
|
||||
export const SnippetClient = ({
|
||||
preview,
|
||||
codeContent,
|
||||
align = 'left',
|
||||
px = 2,
|
||||
py = 2,
|
||||
open = false,
|
||||
height = 'auto',
|
||||
}: SnippetProps) => {
|
||||
const [isOpen, setIsOpen] = useState(open);
|
||||
|
||||
return (
|
||||
<Collapsible.Root
|
||||
className={styles.container}
|
||||
defaultOpen={open}
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
<div className={styles.preview} style={{ height }}>
|
||||
<div
|
||||
className={`${styles.previewContent} ${styles[align]}`}
|
||||
style={{ padding: `${py}rem ${px}rem` }}
|
||||
>
|
||||
{preview}
|
||||
</div>
|
||||
<Collapsible.Trigger className={styles.trigger}>
|
||||
{isOpen ? 'Hide code' : 'View code'}
|
||||
</Collapsible.Trigger>
|
||||
</div>
|
||||
<Collapsible.Panel className={styles.panel}>
|
||||
{codeContent}
|
||||
</Collapsible.Panel>
|
||||
</Collapsible.Root>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import { CodeBlock } from '../CodeBlock';
|
||||
import { SnippetClient } from './client';
|
||||
|
||||
interface SnippetProps {
|
||||
preview: JSX.Element;
|
||||
code: string;
|
||||
align?: 'left' | 'center';
|
||||
px?: number;
|
||||
py?: number;
|
||||
open?: boolean;
|
||||
height?: string | number;
|
||||
}
|
||||
|
||||
export const Snippet = ({
|
||||
preview,
|
||||
code = '',
|
||||
align = 'left',
|
||||
px = 2,
|
||||
py = 2,
|
||||
open = false,
|
||||
height = 'auto',
|
||||
}: SnippetProps) => {
|
||||
return (
|
||||
<SnippetClient
|
||||
preview={preview}
|
||||
codeContent={<CodeBlock code={code} />}
|
||||
align={align}
|
||||
px={px}
|
||||
py={py}
|
||||
open={open}
|
||||
height={height}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.preview {
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
background-color: var(--bg);
|
||||
padding: 1px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.previewContent {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: radial-gradient(rgba(0, 0, 0, 0.08) 1px, transparent 0);
|
||||
background-position: calc((8px / 2) * -1) calc((8px / 2) * -1);
|
||||
background-size: 8px 8px;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.trigger {
|
||||
all: unset;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .previewContent {
|
||||
background-image: radial-gradient(
|
||||
rgba(255, 255, 255, 0.18) 1px,
|
||||
transparent 0
|
||||
);
|
||||
}
|
||||
|
||||
.panel {
|
||||
height: var(--collapsible-panel-height);
|
||||
transition: all 0.2s ease-out;
|
||||
overflow: hidden;
|
||||
|
||||
&[data-starting-style],
|
||||
&[data-ending-style] {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&[data-closed] {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&[data-open] {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { CSSProperties, ReactNode } from 'react';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export const Root = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<table className={styles.table}>{children}</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Header = ({ children }: { children: ReactNode }) => {
|
||||
return <thead>{children}</thead>;
|
||||
};
|
||||
|
||||
export const Body = ({ children }: { children: ReactNode }) => {
|
||||
return <tbody>{children}</tbody>;
|
||||
};
|
||||
|
||||
export const HeaderRow = ({ children }: { children: ReactNode }) => {
|
||||
return <tr>{children}</tr>;
|
||||
};
|
||||
|
||||
export const HeaderCell = ({
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
style?: CSSProperties;
|
||||
}) => {
|
||||
return (
|
||||
<th
|
||||
className={`${styles.tableCell} ${styles.tableHeaderCell}`}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
};
|
||||
|
||||
export const Row = ({ children }: { children: ReactNode }) => {
|
||||
return <tr className={styles.tableRow}>{children}</tr>;
|
||||
};
|
||||
|
||||
export const Cell = ({
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
style?: CSSProperties;
|
||||
}) => {
|
||||
return (
|
||||
<td className={styles.tableCell} style={style}>
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Root, Header, Body, Row, Cell, HeaderRow, HeaderCell } from './Table';
|
||||
@@ -0,0 +1,56 @@
|
||||
.wrapper {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border-spacing: 0px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.tableCell {
|
||||
padding: 12px 16px !important;
|
||||
border: none !important;
|
||||
text-align: left;
|
||||
background-color: var(--panel) !important;
|
||||
font-size: 16px;
|
||||
|
||||
& p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tableHeaderCell {
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tableRow {
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tableChip {
|
||||
display: inline-block;
|
||||
font-size: 14px !important;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 0px 6px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.tableType {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * as Tabs from './parts';
|
||||
@@ -0,0 +1,42 @@
|
||||
'use client';
|
||||
|
||||
import { Tabs as TabsPrimitive } from '@base-ui-components/react/tabs';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export const Root = ({
|
||||
className,
|
||||
...rest
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Root>) => (
|
||||
<TabsPrimitive.Root className={`${styles.root} ${className}`} {...rest} />
|
||||
);
|
||||
|
||||
export const List = ({
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: React.ComponentProps<typeof TabsPrimitive.List>) => (
|
||||
<TabsPrimitive.List className={`${styles.list} ${className}`} {...rest}>
|
||||
{children}
|
||||
<TabsPrimitive.Indicator className={styles.indicator} />
|
||||
</TabsPrimitive.List>
|
||||
);
|
||||
|
||||
export const Tab = (props: React.ComponentProps<typeof TabsPrimitive.Tab>) => (
|
||||
<TabsPrimitive.Tab
|
||||
{...props}
|
||||
render={({ children, ...rest }, state) => {
|
||||
return (
|
||||
<button className={styles.tab} data-selected={state.selected} {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Panel = ({
|
||||
className,
|
||||
...rest
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Panel>) => (
|
||||
<TabsPrimitive.Panel className={`${styles.panel} ${className}`} {...rest} />
|
||||
);
|
||||
@@ -0,0 +1,41 @@
|
||||
.root {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
position: relative;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
padding-bottom: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
|
||||
&[data-selected='false'] {
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
&[data-selected='false']:hover {
|
||||
color: var(--primary);
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: var(--active-tab-left);
|
||||
width: var(--active-tab-width);
|
||||
height: 1px;
|
||||
background-color: var(--primary);
|
||||
transition-property: left, width;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
@@ -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,78 @@
|
||||
.tabs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.tabs {
|
||||
display: block;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.tabsTheme {
|
||||
width: 142px;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--bg);
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.tab {
|
||||
all: unset;
|
||||
height: 60px;
|
||||
color: var(--secondary);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transition: color 0.2s ease-in-out;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--primary);
|
||||
|
||||
& p {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0.25rem 0;
|
||||
border-radius: 0.25rem;
|
||||
outline: 2px solid var(--panel);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab p {
|
||||
color: var(--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(--primary);
|
||||
transition-property: translate, width;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
@@ -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,22 @@
|
||||
.toolbar {
|
||||
position: sticky;
|
||||
top: 16px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 0.5rem;
|
||||
z-index: 10;
|
||||
background-color: var(--panel);
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 1.5rem;
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.025);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
@@ -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(--surface-1);
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--color-blue);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.SelectIcon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.SelectValue {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.Positioner {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.Popup {
|
||||
box-sizing: border-box;
|
||||
padding-block: 0.25rem;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--panel);
|
||||
color: var(--color-gray-900);
|
||||
border: 1px solid var(--border);
|
||||
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: 0.875rem;
|
||||
font-weight: 600;
|
||||
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(--bg);
|
||||
}
|
||||
|
||||
&[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;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
'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', value: 'default' },
|
||||
{ name: 'Spotify', value: 'spotify' },
|
||||
{ name: 'Custom theme', value: 'custom' },
|
||||
];
|
||||
|
||||
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="chevron-down" />
|
||||
</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} key={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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
.tabs {
|
||||
border-radius: 0.375rem;
|
||||
width: 100%;
|
||||
background-color: var(--surface-1);
|
||||
}
|
||||
|
||||
.tabsTheme {
|
||||
width: 100px;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--surface-1);
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
padding-inline: 0.25rem;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
background: none;
|
||||
appearance: none;
|
||||
color: var(--secondary);
|
||||
user-select: none;
|
||||
height: 2rem;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--primary);
|
||||
|
||||
& p {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0.25rem 0;
|
||||
border-radius: 0.25rem;
|
||||
outline: 2px solid var(--panel);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab p {
|
||||
color: var(--secondary) !important;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
translate: var(--active-tab-left) -50%;
|
||||
width: var(--active-tab-width);
|
||||
height: 1.5rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--panel);
|
||||
transition-property: translate, width;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { AvatarSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
avatarPropDefs,
|
||||
snippetUsage,
|
||||
snippetSizes,
|
||||
snippetFallback,
|
||||
} from './avatar.props';
|
||||
import { ComponentInfos } from '@/components/ComponentInfos';
|
||||
|
||||
# Avatar
|
||||
|
||||
An avatar component with a fallback for initials.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<AvatarSnippet story="Default" />}
|
||||
code={`<Avatar src="https://avatars.githubusercontent.com/u/1540635?v=4" name="Charles de Dreuille" />`}
|
||||
/>
|
||||
|
||||
<ComponentInfos
|
||||
component="avatar"
|
||||
usageCode={snippetUsage}
|
||||
classNames={[
|
||||
'canon-AvatarRoot',
|
||||
'canon-AvatarRoot[data-size="small"]',
|
||||
'canon-AvatarRoot[data-size="medium"]',
|
||||
'canon-AvatarRoot[data-size="large"]',
|
||||
'canon-AvatarImage',
|
||||
'canon-AvatarFallback',
|
||||
]}
|
||||
/>
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={avatarPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
Avatar sizes can be set using the `size` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<AvatarSnippet story="Sizes" />}
|
||||
code={snippetSizes}
|
||||
/>
|
||||
|
||||
### Fallback
|
||||
|
||||
If the image is not available, the avatar will show the initials of the name.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<AvatarSnippet story="Fallback" />}
|
||||
code={snippetFallback}
|
||||
/>
|
||||
@@ -0,0 +1,46 @@
|
||||
import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
|
||||
import type { PropDef } from '@/utils/propDefs';
|
||||
|
||||
export const avatarPropDefs: Record<string, PropDef> = {
|
||||
src: {
|
||||
type: 'string',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium', 'large'],
|
||||
default: 'medium',
|
||||
responsive: true,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const snippetUsage = `import { Avatar } from '@backstage/canon';
|
||||
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille"
|
||||
/>`;
|
||||
|
||||
export const snippetSizes = `<Flex gap="4" direction="column">
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille" size="small"
|
||||
/>
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille" size="medium"
|
||||
/>
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille" size="large"
|
||||
/>
|
||||
</Flex>`;
|
||||
|
||||
export const snippetFallback = `<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/15406AAAAAAAAA"
|
||||
name="Charles de Dreuille"
|
||||
/>`;
|
||||
@@ -0,0 +1,55 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { BoxSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
boxPropDefs,
|
||||
snippetUsage,
|
||||
boxPreviewSnippet,
|
||||
boxSimpleSnippet,
|
||||
boxResponsiveSnippet,
|
||||
} from './box.props';
|
||||
import { spacingPropDefs } from '@/utils/propDefs';
|
||||
import { ComponentInfos } from '@/components/ComponentInfos';
|
||||
|
||||
# Box
|
||||
|
||||
Box is the lowest-level component in Canon. It provides a consistent API for styling and layout.
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<BoxSnippet story="Preview" />}
|
||||
code={boxPreviewSnippet}
|
||||
align="center"
|
||||
/>
|
||||
|
||||
<ComponentInfos component="box" usageCode={snippetUsage} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Box
|
||||
|
||||
This is the Box component, our lowest-level component. Here are all the
|
||||
available properties.
|
||||
|
||||
<PropsTable data={boxPropDefs} />
|
||||
|
||||
Padding and margin are used to create space around your component using our
|
||||
predefined spacing tokens. We would recommend to use padding over margin to
|
||||
avoid collapsing margins but both are available.
|
||||
|
||||
<PropsTable data={spacingPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple example
|
||||
|
||||
A simple example of how to use the Box component.
|
||||
|
||||
<CodeBlock code={boxSimpleSnippet} />
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when buttons are responsive.
|
||||
|
||||
<CodeBlock code={boxResponsiveSnippet} />
|
||||
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
displayPropDefs,
|
||||
heightPropDefs,
|
||||
positionPropDefs,
|
||||
stylePropDefs,
|
||||
widthPropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const boxPropDefs: Record<string, PropDef> = {
|
||||
as: {
|
||||
type: 'enum',
|
||||
values: ['div', 'span'],
|
||||
default: 'div',
|
||||
responsive: true,
|
||||
},
|
||||
...widthPropDefs,
|
||||
...heightPropDefs,
|
||||
...positionPropDefs,
|
||||
...displayPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const snippetUsage = `import { Box } from '@backstage/canon';
|
||||
|
||||
<Box />`;
|
||||
|
||||
export const boxPreviewSnippet = `<Box>
|
||||
<DecorativeBox />
|
||||
</Box>`;
|
||||
|
||||
export const boxSimpleSnippet = `<Box padding="md" borderRadius="md">Hello World</Box>`;
|
||||
|
||||
export const boxResponsiveSnippet = `<Box
|
||||
padding={{ xs: 'sm', md: 'md' }}
|
||||
borderRadius={{ xs: 'sm', md: 'md' }}>
|
||||
Hello World
|
||||
</Box>`;
|
||||
@@ -0,0 +1,80 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ButtonIconSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
buttonIconPropDefs,
|
||||
buttonIconUsageSnippet,
|
||||
buttonIconDefaultSnippet,
|
||||
buttonIconVariantsSnippet,
|
||||
buttonIconSizesSnippet,
|
||||
buttonIconDisabledSnippet,
|
||||
buttonIconResponsiveSnippet,
|
||||
buttonIconAsLinkSnippet,
|
||||
} from './button-icon.props';
|
||||
import { ComponentInfos } from '@/components/ComponentInfos';
|
||||
|
||||
# ButtonIcon
|
||||
|
||||
A button component with a single icon that can be used to trigger actions.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<ButtonIconSnippet story="Variants" />}
|
||||
code={buttonIconDefaultSnippet}
|
||||
/>
|
||||
|
||||
<ComponentInfos
|
||||
component="button-icon"
|
||||
classNames={['canon-Button', 'canon-ButtonIcon']}
|
||||
usageCode={buttonIconUsageSnippet}
|
||||
/>
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={buttonIconPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Variants
|
||||
|
||||
Here's a view when buttons have different variants.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonIconSnippet story="Variants" />}
|
||||
code={buttonIconVariantsSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when buttons have different sizes.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonIconSnippet story="Sizes" />}
|
||||
code={buttonIconSizesSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
Here's a view when buttons are disabled.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonIconSnippet story="Disabled" />}
|
||||
code={buttonIconDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when buttons are responsive.
|
||||
|
||||
<CodeBlock code={buttonIconResponsiveSnippet} />
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const buttonIconPropDefs: Record<string, PropDef> = {
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary'],
|
||||
default: 'primary',
|
||||
responsive: true,
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'medium',
|
||||
responsive: true,
|
||||
},
|
||||
icon: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isDisabled: { type: 'boolean', default: 'false', responsive: false },
|
||||
type: {
|
||||
type: 'enum',
|
||||
values: ['button', 'submit', 'reset'],
|
||||
default: 'button',
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/canon';
|
||||
|
||||
<ButtonIcon />`;
|
||||
|
||||
export const buttonIconDefaultSnippet = `<Flex align="center">
|
||||
<ButtonIcon icon={<Icon name="cloud" />} variant="primary" />
|
||||
<ButtonIcon icon={<Icon name="cloud" />} variant="secondary" />
|
||||
</Flex>`;
|
||||
|
||||
export const buttonIconVariantsSnippet = `<Flex align="center">
|
||||
<ButtonIcon icon={<Icon name="cloud" />} variant="primary" />
|
||||
<ButtonIcon icon={<Icon name="cloud" />} variant="secondary" />
|
||||
</Flex>`;
|
||||
|
||||
export const buttonIconSizesSnippet = `<Flex align="center">
|
||||
<ButtonIcon icon={<Icon name="cloud" />} size="small" />
|
||||
<ButtonIcon icon={<Icon name="cloud" />} size="medium" />
|
||||
</Flex>`;
|
||||
|
||||
export const buttonIconDisabledSnippet = `<ButtonIcon icon={<Icon name="cloud" />} isDisabled />`;
|
||||
|
||||
export const buttonIconResponsiveSnippet = `<ButtonIcon icon={<Icon name="cloud" />} variant={{ initial: 'primary', lg: 'secondary' }} />`;
|
||||
|
||||
export const buttonIconAsLinkSnippet = `import { ButtonLink } from '@backstage/canon';
|
||||
|
||||
<ButtonLink href="https://canon.backstage.io" target="_blank">
|
||||
Button
|
||||
</ButtonLink>`;
|
||||
@@ -0,0 +1,91 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ButtonLinkSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
buttonLinkPropDefs,
|
||||
buttonLinkSnippetUsage,
|
||||
buttonLinkVariantsSnippet,
|
||||
buttonLinkSizesSnippet,
|
||||
buttonLinkIconsSnippet,
|
||||
buttonLinkDisabledSnippet,
|
||||
buttonLinkResponsiveSnippet,
|
||||
} from './button-link.props';
|
||||
import { ComponentInfos } from '@/components/ComponentInfos';
|
||||
|
||||
# ButtonLink
|
||||
|
||||
A button component that can be used as a link.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<ButtonLinkSnippet story="Variants" />}
|
||||
code={buttonLinkVariantsSnippet}
|
||||
/>
|
||||
|
||||
<ComponentInfos
|
||||
component="button-link"
|
||||
classNames={['canon-Button', 'canon-ButtonLink']}
|
||||
usageCode={buttonLinkSnippetUsage}
|
||||
/>
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={buttonLinkPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Variants
|
||||
|
||||
Here's a view when buttons have different variants.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonLinkSnippet story="Variants" />}
|
||||
code={buttonLinkVariantsSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when buttons have different sizes.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonLinkSnippet story="Sizes" />}
|
||||
code={buttonLinkSizesSnippet}
|
||||
/>
|
||||
|
||||
### With Icons
|
||||
|
||||
Here's a view when buttons have icons.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonLinkSnippet story="WithIcons" />}
|
||||
code={buttonLinkIconsSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
Here's a view when buttons are disabled.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonLinkSnippet story="Disabled" />}
|
||||
code={buttonLinkDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when buttons are responsive.
|
||||
|
||||
<CodeBlock code={buttonLinkResponsiveSnippet} />
|
||||
@@ -0,0 +1,73 @@
|
||||
import { classNamePropDefs, stylePropDefs } from '../../utils/propDefs';
|
||||
import type { PropDef } from '../../utils/propDefs';
|
||||
|
||||
export const buttonLinkPropDefs: Record<string, PropDef> = {
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary'],
|
||||
default: 'primary',
|
||||
responsive: true,
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'medium',
|
||||
responsive: true,
|
||||
},
|
||||
iconStart: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isDisabled: { type: 'boolean', default: 'false', responsive: false },
|
||||
href: { type: 'string', responsive: false },
|
||||
hrefLang: { type: 'string', responsive: false },
|
||||
target: {
|
||||
type: 'enum',
|
||||
values: ['HTMLAttributeAnchorTarget'],
|
||||
default: '_self',
|
||||
responsive: false,
|
||||
},
|
||||
rel: { type: 'string', responsive: false },
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const buttonLinkSnippetUsage = `import { ButtonLink } from '@backstage/canon';
|
||||
|
||||
<ButtonLink />`;
|
||||
|
||||
export const buttonLinkVariantsSnippet = `<Flex align="center">
|
||||
<ButtonLink iconStart={<Icon name="cloud" />} variant="primary">
|
||||
Button
|
||||
</ButtonLink>
|
||||
<ButtonLink iconStart={<Icon name="cloud" />} variant="secondary">
|
||||
Button
|
||||
</ButtonLink>
|
||||
</Flex>`;
|
||||
|
||||
export const buttonLinkSizesSnippet = `<Flex align="center">
|
||||
<ButtonLink size="small">Small</ButtonLink>
|
||||
<ButtonLink size="medium">Medium</ButtonLink>
|
||||
</Flex>`;
|
||||
|
||||
export const buttonLinkIconsSnippet = `<Flex align="center">
|
||||
<ButtonLink iconStart={<Icon name="cloud" />}>Button</ButtonLink>
|
||||
<ButtonLink iconEnd={<Icon name="chevronRight" />}>Button</ButtonLink>
|
||||
<ButtonLink
|
||||
iconStart={<Icon name="cloud" />}
|
||||
iconEnd={<Icon name="chevronRight" />}>
|
||||
Button
|
||||
</ButtonLink>
|
||||
</Flex>`;
|
||||
|
||||
export const buttonLinkDisabledSnippet = `<Flex gap="4">
|
||||
<ButtonLink variant="primary" isDisabled>
|
||||
Primary
|
||||
</ButtonLink>
|
||||
<ButtonLink variant="secondary" isDisabled>
|
||||
Secondary
|
||||
</ButtonLink>
|
||||
</Flex>`;
|
||||
|
||||
export const buttonLinkResponsiveSnippet = `<ButtonLink variant={{ initial: 'primary', lg: 'secondary' }}>
|
||||
Responsive Button
|
||||
</ButtonLink>`;
|
||||
@@ -0,0 +1,104 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ButtonSnippet, ButtonLinkSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
buttonPropDefs,
|
||||
buttonSnippetUsage,
|
||||
buttonVariantsSnippet,
|
||||
buttonSizesSnippet,
|
||||
buttonIconsSnippet,
|
||||
buttonDisabledSnippet,
|
||||
buttonResponsiveSnippet,
|
||||
buttonAsLinkSnippet,
|
||||
} from './button.props';
|
||||
import { ComponentInfos } from '@/components/ComponentInfos';
|
||||
|
||||
# Button
|
||||
|
||||
A button component that can be used to trigger actions.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<ButtonSnippet story="Variants" />}
|
||||
code={buttonVariantsSnippet}
|
||||
/>
|
||||
|
||||
<ComponentInfos
|
||||
component="button"
|
||||
classNames={['canon-Button']}
|
||||
usageCode={buttonSnippetUsage}
|
||||
/>
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={buttonPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Variants
|
||||
|
||||
Here's a view when buttons have different variants.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonSnippet story="Variants" />}
|
||||
code={buttonVariantsSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when buttons have different sizes.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonSnippet story="Sizes" />}
|
||||
code={buttonSizesSnippet}
|
||||
/>
|
||||
|
||||
### With Icons
|
||||
|
||||
Here's a view when buttons have icons.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonSnippet story="WithIcons" />}
|
||||
code={buttonIconsSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
Here's a view when buttons are disabled.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonSnippet story="Disabled" />}
|
||||
code={buttonDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when buttons are responsive.
|
||||
|
||||
<CodeBlock code={buttonResponsiveSnippet} />
|
||||
|
||||
### As Link
|
||||
|
||||
If you want to use a button as a link, please use the `ButtonLink` component.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonLinkSnippet story="Variants" />}
|
||||
code={buttonAsLinkSnippet}
|
||||
/>
|
||||
@@ -0,0 +1,72 @@
|
||||
import { classNamePropDefs, stylePropDefs } from '../../utils/propDefs';
|
||||
import type { PropDef } from '../../utils/propDefs';
|
||||
|
||||
export const buttonPropDefs: Record<string, PropDef> = {
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary'],
|
||||
default: 'primary',
|
||||
responsive: true,
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'medium',
|
||||
responsive: true,
|
||||
},
|
||||
iconStart: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isDisabled: { type: 'boolean', default: 'false', responsive: false },
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
type: {
|
||||
type: 'enum',
|
||||
values: ['button', 'submit', 'reset'],
|
||||
default: 'button',
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const buttonSnippetUsage = `import { Button } from '@backstage/canon';
|
||||
|
||||
<Button />`;
|
||||
|
||||
export const buttonVariantsSnippet = `<Flex align="center">
|
||||
<Button iconStart="cloud" variant="primary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart="cloud" variant="secondary">
|
||||
Button
|
||||
</Button>
|
||||
</Flex>`;
|
||||
|
||||
export const buttonSizesSnippet = `<Flex align="center">
|
||||
<Button size="small">Small</Button>
|
||||
<Button size="medium">Medium</Button>
|
||||
</Flex>`;
|
||||
|
||||
export const buttonIconsSnippet = `<Flex align="center">
|
||||
<Button iconStart={<Icon name="cloud" />}>Button</Button>
|
||||
<Button iconEnd={<Icon name="chevronRight" />}>Button</Button>
|
||||
<Button iconStart={<Icon name="cloud" />} iconEnd={<Icon name="chevronRight" />}>Button</Button>
|
||||
</Flex>`;
|
||||
|
||||
export const buttonDisabledSnippet = `<Flex gap="4">
|
||||
<Button variant="primary" isDisabled>
|
||||
Primary
|
||||
</Button>
|
||||
<Button variant="secondary" isDisabled>
|
||||
Secondary
|
||||
</Button>
|
||||
</Flex>`;
|
||||
|
||||
export const buttonResponsiveSnippet = `<Button variant={{ initial: 'primary', lg: 'secondary' }}>
|
||||
Responsive Button
|
||||
</Button>`;
|
||||
|
||||
export const buttonAsLinkSnippet = `import { ButtonLink } from '@backstage/canon';
|
||||
|
||||
<ButtonLink href="https://canon.backstage.io" target="_blank">
|
||||
Button
|
||||
</ButtonLink>`;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user