Refactor/new menu item (#1448)

* wip

* finished

* Added disabled

* Fixed disabled

* Finished cleaning

* Minor fixes from merge

* Added docs

* Added PascalCase

* Fix from review

* Fixes from merge

* Fix lint

* Fixed storybook tests
This commit is contained in:
Lucas Bordeau
2023-09-06 16:41:26 +02:00
committed by GitHub
parent 5c7660f588
commit 28ca9a9e49
96 changed files with 816 additions and 918 deletions

View File

@ -14,44 +14,15 @@ import { ReadonlyDeep } from 'type-fest';
import type { SelectOption } from '@/spreadsheet-import/types';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { IconChevronDown, TablerIconsProps } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { MenuItemSelect } from '@/ui/menu-item/components/MenuItemSelect';
import { AppTooltip } from '@/ui/tooltip/AppTooltip';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
const StyledDropdownItem = styled.div`
align-items: center;
background-color: ${({ theme }) => theme.background.tertiary};
border-radius: ${({ theme }) => theme.border.radius.sm};
box-sizing: border-box;
display: flex;
flex-direction: row;
height: 32px;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
width: 100%;
&:hover {
background-color: ${({ theme }) => theme.background.quaternary};
}
`;
const StyledDropdownLabel = styled.span<{ isPlaceholder: boolean }>`
color: ${({ theme, isPlaceholder }) =>
isPlaceholder ? theme.font.color.tertiary : theme.font.color.primary};
display: flex;
flex: 1;
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.regular};
padding-left: ${({ theme }) => theme.spacing(1)};
padding-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledFloatingDropdown = styled.div`
z-index: ${({ theme }) => theme.lastLayerZIndex};
`;
@ -69,11 +40,9 @@ export const MatchColumnSelect = ({
value,
options: initialOptions,
placeholder,
name,
}: Props) => {
const theme = useTheme();
const dropdownItemRef = useRef<HTMLDivElement>(null);
const dropdownContainerRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
@ -123,16 +92,6 @@ export const MatchColumnSelect = ({
setIsOpen(false);
}
function renderIcon(icon: ReadonlyDeep<React.ReactNode>) {
if (icon && React.isValidElement(icon)) {
return React.cloneElement<TablerIconsProps>(icon as any, {
size: 16,
color: theme.font.color.primary,
});
}
return null;
}
useListenClickOutside({
refs: [dropdownContainerRef],
callback: () => {
@ -146,28 +105,20 @@ export const MatchColumnSelect = ({
return (
<>
<StyledDropdownItem
id={name}
ref={(node) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
dropdownItemRef.current = node;
refs.setReference(node);
}}
onClick={handleDropdownItemClick}
>
{renderIcon(value?.icon)}
<StyledDropdownLabel isPlaceholder={!value?.label}>
{value?.label ?? placeholder}
</StyledDropdownLabel>
<IconChevronDown size={16} color={theme.font.color.tertiary} />
</StyledDropdownItem>
<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}>
<StyledDropdownMenu
ref={dropdownContainerRef}
width={dropdownItemRef.current?.clientWidth}
width={refs.domReference.current?.clientWidth}
>
<DropdownMenuInput
value={searchFilter}
@ -178,18 +129,16 @@ export const MatchColumnSelect = ({
<StyledDropdownMenuItemsContainer hasMaxHeight>
{options?.map((option) => (
<>
<DropdownMenuSelectableItem
id={option.value}
<MenuItemSelect
key={option.label}
selected={value?.label === option.label}
onClick={() => handleChange(option)}
disabled={
option.disabled && value?.value !== option.value
}
>
{renderIcon(option?.icon)}
{option.label}
</DropdownMenuSelectableItem>
LeftIcon={option?.icon}
text={option.label}
/>
{option.disabled &&
value?.value !== option.value &&
createPortal(
@ -204,9 +153,7 @@ export const MatchColumnSelect = ({
)}
</>
))}
{options?.length === 0 && (
<DropdownMenuItem>No result</DropdownMenuItem>
)}
{options?.length === 0 && <MenuItem text="No result" />}
</StyledDropdownMenuItemsContainer>
</StyledDropdownMenu>
</StyledFloatingDropdown>,

View File

@ -109,7 +109,7 @@ export const TemplateColumn = <T extends string>({
});
const selectOptions = [
{
icon: <IconForbid />,
icon: IconForbid,
value: 'do-not-import',
label: 'Do not import',
},

View File

@ -134,7 +134,7 @@ export const generateColumns = <T extends string>(
value={
value
? ({
icon: null,
icon: undefined,
...value,
} as const)
: value

View File

@ -1,5 +1,5 @@
import { defaultSpreadsheetImportProps } from '@/spreadsheet-import/provider/components/SpreadsheetImport';
import type { SpreadsheetOptions } from '@/spreadsheet-import/types';
import type { Fields, SpreadsheetOptions } from '@/spreadsheet-import/types';
const fields = [
{
@ -85,7 +85,7 @@ const fields = [
},
example: 'true',
},
] as const;
] as Fields<string>;
const mockComponentBehaviourForTypes = <T extends string>(
props: SpreadsheetOptions<T>,

View File

@ -3,6 +3,7 @@ import { ReadonlyDeep } from 'type-fest';
import { Columns } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { StepState } from '@/spreadsheet-import/steps/components/UploadFlow';
import { Meta } from '@/spreadsheet-import/steps/components/ValidationStep/types';
import { IconComponent } from '@/ui/icon/types/IconComponent';
export type SpreadsheetOptions<Keys extends string> = {
// Is modal visible.
@ -65,7 +66,7 @@ export type Fields<T extends string> = ReadonlyDeep<Field<T>[]>;
export type Field<T extends string> = {
// Icon
icon: React.ReactNode;
icon: IconComponent | null | undefined;
// UI-facing field label
label: string;
// Field's unique identifier
@ -96,7 +97,7 @@ export type Select = {
export type SelectOption = {
// Icon
icon?: React.ReactNode;
icon?: IconComponent | null;
// UI-facing option label
label: string;
// Field entry matching criteria as well as select output