Polish category edit, product image placeholder, and menu blur.

Add a clear pencil edit on category rows, show a proper no-image placeholder on product cards, and strengthen overflow menu glass blur.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-08 14:46:30 +03:30
co-authored by Cursor
parent 12ab1f8006
commit 5eaf7fb046
6 changed files with 64 additions and 24 deletions
+7 -7
View File
@@ -1,4 +1,4 @@
import { FolderPlus, Layers, ListTree, Trash2, ChevronRight, ClipboardList } from 'lucide-react'
import { FolderPlus, Layers, Trash2, ChevronRight, ClipboardList, Pencil } from 'lucide-react'
import { useLocale } from '@meshkee/dashboard-ui'
import type { Category } from '../types/category'
import { useT } from '../i18n/useT'
@@ -16,7 +16,7 @@ interface CategoryRowProps {
onAddSub: (id: string) => void
onVariations: (id: string) => void
onTechnicalForm: (id: string) => void
onOptions: (id: string) => void
onEdit: (id: string) => void
onRemove: (id: string) => void
}
@@ -31,7 +31,7 @@ export function CategoryRow({
onAddSub,
onVariations,
onTechnicalForm,
onOptions,
onEdit,
onRemove,
}: CategoryRowProps) {
const t = useT()
@@ -121,13 +121,13 @@ export function CategoryRow({
)}
</button>
</Tooltip>
<Tooltip label={t('categories.tooltip.options')}>
<Tooltip label={t('categories.tooltip.edit')}>
<button
className={styles.controlBtn}
onClick={() => onOptions(category.id)}
aria-label={t('categories.tooltip.options')}
onClick={() => onEdit(category.id)}
aria-label={t('categories.tooltip.edit')}
>
<ListTree size={17} />
<Pencil size={17} />
</button>
</Tooltip>
<Tooltip label={t('categories.tooltip.remove')}>
@@ -12,7 +12,7 @@ interface CategoryTreeProps {
onAddSub: (id: string) => void
onVariations: (id: string) => void
onTechnicalForm: (id: string) => void
onOptions: (id: string) => void
onEdit: (id: string) => void
onRemove: (id: string) => void
parentId?: string | null
depth?: number
@@ -27,7 +27,7 @@ export function CategoryTree({
onAddSub,
onVariations,
onTechnicalForm,
onOptions,
onEdit,
onRemove,
parentId = null,
depth = 0,
@@ -53,7 +53,7 @@ export function CategoryTree({
onAddSub={onAddSub}
onVariations={onVariations}
onTechnicalForm={onTechnicalForm}
onOptions={onOptions}
onEdit={onEdit}
onRemove={onRemove}
/>
{childCount && (
@@ -68,7 +68,7 @@ export function CategoryTree({
onAddSub={onAddSub}
onVariations={onVariations}
onTechnicalForm={onTechnicalForm}
onOptions={onOptions}
onEdit={onEdit}
onRemove={onRemove}
parentId={category.id}
depth={depth + 1}
@@ -46,6 +46,20 @@
padding: 10px;
}
.imagePlaceholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: rgba(148, 163, 184, 0.75);
background: linear-gradient(
135deg,
rgba(148, 163, 184, 0.14) 0%,
rgba(148, 163, 184, 0.05) 100%
);
}
.badge {
position: absolute;
top: 8px;
+30 -7
View File
@@ -1,5 +1,14 @@
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { Pencil, Info, Trash2, Layers, MessageSquare, ClipboardList } from 'lucide-react'
import {
Pencil,
Info,
Trash2,
Layers,
MessageSquare,
ClipboardList,
ImageOff,
} from 'lucide-react'
import { useLocale } from '@meshkee/dashboard-ui'
import type { Product } from '../types/product'
import { useT } from '../i18n/useT'
@@ -39,17 +48,31 @@ export function ProductCard({
: ''
: product.nameFa
const categoryLabel = isFa ? product.categoryFa || product.category : product.category
const imageSrc = product.image?.trim() || ''
const [imageFailed, setImageFailed] = useState(false)
const showImage = Boolean(imageSrc) && !imageFailed
useEffect(() => {
setImageFailed(false)
}, [imageSrc])
return (
<article className={styles.card}>
<Link to={`/products/detail/${product.id}`} className={styles.clickable}>
<div className={styles.imageWrap}>
<img
src={product.image}
alt={primaryName}
className={styles.image}
loading="lazy"
/>
{showImage ? (
<img
src={imageSrc}
alt={primaryName}
className={styles.image}
loading="lazy"
onError={() => setImageFailed(true)}
/>
) : (
<div className={styles.imagePlaceholder} aria-hidden="true">
<ImageOff size={28} strokeWidth={1.5} />
</div>
)}
{product.status === 'draft' && (
<span className={styles.badge}>{t('products.card.draft')}</span>
)}
+1 -1
View File
@@ -453,7 +453,7 @@ export function CategoriesPage() {
onAddSub={(id) => openCreateModal(id, t('categories.addSub'))}
onVariations={openVariations}
onTechnicalForm={openTechnicalForm}
onOptions={(id) => {
onEdit={(id) => {
const category = categories.find((item) => item.id === id)
if (category) openEditModal(category)
}}
@@ -31,11 +31,14 @@
min-width: 220px;
padding: 6px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.85);
background: rgba(255, 255, 255, 0.78);
backdrop-filter: blur(72px) saturate(1.35);
-webkit-backdrop-filter: blur(72px) saturate(1.35);
box-shadow: 0 12px 40px rgba(15, 23, 42, 0.16);
border: 1px solid rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.58);
backdrop-filter: blur(160px) saturate(1.85);
-webkit-backdrop-filter: blur(160px) saturate(1.85);
box-shadow:
0 12px 40px rgba(15, 23, 42, 0.16),
inset 0 1px 0 rgba(255, 255, 255, 0.65);
isolation: isolate;
animation: menuIn 0.14s ease;
}