Merge pull request #28634 from backstage/canon-flex

Canon - Merge Stack + Inline into Flex
This commit is contained in:
Charles de Dreuille
2025-01-28 10:52:47 +00:00
committed by GitHub
47 changed files with 498 additions and 1570 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': minor
---
**BREAKING**: Merged the Stack and Inline component into a single component called Flex.
@@ -141,9 +141,9 @@ Here's a view when buttons are full width.
py={4}
open
preview={<ButtonFullWidth />}
code={`<Stack style={{ width: '300px' }}>
code={`<Flex style={{ width: '300px' }}>
<Button fullWidth>Full width</Button>
</Stack>`}
</Flex>`}
/>
### Disabled
@@ -3,29 +3,29 @@ import { spacePropsList } from '@/utils/spaceProps';
import { Tabs } from '@/components/Tabs';
import { CodeBlock } from '@/components/CodeBlock';
import {
stackFAQ1,
stackSimple,
stackResponsive,
stackAlign,
flexFAQ1,
flexSimple,
flexResponsive,
flexAlign,
} from '@/snippets/_snippets';
import { Snippet } from '@/components/Snippet';
import { StackPreview } from '@/snippets/stack';
import { FlexPreview } from '@/snippets/flex';
# Stack
# Flex
This is the stack container component. It will help to define the number of
This is the flex 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.
<Snippet
py={4}
align="center"
preview={<StackPreview />}
code={`<Stack>
preview={<FlexPreview />}
code={`<Flex>
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Stack>`}
</Flex>`}
/>
<Tabs.Root>
@@ -34,9 +34,9 @@ columns. All values are responsive.
</Tabs.List>
<Tabs.Panel>
<CodeBlock
code={`import { Stack } from '@backstage/canon';
code={`import { Flex } from '@backstage/canon';
<Stack />
<Flex />
`}
/>
</Tabs.Panel>
@@ -73,29 +73,29 @@ The grid component also accepts all the spacing props from the Box component.
### Can I stack horizontally?
The Stack component only allows for stacking elements vertically. If you want
The Flex component only allows for stacking elements vertically. If you want
to create a column layout, please use the Grid component.
<CodeBlock code={stackFAQ1} />
<CodeBlock code={flexFAQ1} />
## Examples
### Simple
A simple example of how to use the Stack component.
A simple example of how to use the Flex component.
<CodeBlock code={stackSimple} />
<CodeBlock code={flexSimple} />
### Responsive
The Stack component also supports responsive values, making it easy to create
The Flex component also supports responsive values, making it easy to create
responsive designs.
<CodeBlock code={stackResponsive} />
<CodeBlock code={flexResponsive} />
### Align
The Stack component also supports responsive alignment, making it easy to
The Flex component also supports responsive alignment, making it easy to
create responsive designs.
<CodeBlock code={stackAlign} />
<CodeBlock code={flexAlign} />
@@ -75,13 +75,13 @@ appearance of the heading.
py={2}
open
preview={<HeadingAllVariants />}
code={`<Stack gap="md">
code={`<Flex direction="column" gap="4">
<Heading variant="display">Display</Heading>
<Heading variant="title1">Title 1</Heading>
<Heading variant="title2">Title 2</Heading>
<Heading variant="title3">Title 3</Heading>
<Heading variant="title4">Title 4</Heading>
</Stack>`}
</Flex>`}
/>
### Responsive
@@ -1,118 +0,0 @@
import { CodeBlock } from '@/components/CodeBlock';
import { PropsTable } from '@/components/PropsTable';
import { spacePropsList } from '@/utils/spaceProps';
import { Tabs } from '@/components/Tabs';
import { Snippet } from '@/components/Snippet';
import { InlinePreview } from '@/snippets/inline';
# Inline
The Inline component is used to create a horizontal layout of elements.
<Snippet
py={4}
preview={<InlinePreview />}
code={`<Inline>
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Inline>`}
/>
<Tabs.Root>
<Tabs.List>
<Tabs.Tab>Usage</Tabs.Tab>
</Tabs.List>
<Tabs.Panel>
<CodeBlock
code={`import { Inline } from '@backstage/canon';
<Inline />
`}
/>
</Tabs.Panel>
</Tabs.Root>
## API reference
<PropsTable
data={{
align: {
type: 'start | center | end',
responsive: true,
},
alignY: {
type: 'start | center | end',
responsive: true,
},
children: {
type: 'ReactNode',
responsive: false,
},
className: {
type: 'string',
responsive: false,
},
style: {
type: 'CSSProperties',
responsive: false,
},
}}
/>
The grid component also accepts all the spacing props from the Box component.
<PropsTable data={spacePropsList} />
## Examples
### Simple
A simple example of how to use the Inline component.
<CodeBlock
code={`<Inline>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Inline>`}
/>
### Responsive
The Inline component also supports responsive values, making it easy to create
responsive designs.
<CodeBlock
code={`<Inline gap={{ xs: 'sm', md: 'md' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Inline>`}
/>
### Align
The Inline component also supports responsive alignment, making it easy to
create responsive designs.
<CodeBlock
code={`<Inline align={{ xs: 'left', md: 'center' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Inline>`}
/>
### Align vertically
The Inline component also supports responsive vertical alignment, making it
easy to create responsive designs.
<CodeBlock
code={`<Inline alignY={{ xs: 'top', md: 'center' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Inline>`}
/>
@@ -83,7 +83,7 @@ appearance of the text.
<Snippet
open
preview={<TextAllVariants />}
code={`<Stack gap="md">
code={`<Flex direction="column" gap="4">
<Text variant="subtitle" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, “Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son.” Who is
@@ -104,7 +104,7 @@ appearance of the text.
have none, but that man&apos;s father is my father&apos;s son.” Who is
in the painting?
</Text>
</Stack>`}
</Flex>`}
/>
### All weights
@@ -115,7 +115,7 @@ appearance of the text.
<Snippet
open
preview={<TextAllWeights />}
code={`<Stack gap="md">
code={`<Flex direction="column" gap="4">
<Text weight="regular" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, “Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son.” Who is
@@ -126,7 +126,7 @@ appearance of the text.
have none, but that man&apos;s father is my father&apos;s son.” Who is
in the painting?
</Text>
</Stack>`}
</Flex>`}
/>
### Responsive
+3 -3
View File
@@ -45,12 +45,12 @@ import { ThemeProvider } from '@backstage/canon';
Now you can start building your plugin using the new design system.
```tsx
import { Stack, Button } from '@backstage/canon';
import { Flex, Button, Text } from '@backstage/canon';
<Stack>
<Flex>
<Text>Hello World</Text>
<Button>Click me</Button>
</Stack>;
</Flex>;
```
## Roadmap
@@ -22,13 +22,13 @@ provide are the ones coming from the theme.
<CodeBlock
title="Layout components"
code={`<Stack direction="column" gap="md">
code={`<Flex direction="column" gap="4">
<Box>Hello World</Box>
<Inline gap="sm">
<Box>Project 1</Box>
<Box>Project 2</Box>
</Inline>
</Stack>
</Flex>
`}
/>
@@ -21,13 +21,13 @@ component](?path=/docs/components-heading--docs).
py={2}
open
preview={<HeadingAllVariants />}
code={`<Stack gap="md">
code={`<Flex direction="column" gap="4">
<Heading variant="display">Display</Heading>
<Heading variant="title1">Title 1</Heading>
<Heading variant="title2">Title 2</Heading>
<Heading variant="title3">Title 3</Heading>
<Heading variant="title4">Title 4</Heading>
</Stack>`}
</Flex>`}
/>
## Text
@@ -41,7 +41,7 @@ page.
<Snippet
open
preview={<TextAllVariants />}
code={`<Stack gap="md">
code={`<Flex direction="column" gap="4">
<Text variant="subtitle" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, “Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son.” Who is
@@ -62,5 +62,5 @@ page.
have none, but that man&apos;s father is my father&apos;s son.” Who is
in the painting?
</Text>
</Stack>`}
</Flex>`}
/>
@@ -1,7 +1,7 @@
'use client';
import { ReactNode } from 'react';
import { Grid, Stack, Text } from '../../../../../packages/canon';
import { Grid, Flex, Text } from '../../../../../packages/canon';
import { screenSizes } from '@/utils/data';
import { Frame } from '@/components/Frame';
import { usePlayground } from '@/utils/playground-context';
@@ -53,7 +53,7 @@ const Content = () => {
const { selectedComponents } = usePlayground();
return (
<Stack gap="xl">
<Flex direction="column" gap="4">
{selectedComponents.find(c => c === 'button') && (
<Line content={<ButtonPlayground />} title="Button" />
)}
@@ -69,17 +69,17 @@ const Content = () => {
{/* {selectedComponents.find(c => c === 'input') && (
<Line content={<InputPlayground />} title="Input" />
)} */}
</Stack>
</Flex>
);
};
const Line = ({ content, title }: { content: ReactNode; title: string }) => {
return (
<Grid gap={{ xs: 'xs', md: 'xl' }}>
<Grid.Item colSpan={2}>
<Grid gap={{ xs: '2', md: '4' }}>
<Grid.Item colSpan="2">
<Text>{title}</Text>
</Grid.Item>
<Grid.Item colSpan={10}>{content}</Grid.Item>
<Grid.Item colSpan="10">{content}</Grid.Item>
</Grid>
);
};
@@ -1,6 +1,6 @@
import React from 'react';
import { BoxSvg } from './svgs/box';
import { StackSvg } from './svgs/stack';
import { FlexSvg } from './svgs/flex';
import { GridSvg } from './svgs/grid';
import { InlineSvg } from './svgs/inline';
import { ContainerSvg } from './svgs/container';
@@ -20,10 +20,10 @@ export const LayoutComponents = () => {
</div>
</div>
<div className={styles.box}>
<Link className={styles.content} href="/components/stack">
<StackSvg />
<Link className={styles.content} href="/components/flex">
<FlexSvg />
</Link>
<div className={styles.title}>Stack</div>
<div className={styles.title}>Flex</div>
<div className={styles.description}>
Arrange your components vertically
</div>
@@ -15,7 +15,7 @@
*/
import React from 'react';
export const StackSvg = () => {
export const FlexSvg = () => {
return (
<svg
width="100"
+7 -7
View File
@@ -51,26 +51,26 @@ export const buttonVariants = `<Inline alignY="center">
</Inline>
`;
export const stackFAQ1 = `<Grid columns={3} gap="md">
export const flexFAQ1 = `<Grid columns={3} gap="4">
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Grid>`;
export const stackSimple = `<Stack>
export const flexSimple = `<Flex>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Stack>`;
</Flex>`;
export const stackResponsive = `<Stack gap={{ xs: 'sm', md: 'md' }}>
export const flexResponsive = `<Flex gap={{ xs: 'sm', md: 'md' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Stack>`;
</Flex>`;
export const stackAlign = `<Stack align={{ xs: 'left', md: 'center' }}>
export const flexAlign = `<Flex align={{ xs: 'left', md: 'center' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Stack>`;
</Flex>`;
+15 -21
View File
@@ -1,16 +1,10 @@
'use client';
import {
Inline,
Button,
Stack,
ButtonProps,
Text,
} from '../../../packages/canon';
import { Button, Flex, ButtonProps, Text } from '../../../packages/canon';
export const ButtonPreview = () => {
return (
<Inline alignY="center">
<Flex align="center">
<Button iconStart="cloud" variant="primary">
Button
</Button>
@@ -20,40 +14,40 @@ export const ButtonPreview = () => {
<Button iconStart="cloud" variant="tertiary">
Button
</Button>
</Inline>
</Flex>
);
};
export const ButtonSizes = () => {
return (
<Inline alignY="center">
<Flex align="center">
<Button size="medium">Medium</Button>
<Button size="small">Small</Button>
</Inline>
</Flex>
);
};
export const ButtonWithIcons = () => {
return (
<Inline alignY="center">
<Flex align="center">
<Button iconStart="cloud">Button</Button>
<Button iconEnd="chevronRight">Button</Button>
<Button iconStart="cloud" iconEnd="chevronRight">
Button
</Button>
</Inline>
</Flex>
);
};
export const ButtonFullWidth = () => {
return (
<Stack style={{ width: '300px' }}>
<Flex direction="column" style={{ width: '300px' }}>
<Button iconStart="cloud">Button</Button>
<Button iconEnd="chevronRight">Button</Button>
<Button iconStart="cloud" iconEnd="chevronRight">
Button
</Button>
</Stack>
</Flex>
);
};
@@ -73,12 +67,12 @@ export const ButtonPlayground = () => {
const variants: string[] = ['primary', 'secondary', 'tertiary'];
return (
<Stack>
<Flex direction="column">
{variants.map(variant => (
<Stack key={variant}>
<Flex direction="column" key={variant}>
<Text>{variant}</Text>
{['small', 'medium'].map(size => (
<Inline alignY="center" key={size}>
<Flex align="center" key={size}>
<Button
iconStart="cloud"
variant={variant as ButtonProps['variant']}
@@ -102,10 +96,10 @@ export const ButtonPlayground = () => {
>
Button
</Button>
</Inline>
</Flex>
))}
</Stack>
</Flex>
))}
</Stack>
</Flex>
);
};
+7 -7
View File
@@ -1,6 +1,6 @@
'use client';
import { Inline, Checkbox, Stack, Text } from '../../../packages/canon';
import { Checkbox, Flex, Text } from '../../../packages/canon';
export const CheckboxPreview = () => {
return <Checkbox label="Accept terms and conditions" />;
@@ -8,25 +8,25 @@ export const CheckboxPreview = () => {
export const CheckboxAllVariants = () => {
return (
<Inline alignY="center">
<Flex align="center">
<Checkbox />
<Checkbox checked />
<Checkbox label="Checkbox" />
<Checkbox label="Checkbox" checked />
</Inline>
</Flex>
);
};
export const CheckboxPlayground = () => {
return (
<Stack>
<Flex direction="column">
<Text>All variants</Text>
<Inline alignY="center">
<Flex align="center">
<Checkbox />
<Checkbox checked />
<Checkbox label="Checkbox" />
<Checkbox label="Checkbox" checked />
</Inline>
</Stack>
</Flex>
</Flex>
);
};
@@ -1,6 +1,6 @@
'use client';
import { Stack } from '../../../packages/canon';
import { Flex } from '../../../packages/canon';
const DecorativeBox = () => {
return (
@@ -18,14 +18,14 @@ const DecorativeBox = () => {
);
};
export const StackPreview = () => {
export const FlexPreview = () => {
return (
<div style={{ width: '320px' }}>
<Stack>
<Flex>
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Stack>
</Flex>
</div>
);
};
+7 -7
View File
@@ -1,6 +1,6 @@
'use client';
import { Heading, Stack, Text } from '../../../packages/canon';
import { Heading, Flex, Text } from '../../../packages/canon';
export const HeadingPreview = () => {
return <Heading>Look mum, no hands!</Heading>;
@@ -8,35 +8,35 @@ export const HeadingPreview = () => {
export const HeadingAllVariants = () => {
return (
<Stack>
<Flex direction="column">
<Heading variant="display">Display</Heading>
<Heading variant="title1">Title 1</Heading>
<Heading variant="title2">Title 2</Heading>
<Heading variant="title3">Title 3</Heading>
<Heading variant="title4">Title 4</Heading>
</Stack>
</Flex>
);
};
export const HeadingResponsive = () => {
return (
<Stack>
<Flex direction="column">
<Heading variant={{ initial: 'title2', lg: 'title1' }}>
Responsive heading
</Heading>
</Stack>
</Flex>
);
};
export const HeadingPlayground = () => {
return (
<Stack>
<Flex direction="column">
<Text>All variants</Text>
<Heading variant="display">Display</Heading>
<Heading variant="title1">Title 1</Heading>
<Heading variant="title2">Title 2</Heading>
<Heading variant="title3">Title 3</Heading>
<Heading variant="title4">Title 4</Heading>
</Stack>
</Flex>
);
};
+7 -7
View File
@@ -1,6 +1,6 @@
'use client';
import { Stack, Text } from '../../../packages/canon';
import { Flex, Text } from '../../../packages/canon';
export const TextPreview = () => {
return (
@@ -14,7 +14,7 @@ export const TextPreview = () => {
export const TextAllVariants = () => {
return (
<Stack gap="md">
<Flex direction="column">
<Text variant="subtitle" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son. Who is
@@ -35,13 +35,13 @@ export const TextAllVariants = () => {
have none, but that man&apos;s father is my father&apos;s son. Who is
in the painting?
</Text>
</Stack>
</Flex>
);
};
export const TextAllWeights = () => {
return (
<Stack gap="md">
<Flex direction="column">
<Text weight="regular" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son. Who is
@@ -52,7 +52,7 @@ export const TextAllWeights = () => {
have none, but that man&apos;s father is my father&apos;s son. Who is
in the painting?
</Text>
</Stack>
</Flex>
);
};
@@ -64,7 +64,7 @@ export const TextResponsive = () => {
export const TextPlayground = () => {
return (
<Stack>
<Flex direction="column">
<Text>Subtitle</Text>
<Text variant="subtitle" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, Brothers and sisters I
@@ -89,6 +89,6 @@ export const TextPlayground = () => {
have none, but that man&apos;s father is my father&apos;s son. Who is
in the painting?
</Text>
</Stack>
</Flex>
);
};
+2 -7
View File
@@ -55,13 +55,8 @@ export const layoutComponents: Page[] = [
status: 'alpha',
},
{
title: 'Inline',
slug: 'inline',
status: 'alpha',
},
{
title: 'Stack',
slug: 'stack',
title: 'Flex',
slug: 'flex',
status: 'alpha',
},
];
+48 -64
View File
@@ -283,9 +283,57 @@ export const Field: {
}) => React_2.JSX.Element;
};
// @public (undocumented)
export const Flex: ForwardRefExoticComponent<
FlexProps & RefAttributes<HTMLDivElement>
>;
// @public (undocumented)
export type FlexDirection = 'row' | 'column';
// @public (undocumented)
export type FlexOwnProps = GetPropDefTypes<typeof flexPropDefs>;
// @public (undocumented)
export const flexPropDefs: {
align: {
type: 'enum';
className: string;
values: readonly ['start', 'center', 'end', 'baseline', 'stretch'];
responsive: true;
};
direction: {
type: 'enum';
className: string;
values: readonly ['row', 'column', 'row-reverse', 'column-reverse'];
responsive: true;
};
justify: {
type: 'enum';
className: string;
values: readonly ['start', 'center', 'end', 'between'];
responsive: true;
};
};
// @public (undocumented)
export interface FlexProps extends SpaceProps {
// (undocumented)
align?: FlexOwnProps['align'];
// (undocumented)
children: React.ReactNode;
// (undocumented)
className?: string;
// (undocumented)
direction?: FlexOwnProps['direction'];
// (undocumented)
gap?: GapProps['gap'];
// (undocumented)
justify?: FlexOwnProps['justify'];
// (undocumented)
style?: React.CSSProperties;
}
// @public (undocumented)
export type FlexWrap = 'wrap' | 'nowrap' | 'wrap-reverse';
@@ -589,37 +637,6 @@ export type IconProps = {
// @public (undocumented)
export const icons: IconMap;
// @public (undocumented)
export const Inline: ForwardRefExoticComponent<
InlineProps & RefAttributes<HTMLElement>
>;
// @public (undocumented)
export interface InlineProps extends SpaceProps {
// (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)
gap?: UtilityProps['gap'];
// (undocumented)
style?: React.CSSProperties;
}
// @public (undocumented)
export const Input: React_2.ForwardRefExoticComponent<
InputProps & React_2.RefAttributes<HTMLInputElement>
@@ -853,39 +870,6 @@ export interface SpaceProps {
py?: Responsive<Space>;
}
// @public (undocumented)
export const Stack: ForwardRefExoticComponent<
StackProps & RefAttributes<HTMLDivElement>
>;
// @public (undocumented)
export type StackOwnProps = GetPropDefTypes<typeof stackPropDefs>;
// @public (undocumented)
export const stackPropDefs: {
align: {
type: 'enum';
className: string;
values: readonly ['start', 'center', 'end', 'baseline', 'stretch'];
responsive: true;
default: 'stretch';
};
};
// @public (undocumented)
export interface StackProps extends SpaceProps {
// (undocumented)
align?: StackOwnProps['align'];
// (undocumented)
children: React.ReactNode;
// (undocumented)
className?: string;
// (undocumented)
gap?: GapProps['gap'];
// (undocumented)
style?: React.CSSProperties;
}
// @public (undocumented)
export type StringPropDef = {
type: 'string';
@@ -17,8 +17,7 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Box } from './Box';
import { Stack } from '../Stack';
import { Inline } from '../Inline';
import { Flex } from '../Flex';
const meta = {
title: 'Components/Box',
@@ -81,8 +80,8 @@ const CardDisplay = ({ children }: { children?: React.ReactNode }) => {
export const Display: Story = {
render: args => (
<Stack align="center">
<Inline>
<Flex direction="column" align="center">
<Flex>
<Box display="block" {...args}>
<CardDisplay>Block</CardDisplay>
</Box>
@@ -92,11 +91,11 @@ export const Display: Story = {
<Box display="none" {...args}>
<CardDisplay>None</CardDisplay>
</Box>
</Inline>
</Flex>
<Box display={{ initial: 'block', md: 'inline' }} {...args}>
<CardDisplay>Responsive</CardDisplay>
</Box>
</Stack>
</Flex>
),
};
@@ -116,8 +115,8 @@ export const Padding: Story = {
},
},
render: args => (
<Stack align="center" gap="md">
<Inline alignY="center" gap="md">
<Flex direction="column" align="center" gap="4">
<Flex gap="4" align="center">
<Box p="3" style={styleInsideBox}>
<Box {...args}>Padding</Box>
</Box>
@@ -127,8 +126,8 @@ export const Padding: Story = {
<Box py="3" style={styleInsideBox}>
<Box {...args}>Padding Y</Box>
</Box>
</Inline>
<Inline alignY="center" gap="md">
</Flex>
<Flex gap="4" align="center">
<Box pt="3" style={styleInsideBox}>
<Box {...args}>Padding Top</Box>
</Box>
@@ -141,8 +140,8 @@ export const Padding: Story = {
<Box pl="3" style={styleInsideBox}>
<Box {...args}>Padding Left</Box>
</Box>
</Inline>
</Stack>
</Flex>
</Flex>
),
};
@@ -156,8 +155,8 @@ export const Margin: Story = {
},
},
render: args => (
<Stack align="center" gap="md">
<Inline alignY="center" gap="md">
<Flex direction="column" align="center" gap="4">
<Flex align="center" gap="4">
<Box style={styleInsideBox}>
<Box m="3" {...args}>
Margin
@@ -173,8 +172,8 @@ export const Margin: Story = {
Margin Y
</Box>
</Box>
</Inline>
<Inline alignY="center" gap="md">
</Flex>
<Flex align="center" gap="4">
<Box style={styleInsideBox}>
<Box mt="3" {...args}>
Margin Top
@@ -195,7 +194,7 @@ export const Margin: Story = {
Margin Left
</Box>
</Box>
</Inline>
</Stack>
</Flex>
</Flex>
),
};
@@ -17,8 +17,7 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
import { Inline } from '../Inline';
import { Stack } from '../Stack';
import { Flex } from '../Flex';
import { Text } from '../Text';
import { ButtonProps } from './types';
@@ -51,7 +50,7 @@ export const Variants: Story = {
},
},
render: () => (
<Inline alignY="center">
<Flex align="center">
<Button iconStart="cloud" variant="primary">
Button
</Button>
@@ -61,7 +60,7 @@ export const Variants: Story = {
<Button iconStart="cloud" variant="tertiary">
Button
</Button>
</Inline>
</Flex>
),
};
@@ -70,10 +69,10 @@ export const Sizes: Story = {
children: 'Button',
},
render: () => (
<Inline alignY="center">
<Flex align="center">
<Button size="medium">Medium</Button>
<Button size="small">Small</Button>
</Inline>
</Flex>
),
};
@@ -82,11 +81,11 @@ export const WithIcons: Story = {
children: 'Button',
},
render: args => (
<Inline alignY="center">
<Flex align="center">
<Button {...args} iconStart="cloud" />
<Button {...args} iconEnd="chevronRight" />
<Button {...args} iconStart="cloud" iconEnd="chevronRight" />
</Inline>
</Flex>
),
};
@@ -95,11 +94,11 @@ export const FullWidth: Story = {
children: 'Button',
},
render: args => (
<Stack style={{ width: '300px' }}>
<Flex style={{ width: '300px' }}>
<Button {...args} iconStart="cloud" />
<Button {...args} iconEnd="chevronRight" />
<Button {...args} iconStart="cloud" iconEnd="chevronRight" />
</Stack>
</Flex>
),
};
@@ -132,12 +131,12 @@ export const Playground: Story = {
children: 'Button',
},
render: () => (
<Stack>
<Flex direction="column">
{variants.map(variant => (
<Stack key={variant}>
<Flex direction="column" key={variant}>
<Text>{variant}</Text>
{['small', 'medium'].map(size => (
<Inline alignY="center" key={size}>
<Flex align="center" key={size}>
<Button
iconStart="cloud"
variant={variant as ButtonProps['variant']}
@@ -161,10 +160,10 @@ export const Playground: Story = {
>
Button
</Button>
</Inline>
</Flex>
))}
</Stack>
</Flex>
))}
</Stack>
</Flex>
),
};
@@ -17,8 +17,7 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Checkbox } from './Checkbox';
import { Inline } from '../Inline';
import { Stack } from '../Stack';
import { Flex } from '../Flex';
import { Text } from '../Text';
const meta = {
@@ -40,25 +39,25 @@ export const Default: Story = {
export const AllVariants: Story = {
render: () => (
<Inline alignY="center">
<Flex align="center">
<Checkbox />
<Checkbox checked />
<Checkbox label="Checkbox" />
<Checkbox label="Checkbox" checked />
</Inline>
</Flex>
),
};
export const Playground: Story = {
render: () => (
<Stack>
<Flex>
<Text>All variants</Text>
<Inline alignY="center">
<Flex align="center">
<Checkbox />
<Checkbox checked />
<Checkbox label="Checkbox" />
<Checkbox label="Checkbox" checked />
</Inline>
</Stack>
</Flex>
</Flex>
),
};
@@ -17,22 +17,42 @@
import type { PropDef, GetPropDefTypes } from '../../props/prop-def';
const alignValues = ['start', 'center', 'end', 'baseline', 'stretch'] as const;
const directionValues = [
'row',
'column',
'row-reverse',
'column-reverse',
] as const;
const justifyValues = ['start', 'center', 'end', 'between'] as const;
/** @public */
const stackPropDefs = {
const flexPropDefs = {
align: {
type: 'enum',
className: 'cu-align',
values: alignValues,
responsive: true,
default: 'stretch',
},
direction: {
type: 'enum',
className: 'cu-fd',
values: directionValues,
responsive: true,
},
justify: {
type: 'enum',
className: 'cu-jc',
values: justifyValues,
responsive: true,
},
} satisfies {
align: PropDef<(typeof alignValues)[number]>;
direction: PropDef<(typeof directionValues)[number]>;
justify: PropDef<(typeof justifyValues)[number]>;
};
/** @public */
type StackOwnProps = GetPropDefTypes<typeof stackPropDefs>;
type FlexOwnProps = GetPropDefTypes<typeof flexPropDefs>;
export { stackPropDefs };
export type { StackOwnProps };
export { flexPropDefs };
export type { FlexOwnProps };
@@ -16,11 +16,11 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Stack } from './Stack';
import { Flex } from './Flex';
const meta = {
title: 'Components/Stack',
component: Stack,
title: 'Components/Flex',
component: Flex,
argTypes: {
align: {
control: 'inline-radio',
@@ -38,7 +38,7 @@ const meta = {
gap: '4',
children: 'hello world',
},
} satisfies Meta<typeof Stack>;
} satisfies Meta<typeof Flex>;
export default meta;
type Story = StoryObj<typeof meta>;
@@ -65,62 +65,76 @@ export const Default: Story = {
},
};
export const ColumnDirection: Story = {
args: {
direction: 'column',
children: [<DecorativeBox />, <DecorativeBox />, <DecorativeBox />],
},
};
export const RowDirection: Story = {
args: {
direction: 'row',
children: [<DecorativeBox />, <DecorativeBox />, <DecorativeBox />],
},
};
export const AlignLeft: Story = {
render: () => (
<Stack align="start">
<Flex align="start">
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Stack>
</Flex>
),
};
export const AlignCenter: Story = {
render: () => (
<Stack align="center">
<Flex align="center">
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Stack>
</Flex>
),
};
export const AlignRight: Story = {
render: () => (
<Stack align="end">
<Flex align="end">
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Stack>
</Flex>
),
};
export const ResponsiveAlign: Story = {
render: () => (
<Stack align={{ xs: 'start', md: 'center', lg: 'end' }}>
<Flex align={{ xs: 'start', md: 'center', lg: 'end' }}>
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Stack>
</Flex>
),
};
export const ResponsiveGap: Story = {
render: () => (
<Stack gap={{ xs: '4', md: '8', lg: '12' }}>
<Flex gap={{ xs: '4', md: '8', lg: '12' }}>
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Stack>
</Flex>
),
};
export const LargeGap: Story = {
render: () => (
<Stack gap="8">
<Flex gap="8">
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Stack>
</Flex>
),
};
@@ -15,24 +15,24 @@
*/
import { createElement, forwardRef } from 'react';
import { StackProps } from './types';
import { FlexProps } from './types';
import clsx from 'clsx';
import { stackPropDefs } from './Stack.props';
import { flexPropDefs } from './Flex.props';
import { extractProps } from '../../utils/extractProps';
import { gapPropDefs } from '../../props/gap-props';
/** @public */
export const Stack = forwardRef<HTMLDivElement, StackProps>((props, ref) => {
export const Flex = forwardRef<HTMLDivElement, FlexProps>((props, ref) => {
const propDefs = {
...gapPropDefs,
...stackPropDefs,
...flexPropDefs,
};
const { className, style } = extractProps(props, propDefs);
return createElement('div', {
ref,
className: clsx('canon-Stack', className),
className: clsx('canon-Flex', className),
style,
children: props.children,
});
@@ -13,5 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { Inline } from './Inline';
export type { InlineProps } from './types';
export { Flex } from './Flex';
export { flexPropDefs } from './Flex.props';
export type { FlexProps } from './types';
export type { FlexOwnProps } from './Flex.props';
@@ -14,7 +14,6 @@
* limitations under the License.
*/
.canon-Inline {
.canon-Flex {
display: flex;
flex-wrap: wrap;
}
@@ -15,14 +15,16 @@
*/
import type { SpaceProps } from '../../types';
import { StackOwnProps } from './Stack.props';
import { FlexOwnProps } from './Flex.props';
import type { GapProps } from '../../props/gap-props';
/** @public */
export interface StackProps extends SpaceProps {
export interface FlexProps extends SpaceProps {
children: React.ReactNode;
gap?: GapProps['gap'];
align?: StackOwnProps['align'];
align?: FlexOwnProps['align'];
justify?: FlexOwnProps['justify'];
direction?: FlexOwnProps['direction'];
className?: string;
style?: React.CSSProperties;
}
@@ -19,7 +19,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Grid } from './Grid';
import type { GridItemProps } from './types';
import { Box } from '../Box/Box';
import { Stack } from '../Stack';
import { Flex } from '../Flex';
const meta = {
title: 'Components/Grid',
@@ -82,7 +82,7 @@ export const ColumnSizes: Story = {
columns: '12',
},
render: args => (
<Stack gap="4">
<Flex gap="4">
{Array.from({ length: 11 }, (_, i) => (
<Grid {...args} key={i}>
<Grid.Item colSpan={String(i + 1) as GridItemProps['colSpan']}>
@@ -93,7 +93,7 @@ export const ColumnSizes: Story = {
</Grid.Item>
</Grid>
))}
</Stack>
</Flex>
),
};
@@ -102,7 +102,7 @@ export const RowAndColumns: Story = {
columns: '12',
},
render: args => (
<Stack gap="md">
<Flex gap="4">
<Grid {...args} columns="3">
<Grid.Item colSpan="1" rowSpan="2">
<Box
@@ -123,6 +123,6 @@ export const RowAndColumns: Story = {
<FakeBox />
</Grid.Item>
</Grid>
</Stack>
</Flex>
),
};
@@ -17,8 +17,9 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Heading } from './Heading';
import { Stack } from '../Stack';
import { Flex } from '../Flex';
import { Text } from '../Text';
const meta = {
title: 'Components/Heading',
component: Heading,
@@ -41,13 +42,13 @@ export const Title1: Story = {
export const AllVariants: Story = {
render: () => (
<Stack>
<Flex>
<Heading variant="display">Display</Heading>
<Heading variant="title1">Title 1</Heading>
<Heading variant="title2">Title 2</Heading>
<Heading variant="title3">Title 3</Heading>
<Heading variant="title4">Title 4</Heading>
</Stack>
</Flex>
),
};
@@ -69,7 +70,7 @@ export const CustomTag: Story = {
export const Playground: Story = {
render: () => (
<Stack>
<Flex>
<Text>All variants</Text>
<Heading variant="display">Display</Heading>
<Heading variant="title1">Title 1</Heading>
@@ -77,6 +78,6 @@ export const Playground: Story = {
<Heading variant="title3">Title 3</Heading>
<Heading variant="title4">Title 4</Heading>
<Heading variant="title5">Title 5</Heading>
</Stack>
</Flex>
),
};
@@ -1,136 +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 type { Meta, StoryObj } from '@storybook/react';
import { Inline } from './Inline';
import { Box } from '../Box/Box';
const meta = {
title: 'Components/Inline',
component: Inline,
argTypes: {
align: {
control: 'inline-radio',
options: ['left', 'center', 'right'],
},
alignY: {
control: 'inline-radio',
options: ['top', 'center', 'bottom'],
},
children: {
control: false,
},
as: {
control: false,
},
className: {
control: 'text',
},
},
args: {
children: 'hello world',
},
} satisfies Meta<typeof Inline>;
export default meta;
type Story = StoryObj<typeof meta>;
const fakeBlockList = [
{ width: 45, height: 60 },
{ width: 150, height: 75 },
{ width: 80, height: 50 },
{ width: 120, height: 70 },
{ width: 95, height: 65 },
{ width: 80, height: 32 },
{ width: 130, height: 60 },
{ width: 100, height: 80 },
{ width: 140, height: 45 },
{ width: 85, height: 70 },
{ width: 125, height: 50 },
];
const FakeBox = ({
width = 120,
height = 80,
}: {
width?: number;
height?: number;
}) => (
<Box
style={{
background: '#eaf2fd',
borderRadius: '4px',
boxShadow: '0 0 0 1px #2563eb',
width,
height,
backgroundImage:
'url("data:image/svg+xml,%3Csvg%20width%3D%226%22%20height%3D%226%22%20viewBox%3D%220%200%206%206%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cg%20fill%3D%22%232563eb%22%20fill-opacity%3D%220.3%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M5%200h1L0%206V5zM6%205v1H5z%22/%3E%3C/g%3E%3C/svg%3E")',
}}
/>
);
export const Default: Story = {
render: () => (
<Inline>
{fakeBlockList.map((block, index) => (
<FakeBox key={index} width={block.width} height={block.height} />
))}
</Inline>
),
};
export const AlignLeft: Story = {
args: {
align: 'left',
},
};
export const AlignCenter: Story = {
args: {
align: 'center',
},
};
export const AlignRight: Story = {
args: {
align: 'right',
},
};
export const VerticalAlignTop: Story = {
args: {
alignY: 'top',
},
};
export const VerticalAlignCenter: Story = {
args: {
alignY: 'center',
},
};
export const VerticalAlignBottom: Story = {
args: {
alignY: 'bottom',
},
};
export const LargeGap: Story = {
args: {
gap: 'xl',
},
};
@@ -1,85 +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 { createElement, forwardRef } from 'react';
import type { InlineProps } from './types';
import { getClassNames } from '../../utils/getClassNames';
import { JustifyContent, Breakpoint, AlignItems } from '../../types';
import clsx from 'clsx';
// Function to map align values
const mapAlignValue = (value?: InlineProps['align']) => {
if (typeof value === 'string') {
let returnedValue: JustifyContent = 'stretch';
if (value === 'left') returnedValue = 'start';
if (value === 'center') returnedValue = 'center';
if (value === 'right') returnedValue = 'end';
return returnedValue;
} else if (typeof value === 'object') {
const returnedValue: Partial<Record<Breakpoint, JustifyContent>> = {};
for (const [key, val] of Object.entries(value)) {
returnedValue[key as Breakpoint] = mapAlignValue(val) as JustifyContent;
}
return returnedValue;
}
return 'stretch';
};
const mapAlignYValue = (value?: InlineProps['alignY']) => {
if (typeof value === 'string') {
let returnedValue: AlignItems = 'stretch';
if (value === 'top') returnedValue = 'start';
if (value === 'center') returnedValue = 'center';
if (value === 'bottom') returnedValue = 'end';
return returnedValue;
} else if (typeof value === 'object') {
const returnedValue: Partial<Record<Breakpoint, AlignItems>> = {};
for (const [key, val] of Object.entries(value)) {
returnedValue[key as Breakpoint] = mapAlignYValue(val) as AlignItems;
}
return returnedValue;
}
return 'stretch';
};
/** @public */
export const Inline = forwardRef<HTMLElement, InlineProps>((props, ref) => {
const {
as = 'div',
children,
align = 'left',
alignY = 'top',
gap = 'xs',
className,
style,
...restProps
} = props;
// Generate utility class names
const utilityClassNames = getClassNames({
gap,
alignItems: mapAlignYValue(alignY),
justifyContent: mapAlignValue(align),
...restProps,
});
return createElement(as, {
ref,
className: clsx('canon-Inline', utilityClassNames, className),
style,
children,
});
});
@@ -1,41 +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 type {
SpaceProps,
UtilityProps,
AsProps,
Breakpoint,
} from '../../types';
/** @public */
export interface InlineProps extends SpaceProps {
children: React.ReactNode;
as?: AsProps;
gap?: UtilityProps['gap'];
align?:
| 'left'
| 'center'
| 'right'
| Partial<Record<Breakpoint, 'left' | 'center' | 'right'>>;
alignY?:
| 'top'
| 'center'
| 'bottom'
| Partial<Record<Breakpoint, 'top' | 'center' | 'bottom'>>;
className?: string;
style?: React.CSSProperties;
}
@@ -17,7 +17,7 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Input } from './Input';
import { Inline } from '../Inline';
import { Flex } from '../Flex';
const meta = {
title: 'Components/Input',
@@ -33,9 +33,9 @@ export const Primary: Story = {
export const Sizes: Story = {
render: () => (
<Inline>
<Flex>
<Input size="sm" />
<Input size="md" />
</Inline>
</Flex>
),
};
@@ -1,19 +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.
*/
export { Stack } from './Stack';
export { stackPropDefs } from './Stack.props';
export type { StackProps } from './types';
export type { StackOwnProps } from './Stack.props';
@@ -1,20 +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.
*/
.canon-Stack {
display: flex;
flex-direction: column;
}
@@ -17,7 +17,7 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Text } from './Text';
import { Stack } from '../Stack';
import { Flex } from '../Flex';
const meta = {
title: 'Components/Text',
@@ -40,7 +40,7 @@ export const Default: Story = {
export const AllVariants: Story = {
render: () => (
<Stack gap="md">
<Flex gap="4">
<Text variant="subtitle" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son. Who is
@@ -61,13 +61,13 @@ export const AllVariants: Story = {
have none, but that man&apos;s father is my father&apos;s son. Who is
in the painting?
</Text>
</Stack>
</Flex>
),
};
export const AllWeights: Story = {
render: () => (
<Stack gap="md">
<Flex gap="4">
<Text weight="regular" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son. Who is
@@ -78,7 +78,7 @@ export const AllWeights: Story = {
have none, but that man&apos;s father is my father&apos;s son. Who is
in the painting?
</Text>
</Stack>
</Flex>
),
};
@@ -93,7 +93,7 @@ export const Responsive: Story = {
export const Playground: Story = {
render: () => (
<Stack>
<Flex>
<Text>Subtitle</Text>
<Text variant="subtitle" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, Brothers and sisters I
@@ -118,6 +118,6 @@ export const Playground: Story = {
have none, but that man&apos;s father is my father&apos;s son. Who is
in the painting?
</Text>
</Stack>
</Flex>
),
};
+1 -2
View File
@@ -17,8 +17,7 @@
/* Components */
@import '../components/Box/styles.css';
@import '../components/Button/styles.css';
@import '../components/Stack/styles.css';
@import '../components/Inline/styles.css';
@import '../components/Flex/styles.css';
@import '../components/Grid/styles.css';
@import '../components/Container/styles.css';
@import '../components/Icon/styles.css';
-18
View File
@@ -14,24 +14,6 @@
* limitations under the License.
*/
/* Default utilities */
@import './utilities/xs.css';
/* Min 640px */
@import './utilities/sm.css';
/* Min 768px */
@import './utilities/md.css';
/* Min 1024px */
@import './utilities/lg.css';
/* Min 1280px */
@import './utilities/xl.css';
/* Min 1536px */
@import './utilities/2xl.css';
/* Padding */
@import './utilities/p.css';
@import './utilities/pl.css';
-164
View File
@@ -1,164 +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.
*/
@layer utilities {
@media (min-width: 1536px) {
.cu-2xl-block {
display: block;
}
.cu-2xl-border-base {
border-color: var(--canon-border);
border-width: 1px;
border-style: solid;
}
.cu-2xl-border-error {
border-color: var(--canon-border-danger);
border-width: 1px;
border-style: solid;
}
.cu-2xl-border-none {
border-color: transparent;
border-width: 0;
}
.cu-2xl-border-selected {
border-color: var(--canon-border-focus);
border-width: 1px;
border-style: solid;
}
.cu-2xl-border-warning {
border-color: var(--canon-border-warning);
border-width: 1px;
border-style: solid;
}
.cu-2xl-flex {
display: flex;
}
.cu-2xl-flex-col {
flex-direction: column;
}
.cu-2xl-flex-nowrap {
flex-wrap: nowrap;
}
.cu-2xl-flex-row {
flex-direction: row;
}
.cu-2xl-flex-wrap {
flex-wrap: wrap;
}
.cu-2xl-flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.cu-2xl-hidden {
display: none;
}
.cu-2xl-inline {
display: inline;
}
.cu-2xl-inline-block {
display: inline-block;
}
.cu-2xl-items-center {
align-items: center;
}
.cu-2xl-items-end {
align-items: flex-end;
}
.cu-2xl-items-start {
align-items: flex-start;
}
.cu-2xl-items-stretch {
align-items: stretch;
}
.cu-2xl-justify-around {
justify-content: space-around;
}
.cu-2xl-justify-between {
justify-content: space-between;
}
.cu-2xl-justify-center {
justify-content: center;
}
.cu-2xl-justify-end {
justify-content: flex-end;
}
.cu-2xl-justify-start {
justify-content: flex-start;
}
.cu-2xl-justify-stretch {
justify-content: stretch;
}
.cu-2xl-rounded-2xl {
border-radius: var(--canon-radius-full);
}
.cu-2xl-rounded-2xs {
border-radius: var(--canon-radius-1);
}
.cu-2xl-rounded-lg {
border-radius: var(--canon-radius-5);
}
.cu-2xl-rounded-md {
border-radius: var(--canon-radius-4);
}
.cu-2xl-rounded-none {
border-radius: 0;
}
.cu-2xl-rounded-sm {
border-radius: var(--canon-radius-3);
}
.cu-2xl-rounded-xl {
border-radius: var(--canon-radius-6);
}
.cu-2xl-rounded-xs {
border-radius: var(--canon-radius-2);
}
.cu-2xl-wrap-reverse {
flex-wrap: wrap-reverse;
}
}
}
+216
View File
@@ -10,6 +10,42 @@
align-items: end;
}
.cu-align-stretch {
align-items: stretch;
}
.cu-jc-start {
justify-content: start;
}
.cu-jc-center {
justify-content: center;
}
.cu-jc-end {
justify-content: end;
}
.cu-jc-between {
justify-content: space-between;
}
.cu-fd-row {
flex-direction: row;
}
.cu-fd-column {
flex-direction: column;
}
.cu-fd-row-reverse {
flex-direction: row-reverse;
}
.cu-fd-column-reverse {
flex-direction: column-reverse;
}
/* Breakpoint xs */
@media (min-width: 640px) {
.xs\:cu-align-start {
@@ -23,6 +59,42 @@
.xs\:cu-align-end {
align-items: end;
}
.xs\:cu-align-stretch {
align-items: stretch;
}
.xs\:cu-jc-start {
justify-content: start;
}
.xs\:cu-jc-center {
justify-content: center;
}
.xs\:cu-jc-end {
justify-content: end;
}
.xs\:cu-jc-between {
justify-content: space-between;
}
.xs\:cu-fd-row {
flex-direction: row;
}
.xs\:cu-fd-column {
flex-direction: column;
}
.xs\:cu-fd-row-reverse {
flex-direction: row-reverse;
}
.xs\:cu-fd-column-reverse {
flex-direction: column-reverse;
}
}
/* Breakpoint sm */
@@ -38,6 +110,42 @@
.sm\:cu-align-end {
align-items: end;
}
.sm\:cu-align-stretch {
align-items: stretch;
}
.sm\:cu-jc-start {
justify-content: start;
}
.sm\:cu-jc-center {
justify-content: center;
}
.sm\:cu-jc-end {
justify-content: end;
}
.sm\:cu-jc-between {
justify-content: space-between;
}
.sm\:cu-fd-row {
flex-direction: row;
}
.sm\:cu-fd-column {
flex-direction: column;
}
.sm\:cu-fd-row-reverse {
flex-direction: row-reverse;
}
.sm\:cu-fd-column-reverse {
flex-direction: column-reverse;
}
}
/* Breakpoint md */
@@ -53,6 +161,42 @@
.md\:cu-align-end {
align-items: end;
}
.md\:cu-align-stretch {
align-items: stretch;
}
.md\:cu-jc-start {
justify-content: start;
}
.md\:cu-jc-center {
justify-content: center;
}
.md\:cu-jc-end {
justify-content: end;
}
.md\:cu-jc-between {
justify-content: space-between;
}
.md\:cu-fd-row {
flex-direction: row;
}
.md\:cu-fd-column {
flex-direction: column;
}
.md\:cu-fd-row-reverse {
flex-direction: row-reverse;
}
.md\:cu-fd-column-reverse {
flex-direction: column-reverse;
}
}
/* Breakpoint lg */
@@ -68,6 +212,42 @@
.lg\:cu-align-end {
align-items: end;
}
.lg\:cu-align-stretch {
align-items: stretch;
}
.lg\:cu-jc-start {
justify-content: start;
}
.lg\:cu-jc-center {
justify-content: center;
}
.lg\:cu-jc-end {
justify-content: end;
}
.lg\:cu-jc-between {
justify-content: space-between;
}
.lg\:cu-fd-row {
flex-direction: row;
}
.lg\:cu-fd-column {
flex-direction: column;
}
.lg\:cu-fd-row-reverse {
flex-direction: row-reverse;
}
.lg\:cu-fd-column-reverse {
flex-direction: column-reverse;
}
}
/* Breakpoint xl */
@@ -83,4 +263,40 @@
.xl\:cu-align-end {
align-items: end;
}
.xl\:cu-align-stretch {
align-items: stretch;
}
.xl\:cu-jc-start {
justify-content: start;
}
.xl\:cu-jc-center {
justify-content: center;
}
.xl\:cu-jc-end {
justify-content: end;
}
.xl\:cu-jc-between {
justify-content: space-between;
}
.xl\:cu-fd-row {
flex-direction: row;
}
.xl\:cu-fd-column {
flex-direction: column;
}
.xl\:cu-fd-row-reverse {
flex-direction: row-reverse;
}
.xl\:cu-fd-column-reverse {
flex-direction: column-reverse;
}
}
-164
View File
@@ -1,164 +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.
*/
@layer utilities {
@media (min-width: 1024px) {
.cu-lg-block {
display: block;
}
.cu-lg-border-base {
border-color: var(--canon-border);
border-width: 1px;
border-style: solid;
}
.cu-lg-border-error {
border-color: var(--canon-border-danger);
border-width: 1px;
border-style: solid;
}
.cu-lg-border-none {
border-color: transparent;
border-width: 0;
}
.cu-lg-border-selected {
border-color: var(--canon-border-focus);
border-width: 1px;
border-style: solid;
}
.cu-lg-border-warning {
border-color: var(--canon-border-warning);
border-width: 1px;
border-style: solid;
}
.cu-lg-flex {
display: flex;
}
.cu-lg-flex-col {
flex-direction: column;
}
.cu-lg-flex-nowrap {
flex-wrap: nowrap;
}
.cu-lg-flex-row {
flex-direction: row;
}
.cu-lg-flex-wrap {
flex-wrap: wrap;
}
.cu-lg-flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.cu-lg-hidden {
display: none;
}
.cu-lg-inline {
display: inline;
}
.cu-lg-inline-block {
display: inline-block;
}
.cu-lg-items-center {
align-items: center;
}
.cu-lg-items-end {
align-items: flex-end;
}
.cu-lg-items-start {
align-items: flex-start;
}
.cu-lg-items-stretch {
align-items: stretch;
}
.cu-lg-justify-around {
justify-content: space-around;
}
.cu-lg-justify-between {
justify-content: space-between;
}
.cu-lg-justify-center {
justify-content: center;
}
.cu-lg-justify-end {
justify-content: flex-end;
}
.cu-lg-justify-start {
justify-content: flex-start;
}
.cu-lg-justify-stretch {
justify-content: stretch;
}
.cu-lg-rounded-2xl {
border-radius: var(--canon-radius-full);
}
.cu-lg-rounded-2xs {
border-radius: var(--canon-radius-1);
}
.cu-lg-rounded-lg {
border-radius: var(--canon-radius-5);
}
.cu-lg-rounded-md {
border-radius: var(--canon-radius-4);
}
.cu-lg-rounded-none {
border-radius: 0;
}
.cu-lg-rounded-sm {
border-radius: var(--canon-radius-3);
}
.cu-lg-rounded-xl {
border-radius: var(--canon-radius-6);
}
.cu-lg-rounded-xs {
border-radius: var(--canon-radius-2);
}
.cu-lg-wrap-reverse {
flex-wrap: wrap-reverse;
}
}
}
-164
View File
@@ -1,164 +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.
*/
@layer utilities {
@media (min-width: 768px) {
.cu-md-block {
display: block;
}
.cu-md-border-base {
border-color: var(--canon-border);
border-width: 1px;
border-style: solid;
}
.cu-md-border-error {
border-color: var(--canon-border-danger);
border-width: 1px;
border-style: solid;
}
.cu-md-border-none {
border-color: transparent;
border-width: 0;
}
.cu-md-border-selected {
border-color: var(--canon-border-focus);
border-width: 1px;
border-style: solid;
}
.cu-md-border-warning {
border-color: var(--canon-border-warning);
border-width: 1px;
border-style: solid;
}
.cu-md-flex {
display: flex;
}
.cu-md-flex-col {
flex-direction: column;
}
.cu-md-flex-nowrap {
flex-wrap: nowrap;
}
.cu-md-flex-row {
flex-direction: row;
}
.cu-md-flex-wrap {
flex-wrap: wrap;
}
.cu-md-flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.cu-md-hidden {
display: none;
}
.cu-md-inline {
display: inline;
}
.cu-md-inline-block {
display: inline-block;
}
.cu-md-items-center {
align-items: center;
}
.cu-md-items-end {
align-items: flex-end;
}
.cu-md-items-start {
align-items: flex-start;
}
.cu-md-items-stretch {
align-items: stretch;
}
.cu-md-justify-around {
justify-content: space-around;
}
.cu-md-justify-between {
justify-content: space-between;
}
.cu-md-justify-center {
justify-content: center;
}
.cu-md-justify-end {
justify-content: flex-end;
}
.cu-md-justify-start {
justify-content: flex-start;
}
.cu-md-justify-stretch {
justify-content: stretch;
}
.cu-md-rounded-2xl {
border-radius: var(--canon-radius-full);
}
.cu-md-rounded-2xs {
border-radius: var(--canon-radius-1);
}
.cu-md-rounded-lg {
border-radius: var(--canon-radius-5);
}
.cu-md-rounded-md {
border-radius: var(--canon-radius-4);
}
.cu-md-rounded-none {
border-radius: 0;
}
.cu-md-rounded-sm {
border-radius: var(--canon-radius-3);
}
.cu-md-rounded-xl {
border-radius: var(--canon-radius-6);
}
.cu-md-rounded-xs {
border-radius: var(--canon-radius-2);
}
.cu-md-wrap-reverse {
flex-wrap: wrap-reverse;
}
}
}
-164
View File
@@ -1,164 +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.
*/
@layer utilities {
@media (min-width: 1280px) {
.cu-xl-block {
display: block;
}
.cu-xl-border-base {
border-color: var(--canon-border);
border-width: 1px;
border-style: solid;
}
.cu-xl-border-error {
border-color: var(--canon-border-danger);
border-width: 1px;
border-style: solid;
}
.cu-xl-border-none {
border-color: transparent;
border-width: 0;
}
.cu-xl-border-selected {
border-color: var(--canon-border-focus);
border-width: 1px;
border-style: solid;
}
.cu-xl-border-warning {
border-color: var(--canon-border-warning);
border-width: 1px;
border-style: solid;
}
.cu-xl-flex {
display: flex;
}
.cu-xl-flex-col {
flex-direction: column;
}
.cu-xl-flex-nowrap {
flex-wrap: nowrap;
}
.cu-xl-flex-row {
flex-direction: row;
}
.cu-xl-flex-wrap {
flex-wrap: wrap;
}
.cu-xl-flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.cu-xl-hidden {
display: none;
}
.cu-xl-inline {
display: inline;
}
.cu-xl-inline-block {
display: inline-block;
}
.cu-xl-items-center {
align-items: center;
}
.cu-xl-items-end {
align-items: flex-end;
}
.cu-xl-items-start {
align-items: flex-start;
}
.cu-xl-items-stretch {
align-items: stretch;
}
.cu-xl-justify-around {
justify-content: space-around;
}
.cu-xl-justify-between {
justify-content: space-between;
}
.cu-xl-justify-center {
justify-content: center;
}
.cu-xl-justify-end {
justify-content: flex-end;
}
.cu-xl-justify-start {
justify-content: flex-start;
}
.cu-xl-justify-stretch {
justify-content: stretch;
}
.cu-xl-rounded-2xl {
border-radius: var(--canon-radius-full);
}
.cu-xl-rounded-2xs {
border-radius: var(--canon-radius-1);
}
.cu-xl-rounded-lg {
border-radius: var(--canon-radius-5);
}
.cu-xl-rounded-md {
border-radius: var(--canon-radius-4);
}
.cu-xl-rounded-none {
border-radius: 0;
}
.cu-xl-rounded-sm {
border-radius: var(--canon-radius-3);
}
.cu-xl-rounded-xl {
border-radius: var(--canon-radius-6);
}
.cu-xl-rounded-xs {
border-radius: var(--canon-radius-2);
}
.cu-xl-wrap-reverse {
flex-wrap: wrap-reverse;
}
}
}
+1 -2
View File
@@ -26,8 +26,7 @@ export * from './contexts/canon';
// Layout components
export * from './components/Box';
export * from './components/Grid';
export * from './components/Stack';
export * from './components/Inline';
export * from './components/Flex';
export * from './components/Container';
export * from './components/Text';
export * from './components/Heading';
-206
View File
@@ -1,206 +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 type { Breakpoint, UtilityProps } from '../types';
const spaceMap = (type: string) => ({
none: `${type}-none`,
'2xs': `${type}-2xs`,
xs: `${type}-xs`,
sm: `${type}-sm`,
md: `${type}-md`,
lg: `${type}-lg`,
xl: `${type}-xl`,
'2xl': `${type}-2xl`,
});
const valueMap: Record<string, Record<string, string>> = {
alignItems: {
stretch: 'items-stretch',
start: 'items-start',
center: 'items-center',
end: 'items-end',
},
border: {
none: 'border-none',
base: 'border-base',
error: 'border-error',
warning: 'border-warning',
selected: 'border-selected',
},
borderRadius: {
none: 'rounded-none',
'2xs': 'rounded-2xs',
xs: 'rounded-xs',
sm: 'rounded-sm',
md: 'rounded-md',
lg: 'rounded-lg',
xl: 'rounded-xl',
'2xl': 'rounded-2xl',
},
colEnd: {
1: 'col-end-1',
2: 'col-end-2',
3: 'col-end-3',
4: 'col-end-4',
5: 'col-end-5',
6: 'col-end-6',
7: 'col-end-7',
8: 'col-end-8',
9: 'col-end-9',
10: 'col-end-10',
11: 'col-end-11',
12: 'col-end-12',
auto: 'col-end-auto',
},
colSpan: {
1: 'col-span-1',
2: 'col-span-2',
3: 'col-span-3',
4: 'col-span-4',
5: 'col-span-5',
6: 'col-span-6',
7: 'col-span-7',
8: 'col-span-8',
9: 'col-span-9',
10: 'col-span-10',
11: 'col-span-11',
12: 'col-span-12',
auto: 'col-span-auto',
},
colStart: {
1: 'col-start-1',
2: 'col-start-2',
3: 'col-start-3',
4: 'col-start-4',
5: 'col-start-5',
6: 'col-start-6',
7: 'col-start-7',
8: 'col-start-8',
9: 'col-start-9',
10: 'col-start-10',
11: 'col-start-11',
12: 'col-start-12',
auto: 'col-start-auto',
},
columns: {
1: 'grid-cols-1',
2: 'grid-cols-2',
3: 'grid-cols-3',
4: 'grid-cols-4',
5: 'grid-cols-5',
6: 'grid-cols-6',
7: 'grid-cols-7',
8: 'grid-cols-8',
9: 'grid-cols-9',
10: 'grid-cols-10',
11: 'grid-cols-11',
12: 'grid-cols-12',
auto: 'grid-cols-auto',
},
display: {
none: 'hidden',
flex: 'flex',
block: 'block',
inline: 'inline',
},
flexDirection: {
row: 'flex-row',
column: 'flex-col',
},
flexWrap: {
wrap: 'flex-wrap',
nowrap: 'flex-nowrap',
'wrap-reverse': 'flex-wrap-reverse',
},
gap: spaceMap('gap'),
justifyContent: {
stretch: 'justify-stretch',
start: 'justify-start',
center: 'justify-center',
end: 'justify-end',
around: 'justify-around',
between: 'justify-between',
},
margin: spaceMap('m'),
marginBottom: spaceMap('mb'),
marginLeft: spaceMap('ml'),
marginRight: spaceMap('mr'),
marginTop: spaceMap('mt'),
marginX: spaceMap('mx'),
marginY: spaceMap('my'),
padding: spaceMap('p'),
paddingBottom: spaceMap('pb'),
paddingLeft: spaceMap('pl'),
paddingRight: spaceMap('pr'),
paddingTop: spaceMap('pt'),
paddingX: spaceMap('px'),
paddingY: spaceMap('py'),
rowSpan: {
1: 'row-span-1',
2: 'row-span-2',
3: 'row-span-3',
4: 'row-span-4',
5: 'row-span-5',
6: 'row-span-6',
7: 'row-span-7',
8: 'row-span-8',
9: 'row-span-9',
10: 'row-span-10',
11: 'row-span-11',
12: 'row-span-12',
full: 'row-span-full',
},
};
const generateClassNames = (propName: string, propValue: any) => {
const classNames: string[] = [];
// If the property name is not in the valueMap, return an empty array
if (!valueMap.hasOwnProperty(propName)) {
return classNames;
}
if (typeof propValue === 'string' || typeof propValue === 'number') {
// If the property value is a string, map it to the valueMap
const value = valueMap[propName]?.[propValue] || propValue;
classNames.push(`cu-${value}`);
} else if (typeof propValue === 'object') {
// If the property value is an object, map each key-value pair
Object.entries(propValue as Record<Breakpoint, string>).forEach(
([breakpoint, value]) => {
const mappedValue = valueMap[propName]?.[value] || value;
if (breakpoint === 'xs') {
classNames.push(`cu-${mappedValue}`);
} else {
classNames.push(`cu-${breakpoint}-${mappedValue}`);
}
},
);
}
return classNames;
};
export const getClassNames = (props: UtilityProps) => {
const classNames: string[] = [];
Object.entries(props).forEach(([propName, propValue]) => {
classNames.push(...generateClassNames(propName, propValue));
});
return classNames.filter(Boolean).join(' ');
};