From 5e43839efbafcdc4f52fa7fc7369352bfbfb7d63 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Thu, 3 Apr 2025 16:28:15 +0200 Subject: [PATCH] Fix CSV import select field matching (#11361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR fixes a bug that prevented to do the matching of an imported CSV file that contains a SELECT type column. Fixes https://github.com/twentyhq/twenty/issues/11220 ## Stacking context improvement During the development it was clear that we lacked a reliable way to understand our own z indices for components like modal, portaled dropdown, overlay background, etc. So in this PR we introduce a new enum RootStackingContextZIndices, this enum allows to keep track of our root stacking context component z-index, and because it is an enum, it prevents any conflict. See https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context for reference. ## Component cleaning Components have been reorganized in a SubMatchingSelectRow component The Dropdown component has been used to replace the SelectInput component which doesn't fit this use case because we are not in a cell, we just need a simple standalone dropdown, though it would be interesting to extract the UI part of the SelectInput, to share it here, the benefit is not obvious since we already have good shared components like Tag and Dropdown to implement this specific use case. --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait --- .../components/CommandMenuContainer.tsx | 3 +- .../components/MatchColumnSelect.tsx | 176 +++++++----------- .../components/SubMatchingSelect.tsx | 2 +- .../SubMatchingSelectControlContainer.tsx | 19 ++ .../SubMatchingSelectDropdownButton.tsx | 54 ++++++ .../components/SubMatchingSelectInput.tsx | 72 +++++++ .../components/SubMatchingSelectRow.tsx | 46 +++++ .../SubMatchingSelectRowLeftSelect.tsx | 44 +++++ .../SubMatchingSelectRowRightDropdown.tsx | 75 ++++++++ .../components/TemplateColumn.tsx | 4 +- .../components/UnmatchColumn.tsx | 4 +- .../components/UnmatchColumnBanner.tsx | 29 ++- .../ValidationStep/components/columns.tsx | 3 +- .../dialog-manager/components/Dialog.tsx | 5 +- .../components/SnackBarProvider.tsx | 7 +- .../constants/RootStackingContextZIndices.ts | 24 +++ .../dropdown/components/DropdownContent.tsx | 3 +- .../ui/layout/modal/components/Modal.tsx | 5 +- .../PageHeaderToggleCommandMenuButton.tsx | 3 +- .../src/pages/not-found/NotFound.tsx | 3 +- 20 files changed, 446 insertions(+), 135 deletions(-) create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectControlContainer.tsx create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectDropdownButton.tsx create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectInput.tsx create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRow.tsx create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowLeftSelect.tsx create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowRightDropdown.tsx create mode 100644 packages/twenty-front/src/modules/ui/layout/constants/RootStackingContextZIndices.ts 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`