Photo by RetroSupply on Unsplash
Crafting a Seamless Multi-Check Items List with Auto-Select Tabs in Next.js Using Material-UI
Welcome, fellow developers! In this tutorial, we'll embark on a journey to create a feature-rich multi-check items list in your Next.js application, utilizing the power of Material-UI. We'll dive into the process of fetching data from a custom API, generating dynamic tabs, and implementing an auto-select mechanism for a smoother user experience.
Let's get started:
Setting Up Your Next.js App
Begin by creating a new Next.js app with the following command:
npx create-next-app multi-check-items-list
Now, let's integrate Material-UI into our project:
npm install @mui/material @emotion/react @emotion/styled
Crafting a Dynamic API for Items Listing
Our API, located at src/pages/api/getItemsCustomizations.ts
, is designed to provide a structured list of customizable items. We've organized these items into categories and subcategories, ensuring a clear hierarchy for a more user-friendly interface.
// src/pages/api/getItemsCustomizations.ts
import type { NextApiRequest, NextApiResponse } from "next";
type Data = {
id: number;
parentId: number | "";
name: string;
};
const categoriesList: Data[] = [
{
id: 1,
parentId: "",
name: "Super Mega Deal",
},
{
id: 2,
parentId: 1,
name: "Super Mega Deal - Original",
},
{
id: 3,
parentId: 1,
name: "Super Mega Deal - Spicy",
},
{
id: 4,
parentId: 1,
name: "Super Mega Deal - Mix",
},
{
id: 5,
parentId: "",
name: "Would you like to add extra side to your meal?",
},
{
id: 6,
parentId: 5,
name: "Pop Corn Large",
},
{
id: 7,
parentId: 5,
name: "Twister Sandwich",
},
{
id: 8,
parentId: 5,
name: "Rice",
},
{
id: 9,
parentId: "",
name: "Select Your Favorite Side Item",
},
{
id: 10,
parentId: 9,
name: "Family Fries",
},
{
id: 11,
parentId: 9,
name: "Coleslaw Salad Large",
},
{
id: 12,
parentId: 9,
name: "Family Spicy Fries",
},
{
id: 13,
parentId: "",
name: "Special Deal",
},
{
id: 14,
parentId: 13,
name: "Special Deal - Original",
},
{
id: 15,
parentId: 13,
name: "Special Deal - Spicy",
},
{
id: 16,
parentId: 13,
name: "Special Deal - Mix",
},
{
id: 17,
parentId: "",
name: "Deluxe Deal",
},
{
id: 18,
parentId: 17,
name: "Deluxe Deal - Original",
},
{
id: 19,
parentId: 17,
name: "Deluxe Deal - Spicy",
},
{
id: 20,
parentId: 17,
name: "Deluxe Deal - Mix",
},
];
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data[]>
) {
const getCustomizationsMainCategories = categoriesList.filter(
(item) => item.parentId === ""
);
const getCustomizations = getCustomizationsMainCategories.map((item) => {
return { ...item, children: getCustomizationsChildren(item.id) };
});
res.status(200).json(getCustomizations);
}
const getCustomizationsChildren = (parentId: number | "") => {
return categoriesList.filter((item) => item.parentId === parentId);
};
Building the Multi-Check Component
In src/pages/index.tsx
, we've implemented a comprehensive multi-check component. This component features dynamic tabs, auto-selection on scroll, and a visually appealing design. The code is thoroughly commented to guide you through each step.
// src/pages/index.tsx
import React, { useEffect, useRef, useState } from "react";
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import { Checkbox, Grid } from "@mui/material";
import Card from "@mui/material/Card";
import Typography from "@mui/material/Typography";
interface CustomizedOption {
id: number;
name: string;
children: { id: number; name: string }[];
}
const buttonStyle = {
cursor: "pointer",
padding: "10px",
boxShadow: "0px 0px 1px 1px green",
margin: "10px",
borderRadius: "10px",
};
const Home: React.FC = () => {
const [isTabClick, setIsTabClick] = useState(false);
const [open, setOpen] = useState(false);
const [customizedOptionsList, setCustomizedOptionsList] = useState<
CustomizedOption[]
>([]);
const [selectedId, setSelectedId] = useState(0);
const [checkedIds, setCheckedIds] = useState<number[]>([]);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
useEffect(() => {
getCustomizationsList();
}, []);
const getCustomizationsList = async () => {
const response = await fetch(
"http://localhost:3000/api/getItemsCustomizations"
);
const data = await response.json();
setCustomizedOptionsList(data);
};
const handleTabClick = (item: CustomizedOption) => {
setIsTabClick(true);
setSelectedId(item?.id);
const anchorNodeElem = document.createElement("a");
anchorNodeElem.href = `#MAIN_SECTION_${item?.id}`;
anchorNodeElem.click();
};
const handleChange = (item: { id: number }) => {
if (checkedIds.includes(item?.id)) {
setCheckedIds(checkedIds.filter((itemR) => itemR !== item?.id));
return;
}
setCheckedIds([...checkedIds, item?.id]);
};
const ActionAreaCard: React.FC<{ customizedOption: CustomizedOption }> = ({
customizedOption,
}) => {
let callback = (entries: any, observer: any) => {
entries.forEach((entry: any) => {
if (entry?.isIntersecting && !isTabClick) {
setSelectedId(customizedOption?.id);
}
});
};
const sectionRef: any = useRef(null);
useEffect(() => {
let options = {
root: null,
rootMargin: "0px",
threshold: 1.0,
};
let observer = new IntersectionObserver(callback, options);
sectionRef?.current ? observer.observe(sectionRef.current) : "";
return () => {
observer.disconnect();
};
}, []);
return (
<Card ref={sectionRef} sx={{ maxWidth: 345 }}>
<Typography gutterBottom variant="h5" component="div">
{customizedOption?.name}
</Typography>
{customizedOption?.children.map((item, index) => (
<div key={index} style={{ display: "flex", alignItems: "center" }}>
<Checkbox
checked={checkedIds.includes(item?.id)}
onChange={() => handleChange(item)}
/>
<Typography variant="body2" color="text.secondary">
{item?.name}
</Typography>
</div>
))}
</Card>
);
};
return (
<React.Fragment>
<Button onClick={handleClickOpen}>Open food item customizations</Button>
<Dialog open={open} onClose={handleClose}>
<Grid container spacing={2}>
<Grid item xs={12}>
<h2 style={{ textAlign: "center" }}>Food item customizations</h2>
</Grid>
<Grid item xs={4}>
<aside style={{ maxHeight: "65vh", overflowY: "auto" }}>
<nav>
<ul style={{ listStyle: "none" }}>
{customizedOptionsList.map((item, index) => (
<li
onClick={() => handleTabClick(item)}
style={{
...buttonStyle,
color: item.id === selectedId ? "purple" : undefined,
background:
item.id === selectedId ? "yellow" : undefined,
}}
key={item.id}
>
{item.name}
</li>
))}
</ul>
</nav>
</aside>
</Grid>
<Grid item xs={8}>
<div
id={"KALI"}
style={{
maxHeight: "65vh",
overflowY: "auto",
display: "grid",
gap: "5rem",
}}
>
{customizedOptionsList.map((item, index) => (
<div
key={index}
id={`MAIN_SECTION_${item?.id}`}
onMouseEnter={() => setIsTabClick(false)}
>
<ActionAreaCard customizedOption={item} />
</div>
))}
</div>
</Grid>
</Grid>
</Dialog>
</React.Fragment>
);
};
export default Home;
Enhancing User Experience
To ensure a seamless user experience, we've incorporated smooth scrolling behavior in our global styles file (src/styles/globals.css
). This provides users with a pleasant and effortless navigation experience.
// src/styles/globals.css
html {
scroll-behavior: smooth;
}
Conclusion
And there you have it – a powerful, feature-rich multi-check items list in your Next.js application! This tutorial covers everything from setting up your project to implementing a polished user interface with Material-UI.
How You Can Support:
If you found this tutorial valuable and it has saved you time or effort, consider supporting my work by buying me a coffee. Your support fuels future tutorials and helps maintain the quality of content. Every contribution is greatly appreciated!
Buy Me a Coffee - Your generosity keeps the coding fuel flowing!
Thank you for choosing this tutorial, and happy coding!