Introduction
Let us continue building our chakra components using styled-components & styled-system. In this tutorial we will be cloning the Chakra UI Wrap & WrapItem components.
I would like you to first check the [chakra docs] for wrap.
We will compose (extend) our
Flexcomponent to create theWrap&WrapItemcomponents.All the code for this tutorial can be found here under the atom-layout-wrap branch.
Prerequisite
Please check the previous post where we have completed the Container Components. Also please check the Chakra Wrap Component code here.
In this tutorial we will -
- Create a Wrap component.
 - Create a WrapItem component.
 - Create story for the components.
 - As opposed to the Chakra Wrap component which renders a 
ultag and WrapItem which renders anlitag, we will renderdivtags instead. Though we can pass the polymorphicasprop but our props aren't applying the styles, one of the many drawbacks / caveats of usingstyled-components&styled-systemswhich we will cover later. You can check the issue here. 
Setup
- First let us create a branch, from the main branch run -
 
git checkout -b atom-layout-wrap
Under the
components/atoms/layoutfolder create a new folder called wrap. Under wrap folder create 2 filesindex.tsxandwrap.stories.tsx.So our folder structure stands like - src/components/atoms/layout/wrap.
Wrap Component
- After reading the docs you got an idea of how the wrap component works, it basically has a margin around its children. The code is below -
 
import * as React from "react";
import styled from "styled-components";
import { Flex, FlexProps } from "../flex";
type WrapOptions = {
  spacing: string;
};
type BaseWrapProps = FlexProps & WrapOptions;
const BaseWrap = styled(Flex)<BaseWrapProps>`
  ${({ spacing, theme }) => {
    const margin = theme.space[spacing] ?? spacing;
    return {
      margin: `calc(${margin} / 2 * -1)`,
      "& > *:not(style)": {
        margin,
      },
    };
  }}
`;
export interface WrapProps extends FlexProps, Partial<WrapOptions> {
  shouldWrapChildren?: boolean;
}
export const Wrap = React.forwardRef<HTMLDivElement, WrapProps>(
  (props, ref) => {
    const {
      spacing = "md",
      shouldWrapChildren = false,
      children,
      ...delegated
    } = props;
    const childrenToRender = shouldWrapChildren
      ? React.Children.map(children, (child, index) => (
          // eslint-disable-next-line react/no-array-index-key
          <WrapItem key={index}>{child}</WrapItem>
        ))
      : children;
    return (
      <BaseWrap ref={ref} spacing={spacing} wrap="wrap" {...delegated}>
        {childrenToRender}
      </BaseWrap>
    );
  }
);
First we start with our imports. Given the fact that we are extending the
FlexComponent and creating theWrapComponent, we extendFlexProps. We have 2 additional propsspacingfor actual margin andshouldWrapChildrenif passed as true we will wrap the children withWrapItem.Also take a note on the BaseWrap styled component, here we can pass both design tokens like "sm, md" from our theme or raw values like "1rem, 20px, etc". Therefore we first check our theme.space else just pass the incoming spacing value.
As mentioned earlier we are adding a defined space / margin around our children. Also note that we passed wrap = "wrap" for the BaseWrap, which means our children will wrap automatically if there isn't enough space to fit any more in the same row. And each our children gets a margin thanks to this selector -
& > *:not(style).It is very important to note that because each children inside the
Wrapcomponent gets a margin, we kind a give some negative margin to the Wrap container (half the value of the spacing to be precise). Check this code for theBaseWrap- margin:calc(${margin} / 2 * -1).
WrapItem Component
- It is very simple, paste the code below -
 
export interface WrapItemProps extends FlexProps {}
export const WrapItem = React.forwardRef<HTMLDivElement, WrapItemProps>(
  (props, ref) => {
    return <Flex ref={ref} align="flex-start" {...props} />;
  }
);
Story
- With the above our 
Wrap&WrapItemcomponents are completed, let us create a story. - Under the 
src/components/atoms/layout/wrap/wrap.stories.tsxfile we add the below story code. - We will create 2 stories - Playground and Default.
 
import * as React from "react";
import { spacingOptions } from "../../../../theme/spacing";
import { Center } from "../containers";
import { Wrap, WrapItem, WrapProps } from ".";
export default {
  title: "Atoms/Layout/Wrap",
};
export const Playground = {
  argTypes: {
    spacing: {
      name: "spacing",
      type: { name: "string", required: false },
      defaultValue: "md",
      description: `Margin for the child elements`,
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "md" },
      },
      control: {
        type: "select",
        ...spacingOptions(),
      },
    },
    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: WrapProps) => (
    <Wrap {...args}>
      <WrapItem>
        <Center w="180px" h="80px" bg="red200">
          Box 1
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="180px" h="40px" bg="green200">
          Box 2
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="120px" h="80px" bg="tomato">
          Box 3
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="180px" h="120px" bg="blue200">
          Box 4
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="180px" h="80px" bg="blackAlpha500">
          Box 5
        </Center>
      </WrapItem>
    </Wrap>
  ),
};
export const Default = {
  render: () => (
    <Wrap spacing="30px">
      <WrapItem>
        <Center w="180px" h="80px" bg="red200">
          Box 1
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="180px" h="80px" bg="green200">
          Box 2
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="120px" h="80px" bg="tomato">
          Box 3
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="180px" h="80px" bg="blue200">
          Box 4
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="180px" h="80px" bg="blackAlpha500">
          Box 5
        </Center>
      </WrapItem>
    </Wrap>
  ),
};
- Now run 
npm run storybookcheck 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.tsfile and paste the following - 
export * from "./box";
export * from "./flex";
export * from "./stack";
export * from "./containers";
export * from "./wrap";
Now
npm run build.Under the folder
example/src/App.tsxwe can test ourWrapcomponent. Copy paste the following code and runnpm run startfrom theexampledirectory.
import * as React from "react";
import { Wrap, WrapItem, Center } from "chakra-ui-clone";
export function App() {
  return (
    <Wrap spacing="30px">
      <WrapItem>
        <Center w="180px" h="80px" bg="red.200">
          Box 1
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="180px" h="80px" bg="green.200">
          Box 2
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="180px" h="80px" bg="tomato">
          Box 3
        </Center>
      </WrapItem>
      <WrapItem>
        <Center w="180px" h="80px" bg="blue.200">
          Box 4
        </Center>
      </WrapItem>
    </Wrap>
  );
}
Summary
There you go guys in this tutorial we created Wrap and WrapItem components just like chakra ui and stories for them. You can find the code for this tutorial under the atom-layout-wrap branch here. In the next tutorial we will create Grid component. Until next time PEACE.
    
Top comments (0)