Introduction
Let us continue building our chakra components using styled-components
& styled-system
. In this tutorial we will be cloning the Chakra UI Heading
component.
- I would like you to first check the chakra docs for heading.
- We will create the
Heading
component from scratch, just like theText
component. We could have extended the Text component itself but unfortunately,isTruncated
andnoOfLines
props did not work as expected. - All the code for this tutorial can be found here under the atom-typography-heading branch.
Prerequisite
Please check the previous post where we have completed the Heading Component. Also please check the Chakra Heading Component code here. And the associated chakra theme / styles setup for Heading Component here.
In this tutorial we will -
- Create a Heading component.
- Create stories for the Heading component.
Setup
- First let us create a branch, from the main branch run -
git checkout -b atom-typography-heading
Create a new folder under the
typography
folder calledheading
.Under the
atoms/typography/heading
folder we will create 2 filesindex.tsx
&heading.stories.tsx
.So our folder structure stands like - src/components/atoms/typography/heading.
Heading Component
It is similar to the
Text
component, the only addition is heading variants.Paste the following code -
import * as React from "react";
import styled, { CSSProperties } from "styled-components";
import shouldForwardProp from "@styled-system/should-forward-prop";
import {
compose,
display,
space,
typography,
color,
colorStyle,
borderRadius,
layout,
system,
variant as variantFun,
DisplayProps,
SpaceProps,
TypographyProps,
ColorProps,
ColorStyleProps,
BorderRadiusProps,
LayoutProps,
ResponsiveValue,
} from "styled-system";
type VariantSize = "sm" | "md" | "lg" | "xl" | "2xl" | "xs" | "3xl" | "4xl";
interface TextOptions {
isTruncated?: boolean;
noOfLines?: number;
variant?: ResponsiveValue<VariantSize>;
whiteSpace?: ResponsiveValue<CSSProperties["whiteSpace"]>;
textOverflow?: ResponsiveValue<CSSProperties["textOverflow"]>;
decoration?: ResponsiveValue<CSSProperties["textDecoration"]>;
transform?: ResponsiveValue<CSSProperties["textTransform"]>;
}
export type HeadingProps = DisplayProps &
SpaceProps &
TypographyProps &
ColorProps &
ColorStyleProps &
BorderRadiusProps &
LayoutProps &
React.ComponentPropsWithoutRef<"h2"> &
TextOptions & {
as?: React.ElementType;
children?: React.ReactNode;
};
const BaseHeading = styled.h2.withConfig({
shouldForwardProp,
})<HeadingProps>`
${variantFun({
prop: "variant",
variants: {
"4xl": {
fontSize: ["6xl", null, "7xl"],
lineHeight: "none",
},
"3xl": {
fontSize: ["5xl", null, "6xl"],
lineHeight: "none",
},
"2xl": {
fontSize: ["4xl", null, "5xl"],
lineHeight: ["shorter", null, "none"],
},
xl: {
fontSize: ["3xl", null, "4xl"],
lineHeight: ["short", null, "shorter"],
},
lg: {
fontSize: ["2xl", null, "3xl"],
lineHeight: ["short", null, "shorter"],
},
md: { fontSize: "xl", lineHeight: "shorter" },
sm: { fontSize: "md", lineHeight: "shorter" },
xs: { fontSize: "sm", lineHeight: "shorter" },
},
})}
${compose(
space,
display,
typography,
color,
colorStyle,
borderRadius,
layout,
system({
whiteSpace: true,
textOverflow: true,
decoration: {
property: "textDecoration",
},
transform: {
property: "textTransform",
},
})
)}
${({ noOfLines }) =>
noOfLines &&
`
display: -webkit-box;
line-clamp: ${noOfLines};
overflow: hidden;
-webkit-line-clamp: ${noOfLines};
-webkit-box-orient: vertical;
`};
`;
export const Heading = React.forwardRef<HTMLParagraphElement, HeadingProps>(
(props, ref) => {
const { children, variant = "md", isTruncated, ...delegated } = props;
const truncatedProps = isTruncated
? {
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
}
: {};
return (
<BaseHeading
ref={ref}
variant={variant}
{...truncatedProps}
{...delegated}
>
{children}
</BaseHeading>
);
}
);
I would like to draw your attention towards the variant function. Check for fontSizes and lineHeights we are passing arrays, these are responsive styles docs. We can use responsive styles in our variants too.
Also check we are passing the theme tokens (like "2xl", "shorter") and not raw values (like "2rem", "1").
Story
- With the above our
Heading
component is completed, let us create a story. - Under the
src/components/atoms/typography/heading/heading.stories.tsx
file we add the below story code. - We will create 2 stories - Playground, Default.
import * as React from "react";
import { Heading, HeadingProps } from ".";
export default {
title: "Atoms/Typography/Heading",
};
export const Playground = {
argTypes: {
variant: {
name: "variant",
type: { name: "string", required: false },
defaultValue: "md",
description: `Responsive Values.
The font size of the heading will
automatically decrease in size for smaller screens`,
table: {
type: { summary: "string" },
defaultValue: { summary: "md" },
},
control: {
type: "select",
options: ["xs", "sm", "md", "lg", "xl", "2xl", "3xl", "4xl"],
},
},
isTruncated: {
name: "isTruncated",
type: { name: "boolean", required: false },
defaultValue: false,
description: "Truncate Text.",
table: {
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
},
noOfLines: {
name: "noOfLines",
type: { name: "number", required: false },
defaultValue: "0",
description: "Number of Lines to show",
table: {
type: { summary: "number" },
defaultValue: { summary: "-" },
},
},
as: {
name: "as",
type: { name: "string", required: false },
defaultValue: "h2",
description: "Element type to render.",
table: {
type: { summary: "string" },
defaultValue: { summary: "h2" },
},
control: {
type: "select",
options: ["h1", "h2", "h3", "h4", "h5", "h6"],
},
},
},
render: (args: HeadingProps) => {
return <Heading {...args}>In love with React & Next and Gatsby.</Heading>;
},
};
export const Default = {
render: () => <Heading>I am a Heading</Heading>,
};
- 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
/typography/index.ts
file and paste the following -
export * from "./text";
export * from "./heading";
Now
npm run build
.Under the folder
example/src/App.tsx
we can test ourText
component. Copy paste the following code and runnpm run start
from theexample
directory.
import * as React from "react";
import { Stack, Heading } from "chakra-ui-clone";
export function App() {
return (
<Stack direction="column" spacing="lg">
<Heading as="h1" variant="4xl" isTruncated>
(4xl) In love with React & Next
</Heading>
<Heading as="h2" variant="3xl" isTruncated>
(3xl) In love with React & Next
</Heading>
<Heading as="h2" variant="2xl">
(2xl) In love with React & Next
</Heading>
<Heading as="h2" variant="xl">
(xl) In love with React & Next
</Heading>
<Heading as="h3" variant="lg">
(lg) In love with React & Next
</Heading>
<Heading as="h4" variant="md">
(md) In love with React & Next
</Heading>
<Heading as="h5" variant="sm">
(sm) In love with React & Next
</Heading>
<Heading as="h6" variant="xs">
(xs) In love with React & Next
</Heading>
<Heading variant="lg" fontSize={["50px", "50px"]}>
I'm overriding this heading
</Heading>
</Stack>
);
}
Caveats
- If you are to over-ride say the "fontSize" of the Heading of variant say "lg", now given the fact that our variant is written in a responsive setting i.e. using an array to override it we need to use an array like so -
<Heading variant="lg" fontSize={["50px", "50px"]}>
I'm overriding this heading
</Heading>
This can be powerful and limiting at the same time.
Limiting in the sense say I want to apply the styles of variant lg but want a fontSize of 50px I have to pass an array instead of a normal string fontSize="50px" which will only apply to a certain width just for the first breakpoint mentioned under
theme.breakpoints
.Powerful, because we can override the responsive styles, lets say we want to override the fontSize for the second breakpoint we do -
<Heading variant="lg" fontSize={["35px", null, "70px"]}>
I'm overriding this heading
</Heading>
- Keep in mind the first element of the array is the default size, that will apply to all breakpoints for which we don't provide a value or pass a null. More on that here.
Summary
There you go guys in this tutorial we created Heading
component just like chakra ui and stories for them. You can find the code for this tutorial under the atom-typography-heading branch here. In the next tutorial we will create a Spinner component. Until next time PEACE.
Top comments (0)