آکادمی بنو/آموزش Tailwind

ساخت Components قابل استفاده مجدد در Tailwind CSS

بهنام نوبخت · ۱۴۰۵/۷/۱۳

یکی از مزیت‌های Tailwind CSS این است که می‌توانیم ظاهر عناصر را مستقیماً با Utility Classها ایجاد کنیم؛ اما در پروژه‌های واقعی ممکن است یک مشکل ایجاد شود: تکرار کد. فرض کنید در یک فروشگاه چندین Button، Card، Badge و Input داریم که ظاهر مشابهی دارند. اگر برای هر عنصر تمام Classها را دوباره بنویسیم، کد HTML طولانی و نگهداری آن دشوار می‌شود. راه‌حل، ایجاد Componentهای قابل استفاده مجدد است. در این مقاله یاد می‌گیریم مفهوم Component چیست، چرا باید از آن استفاده کنیم، چگونه الگوهای تکرارشونده را شناسایی کنیم و چگونه Button، Card، Badge و سایر عناصر را به Component تبدیل کنیم. همچنین با مفهوم Variant آشنا می‌شویم تا بتوانیم یک Component را با ظاهرهای مختلف استفاده کنیم.

Component چیست؟

Component یک بخش مستقل و قابل استفاده مجدد از رابط کاربری است.

برای مثال در یک سایت ممکن است چندین Button داشته باشیم:

Primary Button
Secondary Button
Danger Button

یا چندین Card:

Product Card
Blog Card
Service Card

به جای اینکه هر بار ساختار و Style را از ابتدا بنویسیم، می‌توانیم یک الگوی قابل استفاده مجدد ایجاد کنیم.

به صورت ساده:

Component
    ↓
Reusable UI
    ↓
Multiple Places

چرا Component مهم است؟

فرض کنید این Button را در ۱۰ قسمت پروژه استفاده کرده‌ایم:

<button class="rounded-lg bg-blue-600 px-5 py-3 text-white">
    خرید
</button>

حالا تصمیم می‌گیریم رنگ Button را تغییر دهیم.

اگر Classها در ۱۰ قسمت مختلف تکرار شده باشند، باید هر ۱۰ قسمت را بررسی کنیم.

اما اگر Button یک Component باشد:

Button Component
      ↓
10 usages

می‌توانیم Style آن را در یک محل مدیریت کنیم.

Componentها فقط برای کوتاه کردن کد نیستند؛ هدف اصلی آن‌ها ایجاد ساختار منظم، قابل نگهداری و قابل توسعه است.

Component در Tailwind چگونه ایجاد می‌شود؟

Tailwind CSS خودش یک Framework برای Componentهای JavaScript نیست.

Tailwind بیشتر روی Styling تمرکز دارد.

Component می‌تواند با تکنولوژی‌های مختلف ساخته شود:

HTML
React
Vue
Angular
Next.js
ASP.NET Razor
Blazor

بنابراین باید بین این دو مفهوم تفاوت قائل شویم:

Tailwind
→ Styling

Framework
→ Component Structure

در این مقاله ابتدا مفهوم Component را با HTML و الگوهای ساده یاد می‌گیریم و سپس ساختار آن را در Frameworkها بررسی می‌کنیم.

اولین Component — Button

فرض کنید Button اصلی پروژه ما این Style را دارد:

<button
    class="rounded-lg bg-blue-600 px-5 py-3
           font-semibold text-white
           hover:bg-blue-700"
>
    خرید
</button>

اگر این Button چندین بار استفاده شود، Classها نیز تکرار می‌شوند.

به جای آن می‌توانیم یک Component به نام:

Button

داشته باشیم.

و فقط محتوای آن را تغییر دهیم:

Button
├── خرید
├── ثبت نام
├── ورود
└── ادامه

Button Variant

گاهی همه Buttonها یکسان نیستند.

مثلاً:

Primary
Secondary
Danger
Success

به این حالت‌ها معمولاً Variant گفته می‌شود.

مثلاً:

Button
 ├── Primary
 ├── Secondary
 └── Danger

طراحی Button Variant با HTML

برای درک مفهوم، می‌توانیم سه Button داشته باشیم:

<button class="rounded-lg bg-blue-600 px-5 py-3 text-white hover:bg-blue-700">
    Primary
</button>

<button class="rounded-lg bg-gray-200 px-5 py-3 text-gray-900 hover:bg-gray-300">
    Secondary
</button>

<button class="rounded-lg bg-red-600 px-5 py-3 text-white hover:bg-red-700">
    Delete
</button>

می‌بینیم که بخش زیادی از Classها تکراری است.

ساختار مشترک:

rounded-lg
px-5
py-3
font-semibold

و بخش متفاوت:

Primary
→ Blue

Secondary
→ Gray

Danger
→ Red

این دقیقاً جایی است که Component و Variant ارزش خودشان را نشان می‌دهند.

Componentهای HTML

در پروژه‌های ساده می‌توانیم الگوهای مشترک را در فایل‌های جداگانه قرار دهیم.

مثلاً:

components/
    button.html
    card.html
    badge.html

سپس هر Component می‌تواند ساختار مشخصی داشته باشد.

در پروژه‌های واقعی، نحوه استفاده از این Componentها به تکنولوژی پروژه بستگی دارد.

Component در React

اگر از React یا Next.js استفاده کنیم، یک Component می‌تواند یک Function باشد.

مثلاً:

function Button() {
    return (
        <button className="rounded-lg bg-blue-600 px-5 py-3 text-white">
            Button
        </button>
    );
}

حالا:

<Button />

همان Button را نمایش می‌دهد.

ارسال متن به Component

می‌توانیم متن Button را نیز قابل تغییر کنیم.

function Button({ children }) {
    return (
        <button className="rounded-lg bg-blue-600 px-5 py-3 text-white">
            {children}
        </button>
    );
}

حالا:

<Button>
    خرید
</Button>

<Button>
    ثبت نام
</Button>

یک Component مشترک داریم اما محتوای آن متفاوت است.

Button با Variant

می‌توانیم Variant نیز ایجاد کنیم:

function Button({ variant = "primary", children }) {

    const styles = {
        primary: "bg-blue-600 hover:bg-blue-700 text-white",
        secondary: "bg-gray-200 hover:bg-gray-300 text-gray-900",
        danger: "bg-red-600 hover:bg-red-700 text-white"
    };

    return (
        <button
            className={`rounded-lg px-5 py-3 font-semibold ${styles[variant]}`}
        >
            {children}
        </button>
    );
}

استفاده:

<Button variant="primary">
    خرید
</Button>

<Button variant="secondary">
    انصراف
</Button>

<Button variant="danger">
    حذف
</Button>

در اینجا یک Component داریم که چند ظاهر مختلف دارد.

ساختار ذهنی Variant

Button
  │
  ├── Primary
  │
  ├── Secondary
  │
  └── Danger

این الگو در پروژه‌های بزرگ بسیار رایج است.

Card Component

حالا یک Card ایجاد کنیم.

نسخه تکراری:

<div class="rounded-xl border bg-white p-6 shadow-sm">
    <h3 class="text-lg font-bold">
        Product
    </h3>

    <p class="mt-2 text-gray-600">
        Product description.
    </p>
</div>

اگر این Card در چند قسمت استفاده شود، بهتر است Component شود.

ساخت Card با React

function Card({ title, description }) {
    return (
        <div className="rounded-xl border bg-white p-6 shadow-sm">

            <h3 className="text-lg font-bold">
                {title}
            </h3>

            <p className="mt-2 text-gray-600">
                {description}
            </p>

        </div>
    );
}

حالا:

<Card
    title="Product 1"
    description="Product description"
/>

<Card
    title="Product 2"
    description="Another product description"
/>

ساختار مشترک باقی می‌ماند، اما اطلاعات تغییر می‌کند.

Card با Hover

می‌توانیم Stateهایی که در مقاله قبل یاد گرفتیم را نیز داخل Component قرار دهیم:

function Card({ title, description }) {
    return (
        <div
            className="group rounded-xl border bg-white p-6 shadow-sm
                       transition-all duration-300
                       hover:-translate-y-1
                       hover:shadow-lg"
        >

            <h3 className="text-lg font-bold group-hover:text-blue-600">
                {title}
            </h3>

            <p className="mt-2 text-gray-600">
                {description}
            </p>

        </div>
    );
}

اکنون هر Card رفتار یکسانی دارد.

Badge Component

Badgeها نیز معمولاً چندین بار در پروژه استفاده می‌شوند.

مثلاً:

<span class="rounded-full bg-green-100 px-3 py-1 text-sm text-green-700">
    Available
</span>

می‌توانیم Badgeهای مختلف داشته باشیم:

Success
Warning
Danger
Info

Badge با Variant

function Badge({ variant = "success", children }) {

    const styles = {
        success: "bg-green-100 text-green-700",
        warning: "bg-yellow-100 text-yellow-700",
        danger: "bg-red-100 text-red-700",
        info: "bg-blue-100 text-blue-700"
    };

    return (
        <span
            className={`rounded-full px-3 py-1 text-sm ${styles[variant]}`}
        >
            {children}
        </span>
    );
}

استفاده:

<Badge variant="success">
    Available
</Badge>

<Badge variant="warning">
    Pending
</Badge>

<Badge variant="danger">
    Failed
</Badge>

Input Component

Input نیز گزینه خوبی برای Component شدن است.

function Input({ label, placeholder }) {
    return (
        <div>
            <label className="mb-2 block font-medium">
                {label}
            </label>

            <input
                type="text"
                placeholder={placeholder}
                className="w-full rounded-lg border border-gray-300
                           px-4 py-3
                           focus:border-blue-500
                           focus:ring-2
                           focus:ring-blue-200"
            />
        </div>
    );
}

حالا:

<Input
    label="نام"
    placeholder="نام خود را وارد کنید"
/>

<Input
    label="ایمیل"
    placeholder="example@email.com"
/>

ساختار یکسان است و فقط اطلاعات تغییر می‌کند.

چرا Component بهتر از Copy/Paste است؟

فرض کنید ۲۰ Product Card داریم.

روش اول:

Copy
Copy
Copy
Copy
Copy
...

مشکل:

  • کد زیاد

  • نگهداری سخت

  • احتمال خطا

  • تغییرات دشوار

روش دوم:

ProductCard
     ↓
20 usages

مزایا:

  • یک محل برای Style

  • تغییر سریع

  • ساختار منظم

  • استفاده مجدد

  • کاهش تکرار

Component فقط برای Style نیست

Component می‌تواند علاوه بر ظاهر، منطق مربوط به همان بخش را نیز در خود داشته باشد.

مثلاً:

ProductCard
├── Image
├── Title
├── Price
├── Badge
└── Button

این Component می‌تواند اطلاعات Product را دریافت کند و آن‌ها را نمایش دهد.

ساخت ProductCard

مثال:

function ProductCard({ product }) {
    return (
        <div
            className="overflow-hidden rounded-xl border bg-white
                       shadow-sm transition-all duration-300
                       hover:-translate-y-1 hover:shadow-lg"
        >

            <img
                src={product.image}
                alt={product.title}
                className="h-56 w-full object-cover"
            />

            <div className="p-5">

                <h3 className="text-lg font-bold">
                    {product.title}
                </h3>

                <p className="mt-2 text-gray-600">
                    {product.description}
                </p>

                <div className="mt-5 flex items-center justify-between">

                    <span className="font-bold">
                        {product.price}
                    </span>

                    <button
                        className="rounded-lg bg-blue-600
                                   px-4 py-2 text-white
                                   hover:bg-blue-700"
                    >
                        خرید
                    </button>

                </div>

            </div>

        </div>
    );
}

حالا اطلاعات می‌تواند از یک Object بیاید:

const product = {
    image: "product.jpg",
    title: "Modern Product",
    description: "Product description",
    price: "$120"
};

و:

<ProductCard product={product} />

Component Composition

گاهی یک Component از چند Component کوچک‌تر ساخته می‌شود.

مثلاً:

ProductCard
│
├── ProductImage
├── ProductInfo
│   ├── ProductTitle
│   ├── ProductDescription
│   └── ProductPrice
│
└── Button

این مفهوم را Composition می‌نامیم.

به جای اینکه یک Component بسیار بزرگ بسازیم، می‌توانیم آن را به بخش‌های کوچک‌تر تقسیم کنیم.

چه زمانی یک Component بسازیم؟

هر بخش صفحه الزاماً نیاز به Component جداگانه ندارد.

معمولاً زمانی Component ایجاد می‌کنیم که:

  • یک بخش چند بار استفاده می‌شود.

  • ساختار آن مشخص و مستقل است.

  • منطق خاصی دارد.

  • تغییرات آن باید در یک محل مدیریت شود.

  • می‌خواهیم کد خواناتر شود.

مثلاً:

Button
Card
Input
Modal
Navbar
Footer
Badge
Alert

معمولاً Componentهای مناسبی هستند.

Component بیش از حد کوچک

گاهی ممکن است بیش از حد Component ایجاد کنیم.

مثلاً:

SmallText
SmallIcon
SmallWrapper
SmallTitle

برای هر عنصر کوچک یک Component بسازیم.

این کار می‌تواند پروژه را پیچیده کند.

بنابراین هدف این نیست که همه چیز Component باشد.

هدف این است که بخش‌های منطقی و قابل استفاده مجدد را جدا کنیم.

Component و Responsive Design

Componentها می‌توانند Responsive نیز باشند.

مثلاً:

function ProductCard() {
    return (
        <div
            className="w-full
                       rounded-xl border p-5
                       md:w-1/2
                       lg:w-1/3"
        >
            Product
        </div>
    );
}

هر بار که Component استفاده شود، همان Responsive behavior را خواهد داشت.

Component و Dark Mode

همچنین می‌توانیم Dark Mode را داخل Component قرار دهیم:

function Card({ title }) {
    return (
        <div
            className="rounded-xl border
                       bg-white text-gray-900
                       dark:border-gray-700
                       dark:bg-gray-800
                       dark:text-white"
        >
            <h3 className="font-bold">
                {title}
            </h3>
        </div>
    );
}

اکنون تمام Cardهای پروژه از همین Theme پشتیبانی می‌کنند.

Component و State

Stateهایی که در مقاله قبل یاد گرفتیم نیز می‌توانند داخل Component قرار بگیرند.

مثلاً:

function Button() {
    return (
        <button
            className="rounded-lg bg-blue-600 px-5 py-3 text-white
                       transition-colors duration-300
                       hover:bg-blue-700
                       focus:ring-2
                       focus:ring-blue-300
                       active:bg-blue-800"
        >
            Submit
        </button>
    );
}

حالا تمام Buttonهای پروژه می‌توانند رفتار مشابه داشته باشند.

یک ساختار پیشنهادی برای پروژه

در یک پروژه Frontend می‌توانیم ساختاری مانند زیر داشته باشیم:

src/
│
├── components/
│   ├── Button
│   ├── Card
│   ├── Badge
│   ├── Input
│   └── Navbar
│
├── pages/
│
├── layouts/
│
└── styles/

ساختار دقیق به Framework و معماری پروژه بستگی دارد، اما ایده اصلی این است که Componentهای قابل استفاده مجدد در یک محل مشخص قرار بگیرند.

Tailwind و Component Classها

در Tailwind می‌توان گاهی مجموعه‌ای از Utilityها را به یک Class سفارشی تبدیل کرد.

برای مثال در CSS می‌توان از @apply در شرایط و نسخه‌های پشتیبانی‌شده استفاده کرد:

.btn-primary {
    @apply rounded-lg bg-blue-600 px-5 py-3 text-white;
}

سپس:

<button class="btn-primary">
    خرید
</button>

اما بهتر است @apply را برای هر عنصر به‌صورت بی‌دلیل استفاده نکنیم.

در بسیاری از پروژه‌ها، Componentهای Framework مانند React یا Vue محل مناسب‌تری برای تعریف الگوهای قابل استفاده مجدد هستند.

اگر یک UI Component واقعی دارید، معمولاً بهتر است آن را در سطح Component مدیریت کنید؛ نه اینکه تمام پروژه را به مجموعه‌ای از Classهای CSS سفارشی تبدیل کنید.

یک مثال کامل

فرض کنید یک صفحه محصولات داریم.

ساختار:

Products Page
│
├── Navbar
│
├── ProductGrid
│   ├── ProductCard
│   ├── ProductCard
│   └── ProductCard
│
└── Footer

و هر ProductCard:

ProductCard
│
├── Image
├── Title
├── Description
├── Price
├── Badge
└── Button

این ساختار بسیار قابل توسعه‌تر از یک فایل HTML بسیار بزرگ است.

اشتباهات رایج

۱. Copy/Paste زیاد

اگر یک بخش چندین بار تکرار شده، بررسی کنید آیا می‌تواند Component شود.

۲. Componentهای بسیار بزرگ

یک Component با صدها خط کد معمولاً مدیریت دشوارتری دارد.

۳. Componentهای بیش از حد کوچک

برای هر <span> یا <div> نباید الزاماً Component ایجاد کنیم.

۴. مخلوط کردن مسئولیت‌ها

بهتر است هر Component مسئولیت مشخصی داشته باشد.

۵. Variantهای بیش از حد

اگر یک Button دارای ده‌ها Variant باشد، ممکن است طراحی Component نیاز به بازنگری داشته باشد.

تمرین عملی

یک فروشگاه کوچک با Componentهای زیر طراحی کنید:

Navbar
Button
Badge
ProductCard
ProductGrid
Footer

Button

حداقل سه Variant داشته باشد:

Primary
Secondary
Danger

Badge

حداقل سه Variant:

Success
Warning
Danger

ProductCard

شامل:

Image
Title
Description
Price
Badge
Button

ProductGrid

Responsive باشد:

Mobile
1 column

Tablet
2 columns

Desktop
3 columns

Dark Mode

تمام Componentها باید از Dark Mode پشتیبانی کنند.

States

Buttonها و Cardها باید hover و focus مناسب داشته باشند.

در پایان بررسی کنید آیا برای تغییر ظاهر Button یا Card لازم است چندین فایل را ویرایش کنید یا فقط یک Component را.

جمع‌بندی

در این مقاله با مفهوم Reusable Component آشنا شدیم.

یاد گرفتیم:

  • Component چیست.

  • چرا Componentها اهمیت دارند.

  • چگونه Button را به Component تبدیل کنیم.

  • مفهوم Variant چیست.

  • چگونه Buttonهای مختلف ایجاد کنیم.

  • چگونه Card و Badge بسازیم.

  • چگونه Input را به Component تبدیل کنیم.

  • چگونه ProductCard طراحی کنیم.

  • مفهوم Composition را بررسی کردیم.

  • Componentها را با Responsive Design ترکیب کردیم.

  • Dark Mode را داخل Component قرار دادیم.

  • Stateهایی مانند Hover و Focus را به Componentها اضافه کردیم.

  • درباره استفاده صحیح از @apply صحبت کردیم.

  • با ساختار پیشنهادی Componentها در پروژه آشنا شدیم.

مفهوم اصلی این مقاله را می‌توان این‌گونه خلاصه کرد:

Reusable Component
        ↓
Consistent UI
        ↓
Less Repetition
        ↓
Easier Maintenance
        ↓
Scalable Project

در یک پروژه واقعی، ترکیب Tailwind CSS + Component Architecture به ما کمک می‌کند رابط کاربری را هم سریع‌تر توسعه دهیم و هم ساختار منظم‌تری داشته باشیم.

۰۰

نظرات

کد در دسترس نیست

هنوز نظری ثبت نشده است.