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

feat: Create Content Section #31

Open
wants to merge 4 commits into
base: development
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
"@types/react-dom": "18.2.22",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"embla-carousel": "^8.0.0",
"embla-carousel-react": "^8.0.0",
"eslint": "8.57.0",
"eslint-config-next": "14.1.4",
"next": "14.1.4",
"next-themes": "^0.3.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^5.0.1",
Expand Down
Binary file removed public/images/contentCardDefault.png
Binary file not shown.
Binary file added public/images/content_card_gopportunities.webp
Binary file not shown.
Binary file added public/images/content_card_guia_dev.webp
Binary file not shown.
Binary file added public/images/content_card_repowars.webp
Binary file not shown.
23 changes: 14 additions & 9 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { Navbar, Button, NavbarMobile, SocialMedia } from "@/components"
import { ContentSection } from "@/components/sections/content/ContentSection"
import { ThemeSwitcherSkeleton } from "@/components/theme-switcher/ThemeSwitcher"
import dynamic from "next/dynamic"

const ThemeSwitcher = dynamic(
() => import("@/components/theme-switcher/ThemeSwitcher"),
{
ssr: false,
loading: () => <ThemeSwitcherSkeleton />,
}
)

export default function Home() {
return (
<main className="flex flex-col items-center justify-center h-screen gap-8 p-24 bg-surface-primary">
<Navbar />

<NavbarMobile />

<Button>Click me</Button>

<SocialMedia />
<main className="transition-colors bg-surface-primary">
<ThemeSwitcher />
<ContentSection />
</main>
)
}
43 changes: 0 additions & 43 deletions src/components/card/ContentCard.tsx

This file was deleted.

90 changes: 90 additions & 0 deletions src/components/carousel/ArrowButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, {
PropsWithChildren,
useCallback,
useEffect,
useState,
} from "react"
import type { EmblaCarouselType } from "embla-carousel"
import { BsArrowRight, BsArrowRightShort } from "react-icons/bs"
import { FaArrowLeft, FaArrowRight } from "react-icons/fa"

type UsePrevNextButtonsType = {
prevBtnDisabled: boolean
nextBtnDisabled: boolean
onPrevButtonClick: () => void
onNextButtonClick: () => void
}

export const usePrevNextButtons = (
emblaApi: EmblaCarouselType | undefined
): UsePrevNextButtonsType => {
const [prevBtnDisabled, setPrevBtnDisabled] = useState(true)
const [nextBtnDisabled, setNextBtnDisabled] = useState(true)

const onPrevButtonClick = useCallback(() => {
if (!emblaApi) return
emblaApi.scrollPrev()
}, [emblaApi])

const onNextButtonClick = useCallback(() => {
if (!emblaApi) return
emblaApi.scrollNext()
}, [emblaApi])

const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
setPrevBtnDisabled(!emblaApi.canScrollPrev())
setNextBtnDisabled(!emblaApi.canScrollNext())
}, [])

useEffect(() => {
if (!emblaApi) return

onSelect(emblaApi)
emblaApi.on("reInit", onSelect)
emblaApi.on("select", onSelect)
}, [emblaApi, onSelect])

return {
prevBtnDisabled,
nextBtnDisabled,
onPrevButtonClick,
onNextButtonClick,
}
}

type PropType = PropsWithChildren<
React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>
>

export const PrevButton: React.FC<PropType> = (props) => {
const { children, ...restProps } = props

return (
<button
className="p-2 transition-colors border rounded-full border-on-primary-light dark:border-on-primary-dark text-on-primary hover:bg-surface-primary-dark/10 dark:hover:bg-surface-primary-light/10"
type="button"
{...restProps}
>
<FaArrowLeft className="w-5 h-5" />
{children}
</button>
)
}

export const NextButton: React.FC<PropType> = (props) => {
const { children, ...restProps } = props

return (
<button
className="p-2 transition-colors border rounded-full border-on-primary-light dark:border-on-primary-dark text-on-primary hover:bg-surface-primary-dark/10 dark:hover:bg-surface-primary-light/10"
type="button"
{...restProps}
>
<FaArrowRight className="w-5 h-5" />
{children}
</button>
)
}
92 changes: 92 additions & 0 deletions src/components/carousel/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"use client"
import React, { useCallback, useEffect, useRef } from "react"
import type {
EmblaCarouselType,
EmblaEventType,
EmblaOptionsType,
} from "embla-carousel"
import useEmblaCarousel from "embla-carousel-react"
import { DotButton, useDotButton } from "./DotButton"
import { PrevButton, NextButton, usePrevNextButtons } from "./ArrowButtons"
import { cn } from "@/lib/utils"
import { useEmblaWithScaleTween } from "./hooks/useEmblaWithScaleTween"

interface CarouselProps extends React.HTMLAttributes<HTMLDivElement> {
options?: EmblaOptionsType
}

const Carousel: React.FC<CarouselProps> = ({
children,
options,
className,
...props
}) => {
const {
emblaRef,
scrollSnaps,
selectedIndex,
onDotButtonClick,
onNextButtonClick,
onPrevButtonClick,
prevBtnDisabled,
nextBtnDisabled,
} = useEmblaWithScaleTween(options)

return (
<section
aria-roledescription="carousel"
className={cn("relative", className)}
{...props}
>
<div
className={
"overflow-hidden " +
"lg:before:absolute lg:before:top-0 lg:before:bottom-0 lg:before:left-0 lg:before:w-1/6 lg:before:z-10 " +
"lg:after:absolute lg:after:top-0 lg:after:bottom-0 lg:after:right-0 lg:after:w-1/6 " +
"lg:before:bg-gradient-to-r lg:before:from-surface-primary-light dark:lg:before:from-surface-primary-dark " +
"lg:after:bg-gradient-to-l lg:after:from-surface-primary-light dark:lg:after:from-surface-primary-dark"
}
ref={emblaRef}
>
<div className="flex 2xl:w-fit 2xl:mx-auto [backface-visibility:hidden] touch-pan-y">
{React.Children.map(children, (child) => (
<div
className={cn(
"flex-[0_0_87.5%] md:flex-[0_0_40%] lg:flex-[0_0_30%] ml-6",
className
)}
>
{child}
</div>
))}
</div>
</div>

<div className="flex items-center gap-5 mx-auto mt-9 w-fit">
<PrevButton onClick={onPrevButtonClick} disabled={prevBtnDisabled} />

<div className="flex gap-2.5 items-center">
{scrollSnaps.map((_, index) => (
<DotButton
key={index}
onClick={() => onDotButtonClick(index)}
className={cn(
"w-4 h-4 bg-on-primary-dark border border-[#353544] rounded-[0.25rem] transition-all",
{
"border-4 border-palette-blue-600 w-6 h-6":
index === selectedIndex,
}
)}
/>
))}
</div>

<NextButton onClick={onNextButtonClick} disabled={nextBtnDisabled} />
</div>
</section>
)
}

Carousel.displayName = "Carousel"

export { Carousel }
69 changes: 69 additions & 0 deletions src/components/carousel/DotButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, {
PropsWithChildren,
useCallback,
useEffect,
useState,
} from "react"
import type { EmblaCarouselType } from "embla-carousel"

type UseDotButtonType = {
selectedIndex: number
scrollSnaps: number[]
onDotButtonClick: (index: number) => void
}

export const useDotButton = (
emblaApi: EmblaCarouselType | undefined
): UseDotButtonType => {
const [selectedIndex, setSelectedIndex] = useState(0)
const [scrollSnaps, setScrollSnaps] = useState<number[]>([])

const onDotButtonClick = useCallback(
(index: number) => {
if (!emblaApi) return
emblaApi.scrollTo(index)
},
[emblaApi]
)

const onInit = useCallback((emblaApi: EmblaCarouselType) => {
setScrollSnaps(emblaApi.scrollSnapList())
}, [])

const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
setSelectedIndex(emblaApi.selectedScrollSnap())
}, [])

useEffect(() => {
if (!emblaApi) return

onInit(emblaApi)
onSelect(emblaApi)
emblaApi.on("reInit", onInit)
emblaApi.on("reInit", onSelect)
emblaApi.on("select", onSelect)
}, [emblaApi, onInit, onSelect])

return {
selectedIndex,
scrollSnaps,
onDotButtonClick,
}
}

type PropType = PropsWithChildren<
React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>
>

export const DotButton: React.FC<PropType> = (props) => {
const { children, ...restProps } = props

return (
<button type="button" {...restProps}>
{children}
</button>
)
}
Loading