Uniformize folder structure (#693)

* Uniformize folder structure

* Fix icons

* Fix icons

* Fix tests

* Fix tests
This commit is contained in:
Charles Bochet
2023-07-16 14:29:28 -07:00
committed by GitHub
parent 900ec5572f
commit 6ced8434bd
462 changed files with 931 additions and 960 deletions

View File

@ -0,0 +1,21 @@
import styled from '@emotion/styled';
import NavItemsContainer from './NavItemsContainer';
import NavWorkspaceButton from './NavWorkspaceButton';
type OwnProps = {
children: JSX.Element;
};
const StyledContainer = styled.div`
width: 220px;
`;
export default function MainNavbar({ children }: OwnProps) {
return (
<StyledContainer>
<NavWorkspaceButton />
<NavItemsContainer>{children}</NavItemsContainer>
</StyledContainer>
);
}

View File

@ -0,0 +1,57 @@
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { IconChevronLeft } from '@/ui/icon/index';
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
import NavCollapseButton from './NavCollapseButton';
type OwnProps = {
title: string;
};
const IconAndButtonContainer = styled.button`
align-items: center;
background: inherit;
border: none;
color: ${({ theme }) => theme.font.color.secondary};
cursor: pointer;
display: flex;
flex-direction: row;
font-size: ${({ theme }) => theme.font.size.lg};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
const StyledContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
`;
export default function NavBackButton({ title }: OwnProps) {
const navigate = useNavigate();
const [, setIsNavbarSwitchingSize] = useRecoilState(
isNavbarSwitchingSizeState,
);
return (
<>
<StyledContainer>
<IconAndButtonContainer
onClick={() => {
setIsNavbarSwitchingSize(true);
navigate('/', { replace: true });
}}
>
<IconChevronLeft />
<span>{title}</span>
</IconAndButtonContainer>
<NavCollapseButton hideOnDesktop={true} />
</StyledContainer>
</>
);
}

View File

@ -0,0 +1,72 @@
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import {
IconLayoutSidebarLeftCollapse,
IconLayoutSidebarRightCollapse,
} from '@/ui/icon';
import { isNavbarOpenedState } from '@/ui/layout/states/isNavbarOpenedState';
import { MOBILE_VIEWPORT } from '@/ui/themes/themes';
const CollapseButton = styled.button<{ hideOnDesktop: boolean | undefined }>`
align-items: center;
background: inherit;
border: 0;
&:hover {
background: ${({ theme }) => theme.background.quaternary};
}
border-radius: ${({ theme }) => theme.border.radius.md};
color: ${({ theme }) => theme.font.color.light};
cursor: pointer;
display: flex;
height: 32px;
justify-content: center;
padding: 0;
user-select: none;
width: 32px;
${(props) =>
props.hideOnDesktop &&
`@media (min-width: ${MOBILE_VIEWPORT}px) {
display:none;
}
`}
`;
type CollapseButtonProps = {
hideIfOpen?: boolean;
hideIfClosed?: boolean;
hideOnDesktop?: boolean;
};
export default function NavCollapseButton({
hideIfOpen,
hideOnDesktop,
}: CollapseButtonProps) {
const [isNavOpen, setIsNavOpen] = useRecoilState(isNavbarOpenedState);
return (
<>
{isNavOpen && !hideIfOpen && (
<CollapseButton
onClick={() => setIsNavOpen(!isNavOpen)}
hideOnDesktop={hideOnDesktop}
>
<IconLayoutSidebarLeftCollapse size={16} />
</CollapseButton>
)}
{!isNavOpen && (
<CollapseButton
onClick={() => setIsNavOpen(!isNavOpen)}
hideOnDesktop={hideOnDesktop}
>
<IconLayoutSidebarRightCollapse size={16} />
</CollapseButton>
)}
</>
);
}

View File

@ -0,0 +1,109 @@
import { ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
import { MOBILE_VIEWPORT } from '@/ui/themes/themes';
type OwnProps = {
label: string;
to?: string;
onClick?: () => void;
active?: boolean;
icon: ReactNode;
danger?: boolean;
soon?: boolean;
};
type StyledItemProps = {
active?: boolean;
danger?: boolean;
soon?: boolean;
};
const StyledItem = styled.button<StyledItemProps>`
align-items: center;
background: ${(props) =>
props.active ? props.theme.background.transparent.light : 'inherit'};
border: none;
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${(props) => {
if (props.active) {
return props.theme.font.color.primary;
}
if (props.danger) {
return props.theme.color.red;
}
if (props.soon) {
return props.theme.font.color.light;
}
return props.theme.font.color.secondary;
}};
cursor: ${(props) => (props.soon ? 'default' : 'pointer')};
display: flex;
font-family: 'Inter';
font-size: ${({ theme }) => theme.font.size.md};
margin-bottom: calc(${({ theme }) => theme.spacing(1)} / 2);
padding-bottom: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(1)};
padding-top: ${({ theme }) => theme.spacing(1)};
pointer-events: ${(props) => (props.soon ? 'none' : 'auto')};
:hover {
background: ${({ theme }) => theme.background.transparent.light};
color: ${(props) =>
props.danger ? props.theme.color.red : props.theme.font.color.primary};
}
user-select: none;
@media (max-width: ${MOBILE_VIEWPORT}px) {
font-size: ${({ theme }) => theme.font.size.lg};
}
`;
const StyledItemLabel = styled.div`
display: flex;
margin-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledSoonPill = styled.div`
align-items: center;
background-color: ${({ theme }) => theme.background.transparent.light};
border-radius: 50px;
display: flex;
font-size: ${({ theme }) => theme.font.size.xs};
height: 16px;
justify-content: center;
margin-left: auto;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
`;
function NavItem({ label, icon, to, onClick, active, danger, soon }: OwnProps) {
const navigate = useNavigate();
const onItemClick = () => {
if (onClick) {
onClick();
return;
}
if (to) {
navigate(to);
return;
}
};
return (
<StyledItem
onClick={onItemClick}
active={active}
aria-selected={active}
danger={danger}
soon={soon}
>
{icon}
<StyledItemLabel>{label}</StyledItemLabel>
{soon && <StyledSoonPill>Soon</StyledSoonPill>}
</StyledItem>
);
}
export default NavItem;

View File

@ -0,0 +1,17 @@
import styled from '@emotion/styled';
type OwnProps = {
children: React.ReactNode;
};
const StyledNavItemsContainer = styled.div`
display: flex;
flex-direction: column;
margin-top: 40px;
`;
function NavItemsContainer({ children }: OwnProps) {
return <StyledNavItemsContainer>{children}</StyledNavItemsContainer>;
}
export default NavItemsContainer;

View File

@ -0,0 +1,22 @@
import styled from '@emotion/styled';
type OwnProps = {
label: string;
};
const StyledTitle = styled.div`
color: ${({ theme }) => theme.font.color.light};
display: flex;
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
padding-bottom: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(1)};
padding-top: ${({ theme }) => theme.spacing(8)};
text-transform: uppercase;
`;
function NavTitle({ label }: OwnProps) {
return <StyledTitle>{label}</StyledTitle>;
}
export default NavTitle;

View File

@ -0,0 +1,73 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI';
import NavCollapseButton from './NavCollapseButton';
const StyledContainer = styled.div`
align-items: center;
align-self: flex-start;
background: inherit;
border: 0;
display: flex;
height: 34px;
justify-content: space-between;
margin-left: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(1)};
padding-top: ${({ theme }) => theme.spacing(1)};
user-select: none;
`;
const LogoAndNameContainer = styled.div`
align-items: center;
display: flex;
`;
type StyledLogoProps = {
logo?: string | null;
};
const StyledLogo = styled.div<StyledLogoProps>`
background: url(${(props) => props.logo});
background-size: cover;
border-radius: ${({ theme }) => theme.border.radius.xs};
height: 16px;
width: 16px;
`;
const StyledName = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-family: 'Inter';
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.medium};
margin-left: ${({ theme }) => theme.spacing(2)};
`;
function NavWorkspaceButton() {
const currentUser = useRecoilValue(currentUserState);
const currentWorkspace = currentUser?.workspaceMember?.workspace;
const DEFAULT_LOGO =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII=';
return (
<StyledContainer>
<LogoAndNameContainer>
<StyledLogo
logo={
currentWorkspace?.logo
? getImageAbsoluteURIOrBase64(currentWorkspace.logo)
: DEFAULT_LOGO
}
></StyledLogo>
<StyledName>{currentWorkspace?.displayName ?? 'Twenty'}</StyledName>
</LogoAndNameContainer>
<NavCollapseButton />
</StyledContainer>
);
}
export default NavWorkspaceButton;

View File

@ -0,0 +1,56 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useIsSubNavbarDisplayed } from '@/ui/layout/hooks/useIsSubNavbarDisplayed';
import { isNavbarOpenedState } from '@/ui/layout/states/isNavbarOpenedState';
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
import { MOBILE_VIEWPORT } from '@/ui/themes/themes';
const StyledNavbarContainer = styled(motion.div)`
align-items: end;
display: flex;
flex-direction: column;
flex-shrink: 0;
overflow: hidden;
padding: ${({ theme }) => theme.spacing(2)};
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: ${(props) =>
useRecoilValue(isNavbarOpenedState)
? `calc(100% - ` + props.theme.spacing(4) + `)`
: '0'};
}
`;
type NavbarProps = {
children: React.ReactNode;
layout?: string;
};
export function NavbarAnimatedContainer({ children, layout }: NavbarProps) {
const isMenuOpened = useRecoilValue(isNavbarOpenedState);
const [, setIsNavbarSwitchingSize] = useRecoilState(
isNavbarSwitchingSizeState,
);
const isSubNavbarDisplayed = useIsSubNavbarDisplayed();
const theme = useTheme();
return (
<StyledNavbarContainer
onAnimationComplete={() => {
setIsNavbarSwitchingSize(false);
}}
animate={{
width: isMenuOpened ? (isSubNavbarDisplayed ? '520px' : '220px') : '0',
opacity: isMenuOpened ? 1 : 0,
}}
transition={{
duration: theme.animation.duration.visible,
}}
>
{children}
</StyledNavbarContainer>
);
}

View File

@ -0,0 +1,25 @@
import styled from '@emotion/styled';
import NavBackButton from './NavBackButton';
import NavItemsContainer from './NavItemsContainer';
type OwnProps = {
children: JSX.Element;
backButtonTitle: string;
};
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
padding-top: ${({ theme }) => theme.spacing(6)};
width: 220px;
`;
export default function SubNavbar({ children, backButtonTitle }: OwnProps) {
return (
<StyledContainer>
<NavBackButton title={backButtonTitle} />
<NavItemsContainer>{children}</NavItemsContainer>
</StyledContainer>
);
}