# Livewire Coding Style Guide — DR_PADHI Project

Reference this document when building new Livewire pages or components in this project.

---

## 1. Livewire Version & Setup

- **Livewire v3** with Volt-style **Single File Components (SFC)**.
- Config: `make_command.type = 'sfc'`, no emoji prefix.
- Component class namespace: `App\Livewire` (for standalone components).
- SFC page files live in: `resources/views/pages/` (or `resources/views/livewire/`).
- Blade views: `resources/views/livewire/`.

---

## 2. Single File Component (SFC) Structure

Pages use an anonymous class embedded at the top of the `.blade.php` file:

```php
<?php

use Livewire\Component;
use App\Models\Post;

new class extends Component
{
    // public properties

    public function render()
    {
        return $this->view()->layout('layouts::frontend');
        // or pass data:
        // return $this->view(['key' => $value])->layout('layouts::auth');
    }

    // lifecycle hooks, actions, with()
};
?>

<div> {{-- template here --}} </div>
```

Standalone components (e.g. `NewsletterForm`) are class files in `app/Livewire/` with a
separate view in `resources/views/livewire/`.

---

## 3. Layouts

| Context       | Layout string          |
|---------------|------------------------|
| Public pages  | `layouts::frontend`    |
| Admin pages   | `layouts::auth`        |
| Generic       | `layouts::app`         |

Always pass the layout inside `render()` using `->layout('...')`.

---

## 4. Public Properties — Typing Convention

- Use **typed properties** where possible: `public string $email = ''`.
- Use untyped for mixed/nullable: `public $image_file;`.
- Boolean flags: `public bool $subscribed = false;`.
- String message fields: `public string $errorMsg = '';`.

```php
public string $email       = '';
public bool   $subscribed  = false;
public string $errorMsg    = '';
```

Align assignment operators with spaces when declaring multiple related properties:

```php
$this->email      = '';
$this->subscribed = true;
```

---

## 5. Validation

Use inline `$this->validate()` at the top of action methods:

```php
public function subscribe(): void
{
    $this->validate(['email' => 'required|email|max:255']);
    // ... business logic
}
```

For forms with many fields (admin), use a `save()` method with a full rules array. Always
type-hint `void` on actions that don't return.

---

## 6. Wire Directives — Cheat Sheet

| Directive                        | When to use                                      |
|----------------------------------|--------------------------------------------------|
| `wire:model`                     | Standard two-way binding                         |
| `wire:model.live`                | Live binding (e.g. auto-slug from title)         |
| `wire:submit.prevent="method"`   | Form submission                                  |
| `wire:click="method"`            | Button actions                                   |
| `wire:loading.attr="disabled"`   | Disable button during request                    |
| `wire:loading` / `wire:loading.remove` | Show/hide loading text                   |
| `wire:target="method"`           | Scope loading indicator to a specific method     |
| `wire:navigate`                  | SPA-style link navigation (`<a>` tags)           |
| `wire:ignore`                    | Exclude 3rd-party JS elements from DOM morphing  |

SPA redirect after action:
```php
return $this->redirect(route('admin.blog'), navigate: true);
```

---

## 7. File Uploads

Use the `WithFileUploads` trait. Always provide both URL and file upload options:

```php
use Livewire\WithFileUploads;

public $image_file;
public $image_url = '';

// In save():
$featured_image = $this->image_url;
if ($this->image_file) {
    $featured_image = $this->image_file->store('posts', 'public');
}
```

Show preview with `$image_file->temporaryUrl()`.

---

## 8. Flash Messages & Error Display

Flash messages (after redirect):
```php
session()->flash('message', 'Post created successfully.');
```

In templates:
```blade
@if (session()->has('message'))
    <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
        {{ session('message') }}
    </div>
@endif
```

Inline validation errors:
```blade
@error('field') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
@if($errorMsg) <p class="text-amber-400 text-[10px] mt-1">{{ $errorMsg }}</p> @endif
```

---

## 9. CSS / Tailwind Approach

- **Frontend layout**: Tailwind loaded via CDN (`<script src="https://cdn.tailwindcss.com">`).
- **Admin layout**: Tailwind loaded via Vite (`@vite`).
- Custom brand tokens defined in inline `tailwind.config`:

```js
colors: {
    brand: {
        red:   '#E11D48',
        blue:  '#1e40af',
        dark:  '#0f172a',
        light: '#f8fafc',
    }
}
```

- Use `bg-brand-blue`, `text-brand-red`, `border-brand-dark` etc. throughout.
- Font families: `font-odia` (Anek Odia — Odia script), `font-sans` (Inter/Roboto).
- Reusable component classes defined in `<style>` in the layout:
  - `.news-card-v2` — article card base
  - `.input-field` — admin form inputs
  - `.card` — admin content card

---

## 10. Typography Scale (Frontend)

| Element               | Classes                                               |
|-----------------------|-------------------------------------------------------|
| Section heading       | `text-2xl font-black tracking-tight uppercase font-odia` |
| Article title (hero)  | `text-2xl md:text-4xl font-black text-white font-odia` |
| Card heading          | `text-lg font-bold text-slate-900 font-odia`         |
| Small label / tag     | `text-[10px] font-black uppercase tracking-wider`    |
| Body / summary        | `text-xs text-slate-400 leading-relaxed font-odia`   |
| Meta / timestamp      | `text-[9px] font-bold text-slate-400 uppercase tracking-wider` |

Use `font-odia` for all Odia-language text.

---

## 11. Layout Structure (Frontend)

```
Top bar (bg-brand-dark)
  → Date + social links (left) | About/Contact (right)
Header (bg-white)
  → Weather widget | Logo (center) | Search + mobile btn
Main Nav (sticky, bg-white, border-y)
  → Home icon | Category links | Live Updates badge
Breaking News Ticker (bg-slate-50)
  → Brand-red "Breaking" pill + scrolling ticker-wrapper
{{ $slot }}
Footer (bg-brand-dark)
  → 4-column grid: Brand info | Links | Categories | Newsletter
```

The newsletter in the footer uses `@livewire('newsletter-form')`.

---

## 12. Admin Panel

- Separate `layouts::auth` layout with its own sidebar component.
- Custom CSS classes: `.card`, `.input-field`, `.max-content`.
- Labels: `text-[11px] font-bold text-gray-500 uppercase mb-2`.
- Form cards: `<div class="card p-8 space-y-6">`.
- Rich-text editor: **Quill.js** (CDN), initialized inside `wire:ignore`, re-initialized on
  `livewire:navigated` event:

```js
(function() {
    function initQuill() {
        const editorEl = document.getElementById('editor');
        if (!editorEl || editorEl.classList.contains('ql-container')) return;
        const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: [...] } });
        quill.on('text-change', function() {
            @this.set('content', quill.root.innerHTML);
        });
    }
    initQuill();
    document.addEventListener('livewire:navigated', initQuill);
})();
```

---

## 13. Models

- Always declare `$fillable` array.
- Use `booted()` for model event hooks (delete/update file cleanup etc.).
- Private static helpers for reusable model logic:

```php
protected static function booted(): void
{
    static::deleted(fn (Post $post) => self::deleteStorageFile($post->featured_image));
    static::updating(function (Post $post) {
        if ($post->isDirty('featured_image')) {
            self::deleteStorageFile($post->getOriginal('featured_image'));
        }
    });
}

private static function deleteStorageFile(?string $path): void
{
    if ($path && ! str_starts_with($path, 'http')) {
        Storage::disk('public')->delete($path);
    }
}
```

---

## 14. Icons

Font Awesome 6 — three styles used:

```html
<i class="fa-solid fa-paper-plane"></i>
<i class="fa-regular fa-clock"></i>
<i class="fa-brands fa-facebook-f"></i>
```

Common icon patterns: `text-[8px]` / `text-xs` inside buttons/badges.

---

## 15. JavaScript Patterns

- Vanilla JS only (no Alpine beyond what Livewire injects).
- Wrap all JS in **IIFEs** `(function() { ... })()` to avoid global scope pollution.
- Mobile nav toggle pattern: `classList.remove('hidden')` / `classList.add('hidden')`.
- Always listen for `livewire:navigated` when initialising 3rd-party JS libraries.

---

## 16. Livewire Config Notes

| Setting                    | Value       | Notes                                |
|----------------------------|-------------|--------------------------------------|
| `inject_assets`            | `true`      | Auto-injects JS/CSS                  |
| `navigate.show_progress_bar` | `true`    | SPA progress bar enabled             |
| `navigate.progress_bar_color` | `#2299dd` | Blue progress bar                   |
| `pagination_theme`         | `tailwind`  | Use Tailwind pagination views        |
| `legacy_model_binding`     | `false`     | No direct Eloquent `wire:model`      |
| `smart_wire_keys`          | `true`      | Auto keys for nested components      |
| `payload.max_size`         | `1MB`       | Request size limit                   |
| `inject_morph_markers`     | `true`      | Better DOM morphing with `@if/@foreach` |

---

## 17. `with()` Helper for View Data

Use `with()` method for read-only data passed to the view (avoids putting queries in `render()`):

```php
public function with()
{
    return [
        'categories' => Category::orderBy('name')->get(),
    ];
}
```

Use `$this->view(['key' => $value])` in `render()` for computed/dynamic data.

---

## 18. Quick Reference — Creating a New SFC Page

1. Create file: `resources/views/pages/my-page.blade.php`
2. Add PHP block with anonymous class + `render()` returning correct layout
3. Register route in `routes/web.php`
4. Place HTML template below the `?>` closing tag, wrapped in `<div>` or `<main>`
5. Use `wire:navigate` on all internal `<a>` links to keep SPA feel
