feat(landing): slicing faq section
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms'
|
||||||
|
import { Accordion, AccordionOptions } from '@imphnen-frontend-service/ui/molecules'
|
||||||
|
import { FC, useRef } from 'react'
|
||||||
|
import { motion, useInView, Variants } from 'framer-motion'
|
||||||
|
import { cn, For } from '@imphnen-frontend-service/utils'
|
||||||
|
|
||||||
|
const FAQ_DATA: AccordionOptions[] = [
|
||||||
|
{ title: 'Apakah saya bisa memilih mentor sendiri?', description: 'Tentu! Kamu bisa memilih mentor yang sesuai dengan kebutuhan dan level skill kamu.' },
|
||||||
|
{ title: 'Apakah mentoring ini cocok untuk pemula?', description: 'Yap! Baik newbie maupun seasoned dev bisa menemukan mentor yang pas untuk mempercepat progres belajarnya.' },
|
||||||
|
{ title: 'Apakah ini seperti sekolah coding biasa?', description: 'Nope! Ini lebih dari sekadar kursus-kursus AI guide dan mentor berpengalaman, kamu mendapatkan pengalaman belajar yang jauh lebih personal dan efektif.' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const FAQSection: FC = () => {
|
||||||
|
const ref = useRef(null)
|
||||||
|
const isInView = useInView(ref, { once: true, amount: 0.2 })
|
||||||
|
|
||||||
|
const containerVariants: Variants = {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.1,
|
||||||
|
delayChildren: 0.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const childVariants: Variants = {
|
||||||
|
hidden: { opacity: 0, y: 20 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
duration: 0.4,
|
||||||
|
ease: [0.25, 0.46, 0.45, 0.94],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section ref={ref} className="mx-auto py-4 px-8 md:py-8 md:px-[60px] lg:px-20 md:pb-8">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="mb-6 flex justify-center md:mb-10"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
variant="bordered"
|
||||||
|
className="h-auto px-3 py-2 font-semibold bg-primary-100 md:text-base md:px-5 md:py-2.5 lg:text-2xl lg:px-6 lg:py-3"
|
||||||
|
>
|
||||||
|
Frequently Ask Question
|
||||||
|
</Button>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"grid max-w-7xl mx-auto gap-y-7 divide-y-2 divide-primary-200",
|
||||||
|
"md:grid-cols-11 md:gap-x-10 md:divide-y-0 md:divide-x-2 lg:grid-cols-7 lg:gap-x-[72px]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<motion.h1
|
||||||
|
className={cn(
|
||||||
|
"pb-7 text-2xl font-semibold text-primary-500 text-center leading-none",
|
||||||
|
"md:pe-10 md:pb-0 md:text-start md:col-span-5 lg:text-4xl lg:col-span-3 lg:pe-[72px]"
|
||||||
|
)}
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
>
|
||||||
|
Console log for new developers
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className="flex flex-col gap-4 md:col-span-6 lg:col-span-4"
|
||||||
|
variants={containerVariants}
|
||||||
|
animate={isInView ? "visible" : "hidden"}
|
||||||
|
>
|
||||||
|
<For data={FAQ_DATA}>
|
||||||
|
{(props) => (
|
||||||
|
<motion.div key={props.title} variants={childVariants}>
|
||||||
|
<Accordion {...props} />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { FC, ReactElement } from 'react';
|
|||||||
import { HeroSection } from './_components/hero-section';
|
import { HeroSection } from './_components/hero-section';
|
||||||
import { WhatWeOfferSection } from './_components/what-we-offer-section';
|
import { WhatWeOfferSection } from './_components/what-we-offer-section';
|
||||||
import { TestimonialSection } from './_components/testimonial-section';
|
import { TestimonialSection } from './_components/testimonial-section';
|
||||||
|
import { FAQSection } from './_components/faq-section';
|
||||||
|
|
||||||
export const Components: FC = (): ReactElement => {
|
export const Components: FC = (): ReactElement => {
|
||||||
return (
|
return (
|
||||||
@@ -9,6 +10,7 @@ export const Components: FC = (): ReactElement => {
|
|||||||
<HeroSection />
|
<HeroSection />
|
||||||
<WhatWeOfferSection />
|
<WhatWeOfferSection />
|
||||||
<TestimonialSection />
|
<TestimonialSection />
|
||||||
|
<FAQSection />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { cn, Show } from "@imphnen-frontend-service/utils"
|
||||||
|
import { DetailedHTMLProps, FC, HTMLAttributes, useState } from "react"
|
||||||
|
import { AnimatePresence, motion, Variants } from "framer-motion"
|
||||||
|
import { MinusOutlined, PlusOutlined } from "@ant-design/icons"
|
||||||
|
|
||||||
|
export type AccordionOptions = {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AccordionProps = DetailedHTMLProps<
|
||||||
|
HTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
> & AccordionOptions & {
|
||||||
|
titleClassName?: string
|
||||||
|
contentClassName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Accordion: FC<AccordionProps> = ({ title, description, className, titleClassName, contentClassName, ...rest }) => {
|
||||||
|
const [expand, setExpand] = useState(false)
|
||||||
|
|
||||||
|
const variants: Variants = {
|
||||||
|
expand: { opacity: 1, y: 0, marginBottom: 16, height: 'auto' },
|
||||||
|
collapse: { opacity: 0, y: -10, marginBottom: 0, height: 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("bg-white rounded-lg shadow cursor-pointer", className)} {...rest}>
|
||||||
|
<div className="px-5 py-4 select-none flex justify-between items-center gap-x-2" onClick={() => setExpand(prev => !prev)}>
|
||||||
|
<h4 className={cn("text-primary-500 font-semibold text-lg leading-tight md:text-xl", titleClassName)}>{title}</h4>
|
||||||
|
<div className={cn("text-primary-500 transition-all duration-300", expand && "rotate-90")}>
|
||||||
|
<Show condition={expand} fallback={<PlusOutlined />}>
|
||||||
|
<MinusOutlined className="rotate-90" />
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AnimatePresence>
|
||||||
|
{expand && (
|
||||||
|
<motion.p
|
||||||
|
className={cn("px-5 text-neutral-500 text-sm font-medium cursor-text md:text-base", contentClassName)}
|
||||||
|
variants={variants}
|
||||||
|
initial="collapse"
|
||||||
|
animate="expand"
|
||||||
|
exit="collapse"
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</motion.p>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './accordion';
|
||||||
@@ -4,3 +4,4 @@ export * from './input-field';
|
|||||||
export * from './pagination';
|
export * from './pagination';
|
||||||
export * from './modal/modal';
|
export * from './modal/modal';
|
||||||
export * from './stepper';
|
export * from './stepper';
|
||||||
|
export * from './accordion';
|
||||||
Reference in New Issue
Block a user