Skip to content

Commit

Permalink
added image slider component
Browse files Browse the repository at this point in the history
  • Loading branch information
LawrenceQiu committed May 17, 2024
1 parent 0a6e437 commit 5e75e7a
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 0 deletions.
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"@storybook/manager-api": "^8.0.5",
"@storybook/theming": "^8.0.5",
"@types/styled-components": "^5.1.34",
"lucide-react": "^0.378.0",
"polished": "^4.3.1",
"react-icons": "^5.2.1",
"styled-components": "^6.1.8"
},
"peerDependencies": {
Expand Down
70 changes: 70 additions & 0 deletions src/components/ImageSlider/ImageSlider.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import ImageSlider from './ImageSlider';
import type { Meta, StoryObj } from '@storybook/react';

// Declaring images array
const images = [
'https://images.pexels.com/photos/248547/pexels-photo-248547.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
'https://images.pexels.com/photos/12838/pexels-photo-12838.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
'https://images.pexels.com/photos/161172/cycling-bike-trail-sport-161172.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
];

const meta = {
title: 'Components/ImageSlider',
component: ImageSlider,
} satisfies Meta<typeof ImageSlider>;

export default meta;
type Story = StoryObj<typeof meta>;


const defaultProps = {

};

const disableControls = {
parameters: {
controls: {
disable: true
},
actions: {
disable: true
},
}
};

export const Demo: Story = {
args: {
...defaultProps,
images: images,
size: 'large',
},
tags: ['excludeFromSidebar']
};

export const Default: Story = {
args: {
...defaultProps,
images: images,
size: 'large',
},
...disableControls
};

export const Large: Story = {
args: {
...defaultProps,
images: images,
size: 'large',
},
...disableControls
};

export const Small: Story = {
args: {
...defaultProps,
images: images,
size: 'small',
},
...disableControls
};

70 changes: 70 additions & 0 deletions src/components/ImageSlider/ImageSlider.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import styled from 'styled-components';

export const StyledImageSlider = styled.div<{ size: 'small' | 'large' }>`
.slider-container {
position: relative;
width: 100%;
max-width: ${props => (props.size === 'small' ? '400px' : '800px')};
height: ${props => (props.size === 'small' ? '250px' : '500px')};
margin: 0 auto;
overflow: hidden;
}
.slider-image {
object-fit: cover;
width: 100%;
height: 100%;
aspect-ratio: 10/6;
display: block;
flex-shrink: 0;
flex-grow: 0;
}
.slider-button {
all: unset;
display: block;
position: absolute;
top: 0;
bottom: 0;
padding: 0.8rem;
cursor: pointer;
transition: background-color 100ms ease-in-out, transform 100ms ease; /* Added transform transition */
}
.slider-button:hover {
background-color: rgba(0, 0, 0, 0.09);
}
.slider-button > * {
stroke: white;
width: 2rem;
height: 2rem;
transform: scale(${props => (props.size === 'small' ? '0.8' : '1')}); /* Scale based on size prop */
}
.slider-index-button {
all: unset;
display: block;
cursor: pointer;
width: 1rem;
height: 1rem;
transition: scale 100ms ease-in-out;
transform: scale(${props => (props.size === 'small' ? '0.8' : '1')}); /* Scale based on size prop */
}
.slider-index-button:hover {
scale: 1.2;
}
.slider-index-button > * {
stroke: white;
height: 100%;
width: 100%;
}
.turn-horizontal {
height: ${props => (props.size === 'small' ? '250px' : '500px')};
transition: transform 0.5s ease;
display: flex;
}
`;
12 changes: 12 additions & 0 deletions src/components/ImageSlider/ImageSlider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { render, screen } from '@testing-library/react';
import ImageSlider from './ImageSlider';

describe('<ImageSlider />', () => {
it('should mount', () => {
render(<ImageSlider/>);

const imageSlider = screen.getByTestId('ImageSlider');

expect(imageSlider).toBeInTheDocument();
});
});
58 changes: 58 additions & 0 deletions src/components/ImageSlider/ImageSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { FC, useState } from 'react';
import { StyledImageSlider } from './ImageSlider.style';
import { ArrowBigLeft, ArrowBigRight, Circle, CircleDot } from 'lucide-react';




type ImageSliderProps = {
images: string[]; // Array of image URLs
size: 'small' | 'large'; // Define the size prop
}

const ImageSlider: FC<ImageSliderProps> = ({ images, size }:ImageSliderProps ) => {
// State to track current image index
const [currentImageIndex, setCurrentImageIndex] = useState(0);
// Function to navigate to previous slide
const goToPreviousSlide = () => {
const newIndex = (currentImageIndex - 1 + images.length) % images.length;
setCurrentImageIndex(newIndex);
};
// Function to navigate to next slide
const goToNextSlide = () => {
const newIndex = (currentImageIndex + 1) % images.length;
setCurrentImageIndex(newIndex);
};

return (
<StyledImageSlider data-testid="ImageSlider" size={size}>
<div className="slider-container">
{/* Slider container div */}
<div className="turn-horizontal" style={{
width: `${100 * images.length}%`, // Set the width to accommodate all images side by side
transform: `translateX(-${currentImageIndex * (100 / images.length)}%)`, // Move the container horizontally to show the current slide
}}>
{/* Render images */}
{images.map((url, index) => (
<img key={index} src={url} alt={`Slide ${index}`} className="slider-image" style={{ width: `${100 / images.length}%` }} />
))}
</div>
{/* Navigation buttons */}
<button onClick={goToPreviousSlide} className="slider-button" style={{ left: 0 }}>
<ArrowBigLeft />
</button>
<button onClick={goToNextSlide} className="slider-button" style={{ right: 0 }}>
<ArrowBigRight />
</button>
{/* Index buttons */}
<div style={{ position: 'absolute', bottom: '1.5rem', left: '50%', translate: '-50%', display: 'flex', gap: '.25rem', }}>
{images.map((_, index) => (
<button key={index} className="slider-index-button" onClick={() => setCurrentImageIndex(index)} > {index === currentImageIndex ? <CircleDot/> : <Circle/> } </button>
))}
</div>
</div>
</StyledImageSlider>
);
};

export default ImageSlider;

0 comments on commit 5e75e7a

Please sign in to comment.