feat(29-02): update ItemCard and GlobalItemCard to use GearImage
Replace object-cover with GearImage component for fit-within rendering. Add dominantColor and crop props to both card components. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
import { useFormatters } from "../hooks/useFormatters";
|
import { useFormatters } from "../hooks/useFormatters";
|
||||||
|
import { GearImage, imageContainerBg } from "./GearImage";
|
||||||
|
|
||||||
interface GlobalItemCardProps {
|
interface GlobalItemCardProps {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -9,6 +10,10 @@ interface GlobalItemCardProps {
|
|||||||
weightGrams: number | null;
|
weightGrams: number | null;
|
||||||
priceCents: number | null;
|
priceCents: number | null;
|
||||||
imageUrl: string | null;
|
imageUrl: string | null;
|
||||||
|
dominantColor?: string | null;
|
||||||
|
cropZoom?: number | null;
|
||||||
|
cropX?: number | null;
|
||||||
|
cropY?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GlobalItemCard({
|
export function GlobalItemCard({
|
||||||
@@ -19,6 +24,10 @@ export function GlobalItemCard({
|
|||||||
weightGrams,
|
weightGrams,
|
||||||
priceCents,
|
priceCents,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
|
dominantColor,
|
||||||
|
cropZoom,
|
||||||
|
cropX,
|
||||||
|
cropY,
|
||||||
}: GlobalItemCardProps) {
|
}: GlobalItemCardProps) {
|
||||||
const { weight, price } = useFormatters();
|
const { weight, price } = useFormatters();
|
||||||
|
|
||||||
@@ -28,15 +37,25 @@ export function GlobalItemCard({
|
|||||||
params={{ globalItemId: String(id) }}
|
params={{ globalItemId: String(id) }}
|
||||||
className="block bg-white rounded-xl border border-gray-100 hover:border-gray-200 hover:shadow-sm transition-all overflow-hidden group"
|
className="block bg-white rounded-xl border border-gray-100 hover:border-gray-200 hover:shadow-sm transition-all overflow-hidden group"
|
||||||
>
|
>
|
||||||
<div className="aspect-[4/3] bg-gray-50">
|
<div
|
||||||
|
className="aspect-[4/3] overflow-hidden"
|
||||||
|
style={{
|
||||||
|
backgroundColor: imageUrl
|
||||||
|
? imageContainerBg(dominantColor)
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{imageUrl ? (
|
{imageUrl ? (
|
||||||
<img
|
<GearImage
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
alt={`${brand} ${model}`}
|
alt={`${brand} ${model}`}
|
||||||
className="w-full h-full object-cover"
|
dominantColor={dominantColor}
|
||||||
|
cropZoom={cropZoom}
|
||||||
|
cropX={cropX}
|
||||||
|
cropY={cropY}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
<div className="w-full h-full bg-gray-50 flex flex-col items-center justify-center">
|
||||||
<svg
|
<svg
|
||||||
className="w-9 h-9 text-gray-300"
|
className="w-9 h-9 text-gray-300"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useDuplicateItem } from "../hooks/useItems";
|
|||||||
import { LucideIcon } from "../lib/iconData";
|
import { LucideIcon } from "../lib/iconData";
|
||||||
import { useUIStore } from "../stores/uiStore";
|
import { useUIStore } from "../stores/uiStore";
|
||||||
import { ClassificationBadge } from "./ClassificationBadge";
|
import { ClassificationBadge } from "./ClassificationBadge";
|
||||||
|
import { GearImage, imageContainerBg } from "./GearImage";
|
||||||
|
|
||||||
interface ItemCardProps {
|
interface ItemCardProps {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -17,6 +18,10 @@ interface ItemCardProps {
|
|||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
productUrl?: string | null;
|
productUrl?: string | null;
|
||||||
brand?: string | null;
|
brand?: string | null;
|
||||||
|
dominantColor?: string | null;
|
||||||
|
cropZoom?: number | null;
|
||||||
|
cropX?: number | null;
|
||||||
|
cropY?: number | null;
|
||||||
onRemove?: () => void;
|
onRemove?: () => void;
|
||||||
classification?: string;
|
classification?: string;
|
||||||
onClassificationCycle?: () => void;
|
onClassificationCycle?: () => void;
|
||||||
@@ -34,6 +39,10 @@ export function ItemCard({
|
|||||||
imageUrl,
|
imageUrl,
|
||||||
productUrl,
|
productUrl,
|
||||||
brand,
|
brand,
|
||||||
|
dominantColor,
|
||||||
|
cropZoom,
|
||||||
|
cropX,
|
||||||
|
cropY,
|
||||||
onRemove,
|
onRemove,
|
||||||
classification,
|
classification,
|
||||||
onClassificationCycle,
|
onClassificationCycle,
|
||||||
@@ -161,15 +170,25 @@ export function ItemCard({
|
|||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div className="aspect-[4/3] bg-gray-50">
|
<div
|
||||||
|
className="aspect-[4/3] overflow-hidden"
|
||||||
|
style={{
|
||||||
|
backgroundColor: imageUrl
|
||||||
|
? imageContainerBg(dominantColor)
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{imageUrl ? (
|
{imageUrl ? (
|
||||||
<img
|
<GearImage
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
alt={name}
|
alt={name}
|
||||||
className="w-full h-full object-cover"
|
dominantColor={dominantColor}
|
||||||
|
cropZoom={cropZoom}
|
||||||
|
cropX={cropX}
|
||||||
|
cropY={cropY}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
<div className="w-full h-full bg-gray-50 flex flex-col items-center justify-center">
|
||||||
<LucideIcon
|
<LucideIcon
|
||||||
name={categoryIcon}
|
name={categoryIcon}
|
||||||
size={36}
|
size={36}
|
||||||
|
|||||||
Reference in New Issue
Block a user