150 lines
3.5 KiB
TypeScript
150 lines
3.5 KiB
TypeScript
import { withSubComponents } from "@components/helpers/withSubComponents.ts";
|
|
import Image from "@components/Image.tsx";
|
|
import { IconExternalLink } from "@components/icons.tsx";
|
|
import { type ComponentChildren, createContext } from "preact";
|
|
import { IconArrowNarrowLeft } from "@components/icons.tsx";
|
|
import { IconEdit } from "@components/icons.tsx";
|
|
import { useContext } from "preact/hooks";
|
|
|
|
const HeroContext = createContext<{ image?: string; thumbnail?: string }>({
|
|
image: undefined,
|
|
thumbnail: undefined,
|
|
});
|
|
|
|
function Wrapper(
|
|
{ children, image, thumbnail }: {
|
|
children: ComponentChildren;
|
|
image?: string;
|
|
thumbnail?: string;
|
|
},
|
|
) {
|
|
return (
|
|
<div
|
|
class={`flex justify-between flex-col relative w-full ${
|
|
image ? "min-h-[400px]" : "min-h-[200px]"
|
|
} rounded-3xl overflow-hidden`}
|
|
>
|
|
<HeroContext.Provider value={{ image }}>
|
|
{image &&
|
|
(
|
|
<Image
|
|
fill
|
|
src={image}
|
|
thumbnail={thumbnail}
|
|
alt="Recipe Banner"
|
|
// style={{ objectPosition: "0% 25%" }}
|
|
class="absolute object-cover w-full h-full -z-10"
|
|
/>
|
|
)}
|
|
{children}
|
|
</HeroContext.Provider>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Title(
|
|
{ children, link }: { children: ComponentChildren; link?: string },
|
|
) {
|
|
const ctx = useContext(HeroContext);
|
|
|
|
const OuterTag = link ? "a" : "div";
|
|
|
|
return (
|
|
<OuterTag
|
|
href={link}
|
|
class={`${
|
|
ctx.image ? "noisy-gradient" : ""
|
|
} after:opacity-90 flex gap-4 items-center ${ctx.image ? "pt-12" : ""}`}
|
|
>
|
|
<h2
|
|
class="flex gap-2 items-center text-4xl font-bold z-10"
|
|
style={{ color: ctx.image ? "#1F1F1F" : "white" }}
|
|
>
|
|
{children}
|
|
{link &&
|
|
<IconExternalLink />}
|
|
</h2>
|
|
</OuterTag>
|
|
);
|
|
}
|
|
|
|
function BackLink({ href }: { href: string }) {
|
|
return (
|
|
<a
|
|
class="px-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex gap-1 items-center"
|
|
href={href}
|
|
>
|
|
<IconArrowNarrowLeft class="w-5 h-5" /> Back
|
|
</a>
|
|
);
|
|
}
|
|
|
|
function EditLink({ href }: { href: string }) {
|
|
const ctx = useContext(HeroContext);
|
|
return (
|
|
<a
|
|
class={`px-4 py-2 ${
|
|
ctx.image ? "bg-gray-300 text-gray-800" : "text-gray-200"
|
|
} rounded-lg flex gap-1 items-center`}
|
|
href={href}
|
|
>
|
|
<IconEdit class="w-5 h-5" />
|
|
</a>
|
|
);
|
|
}
|
|
|
|
function Header({ children }: { children: ComponentChildren }) {
|
|
return (
|
|
<div class="flex justify-between mx-8 mt-8">
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Subline(
|
|
{ entries, children }: {
|
|
children?: ComponentChildren;
|
|
entries: (string | { href: string; title: string })[];
|
|
},
|
|
) {
|
|
const ctx = useContext(HeroContext);
|
|
return (
|
|
<div
|
|
class={`relative flex items-center z-10 flex gap-5 font-sm text-light mt-3`}
|
|
style={{ color: ctx.image ? "#1F1F1F" : "white" }}
|
|
>
|
|
{children}
|
|
{entries.filter((s) =>
|
|
s && (typeof s === "string" ? s?.length > 1 : true)
|
|
).map((s) => {
|
|
if (typeof s === "string") {
|
|
return <span>{s}</span>;
|
|
} else {
|
|
return <a href={s.href}>{s.title}</a>;
|
|
}
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Footer({ children }: { children: ComponentChildren }) {
|
|
const ctx = useContext(HeroContext);
|
|
|
|
return (
|
|
<div
|
|
class={`relative inset-x-0 py-4 px-8 ${ctx.image ? "py-8" : ""} `}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default withSubComponents(Wrapper, {
|
|
EditLink,
|
|
BackLink,
|
|
Footer,
|
|
Subline,
|
|
Header,
|
|
Title,
|
|
});
|