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:
@ -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>,
|
||||
|
||||
@ -109,7 +109,7 @@ export const TemplateColumn = <T extends string>({
|
||||
});
|
||||
const selectOptions = [
|
||||
{
|
||||
icon: <IconForbid />,
|
||||
icon: IconForbid,
|
||||
value: 'do-not-import',
|
||||
label: 'Do not import',
|
||||
},
|
||||
|
||||
@ -134,7 +134,7 @@ export const generateColumns = <T extends string>(
|
||||
value={
|
||||
value
|
||||
? ({
|
||||
icon: null,
|
||||
icon: undefined,
|
||||
...value,
|
||||
} as const)
|
||||
: value
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user