Prisma
Live Preview

Spinner / Loading Component

Skill này hướng dẫn tạo component Spinner — circular loading indicator cho inline, button, section, và page loading states.

1. Mục tiêu (Objective)

Tạo component Spinner cho hiển thị loading state: inline, button, section, page. Dùng khi không biết content layout (nếu biết → dùng Skeleton).

2. AI Context & Intent (Ngữ cảnh cho AI)

Khi nào dùng Spinner?

  • Button loading: Submit button đang xử lý
  • Inline loading: "Saving..." next to text
  • Section loading: Loading a widget/card
  • Page loading: Initial page load

⚠️ Phân biệt Spinner vs Skeleton vs Progress Bar (QUAN TRỌNG)

Tiêu chí Spinner Skeleton Progress Bar
Bản chất Circular animation Content placeholder Linear bar
Best for Unknown layout Known layout Known progress %
Info Just "loading" Visual preview Exact percentage
Shape Circle Mimics content Horizontal bar
Ví dụ Button loading Page layout loading File upload 45%

Decision Tree cho AI

text
Cần show loading?
├─ Biết content layout sẽ hiện → Skeleton
├─ Biết progress % → Progress Bar
├─ Không biết layout/progress → Spinner
│   ├─ Inside button → Spinner (xs/sm, inverted nếu primary bg)
│   ├─ Inline text → Spinner (xs) + "Saving..."
│   ├─ Card/section → Spinner (md/lg) centered
│   └─ Full page → Spinner (xl) + optional backdrop
│
└─ Process steps → Stepper (read-only)

3. Anatomy

text
  ┌──┐
  │╱ │  ← Circular arc (--primary)
  │  │     Rotating animation
  └──┘     stroke-width varies by size

  Sizes:
  ·    ○    ◎    ◉    ⊙
  xs   sm   md   lg   xl
  12px 20px 24px 32px 48px

  Button loading:
  ┌──────────────────┐
  │   ◎ Submitting   │ ← Spinner replaces/accompanies text
  └──────────────────┘   variant="inverted" on primary bg

4. Platform Mapping

Platform Component Key Difference
Web Spinner (CSS animation) Circular ring, rotating
iOS UIActivityIndicatorView System spinner, 2 sizes
Android (M3) CircularProgressIndicator Indeterminate arc animation
Fluent Spinner Ring, 5 sizes, inverted variant
Carbon InlineLoading / Loading Inline + overlay variants

Cross-platform: ALL platforms có spinner/loading indicator.

5. Sizes

xs (12px), sm (20px), md (24px), lg (32px), xl (48px)

6. Variants

Variant Use case
default Primary color on light bg
inverted White on dark/primary bg
button Inside button, replaces label

7. Token Mapping

📦 Atomic Mapping: Xem ATOMIC-MAPPING.md → mục spinner — Density Tier: comp.

Color dùng --primary, sizes dùng --icon-sm/md/lg, animation spin 0.75s linear infinite. Component token JSON files đã deprecated.

8. Props & API

typescript
interface SpinnerProps {
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
  variant?: 'default' | 'inverted';
  'aria-label'?: string; // Default: "Loading"
}

9. Accessibility (a11y)

  • role="status", aria-label="Loading".
  • Visually hidden text: "Loading..." for screen readers.
  • Khi complete: remove spinner + announce "Content loaded" via live region.

10. Best Practices & Rules

  • Button loading: Replace button text, keep button size, add disabled.
  • Page loading: Center xl spinner, optional backdrop overlay.
  • Inline loading: xs/sm next to text ("Saving...").
  • Skeleton preferred: Nếu biết layout, dùng Skeleton thay vì Spinner.
  • Không Hardcode: Mọi giá trị từ Token.

11. Example Usage

jsx
{/* Button loading */}
<Button variant="primary" disabled>
  <Spinner size="xs" variant="inverted" />
  Submitting...
</Button>

{/* Section loading */}
<Card>
  <div className="flex-center" style={{ minHeight: '200px' }}>
    <Spinner size="lg" aria-label="Loading transactions" />
  </div>
</Card>

{/* Inline loading */}
<div className="inline-flex items-center gap-2">
  <Spinner size="xs" />
  <span>Saving changes...</span>
</div>

{/* Full page loading */}
<div className="page-loader">
  <Spinner size="xl" aria-label="Loading application" />
</div>