Introduction
Let us continue building our chakra components using styled-components
& styled-system
. In this tutorial we will complete cloning the Chakra UI Button
component.
- I would like you to first check the [chakra docs] for button.
- All the code for this tutorial can be found under the atom-form-button branch here.
Prerequisite
This is Part 2 building the Button
Component. Please check the previous post for Part One. Also please check the Chakra Button Component code here, along with the theme / styles for the button here. In this we will build the actual Button
Component, by bringing all the components we created in Part 1 together -
- Create a Button component.
- Create story for the Button component.
Button Component
- As always let me first paste the code for you -
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => {
const {
children,
colorScheme = "gray",
variant = "solid",
s = "md",
loadingText,
isLoading = false,
isDisabled = false,
spinnerPlacement = "start",
spinner,
rightIcon,
leftIcon,
iconSpacing = "0.5rem",
...delegated
} = props;
const contentProps = { rightIcon, leftIcon, iconSpacing, children };
return (
<BaseButton
ref={ref}
disabled={isDisabled || isLoading}
colorScheme={colorScheme}
variant={variant}
s={s}
{...delegated}
>
{isLoading && spinnerPlacement === "start" && (
<ButtonSpinner label={loadingText} placement="start">
{spinner}
</ButtonSpinner>
)}
{isLoading ? (
loadingText || (
<span style={{ opacity: 0 }}>
<ButtonContent {...contentProps} />
</span>
)
) : (
<ButtonContent {...contentProps} />
)}
{isLoading && spinnerPlacement === "end" && (
<ButtonSpinner label={loadingText} placement="end">
{spinner}
</ButtonSpinner>
)}
</BaseButton>
);
}
);
Again as always I would like you to check Chakra's Button docs play around with all the props, you will understand the above code auto-magically.
First and foremost we de-structure all the props, and assign default values to the props, wherever necessary.
For
isLoading
prop we will show theButtonSpinner
component and place it depending on the value of thespinnerPlacement
prop and pass theloadingText
if we are to pass a custom loadingText.Look at this simple trick that chakra ui does it just sets opacity = 0 for the main
ButtonContent
when isLoading = true.
Story
- With the above our
Button
component is completed, let us create a story. - Under the
src/components/atoms/form/button/button.stories.tsx
file we add the below story code. - We will create 2 stories - Playground and Default.
import * as React from "react";
import { colorSchemeOptions } from "../../../../theme/colors";
import { Stack } from "../../layout";
import { Button, ButtonProps } from "./button";
import {
SearchIcon,
PhoneIcon,
EmailIcon,
ArrowForwardIcon,
} from "../../icons";
export default {
title: "Atoms/Form/Button",
};
export const Playground = {
argTypes: {
colorScheme: {
name: "colorScheme",
type: { name: "string", required: false },
defaultValue: "gray",
description: "The Color Scheme for the button",
table: {
type: { summary: "string" },
defaultValue: { summary: "gray" },
},
control: {
type: "select",
options: colorSchemeOptions,
},
},
s: {
name: "s",
type: { name: "string", required: false },
defaultValue: "md",
description: "Button size height width and vertical padding",
table: {
type: { summary: "string" },
defaultValue: { summary: "md" },
},
control: {
type: "select",
options: ["xs", "sm", "md", "lg"],
},
},
variant: {
name: "variant",
type: { name: "string", required: false },
defaultValue: "solid",
description: "Button variants",
table: {
type: { summary: "string" },
defaultValue: { summary: "solid" },
},
control: {
type: "select",
options: ["link", "outline", "solid", "ghost", "unstyled"],
},
},
isLoading: {
name: "isLoading",
type: { name: "boolean", required: false },
defaultValue: false,
description: "Pass the isLoading prop to show loading state.",
table: {
type: { summary: "string" },
defaultValue: { summary: "false" },
},
},
loadingText: {
name: "loadingText",
type: { name: "string", required: false },
defaultValue: "",
description: "Prop to show a spinner and the loading text.",
table: {
type: { summary: "string" },
defaultValue: { summary: "-" },
},
},
spinnerPlacement: {
name: "spinnerPlacement",
type: { name: "string", required: false },
defaultValue: "start",
description: `When a loadingText is present, you can change the
placement of the spinner element to either start or end.
It is start by default.`,
table: {
type: { summary: "string" },
defaultValue: { summary: "start" },
},
control: {
type: "select",
options: ["start", "end"],
},
},
isDisabled: {
name: "isDisabled",
type: { name: "boolean", required: false },
defaultValue: false,
description: "Pass the isDisable prop to show disabled state.",
table: {
type: { summary: "string" },
defaultValue: { summary: "false" },
},
},
isFullWidth: {
name: "isFullWidth",
type: { name: "boolean", required: false },
defaultValue: false,
description: "If true will expand full width.",
table: {
type: { summary: "string" },
defaultValue: { summary: "false" },
},
},
},
render: (args: ButtonProps) => <Button {...args}>Button</Button>,
};
export const Default = {
render: () => (
<Stack direction="column" spacing="xl">
<Stack spacing="lg" align="center">
<Button colorScheme="teal" s="xs">
Button
</Button>
<Button colorScheme="teal" s="sm">
Button
</Button>
<Button colorScheme="teal" s="md">
Button
</Button>
<Button colorScheme="teal" s="lg">
Button
</Button>
</Stack>
<Stack spacing="lg" align="center">
<Button colorScheme="teal" variant="solid">
Button
</Button>
<Button colorScheme="teal" variant="outline">
Button
</Button>
<Button colorScheme="teal" variant="ghost">
Button
</Button>
<Button colorScheme="teal" variant="link">
Button
</Button>
</Stack>
<Stack spacing="lg">
<Button isLoading colorScheme="teal" variant="solid">
Email
</Button>
<Button
isLoading
loadingText="Loading"
colorScheme="teal"
variant="outline"
spinnerPlacement="start"
>
Submit
</Button>
<Button
isLoading
loadingText="Loading"
colorScheme="teal"
variant="outline"
spinnerPlacement="end"
>
Continue
</Button>
</Stack>
<Stack spacing="lg">
<Button leftIcon={<EmailIcon />} colorScheme="teal" variant="solid">
Email
</Button>
<Button
rightIcon={<ArrowForwardIcon />}
colorScheme="teal"
variant="outline"
>
Call us
</Button>
</Stack>
</Stack>
),
};
- 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
/button/index.ts
file paste the following -
export * from "./button";
- Under the
/form/index.ts
file paste the following -
export * from "./button";
- Under the
/atom/index.ts
file paste the following -
export * from "./layout";
export * from "./typography";
export * from "./feedback";
export * from "./icon";
export * from "./icons";
export * from "./form";
Now
npm run build
.Under the folder
example/src/App.tsx
we can test ourButton
component. Copy paste the default story code and runnpm run start
from theexample
directory.
Summary
There you go guys our Button component just is completed. You can find the code for this tutorial under the atom-form-button branch here. In the next tutorial we will create IconButton
component. Until next time PEACE.
Top comments (0)