feat(29-02): update CandidateCard and CandidateListItem to use GearImage
Replace object-cover with GearImage for fit-within rendering on candidate cards and list items. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import type { CandidateDelta } from "../hooks/useImpactDeltas";
|
|||||||
import { LucideIcon } from "../lib/iconData";
|
import { LucideIcon } from "../lib/iconData";
|
||||||
import { useUIStore } from "../stores/uiStore";
|
import { useUIStore } from "../stores/uiStore";
|
||||||
import { RankBadge } from "./CandidateListItem";
|
import { RankBadge } from "./CandidateListItem";
|
||||||
|
import { GearImage, imageContainerBg } from "./GearImage";
|
||||||
import { ImpactDeltaBadge } from "./ImpactDeltaBadge";
|
import { ImpactDeltaBadge } from "./ImpactDeltaBadge";
|
||||||
import { StatusBadge } from "./StatusBadge";
|
import { StatusBadge } from "./StatusBadge";
|
||||||
|
|
||||||
@@ -17,6 +18,10 @@ interface CandidateCardProps {
|
|||||||
imageFilename: string | null;
|
imageFilename: string | null;
|
||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
productUrl?: string | null;
|
productUrl?: string | null;
|
||||||
|
dominantColor?: string | null;
|
||||||
|
cropZoom?: number | null;
|
||||||
|
cropX?: number | null;
|
||||||
|
cropY?: number | null;
|
||||||
threadId: number;
|
threadId: number;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
status: "researching" | "ordered" | "arrived";
|
status: "researching" | "ordered" | "arrived";
|
||||||
@@ -37,6 +42,10 @@ export function CandidateCard({
|
|||||||
imageFilename: _imageFilename,
|
imageFilename: _imageFilename,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
productUrl,
|
productUrl,
|
||||||
|
dominantColor,
|
||||||
|
cropZoom,
|
||||||
|
cropX,
|
||||||
|
cropY,
|
||||||
threadId,
|
threadId,
|
||||||
isActive,
|
isActive,
|
||||||
status,
|
status,
|
||||||
@@ -149,15 +158,25 @@ export function CandidateCard({
|
|||||||
</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}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useFormatters } from "../hooks/useFormatters";
|
|||||||
import type { CandidateDelta } from "../hooks/useImpactDeltas";
|
import type { CandidateDelta } from "../hooks/useImpactDeltas";
|
||||||
import { LucideIcon } from "../lib/iconData";
|
import { LucideIcon } from "../lib/iconData";
|
||||||
import { useUIStore } from "../stores/uiStore";
|
import { useUIStore } from "../stores/uiStore";
|
||||||
|
import { GearImage, imageContainerBg } from "./GearImage";
|
||||||
import { ImpactDeltaBadge } from "./ImpactDeltaBadge";
|
import { ImpactDeltaBadge } from "./ImpactDeltaBadge";
|
||||||
import { StatusBadge } from "./StatusBadge";
|
import { StatusBadge } from "./StatusBadge";
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ interface CandidateWithCategory {
|
|||||||
productUrl: string | null;
|
productUrl: string | null;
|
||||||
imageFilename: string | null;
|
imageFilename: string | null;
|
||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
|
dominantColor?: string | null;
|
||||||
status: "researching" | "ordered" | "arrived";
|
status: "researching" | "ordered" | "arrived";
|
||||||
pros: string | null;
|
pros: string | null;
|
||||||
cons: string | null;
|
cons: string | null;
|
||||||
@@ -84,12 +86,18 @@ export function CandidateListItem({
|
|||||||
<RankBadge rank={rank} />
|
<RankBadge rank={rank} />
|
||||||
|
|
||||||
{/* Image thumbnail */}
|
{/* Image thumbnail */}
|
||||||
<div className="w-12 h-12 rounded-lg overflow-hidden shrink-0 bg-gray-50 flex items-center justify-center">
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg overflow-hidden shrink-0 flex items-center justify-center"
|
||||||
|
style={{
|
||||||
|
backgroundColor: candidate.imageUrl
|
||||||
|
? imageContainerBg(candidate.dominantColor)
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{candidate.imageUrl ? (
|
{candidate.imageUrl ? (
|
||||||
<img
|
<GearImage
|
||||||
src={candidate.imageUrl}
|
src={candidate.imageUrl}
|
||||||
alt={candidate.name}
|
alt={candidate.name}
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<LucideIcon
|
<LucideIcon
|
||||||
|
|||||||
Reference in New Issue
Block a user