البداية · فصل ١

الـ Stack الكامل لبناء Component Library

npm package · shadcn registry · Base UI · Tailwind · cva · tsdown · Storybook · Vitest · Changesets
🎯 الهدف: تعرف الطبقات اللي محتاجها عشان تبني lib، والأداة المناسبة لكل طبقة.

أول سؤال: npm package ولا registry؟

قبل ما تختار أي أداة، حدد الـ lib هتوصل للمشاريع إزاي:

الطريقةالمستخدم بياخدها إزايامتى تختارها
npm packagepnpm add @saeed/uiعايز versioning وتحكم مركزي، وكذا مشروع يتحدث مرة واحدة
shadcn registrynpx shadcn add @saeed/button والكود يتنسخ عندهلنفسك أو لتيم صغير، وكل مشروع يعدّل في الكود براحته

الطبقات والأداة المناسبة لكل واحدة

١. الأساس: Behavior و Accessibility

ماتكتبش dialog و dropdown و combobox من الصفر. الـ focus trap والـ keyboard navigation والـ ARIA فيهم صعبين جدًا. استخدم headless library:

  • Base UI: من نفس صناع Radix و Floating UI و Material UI، ووصلت stable v1.0 في 2026. shadcn بقى بيستخدمها كـ default للمشاريع الجديدة.
  • React Aria: لو محتاج RTL و i18n قوي جدًا.
  • Radix: لسه ممتازة ومنتشرة، بس Base UI هي الاتجاه الحالي.

المقارنة الكاملة في الفصل الجاي.

٢. الـ Styling

  • Tailwind v4 مع CSS variables للـ design tokens (ألوان، spacing، radius) عشان الـ theming والـ dark mode.
  • cva أو tailwind-variants للـ variants زي size و intent.
  • tailwind-merge + clsx عشان اللي بيستخدم الـ component يقدر يعمل override للـ classes.
button.tsx
import { cva, type VariantProps } from "class-variance-authority";import { cn } from "../lib/cn"; // clsx + tailwind-mergeconst button = cva("inline-flex items-center rounded-[--radius] font-medium", {  variants: {    intent: {      primary: "bg-[--color-primary] text-white",      ghost: "bg-transparent hover:bg-[--color-muted]",    },    size: { sm: "h-8 px-3 text-sm", md: "h-10 px-4" },  },  defaultVariants: { intent: "primary", size: "md" },});type Props = React.ComponentProps<"button"> & VariantProps<typeof button>;export function Button({ className, intent, size, ...rest }: Props) {  // className من برا بيكسب على الـ default بفضل tailwind-merge  return <button className={cn(button({ intent, size }), className)} {...rest} />;}

٣. الـ Build (لو npm package)

  • tsdown هو الاختيار الحديث: بيطلع ESM و CJS و d.ts، وفيه unbundle mode بيحافظ على هيكل الفولدرات، وده مهم للـ tree-shaking. TanStack نقلوا الـ build بتاعهم من Vite library mode لـ tsdown.
  • البديل: Vite library mode + vite-plugin-dts، مريح لو انت شغال Vite أصلًا.
tsdown.config.ts
import { defineConfig } from "tsdown";export default defineConfig({  entry: ["src/index.ts"],  format: ["esm", "cjs"],  dts: true,  unbundle: true, // كل component في ملف لوحده → tree-shaking أحسن  // react مايتعملوش bundle — راجع الـ docs لاسم الـ option في نسختك});

٤. الـ Docs والتطوير

Storybook: story لكل component، و @storybook/addon-a11y للـ accessibility، و interaction tests.

٥. الاختبارات

Vitest + Testing Library للـ unit، ولو عايز visual regression ضيف Chromatic أو Playwright screenshots.

٦. التنظيم والنشر

  • monorepo بـ pnpm workspaces + Turborepo لو هتعمل أكتر من package (tokens و icons و react). شوف مذكرة الـ Monorepo.
  • Changesets للـ versioning والـ changelog.
  • GitHub Actions للـ CI والـ publish.

الخلاصة في سطر

خلّصت الفصل لما

🧠 اختبر نفسك
١) امتى أختار shadcn registry بدل npm package؟
لما الـ lib لنفسك أو لتيم صغير، وكل مشروع محتاج يعدّل في كود الـ components براحته، ومش محتاج versioning مركزي.
٢) ليه tailwind-merge مهم في الـ lib؟
عشان لما المستخدم يبعت className فيه class بيتعارض مع الـ default (زي px-6 و px-4)، الـ class بتاعه هو اللي يكسب.
٣) إيه اللي يحصل لو عملت bundle لـ react جوه الـ lib؟
المشروع هيبقى فيه نسختين React، والـ hooks والـ context هيبوظوا. عشان كده react يبقى peerDependency.