Setting up Canon

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2024-11-18 10:32:45 +00:00
parent 5e0873ad2c
commit 21f0884a4d
38 changed files with 4334 additions and 118 deletions
+4
View File
@@ -0,0 +1,4 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname), {
extends: ['plugin:storybook/recommended'],
};
+41
View File
@@ -0,0 +1,41 @@
import type { StorybookConfig } from '@storybook/react-webpack5';
const { VanillaExtractPlugin } = require('@vanilla-extract/webpack-plugin');
import { merge } from 'webpack-merge';
import { join, dirname } from 'path';
/**
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, 'package.json')));
}
const config: StorybookConfig = {
stories: [
'../src/docs/**/*.mdx',
'../src/components/**/*.mdx',
'../src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)',
],
staticDirs: ['../static'],
addons: [
getAbsolutePath('@storybook/addon-webpack5-compiler-swc'),
getAbsolutePath('@storybook/addon-onboarding'),
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@chromatic-com/storybook'),
getAbsolutePath('@storybook/addon-interactions'),
// getAbsolutePath("@storybook/addon-styling-webpack")
],
framework: {
name: getAbsolutePath('@storybook/react-webpack5'),
options: {
plugins: [new VanillaExtractPlugin()],
},
},
webpackFinal: config => {
return merge(config, {
plugins: [new VanillaExtractPlugin()],
});
},
};
export default config;
+46
View File
@@ -0,0 +1,46 @@
import React from 'react';
import type { Preview, ReactRenderer } from '@storybook/react';
import {
withThemeByClassName,
withThemeByDataAttribute,
} from '@storybook/addon-themes';
import '../src/theme/styles.css';
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
disable: true,
},
},
decorators: [
withThemeByDataAttribute<ReactRenderer>({
themes: {
light: 'light',
dark: 'dark',
},
defaultTheme: 'light',
}),
(Story, context) => {
const theme = context.globals.theme || 'light';
const backgroundColor = theme === 'light' ? '#ffffff' : '#000000';
document.body.style.backgroundColor = backgroundColor;
const docsStoryElements = document.getElementsByClassName('docs-story');
Array.from(docsStoryElements).forEach(element => {
(element as HTMLElement).style.backgroundColor = backgroundColor;
});
return <Story />;
},
],
};
export default preview;
+12
View File
@@ -0,0 +1,12 @@
# @backstage/canon
_This package was created through the Backstage CLI_.
## Installation
Install the package via Yarn:
```sh
cd <package-dir> # if within a monorepo
yarn add @backstage/canon
```
+9
View File
@@ -0,0 +1,9 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-canon
title: '@backstage/canon'
spec:
lifecycle: experimental
type: backstage-web-library
owner: maintainers
+86
View File
@@ -0,0 +1,86 @@
{
"name": "@backstage/canon",
"version": "0.0.0",
"backstage": {
"role": "web-library"
},
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"keywords": [
"backstage"
],
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/canon"
},
"license": "Apache-2.0",
"sideEffects": false,
"main": "src/index.ts",
"types": "src/index.ts",
"files": [
"dist"
],
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"test": "backstage-cli package test",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"@base_ui/react": "^1.0.0-alpha.3",
"@vanilla-extract/css": "^1.16.0",
"@vanilla-extract/dynamic": "^2.1.2",
"@vanilla-extract/recipes": "^0.5.5",
"@vanilla-extract/sprinkles": "^1.6.3"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@chromatic-com/storybook": "^3.2.2",
"@eslint/js": "^9.13.0",
"@storybook/addon-essentials": "^8.4.4",
"@storybook/addon-interactions": "^8.4.4",
"@storybook/addon-onboarding": "^8.4.4",
"@storybook/addon-styling-webpack": "^1.0.1",
"@storybook/addon-themes": "^8.4.4",
"@storybook/addon-webpack5-compiler-swc": "^1.0.5",
"@storybook/blocks": "^8.4.4",
"@storybook/react": "^8.4.4",
"@storybook/react-webpack5": "^8.4.4",
"@storybook/test": "^8.4.4",
"@testing-library/jest-dom": "^6.0.0",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@vanilla-extract/rollup-plugin": "^1.3.10",
"@vanilla-extract/webpack-plugin": "^2.3.14",
"eslint": "^9.13.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.13",
"eslint-plugin-storybook": "^0.11.0",
"globals": "^15.11.0",
"mini-css-extract-plugin": "^2.9.2",
"storybook": "^8.4.4",
"typescript": "~5.6.2",
"typescript-eslint": "^8.10.0",
"webpack-merge": "^6.0.1"
},
"peerDependencies": {
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
}
@@ -0,0 +1,20 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { style } from '@vanilla-extract/css';
export const base = style({
fontFamily: 'var(--canon-font-sans)',
});
+117
View File
@@ -0,0 +1,117 @@
import { Canvas, Meta, Unstyled } from '@storybook/blocks';
import * as BoxStories from './box.stories';
import { PropsTable } from '../../storybook/props-table';
import { Chip } from '../../storybook/chip';
import * as Table from '../../storybook/table';
<Meta of={BoxStories} />
# Box
Box is the lowest-level component in Canon. We use it internally to build all of our components.
<Canvas of={BoxStories.Basic} />
<Unstyled>
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Type</Table.HeaderCell>
<Table.HeaderCell>Default</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip>background</Chip>
</Table.Cell>
<Table.Cell>
<Chip>string</Chip>
</Table.Cell>
<Table.Cell>
<Chip>transparent</Chip>
</Table.Cell>
<Table.Cell>The background color of the box.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip>display</Chip>
</Table.Cell>
<Table.Cell>
<Chip>block</Chip>
<Chip>flex</Chip>
<Chip>inline-block</Chip>
<Chip>inline-flex</Chip>
</Table.Cell>
<Table.Cell>
<Chip>block</Chip>
</Table.Cell>
<Table.Cell>The display property of the box.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip>padding</Chip>
</Table.Cell>
<Table.Cell>
<Chip>number</Chip>
</Table.Cell>
<Table.Cell>
<Chip>0</Chip>
</Table.Cell>
<Table.Cell>The padding of the box.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip>paddingX</Chip>
</Table.Cell>
<Table.Cell>
<Chip>number</Chip>
</Table.Cell>
<Table.Cell>
<Chip>0</Chip>
</Table.Cell>
<Table.Cell>The horizontal padding of the box.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip>paddingY</Chip>
</Table.Cell>
<Table.Cell>
<Chip>number</Chip>
</Table.Cell>
<Table.Cell>
<Chip>0</Chip>
</Table.Cell>
<Table.Cell>The vertical padding of the box.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip>paddingLeft</Chip>
</Table.Cell>
<Table.Cell>
<Chip>number</Chip>
</Table.Cell>
<Table.Cell>
<Chip>0</Chip>
</Table.Cell>
<Table.Cell>The left padding of the box.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip>paddingRight</Chip>
</Table.Cell>
<Table.Cell>
<Chip>number</Chip>
</Table.Cell>
<Table.Cell>
<Chip>0</Chip>
</Table.Cell>
<Table.Cell>The right padding of the box.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
</Unstyled>
@@ -0,0 +1,151 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Box } from './box';
const meta = {
title: 'Components/Box',
component: Box,
parameters: {
layout: 'centered',
},
// tags: ['autodocs'],
} satisfies Meta<typeof Box>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Basic: Story = {
render: () => (
<Box padding="medium" background="background">
Basic Box
</Box>
),
};
export const Responsive: Story = {
render: () => (
<Box
display={{ xs: 'block', sm: 'flex' }}
padding={{ xs: 'small', sm: 'medium', lg: 'large' }}
background="background"
>
Responsive Box
</Box>
),
};
export const FlexContainer: Story = {
render: () => (
<Box
display="flex"
flexDirection="column"
padding="large"
background="background"
>
<Box padding="medium" background="background">
Item 1
</Box>
<Box padding="medium" background="background">
Item 2
</Box>
<Box padding="medium" background="background">
Item 3
</Box>
</Box>
),
};
export const Nested: Story = {
render: () => (
<Box padding="large" background="background">
<Box padding="medium" background="background">
Header
</Box>
<Box
display="flex"
padding="medium"
background="background"
justifyContent="space-between"
>
<Box padding="small" background="background">
Sidebar
</Box>
<Box padding="small" background="background">
Main Content
</Box>
</Box>
<Box padding="medium" background="background">
Footer
</Box>
</Box>
),
};
export const Alignment: Story = {
render: () => (
<Box
display="flex"
padding="large"
background="background"
justifyContent="center"
alignItems="center"
style={{ height: '200px' }}
>
<Box padding="medium" background="background">
Centered Content
</Box>
</Box>
),
};
// Example showing different spacing combinations
export const Spacing: Story = {
render: () => (
<Box display="flex" flexDirection="column" gap="medium">
<Box padding="small" background="background">
Small Padding
</Box>
<Box padding="medium" background="background">
Medium Padding
</Box>
<Box padding="large" background="background">
Large Padding
</Box>
<Box paddingX="large" paddingY="small" background="background">
Mixed Padding
</Box>
</Box>
),
};
// Example showing different display values
export const DisplayVariants: Story = {
render: () => (
<Box display="flex" flexDirection="column" gap="medium">
<Box padding="medium" background="background" display="block">
Display Block
</Box>
<Box padding="medium" background="background" display="flex">
Display Flex
</Box>
<Box padding="medium" background="background" display="inline">
Display Inline
</Box>
</Box>
),
};
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createElement } from 'react';
import { sprinkles, Sprinkles } from './sprinkles.css';
import { base } from './box.css';
type HTMLProperties = Omit<
React.AllHTMLAttributes<HTMLElement>,
keyof Sprinkles
>;
type BoxProps = Sprinkles &
HTMLProperties & {
as?: keyof JSX.IntrinsicElements;
};
export const Box = ({ as = 'div', className, style, ...props }: BoxProps) => {
const sprinklesProps: Record<string, unknown> = {};
const nativeProps: Record<string, unknown> = {};
// Split props between sprinkles and native HTML props
Object.entries(props).forEach(([key, value]) => {
if (value === undefined) return;
if (sprinkles.properties.has(key as keyof Sprinkles)) {
sprinklesProps[key] = value;
} else {
nativeProps[key] = value;
}
});
const sprinklesClassName = sprinkles(sprinklesProps);
return createElement(as, {
className: [base, sprinklesClassName, className].filter(Boolean).join(' '),
style,
...nativeProps,
});
};
@@ -0,0 +1,83 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';
const space = {
none: 0,
small: '4px',
medium: '8px',
large: '16px',
// etc.
};
const colors = {
background: '#eff6ff',
};
const responsiveProperties = defineProperties({
conditions: {
xs: {},
sm: { '@media': 'screen and (min-width: 640px)' },
md: { '@media': 'screen and (min-width: 768px)' },
lg: { '@media': 'screen and (min-width: 1024px)' },
xl: { '@media': 'screen and (min-width: 1280px)' },
'2xl': { '@media': 'screen and (min-width: 1536px)' },
},
defaultCondition: 'xs',
properties: {
display: ['none', 'flex', 'block', 'inline'],
flexDirection: ['row', 'column'],
justifyContent: [
'stretch',
'flex-start',
'center',
'flex-end',
'space-around',
'space-between',
],
alignItems: ['stretch', 'flex-start', 'center', 'flex-end'],
paddingTop: space,
paddingBottom: space,
paddingLeft: space,
paddingRight: space,
// fontFamily: font,
gap: space,
// etc.
},
shorthands: {
padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
paddingX: ['paddingLeft', 'paddingRight'],
paddingY: ['paddingTop', 'paddingBottom'],
placeItems: ['justifyContent', 'alignItems'],
},
});
const colorProperties = defineProperties({
conditions: {
lightMode: {},
darkMode: { '@media': '(prefers-color-scheme: dark)' },
},
defaultCondition: 'lightMode',
properties: {
color: colors,
background: colors,
},
});
export const sprinkles = createSprinkles(responsiveProperties, colorProperties);
// It's a good idea to export the Sprinkles type too
export type Sprinkles = Parameters<typeof sprinkles>[0];
@@ -0,0 +1,74 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { recipe } from '@vanilla-extract/recipes';
// import { vars } from '../../theme/themes.css';
export const button = recipe({
base: {
all: 'unset',
display: 'inline-block',
userSelect: 'none',
fontFamily: 'var(--canon-font-sans)',
fontWeight: 600,
fontSize: '0.875rem',
lineHeight: '1.5',
padding: 0,
transition: 'all 150ms ease',
cursor: 'pointer',
},
variants: {
variant: {
primary: {
backgroundColor: 'var(--button-primary-background-color)',
color: 'var(--button-primary-text-color)',
borderRadius: 'var(--button-primary-border-radius)',
border: `1px solid var(--button-primary-border-color)`,
'&:hover': {
backgroundColor: 'var(--button-primary-background-color-hover)',
borderColor: 'var(--button-primary-border-color-hover)',
},
},
secondary: {
backgroundColor: 'transparent',
color: 'var(--button-secondary-text-color)',
borderRadius: 'var(--button-secondary-border-radius)',
border: `1px solid var(--button-secondary-border-color)`,
'&:hover': {
backgroundColor: 'var(--button-secondary-background-color-hover)',
borderColor: 'var(--button-secondary-border-color-hover)',
},
},
},
size: {
small: {
paddingLeft: '16px',
paddingRight: '16px',
height: 'var(--button-size-small)',
},
medium: {
paddingLeft: '32px',
paddingRight: '32px',
height: 'var(--button-size-medium)',
},
},
disabled: {
true: {
opacity: 0.3,
cursor: 'not-allowed',
},
},
},
});
@@ -0,0 +1,93 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './button';
const meta = {
title: 'Components/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
size: {
control: 'select',
options: ['small', 'medium'],
},
},
args: {
size: 'medium',
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
children: 'Primary button',
},
};
export const Secondary: Story = {
args: {
children: 'Secondary button',
variant: 'secondary',
},
};
export const Sizes: Story = {
args: {
children: 'Button',
},
render: args => (
<div style={{ display: 'flex', gap: '10px' }}>
<Button {...args} size="small" />
<Button {...args} size="medium" />
</div>
),
};
export const Disabled: Story = {
args: {
children: 'Button',
disabled: true,
},
};
export const CustomTheme: Story = {
args: {
children: 'Custom Button',
},
decorators: [
Story => (
<div
style={
{
'--button-primary-background-color': 'blue',
'--button-primary-border-color': 'blue',
'--button-primary-text-color': 'white',
'--button-primary-border-radius': '8px',
} as React.CSSProperties
}
>
<Story />
</div>
),
],
};
@@ -0,0 +1,46 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { button } from './button.css';
import { Box } from '../box/box';
export interface ButtonProps {
size?: 'small' | 'medium';
variant?: 'primary' | 'secondary';
children: React.ReactNode;
disabled?: boolean;
}
export const Button = ({
size = 'medium',
variant = 'primary',
children,
disabled,
...props
}: ButtonProps) => {
return (
<Box
{...props}
as="button"
disabled={disabled}
className={button({ size, variant, disabled })}
>
{children}
</Box>
);
};
export default Button;
@@ -0,0 +1,41 @@
:root {
--button-primary-background-color: #1ed760;
--button-primary-background-color-hover: #3be477;
--button-primary-background-color-active: #1abc54;
--button-primary-border-color: #1ed760;
--button-primary-border-color-hover: #3be477;
--button-primary-border-color-active: #1abc54;
--button-primary-text-color: #000;
--button-primary-border-radius: 9999px;
--button-secondary-background-color: transparent;
--button-secondary-background-color-hover: transparent;
--button-secondary-background-color-active: rgba(0, 0, 0, 0.03);
--button-secondary-border-color: #000;
--button-secondary-border-color-hover: #000;
--button-secondary-border-color-active: #000;
--button-secondary-text-color: #000;
--button-secondary-border-radius: 9999px;
--button-size-small: 32px;
--button-size-medium: 48px;
}
[data-theme='dark'] {
--button-primary-background-color: #1ed760;
--button-primary-background-color-hover: #3be477;
--button-primary-background-color-active: #1abc54;
--button-primary-border-color: #1ed760;
--button-primary-border-color-hover: #3be477;
--button-primary-border-color-active: #1abc54;
--button-primary-text-color: #000;
--button-primary-border-radius: 9999px;
--button-secondary-background-color: transparent;
--button-secondary-background-color-hover: transparent;
--button-secondary-background-color-active: rgba(255, 255, 255, 0.1);
--button-secondary-border-color: #fff;
--button-secondary-border-color-hover: #fff;
--button-secondary-border-color-active: #fff;
--button-secondary-text-color: #fff;
--button-secondary-border-radius: 9999px;
--button-size-small: 32px;
--button-size-medium: 48px;
}
+46
View File
@@ -0,0 +1,46 @@
import { Unstyled } from '@storybook/blocks';
import { Text } from '../storybook/text';
import { ComponentStatus } from '../storybook/component-status';
import { Columns } from '../storybook/columns';
![Canon Header](header.png)
<Unstyled>
<Text style={{ marginTop: '64px' }}>
Welcome to the Canon, the new library for building 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.
</Text>
<Columns style={{ marginTop: '64px' }}>
<ComponentStatus
name="Theming system"
status="inProgress"
link="/?path=/docs/theme--docs"
/>
<ComponentStatus name="Storybook setup" status="inProgress" />
<ComponentStatus name="Tokens" status="inProgress" />
<ComponentStatus
name="Box"
status="inProgress"
link="/?path=/docs/components-box--docs"
/>
<ComponentStatus
name="Button"
status="inProgress"
link="/?path=/docs/components-button--docs"
/>
<ComponentStatus name="Input" status="notStarted" />
<ComponentStatus name="Select" status="notStarted" />
<ComponentStatus name="Checkbox" status="notStarted" />
<ComponentStatus name="Radio" status="notStarted" />
<ComponentStatus name="Switch" status="notStarted" />
<ComponentStatus name="Tooltip" status="notStarted" />
<ComponentStatus name="Header" status="notStarted" />
<ComponentStatus name="Tabs" status="notStarted" />
</Columns>
</Unstyled>
+100
View File
@@ -0,0 +1,100 @@
import { Unstyled, Source } from '@storybook/blocks';
import { Title } from '../storybook/title';
import { Text } from '../storybook/text';
import { Chip } from '../storybook/chip';
import * as Table from '../storybook/table';
<Unstyled>
<Title type="h1">Theming</Title>
<Text>
Backstage ships with a default theme with a light and dark mode variant. The
themes are provided as a part of the{' '}
<a href="https://github.com/backstage/backstage">@backstage/canon</a> package,
which also includes utilities for customizing the default theme, or creating
completely new themes.
</Text>
<Title type="h2">Light & Dark modes</Title>
<Text>
By default we are supporting both light and dark modes. Each user can opt to
choose what theme they want to use or to use their system decide what theme to
use. If you want to create your own theme, you will have to set both light and
dark themes following the instructions below. If you only set one of them, the
other mode will fallback to the default theme.
</Text>
<Title type="h2">How to create your own theme</Title>
<Text>
To create your own theme, you will have to define the variables below. To do
that, create a theme.css file and import it in your application. Here's an
example below on how to set your light and dark mode.
</Text>
<Source
code={`/** Light theme **/
[data-theme='light'] {
--canon-accent: #1ed760;
--canon-bg: #fff;
--canon-surface-1: #f5f5f5;
--canon-surface-2: #000;
--canon-outline: #666;
--canon-outline-focus: #ccc;
--canon-text-primary: #f0f0f0;
--canon-text-secondary: #666;
--canon-font-sans: 'Geist', serif;
--canon-font-monospace: 'Monospace', monospace;
--canon-font-emoji: 'Apple Color Emoji', 'Segoe UI Emoji';
/* ... other values */
}
/** Dark theme **/
[data-theme='dark'] {
--canon-accent: #1ed760;
--canon-bg: #000;
--canon-surface-1: #f5f5f5;
--canon-surface-2: #000;
--canon-outline: #666;
--canon-outline-focus: #ccc;
--canon-text-primary: #fff;
--canon-text-secondary: #666;
--canon-font-sans: 'Geist', serif;
--canon-font-monospace: 'Monospace', monospace;
--canon-font-emoji: 'Apple Color Emoji', 'Segoe UI Emoji';
/* ... other values */
}
`}
language="css"
dark
/>
<Title type="h2">Colors</Title>
<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>--canon-accent</Chip>
</Table.Cell>
<Table.Cell>The accent color for the theme.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip>--canon-bg</Chip>
</Table.Cell>
<Table.Cell>The background color for the theme.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
<Title type="h2">Typography</Title>
<Text>
Our theming is system is still a work in progress. We will be adding more
features and documentation over time.
</Text>
</Unstyled>
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Components
export { Button } from './components/button/button';
export { Box } from './components/box/box';
@@ -0,0 +1,29 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { style } from '@vanilla-extract/css';
export const chipStyles = style({
display: 'inline-flex',
alignItems: 'center',
fontFamily: 'Geist Mono, monospace',
fontSize: '13px',
borderRadius: '6px',
padding: '0px 8px',
height: '24px',
backgroundColor: '#eaf2fd',
color: '#2563eb',
marginRight: '4px',
});
@@ -0,0 +1,21 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { chipStyles } from './chip.css';
export const Chip = ({ children }: { children: React.ReactNode }) => {
return <span className={chipStyles}>{children}</span>;
};
@@ -0,0 +1,23 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { style } from '@vanilla-extract/css';
export const styles = style({
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
rowGap: '20px',
columnGap: '80px',
});
@@ -0,0 +1,31 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { styles } from './columns.css';
export const Columns = ({
children,
style,
}: {
children: React.ReactNode;
style?: React.CSSProperties;
}) => {
return (
<div className={styles} style={style}>
{children}
</div>
);
};
@@ -0,0 +1,66 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';
export const container = style({
display: 'flex',
justifyContent: 'space-between',
});
export const title = style({
color: '#3b59ff',
fontFamily: '"Geist", sans-serif',
fontSize: '16px',
fontWeight: '400',
textDecoration: 'none',
});
export const pill = recipe({
base: {
fontFamily: '"Geist", sans-serif',
display: 'inline-flex',
alignItems: 'center',
color: '#000',
borderRadius: '40px',
padding: '0px 8px',
height: '24px',
fontSize: '12px',
fontWeight: '600',
borderStyle: 'solid',
borderWidth: '1px',
marginLeft: '8px',
},
variants: {
status: {
notStarted: {
backgroundColor: '#F2F2F2',
borderColor: '#CDCDCD',
color: '#888888',
},
inProgress: {
backgroundColor: '#FFF2B9',
borderColor: '#FFD000',
color: '#D79927',
},
done: {
backgroundColor: '#D7F9D7',
borderColor: '#4ED14A',
color: '#3A9837',
},
},
},
});
@@ -0,0 +1,46 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { container, pill, title } from './component-status.css';
export const ComponentStatus = ({
name,
status = 'notStarted',
style,
link,
}: {
name: string;
status: 'notStarted' | 'inProgress' | 'done';
style?: React.CSSProperties;
link?: string;
}) => {
return (
<div className={container} style={style}>
{link ? (
<a href={link} className={title}>
{name}
</a>
) : (
<span className={title}>{name}</span>
)}
<span className={pill({ status })}>
{status === 'notStarted' && 'Not Started'}
{status === 'inProgress' && 'In Progress'}
{status === 'done' && 'Done'}
</span>
</div>
);
};
@@ -0,0 +1,74 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import {
tableStyles,
cellStyles,
headerCellStyles,
rowStyles,
wrapperStyles,
chipStyles,
headerRowStyles,
typeStyles,
} from './props-table.css';
type PropsTableData = {
prop: string;
type: string | string[];
default: string;
description: string;
};
export const PropsTable = ({ data }: { data: PropsTableData[] }) => {
return (
<div className={wrapperStyles}>
<table className={tableStyles}>
<thead className={headerRowStyles}>
<tr className={rowStyles}>
<th className={`${cellStyles} ${headerCellStyles}`}>Prop</th>
<th className={`${cellStyles} ${headerCellStyles}`}>Type</th>
<th className={`${cellStyles} ${headerCellStyles}`}>Default</th>
<th className={`${cellStyles} ${headerCellStyles}`}>Description</th>
</tr>
</thead>
<tbody>
{data.map(item => (
<tr className={rowStyles}>
<td className={cellStyles}>
<span className={chipStyles}>{item.prop}</span>
</td>
<td className={cellStyles}>
<div className={typeStyles}>
{Array.isArray(item.type) ? (
item.type.map(type => (
<span className={chipStyles}>{type}</span>
))
) : (
<span className={chipStyles}>{item.type}</span>
)}
</div>
</td>
<td className={cellStyles}>
<span className={chipStyles}>{item.default}</span>
</td>
<td className={cellStyles}>{item.description}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
@@ -0,0 +1,70 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { style } from '@vanilla-extract/css';
export const wrapperStyles = style({
border: '1px solid #e7e7e7',
borderRadius: '4px',
overflow: 'hidden',
});
export const tableStyles = style({
width: '100%',
margin: '0 !important',
padding: '0 !important',
});
export const cellStyles = style({
padding: '12px 16px !important',
border: 'none !important',
textAlign: 'left',
backgroundColor: 'white !important',
});
export const headerCellStyles = style({
backgroundColor: '#f5f5f5 !important',
fontWeight: 'bold',
});
export const rowStyles = style({
border: 'none !important',
borderBottom: '1px solid #e7e7e7 !important',
selectors: {
'&:last-child': {
borderBottom: 'none !important',
},
},
});
export const chipStyles = style({
display: 'inline-block',
fontSize: '14px !important',
border: '1px solid #e7e7e7',
borderRadius: '6px',
padding: '0px 6px',
height: '24px',
});
export const headerRowStyles = style({
borderBottom: '1px solid #e7e7e7 !important',
});
export const typeStyles = style({
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
gap: '8px',
});
@@ -0,0 +1,55 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import {
cellStyles,
headerCellStyles,
tableStyles,
wrapperStyles,
rowStyles,
} from './table.css';
export const Root = ({ children }: { children: React.ReactNode }) => {
return (
<div className={wrapperStyles}>
<table className={tableStyles}>{children}</table>
</div>
);
};
export const Header = ({ children }: { children: React.ReactNode }) => {
return <thead>{children}</thead>;
};
export const Body = ({ children }: { children: React.ReactNode }) => {
return <tbody>{children}</tbody>;
};
export const HeaderRow = ({ children }: { children: React.ReactNode }) => {
return <tr>{children}</tr>;
};
export const HeaderCell = ({ children }: { children: React.ReactNode }) => {
return <th className={`${cellStyles} ${headerCellStyles}`}>{children}</th>;
};
export const Row = ({ children }: { children: React.ReactNode }) => {
return <tr className={rowStyles}>{children}</tr>;
};
export const Cell = ({ children }: { children: React.ReactNode }) => {
return <td className={cellStyles}>{children}</td>;
};
@@ -0,0 +1,76 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { style } from '@vanilla-extract/css';
export const wrapperStyles = style({
border: '1px solid #e7e7e7',
borderRadius: '4px',
overflow: 'hidden',
});
export const tableStyles = style({
width: '100%',
margin: '0 !important',
padding: '0 !important',
borderSpacing: '0px',
borderCollapse: 'collapse',
});
export const cellStyles = style({
padding: '12px 16px !important',
border: 'none !important',
textAlign: 'left',
backgroundColor: 'white !important',
fontFamily: '"Geist", sans-serif',
fontSize: '16px',
fontWeight: '300',
});
export const headerRowStyles = style({
borderBottom: '1px solid #e7e7e7',
});
export const headerCellStyles = style({
backgroundColor: '#f5f5f5 !important',
borderBottom: '1px solid #e7e7e7 !important',
fontWeight: '500',
});
export const rowStyles = style({
border: 'none',
borderBottom: '1px solid #e7e7e7',
selectors: {
'&:last-child': {
borderBottom: 'none',
},
},
});
export const chipStyles = style({
display: 'inline-block',
fontSize: '14px !important',
border: '1px solid #e7e7e7',
borderRadius: '6px',
padding: '0px 6px',
height: '24px',
});
export const typeStyles = style({
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
gap: '8px',
});
@@ -0,0 +1,31 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { textStyles } from './text.css';
export const Text = ({
children,
style,
}: {
children: React.ReactNode;
style?: React.CSSProperties;
}) => {
return (
<div className={textStyles} style={style}>
{children}
</div>
);
};
@@ -0,0 +1,33 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { globalStyle, style } from '@vanilla-extract/css';
export const textStyles = style({
fontFamily: '"Geist", sans-serif',
fontSize: '18px',
lineHeight: '28px',
margin: '0',
fontWeight: '300',
paddingBottom: '16px',
});
globalStyle(`${textStyles} p`, {
fontFamily: '"Geist", sans-serif',
fontSize: '18px',
lineHeight: '28px',
margin: '0',
fontWeight: '300',
});
@@ -0,0 +1,38 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { titleStyles } from './title.css';
export const Title = ({
children,
style,
type = 'h1',
}: {
children: React.ReactNode;
style?: React.CSSProperties;
type?: 'h1' | 'h2' | 'h3';
}) => {
let Component = 'h1';
if (type === 'h1') Component = 'h1';
if (type === 'h2') Component = 'h2';
if (type === 'h3') Component = 'h3';
return React.createElement(Component, {
className: titleStyles({ type }),
style,
children,
});
};
@@ -0,0 +1,47 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { recipe } from '@vanilla-extract/recipes';
export const titleStyles = recipe({
base: {
fontFamily: '"Geist", sans-serif',
margin: '0',
fontWeight: '500',
border: 'none',
},
variants: {
type: {
h1: {
fontSize: '40px',
lineHeight: '48px',
marginBottom: '32px',
marginTop: '0px',
},
h2: {
fontSize: '24px',
lineHeight: '32px',
marginBottom: '12px',
marginTop: '52px',
},
h3: {
fontSize: '20px',
lineHeight: '28px',
marginBottom: '12px',
marginTop: '52px',
},
},
},
});
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
/* Geist font */
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&display=swap');
/* Theme */
@import './theme.css';
/* Components */
@import '../components/button/styles.css';
+33
View File
@@ -0,0 +1,33 @@
/* Light theme */
:root {
--canon-accent: #1ed760;
--canon-bg: #fff;
--canon-surface-1: #f5f5f5;
--canon-surface-2: #000;
--canon-outline: #666;
--canon-outline-focus: #ccc;
--canon-text-primary: #f0f0f0;
--canon-text-secondary: #666;
--canon-font-sans: 'Geist', serif;
--canon-font-monospace: 'Monospace', monospace;
--canon-font-emoji: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
}
/* Dark theme */
[data-theme='dark'] {
--canon-accent: #1ed760;
--canon-bg: #fff;
--canon-surface-1: #f5f5f5;
--canon-surface-2: #000;
--canon-outline: #666;
--canon-outline-focus: #ccc;
--canon-text-primary: #f0f0f0;
--canon-text-secondary: #666;
--canon-font-sans: 'Geist', serif;
--canon-font-monospace: 'Monospace', monospace;
--canon-font-emoji: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

+9
View File
@@ -0,0 +1,9 @@
{
"extends": "@backstage/cli/config/tsconfig.json",
"include": ["src"],
"exclude": ["node_modules"],
"compilerOptions": {
"outDir": "dist-types",
"rootDir": "."
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
}
}
+2583 -118
View File
File diff suppressed because it is too large Load Diff