Build Chakra UI Spinner component using react, typescript, styled-components and styled-system
Tags: react, typescript
Published: 2021-09-04 07:50:10

### Introduction

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

- I would like you to first check the chakra docs for [spinner](https://chakra-ui.com/docs/feedback/spinner).
- We will **compose** (extend) our `Box` component to create the `Spinner` component and add some more styles and a size variant.
- All the code for this tutorial can be found [here](https://github.com/yaldram/chakra-ui-clone) under the **atom-feedback-spinner** branch.

### Prerequisite

Please check the previous post where we have completed the Heading Component. Also please check the Chakra Spinner Component code [here](https://github.com/chakra-ui/chakra-ui/blob/main/packages/spinner/src/spinner.tsx). Check the theme and styles for the Spinner Component [here](https://github.com/chakra-ui/chakra-ui/blob/main/packages/theme/src/components/spinner.ts).

In this tutorial we will -

- Create a Spinner component.

### Setup

- First let us create a branch, from the main branch run -

```bash
git checkout -b atom-feedback-spinner
```

- Under the `components/atoms` folder create a new folder called feedback. Under feedback folder create 2 files `index.ts` and `spinner.tsx`.

- So our folder structure stands like - **src/components/atoms/feedback**.

### Spinner Component

- If you did read and try out chakra's Spinner component you know what we need here. We need to handle the size of the Spinner, the color, its speed and also an emptyColor property.

- Let me paste the code -

```tsx
import * as React from "react";
import styled, { css, keyframes } from "styled-components";
import { variant as variantFun, ResponsiveValue } from "styled-system";

import { Box, BoxProps } from "../layout";
import { VisuallyHiddenText } from "../typography";

const spin = keyframes`
 0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
`;

type SpinnerSize = "xs" | "sm" | "md" | "lg" | "xl";

interface SpinnerOptions {
  emptyColor?: string;
  thickness?: string;
  speed?: string;
  label?: string;
  variant?: ResponsiveValue<SpinnerSize>;
}

export interface SpinnerProps
  extends Omit<BoxProps, keyof SpinnerOptions>,
    SpinnerOptions {}

const animation = (speed = "0.45s") => css`
  ${spin} ${speed} linear infinite;
`;
const BaseSpinner = styled(Box)<SpinnerProps>`
  animation: ${({ speed }) => animation(speed)};

  width: 1em;
  height: 1em;

  ${variantFun({
    prop: "variant",
    variants: {
      xs: {
        width: "1.5rem",
        height: "1.5rem",
      },
      sm: {
        width: "2rem",
        height: "2rem",
      },
      md: {
        width: "2.5rem",
        height: "2.5rem",
      },
      lg: {
        width: "3rem",
        height: "3rem",
      },
      xl: {
        width: "3.5rem",
        height: "3.5rem",
      },
    },
  })}
`;

export const Spinner = React.forwardRef<HTMLDivElement, SpinnerProps>(
  (props, ref) => {
    const {
      label = "Loading....",
      thickness = "2px",
      emptyColor = "transparent",
      color,
      ...delegated
    } = props;

    const spinnerStyles = {
      display: "inline-block",
      borderColor: "currentColor",
      borderStyle: "solid",
      borderRadius: "99999px",
      borderWidth: thickness,
      borderBottomColor: emptyColor,
      borderLeftColor: emptyColor,
      color,
    };

    return (
      <BaseSpinner ref={ref} {...spinnerStyles} {...delegated}>
        {label && <VisuallyHiddenText>{label}</VisuallyHiddenText>}
      </BaseSpinner>
    );
  }
);
```

- First thing to notice, we are using the animation function from styled-components. We are passing in the speed parameter, which we can now control via props.

- Second thing to notice, we introduced a prop called variant by which we control the size of the Spinner component. The prop is named variant so that it does not conflict with the styled-system `size prop`.

- Third the use of `VisuallyHiddenText` which we created in the Text component tutorial. It will be used by screen readers, pass some meaningful accessible text using the label prop.

- Fourth thing, we are passing the color prop and using the CSS "currentColor" property, this will make the borderColor = color.

### Build the Library

- Under the `/feedback/index.ts` file and paste the following -

```tsx
export * from "./spinner";
```

- Under the `/atom/index.ts` file and paste the following -

```tsx
export * from "./layout";
export * from "./typography";
export * from "./feedback";
```

- Now `npm run build`.

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

```tsx
import * as React from "react";
import { Stack, Spinner } from "chakra-ui-clone";

export function App() {
  return (
    <Stack spacing="lg">
      <Spinner variant="xs" />
      <Spinner variant="sm" />
      <Spinner variant="md" />
      <Spinner variant="lg" />
      <Spinner variant="xl" />
      <Spinner variant="sm" color="red500" />
      <Spinner
        thickness="4px"
        speed="0.65s"
        emptyColor="gray200"
        color="blue500"
        variant="xl"
      />
    </Stack>
  );
}
```

### Summary

There you go guys in this tutorial we created Spinner component just like chakra ui. You can find the code for this tutorial under the **atom-feedback-spinner** branch [here](https://github.com/yaldram/chakra-ui-clone). In the next tutorial we will create Icon component. Until next time PEACE.