Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"svgs": "./src/assets/svgs"
},
"volta": {
"node": "20.18.3"
"node": "20.18.3",
"yarn": "4.9.2"
},
"scripts": {
"start": "yarn start-devnet",
Expand Down
2 changes: 2 additions & 0 deletions web/src/layout/Header/navbar/Explore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { landscapeStyle } from "styles/landscapeStyle";
import { Link, useLocation } from "react-router-dom";

import { useOpenContext } from "../MobileHeader";
import Policies from "./Policies";

const Container = styled.div`
display: flex;
Expand Down Expand Up @@ -76,6 +77,7 @@ const Explore: React.FC<IExplore> = ({ isMobileNavbar }) => {
{text}
</StyledLink>
))}
<Policies isMobileNavbar={isMobileNavbar} />
</Container>
);
};
Expand Down
270 changes: 270 additions & 0 deletions web/src/layout/Header/navbar/Policies.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import React, { useState, useRef, useEffect, useId } from "react";
import styled, { css } from "styled-components";
import { landscapeStyle } from "styles/landscapeStyle";

import { useOpenContext } from "../MobileHeader";

const Container = styled.div`
position: relative;
display: flex;
flex-direction: column;

${landscapeStyle(
() => css`
flex-direction: row;
`
)};
`;

const Title = styled.h1`
display: block;
margin-bottom: 8px;

${landscapeStyle(
() => css`
display: none;
`
)};
`;

const PoliciesButton = styled.button<{ isActive: boolean; isMobileNavbar?: boolean }>`
display: flex;
align-items: center;
gap: 8px;
background: none;
border: none;
cursor: pointer;
font-size: 16px;
color: ${({ isActive, theme }) => (isActive ? theme.primaryText : `${theme.primaryText}BA`)};
font-weight: ${({ isActive, isMobileNavbar }) => (isMobileNavbar && isActive ? "600" : "normal")};
padding: 8px 8px 8px 0;
border-radius: 7px;

&:hover {
color: ${({ theme, isMobileNavbar }) => (isMobileNavbar ? theme.primaryText : theme.white)} !important;
}

${landscapeStyle(
() => css`
color: ${({ theme }) => theme.white};
padding: 16px 8px;
`
)};
`;

const ChevronIcon = styled.span<{ isOpen: boolean }>`
display: inline-block;
width: 6px;
height: 6px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(45deg);
transition: transform 0.2s ease;
margin-top: -4px;
`;

const DropdownContainer = styled.div<{ isOpen: boolean }>`
position: absolute;
top: 100%;
left: 0;
background: ${({ theme }) => theme.whiteBackground};
border: 1px solid ${({ theme }) => theme.stroke};
border-radius: 3px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15);
width: 247px;
z-index: 1000;
opacity: ${({ isOpen }) => (isOpen ? 1 : 0)};
visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")};
transform: translateY(${({ isOpen }) => (isOpen ? "0" : "-10px")});
transition: all 0.2s ease;

${landscapeStyle(
() => css`
top: calc(100% + 8px);
left: 50%;
transform: translateX(-50%) translateY(0);
`
)};
`;

const DropdownItem = styled.a`
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
text-decoration: none;
color: ${({ theme }) => theme.primaryText};
background: transparent;
border-left: 3px solid transparent;
height: 45px;
transition: all 0.2s ease;

&:hover {
background: ${({ theme }) => theme.mediumBlue};
border-left-color: ${({ theme }) => theme.primaryBlue};
}

&:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}

&:last-child {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
`;

const ItemIcon = styled.div`
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
color: ${({ theme }) => theme.primaryBlue};
`;

const CheckIcon = styled.div`
width: 18px;
height: 18px;
border: 2px solid currentColor;
border-radius: 50%;
position: relative;

&::after {
content: "";
position: absolute;
top: 2px;
left: 5px;
width: 5px;
height: 8px;
border: solid currentColor;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
`;

const DocumentIcon = styled.div`
width: 18px;
height: 22px;
border: 2px solid currentColor;
border-radius: 2px;
position: relative;

&::after {
content: "";
position: absolute;
top: 3px;
left: 3px;
right: 3px;
height: 2px;
background: currentColor;
border-radius: 1px;
}

&::before {
content: "";
position: absolute;
top: 8px;
left: 3px;
right: 3px;
height: 1px;
background: currentColor;
border-radius: 0.5px;
}
`;

interface IPolicies {
/** Whether the component is being used in the mobile navbar */
isMobileNavbar?: boolean;
}

const Policies: React.FC<IPolicies> = ({ isMobileNavbar }) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const { toggleIsOpen } = useOpenContext();
const menuId = useId();

// Policy documents configuration

const policies = [
{
id: "general-policy",
name: "General Policy",
url: "https://cdn.kleros.link/ipfs/QmU2GuwcSs8tFp8gWf5hcXVbcJKRqwoecNnERz9XjKr18d",
icon: CheckIcon,
},
{
id: "good-practices",
name: "Good Practices",
url: "https://cdn.kleros.link/ipfs/QmcCyR68RmwWfdVKinY8Fmiy73a6xEzGqYDcvAh9EFUnLF",
icon: DocumentIcon,
},
];

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);

const handleItemClick = () => {
setIsOpen(false);
if (isMobileNavbar) {
toggleIsOpen();
}
// Let the anchor's default behavior open the link in a new tab (with rel attr).
};

const handleToggleDropdown = () => {
setIsOpen(!isOpen);
};

return (
<Container ref={dropdownRef}>
<Title>Policies</Title>
<PoliciesButton
type="button"
onClick={handleToggleDropdown}
isActive={isOpen}
isMobileNavbar={isMobileNavbar}
aria-haspopup="menu"
aria-expanded={isOpen}
aria-controls={menuId}
>
Policies
<ChevronIcon isOpen={isOpen} />
</PoliciesButton>

<DropdownContainer id={menuId} role="menu" isOpen={isOpen}>
{policies.map((policy) => {
const IconComponent = policy.icon;
return (
<DropdownItem
key={policy.id}
href={policy.url}
target="_blank"
rel="noopener noreferrer"
onClick={handleItemClick}
role="menuitem"
>
<ItemIcon>
<IconComponent />
</ItemIcon>
{policy.name}
</DropdownItem>
);
})}
</DropdownContainer>
</Container>
);
};

export default Policies;