feat: update some shit
This commit is contained in:
154
components/PageHero.tsx
Normal file
154
components/PageHero.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
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";
|
||||
import { GenericResource } from "@lib/types.ts";
|
||||
|
||||
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 min-h-[${
|
||||
image ? 400 : 200
|
||||
}px] 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);
|
||||
return (
|
||||
<div
|
||||
class={`${
|
||||
ctx.image ? "noisy-gradient" : ""
|
||||
} after:opacity-90 flex gap-4 items-center ${ctx.image ? "pt-12" : ""}`}
|
||||
>
|
||||
<h2
|
||||
class="relative text-4xl font-bold z-10"
|
||||
style={{ color: ctx.image ? "#1F1F1F" : "white" }}
|
||||
>
|
||||
{children}
|
||||
{link &&
|
||||
(
|
||||
<a
|
||||
href={link}
|
||||
target="__blank"
|
||||
class="p-2 inline-flex"
|
||||
>
|
||||
<IconExternalLink />
|
||||
</a>
|
||||
)}
|
||||
</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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-50 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,
|
||||
});
|
@ -1,106 +0,0 @@
|
||||
import { Star } from "@components/Stars.tsx";
|
||||
import {
|
||||
IconArrowNarrowLeft,
|
||||
IconEdit,
|
||||
IconExternalLink,
|
||||
} from "@components/icons.tsx";
|
||||
import Image from "@components/Image.tsx";
|
||||
import { GenericResource } from "@lib/types.ts";
|
||||
|
||||
export function RecipeHero(
|
||||
{ data, subline, backlink, editLink }: {
|
||||
backlink: string;
|
||||
subline?: (string | { title: string; href: string })[];
|
||||
editLink?: string;
|
||||
data: GenericResource;
|
||||
},
|
||||
) {
|
||||
const { meta: { image } = {} } = data;
|
||||
|
||||
return (
|
||||
<div
|
||||
class={`flex justify-between flex-col relative w-full min-h-[${
|
||||
image ? 400 : 200
|
||||
}px] rounded-3xl overflow-hidden`}
|
||||
>
|
||||
{image &&
|
||||
(
|
||||
<Image
|
||||
fill
|
||||
src={image}
|
||||
thumbnail={data.meta?.thumbnail}
|
||||
alt="Recipe Banner"
|
||||
style={{ objectPosition: "0% 25%" }}
|
||||
class="absolute object-cover w-full h-full -z-10"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div class="flex justify-between mx-8 mt-8">
|
||||
<a
|
||||
class="px-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex gap-1 items-center"
|
||||
href={backlink}
|
||||
>
|
||||
<IconArrowNarrowLeft class="w-5 h-5" /> Back
|
||||
</a>
|
||||
{editLink &&
|
||||
(
|
||||
<a
|
||||
class={`px-4 py-2 ${
|
||||
image ? "bg-gray-300 text-gray-800" : "text-gray-200"
|
||||
} rounded-lg flex gap-1 items-center`}
|
||||
href={editLink}
|
||||
>
|
||||
<IconEdit class="w-5 h-5" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class={`relative inset-x-0 py-4 px-8 ${image ? "py-8" : ""} `}
|
||||
>
|
||||
<div
|
||||
class={`${
|
||||
image ? "noisy-gradient" : ""
|
||||
} after:opacity-90 flex gap-4 items-center ${image ? "pt-12" : ""}`}
|
||||
>
|
||||
<h2
|
||||
class="relative text-4xl font-bold z-10"
|
||||
style={{ color: image ? "#1F1F1F" : "white" }}
|
||||
>
|
||||
{data.name}
|
||||
{data.meta?.link &&
|
||||
(
|
||||
<a
|
||||
href={data.meta.link}
|
||||
target="__blank"
|
||||
class="p-2 inline-flex"
|
||||
name="Link to Original recipe"
|
||||
>
|
||||
<IconExternalLink />
|
||||
</a>
|
||||
)}
|
||||
</h2>
|
||||
|
||||
{data.meta?.rating && <Star rating={data.meta.rating} />}
|
||||
</div>
|
||||
{subline?.length &&
|
||||
(
|
||||
<div
|
||||
class={`relative z-50 flex gap-5 font-sm text-light mt-3`}
|
||||
style={{ color: image ? "#1F1F1F" : "white" }}
|
||||
>
|
||||
{subline.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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
11
components/helpers/withSubComponents.ts
Normal file
11
components/helpers/withSubComponents.ts
Normal file
@ -0,0 +1,11 @@
|
||||
// deno-lint-ignore no-explicit-any
|
||||
export function withSubComponents<A, B extends Record<string, any>>(
|
||||
component: A,
|
||||
subcomponents: B,
|
||||
): A & B {
|
||||
Object.keys(subcomponents).forEach((key) => {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
(component as any)[key] = (subcomponents as any)[key];
|
||||
});
|
||||
return component as A & B;
|
||||
}
|
@ -16,3 +16,4 @@ export { default as IconLogout } from "https://deno.land/x/tabler_icons_tsx@0.0.
|
||||
export { default as IconSearch } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/search.tsx";
|
||||
export { default as IconGhost } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/ghost.tsx";
|
||||
export { default as IconBrandYoutube } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/brand-youtube.tsx";
|
||||
export { default as IconWand } from "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/wand.tsx";
|
||||
|
Reference in New Issue
Block a user