Migrate to a monorepo structure (#2909)

This commit is contained in:
Charles Bochet
2023-12-10 18:10:54 +01:00
committed by GitHub
parent a70a9281eb
commit 5bdca9de6c
2304 changed files with 37152 additions and 25869 deletions

View File

@ -0,0 +1,37 @@
import styled from '@emotion/styled';
import { CircularProgressBar } from '@/ui/feedback/progress-bar/components/CircularProgressBar';
import { MainButton } from '@/ui/input/button/components/MainButton';
import { Modal } from '@/ui/layout/modal/components/Modal';
const StyledFooter = styled(Modal.Footer)`
height: 60px;
justify-content: center;
padding: 0px;
padding-left: ${({ theme }) => theme.spacing(30)};
padding-right: ${({ theme }) => theme.spacing(30)};
`;
const StyledButton = styled(MainButton)`
width: 200px;
`;
type ContinueButtonProps = {
onContinue: (val: any) => void;
title: string;
isLoading?: boolean;
};
export const ContinueButton = ({
onContinue,
title,
isLoading,
}: ContinueButtonProps) => (
<StyledFooter>
<StyledButton
Icon={isLoading ? CircularProgressBar : undefined}
title={title}
onClick={!isLoading ? onContinue : undefined}
/>
</StyledFooter>
);

View File

@ -0,0 +1,35 @@
import React from 'react';
import styled from '@emotion/styled';
export type HeadingProps = {
title: string;
description?: string;
};
const StyledContainer = styled.div`
align-items: center;
display: flex;
flex-direction: column;
`;
const StyledTitle = styled.span`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.lg};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
text-align: center;
`;
const StyledDescription = styled.span`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin-top: ${({ theme }) => theme.spacing(3)};
text-align: center;
`;
export const Heading = ({ title, description }: HeadingProps) => (
<StyledContainer>
<StyledTitle>{title}</StyledTitle>
{description && <StyledDescription>{description}</StyledDescription>}
</StyledContainer>
);

View File

@ -0,0 +1,165 @@
import React, { useCallback, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
autoUpdate,
flip,
offset,
size,
useFloating,
} from '@floating-ui/react';
import debounce from 'lodash.debounce';
import { ReadonlyDeep } from 'type-fest';
import { SelectOption } from '@/spreadsheet-import/types';
import { AppTooltip } from '@/ui/display/tooltip/AppTooltip';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
const StyledFloatingDropdown = styled.div`
z-index: ${({ theme }) => theme.lastLayerZIndex};
`;
interface MatchColumnSelectProps {
onChange: (value: ReadonlyDeep<SelectOption> | null) => void;
value?: ReadonlyDeep<SelectOption>;
options: readonly ReadonlyDeep<SelectOption>[];
placeholder?: string;
name?: string;
}
export const MatchColumnSelect = ({
onChange,
value,
options: initialOptions,
placeholder,
}: MatchColumnSelectProps) => {
const theme = useTheme();
const dropdownContainerRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [searchFilter, setSearchFilter] = useState('');
const [options, setOptions] = useState(initialOptions);
const { refs, floatingStyles } = useFloating({
strategy: 'absolute',
middleware: [
offset(() => {
return parseInt(theme.spacing(2), 10);
}),
flip(),
size(),
],
whileElementsMounted: autoUpdate,
open: isOpen,
placement: 'bottom-start',
});
const handleSearchFilterChange = useCallback(
(text: string) => {
setOptions(
initialOptions.filter((option) => option.label.includes(text)),
);
},
[initialOptions],
);
const debouncedHandleSearchFilter = debounce(handleSearchFilterChange, 100, {
leading: true,
});
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.currentTarget.value;
setSearchFilter(value);
debouncedHandleSearchFilter(value);
};
const handleDropdownItemClick = () => {
setIsOpen(true);
};
const handleChange = (option: ReadonlyDeep<SelectOption>) => {
onChange(option);
setIsOpen(false);
};
useListenClickOutside({
refs: [dropdownContainerRef],
callback: () => {
setIsOpen(false);
},
});
useUpdateEffect(() => {
setOptions(initialOptions);
}, [initialOptions]);
return (
<>
<div ref={refs.setReference}>
<MenuItem
LeftIcon={value?.icon}
onClick={handleDropdownItemClick}
text={value?.label ?? placeholder ?? ''}
accent={value?.label ? 'default' : 'placeholder'}
/>
</div>
{isOpen &&
createPortal(
<StyledFloatingDropdown ref={refs.setFloating} style={floatingStyles}>
<DropdownMenu
data-select-disable
ref={dropdownContainerRef}
width={refs.domReference.current?.clientWidth}
>
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleFilterChange}
autoFocus
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{options?.map((option) => (
<>
<MenuItemSelect
key={option.label}
selected={value?.label === option.label}
onClick={() => handleChange(option)}
disabled={
option.disabled && value?.value !== option.value
}
LeftIcon={option?.icon}
text={option.label}
/>
{option.disabled &&
value?.value !== option.value &&
createPortal(
<AppTooltip
key={option.value}
anchorSelect={`#${option.value}`}
content="You are already importing this column."
place="right"
offset={-20}
/>,
document.body,
)}
</>
))}
{options?.length === 0 && <MenuItem text="No result" />}
</DropdownMenuItemsContainer>
</DropdownMenu>
</StyledFloatingDropdown>,
document.body,
)}
</>
);
};

View File

@ -0,0 +1,58 @@
import styled from '@emotion/styled';
import { useSpreadsheetImportInitialStep } from '@/spreadsheet-import/hooks/useSpreadsheetImportInitialStep';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { IconX } from '@/ui/display/icon/index';
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
import { IconButton } from '@/ui/input/button/components/IconButton';
import { useStepBar } from '@/ui/navigation/step-bar/hooks/useStepBar';
const StyledCloseButtonContainer = styled.div`
align-items: center;
aspect-ratio: 1;
display: flex;
height: 60px;
justify-content: center;
position: absolute;
right: 0;
top: 0;
`;
type ModalCloseButtonProps = {
onClose: () => void;
};
export const ModalCloseButton = ({ onClose }: ModalCloseButtonProps) => {
const { initialStepState } = useSpreadsheetImportInternal();
const { initialStep } = useSpreadsheetImportInitialStep(
initialStepState?.type,
);
const { activeStep } = useStepBar({
initialStep,
});
const { enqueueDialog } = useDialogManager();
const handleClose = () => {
if (activeStep === -1) {
onClose();
return;
}
enqueueDialog({
title: 'Exit import flow',
message: 'Are you sure? Your current information will not be saved.',
buttons: [
{ title: 'Cancel' },
{ title: 'Exit', onClick: onClose, accent: 'danger', role: 'confirm' },
],
});
};
return (
<StyledCloseButtonContainer>
<IconButton Icon={IconX} onClick={handleClose} />
</StyledCloseButtonContainer>
);
};

View File

@ -0,0 +1,50 @@
import styled from '@emotion/styled';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
import { ModalCloseButton } from './ModalCloseButton';
const StyledModal = styled(Modal)`
height: 61%;
min-height: 600px;
min-width: 800px;
position: relative;
width: 63%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
min-width: auto;
min-height: auto;
width: 100%;
height: 100%;
}
`;
const StyledRtlLtr = styled.div`
display: flex;
flex: 1;
flex-direction: column;
`;
type ModalWrapperProps = {
children: React.ReactNode;
isOpen: boolean;
onClose: () => void;
};
export const ModalWrapper = ({
children,
isOpen,
onClose,
}: ModalWrapperProps) => {
const { rtl } = useSpreadsheetImportInternal();
return (
<StyledModal isOpen={isOpen} size="large">
<StyledRtlLtr dir={rtl ? 'rtl' : 'ltr'}>
<ModalCloseButton onClose={onClose} />
{children}
</StyledRtlLtr>
</StyledModal>
);
};

View File

@ -0,0 +1,21 @@
import { createContext } from 'react';
import { SpreadsheetOptions } from '@/spreadsheet-import/types';
export const RsiContext = createContext({} as any);
type ProvidersProps<T extends string> = {
children: React.ReactNode;
values: SpreadsheetOptions<T>;
};
export const Providers = <T extends string>({
children,
values,
}: ProvidersProps<T>) => {
if (!values.fields) {
throw new Error('Fields must be provided to spreadsheet-import');
}
return <RsiContext.Provider value={values}>{children}</RsiContext.Provider>;
};

View File

@ -0,0 +1,147 @@
// @ts-expect-error // Todo: remove usage of react-data-grid
import DataGrid, { DataGridProps } from 'react-data-grid';
import styled from '@emotion/styled';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { rgba } from '@/ui/theme/constants/colors';
const StyledDataGrid = styled(DataGrid)`
--rdg-background-color: ${({ theme }) => theme.background.primary};
--rdg-border-color: ${({ theme }) => theme.border.color.medium};
--rdg-color: ${({ theme }) => theme.font.color.primary};
--rdg-error-cell-background-color: ${({ theme }) =>
rgba(theme.color.red, 0.4)};
--rdg-font-size: ${({ theme }) => theme.font.size.sm};
--rdg-frozen-cell-box-shadow: none;
--rdg-header-background-color: ${({ theme }) => theme.background.primary};
--rdg-info-cell-background-color: ${({ theme }) => theme.color.blue};
--rdg-row-hover-background-color: ${({ theme }) =>
theme.background.secondary};
--rdg-row-selected-background-color: ${({ theme }) =>
theme.background.primary};
--rdg-row-selected-hover-background-color: ${({ theme }) =>
theme.background.secondary};
--rdg-selection-color: ${({ theme }) => theme.color.blue};
--rdg-summary-border-color: ${({ theme }) => theme.border.color.medium};
--rdg-warning-cell-background-color: ${({ theme }) => theme.color.orange};
--row-selected-hover-background-color: ${({ theme }) =>
theme.background.secondary};
block-size: 100%;
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
width: 100%;
.rdg-header-row .rdg-cell {
box-shadow: none;
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
letter-spacing: wider;
text-transform: uppercase;
${({ headerRowHeight }) => {
if (headerRowHeight === 0) {
return `
border: none;
`;
}
}};
}
.rdg-cell {
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
border-inline-end: none;
border-right: none;
box-shadow: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rdg-row:last-child > .rdg-cell {
border-bottom: none;
}
.rdg-cell[aria-selected='true'] {
outline: none;
}
.rdg-cell-error {
background-color: ${({ theme }) => rgba(theme.color.red, 0.08)};
}
.rdg-cell-warning {
background-color: ${({ theme }) => rgba(theme.color.orange, 0.08)};
}
.rdg-cell-info {
background-color: ${({ theme }) => rgba(theme.color.blue, 0.08)};
}
.rdg-static {
cursor: pointer;
}
.rdg-static .rdg-header-row {
display: none;
}
.rdg-static .rdg-cell {
--rdg-selection-color: none;
}
.rdg-example .rdg-cell {
--rdg-selection-color: none;
border-bottom: none;
}
.rdg-radio {
align-items: center;
display: flex;
}
.rdg-checkbox {
align-items: center;
display: flex;
line-height: none;
}
` as typeof DataGrid;
type TableProps<Data> = DataGridProps<Data> & {
rowHeight?: number;
hiddenHeader?: boolean;
};
export const Table = <Data,>({
className,
columns,
components,
headerRowHeight,
rowKeyGetter,
rows,
onRowClick,
onRowsChange,
onSelectedRowsChange,
selectedRows,
}: TableProps<Data>) => {
const { rtl } = useSpreadsheetImportInternal();
return (
<StyledDataGrid
direction={rtl ? 'rtl' : 'ltr'}
rowHeight={52}
{...{
className,
columns,
components,
headerRowHeight,
rowKeyGetter,
rows,
onRowClick,
onRowsChange,
onSelectedRowsChange,
selectedRows,
}}
/>
);
};