diff --git a/front/src/modules/companies/components/CompanyProgressPicker.tsx b/front/src/modules/companies/components/CompanyProgressPicker.tsx index 2e65ae89f..26ddecb0d 100644 --- a/front/src/modules/companies/components/CompanyProgressPicker.tsx +++ b/front/src/modules/companies/components/CompanyProgressPicker.tsx @@ -3,17 +3,16 @@ import { useTheme } from '@emotion/react'; import { useRecoilState } from 'recoil'; import { currentPipelineState } from '@/pipeline/states/currentPipelineState'; -import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; -import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; +import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { IconChevronDown } from '@/ui/icon'; import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase'; import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; -import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery'; @@ -48,17 +47,6 @@ export function CompanyProgressPicker({ string | null >(null); - useListenClickOutside({ - refs: [containerRef], - callback: (event) => { - event.stopImmediatePropagation(); - event.stopPropagation(); - event.preventDefault(); - - onCancel?.(); - }, - }); - const theme = useTheme(); const [currentPipeline] = useRecoilState(currentPipelineState); @@ -94,12 +82,12 @@ export function CompanyProgressPicker({ ); return ( - {isProgressSelectionUnfolded ? ( - + {currentPipelineStages.map((pipelineStage, index) => ( ))} - + ) : ( <> {selectedPipelineStage?.name} - + - + )} - + ); } diff --git a/front/src/modules/people/components/PeopleCard.tsx b/front/src/modules/people/components/PeopleCard.tsx index be057c03c..7c73ca569 100644 --- a/front/src/modules/people/components/PeopleCard.tsx +++ b/front/src/modules/people/components/PeopleCard.tsx @@ -7,9 +7,9 @@ import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react'; import { IconDotsVertical, IconLinkOff, IconTrash } from '@tabler/icons-react'; import { IconButton } from '@/ui/button/components/IconButton'; -import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { Avatar } from '@/users/components/Avatar'; import { @@ -172,8 +172,10 @@ export function PeopleCard({ icon={} /> {isOptionsOpen && ( - - e.stopPropagation()}> + + e.stopPropagation()} + > } size="small" /> Detach relation @@ -186,8 +188,8 @@ export function PeopleCard({ /> Delete person - - + + )} )} diff --git a/front/src/modules/pipeline/components/PipelineAddButton.tsx b/front/src/modules/pipeline/components/PipelineAddButton.tsx index b36fcf84c..d51940376 100644 --- a/front/src/modules/pipeline/components/PipelineAddButton.tsx +++ b/front/src/modules/pipeline/components/PipelineAddButton.tsx @@ -12,7 +12,9 @@ import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar'; export function PipelineAddButton() { const { enqueueSnackBar } = useSnackBar(); - const { closeDropdownButton } = useDropdownButton(); + const { closeDropdownButton, toggleDropdownButton } = useDropdownButton({ + key: 'add-company-progress', + }); const createCompanyProgress = useCreateCompanyProgress(); @@ -51,6 +53,7 @@ export function PipelineAddButton() { return ( } @@ -58,6 +61,7 @@ export function PipelineAddButton() { data-testid="add-company-progress-button" textColor={'secondary'} variant="border" + onClick={toggleDropdownButton} /> } dropdownComponents={ @@ -71,7 +75,7 @@ export function PipelineAddButton() { key: 'c', scope: PageHotkeyScope.OpportunitiesPage, }} - dropdownScopeToSet={{ + dropdownHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker, }} /> diff --git a/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx b/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx index 704bcf72c..4f2245dfc 100644 --- a/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx +++ b/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx @@ -14,12 +14,12 @@ import debounce from 'lodash.debounce'; import { ReadonlyDeep } from 'type-fest'; import type { SelectOption } from '@/spreadsheet-import/types'; -import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; -import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; +import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { IconChevronDown } from '@/ui/icon'; import { AppTooltip } from '@/ui/tooltip/AppTooltip'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; @@ -166,7 +166,7 @@ export const MatchColumnSelect = ({ {isOpen && createPortal( - @@ -175,8 +175,8 @@ export const MatchColumnSelect = ({ onChange={handleFilterChange} autoFocus /> - - + + {options?.map((option) => ( <> No result )} - - + + , document.body, )} diff --git a/front/src/modules/ui/board/components/BoardColumnEditTitleMenu.tsx b/front/src/modules/ui/board/components/BoardColumnEditTitleMenu.tsx index ed2514c35..7a67f37b5 100644 --- a/front/src/modules/ui/board/components/BoardColumnEditTitleMenu.tsx +++ b/front/src/modules/ui/board/components/BoardColumnEditTitleMenu.tsx @@ -1,9 +1,9 @@ import { ChangeEvent, useState } from 'react'; import styled from '@emotion/styled'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; -import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; +import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { textInputStyle } from '@/ui/theme/constants/effects'; import { debounce } from '~/utils/debounce'; @@ -75,7 +75,7 @@ export function BoardColumnEditTitleMenu({ debouncedOnUpdateTitle(event.target.value); }; return ( - + - + {COLOR_OPTIONS.map((colorOption) => ( ))} - + ); } diff --git a/front/src/modules/ui/board/components/BoardColumnMenu.tsx b/front/src/modules/ui/board/components/BoardColumnMenu.tsx index 330570435..679ab1df4 100644 --- a/front/src/modules/ui/board/components/BoardColumnMenu.tsx +++ b/front/src/modules/ui/board/components/BoardColumnMenu.tsx @@ -3,9 +3,9 @@ import styled from '@emotion/styled'; import { IconPencil } from '@tabler/icons-react'; import { Key } from 'ts-key-enum'; -import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { icon } from '@/ui/theme/constants/icon'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; @@ -50,14 +50,14 @@ export function BoardColumnMenu({ return ( - + {openMenu === 'actions' && ( - + setOpenMenu('title')}> Rename - + )} {openMenu === 'title' && ( )} - + ); } diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx new file mode 100644 index 000000000..ca9ff0732 --- /dev/null +++ b/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx @@ -0,0 +1,14 @@ +import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; + +import { BoardOptionsDropdownButton } from './BoardOptionsDropdownButton'; +import { BoardOptionsDropdownContent } from './BoardOptionsDropdownContent'; + +export function BoardOptionsDropdown() { + return ( + } + dropdownComponents={} + > + ); +} diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdownButton.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdownButton.tsx new file mode 100644 index 000000000..6b9f6223e --- /dev/null +++ b/front/src/modules/ui/board/components/BoardOptionsDropdownButton.tsx @@ -0,0 +1,21 @@ +import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton'; +import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; + +export function BoardOptionsDropdownButton() { + const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({ + key: 'options', + }); + + function handleClick() { + toggleDropdownButton(); + } + + return ( + + Options + + ); +} diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx new file mode 100644 index 000000000..9d06a33ee --- /dev/null +++ b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx @@ -0,0 +1,55 @@ +import { useState } from 'react'; +import { useTheme } from '@emotion/react'; + +import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; +import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; +import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; +import { IconChevronLeft } from '@/ui/icon'; + +type BoardOptionsDropdownMenu = 'options' | 'fields'; + +export function BoardOptionsDropdownContent() { + const theme = useTheme(); + + const [menuShown, setMenuShown] = + useState('options'); + + function handleFieldsClick() { + setMenuShown('fields'); + } + + function handleMenuHeaderClick() { + setMenuShown('options'); + } + + return ( + + {menuShown === 'options' ? ( + <> + Options + + + + Fields + + + + ) : ( + menuShown === 'fields' && ( + <> + } + onClick={handleMenuHeaderClick} + > + Fields + + + {} + + ) + )} + + ); +} diff --git a/front/src/modules/ui/context-menu/components/ContextMenu.tsx b/front/src/modules/ui/context-menu/components/ContextMenu.tsx index 66276a0c0..ba439558b 100644 --- a/front/src/modules/ui/context-menu/components/ContextMenu.tsx +++ b/front/src/modules/ui/context-menu/components/ContextMenu.tsx @@ -4,8 +4,8 @@ import { useRecoilValue, useSetRecoilState } from 'recoil'; import { actionBarOpenState } from '@/ui/action-bar/states/actionBarIsOpenState'; import { contextMenuPositionState } from '@/ui/context-menu/states/contextMenuPositionState'; -import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { contextMenuEntriesState } from '../states/contextMenuEntriesState'; @@ -60,11 +60,11 @@ export function ContextMenu({ selectedIds }: OwnProps) { } return ( - - + + {contextMenuEntries} - - + + ); } diff --git a/front/src/modules/ui/dropdown/components/DropdownButton.tsx b/front/src/modules/ui/dropdown/components/DropdownButton.tsx index d26639b69..81cfb9452 100644 --- a/front/src/modules/ui/dropdown/components/DropdownButton.tsx +++ b/front/src/modules/ui/dropdown/components/DropdownButton.tsx @@ -1,10 +1,16 @@ +import { useEffect, useRef } from 'react'; import { Keys } from 'react-hotkeys-hook'; import styled from '@emotion/styled'; -import { flip, offset, useFloating } from '@floating-ui/react'; +import { flip, offset, Placement, useFloating } from '@floating-ui/react'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { useRecoilScopedFamilyState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedFamilyState'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { useDropdownButton } from '../hooks/useDropdownButton'; +import { dropdownButtonCustomHotkeyScopeScopedFamilyState } from '../states/dropdownButtonCustomHotkeyScopeScopedFamilyState'; +import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext'; import { HotkeyEffect } from './HotkeyEffect'; @@ -16,38 +22,74 @@ const StyledContainer = styled.div` type OwnProps = { buttonComponents: JSX.Element | JSX.Element[]; dropdownComponents: JSX.Element | JSX.Element[]; + dropdownKey: string; hotkey?: { key: Keys; scope: string; }; - dropdownScopeToSet?: HotkeyScope; + dropdownHotkeyScope?: HotkeyScope; + dropdownPlacement?: Placement; }; export function DropdownButton({ buttonComponents, dropdownComponents, + dropdownKey, hotkey, - dropdownScopeToSet, + dropdownHotkeyScope, + dropdownPlacement = 'bottom-end', }: OwnProps) { - const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton(); + const containerRef = useRef(null); + + const { isDropdownButtonOpen, toggleDropdownButton, closeDropdownButton } = + useDropdownButton({ + key: dropdownKey, + }); const { refs, floatingStyles } = useFloating({ - placement: 'bottom-end', + placement: dropdownPlacement, middleware: [flip(), offset()], }); - function handleButtonClick() { - toggleDropdownButton(dropdownScopeToSet); + function handleHotkeyTriggered() { + toggleDropdownButton(); } + useListenClickOutside({ + refs: [containerRef], + callback: () => { + if (isDropdownButtonOpen) { + closeDropdownButton(); + } + }, + }); + + const [dropdownButtonCustomHotkeyScope, setDropdownButtonCustomHotkeyScope] = + useRecoilScopedFamilyState( + dropdownButtonCustomHotkeyScopeScopedFamilyState, + dropdownKey, + DropdownRecoilScopeContext, + ); + + useEffect(() => { + if (!isDeeplyEqual(dropdownButtonCustomHotkeyScope, dropdownHotkeyScope)) { + setDropdownButtonCustomHotkeyScope(dropdownHotkeyScope); + } + }, [ + setDropdownButtonCustomHotkeyScope, + dropdownHotkeyScope, + dropdownButtonCustomHotkeyScope, + ]); + return ( - + {hotkey && ( - + )} -
- {buttonComponents} -
+
{buttonComponents}
{isDropdownButtonOpen && (
{dropdownComponents} diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx index e35b14707..f3a7a49d3 100644 --- a/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx +++ b/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx @@ -44,15 +44,19 @@ type DropdownMenuHeaderProps = ComponentProps<'li'> & { endIcon?: ReactElement; }; -export const DropdownMenuHeader = ({ +export function DropdownMenuHeader({ children, startIcon, endIcon, ...props -}: DropdownMenuHeaderProps) => ( - - {startIcon && {startIcon}} - {children} - {endIcon && {endIcon}} - -); +}: DropdownMenuHeaderProps) { + return ( + + {startIcon && ( + {startIcon} + )} + {children} + {endIcon && {endIcon}} + + ); +} diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx index a6fc58420..578e17fab 100644 --- a/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx +++ b/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx @@ -58,22 +58,24 @@ export type DropdownMenuItemProps = ComponentProps<'li'> & { accent?: DropdownMenuItemAccent; }; -export const DropdownMenuItem = ({ +export function DropdownMenuItem({ actions, children, accent = 'regular', ...props -}: DropdownMenuItemProps) => ( - - {children} - {actions && ( - - {actions} - - )} - -); +}: DropdownMenuItemProps) { + return ( + + {children} + {actions && ( + + {actions} + + )} + + ); +} diff --git a/front/src/modules/ui/dropdown/components/DropdownMenu.tsx b/front/src/modules/ui/dropdown/components/StyledDropdownMenu.tsx similarity index 83% rename from front/src/modules/ui/dropdown/components/DropdownMenu.tsx rename to front/src/modules/ui/dropdown/components/StyledDropdownMenu.tsx index bb6b8a608..9dd444de3 100644 --- a/front/src/modules/ui/dropdown/components/DropdownMenu.tsx +++ b/front/src/modules/ui/dropdown/components/StyledDropdownMenu.tsx @@ -1,7 +1,6 @@ -/* eslint-disable twenty/styled-components-prefixed-with-styled */ import styled from '@emotion/styled'; -export const DropdownMenu = styled.div<{ +export const StyledDropdownMenu = styled.div<{ disableBlur?: boolean; width?: number; }>` diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuItemsContainer.tsx b/front/src/modules/ui/dropdown/components/StyledDropdownMenuItemsContainer.tsx similarity index 75% rename from front/src/modules/ui/dropdown/components/DropdownMenuItemsContainer.tsx rename to front/src/modules/ui/dropdown/components/StyledDropdownMenuItemsContainer.tsx index 02d562363..0425bc1a9 100644 --- a/front/src/modules/ui/dropdown/components/DropdownMenuItemsContainer.tsx +++ b/front/src/modules/ui/dropdown/components/StyledDropdownMenuItemsContainer.tsx @@ -1,7 +1,6 @@ -/* eslint-disable twenty/styled-components-prefixed-with-styled */ import styled from '@emotion/styled'; -export const DropdownMenuItemsContainer = styled.div<{ +export const StyledDropdownMenuItemsContainer = styled.div<{ hasMaxHeight?: boolean; }>` --padding: ${({ theme }) => theme.spacing(1)}; diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuSeparator.tsx b/front/src/modules/ui/dropdown/components/StyledDropdownMenuSeparator.tsx similarity index 54% rename from front/src/modules/ui/dropdown/components/DropdownMenuSeparator.tsx rename to front/src/modules/ui/dropdown/components/StyledDropdownMenuSeparator.tsx index 88b2fec72..aee2b5c19 100644 --- a/front/src/modules/ui/dropdown/components/DropdownMenuSeparator.tsx +++ b/front/src/modules/ui/dropdown/components/StyledDropdownMenuSeparator.tsx @@ -1,7 +1,6 @@ -/* eslint-disable twenty/styled-components-prefixed-with-styled */ import styled from '@emotion/styled'; -export const DropdownMenuSeparator = styled.div` +export const StyledDropdownMenuSeparator = styled.div` background-color: ${({ theme }) => theme.border.color.light}; height: 1px; diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuSubheader.tsx b/front/src/modules/ui/dropdown/components/StyledDropdownMenuSubheader.tsx similarity index 77% rename from front/src/modules/ui/dropdown/components/DropdownMenuSubheader.tsx rename to front/src/modules/ui/dropdown/components/StyledDropdownMenuSubheader.tsx index e0fb1b625..c659e3dc6 100644 --- a/front/src/modules/ui/dropdown/components/DropdownMenuSubheader.tsx +++ b/front/src/modules/ui/dropdown/components/StyledDropdownMenuSubheader.tsx @@ -1,7 +1,6 @@ -/* eslint-disable twenty/styled-components-prefixed-with-styled */ import styled from '@emotion/styled'; -export const DropdownMenuSubheader = styled.div` +export const StyledDropdownMenuSubheader = styled.div` background-color: ${({ theme }) => theme.background.transparent.lighter}; color: ${({ theme }) => theme.font.color.light}; font-size: ${({ theme }) => theme.font.size.xxs}; diff --git a/front/src/modules/ui/dropdown/components/StyledHeaderDropdownButton.tsx b/front/src/modules/ui/dropdown/components/StyledHeaderDropdownButton.tsx new file mode 100644 index 000000000..972e57330 --- /dev/null +++ b/front/src/modules/ui/dropdown/components/StyledHeaderDropdownButton.tsx @@ -0,0 +1,27 @@ +import styled from '@emotion/styled'; + +type StyledDropdownButtonProps = { + isUnfolded?: boolean; + isActive?: boolean; +}; + +export const StyledHeaderDropdownButton = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.primary}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + color: ${({ isActive, theme, color }) => + color ?? (isActive ? theme.color.blue : 'none')}; + cursor: pointer; + display: flex; + filter: ${(props) => (props.isUnfolded ? 'brightness(0.95)' : 'none')}; + + padding: ${({ theme }) => theme.spacing(1)}; + padding-left: ${({ theme }) => theme.spacing(2)}; + + padding-right: ${({ theme }) => theme.spacing(2)}; + user-select: none; + + &:hover { + filter: brightness(0.95); + } +`; diff --git a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx index f6d81b024..0b27fef41 100644 --- a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx +++ b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx @@ -8,19 +8,19 @@ import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/ import { Avatar } from '@/users/components/Avatar'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; -import { DropdownMenu } from '../DropdownMenu'; import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem'; import { DropdownMenuHeader } from '../DropdownMenuHeader'; import { DropdownMenuInput } from '../DropdownMenuInput'; import { DropdownMenuItem } from '../DropdownMenuItem'; -import { DropdownMenuItemsContainer } from '../DropdownMenuItemsContainer'; import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem'; -import { DropdownMenuSeparator } from '../DropdownMenuSeparator'; -import { DropdownMenuSubheader } from '../DropdownMenuSubheader'; +import { StyledDropdownMenu } from '../StyledDropdownMenu'; +import { StyledDropdownMenuItemsContainer } from '../StyledDropdownMenuItemsContainer'; +import { StyledDropdownMenuSeparator } from '../StyledDropdownMenuSeparator'; +import { StyledDropdownMenuSubheader } from '../StyledDropdownMenuSubheader'; -const meta: Meta = { +const meta: Meta = { title: 'UI/Dropdown/DropdownMenu', - component: DropdownMenu, + component: StyledDropdownMenu, decorators: [ComponentDecorator], argTypes: { as: { table: { disable: true } }, @@ -29,7 +29,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; const FakeContentBelow = () => (
@@ -156,9 +156,9 @@ const FakeCheckableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => { export const Empty: Story = { render: (args) => ( - + - + ), }; @@ -179,60 +179,60 @@ export const WithContentBelow: Story = { export const SimpleMenuItem: Story = { ...WithContentBelow, render: (args) => ( - - + + {mockSelectArray.map(({ name }) => ( {name} ))} - - + + ), }; export const WithHeaders: Story = { ...WithContentBelow, render: (args) => ( - + Header - - Subheader 1 - + + Subheader 1 + {mockSelectArray.slice(0, 3).map(({ name }) => ( {name} ))} - - - Subheader 2 - + + + Subheader 2 + {mockSelectArray.slice(3).map(({ name }) => ( {name} ))} - - + + ), }; export const WithIcons: Story = { ...WithContentBelow, render: (args) => ( - - + + {mockSelectArray.map(({ name }) => ( {name} ))} - - + + ), }; export const WithActions: Story = { ...WithContentBelow, render: (args) => ( - - + + {mockSelectArray.map(({ name }, index) => ( ))} - - + + ), parameters: { pseudo: { hover: ['.hover'] }, @@ -255,71 +255,71 @@ export const WithActions: Story = { export const LoadingMenu: Story = { ...WithContentBelow, render: () => ( - + - - + + - - + + ), }; export const Search: Story = { ...WithContentBelow, render: (args) => ( - + - - + + {mockSelectArray.map(({ name }) => ( {name} ))} - - + + ), }; export const SelectableMenuItem: Story = { ...WithContentBelow, render: (args) => ( - - + + - - + + ), }; export const SelectableMenuItemWithAvatar: Story = { ...WithContentBelow, render: (args) => ( - - + + - - + + ), }; export const CheckableMenuItem: Story = { ...WithContentBelow, render: (args) => ( - - + + - - + + ), }; export const CheckableMenuItemWithAvatar: Story = { ...WithContentBelow, render: (args) => ( - - + + - - + + ), }; diff --git a/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts b/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts index e92f1f2eb..c921c3dbd 100644 --- a/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts +++ b/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts @@ -1,17 +1,27 @@ import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; -import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; +import { useRecoilScopedFamilyState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedFamilyState'; -import { isDropdownButtonOpenScopedState } from '../states/isDropdownButtonOpenScopedState'; +import { dropdownButtonCustomHotkeyScopeScopedFamilyState } from '../states/dropdownButtonCustomHotkeyScopeScopedFamilyState'; +import { isDropdownButtonOpenScopedFamilyState } from '../states/isDropdownButtonOpenScopedFamilyState'; +import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext'; -export function useDropdownButton() { +export function useDropdownButton({ key }: { key: string }) { const { setHotkeyScopeAndMemorizePreviousScope, goBackToPreviousHotkeyScope, } = usePreviousHotkeyScope(); - const [isDropdownButtonOpen, setIsDropdownButtonOpen] = useRecoilScopedState( - isDropdownButtonOpenScopedState, + const [isDropdownButtonOpen, setIsDropdownButtonOpen] = + useRecoilScopedFamilyState( + isDropdownButtonOpenScopedFamilyState, + key, + DropdownRecoilScopeContext, + ); + + const [dropdownButtonCustomHotkeyScope] = useRecoilScopedFamilyState( + dropdownButtonCustomHotkeyScopeScopedFamilyState, + key, + DropdownRecoilScopeContext, ); function closeDropdownButton() { @@ -19,22 +29,22 @@ export function useDropdownButton() { setIsDropdownButtonOpen(false); } - function openDropdownButton(hotkeyScopeToSet?: HotkeyScope) { + function openDropdownButton() { setIsDropdownButtonOpen(true); - if (hotkeyScopeToSet) { + if (dropdownButtonCustomHotkeyScope) { setHotkeyScopeAndMemorizePreviousScope( - hotkeyScopeToSet.scope, - hotkeyScopeToSet.customScopes, + dropdownButtonCustomHotkeyScope.scope, + dropdownButtonCustomHotkeyScope.customScopes, ); } } - function toggleDropdownButton(hotkeyScopeToSet?: HotkeyScope) { + function toggleDropdownButton() { if (isDropdownButtonOpen) { closeDropdownButton(); } else { - openDropdownButton(hotkeyScopeToSet); + openDropdownButton(); } } diff --git a/front/src/modules/ui/dropdown/states/dropdownButtonCustomHotkeyScopeScopedFamilyState.ts b/front/src/modules/ui/dropdown/states/dropdownButtonCustomHotkeyScopeScopedFamilyState.ts new file mode 100644 index 000000000..2e73d7f82 --- /dev/null +++ b/front/src/modules/ui/dropdown/states/dropdownButtonCustomHotkeyScopeScopedFamilyState.ts @@ -0,0 +1,11 @@ +import { atomFamily } from 'recoil'; + +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; + +export const dropdownButtonCustomHotkeyScopeScopedFamilyState = atomFamily< + HotkeyScope | null | undefined, + string +>({ + key: 'dropdownButtonCustomHotkeyScopeScopedState', + default: null, +}); diff --git a/front/src/modules/ui/dropdown/states/isDropdownButtonOpenScopedState.ts b/front/src/modules/ui/dropdown/states/isDropdownButtonOpenScopedFamilyState.ts similarity index 53% rename from front/src/modules/ui/dropdown/states/isDropdownButtonOpenScopedState.ts rename to front/src/modules/ui/dropdown/states/isDropdownButtonOpenScopedFamilyState.ts index a46b7befe..c81d0173f 100644 --- a/front/src/modules/ui/dropdown/states/isDropdownButtonOpenScopedState.ts +++ b/front/src/modules/ui/dropdown/states/isDropdownButtonOpenScopedFamilyState.ts @@ -1,6 +1,9 @@ import { atomFamily } from 'recoil'; -export const isDropdownButtonOpenScopedState = atomFamily({ +export const isDropdownButtonOpenScopedFamilyState = atomFamily< + boolean, + string +>({ key: 'isDropdownButtonOpenScopedState', default: false, }); diff --git a/front/src/modules/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext.ts b/front/src/modules/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext.ts new file mode 100644 index 000000000..139778002 --- /dev/null +++ b/front/src/modules/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const DropdownRecoilScopeContext = createContext(null); diff --git a/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx b/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx index 336aca561..fa5df064f 100644 --- a/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx +++ b/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx @@ -1,7 +1,7 @@ import { type HTMLAttributes, useRef } from 'react'; import styled from '@emotion/styled'; -import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; const StyledDropdownMenuContainer = styled.ul<{ @@ -38,7 +38,7 @@ export function DropdownMenuContainer({ return ( - {children} + {children} ); } diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownEntitySelect.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownEntitySelect.tsx index 3a686e368..0095e3d02 100644 --- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownEntitySelect.tsx +++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownEntitySelect.tsx @@ -1,6 +1,6 @@ import { Context } from 'react'; -import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; +import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; @@ -22,7 +22,7 @@ export function FilterDropdownEntitySelect({ return ( <> - + {filterDefinitionUsedInDropdown.entitySelectComponent} diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx index 00b8f6d13..1b310875e 100644 --- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx +++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx @@ -1,7 +1,7 @@ import { Context } from 'react'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; @@ -41,7 +41,7 @@ export function FilterDropdownFilterSelect({ const setHotkeyScope = useSetHotkeyScope(); return ( - + {availableFilters.map((availableFilter, index) => ( ))} - + ); } diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandSelect.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandSelect.tsx index cdb6bc0bd..2a234c79f 100644 --- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandSelect.tsx +++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandSelect.tsx @@ -1,7 +1,7 @@ import { Context } from 'react'; import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited'; @@ -62,7 +62,7 @@ export function FilterDropdownOperandSelect({ } return ( - + {operandsForFilterType.map((filterOperand, index) => ( ))} - + ); } diff --git a/front/src/modules/ui/filter-n-sort/components/MultipleFiltersDropdownButton.tsx b/front/src/modules/ui/filter-n-sort/components/MultipleFiltersDropdownButton.tsx index cc2ff24f8..dd60a11bd 100644 --- a/front/src/modules/ui/filter-n-sort/components/MultipleFiltersDropdownButton.tsx +++ b/front/src/modules/ui/filter-n-sort/components/MultipleFiltersDropdownButton.tsx @@ -1,6 +1,6 @@ import { Context, useCallback, useState } from 'react'; -import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; +import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { filterDefinitionUsedInDropdownScopedState } from '@/ui/filter-n-sort/states/filterDefinitionUsedInDropdownScopedState'; import { filterDropdownSearchInputScopedState } from '@/ui/filter-n-sort/states/filterDropdownSearchInputScopedState'; import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; @@ -119,7 +119,7 @@ export function MultipleFiltersDropdownButton({ selectedOperandInDropdown && ( <> - + {filterDefinitionUsedInDropdown.type === 'text' && ( )} diff --git a/front/src/modules/ui/filter-n-sort/components/SingleEntityFilterDropdownButton.tsx b/front/src/modules/ui/filter-n-sort/components/SingleEntityFilterDropdownButton.tsx index a31dbf994..a7f35d22b 100644 --- a/front/src/modules/ui/filter-n-sort/components/SingleEntityFilterDropdownButton.tsx +++ b/front/src/modules/ui/filter-n-sort/components/SingleEntityFilterDropdownButton.tsx @@ -10,6 +10,7 @@ import { selectedOperandInDropdownScopedState } from '@/ui/filter-n-sort/states/ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; +import { StyledHeaderDropdownButton } from '../../dropdown/components/StyledHeaderDropdownButton'; import { availableFiltersScopedState } from '../states/availableFiltersScopedState'; import { filtersScopedState } from '../states/filtersScopedState'; import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; @@ -27,29 +28,6 @@ const StyledDropdownButtonContainer = styled.div` z-index: 1; `; -type StyledDropdownButtonProps = { - isUnfolded: boolean; -}; - -const StyledDropdownButton = styled.div` - align-items: center; - background: ${({ theme }) => theme.background.primary}; - border-radius: ${({ theme }) => theme.border.radius.sm}; - cursor: pointer; - display: flex; - - filter: ${(props) => (props.isUnfolded ? 'brightness(0.95)' : 'none')}; - padding: ${({ theme }) => theme.spacing(1)}; - - padding-left: ${({ theme }) => theme.spacing(2)}; - padding-right: ${({ theme }) => theme.spacing(2)}; - - &:hover { - filter: brightness(0.95); - } - user-select: none; -`; - export function SingleEntityFilterDropdownButton({ context, HotkeyScope, @@ -109,7 +87,7 @@ export function SingleEntityFilterDropdownButton({ return ( - handleIsUnfoldedChange(!isUnfolded)} > @@ -119,7 +97,7 @@ export function SingleEntityFilterDropdownButton({ 'Filter' )} - + {isUnfolded && ( handleIsUnfoldedChange(false)}> diff --git a/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx b/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx index 2eeea865b..f7580bd9b 100644 --- a/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx +++ b/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx @@ -3,9 +3,9 @@ import { useTheme } from '@emotion/react'; import { IconChevronDown } from '@tabler/icons-react'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; -import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; +import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; @@ -81,7 +81,7 @@ export function SortDropdownButton({ HotkeyScope={HotkeyScope} > {isOptionUnfolded ? ( - + {options.map((option, index) => ( ({ {option === 'asc' ? 'Ascending' : 'Descending'} ))} - + ) : ( <> ({ > {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} - + - + {availableSorts.map((sort, index) => ( ({ ))} - + )} diff --git a/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx b/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx index 13482a38f..943d6ee3a 100644 --- a/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx +++ b/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx @@ -1,12 +1,12 @@ import { useRef } from 'react'; import debounce from 'lodash.debounce'; -import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; import { DropdownMenuCheckableItem } from '@/ui/dropdown/components/DropdownMenuCheckableItem'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; -import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; +import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { Avatar } from '@/users/components/Avatar'; import { isNonEmptyString } from '~/utils/isNonEmptyString'; @@ -72,14 +72,14 @@ export function MultipleEntitySelect< }); return ( - + - - + + {entitiesInDropdown?.map((entity) => ( No result )} - - + + ); } diff --git a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx index 21324bb46..93f5fb9be 100644 --- a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx +++ b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx @@ -1,11 +1,11 @@ import { useRef } from 'react'; import { useTheme } from '@emotion/react'; -import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; -import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; +import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { IconPlus } from '@/ui/icon'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { isDefined } from '~/utils/isDefined'; @@ -55,7 +55,7 @@ export function SingleEntitySelect< }); return ( - - + {showCreateButton && ( <> - + Add New - - + + )} - + ); } diff --git a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx index 16ecabe3c..784d47a20 100644 --- a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx +++ b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx @@ -2,8 +2,8 @@ import { useRef } from 'react'; import { Key } from 'ts-key-enum'; import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { Avatar } from '@/users/components/Avatar'; @@ -73,7 +73,7 @@ export function SingleEntitySelectBase< ); return ( - + {entities.loading ? ( ) : entitiesInDropdown.length === 0 ? ( @@ -97,6 +97,6 @@ export function SingleEntitySelectBase< )) )} - + ); } diff --git a/front/src/modules/ui/input/text/components/TextInputDisplay.tsx b/front/src/modules/ui/input/text/components/TextInputDisplay.tsx index 010f45fa0..a88132d55 100644 --- a/front/src/modules/ui/input/text/components/TextInputDisplay.tsx +++ b/front/src/modules/ui/input/text/components/TextInputDisplay.tsx @@ -1,9 +1,16 @@ -/* eslint-disable twenty/styled-components-prefixed-with-styled */ import styled from '@emotion/styled'; -export const TextInputDisplay = styled.div` +const StyledTextInputDisplay = styled.div` overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%; `; + +export type TextInputDisplayProps = { + children: React.ReactNode; +}; + +export function TextInputDisplay({ children }: TextInputDisplayProps) { + return {children}; +} diff --git a/front/src/modules/ui/layout/components/ShowPageContainer.tsx b/front/src/modules/ui/layout/components/ShowPageContainer.tsx index 4260a9832..61b2b29ba 100644 --- a/front/src/modules/ui/layout/components/ShowPageContainer.tsx +++ b/front/src/modules/ui/layout/components/ShowPageContainer.tsx @@ -1,9 +1,9 @@ -/* eslint-disable twenty/styled-components-prefixed-with-styled */ +import { ReactElement } from 'react'; import styled from '@emotion/styled'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -export const ShowPageContainer = styled.div` +export const StyledShowPageContainer = styled.div` display: flex; flex-direction: ${() => (useIsMobile() ? 'column' : 'row')}; gap: ${({ theme }) => (useIsMobile() ? theme.spacing(3) : '0')}; @@ -11,3 +11,11 @@ export const ShowPageContainer = styled.div` overflow-x: ${() => (useIsMobile() ? 'hidden' : 'auto')}; width: ${() => (useIsMobile() ? `calc(100% - 2px);` : '100%')}; `; + +export type ShowPageContainerProps = { + children: ReactElement[]; +}; + +export function ShowPageContainer({ children }: ShowPageContainerProps) { + return {children} ; +} diff --git a/front/src/modules/ui/layout/show-page/components/ShowPageLeftContainer.tsx b/front/src/modules/ui/layout/show-page/components/ShowPageLeftContainer.tsx index ca4add4b9..1941a01f8 100644 --- a/front/src/modules/ui/layout/show-page/components/ShowPageLeftContainer.tsx +++ b/front/src/modules/ui/layout/show-page/components/ShowPageLeftContainer.tsx @@ -1,9 +1,9 @@ -/* eslint-disable twenty/styled-components-prefixed-with-styled */ +import { ReactElement } from 'react'; import styled from '@emotion/styled'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -export const ShowPageLeftContainer = styled.div` +const StyledShowPageLeftContainer = styled.div` background: ${({ theme }) => theme.background.secondary}; border-bottom-left-radius: 8px; border-right: 1px solid @@ -24,3 +24,13 @@ export const ShowPageLeftContainer = styled.div` z-index: 10; `; + +export type ShowPageLeftContainerProps = { + children: ReactElement[]; +}; + +export function ShowPageLeftContainer({ + children, +}: ShowPageLeftContainerProps) { + return {children} ; +} diff --git a/front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx b/front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx index 18eff5d62..e619d49b9 100644 --- a/front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx +++ b/front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx @@ -1,9 +1,9 @@ -/* eslint-disable twenty/styled-components-prefixed-with-styled */ +import { ReactElement } from 'react'; import styled from '@emotion/styled'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -export const ShowPageRightContainer = styled.div` +export const StyledShowPageRightContainer = styled.div` display: flex; flex: 1 0 0; flex-direction: column; @@ -15,3 +15,15 @@ export const ShowPageRightContainer = styled.div` return isMobile ? `calc(100% - ${theme.spacing(6)})` : 'auto'; }}; `; + +export type ShowPageRightContainerProps = { + children: ReactElement; +}; + +export function ShowPageRightContainer({ + children, +}: ShowPageRightContainerProps) { + return ( + {children} + ); +} diff --git a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx index 770789b65..cf5fb87c6 100644 --- a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx +++ b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx @@ -4,15 +4,15 @@ import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; import { IconButton } from '@/ui/button/components/IconButton'; -import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; +import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { IconPlus } from '@/ui/icon'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { hiddenTableColumnsState } from '../states/tableColumnsState'; -const StyledColumnMenu = styled(DropdownMenu)` +const StyledColumnMenu = styled(StyledDropdownMenu)` font-weight: ${({ theme }) => theme.font.weight.regular}; `; @@ -38,7 +38,7 @@ export const EntityTableColumnMenu = ({ return ( - + {hiddenColumns.map((column) => ( ))} - + ); }; diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx new file mode 100644 index 000000000..361443317 --- /dev/null +++ b/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx @@ -0,0 +1,40 @@ +import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; +import type { + ViewFieldDefinition, + ViewFieldMetadata, +} from '@/ui/editable-field/types/ViewField'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; + +import { type TableView } from '../../states/tableViewsState'; + +import { TableOptionsDropdownButton } from './TableOptionsDropdownButton'; +import { TableOptionsDropdownContent } from './TableOptionsDropdownContent'; + +type TableOptionsDropdownProps = { + onColumnsChange?: (columns: ViewFieldDefinition[]) => void; + onViewsChange?: (views: TableView[]) => void; + onImport?: () => void; + customHotkeyScope: HotkeyScope; +}; + +export function TableOptionsDropdown({ + onColumnsChange, + onViewsChange, + onImport, + customHotkeyScope, +}: TableOptionsDropdownProps) { + return ( + } + dropdownHotkeyScope={customHotkeyScope} + dropdownKey="options" + dropdownComponents={ + + } + /> + ); +} diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx index 59b519508..9e4094b97 100644 --- a/front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx +++ b/front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx @@ -1,273 +1,17 @@ -import { - type FormEvent, - useCallback, - useEffect, - useRef, - useState, -} from 'react'; -import { useTheme } from '@emotion/react'; -import { useRecoilState, useRecoilValue } from 'recoil'; -import { v4 } from 'uuid'; +import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton'; +import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; -import { IconButton } from '@/ui/button/components/IconButton'; -import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; -import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; -import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; -import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; -import type { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; -import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton'; -import { - IconChevronLeft, - IconFileImport, - IconMinus, - IconPlus, - IconTag, -} from '@/ui/icon'; -import { - hiddenTableColumnsState, - tableColumnsState, - visibleTableColumnsState, -} from '@/ui/table/states/tableColumnsState'; -import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; -import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; -import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; - -import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; -import { - type TableView, - tableViewEditModeState, - tableViewsByIdState, - tableViewsState, -} from '../../states/tableViewsState'; -import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope'; - -import { TableOptionsDropdownSection } from './TableOptionsDropdownSection'; - -type TableOptionsDropdownButtonProps = { - onColumnsChange?: (columns: ViewFieldDefinition[]) => void; - onViewsChange?: (views: TableView[]) => void; - onImport?: () => void; - HotkeyScope: TableOptionsHotkeyScope; -}; - -enum Option { - Properties = 'Properties', -} - -export const TableOptionsDropdownButton = ({ - onColumnsChange, - onViewsChange, - onImport, - HotkeyScope, -}: TableOptionsDropdownButtonProps) => { - const theme = useTheme(); - - const [isUnfolded, setIsUnfolded] = useState(false); - const [selectedOption, setSelectedOption] = useState ); }; diff --git a/front/src/modules/ui/table/table-header/components/TableHeader.tsx b/front/src/modules/ui/table/table-header/components/TableHeader.tsx index ae804a766..ed9eb9dbf 100644 --- a/front/src/modules/ui/table/table-header/components/TableHeader.tsx +++ b/front/src/modules/ui/table/table-header/components/TableHeader.tsx @@ -1,5 +1,6 @@ import { useCallback } from 'react'; +import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext'; import type { ViewFieldDefinition, ViewFieldMetadata, @@ -10,10 +11,11 @@ import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownBu import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState'; import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope'; import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface'; -import { TableOptionsDropdownButton } from '@/ui/table/options/components/TableOptionsDropdownButton'; import { TopBar } from '@/ui/top-bar/TopBar'; +import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; +import { TableOptionsDropdown } from '../../options/components/TableOptionsDropdown'; import { TableUpdateViewButtonGroup } from '../../options/components/TableUpdateViewButtonGroup'; import { TableViewsDropdownButton } from '../../options/components/TableViewsDropdownButton'; import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; @@ -60,54 +62,56 @@ export function TableHeader({ ); return ( - - } - displayBottomBorder={false} - rightComponent={ - <> - - - context={TableRecoilScopeContext} - isSortSelected={sorts.length > 0} - availableSorts={availableSorts || []} - onSortSelect={sortSelect} - HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} - isPrimaryButton - /> - + - - } - bottomComponent={ - setSorts([])} - hasFilterButton - rightComponent={ - + - } - /> - } - /> + + context={TableRecoilScopeContext} + isSortSelected={sorts.length > 0} + availableSorts={availableSorts || []} + onSortSelect={sortSelect} + HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} + isPrimaryButton + /> + + + } + bottomComponent={ + setSorts([])} + hasFilterButton + rightComponent={ + + } + /> + } + /> + ); } diff --git a/front/src/modules/ui/tooltip/AppTooltip.tsx b/front/src/modules/ui/tooltip/AppTooltip.tsx index d0739ac29..1c96b745b 100644 --- a/front/src/modules/ui/tooltip/AppTooltip.tsx +++ b/front/src/modules/ui/tooltip/AppTooltip.tsx @@ -1,5 +1,4 @@ -/* eslint-disable twenty/styled-components-prefixed-with-styled */ -import { Tooltip } from 'react-tooltip'; +import { PlacesType, PositionStrategy, Tooltip } from 'react-tooltip'; import styled from '@emotion/styled'; import { rgba } from '../theme/constants/colors'; @@ -11,7 +10,7 @@ export enum TooltipPosition { Bottom = 'bottom', } -export const AppTooltip = styled(Tooltip)` +const StyledAppTooltip = styled(Tooltip)` backdrop-filter: ${({ theme }) => theme.blur.strong}; background-color: ${({ theme }) => rgba(theme.color.gray80, 0.8)}; border-radius: ${({ theme }) => theme.border.radius.sm}; @@ -31,3 +30,19 @@ export const AppTooltip = styled(Tooltip)` z-index: ${({ theme }) => theme.lastLayerZIndex}; `; + +export type AppToolipProps = { + className?: string; + anchorSelect?: string; + content?: string; + delayHide?: number; + offset?: number; + noArrow?: boolean; + isOpen?: boolean; + place?: PlacesType; + positionStrategy?: PositionStrategy; +}; + +export function AppTooltip(props: AppToolipProps) { + return ; +} diff --git a/front/src/modules/ui/utilities/recoil-scope/hooks/useRecoilScopedFamilyState.ts b/front/src/modules/ui/utilities/recoil-scope/hooks/useRecoilScopedFamilyState.ts new file mode 100644 index 000000000..4245d06b2 --- /dev/null +++ b/front/src/modules/ui/utilities/recoil-scope/hooks/useRecoilScopedFamilyState.ts @@ -0,0 +1,21 @@ +import { Context, useContext } from 'react'; +import { RecoilState, useRecoilState } from 'recoil'; + +import { RecoilScopeContext } from '../states/RecoilScopeContext'; + +export function useRecoilScopedFamilyState( + recoilState: (param: string) => RecoilState, + stateKey: string, + SpecificContext?: Context, +) { + const recoilScopeId = useContext(SpecificContext ?? RecoilScopeContext); + + if (!recoilScopeId) + throw new Error( + `Using a scoped atom without a RecoilScope : ${ + recoilState(stateKey).key + }, verify that you are using a RecoilScope with a specific context if you intended to do so.`, + ); + + return useRecoilState(recoilState(recoilScopeId + stateKey)); +} diff --git a/front/src/pages/opportunities/Opportunities.tsx b/front/src/pages/opportunities/Opportunities.tsx index 0cd3fbc02..1e212a434 100644 --- a/front/src/pages/opportunities/Opportunities.tsx +++ b/front/src/pages/opportunities/Opportunities.tsx @@ -8,6 +8,7 @@ import { EntityBoard } from '@/ui/board/components/EntityBoard'; import { EntityBoardActionBar } from '@/ui/board/components/EntityBoardActionBar'; import { EntityBoardContextMenu } from '@/ui/board/components/EntityBoardContextMenu'; import { BoardOptionsContext } from '@/ui/board/contexts/BoardOptionsContext'; +import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext'; import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers'; import { SelectedSortType } from '@/ui/filter-n-sort/types/interface'; import { IconTargetArrow } from '@/ui/icon'; @@ -72,7 +73,7 @@ export function Opportunities() { title="Opportunities" icon={} > - +