إطار Tailwind CSS

@apply واستخراج المكونات

20 دقيقة الدرس 23 من 35

@apply واستخراج المكونات

بينما يشجع Tailwind على نهج الأدوات أولاً مع الفئات في HTML الخاص بك، هناك أوقات تحتاج فيها إلى استخراج الأنماط المتكررة إلى مكونات قابلة لإعادة الاستخدام أو إنشاء فئات أدوات مخصصة. توجيه @apply هو أداتك لهذا—ولكن يجب استخدامه بحكمة.

في هذا الدرس، سنستكشف متى وكيف نستخدم @apply، ونفهم الفلسفة وراء استخراج المكونات، ونتعلم أفضل الممارسات لتحقيق التوازن بين CSS الأدوات أولاً والأنماط القائمة على المكونات.

ما هو @apply؟

يتيح لك توجيه @apply تكوين فئات أدوات Tailwind في فئات CSS مخصصة. يستخرج فئات الأدوات من HTML الخاص بك وينقلها إلى CSS الخاص بك:

مثال @apply الأساسي

/* في ملف CSS الخاص بك */
.btn-primary {
  @apply px-6 py-3 bg-blue-500 text-white rounded-lg;
  @apply hover:bg-blue-600 focus:outline-none focus:ring-2;
  @apply focus:ring-blue-500 focus:ring-offset-2;
  @apply transition-colors duration-200;
}

الآن في HTML الخاص بك، بدلاً من هذا:

قبل @apply (HTML مطوّل)

<button class="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600
               focus:outline-none focus:ring-2 focus:ring-blue-500
               focus:ring-offset-2 transition-colors duration-200">
  اضغط هنا
</button>

يمكنك كتابة هذا:

بعد @apply (HTML نظيف)

<button class="btn-primary">
  اضغط هنا
</button>

متى تستخدم @apply

قبل استخدام @apply، اسأل نفسك: "هل أستخدم هذا النمط بالضبط في أماكن متعددة؟" إليك حالات الاستخدام الجيدة:

1. أنماط المكونات المتكررة

نظام الأزرار مع @apply

/* أنماط الزر الأساسية */
.btn {
  @apply inline-flex items-center justify-center;
  @apply px-4 py-2 rounded-md font-medium;
  @apply transition-colors duration-200;
  @apply focus:outline-none focus:ring-2 focus:ring-offset-2;
}

/* متغيرات الزر */
.btn-primary {
  @apply btn bg-blue-500 text-white;
  @apply hover:bg-blue-600 focus:ring-blue-500;
}

.btn-secondary {
  @apply btn bg-gray-200 text-gray-800;
  @apply hover:bg-gray-300 focus:ring-gray-500;
}

.btn-danger {
  @apply btn bg-red-500 text-white;
  @apply hover:bg-red-600 focus:ring-red-500;
}

/* أحجام الزر */
.btn-sm {
  @apply px-3 py-1.5 text-sm;
}

.btn-lg {
  @apply px-6 py-3 text-lg;
}

استخدام فئات الأزرار

<button class="btn btn-primary">زر أساسي</button>
<button class="btn btn-secondary btn-sm">ثانوي صغير</button>
<button class="btn btn-danger btn-lg">خطر كبير</button>

2. عناصر النماذج

فئات مكونات النماذج

/* أنماط حقل الإدخال الأساسية */
.input {
  @apply w-full px-4 py-2 border border-gray-300 rounded-md;
  @apply focus:outline-none focus:ring-2 focus:ring-blue-500;
  @apply focus:border-transparent transition-all duration-200;
}

.input-error {
  @apply input border-red-500 focus:ring-red-500;
}

/* أنماط التسمية */
.label {
  @apply block text-sm font-medium text-gray-700 mb-1;
}

.label-required::after {
  content: " *";
  @apply text-red-500;
}

/* مجموعة النموذج */
.form-group {
  @apply mb-4;
}

/* رسالة الخطأ */
.error-message {
  @apply mt-1 text-sm text-red-600;
}

استخدام فئات النماذج

<div class="form-group">
  <label class="label label-required">البريد الإلكتروني</label>
  <input type="email" class="input" placeholder="you@example.com">
</div>

<div class="form-group">
  <label class="label">كلمة المرور</label>
  <input type="password" class="input-error">
  <p class="error-message">يجب أن تكون كلمة المرور 8 أحرف على الأقل</p>
</div>

3. مكونات البطاقات

نظام البطاقات مع @apply

/* بطاقة أساسية */
.card {
  @apply bg-white rounded-lg shadow-md overflow-hidden;
  @apply border border-gray-200;
}

/* أقسام البطاقة */
.card-header {
  @apply px-6 py-4 border-b border-gray-200;
  @apply bg-gray-50;
}

.card-body {
  @apply px-6 py-4;
}

.card-footer {
  @apply px-6 py-4 border-t border-gray-200;
  @apply bg-gray-50;
}

/* عنوان البطاقة */
.card-title {
  @apply text-xl font-semibold text-gray-900;
}

/* متغيرات البطاقة */
.card-hover {
  @apply card transition-all duration-300;
  @apply hover:shadow-lg hover:-translate-y-1;
}

.card-interactive {
  @apply card cursor-pointer;
  @apply hover:border-blue-500 hover:shadow-lg;
}

متى لا تستخدم @apply

فهم متى تتجنب @apply مهم بنفس القدر مثل معرفة متى تستخدمه:

تجنب @apply لـ:
  • أنماط لمرة واحدة: إذا كنت تستخدم نمطًا مرة واحدة فقط، احتفظ بالأدوات في HTML
  • توليفات بسيطة: 2-3 أدوات لا تبرر الاستخراج
  • أنماط خاصة بالصفحة: التخطيطات الفريدة يجب أن تبقى في HTML
  • مكونات الإطار: React، Vue، إلخ. تتعامل مع التركيب بشكل أفضل
  • التحسين المبكر: انتظر حتى تتكرر الأنماط 3+ مرات

استخدام سيئ لـ @apply (لا تفعل هذا)

/* لا تفعل: استخراج كل شيء */
.my-div {
  @apply w-full h-full bg-white p-4 m-2 rounded shadow;
}

/* لا تفعل: أنماط لمرة واحدة */
.about-page-hero {
  @apply h-screen flex items-center justify-center bg-gradient-to-r from-blue-500 to-purple-600;
}

/* لا تفعل: توليفات بسيطة */
.flex-center {
  @apply flex items-center justify-center;
}

/* من الأفضل الاحتفاظ بهذا في HTML أو كمكون */

توجيه @layer

عند استخدام @apply، نظم أنماطك المخصصة مع توجيه @layer. يخبر هذا Tailwind أين ينتمي CSS المخصص الخاص بك في التسلسل:

استخدام @layer للتنظيم

/* في ملف CSS الرئيسي الخاص بك */

/* طبقة Base - افتراضيات عناصر HTML */
@layer base {
  h1 {
    @apply text-4xl font-bold text-gray-900;
  }

  h2 {
    @apply text-3xl font-semibold text-gray-800;
  }

  a {
    @apply text-blue-600 hover:text-blue-800 underline;
  }

  body {
    @apply font-sans text-gray-900 antialiased;
  }
}

/* طبقة Components - مكونات قابلة لإعادة الاستخدام */
@layer components {
  .btn {
    @apply px-4 py-2 rounded-md font-medium;
    @apply transition-colors duration-200;
  }

  .btn-primary {
    @apply btn bg-blue-500 text-white;
    @apply hover:bg-blue-600;
  }

  .card {
    @apply bg-white rounded-lg shadow-md p-6;
  }

  .input {
    @apply w-full px-4 py-2 border rounded-md;
    @apply focus:outline-none focus:ring-2;
  }
}

/* طبقة Utilities - فئات أدوات مخصصة */
@layer utilities {
  .text-shadow {
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
  }

  .bg-grid {
    background-image: repeating-linear-gradient(
      0deg, #e5e7eb 0px, #e5e7eb 1px,
      transparent 1px, transparent 20px
    );
  }

  .scrollbar-hide {
    -ms-overflow-style: none;
    scrollbar-width: none;
  }

  .scrollbar-hide::-webkit-scrollbar {
    display: none;
  }
}
ترتيب الطبقات:
  • base: يتم تحميله أولاً، لافتراضيات عناصر HTML
  • components: يتم تحميله ثانيًا، لفئات المكونات
  • utilities: يتم تحميله أخيرًا، أعلى خصوصية

يمكن للأدوات تجاوز المكونات، ويمكن للمكونات تجاوز أنماط base.

استخراج الأنماط المعقدة

أحيانًا تحتاج إلى دمج @apply مع CSS العادي للأنماط المعقدة:

مكون معقد مع أنماط مختلطة

@layer components {
  /* مكون القائمة المنسدلة */
  .dropdown {
    @apply relative inline-block;
  }

  .dropdown-trigger {
    @apply px-4 py-2 bg-white border border-gray-300 rounded-md;
    @apply hover:bg-gray-50 focus:outline-none;
    cursor: pointer;
  }

  .dropdown-menu {
    @apply absolute left-0 mt-2 w-56 rounded-md shadow-lg;
    @apply bg-white ring-1 ring-black ring-opacity-5;
    @apply opacity-0 invisible transition-all duration-200;
    @apply transform origin-top-left scale-95;
    z-index: 1000;
  }

  .dropdown.open .dropdown-menu {
    @apply opacity-100 visible scale-100;
  }

  .dropdown-item {
    @apply block px-4 py-2 text-sm text-gray-700;
    @apply hover:bg-gray-100 hover:text-gray-900;
    @apply transition-colors duration-150;
  }

  .dropdown-divider {
    @apply my-1 border-t border-gray-200;
  }

  /* مكون التلميح */
  .tooltip {
    @apply relative inline-block;
  }

  .tooltip-text {
    @apply invisible absolute z-10 w-32 px-3 py-2;
    @apply bg-gray-900 text-white text-sm text-center rounded-md;
    @apply -top-12 left-1/2 -translate-x-1/2;
    @apply opacity-0 transition-opacity duration-300;
  }

  .tooltip-text::after {
    content: "";
    @apply absolute top-full left-1/2 -translate-x-1/2;
    border-width: 5px;
    border-style: solid;
    border-color: #1f2937 transparent transparent transparent;
  }

  .tooltip:hover .tooltip-text {
    @apply visible opacity-100;
  }
}

أفضل ممارسات استخراج المكونات

اتبع هذه المبادئ عند تقرير ما إذا كنت ستستخرج المكونات:

قاعدة "الثلاثة":
  1. الاستخدام الأول: اكتب الأدوات مباشرة في HTML
  2. الاستخدام الثاني: انسخ والصق، اتخذ ملاحظة ذهنية
  3. الاستخدام الثالث: فكر في الاستخراج إلى فئة @apply

لا تجرد بشكل مبكر—انتظر ظهور الأنماط الواضحة.

قابل للتركيب على الأحادي

جيد: نظام أزرار قابل للتركيب

/* فئات صغيرة قابلة للتركيب */
.btn {
  @apply px-4 py-2 rounded-md font-medium;
}

.btn-solid {
  @apply btn;
}

.btn-outline {
  @apply btn border-2 bg-transparent;
}

/* الألوان كفئات منفصلة */
.btn-blue {
  @apply bg-blue-500 text-white hover:bg-blue-600;
}

.btn-red {
  @apply bg-red-500 text-white hover:bg-red-600;
}

/* الاستخدام: امزج وطابق */
<button class="btn btn-solid btn-blue">أزرق صلب</button>
<button class="btn btn-outline btn-red">أحمر محدد</button>

سيئ: فئات أزرار أحادية

/* لا تفعل: محدد جدًا، غير قابل للتركيب */
.btn-solid-blue {
  @apply px-4 py-2 rounded-md font-medium;
  @apply bg-blue-500 text-white hover:bg-blue-600;
}

.btn-solid-red {
  @apply px-4 py-2 rounded-md font-medium;
  @apply bg-red-500 text-white hover:bg-red-600;
}

.btn-outline-blue {
  @apply px-4 py-2 rounded-md font-medium;
  @apply border-2 border-blue-500 text-blue-500 bg-transparent;
}

/* عدد كبير جدًا من الفئات المحددة، الكثير من التكرار */

تحقيق التوازن بين الأدوات أولاً والمكونات

المفتاح هو إيجاد التوازن الصحيح بين فئات الأدوات وفئات المكونات:

مثال على النهج المتوازن

/* استخراج الأنماط الشائعة */
@layer components {
  .card {
    @apply bg-white rounded-lg shadow-md p-6;
  }

  .btn {
    @apply px-4 py-2 rounded-md font-medium;
    @apply focus:outline-none focus:ring-2 focus:ring-offset-2;
    @apply transition-colors duration-200;
  }
}

/* HTML: فئات مكونات + معدلات أدوات */
<div class="card border border-gray-200">
  <h2 class="text-2xl font-bold mb-4">عنوان البطاقة</h2>
  <p class="text-gray-600 mb-6">محتوى البطاقة يذهب هنا</p>
  <button class="btn bg-blue-500 text-white hover:bg-blue-600">
    إجراء
  </button>
</div>

أنماط مكونات الإطار

في أطر JavaScript، فكر في الاستخراج القائم على المكونات بدلاً من فئات CSS:

مكون React (غالبًا أفضل من @apply)

// Button.jsx
function Button({ variant = 'primary', size = 'md', children }) {
  const baseClasses = 'inline-flex items-center justify-center ' +
                      'rounded-md font-medium transition-colors ' +
                      'focus:outline-none focus:ring-2 focus:ring-offset-2';

  const variantClasses = {
    primary: 'bg-blue-500 text-white hover:bg-blue-600 focus:ring-blue-500',
    secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500',
    danger: 'bg-red-500 text-white hover:bg-red-600 focus:ring-red-500'
  };

  const sizeClasses = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base',
    lg: 'px-6 py-3 text-lg'
  };

  return (
    <button
      className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`}
    >
      {children}
    </button>
  );
}

// الاستخدام
<Button variant="primary" size="lg">اضغط هنا</Button>
<Button variant="danger">حذف</Button>
مكونات الإطار مقابل @apply:
  • استخدم @apply: لمواقع HTML الثابتة، القوالب، أو الأنماط العالمية حقًا
  • استخدم المكونات: في React/Vue/Svelte للتركيب الأفضل والخصائص

مكتبة مكونات من العالم الحقيقي

إليك مكتبة مكونات عملية باستخدام @apply:

نظام مكونات كامل

@layer components {
  /* الأزرار */
  .btn {
    @apply inline-flex items-center justify-center;
    @apply px-4 py-2 border border-transparent rounded-md;
    @apply font-medium text-sm transition-all duration-200;
    @apply focus:outline-none focus:ring-2 focus:ring-offset-2;
    @apply disabled:opacity-50 disabled:cursor-not-allowed;
  }

  .btn-primary { @apply btn bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500; }
  .btn-secondary { @apply btn bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500; }
  .btn-success { @apply btn bg-green-600 text-white hover:bg-green-700 focus:ring-green-500; }
  .btn-danger { @apply btn bg-red-600 text-white hover:bg-red-700 focus:ring-red-500; }

  /* الشارات */
  .badge {
    @apply inline-flex items-center px-2.5 py-0.5 rounded-full;
    @apply text-xs font-medium;
  }

  .badge-primary { @apply badge bg-blue-100 text-blue-800; }
  .badge-success { @apply badge bg-green-100 text-green-800; }
  .badge-warning { @apply badge bg-yellow-100 text-yellow-800; }
  .badge-danger { @apply badge bg-red-100 text-red-800; }

  /* التنبيهات */
  .alert {
    @apply p-4 rounded-md border;
  }

  .alert-info { @apply alert bg-blue-50 border-blue-200 text-blue-800; }
  .alert-success { @apply alert bg-green-50 border-green-200 text-green-800; }
  .alert-warning { @apply alert bg-yellow-50 border-yellow-200 text-yellow-800; }
  .alert-error { @apply alert bg-red-50 border-red-200 text-red-800; }

  /* عناصر النموذج */
  .form-input {
    @apply w-full px-3 py-2 border border-gray-300 rounded-md;
    @apply placeholder-gray-400 focus:outline-none;
    @apply focus:ring-2 focus:ring-blue-500 focus:border-transparent;
    @apply transition-all duration-200;
  }

  .form-label {
    @apply block text-sm font-medium text-gray-700 mb-1;
  }

  .form-error {
    @apply text-sm text-red-600 mt-1;
  }

  /* الروابط */
  .link {
    @apply text-blue-600 hover:text-blue-800 underline;
    @apply transition-colors duration-200;
  }

  .link-muted {
    @apply text-gray-600 hover:text-gray-900 no-underline;
  }
}

تمرين تطبيقي

المهمة: أنشئ نظام مكونات الإشعارات باستخدام @apply:

  1. أنشئ فئة .notification أساسية مع padding، زوايا مستديرة، حدود، وظل
  2. أضف متغيرات: .notification-info، .notification-success، .notification-warning، .notification-error
  3. أنشئ المكونات الفرعية .notification-title و .notification-body
  4. أضف متغير .notification-dismissable مع زر إغلاق
  5. ابنِ أمثلة HTML تعرض جميع المتغيرات
  6. استخدم توجيه @layer لتنظيم الكود الخاص بك بشكل صحيح

تمرين التحدي

المهمة المتقدمة: ابنِ نظام مكونات بطاقة تسعير كامل:

  1. أنشئ .pricing-card أساسية مع أقسام header، body، و footer
  2. أضف متغير .pricing-card-featured مع تصميم بارز
  3. أنشئ المكونات الفرعية: .pricing-header، .pricing-price، .pricing-features، .pricing-cta
  4. أضف تأثيرات hover والانتقالات
  5. اجعلها متجاوبة مع تخطيطات مختلفة للجوال/سطح المكتب
  6. ابنِ صفحة تسعير من 3 مستويات (أساسي، محترف، مؤسسة) باستخدام مكوناتك
  7. وازن بين فئات @apply والأدوات المضمنة للمرونة
نصيحة احترافية: ابدأ بفئات الأدوات في HTML الخاص بك. استخرج فقط إلى @apply عندما تكون قد نسخت ولصقت نفس النمط 3+ مرات. هذا يمنع التجريد المبكر ويحافظ على CSS الخاص بك خفيفًا.

الملخص

في هذا الدرس، تعلمت:

  • ما الذي يفعله @apply وكيفية استخدامه بفعالية
  • متى تستخدم @apply (الأنماط المتكررة، أنظمة المكونات)
  • متى لا تستخدم @apply (مرات واحدة، تركيبات بسيطة، مكونات الإطار)
  • توجيه @layer لتنظيم الأنماط المخصصة
  • أفضل الممارسات لاستخراج المكونات (قاعدة الثلاثة)
  • تحقيق التوازن بين نهج الأدوات أولاً وفئات المكونات
  • التصميم القابل للتركيب مقابل الأحادي للمكونات
  • الأنماط الخاصة بالإطار (مكونات React/Vue مقابل فئات CSS)

المفتاح لإتقان @apply هو ضبط النفس—استخدمه عندما يحسن حقًا قابلية الصيانة، وليس فقط لأنك تستطيع. في الدرس التالي، سنستكشف تنفيذ الوضع الداكن في Tailwind.