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 { useLocale } from '@meshkee/dashboard-ui'
import type { Category } from '../types/category' import type { Category } from '../types/category'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
@@ -16,7 +16,7 @@ interface CategoryRowProps {
onAddSub: (id: string) => void onAddSub: (id: string) => void
onVariations: (id: string) => void onVariations: (id: string) => void
onTechnicalForm: (id: string) => void onTechnicalForm: (id: string) => void
onOptions: (id: string) => void onEdit: (id: string) => void
onRemove: (id: string) => void onRemove: (id: string) => void
} }
@@ -31,7 +31,7 @@ export function CategoryRow({
onAddSub, onAddSub,
onVariations, onVariations,
onTechnicalForm, onTechnicalForm,
onOptions, onEdit,
onRemove, onRemove,
}: CategoryRowProps) { }: CategoryRowProps) {
const t = useT() const t = useT()
@@ -121,13 +121,13 @@ export function CategoryRow({
)} )}
</button> </button>
</Tooltip> </Tooltip>
<Tooltip label={t('categories.tooltip.options')}> <Tooltip label={t('categories.tooltip.edit')}>
<button <button
className={styles.controlBtn} className={styles.controlBtn}
onClick={() => onOptions(category.id)} onClick={() => onEdit(category.id)}
aria-label={t('categories.tooltip.options')} aria-label={t('categories.tooltip.edit')}
> >
<ListTree size={17} /> <Pencil size={17} />
</button> </button>
</Tooltip> </Tooltip>
<Tooltip label={t('categories.tooltip.remove')}> <Tooltip label={t('categories.tooltip.remove')}>
@@ -12,7 +12,7 @@ interface CategoryTreeProps {
onAddSub: (id: string) => void onAddSub: (id: string) => void
onVariations: (id: string) => void onVariations: (id: string) => void
onTechnicalForm: (id: string) => void onTechnicalForm: (id: string) => void
onOptions: (id: string) => void onEdit: (id: string) => void
onRemove: (id: string) => void onRemove: (id: string) => void
parentId?: string | null parentId?: string | null
depth?: number depth?: number
@@ -27,7 +27,7 @@ export function CategoryTree({
onAddSub, onAddSub,
onVariations, onVariations,
onTechnicalForm, onTechnicalForm,
onOptions, onEdit,
onRemove, onRemove,
parentId = null, parentId = null,
depth = 0, depth = 0,
@@ -53,7 +53,7 @@ export function CategoryTree({
onAddSub={onAddSub} onAddSub={onAddSub}
onVariations={onVariations} onVariations={onVariations}
onTechnicalForm={onTechnicalForm} onTechnicalForm={onTechnicalForm}
onOptions={onOptions} onEdit={onEdit}
onRemove={onRemove} onRemove={onRemove}
/> />
{childCount && ( {childCount && (
@@ -68,7 +68,7 @@ export function CategoryTree({
onAddSub={onAddSub} onAddSub={onAddSub}
onVariations={onVariations} onVariations={onVariations}
onTechnicalForm={onTechnicalForm} onTechnicalForm={onTechnicalForm}
onOptions={onOptions} onEdit={onEdit}
onRemove={onRemove} onRemove={onRemove}
parentId={category.id} parentId={category.id}
depth={depth + 1} depth={depth + 1}
@@ -46,6 +46,20 @@
padding: 10px; 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 { .badge {
position: absolute; position: absolute;
top: 8px; top: 8px;
+30 -7
View File
@@ -1,5 +1,14 @@
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom' 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 { useLocale } from '@meshkee/dashboard-ui'
import type { Product } from '../types/product' import type { Product } from '../types/product'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
@@ -39,17 +48,31 @@ export function ProductCard({
: '' : ''
: product.nameFa : product.nameFa
const categoryLabel = isFa ? product.categoryFa || product.category : product.category 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 ( return (
<article className={styles.card}> <article className={styles.card}>
<Link to={`/products/detail/${product.id}`} className={styles.clickable}> <Link to={`/products/detail/${product.id}`} className={styles.clickable}>
<div className={styles.imageWrap}> <div className={styles.imageWrap}>
<img {showImage ? (
src={product.image} <img
alt={primaryName} src={imageSrc}
className={styles.image} alt={primaryName}
loading="lazy" 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' && ( {product.status === 'draft' && (
<span className={styles.badge}>{t('products.card.draft')}</span> <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'))} onAddSub={(id) => openCreateModal(id, t('categories.addSub'))}
onVariations={openVariations} onVariations={openVariations}
onTechnicalForm={openTechnicalForm} onTechnicalForm={openTechnicalForm}
onOptions={(id) => { onEdit={(id) => {
const category = categories.find((item) => item.id === id) const category = categories.find((item) => item.id === id)
if (category) openEditModal(category) if (category) openEditModal(category)
}} }}
@@ -31,11 +31,14 @@
min-width: 220px; min-width: 220px;
padding: 6px; padding: 6px;
border-radius: 10px; border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.85); border: 1px solid rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.78); background: rgba(255, 255, 255, 0.58);
backdrop-filter: blur(72px) saturate(1.35); backdrop-filter: blur(160px) saturate(1.85);
-webkit-backdrop-filter: blur(72px) saturate(1.35); -webkit-backdrop-filter: blur(160px) saturate(1.85);
box-shadow: 0 12px 40px rgba(15, 23, 42, 0.16); 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; animation: menuIn 0.14s ease;
} }