DEV Community

Arsalan Ahmed Yaldram
Arsalan Ahmed Yaldram

Posted on • Updated on

Chakra UI Flex component using react, typescript, styled-components and styled-system

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.
  • 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.

Prerequisite

Please check the previous post where we have completed the Box Component. Also please check the Chakra Flex Component code here. 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 -
git checkout -b atom-layout-flex
Enter fullscreen mode Exit fullscreen mode
  • 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 -
import * as React from "react";
import styled from "styled-components";
import { system, FlexboxProps } from "styled-system";

import { Box, BoxProps } from "../box";
Enter fullscreen mode Exit fullscreen mode
  • 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).
const BaseFlex = styled(Box)`
  display: flex;
  ${system({
    direction: {
      property: "flexDirection",
    },
    align: {
      property: "alignItems",
    },
    justify: {
      property: "justifyContent",
    },
    wrap: {
      property: "flexWrap",
    },
  })}
`;
Enter fullscreen mode Exit fullscreen mode
  • 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.

  • If the above code makes no sense, I request you to please check the previous post. Also check my introductory post.

  • 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 -

type FlexOmitted = "display";

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

type BaseFlexProps = FlexOptions & BoxProps;
Enter fullscreen mode Exit fullscreen mode
  • 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.
const BaseFlex = styled(Box)<BaseFlexProps>`...`;
Enter fullscreen mode Exit fullscreen mode
  • Now we will create our Flex component. The complete code is as follows -
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>
    );
  }
);
Enter fullscreen mode Exit fullscreen mode
  • 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.

  • 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 -

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>
    );
  }
);
Enter fullscreen mode Exit fullscreen mode

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.
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>
  ),
};
Enter fullscreen mode Exit fullscreen mode
  • 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 -
export * from "./box";
export * from "./flex";
Enter fullscreen mode Exit fullscreen mode
  • 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.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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. In the next tutorial we will create stack component. Until next time PEACE.

Discussion (0)