mirror of
https://github.com/lingble/twenty.git
synced 2025-11-02 21:57:56 +00:00
Fixed many dropdown bugs (#8256)
Many dropdown bugs have been fixed, more refactoring is needed. Dropdown fixed : - Filter select - Sort select - Visible field select - Hidden field select - Multi item picker (phones, links, emails, etc.) - Phone country select
This commit is contained in:
@@ -5,15 +5,10 @@ import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter
|
|||||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
||||||
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
|
||||||
position: relative;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type MultipleFiltersDropdownContentProps = {
|
type MultipleFiltersDropdownContentProps = {
|
||||||
filterDropdownId?: string;
|
filterDropdownId?: string;
|
||||||
};
|
};
|
||||||
@@ -46,7 +41,7 @@ export const MultipleFiltersDropdownContent = ({
|
|||||||
const shoudShowFilterInput = objectFilterDropdownFilterIsSelected;
|
const shoudShowFilterInput = objectFilterDropdownFilterIsSelected;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<>
|
||||||
{shoudShowFilterInput ? (
|
{shoudShowFilterInput ? (
|
||||||
<ObjectFilterOperandSelectAndInput
|
<ObjectFilterOperandSelectAndInput
|
||||||
filterDropdownId={filterDropdownId}
|
filterDropdownId={filterDropdownId}
|
||||||
@@ -61,6 +56,6 @@ export const MultipleFiltersDropdownContent = ({
|
|||||||
filterDefinitionUsedInDropdown?.type
|
filterDefinitionUsedInDropdown?.type
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,8 +28,13 @@ export const ObjectFilterDropdownFilterInput = ({
|
|||||||
const {
|
const {
|
||||||
filterDefinitionUsedInDropdownState,
|
filterDefinitionUsedInDropdownState,
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
} = useFilterDropdown({ filterDropdownId });
|
} = useFilterDropdown({ filterDropdownId });
|
||||||
|
|
||||||
|
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
||||||
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
|
);
|
||||||
|
|
||||||
const filterDefinitionUsedInDropdown = useRecoilValue(
|
const filterDefinitionUsedInDropdown = useRecoilValue(
|
||||||
filterDefinitionUsedInDropdownState,
|
filterDefinitionUsedInDropdownState,
|
||||||
);
|
);
|
||||||
@@ -53,7 +58,9 @@ export const ObjectFilterDropdownFilterInput = ({
|
|||||||
ViewFilterOperand.IsRelative,
|
ViewFilterOperand.IsRelative,
|
||||||
].includes(selectedOperandInDropdown);
|
].includes(selectedOperandInDropdown);
|
||||||
|
|
||||||
if (!isDefined(filterDefinitionUsedInDropdown)) {
|
const shouldHide = isObjectFilterDropdownOperandSelectUnfolded;
|
||||||
|
|
||||||
|
if (shouldHide || !isDefined(filterDefinitionUsedInDropdown)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ const StyledOperandSelectContainer = styled.div`
|
|||||||
background: ${({ theme }) => theme.background.secondary};
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
left: 10px;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
|
|||||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||||
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||||
@@ -35,7 +36,7 @@ export const StyledInput = styled.input`
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: ${({ theme }) => theme.spacing(2)};
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
height: 19px;
|
min-height: 19px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
|
||||||
@@ -160,6 +161,7 @@ export const ObjectFilterDropdownFilterSelect = ({
|
|||||||
setObjectFilterDropdownSearchInput(event.target.value)
|
setObjectFilterDropdownSearchInput(event.target.value)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||||
<SelectableList
|
<SelectableList
|
||||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||||
selectableItemIdArray={selectableListItemIds}
|
selectableItemIdArray={selectableListItemIds}
|
||||||
@@ -197,6 +199,7 @@ export const ObjectFilterDropdownFilterSelect = ({
|
|||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</SelectableList>
|
</SelectableList>
|
||||||
{shouldShowAdvancedFilterButton && <AdvancedFilterButton />}
|
{shouldShowAdvancedFilterButton && <AdvancedFilterButton />}
|
||||||
|
</ScrollWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,17 +10,28 @@ export const ObjectFilterDropdownOperandButton = () => {
|
|||||||
const {
|
const {
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||||
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
} = useFilterDropdown();
|
} = useFilterDropdown();
|
||||||
|
|
||||||
const selectedOperandInDropdown = useRecoilValue(
|
const selectedOperandInDropdown = useRecoilValue(
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
||||||
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
setIsObjectFilterDropdownOperandSelectUnfolded(
|
||||||
|
!isObjectFilterDropdownOperandSelectUnfolded,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
key={'selected-filter-operand'}
|
key={'selected-filter-operand'}
|
||||||
EndIcon={IconChevronDown}
|
EndIcon={IconChevronDown}
|
||||||
onClick={() => setIsObjectFilterDropdownOperandSelectUnfolded(true)}
|
onClick={handleButtonClick}
|
||||||
>
|
>
|
||||||
{getOperandLabel(selectedOperandInDropdown)}
|
{getOperandLabel(selectedOperandInDropdown)}
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
|
|||||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
import { MenuItemMultiSelect } from '@/ui/navigation/menu-item/components/MenuItemMultiSelect';
|
import { MenuItemMultiSelect } from '@/ui/navigation/menu-item/components/MenuItemMultiSelect';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const EMPTY_FILTER_VALUE = '';
|
export const EMPTY_FILTER_VALUE = '';
|
||||||
@@ -162,6 +163,7 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
{optionsInDropdown?.map((option) => (
|
{optionsInDropdown?.map((option) => (
|
||||||
<MenuItemMultiSelect
|
<MenuItemMultiSelect
|
||||||
@@ -178,6 +180,7 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
|||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
{showNoResult && <MenuItem text="No result" />}
|
{showNoResult && <MenuItem text="No result" />}
|
||||||
|
</ScrollWrapper>
|
||||||
</SelectableList>
|
</SelectableList>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/Styl
|
|||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { SORT_DIRECTIONS } from '../types/SortDirection';
|
import { SORT_DIRECTIONS } from '../types/SortDirection';
|
||||||
|
|
||||||
@@ -42,17 +43,13 @@ export const StyledInput = styled.input`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
|
||||||
position: relative;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledSelectedSortDirectionContainer = styled.div`
|
const StyledSelectedSortDirectionContainer = styled.div`
|
||||||
background: ${({ theme }) => theme.background.secondary};
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
left: 10px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 32px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
`;
|
`;
|
||||||
@@ -166,10 +163,11 @@ export const ObjectSortDropdownButton = ({
|
|||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</StyledSelectedSortDirectionContainer>
|
</StyledSelectedSortDirectionContainer>
|
||||||
)}
|
)}
|
||||||
<StyledContainer>
|
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
EndIcon={IconChevronDown}
|
EndIcon={IconChevronDown}
|
||||||
onClick={() => setIsSortDirectionMenuUnfolded(true)}
|
onClick={() =>
|
||||||
|
setIsSortDirectionMenuUnfolded(!isSortDirectionMenuUnfolded)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
@@ -181,6 +179,7 @@ export const ObjectSortDropdownButton = ({
|
|||||||
setObjectSortDropdownSearchInput(event.target.value)
|
setObjectSortDropdownSearchInput(event.target.value)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{visibleColumnsSortDefinitions.map(
|
{visibleColumnsSortDefinitions.map(
|
||||||
(visibleSortDefinition, index) => (
|
(visibleSortDefinition, index) => (
|
||||||
@@ -214,7 +213,7 @@ export const ObjectSortDropdownButton = ({
|
|||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</StyledContainer>
|
</ScrollWrapper>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
onClose={handleDropdownButtonClose}
|
onClose={handleDropdownButtonClose}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmp
|
|||||||
|
|
||||||
const StyledDropdownMenu = styled(DropdownMenu)`
|
const StyledDropdownMenu = styled(DropdownMenu)`
|
||||||
left: -1px;
|
left: -1px;
|
||||||
position: absolute;
|
|
||||||
top: -1px;
|
top: -1px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -46,6 +45,7 @@ type MultiItemFieldInputProps<T> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Todo: the API of this component does not look healthy: we have renderInput, renderItem, formatInput, ...
|
// Todo: the API of this component does not look healthy: we have renderInput, renderItem, formatInput, ...
|
||||||
|
// This should be refactored with a hook instead that exposes those events in a context around this component and its children.
|
||||||
export const MultiItemFieldInput = <T,>({
|
export const MultiItemFieldInput = <T,>({
|
||||||
items,
|
items,
|
||||||
onPersist,
|
onPersist,
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
import styled from '@emotion/styled';
|
import { MenuItemWithOptionDropdown } from '@/ui/navigation/menu-item/components/MenuItemWithOptionDropdown';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
IconBookmark,
|
IconBookmark,
|
||||||
IconBookmarkPlus,
|
IconBookmarkPlus,
|
||||||
IconComponent,
|
|
||||||
IconDotsVertical,
|
|
||||||
IconPencil,
|
IconPencil,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
@@ -24,12 +21,6 @@ type MultiItemFieldMenuItemProps<T> = {
|
|||||||
hasPrimaryButton?: boolean;
|
hasPrimaryButton?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledIconBookmark = styled(IconBookmark)`
|
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
|
||||||
height: ${({ theme }) => theme.icon.size.sm}px;
|
|
||||||
width: ${({ theme }) => theme.icon.size.sm}px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const MultiItemFieldMenuItem = <T,>({
|
export const MultiItemFieldMenuItem = <T,>({
|
||||||
dropdownId,
|
dropdownId,
|
||||||
isPrimary,
|
isPrimary,
|
||||||
@@ -47,46 +38,42 @@ export const MultiItemFieldMenuItem = <T,>({
|
|||||||
const handleMouseLeave = () => setIsHovered(false);
|
const handleMouseLeave = () => setIsHovered(false);
|
||||||
|
|
||||||
const handleDeleteClick = () => {
|
const handleDeleteClick = () => {
|
||||||
|
closeDropdown();
|
||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
onDelete?.();
|
onDelete?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleSetAsPrimaryClick = () => {
|
||||||
if (isDropdownOpen) {
|
closeDropdown();
|
||||||
return () => closeDropdown();
|
onSetAsPrimary?.();
|
||||||
}
|
};
|
||||||
}, [closeDropdown, isDropdownOpen]);
|
|
||||||
|
const handleEditClick = () => {
|
||||||
|
closeDropdown();
|
||||||
|
onEdit?.();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItemWithOptionDropdown
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
text={<DisplayComponent value={value} />}
|
text={<DisplayComponent value={value} />}
|
||||||
isIconDisplayedOnHoverOnly={!isPrimary && !isDropdownOpen}
|
isIconDisplayedOnHoverOnly={!isPrimary && !isDropdownOpen}
|
||||||
iconButtons={[
|
RightIcon={isHovered ? null : IconBookmark}
|
||||||
{
|
|
||||||
Wrapper: isHovered
|
|
||||||
? ({ iconButton }) => (
|
|
||||||
<Dropdown
|
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
dropdownContent={
|
||||||
dropdownPlacement="right-start"
|
|
||||||
dropdownStrategy="fixed"
|
|
||||||
disableBlur
|
|
||||||
clickableComponent={iconButton}
|
|
||||||
dropdownComponents={
|
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{hasPrimaryButton && !isPrimary && (
|
{hasPrimaryButton && !isPrimary && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
LeftIcon={IconBookmarkPlus}
|
LeftIcon={IconBookmarkPlus}
|
||||||
text="Set as Primary"
|
text="Set as Primary"
|
||||||
onClick={onSetAsPrimary}
|
onClick={handleSetAsPrimaryClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
LeftIcon={IconPencil}
|
LeftIcon={IconPencil}
|
||||||
text="Edit"
|
text="Edit"
|
||||||
onClick={onEdit}
|
onClick={handleEditClick}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
accent="danger"
|
accent="danger"
|
||||||
@@ -97,16 +84,5 @@ export const MultiItemFieldMenuItem = <T,>({
|
|||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
Icon:
|
|
||||||
isPrimary && !isHovered
|
|
||||||
? (StyledIconBookmark as IconComponent)
|
|
||||||
: IconDotsVertical,
|
|
||||||
accent: 'tertiary',
|
|
||||||
onClick: isHovered ? () => {} : undefined,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemN
|
|||||||
import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle';
|
import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle';
|
||||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
|
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
|
||||||
import { ViewGroupsVisibilityDropdownSection } from '@/views/components/ViewGroupsVisibilityDropdownSection';
|
import { ViewGroupsVisibilityDropdownSection } from '@/views/components/ViewGroupsVisibilityDropdownSection';
|
||||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
@@ -259,6 +260,7 @@ export const RecordIndexOptionsDropdownContent = ({
|
|||||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
|
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
|
||||||
Fields
|
Fields
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
|
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||||
<ViewFieldsVisibilityDropdownSection
|
<ViewFieldsVisibilityDropdownSection
|
||||||
title="Visible"
|
title="Visible"
|
||||||
fields={visibleRecordFields}
|
fields={visibleRecordFields}
|
||||||
@@ -268,6 +270,7 @@ export const RecordIndexOptionsDropdownContent = ({
|
|||||||
showSubheader={false}
|
showSubheader={false}
|
||||||
showDragGrip={true}
|
showDragGrip={true}
|
||||||
/>
|
/>
|
||||||
|
</ScrollWrapper>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
<MenuItemNavigate
|
<MenuItemNavigate
|
||||||
@@ -317,7 +320,7 @@ export const RecordIndexOptionsDropdownContent = ({
|
|||||||
Hidden Fields
|
Hidden Fields
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
{hiddenRecordFields.length > 0 && (
|
{hiddenRecordFields.length > 0 && (
|
||||||
<>
|
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||||
<ViewFieldsVisibilityDropdownSection
|
<ViewFieldsVisibilityDropdownSection
|
||||||
title="Hidden"
|
title="Hidden"
|
||||||
fields={hiddenRecordFields}
|
fields={hiddenRecordFields}
|
||||||
@@ -326,7 +329,7 @@ export const RecordIndexOptionsDropdownContent = ({
|
|||||||
showSubheader={false}
|
showSubheader={false}
|
||||||
showDragGrip={false}
|
showDragGrip={false}
|
||||||
/>
|
/>
|
||||||
</>
|
</ScrollWrapper>
|
||||||
)}
|
)}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ export const PhoneCountryPickerDropdownButton = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = (countryCode: string) => {
|
const handleChange = (countryCode: string) => {
|
||||||
onChange(countryCode);
|
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
|
onChange(countryCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
const countries = useCountries();
|
const countries = useCountries();
|
||||||
@@ -89,7 +89,6 @@ export const PhoneCountryPickerDropdownButton = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownMenuWidth={'100%'}
|
|
||||||
dropdownId="country-picker-dropdown-id"
|
dropdownId="country-picker-dropdown-id"
|
||||||
dropdownHotkeyScope={{ scope: CountryPickerHotkeyScope.CountryPicker }}
|
dropdownHotkeyScope={{ scope: CountryPickerHotkeyScope.CountryPicker }}
|
||||||
clickableComponent={
|
clickableComponent={
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
size,
|
size,
|
||||||
useFloating,
|
useFloating,
|
||||||
} from '@floating-ui/react';
|
} from '@floating-ui/react';
|
||||||
import { MouseEvent, useEffect, useRef } from 'react';
|
import { MouseEvent, ReactNode, useRef } from 'react';
|
||||||
import { Keys } from 'react-hotkeys-hook';
|
import { Keys } from 'react-hotkeys-hook';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
@@ -27,8 +27,8 @@ import { DropdownOnToggleEffect } from './DropdownOnToggleEffect';
|
|||||||
|
|
||||||
type DropdownProps = {
|
type DropdownProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
clickableComponent?: JSX.Element | JSX.Element[];
|
clickableComponent?: ReactNode;
|
||||||
dropdownComponents: JSX.Element | JSX.Element[];
|
dropdownComponents: ReactNode;
|
||||||
hotkey?: {
|
hotkey?: {
|
||||||
key: Keys;
|
key: Keys;
|
||||||
scope: string;
|
scope: string;
|
||||||
@@ -65,13 +65,8 @@ export const Dropdown = ({
|
|||||||
}: DropdownProps) => {
|
}: DropdownProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const {
|
const { isDropdownOpen, toggleDropdown, closeDropdown, dropdownWidth } =
|
||||||
isDropdownOpen,
|
useDropdown(dropdownId);
|
||||||
toggleDropdown,
|
|
||||||
closeDropdown,
|
|
||||||
dropdownWidth,
|
|
||||||
setDropdownPlacement,
|
|
||||||
} = useDropdown(dropdownId);
|
|
||||||
|
|
||||||
const offsetMiddlewares = [];
|
const offsetMiddlewares = [];
|
||||||
|
|
||||||
@@ -83,18 +78,21 @@ export const Dropdown = ({
|
|||||||
offsetMiddlewares.push(offset({ mainAxis: dropdownOffset.y }));
|
offsetMiddlewares.push(offset({ mainAxis: dropdownOffset.y }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { refs, floatingStyles, placement } = useFloating({
|
const { refs, floatingStyles } = useFloating({
|
||||||
placement: dropdownPlacement,
|
placement: dropdownPlacement,
|
||||||
middleware: [
|
middleware: [
|
||||||
flip(),
|
flip(),
|
||||||
size({
|
size({
|
||||||
padding: 12 + 20, // 12px for padding bottom, 20px for dropdown bottom margin target
|
padding: 32,
|
||||||
apply: ({ availableHeight, elements }) => {
|
apply: ({ availableHeight, elements }) => {
|
||||||
elements.floating.style.maxHeight =
|
elements.floating.style.maxHeight =
|
||||||
availableHeight >= elements.floating.scrollHeight
|
availableHeight >= elements.floating.scrollHeight
|
||||||
? ''
|
? ''
|
||||||
: `${availableHeight}px`;
|
: `${availableHeight}px`;
|
||||||
|
|
||||||
|
elements.floating.style.height = 'auto';
|
||||||
},
|
},
|
||||||
|
boundary: document.querySelector('#root') ?? undefined,
|
||||||
}),
|
}),
|
||||||
...offsetMiddlewares,
|
...offsetMiddlewares,
|
||||||
],
|
],
|
||||||
@@ -102,10 +100,6 @@ export const Dropdown = ({
|
|||||||
strategy: dropdownStrategy,
|
strategy: dropdownStrategy,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setDropdownPlacement(placement);
|
|
||||||
}, [placement, setDropdownPlacement]);
|
|
||||||
|
|
||||||
const handleHotkeyTriggered = () => {
|
const handleHotkeyTriggered = () => {
|
||||||
toggleDropdown();
|
toggleDropdown();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ const StyledDropdownMenu = styled.div<{
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
width: ${({ width = 160 }) =>
|
width: ${({ width = 160 }) =>
|
||||||
typeof width === 'number' ? `${width}px` : width};
|
typeof width === 'number' ? `${width}px` : width};
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ const StyledDropdownMenuItemsExternalContainer = styled.div<{
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
max-height: ${({ hasMaxHeight }) => (hasMaxHeight ? '188px' : 'none')};
|
max-height: ${({ hasMaxHeight }) => (hasMaxHeight ? '188px' : 'none')};
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
padding: var(--padding);
|
padding: var(--padding);
|
||||||
|
|
||||||
@@ -34,6 +33,8 @@ const StyledDropdownMenuItemsInternalContainer = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// TODO: refactor this, the dropdown should handle the max height behavior + scroll with the size middleware
|
||||||
|
// We should instead create a DropdownMenuItemsContainerScrollable or take for granted that it is the default behavior
|
||||||
export const DropdownMenuItemsContainer = ({
|
export const DropdownMenuItemsContainer = ({
|
||||||
children,
|
children,
|
||||||
hasMaxHeight,
|
hasMaxHeight,
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import { FunctionComponent, MouseEvent, ReactElement, ReactNode } from 'react';
|
||||||
|
import {
|
||||||
|
IconChevronRight,
|
||||||
|
IconComponent,
|
||||||
|
IconDotsVertical,
|
||||||
|
LightIconButton,
|
||||||
|
LightIconButtonProps,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
|
||||||
|
import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope';
|
||||||
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';
|
||||||
|
import {
|
||||||
|
StyledHoverableMenuItemBase,
|
||||||
|
StyledMenuItemLeftContent,
|
||||||
|
} from '../internals/components/StyledMenuItemBase';
|
||||||
|
import { MenuItemAccent } from '../types/MenuItemAccent';
|
||||||
|
|
||||||
|
export type MenuItemIconButton = {
|
||||||
|
Wrapper?: FunctionComponent<{ iconButton: ReactElement }>;
|
||||||
|
Icon: IconComponent;
|
||||||
|
accent?: LightIconButtonProps['accent'];
|
||||||
|
onClick?: (event: MouseEvent<any>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MenuItemWithOptionDropdownProps = {
|
||||||
|
accent?: MenuItemAccent;
|
||||||
|
className?: string;
|
||||||
|
dropdownContent: ReactNode;
|
||||||
|
dropdownId: string;
|
||||||
|
isIconDisplayedOnHoverOnly?: boolean;
|
||||||
|
isTooltipOpen?: boolean;
|
||||||
|
LeftIcon?: IconComponent | null;
|
||||||
|
RightIcon?: IconComponent | null;
|
||||||
|
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||||
|
onMouseEnter?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||||
|
onMouseLeave?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||||
|
testId?: string;
|
||||||
|
text: ReactNode;
|
||||||
|
hasSubMenu?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: refactor this
|
||||||
|
export const MenuItemWithOptionDropdown = ({
|
||||||
|
accent = 'default',
|
||||||
|
className,
|
||||||
|
isIconDisplayedOnHoverOnly = true,
|
||||||
|
dropdownContent,
|
||||||
|
dropdownId,
|
||||||
|
LeftIcon,
|
||||||
|
RightIcon,
|
||||||
|
onClick,
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave,
|
||||||
|
testId,
|
||||||
|
text,
|
||||||
|
hasSubMenu = false,
|
||||||
|
}: MenuItemWithOptionDropdownProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const handleMenuItemClick = (event: MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (!onClick) return;
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
onClick?.(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledHoverableMenuItemBase
|
||||||
|
data-testid={testId ?? undefined}
|
||||||
|
onClick={handleMenuItemClick}
|
||||||
|
className={className}
|
||||||
|
accent={accent}
|
||||||
|
isIconDisplayedOnHoverOnly={isIconDisplayedOnHoverOnly}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
<StyledMenuItemLeftContent>
|
||||||
|
<MenuItemLeftContent LeftIcon={LeftIcon ?? undefined} text={text} />
|
||||||
|
</StyledMenuItemLeftContent>
|
||||||
|
<div className="hoverable-buttons">
|
||||||
|
<Dropdown
|
||||||
|
clickableComponent={
|
||||||
|
<LightIconButton
|
||||||
|
Icon={RightIcon ?? IconDotsVertical}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
dropdownComponents={dropdownContent}
|
||||||
|
dropdownId={dropdownId}
|
||||||
|
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
|
||||||
|
disableBlur
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{hasSubMenu && (
|
||||||
|
<IconChevronRight
|
||||||
|
size={theme.icon.size.sm}
|
||||||
|
color={theme.font.color.tertiary}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StyledHoverableMenuItemBase>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user