# UI Components Library

**Version:** 2.0
**Last Updated:** December 2024
**Live Reference:** `/admin/components`

---

## Overview

The admin panel uses a consistent set of UI components built with Tailwind CSS and Alpine.js. All components support dark mode and are fully accessible.

**Live Component Library:** Visit `/admin/components` in the admin panel to see all components with copy-paste ready code.

!!! tip "Use Jinja Macros Instead"
    For new development, prefer using the **[Jinja Macros Library](jinja-macros.md)** instead of copy-pasting HTML. Macros provide:

    - **Consistency:** Same styling across all pages
    - **Maintainability:** Update once, apply everywhere
    - **Less code:** Parameters instead of full HTML
    - **Type safety:** Built-in Alpine.js bindings

    ```jinja
    {# Instead of 20 lines of HTML... #}
    {% from 'shared/macros/buttons.html' import btn_primary %}
    {{ btn_primary('Save Changes', icon='check', onclick='save()') }}
    ```

    See [Jinja Macros Library](jinja-macros.md) for full documentation.

---

## Page Layout Structure

All admin list pages follow a consistent structure:

```
┌─────────────────────────────────────────────────────┐
│  Page Header (Title + Action Button)                │
├─────────────────────────────────────────────────────┤
│  Stats Cards (4 columns on desktop)                 │
├─────────────────────────────────────────────────────┤
│  Search & Filters Bar                               │
├─────────────────────────────────────────────────────┤
│  Data Table                                         │
│  ├── Table Header                                   │
│  ├── Table Rows                                     │
│  └── Pagination                                     │
└─────────────────────────────────────────────────────┘
```

---

## Form Components

### Basic Input

```html
<label class="block mb-4 text-sm">
    <span class="text-gray-700 dark:text-gray-400">Field Label</span>
    <input
        type="text"
        x-model="formData.field"
        class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
    />
</label>
```

### Required Field with Validation

```html
<label class="block mb-4 text-sm">
    <span class="text-gray-700 dark:text-gray-400">
        Email Address <span class="text-red-600">*</span>
    </span>
    <input
        type="email"
        x-model="formData.email"
        required
        :class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.email }"
        class="block w-full mt-1 text-sm dark:text-gray-300 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
    />
    <span x-show="errors.email" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.email"></span>
</label>
```

### Disabled/Read-Only Input

```html
<input
    type="text"
    x-model="data.field"
    disabled
    class="block w-full mt-1 text-sm bg-gray-100 border-gray-300 rounded-md dark:bg-gray-700 dark:text-gray-400 dark:border-gray-600 cursor-not-allowed"
/>
```

### Textarea

```html
<textarea
    x-model="formData.description"
    rows="3"
    class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-textarea"
></textarea>
```

### Select

```html
<select
    x-model="formData.option"
    class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-select"
>
    <option value="">Select an option</option>
    <option value="option1">Option 1</option>
    <option value="option2">Option 2</option>
</select>
```

---

## Button Components

### Primary Button

```html
<button class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
    Primary Button
</button>
```

### Button with Icon

```html
<button class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none">
    <span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
    Add Item
</button>
```

### Secondary Button

```html
<button class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg hover:border-gray-400 focus:outline-none dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
    Cancel
</button>
```

### Danger Button

```html
<button class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700">
    Delete
</button>
```

---

## Card Components

### Stats Card

```html
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
    <div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
        <span x-html="$icon('user-group', 'w-5 h-5')"></span>
    </div>
    <div>
        <p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
            Total Users
        </p>
        <p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.total">
            0
        </p>
    </div>
</div>
```

### Stats Card Colors

| Color | Use Case | Classes |
|-------|----------|---------|
| Blue | Total counts | `text-blue-500 bg-blue-100` |
| Green | Active/Success | `text-green-500 bg-green-100` |
| Red | Inactive/Errors | `text-red-500 bg-red-100` |
| Orange | Warnings/Admin | `text-orange-500 bg-orange-100` |
| Purple | Primary metrics | `text-purple-500 bg-purple-100` |

### Info Card

```html
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
    <h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
        Card Title
    </h3>
    <div class="space-y-3">
        <div>
            <p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Field Label</p>
            <p class="text-sm text-gray-700 dark:text-gray-300">Field Value</p>
        </div>
    </div>
</div>
```

---

## Status Badges

### Success Badge

```html
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-green-700 bg-green-100 rounded-full dark:bg-green-700 dark:text-green-100">
    <span x-html="$icon('check-circle', 'w-3 h-3 mr-1')"></span>
    Active
</span>
```

### Warning Badge

```html
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-orange-700 bg-orange-100 rounded-full dark:bg-orange-700 dark:text-orange-100">
    <span x-html="$icon('clock', 'w-3 h-3 mr-1')"></span>
    Pending
</span>
```

### Danger Badge

```html
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-red-700 bg-red-100 rounded-full dark:bg-red-700 dark:text-red-100">
    <span x-html="$icon('x-circle', 'w-3 h-3 mr-1')"></span>
    Inactive
</span>
```

### Dynamic Badge

```html
<span class="px-2 py-1 font-semibold leading-tight rounded-full text-xs"
      :class="item.is_active
          ? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100'
          : 'text-red-700 bg-red-100 dark:bg-red-700 dark:text-red-100'"
      x-text="item.is_active ? 'Active' : 'Inactive'">
</span>
```

---

## Data Tables

### Basic Table Structure

```html
<div class="w-full overflow-hidden rounded-lg shadow-xs">
    <div class="w-full overflow-x-auto">
        <table class="w-full whitespace-no-wrap">
            <thead>
                <tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
                    <th class="px-4 py-3">Name</th>
                    <th class="px-4 py-3">Status</th>
                    <th class="px-4 py-3">Actions</th>
                </tr>
            </thead>
            <tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
                <template x-for="item in items" :key="item.id">
                    <tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700">
                        <td class="px-4 py-3" x-text="item.name"></td>
                        <td class="px-4 py-3"><!-- Status badge --></td>
                        <td class="px-4 py-3"><!-- Action buttons --></td>
                    </tr>
                </template>
            </tbody>
        </table>
    </div>
</div>
```

### Action Buttons

```html
<div class="flex items-center space-x-2 text-sm">
    <!-- View -->
    <a :href="'/admin/resource/' + item.id"
       class="flex items-center justify-center p-2 text-blue-600 rounded-lg hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-gray-700"
       title="View">
        <span x-html="$icon('eye', 'w-5 h-5')"></span>
    </a>

    <!-- Edit -->
    <a :href="'/admin/resource/' + item.id + '/edit'"
       class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700"
       title="Edit">
        <span x-html="$icon('edit', 'w-5 h-5')"></span>
    </a>

    <!-- Delete -->
    <button @click="deleteItem(item)"
            class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700"
            title="Delete">
        <span x-html="$icon('delete', 'w-5 h-5')"></span>
    </button>
</div>
```

---

## Loading & Error States

### Loading State

```html
<div x-show="loading" class="text-center py-12">
    <span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
    <p class="mt-2 text-gray-600 dark:text-gray-400">Loading...</p>
</div>
```

### Error Alert

```html
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
    <span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
    <div>
        <p class="font-semibold">Error loading data</p>
        <p class="text-sm" x-text="error"></p>
    </div>
</div>
```

### Empty State

```html
<div class="flex flex-col items-center py-12">
    <span x-html="$icon('inbox', 'w-12 h-12 text-gray-400 mb-4')"></span>
    <p class="text-lg font-medium text-gray-600 dark:text-gray-400">No items found</p>
    <p class="text-sm text-gray-500" x-text="filters.search ? 'Try adjusting your search' : 'Create your first item'"></p>
</div>
```

---

## Modal Components

### Confirmation Modal

```html
<div
    x-show="showModal"
    x-cloak
    class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
    @click.self="showModal = false">

    <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-6 m-4 max-w-md w-full">
        <div class="flex items-start justify-between mb-4">
            <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200">
                Confirm Action
            </h3>
            <button @click="showModal = false" class="text-gray-400 hover:text-gray-600">
                <span x-html="$icon('x', 'w-5 h-5')"></span>
            </button>
        </div>

        <p class="mb-6 text-sm text-gray-600 dark:text-gray-400">
            Are you sure you want to perform this action?
        </p>

        <div class="flex justify-end gap-3">
            <button @click="showModal = false"
                    class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600">
                Cancel
            </button>
            <button @click="confirmAction()"
                    class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
                Confirm
            </button>
        </div>
    </div>
</div>
```

---

## Toast Notifications

Use the global Utils helper:

```javascript
Utils.showToast('Operation successful!', 'success');
Utils.showToast('Something went wrong', 'error');
Utils.showToast('Please check your input', 'warning');
Utils.showToast('Here is some information', 'info');
```

---

## Grid Layouts

### 2 Columns (Desktop)

```html
<div class="grid gap-6 md:grid-cols-2">
    <div>Column 1</div>
    <div>Column 2</div>
</div>
```

### 4 Columns (Stats Cards)

```html
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
    <!-- Stats cards here -->
</div>
```

---

## Color Reference

| Type | Primary | Success | Warning | Danger | Info |
|------|---------|---------|---------|--------|------|
| Background | `bg-purple-600` | `bg-green-600` | `bg-orange-600` | `bg-red-600` | `bg-blue-600` |
| Text | `text-purple-600` | `text-green-600` | `text-orange-600` | `text-red-600` | `text-blue-600` |
| Light BG | `bg-purple-100` | `bg-green-100` | `bg-orange-100` | `bg-red-100` | `bg-blue-100` |

---

## Common Icons

| Icon | Use Case |
|------|----------|
| `user-group` | Users/Teams |
| `badge-check` | Verified |
| `check-circle` | Success |
| `x-circle` | Error/Inactive |
| `clock` | Pending |
| `calendar` | Dates |
| `edit` | Edit |
| `delete` | Delete |
| `plus` | Add |
| `arrow-left` | Back |
| `exclamation` | Warning |
| `spinner` | Loading |

---

## JavaScript Patterns

### List Page Structure

```javascript
function adminResourceList() {
    return {
        ...data(),  // Inherit base layout
        currentPage: 'resource-name',

        // State
        items: [],
        loading: false,
        error: null,
        filters: { search: '', status: '' },
        stats: {},
        pagination: { page: 1, per_page: 10, total: 0 },

        async init() {
            await this.loadItems();
            await this.loadStats();
        },

        async loadItems() { /* ... */ },
        debouncedSearch() { /* ... */ },
        async deleteItem(item) { /* ... */ }
    };
}
```

---

## Related Documentation

- **[Jinja Macros Library](jinja-macros.md)** - Reusable macro components (recommended)
- [Pagination Guide](pagination.md)
- [CDN Fallback Strategy](../cdn-fallback-strategy.md)
- [Tailwind CSS](../tailwind-css.md)
- [Icons Guide](../../development/icons-guide.md)
- [Tailwind CSS Official Docs](https://tailwindcss.com/docs)
- [Alpine.js Official Docs](https://alpinejs.dev/)
