-
Notifications
You must be signed in to change notification settings - Fork 14
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
feat: add landing page carousel #3407
base: feat/landing-page
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import clsx from 'clsx'; | ||
import React, { type FC } from 'react'; | ||
import React, { useEffect, type FC } from 'react'; | ||
|
||
import { images } from './consts.ts'; | ||
import { useEmblaCarouselSettings } from './hooks.ts'; | ||
|
@@ -11,10 +11,18 @@ const displayName = 'common.Extensions.ImageCarousel'; | |
const ImageCarousel: FC<ImageCarouselProps> = ({ | ||
slideUrls = images, | ||
options = { loop: true, align: 'start' }, | ||
isAutoplay = false, | ||
isImageFullWidth, | ||
isChangeSlideDotButton = true, | ||
setSelectedIndex, | ||
className, | ||
}) => { | ||
const { scrollSnaps, emblaRef, scrollTo, selectedIndex } = | ||
useEmblaCarouselSettings(options); | ||
useEmblaCarouselSettings(options, isAutoplay); | ||
|
||
useEffect(() => { | ||
setSelectedIndex?.(selectedIndex); | ||
}, [selectedIndex]); | ||
|
||
return ( | ||
<div className={clsx(className, 'relative pb-[1.75rem]')}> | ||
|
@@ -23,13 +31,20 @@ const ImageCarousel: FC<ImageCarouselProps> = ({ | |
<div className="flex"> | ||
{slideUrls.map((url) => ( | ||
<div | ||
className="min-w-full sm:mr-4 sm:w-[31.875rem] sm:min-w-[31.875rem]" | ||
className={clsx('min-w-full', { | ||
'sm:mr-4': !isImageFullWidth, | ||
'sm:w-[31.875rem]': !isImageFullWidth, | ||
'sm:min-w-[31.875rem]': !isImageFullWidth, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we group all these classes into a single line |
||
})} | ||
key={url} | ||
> | ||
<img | ||
alt="file" | ||
src={url} | ||
className="aspect-[380/248] w-full object-cover sm:aspect-[510/248]" | ||
className={clsx('w-full object-cover', { | ||
'aspect-[380/248]': !isImageFullWidth, | ||
'sm:aspect-[510/248]': !isImageFullWidth, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also here |
||
})} | ||
/> | ||
</div> | ||
))} | ||
|
@@ -42,13 +57,23 @@ const ImageCarousel: FC<ImageCarouselProps> = ({ | |
// eslint-disable-next-line react/no-array-index-key | ||
key={index} | ||
onClick={() => scrollTo(index)} | ||
className={clsx( | ||
'mx-1 h-2 w-2 cursor-pointer rounded-full bg-gray-200 transition-all duration-normal hover:bg-blue-400', | ||
{ | ||
'bg-gray-500': index === selectedIndex, | ||
}, | ||
)} | ||
/> | ||
className={clsx('group', { | ||
'py-[10px]': !isChangeSlideDotButton, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we just render a whole different thing if |
||
'my-[-10px]': !isChangeSlideDotButton, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also here |
||
})} | ||
> | ||
<div | ||
className={clsx( | ||
'mx-1 h-2 w-2 cursor-pointer rounded-full bg-gray-200 transition-all duration-normal group-hover:bg-blue-400', | ||
{ | ||
'bg-gray-500': index === selectedIndex, | ||
'w-[5.875rem]': !isChangeSlideDotButton, | ||
'h-[.1875rem]': !isChangeSlideDotButton, | ||
'mx-[.3125rem]': !isChangeSlideDotButton, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also here |
||
}, | ||
)} | ||
/> | ||
</DotButton> | ||
))} | ||
</div> | ||
</div> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,19 @@ | ||
import Autoplay from 'embla-carousel-autoplay'; | ||
import useEmblaCarousel, { type EmblaCarouselType } from 'embla-carousel-react'; | ||
import { useCallback, useEffect, useState } from 'react'; | ||
|
||
export const useEmblaCarouselSettings = (options) => { | ||
const [emblaRef, emblaApi] = useEmblaCarousel(options); | ||
export const useEmblaCarouselSettings = (options, autoplay) => { | ||
const [emblaRef, emblaApi] = useEmblaCarousel( | ||
options, | ||
autoplay | ||
? [ | ||
Autoplay({ | ||
playOnInit: true, | ||
delay: 3000, | ||
}), | ||
] | ||
: [], | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we maybe refactor the plugins part as
thus making it clearer in the future if we plan on adding more plugins |
||
const [selectedIndex, setSelectedIndex] = useState(0); | ||
const [scrollSnaps, setScrollSnaps] = useState<number[]>([]); | ||
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice job with this component @adam-strzelec 👍 however I would propose refactoring it as following, in order to store the messages in a single variable, then easily adjust the number of slides and their parsing.
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import clsx from 'clsx'; | ||
import React, { useState } from 'react'; | ||
import { defineMessages, FormattedMessage } from 'react-intl'; | ||
|
||
import ImageCarousel from '~common/Extensions/ImageCarousel/ImageCarousel.tsx'; | ||
import Slide1 from '~images/assets/landing/slider1.png'; | ||
import Slide2 from '~images/assets/landing/slider2.png'; | ||
import Slide3 from '~images/assets/landing/slider3.png'; | ||
import SlideMobile from '~images/assets/landing/sliderMobile.png'; | ||
|
||
const displayName = 'frame.LandingPageCarousel'; | ||
|
||
const titleMSG = defineMessages({ | ||
titleSlide0: { | ||
id: `${displayName}.titleSlide0`, | ||
defaultMessage: 'A powerful, all-in-one payments suite', | ||
}, | ||
titleSlide1: { | ||
id: `${displayName}.titleSlide1`, | ||
defaultMessage: 'Make bulk payments your way with ease', | ||
}, | ||
titleSlide2: { | ||
id: `${displayName}.titleSlide2`, | ||
defaultMessage: 'Easily track & manage shared finances', | ||
}, | ||
}); | ||
|
||
const descriptionMSG = defineMessages({ | ||
descriptionSlide0: { | ||
id: `${displayName}.dascriptionSlide0`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please update |
||
defaultMessage: | ||
'From simple transactions to complex financial operations like Streaming, Milestone based, and Split payments, you can do it with Colony.', | ||
}, | ||
descriptionSlide1: { | ||
id: `${displayName}.dascriptionSlide0`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please update |
||
defaultMessage: | ||
'Make bulk payments to different recipients using various tokens, amounts and scheduling. Saving time and reducing potential errors.', | ||
}, | ||
descriptionSlide2: { | ||
id: `${displayName}.dascriptionSlide0`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please update |
||
defaultMessage: | ||
'Transparency and clarity around shared finances is made simply with dashboard highlights, transaction history, and shared decision making. ', | ||
}, | ||
}); | ||
|
||
const LandingPageCarousel = () => { | ||
const [currentSlide, setCurrentSlide] = useState(0); | ||
|
||
return ( | ||
<div> | ||
<div className="w-full max-w-[31.25rem] md:hidden"> | ||
<img className="h-auto w-full" src={SlideMobile} alt="slider mobile" /> | ||
</div> | ||
<div className="hidden w-full max-w-[31.25rem] overflow-hidden md:block"> | ||
<div> | ||
<div className="relative h-[4.75rem]"> | ||
{Object.keys(titleMSG).map((_, index) => ( | ||
<h1 | ||
className={clsx( | ||
'absolute left-0 top-0 transition-opacity duration-normal heading-2', | ||
{ | ||
'opacity-100': currentSlide === index, | ||
'opacity-0': currentSlide !== index, | ||
'delay-150': currentSlide === index, | ||
}, | ||
)} | ||
> | ||
<FormattedMessage {...titleMSG[`titleSlide${index}`]} /> | ||
</h1> | ||
))} | ||
</div> | ||
<div className="relative mb-9 mt-[.875rem] h-[2.5rem]"> | ||
{Object.keys(descriptionMSG).map((_, index) => ( | ||
<p | ||
className={clsx( | ||
'absolute left-0 top-0 text-md font-normal transition-opacity duration-normal', | ||
{ | ||
'opacity-100': currentSlide === index, | ||
'opacity-0': currentSlide !== index, | ||
'delay-150': currentSlide === index, | ||
}, | ||
)} | ||
> | ||
<FormattedMessage | ||
{...descriptionMSG[`descriptionSlide${index}`]} | ||
/> | ||
</p> | ||
))} | ||
</div> | ||
</div> | ||
<ImageCarousel | ||
slideUrls={[Slide1, Slide2, Slide3]} | ||
options={{ align: 'center', loop: true }} | ||
isImageFullWidth | ||
isAutoplay | ||
isChangeSlideDotButton={false} | ||
setSelectedIndex={(currentSlideIndex) => | ||
setCurrentSlide(currentSlideIndex) | ||
} | ||
className="mx-[-30px] pb-[115px]" | ||
/> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
LandingPageCarousel.displayName = displayName; | ||
|
||
export default LandingPageCarousel; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { type Meta, type StoryObj } from '@storybook/react'; | ||
import React from 'react'; | ||
|
||
import LandingPageCarousel from '~frame/LandingPage/LandingPageCarousel.tsx'; | ||
|
||
const meta: Meta<typeof LandingPageCarousel> = { | ||
title: 'Common/Landing Page Carousel', | ||
component: LandingPageCarousel, | ||
decorators: [ | ||
(Story) => ( | ||
<div className="flex w-full justify-center"> | ||
<Story /> | ||
</div> | ||
), | ||
], | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof LandingPageCarousel>; | ||
|
||
export const Base: Story = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if we rework
slideUrls
to be an array of image urls and classNames? would be a bit cleaner, since now we control all images from one variable