diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx index 77cf3c343..8ed2d7598 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx @@ -23,6 +23,7 @@ import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/reco import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext'; import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; +import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices'; import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -46,7 +47,7 @@ const StyledCommandMenu = styled(motion.div)` position: fixed; right: 0%; top: 0%; - z-index: 30; + z-index: ${RootStackingContextZIndices.CommandMenu}; display: flex; flex-direction: column; `; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx b/packages/twenty-front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx index 1afa0028b..c157d3df0 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx @@ -1,39 +1,26 @@ -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { - autoUpdate, - flip, - offset, - size, - useFloating, -} from '@floating-ui/react'; -import React, { useCallback, useId, useRef, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { createPortal } from 'react-dom'; import { ReadonlyDeep } from 'type-fest'; import { useDebouncedCallback } from 'use-debounce'; -import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; 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 { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer'; -import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useLingui } from '@lingui/react/macro'; -import { useUpdateEffect } from '~/hooks/useUpdateEffect'; import { AppTooltip } from 'twenty-ui/display'; -import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation'; import { SelectOption } from 'twenty-ui/input'; - -const StyledFloatingDropdown = styled.div` - z-index: ${({ theme }) => theme.lastLayerZIndex}; -`; +import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation'; +import { v4 } from 'uuid'; +import { useUpdateEffect } from '~/hooks/useUpdateEffect'; interface MatchColumnSelectProps { + columnIndex: string; onChange: (value: ReadonlyDeep | null) => void; value?: ReadonlyDeep; options: readonly ReadonlyDeep[]; placeholder?: string; - name?: string; } export const MatchColumnSelect = ({ @@ -41,30 +28,15 @@ export const MatchColumnSelect = ({ value, options: initialOptions, placeholder, + columnIndex, }: MatchColumnSelectProps) => { - const theme = useTheme(); - const idPrefix = useId(); + const dropdownId = `match-column-select-dropdown-${columnIndex}`; - const dropdownContainerRef = useRef(null); + const { closeDropdown } = useDropdown(dropdownId); - const [isOpen, setIsOpen] = useState(false); const [searchFilter, setSearchFilter] = useState(''); const [options, setOptions] = useState(initialOptions); - const { refs, floatingStyles } = useFloating({ - strategy: 'absolute', - middleware: [ - offset(() => { - return parseInt(theme.spacing(2), 10); - }), - flip(), - size(), - ], - whileElementsMounted: autoUpdate, - open: isOpen, - placement: 'bottom-start', - }); - const handleSearchFilterChange = useCallback( (text: string) => { setOptions( @@ -91,23 +63,11 @@ export const MatchColumnSelect = ({ debouncedHandleSearchFilter(value); }; - const handleDropdownItemClick = () => { - setIsOpen(true); - }; - const handleChange = (option: ReadonlyDeep) => { onChange(option); - setIsOpen(false); + closeDropdown(); }; - useListenClickOutside({ - refs: [dropdownContainerRef], - callback: () => { - setIsOpen(false); - }, - listenerId: 'match-column-select', - }); - useUpdateEffect(() => { setOptions(initialOptions); }, [initialOptions]); @@ -115,70 +75,64 @@ export const MatchColumnSelect = ({ const { t } = useLingui(); return ( - <> -
+ -
- {isOpen && - createPortal( - - - - - - - {options?.map((option, index) => { - const id = `${idPrefix}-option-${index}`; - return ( - -
- handleChange(option)} - disabled={ - option.disabled && value?.value !== option.value - } - LeftIcon={option?.Icon} - text={option.label} - /> -
- {option.disabled && - value?.value !== option.value && - createPortal( - , - document.body, - )} -
- ); - })} - {options?.length === 0 && ( - - )} -
-
-
-
, - document.body, - )} - + } + dropdownComponents={ + <> + + + + {options?.map((option) => { + const id = `${v4()}-${option.value}`; + return ( + +
+ handleChange(option)} + disabled={ + option.disabled && value?.value !== option.value + } + LeftIcon={option?.Icon} + text={option.label} + /> +
+ {option.disabled && + value?.value !== option.value && + createPortal( + , + document.body, + )} +
+ ); + })} + {options?.length === 0 && ( + + )} +
+ + } + /> ); }; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelect.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelect.tsx index 0ef0bc154..b232a2a67 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelect.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelect.tsx @@ -14,9 +14,9 @@ import { SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/Spreadshee import { SelectInput } from '@/ui/input/components/SelectInput'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useEffect, useState } from 'react'; +import { Tag, TagColor } from 'twenty-ui/components'; import { IconChevronDown } from 'twenty-ui/display'; import { SelectOption } from 'twenty-ui/input'; -import { Tag, TagColor } from 'twenty-ui/components'; const StyledContainer = styled.div` align-items: center; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectControlContainer.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectControlContainer.tsx new file mode 100644 index 000000000..6842b3136 --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectControlContainer.tsx @@ -0,0 +1,19 @@ +import styled from '@emotion/styled'; + +const StyledControlContainer = styled.div<{ cursor: string }>` + align-items: center; + background-color: ${({ theme }) => theme.background.transparent.lighter}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + box-sizing: border-box; + border-radius: ${({ theme }) => theme.border.radius.sm}; + color: ${({ theme }) => theme.font.color.primary}; + cursor: ${({ cursor }) => cursor}; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; + height: ${({ theme }) => theme.spacing(8)}; + justify-content: space-between; + padding: 0 ${({ theme }) => theme.spacing(2)}; + width: 100%; +`; + +export const SubMatchingSelectControlContainer = StyledControlContainer; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectDropdownButton.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectDropdownButton.tsx new file mode 100644 index 000000000..cf81bf33d --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectDropdownButton.tsx @@ -0,0 +1,54 @@ +import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal'; +import { SubMatchingSelectControlContainer } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectControlContainer'; +import { + SpreadsheetMatchedSelectColumn, + SpreadsheetMatchedSelectOptionsColumn, +} from '@/spreadsheet-import/types/SpreadsheetColumn'; + +import { SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/SpreadsheetMatchedOptions'; +import { getFieldOptions } from '@/spreadsheet-import/utils/getFieldOptions'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { Tag, TagColor } from 'twenty-ui/components'; +import { IconChevronDown } from 'twenty-ui/display'; +import { SelectOption } from 'twenty-ui/input'; +const StyledIconChevronDown = styled(IconChevronDown)` + color: ${({ theme }) => theme.font.color.tertiary}; +`; + +export type SubMatchingSelectDropdownButtonProps = { + option: SpreadsheetMatchedOptions | Partial>; + column: + | SpreadsheetMatchedSelectColumn + | SpreadsheetMatchedSelectOptionsColumn; + placeholder: string; +}; + +export const SubMatchingSelectDropdownButton = ({ + option, + column, + placeholder, +}: SubMatchingSelectDropdownButtonProps) => { + const { openDropdown } = useDropdown(); + + const { fields } = useSpreadsheetImportInternal(); + const options = getFieldOptions(fields, column.value) as SelectOption[]; + const value = options.find((opt) => opt.value === option.value); + + const theme = useTheme(); + + return ( + openDropdown()} + id="control" + > + + + + ); +}; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectInput.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectInput.tsx new file mode 100644 index 000000000..26052628c --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectInput.tsx @@ -0,0 +1,72 @@ +import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; +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 { useMemo, useRef, useState } from 'react'; +import { TagColor } from 'twenty-ui/components'; +import { SelectOption } from 'twenty-ui/input'; +import { MenuItemSelectTag } from 'twenty-ui/navigation'; + +interface SubMatchingSelectInputProps { + onOptionSelected: (selectedOption: SelectOption) => void; + options: SelectOption[]; + defaultOption?: SelectOption; +} + +export const SubMatchingSelectInput = ({ + onOptionSelected, + options, + defaultOption, +}: SubMatchingSelectInputProps) => { + const containerRef = useRef(null); + + const [searchFilter, setSearchFilter] = useState(''); + const [selectedOption, setSelectedOption] = useState< + SelectOption | undefined + >(defaultOption); + + const optionsToSelect = useMemo( + () => + options.filter((option) => { + return ( + option.value !== selectedOption?.value && + option.label.toLowerCase().includes(searchFilter.toLowerCase()) + ); + }) || [], + [options, searchFilter, selectedOption?.value], + ); + + const optionsInDropDown = useMemo( + () => + selectedOption ? [selectedOption, ...optionsToSelect] : optionsToSelect, + [optionsToSelect, selectedOption], + ); + + const handleOptionChange = (option: SelectOption) => { + setSelectedOption(option); + onOptionSelected(option); + }; + + return ( + + setSearchFilter(e.target.value)} + autoFocus + /> + + + {optionsInDropDown.map((option) => ( + handleOptionChange(option)} + LeftIcon={option.Icon} + /> + ))} + + + ); +}; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRow.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRow.tsx new file mode 100644 index 000000000..3786c2733 --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRow.tsx @@ -0,0 +1,46 @@ +import { SubMatchingSelectRowLeftSelect } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowLeftSelect'; +import { SubMatchingSelectRowRightDropdown } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowRightDropdown'; +import { + SpreadsheetMatchedSelectColumn, + SpreadsheetMatchedSelectOptionsColumn, +} from '@/spreadsheet-import/types/SpreadsheetColumn'; +import { SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/SpreadsheetMatchedOptions'; +import styled from '@emotion/styled'; + +const StyledRowContainer = styled.div` + align-items: center; + display: flex; + gap: ${({ theme }) => theme.spacing(4)}; + justify-content: space-between; + padding-bottom: ${({ theme }) => theme.spacing(1)}; +`; + +interface SubMatchingSelectRowProps { + option: SpreadsheetMatchedOptions | Partial>; + column: + | SpreadsheetMatchedSelectColumn + | SpreadsheetMatchedSelectOptionsColumn; + onSubChange: (val: T, index: number, option: string) => void; + placeholder: string; + selectedOption?: + | SpreadsheetMatchedOptions + | Partial>; +} +export const SubMatchingSelectRow = ({ + option, + column, + onSubChange, + placeholder, +}: SubMatchingSelectRowProps) => { + return ( + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowLeftSelect.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowLeftSelect.tsx new file mode 100644 index 000000000..e08a5eb26 --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowLeftSelect.tsx @@ -0,0 +1,44 @@ +import { SubMatchingSelectControlContainer } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectControlContainer'; + +import { SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/SpreadsheetMatchedOptions'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { IconChevronDown } from 'twenty-ui/display'; + +const StyledIconChevronDown = styled(IconChevronDown)` + color: ${({ theme }) => theme.font.color.tertiary}; +`; + +const StyledLabel = styled.span` + color: ${({ theme }) => theme.font.color.primary}; + font-weight: ${({ theme }) => theme.font.weight.regular}; + font-size: ${({ theme }) => theme.font.size.md}; +`; + +const StyledControlLabel = styled.div` + align-items: center; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +export type SubMatchingSelectRowLeftSelectProps = { + option: SpreadsheetMatchedOptions | Partial>; +}; + +export const SubMatchingSelectRowLeftSelect = ({ + option, +}: SubMatchingSelectRowLeftSelectProps) => { + const theme = useTheme(); + + return ( + + + {option.entry} + + + + ); +}; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowRightDropdown.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowRightDropdown.tsx new file mode 100644 index 000000000..627c97078 --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowRightDropdown.tsx @@ -0,0 +1,75 @@ +import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal'; + +import { getFieldOptions } from '@/spreadsheet-import/utils/getFieldOptions'; + +import { SubMatchingSelectDropdownButton } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectDropdownButton'; +import { SubMatchingSelectInput } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectInput'; +import { + SpreadsheetMatchedSelectColumn, + SpreadsheetMatchedSelectOptionsColumn, +} from '@/spreadsheet-import/types/SpreadsheetColumn'; +import { SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/SpreadsheetMatchedOptions'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import styled from '@emotion/styled'; +import { SelectOption } from 'twenty-ui/input'; + +const StyledDropdownContainer = styled.div` + width: 100%; +`; + +interface SubMatchingSelectRowRightDropdownProps { + option: SpreadsheetMatchedOptions | Partial>; + column: + | SpreadsheetMatchedSelectColumn + | SpreadsheetMatchedSelectOptionsColumn; + onSubChange: (val: T, index: number, option: string) => void; + placeholder: string; + selectedOption?: + | SpreadsheetMatchedOptions + | Partial>; +} + +export const SubMatchingSelectRowRightDropdown = ({ + option, + column, + onSubChange, + placeholder, +}: SubMatchingSelectRowRightDropdownProps) => { + const dropdownId = `sub-matching-select-dropdown-${option.entry}`; + + const { closeDropdown } = useDropdown(dropdownId); + + const { fields } = useSpreadsheetImportInternal(); + const options = getFieldOptions(fields, column.value) as SelectOption[]; + const value = options.find((opt) => opt.value === option.value); + + const handleSelect = (selectedOption: SelectOption) => { + onSubChange(selectedOption.value as T, column.index, option.entry ?? ''); + closeDropdown(); + }; + + return ( + + + } + dropdownComponents={ + + } + /> + + ); +}; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx index 905f9dd95..a48838856 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx @@ -2,8 +2,8 @@ import styled from '@emotion/styled'; import { MatchColumnSelect } from '@/spreadsheet-import/components/MatchColumnSelect'; import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal'; -import { SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns'; import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType'; +import { SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns'; import { useLingui } from '@lingui/react/macro'; import { FieldMetadataType } from 'twenty-shared/types'; import { IconForbid } from 'twenty-ui/display'; @@ -75,7 +75,7 @@ export const TemplateColumn = ({ value={isIgnored ? ignoreValue : selectValue} onChange={(value) => onChange(value?.value as T, column.index)} options={selectOptions} - name={column.header} + columnIndex={column.index.toString()} /> ); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn.tsx index 3be1360cd..30c32438c 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn.tsx @@ -1,5 +1,5 @@ import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal'; -import { SubMatchingSelect } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelect'; +import { SubMatchingSelectRow } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRow'; import { UnmatchColumnBanner } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumnBanner'; import { SpreadsheetImportFields } from '@/spreadsheet-import/types'; import { SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn'; @@ -71,7 +71,7 @@ export const UnmatchColumn = ({ > {column.matchedOptions.map((option) => ( - void; }) => { const theme = useTheme(); + return ( - {message} - {buttonOnClick && ( - + {isDefined(buttonOnClick) ? ( + + {message} + + + ) : ( + {message} )} ); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx index be112ef7b..4c4daedef 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx @@ -3,13 +3,13 @@ import styled from '@emotion/styled'; import { Column, useRowSelection } from 'react-data-grid'; import { createPortal } from 'react-dom'; -import { MatchColumnSelect } from '@/spreadsheet-import/components/MatchColumnSelect'; import { ImportedStructuredRow, SpreadsheetImportFields, } from '@/spreadsheet-import/types'; import { TextInput } from '@/ui/input/components/TextInput'; +import { MatchColumnSelect } from '@/spreadsheet-import/components/MatchColumnSelect'; import { isDefined } from 'twenty-shared/utils'; import { ImportedStructuredRowMetadata } from '../types'; import { AppTooltip } from 'twenty-ui/display'; @@ -148,6 +148,7 @@ export const generateColumns = ( onRowChange({ ...row, [columnKey]: value?.value }, true); }} options={column.fieldType.options} + columnIndex={column.key} /> ); break; diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/Dialog.tsx b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/Dialog.tsx index 1f840c469..893958fdc 100644 --- a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/Dialog.tsx +++ b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/Dialog.tsx @@ -5,9 +5,10 @@ import { Key } from 'ts-key-enum'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { DialogHotkeyScope } from '../types/DialogHotkeyScope'; +import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices'; import { isDefined } from 'twenty-shared/utils'; import { Button } from 'twenty-ui/input'; +import { DialogHotkeyScope } from '../types/DialogHotkeyScope'; const StyledDialogOverlay = styled(motion.div)` align-items: center; @@ -19,7 +20,7 @@ const StyledDialogOverlay = styled(motion.div)` position: fixed; top: 0; width: 100vw; - z-index: 9999; + z-index: ${RootStackingContextZIndices.Dialog}; `; const StyledDialogContainer = styled(motion.div)` diff --git a/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBarProvider.tsx b/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBarProvider.tsx index 66a308b30..8c549a976 100644 --- a/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBarProvider.tsx +++ b/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBarProvider.tsx @@ -5,22 +5,23 @@ import { useSnackBarManagerScopedStates } from '@/ui/feedback/snack-bar-manager/ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices'; import { SnackBar } from './SnackBar'; import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; const StyledSnackBarContainer = styled.div` + bottom: ${({ theme }) => theme.spacing(3)}; display: flex; flex-direction: column; position: fixed; right: ${({ theme }) => theme.spacing(3)}; - bottom: ${({ theme }) => theme.spacing(3)}; - z-index: ${({ theme }) => theme.lastLayerZIndex}; + z-index: ${RootStackingContextZIndices.SnackBar}; @media (max-width: ${MOBILE_VIEWPORT}px) { - top: 0; bottom: auto; left: 0; right: 0; + top: 0; } `; diff --git a/packages/twenty-front/src/modules/ui/layout/constants/RootStackingContextZIndices.ts b/packages/twenty-front/src/modules/ui/layout/constants/RootStackingContextZIndices.ts new file mode 100644 index 000000000..508636c5e --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/constants/RootStackingContextZIndices.ts @@ -0,0 +1,24 @@ +/** + * Please read this article to understand why we use this enum : https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context + * + * It is important to keep track of the stacking contexts that are created on top of the root stacking context of the document. + * + * Right now we have to guess it by looking into the developer console + * + * This way we can avoid hazardous fidgeting with z-index CSS properties + * and having to look down the tree in the developer console to see which component is in the root stacking context or not + * + * Using an enum enforces a single z-index for each component in the root stacking context + * + * TODO: add the other remaining components that can appear in the root stacking context + */ +export enum RootStackingContextZIndices { + CommandMenu = 21, + CommandMenuButton = 22, + RootModalBackDrop = 39, + RootModal = 40, + DropdownPortal = 50, + Dialog = 9999, + SnackBar = 10002, + NotFound = 10001, +} diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownContent.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownContent.tsx index acbae1755..0a71df52b 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownContent.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownContent.tsx @@ -1,3 +1,4 @@ +import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useInternalHotkeyScopeManagement } from '@/ui/layout/dropdown/hooks/useInternalHotkeyScopeManagement'; @@ -23,7 +24,7 @@ import { Key } from 'ts-key-enum'; export const StyledDropdownContentContainer = styled.div` display: flex; - z-index: 30; + z-index: ${RootStackingContextZIndices.DropdownPortal}; `; export type DropdownContentProps = { diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx index c08f7f06e..ac3279a3d 100644 --- a/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx +++ b/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx @@ -1,3 +1,4 @@ +import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices'; import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; @@ -31,7 +32,7 @@ const StyledModalDiv = styled(motion.div)<{ }}; overflow-x: hidden; overflow-y: auto; - z-index: 10000; // should be higher than Backdrop's z-index + z-index: ${RootStackingContextZIndices.RootModal}; // should be higher than Backdrop's z-index width: ${({ isMobile, size, theme }) => { if (isMobile) return theme.modal.size.fullscreen; @@ -109,7 +110,7 @@ const StyledBackDrop = styled(motion.div)<{ position: fixed; top: 0; width: 100%; - z-index: 9999; + z-index: ${RootStackingContextZIndices.RootModalBackDrop}; user-select: none; `; diff --git a/packages/twenty-front/src/modules/ui/layout/page-header/components/PageHeaderToggleCommandMenuButton.tsx b/packages/twenty-front/src/modules/ui/layout/page-header/components/PageHeaderToggleCommandMenuButton.tsx index 5ca31270e..aa4b6aedc 100644 --- a/packages/twenty-front/src/modules/ui/layout/page-header/components/PageHeaderToggleCommandMenuButton.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page-header/components/PageHeaderToggleCommandMenuButton.tsx @@ -1,5 +1,6 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; +import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { i18n } from '@lingui/core'; @@ -11,7 +12,7 @@ import { AppTooltip, TooltipDelay, TooltipPosition } from 'twenty-ui/display'; import { getOsControlSymbol, useIsMobile } from 'twenty-ui/utilities'; const StyledButtonWrapper = styled.div` - z-index: 30; + z-index: ${RootStackingContextZIndices.CommandMenuButton}; `; const StyledTooltipWrapper = styled.div` diff --git a/packages/twenty-front/src/pages/not-found/NotFound.tsx b/packages/twenty-front/src/pages/not-found/NotFound.tsx index 38eba1fc9..2c16a20b3 100644 --- a/packages/twenty-front/src/pages/not-found/NotFound.tsx +++ b/packages/twenty-front/src/pages/not-found/NotFound.tsx @@ -2,6 +2,7 @@ import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/S import { AppPath } from '@/types/AppPath'; import { Trans, useLingui } from '@lingui/react/macro'; +import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices'; import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; import styled from '@emotion/styled'; import { @@ -26,7 +27,7 @@ const StyledBackDrop = styled.div` position: fixed; top: 0; width: 100%; - z-index: 10000; + z-index: ${RootStackingContextZIndices.NotFound}; `; const StyledButtonContainer = styled.div`