feat: Standardize toolbar buttons across Orders and Products

**Issue:**
- Products: Delete button was black (bg-black), always visible
- Products: Used inline mr-2 for icon spacing
- Orders: Delete button was red (bg-red-600), conditional
- Orders: Used inline-flex gap-2 for icon spacing
- Inconsistent UX between modules

**Solution:**
1. Added "Toolbar Button Standards" to PROJECT_SOP.md
2. Updated Products to match Orders standard

**Changes to PROJECT_SOP.md:**
- Added button type definitions (Delete, Refresh, Secondary)
- Specified Delete button: bg-red-600 (NOT bg-black)
- Specified icon spacing: inline-flex items-center gap-2
- Specified conditional rendering for destructive actions
- Added 8 mandatory rules for toolbar buttons

**Changes to Products/index.tsx:**
- Delete button: bg-black → bg-red-600 text-white hover:bg-red-700
- Delete button: Always visible → Conditional (only when items selected)
- Icon spacing: inline mr-2 → inline-flex items-center gap-2
- Delete disabled: selectedIds.length === 0 → deleteMutation.isPending
- Refresh icon: inline mr-2 → inline-flex items-center gap-2

**Result:**
 Consistent red delete button (destructive color)
 Delete only shows when items selected (better UX)
 Consistent icon spacing (gap-2)
 Consistent hover effects
 Both modules now identical

**Visual Improvements:**
- Red delete button clearly indicates destructive action
- Cleaner toolbar when no items selected
- Better visual hierarchy
This commit is contained in:
dwindown
2025-11-20 10:21:32 +07:00
parent e267e3c2b2
commit a36094f6df
2 changed files with 65 additions and 10 deletions

View File

@@ -260,6 +260,59 @@ When updating an existing module to follow this pattern:
</Toolbar> </Toolbar>
``` ```
#### Toolbar Button Standards
All CRUD list pages MUST use consistent button styling in the toolbar:
**Button Types:**
| Button Type | Classes | Use Case |
|-------------|---------|----------|
| **Delete (Destructive)** | `border rounded-md px-3 py-2 text-sm bg-red-600 text-white hover:bg-red-700 disabled:opacity-50 inline-flex items-center gap-2` | Bulk delete action |
| **Refresh** | `border rounded-md px-3 py-2 text-sm hover:bg-accent disabled:opacity-50 inline-flex items-center gap-2` | Refresh data |
| **Export/Secondary** | `border rounded-md px-3 py-2 text-sm hover:bg-accent disabled:opacity-50 inline-flex items-center gap-2` | Secondary actions |
**Button Structure:**
```tsx
<button
className="border rounded-md px-3 py-2 text-sm bg-red-600 text-white hover:bg-red-700 disabled:opacity-50 inline-flex items-center gap-2"
onClick={handleAction}
disabled={condition}
>
<IconComponent className="w-4 h-4" />
{__('Button Label')}
</button>
```
**Rules:**
1. ✅ **Delete button** - Always use `bg-red-600` (NOT `bg-black`)
2. ✅ **Icon placement** - Use `inline-flex items-center gap-2` (NOT `inline mr-2`)
3. ✅ **Destructive actions** - Only show when items selected (conditional render)
4. ✅ **Non-destructive actions** - Can be always visible (use `disabled` state)
5. ✅ **Consistent spacing** - Use `gap-2` between icon and text
6. ✅ **Hover states** - Destructive: `hover:bg-red-700`, Secondary: `hover:bg-accent`
7. ❌ **Never use `bg-black`** for delete buttons
8. ❌ **Never use `inline mr-2`** - use `inline-flex gap-2` instead
**Toolbar Layout:**
```tsx
<div className="flex gap-3">
{/* Bulk Actions - Show only when items selected */}
{selectedIds.length > 0 && (
<button className="...bg-red-600...">
<Trash2 className="w-4 h-4" />
{__('Delete')} ({selectedIds.length})
</button>
)}
{/* Always-visible actions */}
<button className="...hover:bg-accent...">
<RefreshCw className="w-4 h-4" />
{__('Refresh')}
</button>
</div>
```
#### Table/List UI Standards #### Table/List UI Standards
All CRUD list pages MUST follow these consistent UI patterns: All CRUD list pages MUST follow these consistent UI patterns:

View File

@@ -226,21 +226,23 @@ export default function Products() {
<div className="hidden md:block rounded-lg border border-border p-4 bg-card"> <div className="hidden md:block rounded-lg border border-border p-4 bg-card">
<div className="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-3"> <div className="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-3">
<div className="flex gap-3"> <div className="flex gap-3">
{selectedIds.length > 0 && (
<button <button
className="border rounded-md px-3 py-2 text-sm bg-black text-white disabled:opacity-50" className="border rounded-md px-3 py-2 text-sm bg-red-600 text-white hover:bg-red-700 disabled:opacity-50 inline-flex items-center gap-2"
onClick={handleDeleteClick} onClick={handleDeleteClick}
disabled={selectedIds.length === 0} disabled={deleteMutation.isPending}
> >
<Trash2 className="w-4 h-4 inline mr-2" /> <Trash2 className="w-4 h-4" />
{selectedIds.length > 0 ? __(`Delete (${selectedIds.length})`) : __('Delete')} {__('Delete')} ({selectedIds.length})
</button> </button>
)}
<button <button
className="border rounded-md px-3 py-2 text-sm hover:bg-accent disabled:opacity-50" className="border rounded-md px-3 py-2 text-sm hover:bg-accent disabled:opacity-50 inline-flex items-center gap-2"
onClick={handleRefresh} onClick={handleRefresh}
disabled={q.isLoading || isRefreshing} disabled={q.isLoading || isRefreshing}
> >
<RefreshCw className={`w-4 h-4 inline mr-2 ${isRefreshing ? 'animate-spin' : ''}`} /> <RefreshCw className={`w-4 h-4 ${isRefreshing ? 'animate-spin' : ''}`} />
{__('Refresh')} {__('Refresh')}
</button> </button>
</div> </div>