All files / src/screens/components RecipeHeader.tsx

75% Statements 3/4
84.61% Branches 11/13
50% Functions 1/2
75% Lines 3/4

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119                                                              10x   10x       10x                                                                                                                                                                  
import { type ReactNode, useState, useCallback } from 'react'
import { Star, ExternalLink } from 'lucide-react'
import { type RecipeDetail, type ScaleResponse } from '../../api/client'
import MetaSection from './MetaSection'
import LinkedRecipesNav from './LinkedRecipesNav'
 
interface RecipeHeaderProps {
  recipe: RecipeDetail
  imageUrl: string | null | undefined
  metaExpanded: boolean
  setMetaExpanded: (expanded: boolean) => void
  canShowServingAdjustment: boolean
  servings: number | null
  scaledData: ScaleResponse | null
  scalingLoading: boolean
  onServingChange: (delta: number) => void
  children?: ReactNode
}
 
export default function RecipeHeader({
  recipe,
  imageUrl,
  metaExpanded,
  setMetaExpanded,
  canShowServingAdjustment,
  servings,
  scaledData,
  scalingLoading,
  onServingChange,
  children,
}: RecipeHeaderProps) {
  const [imgError, setImgError] = useState(false)
 
  const handleImgError = useCallback(() => {
    setImgError(true)
  }, [])
 
  return (
    <>
      {/* Hero Image */}
      <div className="relative h-64 sm:h-80">
        {imageUrl && !imgError ? (
          <img
            src={imageUrl}
            alt={recipe.title}
            loading="lazy"
            onError={handleImgError}
            className="h-full w-full object-cover"
          />
        ) : (
          <div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-muted to-muted/60">
            <span className="px-4 text-center text-lg font-medium text-muted-foreground">
              {recipe.title}
            </span>
          </div>
        )}
 
        {/* Gradient overlay */}
        <div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent" />
 
        {/* Title, meta, and actions overlay */}
        <div className="absolute inset-x-0 bottom-0 flex flex-col gap-2 px-4 pb-4">
          <div>
            <h1 className="text-xl font-medium text-white drop-shadow-lg sm:text-2xl">
              {recipe.title}
            </h1>
            <div className="mt-1 flex items-center gap-3 text-sm text-white/90">
              {recipe.rating && (
                <span className="flex items-center gap-1">
                  <Star className="h-4 w-4 fill-star text-star" />
                  {recipe.rating.toFixed(1)}
                  {recipe.rating_count && (
                    <span className="text-white/70">
                      ({recipe.rating_count})
                    </span>
                  )}
                </span>
              )}
              {recipe.canonical_url ? (
              <a
                href={recipe.canonical_url}
                target="_blank"
                rel="noopener noreferrer"
                className="inline-flex items-center gap-1 underline decoration-white/40 underline-offset-2"
              >
                {recipe.host}
                <ExternalLink className="h-3 w-3" />
              </a>
            ) : (
              <span>{recipe.host}</span>
            )}
            </div>
          </div>
 
          {/* Action buttons row */}
          {children}
        </div>
      </div>
 
      {/* Meta info (collapsible) */}
      <MetaSection
        recipe={recipe}
        metaExpanded={metaExpanded}
        setMetaExpanded={setMetaExpanded}
        canShowServingAdjustment={canShowServingAdjustment}
        servings={servings}
        scaledData={scaledData}
        scalingLoading={scalingLoading}
        onServingChange={onServingChange}
      />
 
      {/* Linked Recipes Navigation */}
      {recipe.linked_recipes && recipe.linked_recipes.length > 0 && (
        <LinkedRecipesNav linkedRecipes={recipe.linked_recipes} />
      )}
    </>
  )
}
 
← Back to Dashboard