Files
crepes-demo/lib/components.tsx
OpenClaw 9b750238c2
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
Initial commit: json-render crepes demo
2026-02-09 07:30:54 +01:00

173 lines
5.3 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from "react";
export interface RecipeHeaderProps {
title: string;
chef: string;
origin: string;
prepTime: string;
servings: number;
}
export function RecipeHeader({ title, chef, origin, prepTime, servings }: RecipeHeaderProps) {
return (
<div className="bg-gradient-to-r from-amber-50 to-orange-50 p-8 rounded-2xl shadow-lg mb-8 border-2 border-amber-200">
<h2 className="text-4xl font-bold text-gray-900 mb-3">{title}</h2>
<div className="flex flex-wrap gap-4 text-lg">
<span className="bg-white px-4 py-2 rounded-full shadow-sm">
👨🍳 <strong>Chef:</strong> {chef}
</span>
<span className="bg-white px-4 py-2 rounded-full shadow-sm">
🇫🇷 <strong>Origin:</strong> {origin}
</span>
<span className="bg-white px-4 py-2 rounded-full shadow-sm">
<strong>Prep:</strong> {prepTime}
</span>
<span className="bg-white px-4 py-2 rounded-full shadow-sm">
🍽 <strong>Serves:</strong> {servings}
</span>
</div>
</div>
);
}
export interface ChefInfoProps {
name: string;
bio: string;
sourceUrl: string;
imageUrl?: string;
}
export function ChefInfo({ name, bio, sourceUrl, imageUrl }: ChefInfoProps) {
return (
<div className="bg-blue-50 p-6 rounded-xl shadow-md mb-8 border-l-4 border-blue-500">
<div className="flex items-start gap-4">
{imageUrl && (
<img
src={imageUrl}
alt={name}
className="w-24 h-24 rounded-full object-cover shadow-lg"
/>
)}
<div className="flex-1">
<h3 className="text-2xl font-bold text-gray-900 mb-2">{name}</h3>
<p className="text-gray-700 mb-3">{bio}</p>
<a
href={sourceUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-800 font-semibold underline"
>
📖 View Original Recipe
</a>
</div>
</div>
</div>
);
}
export interface IngredientListProps {
title: string;
ingredients: Array<{ quantity: string; name: string }>;
}
export function IngredientList({ title, ingredients }: IngredientListProps) {
return (
<div className="bg-green-50 p-6 rounded-xl shadow-md mb-6 border-l-4 border-green-500">
<h4 className="text-2xl font-bold text-gray-900 mb-4 flex items-center gap-2">
🥚 {title}
</h4>
<ul className="space-y-2">
{ingredients.map((ingredient, index) => (
<li key={index} className="flex items-baseline gap-3 text-lg">
<span className="text-green-600 font-bold"></span>
<span className="font-semibold text-gray-900 min-w-[120px]">
{ingredient.quantity}
</span>
<span className="text-gray-700">{ingredient.name}</span>
</li>
))}
</ul>
</div>
);
}
export interface InstructionStepsProps {
steps: Array<{ number: number; instruction: string }>;
}
export function InstructionSteps({ steps }: InstructionStepsProps) {
return (
<div className="bg-purple-50 p-6 rounded-xl shadow-md mb-6 border-l-4 border-purple-500">
<h4 className="text-2xl font-bold text-gray-900 mb-4 flex items-center gap-2">
📝 Instructions
</h4>
<ol className="space-y-4">
{steps.map((step) => (
<li key={step.number} className="flex gap-4">
<span className="flex-shrink-0 w-8 h-8 bg-purple-500 text-white rounded-full flex items-center justify-center font-bold">
{step.number}
</span>
<p className="text-gray-700 text-lg pt-1">{step.instruction}</p>
</li>
))}
</ol>
</div>
);
}
export interface TipCardProps {
tip: string;
author: string;
}
export function TipCard({ tip, author }: TipCardProps) {
return (
<div className="bg-yellow-50 p-6 rounded-xl shadow-md mb-6 border-l-4 border-yellow-500">
<div className="flex items-start gap-3">
<span className="text-3xl">💡</span>
<div>
<p className="text-gray-800 text-lg mb-2 italic">&ldquo;{tip}&rdquo;</p>
<p className="text-gray-600 font-semibold"> {author}</p>
</div>
</div>
</div>
);
}
export interface Recipe {
title: string;
description: string;
imageUrl?: string;
chef: ChefInfoProps;
header: RecipeHeaderProps;
ingredientLists: IngredientListProps[];
steps: InstructionStepsProps;
tip: TipCardProps;
}
export function RecipeCard({ recipe }: { recipe: Recipe }) {
return (
<div className="bg-white p-8 rounded-2xl shadow-xl mb-8 border border-gray-200">
{recipe.imageUrl && (
<img
src={recipe.imageUrl}
alt={recipe.title}
className="w-full h-64 object-cover rounded-xl mb-6 shadow-md"
/>
)}
<h2 className="text-3xl font-bold text-gray-900 mb-3">{recipe.title}</h2>
<p className="text-gray-600 text-lg mb-6">{recipe.description}</p>
<ChefInfo {...recipe.chef} />
<RecipeHeader {...recipe.header} />
{recipe.ingredientLists.map((list, index) => (
<IngredientList key={index} {...list} />
))}
<InstructionSteps {...recipe.steps} />
<TipCard {...recipe.tip} />
</div>
);
}