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
Flex
component to create theWrap
&WrapItem
components.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
ul
tag and WrapItem which renders anli
tag, we will renderdiv
tags instead. Though we can pass the polymorphicas
prop but our props aren't applying the styles, one of the many drawbacks / caveats of usingstyled-components
&styled-systems
which 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/layout
folder create a new folder called wrap. Under wrap folder create 2 filesindex.tsx
andwrap.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
Flex
Component and creating theWrap
Component, we extendFlexProps
. We have 2 additional propsspacing
for actual margin andshouldWrapChildren
if 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
Wrap
component 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
&WrapItem
components are completed, let us create a story. - Under the
src/components/atoms/layout/wrap/wrap.stories.tsx
file 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 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";
export * from "./stack";
export * from "./containers";
export * from "./wrap";
Now
npm run build
.Under the folder
example/src/App.tsx
we can test ourWrap
component. Copy paste the following code and runnpm run start
from theexample
directory.
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)