feat: optimize navigation
This commit is contained in:
parent
0632ae05c1
commit
28c72264c5
@ -2,7 +2,7 @@ import { Card } from "@components/Card.tsx";
|
||||
import { Recipe } from "@lib/recipes.ts";
|
||||
|
||||
export function RecipeCard({ recipe }: { recipe: Recipe }) {
|
||||
const { meta: { image = "Recipes/images/placeholder.jpg" } = {} } = recipe;
|
||||
const { meta: { image = "/placeholder.svg" } = {} } = recipe;
|
||||
|
||||
const imageUrl = image.startsWith("Recipes/images/")
|
||||
? `/api/images?image=${image}&width=200&height=200`
|
||||
|
@ -3,19 +3,26 @@ import { Recipe } from "@lib/recipes.ts";
|
||||
import IconExternalLink from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/external-link.tsx";
|
||||
import { Star } from "@components/Stars.tsx";
|
||||
export function RecipeHero({ recipe }: { recipe: Recipe }) {
|
||||
const { meta: { image = "Recipes/images/placeholder.jpg" } = {} } = recipe;
|
||||
const { meta: { image } = {} } = recipe;
|
||||
|
||||
const imageUrl = image.startsWith("Recipes/images/")
|
||||
const imageUrl = image?.startsWith("Recipes/images/")
|
||||
? `/api/images?image=${image}&width=800`
|
||||
: image;
|
||||
|
||||
return (
|
||||
<div class="relative w-full h-[400px] rounded-3xl overflow-hidden bg-black">
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt="Recipe Banner"
|
||||
class="object-cover w-full h-full"
|
||||
/>
|
||||
<div
|
||||
class={`relative w-full h-[${
|
||||
imageUrl ? 400 : 200
|
||||
}px] rounded-3xl overflow-hidden`}
|
||||
>
|
||||
{imageUrl &&
|
||||
(
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt="Recipe Banner"
|
||||
class="object-cover w-full h-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div class="absolute top-8 left-8 ">
|
||||
<a
|
||||
@ -40,10 +47,14 @@ export function RecipeHero({ recipe }: { recipe: Recipe }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="absolute inset-x-0 bottom-0 py-4 px-8 py-8 noisy-gradient flex gap-4 items-center pt-12">
|
||||
<div
|
||||
class={`absolute inset-x-0 bottom-0 py-4 px-8 py-8 ${
|
||||
imageUrl ? "noisy-gradient" : ""
|
||||
} flex gap-4 items-center pt-12`}
|
||||
>
|
||||
<h2
|
||||
class="relative text-4xl font-bold z-10"
|
||||
style={{ color: "#1F1F1F" }}
|
||||
style={{ color: imageUrl ? "#1F1F1F" : "white" }}
|
||||
>
|
||||
{recipe.name}
|
||||
</h2>
|
||||
|
@ -4,13 +4,14 @@ import IconStarFilled from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/star-
|
||||
export const Star = (
|
||||
{ max = 5, rating = 3 }: { max?: number; rating: number },
|
||||
) => {
|
||||
console.log({ max, rating });
|
||||
return (
|
||||
<div
|
||||
class="flex gap-2 px-4 py-2 rounded-2xl bg-gray-200 z-10"
|
||||
style={{ color: "#1F1F1F" }}
|
||||
>
|
||||
{Array.from({ length: max }).map((_, i) => {
|
||||
if (rating >= i) {
|
||||
if ((i + 1) <= rating) {
|
||||
return <IconStarFilled class="w-4 h-4" />;
|
||||
}
|
||||
return <IconStar class="w-4 h-4" />;
|
||||
|
@ -4,18 +4,50 @@ export type Props = {
|
||||
children: ComponentChildren;
|
||||
title?: string;
|
||||
name?: string;
|
||||
url: URL;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export const MainLayout = ({ children }: Props) => {
|
||||
export const MainLayout = ({ children, url }: Props) => {
|
||||
const menu = [
|
||||
{
|
||||
name: "🏡 Home",
|
||||
link: "/",
|
||||
},
|
||||
{
|
||||
name: "🍽️ Recipes",
|
||||
link: "/recipes",
|
||||
},
|
||||
{
|
||||
name: "🍿 Movies",
|
||||
link: "/movies",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="md:grid" style={{ gridTemplateColumns: "200px 1fr" }}>
|
||||
<aside class="p-4 hidden md:block">
|
||||
<nav class="min-h-fit rounded-3xl p-3 grid gap-3">
|
||||
{menu.map((m) => {
|
||||
return (
|
||||
<a
|
||||
href={m.link}
|
||||
class={`block ${
|
||||
m.link === url.pathname ? "bg-white text-black" : "text-white"
|
||||
} p-3 text-xl w-full rounded-2xl`}
|
||||
>
|
||||
{m.name}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
<main
|
||||
class="max-w-2xl mx-auto lg:max-w-4xl py-5"
|
||||
class="py-5"
|
||||
style={{ fontFamily: "Work Sans" }}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
12
fresh.gen.ts
12
fresh.gen.ts
@ -9,8 +9,10 @@ import * as $3 from "./routes/api/index.ts";
|
||||
import * as $4 from "./routes/api/recipes/[name].ts";
|
||||
import * as $5 from "./routes/api/recipes/index.ts";
|
||||
import * as $6 from "./routes/index.tsx";
|
||||
import * as $7 from "./routes/recipes/[name].tsx";
|
||||
import * as $8 from "./routes/recipes/index.tsx";
|
||||
import * as $7 from "./routes/movies/[name].tsx";
|
||||
import * as $8 from "./routes/movies/index.tsx";
|
||||
import * as $9 from "./routes/recipes/[name].tsx";
|
||||
import * as $10 from "./routes/recipes/index.tsx";
|
||||
import * as $$0 from "./islands/Counter.tsx";
|
||||
import * as $$1 from "./islands/IngredientsList.tsx";
|
||||
|
||||
@ -23,8 +25,10 @@ const manifest = {
|
||||
"./routes/api/recipes/[name].ts": $4,
|
||||
"./routes/api/recipes/index.ts": $5,
|
||||
"./routes/index.tsx": $6,
|
||||
"./routes/recipes/[name].tsx": $7,
|
||||
"./routes/recipes/index.tsx": $8,
|
||||
"./routes/movies/[name].tsx": $7,
|
||||
"./routes/movies/index.tsx": $8,
|
||||
"./routes/recipes/[name].tsx": $9,
|
||||
"./routes/recipes/index.tsx": $10,
|
||||
},
|
||||
islands: {
|
||||
"./islands/Counter.tsx": $$0,
|
||||
|
@ -3,15 +3,15 @@ import { useSignal } from "@preact/signals";
|
||||
import Counter from "@islands/Counter.tsx";
|
||||
import { MainLayout } from "@components/layouts/main.tsx";
|
||||
import { Card } from "@components/Card.tsx";
|
||||
import { PageProps } from "$fresh/server.ts";
|
||||
|
||||
export default function Home() {
|
||||
const count = useSignal(3);
|
||||
export default function Home(props: PageProps) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>app</title>
|
||||
</Head>
|
||||
<MainLayout>
|
||||
<MainLayout url={props.url}>
|
||||
<div class="flex flex-wrap justify-center items-center gap-4 px-4">
|
||||
<Card
|
||||
title="Recipes"
|
||||
|
46
routes/movies/[name].tsx
Normal file
46
routes/movies/[name].tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { Handlers, PageProps } from "$fresh/server.ts";
|
||||
import { IngredientsList } from "@islands/IngredientsList.tsx";
|
||||
import { RecipeHero } from "@components/RecipeHero.tsx";
|
||||
import { MainLayout } from "@components/layouts/main.tsx";
|
||||
import { Recipe } from "@lib/recipes.ts";
|
||||
import { getRecipe } from "../api/recipes/[name].ts";
|
||||
import Counter from "@islands/Counter.tsx";
|
||||
import { useSignal } from "@preact/signals";
|
||||
|
||||
export const handler: Handlers<Recipe | null> = {
|
||||
async GET(_, ctx) {
|
||||
const recipe = await getRecipe(ctx.params.name);
|
||||
return ctx.render(recipe);
|
||||
},
|
||||
};
|
||||
|
||||
export default function Greet(props: PageProps<Recipe>) {
|
||||
const recipe = props.data;
|
||||
|
||||
const portion = recipe.meta?.portion;
|
||||
const amount = useSignal(portion || 1);
|
||||
|
||||
return (
|
||||
<MainLayout url={props.url}>
|
||||
<RecipeHero recipe={recipe} />
|
||||
<div class="px-8 text-white mt-10">
|
||||
<div class="flex items-center gap-8">
|
||||
<h3 class="text-3xl my-5">Ingredients</h3>
|
||||
{portion && <Counter count={amount} />}
|
||||
</div>
|
||||
<IngredientsList
|
||||
ingredients={recipe.ingredients}
|
||||
amount={amount}
|
||||
portion={portion}
|
||||
/>
|
||||
<h3 class="text-3xl my-5">Preparation</h3>
|
||||
<pre
|
||||
class="whitespace-break-spaces"
|
||||
dangerouslySetInnerHTML={{ __html: recipe.preparation || "" }}
|
||||
>
|
||||
{recipe.preparation}
|
||||
</pre>
|
||||
</div>
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
35
routes/movies/index.tsx
Normal file
35
routes/movies/index.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { Handlers, PageProps } from "$fresh/server.ts";
|
||||
import { RecipeCard } from "@components/RecipeCard.tsx";
|
||||
import { MainLayout } from "@components/layouts/main.tsx";
|
||||
import { Recipe } from "@lib/recipes.ts";
|
||||
import { getRecipes } from "../api/recipes/index.ts";
|
||||
import IconArrowLeft from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/arrow-left.tsx";
|
||||
export const handler: Handlers<Recipe[] | null> = {
|
||||
async GET(_, ctx) {
|
||||
const recipes = await getRecipes();
|
||||
return ctx.render(recipes);
|
||||
},
|
||||
};
|
||||
|
||||
export default function Greet(props: PageProps<Recipe[] | null>) {
|
||||
return (
|
||||
<MainLayout url={props.url}>
|
||||
<header class="flex gap-4 items-center mb-5 md:hidden">
|
||||
<a
|
||||
class="px-4 ml-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex items-center gap-1"
|
||||
href="/"
|
||||
>
|
||||
<IconArrowLeft class="w-5 h-5" />
|
||||
Back
|
||||
</a>
|
||||
|
||||
<h3 class="text-2xl text-white font-light">Recipes</h3>
|
||||
</header>
|
||||
<div class="flex flex-wrap items-center gap-4 px-4">
|
||||
{props.data?.map((doc) => {
|
||||
return <RecipeCard recipe={doc} />;
|
||||
})}
|
||||
</div>
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
@ -21,7 +21,7 @@ export default function Greet(props: PageProps<Recipe>) {
|
||||
const amount = useSignal(portion || 1);
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<MainLayout url={props.url}>
|
||||
<RecipeHero recipe={recipe} />
|
||||
<div class="px-8 text-white mt-10">
|
||||
<div class="flex items-center gap-8">
|
||||
|
@ -13,8 +13,8 @@ export const handler: Handlers<Recipe[] | null> = {
|
||||
|
||||
export default function Greet(props: PageProps<Recipe[] | null>) {
|
||||
return (
|
||||
<MainLayout>
|
||||
<header class="flex gap-4 items-center mb-5">
|
||||
<MainLayout url={props.url}>
|
||||
<header class="flex gap-4 items-center mb-5 md:hidden">
|
||||
<a
|
||||
class="px-4 ml-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex items-center gap-1"
|
||||
href="/"
|
||||
@ -25,7 +25,7 @@ export default function Greet(props: PageProps<Recipe[] | null>) {
|
||||
|
||||
<h3 class="text-2xl text-white font-light">Recipes</h3>
|
||||
</header>
|
||||
<div class="flex flex-wrap justify-center items-center gap-4 px-4">
|
||||
<div class="flex flex-wrap items-center gap-4 px-4">
|
||||
{props.data?.map((doc) => {
|
||||
return <RecipeCard recipe={doc} />;
|
||||
})}
|
||||
|
3
static/g10.svg:Zone.Identifier
Normal file
3
static/g10.svg:Zone.Identifier
Normal file
@ -0,0 +1,3 @@
|
||||
[ZoneTransfer]
|
||||
ZoneId=3
|
||||
HostUrl=about:internet
|
3
static/g8.svg:Zone.Identifier
Normal file
3
static/g8.svg:Zone.Identifier
Normal file
@ -0,0 +1,3 @@
|
||||
[ZoneTransfer]
|
||||
ZoneId=3
|
||||
HostUrl=about:internet
|
Binary file not shown.
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 28 KiB |
4
static/placeholder.svg
Normal file
4
static/placeholder.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="1185" height="1185" viewBox="0 0 1185 1185" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 -4.3099e-05H1185V1184.99H0V-4.3099e-05Z" fill="#1F1F1F"/>
|
||||
<path d="M305.227 296.249H879.769C884.726 296.249 888.747 300.266 888.747 305.223V879.766C888.747 884.723 884.726 888.744 879.769 888.744H305.227C300.269 888.744 296.249 884.723 296.249 879.766V305.223C296.249 300.266 300.269 296.249 305.227 296.249ZM870.791 314.201H314.204V870.789H870.791V314.201ZM641.944 435.489C656.788 435.489 670.234 441.509 679.963 451.238C680.133 451.408 680.295 451.586 680.449 451.767C689.889 461.457 695.712 474.689 695.712 489.257C695.712 504.081 689.692 517.511 679.963 527.244L679.943 527.259L679.963 527.275C670.234 537.004 656.788 543.028 641.944 543.028C627.1 543.028 613.659 537.004 603.926 527.275C594.197 517.546 588.177 504.101 588.177 489.257C588.177 474.417 594.197 460.971 603.926 451.242C613.659 441.509 627.1 435.489 641.944 435.489ZM667.268 463.929C660.794 457.455 651.831 453.446 641.944 453.446C632.054 453.446 623.099 457.455 616.617 463.933C610.143 470.411 606.134 479.37 606.134 489.257C606.134 499.147 610.143 508.106 616.617 514.58C623.099 521.062 632.054 525.071 641.944 525.071C651.831 525.071 660.794 521.062 667.268 514.584L667.284 514.6C673.754 508.122 677.755 499.163 677.755 489.257C677.755 479.556 673.916 470.763 667.683 464.316L667.268 463.929ZM592.499 655.839L655.236 590.163C658.645 586.58 664.317 586.442 667.9 589.847L668.239 590.191L737.084 662.254L805.952 734.349C809.361 737.932 809.222 743.604 805.64 747.009C802.053 750.418 796.385 750.279 792.976 746.697L724.104 674.602L661.726 609.305L604.882 668.807L667.493 734.349C670.902 737.932 670.76 743.604 667.177 747.009C663.594 750.418 657.922 750.279 654.513 746.697L585.645 674.602L523.267 609.305L460.885 674.602L392.019 746.697C388.612 750.279 382.94 750.418 379.357 747.009C375.774 743.604 375.634 737.932 379.041 734.349L447.909 662.254L516.777 590.163C520.186 586.58 525.858 586.442 529.441 589.847L529.781 590.191L592.499 655.839Z" fill="white" fill-opacity="0.1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,4 @@
|
||||
[ZoneTransfer]
|
||||
ZoneId=3
|
||||
ReferrerUrl=https://cloudconvert.com/
|
||||
HostUrl=https://storage.cloudconvert.com/tasks/748af8fd-a74c-4374-b73b-fa782814df9d/vecteezy_picture-gallery-image-line-icon-vector-illustration_.svg?AWSAccessKeyId=cloudconvert-production&Expires=1690830191&Signature=fBYB2Q0uKneAnH7%2BrXXzsWOKUXw%3D&response-content-disposition=attachment%3B%20filename%3D%22vecteezy_picture-gallery-image-line-icon-vector-illustration_.svg%22&response-content-type=image%2Fsvg%2Bxml
|
Loading…
x
Reference in New Issue
Block a user