Enforce front project structure through ESLINT (#7863)

Fixes: https://github.com/twentyhq/twenty/issues/7329
This commit is contained in:
Charles Bochet
2024-10-20 20:20:19 +02:00
committed by GitHub
parent f801f3aa9f
commit eccf0bf8ba
260 changed files with 500 additions and 290 deletions

View File

@ -0,0 +1,31 @@
import { css, Global, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Outlet } from 'react-router-dom';
const StyledLayout = styled.div`
background: ${({ theme }) => theme.background.noisy};
display: flex;
flex-direction: column;
height: 100dvh;
position: relative;
scrollbar-width: 4px;
width: 100%;
`;
export const BlankLayout = () => {
const theme = useTheme();
return (
<>
<Global
styles={css`
body {
background: ${theme.background.tertiary};
}
`}
/>
<StyledLayout>
<Outlet />
</StyledLayout>
</>
);
};

View File

@ -0,0 +1,120 @@
import { AuthModal } from '@/auth/components/AuthModal';
import { CommandMenu } from '@/command-menu/components/CommandMenu';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
import { AppNavigationDrawer } from '@/navigation/components/AppNavigationDrawer';
import { MobileNavigationBar } from '@/navigation/components/MobileNavigationBar';
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize';
import { css, Global, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { AnimatePresence, LayoutGroup, motion } from 'framer-motion';
import { Outlet } from 'react-router-dom';
const StyledLayout = styled.div`
background: ${({ theme }) => theme.background.noisy};
display: flex;
flex-direction: column;
height: 100dvh;
position: relative;
scrollbar-color: ${({ theme }) => theme.border.color.medium};
scrollbar-width: 4px;
width: 100%;
*::-webkit-scrollbar {
height: 4px;
width: 4px;
}
*::-webkit-scrollbar-corner {
background-color: transparent;
}
*::-webkit-scrollbar-thumb {
background-color: transparent;
border-radius: ${({ theme }) => theme.border.radius.sm};
}
`;
const StyledPageContainer = styled(motion.div)`
display: flex;
flex: 1 1 auto;
flex-direction: row;
min-height: 0;
`;
const StyledAppNavigationDrawer = styled(AppNavigationDrawer)`
flex-shrink: 0;
`;
const StyledMainContainer = styled.div`
display: flex;
flex: 0 1 100%;
overflow: hidden;
`;
export const DefaultLayout = () => {
const isMobile = useIsMobile();
const isSettingsPage = useIsSettingsPage();
const theme = useTheme();
const windowsWidth = useScreenSize().width;
const showAuthModal = useShowAuthModal();
return (
<>
<Global
styles={css`
body {
background: ${theme.background.tertiary};
}
`}
/>
<StyledLayout>
<CommandMenu />
<KeyboardShortcutMenu />
<StyledPageContainer
animate={{
marginLeft:
isSettingsPage && !isMobile
? (windowsWidth -
(OBJECT_SETTINGS_WIDTH +
NAV_DRAWER_WIDTHS.menu.desktop.expanded +
64)) /
2
: 0,
}}
transition={{
duration: theme.animation.duration.normal,
}}
>
<StyledAppNavigationDrawer />
<StyledMainContainer>
{showAuthModal ? (
<>
<SignInBackgroundMockPage />
<AnimatePresence mode="wait">
<LayoutGroup>
<AuthModal>
<Outlet />
</AuthModal>
</LayoutGroup>
</AnimatePresence>
</>
) : (
<AppErrorBoundary>
<Outlet />
</AppErrorBoundary>
)}
</StyledMainContainer>
</StyledPageContainer>
{isMobile && <MobileNavigationBar />}
</StyledLayout>
</>
);
};

View File

@ -0,0 +1,19 @@
import { IconPlus } from 'twenty-ui';
import { IconButton } from '@/ui/input/button/components/IconButton';
type PageAddButtonProps = {
onClick: () => void;
};
export const PageAddButton = ({ onClick }: PageAddButtonProps) => (
<IconButton
Icon={IconPlus}
dataTestId="add-button"
size="medium"
variant="secondary"
accent="default"
onClick={onClick}
ariaLabel="Add"
/>
);

View File

@ -0,0 +1,50 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';
import { MOBILE_VIEWPORT } from 'twenty-ui';
import { RightDrawer } from '@/ui/layout/right-drawer/components/RightDrawer';
import { PagePanel } from './PagePanel';
type PageBodyProps = {
children: ReactNode;
};
const StyledMainContainer = styled.div`
background: ${({ theme }) => theme.background.noisy};
box-sizing: border-box;
display: flex;
flex: 1 1 auto;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
min-height: 0;
padding-bottom: ${({ theme }) => theme.spacing(3)};
padding-right: ${({ theme }) => theme.spacing(3)};
padding-left: 0;
width: 100%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
padding-left: ${({ theme }) => theme.spacing(3)};
padding-bottom: 0;
}
`;
type LeftContainerProps = {
isRightDrawerOpen?: boolean;
};
const StyledLeftContainer = styled.div<LeftContainerProps>`
display: flex;
flex-direction: column;
position: relative;
width: 100%;
`;
export const PageBody = ({ children }: PageBodyProps) => (
<StyledMainContainer>
<StyledLeftContainer>
<PagePanel>{children}</PagePanel>
</StyledLeftContainer>
<RightDrawer />
</StyledMainContainer>
);

View File

@ -0,0 +1,9 @@
import styled from '@emotion/styled';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
export { StyledContainer as PageContainer };

View File

@ -0,0 +1,22 @@
import { IconHeart } from 'twenty-ui';
import { IconButton } from '@/ui/input/button/components/IconButton';
type PageFavoriteButtonProps = {
isFavorite: boolean;
onClick: () => void;
};
export const PageFavoriteButton = ({
isFavorite,
onClick,
}: PageFavoriteButtonProps) => (
<IconButton
Icon={IconHeart}
size="medium"
variant="secondary"
data-testid="add-button"
accent={isFavorite ? 'danger' : 'default'}
onClick={onClick}
/>
);

View File

@ -0,0 +1,165 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { ReactNode } from 'react';
import { useRecoilValue } from 'recoil';
import {
IconChevronDown,
IconChevronUp,
IconComponent,
IconX,
MOBILE_VIEWPORT,
OverflowingTextWithTooltip,
} from 'twenty-ui';
import { IconButton } from '@/ui/input/button/components/IconButton';
import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerCollapseButton';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
export const PAGE_BAR_MIN_HEIGHT = 40;
const StyledTopBarContainer = styled.div<{ width?: number }>`
align-items: center;
background: ${({ theme }) => theme.background.noisy};
color: ${({ theme }) => theme.font.color.primary};
display: flex;
flex-direction: row;
font-size: ${({ theme }) => theme.font.size.lg};
justify-content: space-between;
min-height: ${PAGE_BAR_MIN_HEIGHT}px;
padding: ${({ theme }) => theme.spacing(2)};
padding-left: 0;
padding-right: ${({ theme }) => theme.spacing(3)};
width: ${({ width }) => width + 'px' || '100%'};
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%;
box-sizing: border-box;
padding: ${({ theme }) => theme.spacing(3)};
}
`;
const StyledLeftContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(1)};
width: 100%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
padding-left: ${({ theme }) => theme.spacing(1)};
}
`;
const StyledTitleContainer = styled.div`
display: flex;
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.medium};
margin-left: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
const StyledTopBarIconStyledTitleContainer = styled.div`
align-items: center;
display: flex;
flex: 1 0 auto;
gap: ${({ theme }) => theme.spacing(1)};
flex-direction: row;
width: 100%;
`;
const StyledPageActionContainer = styled.div`
display: inline-flex;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledTopBarButtonContainer = styled.div`
margin-left: ${({ theme }) => theme.spacing(1)};
margin-right: ${({ theme }) => theme.spacing(1)};
`;
type PageHeaderProps = {
title: ReactNode;
hasClosePageButton?: boolean;
onClosePage?: () => void;
hasPaginationButtons?: boolean;
hasPreviousRecord?: boolean;
hasNextRecord?: boolean;
navigateToPreviousRecord?: () => void;
navigateToNextRecord?: () => void;
Icon?: IconComponent;
children?: ReactNode;
width?: number;
};
export const PageHeader = ({
title,
hasClosePageButton,
onClosePage,
hasPaginationButtons,
hasPreviousRecord,
hasNextRecord,
navigateToPreviousRecord,
navigateToNextRecord,
Icon,
children,
width,
}: PageHeaderProps) => {
const isMobile = useIsMobile();
const theme = useTheme();
const isNavigationDrawerExpanded = useRecoilValue(
isNavigationDrawerExpandedState,
);
return (
<StyledTopBarContainer width={width}>
<StyledLeftContainer>
{!isMobile && !isNavigationDrawerExpanded && (
<StyledTopBarButtonContainer>
<NavigationDrawerCollapseButton direction="right" />
</StyledTopBarButtonContainer>
)}
{hasClosePageButton && (
<IconButton
Icon={IconX}
size="small"
variant="tertiary"
onClick={() => onClosePage?.()}
/>
)}
<StyledTopBarIconStyledTitleContainer>
{hasPaginationButtons && (
<>
<IconButton
Icon={IconChevronUp}
size="small"
variant="secondary"
disabled={!hasPreviousRecord}
onClick={() => navigateToPreviousRecord?.()}
/>
<IconButton
Icon={IconChevronDown}
size="small"
variant="secondary"
disabled={!hasNextRecord}
onClick={() => navigateToNextRecord?.()}
/>
</>
)}
{Icon && <Icon size={theme.icon.size.md} />}
<StyledTitleContainer data-testid="top-bar-title">
{typeof title === 'string' ? (
<OverflowingTextWithTooltip text={title} />
) : (
title
)}
</StyledTitleContainer>
</StyledTopBarIconStyledTitleContainer>
</StyledLeftContainer>
<StyledPageActionContainer>{children}</StyledPageActionContainer>
</StyledTopBarContainer>
);
};

View File

@ -0,0 +1,16 @@
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
type PageHotkeysEffectProps = {
onAddButtonClick?: () => void;
};
export const PageHotkeysEffect = ({
onAddButtonClick,
}: PageHotkeysEffectProps) => {
useScopedHotkeys('c', () => onAddButtonClick?.(), TableHotkeyScope.Table, [
onAddButtonClick,
]);
return <></>;
};

View File

@ -0,0 +1,21 @@
import styled from '@emotion/styled';
import React from 'react';
const StyledPanel = styled.div`
background: ${({ theme }) => theme.background.primary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
height: 100%;
overflow-x: auto;
overflow-y: hidden;
width: 100%;
`;
type PagePanelProps = {
children: React.ReactNode;
hasInformationBar?: boolean;
};
export const PagePanel = ({ children }: PagePanelProps) => (
<StyledPanel>{children}</StyledPanel>
);

View File

@ -0,0 +1,52 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';
import { MOBILE_VIEWPORT } from 'twenty-ui';
import { RightDrawer } from '@/ui/layout/right-drawer/components/RightDrawer';
import { PagePanel } from './PagePanel';
type RightDrawerContainerProps = {
children: ReactNode;
};
const StyledMainContainer = styled.div`
background: ${({ theme }) => theme.background.noisy};
box-sizing: border-box;
display: flex;
flex: 1 1 auto;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
min-height: 0;
padding-bottom: ${({ theme }) => theme.spacing(3)};
padding-right: ${({ theme }) => theme.spacing(3)};
padding-left: 0;
width: 100%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
padding-left: ${({ theme }) => theme.spacing(3)};
padding-bottom: 0;
}
`;
type LeftContainerProps = {
isRightDrawerOpen?: boolean;
};
const StyledLeftContainer = styled.div<LeftContainerProps>`
display: flex;
flex-direction: column;
position: relative;
width: 100%;
`;
export const RightDrawerContainer = ({
children,
}: RightDrawerContainerProps) => (
<StyledMainContainer>
<StyledLeftContainer>
<PagePanel>{children}</PagePanel>
</StyledLeftContainer>
<RightDrawer />
</StyledMainContainer>
);

View File

@ -0,0 +1,43 @@
import styled from '@emotion/styled';
import { ReactElement } from 'react';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
const StyledOuterContainer = styled.div`
display: flex;
gap: ${({ theme }) => (useIsMobile() ? theme.spacing(3) : '0')};
height: 100%;
width: 100%;
`;
const StyledInnerContainer = styled.div`
display: flex;
flex-direction: ${() => (useIsMobile() ? 'column' : 'row')};
width: 100%;
height: 100%;
`;
const StyledScrollWrapper = styled(ScrollWrapper)`
background-color: ${({ theme }) => theme.background.secondary};
border-radius: ${({ theme }) => theme.border.radius.md};
`;
export type ShowPageContainerProps = {
children: ReactElement[] | ReactElement;
};
export const ShowPageContainer = ({ children }: ShowPageContainerProps) => {
const isMobile = useIsMobile();
return isMobile ? (
<StyledOuterContainer>
<StyledScrollWrapper contextProviderName="showPageContainer">
<StyledInnerContainer>{children}</StyledInnerContainer>
</StyledScrollWrapper>
</StyledOuterContainer>
) : (
<StyledOuterContainer>
<StyledInnerContainer>{children}</StyledInnerContainer>
</StyledOuterContainer>
);
};

View File

@ -0,0 +1,53 @@
import styled from '@emotion/styled';
import { JSX, ReactNode } from 'react';
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
import {
Breadcrumb,
BreadcrumbProps,
} from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { PageBody } from './PageBody';
import { PageHeader } from './PageHeader';
type SubMenuTopBarContainerProps = {
children: JSX.Element | JSX.Element[];
title?: string;
actionButton?: ReactNode;
className?: string;
links: BreadcrumbProps['links'];
};
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
const StyledTitle = styled.h3`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.lg};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: 1.2;
margin: ${({ theme }) => theme.spacing(8, 8, 2)};
`;
export const SubMenuTopBarContainer = ({
children,
title,
actionButton,
className,
links,
}: SubMenuTopBarContainerProps) => {
return (
<StyledContainer className={className}>
<PageHeader title={<Breadcrumb links={links} />}>
{actionButton}
</PageHeader>
<PageBody>
<InformationBannerWrapper />
{title && <StyledTitle>{title}</StyledTitle>}
{children}
</PageBody>
</StyledContainer>
);
};