Chakra UI Flex component using react, typescript, styled-components and styled-system
Tags: react, typescript
Published: 2021-09-01 13:50:00

### Introduction

Let us continue building our chakra components clone using `styled-components` & `styled-system`. In this tutorial we will be cloning the Chakra UI `Flex` component.

- I would like you to first check the chakra docs for [flex](https://chakra-ui.com/docs/layout/flex).
- We will **compose** (extend) our `Box` component to create the `Flex` component and further extend the `Flex` component to create a `Spacer` component.
- All the code for this tutorial can be found under the **atom-layout-flex** branch [here](https://github.com/yaldram/chakra-ui-clone).

### Prerequisite

Please check the previous post where we have completed the Box Component. Also please check the Chakra Flex Component code [here](https://github.com/chakra-ui/chakra-ui/blob/main/packages/layout/src/flex.tsx). In this tutorial we will -

- Create a Flex component.
- Create a Spacer component.
- Create story for the Flex component.

### Setup

- First let us create a branch, from the main branch run -

```bash
git checkout -b atom-layout-flex
```

- Under the `components/atoms/layout` folder create a new folder called `flex`. Under flex folder create 2 files `index.tsx` and `flex.stories.tsx`.

- So our folder structure stands like - **src/components/atoms/layout/flex**.

### Flex Component

- First lets import the necessary stuff -

```tsx
import * as React from "react";
import styled from "styled-components";
import { system, FlexboxProps } from "styled-system";

import { Box, BoxProps } from "../box";
```

- Compose the Box component to create the `BaseFlex` styled component. By composing our `Box`, we extend it meaning our `BaseFlex` also takes in all props that we pass to `Box`, inherits the variants and the system extensions we had for our `Box` (marginStart & marginEnd).

```tsx
const BaseFlex = styled(Box)`
  display: flex;
  ${system({
    direction: {
      property: "flexDirection",
    },
    align: {
      property: "alignItems",
    },
    justify: {
      property: "justifyContent",
    },
    wrap: {
      property: "flexWrap",
    },
  })}
`;
```

- We also are extending the system using the system function so that we can have shorthands for our props like `align instead of alignItems`, just like chakra ui [flex props](https://chakra-ui.com/docs/layout/flex#props).

- If the above code makes no sense, I request you to please check the previous post. Also check my [introductory post](https://dev.to/yaldram/working-with-styled-system-using-typescript-4mkj).

- Let us create the type for our props, now given that we are composing our Box component for making our Flex component it will inherit all props by default, but we don't want the user to pass display prop to our Flex component so we will omit that prop from the type. Let me show you what I mean the below code is pretty self-explanatory -

```tsx
type FlexOmitted = "display";

type FlexOptions = {
  direction?: FlexboxProps["flexDirection"];
  align?: FlexboxProps["alignItems"];
  justify?: FlexboxProps["justifyContent"];
  wrap?: FlexboxProps["flexWrap"];
};

type BaseFlexProps = FlexOptions & BoxProps;
```

- We create the FlexOptions prop to cover the system props, and finally create the `BaseFlexProps` which is a union type of FlexOptions and BoxProps. Pass the `BaseFlexProps` type to `BaseFlex`.

```tsx
const BaseFlex = styled(Box)<BaseFlexProps>`...`;
```

- Now we will create our Flex component. The complete code is as follows -

```tsx
import * as React from "react";
import styled from "styled-components";
import { system, FlexboxProps } from "styled-system";

import { Box, BoxProps } from "../box";

type FlexOmitted = "display";

type FlexOptions = {
  direction?: FlexboxProps["flexDirection"];
  align?: FlexboxProps["alignItems"];
  justify?: FlexboxProps["justifyContent"];
  wrap?: FlexboxProps["flexWrap"];
};

type BaseFlexProps = FlexOptions & BoxProps;

const BaseFlex = styled(Box)<BaseFlexProps>`
  display: flex;
  ${system({
    direction: {
      property: "flexDirection",
    },
    align: {
      property: "alignItems",
    },
    justify: {
      property: "justifyContent",
    },
    wrap: {
      property: "flexWrap",
    },
  })}
`;

export interface FlexProps extends Omit<BaseFlexProps, FlexOmitted> {}

export const Flex = React.forwardRef<HTMLDivElement, FlexProps>(
  (props, ref) => {
    const { direction = "row", children, ...delegated } = props;

    return (
      <BaseFlex ref={ref} direction={direction} {...delegated}>
        {children}
      </BaseFlex>
    );
  }
);
```

- I really like this pattern of separating the styled component from the React component so that it becomes easy to intercept any props before passing on, adding default values to props, consistent types and also passing the ref.

- Keep in mind we neither export the `BaseFlexProps` or `BaseFlex` we export `FlexProps` and `Flex`.

### Spacer Component

- Please check the docs and first understand how spacer works [here](https://chakra-ui.com/docs/layout/flex#using-the-spacer).

- In its essence it is a flexible flex component that expands along the major axis of its containing flex layout. It renders a `div` by default, and takes up any available space.

- If we use `Spacer` and `Flex` component together, the children will span the entire width of the container and also have equal spacing between them.

- Paste the following code for Spacer component below the Flex component code -

```tsx
type SpaceOmitted = "flex" | "justifySelf" | "alignSelf";

export interface SpacerProps extends Omit<BoxProps, SpaceOmitted> {}

export const Spacer = React.forwardRef<HTMLDivElement, SpacerProps>(
  (props, ref) => {
    const { children, ...delegated } = props;

    return (
      <Box
        ref={ref}
        flex="1"
        justifySelf="stretch"
        alignSelf="stretch"
        {...delegated}
      >
        {children}
      </Box>
    );
  }
);
```

### Story

- With the above our `Flex` and `Spacer` components are completed, let us create a story.
- Under the `src/components/atoms/layout/flex/flex.stories.tsx` file we add the below story code.
- We will create 2 stories one for the Playground for `Flex` and one for `Spacer`.

```tsx
import * as React from "react";

import { Box } from "../box";
import { Flex, FlexProps, Spacer } from ".";

export default {
  title: "Atoms/Layout/Flex",
};

export const Playground = {
  argTypes: {
    direction: {
      name: "direction",
      type: { name: "string", required: false },
      defaultValue: "row",
      description: "Shorthand for flexDirection style prop",
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "row" },
      },
      control: {
        type: "select",
        options: [
          "initial",
          "inherit",
          "unset",
          "revert",
          "row",
          "row-reverse",
          "column",
          "column-reverse",
        ],
      },
    },
    justify: {
      name: "justify",
      type: { name: "string", required: false },
      defaultValue: "flex-start",
      description: "Shorthand for justifyContent style prop",
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "flex-start" },
      },
      control: {
        type: "select",
        options: [
          "justify-content",
          "flex-start",
          "flex-end",
          "center",
          "space-between",
          "space-around",
          "space-evenly",
          "initial",
          "inherit",
        ],
      },
    },
    align: {
      name: "align",
      type: { name: "string", required: false },
      defaultValue: "stretch",
      description: "Shorthand for alignItems style prop",
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "stretch" },
      },
      control: {
        type: "select",
        options: [
          "stretch",
          "center",
          "flex-start",
          "flex-end",
          "baseline",
          "initial",
          "inherit",
        ],
      },
    },
  },
  render: (args: FlexProps) => (
    <Flex justify="space-between" color="white" {...args}>
      <Box size="100px" bg="green500">
        Box 1
      </Box>
      <Box size="100px" bg="blue500">
        Box 2
      </Box>
      <Box basis="300px" size="100px" bg="tomato">
        Box 3
      </Box>
    </Flex>
  ),
};

export const FlexSpacer = {
  argTypes: {
    direction: {
      name: "direction",
      type: { name: "string", required: false },
      defaultValue: "row",
      description: "Shorthand for flexDirection style prop",
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "row" },
      },
      control: {
        type: "select",
        options: [
          "initial",
          "inherit",
          "unset",
          "revert",
          "row",
          "row-reverse",
          "column",
          "column-reverse",
        ],
      },
    },
  },
  render: (args: FlexProps) => (
    <Flex h="80vh" color="white" {...args}>
      <Box size="100px" p="md" bg="red400">
        Box 1
      </Box>
      <Spacer />
      <Box size="100px" p="md" bg="green400">
        Box 2
      </Box>
    </Flex>
  ),
};
```

- Now run `npm run storybook` check the stories. Under the Playground stories check the controls section play with the props, add more controls if you like.

### Build the Library

- Under the `/layout/index.ts` file and paste the following -

```tsx
export * from "./box";
export * from "./flex";
```

- Now `npm run build`.

- Under the folder `example/src/App.tsx` we can test our `Flex` component. Copy paste the following code and run `npm run start` from the `example` directory.

```tsx
import * as React from "react";
import { Box, Flex, Spacer } from "chakra-ui-clone";

export function App() {
  return (
    <Flex color="white">
      <Box size="100px" p="md" bg="red400">
        Box 1
      </Box>
      <Spacer />
      <Box size="100px" p="md" bg="green400">
        Box 2
      </Box>
    </Flex>
  );
}
```

### Summary

There you go guys in this tutorial we created `Flex` and `Spacer` components just like chakra ui and stories for them. You can find the code for this tutorial under the **atom-layout-flex** branch [here](https://github.com/yaldram/chakra-ui-clone). In the next tutorial we will create stack component. Until next time PEACE.