Reorganize frontend and install Craco to alias modules (#190)
This commit is contained in:
40
front/src/modules/ui/layout/AppLayout.tsx
Normal file
40
front/src/modules/ui/layout/AppLayout.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { User } from '@/users/interfaces/user.interface';
|
||||
|
||||
import { Navbar } from './navbar/Navbar';
|
||||
import { lightTheme } from './styles/themes';
|
||||
|
||||
const StyledLayout = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: ${(props) => props.theme.noisyBackground};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const NAVBAR_WIDTH = '236px';
|
||||
|
||||
const MainContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: calc(100% - ${NAVBAR_WIDTH});
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
children: JSX.Element;
|
||||
user?: User;
|
||||
};
|
||||
|
||||
export function AppLayout({ children, user }: OwnProps) {
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<StyledLayout>
|
||||
<Navbar user={user} workspace={user?.workspaceMember?.workspace} />
|
||||
<MainContainer>{children}</MainContainer>
|
||||
</StyledLayout>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
14
front/src/modules/ui/layout/Panel.tsx
Normal file
14
front/src/modules/ui/layout/Panel.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledPanel = styled.div`
|
||||
display: flex;
|
||||
background: ${(props) => props.theme.primaryBackground};
|
||||
border-radius: 8px;
|
||||
border: 1px solid ${(props) => props.theme.primaryBorder};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export function Panel({ children }: { children: React.ReactNode }) {
|
||||
return <StyledPanel>{children}</StyledPanel>;
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { Panel } from '../Panel';
|
||||
import { RightDrawer } from '../right-drawer/components/RightDrawer';
|
||||
import { isRightDrawerOpenState } from '../right-drawer/states/isRightDrawerOpenState';
|
||||
import { TopBar } from '../top-bar/TopBar';
|
||||
|
||||
type OwnProps = {
|
||||
children: JSX.Element;
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
onAddButtonClick?: () => void;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const TOPBAR_HEIGHT = '48px';
|
||||
|
||||
const MainContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: calc(100% - ${(props) => props.theme.spacing(3)});
|
||||
height: calc(100% - ${TOPBAR_HEIGHT} - ${(props) => props.theme.spacing(3)});
|
||||
background: ${(props) => props.theme.noisyBackground};
|
||||
padding-right: ${(props) => props.theme.spacing(3)};
|
||||
padding-bottom: ${(props) => props.theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const RIGHT_DRAWER_WIDTH = '300px';
|
||||
|
||||
type LeftContainerProps = {
|
||||
isRightDrawerOpen?: boolean;
|
||||
};
|
||||
|
||||
const LeftContainer = styled.div<LeftContainerProps>`
|
||||
display: flex;
|
||||
width: calc(
|
||||
100% - ${(props) => (props.isRightDrawerOpen ? RIGHT_DRAWER_WIDTH : '0px')}
|
||||
);
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export function WithTopBarContainer({
|
||||
children,
|
||||
title,
|
||||
icon,
|
||||
onAddButtonClick,
|
||||
}: OwnProps) {
|
||||
const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<TopBar title={title} icon={icon} onAddButtonClick={onAddButtonClick} />
|
||||
<MainContainer>
|
||||
<LeftContainer isRightDrawerOpen={isRightDrawerOpen}>
|
||||
<Panel>{children}</Panel>
|
||||
</LeftContainer>
|
||||
<RightDrawer />
|
||||
</MainContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
60
front/src/modules/ui/layout/navbar/NavItem.tsx
Normal file
60
front/src/modules/ui/layout/navbar/NavItem.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
type OwnProps = {
|
||||
label: string;
|
||||
to: string;
|
||||
active?: boolean;
|
||||
icon: ReactNode;
|
||||
};
|
||||
|
||||
type StyledItemProps = {
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
const StyledItem = styled.button<StyledItemProps>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
font-size: ${(props) => props.theme.fontSizeMedium};
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background: ${(props) => (props.active ? 'rgba(0, 0, 0, 0.04)' : 'inherit')};
|
||||
padding-top: ${(props) => props.theme.spacing(1)};
|
||||
padding-bottom: ${(props) => props.theme.spacing(1)};
|
||||
padding-left: ${(props) => props.theme.spacing(1)};
|
||||
font-family: 'Inter';
|
||||
color: ${(props) =>
|
||||
props.active ? props.theme.text100 : props.theme.text60};
|
||||
border-radius: 4px;
|
||||
:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: ${(props) => props.theme.text100};
|
||||
}
|
||||
margin-bottom: calc(${(props) => props.theme.spacing(1)} / 2);
|
||||
`;
|
||||
|
||||
const StyledItemLabel = styled.div`
|
||||
display: flex;
|
||||
margin-left: ${(props) => props.theme.spacing(2)};
|
||||
`;
|
||||
|
||||
function NavItem({ label, icon, to, active }: OwnProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<StyledItem
|
||||
onClick={() => {
|
||||
navigate(to);
|
||||
}}
|
||||
active={active}
|
||||
aria-selected={active}
|
||||
>
|
||||
{icon}
|
||||
<StyledItemLabel>{label}</StyledItemLabel>
|
||||
</StyledItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavItem;
|
||||
22
front/src/modules/ui/layout/navbar/NavTitle.tsx
Normal file
22
front/src/modules/ui/layout/navbar/NavTitle.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
type OwnProps = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
display: flex;
|
||||
text-transform: uppercase;
|
||||
color: ${(props) => props.theme.text30};
|
||||
font-size: ${(props) => props.theme.fontSizeExtraSmall};
|
||||
font-weight: 600;
|
||||
padding-top: ${(props) => props.theme.spacing(1)};
|
||||
padding-bottom: ${(props) => props.theme.spacing(2)};
|
||||
padding-left: ${(props) => props.theme.spacing(1)};
|
||||
`;
|
||||
|
||||
function NavTitle({ label }: OwnProps) {
|
||||
return <StyledTitle>{label}</StyledTitle>;
|
||||
}
|
||||
|
||||
export default NavTitle;
|
||||
64
front/src/modules/ui/layout/navbar/Navbar.tsx
Normal file
64
front/src/modules/ui/layout/navbar/Navbar.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { TbBuilding, TbUser } from 'react-icons/tb';
|
||||
import { useMatch, useResolvedPath } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { User } from '@/users/interfaces/user.interface';
|
||||
import { Workspace } from '@/workspaces/interfaces/workspace.interface';
|
||||
|
||||
import NavItem from './NavItem';
|
||||
import NavTitle from './NavTitle';
|
||||
import WorkspaceContainer from './WorkspaceContainer';
|
||||
|
||||
const NavbarContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 220px;
|
||||
padding: ${(props) => props.theme.spacing(2)};
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const NavItemsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 40px;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
user?: User;
|
||||
workspace?: Workspace;
|
||||
};
|
||||
|
||||
export function Navbar({ workspace }: OwnProps) {
|
||||
return (
|
||||
<>
|
||||
<NavbarContainer>
|
||||
{workspace && <WorkspaceContainer workspace={workspace} />}
|
||||
<NavItemsContainer>
|
||||
<NavTitle label="Workspace" />
|
||||
<NavItem
|
||||
label="People"
|
||||
to="/people"
|
||||
icon={<TbUser size={16} />}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/people').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="Companies"
|
||||
to="/companies"
|
||||
icon={<TbBuilding size={16} />}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/companies').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</NavItemsContainer>
|
||||
</NavbarContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
50
front/src/modules/ui/layout/navbar/WorkspaceContainer.tsx
Normal file
50
front/src/modules/ui/layout/navbar/WorkspaceContainer.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Workspace } from '@/workspaces/interfaces/workspace.interface';
|
||||
|
||||
type OwnProps = {
|
||||
workspace: Workspace;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.button`
|
||||
display: inline-flex;
|
||||
height: 34px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border: 0;
|
||||
background: inherit;
|
||||
padding: ${(props) => props.theme.spacing(2)};
|
||||
margin-left: ${(props) => props.theme.spacing(1)};
|
||||
align-self: flex-start;
|
||||
`;
|
||||
|
||||
type StyledLogoProps = {
|
||||
logo?: string | null;
|
||||
};
|
||||
|
||||
const StyledLogo = styled.div<StyledLogoProps>`
|
||||
background: url(${(props) => props.logo});
|
||||
background-size: cover;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 2px;
|
||||
`;
|
||||
|
||||
const StyledName = styled.div`
|
||||
margin-left: ${(props) => props.theme.spacing(1)};
|
||||
font-family: 'Inter';
|
||||
font-weight: 500;
|
||||
font-size: ${(props) => props.theme.fontSizeLarge};
|
||||
`;
|
||||
|
||||
function WorkspaceContainer({ workspace }: OwnProps) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledLogo logo={workspace.logo}></StyledLogo>
|
||||
<StyledName>{workspace?.displayName}</StyledName>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default WorkspaceContainer;
|
||||
@ -0,0 +1,34 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { isDefined } from '@/utils/type-guards/isDefined';
|
||||
|
||||
import { Panel } from '../../Panel';
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
|
||||
import { RightDrawerRouter } from './RightDrawerRouter';
|
||||
|
||||
const StyledRightDrawer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 300px;
|
||||
margin-left: ${(props) => props.theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export function RightDrawer() {
|
||||
const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
||||
|
||||
if (!isRightDrawerOpen || !isDefined(rightDrawerPage)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRightDrawer>
|
||||
<Panel>
|
||||
<RightDrawerRouter />
|
||||
</Panel>
|
||||
</StyledRightDrawer>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const RightDrawerBody = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
padding: 8px;
|
||||
`;
|
||||
@ -0,0 +1,8 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const RightDrawerPage = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
@ -0,0 +1,16 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { RightDrawerComments } from '@/comments/components/comments/RightDrawerComments';
|
||||
import { isDefined } from '@/utils/type-guards/isDefined';
|
||||
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
|
||||
export function RightDrawerRouter() {
|
||||
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
||||
|
||||
if (!isDefined(rightDrawerPage)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return rightDrawerPage === 'comments' ? <RightDrawerComments /> : <></>;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { RightDrawerTopBarCloseButton } from './RightDrawerTopBarCloseButton';
|
||||
|
||||
const StyledRightDrawerTopBar = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
font-size: 13px;
|
||||
color: ${(props) => props.theme.text60};
|
||||
border-bottom: 1px solid ${(props) => props.theme.lightBorder};
|
||||
`;
|
||||
|
||||
const StyledTopBarTitle = styled.div`
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
export function RightDrawerTopBar({
|
||||
title,
|
||||
}: {
|
||||
title: string | null | undefined;
|
||||
}) {
|
||||
return (
|
||||
<StyledRightDrawerTopBar>
|
||||
<StyledTopBarTitle>{title}</StyledTopBarTitle>
|
||||
<RightDrawerTopBarCloseButton />
|
||||
</StyledRightDrawerTopBar>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
|
||||
const StyledButton = styled.button`
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
border: 1px solid ${(props) => props.theme.lightBorder};
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
transition: ${(props) => props.theme.clickableElementBackgroundTransition};
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.clickableElementBackgroundHover};
|
||||
}
|
||||
`;
|
||||
|
||||
export function RightDrawerTopBarCloseButton() {
|
||||
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||
|
||||
function handleButtonClick() {
|
||||
setIsRightDrawerOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledButton onClick={handleButtonClick}>
|
||||
<FaTimes />
|
||||
</StyledButton>
|
||||
);
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
import { RightDrawerPage } from '../types/RightDrawerPage';
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { RightDrawerPage } from '../types/RightDrawerPage';
|
||||
|
||||
export const rightDrawerPageState = atom<RightDrawerPage | null>({
|
||||
|
||||
BIN
front/src/modules/ui/layout/styles/dark-noise.jpg
Normal file
BIN
front/src/modules/ui/layout/styles/dark-noise.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
front/src/modules/ui/layout/styles/light-noise.jpg
Normal file
BIN
front/src/modules/ui/layout/styles/light-noise.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
130
front/src/modules/ui/layout/styles/themes.ts
Normal file
130
front/src/modules/ui/layout/styles/themes.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import DarkNoise from './dark-noise.jpg';
|
||||
import LightNoise from './light-noise.jpg';
|
||||
|
||||
const commonTheme = {
|
||||
fontSizeExtraSmall: '0.85rem',
|
||||
fontSizeSmall: '0.92rem',
|
||||
fontSizeMedium: '1rem',
|
||||
fontSizeLarge: '1.08rem',
|
||||
|
||||
iconSizeSmall: '0.92rem',
|
||||
iconSizeMedium: '1.08rem',
|
||||
iconSizeLarge: '1.23rem',
|
||||
|
||||
fontWeightBold: 500,
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
|
||||
spacing: (multiplicator: number) => `${multiplicator * 4}px`,
|
||||
|
||||
table: {
|
||||
horizontalCellMargin: '8px',
|
||||
},
|
||||
|
||||
borderRadius: '4px',
|
||||
};
|
||||
|
||||
const lightThemeSpecific = {
|
||||
noisyBackground: `url(${LightNoise.toString()});`,
|
||||
primaryBackground: '#fff',
|
||||
secondaryBackground: '#fcfcfc',
|
||||
tertiaryBackground: '#f5f5f5',
|
||||
quadraryBackground: '#ebebeb',
|
||||
|
||||
pinkBackground: '#ffe5f4',
|
||||
greenBackground: '#e6fff2',
|
||||
purpleBackground: '#e0e0ff',
|
||||
yellowBackground: '#fff2e7',
|
||||
|
||||
secondaryBackgroundTransparent: 'rgba(252, 252, 252, 0.8)',
|
||||
|
||||
primaryBorder: 'rgba(0, 0, 0, 0.08)',
|
||||
lightBorder: '#f5f5f5',
|
||||
|
||||
clickableElementBackgroundHover: 'rgba(0, 0, 0, 0.04)',
|
||||
clickableElementBackgroundTransition: 'background 0.1s ease',
|
||||
|
||||
text100: '#000',
|
||||
text80: '#333333',
|
||||
text60: '#666',
|
||||
text40: '#999999',
|
||||
text30: '#b3b3b3',
|
||||
text20: '#cccccc',
|
||||
text0: '#fff',
|
||||
|
||||
blue: '#1961ed',
|
||||
pink: '#cc0078',
|
||||
green: '#1e7e50',
|
||||
purple: '#1111b7',
|
||||
yellow: '#cc660a',
|
||||
red: '#ff2e3f',
|
||||
|
||||
blueHighTransparency: 'rgba(25, 97, 237, 0.03)',
|
||||
blueLowTransparency: 'rgba(25, 97, 237, 0.32)',
|
||||
};
|
||||
|
||||
const darkThemeSpecific: typeof lightThemeSpecific = {
|
||||
noisyBackground: `url(${DarkNoise.toString()});`,
|
||||
primaryBackground: '#141414',
|
||||
secondaryBackground: '#171717',
|
||||
tertiaryBackground: '#333333',
|
||||
quadraryBackground: '#444444',
|
||||
|
||||
pinkBackground: '#cc0078',
|
||||
greenBackground: '#1e7e50',
|
||||
purpleBackground: '#1111b7',
|
||||
yellowBackground: '#cc660a',
|
||||
|
||||
secondaryBackgroundTransparent: 'rgba(23, 23, 23, 0.8)',
|
||||
|
||||
clickableElementBackgroundHover: 'rgba(0, 0, 0, 0.04)',
|
||||
clickableElementBackgroundTransition: 'background 0.1s ease',
|
||||
|
||||
primaryBorder: 'rgba(255, 255, 255, 0.08)',
|
||||
lightBorder: '#222222',
|
||||
|
||||
text100: '#ffffff',
|
||||
text80: '#cccccc',
|
||||
text60: '#999',
|
||||
text40: '#666',
|
||||
text30: '#4c4c4c',
|
||||
text20: '#333333',
|
||||
text0: '#000',
|
||||
|
||||
blue: '#6895ec',
|
||||
pink: '#ffe5f4',
|
||||
green: '#e6fff2',
|
||||
purple: '#e0e0ff',
|
||||
yellow: '#fff2e7',
|
||||
red: '#ff2e3f',
|
||||
|
||||
blueHighTransparency: 'rgba(104, 149, 236, 0.03)',
|
||||
blueLowTransparency: 'rgba(104, 149, 236, 0.32)',
|
||||
};
|
||||
|
||||
export const overlayBackground = (props: any) =>
|
||||
css`
|
||||
background: ${props.theme.secondaryBackgroundTransparent};
|
||||
backdrop-filter: blur(8px);
|
||||
box-shadow: 0px 3px 12px rgba(0, 0, 0, 0.09);
|
||||
`;
|
||||
|
||||
export const textInputStyle = (props: any) =>
|
||||
css`
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
|
||||
&::placeholder,
|
||||
&::-webkit-input-placeholder {
|
||||
font-family: ${props.theme.fontFamily};
|
||||
color: ${props.theme.text30};
|
||||
font-weight: ${props.theme.fontWeightBold};
|
||||
}
|
||||
`;
|
||||
|
||||
export const lightTheme = { ...commonTheme, ...lightThemeSpecific };
|
||||
export const darkTheme = { ...commonTheme, ...darkThemeSpecific };
|
||||
|
||||
export type ThemeType = typeof lightTheme;
|
||||
63
front/src/modules/ui/layout/top-bar/TopBar.tsx
Normal file
63
front/src/modules/ui/layout/top-bar/TopBar.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { TbPlus } from 'react-icons/tb';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const TopBarContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 38px;
|
||||
align-items: center;
|
||||
background: ${(props) => props.theme.noisyBackground};
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
color: ${(props) => props.theme.text80};
|
||||
`;
|
||||
|
||||
const TitleContainer = styled.div`
|
||||
font-family: 'Inter';
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const AddButtonContainer = styled.div`
|
||||
display: flex;
|
||||
justify-self: flex-end;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid ${(props) => props.theme.primaryBorder};
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
color: ${(props) => props.theme.text80};
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
margin-right: ${(props) => props.theme.spacing(1)};
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
onAddButtonClick?: () => void;
|
||||
};
|
||||
|
||||
export function TopBar({ title, icon, onAddButtonClick }: OwnProps) {
|
||||
return (
|
||||
<>
|
||||
<TopBarContainer>
|
||||
{icon}
|
||||
<TitleContainer data-testid="top-bar-title">{title}</TitleContainer>
|
||||
{onAddButtonClick && (
|
||||
<AddButtonContainer
|
||||
data-testid="add-button"
|
||||
onClick={onAddButtonClick}
|
||||
>
|
||||
<TbPlus size={16} />
|
||||
</AddButtonContainer>
|
||||
)}
|
||||
</TopBarContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user