diff --git a/packages/canon/docs/Home.mdx b/packages/canon/docs/Home.mdx index cbe84fcfb1..5b85ab15ad 100644 --- a/packages/canon/docs/Home.mdx +++ b/packages/canon/docs/Home.mdx @@ -50,7 +50,7 @@ import { list } from './components/Roadmap/list'; {
Arrange your components in a row
- +
Container
diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index 5a80802cc6..c27b8da09b 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -5,11 +5,9 @@ ```ts /// -import { CSSProperties } from 'react'; -import { DetailedReactHTMLElement } from 'react'; -import { DOMElement } from 'react'; +import { ForwardRefExoticComponent } from 'react'; import { default as React_2 } from 'react'; -import { ReactNode } from 'react'; +import { RefAttributes } from 'react'; // @public (undocumented) export type AlignItemsProps = @@ -59,13 +57,8 @@ export type BorderRadiusProps = | Partial>; // @public (undocumented) -export const Box: (props: BoxProps) => DOMElement< - { - className: string; - style: CSSProperties | undefined; - children: ReactNode; - }, - Element +export const Box: ForwardRefExoticComponent< + BoxProps & RefAttributes >; // @public (undocumented) @@ -154,6 +147,33 @@ export interface ColorProps { // @public (undocumented) export type Columns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; +// @public (undocumented) +export const Container: React_2.ForwardRefExoticComponent< + ContainerProps & React_2.RefAttributes +>; + +// @public (undocumented) +export interface ContainerProps + extends Omit< + SpaceProps, + | 'padding' + | 'paddingLeft' + | 'paddingRight' + | 'paddingX' + | 'margin' + | 'marginLeft' + | 'marginRight' + | 'marginX' + | 'gap' + > { + // (undocumented) + children?: React.ReactNode; + // (undocumented) + className?: string; + // (undocumented) + style?: React.CSSProperties; +} + // @public (undocumented) export type DisplayProps = | 'flex' @@ -178,20 +198,11 @@ export type FlexWrapProps = export type Gap = Space | Partial>; // @public (undocumented) -export const Grid: { - (props: GridProps): DetailedReactHTMLElement< - { - className: string; - style: CSSProperties | undefined; - }, - HTMLElement - >; - Item: (props: GridItemProps) => DetailedReactHTMLElement< - { - className: string; - style: CSSProperties | undefined; - }, - HTMLElement +export const Grid: ForwardRefExoticComponent< + GridProps & RefAttributes +> & { + Item: ForwardRefExoticComponent< + GridItemProps & RefAttributes >; }; @@ -251,13 +262,8 @@ export type IconNames = | 'trash'; // @public (undocumented) -export const Inline: (props: InlineProps) => DetailedReactHTMLElement< - { - className: string; - style: CSSProperties | undefined; - children: ReactNode; - }, - HTMLElement +export const Inline: ForwardRefExoticComponent< + InlineProps & RefAttributes >; // @public (undocumented) @@ -396,13 +402,8 @@ export interface SpaceProps { } // @public (undocumented) -export const Stack: (props: StackProps) => DetailedReactHTMLElement< - { - className: string; - style: CSSProperties | undefined; - children: ReactNode; - }, - HTMLElement +export const Stack: ForwardRefExoticComponent< + StackProps & RefAttributes >; // @public (undocumented) diff --git a/packages/canon/src/components/Box/Box.tsx b/packages/canon/src/components/Box/Box.tsx index d134abff35..a470c0584d 100644 --- a/packages/canon/src/components/Box/Box.tsx +++ b/packages/canon/src/components/Box/Box.tsx @@ -14,13 +14,13 @@ * limitations under the License. */ -import { createElement } from 'react'; +import { createElement, forwardRef } from 'react'; import { boxSprinkles } from './sprinkles.css'; import { base } from './box.css'; import { BoxProps } from './types'; /** @public */ -export const Box = (props: BoxProps) => { +export const Box = forwardRef((props, ref) => { const { as = 'div', className, style, children, ...restProps } = props; // Generate the list of class names @@ -32,8 +32,9 @@ export const Box = (props: BoxProps) => { .join(' '); return createElement(as, { + ref, className: classNames, style, children, }); -}; +}); diff --git a/packages/canon/src/components/Box/Docs.mdx b/packages/canon/src/components/Box/Docs.mdx index c5eec1870b..11cc53b148 100644 --- a/packages/canon/src/components/Box/Docs.mdx +++ b/packages/canon/src/components/Box/Docs.mdx @@ -17,7 +17,7 @@ import { spacingProperties } from '../../layout/sprinkles.css'; Installation - + API reference diff --git a/packages/canon/src/components/Container/Container.stories.tsx b/packages/canon/src/components/Container/Container.stories.tsx new file mode 100644 index 0000000000..bedf96ec9e --- /dev/null +++ b/packages/canon/src/components/Container/Container.stories.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Box } from '../Box/Box'; +import { argTypesSpacing } from '../../../docs/utils/argTypes'; +import { Container } from './Container'; + +const argTypesSpacingWithoutLeftAndRight = { ...argTypesSpacing }; +delete argTypesSpacingWithoutLeftAndRight.padding; +delete argTypesSpacingWithoutLeftAndRight.paddingLeft; +delete argTypesSpacingWithoutLeftAndRight.paddingRight; +delete argTypesSpacingWithoutLeftAndRight.paddingX; +delete argTypesSpacingWithoutLeftAndRight.margin; +delete argTypesSpacingWithoutLeftAndRight.marginLeft; +delete argTypesSpacingWithoutLeftAndRight.marginRight; +delete argTypesSpacingWithoutLeftAndRight.marginX; +delete argTypesSpacingWithoutLeftAndRight.gap; + +const meta = { + title: 'Components/Container', + component: Container, + argTypes: { + ...argTypesSpacingWithoutLeftAndRight, + children: { + control: false, + }, + className: { + control: 'text', + }, + }, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const FakeBox = () => ( + +); + +export const Default: Story = { + args: {}, + render: args => ( + + + + ), +}; diff --git a/packages/canon/src/components/Container/Container.tsx b/packages/canon/src/components/Container/Container.tsx new file mode 100644 index 0000000000..b27775e116 --- /dev/null +++ b/packages/canon/src/components/Container/Container.tsx @@ -0,0 +1,37 @@ +/* + * 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, { forwardRef } from 'react'; +import { containerSprinkles } from './sprinkles.css'; +import { ContainerProps } from './types'; + +/** @public */ +export const Container = forwardRef( + (props, ref) => { + const { children, className, style, ...restProps } = props; + + const containerClassName = containerSprinkles(restProps); + + return ( +
+ {children} +
+ ); + }, +); diff --git a/packages/canon/src/components/Container/Docs.mdx b/packages/canon/src/components/Container/Docs.mdx new file mode 100644 index 0000000000..733dbae637 --- /dev/null +++ b/packages/canon/src/components/Container/Docs.mdx @@ -0,0 +1,78 @@ +import { Meta, Unstyled, Source } from '@storybook/blocks'; +import * as ContainerStories from './Container.stories'; +import { Title, Text } from '../../../docs/components'; +import { PropsTable, getBoxProps } from '../../../docs/components/PropsTable'; +import { spacingProperties } from '../../layout/sprinkles.css'; +import { containerProperties } from './sprinkles.css'; + + + + + +Container + + + The container component let you use our default max-width and center the + content on the page. + + +Installation + + + + API reference + + + + +Examples + +Simple +A simple example of how to use the Container component. + + + Hello World + Hello World + Hello World +`} + language="tsx" + dark +/> + +Responsive padding & margin + + The Container component also supports responsive values, making it easy to + create responsive designs. + + + Hello World + Hello World + Hello World +`} + language="tsx" + dark +/> + + diff --git a/packages/canon/src/components/Container/index.tsx b/packages/canon/src/components/Container/index.tsx new file mode 100644 index 0000000000..81614c352d --- /dev/null +++ b/packages/canon/src/components/Container/index.tsx @@ -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 { Container } from './Container'; +export type { ContainerProps } from './types'; diff --git a/packages/canon/src/components/Container/sprinkles.css.ts b/packages/canon/src/components/Container/sprinkles.css.ts new file mode 100644 index 0000000000..989e369433 --- /dev/null +++ b/packages/canon/src/components/Container/sprinkles.css.ts @@ -0,0 +1,40 @@ +/* + * 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, space } from '../../layout/properties'; +import { colorProperties, spacingProperties } from '../../layout/sprinkles.css'; + +export const containerProperties = defineProperties({ + conditions: breakpoints, + defaultCondition: 'xs', + properties: { + paddingTop: space, + paddingBottom: space, + marginTop: space, + marginBottom: space, + }, + shorthands: { + paddingY: ['paddingTop', 'paddingBottom'], + marginY: ['marginTop', 'marginBottom'], + }, +}); + +export const containerSprinkles = createSprinkles( + spacingProperties, + colorProperties, + containerProperties, +); diff --git a/packages/canon/src/components/Container/styles.css b/packages/canon/src/components/Container/styles.css new file mode 100644 index 0000000000..4da813623f --- /dev/null +++ b/packages/canon/src/components/Container/styles.css @@ -0,0 +1,5 @@ +.container { + max-width: var(--canon-container-max-width); + margin: 0 auto; + padding: 0 var(--canon-container-padding); +} diff --git a/packages/canon/src/components/Container/types.ts b/packages/canon/src/components/Container/types.ts new file mode 100644 index 0000000000..b1f414cf30 --- /dev/null +++ b/packages/canon/src/components/Container/types.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { SpaceProps } from '../../layout/types'; + +/** @public */ +export interface ContainerProps + extends Omit< + SpaceProps, + | 'padding' + | 'paddingLeft' + | 'paddingRight' + | 'paddingX' + | 'margin' + | 'marginLeft' + | 'marginRight' + | 'marginX' + | 'gap' + > { + children?: React.ReactNode; + className?: string; + style?: React.CSSProperties; +} diff --git a/packages/canon/src/components/Grid/Docs.mdx b/packages/canon/src/components/Grid/Docs.mdx index c37d083ef1..4a3d64d06f 100644 --- a/packages/canon/src/components/Grid/Docs.mdx +++ b/packages/canon/src/components/Grid/Docs.mdx @@ -15,14 +15,14 @@ import { spacingProperties } from '../../layout/sprinkles.css'; Installation - + Anatomy The grid component is made of two parts: the grid container and the grid item. Import all parts and piece them together. ( diff --git a/packages/canon/src/components/Grid/Grid.tsx b/packages/canon/src/components/Grid/Grid.tsx index 8e426ff49b..2b61d1d3d4 100644 --- a/packages/canon/src/components/Grid/Grid.tsx +++ b/packages/canon/src/components/Grid/Grid.tsx @@ -14,12 +14,11 @@ * limitations under the License. */ -import { createElement } from 'react'; +import { createElement, forwardRef } from 'react'; import { GridItemProps, GridProps } from './types'; import { gridItemSprinkles, gridSprinkles } from './sprinkles.css'; -/** @public */ -export const Grid = (props: GridProps) => { +const GridBase = forwardRef((props, ref) => { const { children, columns, @@ -42,14 +41,15 @@ export const Grid = (props: GridProps) => { return createElement( 'div', { + ref, className: classNames, style, }, children, ); -}; +}); -const GridItem = (props: GridItemProps) => { +const GridItem = forwardRef((props, ref) => { const { children, rowSpan, colSpan, start, end, className, style } = props; const sprinklesClassName = gridItemSprinkles({ @@ -63,7 +63,16 @@ const GridItem = (props: GridItemProps) => { .filter(Boolean) .join(' '); - return createElement('div', { className: classNames, style }, children); -}; + return createElement( + 'div', + { + ref, + className: classNames, + style, + }, + children, + ); +}); -Grid.Item = GridItem; +/** @public */ +export const Grid = Object.assign(GridBase, { Item: GridItem }); diff --git a/packages/canon/src/components/Inline/Docs.mdx b/packages/canon/src/components/Inline/Docs.mdx index fb4e664b42..0469ff0860 100644 --- a/packages/canon/src/components/Inline/Docs.mdx +++ b/packages/canon/src/components/Inline/Docs.mdx @@ -18,7 +18,11 @@ import { inlineProperties } from './sprinkles.css'; Installation - + API reference diff --git a/packages/canon/src/components/Inline/Inline.tsx b/packages/canon/src/components/Inline/Inline.tsx index a46d4dc948..b583b2ae08 100644 --- a/packages/canon/src/components/Inline/Inline.tsx +++ b/packages/canon/src/components/Inline/Inline.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createElement } from 'react'; +import { createElement, forwardRef } from 'react'; import { inlineSprinkles } from './sprinkles.css'; import type { InlineProps } from './types'; @@ -33,7 +33,7 @@ const alignToFlexAlignY = (align: InlineProps['align']) => { }; /** @public */ -export const Inline = (props: InlineProps) => { +export const Inline = forwardRef<HTMLElement, InlineProps>((props, ref) => { const { as = 'div', children, @@ -59,8 +59,9 @@ export const Inline = (props: InlineProps) => { .join(' '); return createElement(as, { + ref, className: classNames, style, children, }); -}; +}); diff --git a/packages/canon/src/components/Stack/Docs.mdx b/packages/canon/src/components/Stack/Docs.mdx index 62fd06cc62..5ec864937b 100644 --- a/packages/canon/src/components/Stack/Docs.mdx +++ b/packages/canon/src/components/Stack/Docs.mdx @@ -17,7 +17,11 @@ import { spacingProperties } from '../../layout/sprinkles.css'; </Text> <Title type="h2">Installation - + API reference diff --git a/packages/canon/src/components/Stack/Stack.tsx b/packages/canon/src/components/Stack/Stack.tsx index 85a67db05d..e0c36f2043 100644 --- a/packages/canon/src/components/Stack/Stack.tsx +++ b/packages/canon/src/components/Stack/Stack.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createElement } from 'react'; +import { createElement, forwardRef } from 'react'; import { StackProps } from './types'; import { stackSprinkles } from './sprinkles.css'; @@ -26,7 +26,7 @@ const alignToFlexAlign = (align: StackProps['align']) => { }; /** @public */ -export const Stack = (props: StackProps) => { +export const Stack = forwardRef<HTMLDivElement, StackProps>((props, ref) => { const { as = 'div', children, @@ -53,8 +53,9 @@ export const Stack = (props: StackProps) => { .join(' '); return createElement(as, { + ref, className: classNames, style, children, }); -}; +}); diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 5901f87bc0..6497be2945 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -28,6 +28,7 @@ export * from './components/Box'; export * from './components/Grid'; export * from './components/Stack'; export * from './components/Inline'; +export * from './components/Container'; // UI components export * from './components/Button'; diff --git a/packages/canon/src/theme/styles.css b/packages/canon/src/theme/styles.css index 65aaabe909..ad2433ef74 100644 --- a/packages/canon/src/theme/styles.css +++ b/packages/canon/src/theme/styles.css @@ -26,3 +26,4 @@ @import '../components/Stack/styles.css'; @import '../components/Inline/styles.css'; @import '../components/Grid/styles.css'; +@import '../components/Container/styles.css'; diff --git a/packages/canon/src/theme/theme.css b/packages/canon/src/theme/theme.css index 6784cb336f..ef78b2221a 100644 --- a/packages/canon/src/theme/theme.css +++ b/packages/canon/src/theme/theme.css @@ -74,6 +74,10 @@ --canon-spacing-lg: 2rem; --canon-spacing-xl: 3.25rem; --canon-spacing-2xl: 5.25rem; + + /* Container */ + --canon-container-max-width: 1200px; + --canon-container-padding: 1rem; } /* Dark theme */