Tailwind مع React و Vue
Tailwind مع React و Vue
تعلم كيفية استخدام Tailwind CSS بفعالية مع أطر عمل JavaScript الحديثة مثل React و Vue. فهم أنماط المكونات، التنسيق الشرطي، وأفضل الممارسات الخاصة بكل إطار عمل.
إعداد Tailwind مع React
يتكامل Tailwind بسلاسة مع مشاريع React. إليك كيفية إعداده في مشروع Create React App أو Vite:
التثبيت في مشروع React
# تثبيت Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
# إنشاء ملفات التكوين
npx tailwindcss init -p
# في tailwind.config.js
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
# في src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
className بدلاً من class لأن class كلمة محجوزة في JavaScript. هذا هو الخطأ الأكثر شيوعاً عند البدء مع React و Tailwind.
استخدام className في مكونات React
يستخدم React لغة JSX، التي تتطلب خاصية className لفئات CSS:
مكون React أساسي مع Tailwind
import React from 'react';
function Button({ children, variant = 'primary' }) {
const baseClasses = 'px-4 py-2 rounded-lg font-semibold transition-colors';
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
};
return (
<button className={`${baseClasses} ${variants[variant]}`}>
{children}
</button>
);
}
export default Button;
الفئات الشرطية مع clsx و classnames
إدارة الفئات الشرطية بالدمج النصي يمكن أن تصبح فوضوية. مكتبات مثل clsx أو classnames تجعل هذا أكثر وضوحاً:
تثبيت واستخدام clsx
# تثبيت clsx
npm install clsx
// استخدام clsx في مكون
import clsx from 'clsx';
function Alert({ message, type, dismissible }) {
return (
<div className={clsx(
'p-4 rounded-lg border',
{
'bg-blue-50 border-blue-200 text-blue-800': type === 'info',
'bg-green-50 border-green-200 text-green-800': type === 'success',
'bg-yellow-50 border-yellow-200 text-yellow-800': type === 'warning',
'bg-red-50 border-red-200 text-red-800': type === 'error',
'pr-12': dismissible,
}
)}>
{message}
</div>
);
}
clsx لمنطق فئات شرطية أوضح. تتعامل مع المصفوفات والكائنات والتعبيرات الشرطية بأناقة، مما يجعل كود المكون أكثر قابلية للقراءة.
نمط دالة المساعدة cn()
تستخدم العديد من مشاريع React الحديثة دالة مساعدة cn() تجمع بين clsx و tailwind-merge للتعامل بشكل صحيح مع تعارضات الفئات:
إنشاء أداة cn()
# تثبيت التبعيات
npm install clsx tailwind-merge
// lib/utils.js
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs) {
return twMerge(clsx(inputs));
}
// الاستخدام في المكون
import { cn } from '@/lib/utils';
function Card({ className, children, ...props }) {
return (
<div
className={cn(
'rounded-lg border bg-white p-6 shadow-sm',
className
)}
{...props}
>
{children}
</div>
);
}
// الآن يمكنك تجاوز الفئات الافتراضية
<Card className="bg-blue-50 p-8">
Content
</Card>
className="p-8" لتجاوز p-6، تطبق كلا الفئتين والأخيرة في ملف CSS تفوز (غير متوقع). tailwind-merge يزيل الفئات المتعارضة بذكاء.
أنماط مكونات React مع Tailwind
إليك أنماط شائعة لبناء مكونات قابلة لإعادة الاستخدام:
مكون زر قائم على المتغيرات
import { cn } from '@/lib/utils';
const buttonVariants = {
variant: {
default: 'bg-blue-600 text-white hover:bg-blue-700',
destructive: 'bg-red-600 text-white hover:bg-red-700',
outline: 'border border-gray-300 bg-transparent hover:bg-gray-100',
ghost: 'hover:bg-gray-100',
link: 'text-blue-600 underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
};
function Button({
className,
variant = 'default',
size = 'default',
children,
...props
}) {
return (
<button
className={cn(
'inline-flex items-center justify-center rounded-md font-medium',
'transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
'disabled:pointer-events-none disabled:opacity-50',
buttonVariants.variant[variant],
buttonVariants.size[size],
className
)}
{...props}
>
{children}
</button>
);
}
التنسيق القائم على الحالة في React
يمكن لحالة React أن تتحكم في فئات Tailwind الخاصة بك:
مكون أكورديون تفاعلي
import { useState } from 'react';
import { ChevronDown } from 'lucide-react';
import { cn } from '@/lib/utils';
function Accordion({ title, children }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="border rounded-lg overflow-hidden">
<button
onClick={() => setIsOpen(!isOpen)}
className={cn(
'w-full px-4 py-3 flex items-center justify-between',
'text-left font-medium transition-colors',
isOpen ? 'bg-blue-50' : 'bg-white hover:bg-gray-50'
)}
>
<span>{title}</span>
<ChevronDown
className={cn(
'w-5 h-5 transition-transform duration-200',
isOpen && 'rotate-180'
)}
/>
</button>
<div
className={cn(
'px-4 overflow-hidden transition-all duration-200',
isOpen ? 'py-3 max-h-96' : 'max-h-0'
)}
>
{children}
</div>
</div>
);
}
إعداد Tailwind مع Vue 3
يعمل Vue 3 بشكل رائع مع Tailwind CSS. إليك عملية الإعداد:
التثبيت في مشروع Vue
# إنشاء مشروع Vue مع Vite
npm create vite@latest my-vue-app -- --template vue
# تثبيت Tailwind
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
# تكوين tailwind.config.js
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
# في src/style.css
@tailwind base;
@tailwind components;
@tailwind utilities;
ربط الفئات في Vue
يوفر Vue صيغة ربط فئات قوية تعمل بشكل مثالي مع Tailwind:
أنماط ربط الفئات في Vue
<template>
<!-- ربط نصي -->
<div :class="classes">Content</div>
<!-- صيغة الكائن -->
<button
:class="{
'bg-blue-600 text-white': variant === 'primary',
'bg-gray-200 text-gray-800': variant === 'secondary',
'opacity-50 cursor-not-allowed': disabled,
}"
>
Click me
</button>
<!-- صيغة المصفوفة -->
<div :class="[baseClasses, variantClasses, sizeClasses]">
Content
</div>
<!-- صيغة مختلطة -->
<div :class="[
'rounded-lg border',
{ 'bg-blue-50': isActive },
isLarge ? 'p-8' : 'p-4'
]">
Content
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const variant = ref('primary');
const disabled = ref(false);
const isActive = ref(true);
const isLarge = ref(false);
const classes = computed(() => {
return `px-4 py-2 rounded ${variant.value === 'primary' ? 'bg-blue-600' : 'bg-gray-200'}`;
});
</script>
clsx. صيغة الكائن والمصفوفة تتعامل مع الفئات الشرطية بأناقة بشكل افتراضي.
مكون Vue 3 مع Tailwind
إليك مكون Vue 3 كامل باستخدام Composition API:
مكون Badge قابل لإعادة الاستخدام
<template>
<span
:class="[
'inline-flex items-center rounded-full px-3 py-1 text-sm font-medium',
variantClasses,
sizeClasses,
className
]"
>
<slot name="icon" />
<slot />
</span>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
variant: {
type: String,
default: 'default',
validator: (value) => ['default', 'success', 'warning', 'error'].includes(value)
},
size: {
type: String,
default: 'md',
validator: (value) => ['sm', 'md', 'lg'].includes(value)
},
className: {
type: String,
default: ''
}
});
const variantClasses = computed(() => {
const variants = {
default: 'bg-gray-100 text-gray-800',
success: 'bg-green-100 text-green-800',
warning: 'bg-yellow-100 text-yellow-800',
error: 'bg-red-100 text-red-800',
};
return variants[props.variant];
});
const sizeClasses = computed(() => {
const sizes = {
sm: 'text-xs px-2 py-0.5',
md: 'text-sm px-3 py-1',
lg: 'text-base px-4 py-1.5',
};
return sizes[props.size];
});
</script>
Vue 3 مع التنسيق الديناميكي
استفد من تفاعلية Vue مع فئات Tailwind:
مكون بطاقة تفاعلية
<template>
<div
@mouseenter="isHovered = true"
@mouseleave="isHovered = false"
:class="cardClasses"
>
<div
:class="[
'absolute inset-0 bg-gradient-to-br transition-opacity duration-300',
'from-blue-500/20 to-purple-500/20',
isHovered ? 'opacity-100' : 'opacity-0'
]"
></div>
<div class="relative z-10">
<h3 class="text-xl font-bold mb-2">{{ title }}</h3>
<p class="text-gray-600">{{ description }}</p>
<button
@click="toggleFavorite"
:class="buttonClasses"
>
{{ isFavorite ? '❤️' : '🤍' }}
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const props = defineProps({
title: String,
description: String
});
const isHovered = ref(false);
const isFavorite = ref(false);
const cardClasses = computed(() => [
'relative overflow-hidden rounded-xl border p-6 transition-all duration-300',
isHovered.value
? 'shadow-xl scale-105 border-blue-300'
: 'shadow-md border-gray-200'
]);
const buttonClasses = computed(() => [
'mt-4 px-4 py-2 rounded-lg transition-colors',
isFavorite.value
? 'bg-red-100 text-red-600'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
]);
const toggleFavorite = () => {
isFavorite.value = !isFavorite.value;
};
</script>
أنماط التركيب المكون
يستفيد كل من React و Vue من أنماط التركيب مع Tailwind:
مكونات React المركبة
// Card.jsx
function Card({ children, className }) {
return (
<div className={cn('rounded-lg border bg-white shadow-sm', className)}>
{children}
</div>
);
}
Card.Header = function CardHeader({ children, className }) {
return (
<div className={cn('px-6 py-4 border-b', className)}>
{children}
</div>
);
};
Card.Body = function CardBody({ children, className }) {
return (
<div className={cn('px-6 py-4', className)}>
{children}
</div>
);
};
Card.Footer = function CardFooter({ children, className }) {
return (
<div className={cn('px-6 py-4 border-t bg-gray-50', className)}>
{children}
</div>
);
};
// الاستخدام
<Card>
<Card.Header>
<h3 className="text-lg font-semibold">Title</h3>
</Card.Header>
<Card.Body>
<p>Content goes here</p>
</Card.Body>
<Card.Footer>
<button className="btn-primary">Action</button>
</Card.Footer>
</Card>
مكتبات Headless UI
توفر مكتبات Headless UI مكونات يمكن الوصول إليها بدون تنسيق، مثالية لـ Tailwind:
Headless UI مع React
npm install @headlessui/react
import { Menu, Transition } from '@headlessui/react';
import { Fragment } from 'react';
function Dropdown() {
return (
<Menu as="div" className="relative inline-block text-left">
<Menu.Button className="inline-flex justify-center w-full px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700">
Options
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 w-56 mt-2 origin-top-right bg-white divide-y divide-gray-100 rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="px-1 py-1">
<Menu.Item>
{({ active }) => (
<button
className={`${
active ? 'bg-blue-500 text-white' : 'text-gray-900'
} group flex rounded-md items-center w-full px-2 py-2 text-sm`}
>
Edit
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
);
}
className={`text-${color}-500`}. عملية التنقية في Tailwind لا يمكنها اكتشاف هذه ولن تعمل. استخدم دائماً أسماء فئات كاملة أو حدد كائن تعيين.
تمرين 1: مكون زر React
أنشئ مكون زر React قابل لإعادة الاستخدام مع:
- ثلاثة متغيرات: primary، secondary، outline
- ثلاثة أحجام: sm، md، lg
- حالة تحميل مع دوار
- حالة معطلة
- استخدام clsx أو دالة المساعدة cn()
تمرين 2: مكون إدخال Vue
ابنِ مكون إدخال Vue 3 مع Tailwind يتضمن:
- دعم تسمية ورسالة خطأ
- حالات مختلفة: افتراضي، تركيز، خطأ، معطل
- فتحة أيقونة اختيارية
- فئات محسوبة بناءً على الخصائص
- دعم v-model
تمرين 3: شبكة بطاقات مستقلة عن الإطار
أنشئ مكون شبكة بطاقات متجاوبة في React أو Vue:
- تخطيط شبكة: عمود واحد على الموبايل، 2 على التابلت، 3 على سطح المكتب
- بطاقات مع تأثيرات التمرير
- صورة، عنوان، وصف، وزر إجراء
- وظيفة تبديل المفضلة
- استخدام نمط التركيب (Card.Header، Card.Body، إلخ)