Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -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>
|
||||
);
|
||||
@ -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>
|
||||
);
|
||||
@ -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,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>;
|
||||
};
|
||||
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user