Skip to content

Commit

Permalink
Merge pull request #13 from threshold-network/ui/header-component
Browse files Browse the repository at this point in the history
UI: Header component

Ref: #2 
~Depends on: #12~

This pull request introduces:
- `Logo` component:
- `Header` component
- `SelectWallet` component redesign

Additional changes:
- `Button` component theme adjustments
- `Modal` component theme adjustments 

Note: Since this PR introduces changes widely used in the project eg. `Modal` or
`Button` changes it's recommended to review it and merge primarily.
  • Loading branch information
michalsmiarowski authored Jan 25, 2024
2 parents 24a2dc2 + 764b0c9 commit 3db2d75
Show file tree
Hide file tree
Showing 26 changed files with 806 additions and 239 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ REACT_APP_ELECTRUM_HOST=$ELECTRUM_HOST
REACT_APP_ELECTRUM_PORT=$ELECTRUM_PORT
REACT_APP_MOCK_BITCOIN_CLIENT=true

REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_PROJECT_ID
REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_PROJECT_ID
27 changes: 16 additions & 11 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {
useSubscribeToRedemptionRequestedEvent,
} from "./hooks/tbtc"
import { useSentry } from "./hooks/sentry"
import { Header } from "./components/Header"
import { VStack } from "@threshold-network/components"

const Web3EventHandlerComponent = () => {
useSubscribeToERC20TransferEvent(Token.TBTCV2)
Expand Down Expand Up @@ -99,19 +101,22 @@ const AppBody = () => {

const Layout = () => {
return (
<Box display="flex">
<Sidebar />
<Box
// 100% - 80px is to account for the sidebar
w={{ base: "100%", md: "calc(100% - 80px)" }}
bg={useColorModeValue("transparent", "gray.900")}
>
<Navbar />
<Box as="main" data-cy="app-container">
<Outlet />
<VStack alignItems="normal" spacing="0">
<Header />
<Box display="flex">
<Sidebar />
<Box
// 100% - 80px is to account for the sidebar
w={{ base: "100%", md: "calc(100% - 80px)" }}
bg={useColorModeValue("transparent", "gray.900")}
>
<Navbar />
<Box as="main" data-cy="app-container">
<Outlet />
</Box>
</Box>
</Box>
</Box>
</VStack>
)
}

Expand Down
77 changes: 51 additions & 26 deletions src/components/DotsLoadingIndicator/index.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,54 @@
import { FC } from "react"
import {
Box,
HStack,
StackProps,
ThemingProps,
useStyleConfig,
} from "@threshold-network/components"
import { Box, StackProps, HStack } from "@threshold-network/components"
import { motion } from "framer-motion"

type DotsLoadingIndicatorProps = StackProps & Omit<ThemingProps, "orientation">
const Dot: FC = () => (
<motion.div
variants={{
initial: {
y: "50%",
opacity: 0.85,
},
animate: {
y: "-50%",
opacity: 0,
},
}}
transition={{
duration: 0.8,
repeat: Infinity,
repeatType: "reverse",
ease: "easeInOut",
}}
>
<Box w={1} h={1} rounded="full" bg="white" />
</motion.div>
)

export const DotsLoadingIndicator: FC<DotsLoadingIndicatorProps> = ({
colorScheme = "brand",
size = "sm",
...restProps
}) => {
const styles = useStyleConfig("DotsLoadingIndicator", {
colorScheme,
size,
})

return (
<HStack spacing="4" {...restProps}>
<Box __css={styles} />
<Box __css={styles} />
<Box __css={styles} />
</HStack>
)
}
export const DotsLoadingIndicator: FC<StackProps> = (props) => (
<HStack
spacing={0.5}
w={5}
h={5}
as={motion.div}
variants={{
initial: {
transition: {
staggerChildren: 0.2,
},
},
animate: {
transition: {
staggerChildren: 0.2,
},
},
}}
initial="initial"
animate="animate"
{...props}
>
<Dot />
<Dot />
<Dot />
</HStack>
)
60 changes: 60 additions & 0 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Box, BoxProps, Flex } from "@threshold-network/components"
import { useWeb3React } from "@web3-react/core"
import { FC } from "react"
import { ModalType, Token } from "../../enums"
import { useModal } from "../../hooks/useModal"
import { useToken } from "../../hooks/useToken"
import { Logo } from "../Logo"
import { NavigationMenu } from "./NavigationMenu"
import { UserPanel } from "./UserPanel"

// TODO: Load new fonts

const Header: FC<BoxProps> = (props) => {
const {
active: isConnected,
deactivate: handleWalletDisconnection,
account: accountAddress,
chainId,
} = useWeb3React()
const { openModal } = useModal()
const handleWalletConnection = () => openModal(ModalType.SelectWallet)
const { balance } = useToken(Token.TBTCV2)

return (
<Box
bg={"black"}
color={"white"}
borderBottom={"1px solid"}
borderColor={"whiteAlpha.350"}
{...props}
>
<Flex
maxW={"1920px"}
mx={"auto"}
alignItems={"center"}
px={{ base: 2, lg: 10 }}
h={{ base: 16, lg: 24 }}
>
<Logo zIndex="popover" />
<NavigationMenu
items={[
{ label: "Bridge", to: "/tBTC/mint" },
{ label: "Collect", to: "/collect" },
{ label: "Earn", to: "/earn" },
]}
/>
<UserPanel
isConnected={isConnected}
accountAddress={accountAddress}
balance={balance}
chainId={chainId}
onConnectClick={handleWalletConnection}
onDisconnectClick={handleWalletDisconnection}
/>
</Flex>
</Box>
)
}

export default Header
199 changes: 199 additions & 0 deletions src/components/Header/NavigationMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { spacing } from "@chakra-ui/theme/foundations/spacing"
import {
Button,
Drawer,
DrawerContent,
DrawerOverlay,
HStack,
Icon,
Link,
List,
ListItem,
ListItemProps,
StackDivider,
StackProps,
SystemStyleObject,
useDisclosure,
VisuallyHidden,
VStack,
} from "@threshold-network/components"
import { motion } from "framer-motion"
import { FC, useRef } from "react"
import { NavLink } from "react-router-dom"
import useChakraBreakpoint from "../../hooks/useChakraBreakpoint"

const NavigationMenuMobileContainer: FC<StackProps> = (props) => (
<VStack
divider={<StackDivider />}
{...props}
spacing={0}
position="fixed"
inset={0}
h="100vh"
py={16}
bgGradient="radial(circle at bottom right, #0A1616, #090909)"
borderLeft="1px solid"
borderColor="whiteAlpha.250"
alignItems="stretch"
/>
)

const NavigationMenuDesktopContainer: FC<StackProps> = (props) => (
<HStack
{...props}
spacing={0}
alignSelf="stretch"
alignItems="stretch"
ml={{
lg: `calc(${spacing[16]} - ${spacing[5]})`,
xl: `calc(142px - ${spacing[5]})`,
}}
mr="auto"
/>
)

const HamburgerIcon: FC<{ isToggled: boolean }> = ({ isToggled }) => (
<Icon
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
w={5}
h={5}
fill="none"
stroke="white"
strokeWidth={1}
strokeLinecap="round"
strokeLinejoin="round"
>
<motion.path
initial={false}
animate={{
rotate: isToggled ? -45 : 0,
y: isToggled ? "25%" : 0,
}}
d="M3 6h18"
/>
<motion.path
initial={false}
animate={{
scaleX: isToggled ? 0 : 1,
}}
d="M3 12h18"
/>
<motion.path
initial={false}
animate={{
rotate: isToggled ? 45 : 0,
y: isToggled ? "-25%" : 0,
}}
d="M3 18h18"
/>
</Icon>
)

const activeLinkIndicatorStyles: SystemStyleObject = {
position: "relative",
"&.active": {
"&:before": {
content: '""',
position: "absolute",
bottom: 0,
left: { base: 0, lg: 5 },
width: { base: 0.5, lg: `calc(100% - 2 * ${spacing[5]})` }, // To account for container's padding
height: { base: "full", lg: 0.5 },
bg: "#53D2FF",
},
},
}

export type NavigationMenuItemType = {
/** The label of the menu item */
label: string
/** The route to navigate to when the menu item is clicked */
to: string
}
interface NavigationMenuItemProps
extends ListItemProps,
NavigationMenuItemType {}

const NavigationMenuItem: FC<NavigationMenuItemProps> = ({
label,
to,
...restProps
}) => {
return (
<ListItem {...restProps}>
<Link
as={NavLink}
to={to}
display={"inline-flex"}
alignItems={"center"}
w="full"
h="full"
p={5}
sx={activeLinkIndicatorStyles}
fontWeight={"black"}
textTransform={"uppercase"}
_hover={{ textDecoration: "none" }}
>
{label}
</Link>
</ListItem>
)
}

const renderNavigationMenuItems = (items: NavigationMenuItemType[]) =>
items.map((item) => <NavigationMenuItem {...item} key={item.to} />)

interface NavigationMenuProps extends StackProps {
/** The menu items to display */
items: NavigationMenuItemType[]
}

export const NavigationMenu: FC<NavigationMenuProps> = ({
items,
...restProps
}) => {
const { isOpen, onOpen, onClose } = useDisclosure()
const isMobile = useChakraBreakpoint("lg")
const buttonRef = useRef<HTMLButtonElement>(null)

return (
<>
{isMobile ? (
<Button
ref={buttonRef}
variant="unstyled"
order={-1}
onClick={isOpen ? onClose : onOpen}
zIndex="popover"
mr={2}
>
<VisuallyHidden>
{isOpen ? "Close" : "Open"} navigation menu
</VisuallyHidden>
<HamburgerIcon isToggled={isOpen} />
</Button>
) : null}
{isMobile ? (
<Drawer
isOpen={isOpen}
placement="right"
onClose={onClose}
size="sm"
finalFocusRef={buttonRef}
>
<DrawerOverlay backdropFilter="auto" backdropBlur="lg" />
<DrawerContent>
<NavigationMenuMobileContainer as={List} {...restProps}>
{renderNavigationMenuItems(items)}
</NavigationMenuMobileContainer>
</DrawerContent>
</Drawer>
) : (
<NavigationMenuDesktopContainer as={List} {...restProps}>
{renderNavigationMenuItems(items)}
</NavigationMenuDesktopContainer>
)}
</>
)
}
Loading

0 comments on commit 3db2d75

Please sign in to comment.