Skip to content

Commit

Permalink
added carousel on SLR themes page
Browse files Browse the repository at this point in the history
  • Loading branch information
freitagb committed Nov 12, 2024
1 parent 794e410 commit 1e22710
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 6 deletions.
Binary file added overrides/home/media/global-slr-no-text.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added overrides/home/media/global-slr.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 75 additions & 0 deletions stories/components/carousel/carousel-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from '$veda-ui/react';
import styled from '$veda-ui/styled-components';
import { Button, Icon } from '$veda-ui/@trussworks/react-uswds';

import {
Card,
CardBody,
CardFooter,
} from '$veda-ui/@trussworks/react-uswds';


const progressColor = '#1565EF';

const ProgressIndicator = styled.div`
background-color: ${progressColor};
width: ${props => props.progressWidth}%;
transition: ${props => props.noTransition? null: 'width 200ms ease-out'};
`;

function ProgressBar({ selected, shouldProgress, progressDone, progressPercentage }) {
// If progress is done, 100% - false if something is manually selected
// If it is in progress, progress Percentage - false if something is manually selected
// If it is manually selected, 100%
const progressWidth = progressDone? 100: shouldProgress? progressPercentage: selected? 100: 0;
const noTransition = (!shouldProgress && !progressDone && progressPercentage === 0)? true : false;

return <>
<div className="height-05 bg-base-lighter">
<ProgressIndicator className="height-full" progressWidth={progressWidth} noTransition={noTransition} />
</div>
</>
}

export function ItemPanel({ item, linkComponent: LinkComponent }) {
return (<>

<div className="tablet:margin-top-0 margin-top-2 flex-align-self-stretch">
<p className="margin-top-2 margin-bottom-2 flex-align-self-stretch">{item.description}</p>
<LinkComponent className="display-flex flex-align-center veda-color--link" to={item.link}>
<Icon.ArrowForward stroke={progressColor} fill={progressColor} />
<span className="padding-left-1">Read more</span>
</LinkComponent>
</div>
</>)
}

export default function CarouselItem({ item, itemIdx, onTitleClick, shouldProgress, progressDone, progressPercentage, selected, linkComponent: LinkComponent }) {
return <Card
gridLayout={{ tablet: { col: 4 } }}
containerProps={{className:`hover:bg-base-lightest padding-x-1 radius-0 border-0 animation--transition ${(selected || shouldProgress)? 'opacity-100':'opacity-50'}`}}>
<ProgressBar shouldProgress={shouldProgress} progressDone={progressDone} progressPercentage={progressPercentage}selected={selected} />
<CardBody className="padding-left-0 position-relative">
<h3 className="tablet:margin-top-1 carousel--title text-bold veda-color--base">
{item.title}
</h3>
<p className="margin-top-2 flex-align-self-stretch">{item.description}</p>
<Button
unstyled={true}
className="position-absolute top-0 left-0 width-full height-full blocklink"
onClick={() => { onTitleClick(item); } }
type="button"
role="tab"
aria-label={`Slide ${itemIdx+1}`}
aria-selected={selected.toString()}
aria-controls={`carousel-item-${itemIdx+1}`}
children={undefined} />
</CardBody>
<CardFooter className="padding-left-0 padding-top-1">
<LinkComponent className="display-flex flex-align-center veda-color--link" to={item.link}>
<Icon.ArrowForward stroke={progressColor} fill={progressColor} />
<span className="padding-left-1">Read more</span>
</LinkComponent>
</CardFooter>
</Card>
}
44 changes: 44 additions & 0 deletions stories/components/carousel/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* The classes for css transition group */
.imagetransition {
&-enter {
opacity: 0.5;
}
&-enter-active {
opacity: 1;
transition: opacity 400ms ease-in;
}
&-exit {
opacity: 1;
}
/* ending EXIT animation */
&-exit-active {
opacity: 0;
transition: opacity 400ms ease-out;
}
}

.veda-color--link {
color: var(--veda-color-link);
}
.veda-color--base {
color: var(--veda-color-base);
}

.animation--transition {
transition: opacity 200ms ease-out;
}

.carousel {
&--height {
height: 500px;
}

&--content-image {
width: 100%;
height: 100%;
object-fit: cover;
}
&--title {
font-size: 1.25rem;
}
}
114 changes: 114 additions & 0 deletions stories/components/carousel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { useState, useEffect, useCallback } from '$veda-ui/react';

import { CSSTransition, TransitionGroup } from "react-transition-group";
import { useMediaQuery } from "$veda-ui-scripts/utils/use-media-query";
import { GridContainer, Grid } from '$veda-ui/@trussworks/react-uswds';
import LazyLoad from '$veda-ui/react-lazyload';
import { CardGroup } from '$veda-ui/@trussworks/react-uswds';

import CarouselItems from './items';
import CarouselItem, { ItemPanel } from './carousel-item';


import SmartLink from '$veda-ui-scripts/components/common/smart-link';

import '/common/styles.scss';
import './index.scss';

const interval = 100;
const slide_length = 50;
const item_n = CarouselItems.length;

export function DesktopCarousel () {
const [timer, setTimer] = useState(0);
const [selectedItem, setSelectedItem] = useState(null);
const [timerAnimationId, setTimerAnimationId] = useState(null);

// Animation starts on landing, once it is stopped, it is not going to be played again.
useEffect(() => {
const intervalId = setInterval(() => {
setTimer(prev => {
return prev + 1;
});
}, interval);
setTimerAnimationId(intervalId);
return () => {
clearInterval(intervalId);
};
}, []);

const animationTimer = timer % slide_length;
// animationTimer/slide_length will never be 1, compensating the value here
const progressPercentage = Math.floor((animationTimer/slide_length) * (100*(slide_length/(slide_length-1))));
const currentProgressItemIdx = Math.floor((timer / slide_length)%item_n);
const itemInProgress = selectedItem?? CarouselItems[currentProgressItemIdx];

const onTitleClick = useCallback((clickedItem) => {
clearInterval(timerAnimationId);
setTimerAnimationId(null);
setSelectedItem(clickedItem);
},[timerAnimationId]);


return (
<GridContainer aria-roledescription="carousel" aria-label="Highlighted VEDA Dashboard projects">
<Grid row className="position-relative carousel--height" aria-live="off">
<TransitionGroup>
<CSSTransition
key={itemInProgress.title}
timeout={2000}
classNames="imagetransition"
>
<div className="carousel--height width-full position-absolute left-0 top-0 shadow-1">
<img className="carousel--content-image" src={itemInProgress.image} alt={itemInProgress.imageAlt} />
</div>
</CSSTransition>
</TransitionGroup>
</Grid>
<CardGroup className="tablet:margin-top-4 margin-top-2" role="tablist" aria-label="Slides">
{CarouselItems.map((item, itemIdx) => {
return <CarouselItem
key={item.title}
item={item}
itemIdx={itemIdx}
onTitleClick={onTitleClick}
progressDone= {selectedItem? false: itemIdx < currentProgressItemIdx}
shouldProgress = {selectedItem? false: currentProgressItemIdx == itemIdx}
selected={!timerAnimationId && selectedItem?.title === item.title}
progressPercentage = {progressPercentage}
linkComponent={SmartLink}
/>
})}
</CardGroup>
</GridContainer>)
}

function TabletCarousel() {
return <GridContainer>
<Grid row className="margin-top-2">
{CarouselItems.map((item) => {
return <Grid col={12} key={item.title} className="margin-bottom-4">
<div>
<img className="carousel--content-image" src={item.image} />
</div>
<h3 className="margin-top-1">{item.title}</h3>
<ItemPanel item={item} linkComponent={SmartLink} />
</Grid>
})}
</Grid>
</GridContainer>
}


export default function Carousel-stories() {
const { isMediumUp } = useMediaQuery();
return isMediumUp?
<LazyLoad
className="hug-reset-container"
offset={100}
once
>
<DesktopCarousel />
</LazyLoad>:
<div className="hug-reset-container"><TabletCarousel /></div>
}
15 changes: 15 additions & 0 deletions stories/components/carousel/items.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default [
{
link: 'https://earth.gov/sealevel',
title: 'Global Sea Level Rise',
description: 'The Global Sea Level Rise Portal offers essential information on current and project sea level rise to inform coastal communities across the globe.',
image: new URL('../media/global_slr.png', import.meta.url).href,
imageAlt: 'Coastline with crashing waves'
},
{
link: 'https://earth.gov/sealevel/us',
title: 'U.S. Sea Level Rise',
description: ' The U.S. Sea Level Rise portal is a federally supported data visualizations coupled with explanations and science education to help communities prepare for challenges that will affect our coastal environments.',
image: new URL('../media/us_slr.png', import.meta.url).href,
imageAlt: 'Coastline with crashing waves'
}]
9 changes: 3 additions & 6 deletions stories/theme.SLR.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,13 @@ taxonomy:

import CardGallery from "./components/card_gallery";
import { seaLevelRiseStoryIds } from "../overrides/common/story-data";
import VisitSLR from "../overrides/components/visit-slr";

import Carousel from "../overrides/common/embedded-video-carousel";
import contentArray from './theme.SLR.introduction_sea_level_rise/carousel_content.json';

<Block type="wide">
<Figure>
<VisitSLR />
</Figure>
</Block>
import Carousel-stories from '../components/carousel/'

<Carousel />

<Block>
<Prose>
Expand Down

0 comments on commit 1e22710

Please sign in to comment.