Redesign ListingCard grid

This commit is contained in:
thesn10
2026-07-03 13:03:09 +02:00
parent 3c52730393
commit a08e4943fc

View File

@@ -1,11 +1,14 @@
import type { GestureResponderEvent } from "react-native"; import type { GestureResponderEvent } from "react-native";
import type { JSX } from "react"; import type { JSX } from "react";
import { Pressable, View } from "react-native"; import { useState } from "react";
import { Pressable, StyleSheet, View } from "react-native";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import { Button, Separator, Surface, Typography } from "heroui-native"; import { Button, Separator, Surface, Typography } from "heroui-native";
import Svg, { Defs, RadialGradient, Rect, Stop } from "react-native-svg";
import type { Product } from "@/data/mock"; import type { Product } from "@/data/mock";
import { availabilityInfo } from "@/lib/status"; import { availabilityInfo } from "@/lib/status";
import { StatusChip } from "@/components/StatusChip"; import { StatusChip } from "@/components/StatusChip";
import { MonoText } from "@/components/MonoText";
import { useModals } from "@/state/modals"; import { useModals } from "@/state/modals";
interface ListingCardProps { interface ListingCardProps {
@@ -13,9 +16,24 @@ interface ListingCardProps {
variant?: "list" | "grid"; variant?: "list" | "grid";
} }
function CardHighlightOverlay({ gradientId }: { gradientId: string }): JSX.Element {
return (
<Svg style={StyleSheet.absoluteFill} width="100%" height="100%" pointerEvents="none">
<Defs>
<RadialGradient id={gradientId} cx="30%" cy="25%" r="60%">
<Stop offset="0%" stopColor="#ffffff" stopOpacity={0.07} />
<Stop offset="100%" stopColor="#ffffff" stopOpacity={0} />
</RadialGradient>
</Defs>
<Rect width="100%" height="100%" fill={`url(#${gradientId})`} />
</Svg>
);
}
export function ListingCard({ product, variant = "grid" }: ListingCardProps): JSX.Element { export function ListingCard({ product, variant = "grid" }: ListingCardProps): JSX.Element {
const router = useRouter(); const router = useRouter();
const { requestBuy } = useModals(); const { requestBuy } = useModals();
const [isHovered, setIsHovered] = useState(false);
const availability = availabilityInfo(product.qty); const availability = availabilityInfo(product.qty);
const isOut = product.qty === 0; const isOut = product.qty === 0;
@@ -28,8 +46,17 @@ export function ListingCard({ product, variant = "grid" }: ListingCardProps): JS
if (variant === "list") { if (variant === "list") {
return ( return (
<Pressable onPress={openListing}> <Pressable
<Surface variant="default" className="flex-row items-stretch gap-0 overflow-hidden p-0"> onPress={openListing}
onHoverIn={() => setIsHovered(true)}
onHoverOut={() => setIsHovered(false)}
>
<Surface
variant="default"
className={`flex-row items-stretch gap-0 overflow-hidden rounded-xl border p-0 ${
isHovered ? "border-border-secondary" : "border-border"
}`}
>
<View <View
className="w-24 items-center justify-center md:w-36" className="w-24 items-center justify-center md:w-36"
style={{ backgroundColor: product.bg }} style={{ backgroundColor: product.bg }}
@@ -72,27 +99,33 @@ export function ListingCard({ product, variant = "grid" }: ListingCardProps): JS
} }
return ( return (
<Pressable onPress={openListing} className="w-1/2 p-1.5 md:w-1/3 lg:w-1/4"> <Pressable
<Surface variant="default" className="gap-0 overflow-hidden p-0"> onPress={openListing}
<View onHoverIn={() => setIsHovered(true)}
className="h-32 items-center justify-center" onHoverOut={() => setIsHovered(false)}
style={{ backgroundColor: product.bg }} className="w-full p-1.5 md:w-1/2 lg:w-1/4"
> >
<Typography className="text-4xl">{product.emoji}</Typography> <Surface
variant="default"
className={`gap-0 overflow-hidden rounded-xl border p-0 ${isHovered ? "border-border-secondary" : "border-border"}`}
>
<View className="aspect-[4/3] items-center justify-center" style={{ backgroundColor: product.bg }}>
<CardHighlightOverlay gradientId={`card-highlight-${product.id}`} />
<Typography className="text-6xl lg:text-7xl">{product.emoji}</Typography>
</View> </View>
<View className="gap-1 p-2.5"> <View className="gap-1 p-2.5">
<Typography type="body-sm" weight="semibold" numberOfLines={2}> <View className="flex-row items-start justify-between gap-2">
{product.name} <Typography type="body-sm" weight="semibold" numberOfLines={2} className="flex-1">
</Typography> {product.name}
<Typography type="body-xs" color="muted" numberOfLines={1}>
{product.seller}
</Typography>
<View className="mt-1 flex-row items-center justify-between gap-1">
<Typography type="body" weight="bold" className="text-accent">
{product.price}
</Typography> </Typography>
<StatusChip label={availability.label} color={availability.color} /> <StatusChip label={availability.label} color={availability.color} />
</View> </View>
<MonoText type="body-xs" color="muted" numberOfLines={1}>
{product.seller}
</MonoText>
<Typography type="body" weight="bold" className="mt-1 text-accent">
{product.price}
</Typography>
<Button <Button
size="sm" size="sm"
variant={isOut ? "secondary" : "primary"} variant={isOut ? "secondary" : "primary"}