Advanced filter UI fast follow-ups (#11272)

This PR fixes the issue about the easy fast follow-up part on advanced
filter.

Fixes https://github.com/twentyhq/core-team-issues/issues/675

Changes : 
- Changed horizontal gap to spacing(1) for AdvancedFilterDropdownRow
- Created a DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET for all
sub-dropdowns in advanced filter dropdown with a y-offset of 2px.
- Created a DropdownOffset type
- Used a padding-left of spacing(2.25) in
AdvancedFilterLogicalOperatorCell to allign the disabled text with the
text in the Select component
- Added IconTrash and accent danger on
AdvancedFilterRecordFilterGroupOptionsDropdown and
AdvancedFilterRecordFilterOptionsDropdown
- Removed unnecessary CSS properties on
AdvancedFilterRootRecordFilterGroup
- Set dropdownMenuWith to 280 for AdvancedFilterValueInputDropdownButton
- Fixed Dropdown generic clickable component container that was
expanding
- Set IconFilter instead of IconFilterCog in AdvancedFilterChip
- Set AdvancedFilterDropdownButton dropdown content width to 650 instead
of 800
- Refactored generic IconButton component so that it disambiguates
secondary and tertiary variant for the color CSS props
This commit is contained in:
Lucas Bordeau
2025-03-31 10:24:52 +02:00
committed by GitHub
parent eea30828a4
commit 98475ee63e
16 changed files with 61 additions and 26 deletions

View File

@ -3,7 +3,7 @@ import styled from '@emotion/styled';
const StyledRow = styled.div` const StyledRow = styled.div`
display: flex; display: flex;
width: 100%; width: 100%;
gap: ${({ theme }) => theme.spacing(2)}; gap: ${({ theme }) => theme.spacing(1)};
`; `;
export const AdvancedFilterDropdownRow = StyledRow; export const AdvancedFilterDropdownRow = StyledRow;

View File

@ -1,4 +1,5 @@
import { AdvancedFilterFieldSelectDropdownContent } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownContent'; import { AdvancedFilterFieldSelectDropdownContent } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownContent';
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown'; import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
@ -49,7 +50,7 @@ export const AdvancedFilterFieldSelectDropdownButton = ({
/> />
} }
dropdownHotkeyScope={{ scope: advancedFilterFieldSelectDropdownId }} dropdownHotkeyScope={{ scope: advancedFilterFieldSelectDropdownId }}
dropdownOffset={{ y: 8, x: 0 }} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />
</StyledContainer> </StyledContainer>

View File

@ -8,6 +8,8 @@ const StyledText = styled.div`
height: ${({ theme }) => theme.spacing(8)}; height: ${({ theme }) => theme.spacing(8)};
display: flex; display: flex;
align-items: center; align-items: center;
padding-left: ${({ theme }) => theme.spacing(2.25)};
`; `;
const StyledContainer = styled.div` const StyledContainer = styled.div`

View File

@ -1,4 +1,5 @@
import { ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS } from '@/object-record/advanced-filter/constants/AdvancedFilterLogicalOperatorOptions'; import { ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS } from '@/object-record/advanced-filter/constants/AdvancedFilterLogicalOperatorOptions';
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup'; import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator'; import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
@ -30,6 +31,7 @@ export const AdvancedFilterLogicalOperatorDropdown = ({
value={recordFilterGroup.logicalOperator} value={recordFilterGroup.logicalOperator}
onChange={handleChange} onChange={handleChange}
options={ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS} options={ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
/> />
); );
}; };

View File

@ -6,7 +6,7 @@ import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRe
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; 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 { IconButton, IconDotsVertical, MenuItem } from 'twenty-ui'; import { IconButton, IconDotsVertical, IconTrash, MenuItem } from 'twenty-ui';
type AdvancedFilterRecordFilterGroupOptionsDropdownProps = { type AdvancedFilterRecordFilterGroupOptionsDropdownProps = {
recordFilterGroupId: string; recordFilterGroupId: string;
@ -52,11 +52,16 @@ export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({
} }
dropdownComponents={ dropdownComponents={
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem text="Remove rule group" onClick={handleRemove} /> <MenuItem
text="Remove rule group"
onClick={handleRemove}
LeftIcon={IconTrash}
accent="danger"
/>
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
} }
dropdownHotkeyScope={{ scope: dropdownId }} dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }} dropdownOffset={{ y: 2, x: 0 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />
); );

View File

@ -1,5 +1,6 @@
import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById'; import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue'; import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel'; import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
@ -17,7 +18,7 @@ import { isDefined } from 'twenty-shared/utils';
import { MenuItem } from 'twenty-ui'; import { MenuItem } from 'twenty-ui';
const StyledContainer = styled.div` const StyledContainer = styled.div`
flex: 1; width: 100px;
`; `;
type AdvancedFilterRecordFilterOperandSelectProps = { type AdvancedFilterRecordFilterOperandSelectProps = {
@ -126,7 +127,7 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
} }
dropdownHotkeyScope={{ scope: dropdownId }} dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />
</StyledContainer> </StyledContainer>

View File

@ -5,12 +5,13 @@ import { useRemoveRootRecordFilterGroupIfEmpty } from '@/object-record/record-fi
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { IconButton, IconDotsVertical, MenuItem } from 'twenty-ui';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { IconButton, IconDotsVertical, IconTrash, MenuItem } from 'twenty-ui';
type AdvancedFilterRecordFilterOptionsDropdownProps = { type AdvancedFilterRecordFilterOptionsDropdownProps = {
recordFilterId: string; recordFilterId: string;
@ -71,11 +72,16 @@ export const AdvancedFilterRecordFilterOptionsDropdown = ({
} }
dropdownComponents={ dropdownComponents={
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem text="Remove rule" onClick={handleRemove} /> <MenuItem
text="Remove rule"
onClick={handleRemove}
LeftIcon={IconTrash}
accent="danger"
/>
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
} }
dropdownHotkeyScope={{ scope: dropdownId }} dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />
); );

View File

@ -11,10 +11,6 @@ import { isDefined } from 'twenty-shared/utils';
const StyledContainer = styled.div<{ isGrayBackground?: boolean }>` const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
align-items: start; align-items: start;
background-color: ${({ theme, isGrayBackground }) =>
isGrayBackground ? theme.background.transparent.lighter : 'transparent'};
border: ${({ theme }) => `1px solid ${theme.border.color.medium}`};
border-radius: ${({ theme }) => theme.border.radius.md};
display: flex; display: flex;
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;

View File

@ -1,3 +1,4 @@
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput'; import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
@ -81,9 +82,9 @@ export const AdvancedFilterValueInputDropdownButton = ({
<ObjectFilterDropdownFilterInput recordFilterId={filter.id} /> <ObjectFilterDropdownFilterInput recordFilterId={filter.id} />
} }
dropdownHotkeyScope={{ scope: dropdownId }} dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownMenuWidth={200} dropdownMenuWidth={280}
/> />
)} )}
</StyledValueDropdownContainer> </StyledValueDropdownContainer>

View File

@ -0,0 +1,6 @@
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
export const DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET: DropdownOffset = {
y: 2,
x: 0,
};

View File

@ -14,6 +14,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectControl } from '@/ui/input/components/SelectControl'; import { SelectControl } from '@/ui/input/components/SelectControl';
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { SelectHotkeyScope } from '../types/SelectHotkeyScope'; import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
@ -44,6 +45,7 @@ export type SelectProps<Value extends SelectValue> = {
withSearchInput?: boolean; withSearchInput?: boolean;
needIconCheck?: boolean; needIconCheck?: boolean;
callToActionButton?: CallToActionButton; callToActionButton?: CallToActionButton;
dropdownOffset?: DropdownOffset;
}; };
const StyledContainer = styled.div<{ fullWidth?: boolean }>` const StyledContainer = styled.div<{ fullWidth?: boolean }>`
@ -75,6 +77,7 @@ export const Select = <Value extends SelectValue>({
withSearchInput, withSearchInput,
needIconCheck, needIconCheck,
callToActionButton, callToActionButton,
dropdownOffset,
}: SelectProps<Value>) => { }: SelectProps<Value>) => {
const selectContainerRef = useRef<HTMLDivElement>(null); const selectContainerRef = useRef<HTMLDivElement>(null);
@ -127,6 +130,7 @@ export const Select = <Value extends SelectValue>({
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownMenuWidth={dropDownMenuWidth} dropdownMenuWidth={dropDownMenuWidth}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownOffset={dropdownOffset}
clickableComponent={ clickableComponent={
<SelectControl <SelectControl
selectedOption={selectedOption} selectedOption={selectedOption}

View File

@ -3,6 +3,7 @@ import { DropdownOnToggleEffect } from '@/ui/layout/dropdown/components/Dropdown
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponeInstanceContext'; import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponeInstanceContext';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { dropdownHotkeyComponentState } from '@/ui/layout/dropdown/states/dropdownHotkeyComponentState'; import { dropdownHotkeyComponentState } from '@/ui/layout/dropdown/states/dropdownHotkeyComponentState';
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
@ -18,9 +19,9 @@ import { MouseEvent, ReactNode } from 'react';
import { flushSync } from 'react-dom'; import { flushSync } from 'react-dom';
import { Keys } from 'react-hotkeys-hook'; import { Keys } from 'react-hotkeys-hook';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { sleep } from '~/utils/sleep'; import { sleep } from '~/utils/sleep';
import { useDropdown } from '../hooks/useDropdown'; import { useDropdown } from '../hooks/useDropdown';
import { isDefined } from 'twenty-shared/utils';
const StyledDropdownFallbackAnchor = styled.div` const StyledDropdownFallbackAnchor = styled.div`
left: 0; left: 0;
@ -28,6 +29,10 @@ const StyledDropdownFallbackAnchor = styled.div`
top: 0; top: 0;
`; `;
const StyledClickableComponent = styled.div`
height: fit-content;
`;
export type DropdownProps = { export type DropdownProps = {
className?: string; className?: string;
clickableComponent?: ReactNode; clickableComponent?: ReactNode;
@ -40,7 +45,7 @@ export type DropdownProps = {
dropdownId: string; dropdownId: string;
dropdownPlacement?: Placement; dropdownPlacement?: Placement;
dropdownMenuWidth?: `${string}px` | `${number}%` | 'auto' | number; dropdownMenuWidth?: `${string}px` | `${number}%` | 'auto' | number;
dropdownOffset?: { x?: number; y?: number }; dropdownOffset?: DropdownOffset;
dropdownStrategy?: 'fixed' | 'absolute'; dropdownStrategy?: 'fixed' | 'absolute';
onClickOutside?: () => void; onClickOutside?: () => void;
onClose?: () => void; onClose?: () => void;
@ -125,7 +130,7 @@ export const Dropdown = ({
<DropdownScope dropdownScopeId={getScopeIdFromComponentId(dropdownId)}> <DropdownScope dropdownScopeId={getScopeIdFromComponentId(dropdownId)}>
<> <>
{isDefined(clickableComponent) ? ( {isDefined(clickableComponent) ? (
<div <StyledClickableComponent
ref={refs.setReference} ref={refs.setReference}
onClick={handleClickableComponentClick} onClick={handleClickableComponentClick}
aria-controls={`${dropdownId}-options`} aria-controls={`${dropdownId}-options`}
@ -134,7 +139,7 @@ export const Dropdown = ({
role="button" role="button"
> >
{clickableComponent} {clickableComponent}
</div> </StyledClickableComponent>
) : ( ) : (
<StyledDropdownFallbackAnchor ref={refs.setReference} /> <StyledDropdownFallbackAnchor ref={refs.setReference} />
)} )}

View File

@ -0,0 +1,4 @@
export type DropdownOffset = {
x?: number;
y?: number;
};

View File

@ -1,4 +1,4 @@
import { IconFilterCog } from 'twenty-ui'; import { IconFilter } from 'twenty-ui';
import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup'; import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup';
import { useRemoveRootRecordFilterGroupIfEmpty } from '@/object-record/record-filter-group/hooks/useRemoveRootRecordFilterGroupIfEmpty'; import { useRemoveRootRecordFilterGroupIfEmpty } from '@/object-record/record-filter-group/hooks/useRemoveRootRecordFilterGroupIfEmpty';
@ -61,7 +61,7 @@ export const AdvancedFilterChip = () => {
testId={ADVANCED_FILTER_DROPDOWN_ID} testId={ADVANCED_FILTER_DROPDOWN_ID}
labelKey={chipLabel} labelKey={chipLabel}
labelValue="" labelValue=""
Icon={IconFilterCog} Icon={IconFilter}
onRemove={handleRemoveClick} onRemove={handleRemoveClick}
/> />
); );

View File

@ -24,7 +24,7 @@ export const AdvancedFilterDropdownButton = () => {
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }} dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}
dropdownOffset={{ y: 8, x: 0 }} dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownMenuWidth={800} dropdownMenuWidth={650}
/> />
); );
}; };

View File

@ -125,9 +125,11 @@ const StyledButton = styled.button<
box-shadow: ${!disabled && focus box-shadow: ${!disabled && focus
? `0 0 0 3px ${theme.accent.tertiary}` ? `0 0 0 3px ${theme.accent.tertiary}`
: 'none'}; : 'none'};
color: ${!disabled color: ${disabled
? theme.font.color.secondary ? theme.font.color.extraLight
: theme.font.color.extraLight}; : variant === 'secondary'
? theme.font.color.secondary
: theme.font.color.tertiary};
&:hover { &:hover {
background: ${!disabled background: ${!disabled
? theme.background.transparent.light ? theme.background.transparent.light