Carousel added

This commit is contained in:
Jacob Delgado 2024-10-29 04:19:51 -07:00
parent d73e8d9cbd
commit 353f716f53
14 changed files with 509 additions and 186 deletions

Binary file not shown.

View File

@ -16,6 +16,8 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"dotenv": "^16.4.5",
"embla-carousel-autoplay": "^8.3.0",
"embla-carousel-react": "^8.3.0",
"framer-motion": "^11.11.10",
"lucide-react": "^0.427.0",
"next": "14.1.4",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -188,5 +188,25 @@
"inStock": "yes",
"favorite": "no",
"description": "Bear-Relax"
},
{
"id": 23,
"name": "Mockup 132",
"photo": "batch1/Mockup 132.png",
"price": 25,
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
"inStock": "yes",
"favorite": "no",
"description": "Running-man"
},
{
"id": 24,
"name": "Mockup 133",
"photo": "batch1/Mockup 133.png",
"price": 25,
"size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL",
"inStock": "yes",
"favorite": "no",
"description": "Running-woman"
}
]

BIN
frontend/public/payment.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,77 +1,77 @@
//pages/index.tsx
import { useState } from "react";
import styles from "@/styles/Home.module.css";
import ClientPortal from "../components/ClientPortal/ClientPortal";
export default function Home() {
const [showPortal, setShowPortal] = useState(false);
const handleModal = () => {
setShowPortal(!showPortal);
};
// import { useState } from "react";
// import styles from "@/styles/Home.module.css";
// import ClientPortal from "../components/ClientPortal/ClientPortal";
// export default function Home() {
// const [showPortal, setShowPortal] = useState(false);
// const handleModal = () => {
// setShowPortal(!showPortal);
// };
return (
<>
<main className={`${styles.main}`}>
<h1>Implementation portal in next JS</h1>
<button onClick={handleModal} className={styles.btn}>
Read Blog
</button>
</main>
<ClientPortal
selector="myportal"
show={showPortal}
>
<div className={styles.overflow}>
<div className={styles.modal}>
<div className={styles.header}>
<h4>Lorem Ipsum</h4>
<button onClick={handleModal} className={styles.removeButton}>
{/* <button className={styles.removeButton}> */}
<svg
xmlns="http://www.w3.org/2000/svg"
height="1em"
viewBox="0 0 384 512"
>
<path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z" />
</svg>
</button>
</div>
<div className={styles.body}>
<p>
Basically, React Portal is a powerful technique to create a
first-class way to render children into a DOM (Document object
modal) Node from outside of their parent component hierarchy.
</p>
<div className={styles.reactontent}>
<h4>Industry&apos;s stand 1960s with the release</h4>
<p>
has been the industry&apos;s stand 1960s with the release of
Letraset sheets containing Lorem Ipsum passages, and more
recently with desktop publishing software like Aldus PageMaker
including versions of Lorem Ipsum.
</p>
</div>
<div className={styles.reactontent}>
<p>
Lorem Ipsum is simply dummy text of the printing It has
survived not only five centuries, but also the leap into
electronic typesetting, remaining essentially unchanged. It
was popularised in the 1960s with the release of Letraset
sheets containing Lorem Ipsum passages, and more recently
</p>
</div>
<div className={styles.reactontent}>
<p>
Lorem Ipsum is simply dummy text of the printing and
typesetting industry. Lorem Ipsum has been the industry&apos;s
standard dummy text ever since the 1500s, when an unknown
printer took a galley of type and Lorem Ipsum.
</p>
</div>
</div>
</div>
</div>
</ClientPortal>
</>
);
}
// return (
// <>
// <main className={`${styles.main}`}>
// <h1>Implementation portal in next JS</h1>
// <button onClick={handleModal} className={styles.btn}>
// Read Blog
// </button>
// </main>
// <ClientPortal
// selector="myportal"
// show={showPortal}
// >
// <div className={styles.overflow}>
// <div className={styles.modal}>
// <div className={styles.header}>
// <h4>Lorem Ipsum</h4>
// <button onClick={handleModal} className={styles.removeButton}>
// {/* <button className={styles.removeButton}> */}
// <svg
// xmlns="http://www.w3.org/2000/svg"
// height="1em"
// viewBox="0 0 384 512"
// >
// <path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z" />
// </svg>
// </button>
// </div>
// <div className={styles.body}>
// <p>
// Basically, React Portal is a powerful technique to create a
// first-class way to render children into a DOM (Document object
// modal) Node from outside of their parent component hierarchy.
// </p>
// <div className={styles.reactontent}>
// <h4>Industry&apos;s stand 1960s with the release</h4>
// <p>
// has been the industry&apos;s stand 1960s with the release of
// Letraset sheets containing Lorem Ipsum passages, and more
// recently with desktop publishing software like Aldus PageMaker
// including versions of Lorem Ipsum.
// </p>
// </div>
// <div className={styles.reactontent}>
// <p>
// Lorem Ipsum is simply dummy text of the printing It has
// survived not only five centuries, but also the leap into
// electronic typesetting, remaining essentially unchanged. It
// was popularised in the 1960s with the release of Letraset
// sheets containing Lorem Ipsum passages, and more recently
// </p>
// </div>
// <div className={styles.reactontent}>
// <p>
// Lorem Ipsum is simply dummy text of the printing and
// typesetting industry. Lorem Ipsum has been the industry&apos;s
// standard dummy text ever since the 1500s, when an unknown
// printer took a galley of type and Lorem Ipsum.
// </p>
// </div>
// </div>
// </div>
// </div>
// </ClientPortal>
// </>
// );
// }

View File

@ -10,6 +10,7 @@ import styles from "@/styles/Home.module.css";
import { Poppins } from "next/font/google";
import { sendEmail } from "./actions/sendEmail";
import { sendEmails } from "@/lib/mail.utils";
import ProductCarousel from '@/components/ProductCarousel/ProductCarousel';
const Popp = Poppins({ weight: "400", subsets: ["latin"] });
@ -75,44 +76,9 @@ export default function Home() {
}
return (
<main className="flex min-h-screen flex-col px-20">
{/* Title */}
<div className="text-4xl w-full justify-center text-center items-center flex flex-col gap-9 py-12 font-medium text-orange-600">
{/* <h1 className={`${Popp.className} text-white text-5xl`}>
Kevo&#39;s Attire
</h1> */}
<div
className={`${Popp.className} text-orange-600 text-center flex flex-col gap-5`}
>
<div>
<p>
<span className="text-blue-500">Youth:</span>{" "}
<span className="text-orange-600">
S <span className="text-gray-600">|</span> M{" "}
<span className="text-gray-600">|</span> L
</span>
</p>
<p>
<span className="text-blue-500">Adult:</span>{" "}
<span className="text-orange-600">
S <span className="text-gray-600">|</span> M{" "}
<span className="text-gray-600">|</span> L{" "}
<span className="text-gray-600">|</span> XL{" "}
<span className="text-gray-600">|</span> 2XL
</span>
</p>
</div>
<div
className={`${Popp.className} text-gray-400 text-center gap-1 flex flex-col text-2xl`}
>
<p className="">
Small - Large: <span className="text-orange-600 pl-4">$25</span>
</p>
<p className="">
XL - 2XL: <span className="text-orange-600 pl-4">$27</span>
</p>
</div>
</div>
<main className="flex min-h-screen flex-col px-20 gap-y-6">
<div className="sm:max-w-max md:w-full">
<ProductCarousel/>
</div>
{/* Body */}

View File

@ -2,21 +2,22 @@ import React from "react";
import { FaFacebookF } from "react-icons/fa";
import { FaInstagram } from "react-icons/fa";
import { FaTiktok } from "react-icons/fa";
import Image from "next/image";
const Footer = () => {
return (
<div className="footer text-white flex flex-col gap-8 px-16 pb-2">
<div className="top">
<div className="item">
<h1>Categories</h1>
<div className="footer text-white mb-[20px] mt-[100px] mx-[200px]">
<div className="top flex gap-[50px]">
<div className="item flex-1 flex flex-col gap-[10px] text-justify text-sm text-gray-400">
<h1 className="text-xl font-bold text-gray-200">Categories</h1>
<span>Women</span>
<span>Men</span>
<span>Shoes</span>
<span>Accessories</span>
<span>New Arrivals</span>
</div>
<div className="item">
<h1>Links</h1>
<div className="item flex-1 flex flex-col gap-[10px] text-justify text-sm text-gray-400">
<h1 className="text-xl font-bold text-gray-200">Links</h1>
<span>FAQ</span>
<span>Pages</span>
<span>Stores</span>
@ -31,12 +32,24 @@ const Footer = () => {
<FaInstagram />
<FaTiktok />
</div>
<div className="bottom text-xs flex flex-col gap-4">
<span>
All items are sold under the assumption that they are original works and not copyrighted under another company.
<div className="bottom text-xs flex gap-4 items-center justify-between mt-[50px]">
<div className="flex flex-col gap-4">
<span className="flex max-w-[320px]">
All items are sold under the beleif that they are original works and
not copyrighted under another company.
</span>
<span>© 2024 - Kevos Attire LLC. All rights reserved. </span>
</div>
<div className="relative flex w-1/2 h-[50px]">
<Image
src="/payment.png"
fill
style={{ objectFit: "cover", objectPosition: "center" }}
className="hidden md:block"
alt="payment systems"
/>
</div>
</div>
</div>
);
};

View File

@ -1,6 +1,6 @@
"use client";
import { motion } from "framer-motion";
import { useState } from "react";
import { useState, useEffect } from "react";
import { useMediaQuery } from "@/util/useMediaQuery";
import { FiShoppingCart } from "react-icons/fi";
import Image from "next/image";
@ -74,10 +74,11 @@ export default function Nav() {
<a href="/">Home</a>
<a href="/products">Shirts</a>
<a href="/about">About</a>
{/* <a href="/contact">Contact</a> */}
<a className="flex relative items-center justify-center" href="/cart">
<FiShoppingCart className="text-lg lg:text-xl" />
<span className="absolute text-white text-xs w-5 h-5 rounded-[50%] bg-slate-400 -top-2.5 -right-2.5 flex items-center justify-center">0</span>
<span className="absolute text-white text-xs w-5 h-5 rounded-[50%] bg-slate-400 -top-2.5 -right-2.5 flex items-center justify-center">
0
</span>
</a>
</div>
)}
@ -129,9 +130,6 @@ export default function Nav() {
<motion.a variants={itemMotion} href="/cart">
Cart
</motion.a>
{/* <motion.a variants={itemMotion} href="/contact">
Contact
</motion.a> */}
</motion.div>
</motion.div>
)}

View File

@ -0,0 +1,98 @@
import * as React from "react"
import Autoplay from "embla-carousel-autoplay"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel"
import Image from 'next/image';
const ProductCarousel = () => {
const plugin = React.useRef(
Autoplay({ delay: 3000, stopOnInteraction: true })
)
return (
<Carousel
plugins={[plugin.current]}
className="w-full max-w-full "
onMouseEnter={plugin.current.stop}
onMouseLeave={plugin.current.reset}
>
<CarouselContent className='-ml-4'>
<CarouselItem className="md:basis-1/2 lg:basis-1/3 xl:basis-1/4 2xl:basis-1/5 pl-4">
<Image
src="/batch1/Mockup 126.png"
width={300}
height={300}
alt="shirt 3"
/>
</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3 xl:basis-1/4 2xl:basis-1/5 pl-4">
<Image
src="/batch1/Mockup 18.png"
width={300}
height={300}
alt="shirt 6"
/>
</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3 xl:basis-1/4 2xl:basis-1/5 pl-4">
<Image
src="/batch1/Mockup 73.png"
width={300}
height={300}
alt="shirt 10"
/>
</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3 xl:basis-1/4 2xl:basis-1/5 pl-4">
<Image
src="/batch1/Mockup 112.png"
width={300}
height={300}
alt="shirt 3"
/>
</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3 xl:basis-1/4 2xl:basis-1/5 pl-4">
<Image
src="/batch1/Mockup 4.png"
width={300}
height={300}
alt="shirt 6"
/>
</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3 xl:basis-1/4 2xl:basis-1/5 pl-4">
<Image
src="/batch1/Mockup 97.png"
width={300}
height={300}
alt="shirt 10"
/>
</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3 xl:basis-1/4 2xl:basis-1/5 pl-4">
<Image
src="/batch1/Mockup 132.png"
width={300}
height={300}
alt="shirt 10"
/>
</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3 xl:basis-1/4 2xl:basis-1/5 pl-4">
<Image
src="/batch1/Mockup 133.png"
width={300}
height={300}
alt="shirt 10"
/>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
)
}
export default ProductCarousel

View File

@ -1,10 +1,11 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "../../lib/utils"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {

View File

@ -0,0 +1,262 @@
"use client"
import * as React from "react"
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
const Carousel = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & CarouselProps
>(
(
{
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
},
ref
) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
return
}
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
React.useEffect(() => {
if (!api || !setApi) {
return
}
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) {
return
}
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
ref={ref}
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
)
Carousel.displayName = "Carousel"
const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel()
return (
<div ref={carouselRef} className="overflow-hidden">
<div
ref={ref}
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props}
/>
</div>
)
})
CarouselContent.displayName = "CarouselContent"
const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel()
return (
<div
ref={ref}
role="group"
aria-roledescription="slide"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props}
/>
)
})
CarouselItem.displayName = "CarouselItem"
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
)
})
CarouselPrevious.displayName = "CarouselPrevious"
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
)
})
CarouselNext.displayName = "CarouselNext"
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
}

View File

@ -1,61 +1,24 @@
import { useState } from 'react'
import { useEffect, useState } from 'react';
import { useIsomorphicLayoutEffect } from 'usehooks-ts'
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState<boolean>(false);
type UseMediaQueryOptions = {
defaultValue?: boolean
initializeWithValue?: boolean
}
useEffect(() => {
if (typeof window === 'undefined') return; // Ensures no window access on the server
const IS_SERVER = typeof window === 'undefined'
const matchMedia = window.matchMedia(query);
export function useMediaQuery(
query: string,
{
defaultValue = false,
initializeWithValue = true,
}: UseMediaQueryOptions = {},
): boolean {
const getMatches = (query: string): boolean => {
if (IS_SERVER) {
return defaultValue
}
return window.matchMedia(query).matches
}
const handleChange = () => setMatches(matchMedia.matches);
const [matches, setMatches] = useState<boolean>(() => {
if (initializeWithValue) {
return getMatches(query)
}
return defaultValue
})
// Handles the change event of the media query.
function handleChange() {
setMatches(getMatches(query))
}
useIsomorphicLayoutEffect(() => {
const matchMedia = window.matchMedia(query)
// Triggered at the first client-side load and if query changes
handleChange()
// Use deprecated `addListener` and `removeListener` to support Safari < 14 (#135)
if (matchMedia.addListener) {
matchMedia.addListener(handleChange)
} else {
matchMedia.addEventListener('change', handleChange)
}
// Set initial value
handleChange();
// Add and remove event listeners for changes
matchMedia.addEventListener('change', handleChange);
return () => {
if (matchMedia.removeListener) {
matchMedia.removeListener(handleChange)
} else {
matchMedia.removeEventListener('change', handleChange)
}
}
}, [query])
matchMedia.removeEventListener('change', handleChange);
};
}, [query]);
return matches
return matches;
}