feat(29-02): update ComparisonTable, CatalogSearchOverlay, ImageUpload
Replace object-cover with GearImage across ComparisonTable, CatalogSearchOverlay (2 instances), and ImageUpload preview. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { useFormatters } from "../hooks/useFormatters";
|
|||||||
import { useGlobalItems } from "../hooks/useGlobalItems";
|
import { useGlobalItems } from "../hooks/useGlobalItems";
|
||||||
import { useTags } from "../hooks/useTags";
|
import { useTags } from "../hooks/useTags";
|
||||||
import { useUIStore } from "../stores/uiStore";
|
import { useUIStore } from "../stores/uiStore";
|
||||||
|
import { GearImage } from "./GearImage";
|
||||||
import { ManualEntryForm } from "./ManualEntryForm";
|
import { ManualEntryForm } from "./ManualEntryForm";
|
||||||
|
|
||||||
type ViewMode = "grid" | "list";
|
type ViewMode = "grid" | "list";
|
||||||
@@ -626,15 +627,21 @@ function GridCard({ item, onAdd, onCardClick, weight, price }: CardProps) {
|
|||||||
className="bg-white rounded-xl border border-gray-100 overflow-hidden cursor-pointer hover:border-gray-200 hover:shadow-sm transition-all"
|
className="bg-white rounded-xl border border-gray-100 overflow-hidden cursor-pointer hover:border-gray-200 hover:shadow-sm transition-all"
|
||||||
onClick={onCardClick}
|
onClick={onCardClick}
|
||||||
>
|
>
|
||||||
<div className="aspect-[4/3] bg-gray-50">
|
<div
|
||||||
|
className="aspect-[4/3] overflow-hidden"
|
||||||
|
style={{
|
||||||
|
backgroundColor: item.imageUrl
|
||||||
|
? ((item as Record<string, unknown>).dominantColor as string) || "#f3f4f6"
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{item.imageUrl ? (
|
{item.imageUrl ? (
|
||||||
<img
|
<GearImage
|
||||||
src={item.imageUrl}
|
src={item.imageUrl}
|
||||||
alt={`${item.brand} ${item.model}`}
|
alt={`${item.brand} ${item.model}`}
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
<div className="w-full h-full bg-gray-50 flex 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"
|
||||||
@@ -696,15 +703,21 @@ function ListRow({ item, onAdd, onCardClick, weight, price }: CardProps) {
|
|||||||
onClick={onCardClick}
|
onClick={onCardClick}
|
||||||
>
|
>
|
||||||
{/* Thumbnail */}
|
{/* Thumbnail */}
|
||||||
<div className="w-12 h-12 rounded-lg bg-gray-50 shrink-0 overflow-hidden">
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg shrink-0 overflow-hidden"
|
||||||
|
style={{
|
||||||
|
backgroundColor: item.imageUrl
|
||||||
|
? ((item as Record<string, unknown>).dominantColor as string) || "#f3f4f6"
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{item.imageUrl ? (
|
{item.imageUrl ? (
|
||||||
<img
|
<GearImage
|
||||||
src={item.imageUrl}
|
src={item.imageUrl}
|
||||||
alt={`${item.brand} ${item.model}`}
|
alt={`${item.brand} ${item.model}`}
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
<div className="w-full h-full bg-gray-50 flex items-center justify-center">
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5 text-gray-300"
|
className="w-5 h-5 text-gray-300"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|||||||
@@ -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 } from "./GearImage";
|
||||||
import { ImpactDeltaBadge } from "./ImpactDeltaBadge";
|
import { ImpactDeltaBadge } from "./ImpactDeltaBadge";
|
||||||
|
|
||||||
interface CandidateWithCategory {
|
interface CandidateWithCategory {
|
||||||
@@ -114,12 +115,18 @@ export function ComparisonTable({
|
|||||||
key: "image",
|
key: "image",
|
||||||
label: "Image",
|
label: "Image",
|
||||||
render: (c) => (
|
render: (c) => (
|
||||||
<div className="w-12 h-12 rounded-lg overflow-hidden bg-gray-50 flex items-center justify-center">
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg overflow-hidden flex items-center justify-center"
|
||||||
|
style={{
|
||||||
|
backgroundColor: c.imageUrl
|
||||||
|
? ((c as Record<string, unknown>).dominantColor as string) || "#f3f4f6"
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{c.imageUrl ? (
|
{c.imageUrl ? (
|
||||||
<img
|
<GearImage
|
||||||
src={c.imageUrl}
|
src={c.imageUrl}
|
||||||
alt={c.name}
|
alt={c.name}
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<LucideIcon
|
<LucideIcon
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { apiUpload } from "../lib/api";
|
import { apiUpload } from "../lib/api";
|
||||||
|
import { GearImage, imageContainerBg } from "./GearImage";
|
||||||
|
|
||||||
interface ImageUploadProps {
|
interface ImageUploadProps {
|
||||||
value: string | null;
|
value: string | null;
|
||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
|
dominantColor?: string | null;
|
||||||
onChange: (filename: string | null) => void;
|
onChange: (filename: string | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,6 +15,7 @@ const ACCEPTED_TYPES = ["image/jpeg", "image/png", "image/webp"];
|
|||||||
export function ImageUpload({
|
export function ImageUpload({
|
||||||
value: _value,
|
value: _value,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
|
dominantColor,
|
||||||
onChange,
|
onChange,
|
||||||
}: ImageUploadProps) {
|
}: ImageUploadProps) {
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
@@ -70,13 +73,18 @@ export function ImageUpload({
|
|||||||
<div
|
<div
|
||||||
onClick={() => inputRef.current?.click()}
|
onClick={() => inputRef.current?.click()}
|
||||||
className="relative w-full aspect-[4/3] rounded-xl overflow-hidden cursor-pointer group"
|
className="relative w-full aspect-[4/3] rounded-xl overflow-hidden cursor-pointer group"
|
||||||
|
style={{
|
||||||
|
backgroundColor: displayUrl
|
||||||
|
? imageContainerBg(dominantColor)
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{displayUrl ? (
|
{displayUrl ? (
|
||||||
<>
|
<>
|
||||||
<img
|
<GearImage
|
||||||
src={displayUrl}
|
src={displayUrl}
|
||||||
alt="Item"
|
alt="Item"
|
||||||
className="w-full h-full object-cover"
|
dominantColor={dominantColor}
|
||||||
/>
|
/>
|
||||||
{/* Remove button */}
|
{/* Remove button */}
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user