| Current Path : /var/www/html/reddsis/docs/ |
| Current File : /var/www/html/reddsis/docs/ICON-SYSTEM.md |
# Icon System - Font Awesome Integration
## Overview
Sistem ikon menggunakan **Font Awesome Free 7.x** yang dipasang melalui npm dan dibundel bersama Vite. Ikon disimpan dalam DB melalui kolom `menu_class` dan dirender secara dinamik di sidebar.
---
## 1. Install & Setup
### NPM Package
```bash
npm install @fortawesome/fontawesome-free@^7
```
### Import CSS
**WAJIB import dalam `resources/js/app.js`**, BUKAN dalam `app.css`.
Fail: `resources/js/app.js`
```js
import '@fortawesome/fontawesome-free/css/all.min.css';
```
**Kenapa tak boleh guna `app.css`?**
Tailwind v4 (`@tailwindcss/vite`) proses semua CSS dalam pipeline dan **tree-shake** class yang tak statik. Oleh kerana class FA dalam blade adalah dinamik (`{{ $item['icon'] }}`), Tailwind tak detect dan buang semua icon-specific rules. Akibatnya:
```css
/* Before (app.css - rosak) */
.fa-solid,.fa-regular,.fa-brands,.fa-classic,.fa):before{content:var(--fa)} /* :before → ):before */
/* After (app.js - betul) */
:is(.fas,.far,.fab,.fa-solid,.fa-regular,.fa-brands,.fa-classic,.fa):before{content:var(--fa)/""}
```
Dengan import dalam JS, Vite handle FA CSS sebagai external asset — Tailwind tak sentuh langsung.
CSS dibundel oleh Vite dan di-load secara global melalui:
```blade
@vite(['resources/js/app.js'])
```
---
## 2. Database
### Kolom
| Table | Column | Type | Contoh Value |
|---|---|---|---|
| `backend_menu` | `menu_class` | `string(50), nullable` | `fa-solid fa-users` |
| `frontend_menu` | `menu_class` | `string(50), nullable` | `fa-solid fa-home` |
| `ref` | `icon_name` | `string(255), nullable` | `fa-star` |
### Set Icon via Tinker
```php
# Activity Log
DB::table('backend_menu')->where('menu_id', 6)->update(['menu_class' => 'fa-solid fa-clock-rotate-left']);
# Roles
DB::table('backend_menu')->where('menu_id', 9)->update(['menu_class' => 'fa-solid fa-users-gear']);
# Permissions
DB::table('backend_menu')->where('menu_id', 10)->update(['menu_class' => 'fa-solid fa-shield-halved']);
# Auto Permissions
DB::table('backend_menu')->where('menu_id', 11)->update(['menu_class' => 'fa-solid fa-robot']);
# Roles & Permissions
DB::table('backend_menu')->where('menu_id', 8)->update(['menu_class' => 'fa-solid fa-user-lock']);
# Frontend (tukar dari menu-icon-frontend ke FA)
DB::table('backend_menu')->where('menu_id', 15)->update(['menu_class' => 'fa-solid fa-globe']);
\App\Http\Helpers\MenuHelper::bustCache();
```
---
## 3. Rendering Logic
Fail: `resources/views/backend/layouts/partials/sidebar-dynamic-items.blade.php`
### Level 0 (Parent Menu)
```
menu_class → Hasil
──────────────────────────────────────────────────
menu-icon-backend → SVG hardcoded (grid icon)
menu-icon-frontend → SVG hardcoded (globe icon)
menu-icon-content → SVG hardcoded (folder icon)
fa-solid fa-* → <i> FontAwesome (warna text-brand-500 aktif / gray-500 inaktif)
null/kosong → Tiada icon
```
### Level > 0 (Child Menu)
```
menu_class → Hasil
──────────────────────────────────────────────────
menu-icon-* → SVG hardcoded dalam <span class="h-4 w-4">
fa-solid fa-* → <i> FontAwesome dalam <span class="h-4 w-4">
null/kosong (level 1) → fa-solid fa-circle (solid, 6px)
null/kosong (level >1) → fa-regular fa-circle (regular, 8px)
```
Semua child menu guna container tetap `h-4 w-4` + `mr-2` (16px + 8px) untuk konsistensi alignment.
### CSS untuk Child Menu FA Icon
Fail: `resources/views/backend/layouts/app.blade.php`
```css
.sidebar-fa-icon:not(svg) {
font-size: 12px;
color: inherit;
}
```
---
## 4. Code Explanation
### 4.1 Variable `$isFaIcon`
Fail: `sidebar-dynamic-items.blade.php:8-10`
```php
$isFaIcon = !empty($item['icon']) && !in_array($item['icon'], [
'menu-icon-backend', 'menu-icon-frontend', 'menu-icon-content'
]);
```
**Tujuan:** Membezakan 3 jenis icon dalam satu pembolehubah:
- `menu-icon-backend/frontend/content` → SVG hardcoded (`$isFaIcon = false`)
- `fa-solid fa-*` / `fa-regular fa-*` → FontAwesome (`$isFaIcon = true`)
- `null` / kosong → guna bullet default
---
### 4.2 Level 0 — Parent Menu (line 31-32)
**Before (masalah):**
```blade
@elseif (!empty($item['icon']))
<i class="{{ $item['icon'] }} {{ $active ? 'menu-item-icon-active' : 'menu-item-icon-inactive' }}"></i>
@else
<svg>fallback...</svg>
@endif
```
**Kenapa gagal:** `menu-item-icon-active` / `menu-item-icon-inactive` guna CSS `fill-*`. `fill` hanya kerja untuk SVG, **tidak** untuk `<i>`. FontAwesome guna `color`, bukan `fill`.
**After (fix):**
```blade
@elseif ($isFaIcon)
<i class="{{ $item['icon'] }} {{ $active ? 'text-brand-500 dark:text-brand-400' : 'text-gray-500 group-hover:text-gray-700 dark:text-gray-400 dark:group-hover:text-gray-300' }}"></i>
@endif
```
**Perubahan:**
| Item | Before | After |
|---|---|---|
| Condition | `!empty($item['icon'])` | `$isFaIcon` (specific) |
| Color class | `menu-item-icon-active/inactive` (fill) | `text-brand-500` / `text-gray-500` (color) |
| Fallback SVG | Ada (default icon) | Dibuang (tiada icon kalau null) |
---
### 4.3 Level > 0 — Child Menu
**Before (masalah):**
```blade
@if (!empty($item['icon']))
<span class="mr-2 inline-flex h-5 w-5 items-center justify-center">
@if (...menu-icon-backend...) <svg>...</svg>
@else <i class="{{ $item['icon'] }} text-xs"></i>
@endif
</span>
@endif
<span>{{ $item['label'] }}</span>
```
**Masalah:**
1. Items tanpa icon takde span — text start posisi berbeza
2. `<i>` guna `fill-*` class yang tak support FA
3. Takde default bullet/icon untuk item kosong
**After (iterasi 1 — negative margin):**
```blade
@if ($isFaIcon)
<i class="{{ $item['icon'] }} sidebar-fa-icon" style="margin-left: -26px"></i>
@elseif (!empty($item['icon']))
{{-- menu-icon-backend/frontend/content SVG --}}
@endif
<span>{{ $item['label'] }}</span>
```
**Masalah:** `margin-left: -26px` ter-clip oleh `overflow-hidden` pada nested submenu (level > 1).
**After (final — fixed container):**
```blade
<span class="mr-2 inline-flex h-4 w-4 items-center justify-center">
@if ($isFaIcon)
<i class="{{ $item['icon'] }} sidebar-fa-icon"></i>
@elseif (!empty($item['icon']))
{{-- menu-icon-backend/frontend/content SVG --}}
@else
@if ($level === 1)
<i class="fa-solid fa-circle sidebar-fa-icon" style="font-size: 6px;"></i>
@else
<i class="fa-regular fa-circle sidebar-fa-icon" style="font-size: 8px;"></i>
@endif
@endif
</span>
<span>{{ $item['label'] }}</span>
```
**Kelebihan:**
1. Semua item guna container tetap `h-4 w-4` + `mr-2` (24px) → alignment konsisten
2. Takde negative margin → tak kena clip oleh `overflow-hidden`
3. Items tanpa icon → FA circle (solid level 1, regular level > 1)
---
### 4.4 CSS `.sidebar-fa-icon` (app.blade.php)
```css
.sidebar-fa-icon:not(svg) {
font-size: 12px;
color: inherit;
}
```
Warna diwarisi dari parent `<a>`:
- `.menu-dropdown-item-active` → `color: var(--color-brand-500)` (#465FFF)
- `.menu-dropdown-item-inactive` → `color: var(--color-gray-700)` (#344054)
---
## 5. Complete Flow
```
Database (backend_menu.menu_class)
↓
MenuHelper::getMenu()
→ query backend_menu + role_mapping
→ cache ikut role user
→ return array['icon' => $menu->menu_class]
↓
sidebar.blade.php
→ $dynamicMenus = MenuHelper::getMenu()
→ @include('sidebar-dynamic-items', ['items' => $dynamicMenus, 'level' => 0])
↓
sidebar-dynamic-items.blade.php
→ $isFaIcon = !empty($icon) && !in_array($icon, ['menu-icon-*'])
↓
Level 0 (Parent)
├─ menu-icon-backend → <svg>hardcoded backend SVG</svg>
├─ menu-icon-frontend → <svg>hardcoded frontend SVG</svg>
├─ menu-icon-content → <svg>hardcoded content SVG</svg>
├─ $isFaIcon (true) → <i class="fa-solid fa-xxx
│ text-brand-500/text-gray-500">
└─ null → (nothing)
↓
Level > 0 (Child)
├─ $isFaIcon (true) → <span class="h-4 w-4"><i class="fa-solid fa-xxx sidebar-fa-icon"></i></span>
├─ menu-icon-* → <span class="h-4 w-4"><svg>hardcoded</svg></span>
├─ null + level 1 → <span class="h-4 w-4"><i class="fa-solid fa-circle" style="font-size:6px"></i></span>
└─ null + level > 1 → <span class="h-4 w-4"><i class="fa-regular fa-circle" style="font-size:8px"></i></span>
```
---
## 6. Cara Guna (Backend Menu)
### Tambah Icon Baru
1. Admin panel → Backend Menu → Edit menu
2. Isi field **Menu Class** dengan class FontAwesome, contoh: `fa-solid fa-users-gear`
3. Simpan
4. Clear cache: `\App\Http\Helpers\MenuHelper::bustCache()`
### Senarai Class Prefix
| Prefix | Contoh | Jenis |
|---|---|---|
| `fa-solid fa-*` | `fa-solid fa-user` | Free (solid) |
| `fa-regular fa-*` | `fa-regular fa-user` | Free (regular) |
| `fa-brands fa-*` | `fa-brands fa-github` | Free (brands) |
| `menu-icon-*` | `menu-icon-backend` | Built-in SVG (3 sahaja) |
### Icon Popular untuk Rujukan
```
fa-solid fa-dashboard fa-solid fa-users-gear
fa-solid fa-user fa-solid fa-shield-halved
fa-solid fa-lock fa-solid fa-robot
fa-solid fa-gear fa-solid fa-clock-rotate-left
fa-solid fa-globe fa-solid fa-file-lines
fa-solid fa-folder-open fa-solid fa-image
fa-solid fa-link fa-solid fa-newspaper
fa-solid fa-bars fa-solid fa-eye
fa-regular fa-star fa-solid fa-route
```
---
## 7. CSS Color Scheme
### Level 0
| State | Color | CSS Class |
|---|---|---|
| Aktif | `brand-500` (#465FFF) | `text-brand-500 dark:text-brand-400` |
| Inaktif | `gray-500` (#667085) | `text-gray-500 group-hover:text-gray-700` |
### Level > 0 (Child)
Warna diwarisi (inherit) dari parent `<a>`:
| State | Color |
|---|---|
| Aktif | `brand-500` (#465FFF) |
| Inaktif | `gray-700` (#344054) |
---
## 8. Files Changed
| File | Perubahan |
|---|---|
| `resources/js/app.js` | Tambah `import '@fortawesome/fontawesome-free/css/all.min.css'` |
| `resources/css/app.css` | Buang `@import "@fortawesome/..."` — pindah ke app.js |
| `resources/views/backend/layouts/app.blade.php` | Tambah CSS `.sidebar-fa-icon`, buang CDN FA |
| `resources/views/backend/layouts/partials/sidebar-dynamic-items.blade.php` | Ubah render FA icon ganti bullet, guna fixed container `h-4 w-4`, tambah default FA circle |
| `public/tailadmin/src/css/style.css` | Buang rules `.menu-dropdown` (list-style, margin-left) |
| `public/tailadmin/build/style.css` | Buang rules `.menu-dropdown` (list-style, margin-left) |
---
## 9. Troubleshooting
**Icon tak muncul**
1. Check `menu_class` ada value kat DB
2. Pastikan cache dibust: `MenuHelper::bustCache()`
3. Hard refresh browser (Ctrl+Shift+R)
4. Check role mapping — menu kena assign dulu baru nampak
**Icon keluar tapi warna salah**
- Level 0: guna `text-brand-500` / `text-gray-500` (bukan `fill-*`)
- Level > 0: guna `color: inherit` — pastikan parent `<a>` ada class `menu-dropdown-item-active/inactive`
**Icon terpotong / clipping**
- Jangan guna `margin-left` negatif / `position: absolute` — akan ter-clip oleh `overflow-hidden` pada nested submenu
- Guna **fixed container** `h-4 w-4` + `mr-2`: semua item ada ruang icon yang sama, tanpa positioning negatif
**Icon tak muncul selepas npm install / build**
- Mungkin Tailwind v4 tree-shake FA CSS. Pastikan FA diimport dalam **`app.js`**, bukan `app.css`:
```js
// ✅ BETUL - app.js
import '@fortawesome/fontawesome-free/css/all.min.css';
```
```css
/* ❌ SALAH - app.css (Tailwind v4 akan rosakkan) */
@import "@fortawesome/fontawesome-free/css/all.min.css";
```
- Rebuild: `npm run build`