fix(projects): improve carousel controls and case study actions
This commit is contained in:
@@ -52,6 +52,7 @@
|
|||||||
|
|
||||||
.navButton:disabled {
|
.navButton:disabled {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
opacity: 0.36;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] .navButtonPrevious {
|
[data-theme="dark"] .navButtonPrevious {
|
||||||
@@ -115,7 +116,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid var(--border-default);
|
border: 1px solid var(--border-default);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
background: var(--bg-surface);
|
background: var(--bg-raised);
|
||||||
scroll-snap-align: start;
|
scroll-snap-align: start;
|
||||||
transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.25s ease;
|
transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.25s ease;
|
||||||
}
|
}
|
||||||
@@ -225,6 +226,7 @@
|
|||||||
font-size: var(--font-size-body-small);
|
font-size: var(--font-size-body-small);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
transition: color 0.2s ease, gap 0.2s ease;
|
transition: color 0.2s ease, gap 0.2s ease;
|
||||||
|
min-height: 2.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caseStudyLink:focus-visible {
|
.caseStudyLink:focus-visible {
|
||||||
@@ -243,67 +245,29 @@
|
|||||||
margin-top: 2.5rem;
|
margin-top: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar {
|
.progressTrack {
|
||||||
--thumb-width: 13.5rem;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
width: 30rem;
|
width: 30rem;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: 1.25rem;
|
|
||||||
margin: 0;
|
|
||||||
appearance: none;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollbar::-webkit-slider-runnable-track {
|
|
||||||
height: 0.375rem;
|
height: 0.375rem;
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
background: var(--color-blue-200);
|
background: var(--color-blue-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar::-webkit-slider-thumb {
|
.progressValue {
|
||||||
width: var(--thumb-width, 13.5rem);
|
display: block;
|
||||||
height: 0.375rem;
|
height: 100%;
|
||||||
margin-top: 0;
|
|
||||||
appearance: none;
|
|
||||||
border: 0;
|
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
background: var(--color-blue-400);
|
background: var(--color-blue-400);
|
||||||
|
transition: width var(--motion-duration-default) var(--motion-ease-out);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar::-moz-range-track {
|
[data-theme="dark"] .progressTrack {
|
||||||
height: 0.375rem;
|
|
||||||
border: 0;
|
|
||||||
border-radius: var(--radius-pill);
|
|
||||||
background: var(--color-blue-200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollbar::-moz-range-thumb {
|
|
||||||
width: var(--thumb-width, 13.5rem);
|
|
||||||
height: 0.375rem;
|
|
||||||
border: 0;
|
|
||||||
border-radius: var(--radius-pill);
|
|
||||||
background: var(--color-blue-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollbar:focus-visible {
|
|
||||||
border-radius: var(--radius-pill);
|
|
||||||
outline: 3px solid var(--focus-ring);
|
|
||||||
outline-offset: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .scrollbar::-webkit-slider-runnable-track {
|
|
||||||
background: var(--color-blue-800);
|
background: var(--color-blue-800);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] .scrollbar::-webkit-slider-thumb {
|
[data-theme="dark"] .progressValue {
|
||||||
background: var(--color-blue-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .scrollbar::-moz-range-track {
|
|
||||||
background: var(--color-blue-800);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .scrollbar::-moz-range-thumb {
|
|
||||||
background: var(--color-blue-300);
|
background: var(--color-blue-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,9 +285,8 @@
|
|||||||
width: clamp(18.125rem, 23vw, 20.625rem);
|
width: clamp(18.125rem, 23vw, 20.625rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar {
|
.progressTrack {
|
||||||
width: min(30rem, 50vw);
|
width: min(30rem, 50vw);
|
||||||
--thumb-width: 7.5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,9 +361,8 @@
|
|||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar {
|
.progressTrack {
|
||||||
width: min(22.5rem, 60vw);
|
width: min(22.5rem, 60vw);
|
||||||
--thumb-width: 5.625rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,8 +425,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cardDesc {
|
.cardDesc {
|
||||||
font-size: 0.8125rem;
|
font-size: 1rem;
|
||||||
line-height: 1.5;
|
line-height: 1.6;
|
||||||
margin-bottom: 0.875rem;
|
margin-bottom: 0.875rem;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
word-break: normal;
|
word-break: normal;
|
||||||
@@ -478,8 +440,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tagBadge {
|
.tagBadge {
|
||||||
padding: 0.25rem 0.5625rem;
|
padding: 0.375rem 0.625rem;
|
||||||
font-size: 0.6875rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbarWrapper {
|
.scrollbarWrapper {
|
||||||
@@ -488,10 +450,8 @@
|
|||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar {
|
.progressTrack {
|
||||||
width: min(12.5rem, 60vw);
|
width: min(12.5rem, 60vw);
|
||||||
height: 1rem;
|
|
||||||
--thumb-width: 3.75rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,12 @@ export const ProjectsSection: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
syncScrollState();
|
syncScrollState();
|
||||||
window.addEventListener('resize', syncScrollState);
|
const viewport = viewportRef.current;
|
||||||
return () => window.removeEventListener('resize', syncScrollState);
|
if (!viewport) return;
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(syncScrollState);
|
||||||
|
resizeObserver.observe(viewport);
|
||||||
|
return () => resizeObserver.disconnect();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const scrollByCard = (direction: -1 | 1) => {
|
const scrollByCard = (direction: -1 | 1) => {
|
||||||
@@ -182,14 +186,6 @@ export const ProjectsSection: React.FC = () => {
|
|||||||
dragState.current.moved = false;
|
dragState.current.moved = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScrollbarChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const viewport = viewportRef.current;
|
|
||||||
if (!viewport) return;
|
|
||||||
|
|
||||||
const maxScroll = Math.max(0, viewport.scrollWidth - viewport.clientWidth);
|
|
||||||
viewport.scrollLeft = (Number(event.target.value) / 100) * maxScroll;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (event.key === 'ArrowLeft') {
|
if (event.key === 'ArrowLeft') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -253,10 +249,11 @@ export const ProjectsSection: React.FC = () => {
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="Scrollable project cards"
|
aria-label="Scrollable project cards"
|
||||||
|
aria-roledescription="carousel"
|
||||||
>
|
>
|
||||||
<div className={styles.carouselTrack}>
|
<div className={styles.carouselTrack}>
|
||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
<article key={project.id} className={styles.card}>
|
<article key={project.id} className={styles.card} aria-label={project.title}>
|
||||||
<div className={styles.mediaContainer}>
|
<div className={styles.mediaContainer}>
|
||||||
<Image
|
<Image
|
||||||
src={project.image}
|
src={project.image}
|
||||||
@@ -304,18 +301,21 @@ export const ProjectsSection: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.scrollbarWrapper}>
|
<div className={styles.scrollbarWrapper}>
|
||||||
<input
|
<div
|
||||||
type="range"
|
className={styles.progressTrack}
|
||||||
min="0"
|
role="progressbar"
|
||||||
max="100"
|
|
||||||
step="0.1"
|
|
||||||
value={scrollProgress}
|
|
||||||
onChange={handleScrollbarChange}
|
|
||||||
className={styles.scrollbar}
|
|
||||||
aria-label="Project carousel position"
|
aria-label="Project carousel position"
|
||||||
|
aria-valuemin={0}
|
||||||
|
aria-valuemax={100}
|
||||||
|
aria-valuenow={Math.round(scrollProgress)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={styles.progressValue}
|
||||||
|
style={{ width: `${12.5 + scrollProgress * 0.875}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user