DEV Community

Cover image for Creating a React component library using with Storybook, TypeScript, Eslint, Husky, Vite & TailwindCSS.
Serif COLAKEL
Serif COLAKEL

Posted on • Edited on

Creating a React component library using with Storybook, TypeScript, Eslint, Husky, Vite & TailwindCSS.

Github link : Github Link

This is a boilerplate for creating a React component library using with Storybook, TypeScript, Eslint, Husky, Vite & TailwindCSS. Deploy on Chromatic.

Owner: Serif Colakel

Dates: June 19, 2023

Npm Package: serif-ui-components

Create a React app with Vite and TypeScript

You can follow these steps:

  • Install Node.js: Make sure you have Node.js installed on your machine. You can download it from the official Node.js website (https://nodejs.org).
  • Install React Vite App with TypeScript: Run the following command to install:
npm create vite@latest serif-ui-components -- --template react
Enter fullscreen mode Exit fullscreen mode

Replace serif-ui-components with the name you want to give to your project. This command will create a new directory named serif-ui-components and set up a basic React project with TypeScript.

  • Navigate to the project directory: Run the following command to navigate into your project directory:
cd serif-ui-components
Enter fullscreen mode Exit fullscreen mode
  • Install the packages: Run the following command to install:
npm i
Enter fullscreen mode Exit fullscreen mode
  • Start the development server: Run the following command to start the development server:
npm run dev
Enter fullscreen mode Exit fullscreen mode

Add the Tailwind CSS

You can follow these steps:

  • Install tailwindcss and its peer dependencies, then generate your tailwind.config.js and postcss.config.js files.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode
  • Configure your template paths: Add the paths to all of your template files in your tailwind.config.js file.
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
};
Enter fullscreen mode Exit fullscreen mode
  • Add the Tailwind directives to your CSS: Add the @tailwind directives for each of Tailwind’s layers to your ./src/index.css file.
@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode
  • Add @tailwindcss/forms plugin in tailwind.config.js file :
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [
    require('@tailwindcss/forms'), // ? tailwinds form added here.
  ],
};
Enter fullscreen mode Exit fullscreen mode
  • Start using Tailwind in your project: Start using Tailwind’s utility classes to style your content.
export default function App() {
  return <h1 className="text-3xl font-bold underline">Hello world!</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Add the Eslint, Airbnb Configuration

You can follow these steps:,

  • Install the eslint as developer dependency: Run the following command to install:
npm i -D eslint
Enter fullscreen mode Exit fullscreen mode
  • Add the basic config file for eslint: Run the following command to start configuration with Eslint CLI:
npx eslint --init

You can also run this command directly using 'npm init @eslint/config'.
Need to install the following packages:
  @eslint/create-config
Ok to proceed? (y)
Enter fullscreen mode Exit fullscreen mode
  1. Question: How would you like to use ESLint? , select the To check syntax and find problems and hit enter.
? How would you like to use ESLint? ...
  To check syntax only
> To check syntax and find problems
  To check syntax, find problems, and enforce code style
Enter fullscreen mode Exit fullscreen mode
  1. Question : What type of modules does your project use? , select the JavaScript modules (import/export) and hit enter.
? What type of modules does your project use? ...
> JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these
Enter fullscreen mode Exit fullscreen mode
  1. Question : Which framework does your project use?, select the React and hit enter.
? Which framework does your project use? ...
> React
  Vue.js
  None of these
Enter fullscreen mode Exit fullscreen mode
  1. Question : Does your project use TypeScript? , select the Yes and hit enter.
? Does your project use TypeScript? » No / **Yes**
Enter fullscreen mode Exit fullscreen mode
  1. Question : Where does your code run?, select the Browser and hit enter.
? Where does your code run? ...  (Press <space> to select, <a> to toggle all, <i> to invert selection)
> Browser
  Node
Enter fullscreen mode Exit fullscreen mode
  1. Question : What format do you want your config file to be in?, select the JavaScript and hit enter.
? What format do you want your config file to be in? ...
> JavaScript
  YAML
  JSON
Enter fullscreen mode Exit fullscreen mode
  1. Question : Would you like to install them now? » No / Yes , select the Yes and hit enter.
The config that you've selected requires the following dependencies:

eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
? Would you like to install them now? » No / Yes
Enter fullscreen mode Exit fullscreen mode
  1. Last Question : Which package manager do you want to use? , select the npm and hit enter.
? Which package manager do you want to use? ...
> npm
  yarn
  pnpm
Enter fullscreen mode Exit fullscreen mode
  • Now we need to setup a eslint style guide in our project, I am using airbnb() as the base style. This helps a developer to write proper and clean code. Run the following command on the terminal:
npx install-peerdeps --dev eslint-config-airbnb
Enter fullscreen mode Exit fullscreen mode

npx install-peerdeps --dev eslint-config-airbnb

Need to install the following packages:
  install-peerdeps
Ok to proceed? (y) y*
Enter fullscreen mode Exit fullscreen mode
  • Install the eslint-config-airbnb-typescript, Then add the following command in .eslintrc.cjs.
npm i -D eslint-config-airbnb-typescript
Enter fullscreen mode Exit fullscreen mode
extends: [
    // ... rest of your extends configuration.
  'airbnb',
    'airbnb-typescript'
]
Enter fullscreen mode Exit fullscreen mode
  • Override eslint rule to .eslintrc.cjs (reference).
rules: {
    'react/react-in-jsx-scope': 'off',
    'no-console': 'error',
}
Enter fullscreen mode Exit fullscreen mode

Add Prettier Configuration

You can follow these steps:,

  • Install the prettier eslint-config-prettier and eslint-plugin-prettier as developer dependency: Run the following command to install:
npm i -D prettier eslint-config-prettier eslint-plugin-prettier
Enter fullscreen mode Exit fullscreen mode
  • Create a prettier.config.cjs file, the copy and paste the following code block:
touch prettier.config.cjs
Enter fullscreen mode Exit fullscreen mode
/** @type {import("prettier").Options} */
const config = {
  trailingComma: 'es5',
  tabWidth: 2,
  semi: true,
  singleQuote: true,
};

module.exports = config;
Enter fullscreen mode Exit fullscreen mode
  • Add prettier plugins the .eslintrc.cjs :
// ...
plugins: [
    // ... other plugins
    'prettier'
  ],
// ...
Enter fullscreen mode Exit fullscreen mode
  • Add the plugin:prettier/recommended config to the extends array in your .eslintrc.cjs , it must be last in the extends array :
extends: [
    // ... other extends
    'plugin:prettier/recommended'
  ],
Enter fullscreen mode Exit fullscreen mode
  • Reload the VS CODE window for setup initializing.

Add Husky Configuration

You can follow these steps:,

  • Install the husky and lint-staged as developer dependency: Run the following command to install:
npm i -D husky lint-staged
Enter fullscreen mode Exit fullscreen mode
  • If you don’t run before git init follow the next command :
git init
Enter fullscreen mode Exit fullscreen mode
  • Husky init configuration : Run the following command :
npx husky-init
Enter fullscreen mode Exit fullscreen mode
  • Set Husky configuration : Run the following command to change your husky configuration :
npx husky set .husky/pre-commit "npm run lint"
Enter fullscreen mode Exit fullscreen mode
  • Add the following script to package.json file.
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
Enter fullscreen mode Exit fullscreen mode
  • Test the husky & eslint : Add the following code in your App.tsx :
function App() {
  console.log('first'); // it will be showing eslint error.

  return <div>Test</div>;
}
Enter fullscreen mode Exit fullscreen mode
  • Check the husky command for commit your changes.
git add .husky/

git commit -m 'TEST (serif) : eslint & husky configuration test.'
Enter fullscreen mode Exit fullscreen mode
// the check the terminal console

/Users/serifcolakel/Desktop/React/serif-ui-components/src/App.tsx
  8:3  error  Unexpected console statement  no-console

✖ 1 problem (1 error, 0 warnings)

husky - pre-commit hook exited with code 1 (error)
Enter fullscreen mode Exit fullscreen mode

Add Vitest Configuration

You can follow these steps:,

  • Install the vitest as developer dependency: Run the following command to install:
npm i -D vitest
Enter fullscreen mode Exit fullscreen mode
  • Set the vite.config.ts with Run the following code block :
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  server: {
    port: 3000,
  },
  test: {
    globals: true,
    setupFiles: ['./src/setupTests.ts'],
    environment: 'jsdom',
  },
  plugins: [react()],
});
Enter fullscreen mode Exit fullscreen mode
  • Install the @testing-library/react and @testing-library/jest-dom as developer dependency: Run the following command to install:
npm i -D @testing-library/react @testing-library/jest-dom
Enter fullscreen mode Exit fullscreen mode
  • Create setupTest.ts file :
touch setupTest.ts
Enter fullscreen mode Exit fullscreen mode
  • Write the following code block to setupTest.ts file :
/* eslint-disable import/no-extraneous-dependencies */
import matchers from '@testing-library/jest-dom/matchers';
import { expect } from 'vitest';

expect.extend(matchers);
Enter fullscreen mode Exit fullscreen mode
  • Add the test script to package.json :
"scripts": {
       // ...
    "test": "vitest"
  },
Enter fullscreen mode Exit fullscreen mode
  • Writing first test case in App.tsx , first step create App.test.tsx then write the following code block :
// App.tsx
export default function App() {
  return (
    <div data-testid="app-wrapper">
      <h1>Hello, world!</h1>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
// App.test.tsx
import { describe, it } from 'vitest';
import { render, screen } from '@testing-library/react';

import App from './App';

// ? INFO (serif): ABOUT TEST WRITING STEPS
// ! 1. Arrange : render the component under test
// ! 2. Act : get the element to test
// ! 3. Assert : check the element is in the document

// ? CHECK (serif) : THE COMMON MISTAKE -> https://kentcdodds.com/blog/common-mistakes-with-react-testing-library

describe('App', () => {
  it('should render the title', () => {
    // Arrange : render the component under test
    render(<App />);

    // Act : get the element to test

    // Assert
    expect(screen.getByText('Hello, world!')).toBeInTheDocument();
    expect(screen.getByTestId('app-wrapper')).toBeInTheDocument();
  });
});
Enter fullscreen mode Exit fullscreen mode
  • Run the test script for test your *.test.ts files.
npm run test
Enter fullscreen mode Exit fullscreen mode
  • Add the lint script to pre-commit in .husky file for checking test :
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run test # add this line
npm run lint
Enter fullscreen mode Exit fullscreen mode

Add Storybook

You can follow these steps: reference

  • Install the storybook : Run the following command to install:
npx storybook@latest init
Enter fullscreen mode Exit fullscreen mode
  • Add the tailwind style to storybook : Write the code block to in top level .storybook in preview.ts :
import type { Preview } from '@storybook/react';
// add next line
import '../src/index.css';

const preview: Preview = {
  // ...
};
Enter fullscreen mode Exit fullscreen mode
  • Run the storybook :
npm run storybook
Enter fullscreen mode Exit fullscreen mode

Creating First Component

You can follow these steps:

  • Install the class-variance-authority , tailwind-merge and clsx as developer dependency: Run the following command to install:
npm i class-variance-authority tailwind-merge clsx
Enter fullscreen mode Exit fullscreen mode
  • Create the src/common/utils/classNameUtils.ts file, then add clsxMerge util for className merge with tailwind styles.
import clsx, { ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

/**
 * Merges classes using clsx and tailwind-merge
 * @example
 * clsxMerge('text-red-500', 'text-2xl', 'font-bold', 'text-center')
 * // => 'text-red-500 text-2xl font-bold text-center'
 * @param classes {ClassValue[]} - Array of classes to merge
 * @returns {string}
 */
export const clsxMerge = (...classes: ClassValue[]): string =>
  twMerge(clsx(...classes));
Enter fullscreen mode Exit fullscreen mode
  • Create a helper.ts for Button.tsx component : file path src/components/button/helpers.ts
import { cva } from 'class-variance-authority';

/**
 * Button styles for the Button component.
 */
export const buttonStyles = cva(
  'flex flex-row gap-x-4 disabled:cursor-not-allowed items-center justify-center',
  {
    variants: {
      buttonType: {
        primary:
          'bg-violet-500 text-white border-violet-500 hover:bg-violet-600',
        secondary:
          'bg-gray-200 text-gray-600 border-gray-200 hover:bg-gray-300',
        warning:
          'bg-yellow-500 text-white border-yellow-500 hover:bg-yellow-600',
        outline:
          'bg-white text-gray-600 hover:bg-gray-100 border hover:border-gray-100 border-gray-300 hover:shadow-md',
        disabled: 'bg-black text-white border-black cursor-not-allowed',
        error: 'bg-red-500 text-white border-red-500 hover:bg-red-600',
      },
      size: {
        default: ['text-base'],
        small: ['text-sm'],
        large: ['text-lg'],
        xxl: ['text-2xl'],
      },
      spacing: {
        default: ['py-2', 'px-4'],
        small: ['py-1', 'px-2'],
        large: ['py-3', 'px-6'],
        xxl: ['py-4', 'px-8'],
      },
      rounded: {
        default: 'rounded-md',
        sm: 'rounded-sm',
        lg: 'rounded-lg',
        xl: 'rounded-xl',
        xxl: 'rounded-2xl',
        none: 'rounded-none',
        full: 'rounded-full',
      },
    },
    compoundVariants: [
      {
        buttonType: 'primary',
        size: 'default',
        spacing: 'default',
        rounded: 'default',
      },
    ],
    defaultVariants: {
      buttonType: 'primary',
      size: 'default',
      rounded: 'default',
      spacing: 'default',
    },
  }
);
Enter fullscreen mode Exit fullscreen mode
  • Create a Button.tsx component : file path src/components/button/Button.tsx
import React, { type ComponentPropsWithRef } from 'react';
import { type VariantProps } from 'class-variance-authority';
import { clsxMerge } from '../../common/utils/classNameUtils';
import { buttonStyles } from './helpers';

type ButtonElementProps = ComponentPropsWithRef<'button'>;

export interface ButtonProps
  extends ButtonElementProps,
    VariantProps<typeof buttonStyles> {
  leftIcon?: React.ReactNode;
  rightIcon?: React.ReactNode;
  label?: string;
}

export default function Button({
  className,
  buttonType,
  size,
  rounded,
  label,
  rightIcon,
  spacing,
  leftIcon,
  ...props
}: ButtonProps) {
  return (
    <button
      className={clsxMerge(
        buttonStyles({ buttonType, size, rounded, spacing }),
        className
      )}
      type="button"
      {...props}
    >
      {Boolean(leftIcon) && leftIcon}
      {Boolean(label) && label}
      {Boolean(rightIcon) && rightIcon}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • Write test for Button.tsx component : file path is src/components/button/Button.test.tsx .
import { render } from '@testing-library/react';
import { describe, it } from 'vitest';
import Button from './Button';

describe('Button', () => {
  it('renders correctly', () => {
    const { container } = render(<Button>Click Me</Button>);

    expect(container.firstChild).toHaveClass('bg-violet-500');
    expect(container.firstChild).toHaveClass('text-white');
    expect(container.firstChild).toHaveClass('rounded-md');
    expect(container.firstChild).toHaveClass('px-4');
  });

  it('renders correctly with left icon', () => {
    const { container } = render(
      <Button leftIcon={<span>👈</span>}>Click Me</Button>
    );

    expect(container).toMatchSnapshot();
    expect(container).toHaveTextContent('👈');
  });

  it('renders correctly with right icon', () => {
    const { container } = render(
      <Button rightIcon={<span>👉</span>}>Click Me</Button>
    );

    expect(container).toMatchSnapshot();
    expect(container).toHaveTextContent('👉');
  });

  it('renders correctly with both icons', () => {
    const { container } = render(
      <Button leftIcon={<span>👈</span>} rightIcon={<span>👉</span>} />
    );

    expect(container).toHaveTextContent('👈');
    expect(container).toHaveTextContent('👉');
  });

  it('renders correctly with label', () => {
    const { container } = render(<Button label="Click Me" />);

    expect(container).toHaveTextContent('Click Me');
  });

  it('renders correctly with label and left icon', () => {
    const { container } = render(
      <Button
        label="Click Me"
        leftIcon={<span>👈</span>}
        rightIcon={<span>👉</span>}
      />
    );

    expect(container).toHaveTextContent('Click Me');
    expect(container).toHaveTextContent('👈');
    expect(container).toHaveTextContent('👉');
  });

  it('render correctly with size prop', () => {
    const { container } = render(<Button size="default" />);

    const { container: containerSmall } = render(<Button size="small" />);

    const { container: containerLarge } = render(<Button size="large" />);

    const { container: containerXXL } = render(<Button size="xxl" />);

    expect(container.firstChild).toHaveClass('text-base');
    expect(containerSmall.firstChild).toHaveClass('text-sm');
    expect(containerLarge.firstChild).toHaveClass('text-lg');
    expect(containerXXL.firstChild).toHaveClass('text-2xl');
  });

  it('render correctly with rounded prop', () => {
    const { container } = render(<Button rounded="full" />);

    const { container: containerLg } = render(<Button rounded="lg" />);

    const { container: containerNone } = render(<Button rounded="none" />);

    const { container: containerXL } = render(<Button rounded="xl" />);

    const { container: containerXXL } = render(<Button rounded="xxl" />);

    const { container: containerDefault } = render(
      <Button rounded="default" />
    );

    const { container: containerSM } = render(<Button rounded="sm" />);

    expect(container).toMatchSnapshot();
    expect(containerLg).toMatchSnapshot();
    expect(containerNone).toMatchSnapshot();
    expect(containerXL).toMatchSnapshot();
    expect(containerXXL).toMatchSnapshot();
    expect(containerDefault).toMatchSnapshot();
    expect(containerSM).toMatchSnapshot();

    expect(container.firstChild).toHaveClass('rounded-full');
    expect(containerLg.firstChild).toHaveClass('rounded-lg');
    expect(containerNone.firstChild).toHaveClass('rounded-none');
    expect(containerXL.firstChild).toHaveClass('rounded-xl');
    expect(containerXXL.firstChild).toHaveClass('rounded-2xl');
    expect(containerDefault.firstChild).toHaveClass('rounded-md');
    expect(containerSM.firstChild).toHaveClass('rounded-sm');
  });

  it('render correctly with buttonType prop', () => {
    const { container: containerPrimary } = render(
      <Button buttonType="primary" />
    );

    const { container } = render(<Button buttonType="secondary" />);

    const { container: containerWarning } = render(
      <Button buttonType="warning" />
    );

    const { container: containerOutline } = render(
      <Button buttonType="outline" />
    );

    const { container: containerDisabled } = render(
      <Button buttonType="disabled" />
    );

    const { container: containerError } = render(<Button buttonType="error" />);

    expect(container).toMatchSnapshot();
    expect(containerPrimary).toMatchSnapshot();
    expect(containerWarning).toMatchSnapshot();
    expect(containerOutline).toMatchSnapshot();
    expect(containerDisabled).toMatchSnapshot();
    expect(containerError).toMatchSnapshot();

    expect(containerPrimary.firstChild).toHaveClass(
      'bg-violet-500 text-white border-violet-500 hover:bg-violet-600'
    );
    expect(container.firstChild).toHaveClass(
      'bg-gray-200 text-gray-600 border-gray-200 hover:bg-gray-300'
    );
    expect(containerOutline.firstChild).toHaveClass(
      'bg-white text-gray-600 hover:bg-gray-100 border hover:border-gray-100 border-gray-300 hover:shadow-md'
    );
    expect(containerWarning.firstChild).toHaveClass(
      'bg-yellow-500 text-white border-yellow-500 hover:bg-yellow-600'
    );

    expect(containerDisabled.firstChild).toHaveClass(
      'bg-black text-white border-black cursor-not-allowed'
    );

    expect(containerError.firstChild).toHaveClass(
      'bg-red-500 text-white border-red-500 hover:bg-red-600'
    );
  });
});
Enter fullscreen mode Exit fullscreen mode
  • Add Button.tsx component for stories : file name Button.stories.ts.
import type { Meta, StoryObj } from '@storybook/react';
import Button from './Button';

// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {},
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof meta>;

// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
export const Primary: Story = {
  args: {
    label: 'Button',
    onClick: () => {
      window.console.log('Button clicked!');
    },
  },
};

export const Secondary: Story = {
  args: {
    label: 'Button',
    buttonType: 'secondary',
    onClick: () => {
      window.console.log('Button clicked!');
    },
  },
};

export const Large: Story = {
  args: {
    label: 'Button',
    size: 'large',
  },
};

export const Small: Story = {
  args: {
    size: 'small',
    label: 'Button',
    onClick: () => {
      window.console.log('Button clicked!');
    },
  },
};
Enter fullscreen mode Exit fullscreen mode
  • Run the test : Run the following test script on terminal.
npm run test
Enter fullscreen mode Exit fullscreen mode
  • Great 🎉 You have custom Button component. Show component on the storybook : Run the following storybook script on terminal.
npm run storybook
Enter fullscreen mode Exit fullscreen mode

Publish to NPM 📦

  • Add the components in the root level index.ts file. Add the following code in the index.ts file.
export { default as Button } from './src/components/button/Button';
Enter fullscreen mode Exit fullscreen mode
  • Install vite-plugin-dts plugin for vite as a dev dependency. Run the following command on terminal.
  npm i -D vite-plugin-dts
Enter fullscreen mode Exit fullscreen mode
  • Add the vite-plugin-dts plugin in the vite.config.ts file. Add the following code in the vite.config.ts file.
import dts from 'vite-plugin-dts';

export default defineConfig({
  // ... other configs
  plugins: [
    // ... other plugins
    dts(),
  ],
});
Enter fullscreen mode Exit fullscreen mode
  • Install the types/node package as a dev dependency. Run the following command on terminal.
  npm i -D @types/node
Enter fullscreen mode Exit fullscreen mode
  • Add configs in vite.config.ts file build option lib and rollupOptions :
// vite.config.ts

import path from 'path';

export default defineConfig({
  // ... other configs
  build: {
    lib: {
      entry: path.resolve(__dirname, 'index.ts'),
      name: 'serif-ui-components',
      fileName: (format) => `index.${format}.js`,
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
    },
    sourcemap: true,
    emptyOutDir: true,
  },
});
Enter fullscreen mode Exit fullscreen mode
  • Add the src and index.ts, set the "declaration": true and "allowSyntheticDefaultImports": true in the tsconfig.json file.
{
  "compilerOptions": {
    // ... other compiler options
    "declaration": true,
    "allowSyntheticDefaultImports": true
  },
  "include": ["index.ts", "src"]
}
Enter fullscreen mode Exit fullscreen mode
  • Add the package.json file main, types, exports, files and module option.
{
  "license": "MIT",
  "author": {
    "name": "Serif Colakel",
    "email": "serifcolakel0@gmail.com"
  },
  "description": "UI Components with React & Typescript with TailwindCSS",
  "main": "dist/index.umd.js",
  "module": "dist/index.es.js",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.es.js",
      "require": "./dist/index.umd.js",
      "types": "./dist/index.d.ts"
    },
    "./package.json": "./package.json",
    "./dist/*": "./dist/*"
  },
  "files": ["/dist"],
  "publishConfig": {
    "access": "public"
  },
  "scripts": {
    // ...
  },
  "devDependencies": {
    // ...
  },
  "dependencies": {
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Add the new release scripts in the ./scripts/release.sh file.
  #!/bin/bash

  # Read the current package.json version
  current_version=$(node -p "require('./package.json').version")
  echo "Current version: $current_version"

  # Increment the version number
  new_version=$(npm version --no-git-tag-version patch)
  echo "New version: $new_version"

  # Build the project
  $(npm run build)

  # Publish the project
  $(npm publish --access public)

  # Commit the changes
  git add .
  git commit -m "RELEASE (serif) : new release $new_version"
  git push origin main

  # Inform the user
  echo "Released $new_version"
Enter fullscreen mode Exit fullscreen mode
  • Add the release script in the package.json file.
{
  "scripts": {
    // ...
    "release": "sh ./scripts/release.sh"
  }
}
Enter fullscreen mode Exit fullscreen mode

Congratulations 🎉 You have published your first npm package.

Publish to Chromatic 🎨

In this section, we will publish the storybook project to the Chromatic. reference

  • Create a new account on Chromatic.

  • First build the storybook project. Run the following command on terminal.

npm run build-storybook
Enter fullscreen mode Exit fullscreen mode
  • Validate the storybook project.
npx http-server ./storybook-static
Enter fullscreen mode Exit fullscreen mode
  • Publish to Github.
  • In this section, push to the Github repository.
  • Create project on the Chromatic and copy the project token.
  • Create a .env file in the root directory and add the CHROMATIC_PROJECT_TOKEN variable.
CHROMATIC_PROJECT_TOKEN=your_chromatic_project_token
Enter fullscreen mode Exit fullscreen mode
  • Add the chromatic script in the package.json file.
{
  "scripts": {
    // ...
    "chromatic": "npx chromatic --project-token=$CHROMATIC_PROJECT_TOKEN"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Install the chromatic package as a dev dependency. Run the following command on terminal.
npm install --save-dev chromatic
Enter fullscreen mode Exit fullscreen mode
  • Publish to Storybook to Chromatic. Run the following command on terminal.
npm run chromatic
Enter fullscreen mode Exit fullscreen mode

Congratulations 🎉 You have published your first storybook project to the Chromatic.

Conclusions 📝

React, TypeScript, and Tailwind CSS form a powerful combination for building modern web applications. React's component-based approach, coupled with TypeScript's type checking and Tailwind CSS's rapid styling capabilities, enables developers to create scalable and visually appealing UIs.

Tools such as Eslint, Husky, and Prettier contribute to enhancing code quality and maintainability. Eslint enforces coding standards and identifies potential errors, Husky automates checks before and after Git operations, and Prettier ensures consistent code formatting. By using these tools, developers can write cleaner and more reliable code.

Vitest and Jest-dom provide efficient testing capabilities for React components. Vitest allows for the execution of test scenarios, ensuring the correct behavior of components, while Jest-dom offers a comprehensive set of utilities for writing component tests. These testing tools aid in improving the reliability and functionality of the developed UI components.

Storybook, combined with Chromatic, offers a seamless workflow for developing and showcasing UI components. Storybook enables developers to isolate and iterate on components, while Chromatic provides a platform for visual regression testing and browser compatibility checks. This integration streamlines the component development process and ensures consistent user experiences across different environments.

Overall, by leveraging these technologies and tools, developers can create high-quality UI components, improve code maintainability, and streamline their development workflows. The combination of React, TypeScript, and Tailwind CSS, along with the integration of testing and deployment tools, empowers developers to build robust and visually appealing web applications.

Thank you for reading this article. I hope you enjoyed it. If you have any questions, please feel free to contact me.

References 📚

Top comments (6)

Collapse
 
joevaugh4n profile image
Joe • Edited

Hey Serif! I'm a maintainer at Storybook and Chromatic. Just wanted to say we love this write-up. Thanks for being part of our community and for sharing Storybook & Chromatic with other devs!

If you'd like to write more about Storybook, we just shipped Storybook 7.1 and we'd love to hear what you think!

Collapse
 
serifcolakel profile image
Serif COLAKEL • Edited

Hii Joe! Thank you so much for the warm words and appreciation! I'm thrilled to be a part of the Storybook and Chromatic community and to help share the benefits of these fantastic tools with other developers.

Congratulations on the release of Storybook 7.1! It's always inspiring to witness the continuous improvements and innovations that come with each new version. As a maintainer of Storybook and Chromatic, your dedication to enhancing the development experience is commendable, and it truly reflects the commitment of the entire team.

The Figma part about the final release excites me more. I think that it will ensure that the integrity of the component you have developed will be preserved and that the differences will be minimized. I would also be more than happy to help announce the exciting features of the final release.

Thank you once again for your kind words, and I look forward to contributing to the celebration of Storybook 7.1! If there's anything specific you'd like to include or any other assistance you need, just let me know. I'm here to help in any way I can!

Collapse
 
zhd4nov profile image
Evgeny Zhdanov

Thanks a lot for the article!

After the "Glob step" I have jsx-runtime-{hash}.js in my dist folder. Do you have any idea why?

Collapse
 
zhd4nov profile image
Evgeny Zhdanov

Hm, just add 'react/jsx-runtime': 'react/jsx-runtime' to globals

fixed)

Collapse
 
dhaneshen1810 profile image
Dhaneshen Moonian

Thank you for the article Serif! I was able to create the component library.
However when I import the component in my NExtJs project, it does not seem to be applying the styling correctly. Do you have an idea of what is happening?

Collapse
 
dhaneshen1810 profile image
Dhaneshen Moonian

It seems like the "vite-plugin-lib-inject-css" module resolved the issue,
// vite.config.js

// imports
import { libInjectCss } from "vite-plugin-lib-inject-css";

// ... config options
plugins: [react(), libInjectCss(), dts()],
// ... config options