Xây dựng bộ Component Kits dễ dàng với Radix Primitives và Tailwind CSS trong Reactjs

By Thái Nguyễn
Picture of the author
Published on
image alt attribute

Table Of Content


Lý do nên sử dụng Radix Primitive

Khi bạn là một Frontend developer và bắt đầu một dự án mới, việc lựa chọn một thư viện UI có thể khiến bạn đau đầu. Đối với dự án thiết kế theo một thư viện cụ thể như Ant Design hoặc Material UI sẽ giúp bạn tiết kiệm thời gian và công sức. Bạn chỉ cần cài đặt thư viện và tùy chỉnh một chút về màu sắc là có thể bắt đầu sử dụng.

Nhưng nếu thiết kế của dự án không tuân theo một thư viện UI cụ thể thì bạn sẽ làm gì? Trong tình huống này, bạn sẽ phải tự tạo và tùy chỉnh các UI Components. Điều này có thể tốn thời gian và công sức đáng kể.

Đây là lúc Radix Primitive trở nên hữu dụng. Sử dụng Radix, bạn có thể tạo ra các component UI tuỳ chỉnh dựa trên thiết kế của designer. Điều này giúp bạn đảm bảo rằng giao diện của dự án phản ánh chính xác ý tưởng thiết kế mà bạn đã nhận được từ designer, mà không phải giới hạn bởi các quy tắc của thư viện UI cụ thể nào đó.

Chúng ta hãy cùng xem cách hoạt động của Radix Primitive

Demo tạo Popover Component

  1. Install component từ command line.
npm install @radix-ui/react-popover@latest -E

Tạo component popover với file name là Popover.tsx với nội dung dưới đây

// Popover.tsx
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
  
const Popover = PopoverPrimitive.Root
 
const PopoverTrigger = PopoverPrimitive.Trigger
 
const PopoverContent = React.forwardRef<
  React.ElementRef<typeof PopoverPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
  <PopoverPrimitive.Portal>
    <PopoverPrimitive.Content
      ref={ref}
      align={align}
      sideOffset={sideOffset}
      className="z-50 w-72 rounded-md border border-violet-400 text-violet-600 font-bold p-4 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
      {...props}
    />
  </PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
 
export { Popover, PopoverTrigger, PopoverContent }

Như vây là chúng ta đã tạo xong component Popover một cách nhanh chóng. Tạo file index.tsx để sử dụng Popover Component như sau:

import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/test-popover";

export default function Home() {
  return (
    <main className="min-h-screen flex flex-col">
      <div className="w-full text-center">
        <Popover>
          <PopoverTrigger>Open</PopoverTrigger>
          <PopoverContent>Place content for the popover here.</PopoverContent>
        </Popover>
      </div>
    </main>
  );
}

Chạy đoạn code trên chúng ta sẽ thu được kết quả:

Screen Recording 2023-10-09 at 16.12.31

Ưu điểm của Radix Primitive

Qua ví dụ trên chúng ta có thể thấy ngay được các ưu điểm của Radix Primitive:

1. Opened

Các component của Radix được thiết kế với tính mở rộng cao, có thể tuỳ chỉnh dễ dàng và có thể truy cập sâu vào từng thành phần nhỏ của component, chúng ta có thể bọc chúng và tự thêm event listeners, props, or refs, ...

2. Unstyled

Các component của Radix Primitive đều chưa có style, vì vậy việc thêm style tuỳ chỉnh rất dễ dàng dù bạn đang sử dụng vanilla CSS, CSS preprocessors hay CSS-in-JS libraries. ( có thể xem ví dụ ở trên )

3. Incremental adoption

Tất cả các component của Radix đều độc lập, chúng ta hoàn toàn có thể install chỉ những component cần thiết, giúp giảm JS bundle size của project.

> npm install @radix-ui/react-dialog
> npm install @radix-ui/react-dropdown-menu
> npm install @radix-ui/react-tooltip

Kết hợp với Tailwind CSS

Tailwind Css cũng không còn xa lạ với các lập trình viên Frontend, nó một thư viện CSS utility, được thiết kế để giúp bạn xây dựng giao diện người dùng nhanh chóng và dễ dàng bằng cách sử dụng các class CSS đã được định sẵn.

Ví dụ sử dụng class của tailwind:

<h1 class="text-3xl font-bold underline">
    Hello world!
</h1>

Sẽ tương đương với:

<h1
  style={{
    fontSize: "1.875rem",
    lineHeight: "2.25rem",
    fontWeight: 700,
    textDecoration: "underline",
  }}
>
  Hello world!
</h1>

Xem cách install Tailwind tại đây

Để tạo ra 1 bộ Component Kits consistent về style thì chúng ta sẽ phải định nghĩa các common style cho Project.

Ví dụ về định nghĩa colors và heading style cho Project:

Trong file index.css

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  h1 {
    @apply text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl;
  }
  h2 {
    @apply text-3xl font-bold tracking-tight md:text-4xl lg:text-5xl;
  }
  h3 {
    @apply text-3xl font-bold tracking-tight lg:text-4xl;
  }
  h4 {
    @apply text-2xl font-bold md:text-3xl;
  }
  h5 {
    @apply text-xl font-bold md:text-2xl;
  }
  h6 {
    @apply text-lg font-bold md:text-xl;
  }

  :root {
    --background: 240 80% 98%; /* Màu nền - Màu xám nhạt và rất sáng */
    --foreground: 0 0% 20%; /* Màu chữ nền - Màu đen */

    --muted: 240 20% 96%; /* Màu nhạt - Màu xám nhạt và sáng */
    --muted-foreground: 0 0% 40%; /* Màu chữ nền cho màu nhạt - Màu đen */

    --primary: 210 40% 50%; /* Màu chính - Màu xanh lam nhạt */
    --primary-foreground: 0 0% 100%; /* Màu chữ nền cho màu chính - Màu trắng */

    --secondary: 30 100% 60%; /* Màu phụ - Màu cam sáng */
    --secondary-foreground: 0 0% 20%; /* Màu chữ nền cho màu phụ - Màu đen */

    --accent: 45 100% 60%; /* Màu điểm nhấn - Màu vàng sáng */
    --accent-foreground: 0 0% 10%; /* Màu chữ nền cho màu điểm nhấn - Màu đen */

    --destructive: 0 100% 50%; /* Màu hủy bỏ - Màu đỏ sáng */
    --destructive-foreground: 210 40% 98%; /* Màu chữ nền cho màu hủy bỏ - Màu xanh lam nhạt */
  }
}

Bên trên mình định nghĩa theo hệ màu hsl, để có thể sử dụng được các mã màu trên với tailwind chúng ta sẽ sửa file tailwind.config.ts như sau:

// tailwind.config.ts
import type { Config } from "tailwindcss";

const config: Config = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {
      colors: {
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
      },
    },
  },
  plugins: [],
};
export default config;

Vậy là chúng ta đã config các biến global colors cho tailwind css và overide lại style cho thẻ Heading. Giờ hãy cùng thử sử dụng tailwind với các biến ở trên:

Thêm border-primary, text-primary cho PopoverPrimitive.Content

// Popover.tsx
import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";

const Popover = PopoverPrimitive.Root;

const PopoverTrigger = PopoverPrimitive.Trigger;

const PopoverContent = React.forwardRef<
  React.ElementRef<typeof PopoverPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
  <PopoverPrimitive.Portal>
    <PopoverPrimitive.Content
      ref={ref}
      align={align}
      sideOffset={sideOffset}
      className="z-50 w-72 rounded-md border border-primary text-primary font-bold p-4 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
      {...props}
    />
  </PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;

export { Popover, PopoverTrigger, PopoverContent };

Test các thẻ Heading và Popover sau khi thêm custom style tailwind:

// index.tsx
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/test-popover";

export default function Home() {
  return (
    <div className="min-h-screen flex flex-col bg-background">
      <div className="w-full text-center">
        <h1 className="text-primary">This is Heading 1 primary</h1>
        <h2 className="text-secondary">This is Heading 2 secondary </h2>
        <h3 className="text-muted-foreground">This is Heading 3 muted</h3>
        <h4 className="text-accent">This is Heading 4 accent</h4>
        <h5 className="text-destructive">This is Heading 5 destructive</h5>
        <div className="mt-20">
          <Popover>
            <PopoverTrigger>
              <h5>Open</h5>
            </PopoverTrigger>
            <PopoverContent>Place content for the popover here.</PopoverContent>
          </Popover>
        </div>
      </div>
    </div>
  );
}

Chúng ta có được kết quả như sau:

Screenshot 2023-10-11 at 10.49.32

Conclusion

Vậy là chúng ta đã thành công sử dụng Radix Primitive và Tailwind CSS để tạo ra một customize Popover component và định nghĩa các common style cho các thẻ Heading, colors trong tailwind để dùng cho dự án. Từ đây bạn có thể tạo thêm nhiều customize component khác phù hợp với yêu cầu của dự án. Với cách trên, chúng ta có thể tạo ra 1 bộ component kits nhanh chóng và vẫn có thể đảm bảo được UI như theo designer thiết kế.