هيكل التحميل (Skeleton)

يحجز هيكل التحميل (Skeleton) مكان المحتوى بشكله أثناء التحميل، فلا تتحرك عناصر الصفحة عند وصول المحتوى.

التثبيت

bash
npx @smicolon/fasla-ui@latest add skeleton

قبل إضافة أي مكوّن، شغّل الأمر init مرة واحدة في مشروعك. التفاصيل في صفحة التثبيت.

هيكل عنصر القائمة

يضع مكوّن SkeletonListItem صورة رمزية في بداية السطر، وبجانبها أسطر النص على مسافة 16 بكسل. استخدمه للقوائم والتعليقات وصفوف الملفات الشخصية.

هيكل البطاقة

مكوّن SkeletonCard كتلة وسائط بارتفاع 122 بكسل فوق سطرين من النص. ليس له إطار ولا خلفية، فضعه داخل مكوّن بطاقة (Card) إذا كان للمحتوى الحقيقي إطار.

هيكل النص

يرسم مكوّن SkeletonText أسطرًا بعرض كامل، ارتفاع كل منها 16 بكسل وبينها 8 بكسل. يعرض سطرين ما لم تحدد عددها بالخاصية lines.

هيكل الصورة الرمزية

مكوّن SkeletonAvatar دائرة قطرها 48 بكسل. مرّر حجمًا آخر في className ليطابق صورة رمزية أصغر أو أكبر.

ابنِ شكلك الخاص

عندما تحتاج تخطيطًا خارج الهياكل الجاهزة الثلاثة، ركّب شكلك الخاص من كتل مفردة.

كتلة واحدة تحدد حجمها بالخاصية className. تحلّ محلّ مكوّن Skeleton في shadcn/ui دون أي تعديل آخر.

تحدد الخاصية variant شكل الكتلة: شريط بحواف مستديرة بمقدار 4 بكسل، أو دائرة، أو زوايا قائمة.

default
circular
rectangular

إتاحة الوصول (accessibility)

  • تحمل كل كتلة السمة (attribute) aria-hidden، فيتجاوز قارئ الشاشة العناصر النائبة.
  • ضع السمة (attribute) aria-busy="true" على المنطقة التي يجري تحميلها، ثم غيّر قيمتها إلى false عند وصول المحتوى.
  • يتوقف النبض إذا فعّل المستخدم إعداد prefers-reduced-motion. ولإيقافه في كل الحالات، اجعل قيمة animate هي false.
  • يتبع هيكل التحميل اتجاه الصفحة المحدد في السمة (attribute) dir، فتظهر الصورة الرمزية دائمًا في بداية السطر.

التعليقات

الخصائص (props)

الخاصيةالنوعالافتراضيالوصف
variant"default" | "circular" | "rectangular""default"شكل الكتلة الواحدة: شريط مستدير الحواف، أو دائرة، أو مستطيل.
animatebooleantrueيحرّك الهيكل بنبض خفيف أثناء التحميل، ويتوقف النبض دائمًا عند تقليل الحركة.
linesnumber2عدد أسطر النص في المكوّنات SkeletonText، SkeletonListItem، SkeletonCard.
classNamestringلا يوجديحدد الحجم، مثل h-4 w-48.

الاستخدام

كتلة واحدة

tsx
import { Skeleton } from "@/components/ui/skeleton"

export function TitleSkeleton() {
  return <Skeleton className="h-4 w-48" />
}

أسطر نصية

tsx
import { SkeletonText } from "@/components/ui/skeleton"

export function ParagraphSkeleton() {
  return <SkeletonText lines={3} />
}

صفّ في قائمة

tsx
import { SkeletonListItem } from "@/components/ui/skeleton"

export function RowSkeleton() {
  return <SkeletonListItem />
}

بطاقة

tsx
import { SkeletonCard } from "@/components/ui/skeleton"

export function CardSkeleton() {
  return <SkeletonCard />
}

حدّد المنطقة التي يجري تحميلها

tsx
import { SkeletonListItem } from "@/components/ui/skeleton"

interface Comment {
  id: number
  text: string
}

export function Comments({ comments }: { comments?: Comment[] }) {
  const isLoading = comments === undefined

  return (
    <section aria-busy={isLoading} aria-labelledby="comments">
      <h3 id="comments">التعليقات</h3>
      {isLoading ? (
        <SkeletonListItem />
      ) : (
        <ul>
          {comments.map((comment) => (
            <li key={comment.id}>{comment.text}</li>
          ))}
        </ul>
      )}
    </section>
  )
}