Skip to content

Commit

Permalink
feat(layout): add Layout component
Browse files Browse the repository at this point in the history
Adds an alpha implementation of the Layout component
  • Loading branch information
cdun committed Aug 4, 2023
1 parent de79284 commit a0c8e64
Show file tree
Hide file tree
Showing 22 changed files with 560 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/bright-lamps-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@contentful/f36-layout': minor
---

Add Layout component
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@contentful/f36-avatar",
"@contentful/f36-image",
"@contentful/f36-header",
"@contentful/f36-layout",
"@contentful/f36-navlist",
"@contentful/f36-website"
],
Expand Down
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions packages/components/layout/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
title: 'Layout'
type: 'layout'
status: 'alpha'
slug: /components/layout/
github: 'https://github.com/contentful/forma-36/tree/main/packages/components/layout'
storybook: 'https://f36-storybook.contentful.com/?path=/story/components-layout--default'
typescript: ./src/Layout.tsx
---

A primitive for implementing common layout patterns seen within Contentful apps.

## Import

```jsx static=true
import { Layout } from '@contentful/f36-components';
// or
import { Layout } from '@contentful/f36-layout';
```

## Examples

### Basic layout with header, sidebar and body

```jsx file=./examples/BasicLayoutExample.tsx

```

## Props (API reference)

<PropsTable of="Layout" />
63 changes: 63 additions & 0 deletions packages/components/layout/examples/BasicLayoutExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useState } from 'react';
import {
Box,
Button,
Form,
FormControl,
TextInput,
Textarea,
} from '@contentful/f36-components';
import { Header } from '@contentful/f36-header';
import { Layout } from '@contentful/f36-layout';
import { NavList } from '@contentful/f36-navlist';

export default function BasicLayoutExample() {
const [submitting, setSubmitting] = useState(false);
const submitForm = () => {
setSubmitting(true);
setTimeout(() => setSubmitting(false), 1000);
};

return (
<Layout
header={
<Layout.Header>
<Header title="Content Types" />
</Layout.Header>
}
leftSidebar={
<Box padding="spacingS">
<NavList>
<NavList.Item>Your Details</NavList.Item>
</NavList>
</Box>
}
>
<Layout.Body>
<Box padding="spacingS">
<Form onSubmit={submitForm}>
<FormControl>
<FormControl.Label isRequired>Name</FormControl.Label>
<TextInput />
<FormControl.HelpText>
Please enter your first name
</FormControl.HelpText>
</FormControl>

<FormControl>
<FormControl.Label>Description</FormControl.Label>
<Textarea />
<FormControl.HelpText>
Tell me about yourself
</FormControl.HelpText>
</FormControl>

<Button variant="primary" type="submit" isDisabled={submitting}>
{submitting ? 'Submitted' : 'Click me to submit'}
</Button>
</Form>
</Box>
</Layout.Body>
</Layout>
);
}
34 changes: 34 additions & 0 deletions packages/components/layout/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@contentful/f36-layout",
"version": "4.0.0-alpha.1",
"description": "Forma 36: Layout component",
"scripts": {
"build": "tsup"
},
"dependencies": {
"@contentful/f36-core": "^4.0.0",
"@contentful/f36-tokens": "^4.0.0",
"emotion": "^10.0.17"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
},
"license": "MIT",
"files": [
"dist"
],
"main": "dist/index.js",
"module": "dist/esm/index.js",
"types": "dist/index.d.ts",
"source": "src/index.ts",
"sideEffects": false,
"browserslist": "extends @contentful/browserslist-config",
"repository": {
"type": "git",
"url": "https://github.com/contentful/forma-36"
},
"publishConfig": {
"access": "public"
}
}
16 changes: 16 additions & 0 deletions packages/components/layout/src/CompoundLayout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Layout as OriginalLayout } from './Layout';

import { LayoutHeader } from './LayoutHeader';
import { LayoutBody } from './LayoutBody';
import { LayoutSidebar } from './LayoutSidebar';

type CompoundLayout = typeof OriginalLayout & {
Header: typeof LayoutHeader;
Body: typeof LayoutBody;
Sidebar: typeof LayoutSidebar;
};

export const Layout = OriginalLayout as CompoundLayout;
Layout.Header = LayoutHeader;
Layout.Body = LayoutBody;
Layout.Sidebar = LayoutSidebar;
27 changes: 27 additions & 0 deletions packages/components/layout/src/Layout.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import tokens from '@contentful/f36-tokens';
import { css } from 'emotion';
import type { LayoutProps } from './Layout';

export const getLayoutMaxWidthStyles = (variant: LayoutProps['variant']) => {
if (variant === 'fullscreen') {
return css({
width: '100%',
maxWidth: '100%',
});
}

return css({
width: '100%',
maxWidth: '1550px',
padding: `0 ${tokens.spacingXs}`,
});
};

export const getLayoutStyles = (variant: LayoutProps['variant']) => ({
root: css({
width: '100%',
}),
mainContainer: css(getLayoutMaxWidthStyles(variant), {
minHeight: '200px',
}),
});
85 changes: 85 additions & 0 deletions packages/components/layout/src/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, {
forwardRef,
useMemo,
type Ref,
type HTMLAttributes,
} from 'react';
import { cx } from 'emotion';
import { type CommonProps, Flex } from '@contentful/f36-core';
import { LayoutContextProvider, LayoutContextType } from './LayoutContext';
import { getLayoutStyles } from './Layout.styles';

export type LayoutProps = {
/**
* The body of the layout.
*/
children: React.ReactNode;
header?: React.ReactNode;
leftSidebar?: React.ReactNode;
rightSidebar?: React.ReactNode;
/**
* Defines the width of the layout and its content.
* @default 'fullscreen'
*/
variant?: 'wide' | 'fullscreen';
/**
* Classname that will be passed to the main content div,
* which holds the sidebars and children div
*/
contentClassName?: string;
contentTestId?: string;
} & CommonProps &
HTMLAttributes<HTMLDivElement>;

const _Layout = (props: LayoutProps, ref: Ref<HTMLDivElement>) => {
const {
children,
header,
leftSidebar,
rightSidebar,
variant = 'fullscreen',
className,
testId = 'cf-layout',
contentTestId = 'cf-layout-main-container',
contentClassName,
...otherProps
} = props;

const styles = getLayoutStyles(variant);

const contextValue: LayoutContextType = useMemo(
() => ({
variant,
}),
[variant],
);

return (
<LayoutContextProvider value={contextValue}>
<Flex
{...otherProps}
as="section"
ref={ref}
className={cx(styles.root, className)}
alignItems="center"
flexDirection="column"
justifyContent="flex-start"
testId={testId}
>
{header}

<Flex
className={cx(styles.mainContainer, contentClassName)}
flexGrow={1}
testId={contentTestId}
>
{leftSidebar}
{children}
{rightSidebar}
</Flex>
</Flex>
</LayoutContextProvider>
);
};

export const Layout = forwardRef(_Layout);
8 changes: 8 additions & 0 deletions packages/components/layout/src/LayoutBody.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { css } from 'emotion';

export const getLayoutBodyStyles = () => ({
root: css({
flexGrow: 1,
minWidth: 0,
}),
});
33 changes: 33 additions & 0 deletions packages/components/layout/src/LayoutBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { forwardRef, type HTMLAttributes, type Ref } from 'react';
import { cx } from 'emotion';
import { type CommonProps, Box } from '@contentful/f36-core';
import { getLayoutBodyStyles } from './LayoutBody.styles';

export type LayoutBodyProps = {
children: React.ReactNode;
} & CommonProps &
HTMLAttributes<HTMLDivElement>;

const _LayoutBody = (props: LayoutBodyProps, ref: Ref<HTMLDivElement>) => {
const {
children,
className,
testId = 'cf-layout-body',
...otherProps
} = props;
const styles = getLayoutBodyStyles();

return (
<Box
{...otherProps}
as="div"
ref={ref}
className={cx(styles.root, className)}
testId={testId}
>
{children}
</Box>
);
};

export const LayoutBody = forwardRef(_LayoutBody);
Loading

0 comments on commit a0c8e64

Please sign in to comment.