I was looking through a tutorial located here: https://daily-dev-tips.com/posts/creating-a-sidebar-layout-in-nextjs-with-tailwind/ about a sidebar.
I was wondering if there was a way to use Icons (like heroicons) within the layout file.
referenced below:
import Link from "next/link";
import { useRouter } from "next/router";
import HomeIcon from "#heroicons/react/outline";
export default function Layout({ children }) {
const router = useRouter();
const menuItems = [
{
href: "/",
title: "Home",
},
{
href: "/about",
title: "About",
},
{
href: "/contact",
title: "Contact",
},
];
return (
<div className="min-h-screen flex flex-col">
<header className="bg-white sticky top-0 h-14 flex justify-center items-center font-semibold uppercase">
Next.js sidebar menu
</header>
<div className="flex flex-col md:flex-row flex-1">
<aside className="bg-black w-full md:w-60">
<nav>
<ul>
{menuItems.map(({ href, title }) => (
<li className="m-2" key={title}>
<Link href={href}>
<a
className={`flex p-2 bg-black text-white rounded hover:bg-red-600 cursor-pointer ${
router.asPath === href && "bg-black text-red-600"
}`}
>
{title}
</a>
</Link>
</li>
))}
</ul>
</nav>
</aside>
<main className="flex-1">{children}</main>
</div>
</div>
);
}
I tried
href: "/",
title: "Home",
icon: <HomeIcon />,
but then I wasn't sure exactly how to incorporate it. Any ideas?
First, import as below:
import { ChatIcon, HomeIcon, PhoneIcon } from "#heroicons/react/outline";
Then put the icons in menu items:
const menuItems = [
{
href: "/",
title: "Homepage",
icon: <HomeIcon className="h-4 w-4 mx-2" />,
},
{
href: "/about",
title: "About",
icon: <ChatIcon className="h-4 w-4 mx-2" />,
},
{
href: "/contact",
title: "Contact",
icon: <PhoneIcon className="h-4 w-4 mx-2" />,
},
];
then, update your list by adding icon here {menuItems.map(({ href, title, icon }) and at the achor {icon} {title}
{menuItems.map(({ href, title, icon }) => (
<li className="m-2" key={title}>
<Link href={href}>
<a className={`inline-flex items-center w-full p-2 bg-fuchsia-200 rounded hover:bg-fuchsia-400 cursor-pointer ${router.asPath === href && "bg-fuchsia-600 text-white"}`}
>
{icon} {title}
</a>
</Link>
</li>
))}
and update the Link anchor class flex become inline-flex items-center w-full
Finally, the complete layout.js file is as follows:
import Link from "next/link";
import { useRouter } from "next/router";
import { ChatIcon, HomeIcon, PhoneIcon } from "#heroicons/react/outline";
export default function Layout({ children }) {
const router = useRouter();
const menuItems = [
{
href: "/",
title: "Homepage",
icon: <HomeIcon className="h-4 w-4 mx-2" />,
},
{
href: "/about",
title: "About",
icon: <ChatIcon className="h-4 w-4 mx-2" />,
},
{
href: "/contact",
title: "Contact",
icon: <PhoneIcon className="h-4 w-4 mx-2" />,
},
];
return (
<div className="min-h-screen flex flex-col">
<header className="bg-purple-200 sticky top-0 h-14 flex justify-center items-center font-semibold uppercase">
Next.js sidebar menu
</header>
<div className="flex flex-col md:flex-row flex-1">
<aside className="bg-fuchsia-100 w-full md:w-60">
<nav>
<ul>
{menuItems.map(({ href, title, icon }) => (
<li className="m-2" key={title}>
<Link href={href}>
<a
className={`inline-flex items-center w-full p-2 bg-fuchsia-200 rounded hover:bg-fuchsia-400 cursor-pointer ${
router.asPath === href && "bg-fuchsia-600 text-white"
}`}
>
{icon} {title}
</a>
</Link>
</li>
))}
</ul>
</nav>
</aside>
<main className="flex-1">{children}</main>
</div>
</div>
);
}
i suggest change the array object like this.
sample https://codesandbox.io/s/epic-stitch-h5gn89?file=/src/App.js
{
href: "/",
title: "Home",
icon: "HomeIcon",
}
and use this component i found on this link.(i changed some of it) https://github.com/tailwindlabs/heroicons/issues/278#issuecomment-851594776
in this solution dont need to import eche icon one by one
DynamicHeroIcon.tsx
// DynamicHeroIcon.tsx
// Simple Dynamic HeroIcons Component for React (typescript / tsx)
// by: Mike Summerfeldt (IT-MikeS - https://github.com/IT-MikeS)
import { FC } from "react";
import * as HIcons from "#heroicons/react/outline";
const DynamicHeroIcon: FC<{ icon: string } & React.HTMLProps<HTMLElement>> = (
props
) => {
const { ...icons } = HIcons;
const Fprops = { ...props };
delete Fprops.icon;
// #ts-ignore
const TheIcon: JSX.Element = icons[props.icon];
return (
<>
{/* #ts-ignore */}
<TheIcon {...Fprops} aria-hidden="true" />
</>
);
};
export default DynamicHeroIcon;
and use it like this.
import DynamicHeroIcon from "./components/DynamicHeroIcon";
<DynamicHeroIcon style={{ width: "52px" }} icon={"HomeIcon"} />
{list.map((item) => {
return <DynamicHeroIcon style={{ width: "52px" }} icon={item.icon} />;
})}
One of the most preferred icons utility is React-icons. You can simply install the npm package from here.
And you can view & search icons from here.
In the code you can use it like this, suppose you want to use AiFillHome icon, then just import it in the file like this,
import {AiFillHome} from "react-icons/ai";
And use it like
<AiFillHome />
You can check the props in the mentioned docs.
Related
"use client";
import { groq } from "next-sanity";
import Image from "next/image";
import { client } from "../../../../lib/sanity.client";
import urlFor from "../../../../lib/urlFor";
import { PortableText } from "#portabletext/react";
import { RichTextComponents } from "../../../../components/RichTextComponents";
import { useForm, SubmitHandler } from "react-hook-form";
interface IFormInput {
_id: string;
name: string;
email: string;
comment: string;
}
type Props = {
params: {
slug: string;
};
};
export const revalidate = 30;
export async function generateStaticParams() {
const query = groq`
*[_type=="post"]
{
slug
}
`;
const slugs: Post[] = await client.fetch(query);
const slugRoutes = slugs.map((slug) => slug.slug.current);
return slugRoutes.map((slug) => ({
slug,
}));
}
const Post = async ({ params: { slug } }: Props) => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<IFormInput>();
const query = groq`
*[_type=='post' && slug.current == $slug][0]
{
...,
author->,
categories[]->
}
`;
const post: Post = await client.fetch(query, { slug });
const onSubmit: SubmitHandler<IFormInput> = async (data) => {
await fetch("/api/createComment", {
method: "POST",
body: JSON.stringify(data),
})
.then(() => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
};
return (
<>
<article className="px-10 pb-28 mt-10">
<section className="space-y-2 borber border-[#f7ab0a] text-white">
<div className="relative min-h-56 flex flex-col md:flex-row justify-between">
<div className="absolute top-0 w-full h-full opacity-10 blur-sm p-10">
<Image
className="object-cover object-center mx-auto"
src={urlFor(post.mainImage).url()}
alt={post.author.name}
fill
/>
</div>
<section className="p-5 bg-[#f7ab0a] w-full">
<div className="flex flex-col md:flex-row justify-between gap-y-5">
<div>
<h1 className="text-4xl font-extrabold">{post.title}</h1>
<p>
{new Date(post._createdAt).toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric",
})}
</p>
</div>
<div className="flex items-center space-x-2">
<Image
className="rounded-full"
src={urlFor(post.author.image).url()}
alt={post.author.name}
height={40}
width={40}
/>
<div className="w-64">
<h3 className="text-lg font-bold">{post.author.name}</h3>
<div> {/* */} </div>
</div>
</div>
</div>
<div>
<h2 className="italic pt-10">{post.description}</h2>
<div className="flex items-center justify-end mt-auto space-x-2">
{post.categories.map((category) => (
<p
className="bg-gray-800 text-white px-3 py-1 rounded-full text-sm font-semibold mt-4"
key={category._id}
>
{category.title}
</p>
))}
</div>
</div>
</section>
</div>
</section>
<PortableText value={post.body} components={RichTextComponents} />
</article>
<div>
<hr className="max-w-lg my-5 mx-auto border border-yellow-500" />
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col p-5 max-w-2xl mx-auto mb-10"
>
<h3 className="text-md text-yellow-500">Enjoyed this article?</h3>
<h4 className="text-3xl font-bold">Leave a comment below!</h4>
<hr className="py-3 mt-2" />
<input
{...register("_id")}
type="hidden"
name="_id"
value={post._id}
/>
<label className="block mb-5 ">
<span className="text-gray-700">Name</span>
<input
{...register("name", { required: true })}
className="shadow border rounded py-2 px-3 form-input mt-1 block w-full outline-none ring-yellow-500 focus:ring"
placeholder="John Doe..."
type="text"
/>
</label>
<label className="block mb-5 ">
<span className="text-gray-700">Email</span>
<input
{...register("email", { required: true })}
className="shadow border rounded py-2 px-3 form-input mt-1 block w-full outline-none ring-yellow-500 focus:ring"
placeholder="example#gmail.com"
type="email"
/>
</label>
<label className="block mb-5 ">
<span className="text-gray-700">Comment</span>
<textarea
{...register("comment", { required: true })}
className="shadow border rounded py-2 px-3 form-textarea mt-1 block w-full outline-none ring-yellow-500 focus:ring"
placeholder="type your comments..."
rows={8}
/>
</label>
<div className="flex flex-col p-5">
{errors.name && (
<span className="text-red-500">
-- The name field is required
</span>
)}
{errors.email && (
<span className="text-red-500">
-- The email field is required
</span>
)}
{errors.comment && (
<span className="text-red-500">
-- The comment field is required
</span>
)}
</div>
<input
type="submit"
className="shadow bg-yellow-500 hover:bg-yellow-400 w-full
focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded cursor-pointer
"
/>
</form>
</div>
<div className="relative">
<a
id="topp"
rel="noreferrer"
href="#top"
className="back-to-top absolute bottom-4 right-0 rounded-3xl bg-[#f7ab0a] text-white hover:-translate-y-2 hover:bg-lime-600 hover:text-[#f7ab0a] p-2 text-2xl"
>
<i className="fas fa-angle-up " aria-hidden="true"></i>
</a>
</div>
</>
);
};
export default Post;
I tried to post comments from my blog post to sanity v3 studio backend. But whenever I tried to test on the localhost:3000 dev build it claims that "Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead." I didn't understand where I render children as object.
I would like have my comment function working, this problem to be solved.
Thanks in advance.
Following line in your Post component is causing the issue:
// ...
const post: Post = await client.fetch(query, { slug });
// ...
You are using post variable to access title, author, categories fields and trying to render it in the JSX.
However, post is a Promise which is not resolved while React is rendering the component.
So, basically you are rendering Promise inside the JSX which is not supported (that's what the error is saying)
So, the better way is to define a state for post variable and make an api call inside a function and when that api is resolved set the response data inside the post state and use that post state inside the JSX.
Something like:
const Post = async ({ params: { slug } }: Props) => {
//... your component body
const [post, setPost] = useState(null) // initialise post state to be null
const fetchPost = async() => {
try {
let response = await client.fetch(query, { slug })
setPost(response.data) // it will be response.data or something else based on your response structure where you are getting the post
} catch(err) {
console.log(err)
}
}
useEffect(() => {
fetchPost()
}, [])
// .... remaining code
// handle loader
if(!post) return <p>loading...</p>
return (
<>
{/* JSX similar to what you are rendering */}
</>
)
}
You can create separate loader state in the component to handle loading more accurately.
Let me know if you have further queries. Happy to resolve them! :-)
I am trying to make an index page and I want it to have the following layout
Index page layout
I am using Deno Fresh thus I have tailwind for styling.
I have the following export for my index page that uses a footer and a header component an image and a Sign In island like so:
return (
<div className={'bg-[#5C7EB5]'}>
<Header active={"/"} flag={false}/>
<div className={"flex h-full gap-52 p-auto justify-center items-center"}>
<SignIn/>
<img src={"https://cdn-icons-png.flaticon.com/512/2974/2974498.png"}
alt={"Couldn't load image..."}
className={"w-1/4 h-1/4"}/>
</div>
<Footer/>
</div>
);
}
The components and the island are the following
Header:
export function Header({ active, flag }: Props, ) {
const menus = [
{ name: "Home", href: "/" },
{ name: "Rack Temperatures", href: "/test-header" },
{ name: "Entrees", href: "/docs" },
{ name: "Temperature Humidity", href: "/dummy"}
];
return (
<div class="sticky top-0 bg-[#28374F] w-full py-5 px-8 flex flex-col md:flex-row gap-4 mx-0">
<div class="flex items-center flex-1">
<div className="ml-1 text-2xl text-gray-50 font-bold">
<a href={"/"}>FlyMonitoring</a>
</div>
<a href={"/"}>
<img src={"https://pngimage.net/wp-content/uploads/2018/06/heisenberg-logo-png-2.png"}
alt={"Couldn't load image..."}
class={"w-12 h-12"}/>
</a>
</div>
<ul class="flex items-center gap-6">
{menus.map((menu) => (
<li>
<a
href={menu.href}
class={"text-gray-50 hover:text-blue-200 py-1 border-gray-50" +
(menu.href === active ? " font-bold border-b-2" : "")}
>
{menu.name}
</a>
</li>
))}
</ul>
<div>
{flag
? <button type={'submit'}
className={"bg-blue-600 hover:bg-blue-700 text-white rounded px-6 py-2.5"}>
Log Out</button>
: ""}
</div>
</div>
);
}
Footer:
import BrandGithub from "https://deno.land/x/tabler_icons_tsx#0.0.1/tsx/brand-github.tsx";
export default function Footer() {
const menus = [
{
title: "Device Control",
children: [
{ name: "Rack Temperature", href: "/rack-temperature" },
{ name: "Temperature Humidity", href: "/temperature-humidity" },
{ name: "Water Level", href: "/water-level" },
{ name: "Smoke", href: "/smoke" },
{ name: "Entrees", href: "/entrees" },
],
},
{
title: "Information",
children: [
{ name: "Email", href: "#" },
{ name: "Phone", href: "#" },
{ name: "Discord", href: "#" }
],
},
];
return (
<div class="bg-[#28374F] w-full flex flex-col md:flex-row w-full gap-2 md:gap-16 px-8 py-4 text-sm">
<div class="flex-1">
<div class="flex items-center gap-1">
<div class="font-bold text-2xl text-gray-50">
FlyMonitoring
</div>
</div>
<div class="text-gray-100">
Application for high security room monitoring
</div>
</div>
{menus.map((item) => (
<div class="mb-4" key={item.title}>
<div class="font-bold text-gray-50">{item.title}</div>
<ul class="mt-2">
{item.children.map((child) => (
<li class="mt-2" key={child.name}>
<a
class="text-gray-200 hover:text-blue-200"
href={child.href}
>
{child.name}
</a>
</li>
))}
</ul>
</div>
))}
<div class="text-gray-100 space-y-2">
<div class="text-xs">
Copyright © 2020<br />
All right reserved.
</div>
<a
href="https://github.com/****************"
class="inline-block hover:text-blue-200"
>
<BrandGithub />
</a>
</div>
</div>
);
}
Your code is unfortunately not reproducible:
Follow the below code structure:
flex flex-col
|_ h-40
|_ flex-1 👈 This fills the entire space
|_ h-60
<div class="flex h-screen flex-col bg-slate-500">
<header class="flex h-20 items-center justify-center bg-blue-600 text-6xl">Header</header>
<main class="flex flex-1 items-center justify-center bg-green-300 text-6xl">Main</main>
<footer class="flex h-40 items-center justify-center bg-yellow-400 text-6xl">Footer</footer>
</div>
tailwind-play
I want to have the option to upload icon to graphcms and then use the icon in Next.js.
I tried to add the "Multi line text" function in graphcms and inside provide the name of the icon like this "AiFillHome".
However unfortunately it looks like this:
Bad result
this is what I need:
Required result
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { getCategories } from "../services";
import { AiFillHome } from "react-icons/ai";
import { MdTravelExplore } from "react-icons/md";
import { MdFastfood } from "react-icons/md";
import { HiTrendingUp } from "react-icons/hi";
import { SiAnydesk } from "react-icons/si";
const Sidebar = () => {
const [categories, setCategories] = useState([]);
useEffect(() => {
getCategories().then((newCategories) => {
setCategories(newCategories);
});
}, []);
console.log(categories);
return (
<div className="px-8 pt-2 -mt-10">
<div className="py-10">
<ul>
<span className="tracking-widest text-gray-500 uppercase text-xxs">
category
</span>
<div className="flex flex-col pt-5 space-y-8">
<div className="flex items-center space-x-4 group">
<span className="p-2 text-gray-500 bg-gray-800 rounded-xl group-hover:text-white group-hover:bg-gray-500">
<AiFillHome />
</span>
<Link
href="/"
className="text-sm text-gray-500 group-hover:text-white group-hover:font-semibold"
>
Discover
</Link>
</div>
</div>
{categories?.map((category, index) => (
<li>
<ul className="flex flex-col pt-5 space-y-8">
<li className="flex items-center space-x-4 group">
<span className="p-2 text-gray-500 bg-gray-800 rounded-xl group-hover:text-white group-hover:bg-gray-500">
{`<${category.icon} />`}
{/*<img src={category?.icon?.url} alt="" className="" /> */}
</span>
<Link
key={index}
href={`/category/${category.slug}`}
className="text-sm text-gray-500 group-hover:text-white group-hover:font-semibold"
>
{category.name}
</Link>
</li>
</ul>
</li>
))}
</ul>
</div>
</div>
);
};
export default Sidebar;
The graphql:
export const getCategories = async () => {
const query = gql`
query GetGategories {
categories {
icon
name
slug
}
}
`;
const result = await request(graphqlAPI, query);
return result.categories;
};
The refs changes the slides. It is working fine in my localhost but when it is deployed in netlify this buttons does not change the slide. Below is my code.
import React, { useRef } from 'react';
import { Swiper, SwiperSlide } from 'swiper/react';
import "swiper/css";
import "swiper/css/navigation";
import { Navigation } from "swiper";
import { FaLessThan, FaGreaterThan } from 'react-icons/fa'
Here are the refs for the changing of the slides.
const ServiceSlider = () => {
const navigationPrevRef = useRef(null);
const navigationNextRef = useRef(null);
return (
<div className='mt-24'>
<div className='relative z-10 flex items-baseline space-x-3 h-[180rem] lg:h-auto'>
This is the previous button.
<div ref={navigationPrevRef}>
<button className='btn text-xs lg:text-base rounded-full bg-black'><FaLessThan /></button>
</div>
<Swiper
spaceBetween={10}
modules={[Navigation]}
onBeforeInit={(swiper) => {
swiper.params.navigation.prevEl = navigationPrevRef.current;
swiper.params.navigation.nextEl = navigationNextRef.current;
}}
navigation={{
prevEl: navigationPrevRef.current,
nextEl: navigationNextRef.current,
}}
slidesPerView={1}
className="mySwiper"
>
<SwiperSlide>
<Quality></Quality>
</SwiperSlide>
<SwiperSlide>
<Art2D></Art2D>
</SwiperSlide>
<SwiperSlide>
<Art3D></Art3D>
</SwiperSlide>
</Swiper>
Here is the next button.
<div ref={navigationNextRef}>
<button className='btn text-xs lg:text-base rounded-full bg-black'><FaGreaterThan /></button>
</div>
</div>
</div>
);
};
export default ServiceSlider;
Navbar menu items are shown in mobile view and function correctly but in the desktop mode, they are hidden. I am a beginner in tailwind so appreciate some detailed answers Thank you!
I am using react with typescript.
code:-
import React, { useState } from 'react'
import CloseIcon from '../../assets/icons/close.svg';
import HamburgerMenuIcon from '../../assets/icons/hamburger-menu.svg';
export const HeaderBar = () => {
const [toggle, setToggle] = useState(false);
const links = [
{ name: "Home", link: "/" },
{ name: "About", link: "/" },
{ name: "Work", link: "/" },
{ name: "Experience", link: "/" },
{ name: "Contact", link: "/" }
]
return (
<div className='shadow-md w-full fixed top-0 left-0'>
<div className='md:flex items:center justify-between bg-white bg-opacity-10 backdrop-filter backdrop-blur-sm py-4 md:px-10 px-7'>
<div className='font-bold text2xl cursor-pointer flex items-center font-[poppins] text-gray-800 bg-slate-900'>
<span className='text-3xl'></span>
Designer
</div>
<div className='text-3xl absolute right-8 top-6 md:hidden cursor-pointer' onClick={() => { setToggle(!toggle) }}>
<img src={toggle ? CloseIcon : HamburgerMenuIcon} height={20} width={20} />
</div>
<ul className={` bg-white md:flex md:items-center md:pb-0 absolute md:static md:z-auto z-[-1] left-0 w-full md:w-auto bg-red-600 md:pl-0 pl-4 transition-all duration-500 ease-in-out ${toggle ? 'top-[55px] opacity-100' : '-top-[140px] opacity-0'} `}>
{links.map((link) => {
return (
<li key={link.name} className='bg-white bg-opacity-10 backdrop-filter backdrop-blur-sm md:ml-8 text-md md:my-0 my-4 '>
<a href={link.link} className='text-gray-800 hover:text-gray-400 duration:500'>
{link.name}
</a>
</li>
)
})}
</ul>
</div>
</div >
);
};
I found the bug here
${toggle ? 'top-[55px] opacity-100' : '-top-[140px] opacity-0'} `}>
should change to
${!toggle ? 'top-[55px] opacity-100' : '-top-[140px] opacity-0'} `}>