-
+
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((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';
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((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 */