Files
twenty/packages/twenty-front/src/modules/ui/input/components/SelectInput.tsx
T
Charles Bochet d37ed7e07c Optimize merge queue to only run E2E and integrate prettier into lint (#18459)
## Summary

- **Merge queue optimization**: Created a dedicated
`ci-merge-queue.yaml` workflow that only runs Playwright E2E tests on
`ubuntu-latest-8-cores`. Removed `merge_group` trigger from all 7
existing CI workflows (front, server, shared, website, sdk, zapier,
docker-compose). The merge queue goes from ~30+ parallel jobs to a
single focused E2E job.
- **Label-based merge queue simulation**: Added `run-merge-queue` label
support so developers can trigger the exact merge queue E2E pipeline on
any open PR before it enters the queue.
- **Prettier in lint**: Chained `prettier --check` into `lint` and
`prettier --write` into `lint --configuration=fix` across `nx.json`
defaults, `twenty-front`, and `twenty-server`. Prettier formatting
errors are now caught by `lint` and fixed by `lint:fix` /
`lint:diff-with-main --configuration=fix`.

## After merge (manual repo settings)

Update GitHub branch protection required status checks:
1. Remove old per-workflow merge queue checks (`ci-front-status-check`,
`ci-e2e-status-check`, `ci-server-status-check`, etc.)
2. Add `ci-merge-queue-status-check` as the required check for the merge
queue
2026-03-06 13:20:57 +01:00

163 lines
5.7 KiB
TypeScript

import { AddSelectOptionMenuItem } from '@/settings/data-model/fields/forms/select/components/AddSelectOptionMenuItem';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useEffect, useMemo, useRef, useState } from 'react';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { type TagColor } from 'twenty-ui/components';
import { type SelectOption } from 'twenty-ui/input';
import { MenuItemSelectTag } from 'twenty-ui/navigation';
import { normalizeSearchText } from '~/utils/normalizeSearchText';
interface SelectInputProps {
onOptionSelected: (selectedOption: SelectOption) => void;
options: SelectOption[];
onCancel?: () => void;
defaultOption?: SelectOption;
onFilterChange?: (filteredOptions: SelectOption[]) => void;
onClear?: () => void;
clearLabel?: string;
focusId: string;
onAddSelectOption?: (optionName: string) => void;
}
export const SelectInput = ({
onOptionSelected,
onClear,
clearLabel,
options,
onCancel,
defaultOption,
onFilterChange,
onAddSelectOption,
}: SelectInputProps) => {
const containerRef = useRef<HTMLDivElement>(null);
// Get the SelectableList instance id from context
const selectableListInstanceId = useAvailableComponentInstanceIdOrThrow(
SelectableListComponentInstanceContext,
);
const selectedItemId = useAtomComponentStateValue(
selectedItemIdComponentState,
selectableListInstanceId,
);
const [searchFilter, setSearchFilter] = useState('');
const [selectedOption, setSelectedOption] = useState<
SelectOption | undefined
>(defaultOption);
const optionsToSelect = useMemo(() => {
const searchTerm = normalizeSearchText(searchFilter);
return options.filter((option) => {
return (
option.value !== selectedOption?.value &&
normalizeSearchText(option.label).includes(searchTerm)
);
});
}, [options, searchFilter, selectedOption?.value]);
const optionsInDropDown = useMemo(
() =>
selectedOption ? [selectedOption, ...optionsToSelect] : optionsToSelect,
[optionsToSelect, selectedOption],
);
const handleOptionChange = (option: SelectOption) => {
setSelectedOption(option);
onOptionSelected(option);
};
const handleClearOption = () => {
setSelectedOption(undefined);
onClear?.();
};
useEffect(() => {
onFilterChange?.(optionsInDropDown);
}, [onFilterChange, optionsInDropDown]);
useListenClickOutside({
refs: [containerRef],
callback: (event) => {
event.stopImmediatePropagation();
event.preventDefault();
const weAreNotInAnHTMLInput = !(
event.target instanceof HTMLInputElement &&
event.target.tagName === 'INPUT'
);
if (weAreNotInAnHTMLInput && isDefined(onCancel)) {
onCancel();
}
},
listenerId: 'select-input',
});
return (
<DropdownContent ref={containerRef} selectDisabled>
<DropdownMenuSearchInput
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
autoFocus
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{onClear && clearLabel && (
<SelectableListItem
itemId={t`No ${clearLabel}`}
onEnter={handleClearOption}
>
<MenuItemSelectTag
key={t`No ${clearLabel}`}
text={t`No ${clearLabel}`}
color="transparent"
variant="outline"
onClick={handleClearOption}
isKeySelected={selectedItemId === t`No ${clearLabel}`}
/>
</SelectableListItem>
)}
{optionsInDropDown.map((option) => {
return (
<SelectableListItem
key={option.value}
itemId={option.value}
onEnter={() => handleOptionChange(option)}
>
<MenuItemSelectTag
key={option.value}
selected={selectedOption?.value === option.value}
text={option.label}
color={(option.color as TagColor) ?? 'transparent'}
onClick={() => handleOptionChange(option)}
LeftIcon={option.Icon}
isKeySelected={selectedItemId === option.value}
/>
</SelectableListItem>
);
})}
</DropdownMenuItemsContainer>
{onAddSelectOption && searchFilter && optionsToSelect.length === 0 && (
<>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer scrollable={false}>
<AddSelectOptionMenuItem
name={searchFilter}
onAddSelectOption={onAddSelectOption}
/>
</DropdownMenuItemsContainer>
</>
)}
</DropdownContent>
);
};