All user-facing components now support dark mode via prefers-color-scheme. - Dark backgrounds: gray-950/900/800 - Dark text: gray-100/200/300/400 - Orange brand colors adapt with darker tints - Glass effects work in both modes - Skeletons, cards, filters, bottom sheet all themed - Google Maps InfoWindow stays light (maps don't support dark) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
3.5 KiB
TypeScript
98 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import type { Restaurant } from "@/lib/api";
|
|
import { getCuisineIcon } from "@/lib/cuisine-icons";
|
|
import { RestaurantListSkeleton } from "@/components/Skeleton";
|
|
|
|
interface RestaurantListProps {
|
|
restaurants: Restaurant[];
|
|
selectedId?: string;
|
|
onSelect: (r: Restaurant) => void;
|
|
loading?: boolean;
|
|
}
|
|
|
|
export default function RestaurantList({
|
|
restaurants,
|
|
selectedId,
|
|
onSelect,
|
|
loading,
|
|
}: RestaurantListProps) {
|
|
if (loading) {
|
|
return <RestaurantListSkeleton />;
|
|
}
|
|
|
|
if (!restaurants.length) {
|
|
return (
|
|
<div className="p-4 text-center text-gray-500 dark:text-gray-400 text-sm">
|
|
표시할 식당이 없습니다
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="p-3 space-y-2">
|
|
{restaurants.map((r) => (
|
|
<button
|
|
key={r.id}
|
|
onClick={() => onSelect(r)}
|
|
className={`w-full text-left p-3 rounded-xl shadow-sm border transition-all hover:shadow-md hover:-translate-y-0.5 ${
|
|
selectedId === r.id
|
|
? "bg-orange-50 dark:bg-orange-900/20 border-orange-300 dark:border-orange-700 shadow-orange-100 dark:shadow-orange-900/10"
|
|
: "bg-white dark:bg-gray-900 border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
}`}
|
|
>
|
|
<div className="flex items-start justify-between gap-2">
|
|
<h4 className="font-semibold text-sm dark:text-gray-100">
|
|
<span className="mr-1">{getCuisineIcon(r.cuisine_type)}</span>
|
|
{r.name}
|
|
</h4>
|
|
{r.rating && (
|
|
<span className="text-xs text-yellow-600 dark:text-yellow-400 font-medium whitespace-nowrap shrink-0">
|
|
★ {r.rating}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-wrap gap-x-2 gap-y-0.5 mt-1.5 text-xs">
|
|
{r.cuisine_type && (
|
|
<span className="px-1.5 py-0.5 bg-gray-100 dark:bg-gray-800 rounded text-gray-600 dark:text-gray-400">{r.cuisine_type}</span>
|
|
)}
|
|
{r.price_range && (
|
|
<span className="px-1.5 py-0.5 bg-gray-50 dark:bg-gray-800 rounded text-gray-600 dark:text-gray-400">{r.price_range}</span>
|
|
)}
|
|
</div>
|
|
{r.region && (
|
|
<p className="mt-1 text-xs text-gray-400 dark:text-gray-500 truncate">{r.region}</p>
|
|
)}
|
|
{r.foods_mentioned && r.foods_mentioned.length > 0 && (
|
|
<div className="flex flex-wrap gap-1 mt-1.5">
|
|
{r.foods_mentioned.slice(0, 5).map((f, i) => (
|
|
<span
|
|
key={i}
|
|
className="px-1.5 py-0.5 bg-orange-50 dark:bg-orange-900/30 text-orange-700 dark:text-orange-400 rounded text-[10px]"
|
|
>
|
|
{f}
|
|
</span>
|
|
))}
|
|
{r.foods_mentioned.length > 5 && (
|
|
<span className="text-[10px] text-gray-400">+{r.foods_mentioned.length - 5}</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
{r.channels && r.channels.length > 0 && (
|
|
<div className="flex flex-wrap gap-1 mt-1">
|
|
{r.channels.map((ch) => (
|
|
<span
|
|
key={ch}
|
|
className="px-1.5 py-0.5 bg-orange-50 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 rounded-full text-[10px] font-medium"
|
|
>
|
|
{ch}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|