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:
@@ -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:
|
||||||
|
|||||||
@@ -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">
|
||||||
<button
|
{selectedIds.length > 0 && (
|
||||||
className="border rounded-md px-3 py-2 text-sm bg-black text-white disabled:opacity-50"
|
<button
|
||||||
onClick={handleDeleteClick}
|
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"
|
||||||
disabled={selectedIds.length === 0}
|
onClick={handleDeleteClick}
|
||||||
>
|
disabled={deleteMutation.isPending}
|
||||||
<Trash2 className="w-4 h-4 inline mr-2" />
|
>
|
||||||
{selectedIds.length > 0 ? __(`Delete (${selectedIds.length})`) : __('Delete')}
|
<Trash2 className="w-4 h-4" />
|
||||||
</button>
|
{__('Delete')} ({selectedIds.length})
|
||||||
|
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user