Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: checkbox state gets lost when toggling between ingredientsinstructions #231

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app/dashboard/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ export default async function RecipePage({
</Stack>

<ScreenAwakeToggle />
<Box component="section" mb={"md"} pb={"md"}>
<Box component="section" pb={"xl"}>
<IngredientsAndInstructionsToggle recipe={convertedRecipe} />
</Box>

<Group mb="md">
<Group pb={"xl"}>
{recipe.tags?.map((tagRelation, index) => (
<Pill key={index} size="md">
{tagRelation.tag.name}
Expand Down
125 changes: 85 additions & 40 deletions src/components/IngredientsAndInstructionsToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"use client";
import { Button, Group, List, Stack } from "@mantine/core";
import { Button, Flex, Group, Paper, Stack, Text } from "@mantine/core";
import { useState } from "react";
import RenderedInstructions from "./RenderedInstructions";
import RenderedIngredients from "./RenderedIngredients";

type IngAndInstToggleProps = {
recipe: UserRecipe;
Expand All @@ -12,45 +13,89 @@ export const IngredientsAndInstructionsToggle = ({
}: IngAndInstToggleProps) => {
const [view, setView] = useState("ingredients");

const [checkboxStates, setCheckboxStates] = useState({
instructions: Array(recipe.instructions.length).fill(false),
ingredients: Array(recipe.ingredients.length).fill(false),
});

const handleCheckboxChange = (
type: "instructions" | "ingredients",
index: number,
checked: boolean
) => {
setCheckboxStates((prev) => ({
...prev,
[type]: prev[type].map((state, idx) => (idx === index ? checked : state)),
}));
};

return (
<Stack mb={"md"}>
<Group justify="space-between" grow mb="md">
<Button
variant={view === "ingredients" ? "filled" : "light"}
size="md"
onClick={() => setView("ingredients")}
>
Ingredients
</Button>

<Button
variant={view === "instructions" ? "filled" : "light"}
size="md"
onClick={() => setView("instructions")}
>
Instructions
</Button>
</Group>

{view === "ingredients" && (
<List listStyleType="none">
{recipe.ingredients.map((ingredient, index) => {
return (
<List.Item
styles={{
itemWrapper: {
display: "inline",
},
}}
key={index}
>
{ingredient}
</List.Item>
);
})}
</List>
)}
{view === "instructions" && <RenderedInstructions recipe={recipe} />}
</Stack>
<>
<Flex gap={"sm"} visibleFrom="sm">
<Paper miw={"30%"} withBorder radius={"xs"} p="md" shadow="xs">
<Text fw={700} size="xl" mb={"md"}>
Ingredients
</Text>
<RenderedIngredients
recipe={recipe}
checkboxStates={checkboxStates.ingredients}
onCheckboxChange={(index, checked) =>
handleCheckboxChange("ingredients", index, checked)
}
/>
</Paper>
<Paper withBorder radius={"xs"} p="md" shadow="xs">
<Text fw={700} size="xl" mb={"md"}>
Instructions
</Text>
<RenderedInstructions
recipe={recipe}
checkboxStates={checkboxStates.instructions}
onCheckboxChange={(index, checked) =>
handleCheckboxChange("instructions", index, checked)
}
/>
</Paper>
</Flex>

<Stack hiddenFrom="sm">
<Group justify="space-between" grow mb="md">
<Button
variant={view === "ingredients" ? "filled" : "light"}
size="md"
onClick={() => setView("ingredients")}
>
Ingredients
</Button>

<Button
variant={view === "instructions" ? "filled" : "light"}
size="md"
onClick={() => setView("instructions")}
>
Instructions
</Button>
</Group>

{view === "ingredients" && (
<RenderedIngredients
recipe={recipe}
checkboxStates={checkboxStates.ingredients}
onCheckboxChange={(index, checked) =>
handleCheckboxChange("ingredients", index, checked)
}
/>
)}
{view === "instructions" && (
<RenderedInstructions
recipe={recipe}
checkboxStates={checkboxStates.instructions}
onCheckboxChange={(index, checked) =>
handleCheckboxChange("instructions", index, checked)
}
/>
)}
</Stack>
</>
);
};
47 changes: 47 additions & 0 deletions src/components/RenderedIngredients.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Checkbox, List } from "@mantine/core";

type RenderedIngredientsProps = {
recipe: UserRecipe;
checkboxStates: boolean[];
onCheckboxChange: (index: number, checked: boolean) => void;
};

export default function RenderedIngredients({
recipe,
checkboxStates,
onCheckboxChange,
}: RenderedIngredientsProps) {
return (
<List listStyleType="none" spacing="xs">
{recipe.ingredients.map((ingredient, index) => {
return (
<List.Item
styles={{
itemWrapper: {
display: "inline",
},
}}
key={index}
>
<Checkbox
size="md"
checked={checkboxStates[index]}
onChange={(event) =>
onCheckboxChange(index, event.currentTarget.checked)
}
label={
<span
style={{
opacity: checkboxStates[index] ? 0.5 : 1,
}}
>
{ingredient}
</span>
}
/>
</List.Item>
);
})}
</List>
);
}
56 changes: 36 additions & 20 deletions src/components/RenderedInstructions.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Checkbox, List, Title } from "@mantine/core";
import { useRef } from "react";
import { Box, Checkbox, List, Text } from "@mantine/core";

type RenderProps = {
recipe: UserRecipe;
checkboxStates: boolean[];
onCheckboxChange: (index: number, checked: boolean) => void;
};

export default function RenderedInstructions({ recipe }: RenderProps) {
const labelRefs = useRef<(HTMLSpanElement | null)[]>([]);

export default function RenderedInstructions({
recipe,
checkboxStates,
onCheckboxChange,
}: RenderProps) {
if (typeof recipe.instructions[0] === "string") {
return (
<List listStyleType="none" spacing="xs">
Expand All @@ -22,18 +25,14 @@ export default function RenderedInstructions({ recipe }: RenderProps) {
>
<Checkbox
size="md"
onChange={(event) => {
if (labelRefs.current[index]) {
labelRefs.current[index].style.opacity = event.currentTarget
.checked
? "0.4"
: "1";
}
}}
checked={checkboxStates[index]}
onChange={(event) =>
onCheckboxChange(index, event.currentTarget.checked)
}
label={
<span
ref={(el) => {
labelRefs.current[index] = el;
style={{
opacity: checkboxStates[index] ? 0.5 : 1,
}}
>
{`${index + 1}. ${instruction}`}
Expand All @@ -50,9 +49,11 @@ export default function RenderedInstructions({ recipe }: RenderProps) {
{recipe.instructions.map((section, sectionIndex) => {
if (typeof section === "object" && "name" in section) {
return (
<div key={sectionIndex}>
<Title order={3}>{section.name}</Title>
<List type="ordered" spacing="xs">
<Box key={sectionIndex} pb={"sm"}>
<Text pl={"md"} ml={"lg"} fw={"bold"}>
{section.name}
</Text>
<List listStyleType="none" spacing="xs">
{section.text.map((instruction: string, index: number) => (
<List.Item
styles={{
Expand All @@ -62,11 +63,26 @@ export default function RenderedInstructions({ recipe }: RenderProps) {
}}
key={index}
>
{instruction}
<Checkbox
size="md"
checked={checkboxStates[index]}
onChange={(event) =>
onCheckboxChange(index, event.currentTarget.checked)
}
label={
<span
style={{
opacity: checkboxStates[index] ? 0.5 : 1,
}}
>
{`${index + 1}. ${instruction}`}
</span>
}
/>
</List.Item>
))}
</List>
</div>
</Box>
);
}
})}
Expand Down