Files
twenty/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx
Lucas Bordeau 98475ee63e 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
2025-03-31 10:24:52 +02:00

171 lines
5.1 KiB
TypeScript

import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownOnToggleEffect } from '@/ui/layout/dropdown/components/DropdownOnToggleEffect';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponeInstanceContext';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
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 { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
import styled from '@emotion/styled';
import {
Placement,
autoUpdate,
flip,
offset,
size,
useFloating,
} from '@floating-ui/react';
import { MouseEvent, ReactNode } from 'react';
import { flushSync } from 'react-dom';
import { Keys } from 'react-hotkeys-hook';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { sleep } from '~/utils/sleep';
import { useDropdown } from '../hooks/useDropdown';
const StyledDropdownFallbackAnchor = styled.div`
left: 0;
position: fixed;
top: 0;
`;
const StyledClickableComponent = styled.div`
height: fit-content;
`;
export type DropdownProps = {
className?: string;
clickableComponent?: ReactNode;
dropdownComponents: ReactNode;
hotkey?: {
key: Keys;
scope: string;
};
dropdownHotkeyScope: HotkeyScope;
dropdownId: string;
dropdownPlacement?: Placement;
dropdownMenuWidth?: `${string}px` | `${number}%` | 'auto' | number;
dropdownOffset?: DropdownOffset;
dropdownStrategy?: 'fixed' | 'absolute';
onClickOutside?: () => void;
onClose?: () => void;
onOpen?: () => void;
avoidPortal?: boolean;
};
export const Dropdown = ({
className,
clickableComponent,
dropdownComponents,
dropdownMenuWidth,
hotkey,
dropdownId,
dropdownHotkeyScope,
dropdownPlacement = 'bottom-end',
dropdownStrategy = 'absolute',
dropdownOffset,
onClickOutside,
onClose,
onOpen,
avoidPortal,
}: DropdownProps) => {
const { isDropdownOpen, toggleDropdown } = useDropdown(dropdownId);
const isUsingOffset =
isDefined(dropdownOffset?.x) || isDefined(dropdownOffset?.y);
const offsetMiddleware = isUsingOffset
? [
offset({
crossAxis: dropdownOffset?.x ?? 0,
mainAxis: dropdownOffset?.y ?? 0,
}),
]
: [];
const { refs, floatingStyles, placement } = useFloating({
placement: dropdownPlacement,
middleware: [
...offsetMiddleware,
flip(),
size({
padding: 32,
apply: () => {
flushSync(() => {
// TODO: I think this is not needed anymore let's remove it if not used for a few weeks
// setDropdownMaxHeight(availableHeight);
});
},
boundary: document.querySelector('#root') ?? undefined,
}),
],
whileElementsMounted: autoUpdate,
strategy: dropdownStrategy,
});
const handleClickableComponentClick = useRecoilCallback(
({ set }) =>
async (event: MouseEvent) => {
event.stopPropagation();
event.preventDefault();
// TODO: refactor this when we have finished dropdown refactor with state and V1 + V2
set(
dropdownHotkeyComponentState({ scopeId: dropdownId }),
dropdownHotkeyScope,
);
await sleep(100);
toggleDropdown();
onClickOutside?.();
},
[dropdownId, dropdownHotkeyScope, onClickOutside, toggleDropdown],
);
return (
<DropdownComponentInstanceContext.Provider
value={{ instanceId: dropdownId }}
>
<DropdownScope dropdownScopeId={getScopeIdFromComponentId(dropdownId)}>
<>
{isDefined(clickableComponent) ? (
<StyledClickableComponent
ref={refs.setReference}
onClick={handleClickableComponentClick}
aria-controls={`${dropdownId}-options`}
aria-expanded={isDropdownOpen}
aria-haspopup={true}
role="button"
>
{clickableComponent}
</StyledClickableComponent>
) : (
<StyledDropdownFallbackAnchor ref={refs.setReference} />
)}
{isDropdownOpen && (
<DropdownContent
className={className}
floatingStyles={floatingStyles}
dropdownMenuWidth={dropdownMenuWidth}
dropdownComponents={dropdownComponents}
dropdownId={dropdownId}
dropdownPlacement={placement}
floatingUiRefs={refs}
hotkeyScope={dropdownHotkeyScope}
hotkey={hotkey}
onClickOutside={onClickOutside}
onHotkeyTriggered={toggleDropdown}
avoidPortal={avoidPortal}
/>
)}
<DropdownOnToggleEffect
onDropdownClose={onClose}
onDropdownOpen={onOpen}
/>
</>
</DropdownScope>
</DropdownComponentInstanceContext.Provider>
);
};