Skip to content

Commit

Permalink
feat: add new Slider component
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksandar-r committed Apr 3, 2024
1 parent e20ddab commit e425828
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 235 deletions.
178 changes: 88 additions & 90 deletions src/core/Slider.tsx
Original file line number Diff line number Diff line change
@@ -1,110 +1,108 @@
import React, { CSSProperties, ReactNode, useEffect, useRef } from "react";
import React, { useState, useEffect, useRef, ReactNode } from "react";
import Icon from "../core/Icon.tsx";
import "./component.css";

import Icon from "./Icon";
import SliderScripts from "./Slider/component.js";
import "./Slider/component.css";
interface SliderProps {
children: ReactNode[];
interval?: number;
}

type SliderProps = {
slides?: ReactNode[];
classes?: string;
slideClasses?: string;
slideMinWidth?: string;
slideMaxWidth?: string;
mqEnableThreshold?: () => boolean;
interface SliderIndicatorProps {
numSlides: number;
activeIndex: number;
interval: number;
}

container?: HTMLDivElement | null;
const SlideIndicator = ({
numSlides,
activeIndex,
interval,
}: SliderIndicatorProps) => {
return (
<div className="flex gap-4 absolute -bottom-40 left-1/2 transform -translate-x-1/2">
{Array.from({ length: numSlides }, (_, i) => (
<div
key={i}
className={"relative w-40 h-4 mx-1 rounded-full bg-neutral-500"}
>
{i === activeIndex && (
<div
className="absolute inset-0 rounded-full bg-active-orange animate-[fillAnimation]"
style={{
animationDuration: `${interval}ms`,
animationTimingFunction: "linear",
}}
></div>
)}
</div>
))}
</div>
);
};

const Slider = ({
slides = [],
classes = "",
slideClasses = "",
slideMinWidth = "16.875rem",
slideMaxWidth = "1fr",
mqEnableThreshold = () => true,
...props
}: SliderProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const Slider = ({ children, interval = 15000 }: SliderProps) => {
const [activeIndex, setActiveIndex] = useState(0);
const timerRef = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
SliderScripts({
container: containerRef.current,
mqEnableThreshold,
});
}, []);
const next = () => {
setActiveIndex((prevIndex) => (prevIndex + 1) % children.length);
resetInterval();
};

const prev = () => {
setActiveIndex((prevIndex) =>
prevIndex > 0 ? prevIndex - 1 : children.length - 1
);
resetInterval();
};

if (slides.length === 0) return;
const resetInterval = () => {
if (timerRef.current) clearInterval(timerRef.current);
timerRef.current = setInterval(next, interval);
};

useEffect(() => {
resetInterval();
return () => {
if (timerRef.current) clearInterval(timerRef.current);
};
}, [children.length, interval]);

return (
<div
className="w-full overflow-x-hidden"
data-id="slider"
style={
{
"--dynamic-grid-columns-count": slides.length,
"--dynamic-grid-column-min-width": slideMinWidth,
"--dynamic-grid-column-max-width": slideMaxWidth,
} as CSSProperties
}
ref={containerRef}
>
<ol
className={`grid ui-grid-gap grid-cols-dynamic transform transition-transform ${classes}`}
data-id="slider-strip"
{...props}
>
{slides.map((slide, i) => (
<li key={i} className={slideClasses} data-id="slider-slide">
{slide}
</li>
))}
</ol>
<div className="relative">
<div className="overflow-hidden w-full py-40">
<div
className="flex items-center transition-transform ease-in-out duration-500"
style={{ transform: `translateX(-${activeIndex * 100}%)` }}
>
{children.map((child, index) => (
<div key={index} className="w-full flex-shrink-0">
{child}
</div>
))}
</div>
</div>

<div
className="justify-center items-center my-24 hidden"
data-id="slider-controls"
>
<div className="hidden sm:absolute inset-0 sm:flex items-center justify-between pointer-events-none">
<button
type="button"
className="p-0 w-24 h-24 flex items-center focus:outline-gui-focus"
data-id="slider-previous"
className="rounded border border-gray-500 p-16 pointer-events-auto"
onClick={prev}
>
<Icon
name="icon-gui-disclosure-arrow"
size="1.5rem"
color="text-cool-black"
additionalCSS="transform rotate-180"
data-id="meganav-control-mobile-dropdown-menu"
/>
<Icon name="icon-gui-arrow-left" size="1.5rem" />
</button>

<ul className="flex justify-center items-center mx-32 relative h-24">
{slides.map((_, i) => (
<li key={i}>
<span
className="ui-slider-marker text-cool-black"
data-id="slider-marker"
>
&#x2b24;
</span>{" "}
{/* ⬤ */}
</li>
))}
</ul>

<button
type="button"
className="p-0 w-24 h-24 flex items-center focus:outline-gui-focus"
data-id="slider-next"
className="rounded border border-gray-500 p-16 pointer-events-auto"
onClick={next}
>
<Icon
name="icon-gui-disclosure-arrow"
size="1.5rem"
color="text-cool-black"
data-id="meganav-control-mobile-dropdown-menu"
/>
<Icon name="icon-gui-arrow-right" size="1.5rem" />
</button>
</div>

<SlideIndicator
numSlides={children.length}
activeIndex={activeIndex}
interval={interval}
/>
</div>
);
};
Expand Down
86 changes: 53 additions & 33 deletions src/core/Slider/Slider.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,65 @@
import React, { ReactNode } from "react";
import Slider from "../Slider";
import React from "react";
import Slider from "../Slider.tsx";
import Icon from "../Icon.tsx";

export default {
title: "Components/Slider",
component: Slider,
parameters: {
layout: "fullscreen",
},
tags: ["autodocs"],
};
const Slide = ({ name }: { name: string }) => (
<div className="relative ">
<div className="relative mx-auto w-full sm:w-[560px] md:w-[784px] lg:w-[960px] bg-white overflow-hidden flex gap-40 rounded-3xl shadow-container-subtle">
<div className="w-full md:w-2/3 flex flex-col gap-24 pr-40 md:pr-0 pt-40 pl-40 pb-40 sm:pb-[120px] md:pb-40">
<h2 className="ui-text-h2 font-medium text-neutral-1000">
“Ably seamlessly absorbs sudden bursts in load during unexpected
client events. The integration was easy and we were live in under a
month.”
</h2>
<div className="flex flex-col sm:flex-row gap-32">
<div className="flex gap-8">
<div className="static self-center sm:absolute sm:-bottom-48 sm:-right-[56px] rounded-full bg-gradient-to-l from-neutral-200 to-50% to-neutral-500 w-[48px] h-[48px] sm:w-[201px] sm:h-[201px] md:w-[257px] md:h-[257px] lg:w-[280px] lg:h-[280px] overflow-hidden flex items-center justify-center sm:border-[16px] border-neutral-200">
{/* <GatsbyImage /> */}
</div>
<div className="sm:py-16">
<p className="ui-text-p1 text-neutral-1300">{name}</p>
<p className="ui-text-p3 text-neutral-800">
Co-Founder & Technical Leader
</p>
</div>
</div>

<div className="w-[80px] h-1 sm:w-1 sm:h-full bg-neutral-500"></div>
<div className="flex items-center gap-4">
{/* <GatsbyImage /> */}
<p className="ui-text-h4 font-bold">Mentimeter</p>
</div>
</div>
<a href="/case-study" className="ui-btn self-start">
Read case study
<Icon
name="icon-gui-arrow-right"
size="1.25rem"
additionalCSS="ml-4"
/>
</a>
</div>
</div>

const Slide = ({ children }: { children: ReactNode }) => (
<div className="h-full p-24 bg-white rounded">
<p className="ui-text-p2 text-center">{children}</p>
<div className="absolute h-256 -z-10 -bottom-48 -left-36 w-1/5 rounded-full blur-xl opacity-50 transform -rotate-45 bg-gradient-to-bl from-bg-glow-green to-bg-glow-teal"></div>
<div className="absolute h-256 -z-10 -top-48 -right-48 w-3/5 rounded-full blur-xl opacity-50 transform rotate-12 bg-gradient-to-br from-bg-glow-green to-bg-glow-teal"></div>
</div>
);

const slides = [
<Slide key="1">
Powers live chat, updates, analytics, and composition for millions of users.
</Slide>,
<Slide key="2">
Powers virtual venues for millions of event attendees around the world.
</Slide>,
<Slide key="3">
Provides 5 million daily users with live financial commentary and stock
tickers.
</Slide>,
<Slide key="4">Monitors live car performance data across the USA.</Slide>,
<Slide key="1" name={"Johan Bengtsson"} />,
<Slide key="2" name={"Mirko Bergman"} />,
<Slide key="3" name={"Stefania Lombardo"} />,
<Slide key="4" name={"Aleksandar Kostadinov"} />,
];

export const SliderOnAllBreakpoints = {
export default {
title: "Components/Slider",
component: Slider,
args: {
slides,
children: slides,
interval: 15000,
},
};

export const SliderOnSmallBreakpointOnly = {
args: {
slides,
classes: `sm:grid-cols-${slides.length / 2} md:grid-cols-${slides.length}`,
mqEnableThreshold: () => !window.matchMedia("(min-width: 48rem)").matches,
},
};
export const Default = {};
12 changes: 7 additions & 5 deletions src/core/Slider/component.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.ui-slider-marker {
font-size: 0.5rem;
top: -1px;

@apply leading-none px-4 relative;
@keyframes fillAnimation {
0% {
width: 0%;
}
100% {
width: 100%;
}
}
Loading

0 comments on commit e425828

Please sign in to comment.