diff --git a/web/package.json b/web/package.json index 7fb6d1826..ea2cc5bb4 100644 --- a/web/package.json +++ b/web/package.json @@ -82,6 +82,8 @@ "graphql": "^16.7.1", "graphql-request": "~6.1.0", "moment": "^2.29.4", + "overlayscrollbars": "^2.3.0", + "overlayscrollbars-react": "^0.5.2", "react": "^18.2.0", "react-chartjs-2": "^4.3.1", "react-dom": "^18.2.0", diff --git a/web/src/app.tsx b/web/src/app.tsx index f6a705ac6..594489272 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -6,6 +6,7 @@ import "react-toastify/dist/ReactToastify.css"; import Web3Provider from "context/Web3Provider"; import QueryClientProvider from "context/QueryClientProvider"; import StyledComponentsProvider from "context/StyledComponentsProvider"; +import { FilterProvider } from "context/FilterProvider"; import RefetchOnBlock from "context/RefetchOnBlock"; import Layout from "layout/index"; import Home from "./pages/Home"; @@ -20,16 +21,18 @@ const App: React.FC = () => { - - }> - } /> - } /> - } /> - } /> - } /> - Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} /> - - + + + }> + } /> + } /> + } /> + } /> + } /> + Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} /> + + + diff --git a/web/src/assets/svgs/header/header-darkmode-desktop.svg b/web/src/assets/svgs/hero/hero-darkmode-desktop.svg similarity index 99% rename from web/src/assets/svgs/header/header-darkmode-desktop.svg rename to web/src/assets/svgs/hero/hero-darkmode-desktop.svg index 53ac43cab..348292627 100644 --- a/web/src/assets/svgs/header/header-darkmode-desktop.svg +++ b/web/src/assets/svgs/hero/hero-darkmode-desktop.svg @@ -1,4 +1,4 @@ - + @@ -71,3 +71,4 @@ + diff --git a/web/src/assets/svgs/header/header-darkmode-mobile.svg b/web/src/assets/svgs/hero/hero-darkmode-mobile.svg similarity index 99% rename from web/src/assets/svgs/header/header-darkmode-mobile.svg rename to web/src/assets/svgs/hero/hero-darkmode-mobile.svg index 5e2f9c3df..19e028aa6 100644 --- a/web/src/assets/svgs/header/header-darkmode-mobile.svg +++ b/web/src/assets/svgs/hero/hero-darkmode-mobile.svg @@ -1,4 +1,4 @@ - + diff --git a/web/src/assets/svgs/header/header-lightmode-desktop.svg b/web/src/assets/svgs/hero/hero-lightmode-desktop.svg similarity index 99% rename from web/src/assets/svgs/header/header-lightmode-desktop.svg rename to web/src/assets/svgs/hero/hero-lightmode-desktop.svg index c654a4710..ffd6e9135 100644 --- a/web/src/assets/svgs/header/header-lightmode-desktop.svg +++ b/web/src/assets/svgs/hero/hero-lightmode-desktop.svg @@ -1,4 +1,4 @@ - + diff --git a/web/src/assets/svgs/header/header-lightmode-mobile.svg b/web/src/assets/svgs/hero/hero-lightmode-mobile.svg similarity index 99% rename from web/src/assets/svgs/header/header-lightmode-mobile.svg rename to web/src/assets/svgs/hero/hero-lightmode-mobile.svg index a50f09da1..f0170be39 100644 --- a/web/src/assets/svgs/header/header-lightmode-mobile.svg +++ b/web/src/assets/svgs/hero/hero-lightmode-mobile.svg @@ -1,4 +1,4 @@ - + diff --git a/web/src/assets/svgs/icons/grid.svg b/web/src/assets/svgs/icons/grid.svg new file mode 100644 index 000000000..eb3fa4e05 --- /dev/null +++ b/web/src/assets/svgs/icons/grid.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/assets/svgs/icons/list.svg b/web/src/assets/svgs/icons/list.svg new file mode 100644 index 000000000..338c5b4a4 --- /dev/null +++ b/web/src/assets/svgs/icons/list.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/assets/svgs/icons/vea.svg b/web/src/assets/svgs/icons/vea.svg new file mode 100644 index 000000000..1fa6470d0 --- /dev/null +++ b/web/src/assets/svgs/icons/vea.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/src/assets/svgs/socialmedia/twitter.svg b/web/src/assets/svgs/socialmedia/twitter.svg deleted file mode 100644 index 458fd4f9b..000000000 --- a/web/src/assets/svgs/socialmedia/twitter.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/web/src/assets/svgs/socialmedia/x.svg b/web/src/assets/svgs/socialmedia/x.svg new file mode 100644 index 000000000..e9faf4183 --- /dev/null +++ b/web/src/assets/svgs/socialmedia/x.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index b9e9c9415..85f4dbf41 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -1,13 +1,38 @@ import React from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; import { StandardPagination } from "@kleros/ui-components-library"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { useFiltersContext } from "context/FilterProvider"; import { CasesPageQuery } from "queries/useCasesQuery"; import DisputeCard from "components/DisputeCard"; +import CasesListHeader from "./CasesListHeader"; +import { useLocation } from "react-router-dom"; -const Container = styled.div` +const GridContainer = styled.div<{ path: string }>` display: flex; flex-wrap: wrap; justify-content: center; + align-items: center; + gap: 8px; + ${({ path }) => + landscapeStyle(() => + path === "/dashboard" + ? css` + display: flex; + ` + : css` + display: grid; + row-gap: 16px; + column-gap: 8px; + grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); + justify-content: space-between; + ` + )} +`; +const ListContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; gap: 8px; `; @@ -26,13 +51,26 @@ export interface ICasesGrid { } const CasesGrid: React.FC = ({ disputes, currentPage, setCurrentPage, numberDisputes, casesPerPage }) => { + const { isList } = useFiltersContext(); + const location = useLocation(); + + const path = location.pathname; return ( <> - - {disputes.map((dispute, i) => { - return ; - })} - + {!isList ? ( + + {disputes.map((dispute) => { + return ; + })} + + ) : ( + + {isList && } + {disputes.map((dispute) => { + return ; + })} + + )} theme.secondaryText} !important; + } + + ${landscapeStyle( + () => + css` + display: flex; + ` + )} +`; + +const StyledLabel = styled.label` + padding-left: calc(4px + (8 - 4) * ((100vw - 300px) / (900 - 300))); +`; + +const tooltipMsg = + "Users have an economic interest in serving as jurors in Kleros: " + + "collecting the Juror Rewards in exchange for their work. Each juror who " + + "is coherent with the final ruling receive the Juror Rewards composed of " + + "arbitration fees (ETH) + PNK redistribution between jurors."; + +const CasesListHeader: React.FC = () => { + return ( + + + + + + + Court + Category + + + + + + + ); +}; + +export default CasesListHeader; diff --git a/web/src/components/CasesDisplay/Filters.tsx b/web/src/components/CasesDisplay/Filters.tsx index d74c06574..26f706793 100644 --- a/web/src/components/CasesDisplay/Filters.tsx +++ b/web/src/components/CasesDisplay/Filters.tsx @@ -1,6 +1,11 @@ import React from "react"; -import styled, { useTheme } from "styled-components"; +import styled, { useTheme, css } from "styled-components"; +import { useWindowSize } from "react-use"; import { DropdownSelect } from "@kleros/ui-components-library"; +import { useFiltersContext } from "context/FilterProvider"; +import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle"; +import ListIcon from "svgs/icons/list.svg"; +import GridIcon from "svgs/icons/grid.svg"; const Container = styled.div` display: flex; @@ -9,8 +14,44 @@ const Container = styled.div` width: fit-content; `; +const glowingEffect = css` + filter: drop-shadow(0 0 4px ${({ theme }) => theme.klerosUIComponentsSecondaryPurple}); +`; + +const StyledGridIcon = styled(GridIcon)<{ isList: boolean }>` + cursor: pointer; + transition: filter 0.2s ease; + fill: ${({ theme }) => theme.primaryBlue}; + width: 16px; + height: 16px; + overflow: hidden; + ${({ isList }) => !isList && glowingEffect} +`; + +const IconsContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + gap: 4px; +`; + +const StyledListIcon = styled(ListIcon)<{ isList: boolean; isScreenBig: boolean }>` + cursor: pointer; + display: ${({ isScreenBig }) => (isScreenBig ? "block" : "none")}; + transition: filter 0.2s ease; + fill: ${({ theme }) => theme.primaryBlue}; + width: 16px; + height: 16px; + overflow: hidden; + ${({ isList }) => isList && glowingEffect} +`; + const Filters: React.FC = () => { const theme = useTheme(); + const { width } = useWindowSize(); + const { isList, setIsList } = useFiltersContext(); + const screenIsBig = width > BREAKPOINT_LANDSCAPE; + return ( { defaultValue={0} callback={() => {}} /> + + setIsList(false)} /> + { + if (screenIsBig) { + setIsList(true); + } + }} + /> + ); }; diff --git a/web/src/components/ConnectWallet/AccountDisplay.tsx b/web/src/components/ConnectWallet/AccountDisplay.tsx index d64cdca5d..d39e7c60e 100644 --- a/web/src/components/ConnectWallet/AccountDisplay.tsx +++ b/web/src/components/ConnectWallet/AccountDisplay.tsx @@ -1,5 +1,6 @@ import React from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; import { useAccount, useNetwork, useEnsAvatar, useEnsName } from "wagmi"; import Identicon from "react-identicons"; import { shortenAddress } from "utils/shortenAddress"; @@ -7,8 +8,24 @@ import { shortenAddress } from "utils/shortenAddress"; const Container = styled.div` display: flex; flex-direction: column; + justify-content: space-between; + height: auto; align-items: flex-start; gap: 8px; + align-items: center; + background-color: ${({ theme }) => theme.whiteBackground}; + padding: 0px; + + ${landscapeStyle( + () => css` + background-color: ${({ theme }) => theme.whiteLowOpacity}; + flex-direction: row; + align-content: center; + border-radius: 300px; + gap: 0px; + padding: 0 12px; + ` + )} `; const AccountContainer = styled.div` @@ -17,22 +34,36 @@ const AccountContainer = styled.div` align-items: center; width: fit-content; gap: 8px; + > label { - color: ${({ theme }) => theme.primaryText}; font-size: 16px; font-weight: 600; } + + ${landscapeStyle( + () => css` + gap: 12px; + > label { + color: ${({ theme }) => theme.primaryText}; + font-weight: 400; + font-size: 14px; + } + ` + )} `; const ChainConnectionContainer = styled.div` + display: flex; width: fit-content; min-height: 32px; - display: flex; align-items: center; + padding-left: 0px; > label { color: ${({ theme }) => theme.success}; font-size: 16px; - margin-right: 4px; + + font-weight: 500; } + :before { content: ""; width: 8px; @@ -41,6 +72,12 @@ const ChainConnectionContainer = styled.div` border-radius: 50%; background-color: ${({ theme }) => theme.success}; } + + ${landscapeStyle( + () => css` + display: none; + ` + )} `; const StyledIdenticon = styled(Identicon)<{ size: `${number}` }>` diff --git a/web/src/components/ConnectWallet/index.tsx b/web/src/components/ConnectWallet/index.tsx index 59c070d32..f1c30a007 100644 --- a/web/src/components/ConnectWallet/index.tsx +++ b/web/src/components/ConnectWallet/index.tsx @@ -1,19 +1,9 @@ import React from "react"; -import styled from "styled-components"; import { useAccount, useNetwork, useSwitchNetwork } from "wagmi"; import { useWeb3Modal } from "@web3modal/react"; import { Button } from "@kleros/ui-components-library"; import { SUPPORTED_CHAINS, DEFAULT_CHAIN } from "consts/chains"; import AccountDisplay from "./AccountDisplay"; -import { DisconnectWalletButton } from "layout/Header/navbar/Menu/Settings/General"; - -const Container = styled.div` - display: flex; - gap: 16px; - justify-content: space-between; - flex-wrap: wrap; - align-items: center; -`; export const SwitchChainButton: React.FC = () => { const { switchNetwork, isLoading } = useSwitchNetwork(); @@ -49,13 +39,7 @@ const ConnectWallet: React.FC = () => { if (isConnected) { if (chain && chain.id !== DEFAULT_CHAIN) { return ; - } else - return ( - - - - - ); + } else return ; } else return ; }; diff --git a/web/src/components/DisputeCard/DisputeInfo.tsx b/web/src/components/DisputeCard/DisputeInfo.tsx index dc36b524f..742db46aa 100644 --- a/web/src/components/DisputeCard/DisputeInfo.tsx +++ b/web/src/components/DisputeCard/DisputeInfo.tsx @@ -1,5 +1,6 @@ import React from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { useFiltersContext } from "context/FilterProvider"; import { Periods } from "consts/periods"; import BookmarkIcon from "svgs/icons/bookmark.svg"; import CalendarIcon from "svgs/icons/calendar.svg"; @@ -8,10 +9,20 @@ import PileCoinsIcon from "svgs/icons/pile-coins.svg"; import RoundIcon from "svgs/icons/round.svg"; import Field from "../Field"; -const Container = styled.div` +const Container = styled.div<{ isList: boolean }>` display: flex; - flex-direction: column; + flex-direction: ${({ isList }) => (isList ? "row" : "column")}; gap: 8px; + + ${({ isList }) => + isList && + css` + gap: calc(4px + (24px - 4px) * ((100vw - 300px) / (900 - 300))); + `}; + justify-content: ${({ isList }) => (isList ? "space-around" : "center")}; + align-items: center; + width: 100%; + height: 100%; `; const getPeriodPhrase = (period: Periods): string => { @@ -37,15 +48,29 @@ export interface IDisputeInfo { round?: number; } +const formatDate = (date: number) => { + const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "long", day: "numeric" }; + const startingDate = new Date(date * 1000); + const formattedDate = startingDate.toLocaleDateString("en-US", options); + return formattedDate; +}; + const DisputeInfo: React.FC = ({ courtId, court, category, rewards, period, date, round }) => { + const { isList } = useFiltersContext(); + return ( - + {court && courtId && } {category && } + {!category && isList && } {round && } {rewards && } {typeof period !== "undefined" && date && ( - + )} ); diff --git a/web/src/components/DisputeCard/PeriodBanner.tsx b/web/src/components/DisputeCard/PeriodBanner.tsx index fb8dd3e0a..0ad1ac647 100644 --- a/web/src/components/DisputeCard/PeriodBanner.tsx +++ b/web/src/components/DisputeCard/PeriodBanner.tsx @@ -3,7 +3,7 @@ import styled, { Theme } from "styled-components"; import { Periods } from "consts/periods"; const Container = styled.div>` - height: 45px; + height: ${({ isCard }) => (isCard ? "45px" : "100%")}; width: auto; border-top-right-radius: 3px; border-top-left-radius: 3px; @@ -21,11 +21,11 @@ const Container = styled.div>` margin-right: 8px; } } - ${({ theme, period }) => { + ${({ theme, period, isCard }) => { const [frontColor, backgroundColor] = getPeriodColors(period, theme); return ` - border-top: 5px solid ${frontColor}; - background-color: ${backgroundColor}; + ${isCard ? `border-top: 5px solid ${frontColor}` : `border-left: 5px solid ${frontColor}`}; + ${isCard && `background-color: ${backgroundColor}`}; .front-color { color: ${frontColor}; } @@ -41,6 +41,7 @@ const Container = styled.div>` export interface IPeriodBanner { id: number; period: Periods; + isCard?: boolean; } const getPeriodColors = (period: Periods, theme: Theme): [string, string] => { @@ -65,9 +66,9 @@ const getPeriodLabel = (period: Periods): string => { } }; -const PeriodBanner: React.FC = ({ id, period }) => ( - - +const PeriodBanner: React.FC = ({ id, period, isCard = true }) => ( + + {isCard && } ); diff --git a/web/src/components/DisputeCard/index.tsx b/web/src/components/DisputeCard/index.tsx index 6a27b0277..9fea91054 100644 --- a/web/src/components/DisputeCard/index.tsx +++ b/web/src/components/DisputeCard/index.tsx @@ -1,10 +1,12 @@ import React from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; import { useNavigate } from "react-router-dom"; import { formatEther } from "viem"; import { StyledSkeleton } from "components/StyledSkeleton"; import { Card } from "@kleros/ui-components-library"; import { Periods } from "consts/periods"; +import { useFiltersContext } from "context/FilterProvider"; +import { landscapeStyle } from "styles/landscapeStyle"; import { CasesPageQuery } from "queries/useCasesQuery"; import { useCourtPolicy } from "queries/useCourtPolicy"; import { useDisputeTemplate } from "queries/useDisputeTemplate"; @@ -14,13 +16,23 @@ import { isUndefined } from "utils/index"; import { useVotingHistory } from "hooks/queries/useVotingHistory"; const StyledCard = styled(Card)` - max-width: 380px; - min-width: 312px; - width: auto; + width: 312px; height: 260px; + ${landscapeStyle( + () => + css` + width: 380px; + ` + )} +`; +const StyledListItem = styled(Card)` + display: flex; + flex-grow: 1; + width: 100%; + height: 64px; `; -const Container = styled.div` +const CardContainer = styled.div` height: 215px; padding: 24px; display: flex; @@ -30,6 +42,25 @@ const Container = styled.div` margin: 0; } `; +const ListContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: flex-start; + width: 100%; + margin-right: 8px; + + h3 { + margin: 0; + } +`; + +const ListTitle = styled.div` + display: flex; + height: 100%; + justify-content: start; + align-items: center; + width: calc(30vw + (40 - 30) * ((100vw - 300px) / (1250 - 300))); +`; export const getPeriodEndTimestamp = ( lastPeriodChange: string, @@ -40,6 +71,11 @@ export const getPeriodEndTimestamp = ( return parseInt(lastPeriodChange) + durationCurrentPeriod; }; +const TruncatedTitle = ({ text, maxLength }) => { + const truncatedText = text.length <= maxLength ? text : text.slice(0, maxLength) + "…"; + return

{truncatedText}

; +}; + const DisputeCard: React.FC = ({ id, arbitrated, @@ -47,6 +83,7 @@ const DisputeCard: React.FC = ({ lastPeriodChange, court, }) => { + const { isList } = useFiltersContext(); const currentPeriodIndex = Periods[period]; const rewards = `≥ ${formatEther(court.feeForJuror)} ETH`; const date = @@ -66,19 +103,41 @@ const DisputeCard: React.FC = ({ const localRounds = votingHistory?.dispute?.disputeKitDispute?.localRounds; const navigate = useNavigate(); return ( - navigate(`/cases/${id.toString()}`)}> - - -

{title}

- -
-
+ <> + {!isList ? ( + navigate(`/cases/${id.toString()}`)}> + + +

{title}

+ +
+
+ ) : ( + navigate(`/cases/${id.toString()}`)}> + + + + + + + + + )} + ); }; diff --git a/web/src/components/EvidenceCard.tsx b/web/src/components/EvidenceCard.tsx index 44a1cabeb..c6f8d0154 100644 --- a/web/src/components/EvidenceCard.tsx +++ b/web/src/components/EvidenceCard.tsx @@ -1,5 +1,6 @@ import React from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; import Identicon from "react-identicons"; import { Card } from "@kleros/ui-components-library"; import AttachmentIcon from "svgs/icons/attachment.svg"; @@ -13,7 +14,7 @@ const StyledCard = styled(Card)` `; const TextContainer = styled.div` - padding: 8px; + padding: calc(8px + (24 - 8) * (min(max(100vw, 375px), 1250px) - 375px) / 875); > * { overflow-wrap: break-word; margin: 0; @@ -32,8 +33,8 @@ const BottomShade = styled.div` background-color: ${({ theme }) => theme.lightBlue}; display: flex; align-items: center; - gap: 8px; - padding: 8px; + gap: 16px; + padding: 12px calc(8px + (24 - 8) * (min(max(100vw, 375px), 1250px) - 375px) / 875); > * { flex-basis: 1; flex-shrink: 0; @@ -42,15 +43,62 @@ const BottomShade = styled.div` `; const StyledA = styled.a` + display: flex; margin-left: auto; - margin-right: 8px; + gap: calc(5px + (6 - 5) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + + ${landscapeStyle( + () => css` + > svg { + width: 16px; + fill: ${({ theme }) => theme.primaryBlue}; + } + ` + )} +`; + +const AccountContainer = styled.div` display: flex; - > svg { - width: 16px; - fill: ${({ theme }) => theme.primaryBlue}; + flex-direction: row; + gap: 8px; + align-items: center; + + canvas { + width: 24px; + height: 24px; + } + + > * { + flex-basis: 1; + flex-shrink: 0; + margin: 0; } `; +const DesktopText = styled.span` + display: none; + ${landscapeStyle( + () => css` + display: inline; + ` + )} +`; + +const MobileText = styled.span` + ${landscapeStyle( + () => css` + display: none; + ` + )} +`; + +const AttachedFileText: React.FC = () => ( + <> + View attached file + File + +); + interface IEvidenceCard { evidence: string; sender: string; @@ -73,11 +121,14 @@ const EvidenceCard: React.FC = ({ evidence, sender, index }) => { )} - -

{shortenAddress(sender)}

+ + +

{shortenAddress(sender)}

+
{data && typeof data.fileURI !== "undefined" && ( + )}
diff --git a/web/src/components/Field.tsx b/web/src/components/Field.tsx index af7b09df8..15a08ce6e 100644 --- a/web/src/components/Field.tsx +++ b/web/src/components/Field.tsx @@ -1,15 +1,17 @@ import React from "react"; import { Link } from "react-router-dom"; import styled from "styled-components"; +import { useFiltersContext } from "context/FilterProvider"; const FieldContainer = styled.div` - width: ${({ width = "100%" }) => width}; + width: ${({ isList }) => (isList ? "auto" : "100%")}; display: flex; align-items: center; justify-content: flex-start; + white-space: nowrap; .value { - flex-grow: 1; - text-align: end; + flex-grow: ${({ isList }) => (isList ? "0" : "1")}; + text-align: ${({ isList }) => (isList ? "center" : "end")}; color: ${({ theme }) => theme.primaryText}; } svg { @@ -27,6 +29,7 @@ const FieldContainer = styled.div` type FieldContainerProps = { width?: string; + isList?: boolean; }; interface IField { @@ -37,18 +40,25 @@ interface IField { width?: string; } -const Field: React.FC = ({ icon: Icon, name, value, link, width }) => ( - - {} - - {link ? ( - - {value} - - ) : ( - - )} - -); +const Field: React.FC = ({ icon: Icon, name, value, link, width }) => { + const { isList } = useFiltersContext(); + return ( + + {!isList && ( + <> + + + + )} + {link ? ( + + {value} + + ) : ( + + )} + + ); +}; export default Field; diff --git a/web/src/components/Popup/index.tsx b/web/src/components/Popup/index.tsx index a289afe1c..92cf5b46c 100644 --- a/web/src/components/Popup/index.tsx +++ b/web/src/components/Popup/index.tsx @@ -1,5 +1,6 @@ import React, { useRef } from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; import { Button } from "@kleros/ui-components-library"; import { Overlay } from "components/Overlay"; import StakeWithdraw from "./Description/StakeWithdraw"; @@ -44,6 +45,8 @@ const Container = styled.div` top: 50%; left: 50%; transform: translate(-50%, -50%); + max-height: 80vh; + overflow-y: auto; z-index: 10; flex-direction: column; @@ -59,6 +62,12 @@ const Container = styled.div` svg { visibility: visible; } + + ${landscapeStyle( + () => css` + overflow-y: hidden; + ` + )} `; const VoteDescriptionContainer = styled.div` diff --git a/web/src/components/StatDisplay.tsx b/web/src/components/StatDisplay.tsx index 43c929663..79d1073c6 100644 --- a/web/src/components/StatDisplay.tsx +++ b/web/src/components/StatDisplay.tsx @@ -1,10 +1,18 @@ import React from "react"; -import styled, { useTheme } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import styled, { useTheme, css } from "styled-components"; const Container = styled.div` display: flex; + max-width: 196px; align-items: center; gap: 8px; + + ${landscapeStyle( + () => css` + margin-bottom: calc(16px + (30 - 16) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + ` + )} `; const SVGContainer = styled.div<{ iconColor: string; backgroundColor: string }>` diff --git a/web/src/components/Verdict/DisputeTimeline.tsx b/web/src/components/Verdict/DisputeTimeline.tsx index c2efa61e1..2daad4db0 100644 --- a/web/src/components/Verdict/DisputeTimeline.tsx +++ b/web/src/components/Verdict/DisputeTimeline.tsx @@ -16,19 +16,17 @@ const Container = styled.div` display: flex; position: relative; margin-left: 8px; + flex-direction: column; `; const StyledTimeline = styled(CustomTimeline)` width: 100%; - margin-bottom: 32px; `; const EnforcementContainer = styled.div` - position: absolute; - bottom: 0; display: flex; gap: 8px; - margin-bottom: 8px; + margin-top: calc(12px + (24 - 12) * (min(max(100vw, 375px), 1250px) - 375px) / 875); fill: ${({ theme }) => theme.secondaryText}; small { diff --git a/web/src/components/Verdict/FinalDecision.tsx b/web/src/components/Verdict/FinalDecision.tsx index 5c40e5d09..dfb30960c 100644 --- a/web/src/components/Verdict/FinalDecision.tsx +++ b/web/src/components/Verdict/FinalDecision.tsx @@ -19,6 +19,7 @@ const JuryContainer = styled.div` gap: 8px; h3 { line-height: 21px; + margin-bottom: 0px; } `; @@ -28,10 +29,6 @@ const JuryDecisionTag = styled.small` color: ${({ theme }) => theme.secondaryText}; `; -const Divider = styled.hr` - color: ${({ theme }) => theme.stroke}; -`; - const UserContainer = styled.div` display: flex; align-items: center; @@ -65,12 +62,19 @@ const StyledButton = styled(LightButton)` > .button-text { color: ${({ theme }) => theme.primaryBlue}; } + padding-top: 0px; `; const AnswerTitle = styled.h3` margin: 0; `; +const Divider = styled.hr` + display: flex; + color: ${({ theme }) => theme.stroke}; + margin: calc(16px + (32 - 16) * (min(max(100vw, 375px), 1250px) - 375px) / 875) 0px; +`; + interface IFinalDecision { arbitrable?: `0x${string}`; } diff --git a/web/src/components/Verdict/VerdictBanner.tsx b/web/src/components/Verdict/VerdictBanner.tsx index 8cfa591f0..516b0779f 100644 --- a/web/src/components/Verdict/VerdictBanner.tsx +++ b/web/src/components/Verdict/VerdictBanner.tsx @@ -6,7 +6,7 @@ import HourglassIcon from "assets/svgs/icons/hourglass.svg"; const BannerContainer = styled.div` display: flex; gap: 8px; - margin: 16px 0px; + margin-bottom: calc(16px + (24 - 16) * (min(max(100vw, 375px), 1250px) - 375px) / 875); svg { width: 16px; height: 16px; diff --git a/web/src/consts/socialmedia.tsx b/web/src/consts/socialmedia.tsx index bd6ffebb7..749e11396 100644 --- a/web/src/consts/socialmedia.tsx +++ b/web/src/consts/socialmedia.tsx @@ -3,7 +3,7 @@ import EtherscanLogo from "svgs/socialmedia/etherscan.svg"; import GithubLogo from "svgs/socialmedia/github.svg"; import SnapshotLogo from "svgs/socialmedia/snapshot.svg"; import DiscordLogo from "svgs/socialmedia/discord.svg"; -import TwitterLogo from "svgs/socialmedia/twitter.svg"; +import XLogo from "svgs/socialmedia/x.svg"; import RedditLogo from "svgs/socialmedia/reddit.svg"; import TelegramLogo from "svgs/socialmedia/telegram.svg"; import GhostBlogLogo from "svgs/socialmedia/ghost-blog.svg"; @@ -26,9 +26,9 @@ export const socialmedia = { icon: , url: "https://discord.com/invite/MhXQGCyHd9", }, - twitter: { - icon: , - url: "https://twitter.com/kleros_io", + x: { + icon: , + url: "https://x.com/kleros_io", }, reddit: { icon: , diff --git a/web/src/context/FilterProvider.tsx b/web/src/context/FilterProvider.tsx new file mode 100644 index 000000000..267059c5e --- /dev/null +++ b/web/src/context/FilterProvider.tsx @@ -0,0 +1,25 @@ +import React, { useState, createContext, useContext } from "react"; + +interface IFilters { + isList: boolean; + setIsList: (arg0: boolean) => void; +} + +const Context = createContext({ + isList: false, + setIsList: () => { + // + }, +}); + +export const FilterProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => { + const [isList, setIsList] = useState(false); + + const value = { + isList, + setIsList, + }; + return {children}; +}; + +export const useFiltersContext = () => useContext(Context); diff --git a/web/src/context/OverlayScrollContext.tsx b/web/src/context/OverlayScrollContext.tsx new file mode 100644 index 000000000..7b9629317 --- /dev/null +++ b/web/src/context/OverlayScrollContext.tsx @@ -0,0 +1,3 @@ +import { createContext, MutableRefObject } from "react"; + +export const OverlayScrollContext = createContext | null>(null); diff --git a/web/src/context/StyledComponentsProvider.tsx b/web/src/context/StyledComponentsProvider.tsx index f5d88f952..7d11c3407 100644 --- a/web/src/context/StyledComponentsProvider.tsx +++ b/web/src/context/StyledComponentsProvider.tsx @@ -8,7 +8,7 @@ import { lightTheme, darkTheme } from "styles/themes"; const StyledComponentsProvider: React.FC<{ children: React.ReactNode; }> = ({ children }) => { - const [theme, setTheme] = useLocalStorage("theme", "light"); + const [theme, setTheme] = useLocalStorage("theme", "dark"); const toggleTheme = () => { if (theme === "light") setTheme("dark"); else setTheme("light"); diff --git a/web/src/context/Web3Provider.tsx b/web/src/context/Web3Provider.tsx index 1677ebbac..c6e161578 100644 --- a/web/src/context/Web3Provider.tsx +++ b/web/src/context/Web3Provider.tsx @@ -8,7 +8,7 @@ import { jsonRpcProvider } from "wagmi/providers/jsonRpc"; import { useToggleTheme } from "hooks/useToggleThemeContext"; import { useTheme } from "styled-components"; -const chains = [mainnet, arbitrumGoerli, gnosisChiado]; +const chains = [arbitrumGoerli, gnosisChiado]; const projectId = process.env.WALLETCONNECT_PROJECT_ID ?? "6efaa26765fa742153baf9281e218217"; const { publicClient, webSocketPublicClient } = configureChains(chains, [ diff --git a/web/src/hooks/queries/useCasesQuery.ts b/web/src/hooks/queries/useCasesQuery.ts index c6b813924..6d1cf7a9e 100644 --- a/web/src/hooks/queries/useCasesQuery.ts +++ b/web/src/hooks/queries/useCasesQuery.ts @@ -5,8 +5,8 @@ import { graphqlQueryFnHelper } from "~src/utils/graphqlQueryFnHelper"; export type { CasesPageQuery }; const casesQuery = graphql(` - query CasesPage($skip: Int) { - disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: desc) { + query CasesPage($first: Int, $skip: Int) { + disputes(first: $first, skip: $skip, orderBy: lastPeriodChange, orderDirection: desc) { id arbitrated { id @@ -26,12 +26,12 @@ const casesQuery = graphql(` } `); -export const useCasesQuery = (skip: number) => { +export const useCasesQuery = (skip: number, first = 3) => { const isEnabled = skip !== undefined; return useQuery({ - queryKey: [`useCasesQuery${skip}`], + queryKey: [`useCasesQuery${skip},${first}`], enabled: isEnabled, - queryFn: async () => await graphqlQueryFnHelper(casesQuery, { skip: skip }), + queryFn: async () => await graphqlQueryFnHelper(casesQuery, { skip, first }), }); }; diff --git a/web/src/hooks/useLockOverlayScroll.ts b/web/src/hooks/useLockOverlayScroll.ts new file mode 100644 index 000000000..146cdcd2a --- /dev/null +++ b/web/src/hooks/useLockOverlayScroll.ts @@ -0,0 +1,28 @@ +import { useContext, useEffect, useCallback } from "react"; +import { OverlayScrollContext } from "context/OverlayScrollContext"; + +export const useLockOverlayScroll = (shouldLock: boolean) => { + const osInstanceRef = useContext(OverlayScrollContext); + + const lockScroll = useCallback(() => { + const osInstance = osInstanceRef?.current?.osInstance(); + if (osInstance) { + osInstance.options({ overflow: { x: "hidden", y: "hidden" } }); + } + }, [osInstanceRef]); + + const unlockScroll = useCallback(() => { + const osInstance = osInstanceRef?.current?.osInstance(); + if (osInstance) { + osInstance.options({ overflow: { x: "scroll", y: "scroll" } }); + } + }, [osInstanceRef]); + + useEffect(() => { + if (shouldLock) { + lockScroll(); + } else { + unlockScroll(); + } + }, [shouldLock, lockScroll, unlockScroll]); +}; diff --git a/web/src/layout/Footer/index.tsx b/web/src/layout/Footer/index.tsx index fe5bc92d8..e6d27e72c 100644 --- a/web/src/layout/Footer/index.tsx +++ b/web/src/layout/Footer/index.tsx @@ -1,5 +1,6 @@ import React from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; import SecuredByKlerosLogo from "svgs/footer/secured-by-kleros.svg"; import { socialmedia } from "consts/socialmedia"; @@ -9,10 +10,19 @@ const Container = styled.div` background-color: ${({ theme }) => theme.primaryPurple}; display: flex; flex-direction: column; - align-items: center; justify-content: center; + align-items: center; + padding: 0 32px; gap: 16px; + ${landscapeStyle( + () => css` + height: 64px; + flex-direction: row; + justify-content: space-between; + ` + )} + .secured-by-kleros { min-height: 24px; } diff --git a/web/src/layout/Header/DesktopHeader.tsx b/web/src/layout/Header/DesktopHeader.tsx new file mode 100644 index 000000000..d118fa85b --- /dev/null +++ b/web/src/layout/Header/DesktopHeader.tsx @@ -0,0 +1,124 @@ +import React from "react"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { useToggle } from "react-use"; +import { Link } from "react-router-dom"; +import { useLockOverlayScroll } from "hooks/useLockOverlayScroll"; +import KlerosSolutionsIcon from "svgs/menu-icons/kleros-solutions.svg"; +import KlerosCourtLogo from "svgs/header/kleros-court.svg"; +import ConnectWallet from "components/ConnectWallet"; +import LightButton from "components/LightButton"; +import DappList from "./navbar/DappList"; +import Explore from "./navbar/Explore"; +import Menu from "./navbar/Menu"; +import Help from "./navbar/Menu/Help"; +import Settings from "./navbar/Menu/Settings"; +import { Overlay } from "components/Overlay"; +import { PopupContainer } from "."; + +const Container = styled.div` + display: none; + position: absolute; + + ${landscapeStyle( + () => css` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + position: relative; + ` + )}; +`; + +const LeftSide = styled.div` + display: flex; +`; + +const MiddleSide = styled.div` + display: flex; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + color: ${({ theme }) => theme.white} !important; +`; + +const RightSide = styled.div` + display: flex; + gap: calc(8px + (16 - 8) * ((100vw - 300px) / (1024 - 300))); + margin-left: 8px; + canvas { + width: 20px; + } +`; + +const LightButtonContainer = styled.div` + display: flex; + align-items: center; + width: 16px; + margin-left: calc(4px + (8 - 4) * ((100vw - 375px) / (1250 - 375))); + margin-right: calc(12px + (16 - 12) * ((100vw - 375px) / (1250 - 375))); +`; + +const StyledLink = styled(Link)` + min-height: 48px; +`; + +const StyledKlerosSolutionsIcon = styled(KlerosSolutionsIcon)` + fill: ${({ theme }) => theme.white} !important; +`; + +const ConnectWalletContainer = styled.div` + label { + color: ${({ theme }) => theme.white}; + } +`; + +const DesktopHeader = () => { + const [isDappListOpen, toggleIsDappListOpen] = useToggle(false); + const [isHelpOpen, toggleIsHelpOpen] = useToggle(false); + const [isSettingsOpen, toggleIsSettingsOpen] = useToggle(false); + useLockOverlayScroll(isDappListOpen || isHelpOpen || isSettingsOpen); + + return ( + <> + + + + { + toggleIsDappListOpen(); + }} + Icon={StyledKlerosSolutionsIcon} + /> + + + + + + + + + + + + + + + + + + {(isDappListOpen || isHelpOpen || isSettingsOpen) && ( + + + {isDappListOpen && } + {isHelpOpen && } + {isSettingsOpen && } + + )} + + ); +}; +export default DesktopHeader; diff --git a/web/src/layout/Header/MobileHeader.tsx b/web/src/layout/Header/MobileHeader.tsx new file mode 100644 index 000000000..4cc4d115c --- /dev/null +++ b/web/src/layout/Header/MobileHeader.tsx @@ -0,0 +1,69 @@ +import React, { useContext, useMemo, useRef } from "react"; +import styled, { css } from "styled-components"; +import { useToggle } from "react-use"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { Link } from "react-router-dom"; +import KlerosCourtLogo from "svgs/header/kleros-court.svg"; +import HamburgerIcon from "svgs/header/hamburger.svg"; +import LightButton from "components/LightButton"; +import NavBar from "./navbar"; +import { useFocusOutside } from "hooks/useFocusOutside"; + +const Container = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + + ${landscapeStyle( + () => css` + display: none; + ` + )} +`; + +const StyledLightButton = styled(LightButton)` + padding: 0; + + .button-svg { + margin-right: 0px; + fill: white; + } + .button-text { + display: none; + } +`; + +const StyledLink = styled(Link)` + min-height: 48px; +`; + +const OpenContext = React.createContext({ + isOpen: false, + toggleIsOpen: () => { + // Placeholder + }, +}); + +export function useOpenContext() { + return useContext(OpenContext); +} + +const MobileHeader = () => { + const [isOpen, toggleIsOpen] = useToggle(false); + const containerRef = useRef(null); + useFocusOutside(containerRef, () => toggleIsOpen(false)); + const memoizedContext = useMemo(() => ({ isOpen, toggleIsOpen }), [isOpen, toggleIsOpen]); + return ( + + + + + + + + + + ); +}; +export default MobileHeader; diff --git a/web/src/layout/Header/index.tsx b/web/src/layout/Header/index.tsx index e4c0b61d0..6287381e6 100644 --- a/web/src/layout/Header/index.tsx +++ b/web/src/layout/Header/index.tsx @@ -1,11 +1,7 @@ -import React, { useState, useRef, useContext } from "react"; +import React from "react"; import styled from "styled-components"; -import { Link } from "react-router-dom"; -import HamburgerIcon from "svgs/header/hamburger.svg"; -import KlerosCourtLogo from "svgs/header/kleros-court.svg"; -import LightButton from "components/LightButton"; -import NavBar from "./navbar"; -import { useFocusOutside } from "hooks/useFocusOutside"; +import MobileHeader from "./MobileHeader"; +import DesktopHeader from "./DesktopHeader"; const Container = styled.div` position: sticky; @@ -17,53 +13,22 @@ const Container = styled.div` padding: 0 24px; display: flex; - align-items: center; - justify-content: space-between; - - .kleros-court-link { - min-height: 48px; - } `; -const StyledLightButton = styled(LightButton)` - padding: 0; - - .button-svg { - margin-right: 0px; - fill: white; - } - .button-text { - display: none; - } +export const PopupContainer = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 30; `; -const OpenContext = React.createContext({ - isOpen: false, - toggleIsOpen: () => { - // Placeholder - }, -}); - -export function useOpenContext() { - return useContext(OpenContext); -} - const Header: React.FC = () => { - const [isOpen, setIsOpen] = useState(false); - const toggleIsOpen = () => setIsOpen(!isOpen); - const containerRef = useRef(null); - useFocusOutside(containerRef, () => setIsOpen(false)); return ( - - - - -
- - -
-
+ +
); }; diff --git a/web/src/layout/Header/navbar/DappList.tsx b/web/src/layout/Header/navbar/DappList.tsx index 81c69a38b..17c231f0d 100644 --- a/web/src/layout/Header/navbar/DappList.tsx +++ b/web/src/layout/Header/navbar/DappList.tsx @@ -1,5 +1,6 @@ import React, { useRef } from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; import { useFocusOutside } from "hooks/useFocusOutside"; import Curate from "svgs/icons/curate-image.png"; import Resolver from "svgs/icons/dispute-resolver.svg"; @@ -8,9 +9,9 @@ import Governor from "svgs/icons/governor.svg"; import Court from "svgs/icons/kleros.svg"; import Linguo from "svgs/icons/linguo.svg"; import POH from "svgs/icons/poh-image.png"; +import Vea from "svgs/icons/vea.svg"; import Tokens from "svgs/icons/tokens.svg"; import Product from "./Product"; -import { Overlay } from "components/Overlay"; const Header = styled.h1` display: flex; @@ -24,11 +25,11 @@ const Header = styled.h1` const Container = styled.div` display: flex; position: absolute; - max-height: 60vh; + max-height: 80vh; top: 5%; left: 50%; transform: translate(-50%); - z-index: 10; + z-index: 1; flex-direction: column; align-items: center; @@ -43,6 +44,16 @@ const Container = styled.div` svg { visibility: visible; } + + ${landscapeStyle( + () => css` + margin-top: 64px; + top: 0; + left: 0; + right: auto; + transform: none; + ` + )} `; const ItemsDiv = styled.div` @@ -58,26 +69,22 @@ const ItemsDiv = styled.div` grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); `; -interface IDappList { - toggleSolution: () => void; -} - const ITEMS = [ { text: "Court v1", Icon: Court, url: "https://court.kleros.io/", }, + { + text: "Vea", + Icon: Vea, + url: "https://veascan.io", + }, { text: "Escrow", Icon: Escrow, url: "https://escrow.kleros.io", }, - { - text: "Tokens", - Icon: Tokens, - url: "https://tokens.kleros.io", - }, { text: "POH", Icon: POH, @@ -88,6 +95,11 @@ const ITEMS = [ Icon: Curate, url: "https://curate.kleros.io", }, + { + text: "Tokens", + Icon: Tokens, + url: "https://tokens.kleros.io", + }, { text: "Resolver", Icon: Resolver, @@ -105,24 +117,25 @@ const ITEMS = [ }, ]; -const DappList: React.FC = ({ toggleSolution }) => { +interface IDappList { + toggleIsDappListOpen: () => void; +} + +const DappList: React.FC = ({ toggleIsDappListOpen }) => { const containerRef = useRef(null); useFocusOutside(containerRef, () => { - toggleSolution(); + toggleIsDappListOpen(); }); return ( - <> - - -
Kleros Solutions
- - {ITEMS.map((item) => { - return ; - })} - -
- + +
Kleros Solutions
+ + {ITEMS.map((item) => { + return ; + })} + +
); }; export default DappList; diff --git a/web/src/layout/Header/navbar/Explore.tsx b/web/src/layout/Header/navbar/Explore.tsx index 87cf63d12..816e3b643 100644 --- a/web/src/layout/Header/navbar/Explore.tsx +++ b/web/src/layout/Header/navbar/Explore.tsx @@ -1,20 +1,50 @@ import React from "react"; -import styled from "styled-components"; -import { Link } from "react-router-dom"; -import { useOpenContext } from "../index"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { Link, useLocation } from "react-router-dom"; +import { useOpenContext } from "../MobileHeader"; -const Container = styled.div``; +const Container = styled.div` + display: flex; + gap: 0px; + flex-direction: column; + + ${landscapeStyle( + () => css` + flex-direction: row; + gap: calc(4px + (16 - 4) * ((100vw - 375px) / (1250 - 375))); + ` + )}; +`; const LinkContainer = styled.div` - min-height: 32px; display: flex; + min-height: 32px; align-items: center; +`; - .sm-link { - color: ${({ theme }) => theme.primaryText}; - text-decoration: none; - font-size: 16px; - } +const Title = styled.h1` + display: block; + + ${landscapeStyle( + () => css` + display: none; + ` + )}; +`; + +const StyledLink = styled(Link)<{ isActive: boolean }>` + color: ${({ theme }) => theme.primaryText}; + text-decoration: none; + font-size: 16px; + + font-weight: ${({ isActive }) => (isActive ? "600" : "normal")}; + + ${landscapeStyle( + () => css` + color: ${({ theme }) => theme.white}; + ` + )}; `; const links = [ @@ -24,15 +54,17 @@ const links = [ ]; const Explore: React.FC = () => { + const location = useLocation(); const { toggleIsOpen } = useOpenContext(); + return ( -

Explore

+ Explore {links.map(({ to, text }) => ( - + {text} - + ))}
diff --git a/web/src/layout/Header/navbar/Menu/Help.tsx b/web/src/layout/Header/navbar/Menu/Help.tsx index 1559d6b68..a242ba067 100644 --- a/web/src/layout/Header/navbar/Menu/Help.tsx +++ b/web/src/layout/Header/navbar/Menu/Help.tsx @@ -1,5 +1,6 @@ import React, { useRef } from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; import { useFocusOutside } from "hooks/useFocusOutside"; import Book from "svgs/icons/book-open.svg"; import Guide from "svgs/icons/book.svg"; @@ -7,22 +8,35 @@ import Bug from "svgs/icons/bug.svg"; import ETH from "svgs/icons/eth.svg"; import Faq from "svgs/menu-icons/help.svg"; import Telegram from "svgs/socialmedia/telegram.svg"; -import { Overlay } from "components/Overlay"; const Container = styled.div` display: flex; flex-direction: column; position: absolute; + max-height: 80vh; + overflow-y: auto; + width: auto; top: 5%; left: 50%; - transform: translate(-50%); - z-index: 10; + transform: translateX(-50%); + z-index: 1; padding: 27px 10px; gap: 23px; border: 1px solid ${({ theme }) => theme.stroke}; background-color: ${({ theme }) => theme.whiteBackground}; border-radius: 3px; box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.06); + + ${landscapeStyle( + () => css` + margin-top: 64px; + width: 240px; + top: 0; + right: 0; + left: auto; + transform: none; + ` + )} `; const ListItem = styled.a` @@ -83,27 +97,24 @@ const ITEMS = [ ]; interface IHelp { - toggle: () => void; + toggleIsHelpOpen: () => void; } -const Help: React.FC = ({ toggle }) => { +const Help: React.FC = ({ toggleIsHelpOpen }) => { const containerRef = useRef(null); useFocusOutside(containerRef, () => { - toggle(); + toggleIsHelpOpen(); }); return ( - <> - - - {ITEMS.map((item) => ( - - - {item.text} - - ))} - - + + {ITEMS.map((item) => ( + + + {item.text} + + ))} + ); }; export default Help; diff --git a/web/src/layout/Header/navbar/Menu/Settings/index.tsx b/web/src/layout/Header/navbar/Menu/Settings/index.tsx index a6bf761d0..91a8faba6 100644 --- a/web/src/layout/Header/navbar/Menu/Settings/index.tsx +++ b/web/src/layout/Header/navbar/Menu/Settings/index.tsx @@ -1,23 +1,37 @@ -import React, { Dispatch, SetStateAction, useRef, useState } from "react"; -import styled from "styled-components"; +import React, { useRef, useState } from "react"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; import { Tabs } from "@kleros/ui-components-library"; import General from "./General"; import SendMeNotifications from "./SendMeNotifications"; import { useFocusOutside } from "hooks/useFocusOutside"; -import { Overlay } from "components/Overlay"; const Container = styled.div` display: flex; position: absolute; + max-height: 80vh; + overflow-y: auto; z-index: 1; background-color: ${({ theme }) => theme.whiteBackground}; flex-direction: column; - border: 1px solid ${({ theme }) => theme.stroke}; - border-radius: 3px; - overflow-y: auto; top: 5%; left: 50%; transform: translateX(-50%); + z-index: 1; + background-color: ${({ theme }) => theme.whiteBackground}; + border: 1px solid ${({ theme }) => theme.stroke}; + border-radius: 3px; + overflow-y: auto; + + ${landscapeStyle( + () => css` + margin-top: 64px; + top: 0; + right: 0; + left: auto; + transform: none; + ` + )} `; const StyledSettingsText = styled.div` @@ -45,29 +59,26 @@ const TABS = [ ]; interface ISettings { - setIsSettingsOpen: Dispatch>; + toggleIsSettingsOpen: () => void; } -const Settings: React.FC = ({ setIsSettingsOpen }) => { +const Settings: React.FC = ({ toggleIsSettingsOpen }) => { const [currentTab, setCurrentTab] = useState(0); const containerRef = useRef(null); - useFocusOutside(containerRef, () => setIsSettingsOpen(false)); + useFocusOutside(containerRef, () => toggleIsSettingsOpen()); return ( - <> - - - Settings - { - setCurrentTab(n); - }} - /> - {currentTab === 0 ? : } - - + + Settings + { + setCurrentTab(n); + }} + /> + {currentTab === 0 ? : } + ); }; diff --git a/web/src/layout/Header/navbar/Menu/index.tsx b/web/src/layout/Header/navbar/Menu/index.tsx index 2886c7c66..6d749e1a5 100644 --- a/web/src/layout/Header/navbar/Menu/index.tsx +++ b/web/src/layout/Header/navbar/Menu/index.tsx @@ -1,36 +1,72 @@ -import React, { useState } from "react"; -import styled from "styled-components"; -import { useToggle } from "react-use"; +import React from "react"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; import LightButton from "components/LightButton"; -import Help from "./Help"; import DarkModeIcon from "svgs/menu-icons/dark-mode.svg"; import HelpIcon from "svgs/menu-icons/help.svg"; import LightModeIcon from "svgs/menu-icons/light-mode.svg"; import NotificationsIcon from "svgs/menu-icons/notifications.svg"; import SettingsIcon from "svgs/menu-icons/settings.svg"; -import Settings from "./Settings"; import { useToggleTheme } from "hooks/useToggleThemeContext"; -const Container = styled.div``; +const Container = styled.div` + display: flex; + + flex-direction: column; + gap: 0px; + + ${landscapeStyle( + () => css` + flex-direction: row; + gap: 8px; + ` + )} +`; const ButtonContainer = styled.div` min-height: 32px; display: flex; align-items: center; + + button { + padding: 0px; + } + + .button-text { + display: block; + } + + .button-svg { + fill: ${({ theme }) => theme.secondaryPurple}; + } + + ${landscapeStyle( + () => css` + .button-svg { + fill: ${({ theme }) => theme.white}; + } + .button-text { + display: none; + } + ` + )} `; -const Menu: React.FC = () => { - const [theme, toggleTheme] = useToggleTheme(); - const [isHelpOpen, toggleIsHelpOpen] = useToggle(true); - const [isSettingsOpen, setIsSettingsOpen] = useState(false); +interface IMenu { + toggleIsSettingsOpen: () => void; + toggleIsHelpOpen: () => void; +} +const Menu: React.FC = ({ toggleIsHelpOpen, toggleIsSettingsOpen }) => { + const [theme, toggleTheme] = useToggleTheme(); const isLightTheme = theme === "light"; + const buttons = [ { text: "Notifications", Icon: NotificationsIcon }, { text: "Settings", Icon: SettingsIcon, - onClick: () => setIsSettingsOpen(true), + onClick: () => toggleIsSettingsOpen(), }, { text: "Help", @@ -49,12 +85,10 @@ const Menu: React.FC = () => { return ( {buttons.map(({ text, Icon, onClick }) => ( - + ))} - {isHelpOpen && } - {isSettingsOpen && } ); }; diff --git a/web/src/layout/Header/navbar/Product.tsx b/web/src/layout/Header/navbar/Product.tsx index 1fa47844c..0dd5c3cf5 100644 --- a/web/src/layout/Header/navbar/Product.tsx +++ b/web/src/layout/Header/navbar/Product.tsx @@ -24,24 +24,9 @@ const Container = styled.a` line-height: 19px; font-size: 14px; } - - svg { - max-width: 48px; - max-height: 48px; - fill: none; - visibility: visible; - display: inline-block; - } `; -const StyledIcon = styled.svg` - max-width: 48px; - max-height: 48px; - fill: none; - visibility: visible; - display: inline-block; - fill: ${({ theme }) => theme.secondaryPurple}; -`; +const StyledIcon = styled.svg``; const StyledImg = styled.img` max-width: 48px; diff --git a/web/src/layout/Header/navbar/index.tsx b/web/src/layout/Header/navbar/index.tsx index 684a36ebf..5a91c3338 100644 --- a/web/src/layout/Header/navbar/index.tsx +++ b/web/src/layout/Header/navbar/index.tsx @@ -1,32 +1,39 @@ import React from "react"; import styled from "styled-components"; -import { useLockBodyScroll, useToggle } from "react-use"; +import { useToggle } from "react-use"; +import { useAccount } from "wagmi"; +import { useLockOverlayScroll } from "hooks/useLockOverlayScroll"; +import { useOpenContext } from "../MobileHeader"; +import DappList from "./DappList"; +import Explore from "./Explore"; import ConnectWallet from "components/ConnectWallet"; import LightButton from "components/LightButton"; +import { Overlay } from "components/Overlay"; import KlerosSolutionsIcon from "svgs/menu-icons/kleros-solutions.svg"; -import { useOpenContext } from "../index"; -import DappList from "./DappList"; -import Explore from "./Explore"; import Menu from "./Menu"; import Debug from "./Debug"; +import Help from "./Menu/Help"; +import Settings from "./Menu/Settings"; +import { DisconnectWalletButton } from "./Menu/Settings/General"; +import { PopupContainer } from ".."; const Container = styled.div<{ isOpen: boolean }>` position: absolute; top: 64px; left: 0; right: 0; + max-height: calc(100vh - 64px); + overflow-y: auto; z-index: 1; background-color: ${({ theme }) => theme.whiteBackground}; border: 1px solid ${({ theme }) => theme.stroke}; box-shadow: 0px 2px 3px ${({ theme }) => theme.defaultShadow}; - transform-origin: top; transform: scaleY(${({ isOpen }) => (isOpen ? "1" : "0")}); visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")}; transition-property: transform, visibility; transition-duration: ${({ theme }) => theme.transitionSpeed}; transition-timing-function: ease; - padding: 24px; hr { @@ -34,30 +41,61 @@ const Container = styled.div<{ isOpen: boolean }>` } `; +const WalletContainer = styled.div` + display: flex; + gap: 16px; + justify-content: space-between; + flex-wrap: wrap; +`; + +const DisconnectWalletButtonContainer = styled.div` + display: flex; + align-items: center; +`; + const NavBar: React.FC = () => { - const [isSolutionsOpen, toggleSolution] = useToggle(false); + const { isConnected } = useAccount(); + const [isDappListOpen, toggleIsDappListOpen] = useToggle(false); + const [isHelpOpen, toggleIsHelpOpen] = useToggle(false); + const [isSettingsOpen, toggleIsSettingsOpen] = useToggle(false); const { isOpen } = useOpenContext(); - useLockBodyScroll(isOpen); + useLockOverlayScroll(isOpen); return ( - - { - toggleSolution(); - }} - Icon={KlerosSolutionsIcon} - /> - {isSolutionsOpen && } -
- -
- -
- -
- - + <> + + { + toggleIsDappListOpen(); + }} + Icon={KlerosSolutionsIcon} + /> +
+ +
+ + + {isConnected && ( + + + + )} + +
+ +
+ + + {(isDappListOpen || isHelpOpen || isSettingsOpen) && ( + + + {isDappListOpen && } + {isHelpOpen && } + {isSettingsOpen && } + + )} + ); }; diff --git a/web/src/layout/index.tsx b/web/src/layout/index.tsx index 4a50039d3..ecfb3153a 100644 --- a/web/src/layout/index.tsx +++ b/web/src/layout/index.tsx @@ -1,7 +1,10 @@ -import React from "react"; +import React, { useRef } from "react"; import styled from "styled-components"; +import "overlayscrollbars/styles/overlayscrollbars.css"; import { Outlet } from "react-router-dom"; import { ToastContainer } from "react-toastify"; +import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; +import { OverlayScrollContext } from "context/OverlayScrollContext"; import Header from "./Header"; import Footer from "./Footer"; @@ -10,18 +13,31 @@ const Container = styled.div` width: 100%; `; +const StyledOverlayScrollbarsComponent = styled(OverlayScrollbarsComponent)` + height: 100vh; + width: 100vw; +`; + const StyledToastContainer = styled(ToastContainer)` padding: 16px; padding-top: 70px; `; -const Layout: React.FC = () => ( - -
- - -