Skip to content

Commit

Permalink
Add shared components CardsSlider, CollapsibleContent, LottieCarousel…
Browse files Browse the repository at this point in the history
…, Motions, Text
  • Loading branch information
gg-1414 committed Nov 13, 2024
1 parent b95e2c4 commit 1a645a0
Show file tree
Hide file tree
Showing 9 changed files with 969 additions and 0 deletions.
132 changes: 132 additions & 0 deletions src/components/shared/CardsSlider.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
@import "~@/scss/solutions/_variables.scss";

.Title {
font-size: 40px;
font-weight: 700;
line-height: 1.04;
letter-spacing: -0.01em;
text-align: center;
color: var(--white);
text-transform: capitalize;
margin: 0;
padding: 64px 24px 0;

strong {
background: var(--gradient-2);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}

@include breakpoint(md) {
font-size: 56px;
padding-top: 80px;
}

@include breakpoint(lg) {
font-size: 64px;
padding-top: 128px;
}
}

.Carousel {
position: relative;
width: 100%;
}

.CarouselContainer {
display: flex;
width: 100%;
padding: {
top: 64px;
bottom: 64px;
}

overflow-x: scroll;
overscroll-behavior-x: auto;
scroll-behavior: smooth;
scrollbar-width: none; /*FireFox*/
-ms-overflow-style: -ms-autohiding-scrollbar; /*IE10+*/

/*Chrome, Safari, Edge*/
&::-webkit-scrollbar {
display: none;
}

@include breakpoint(md) {
padding: {
top: 80px;
bottom: 40px;
}
}

@include breakpoint(lg) {
padding: {
top: 96px;
}
}
}

.Cards {
display: flex;
justify-content: start;
gap: 16px;
padding-left: 16px;
max-width: 80rem;
margin: 0 auto;
}

.CardWrapper {
border-radius: 1.5rem;
box-sizing: content-box;

&:last-child {
padding-right: 3%;

@include breakpoint(md) {
padding-right: 10%;
}

@include breakpoint(lg) {
padding-right: 33%;
}
}

@include breakpoint(md) {
> * {
width: 22rem;
height: 100%;
}
}
}

.Arrows {
display: flex;
justify-content: center;
gap: 16px;
padding-bottom: 64px;

button {
position: relative;
z-index: 40;
height: 40px;
width: 40px;
border-radius: 50%;
background-color: var(--grey-300);
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.1s ease;
transition:
background 0.3s ease-in-out,
transform 0.15s $easeInOutQuart;

&:disabled {
background-color: var(--grey-500);

svg path {
stroke: var(--grey-300);
}
}
}
}
126 changes: 126 additions & 0 deletions src/components/shared/CardsSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { useEffect, useState, useRef, ReactNode } from "react";
import { motion } from "framer-motion";
import { Trans } from "next-i18next";
import { useInView } from "react-intersection-observer";
import classNames from "classnames";

import CaretIcon from "@/components/icons/Caret";
import { AnimatedText } from "@/components/shared/Text";

import styles from "./CardsSlider.module.scss";

interface CarouselProps {
items: Element[] | ReactNode[];
titleKey?: string;
initialScroll?: number;
id?: string;
className?: string;
carouselClassName?: string;
cardsClassName?: string;
cardWrapperClassName?: string;
}

const CardsSlider = ({
items,
titleKey,
initialScroll = 0,
id = "",
className = "",
carouselClassName = "",
cardsClassName = "",
cardWrapperClassName = "",
}: CarouselProps) => {
const carouselRef = useRef<HTMLDivElement>(null);
const [canScrollLeft, setCanScrollLeft] = useState(false);
const [canScrollRight, setCanScrollRight] = useState(true);

useEffect(() => {
if (carouselRef.current) {
carouselRef.current.scrollLeft = initialScroll;
checkScrollability();
}
}, [initialScroll]);

const checkScrollability = () => {
if (carouselRef.current) {
const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current;
setCanScrollLeft(scrollLeft > 0);
setCanScrollRight(scrollLeft < scrollWidth - clientWidth);
}
};

const scrollLeft = () => {
if (carouselRef.current) {
carouselRef.current.scrollBy({ left: -304, behavior: "smooth" });
}
};

const scrollRight = () => {
if (carouselRef.current) {
carouselRef.current.scrollBy({ left: 304, behavior: "smooth" });
}
};

const { ref, inView } = useInView({
triggerOnce: true,
threshold: 0.5,
});

return (
<div ref={ref} id={id} className={className}>
{titleKey && (
<AnimatedText element="h2" as="heading" className={styles.Title}>
<Trans i18nKey={titleKey} />
</AnimatedText>
)}

<div className={styles.Carousel}>
<div
className={classNames(styles.CarouselContainer, carouselClassName)}
ref={carouselRef}
onScroll={checkScrollability}
>
<div className={classNames(styles.Cards, cardsClassName)}>
{items.map((item: any, index: number) => (
<motion.div
initial={{
opacity: 0,
y: 20,
}}
animate={
inView
? {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
delay: 0.2 * index,
ease: "easeOut",
once: true,
},
}
: {}
}
key={"card" + index}
className={classNames(styles.CardWrapper, cardWrapperClassName)}
>
{item}
</motion.div>
))}
</div>
</div>

<div className={styles.Arrows}>
<button onClick={scrollLeft} disabled={!canScrollLeft}>
<CaretIcon direction="left" />
</button>
<button onClick={scrollRight} disabled={!canScrollRight}>
<CaretIcon />
</button>
</div>
</div>
</div>
);
};

export default CardsSlider;
30 changes: 30 additions & 0 deletions src/components/shared/CollapsibleContent.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@import "../../scss/solutions/_variables.scss";

.CollapsibleTrigger {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
gap: 16px;
font-size: 18px;
font-weight: 700;
letter-spacing: -0.01em;
line-height: 1.16;
color: var(--grey-200);
text-align: left;
margin-bottom: 0;

p {
margin: 0;
}
}

.IconArrowDown {
transform: rotate(180deg);
}

@include breakpoint(md) {
.CollapsibleTrigger {
justify-content: flex-start;
}
}
84 changes: 84 additions & 0 deletions src/components/shared/CollapsibleContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useState, ReactNode } from "react";
import { motion, AnimatePresence } from "framer-motion";
import * as Collapsible from "@radix-ui/react-collapsible";
import styles from "./CollapsibleContent.module.scss";
import CaretIcon from "@/components/icons/Caret";

interface CollapsibleContentProps {
label: string;
defaultOpen?: boolean;
className?: string;
triggerClassName?: string;
onOpenChange?: () => void;
previewContent?: ReactNode;
children: ReactNode;
}

const CollapsibleContent: React.FC<CollapsibleContentProps> = ({
label,
defaultOpen,
className,
triggerClassName,
onOpenChange,
previewContent,
children,
}) => {
const [open, setOpen] = useState(defaultOpen || false);

const handleOpenChange = () => {
setOpen(!open);
onOpenChange && onOpenChange();
};

return (
<Collapsible.Root
className={className}
open={open}
onOpenChange={handleOpenChange}
>
<Collapsible.Trigger asChild className={triggerClassName}>
<button className={styles.CollapsibleTrigger}>
{open ? (
<>
<span className={styles.Label}>{label}</span>
<CaretIcon color="#D0D0DC" direction="down" />
</>
) : (
<>
<p className={styles.Label}>{label}</p>
<CaretIcon color="#D0D0DC" direction="up" />
</>
)}
</button>
</Collapsible.Trigger>

{previewContent ? previewContent : null}

<AnimatePresence>
{open && (
<motion.div
initial={{
height: 0,
opacity: 0,
}}
animate={{
height: "auto",
opacity: 1,
transition: {
ease: "circOut",
},
}}
exit={{
height: 0,
opacity: 0,
}}
>
{children}
</motion.div>
)}
</AnimatePresence>
</Collapsible.Root>
);
};

export default CollapsibleContent;
Loading

0 comments on commit 1a645a0

Please sign in to comment.