-
Notifications
You must be signed in to change notification settings - Fork 6
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
Landing page's SeasonCountdownSection
component
#368
Changes from all commits
ea11fbb
322644a
ff59bb6
61a9503
f60dfb8
076d2ce
e962925
a4ba563
e958c8e
102a2d8
2df35a3
d87bc51
7f39cce
b876889
32db87b
bff469a
1087104
789bcac
eb3cb02
a26e6ea
5796c8b
99ba8fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import React, { useMemo } from "react" | ||
import { useCountdown } from "#/hooks" | ||
import { | ||
BoxProps, | ||
HStack, | ||
Grid, | ||
Box, | ||
Text, | ||
TextProps, | ||
StackProps, | ||
} from "@chakra-ui/react" | ||
import { AnimatePresence, motion } from "framer-motion" | ||
import { Tuple } from "#/types" | ||
|
||
const MotionBox = motion(Box) | ||
|
||
type CountdownTimerDigitProps = Omit<BoxProps, "children"> & { | ||
children: number | ||
} | ||
function CountdownTimerDigit(props: CountdownTimerDigitProps) { | ||
const { children, ...restProps } = props | ||
return ( | ||
<MotionBox | ||
px={5} | ||
w={14} | ||
py={4} | ||
rounded="xl" | ||
color="gold.200" | ||
bg="grey.700" | ||
overflow="hidden" | ||
initial={{ opacity: 0 }} | ||
animate={{ opacity: 1, transition: { staggerChildren: 0.1 } }} | ||
{...restProps} | ||
> | ||
<AnimatePresence mode="popLayout"> | ||
<motion.div | ||
key={children} | ||
initial={{ y: -32, rotateX: -90, opacity: 1 }} | ||
animate={{ y: 0, rotateX: 0, opacity: 1 }} | ||
exit={{ y: 32, rotateX: 90, opacity: 0 }} | ||
transition={{ type: "spring", mass: 1.05 }} | ||
> | ||
{children} | ||
</motion.div> | ||
</AnimatePresence> | ||
</MotionBox> | ||
) | ||
} | ||
|
||
function CountdownTimerSegmentLabel(props: TextProps) { | ||
return ( | ||
<Text | ||
color="white" | ||
fontSize="md" | ||
fontWeight="medium" | ||
lineHeight={5} | ||
gridColumn="1/3" | ||
textTransform="capitalize" | ||
{...props} | ||
/> | ||
) | ||
} | ||
|
||
type CountdownTimerSegmentProps = Omit<BoxProps, "children"> & { | ||
label: string | ||
value: Tuple<number> | ||
} | ||
function CountdownTimerSegment(props: CountdownTimerSegmentProps) { | ||
const { label, value, ...restProps } = props | ||
return ( | ||
<HStack | ||
spacing={2} | ||
alignItems="baseline" | ||
color="gold.200" | ||
fontSize="2xl" | ||
fontWeight="bold" | ||
_notLast={{ | ||
_after: { | ||
content: '":"', | ||
}, | ||
}} | ||
> | ||
<Grid templateColumns="repeat(2, 1fr)" gap={2} {...restProps}> | ||
<CountdownTimerDigit>{value[0]}</CountdownTimerDigit> | ||
<CountdownTimerDigit>{value[1]}</CountdownTimerDigit> | ||
<CountdownTimerSegmentLabel>{label}</CountdownTimerSegmentLabel> | ||
</Grid> | ||
</HStack> | ||
) | ||
} | ||
|
||
type CountdownTimerProps = Omit<StackProps, "children"> & { | ||
timestamp: number | ||
} | ||
export function CountdownTimer(props: CountdownTimerProps) { | ||
const { timestamp, ...restProps } = props | ||
const countdown = useCountdown(timestamp) | ||
|
||
const parsedCountdown = useMemo( | ||
() => | ||
Object.entries(countdown).reduce<[string, Tuple<number>][]>( | ||
(accumulator, currentValue) => { | ||
const [key, stringValue] = currentValue | ||
|
||
if (key === "seconds") return accumulator | ||
|
||
const value = +stringValue | ||
const parsedValue = ( | ||
value > 0 | ||
? Array.from(value.toString().padStart(2, "0")).map(Number) | ||
: Array(2).fill(0) | ||
) as Tuple<number> | ||
|
||
return [...accumulator, [key, parsedValue]] | ||
}, | ||
[], | ||
), | ||
[countdown], | ||
) | ||
return ( | ||
<HStack spacing={2} userSelect="none" {...restProps}> | ||
{parsedCountdown.map(([label, value]) => ( | ||
<CountdownTimerSegment key={label} label={label} value={value} /> | ||
))} | ||
</HStack> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import React from "react" | ||
import { TagProps, Tag, TagLabel, Box } from "@chakra-ui/react" | ||
import { motion } from "framer-motion" | ||
|
||
const MotionBox = motion(Box) | ||
|
||
export function LiveTag(props: TagProps) { | ||
return ( | ||
<Tag | ||
px={4} | ||
py={2} | ||
rounded="3xl" | ||
bg="grey.700" | ||
variant="solid" | ||
pos="relative" | ||
{...props} | ||
> | ||
<Box rounded="full" w={2} h={2} mr={3} bg="brand.400" /> | ||
<MotionBox | ||
pos="absolute" | ||
rounded="full" | ||
w={2} | ||
h={2} | ||
bg="brand.400" | ||
animate={{ scale: [1, 6], opacity: [0.5, 0] }} | ||
transition={{ | ||
duration: 2, | ||
ease: "easeInOut", | ||
repeat: Infinity, | ||
}} | ||
/> | ||
<TagLabel | ||
color="gold.200" | ||
textTransform="uppercase" | ||
fontStyle="italic" | ||
overflow="visible" | ||
fontSize="md" | ||
lineHeight={5} | ||
fontWeight="bold" | ||
> | ||
Live | ||
</TagLabel> | ||
</Tag> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import React, { useRef } from "react" | ||
import { Box, BoxProps } from "@chakra-ui/react" | ||
import { useSize } from "@chakra-ui/react-use-size" | ||
import seasonCountdownBackground from "#/assets/images/season-countdown-section-background.png" | ||
import seasonCountdownForeground from "#/assets/images/season-countdown-section-foreground.png" | ||
import { | ||
MotionValue, | ||
motion, | ||
useScroll, | ||
useSpring, | ||
useTransform, | ||
useTime, | ||
wrap, | ||
} from "framer-motion" | ||
|
||
export function SeasonCountdownSectionBackground(props: BoxProps) { | ||
const containerRef = useRef(null) | ||
const { scrollYProgress } = useScroll({ | ||
target: containerRef, | ||
offset: ["center start", "start end"], | ||
}) | ||
const smoothScrollYProgress = useSpring(scrollYProgress, { | ||
damping: 10, | ||
stiffness: 90, | ||
mass: 0.75, | ||
}) as MotionValue<number> | ||
const foregroundParallax = useTransform( | ||
smoothScrollYProgress, | ||
[0, 1], | ||
["25%", "65%"], | ||
) | ||
const time = useTime() | ||
// Seed value is wrapped to prevent infinite increment causing potential memory leaks | ||
const seed = useTransform(time, (value) => wrap(0, 2137, Math.floor(value))) | ||
|
||
const size = useSize(containerRef) | ||
|
||
return ( | ||
<Box as="svg" ref={containerRef} w="full" h="full" rounded="2xl" {...props}> | ||
<defs> | ||
<filter | ||
id="noise-filter" | ||
x="-20%" | ||
y="-20%" | ||
width="140%" | ||
height="140%" | ||
filterUnits="objectBoundingBox" | ||
primitiveUnits="userSpaceOnUse" | ||
colorInterpolationFilters="linearRGB" | ||
> | ||
<motion.feTurbulence | ||
type="fractalNoise" | ||
baseFrequency="0.119" | ||
numOctaves="4" | ||
seed={seed} | ||
stitchTiles="stitch" | ||
x="0%" | ||
y="0%" | ||
width="100%" | ||
height="100%" | ||
result="turbulence" | ||
/> | ||
<feSpecularLighting | ||
surfaceScale="4" | ||
specularConstant="3" | ||
specularExponent="20" | ||
lightingColor="#0600ff" | ||
x="0%" | ||
y="0%" | ||
width="100%" | ||
height="100%" | ||
in="turbulence" | ||
result="specularLighting" | ||
> | ||
<feDistantLight azimuth="3" elevation="107" /> | ||
</feSpecularLighting> | ||
<feColorMatrix | ||
type="saturate" | ||
values="0" | ||
x="0%" | ||
y="0%" | ||
width="100%" | ||
height="100%" | ||
in="specularLighting" | ||
result="colormatrix" | ||
/> | ||
</filter> | ||
</defs> | ||
<Box | ||
as="rect" | ||
x="0" | ||
y="0" | ||
style={{ width: size?.width }} | ||
h="full" | ||
fill="brand.400" | ||
/> | ||
<Box as="g" mixBlendMode="overlay"> | ||
<Box | ||
as="image" | ||
href={seasonCountdownBackground} | ||
style={{ width: size?.width }} | ||
h="full" | ||
preserveAspectRatio="xMinYMin slice" | ||
/> | ||
<Box | ||
as={motion.image} | ||
href={seasonCountdownForeground} | ||
w="full" | ||
y={foregroundParallax} | ||
preserveAspectRatio="xMinYMin slice" | ||
/> | ||
</Box> | ||
<Box | ||
as="rect" | ||
mixBlendMode="soft-light" | ||
// TOOD: Investigate width update delay | ||
style={{ width: size?.width }} | ||
h="full" | ||
fill="#0600ff" | ||
filter="url(#noise-filter)" | ||
opacity={0.64} | ||
/> | ||
</Box> | ||
) | ||
} |
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. I would suggest to change the name of this directory. More information here. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from "./LiveTag" | ||
export * from "./CountdownTimer" | ||
export * from "./SeasonCountdownSectionBackground" |
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. I think we don't need to nest these components in the components directory. I have a feeling that this may cause some confusion. Please check this thread. I believe that if the |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,45 @@ | ||||||||||||||||||
import React from "react" | ||||||||||||||||||
import { Box, VStack, Heading, Text } from "@chakra-ui/react" | ||||||||||||||||||
|
||||||||||||||||||
import { | ||||||||||||||||||
LiveTag, | ||||||||||||||||||
SeasonCountdownSectionBackground, | ||||||||||||||||||
CountdownTimer, | ||||||||||||||||||
} from "#/components/shared/SeasonCountdownSection" | ||||||||||||||||||
|
||||||||||||||||||
const MOCK_SEASON_DUE_TIMESTAMP = new Date(2024, 3, 30).getTime() / 1000 | ||||||||||||||||||
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. We can do it later but just flagging. We would probably want to save this as an
Example solution for acre/dapp/src/constants/staking.ts Line 1 in efcb6ed
|
||||||||||||||||||
|
||||||||||||||||||
export default function SeasonCountdownSection() { | ||||||||||||||||||
return ( | ||||||||||||||||||
<Box display="grid" sx={{ ">*": { gridArea: "-1 / -1" } }}> | ||||||||||||||||||
<VStack | ||||||||||||||||||
spacing={0} | ||||||||||||||||||
px={10} | ||||||||||||||||||
pt={15} | ||||||||||||||||||
pb={20} | ||||||||||||||||||
textAlign="center" | ||||||||||||||||||
color="white" | ||||||||||||||||||
> | ||||||||||||||||||
<LiveTag mb={10} /> | ||||||||||||||||||
<Heading fontSize="5xl" fontWeight="bold" mb={4}> | ||||||||||||||||||
Season 1. Pre-launch staking | ||||||||||||||||||
</Heading> | ||||||||||||||||||
Comment on lines
+24
to
+26
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. We would probably want to use an already prepared component for this. I know that style guide is not yet ready but I believe that it will make it easier for us to standardize later.
Suggested change
|
||||||||||||||||||
<Text fontSize="lg" fontWeight="medium" mb={10}> | ||||||||||||||||||
Season 1 users that stake bitcoin before Acre launches earn the <br /> | ||||||||||||||||||
highest rewards and first access to upcoming Seasons. | ||||||||||||||||||
</Text> | ||||||||||||||||||
Comment on lines
+27
to
+30
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. Similar situation as above.
Suggested change
|
||||||||||||||||||
<CountdownTimer timestamp={MOCK_SEASON_DUE_TIMESTAMP} /> | ||||||||||||||||||
</VStack> | ||||||||||||||||||
<SeasonCountdownSectionBackground | ||||||||||||||||||
pos="absolute" | ||||||||||||||||||
left="50%" | ||||||||||||||||||
translateX="-50%" | ||||||||||||||||||
transform="auto" | ||||||||||||||||||
w="calc(100% - 2 * 2.5rem)" // 100% - 2 * 40px | ||||||||||||||||||
maxW="125rem" // 2000px | ||||||||||||||||||
maxH="43rem" // 688px | ||||||||||||||||||
zIndex={-1} | ||||||||||||||||||
/> | ||||||||||||||||||
</Box> | ||||||||||||||||||
) | ||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default as HeroSection } from "./HeroSection" | ||
export { default as SeasonCountdownSection } from "./SeasonCountdownSection" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,22 @@ | ||
import React from "react" | ||
import { Flex } from "@chakra-ui/react" | ||
import HeroSection from "./HeroSection" | ||
import { | ||
SeasonCountdownSection, | ||
HeroSection, | ||
} from "#/pages/LandingPage/components" | ||
|
||
export default function LandingPage() { | ||
return ( | ||
<Flex | ||
w="full" | ||
flexFlow="column" | ||
px={10} | ||
px={16} // 40px + 24px | ||
pb={10} | ||
maxW="100.625rem" | ||
maxW="87.25rem" // 1268px + 2 * (40px + 24px) | ||
mx="auto" | ||
> | ||
<HeroSection /> | ||
<SeasonCountdownSection /> | ||
</Flex> | ||
Comment on lines
-10
to
20
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. I don't quite understand what's going on here. 😅 I don't quite understand why we set Why can't we do it like this? 🤔
|
||
) | ||
} |
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.
Are we going to address it in a separate PR? Or maybe it's already addressed?
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.
It's a barely noticeable issue. Most of ppl won't even notice.
I gave it a shot and it looks like we can't do much with it. It's browser specific and depends on how the browser (Chromium in Ledger Live's case) processes SVG transformations.
Let's leave it for possible future consideration.
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.
If we want to spend more time on the investigation in the future let's create an issue and add it to the list at #154 EPIC.
If there is nothing more we can do let's live with it and remove the TODO.
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.
Done