diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx index 6d33a4082..43f9e4faf 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx @@ -5,15 +5,10 @@ import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; -import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect'; import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect'; -const StyledContainer = styled.div` - position: relative; -`; - type MultipleFiltersDropdownContentProps = { filterDropdownId?: string; }; @@ -46,7 +41,7 @@ export const MultipleFiltersDropdownContent = ({ const shoudShowFilterInput = objectFilterDropdownFilterIsSelected; return ( - + <> {shoudShowFilterInput ? ( - + > ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx index 98b57174b..050fb9021 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx @@ -28,8 +28,13 @@ export const ObjectFilterDropdownFilterInput = ({ const { filterDefinitionUsedInDropdownState, selectedOperandInDropdownState, + isObjectFilterDropdownOperandSelectUnfoldedState, } = useFilterDropdown({ filterDropdownId }); + const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue( + isObjectFilterDropdownOperandSelectUnfoldedState, + ); + const filterDefinitionUsedInDropdown = useRecoilValue( filterDefinitionUsedInDropdownState, ); @@ -53,7 +58,9 @@ export const ObjectFilterDropdownFilterInput = ({ ViewFilterOperand.IsRelative, ].includes(selectedOperandInDropdown); - if (!isDefined(filterDefinitionUsedInDropdown)) { + const shouldHide = isObjectFilterDropdownOperandSelectUnfolded; + + if (shouldHide || !isDefined(filterDefinitionUsedInDropdown)) { return null; } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterOperandSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterOperandSelect.tsx index b603f0813..4aad6ec39 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterOperandSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterOperandSelect.tsx @@ -8,9 +8,7 @@ const StyledOperandSelectContainer = styled.div` background: ${({ theme }) => theme.background.secondary}; box-shadow: ${({ theme }) => theme.boxShadow.light}; border-radius: ${({ theme }) => theme.border.radius.md}; - left: 10px; - position: absolute; - top: 10px; + width: 100%; z-index: 1000; `; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx index f33bbf38d..7b05d5874 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx @@ -16,6 +16,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; +import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; @@ -35,7 +36,7 @@ export const StyledInput = styled.input` margin: 0; outline: none; padding: ${({ theme }) => theme.spacing(2)}; - height: 19px; + min-height: 19px; font-family: inherit; font-size: ${({ theme }) => theme.font.size.sm}; @@ -160,43 +161,45 @@ export const ObjectFilterDropdownFilterSelect = ({ setObjectFilterDropdownSearchInput(event.target.value) } /> - - - {visibleColumnsFilterDefinitions.map( - (visibleFilterDefinition, index) => ( - - - - ), - )} - - {shoudShowSeparator && } - - {hiddenColumnsFilterDefinitions.map( - (hiddenFilterDefinition, index) => ( - - - - ), - )} - - - {shouldShowAdvancedFilterButton && } + + + + {visibleColumnsFilterDefinitions.map( + (visibleFilterDefinition, index) => ( + + + + ), + )} + + {shoudShowSeparator && } + + {hiddenColumnsFilterDefinitions.map( + (hiddenFilterDefinition, index) => ( + + + + ), + )} + + + {shouldShowAdvancedFilterButton && } + > ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton.tsx index 3931c7654..1e21d36b1 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton.tsx @@ -10,17 +10,28 @@ export const ObjectFilterDropdownOperandButton = () => { const { selectedOperandInDropdownState, setIsObjectFilterDropdownOperandSelectUnfolded, + isObjectFilterDropdownOperandSelectUnfoldedState, } = useFilterDropdown(); const selectedOperandInDropdown = useRecoilValue( selectedOperandInDropdownState, ); + const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue( + isObjectFilterDropdownOperandSelectUnfoldedState, + ); + + const handleButtonClick = () => { + setIsObjectFilterDropdownOperandSelectUnfolded( + !isObjectFilterDropdownOperandSelectUnfolded, + ); + }; + return ( setIsObjectFilterDropdownOperandSelectUnfolded(true)} + onClick={handleButtonClick} > {getOperandLabel(selectedOperandInDropdown)} diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx index b2e2535ca..b0c1ff877 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx @@ -16,6 +16,7 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItemMultiSelect } from '@/ui/navigation/menu-item/components/MenuItemMultiSelect'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { isDefined } from '~/utils/isDefined'; export const EMPTY_FILTER_VALUE = ''; @@ -162,22 +163,24 @@ export const ObjectFilterDropdownOptionSelect = () => { } }} > - - {optionsInDropdown?.map((option) => ( - - handleMultipleOptionSelectChange(option, selected) - } - text={option.label} - color={option.color} - className="" - /> - ))} - - {showNoResult && } + + + {optionsInDropdown?.map((option) => ( + + handleMultipleOptionSelectChange(option, selected) + } + text={option.label} + color={option.color} + className="" + /> + ))} + + {showNoResult && } + ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx index f1e399c1a..4013db110 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx @@ -15,6 +15,7 @@ import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/Styl import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useContext } from 'react'; import { SORT_DIRECTIONS } from '../types/SortDirection'; @@ -42,17 +43,13 @@ export const StyledInput = styled.input` } `; -const StyledContainer = styled.div` - position: relative; -`; - const StyledSelectedSortDirectionContainer = styled.div` background: ${({ theme }) => theme.background.secondary}; box-shadow: ${({ theme }) => theme.boxShadow.light}; border-radius: ${({ theme }) => theme.border.radius.md}; - left: 10px; + position: absolute; - top: 10px; + top: 32px; width: 100%; z-index: 1000; `; @@ -166,21 +163,23 @@ export const ObjectSortDropdownButton = ({ )} - - setIsSortDirectionMenuUnfolded(true)} - > - {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} - - - setObjectSortDropdownSearchInput(event.target.value) - } - /> + + setIsSortDirectionMenuUnfolded(!isSortDirectionMenuUnfolded) + } + > + {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} + + + setObjectSortDropdownSearchInput(event.target.value) + } + /> + {visibleColumnsSortDefinitions.map( (visibleSortDefinition, index) => ( @@ -214,7 +213,7 @@ export const ObjectSortDropdownButton = ({ ), )} - + > } onClose={handleDropdownButtonClose} diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx index 0193e75da..c23b31bc2 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx @@ -21,7 +21,6 @@ import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmp const StyledDropdownMenu = styled(DropdownMenu)` left: -1px; - position: absolute; top: -1px; `; @@ -46,6 +45,7 @@ type MultiItemFieldInputProps = { }; // Todo: the API of this component does not look healthy: we have renderInput, renderItem, formatInput, ... +// This should be refactored with a hook instead that exposes those events in a context around this component and its children. export const MultiItemFieldInput = ({ items, onPersist, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldMenuItem.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldMenuItem.tsx index 383b47121..150efe882 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldMenuItem.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldMenuItem.tsx @@ -1,14 +1,11 @@ -import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; -import styled from '@emotion/styled'; -import { useEffect, useState } from 'react'; +import { MenuItemWithOptionDropdown } from '@/ui/navigation/menu-item/components/MenuItemWithOptionDropdown'; +import { useState } from 'react'; import { IconBookmark, IconBookmarkPlus, - IconComponent, - IconDotsVertical, IconPencil, IconTrash, } from 'twenty-ui'; @@ -24,12 +21,6 @@ type MultiItemFieldMenuItemProps = { hasPrimaryButton?: boolean; }; -const StyledIconBookmark = styled(IconBookmark)` - color: ${({ theme }) => theme.font.color.light}; - height: ${({ theme }) => theme.icon.size.sm}px; - width: ${({ theme }) => theme.icon.size.sm}px; -`; - export const MultiItemFieldMenuItem = ({ dropdownId, isPrimary, @@ -47,66 +38,51 @@ export const MultiItemFieldMenuItem = ({ const handleMouseLeave = () => setIsHovered(false); const handleDeleteClick = () => { + closeDropdown(); setIsHovered(false); onDelete?.(); }; - useEffect(() => { - if (isDropdownOpen) { - return () => closeDropdown(); - } - }, [closeDropdown, isDropdownOpen]); + const handleSetAsPrimaryClick = () => { + closeDropdown(); + onSetAsPrimary?.(); + }; + + const handleEditClick = () => { + closeDropdown(); + onEdit?.(); + }; return ( - } isIconDisplayedOnHoverOnly={!isPrimary && !isDropdownOpen} - iconButtons={[ - { - Wrapper: isHovered - ? ({ iconButton }) => ( - - {hasPrimaryButton && !isPrimary && ( - - )} - - - - } - /> - ) - : undefined, - Icon: - isPrimary && !isHovered - ? (StyledIconBookmark as IconComponent) - : IconDotsVertical, - accent: 'tertiary', - onClick: isHovered ? () => {} : undefined, - }, - ]} + RightIcon={isHovered ? null : IconBookmark} + dropdownId={dropdownId} + dropdownContent={ + + {hasPrimaryButton && !isPrimary && ( + + )} + + + + } /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx index 0375ab340..4fa2394d6 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx @@ -40,6 +40,7 @@ import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemN import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection'; import { ViewGroupsVisibilityDropdownSection } from '@/views/components/ViewGroupsVisibilityDropdownSection'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; @@ -259,15 +260,17 @@ export const RecordIndexOptionsDropdownContent = ({ Fields - + + + {hiddenRecordFields.length > 0 && ( - <> + - > + )} diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownButton.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownButton.tsx index 061820250..ba7e722d3 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownButton.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownButton.tsx @@ -74,8 +74,8 @@ export const PhoneCountryPickerDropdownButton = ({ ); const handleChange = (countryCode: string) => { - onChange(countryCode); closeDropdown(); + onChange(countryCode); }; const countries = useCountries(); @@ -89,7 +89,6 @@ export const PhoneCountryPickerDropdownButton = ({ return ( { const containerRef = useRef(null); - const { - isDropdownOpen, - toggleDropdown, - closeDropdown, - dropdownWidth, - setDropdownPlacement, - } = useDropdown(dropdownId); + const { isDropdownOpen, toggleDropdown, closeDropdown, dropdownWidth } = + useDropdown(dropdownId); const offsetMiddlewares = []; @@ -83,18 +78,21 @@ export const Dropdown = ({ offsetMiddlewares.push(offset({ mainAxis: dropdownOffset.y })); } - const { refs, floatingStyles, placement } = useFloating({ + const { refs, floatingStyles } = useFloating({ placement: dropdownPlacement, middleware: [ flip(), size({ - padding: 12 + 20, // 12px for padding bottom, 20px for dropdown bottom margin target + padding: 32, apply: ({ availableHeight, elements }) => { elements.floating.style.maxHeight = availableHeight >= elements.floating.scrollHeight ? '' : `${availableHeight}px`; + + elements.floating.style.height = 'auto'; }, + boundary: document.querySelector('#root') ?? undefined, }), ...offsetMiddlewares, ], @@ -102,10 +100,6 @@ export const Dropdown = ({ strategy: dropdownStrategy, }); - useEffect(() => { - setDropdownPlacement(placement); - }, [placement, setDropdownPlacement]); - const handleHotkeyTriggered = () => { toggleDropdown(); }; diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenu.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenu.tsx index 8111b291b..778cc2f08 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenu.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenu.tsx @@ -23,10 +23,10 @@ const StyledDropdownMenu = styled.div<{ display: flex; + height: 100%; + flex-direction: column; z-index: 30; - overflow-y: auto; - overflow-x: hidden; width: ${({ width = 160 }) => typeof width === 'number' ? `${width}px` : width}; `; diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuItemsContainer.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuItemsContainer.tsx index 2db2ca5df..8558249f0 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuItemsContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuItemsContainer.tsx @@ -13,7 +13,6 @@ const StyledDropdownMenuItemsExternalContainer = styled.div<{ flex-direction: column; gap: 2px; max-height: ${({ hasMaxHeight }) => (hasMaxHeight ? '188px' : 'none')}; - overflow-y: auto; padding: var(--padding); @@ -34,6 +33,8 @@ const StyledDropdownMenuItemsInternalContainer = styled.div` width: 100%; `; +// TODO: refactor this, the dropdown should handle the max height behavior + scroll with the size middleware +// We should instead create a DropdownMenuItemsContainerScrollable or take for granted that it is the default behavior export const DropdownMenuItemsContainer = ({ children, hasMaxHeight, diff --git a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemWithOptionDropdown.tsx b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemWithOptionDropdown.tsx new file mode 100644 index 000000000..a00ee075f --- /dev/null +++ b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemWithOptionDropdown.tsx @@ -0,0 +1,105 @@ +import { useTheme } from '@emotion/react'; +import { FunctionComponent, MouseEvent, ReactElement, ReactNode } from 'react'; +import { + IconChevronRight, + IconComponent, + IconDotsVertical, + LightIconButton, + LightIconButtonProps, +} from 'twenty-ui'; + +import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent'; +import { + StyledHoverableMenuItemBase, + StyledMenuItemLeftContent, +} from '../internals/components/StyledMenuItemBase'; +import { MenuItemAccent } from '../types/MenuItemAccent'; + +export type MenuItemIconButton = { + Wrapper?: FunctionComponent<{ iconButton: ReactElement }>; + Icon: IconComponent; + accent?: LightIconButtonProps['accent']; + onClick?: (event: MouseEvent) => void; +}; + +export type MenuItemWithOptionDropdownProps = { + accent?: MenuItemAccent; + className?: string; + dropdownContent: ReactNode; + dropdownId: string; + isIconDisplayedOnHoverOnly?: boolean; + isTooltipOpen?: boolean; + LeftIcon?: IconComponent | null; + RightIcon?: IconComponent | null; + onClick?: (event: MouseEvent) => void; + onMouseEnter?: (event: MouseEvent) => void; + onMouseLeave?: (event: MouseEvent) => void; + testId?: string; + text: ReactNode; + hasSubMenu?: boolean; +}; + +// TODO: refactor this +export const MenuItemWithOptionDropdown = ({ + accent = 'default', + className, + isIconDisplayedOnHoverOnly = true, + dropdownContent, + dropdownId, + LeftIcon, + RightIcon, + onClick, + onMouseEnter, + onMouseLeave, + testId, + text, + hasSubMenu = false, +}: MenuItemWithOptionDropdownProps) => { + const theme = useTheme(); + + const handleMenuItemClick = (event: MouseEvent) => { + if (!onClick) return; + event.preventDefault(); + event.stopPropagation(); + + onClick?.(event); + }; + + return ( + + + + + + + } + dropdownComponents={dropdownContent} + dropdownId={dropdownId} + dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }} + disableBlur + /> + + {hasSubMenu && ( + + )} + + ); +};