Merge pull request #27981 from backstage/canon-layout-components

Canon: Add missing layout components
This commit is contained in:
Charles de Dreuille
2024-12-05 13:33:27 +00:00
committed by GitHub
37 changed files with 1910 additions and 383 deletions
+26 -16
View File
@@ -43,33 +43,43 @@ import { list } from './components/Roadmap/list';
link="/?path=/docs/components-box--docs"
/>
<ComponentStatus
name="Stack"
name="Button"
status="inProgress"
link="/?path=/docs/components-button--docs"
/>
<ComponentStatus name="Checkbox" status="notStarted" />
<ComponentStatus
name="Container"
status="inProgress"
link="/?path=/docs/components-container--docs"
/>
<ComponentStatus
name="Grid"
status="alpha"
link="/?path=/docs/components-stack--default"
link="/?path=/story/components-grid--docs"
/>
<ComponentStatus name="Header" status="notStarted" />
<ComponentStatus
name="Icon"
status="alpha"
link="/?path=/docs/components-icon--docs"
/>
<ComponentStatus
name="Inline"
status="alpha"
link="/?path=/story/components-inline--default"
/>
<ComponentStatus
name="Button"
status="inProgress"
link="/?path=/docs/components-button--docs"
/>
<ComponentStatus
name="Icon"
status="alpha"
link="/?path=/docs/components-icon--docs"
/>
<ComponentStatus name="Input" status="notStarted" />
<ComponentStatus name="Select" status="notStarted" />
<ComponentStatus name="Checkbox" status="notStarted" />
<ComponentStatus name="Radio" status="notStarted" />
<ComponentStatus name="Select" status="notStarted" />
<ComponentStatus
name="Stack"
status="alpha"
link="/?path=/docs/components-stack--default"
/>
<ComponentStatus name="Switch" status="notStarted" />
<ComponentStatus name="Tooltip" status="notStarted" />
<ComponentStatus name="Header" status="notStarted" />
<ComponentStatus name="Tabs" status="notStarted" />
<ComponentStatus name="Tooltip" status="notStarted" />
</Columns>
<Title style={{ marginTop: '48px' }} type="h2">
+6 -1
View File
@@ -42,6 +42,11 @@ import * as Table from './components/Table';
<Title type="h2">Layout Helpers</Title>
<Text>TBD</Text>
<Text>
Sometimes you want to use global tokens dynamically outside of React
components. To help you with that we would like to provide a set of helpers
that you can use in your code. These helpers are not available just yet but we
are working on it.
</Text>
</Unstyled>
@@ -0,0 +1,54 @@
/*
* 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';
export const BoxSvg = () => {
return (
<svg
width="100"
height="60"
viewBox="0 0 100 60"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="100" height="60" rx="4" fill="black" fill-opacity="0.06" />
<path
d="M94.5 0.5H96C97.933 0.5 99.5 2.067 99.5 4V5.5"
stroke="#4765FF"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M99.5 54.5L99.5 56C99.5 57.933 97.933 59.5 96 59.5L94.5 59.5"
stroke="#4765FF"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M5.5 59.5L4 59.5C2.067 59.5 0.5 57.933 0.5 56L0.5 54.5"
stroke="#4765FF"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M0.5 5.5L0.5 4C0.5 2.067 2.067 0.5 4 0.5L5.5 0.500001"
stroke="#4765FF"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
};
@@ -0,0 +1,373 @@
/*
* 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';
export const ContainerSvg = () => {
return (
<svg
width="126"
height="111"
viewBox="0 0 126 111"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<mask
id="mask0_1922_1559"
// style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="13"
height="111"
>
<rect width="13" height="111" fill="url(#paint0_linear_1922_1559)" />
</mask>
<g mask="url(#mask0_1922_1559)">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 44.7563L13.0259 51.6995L12.5259 52.5655L0.5 45.6224L1 44.7563Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 50.5655L13.0259 57.5087L12.5259 58.3747L0.5 51.4316L1 50.5655Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 56.3747L13.0259 63.3179L12.5259 64.1839L0.5 57.2408L1 56.3747Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 62.1839L13.0259 69.1271L12.5259 69.9931L0.5 63.05L1 62.1839Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 67.9931L13.0259 74.9363L12.5259 75.8023L0.5 68.8592L1 67.9931Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 73.8023L13.0259 80.7455L12.5259 81.6116L0.5 74.6684L1 73.8023Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 79.6115L13.0259 86.5547L12.5259 87.4208L0.5 80.4776L1 79.6115Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 85.4208L13.0259 92.3639L12.5259 93.23L0.5 86.2868L1 85.4208Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 91.23L13.0259 98.1731L12.5259 99.0392L0.5 92.096L1 91.23Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 97.0392L13.0259 103.982L12.5259 104.848L0.5 97.9052L1 97.0392Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 102.848L13.0259 109.792L12.5259 110.658L0.5 103.714L1 102.848Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 108.658L13.0259 115.601L12.5259 116.467L0.5 109.524L1 108.658Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 38.9471L13.0259 45.8903L12.5259 46.7563L0.5 39.8131L1 38.9471Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 33.1379L13.0259 40.0811L12.5259 40.9471L0.5 34.0039L1 33.1379Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 27.3287L13.0259 34.2719L12.5259 35.1379L0.5 28.1947L1 27.3287Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 21.5195L13.0259 28.4627L12.5259 29.3287L0.5 22.3855L1 21.5195Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 15.7103L13.0259 22.6535L12.5259 23.5195L0.5 16.5763L1 15.7103Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 9.90109L13.0259 16.8443L12.5259 17.7103L0.5 10.7671L1 9.90109Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 4.09188L13.0259 11.0351L12.5259 11.9011L0.5 4.95791L1 4.09188Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 -1.71732L13.0259 5.22586L12.5259 6.09188L0.5 -0.851298L1 -1.71732Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 -7.52653L13.0259 -0.583351L12.5259 0.282675L0.5 -6.66051L1 -7.52653Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0 131L0 -17H1L1 131H0Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 131L12 -17H13L13 131H12Z"
fill="#4765FF"
/>
</g>
<rect
x="13"
y="25.5"
width="100"
height="60"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<mask
id="mask1_1922_1559"
// style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="113"
y="0"
width="13"
height="111"
>
<rect
x="113"
width="13"
height="111"
fill="url(#paint1_linear_1922_1559)"
/>
</mask>
<g mask="url(#mask1_1922_1559)">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 44.7563L126.026 51.6995L125.526 52.5655L113.5 45.6224L114 44.7563Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 50.5655L126.026 57.5087L125.526 58.3747L113.5 51.4316L114 50.5655Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 56.3747L126.026 63.3179L125.526 64.1839L113.5 57.2408L114 56.3747Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 62.1839L126.026 69.1271L125.526 69.9931L113.5 63.05L114 62.1839Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 67.9931L126.026 74.9363L125.526 75.8023L113.5 68.8592L114 67.9931Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 73.8023L126.026 80.7455L125.526 81.6116L113.5 74.6684L114 73.8023Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 79.6115L126.026 86.5547L125.526 87.4208L113.5 80.4776L114 79.6115Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 85.4208L126.026 92.3639L125.526 93.23L113.5 86.2868L114 85.4208Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 91.23L126.026 98.1731L125.526 99.0392L113.5 92.096L114 91.23Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 97.0392L126.026 103.982L125.526 104.848L113.5 97.9052L114 97.0392Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 102.848L126.026 109.792L125.526 110.658L113.5 103.714L114 102.848Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 108.658L126.026 115.601L125.526 116.467L113.5 109.524L114 108.658Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 38.9471L126.026 45.8903L125.526 46.7563L113.5 39.8131L114 38.9471Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 33.1379L126.026 40.0811L125.526 40.9471L113.5 34.0039L114 33.1379Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 27.3287L126.026 34.2719L125.526 35.1379L113.5 28.1947L114 27.3287Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 21.5195L126.026 28.4627L125.526 29.3287L113.5 22.3855L114 21.5195Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 15.7103L126.026 22.6535L125.526 23.5195L113.5 16.5763L114 15.7103Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 9.90109L126.026 16.8443L125.526 17.7103L113.5 10.7671L114 9.90109Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 4.09188L126.026 11.0351L125.526 11.9011L113.5 4.95791L114 4.09188Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 -1.71732L126.026 5.22586L125.526 6.09188L113.5 -0.851298L114 -1.71732Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M114 -7.52653L126.026 -0.583351L125.526 0.282675L113.5 -6.66051L114 -7.52653Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M113 131L113 -17H114L114 131H113Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M125 131L125 -17H126L126 131H125Z"
fill="#4765FF"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_1922_1559"
x1="6.5"
y1="0"
x2="6.5"
y2="111"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.05" stop-color="white" stop-opacity="0" />
<stop offset="0.2" stop-color="white" />
<stop offset="0.8" stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient
id="paint1_linear_1922_1559"
x1="119.5"
y1="0"
x2="119.5"
y2="111"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.05" stop-color="white" stop-opacity="0" />
<stop offset="0.2" stop-color="white" />
<stop offset="0.8" stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
</defs>
</svg>
);
};
@@ -0,0 +1,91 @@
/*
* 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';
export const GridSvg = () => {
return (
<svg
width="166"
height="61"
viewBox="0 0 166 61"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="0.5"
width="52"
height="61"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M53.5 30L64.5 30V31L53.5 31L53.5 30Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M64.5 24.5C64.5 24.2239 64.7239 24 65 24C65.2761 24 65.5 24.2239 65.5 24.5L65.5 36.5C65.5 36.7761 65.2761 37 65 37C64.7239 37 64.5 36.7761 64.5 36.5L64.5 24.5Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M52.5 24.5C52.5 24.2239 52.7239 24 53 24C53.2761 24 53.5 24.2239 53.5 24.5L53.5 36.5C53.5 36.7761 53.2761 37 53 37C52.7239 37 52.5 36.7761 52.5 36.5L52.5 24.5Z"
fill="#4765FF"
/>
<rect
x="65.5"
y="0.5"
width="100"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M115 36.5L115 25.5L116 25.5L116 36.5L115 36.5Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M109.5 25.5C109.224 25.5 109 25.2761 109 25V25C109 24.7239 109.224 24.5 109.5 24.5L121.5 24.5C121.776 24.5 122 24.7239 122 25V25C122 25.2761 121.776 25.5 121.5 25.5L109.5 25.5Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M109.5 36.5C109.224 36.5 109 36.2761 109 36V36C109 35.7239 109.224 35.5 109.5 35.5L121.5 35.5C121.776 35.5 122 35.7239 122 36V36C122 36.2761 121.776 36.5 121.5 36.5L109.5 36.5Z"
fill="#4765FF"
/>
<rect
x="65.5"
y="36.5"
width="100"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
</svg>
);
};
@@ -21,88 +21,50 @@ import {
content,
title,
description,
whiteBox,
whiteBoxStack,
stack,
verticalDivider,
horizontalDivider,
columns,
inline,
tiles,
whiteBoxColumns,
whiteBoxInline,
whiteBoxTiles,
} from './layout-components.css';
import { BoxSvg } from './box';
import { StackSvg } from './stack';
import { GridSvg } from './grid';
import { InlineSvg } from './inline';
import { ContainerSvg } from './container';
export const LayoutComponents = () => {
return (
<div className={container}>
<div className={box}>
<a className={content} href="/?path=/docs/components-box--docs">
<div className={whiteBox} />
<BoxSvg />
</a>
<div className={title}>Box</div>
<div className={description}>The most basic layout component.</div>
<div className={description}>The most basic layout component</div>
</div>
<div className={box}>
<a
className={`${content} ${stack}`}
href="/?path=/docs/components-stack--docs"
>
<div className={whiteBoxStack} />
<div className={verticalDivider} />
<div className={whiteBoxStack} />
<a className={content} href="/?path=/docs/components-stack--docs">
<StackSvg />
</a>
<div className={title}>Stack</div>
<div className={description}>Arrange your components vertically.</div>
<div className={description}>Arrange your components vertically</div>
</div>
<div className={box}>
<a
className={`${content} ${columns}`}
href="/?path=/docs/components-columns--docs"
>
<div className={whiteBoxColumns} />
<div className={horizontalDivider} />
<div className={whiteBoxColumns} />
<a className={content} href="/?path=/docs/components-grid--docs">
<GridSvg />
</a>
<div className={title}>Columns</div>
<div className={description}>Arrange your components horizontally.</div>
<div className={title}>Grid</div>
<div className={description}>Arrange your components in a grid</div>
</div>
<div className={box}>
<a className={content} href="/?path=/docs/components-inline--docs">
<div className={inline}>
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
<div className={whiteBoxInline} />
</div>
<InlineSvg />
</a>
<div className={title}>Inline</div>
<div className={description}>Arrange your components in a row.</div>
<div className={description}>Arrange your components in a row</div>
</div>
<div className={box}>
<a className={content} href="/?path=/docs/components-tiles--docs">
<div className={tiles}>
<div className={whiteBoxTiles} />
<div className={whiteBoxTiles} />
<div className={whiteBoxTiles} />
<div className={whiteBoxTiles} />
<div className={whiteBoxTiles} />
<div className={whiteBoxTiles} />
<div className={whiteBoxTiles} />
<div className={whiteBoxTiles} />
</div>
<ContainerSvg />
</a>
<div className={title}>Tiles</div>
<div className={description}>Arrange your components in a grid.</div>
<div className={title}>Container</div>
<div className={description}>A container for your components</div>
</div>
</div>
);
@@ -0,0 +1,92 @@
/*
* 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';
export const InlineSvg = () => {
return (
<svg
width="159"
height="56"
viewBox="0 0 159 56"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="24" height="24" rx="4" fill="black" fill-opacity="0.06" />
<rect
x="32"
width="24"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<rect
x="64"
width="24"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<rect
x="96"
width="24"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<rect
x="128"
width="24"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<rect
y="32"
width="24"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<rect
x="32"
y="32"
width="24"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<rect
x="64"
y="32"
width="24"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<path
d="M12 12H138.811C141.238 12 141.569 15.5106 139.185 15.9647L12.8145 40.0353C10.4306 40.4894 10.762 44 13.1888 44H77"
stroke="#4765FF"
/>
</svg>
);
};
@@ -33,19 +33,18 @@ export const box = style({
export const content = style({
flex: 'none',
background: '#f2f2f2',
background: 'linear-gradient(180deg, #F3F3F3 0%, #FFF 100%)',
borderRadius: '4px',
width: '100%',
height: '180px',
border: '1px solid #e0e0e0',
transition: 'border-color 0.2s ease-in-out',
transition: 'all 0.2s ease-in-out',
marginBottom: '0.75rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
selectors: {
[`${box}:hover &`]: {
borderColor: '#1f47ff',
transform: 'translateY(-4px)',
},
},
});
@@ -55,11 +54,6 @@ export const title = style({
fontFamily: 'var(--canon-font-regular)',
transition: 'color 0.2s ease-in-out',
marginBottom: '0.25rem',
selectors: {
[`${box}:hover &`]: {
color: '#1f47ff',
},
},
});
export const description = style({
@@ -67,132 +61,3 @@ export const description = style({
fontFamily: 'var(--canon-font-regular)',
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',
},
},
});
@@ -0,0 +1,82 @@
/*
* 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';
export const StackSvg = () => {
return (
<svg
width="100"
height="96"
viewBox="0 0 100 96"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="100" height="24" rx="4" fill="black" fill-opacity="0.06" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M49.5 36L49.5 25L50.5 25L50.5 36L49.5 36Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M44 25C43.7239 25 43.5 24.7761 43.5 24.5V24.5C43.5 24.2239 43.7239 24 44 24L56 24C56.2761 24 56.5 24.2239 56.5 24.5V24.5C56.5 24.7761 56.2761 25 56 25L44 25Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M44 36C43.7239 36 43.5 35.7761 43.5 35.5V35.5C43.5 35.2239 43.7239 35 44 35L56 35C56.2761 35 56.5 35.2239 56.5 35.5V35.5C56.5 35.7761 56.2761 36 56 36L44 36Z"
fill="#4765FF"
/>
<rect
y="36"
width="100"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M49.5 72L49.5 61L50.5 61L50.5 72L49.5 72Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M44 61C43.7239 61 43.5 60.7761 43.5 60.5V60.5C43.5 60.2239 43.7239 60 44 60L56 60C56.2761 60 56.5 60.2239 56.5 60.5V60.5C56.5 60.7761 56.2761 61 56 61L44 61Z"
fill="#4765FF"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M44 72C43.7239 72 43.5 71.7761 43.5 71.5V71.5C43.5 71.2239 43.7239 71 44 71L56 71C56.2761 71 56.5 71.2239 56.5 71.5V71.5C56.5 71.7761 56.2761 72 56 72L44 72Z"
fill="#4765FF"
/>
<rect
y="72"
width="100"
height="24"
rx="4"
fill="black"
fill-opacity="0.06"
/>
</svg>
);
};
@@ -0,0 +1,57 @@
/*
* 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 '..';
import { Chip } from '..';
// Modify the PropsTable component to accept a generic type
export const PropsTable = <T extends Record<string, any>>({
data,
}: {
data: T;
}) => {
return (
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Type</Table.HeaderCell>
<Table.HeaderCell>Responsive</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
{Object.keys(data).map(n => (
<Table.Row key={n}>
<Table.Cell>
<Chip head>{n}</Chip>
</Table.Cell>
<Table.Cell>
{Array.isArray(data[n].type) ? (
data[n].type.map((t: any) => <Chip key={t}>{t}</Chip>)
) : (
<Chip>{data[n].type}</Chip>
)}
</Table.Cell>
<Table.Cell>
<Chip>{data[n].responsive ? 'Yes' : 'No'}</Chip>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
);
};
@@ -0,0 +1,43 @@
/*
* 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 function getBoxProps(styles: Record<string, any>) {
return Object.keys(styles).reduce(
(acc: Record<string, { type: any[]; responsive: boolean }>, n) => {
const style = styles[n];
let values: string[] = [];
if (style.values) {
// If values exist, use them
values = Object.keys(style.values);
} else if (style.mappings && style.mappings.length > 0) {
// If mappings exist, use the first mapping's values
const firstMapping = style.mappings[0];
values = Object.keys(styles[firstMapping].values);
} else {
// Default to an empty array if neither values nor mappings exist
values = [];
}
acc[n] = {
type: values,
responsive: true,
};
return acc;
},
{} as Record<string, { type: string[]; responsive: boolean }>,
);
}
@@ -0,0 +1,17 @@
/*
* 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 { PropsTable } from './PropsTable';
export { getBoxProps } from './getProps';
@@ -22,7 +22,7 @@ export const textStyles = style({
lineHeight: '28px',
margin: '0',
fontWeight: 300,
paddingBottom: '16px',
marginBottom: '16px',
});
globalStyle(`${textStyles} p`, {
@@ -28,7 +28,7 @@ export const titleStyles = recipe({
h1: {
fontSize: '36px',
lineHeight: '44px',
marginBottom: '32px',
marginBottom: '8px',
marginTop: '0px',
},
h2: {
@@ -41,7 +41,7 @@ export const titleStyles = recipe({
fontSize: '20px',
lineHeight: '28px',
marginBottom: '12px',
marginTop: '52px',
marginTop: '40px',
},
},
},
+5
View File
@@ -24,6 +24,11 @@ export const listResponsiveValues = (
) => {
const values = boxProperties.styles[value];
if (!values) {
console.warn(`Value "${value}" not found in boxProperties.styles`);
return [];
}
if ('values' in values) {
return Object.keys(values.values);
}
+114
View File
@@ -6,6 +6,7 @@
/// <reference types="react" />
import { CSSProperties } from 'react';
import { DetailedReactHTMLElement } from 'react';
import { DOMElement } from 'react';
import { default as React_2 } from 'react';
import { ReactNode } from 'react';
@@ -150,6 +151,9 @@ export interface ColorProps {
color?: Color;
}
// @public (undocumented)
export type Columns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
// @public (undocumented)
export type DisplayProps =
| 'flex'
@@ -173,6 +177,54 @@ export type FlexWrapProps =
// @public (undocumented)
export type Gap = Space | Partial<Record<Breakpoint, Space>>;
// @public (undocumented)
export const Grid: {
(props: GridProps): DetailedReactHTMLElement<
{
className: string;
style: CSSProperties | undefined;
},
HTMLElement
>;
Item: (props: GridItemProps) => DetailedReactHTMLElement<
{
className: string;
style: CSSProperties | undefined;
},
HTMLElement
>;
};
// @public (undocumented)
export interface GridItemProps {
// (undocumented)
children: React.ReactNode;
// (undocumented)
className?: string;
// (undocumented)
colSpan?: Columns | 'full';
// (undocumented)
end?: Columns | 'auto';
// (undocumented)
rowSpan?: Columns | 'full';
// (undocumented)
start?: Columns | 'auto';
// (undocumented)
style?: React.CSSProperties;
}
// @public (undocumented)
export interface GridProps extends SpaceProps, ColorProps {
// (undocumented)
children?: React.ReactNode;
// (undocumented)
className?: string;
// (undocumented)
columns?: Columns | Partial<Record<Breakpoint, Columns>>;
// (undocumented)
style?: React.CSSProperties;
}
// @public (undocumented)
export const Icon: ({ name }: { name: IconNames }) => React_2.JSX.Element;
@@ -198,6 +250,40 @@ export type IconNames =
| 'plus'
| 'trash';
// @public (undocumented)
export const Inline: (props: InlineProps) => DetailedReactHTMLElement<
{
className: string;
style: CSSProperties | undefined;
children: ReactNode;
},
HTMLElement
>;
// @public (undocumented)
export interface InlineProps extends SpaceProps, ColorProps {
// (undocumented)
align?:
| 'left'
| 'center'
| 'right'
| Partial<Record<Breakpoint, 'left' | 'center' | 'right'>>;
// (undocumented)
alignY?:
| 'top'
| 'center'
| 'bottom'
| Partial<Record<Breakpoint, 'top' | 'center' | 'bottom'>>;
// (undocumented)
as?: AsProps;
// (undocumented)
children: React.ReactNode;
// (undocumented)
className?: string;
// (undocumented)
style?: React.CSSProperties;
}
// @public (undocumented)
export type JustifyContentProps =
| 'stretch'
@@ -309,6 +395,34 @@ export interface SpaceProps {
paddingY?: PaddingY;
}
// @public (undocumented)
export const Stack: (props: StackProps) => DetailedReactHTMLElement<
{
className: string;
style: CSSProperties | undefined;
children: ReactNode;
},
HTMLElement
>;
// @public (undocumented)
export interface StackProps extends SpaceProps, ColorProps {
// (undocumented)
align?:
| 'left'
| 'center'
| 'right'
| Partial<Record<Breakpoint, 'left' | 'center' | 'right'>>;
// (undocumented)
as?: AsProps;
// (undocumented)
children: React.ReactNode;
// (undocumented)
className?: string;
// (undocumented)
style?: React.CSSProperties;
}
// @public (undocumented)
export type Theme = keyof typeof themes;
+56 -8
View File
@@ -1,9 +1,10 @@
import { Canvas, Meta, Unstyled } from '@storybook/blocks';
import { Meta, Unstyled, Source } from '@storybook/blocks';
import * as BoxStories from './Box.stories';
import { Title, Text } from '../../../docs/components';
import { Padding } from './docs/padding';
import { PropsTable } from './docs/props-table';
import { SpacingTable } from './docs/spacing-table';
import { PropsTable, getBoxProps } from '../../../docs/components/PropsTable';
import { boxProperties } from './sprinkles.css';
import { spacingProperties } from '../../layout/sprinkles.css';
<Meta of={BoxStories} />
@@ -12,14 +13,39 @@ import { SpacingTable } from './docs/spacing-table';
<Title type="h1">Box</Title>
<Text>
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 `<div>` element and
provides a consistent API for styling and layout. You'll find below a list of all the available properties.
of our components. It provides a consistent API for styling and layout.
</Text>
<PropsTable />
<Title type="h2">Installation</Title>
<Source code={`yarn install @backstage/canon/box`} language="bash" dark />
<Title type="h2">Padding & Margin</Title>
<Title type="h2">API reference</Title>
<Title type="h3">Box</Title>
<Text>
This is the Box component, our lowest-level component. Here are all the
available properties.
</Text>
<PropsTable
data={{
...getBoxProps(boxProperties.styles),
children: {
type: 'ReactNode',
required: false,
},
className: {
type: 'string',
required: false,
},
style: {
type: 'CSSProperties',
required: false,
},
}}
/>
<Text style={{ marginTop: '16px' }}>
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.
@@ -27,6 +53,28 @@ import { SpacingTable } from './docs/spacing-table';
<Padding />
<SpacingTable />
<PropsTable data={getBoxProps(spacingProperties.styles)} />
<Title type="h2">Examples</Title>
<Text>Here are some examples of how you can use the Box component.</Text>
<Title type="h3">Simple example</Title>
<Text>A simple example of how to use the Box component.</Text>
<Source
code={`<Box padding="md" borderRadius="md">Hello World</Box>`}
language="tsx"
dark
/>
<Title type="h3">Responsive</Title>
<Text>
Most of the values can be defined per breakpoint, making it easy to create
responsive designs.
</Text>
<Source
code={`<Box padding={{ xs: 'sm', md: 'md' }} borderRadius={{ xs: 'sm', md: 'md' }}>Hello World</Box>`}
language="tsx"
dark
/>
</Unstyled>
@@ -1,70 +0,0 @@
/*
* 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 '../../../../docs/utils/argTypes';
import { boxProperties } from '../sprinkles.css';
export const PropsTable = () => {
return (
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Type</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
{Object.keys(boxProperties.styles)
.filter(
n =>
![
'padding',
'paddingX',
'paddingY',
'paddingLeft',
'paddingRight',
'paddingTop',
'paddingBottom',
'margin',
'marginX',
'marginY',
'marginLeft',
'marginRight',
'marginTop',
'marginBottom',
].includes(n),
)
.map(n => (
<Table.Row key={n}>
<Table.Cell>
<Chip head>{n}</Chip>
</Table.Cell>
<Table.Cell>
{listResponsiveValues(
n as keyof typeof boxProperties.styles,
).map(value => (
<Chip key={value}>{value}</Chip>
))}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
);
};
@@ -1,67 +0,0 @@
/*
* 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 { spacingProperties } from '../../../layout/sprinkles.css';
import { space } from '../../../layout/properties';
export const SpacingTable = () => {
return (
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Type</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
{Object.keys(spacingProperties.styles)
.filter(n =>
[
'padding',
'paddingX',
'paddingY',
'paddingLeft',
'paddingRight',
'paddingTop',
'paddingBottom',
'margin',
'marginX',
'marginY',
'marginLeft',
'marginRight',
'marginTop',
'marginBottom',
].includes(n),
)
.map(n => (
<Table.Row key={n}>
<Table.Cell>
<Chip head>{n}</Chip>
</Table.Cell>
<Table.Cell>
{Object.keys(space).map(value => (
<Chip key={value}>{value}</Chip>
))}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
);
};
+211
View File
@@ -0,0 +1,211 @@
import { Canvas, Meta, Unstyled, Source } from '@storybook/blocks';
import * as GridStories from './Grid.stories';
import { Title, Text } from '../../../docs/components';
import { PropsTable, getBoxProps } from '../../../docs/components/PropsTable';
import { spacingProperties } from '../../layout/sprinkles.css';
<Meta of={GridStories} />
<Unstyled>
<Title type="h1">Grid</Title>
<Text>
A layout component that helps to create simple column-based layouts as well as
more complex ones.
</Text>
<Title type="h2">Installation</Title>
<Source code={`yarn install @backstage/canon/grid`} language="bash" dark />
<Title type="h2">Anatomy</Title>
<Text>
The grid component is made of two parts: the grid container and the grid item. Import all parts and piece them together.
</Text>
<Source
code={`import * as Grid from "@backstage/canon/grid";
export default () => (
<Grid>
<Grid.Item />
</Grid>
);`}
language="tsx"
dark
/>
<Title type="h2">API reference</Title>
<Title type="h3">Grid</Title>
<Text>
This is the grid container component. It will help to define the number of
columns that will be used in the grid. You can also define the gap between the
columns. All values are responsive.
</Text>
<PropsTable
data={{
columns: {
type: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 'auto'],
responsive: true,
},
gap: {
type: ['xs', 'sm', 'md', 'lg', 'xl'],
responsive: true,
},
children: {
type: 'ReactNode',
required: false,
},
className: {
type: 'string',
required: false,
},
style: {
type: 'CSSProperties',
required: false,
},
}}
/>
<Text style={{ marginTop: '16px' }}>
The grid component also accepts all the spacing props from the Box component.
</Text>
<PropsTable data={getBoxProps(spacingProperties.styles)} />
<Title type="h3">Grid Item</Title>
<Text>
If you need more control over the columns, you can use the grid item
component. This will give you access to `rowSpan`, `colSpan`, `start` and
`end`. All values are responsive. This component is optional, you can use any
elements directly if you prefer.
</Text>
<PropsTable
data={{
colSpan: {
type: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 'full'],
responsive: true,
},
rowSpan: {
type: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 'full'],
responsive: true,
},
start: {
type: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 'auto'],
responsive: true,
},
end: {
type: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 'auto'],
responsive: true,
},
children: {
type: 'ReactNode',
required: false,
},
className: {
type: 'string',
required: false,
},
style: {
type: 'CSSProperties',
required: false,
},
}}
/>
<Title type="h2">Examples</Title>
<Title type="h3">Simple grid</Title>
<Text>This is a simple grid with 3 columns and a gap of md.</Text>
<Source
code={`<Grid columns={3} gap="md">
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Grid>
`}
language="tsx"
dark
/>
<Title type="h3">Complex grid</Title>
<Text>
You can also use the grid item to create more complex layouts. In this example
the first column will span 1 column and the second column will span 2 columns.
</Text>
<Source
code={`<Grid columns={3} gap="md">
<Grid.Item colSpan={1}>
<Box>Hello World</Box>
</Grid.Item>
<Grid.Item colSpan={2}>
<Box>Hello World</Box>
</Grid.Item>
</Grid>
`}
language="tsx"
dark
/>
<Title type="h3">Mixing rows and columns</Title>
<Text>
The grid item component also supports the `rowSpan` prop, which allows you to
span multiple rows within the grid layout. In this example, the first item
will span 2 rows to achieve a dynamic and flexible grid structure.
</Text>
<Source
code={`<Grid columns={3} gap="md">
<Grid.Item colSpan={1} rowSpan={2}>
<Box>Hello World</Box>
</Grid.Item>
<Grid.Item colSpan={2}>
<Box>Hello World</Box>
</Grid.Item>
<Grid.Item colSpan={2}>
<Box>Hello World</Box>
</Grid.Item>
</Grid>
`}
language="tsx"
dark
/>
<Title type="h3">Responsive</Title>
<Text>
The grid component also supports responsive values. In this example the grid
will have 1 column on small screens and 3 on large screens, with a gap of xs
on small screens and md on large screens.
</Text>
<Source
code={`<Grid columns={{ xs: 1, md: 3 }} gap={{ xs: 'xs', md: 'md' }}>
<Grid.Item colSpan={{ xs: 1, md: 2 }}>
<Box>Hello World</Box>
</Grid.Item>
<Grid.Item colSpan={{ xs: 1, md: 1 }}>
<Box>Hello World</Box>
</Grid.Item>
</Grid>
`}
language="tsx"
dark
/>
<Title type="h3">Start and End</Title>
<Text>
The start and end props can be used to position the item in the grid.
</Text>
<Source
code={`<Grid columns={3} gap="md">
<Grid.Item start={2} end={4}>
<Box>Hello World</Box>
</Grid.Item>
</Grid>
`}
language="tsx"
dark
/>
</Unstyled>
@@ -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 { Grid } from './Grid';
import type { GridItemProps } from './types';
import { Box } from '../Box/Box';
import { argTypesSpacing, argTypesColor } from '../../../docs/utils/argTypes';
import { Stack } from '../Stack';
const meta = {
title: 'Components/Grid',
component: Grid,
argTypes: {
...argTypesSpacing,
...argTypesColor,
children: {
control: false,
},
className: {
control: 'text',
},
},
args: {
gap: 'xs',
},
} satisfies Meta<typeof Grid>;
export default meta;
type Story = StoryObj<typeof meta>;
const FakeBox = () => (
<Box
borderRadius="small"
style={{ background: '#1f47ff', color: 'white', height: '64px' }}
/>
);
export const Default: Story = {
args: {},
render: args => (
<Grid {...args}>
<FakeBox />
<FakeBox />
<FakeBox />
</Grid>
),
};
export const LargeGap: Story = {
args: {
gap: 'lg',
},
render: args => (
<Grid {...args}>
<FakeBox />
<FakeBox />
<FakeBox />
</Grid>
),
};
export const ColumnSizes: Story = {
args: {
columns: 12,
gap: 'md',
},
render: args => (
<Stack gap="md">
{Array.from({ length: 11 }, (_, i) => (
<Grid {...args} key={i}>
<Grid.Item colSpan={(i + 1) as GridItemProps['colSpan']}>
<FakeBox />
</Grid.Item>
<Grid.Item colSpan={(11 - i) as GridItemProps['colSpan']}>
<FakeBox />
</Grid.Item>
</Grid>
))}
</Stack>
),
};
export const RowAndColumns: Story = {
args: {
columns: 12,
gap: 'md',
},
render: args => (
<Stack gap="md">
<Grid {...args} columns={3}>
<Grid.Item colSpan={1} rowSpan={2}>
<Box
borderRadius="small"
style={{ background: '#1f47ff', color: 'white', height: '100%' }}
/>
</Grid.Item>
<Grid.Item colSpan={2}>
<FakeBox />
</Grid.Item>
<Grid.Item colSpan={2}>
<FakeBox />
</Grid.Item>
</Grid>
</Stack>
),
};
@@ -0,0 +1,69 @@
/*
* 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 { GridItemProps, GridProps } from './types';
import { gridItemSprinkles, gridSprinkles } from './sprinkles.css';
/** @public */
export const Grid = (props: GridProps) => {
const {
children,
columns,
gap = 'xs',
className,
style,
...restProps
} = props;
const sprinklesClassName = gridSprinkles({
...restProps,
gap,
gridTemplateColumns: columns ? columns : 'auto',
});
const classNames = ['grid', sprinklesClassName, className]
.filter(Boolean)
.join(' ');
return createElement(
'div',
{
className: classNames,
style,
},
children,
);
};
const GridItem = (props: GridItemProps) => {
const { children, rowSpan, colSpan, start, end, className, style } = props;
const sprinklesClassName = gridItemSprinkles({
rowSpan,
colSpan,
start,
end,
});
const classNames = ['grid-item', sprinklesClassName, className]
.filter(Boolean)
.join(' ');
return createElement('div', { className: classNames, style }, children);
};
Grid.Item = GridItem;
@@ -0,0 +1,17 @@
/*
* 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 { Grid } from './Grid';
export type { GridProps, GridItemProps, Columns } from './types';
@@ -0,0 +1,130 @@
/*
* 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';
import { breakpoints } from '../../layout/properties';
import { colorProperties, spacingProperties } from '../../layout/sprinkles.css';
const gridProperties = defineProperties({
conditions: breakpoints,
defaultCondition: 'xs',
properties: {
gridTemplateColumns: {
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))',
7: 'repeat(7, minmax(0, 1fr))',
8: 'repeat(8, minmax(0, 1fr))',
9: 'repeat(9, minmax(0, 1fr))',
10: 'repeat(10, minmax(0, 1fr))',
11: 'repeat(11, minmax(0, 1fr))',
12: 'repeat(12, minmax(0, 1fr))',
auto: 'repeat(auto-fit, minmax(0, 1fr))',
},
},
shorthands: {
columns: ['gridTemplateColumns'],
},
});
export const gridSprinkles = createSprinkles(
spacingProperties,
colorProperties,
gridProperties,
);
const gridItemProperties = defineProperties({
conditions: breakpoints,
defaultCondition: 'xs',
properties: {
gridColumn: {
1: 'span 1 / span 1',
2: 'span 2 / span 2',
3: 'span 3 / span 3',
4: 'span 4 / span 4',
5: 'span 5 / span 5',
6: 'span 6 / span 6',
7: 'span 7 / span 7',
8: 'span 8 / span 8',
9: 'span 9 / span 9',
10: 'span 10 / span 10',
11: 'span 11 / span 11',
12: 'span 12 / span 12',
full: '1 / -1',
},
gridRow: {
1: 'span 1 / span 1',
2: 'span 2 / span 2',
3: 'span 3 / span 3',
4: 'span 4 / span 4',
5: 'span 5 / span 5',
6: 'span 6 / span 6',
7: 'span 7 / span 7',
8: 'span 8 / span 8',
9: 'span 9 / span 9',
10: 'span 10 / span 10',
11: 'span 11 / span 11',
12: 'span 12 / span 12',
full: '1 / -1',
},
gridColumnStart: {
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
auto: 'auto',
},
gridColumnEnd: {
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
auto: 'auto',
},
},
shorthands: {
colSpan: ['gridColumn'],
rowSpan: ['gridRow'],
start: ['gridColumnStart'],
end: ['gridColumnEnd'],
},
});
export const gridItemSprinkles = createSprinkles(
colorProperties,
gridItemProperties,
);
@@ -0,0 +1,3 @@
.grid {
display: grid;
}
@@ -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 { Breakpoint, ColorProps } from '../../layout/types';
import { SpaceProps } from '../../layout/types';
/** @public */
export type Columns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
/** @public */
export interface GridProps extends SpaceProps, ColorProps {
children?: React.ReactNode;
columns?: Columns | Partial<Record<Breakpoint, Columns>>;
className?: string;
style?: React.CSSProperties;
}
/** @public */
export interface GridItemProps {
children: React.ReactNode;
rowSpan?: Columns | 'full';
colSpan?: Columns | 'full';
start?: Columns | 'auto';
end?: Columns | 'auto';
className?: string;
style?: React.CSSProperties;
}
@@ -0,0 +1,118 @@
import { Meta, Unstyled, Source } from '@storybook/blocks';
import * as InlineStories from './Inline.stories';
import { Title, Text } from '../../../docs/components';
import { PropsTable, getBoxProps } from '../../../docs/components/PropsTable';
import { spacingProperties } from '../../layout/sprinkles.css';
import { inlineProperties } from './sprinkles.css';
<Meta of={InlineStories} />
<Unstyled>
<Title type="h1">Inline</Title>
<Text>
The Inline component is used to create a horizontal layout of elements. By
default it uses flex and flexWrap to make sure that your content always flows
responsively.
</Text>
<Title type="h2">Installation</Title>
<Source code={`yarn install @backstage/canon/inline`} language="bash" dark />
<Title type="h2" style={{ marginBottom: '16px' }}>
API reference
</Title>
<PropsTable
data={{
align: {
type: 'left | center | right',
responsive: true,
},
alignY: {
type: 'top | center | bottom',
responsive: true,
},
children: {
type: 'ReactNode',
responsive: false,
},
className: {
type: 'string',
responsive: false,
},
style: {
type: 'CSSProperties',
responsive: false,
},
}}
/>
<Text style={{ marginTop: '16px' }}>
The grid component also accepts all the spacing props from the Box component.
</Text>
<PropsTable data={getBoxProps(spacingProperties.styles)} />
<Title type="h2">Examples</Title>
<Title type="h3">Simple</Title>
<Text>A simple example of how to use the Inline component.</Text>
<Source
code={`<Inline>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Inline>`}
language="tsx"
dark
/>
<Title type="h3">Responsive</Title>
<Text>
The Inline component also supports responsive values, making it easy to create
responsive designs.
</Text>
<Source
code={`<Inline gap={{ xs: 'sm', md: 'md' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Inline>`}
language="tsx"
dark
/>
<Title type="h3">Align</Title>
<Text>
The Inline component also supports responsive alignment, making it easy to
create responsive designs.
</Text>
<Source
code={`<Inline align={{ xs: 'left', md: 'center' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Inline>`}
language="tsx"
dark
/>
<Title type="h3">Align vertically</Title>
<Text>
The Inline component also supports responsive vertical alignment, making it
easy to create responsive designs.
</Text>
<Source
code={`<Inline alignY={{ xs: 'top', md: 'center' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Inline>`}
language="tsx"
dark
/>
</Unstyled>
+13 -10
View File
@@ -32,16 +32,19 @@ const alignToFlexAlignY = (align: InlineProps['align']) => {
return undefined;
};
export const Inline = ({
as = 'div',
children,
align = 'left',
alignY = 'top',
gap = 'xs',
className,
style,
...restProps
}: InlineProps) => {
/** @public */
export const Inline = (props: InlineProps) => {
const {
as = 'div',
children,
align = 'left',
alignY = 'top',
gap = 'xs',
className,
style,
...restProps
} = props;
// Generate the list of class names
const sprinklesClassName = inlineSprinkles({
...restProps,
@@ -14,3 +14,4 @@
* limitations under the License.
*/
export { Inline } from './Inline';
export type { InlineProps } from './types';
@@ -18,7 +18,7 @@ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';
import { breakpoints } from '../../layout/properties';
import { colorProperties, spacingProperties } from '../../layout/sprinkles.css';
const inlineProperties = defineProperties({
export const inlineProperties = defineProperties({
conditions: breakpoints,
defaultCondition: 'xs',
properties: {
@@ -17,6 +17,7 @@
import { AsProps, Breakpoint, ColorProps } from '../../layout/types';
import { SpaceProps } from '../../layout/types';
/** @public */
export interface InlineProps extends SpaceProps, ColorProps {
children: React.ReactNode;
as?: AsProps;
@@ -0,0 +1,119 @@
import { Meta, Unstyled, Source } from '@storybook/blocks';
import * as StackStories from './Stack.stories';
import { Title, Text } from '../../../docs/components';
import { PropsTable, getBoxProps } from '../../../docs/components/PropsTable';
import { spacingProperties } from '../../layout/sprinkles.css';
<Meta of={StackStories} />
<Unstyled>
<Title type="h1">Stack</Title>
<Text>
This is the stack container component. It will help to define the number of
columns that will be used in the grid. You can also define the gap between the
columns. All values are responsive.
</Text>
<Title type="h2">Installation</Title>
<Source code={`yarn install @backstage/canon/stack`} language="bash" dark />
<Title type="h2" style={{ marginBottom: '16px' }}>
API reference
</Title>
<PropsTable
data={{
align: {
type: ['left', 'center', 'right'],
responsive: true,
},
children: {
type: 'ReactNode',
required: false,
},
className: {
type: 'string',
required: false,
},
style: {
type: 'CSSProperties',
required: false,
},
}}
/>
<Text style={{ marginTop: '16px' }}>
The grid component also accepts all the spacing props from the Box component.
</Text>
<PropsTable data={getBoxProps(spacingProperties.styles)} />
<Title type="h2">Common questions</Title>
<Title type="h3">Can I stack horizontally?</Title>
<Text>
The Stack component only allows for stacking elements vertically. If you want
to create a column layout, please use the Grid component.
</Text>
<Source
code={`<Grid columns={3} gap="md">
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Grid>
`}
language="tsx"
dark
/>
<Title type="h2">Examples</Title>
<Title type="h3">Simple</Title>
<Text>
A simple example of how to use the Stack component with a medium gap.
</Text>
<Source
code={`<Stack gap="md">
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Stack>`}
language="tsx"
dark
/>
<Title type="h3">Responsive</Title>
<Text>
The Stack component also supports responsive values, making it easy to create
responsive designs.
</Text>
<Source
code={`<Stack gap={{ xs: 'sm', md: 'md' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Stack>`}
language="tsx"
dark
/>
<Title type="h3">Align</Title>
<Text>
The Stack component also supports responsive alignment, making it easy to
create responsive designs.
</Text>
<Source
code={`<Stack align={{ xs: 'left', md: 'center' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Stack>`}
language="tsx"
dark
/>
</Unstyled>
+12 -9
View File
@@ -25,15 +25,18 @@ const alignToFlexAlign = (align: StackProps['align']) => {
return undefined;
};
export const Stack = ({
as = 'div',
children,
align = 'left',
gap = 'xs',
className,
style,
...restProps
}: StackProps) => {
/** @public */
export const Stack = (props: StackProps) => {
const {
as = 'div',
children,
align = 'left',
gap = 'xs',
className,
style,
...restProps
} = props;
// Transform the align prop
const flexAlign = alignToFlexAlign(align);
@@ -14,3 +14,4 @@
* limitations under the License.
*/
export { Stack } from './Stack';
export type { StackProps } from './types';
@@ -16,6 +16,7 @@
import { AsProps, Breakpoint, ColorProps } from '../../layout/types';
import { SpaceProps } from '../../layout/types';
/** @public */
export interface StackProps extends SpaceProps, ColorProps {
children: React.ReactNode;
as?: AsProps;
+11 -3
View File
@@ -20,7 +20,15 @@
* @packageDocumentation
*/
export * from './components/Button';
export * from './components/Box';
export * from './components/Icon';
// Layout types
export * from './layout/types';
// Layout components
export * from './components/Box';
export * from './components/Grid';
export * from './components/Stack';
export * from './components/Inline';
// UI components
export * from './components/Button';
export * from './components/Icon';
+1
View File
@@ -25,3 +25,4 @@
@import '../components/Button/styles.css';
@import '../components/Stack/styles.css';
@import '../components/Inline/styles.css';
@import '../components/Grid/styles.css';