DEV Community

Arsalan Ahmed Yaldram
Arsalan Ahmed Yaldram

Posted on

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

Introduction

Let us continue building our chakra components using styled-components & styled-system. In this tutorial we will be cloning the Chakra UI Text component.

  • I would like you to first check the chakra docs for text.
  • We will create the Text component from scratch.
  • All the code for this tutorial can be found here under the atom-typography-text branch.

Prerequisite

Please check the previous post where we have completed the Grid Components. Also please check the Chakra Text Component code here.

In this tutorial we will -

  • Create a Text component.
  • Create stories for the Text component.

Setup

  • First let us create a branch, from the main branch run -
git checkout -b atom-typography-text
Enter fullscreen mode Exit fullscreen mode
  • Create a new folder under the atoms folder called typography. Under the typography folder create a new folder called text.

  • Under the atoms/typography/text folder we will create 2 files index.tsx & text.stories.tsx.

  • So our folder structure stands like - src/components/atoms/typography/text.

Text Component

  • For our Text component we have 2 additional props, noOfLines and isTruncated. Given noOfLines = 2, it will truncate the text and add an ellipses (...) on the second line. Similarly if we are to pass isTruncated it will limit the text only to one line and add an ellipses (...).

  • Let me 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,
  borderRadius,
  layout,
  system,
  DisplayProps,
  SpaceProps,
  TypographyProps,
  ColorProps,
  BorderRadiusProps,
  LayoutProps,
  ResponsiveValue,
} from "styled-system";

interface TextOptions {
  isTruncated?: boolean;
  noOfLines?: number;
  whiteSpace?: ResponsiveValue<CSSProperties["whiteSpace"]>;
  textOverflow?: ResponsiveValue<CSSProperties["textOverflow"]>;
  decoration?: ResponsiveValue<CSSProperties["textDecoration"]>;
  transform?: ResponsiveValue<CSSProperties["textTransform"]>;
}

export type TextProps = DisplayProps &
  SpaceProps &
  TypographyProps &
  ColorProps &
  BorderRadiusProps &
  LayoutProps &
  React.ComponentPropsWithoutRef<"p"> &
  TextOptions & {
    as?: React.ElementType;
    children?: React.ReactNode;
  };

const BaseText = styled.p.withConfig({
  shouldForwardProp,
})<TextProps>`
  ${compose(space, display, typography, color, 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 Text = React.forwardRef<HTMLParagraphElement, TextProps>(
  (props, ref) => {
    const { children, isTruncated, ...delegated } = props;

    const truncatedProps = isTruncated
      ? {
          textOverflow: "ellipsis",
          overflow: "hidden",
          whiteSpace: "nowrap",
        }
      : {};

    return (
      <BaseText ref={ref} {...truncatedProps} {...delegated}>
        {children}
      </BaseText>
    );
  }
);

export const VisuallyHiddenText = styled.span`
  position: absolute;
  overflow: hidden;
  clip: rect(0 0 0 0);
  height: 1px;
  width: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
`;
Enter fullscreen mode Exit fullscreen mode
  • Notice we have added / extended our system with the following props, whiteSpace, textOverflow, decoration (text-decoration), transform (text-transform).

  • Also we have regular style utility functions from styled-system like space, display, typography, layout, etc.

  • Also check that we have intercepted the isTruncated props and we conditionally add necessary props.

  • We also have an additional VisuallyHiddenText which we will use for accessibility purposes, example say we have a checkbox with a label and we want the screen reader to read some different text.

  • Under src/theme/typography.ts paste the following -

export function fontSizesOptions() {
  const options = Object.keys(typography.fontSizes);
  const labels = Object.entries(typography.fontSizes).reduce(
    (acc, [key, value]) => {
      acc[key] = `${key} (${value})`;
      return acc;
    },
    {}
  );

  return { options, labels };
}
Enter fullscreen mode Exit fullscreen mode

Story

  • With the above our Text component is completed, let us create a story.
  • Under the src/components/atoms/typography/text/text.stories.tsx file we add the below story code.
  • We will create 3 stories - Playground, Default, TextTypes.
import * as React from "react";

import { fontSizesOptions } from "../../../../theme/typography";
import { Text, TextProps } from ".";

export default {
  title: "Atoms/Typography/Text",
};

export const Playground = {
  argTypes: {
    fontSize: {
      name: "fontSize",
      type: { name: "string", required: false },
      defaultValue: "md",
      description: "Font Size from theme.fontSizes",
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "-" },
      },
      control: {
        type: "select",
        ...fontSizesOptions(),
      },
    },
    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: "-" },
      },
    },
  },
  render: (args: TextProps) => (
    <Text {...args}>
      Lorem ipsum is placeholder text commonly used in the graphic, print, and
      publishing industries for previewing layouts and visual mockups.
    </Text>
  ),
};

export const Default = {
  render: () => (
    <Text>
      Text component is the used to render text and paragraphs within an
      interface. It renders a p tag by default.
    </Text>
  ),
};

export const TextTypes = {
  argTypes: {
    as: {
      name: "as",
      type: { name: "string", required: false },
      defaultValue: "p",
      description: "Element type to render.",
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "p" },
      },
      control: {
        type: "select",
        options: [
          "i",
          "u",
          "abbr",
          "cite",
          "del",
          "em",
          "ins",
          "kbd",
          "mark",
          "s",
          "samp",
          "sub",
          "sup",
        ],
      },
    },
  },
  render: (args: TextProps) => <Text {...args}>Sample Text</Text>,
};
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.

  • Also check the TextTypes story we are using the styled-component polymorphic as prop.

Build the Library

  • Under atom/typography folder create an index.ts file.

  • Under the /typography/index.ts file and paste the following -

export * from "./text";
Enter fullscreen mode Exit fullscreen mode
  • Under the atom/index.ts file paste the following -
export * from "./layout";
export * from "./typography";
Enter fullscreen mode Exit fullscreen mode
  • Now npm run build.

  • Under the folder example/src/App.tsx we can test our Text component. Copy paste the following code and run npm run start from the example directory.

import * as React from "react";
import { Text } from "chakra-ui-clone";

export function App() {
  return (
    <>
      <Text fontSize="50px" color="tomato">
        I'm using a custom font-size value for this text
      </Text>
      <Text as="i">Italic</Text>
      <br />
      <Text as="u">Underline</Text>
      <br />
      <Text as="s">Strike Through</Text>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Summary

There you go guys in this tutorial we created Text components just like chakra ui and stories for them. You can find the code for this tutorial under the atom-typography-text branch here. In the next tutorial we will create a Heading component. Until next time PEACE.

Discussion (0)