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

Basic carousel for new site #42

Merged
merged 17 commits into from
Nov 6, 2024
Merged
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# misc
.DS_Store
*.pem

*.vscode
# debug
npm-debug.log*
yarn-debug.log*
Expand Down
5 changes: 3 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"@trussworks/react-uswds": "^9.1.0",
"@uswds/uswds": "^3.9.0",
"classnames": "^2.5.1",
"fs": "^0.0.1-security",
"gray-matter": "^4.0.3",
"next": "14.2.15",
"path": "^0.12.7",
Expand All @@ -35,6 +34,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.37.2",
"fs": "^0.0.1-security",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"postcss": "^8",
Expand Down
8 changes: 8 additions & 0 deletions public/images/placeholder-carousel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
113 changes: 113 additions & 0 deletions src/app/components/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
'use client';
import { useState, useRef } from 'react';
import Image from 'next/image';
import { basePath } from '../utils/constants';

export default function MultiImageCarousel() {
const images = [
`${basePath}/images/placeholder-carousel.svg`,
`${basePath}/images/placeholder-carousel.svg`,
`${basePath}/images/placeholder-carousel.svg`,
`${basePath}/images/placeholder-carousel.svg`,
`${basePath}/images/placeholder-carousel.svg`,
`${basePath}/images/placeholder-carousel.svg`,
`${basePath}/images/placeholder-carousel.svg`,
`${basePath}/images/placeholder-carousel.svg`,
];

const [currentIndex, setCurrentIndex] = useState(0);
const itemsPerView = 5; // Number of images visible at once
jakewheeler marked this conversation as resolved.
Show resolved Hide resolved
const [isDragging, setIsDragging] = useState(false);
const [dragStartX, setDragStartX] = useState(0);
const [dragOffset, setDragOffset] = useState(0);
const [finalOffset, setFinalOffset] = useState(0); // Stores the last offset when dragging stops
const [offsetType, setOffsetType] = useState('%'); // Stores the last offset when dragging stops

const carouselRef = useRef(null);

// Handle drag start
const handleDragStart = (
e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>,
) => {
setOffsetType('px');
const startX = 'touches' in e ? e.touches[0].clientX : e.pageX;
setDragStartX(startX);
setIsDragging(true);
};

// Handle dragging
const handleDragMove = (
e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>,
) => {
if (!isDragging) return;

const currentX = 'touches' in e ? e.touches[0].clientX : e.pageX;
setDragOffset(currentX - dragStartX);
};

// Handle drag end
const handleDragEnd = () => {
setFinalOffset(finalOffset + dragOffset); // Keep track of cumulative offset
setIsDragging(false);
setDragOffset(0); // Reset drag offset after drag ends
// setFinalOffset(0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we delete this line? Was this just for testing?

};

const prevSlide = () => {
setOffsetType('%');
const newIndex = currentIndex === 0 ? 0 : currentIndex - 1;
setCurrentIndex(newIndex);
setFinalOffset(-newIndex * (100 / itemsPerView)); // Update finalOffset to reflect the new position
jakewheeler marked this conversation as resolved.
Show resolved Hide resolved
};

const nextSlide = () => {
setOffsetType('%');
const maxIndex = images.length - itemsPerView;
const newIndex = currentIndex < maxIndex ? currentIndex + 1 : maxIndex;
setCurrentIndex(newIndex);
setFinalOffset(-newIndex * (100 / itemsPerView)); // Update finalOffset to reflect the new position
};

return (
<div
jakewheeler marked this conversation as resolved.
Show resolved Hide resolved
className="carousel-container"
onMouseDown={handleDragStart}
onMouseMove={handleDragMove}
onMouseUp={handleDragEnd}
onMouseLeave={handleDragEnd}
onTouchStart={handleDragStart}
onTouchMove={handleDragMove}
onTouchEnd={handleDragEnd}
ref={carouselRef}
>
<button onClick={prevSlide} className="carousel-btn prev">
❮<span className="sr-only">Previous slide</span>
</button>
<div className="carousel-wrapper">
<div
className="carousel"
style={{
transform: `translateX(calc(${finalOffset}${offsetType} + ${dragOffset}${offsetType}))`,
width: `${(images.length / itemsPerView) * 100}%`,
transition: isDragging ? 'none' : 'transform 0.5s ease-in-out',
}}
>
{images.map((src, index) => (
<div key={index} className="carousel-item">
<Image
src={src}
alt={`Slide ${index + 1}`}
width={160}
height={100}
draggable={false}
/>
</div>
))}
</div>
</div>
<button onClick={nextSlide} className="carousel-btn next">
❯<span className="sr-only">Next slide</span>
jakewheeler marked this conversation as resolved.
Show resolved Hide resolved
</button>
</div>
);
}
74 changes: 74 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,77 @@ p {
margin-top: 1rem;
margin-bottom: 1rem;
}

jakewheeler marked this conversation as resolved.
Show resolved Hide resolved
.carousel-container {
position: relative;
width: 100%; /* Full width of the container */
}

.carousel-wrapper {
display: flex;
width: 100%;
overflow: hidden;
cursor: grab; /* Shows a hand cursor when hovering for drag */
}

.carousel-wrapper:active {
cursor: grabbing; /* Changes cursor when dragging */
}

.carousel {
display: flex;
transition: transform 0.5s ease-in-out;
will-change: transform;
}

.carousel-item {
min-width: calc(100% / 7); /* Adjust based on number of visible items */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this number intended to match itemsPerView?

flex: 1 0 auto;
padding: 0 10px;
}

.carousel-btn {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}

.carousel-btn:focus {
background: rgba(0, 0, 0, 0.5);
color: white;
border: none;
padding: 10px 20px;
font-size: 2rem;
cursor: pointer;
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
transition: background 0.3s ease;
width: auto;
height: auto;
margin: inherit;
overflow: visible;
clip: inherit;
}

.carousel-btn:hover {
background: rgba(0, 0, 0, 0.8);
}

.prev {
left: 10px;
}

.next {
right: 10px;
}

.max-width-100 {
max-width: 100%;
}
jakewheeler marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 5 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import MarkdownContent from './components/MarkdownComponent';
import { Grid } from '@trussworks/react-uswds';
import Image from 'next/image';
import { basePath } from './utils/constants';
import SimpleCarousel from './components/Carousel';

export default async function Home() {
return (
Expand Down Expand Up @@ -42,12 +43,15 @@ export default async function Home() {
</div>
</section>
<section className="usa-section">
<div className="grid-container">
<div className="grid-container padding-x-0 max-width-100">
<Grid row>
<Grid col={12} className="flex-center">
{MarkdownContent('homepage/section_4.md')}
</Grid>
</Grid>
<Grid row>
<SimpleCarousel />
</Grid>
</div>
</section>
</>
Expand Down
Loading