diff --git a/packages/canon/.eslintrc.js b/packages/canon/.eslintrc.js new file mode 100644 index 0000000000..f7c0e8cbd1 --- /dev/null +++ b/packages/canon/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + ...require('@backstage/cli/config/eslint-factory')(__dirname), + extends: ['plugin:storybook/recommended'], + rules: { + 'react/forbid-elements': 'off', + }, +}; + diff --git a/packages/canon/.storybook/main.ts b/packages/canon/.storybook/main.ts new file mode 100644 index 0000000000..41df32404b --- /dev/null +++ b/packages/canon/.storybook/main.ts @@ -0,0 +1,40 @@ +import type { StorybookConfig } from '@storybook/react-webpack5'; +import { VanillaExtractPlugin } from '@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: [ + '../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-essentials'), + getAbsolutePath('@chromatic-com/storybook'), + getAbsolutePath('@storybook/addon-interactions'), + getAbsolutePath('@storybook/addon-themes'), + ], + framework: { + name: getAbsolutePath('@storybook/react-webpack5'), + options: { + plugins: [new VanillaExtractPlugin()], + }, + }, + webpackFinal: config => { + return merge(config, { + plugins: [new VanillaExtractPlugin()], + }); + }, +}; +export default config; diff --git a/packages/canon/.storybook/preview.tsx b/packages/canon/.storybook/preview.tsx new file mode 100644 index 0000000000..fc9736a6ed --- /dev/null +++ b/packages/canon/.storybook/preview.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import type { Preview, ReactRenderer } from '@storybook/react'; +import { 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({ + themes: { + light: 'light', + dark: 'dark', + }, + defaultTheme: 'light', + }), + (Story, context) => { + document.body.style.backgroundColor = 'var(--canon-background)'; + + const docsStoryElements = document.getElementsByClassName('docs-story'); + Array.from(docsStoryElements).forEach(element => { + (element as HTMLElement).style.backgroundColor = + 'var(--canon-background)'; + }); + + return ; + }, + ], +}; + +export default preview; diff --git a/packages/canon/README.md b/packages/canon/README.md new file mode 100644 index 0000000000..a8bf36d7be --- /dev/null +++ b/packages/canon/README.md @@ -0,0 +1,12 @@ +# @backstage/canon + +_This package was created through the Backstage CLI_. + +## Installation + +Install the package via Yarn: + +```sh +cd # if within a monorepo +yarn add @backstage/canon +``` diff --git a/packages/canon/catalog-info.yaml b/packages/canon/catalog-info.yaml new file mode 100644 index 0000000000..37aa7b1caf --- /dev/null +++ b/packages/canon/catalog-info.yaml @@ -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 diff --git a/packages/canon/docs/Home.mdx b/packages/canon/docs/Home.mdx new file mode 100644 index 0000000000..d2bda8cbf6 --- /dev/null +++ b/packages/canon/docs/Home.mdx @@ -0,0 +1,49 @@ +import { Unstyled } from '@storybook/blocks'; +import { Columns, Text, ComponentStatus, Banner } from './components'; + + + + + + + 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. + + + + This library is still under heavy construction. Please be aware that the API + will change until we reach a stable release. + + + + + + + + + + + + + + + + + + + diff --git a/packages/canon/docs/Layout.mdx b/packages/canon/docs/Layout.mdx new file mode 100644 index 0000000000..ed72087cca --- /dev/null +++ b/packages/canon/docs/Layout.mdx @@ -0,0 +1,47 @@ +import { Unstyled, Source } from '@storybook/blocks'; +import { Title, Text, LayoutComponents } from './components'; +import * as Table from './components/Table'; + + + +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. + + + + Hello World + + Project 1 + Project 2 + +`} + language="tsx" + dark +/> + + + +Layout Helpers + +TBD + + diff --git a/packages/canon/docs/Theme.mdx b/packages/canon/docs/Theme.mdx new file mode 100644 index 0000000000..cab948c6ba --- /dev/null +++ b/packages/canon/docs/Theme.mdx @@ -0,0 +1,229 @@ +import { Unstyled, Source } from '@storybook/blocks'; +import { Title, Text, Chip } from './components'; +import * as Table from './components/Table'; + + + +Theming + + Backstage ships with a default theme with a light and dark mode variant. The + themes are provided as a part of the `@backstage/canon` package, which also + includes utilities for customizing the default theme, or creating completely + new themes. + + +Light & Dark modes + + 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. + + +How to create your own theme + + 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. + + + +Colors + + We provide a set of generic colours tokens that we use across Canon. By + changing these colours you can easily change the look and feel of your + application to match your brand. + + + + + Prop + Description + + + + + + --canon-accent + + The accent color for the theme. + + + + --canon-bg + + The background color for the theme. + + + + --canon-surface-1 + + The first surface color for the theme. + + + + --canon-surface-2 + + The second surface color for the theme. + + + + --canon-outline + + The outline color for the theme. + + + + --canon-outline-focus + + The outline focus color for the theme. + + + + --canon-text-primary + + The primary text color for the theme. + + + + --canon-text-secondary + + The secondary text color for the theme. + + + + +Typography + + 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. + + + + + + Prop + Description + + + + + + --canon-font-sans + + The sans-serif font for the theme. + + + + --canon-font-mono + + The monospace font for the theme. + + + + +Spacing + + Our default spacing system is made to work in most scenarios. We have 7 scale + values from `xxs` to `xxl`. We use the values on padding and margin in our + layout components mostly. If you prefer to use a different spacing system, you + can do that by changing the values below. + + + + + + Prop + Description + + + + + + --canon-space-unit + + + The base unit for the spacing system. Default value is `1em` + + + + + --canon-space-xxs + + Default value is `0.25 x space unit` + + + + --canon-space-xs + + Default value is `0.5 x space unit` + + + + --canon-space-sm + + Default value is `0.75 x space unit` + + + + --canon-space-md + + Default value is `1.25 x space unit` + + + + --canon-space-lg + + Default value is `2 x space unit` + + + + --canon-space-xl + + Default value is `3.25 x space unit` + + + + --canon-space-xxl + + Default value is `5.25 x space unit` + + + + + diff --git a/packages/canon/docs/components/Banner/banner.css.ts b/packages/canon/docs/components/Banner/banner.css.ts new file mode 100644 index 0000000000..2dfe443f41 --- /dev/null +++ b/packages/canon/docs/components/Banner/banner.css.ts @@ -0,0 +1,61 @@ +/* + * 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 { globalStyle, style } from '@vanilla-extract/css'; + +export const bannerStyles = recipe({ + base: { + display: 'flex', + alignItems: 'center', + fontFamily: '"Geist", sans-serif', + fontSize: '16px', + lineHeight: '28px', + padding: '16px', + borderRadius: '6px', + marginBottom: '16px', + border: '1px solid #e0e0e0', + }, + variants: { + variant: { + info: { + backgroundColor: '#F2F2F2', + borderColor: '#CDCDCD', + color: '#888888', + }, + warning: { + backgroundColor: '#FFF2B9', + borderColor: '#FFD000', + color: '#D79927', + }, + }, + }, +}); + +globalStyle(`${bannerStyles.classNames.base} p`, { + margin: '0', +}); + +export const bannerIconStyles = style({ + width: '32px', + height: '32px', + backgroundColor: 'rgba(215, 153, 39, 0.2)', + borderRadius: '6px', + marginRight: '16px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}); diff --git a/packages/canon/docs/components/Banner/index.tsx b/packages/canon/docs/components/Banner/index.tsx new file mode 100644 index 0000000000..c3036eac03 --- /dev/null +++ b/packages/canon/docs/components/Banner/index.tsx @@ -0,0 +1,42 @@ +/* + * 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 { bannerIconStyles, bannerStyles } from './banner.css'; + +export const Banner = ({ + children, + variant = 'info', +}: { + children: React.ReactNode; + variant?: 'info' | 'warning'; +}) => { + return ( +
+
+ + + +
+ {children} +
+ ); +}; diff --git a/packages/canon/docs/components/Chip/chip.css.ts b/packages/canon/docs/components/Chip/chip.css.ts new file mode 100644 index 0000000000..027a722f84 --- /dev/null +++ b/packages/canon/docs/components/Chip/chip.css.ts @@ -0,0 +1,42 @@ +/* + * 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 chipStyles = recipe({ + base: { + display: 'inline-flex', + alignItems: 'center', + fontFamily: 'Geist Mono, monospace', + fontSize: '13px', + borderRadius: '6px', + padding: '0px 8px', + height: '24px', + marginRight: '4px', + }, + variants: { + head: { + true: { + backgroundColor: '#eaf2fd', + color: '#2563eb', + }, + false: { + backgroundColor: '#f0f0f0', + color: '#5d5d5d', + }, + }, + }, +}); diff --git a/packages/canon/docs/components/Chip/index.tsx b/packages/canon/docs/components/Chip/index.tsx new file mode 100644 index 0000000000..81609dda61 --- /dev/null +++ b/packages/canon/docs/components/Chip/index.tsx @@ -0,0 +1,28 @@ +/* + * 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, + head = false, +}: { + children: React.ReactNode; + head?: boolean; +}) => { + return {children}; +}; diff --git a/packages/canon/docs/components/Columns/columns.css.ts b/packages/canon/docs/components/Columns/columns.css.ts new file mode 100644 index 0000000000..6efe97c349 --- /dev/null +++ b/packages/canon/docs/components/Columns/columns.css.ts @@ -0,0 +1,24 @@ +/* + * 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', +}); diff --git a/packages/canon/docs/components/Columns/index.tsx b/packages/canon/docs/components/Columns/index.tsx new file mode 100644 index 0000000000..0d6f27f04d --- /dev/null +++ b/packages/canon/docs/components/Columns/index.tsx @@ -0,0 +1,32 @@ +/* + * 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 ( +
+ {children} +
+ ); +}; diff --git a/packages/canon/docs/components/ComponentStatus/component-status.css.ts b/packages/canon/docs/components/ComponentStatus/component-status.css.ts new file mode 100644 index 0000000000..eeb58865ff --- /dev/null +++ b/packages/canon/docs/components/ComponentStatus/component-status.css.ts @@ -0,0 +1,67 @@ +/* + * 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', + }, + }, + }, +}); diff --git a/packages/canon/docs/components/ComponentStatus/index.tsx b/packages/canon/docs/components/ComponentStatus/index.tsx new file mode 100644 index 0000000000..2444fcdc16 --- /dev/null +++ b/packages/canon/docs/components/ComponentStatus/index.tsx @@ -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 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 ( +
+ {link ? ( + + {name} + + ) : ( + {name} + )} + + {status === 'notStarted' && 'Not Started'} + {status === 'inProgress' && 'In Progress'} + {status === 'done' && 'Done'} + +
+ ); +}; diff --git a/packages/canon/docs/components/LayoutComponents/index.tsx b/packages/canon/docs/components/LayoutComponents/index.tsx new file mode 100644 index 0000000000..9e0b039684 --- /dev/null +++ b/packages/canon/docs/components/LayoutComponents/index.tsx @@ -0,0 +1,109 @@ +/* + * 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, + box, + content, + title, + description, + whiteBox, + whiteBoxStack, + stack, + verticalDivider, + horizontalDivider, + columns, + inline, + tiles, + whiteBoxColumns, + whiteBoxInline, + whiteBoxTiles, +} from './layout-components.css'; + +export const LayoutComponents = () => { + return ( +
+
+ +
+ +
Box
+
The most basic layout component.
+
+
+ +
+
+
+ +
Stack
+
Arrange your components vertically.
+
+
+ +
+
+
+ +
Columns
+
Arrange your components horizontally.
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
Inline
+
Arrange your components in a row.
+
+
+ +
+
+
+
+
+
+ + ); +}; diff --git a/packages/canon/docs/components/LayoutComponents/layout-components.css.ts b/packages/canon/docs/components/LayoutComponents/layout-components.css.ts new file mode 100644 index 0000000000..a8440dd01f --- /dev/null +++ b/packages/canon/docs/components/LayoutComponents/layout-components.css.ts @@ -0,0 +1,198 @@ +/* + * 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 container = style({ + display: 'flex', + justifyContent: 'flex-start', + gap: '1rem', + flexWrap: 'wrap', +}); + +export const box = style({ + display: 'flex', + flexDirection: 'column', + width: 'calc(33.33% - 0.67rem)', + marginBottom: '1rem', + alignItems: 'flex-start', +}); + +export const content = style({ + flex: 'none', + background: '#f2f2f2', + borderRadius: '4px', + width: '100%', + height: '180px', + border: '1px solid #e0e0e0', + transition: 'border-color 0.2s ease-in-out', + marginBottom: '0.75rem', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + selectors: { + [`${box}:hover &`]: { + borderColor: '#1f47ff', + }, + }, +}); + +export const title = style({ + fontSize: '16px', + fontFamily: 'var(--canon-font-sans)', + transition: 'color 0.2s ease-in-out', + marginBottom: '0.25rem', + selectors: { + [`${box}:hover &`]: { + color: '#1f47ff', + }, + }, +}); + +export const description = style({ + fontSize: '16px', + fontFamily: 'var(--canon-font-sans)', + color: '#9e9e9e', +}); + +export const whiteBox = style({ + background: '#fff', + width: '40px', + height: '40px', + borderRadius: '4px', + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', +}); + +export const whiteBoxStack = style([ + whiteBox, + { + width: '40%', + height: '28px', + }, +]); + +export const whiteBoxColumns = style([ + whiteBox, + { + width: '30%', + height: '32px', + }, +]); + +export const whiteBoxInline = style([ + whiteBox, + { + width: '32px', + height: '32px', + }, +]); + +export const whiteBoxTiles = style([ + whiteBox, + { + width: '100%', + height: '32px', + }, +]); + +export const stack = style({ + display: 'flex', + flexDirection: 'column', +}); + +export const columns = style({ + display: 'flex', + flexDirection: 'row', +}); + +export const inline = style({ + display: 'flex', + flexWrap: 'wrap', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'flex-start', + gap: '0.5rem', + paddingLeft: '1rem', +}); + +export const tiles = style({ + display: 'grid', + gridTemplateColumns: 'repeat(4, 1fr)', + gap: '0.5rem', + paddingLeft: '1rem', + paddingRight: '1rem', + width: '100%', +}); + +export const verticalDivider = style({ + display: 'flex', + flexShrink: 0, + flexDirection: 'column', + position: 'relative', + height: '20px', + width: '2px', + backgroundColor: '#1f47ff', + selectors: { + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: '-9px', + width: '20px', + height: '2px', + backgroundColor: '#1f47ff', + }, + '&::after': { + content: '""', + position: 'absolute', + bottom: 0, + left: '-9px', + width: '20px', + height: '2px', + backgroundColor: '#1f47ff', + }, + }, +}); + +export const horizontalDivider = style({ + flexShrink: 0, + display: 'flex', + flexDirection: 'row', + position: 'relative', + height: '2px', + width: '20px', + backgroundColor: '#1f47ff', + selectors: { + '&::before': { + content: '""', + position: 'absolute', + left: 0, + top: '-9px', + width: '2px', + height: '20px', + backgroundColor: '#1f47ff', + }, + '&::after': { + content: '""', + position: 'absolute', + right: 0, + top: '-9px', + width: '2px', + height: '20px', + backgroundColor: '#1f47ff', + }, + }, +}); diff --git a/packages/canon/docs/components/Table/index.tsx b/packages/canon/docs/components/Table/index.tsx new file mode 100644 index 0000000000..ead30985aa --- /dev/null +++ b/packages/canon/docs/components/Table/index.tsx @@ -0,0 +1,56 @@ +/* + * 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 ( +
+ {children}
+
+ ); +}; + +export const Header = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +export const Body = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +export const HeaderRow = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +export const HeaderCell = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +export const Row = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +export const Cell = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; diff --git a/packages/canon/docs/components/Table/table.css.ts b/packages/canon/docs/components/Table/table.css.ts new file mode 100644 index 0000000000..b01373ee04 --- /dev/null +++ b/packages/canon/docs/components/Table/table.css.ts @@ -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 { style, globalStyle } 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, +}); + +// Apply global styles to

elements inside cellStyles +globalStyle(`${cellStyles} p`, { + margin: '0', +}); + +export const headerRowStyles = style({ + borderBottom: '1px solid #e7e7e7', +}); + +export const headerCellStyles = style({ + backgroundColor: '#f5f5f5 !important', + borderBottom: '1px solid #e7e7e7 !important', + fontWeight: 500, + fontSize: '14px', +}); + +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', +}); diff --git a/packages/canon/docs/components/Text/index.tsx b/packages/canon/docs/components/Text/index.tsx new file mode 100644 index 0000000000..be882c0a81 --- /dev/null +++ b/packages/canon/docs/components/Text/index.tsx @@ -0,0 +1,32 @@ +/* + * 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 ( +

+ {children} +
+ ); +}; diff --git a/packages/canon/docs/components/Text/text.css.ts b/packages/canon/docs/components/Text/text.css.ts new file mode 100644 index 0000000000..01b4cb8fc8 --- /dev/null +++ b/packages/canon/docs/components/Text/text.css.ts @@ -0,0 +1,34 @@ +/* + * 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: '16px', + lineHeight: '28px', + margin: '0', + fontWeight: 300, + paddingBottom: '16px', +}); + +globalStyle(`${textStyles} p`, { + fontFamily: '"Geist", sans-serif', + fontSize: '16px', + lineHeight: '28px', + margin: '0', + fontWeight: 300, +}); diff --git a/packages/canon/docs/components/Title/index.tsx b/packages/canon/docs/components/Title/index.tsx new file mode 100644 index 0000000000..13e5b964bb --- /dev/null +++ b/packages/canon/docs/components/Title/index.tsx @@ -0,0 +1,39 @@ +/* + * 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, + }); +}; diff --git a/packages/canon/docs/components/Title/title.css.ts b/packages/canon/docs/components/Title/title.css.ts new file mode 100644 index 0000000000..5ca0394af6 --- /dev/null +++ b/packages/canon/docs/components/Title/title.css.ts @@ -0,0 +1,48 @@ +/* + * 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: '36px', + lineHeight: '44px', + marginBottom: '32px', + marginTop: '0px', + }, + h2: { + fontSize: '24px', + lineHeight: '32px', + marginBottom: '12px', + marginTop: '52px', + }, + h3: { + fontSize: '20px', + lineHeight: '28px', + marginBottom: '12px', + marginTop: '52px', + }, + }, + }, +}); diff --git a/packages/canon/docs/components/index.ts b/packages/canon/docs/components/index.ts new file mode 100644 index 0000000000..6acc4910cb --- /dev/null +++ b/packages/canon/docs/components/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './Chip'; +export * from './Table'; +export * from './Title'; +export * from './Text'; +export * from './Columns'; +export * from './ComponentStatus'; +export * from './LayoutComponents'; +export * from './Banner'; diff --git a/packages/canon/package.json b/packages/canon/package.json new file mode 100644 index 0000000000..f1e2e5d759 --- /dev/null +++ b/packages/canon/package.json @@ -0,0 +1,84 @@ +{ + "name": "@backstage/canon", + "version": "0.0.0", + "backstage": { + "role": "web-library" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "private": true, + "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", + "build-storybook": "storybook build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "storybook dev -p 6006", + "test": "backstage-cli package test" + }, + "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", + "lucide-react": "^0.460.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@chromatic-com/storybook": "^3.2.2", + "@storybook/addon-essentials": "^8.4.5", + "@storybook/addon-interactions": "^8.4.5", + "@storybook/addon-styling-webpack": "^1.0.1", + "@storybook/addon-themes": "^8.4.5", + "@storybook/addon-webpack5-compiler-swc": "^1.0.5", + "@storybook/blocks": "^8.4.5", + "@storybook/react": "^8.4.5", + "@storybook/react-webpack5": "^8.4.5", + "@storybook/test": "^8.4.5", + "@testing-library/jest-dom": "^6.0.0", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "@vanilla-extract/rollup-plugin": "^1.3.10", + "@vanilla-extract/webpack-plugin": "^2.3.14", + "eslint-plugin-storybook": "^0.11.1", + "globals": "^15.11.0", + "mini-css-extract-plugin": "^2.9.2", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router-dom": "^6.3.0", + "storybook": "^8.4.5", + "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 + } + } +} diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md new file mode 100644 index 0000000000..7106d5eedb --- /dev/null +++ b/packages/canon/report.api.md @@ -0,0 +1,1517 @@ +## API Report File for "@backstage/canon" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { CSSProperties } from 'react'; +import { JSXElementConstructor } from 'react'; +import { default as React_2 } from 'react'; +import { ReactElement } from 'react'; +import { ReactNode } from 'react'; +import { ResponsiveArray } from '@vanilla-extract/sprinkles'; + +// @public (undocumented) +export const Box: (props: BoxProps) => ReactElement< + { + className: string; + style: CSSProperties | undefined; + }, + string | JSXElementConstructor +>; + +// @public +export type BoxProps = Parameters[0] & + Omit< + React.AllHTMLAttributes, + keyof Parameters[0] + > & { + as?: keyof JSX.IntrinsicElements; + }; + +// @public (undocumented) +export const boxSprinkles: (( + props: { + flexDirection?: + | ( + | 'column' + | 'row' + | { + xs?: 'column' | 'row' | undefined; + sm?: 'column' | 'row' | undefined; + md?: 'column' | 'row' | undefined; + lg?: 'column' | 'row' | undefined; + xl?: 'column' | 'row' | undefined; + '2xl'?: 'column' | 'row' | undefined; + } + | undefined + ) + | ResponsiveArray<2 | 1 | 3 | 4 | 5 | 6, 'column' | 'row' | null>; + justifyContent?: + | ( + | 'center' + | 'space-around' + | 'space-between' + | 'stretch' + | 'flex-end' + | 'flex-start' + | { + xs?: + | 'center' + | 'space-around' + | 'space-between' + | 'stretch' + | 'flex-end' + | 'flex-start' + | undefined; + sm?: + | 'center' + | 'space-around' + | 'space-between' + | 'stretch' + | 'flex-end' + | 'flex-start' + | undefined; + md?: + | 'center' + | 'space-around' + | 'space-between' + | 'stretch' + | 'flex-end' + | 'flex-start' + | undefined; + lg?: + | 'center' + | 'space-around' + | 'space-between' + | 'stretch' + | 'flex-end' + | 'flex-start' + | undefined; + xl?: + | 'center' + | 'space-around' + | 'space-between' + | 'stretch' + | 'flex-end' + | 'flex-start' + | undefined; + '2xl'?: + | 'center' + | 'space-around' + | 'space-between' + | 'stretch' + | 'flex-end' + | 'flex-start' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + | 'center' + | 'space-around' + | 'space-between' + | 'stretch' + | 'flex-end' + | 'flex-start' + | null + >; + alignItems?: + | ( + | 'center' + | 'stretch' + | 'flex-end' + | 'flex-start' + | { + xs?: 'center' | 'stretch' | 'flex-end' | 'flex-start' | undefined; + sm?: 'center' | 'stretch' | 'flex-end' | 'flex-start' | undefined; + md?: 'center' | 'stretch' | 'flex-end' | 'flex-start' | undefined; + lg?: 'center' | 'stretch' | 'flex-end' | 'flex-start' | undefined; + xl?: 'center' | 'stretch' | 'flex-end' | 'flex-start' | undefined; + '2xl'?: + | 'center' + | 'stretch' + | 'flex-end' + | 'flex-start' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'center' | 'stretch' | 'flex-end' | 'flex-start' | null + >; + borderRadius?: + | ( + | 'small' + | 'none' + | 'medium' + | 'full' + | { + xs?: 'small' | 'none' | 'medium' | 'full' | undefined; + sm?: 'small' | 'none' | 'medium' | 'full' | undefined; + md?: 'small' | 'none' | 'medium' | 'full' | undefined; + lg?: 'small' | 'none' | 'medium' | 'full' | undefined; + xl?: 'small' | 'none' | 'medium' | 'full' | undefined; + '2xl'?: 'small' | 'none' | 'medium' | 'full' | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'small' | 'none' | 'medium' | 'full' | null + >; + boxShadow?: + | ( + | 'small' + | 'medium' + | 'large' + | { + xs?: 'small' | 'medium' | 'large' | undefined; + sm?: 'small' | 'medium' | 'large' | undefined; + md?: 'small' | 'medium' | 'large' | undefined; + lg?: 'small' | 'medium' | 'large' | undefined; + xl?: 'small' | 'medium' | 'large' | undefined; + '2xl'?: 'small' | 'medium' | 'large' | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'small' | 'medium' | 'large' | null + >; + border?: + | ( + | 'none' + | 'error' + | 'thin' + | { + xs?: 'none' | 'error' | 'thin' | undefined; + sm?: 'none' | 'error' | 'thin' | undefined; + md?: 'none' | 'error' | 'thin' | undefined; + lg?: 'none' | 'error' | 'thin' | undefined; + xl?: 'none' | 'error' | 'thin' | undefined; + '2xl'?: 'none' | 'error' | 'thin' | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'error' | 'thin' | null + >; + display?: + | ( + | 'flex' + | 'inline' + | 'none' + | 'block' + | { + xs?: 'flex' | 'inline' | 'none' | 'block' | undefined; + sm?: 'flex' | 'inline' | 'none' | 'block' | undefined; + md?: 'flex' | 'inline' | 'none' | 'block' | undefined; + lg?: 'flex' | 'inline' | 'none' | 'block' | undefined; + xl?: 'flex' | 'inline' | 'none' | 'block' | undefined; + '2xl'?: 'flex' | 'inline' | 'none' | 'block' | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'flex' | 'inline' | 'none' | 'block' | null + >; + paddingTop?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + paddingBottom?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + paddingLeft?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + paddingRight?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + marginTop?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + marginBottom?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + marginLeft?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + marginRight?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + gap?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + flexWrap?: + | ( + | 'nowrap' + | 'wrap' + | { + xs?: 'nowrap' | 'wrap' | undefined; + sm?: 'nowrap' | 'wrap' | undefined; + md?: 'nowrap' | 'wrap' | undefined; + lg?: 'nowrap' | 'wrap' | undefined; + xl?: 'nowrap' | 'wrap' | undefined; + '2xl'?: 'nowrap' | 'wrap' | undefined; + } + | undefined + ) + | ResponsiveArray<2 | 1 | 3 | 4 | 5 | 6, 'nowrap' | 'wrap' | null>; + padding?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + paddingX?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + paddingY?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + margin?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + marginX?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + marginY?: + | ( + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | { + xs?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + sm?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + md?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + lg?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + xl?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + '2xl'?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'xxs' + | 'xxl' + | undefined; + } + | undefined + ) + | ResponsiveArray< + 2 | 1 | 3 | 4 | 5 | 6, + 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxs' | 'xxl' | null + >; + } & { + color?: + | 'error' + | 'primary' + | 'secondary' + | { + light?: 'error' | 'primary' | 'secondary' | undefined; + dark?: 'error' | 'primary' | 'secondary' | undefined; + } + | undefined; + background?: + | 'transparent' + | 'background' + | 'elevation1' + | 'elevation2' + | { + light?: + | 'transparent' + | 'background' + | 'elevation1' + | 'elevation2' + | undefined; + dark?: + | 'transparent' + | 'background' + | 'elevation1' + | 'elevation2' + | undefined; + } + | undefined; + }, +) => string) & { + properties: Set< + | 'alignItems' + | 'background' + | 'border' + | 'borderRadius' + | 'boxShadow' + | 'color' + | 'display' + | 'flexDirection' + | 'flexWrap' + | 'gap' + | 'justifyContent' + | 'margin' + | 'marginBottom' + | 'marginLeft' + | 'marginRight' + | 'marginTop' + | 'padding' + | 'paddingBottom' + | 'paddingLeft' + | 'paddingRight' + | 'paddingTop' + | 'marginX' + | 'marginY' + | 'paddingX' + | 'paddingY' + >; +}; + +// @public (undocumented) +export const Button: ({ + size, + variant, + children, + disabled, + iconStart, + iconEnd, + ...props +}: ButtonProps) => React_2.JSX.Element; + +// @public +export interface ButtonProps { + // (undocumented) + children: React_2.ReactNode; + // (undocumented) + disabled?: boolean; + // (undocumented) + iconEnd?: IconNames; + // (undocumented) + iconStart?: IconNames; + // (undocumented) + size?: 'small' | 'medium'; + // (undocumented) + variant?: 'primary' | 'secondary'; +} + +// @public (undocumented) +export const Icon: ({ name }: { name: IconNames }) => React_2.JSX.Element; + +// @public (undocumented) +export type IconNames = + | 'ArrowDown' + | 'ArrowLeft' + | 'ArrowRight' + | 'ArrowUp' + | 'Cloud' + | 'CustomIcon'; + +// @public (undocumented) +export const IconProvider: ({ + children, + overrides, +}: { + children: ReactNode; + overrides: Partial>; +}) => React_2.JSX.Element; +``` diff --git a/packages/canon/src/components/Box/Box.stories.tsx b/packages/canon/src/components/Box/Box.stories.tsx new file mode 100644 index 0000000000..8b8a2d7c56 --- /dev/null +++ b/packages/canon/src/components/Box/Box.stories.tsx @@ -0,0 +1,229 @@ +/* + * 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'; +import { listResponsiveValues } from '../../utils/list-values'; +import { responsiveProperties, colorProperties } from './sprinkles.css'; + +const argTypesResponsive = Object.keys(responsiveProperties.styles).reduce< + Record +>((acc, n) => { + if ( + [ + 'margin', + 'marginBottom', + 'marginLeft', + 'marginRight', + 'marginTop', + 'marginX', + 'marginY', + 'padding', + 'paddingBottom', + 'paddingLeft', + 'paddingRight', + 'paddingTop', + 'paddingX', + 'paddingY', + ].includes(n) + ) { + acc[n] = { + control: 'select', + options: ['xs', 'sm', 'md', 'lg', 'xl', '2xl'], + }; + } else { + acc[n] = { + control: 'select', + options: listResponsiveValues( + n as keyof typeof responsiveProperties.styles, + ), + }; + } + return acc; +}, {}); + +const argTypesColor = Object.keys(colorProperties.styles).reduce< + Record +>((acc, n) => { + acc[n as keyof typeof colorProperties.styles] = { + control: 'select', + options: Object.keys( + colorProperties.styles[n as keyof typeof colorProperties.styles].values, + ), + }; + return acc; +}, {}); + +const argTypes = { + ...argTypesResponsive, + ...argTypesColor, +}; + +// Sort the resulting object keys alphabetically +const sortedArgTypes = Object.keys(argTypes) + .sort() + .reduce((acc, key) => { + acc[key] = argTypes[key]; + return acc; + }, {} as Record); + +// Add 'as' and 'children' to the sortedArgTypes +sortedArgTypes['as'] = { + control: 'select', + options: ['div', 'span', 'article', 'section'], +}; + +sortedArgTypes['children'] = { + control: 'text', +}; + +const meta = { + title: 'Components/Box', + component: Box, + parameters: { + layout: 'centered', + }, + argTypes: sortedArgTypes, + args: { + as: 'div', + background: 'elevation1', + borderRadius: 'small', + children: 'Basic Box', + display: 'block', + padding: 'sm', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: {}, +}; + +export const Responsive: Story = { + render: () => ( + + Responsive Box + + ), +}; + +export const FlexContainer: Story = { + render: () => ( + + + Item 1 + + + Item 2 + + + Item 3 + + + ), +}; + +export const Nested: Story = { + render: () => ( + + + Header + + + + Sidebar + + + Main Content + + + + Footer + + + ), +}; + +export const Alignment: Story = { + render: () => ( + + + Centered Content + + + ), +}; + +// Example showing different spacing combinations +export const Spacing: Story = { + render: () => ( + + + Small Padding + + + Medium Padding + + + Large Padding + + + Mixed Padding + + + ), +}; + +// Example showing different display values +export const DisplayVariants: Story = { + render: () => ( + + + Display Block + + + Display Flex + + + Display Inline + + + ), +}; diff --git a/packages/canon/src/components/Box/Box.tsx b/packages/canon/src/components/Box/Box.tsx new file mode 100644 index 0000000000..d1cb289506 --- /dev/null +++ b/packages/canon/src/components/Box/Box.tsx @@ -0,0 +1,62 @@ +/* + * 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 { boxSprinkles } from './sprinkles.css'; +import { base } from './box.css'; + +/** + * Properties for {@link Box} + * + * @public + */ +export type BoxProps = Parameters[0] & + Omit< + React.AllHTMLAttributes, + keyof Parameters[0] + > & { + as?: keyof JSX.IntrinsicElements; + }; + +/** @public */ +export const Box = (props: BoxProps) => { + const { as = 'div', className, style, ...rest } = props; + const boxSprinklesProps: Record = {}; + const nativeProps: Record = {}; + + // Split props between sprinkles and native HTML props + Object.entries(rest).forEach(([key, value]) => { + if (value === undefined) return; + + if ( + boxSprinkles.properties.has( + key as keyof Parameters[0], + ) + ) { + boxSprinklesProps[key] = value; + } else { + nativeProps[key] = value; + } + }); + + const sprinklesClassName = boxSprinkles(boxSprinklesProps); + + return createElement(as, { + className: [base, sprinklesClassName, className].filter(Boolean).join(' '), + style, + ...nativeProps, + }); +}; diff --git a/packages/canon/src/components/Box/box.css.ts b/packages/canon/src/components/Box/box.css.ts new file mode 100644 index 0000000000..92af783eec --- /dev/null +++ b/packages/canon/src/components/Box/box.css.ts @@ -0,0 +1,22 @@ +/* + * 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)', + color: 'var(--canon-text-primary)', +}); diff --git a/packages/canon/src/components/Box/box.mdx b/packages/canon/src/components/Box/box.mdx new file mode 100644 index 0000000000..a3ce48b06d --- /dev/null +++ b/packages/canon/src/components/Box/box.mdx @@ -0,0 +1,38 @@ +import { Canvas, Meta, Unstyled } from '@storybook/blocks'; +import * as BoxStories from './Box.stories'; +import { Chip } from '../../../docs/components/Chip'; +import * as Table from '../../../docs/components/Table'; +import { Box } from './Box'; +import { Stack } from '../Stack'; +import { Title, Text } from '../../../docs/components'; +import { Padding } from './docs/padding'; +import { listResponsiveValues, listColorValues } from '../../utils/list-values'; +import { responsiveProperties } from './sprinkles.css'; +import { PropsTable } from './docs/props-table'; +import { SpacingTable } from './docs/spacing-table'; + + + + + +Box + + Box is the lowest-level component in Canon. We use it internally to build all + of our components. It is a wrapper around the HTML `
` element and + provides a consistent API for styling and layout. You'll find below a list of all the available properties. + + + + +Padding & Margin + + 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. + + + + + + + diff --git a/packages/canon/src/components/Box/docs/padding.tsx b/packages/canon/src/components/Box/docs/padding.tsx new file mode 100644 index 0000000000..3f78f56692 --- /dev/null +++ b/packages/canon/src/components/Box/docs/padding.tsx @@ -0,0 +1,105 @@ +/* + * 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 { Stack } from '../../Stack'; +import { Inline } from '../../Inline'; +import { Box } from '../Box'; + +const FakeBox = ({ children }: { children: string }) => ( + + {children} + +); + +export const Padding = () => { + return ( + + + + padding + + + paddingX + + + paddingY + + + + + paddingTop + + + paddingBottom + + + paddingLeft + + + paddingRight + + + + ); +}; diff --git a/packages/canon/src/components/Box/docs/props-table.tsx b/packages/canon/src/components/Box/docs/props-table.tsx new file mode 100644 index 0000000000..bcdda93f2a --- /dev/null +++ b/packages/canon/src/components/Box/docs/props-table.tsx @@ -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 React from 'react'; +import * as Table from '../../../../docs/components'; +import { Chip } from '../../../../docs/components'; +import { listResponsiveValues } from '../../../utils/list-values'; +import { responsiveProperties } from '../sprinkles.css'; + +export const PropsTable = () => { + return ( + + + + Prop + Type + + + + {Object.keys(responsiveProperties.styles) + .filter( + n => + ![ + 'padding', + 'paddingX', + 'paddingY', + 'paddingLeft', + 'paddingRight', + 'paddingTop', + 'paddingBottom', + 'margin', + 'marginX', + 'marginY', + 'marginLeft', + 'marginRight', + 'marginTop', + 'marginBottom', + ].includes(n), + ) + .map(n => ( + + + {n} + + + {listResponsiveValues( + n as keyof typeof responsiveProperties.styles, + ).map(value => ( + {value} + ))} + + + ))} + + + ); +}; diff --git a/packages/canon/src/components/Box/docs/spacing-table.tsx b/packages/canon/src/components/Box/docs/spacing-table.tsx new file mode 100644 index 0000000000..7d98576b0e --- /dev/null +++ b/packages/canon/src/components/Box/docs/spacing-table.tsx @@ -0,0 +1,71 @@ +/* + * 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 * as Table from '../../../../docs/components'; +import { Chip } from '../../../../docs/components'; +import { listResponsiveValues } from '../../../utils/list-values'; +import { responsiveProperties } from '../sprinkles.css'; + +console.log(responsiveProperties.styles); + +export const SpacingTable = () => { + return ( + + + + Prop + Type + + + + {Object.keys(responsiveProperties.styles) + .filter(n => + [ + 'padding', + 'paddingX', + 'paddingY', + 'paddingLeft', + 'paddingRight', + 'paddingTop', + 'paddingBottom', + 'margin', + 'marginX', + 'marginY', + 'marginLeft', + 'marginRight', + 'marginTop', + 'marginBottom', + ].includes(n), + ) + .map(n => ( + + + {n} + + + {listResponsiveValues( + 'paddingTop' as keyof typeof responsiveProperties.styles, + ).map(value => ( + {value} + ))} + + + ))} + + + ); +}; diff --git a/packages/canon/src/components/Box/index.tsx b/packages/canon/src/components/Box/index.tsx new file mode 100644 index 0000000000..589528cd1d --- /dev/null +++ b/packages/canon/src/components/Box/index.tsx @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { Box } from './Box'; +export type { BoxProps } from './Box'; +export { boxSprinkles } from './sprinkles.css'; diff --git a/packages/canon/src/components/Box/properties.ts b/packages/canon/src/components/Box/properties.ts new file mode 100644 index 0000000000..ccf6849976 --- /dev/null +++ b/packages/canon/src/components/Box/properties.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const breakpoints = { + 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)' }, +}; + +export const space = { + none: 0, + xxs: 'var(--canon-space-xxs)', + xs: 'var(--canon-space-xs)', + sm: 'var(--canon-space-sm)', + md: 'var(--canon-space-md)', + lg: 'var(--canon-space-lg)', + xl: 'var(--canon-space-xl)', + xxl: 'var(--canon-space-xxl)', +}; diff --git a/packages/canon/src/components/Box/sprinkles.css.ts b/packages/canon/src/components/Box/sprinkles.css.ts new file mode 100644 index 0000000000..12a8118316 --- /dev/null +++ b/packages/canon/src/components/Box/sprinkles.css.ts @@ -0,0 +1,112 @@ +/* + * 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, + RequiredConditionalValue, + ConditionalValue, + createMapValueFn, +} from '@vanilla-extract/sprinkles'; +import { breakpoints, space } from './properties'; + +export const responsiveProperties = defineProperties({ + conditions: breakpoints, + defaultCondition: 'xs', + responsiveArray: ['xs', 'sm', 'md', 'lg', 'xl', '2xl'], + properties: { + flexDirection: ['row', 'column'], + justifyContent: [ + 'stretch', + 'flex-start', + 'center', + 'flex-end', + 'space-around', + 'space-between', + ], + alignItems: ['stretch', 'flex-start', 'center', 'flex-end'], + borderRadius: { + none: 0, + small: '4px', + medium: '8px', + full: '9999px', + }, + boxShadow: { + small: 'var(--canon-box-shadow-small)', + medium: 'var(--canon-box-shadow-medium)', + large: 'var(--canon-box-shadow-large)', + }, + border: { + none: 'none', + thin: '1px solid var(--canon-outline)', + error: '1px solid var(--canon-error)', + }, + display: ['none', 'flex', 'block', 'inline'], + paddingTop: space, + paddingBottom: space, + paddingLeft: space, + paddingRight: space, + marginTop: space, + marginBottom: space, + marginLeft: space, + marginRight: space, + gap: space, + flexWrap: ['wrap', 'nowrap'], + }, + shorthands: { + padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'], + paddingX: ['paddingLeft', 'paddingRight'], + paddingY: ['paddingTop', 'paddingBottom'], + margin: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'], + marginX: ['marginLeft', 'marginRight'], + marginY: ['marginTop', 'marginBottom'], + }, +}); + +export const colorProperties = defineProperties({ + conditions: { + light: { selector: '[data-theme="light"] &' }, + dark: { selector: '[data-theme="dark"] &' }, + }, + defaultCondition: ['light', 'dark'], + properties: { + color: { + primary: 'var(--canon-text-primary)', + secondary: 'var(--canon-text-secondary)', + error: 'var(--canon-error)', + }, + background: { + background: 'var(--canon-background)', + elevation1: 'var(--canon-surface-1)', + elevation2: 'var(--canon-surface-2)', + transparent: 'transparent', + }, + }, +}); + +/** @public */ +export const boxSprinkles = createSprinkles( + responsiveProperties, + colorProperties, +); + +export type OptionalResponsiveValue = + ConditionalValue; + +export type RequiredResponsiveValue = + RequiredConditionalValue; + +export const mapResponsiveValue = createMapValueFn(responsiveProperties); diff --git a/packages/canon/src/components/Button/Button.stories.tsx b/packages/canon/src/components/Button/Button.stories.tsx new file mode 100644 index 0000000000..7359d36f10 --- /dev/null +++ b/packages/canon/src/components/Button/Button.stories.tsx @@ -0,0 +1,121 @@ +/* + * 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'; +import { Box } from '../Box/Box'; + +const meta = { + title: 'Components/Button', + component: Button, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + size: { + control: 'select', + options: ['small', 'medium'], + }, + }, + args: { + size: 'medium', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +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 => ( +
+
+ ), +}; + +export const WithIcons: Story = { + args: { + children: 'Button', + }, + render: args => ( +
+
+ ), +}; + +export const FullWidth: Story = { + args: { + children: 'Button', + }, + render: args => ( + +