[website] - Add Wallet page (#419)
* feat: add wallet page and all its necessary components * feat: border and address element * feat: add navigation with responsive view * feat: add floating menu for mobile version * fix: floating menu mobile * fix: menu floating and footer * feat: adds prefooter and fixes footer and pages layouts * fix: footer mobile border * fix: pages with new layout * fix: minor fixes * fix: some minor fixes * fix: changes from review * fix: performance safari issue * fix: changes from review * feat: add grid hero component with proper variants * fix: remove unnecessary prop * feat: some improvements to shared components * fix: nav-desktop z-index * add passive option to scroll listener * lint tailwind classnames --------- Co-authored-by: Pavel Prichodko <14926950+prichodko@users.noreply.github.com>
After Width: | Height: | Size: 164 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 197 KiB |
After Width: | Height: | Size: 195 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 153 KiB |
After Width: | Height: | Size: 230 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 310 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 642 KiB |
After Width: | Height: | Size: 210 KiB |
|
@ -0,0 +1,161 @@
|
|||
import { cva } from 'class-variance-authority'
|
||||
import Image from 'next/image'
|
||||
|
||||
import type { StaticImageData } from 'next/image'
|
||||
|
||||
// Variants for the grid hero class names
|
||||
const biggerCardClassNames = cva(
|
||||
[
|
||||
'min-w-[350px] rounded-[40px]',
|
||||
'px-[22px] py-6 sm:min-w-0 sm:px-[35px] sm:py-[48px] lg:px-5 xl:px-[34px] xl:py-[68px] 2xl:px-[73px]',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['bg-customisation-yellow/10'],
|
||||
turquoise: ['bg-customisation-turquoise/10'],
|
||||
purple: ['bg-customisation-purple/10'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const imagesWithBorders = cva(['border-4', 'rounded-3xl'], {
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['border-customisation-yellow/5'],
|
||||
turquoise: ['border-customisation-turquoise/5'],
|
||||
purple: ['border-customisation-purple/5'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const imagesWithBordersTopOrBottom = cva(['border-4', 'rounded-3xl'], {
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['border-customisation-yellow/5'],
|
||||
turquoise: ['border-customisation-turquoise/5'],
|
||||
purple: ['border-customisation-purple/5'],
|
||||
},
|
||||
alignment: {
|
||||
top: ['border-t-0'],
|
||||
bottom: ['border-b-0'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const thirdCardClassNames = cva(
|
||||
[
|
||||
'flex min-w-[calc(50%-10px)] rounded-[40px]',
|
||||
'px-[22px] sm:min-w-0 sm:px-[35px] lg:px-5 xl:px-[34px] 2xl:px-[73px]',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['bg-customisation-yellow/10'],
|
||||
turquoise: ['bg-customisation-turquoise/10'],
|
||||
purple: ['bg-customisation-purple/10'],
|
||||
},
|
||||
alignment: {
|
||||
top: [
|
||||
'pt-0',
|
||||
'pb-6 sm:pb-[48px] xl:pb-[68px]',
|
||||
'items-start',
|
||||
'justify-start',
|
||||
],
|
||||
bottom: [
|
||||
'pb-0',
|
||||
'pt-6 sm:pt-[48px] xl:pt-[68px]',
|
||||
'items-end',
|
||||
'justify-end',
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const fourthCardClassNames = cva(
|
||||
[
|
||||
'flex min-w-[calc(50%-10px)] rounded-[40px]',
|
||||
'grow items-center justify-center px-0 sm:px-5 md:px-10 lg:px-5 2xl:px-10',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['bg-customisation-yellow/10'],
|
||||
turquoise: ['bg-customisation-turquoise/10'],
|
||||
purple: ['bg-customisation-purple/10'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type Props = {
|
||||
color: 'yellow' | 'turquoise' | 'purple'
|
||||
cardOne: {
|
||||
image: StaticImageData
|
||||
alt: string
|
||||
}
|
||||
cardTwo: {
|
||||
image: StaticImageData
|
||||
alt: string
|
||||
}
|
||||
cardThree: {
|
||||
image: StaticImageData
|
||||
alt: string
|
||||
alignment?: 'top' | 'bottom'
|
||||
}
|
||||
cardFour: {
|
||||
image: StaticImageData
|
||||
alt: string
|
||||
}
|
||||
}
|
||||
|
||||
const GridHero = (props: Props) => {
|
||||
const { color, cardOne, cardTwo, cardThree, cardFour } = props
|
||||
|
||||
return (
|
||||
<div className="relative z-[2] flex w-full max-w-[1504px] justify-start overflow-x-auto px-5 sm:justify-center sm:px-10">
|
||||
<div className="flex min-w-[712px] flex-row gap-3 sm:min-w-fit sm:flex-col sm:gap-5 lg:flex-row">
|
||||
<div className="flex flex-row gap-3 sm:gap-5">
|
||||
<div className={biggerCardClassNames({ color })}>
|
||||
<Image
|
||||
src={cardOne.image}
|
||||
alt={cardOne.alt}
|
||||
className={imagesWithBorders({ color })}
|
||||
/>
|
||||
</div>
|
||||
<div className={biggerCardClassNames({ color })}>
|
||||
<Image
|
||||
src={cardTwo.image}
|
||||
alt={cardTwo.alt}
|
||||
className={imagesWithBorders({ color })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-w-[350px] flex-col gap-3 pr-5 sm:min-w-0 sm:flex-row sm:gap-5 sm:pr-0 lg:flex-col">
|
||||
<div
|
||||
className={thirdCardClassNames({
|
||||
color,
|
||||
alignment: cardThree.alignment || 'bottom',
|
||||
})}
|
||||
>
|
||||
<Image
|
||||
src={cardThree.image}
|
||||
alt={cardThree.alt}
|
||||
className={imagesWithBordersTopOrBottom({
|
||||
color,
|
||||
alignment: cardThree.alignment || 'bottom',
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className={fourthCardClassNames({ color })}>
|
||||
<Image src={cardFour.image} alt={cardFour.alt} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { GridHero }
|
|
@ -0,0 +1,2 @@
|
|||
export { GridHero } from './grid-hero'
|
||||
export { Section } from './section'
|
|
@ -0,0 +1,149 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { PopupIcon } from '@status-im/icons'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import Image from 'next/image'
|
||||
|
||||
import { Border } from './border'
|
||||
|
||||
import type { StaticImageData } from 'next/image'
|
||||
|
||||
type Props = {
|
||||
direction?: 'ltr' | 'rtl'
|
||||
title: string
|
||||
description: string
|
||||
image: StaticImageData
|
||||
imageAlt: string
|
||||
imageSecondary: StaticImageData
|
||||
imageSecondaryAlt: string
|
||||
secondaryDescription: string
|
||||
secondaryTitle: string
|
||||
customNode?: React.ReactNode
|
||||
color: 'yellow' | 'turquoise' | 'purple'
|
||||
information?: string
|
||||
}
|
||||
|
||||
const containerClassNames = cva(
|
||||
[
|
||||
'relative flex w-full justify-center',
|
||||
'overflow-hidden',
|
||||
'max-h-[854px]',
|
||||
'max-w-[582px]',
|
||||
'rounded-[32px]',
|
||||
'py-[65px]',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['bg-customisation-yellow/10'],
|
||||
turquoise: ['bg-customisation-turquoise/10'],
|
||||
purple: ['bg-customisation-purple/10'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const imageClassNames = cva(
|
||||
['rounded-3xl', 'border-4', 'h-auto max-h-[724px] w-auto'],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['border-customisation-yellow/5'],
|
||||
turquoise: ['border-customisation-turquoise/5'],
|
||||
purple: ['border-customisation-purple/5'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const borderContainerClassNames = cva(
|
||||
['absolute left-0 top-0', 'w-full', 'h-[100%]'],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['text-customisation-yellow/5'],
|
||||
turquoise: ['text-customisation-turquoise/5'],
|
||||
purple: ['text-customisation-purple/5'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Section = (props: Props) => {
|
||||
const {
|
||||
title,
|
||||
color,
|
||||
description,
|
||||
image,
|
||||
imageAlt,
|
||||
imageSecondary,
|
||||
imageSecondaryAlt,
|
||||
secondaryDescription,
|
||||
secondaryTitle,
|
||||
direction = 'ltr',
|
||||
} = props
|
||||
|
||||
const directionOrder = direction === 'ltr' ? 'order-0' : 'order-1'
|
||||
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<div className="relative z-[3] grid auto-cols-auto grid-flow-dense auto-rows-[1fr] grid-cols-1 gap-24 px-5 md:grid-cols-2 lg:gap-12 lg:px-[100px] xl:gap-[140px]">
|
||||
<div
|
||||
className={`${directionOrder} flex max-w-[1504px] justify-center overflow-hidden rounded-[32px]`}
|
||||
>
|
||||
<div className={containerClassNames({ color })}>
|
||||
<div className={borderContainerClassNames({ color })}>
|
||||
<Border />
|
||||
</div>
|
||||
<div className="absolute left-0 top-0 h-full w-full bg-[url('/assets/wallet/texture.png')] bg-contain bg-[left_top_0] bg-no-repeat" />
|
||||
<Image
|
||||
src={image}
|
||||
alt={imageAlt}
|
||||
className={imageClassNames({ color })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-start md:justify-center">
|
||||
<div className="flex flex-col">
|
||||
<Image
|
||||
src={imageSecondary}
|
||||
alt={imageSecondaryAlt}
|
||||
width={48}
|
||||
height={48}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col pt-4">
|
||||
<Text size={27} weight="semibold">
|
||||
{title}
|
||||
</Text>
|
||||
<div className="relative flex pt-1">
|
||||
<Text size={27}>{description}</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 flex flex-col rounded-[20px] border border-dashed border-neutral-80/20 p-4">
|
||||
<Text size={19} weight="semibold">
|
||||
{secondaryTitle}
|
||||
</Text>
|
||||
<div className="flex pt-1">
|
||||
<Text size={19}>
|
||||
{secondaryDescription}
|
||||
{props.information && (
|
||||
<span className="relative left-1 top-1 inline-block">
|
||||
<PopupIcon size={20} color="$neutral-50" />
|
||||
</span>
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{props.customNode && (
|
||||
<div className="mt-4 flex flex-col">{props.customNode}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { Section }
|
|
@ -17,8 +17,8 @@ const DatePicker = (props: Props) => {
|
|||
return (
|
||||
<div className="sticky bottom-5 flex justify-center">
|
||||
<Popover alignOffset={8} align="center" sideOffset={8}>
|
||||
<button className="border-neutral-80/5 bg-blur-neutral-5/70 active inline-flex min-h-[30px] cursor-pointer items-center justify-center gap-2 rounded-xl border-solid pl-3 pr-2 uppercase text-neutral-100 backdrop-blur-sm">
|
||||
<span className="text-blur-neutral-80/80 text-[13px] font-medium">
|
||||
<button className="active inline-flex min-h-[30px] cursor-pointer items-center justify-center gap-2 rounded-xl border-solid border-neutral-80/5 bg-blur-neutral-5/70 pl-3 pr-2 uppercase text-neutral-100 backdrop-blur-sm">
|
||||
<span className="text-[13px] font-medium text-blur-neutral-80/80">
|
||||
Filter between
|
||||
</span>
|
||||
<span className="text-[13px] font-medium text-neutral-100">
|
||||
|
@ -26,7 +26,7 @@ const DatePicker = (props: Props) => {
|
|||
selected?.to ? formatDate(selected.to) : 'End Date'
|
||||
}`}
|
||||
</span>
|
||||
<div className="bg-neutral-80/5 h-full w-[1px]" />
|
||||
<div className="h-full w-[1px] bg-neutral-80/5" />
|
||||
<EditIcon size={20} color="$neutral-80-opa-40" />
|
||||
</button>
|
||||
<Popover.Content>
|
||||
|
|
|
@ -13,7 +13,7 @@ export const ErrorPage = (props: Props) => {
|
|||
// todo!: design review, not in designs
|
||||
case ERROR_CODES.NOT_FOUND:
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-8 bg-white text-center">
|
||||
<div className="bg-white flex h-full w-full flex-col items-center justify-center gap-8 text-center">
|
||||
<div className="h-[160px] w-[160px] rounded-full bg-[#b3b3b3]" />
|
||||
<Text size={27} weight="semibold">
|
||||
Page not found.
|
||||
|
@ -24,7 +24,7 @@ export const ErrorPage = (props: Props) => {
|
|||
case ERROR_CODES.INTERNAL_SERVER_ERROR:
|
||||
default:
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-8 bg-white text-center">
|
||||
<div className="bg-white flex h-full w-full flex-col items-center justify-center gap-8 text-center">
|
||||
<div className="h-[160px] w-[160px] rounded-full bg-[hsla(355,47%,50%,1)]" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text size={27} weight="semibold">
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { Button, Text } from '@status-im/components'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
description: string
|
||||
action: string
|
||||
}
|
||||
|
||||
const ActionCard = (props: Props) => {
|
||||
const { title, description, action } = props
|
||||
|
||||
return (
|
||||
<div className="flex items-center rounded-[20px] border border-neutral-90 bg-neutral-95 px-5 py-3">
|
||||
<div className="grid flex-1 gap-px">
|
||||
<Text size={19} color="$white-100" weight="semibold">
|
||||
{title}
|
||||
</Text>
|
||||
<Text size={15} color="$white-100">
|
||||
{description}
|
||||
</Text>
|
||||
</div>
|
||||
<Button size={32} variant="darkGrey">
|
||||
{action}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { ActionCard }
|
|
@ -0,0 +1,13 @@
|
|||
export const Dot = () => (
|
||||
<span className="flex self-center">
|
||||
<svg
|
||||
width="2"
|
||||
height="2"
|
||||
viewBox="0 0 2 2"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="1" cy="1" r="1" fill="#647084" />
|
||||
</svg>
|
||||
</span>
|
||||
)
|
|
@ -0,0 +1,31 @@
|
|||
export const MessariIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_2650_62112)">
|
||||
<path
|
||||
d="M11.9531 4.24242L16.0019 0V12.2667L11.9531 15.9515V4.24242Z"
|
||||
fill="#647084"
|
||||
/>
|
||||
<path
|
||||
d="M5.97656 6.37524L7.92778 7.97523L10.0253 6.13281V12.2661L5.97656 15.951V6.37524Z"
|
||||
fill="#647084"
|
||||
/>
|
||||
<path
|
||||
d="M4.07317 4.24242L0 0V16L4.07317 12.5576V4.24242Z"
|
||||
fill="#647084"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2650_62112">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
import { Text } from '@status-im/components'
|
||||
|
||||
import { SOCIALS } from '@/config/links'
|
||||
|
||||
import { Logo } from '../logo'
|
||||
import { AccordionMenu } from '../navigation/accordion-menu'
|
||||
import { Dot } from './components/dot'
|
||||
import { MessariIcon } from './components/messari-icon'
|
||||
|
||||
type Props = {
|
||||
hasBorderTop?: boolean
|
||||
}
|
||||
|
||||
export const FooterMobile = (props: Props) => {
|
||||
const { hasBorderTop } = props
|
||||
|
||||
return (
|
||||
<footer
|
||||
className={`block border-dashed border-neutral-80 ${
|
||||
hasBorderTop ? 'border-t' : 'border-t-0'
|
||||
} pb-12 sm:hidden`}
|
||||
>
|
||||
<div className="">
|
||||
<div className="flex flex-col px-2 pt-6">
|
||||
<div className="px-3 pb-3">
|
||||
<Logo />
|
||||
</div>
|
||||
<div className="pb-5">
|
||||
<AccordionMenu />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-2 border-t border-dashed border-neutral-80 px-6 pt-6">
|
||||
<div className="flex w-full flex-col items-start gap-2 ">
|
||||
<Text size={11} color="$neutral-50">
|
||||
© Status Research & Development GmbH
|
||||
</Text>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Text size={11} color="$neutral-40">
|
||||
Terms of use
|
||||
</Text>
|
||||
<Dot />
|
||||
<Text size={11} color="$neutral-40">
|
||||
Privacy policy
|
||||
</Text>
|
||||
<Dot />
|
||||
<Text size={11} color="$neutral-40">
|
||||
Cookies
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start gap-3">
|
||||
<div className="flex w-full items-center justify-start gap-3 py-2">
|
||||
<MessariIcon />
|
||||
<Text size={11} color="$neutral-50">
|
||||
Messari Transparency Verified
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
{Object.values(SOCIALS).map(social => {
|
||||
const IconComponent = social.icon
|
||||
return (
|
||||
<IconComponent
|
||||
key={social.name}
|
||||
size={20}
|
||||
color="$neutral-40"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
import { LINKS, SOCIALS } from '@/config/links'
|
||||
|
||||
import { Logo } from '../logo'
|
||||
import { Dot } from './components/dot'
|
||||
import { MessariIcon } from './components/messari-icon'
|
||||
import { Section } from './section'
|
||||
|
||||
type Props = {
|
||||
hasBorderTop?: boolean
|
||||
}
|
||||
|
||||
const section = cva(
|
||||
[
|
||||
'border-neutral-80',
|
||||
'mb-6',
|
||||
'flex',
|
||||
'items-start',
|
||||
'border-dashed',
|
||||
'pl-6',
|
||||
'pt-6',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
hasBorderTop: {
|
||||
true: ['border-t'],
|
||||
false: ['border-t-0'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export const Footer = (props: Props) => {
|
||||
const { hasBorderTop } = props
|
||||
|
||||
return (
|
||||
<footer className="hidden pb-3 sm:block">
|
||||
<div className="grid grid-cols-4 gap-5 sm:gap-0 lg:mb-10 lg:grid-cols-8">
|
||||
<div
|
||||
className={section({
|
||||
hasBorderTop,
|
||||
})}
|
||||
>
|
||||
<Logo />
|
||||
</div>
|
||||
{Object.entries(LINKS).map(([title, links], index) => (
|
||||
<Section
|
||||
key={title}
|
||||
title={title}
|
||||
links={links}
|
||||
hasBorderLeft={index !== 3}
|
||||
hasBorderTop={hasBorderTop}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col items-start justify-between gap-2 px-5 lg:px-6 md-lg:flex-row md-lg:items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Text size={11} color="$neutral-50">
|
||||
© Status Research & Development GmbH
|
||||
</Text>
|
||||
<Dot />
|
||||
<div className="flex gap-3">
|
||||
<Text size={11} color="$neutral-40">
|
||||
Terms of use
|
||||
</Text>
|
||||
<Text size={11} color="$neutral-40">
|
||||
Privacy policy
|
||||
</Text>
|
||||
<Text size={11} color="$neutral-40">
|
||||
Cookies
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<MessariIcon />
|
||||
<Text size={11} color="$neutral-50">
|
||||
Messari Transparency Verified
|
||||
</Text>
|
||||
<Dot />
|
||||
</div>
|
||||
{Object.values(SOCIALS).map(social => {
|
||||
const IconComponent = social.icon
|
||||
return (
|
||||
<IconComponent key={social.name} size={20} color="$neutral-40" />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
import { Link } from '../link'
|
||||
|
||||
import type { Links } from '@/config/links'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
links: Links
|
||||
hasBorderLeft?: boolean
|
||||
hasBorderTop?: boolean
|
||||
}
|
||||
|
||||
const section = cva(
|
||||
[
|
||||
'border-neutral-80',
|
||||
'relative',
|
||||
'grid',
|
||||
'gap-3',
|
||||
'border-dashed',
|
||||
'px-5',
|
||||
'pb-6',
|
||||
'pt-6',
|
||||
'lg:border-l',
|
||||
'lg:pb-0',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
hasBorderTop: {
|
||||
true: ['border-t'],
|
||||
false: ['border-t-0'],
|
||||
},
|
||||
hasBorderLeft: {
|
||||
true: ['border-l'],
|
||||
false: ['border-l-0'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Section = (props: Props) => {
|
||||
const { title, links, hasBorderLeft, hasBorderTop } = props
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={section({
|
||||
hasBorderTop,
|
||||
hasBorderLeft,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(180deg, transparent 0%, rgba(9 16 28 / 100%))',
|
||||
}}
|
||||
className=" absolute left-[-4px] top-0 h-full w-2"
|
||||
/>
|
||||
<Text size={13} color="$neutral-50">
|
||||
{title}
|
||||
</Text>
|
||||
<ul className="grid gap-1">
|
||||
{links.map(link => (
|
||||
<li key={link.name}>
|
||||
<Link href={link.href}>
|
||||
<Text size={15} color="$white-100" weight="medium">
|
||||
{link.name}
|
||||
</Text>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { Section }
|
|
@ -8,10 +8,11 @@ import logoLearnSrc from '../../public/images/logo/learn.svg'
|
|||
|
||||
type Props = {
|
||||
label?: boolean
|
||||
isTopbarDesktop?: boolean
|
||||
}
|
||||
|
||||
export const Logo = (props: Props) => {
|
||||
const { label = true } = props
|
||||
const { label = true, isTopbarDesktop } = props
|
||||
|
||||
const { pathname } = useRouter()
|
||||
|
||||
|
@ -39,7 +40,7 @@ export const Logo = (props: Props) => {
|
|||
width="70"
|
||||
height="16"
|
||||
fill="none"
|
||||
className="mr-[10px]"
|
||||
className={`mr-[10px] ${isTopbarDesktop ? 'hidden lg:block' : ''}`}
|
||||
>
|
||||
<path
|
||||
fill="#fff"
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import { useState } from 'react'
|
||||
|
||||
import * as Accordion from '@radix-ui/react-accordion'
|
||||
import { Text } from '@status-im/components'
|
||||
import { ChevronRightIcon, ExternalIcon } from '@status-im/icons'
|
||||
|
||||
import { LINKS } from '@/config/links'
|
||||
|
||||
import { Link } from '../link'
|
||||
|
||||
const AccordionMenu = () => {
|
||||
const [openLink, setOpenLink] = useState('')
|
||||
|
||||
const handleToggle = (value: string) => {
|
||||
setOpenLink(value === openLink ? '' : value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col px-3">
|
||||
{Object.entries(LINKS).map(([name, links]) => (
|
||||
<Accordion.Root
|
||||
key={name}
|
||||
type="single"
|
||||
collapsible
|
||||
value={openLink}
|
||||
onValueChange={handleToggle}
|
||||
className="accordion-root flex flex-col pb-3 first-of-type:pt-3"
|
||||
>
|
||||
<Accordion.Item key={name} value={name} className="accordion-item">
|
||||
<div>
|
||||
<Accordion.Trigger className="accordion-trigger">
|
||||
<div className="accordion-chevron inline-flex h-5 w-5">
|
||||
<ChevronRightIcon size={20} color="$white-100" />
|
||||
</div>
|
||||
<Text size={19} weight="medium" color={'$white-100'}>
|
||||
{name}
|
||||
</Text>
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content className="accordion-content pl-5">
|
||||
<div className="overflow-hidden">
|
||||
{links.map((link, index) => {
|
||||
const external = link.href.startsWith('http')
|
||||
return (
|
||||
<div
|
||||
key={link.name + index}
|
||||
className={`pt-3 transition-opacity first-of-type:pt-5 last-of-type:pb-5 hover:opacity-50`}
|
||||
>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<Text size={15} weight="medium" color="$white-100">
|
||||
{link.name}
|
||||
</Text>
|
||||
|
||||
{external && (
|
||||
<ExternalIcon size={20} color="$white-100" />
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Accordion.Content>
|
||||
</div>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { AccordionMenu }
|
|
@ -1,5 +1,3 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import * as NavigationMenu from '@radix-ui/react-navigation-menu'
|
||||
import { Button, Text } from '@status-im/components'
|
||||
import { DownloadIcon, ExternalIcon } from '@status-im/icons'
|
||||
|
@ -7,39 +5,22 @@ import { cx } from 'class-variance-authority'
|
|||
|
||||
import { LINKS } from '@/config/links'
|
||||
|
||||
import { Link } from './link'
|
||||
import { Logo } from './logo'
|
||||
import { Link } from '../link'
|
||||
import { Logo } from '../logo'
|
||||
|
||||
export const NavMenu = () => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
// Using ref to prevent re-running of useEffect
|
||||
const visibleRef = useRef(visible)
|
||||
visibleRef.current = visible
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
if (window.scrollY > 60) {
|
||||
visibleRef.current === false && setVisible(true)
|
||||
} else {
|
||||
visibleRef.current === true && setVisible(false)
|
||||
}
|
||||
type Props = {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
handleScroll()
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
const FloatingDesktop = (props: Props) => {
|
||||
const { visible } = props
|
||||
|
||||
return (
|
||||
<NavigationMenu.Root
|
||||
data-visible={visible}
|
||||
className={cx([
|
||||
'fixed left-1/2 top-5 z-10 min-w-[746px] -translate-x-1/2 overflow-hidden',
|
||||
'rounded-2xl border border-neutral-80/5 bg-blur-neutral-80/80 backdrop-blur-md',
|
||||
'data-[visible=false]:pointer-events-none',
|
||||
'data-[visible=true]:pointer-events-auto',
|
||||
'opacity-0 transition-opacity data-[visible=true]:opacity-100',
|
||||
])}
|
||||
>
|
||||
|
@ -98,3 +79,5 @@ export const NavMenu = () => {
|
|||
</NavigationMenu.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export { FloatingDesktop }
|
|
@ -0,0 +1,82 @@
|
|||
import { useRef, useState } from 'react'
|
||||
|
||||
import { animated, useScroll } from '@react-spring/web'
|
||||
import { cx } from 'class-variance-authority'
|
||||
|
||||
import { useLockScroll } from '@/hooks/use-lock-scroll'
|
||||
import { useOutsideClick } from '@/hooks/use-outside-click'
|
||||
|
||||
import { FloatingDesktop } from './floating-desktop'
|
||||
import { FloatingMobile } from './floating-mobile'
|
||||
|
||||
const FloatingMenu = (): JSX.Element => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
const [open, setOpen] = useState(false)
|
||||
const openRef = useRef(open)
|
||||
openRef.current = open
|
||||
|
||||
// Using ref to prevent re-running of useEffect
|
||||
const visibleRef = useRef(visible)
|
||||
const scrollYRef = useRef(0)
|
||||
visibleRef.current = visible
|
||||
|
||||
// Close menu on outside click
|
||||
const ref = useOutsideClick(() => setOpen(false))
|
||||
useLockScroll(open)
|
||||
|
||||
useScroll({
|
||||
onChange: ({ value: { scrollYProgress } }) => {
|
||||
const isMenuOpen = openRef.current
|
||||
const isScrollingUp = scrollYProgress < scrollYRef.current
|
||||
const detectionPoint = scrollYProgress > 0.005
|
||||
|
||||
if (detectionPoint && isScrollingUp) {
|
||||
if (!visibleRef.current) {
|
||||
setVisible(true)
|
||||
}
|
||||
} else {
|
||||
if (!isMenuOpen) {
|
||||
setVisible(false)
|
||||
}
|
||||
}
|
||||
scrollYRef.current = scrollYProgress
|
||||
},
|
||||
default: {
|
||||
immediate: true,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<animated.div
|
||||
ref={ref}
|
||||
style={{
|
||||
opacity: visible ? 1 : 0,
|
||||
}}
|
||||
className={cx([
|
||||
'fixed left-1/2 top-1 z-10 flex -translate-x-1/2 flex-col items-center justify-between p-2 pb-0 lg:hidden',
|
||||
'bg-blur-neutral-80/80 border-neutral-80/5 rounded-2xl border backdrop-blur-md',
|
||||
' w-[calc(100%-24px)]',
|
||||
' opacity-0 transition-opacity data-[visible=true]:opacity-100',
|
||||
'z-10',
|
||||
])}
|
||||
>
|
||||
<FloatingMobile open={open} setOpen={setOpen} />
|
||||
</animated.div>
|
||||
<animated.div
|
||||
style={{
|
||||
opacity: visible ? 1 : 0,
|
||||
}}
|
||||
className={cx([
|
||||
'fixed left-1/2 top-5 z-10 w-max min-w-[746px] -translate-x-1/2 overflow-hidden',
|
||||
'bg-blur-neutral-80/80 border-neutral-80/5 rounded-2xl border backdrop-blur-md',
|
||||
'md-lg:block hidden',
|
||||
])}
|
||||
>
|
||||
<FloatingDesktop visible={visible} />
|
||||
</animated.div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { FloatingMenu }
|
|
@ -0,0 +1,61 @@
|
|||
import { Button, IconButton } from '@status-im/components'
|
||||
import { CloseIcon, DownloadIcon, MenuIcon } from '@status-im/icons'
|
||||
|
||||
import { Link } from '../link'
|
||||
import { Logo } from '../logo'
|
||||
import { AccordionMenu } from './accordion-menu'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
}
|
||||
|
||||
const FloatingMobile = (props: Props) => {
|
||||
const { open, setOpen } = props
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="z-10 flex w-full items-center justify-between">
|
||||
<div className="flex">
|
||||
<Link href="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<IconButton
|
||||
variant="outline"
|
||||
icon={
|
||||
open ? (
|
||||
<CloseIcon size={20} color={'$white-100'} />
|
||||
) : (
|
||||
<MenuIcon size={20} />
|
||||
)
|
||||
}
|
||||
onPress={() => setOpen(!open)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
height: open ? '100%' : 0,
|
||||
opacity: open ? 1 : 0,
|
||||
pointerEvents: open ? 'auto' : 'none',
|
||||
overflow: open ? 'auto' : 'hidden',
|
||||
}}
|
||||
className={`z-10 flex w-full flex-col justify-between pt-2`}
|
||||
>
|
||||
<AccordionMenu />
|
||||
<div className="flex justify-center py-3">
|
||||
<Button
|
||||
size={40}
|
||||
variant="grey"
|
||||
icon={<DownloadIcon size={20} />}
|
||||
fullWidth
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { FloatingMobile }
|
|
@ -0,0 +1,88 @@
|
|||
import * as NavigationMenu from '@radix-ui/react-navigation-menu'
|
||||
import { Button, Text } from '@status-im/components'
|
||||
import { DownloadIcon, ExternalIcon } from '@status-im/icons'
|
||||
import { cx } from 'class-variance-authority'
|
||||
|
||||
import { Logo } from '@/components/logo'
|
||||
import { LINKS } from '@/config/links'
|
||||
|
||||
import { Link } from '../link'
|
||||
|
||||
const NavDesktop = () => {
|
||||
return (
|
||||
<>
|
||||
<NavigationMenu.Root className="relative z-10 hidden md-lg:block">
|
||||
<div className="flex items-center px-6">
|
||||
<div className="mr-5 flex shrink-0 ">
|
||||
<Link href="/">
|
||||
<Logo isTopbarDesktop />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<NavigationMenu.List className="flex items-center">
|
||||
{Object.entries(LINKS).map(([name, links]) => (
|
||||
<NavigationMenu.Item key={name}>
|
||||
<NavigationMenu.Trigger className="py-4 pr-5 aria-expanded:opacity-50">
|
||||
<Text size={15} weight="medium" color="$white-100">
|
||||
{name}
|
||||
</Text>
|
||||
</NavigationMenu.Trigger>
|
||||
<NavigationMenu.Content className="grid gap-3 pb-8 pl-[164px] pt-3">
|
||||
{links.map(link => {
|
||||
const external = link.href.startsWith('http')
|
||||
|
||||
return (
|
||||
<NavigationMenu.Link key={link.name} asChild>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<Text
|
||||
size={27}
|
||||
weight="semibold"
|
||||
color="$white-100"
|
||||
>
|
||||
{link.name}
|
||||
</Text>
|
||||
{external && (
|
||||
<ExternalIcon size={20} color="$white-100" />
|
||||
)}
|
||||
</Link>
|
||||
</NavigationMenu.Link>
|
||||
)
|
||||
})}
|
||||
</NavigationMenu.Content>
|
||||
</NavigationMenu.Item>
|
||||
))}
|
||||
</NavigationMenu.List>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end ">
|
||||
<Button
|
||||
size={32}
|
||||
variant="darkGrey"
|
||||
icon={<DownloadIcon size={20} />}
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<NavigationMenu.Viewport
|
||||
className={cx([
|
||||
'data-[state=closed]:animate-heightOut data-[state=open]:animate-heightIn',
|
||||
'h-[var(--radix-navigation-menu-viewport-height)] transition-height',
|
||||
// 'data-[state=open]:animate-heightIn animate-',
|
||||
// 'data-[state=closed]:animate-heightOut',
|
||||
// 'transition-height h-[var(--radix-navigation-menu-viewport-height)]',
|
||||
// 'transition-height mb-8 h-[var(--radix-navigation-menu-viewport-height)] duration-1000',
|
||||
// 'h-[var(--radix-navigation-menu-viewport-height)]',
|
||||
// 'data-[state=open]:animate-scaleIn data-[state=closed]:animate-scaleOut relative mt-[10px] h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] overflow-hidden rounded-[6px] bg-white transition-[width,height] duration-300 sm:w-[var(--radix-navigation-menu-viewport-width)]',
|
||||
])}
|
||||
/>
|
||||
</NavigationMenu.Root>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { NavDesktop }
|
|
@ -0,0 +1,67 @@
|
|||
import { useState } from 'react'
|
||||
|
||||
import { Button, IconButton } from '@status-im/components'
|
||||
import { CloseIcon, DownloadIcon, MenuIcon } from '@status-im/icons'
|
||||
|
||||
import { Logo } from '@/components/logo'
|
||||
import { useLockScroll } from '@/hooks/use-lock-scroll'
|
||||
|
||||
import { Link } from '../link'
|
||||
import { AccordionMenu } from './accordion-menu'
|
||||
|
||||
const NavMobile = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const screenHeight = typeof window !== 'undefined' ? window.innerHeight : 0
|
||||
|
||||
useLockScroll(open)
|
||||
const handleToggle = () => {
|
||||
setOpen(prevOpen => !prevOpen)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="z-10 flex flex-col items-center justify-between px-5 py-3 pb-1 md-lg:hidden">
|
||||
<div className="z-10 flex w-full items-center justify-between">
|
||||
<div className="flex">
|
||||
<Link href="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<IconButton
|
||||
variant="outline"
|
||||
icon={
|
||||
open ? (
|
||||
<CloseIcon size={20} color="$white-100" />
|
||||
) : (
|
||||
<MenuIcon size={20} color="$white-100" />
|
||||
)
|
||||
}
|
||||
onPress={handleToggle}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
height: open ? screenHeight - 100 : 0,
|
||||
opacity: open ? 1 : 0,
|
||||
pointerEvents: open ? 'auto' : 'none',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
className={`z-10 flex w-full flex-col justify-between bg-blur-neutral-100/70 pt-2 transition-all duration-300`}
|
||||
>
|
||||
<AccordionMenu />
|
||||
<div className="flex justify-center py-3">
|
||||
<Button
|
||||
size={40}
|
||||
variant="grey"
|
||||
icon={<DownloadIcon size={20} />}
|
||||
fullWidth
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { NavMobile }
|
|
@ -1,135 +0,0 @@
|
|||
import { Button, Text } from '@status-im/components'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { LINKS, SOCIALS } from '@/config/links'
|
||||
|
||||
import { Logo } from './logo'
|
||||
|
||||
import type { Links } from '@/config/links'
|
||||
|
||||
export const PageFooter = () => {
|
||||
return (
|
||||
<footer className="px-6 pb-3 pt-14">
|
||||
<div className="mb-6">
|
||||
<Logo />
|
||||
</div>
|
||||
<div className="mb-10 grid grid-cols-2 lg:grid-cols-8">
|
||||
{Object.entries(LINKS).map(([title, links]) => (
|
||||
<Section key={title} title={title} links={links} />
|
||||
))}
|
||||
|
||||
<div>
|
||||
<Text size={13} color="$neutral-50">
|
||||
{"Let's connect"}
|
||||
</Text>
|
||||
<div className="flex flex-col">
|
||||
{Object.values(SOCIALS).map(social => (
|
||||
<Text size={11} key={social.name}>
|
||||
{social.name}
|
||||
</Text>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 grid gap-5 lg:grid-cols-[2fr_1fr]">
|
||||
<ActionCard
|
||||
title="Own a community?"
|
||||
description="Don't give Discord and Telegram power over your community"
|
||||
action="Take back control"
|
||||
/>
|
||||
<ActionCard
|
||||
title="Give us feedback!"
|
||||
description="Tell us how to make it better"
|
||||
action="Upvote"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Text size={11} color="$neutral-50">
|
||||
© Status Research & Development GmbH
|
||||
</Text>
|
||||
|
||||
<span>
|
||||
<svg
|
||||
width="2"
|
||||
height="2"
|
||||
viewBox="0 0 2 2"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="1" cy="1" r="1" fill="#647084" />
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Text size={11} color="$neutral-40">
|
||||
Terms of use
|
||||
</Text>
|
||||
<Text size={11} color="$neutral-40">
|
||||
Privacy policy
|
||||
</Text>
|
||||
<Text size={11} color="$neutral-40">
|
||||
Cookies
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
type SectionProps = {
|
||||
title: string
|
||||
links: Links
|
||||
}
|
||||
|
||||
const Section = (props: SectionProps) => {
|
||||
const { title, links } = props
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="grid gap-3">
|
||||
<Text size={13} color="$neutral-50">
|
||||
{title}
|
||||
</Text>
|
||||
<ul className="grid gap-1">
|
||||
{links.map(link => (
|
||||
<li key={link.name}>
|
||||
<Link href={link.href}>
|
||||
<Text size={15} color="$white-100" weight="medium">
|
||||
{link.name}
|
||||
</Text>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type ActionCardProps = {
|
||||
title: string
|
||||
description: string
|
||||
action: string
|
||||
}
|
||||
|
||||
const ActionCard = (props: ActionCardProps) => {
|
||||
const { title, description, action } = props
|
||||
|
||||
return (
|
||||
<div className="bg-netural-95 flex items-center rounded-[20px] border border-neutral-90 px-5 py-3">
|
||||
<div className="grid flex-1 gap-px">
|
||||
<Text size={19} color="$white-100" weight="semibold">
|
||||
{title}
|
||||
</Text>
|
||||
<Text size={15} color="$white-100">
|
||||
{description}
|
||||
</Text>
|
||||
</div>
|
||||
<Button size={32} variant="darkGrey">
|
||||
{action}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import { Button, Text } from '@status-im/components'
|
||||
import { DownloadIcon } from '@status-im/icons'
|
||||
import Image from 'next/image'
|
||||
|
||||
import logoSrc from '../../public/images/logo/default.svg'
|
||||
import { ComingSoon } from './coming-soon'
|
||||
|
||||
const Prefooter = () => {
|
||||
return (
|
||||
<div className="bg-neutral-100 p-5 py-[120px]">
|
||||
<div className="flex justify-center">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<Image src={logoSrc} alt="Status logo" width={80} />
|
||||
<h1 className="py-4 pb-3 pt-5 text-center text-[40px] font-bold leading-[44px] text-white-100 lg:pb-5 lg:text-[88px] lg:leading-[84px]">
|
||||
Be unstoppable
|
||||
</h1>
|
||||
<span className="max-w-md text-center font-bold">
|
||||
<Text size={19} color={'$white-100'}>
|
||||
Use the open source, decentralized crypto communication super app.
|
||||
</Text>
|
||||
</span>
|
||||
<div className="relative flex flex-col justify-center pt-8">
|
||||
<div className="inline-flex justify-center">
|
||||
<Button
|
||||
size={40}
|
||||
icon={<DownloadIcon size={20} />}
|
||||
variant="yellow"
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative max-w-xs pt-4 text-center leading-3">
|
||||
<Text size={11} color={'$neutral-40'}>
|
||||
Betas for Mac, Windows, Linux <br />
|
||||
Alphas for iOS & Android
|
||||
</Text>
|
||||
<div className="absolute right-[-48px] top-9">
|
||||
<ComingSoon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { Prefooter }
|
|
@ -334,8 +334,8 @@ export function PreviewPage(props: PreviewPageProps) {
|
|||
style={!bannerURL ? getGradientStyles(data) : undefined}
|
||||
className="relative h-full bg-gradient-to-b from-[var(--gradient-color)] to-[#fff] to-20% xl:grid xl:grid-cols-[560px,auto]"
|
||||
>
|
||||
<div className="absolute left-0 right-0 top-0 xl:hidden">
|
||||
<div className="from-white-100 to-white-60 absolute h-full w-full bg-gradient-to-t" />
|
||||
<div className="absolute inset-x-0 top-0 xl:hidden">
|
||||
<div className="absolute h-full w-full bg-gradient-to-t from-white-100 to-white-60" />
|
||||
{bannerURL && (
|
||||
<img
|
||||
className="aspect-video h-full w-full object-cover"
|
||||
|
@ -435,7 +435,7 @@ export function PreviewPage(props: PreviewPageProps) {
|
|||
|
||||
{/* INSTRUCTIONS */}
|
||||
<div className="mb-6 grid gap-3">
|
||||
<div className="border-neutral-10 bg-white-100 rounded-2xl border px-4 py-3">
|
||||
<div className="rounded-2xl border border-neutral-10 bg-white-100 px-4 py-3">
|
||||
<h3 className="mb-2 text-[15px] font-semibold xl:text-[19px]">
|
||||
{INSTRUCTIONS_HEADING[type]}
|
||||
</h3>
|
||||
|
@ -475,7 +475,7 @@ export function PreviewPage(props: PreviewPageProps) {
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="border-neutral-10 bg-white-100 flex flex-col items-start gap-4 rounded-2xl border p-4 pt-3">
|
||||
<div className="flex flex-col items-start gap-4 rounded-2xl border border-neutral-10 bg-white-100 p-4 pt-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text size={15} weight="semibold">
|
||||
Have Status already?
|
||||
|
|
|
@ -26,8 +26,8 @@ export const QrDialog = (props: Props) => {
|
|||
<Dialog.Content className="data-[state=open]:animate-contentShow fixed inset-0 flex items-center justify-center focus:outline-none">
|
||||
<div>
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="bg-white-5 aspect-square w-full max-w-[335px] rounded-2xl p-3">
|
||||
<div className="bg-white-100 rounded-xl p-3">
|
||||
<div className="aspect-square w-full max-w-[335px] rounded-2xl bg-white-5 p-3">
|
||||
<div className="rounded-xl bg-white-100 p-3">
|
||||
<QRCodeSVG value={value} height={286} width={286} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
import { Button, Counter, Tag, Text } from '@status-im/components'
|
||||
|
||||
import { useParalax } from '@/hooks/use-parallax'
|
||||
|
||||
import { ParalaxCircle } from './parallax-circle'
|
||||
|
||||
const ComparisionSection = () => {
|
||||
const { top, bottom } = useParalax({
|
||||
initialTop: -690,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="relative grid grid-cols-4 border-t border-dashed border-neutral-80/20 bg-white-100 mix-blend-normal">
|
||||
<div className="col-span-2 border-r border-dashed border-neutral-80/20 py-[160px] pl-10 pr-[60px]">
|
||||
<div className="flex h-full flex-col justify-between">
|
||||
<div className="inline-flex max-w-[646px] flex-col">
|
||||
<div className="inline-flex">
|
||||
<Tag label="Use case" size={24} />
|
||||
</div>
|
||||
<h2 className="pt-4 text-[40px] font-bold leading-[44px]">
|
||||
Alice has 50 DAI on both Ethereum Mainnet and Optimism and wants
|
||||
to send 100 DAI to Bob on Arbitrum.
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 flex max-w-[341px] flex-col rounded-[20px] border border-dashed border-neutral-80/20 p-4">
|
||||
<Text size={19} weight="semibold">
|
||||
Finally! Multi-chain done right!
|
||||
</Text>
|
||||
<div className="flex pt-1">
|
||||
<Text size={19}>
|
||||
Interested in how Status’s send with auto routing and bridging
|
||||
works and helps users?{' '}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="inline-flex pt-1">
|
||||
<Button variant="outline">Read more</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-r border-dashed border-neutral-80/20 px-10 py-[160px]">
|
||||
<Text size={19} weight="semibold">
|
||||
Other wallets
|
||||
</Text>
|
||||
<div className="flex flex-col pt-3">
|
||||
{listOneData.map(item => (
|
||||
<Item key={item.count} {...item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-10 py-[160px]">
|
||||
<Text size={19} weight="semibold">
|
||||
Status Wallet
|
||||
</Text>
|
||||
<div className="flex flex-col pt-3">
|
||||
{listTwoData.map(item => (
|
||||
<Item key={item.count} {...item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<ParalaxCircle initialTop={-600} />
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
top: `${top}px`,
|
||||
bottom: `${bottom}px`,
|
||||
left: -40,
|
||||
// TODO: use font from design when it's ready
|
||||
fontFamily: 'Menlo',
|
||||
}}
|
||||
className="absolute whitespace-nowrap text-[240px] font-bold leading-[212px] text-neutral-80/5"
|
||||
>
|
||||
eth:opt:arb:0xAgafhja
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { ComparisionSection }
|
||||
|
||||
const listOneData = [
|
||||
{
|
||||
count: 1,
|
||||
label: 'Open dApp Browser',
|
||||
},
|
||||
{
|
||||
count: 2,
|
||||
label: 'Visit Bridge dApp',
|
||||
},
|
||||
{
|
||||
count: 3,
|
||||
label: 'Bridge DAI from Mainnet to Arbitrum',
|
||||
},
|
||||
{
|
||||
count: 4,
|
||||
label: 'Send DAI on Arbitrum',
|
||||
},
|
||||
{
|
||||
count: 5,
|
||||
label: 'Open dApp Browser',
|
||||
},
|
||||
{
|
||||
count: 6,
|
||||
label: 'Visit Bridge dApp',
|
||||
},
|
||||
{
|
||||
count: 7,
|
||||
label: 'Bridge DAI from Optimism to Arbitrum',
|
||||
},
|
||||
{
|
||||
count: 8,
|
||||
label: 'Send DAI on Arbitrum',
|
||||
noBorder: true,
|
||||
},
|
||||
{
|
||||
count: '😮💨',
|
||||
label: 'Is that it?',
|
||||
secondaryLabel: 'Did I use cheapest route and bridges?',
|
||||
noBorder: true,
|
||||
},
|
||||
]
|
||||
|
||||
const listTwoData = [
|
||||
{
|
||||
count: 1,
|
||||
label: 'Select the token',
|
||||
},
|
||||
{
|
||||
count: 2,
|
||||
label: 'Select the amount',
|
||||
},
|
||||
{
|
||||
count: 3,
|
||||
label: 'Send',
|
||||
noBorder: true,
|
||||
},
|
||||
{
|
||||
count: '🎉',
|
||||
label: 'That’s it!',
|
||||
secondaryLabel: 'Lowest possible cost!',
|
||||
noBorder: true,
|
||||
},
|
||||
]
|
||||
|
||||
const Item = (props: {
|
||||
count: string | number
|
||||
label: string
|
||||
secondaryLabel?: string
|
||||
noBorder?: boolean
|
||||
}) => {
|
||||
const { count, label, secondaryLabel, noBorder } = props
|
||||
const isNumber = typeof count === 'number'
|
||||
return (
|
||||
<div
|
||||
className={`flex border-neutral-80/20 ${
|
||||
noBorder ? 'border-b-0' : 'border-b'
|
||||
} border-dashed py-3`}
|
||||
>
|
||||
{isNumber ? (
|
||||
<Counter value={count} type="outline" />
|
||||
) : (
|
||||
<Text size={19}>{count}</Text>
|
||||
)}
|
||||
<div className="flex flex-col pl-[10px]">
|
||||
<Text size={19} weight={isNumber ? 'regular' : 'semibold'}>
|
||||
{label}
|
||||
</Text>
|
||||
{!isNumber && (
|
||||
<Text size={19} weight="regular">
|
||||
{secondaryLabel}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import { Text } from '@status-im/components'
|
||||
|
||||
const HandsSection = () => {
|
||||
return (
|
||||
<div className="relative bg-[url('/assets/wallet/background-pattern.png')] bg-[length:100%_100%] bg-no-repeat py-[322px]">
|
||||
<div className="mx-40 mb-24">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<h2 className="max-w-[600px] py-4 pb-5 text-center text-[64px] font-bold leading-[68px]">
|
||||
Take control
|
||||
<br />
|
||||
of your crypto
|
||||
</h2>
|
||||
<span className="max-w-xl text-center font-bold">
|
||||
<Text size={27}>
|
||||
No one (including Status!) has the power to freeze, lock-out or
|
||||
stop a Status user from accessing and transacting their tokens.
|
||||
</Text>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-[2] flex justify-center pt-[320px]">
|
||||
<div className="mr-24 flex max-w-[381px] flex-col items-center">
|
||||
<img src="/assets/wallet/skull.png" alt="skull" width="48px" />
|
||||
<div className="flex flex-col items-center pt-4 text-center">
|
||||
<Text size={27} weight="semibold">
|
||||
Ethereum based assets
|
||||
</Text>
|
||||
<Text size={19}>
|
||||
We support all assets in the Uniswap Labs default tokenlist and
|
||||
those minted by communities using Status.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex max-w-[381px] flex-col items-center">
|
||||
<img src="/assets/wallet/nft.png" alt="nft" width="48px" />
|
||||
<div className="flex flex-col items-center pt-4 text-center">
|
||||
<Text size={27} weight="semibold">
|
||||
NFTs and collectibles
|
||||
</Text>
|
||||
<Text size={19}>
|
||||
We will display any NFTs or collectibles listed on OpenSea plus
|
||||
those minted by communities using Status.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="/assets/wallet/hands.png"
|
||||
alt="hands"
|
||||
className="absolute left-0 top-0 w-full"
|
||||
/>
|
||||
<img
|
||||
src="/assets/wallet/gentleman.png"
|
||||
alt="gentleman"
|
||||
className="absolute bottom-0 left-0 mix-blend-multiply"
|
||||
width={403}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { HandsSection }
|
|
@ -0,0 +1,73 @@
|
|||
import { Button, Tag, Text } from '@status-im/components'
|
||||
import { DownloadIcon, WalletIcon } from '@status-im/icons'
|
||||
import image1 from 'public/assets/wallet/1.png'
|
||||
import image2 from 'public/assets/wallet/2.png'
|
||||
import image3 from 'public/assets/wallet/3.png'
|
||||
import image4 from 'public/assets/wallet/vegas.png'
|
||||
|
||||
import { GridHero } from '../cards/grid-hero'
|
||||
import { ParalaxCircle } from './parallax-circle'
|
||||
|
||||
const HeroSection = () => {
|
||||
return (
|
||||
<div className="flex w-full flex-col items-center">
|
||||
<div className="relative mb-16 grid px-5 lg:mb-24 lg:px-40">
|
||||
<ParalaxCircle initialLeft={-206} initialTop={-170} />
|
||||
<div className="relative flex flex-col items-center justify-center">
|
||||
<div className="inline-flex">
|
||||
<Tag
|
||||
size={32}
|
||||
icon={WalletIcon}
|
||||
color="$yellow-50"
|
||||
label="Wallet"
|
||||
/>
|
||||
</div>
|
||||
<h1 className="max-w-[600px] py-4 text-center text-[48px] font-bold leading-[50px] lg:pb-5 lg:text-[88px] lg:leading-[84px]">
|
||||
The future
|
||||
<br />
|
||||
is multi-chain
|
||||
</h1>
|
||||
<span className="max-w-md text-center font-bold">
|
||||
<Text size={19}>
|
||||
L2s made simple - send and manage your crypto easily and safely
|
||||
across multiple networks.
|
||||
</Text>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative flex justify-center">
|
||||
<div className="mt-6 inline-flex rounded-[20px] border border-dashed border-neutral-80/20 p-2 lg:mt-8">
|
||||
<Button
|
||||
size={40}
|
||||
icon={<DownloadIcon size={20} />}
|
||||
variant="yellow"
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<GridHero
|
||||
color="yellow"
|
||||
cardOne={{
|
||||
alt: 'wallet-1',
|
||||
image: image1,
|
||||
}}
|
||||
cardTwo={{
|
||||
alt: 'wallet-2',
|
||||
image: image2,
|
||||
}}
|
||||
cardThree={{
|
||||
alt: 'wallet-3',
|
||||
image: image3,
|
||||
}}
|
||||
cardFour={{
|
||||
alt: 'wallet-4',
|
||||
image: image4,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { HeroSection }
|
|
@ -0,0 +1,5 @@
|
|||
export { ComparisionSection } from './comparision-section'
|
||||
export { HandsSection } from './hands-section'
|
||||
export { HeroSection } from './hero-section'
|
||||
export { ParalaxCircle } from './parallax-circle'
|
||||
export { VideoSection } from './video-section'
|
|
@ -0,0 +1,28 @@
|
|||
import { useParalax } from '@/hooks/use-parallax'
|
||||
|
||||
type ParalaxProps = {
|
||||
initialLeft?: number
|
||||
initialTop?: number
|
||||
initialRight?: number
|
||||
initialBottom?: number
|
||||
}
|
||||
|
||||
const ParalaxCircle = (props: ParalaxProps) => {
|
||||
const { top, bottom } = useParalax(props)
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute z-[0] h-[676px] w-[676px] rounded-full bg-customisation-yellow-50 opacity-[0.10] blur-[260px] "
|
||||
style={{
|
||||
// Fixes the performance issue on safari with filter: blur() to force the browser use GPU acceleration for that particular element instead of the CPU.
|
||||
transform: 'translate3d(0, 0, 0)',
|
||||
left: `${props.initialLeft}px`,
|
||||
top: `${top}px`,
|
||||
right: `${props.initialRight}px`,
|
||||
bottom: `${bottom}px`,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { ParalaxCircle }
|
|
@ -0,0 +1,76 @@
|
|||
import { ContextTag, Text } from '@status-im/components'
|
||||
|
||||
import { ParalaxCircle } from './parallax-circle'
|
||||
|
||||
const VideoSection = () => {
|
||||
return (
|
||||
<div className="relative flex flex-col md:flex-row">
|
||||
<ParalaxCircle initialLeft={-100} initialTop={-100} />
|
||||
|
||||
<div className="relative z-[1] flex flex-col px-5 pt-24 lg:px-[164px] lg:pt-[240px]">
|
||||
<h2 className="text-[40px] font-bold leading-[44px] lg:text-[64px] lg:leading-[68px]">
|
||||
Fully
|
||||
<br />
|
||||
Decentralized
|
||||
<br />
|
||||
Networks
|
||||
</h2>
|
||||
|
||||
<div className="flex max-w-[462px] flex-col pt-4">
|
||||
<Text size={27}>
|
||||
Status supports blockchain networks that are fully committed to
|
||||
decentralization.
|
||||
</Text>
|
||||
<div className="pt-8">
|
||||
<Text size={13} color="$neutral-50" weight="medium">
|
||||
Currently supported networks
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex pt-3">
|
||||
<div className="pr-[10px]">
|
||||
<ContextTag
|
||||
type="network"
|
||||
network={{
|
||||
name: 'Mainnet',
|
||||
src: '/assets/wallet/ethereum.png',
|
||||
}}
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
<div className="pr-[10px]">
|
||||
<ContextTag
|
||||
type="network"
|
||||
network={{
|
||||
name: 'Optmism',
|
||||
src: '/assets/wallet/optimism.png',
|
||||
}}
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ContextTag
|
||||
type="network"
|
||||
network={{
|
||||
name: 'Arbitrum',
|
||||
src: '/assets/wallet/arbitrum.png',
|
||||
}}
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative right-[-5px] top-0 flex justify-center md:absolute">
|
||||
<video autoPlay loop muted playsInline>
|
||||
<source
|
||||
src="/assets/wallet/vitalik.mp4"
|
||||
type="video/mp4;codecs=hvc1"
|
||||
/>
|
||||
<source src="/assets/wallet/vitalik.webm" type="video/webm" />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { VideoSection }
|
|
@ -1,3 +1,10 @@
|
|||
import {
|
||||
AdvancedIcon,
|
||||
AirdropIcon,
|
||||
CommunitiesIcon,
|
||||
TokenIcon,
|
||||
} from '@status-im/icons'
|
||||
|
||||
export const LINKS = {
|
||||
Features: [
|
||||
{ name: 'Communities', href: '/features/communities' },
|
||||
|
@ -45,22 +52,27 @@ export const LINKS = {
|
|||
],
|
||||
} as const
|
||||
|
||||
// TODO Update icons when available
|
||||
export const SOCIALS = {
|
||||
status: {
|
||||
name: 'Status',
|
||||
href: '<TODO>',
|
||||
icon: CommunitiesIcon,
|
||||
},
|
||||
twitter: {
|
||||
name: 'Twitter',
|
||||
href: 'https://twitter.com/ethstatus',
|
||||
icon: TokenIcon,
|
||||
},
|
||||
github: {
|
||||
name: 'GitHub',
|
||||
href: 'https://github.com/status-im',
|
||||
icon: AirdropIcon,
|
||||
},
|
||||
youtube: {
|
||||
name: 'YouTube',
|
||||
href: 'https://youtube.com/<TODO>',
|
||||
icon: AdvancedIcon,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export const useLockScroll = (open = false) => {
|
||||
if (typeof document === 'undefined') {
|
||||
return
|
||||
}
|
||||
// Adds the following code to disable scrolling when the menu is open
|
||||
const rootElement = document.documentElement
|
||||
if (open) {
|
||||
rootElement.style.overflowY = 'hidden'
|
||||
} else {
|
||||
rootElement.style.overflowY = 'auto'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export const useOutsideClick = (callback: () => void) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', handleClick, true)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClick, true)
|
||||
}
|
||||
}, [callback])
|
||||
|
||||
return ref
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
|
||||
type ParalaxProps = {
|
||||
initialLeft?: number
|
||||
initialTop?: number
|
||||
initialRight?: number
|
||||
initialBottom?: number
|
||||
}
|
||||
|
||||
const PARALLAX_SPEED = 4
|
||||
|
||||
function useParalax(props?: ParalaxProps) {
|
||||
const initialTop = props?.initialTop || 0
|
||||
const initialBottom = props?.initialBottom || 0
|
||||
|
||||
const [top, setTop] = useState(initialTop)
|
||||
|
||||
const [bottom, setBottom] = useState(initialBottom)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const scrollY = window.scrollY
|
||||
|
||||
setTop(scrollY / PARALLAX_SPEED + initialTop)
|
||||
setBottom(scrollY / PARALLAX_SPEED + initialBottom)
|
||||
}
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [initialBottom, initialTop])
|
||||
|
||||
return { top, bottom }
|
||||
}
|
||||
|
||||
export { useParalax }
|
|
@ -1,100 +1,38 @@
|
|||
import * as NavigationMenu from '@radix-ui/react-navigation-menu'
|
||||
import { Button, Text } from '@status-im/components'
|
||||
import { DownloadIcon, ExternalIcon } from '@status-im/icons'
|
||||
import { cx } from 'class-variance-authority'
|
||||
import { Footer } from '@/components/footer/footer'
|
||||
import { FooterMobile } from '@/components/footer/footer-mobile'
|
||||
import { FloatingMenu } from '@/components/navigation/floating-menu'
|
||||
import { NavDesktop } from '@/components/navigation/nav-desktop'
|
||||
import { NavMobile } from '@/components/navigation/nav-mobile'
|
||||
import { Prefooter } from '@/components/pre-footer'
|
||||
|
||||
import { Logo } from '@/components/logo'
|
||||
import { NavMenu } from '@/components/nav-menu'
|
||||
import { PageFooter } from '@/components/page-footer'
|
||||
import { LINKS } from '@/config/links'
|
||||
import type { ReactElement } from 'react'
|
||||
|
||||
import { Link } from '../components/link'
|
||||
type AppLayoutProps = {
|
||||
hasPreFooter?: boolean
|
||||
children: ReactElement
|
||||
}
|
||||
|
||||
import type { PageLayout } from 'next'
|
||||
|
||||
export const AppLayout: PageLayout = page => {
|
||||
export const AppLayout: React.FC<AppLayoutProps> = ({
|
||||
hasPreFooter = true,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="hidden lg:block">
|
||||
<NavMenu />
|
||||
</div>
|
||||
<div className="min-h-full bg-neutral-100">
|
||||
<NavigationMenu.Root>
|
||||
<div className="flex items-center px-6 py-3">
|
||||
<div className="mr-5">
|
||||
<Link href="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="hidden flex-1 lg:flex">
|
||||
<NavigationMenu.List className="flex items-center">
|
||||
{Object.entries(LINKS).map(([name, links]) => (
|
||||
<NavigationMenu.Item key={name}>
|
||||
<NavigationMenu.Trigger className="pr-5 aria-expanded:opacity-50">
|
||||
<Text size={15} weight="medium" color="$white-100">
|
||||
{name}
|
||||
</Text>
|
||||
</NavigationMenu.Trigger>
|
||||
<NavigationMenu.Content className="grid gap-3 pb-8 pl-[164px] pt-3">
|
||||
{links.map(link => {
|
||||
const external = link.href.startsWith('http')
|
||||
|
||||
return (
|
||||
<NavigationMenu.Link key={link.name} asChild>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<Text
|
||||
size={27}
|
||||
weight="semibold"
|
||||
color="$white-100"
|
||||
>
|
||||
{link.name}
|
||||
</Text>
|
||||
{external && (
|
||||
<ExternalIcon size={20} color="$white-100" />
|
||||
)}
|
||||
</Link>
|
||||
</NavigationMenu.Link>
|
||||
)
|
||||
})}
|
||||
</NavigationMenu.Content>
|
||||
</NavigationMenu.Item>
|
||||
))}
|
||||
</NavigationMenu.List>
|
||||
</div>
|
||||
|
||||
<div className="hidden justify-end lg:flex">
|
||||
<Button
|
||||
size={32}
|
||||
variant="darkGrey"
|
||||
icon={<DownloadIcon size={20} />}
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<NavigationMenu.Viewport
|
||||
className={cx([
|
||||
'data-[state=closed]:animate-heightOut data-[state=open]:animate-heightIn',
|
||||
'h-[var(--radix-navigation-menu-viewport-height)] transition-height',
|
||||
// 'data-[state=open]:animate-heightIn animate-',
|
||||
// 'data-[state=closed]:animate-heightOut',
|
||||
// 'transition-height h-[var(--radix-navigation-menu-viewport-height)]',
|
||||
// 'transition-height mb-8 h-[var(--radix-navigation-menu-viewport-height)] duration-1000',
|
||||
// 'h-[var(--radix-navigation-menu-viewport-height)]',
|
||||
// 'data-[state=open]:animate-scaleIn data-[state=closed]:animate-scaleOut relative mt-[10px] h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] overflow-hidden rounded-[6px] bg-white transition-[width,height] duration-300 sm:w-[var(--radix-navigation-menu-viewport-width)]',
|
||||
])}
|
||||
/>
|
||||
</NavigationMenu.Root>
|
||||
<FloatingMenu />
|
||||
<div className="min-h-full w-full bg-neutral-100">
|
||||
<NavDesktop />
|
||||
<NavMobile />
|
||||
|
||||
{/* ROUNDED WHITE BG */}
|
||||
{/* <div className="bg-white-100 mx-1 min-h-[900px] rounded-3xl">{page}</div> */}
|
||||
{page}
|
||||
|
||||
<PageFooter />
|
||||
<div className="flex justify-center lg:p-1">
|
||||
{/* TODO Check max-width to use */}
|
||||
<div className="min-h-[900px] w-full rounded-3xl bg-white-100">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
{hasPreFooter && <Prefooter />}
|
||||
<Footer hasBorderTop={hasPreFooter} />
|
||||
<FooterMobile hasBorderTop={hasPreFooter} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { SideBar } from '../components'
|
||||
import { AppLayout } from './app-layout'
|
||||
|
||||
import type { PageLayout } from 'next'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
// Eventually this will be fetched from the API, at least the nested links
|
||||
const MENU_LINKS = [
|
||||
|
@ -81,11 +81,17 @@ const MENU_LINKS = [
|
|||
},
|
||||
]
|
||||
|
||||
export const InsightsLayout: PageLayout = page => {
|
||||
return AppLayout(
|
||||
<div className="relative mx-1 flex min-h-[calc(100vh-56px-4px)] rounded-3xl bg-white-100">
|
||||
interface InsightsLayoutProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const InsightsLayout: React.FC<InsightsLayoutProps> = ({ children }) => {
|
||||
return (
|
||||
<AppLayout hasPreFooter={false}>
|
||||
<div className="relative mx-1 flex min-h-[calc(100vh-56px-4px)] w-full rounded-3xl bg-white-100">
|
||||
<SideBar data={MENU_LINKS} />
|
||||
<main className="flex-1">{page}</main>
|
||||
<main className="flex-1">{children}</main>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -248,6 +248,8 @@ const BlogDetailPage: Page<Props> = ({ post, relatedPosts }) => {
|
|||
)
|
||||
}
|
||||
|
||||
BlogDetailPage.getLayout = AppLayout
|
||||
BlogDetailPage.getLayout = function getLayout(page) {
|
||||
return <AppLayout>{page}</AppLayout>
|
||||
}
|
||||
|
||||
export default BlogDetailPage
|
||||
|
|
|
@ -222,6 +222,8 @@ export const PostCard = (props: PostCardProps) => {
|
|||
)
|
||||
}
|
||||
|
||||
BlogPage.getLayout = AppLayout
|
||||
BlogPage.getLayout = function getLayout(page) {
|
||||
return <AppLayout>{page}</AppLayout>
|
||||
}
|
||||
|
||||
export default BlogPage
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import imageSection1 from 'public/assets/wallet/4.png'
|
||||
import imageSection2 from 'public/assets/wallet/5.png'
|
||||
import imageSection3 from 'public/assets/wallet/6.png'
|
||||
import imageSection4 from 'public/assets/wallet/7.png'
|
||||
import imageSection5 from 'public/assets/wallet/8.png'
|
||||
import cubeImage from 'public/assets/wallet/cube.png'
|
||||
import duckImage from 'public/assets/wallet/duck.png'
|
||||
import megaphone from 'public/assets/wallet/megaphone.png'
|
||||
import pizzaImage from 'public/assets/wallet/pizza.png'
|
||||
import skullImage from 'public/assets/wallet/skull.png'
|
||||
|
||||
import { Section } from '@/components/cards'
|
||||
import {
|
||||
ComparisionSection,
|
||||
HandsSection,
|
||||
HeroSection,
|
||||
ParalaxCircle,
|
||||
VideoSection,
|
||||
} from '@/components/wallet'
|
||||
import { AppLayout } from '@/layouts/app-layout'
|
||||
|
||||
import type { Page } from 'next'
|
||||
|
||||
const WalletPage: Page = () => {
|
||||
return (
|
||||
<div className="overflow-hidden pt-16 lg:pt-32">
|
||||
<HeroSection />
|
||||
<VideoSection />
|
||||
<div className="relative pt-0 md:pt-[220px]">
|
||||
<ParalaxCircle initialRight={-100} initialTop={-100} />
|
||||
<Section
|
||||
color="yellow"
|
||||
title="True multi-chain experience"
|
||||
description=" All networks, all the time. See all your assets and NFTs in one place, even if spread across multiple blockchains."
|
||||
image={imageSection1}
|
||||
imageAlt="wallet-2"
|
||||
imageSecondary={skullImage}
|
||||
imageSecondaryAlt="skull"
|
||||
secondaryDescription=" You can treat it like other wallets by selecting individual chains manually. But once you’ve tried the Status ‘all chains, always’ way of doing things, you won’t want to go back."
|
||||
secondaryTitle=" Can be used as ‘single chain at a time’ wallet"
|
||||
information="This has some information so it will show the icon to open the dialog"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative pt-24 md:pt-[160px]">
|
||||
<Section
|
||||
color="yellow"
|
||||
title="Simple token sending"
|
||||
description=" Select token, amount and press “Send”. Need to bridge between blockchains? Status does this automatically."
|
||||
image={imageSection2}
|
||||
imageAlt="wallet-5"
|
||||
imageSecondary={pizzaImage}
|
||||
imageSecondaryAlt="pizza"
|
||||
secondaryDescription="Status automatically calculates the cheapest way of moving tokens from A to B, considering: gas prices, chains your tokens are on, chains the recipient uses, and bridge costs. All in real time."
|
||||
secondaryTitle="Cost efficient"
|
||||
direction="rtl"
|
||||
customNode={
|
||||
<div className="relative rounded-2xl border border-neutral-80/5 px-4 py-3">
|
||||
<Text size={15} color="$neutral-50">
|
||||
EIP-3770 format:
|
||||
</Text>
|
||||
<p className="text-[19px] leading-[28px] text-neutral-100">
|
||||
<span className="text-networks-ethereum">eth:</span>
|
||||
<span className="text-networks-optimism">opt:</span>
|
||||
<span className="text-networks-arbitrum">artb:</span>
|
||||
0xAbc123...xyz789
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="relative py-0 md:py-[160px]">
|
||||
<Section
|
||||
color="yellow"
|
||||
title="Full control for power users"
|
||||
description="Select exact amounts to send from each blockchain, and precisely set the chains the recipient will receive funds on."
|
||||
image={imageSection3}
|
||||
imageAlt="wallet-6"
|
||||
imageSecondary={duckImage}
|
||||
imageSecondaryAlt="duck"
|
||||
secondaryDescription="Gas limit, max base fee, max priority fee, input data and nounce value for every transaction in each transaction bundle. If you really want to..."
|
||||
secondaryTitle="Pro user? You can edit:"
|
||||
/>
|
||||
<ParalaxCircle initialRight={-200} initialTop={-300} />
|
||||
</div>
|
||||
|
||||
<ComparisionSection />
|
||||
<div className="relative rounded-[48px] border-t border-neutral-10 bg-white-100 py-0 mix-blend-normal shadow-[0_-2px_20px_rgba(9,16,28,0.04)] md:py-[160px]">
|
||||
<Section
|
||||
color="yellow"
|
||||
title="New address format"
|
||||
description="A way for wallets to automatically signal which chains they can receive funds on. So you don’t have to."
|
||||
image={imageSection4}
|
||||
imageAlt="wallet-7"
|
||||
imageSecondary={cubeImage}
|
||||
imageSecondaryAlt="cube"
|
||||
secondaryDescription="Status is working with other wallets to ensure EIP-3770 with multiple chain shortName prefixes becomes the new ethereum address standard."
|
||||
secondaryTitle="On the path to adoption"
|
||||
direction="rtl"
|
||||
/>
|
||||
<div className="relative pt-0 md:pt-[160px]">
|
||||
<Section
|
||||
color="yellow"
|
||||
title="Total balance graphs"
|
||||
description="Who doesn’t want to see how their ‘crypto portfolio number’ has gone up or down over time?"
|
||||
image={imageSection5}
|
||||
imageAlt="wallet-8"
|
||||
imageSecondary={megaphone}
|
||||
imageSecondaryAlt="megaphone"
|
||||
secondaryDescription="Everything is taken into account: fluctuations in fiat exchange rates, daily valuation updates of every crypto asset you hodl, and how your balance changes when tokens are sent/received"
|
||||
secondaryTitle="Graphs that do maths"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<HandsSection />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
WalletPage.getLayout = function getLayout(page) {
|
||||
return <AppLayout>{page}</AppLayout>
|
||||
}
|
||||
|
||||
export default WalletPage
|
|
@ -8,8 +8,8 @@ import type { Page } from 'next'
|
|||
const HomePage: Page = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="mx-1 rounded-3xl bg-white-100 py-32">
|
||||
<div className="mx-40 mb-40 grid gap-8">
|
||||
<div className="px-5 py-32 lg:px-40">
|
||||
<div className="mb-40 grid gap-8 px-5 ">
|
||||
<div className="grid gap-6">
|
||||
<h1 className="text-7xl font-bold">
|
||||
Make the
|
||||
|
@ -23,7 +23,7 @@ const HomePage: Page = () => {
|
|||
</div>
|
||||
|
||||
<div className="grid gap-4">
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col gap-3 lg:flex-col">
|
||||
<Button size={40} icon={<DownloadIcon size={20} />}>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
|
@ -80,7 +80,7 @@ type FeatureSectionProps = {
|
|||
const FeatureSection = ({ title, description }: FeatureSectionProps) => {
|
||||
return (
|
||||
<section className="px-10">
|
||||
<div className="mb-24 flex justify-between">
|
||||
<div className="mb-24 flex flex-col justify-between lg:flex-row">
|
||||
<h2 className="flex-1 whitespace-pre-line text-6xl font-bold">
|
||||
{title}
|
||||
</h2>
|
||||
|
@ -125,6 +125,8 @@ const FeatureGrid = () => {
|
|||
)
|
||||
}
|
||||
|
||||
HomePage.getLayout = AppLayout
|
||||
HomePage.getLayout = function getLayout(page) {
|
||||
return <AppLayout>{page}</AppLayout>
|
||||
}
|
||||
|
||||
export default HomePage
|
||||
|
|
|
@ -26,6 +26,8 @@ const EpicsDetailPage: Page = () => {
|
|||
)
|
||||
}
|
||||
|
||||
EpicsDetailPage.getLayout = InsightsLayout
|
||||
EpicsDetailPage.getLayout = function getLayout(page) {
|
||||
return <InsightsLayout>{page}</InsightsLayout>
|
||||
}
|
||||
|
||||
export default EpicsDetailPage
|
||||
|
|
|
@ -59,12 +59,13 @@ const EpicsPage: Page = () => {
|
|||
</Shadow>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<DatePicker selected={selectedDates} onSelect={setSelectedDates} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
EpicsPage.getLayout = InsightsLayout
|
||||
EpicsPage.getLayout = function getLayout(page) {
|
||||
return <InsightsLayout>{page}</InsightsLayout>
|
||||
}
|
||||
|
||||
export default EpicsPage
|
||||
|
|
|
@ -17,6 +17,8 @@ const OrphansPage: Page = () => {
|
|||
)
|
||||
}
|
||||
|
||||
OrphansPage.getLayout = InsightsLayout
|
||||
OrphansPage.getLayout = function getLayout(page) {
|
||||
return <InsightsLayout>{page}</InsightsLayout>
|
||||
}
|
||||
|
||||
export default OrphansPage
|
||||
|
|
|
@ -85,6 +85,8 @@ const ReposPage: Page = () => {
|
|||
)
|
||||
}
|
||||
|
||||
ReposPage.getLayout = InsightsLayout
|
||||
ReposPage.getLayout = function getLayout(page) {
|
||||
return <InsightsLayout>{page}</InsightsLayout>
|
||||
}
|
||||
|
||||
export default ReposPage
|
||||
|
|
|
@ -26,6 +26,8 @@ const WorkstreamDetailPage: Page = () => {
|
|||
)
|
||||
}
|
||||
|
||||
WorkstreamDetailPage.getLayout = InsightsLayout
|
||||
WorkstreamDetailPage.getLayout = function getLayout(page) {
|
||||
return <InsightsLayout>{page}</InsightsLayout>
|
||||
}
|
||||
|
||||
export default WorkstreamDetailPage
|
||||
|
|
|
@ -64,6 +64,8 @@ const WorkstreamsPage: Page = () => {
|
|||
)
|
||||
}
|
||||
|
||||
WorkstreamsPage.getLayout = InsightsLayout
|
||||
WorkstreamsPage.getLayout = function getLayout(page) {
|
||||
return <InsightsLayout>{page}</InsightsLayout>
|
||||
}
|
||||
|
||||
export default WorkstreamsPage
|
||||
|
|
|
@ -30,6 +30,10 @@ module.exports = {
|
|||
height: 'height',
|
||||
},
|
||||
|
||||
screens: {
|
||||
'md-lg': { raw: '(min-width: 868px)' },
|
||||
},
|
||||
|
||||
keyframes: {
|
||||
heightIn: {
|
||||
from: { height: 0 },
|
||||
|
|
|
@ -19,6 +19,7 @@ type Props = PressableProps & {
|
|||
icon?: React.ReactElement
|
||||
iconAfter?: React.ReactElement
|
||||
disabled?: boolean
|
||||
fullWidth?: boolean
|
||||
}
|
||||
|
||||
const textColors: MapVariant<typeof Base, 'variant'> = {
|
||||
|
@ -29,6 +30,19 @@ const textColors: MapVariant<typeof Base, 'variant'> = {
|
|||
outline: '$neutral-100',
|
||||
ghost: '$neutral-100',
|
||||
danger: '$white-100',
|
||||
blue: '$white-100',
|
||||
purple: '$white-100',
|
||||
orange: '$white-100',
|
||||
army: '$white-100',
|
||||
turquoise: '$white-100',
|
||||
sky: '$white-100',
|
||||
yellow: '$white-100',
|
||||
pink: '$white-100',
|
||||
cooper: '$white-100',
|
||||
camel: '$white-100',
|
||||
magenta: '$white-100',
|
||||
yin: '$white-100',
|
||||
yang: '$neutral-100',
|
||||
}
|
||||
|
||||
const textSizes: Record<NonNullable<Props['size']>, TextProps['size']> = {
|
||||
|
@ -45,6 +59,7 @@ const Button = (props: Props, ref: Ref<HTMLButtonElement>) => {
|
|||
children,
|
||||
icon,
|
||||
iconAfter,
|
||||
fullWidth,
|
||||
...buttonProps
|
||||
} = props
|
||||
|
||||
|
@ -62,8 +77,9 @@ const Button = (props: Props, ref: Ref<HTMLButtonElement>) => {
|
|||
radius={shape === 'circle' ? 'full' : size}
|
||||
size={size}
|
||||
iconOnly={iconOnly}
|
||||
width={fullWidth ? '100%' : 'auto'}
|
||||
>
|
||||
{icon ? cloneElement(icon, { color: '$neutral-40' }) : null}
|
||||
{icon ? cloneElement(icon, { color: textColor || '$neutral-40' }) : null}
|
||||
<Text weight="medium" color={textColor} size={textSize}>
|
||||
{children}
|
||||
</Text>
|
||||
|
@ -133,6 +149,72 @@ const Base = styled(Stack, {
|
|||
// TODO: update background color
|
||||
pressStyle: { backgroundColor: '$danger' },
|
||||
},
|
||||
// TODO sync colors with the design foundation colors
|
||||
blue: {
|
||||
backgroundColor: '$blue-50',
|
||||
hoverStyle: { backgroundColor: '$blue-60' },
|
||||
pressStyle: { backgroundColor: '$blue-50' },
|
||||
},
|
||||
purple: {
|
||||
backgroundColor: '$purple-50',
|
||||
hoverStyle: { backgroundColor: '$purple-60' },
|
||||
pressStyle: { backgroundColor: '$purple-50' },
|
||||
},
|
||||
orange: {
|
||||
backgroundColor: '$orange-50',
|
||||
hoverStyle: { backgroundColor: '$orange-60' },
|
||||
pressStyle: { backgroundColor: '$orange-50' },
|
||||
},
|
||||
army: {
|
||||
backgroundColor: '$indigo-50',
|
||||
hoverStyle: { backgroundColor: '$indigo-60' },
|
||||
pressStyle: { backgroundColor: '$indigo-50' },
|
||||
},
|
||||
turquoise: {
|
||||
backgroundColor: '$turquoise-50',
|
||||
hoverStyle: { backgroundColor: '$turquoise-60' },
|
||||
pressStyle: { backgroundColor: '$turquoise-50' },
|
||||
},
|
||||
sky: {
|
||||
backgroundColor: '$sky-50',
|
||||
hoverStyle: { backgroundColor: '$sky-60' },
|
||||
pressStyle: { backgroundColor: '$sky-50' },
|
||||
},
|
||||
yellow: {
|
||||
backgroundColor: '$yellow-50',
|
||||
hoverStyle: { backgroundColor: '$yellow-60' },
|
||||
pressStyle: { backgroundColor: '$yellow-50' },
|
||||
},
|
||||
pink: {
|
||||
backgroundColor: '$pink-50',
|
||||
hoverStyle: { backgroundColor: '$pink-60' },
|
||||
pressStyle: { backgroundColor: '$pink-50' },
|
||||
},
|
||||
cooper: {
|
||||
backgroundColor: '$cooper-50',
|
||||
hoverStyle: { backgroundColor: '$cooper-60' },
|
||||
pressStyle: { backgroundColor: '$cooper-50' },
|
||||
},
|
||||
camel: {
|
||||
backgroundColor: '$camel-50',
|
||||
hoverStyle: { backgroundColor: '$camel-60' },
|
||||
pressStyle: { backgroundColor: '$camel-50' },
|
||||
},
|
||||
magenta: {
|
||||
backgroundColor: '$magenta',
|
||||
hoverStyle: { backgroundColor: '$magenta-60' },
|
||||
pressStyle: { backgroundColor: '$magenta-50' },
|
||||
},
|
||||
yin: {
|
||||
backgroundColor: '$yin-50',
|
||||
hoverStyle: { backgroundColor: '$yin-60' },
|
||||
pressStyle: { backgroundColor: '$yin-50' },
|
||||
},
|
||||
yang: {
|
||||
backgroundColor: '$yang-50',
|
||||
hoverStyle: { backgroundColor: '$yang-60' },
|
||||
pressStyle: { backgroundColor: '$yang-50' },
|
||||
},
|
||||
},
|
||||
|
||||
disabled: {
|
||||
|
|
|
@ -42,7 +42,10 @@ const Tag = (props: Props) => {
|
|||
return <Text size={textSizes[size]}>{icon}</Text>
|
||||
}
|
||||
|
||||
return createElement(icon, { size: iconSizes[size] })
|
||||
return createElement(icon, {
|
||||
size: iconSizes[size],
|
||||
color,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -14,9 +14,15 @@ import type { ComponentType, SVGProps } from 'react'
|
|||
// }
|
||||
// }
|
||||
|
||||
function isColorTokens(
|
||||
value: `#${string}` | ColorTokens
|
||||
): value is ColorTokens {
|
||||
return typeof value === 'string' && value.startsWith('$')
|
||||
}
|
||||
|
||||
export interface IconProps extends SVGProps<SVGSVGElement> {
|
||||
size: 12 | 16 | 20
|
||||
color?: ColorTokens
|
||||
color?: ColorTokens | `#${string}`
|
||||
}
|
||||
|
||||
export function createIcon<P extends SVGProps<SVGSVGElement>>(
|
||||
|
@ -25,7 +31,7 @@ export function createIcon<P extends SVGProps<SVGSVGElement>>(
|
|||
const Icon = forwardRef<SVGElement, IconProps>((props, ref) => {
|
||||
const { size, color = '$neutral-100', ...rest } = props
|
||||
const theme = useTheme()
|
||||
const token = theme[color]?.val
|
||||
const token = isColorTokens(color) ? theme[color]?.val : color
|
||||
|
||||
return createElement(Component, {
|
||||
ref,
|
||||
|
|