Feat/hide board fields (#1271)
* Renamed AuthAutoRouter * Moved RecoilScope * Refactored old WithTopBarContainer to make it less transclusive * Created new add opportunity button and refactored DropdownButton * Added tests * Deactivated new eslint rule * Refactored Table options with new dropdown * Started BoardDropdown * Fix lint * Refactor dropdown openstate * Fix according to PR * Fix tests --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -3,17 +3,16 @@ import { useTheme } from '@emotion/react';
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
|
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
|
||||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
|
||||||
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
import { IconChevronDown } from '@/ui/icon';
|
import { IconChevronDown } from '@/ui/icon';
|
||||||
import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase';
|
import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase';
|
||||||
import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch';
|
import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch';
|
||||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
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 { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
|
|
||||||
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
|
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
|
||||||
@ -48,17 +47,6 @@ export function CompanyProgressPicker({
|
|||||||
string | null
|
string | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
useListenClickOutside({
|
|
||||||
refs: [containerRef],
|
|
||||||
callback: (event) => {
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
onCancel?.();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [currentPipeline] = useRecoilState(currentPipelineState);
|
const [currentPipeline] = useRecoilState(currentPipelineState);
|
||||||
@ -94,12 +82,12 @@ export function CompanyProgressPicker({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu
|
<StyledDropdownMenu
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
data-testid={`company-progress-dropdown-menu`}
|
data-testid={`company-progress-dropdown-menu`}
|
||||||
>
|
>
|
||||||
{isProgressSelectionUnfolded ? (
|
{isProgressSelectionUnfolded ? (
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{currentPipelineStages.map((pipelineStage, index) => (
|
{currentPipelineStages.map((pipelineStage, index) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={pipelineStage.id}
|
key={pipelineStage.id}
|
||||||
@ -111,7 +99,7 @@ export function CompanyProgressPicker({
|
|||||||
{pipelineStage.name}
|
{pipelineStage.name}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
@ -121,13 +109,13 @@ export function CompanyProgressPicker({
|
|||||||
>
|
>
|
||||||
{selectedPipelineStage?.name}
|
{selectedPipelineStage?.name}
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<DropdownMenuInput
|
<DropdownMenuInput
|
||||||
value={searchFilter}
|
value={searchFilter}
|
||||||
onChange={handleSearchFilterChange}
|
onChange={handleSearchFilterChange}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<RecoilScope>
|
<RecoilScope>
|
||||||
<SingleEntitySelectBase
|
<SingleEntitySelectBase
|
||||||
onEntitySelected={handleEntitySelected}
|
onEntitySelected={handleEntitySelected}
|
||||||
@ -141,6 +129,6 @@ export function CompanyProgressPicker({
|
|||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react';
|
|||||||
import { IconDotsVertical, IconLinkOff, IconTrash } from '@tabler/icons-react';
|
import { IconDotsVertical, IconLinkOff, IconTrash } from '@tabler/icons-react';
|
||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
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 { 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 { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import {
|
import {
|
||||||
@ -172,8 +172,10 @@ export function PeopleCard({
|
|||||||
icon={<IconDotsVertical size={theme.icon.size.md} />}
|
icon={<IconDotsVertical size={theme.icon.size.md} />}
|
||||||
/>
|
/>
|
||||||
{isOptionsOpen && (
|
{isOptionsOpen && (
|
||||||
<DropdownMenu ref={refs.setFloating} style={floatingStyles}>
|
<StyledDropdownMenu ref={refs.setFloating} style={floatingStyles}>
|
||||||
<DropdownMenuItemsContainer onClick={(e) => e.stopPropagation()}>
|
<StyledDropdownMenuItemsContainer
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<DropdownMenuSelectableItem onClick={handleDetachPerson}>
|
<DropdownMenuSelectableItem onClick={handleDetachPerson}>
|
||||||
<IconButton icon={<IconLinkOff size={14} />} size="small" />
|
<IconButton icon={<IconLinkOff size={14} />} size="small" />
|
||||||
Detach relation
|
Detach relation
|
||||||
@ -186,8 +188,8 @@ export function PeopleCard({
|
|||||||
/>
|
/>
|
||||||
<StyledRemoveOption>Delete person</StyledRemoveOption>
|
<StyledRemoveOption>Delete person</StyledRemoveOption>
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -12,7 +12,9 @@ import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
|||||||
export function PipelineAddButton() {
|
export function PipelineAddButton() {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { closeDropdownButton } = useDropdownButton();
|
const { closeDropdownButton, toggleDropdownButton } = useDropdownButton({
|
||||||
|
key: 'add-company-progress',
|
||||||
|
});
|
||||||
|
|
||||||
const createCompanyProgress = useCreateCompanyProgress();
|
const createCompanyProgress = useCreateCompanyProgress();
|
||||||
|
|
||||||
@ -51,6 +53,7 @@ export function PipelineAddButton() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
|
dropdownKey="add-pipeline-progress"
|
||||||
buttonComponents={
|
buttonComponents={
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<IconPlus size={16} />}
|
icon={<IconPlus size={16} />}
|
||||||
@ -58,6 +61,7 @@ export function PipelineAddButton() {
|
|||||||
data-testid="add-company-progress-button"
|
data-testid="add-company-progress-button"
|
||||||
textColor={'secondary'}
|
textColor={'secondary'}
|
||||||
variant="border"
|
variant="border"
|
||||||
|
onClick={toggleDropdownButton}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
@ -71,7 +75,7 @@ export function PipelineAddButton() {
|
|||||||
key: 'c',
|
key: 'c',
|
||||||
scope: PageHotkeyScope.OpportunitiesPage,
|
scope: PageHotkeyScope.OpportunitiesPage,
|
||||||
}}
|
}}
|
||||||
dropdownScopeToSet={{
|
dropdownHotkeyScope={{
|
||||||
scope: RelationPickerHotkeyScope.RelationPicker,
|
scope: RelationPickerHotkeyScope.RelationPicker,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -14,12 +14,12 @@ import debounce from 'lodash.debounce';
|
|||||||
import { ReadonlyDeep } from 'type-fest';
|
import { ReadonlyDeep } from 'type-fest';
|
||||||
|
|
||||||
import type { SelectOption } from '@/spreadsheet-import/types';
|
import type { SelectOption } from '@/spreadsheet-import/types';
|
||||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
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 { IconChevronDown } from '@/ui/icon';
|
||||||
import { AppTooltip } from '@/ui/tooltip/AppTooltip';
|
import { AppTooltip } from '@/ui/tooltip/AppTooltip';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
@ -166,7 +166,7 @@ export const MatchColumnSelect = ({
|
|||||||
{isOpen &&
|
{isOpen &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<StyledFloatingDropdown ref={refs.setFloating} style={floatingStyles}>
|
<StyledFloatingDropdown ref={refs.setFloating} style={floatingStyles}>
|
||||||
<DropdownMenu
|
<StyledDropdownMenu
|
||||||
ref={dropdownContainerRef}
|
ref={dropdownContainerRef}
|
||||||
width={dropdownItemRef.current?.clientWidth}
|
width={dropdownItemRef.current?.clientWidth}
|
||||||
>
|
>
|
||||||
@ -175,8 +175,8 @@ export const MatchColumnSelect = ({
|
|||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{options?.map((option) => (
|
{options?.map((option) => (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSelectableItem
|
<DropdownMenuSelectableItem
|
||||||
@ -208,8 +208,8 @@ export const MatchColumnSelect = ({
|
|||||||
{options?.length === 0 && (
|
{options?.length === 0 && (
|
||||||
<DropdownMenuItem>No result</DropdownMenuItem>
|
<DropdownMenuItem>No result</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
</StyledFloatingDropdown>,
|
</StyledFloatingDropdown>,
|
||||||
document.body,
|
document.body,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { ChangeEvent, useState } from 'react';
|
import { ChangeEvent, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
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 { textInputStyle } from '@/ui/theme/constants/effects';
|
||||||
import { debounce } from '~/utils/debounce';
|
import { debounce } from '~/utils/debounce';
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ export function BoardColumnEditTitleMenu({
|
|||||||
debouncedOnUpdateTitle(event.target.value);
|
debouncedOnUpdateTitle(event.target.value);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
<StyledEditTitleContainer>
|
<StyledEditTitleContainer>
|
||||||
<StyledEditModeInput
|
<StyledEditModeInput
|
||||||
value={internalValue}
|
value={internalValue}
|
||||||
@ -84,7 +84,7 @@ export function BoardColumnEditTitleMenu({
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</StyledEditTitleContainer>
|
</StyledEditTitleContainer>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
{COLOR_OPTIONS.map((colorOption) => (
|
{COLOR_OPTIONS.map((colorOption) => (
|
||||||
<DropdownMenuSelectableItem
|
<DropdownMenuSelectableItem
|
||||||
key={colorOption.name}
|
key={colorOption.name}
|
||||||
@ -98,6 +98,6 @@ export function BoardColumnEditTitleMenu({
|
|||||||
{colorOption.name}
|
{colorOption.name}
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import styled from '@emotion/styled';
|
|||||||
import { IconPencil } from '@tabler/icons-react';
|
import { IconPencil } from '@tabler/icons-react';
|
||||||
import { Key } from 'ts-key-enum';
|
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 { 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 { icon } from '@/ui/theme/constants/icon';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
@ -50,14 +50,14 @@ export function BoardColumnMenu({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledMenuContainer ref={boardColumnMenuRef}>
|
<StyledMenuContainer ref={boardColumnMenuRef}>
|
||||||
<DropdownMenu>
|
<StyledDropdownMenu>
|
||||||
{openMenu === 'actions' && (
|
{openMenu === 'actions' && (
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuSelectableItem onClick={() => setOpenMenu('title')}>
|
<DropdownMenuSelectableItem onClick={() => setOpenMenu('title')}>
|
||||||
<IconPencil size={icon.size.md} stroke={icon.stroke.sm} />
|
<IconPencil size={icon.size.md} stroke={icon.stroke.sm} />
|
||||||
Rename
|
Rename
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
)}
|
)}
|
||||||
{openMenu === 'title' && (
|
{openMenu === 'title' && (
|
||||||
<BoardColumnEditTitleMenu
|
<BoardColumnEditTitleMenu
|
||||||
@ -67,7 +67,7 @@ export function BoardColumnMenu({
|
|||||||
title={title}
|
title={title}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
</StyledMenuContainer>
|
</StyledMenuContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
|
||||||
|
|
||||||
|
import { BoardOptionsDropdownButton } from './BoardOptionsDropdownButton';
|
||||||
|
import { BoardOptionsDropdownContent } from './BoardOptionsDropdownContent';
|
||||||
|
|
||||||
|
export function BoardOptionsDropdown() {
|
||||||
|
return (
|
||||||
|
<DropdownButton
|
||||||
|
dropdownKey="options"
|
||||||
|
buttonComponents={<BoardOptionsDropdownButton />}
|
||||||
|
dropdownComponents={<BoardOptionsDropdownContent />}
|
||||||
|
></DropdownButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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 (
|
||||||
|
<StyledHeaderDropdownButton
|
||||||
|
isUnfolded={isDropdownButtonOpen}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
Options
|
||||||
|
</StyledHeaderDropdownButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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<BoardOptionsDropdownMenu>('options');
|
||||||
|
|
||||||
|
function handleFieldsClick() {
|
||||||
|
setMenuShown('fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMenuHeaderClick() {
|
||||||
|
setMenuShown('options');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledDropdownMenu>
|
||||||
|
{menuShown === 'options' ? (
|
||||||
|
<>
|
||||||
|
<DropdownMenuHeader>Options</DropdownMenuHeader>
|
||||||
|
<StyledDropdownMenuSeparator />
|
||||||
|
<StyledDropdownMenuItemsContainer>
|
||||||
|
<DropdownMenuItem onClick={handleFieldsClick}>
|
||||||
|
Fields
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</StyledDropdownMenuItemsContainer>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
menuShown === 'fields' && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuHeader
|
||||||
|
startIcon={<IconChevronLeft size={theme.icon.size.md} />}
|
||||||
|
onClick={handleMenuHeaderClick}
|
||||||
|
>
|
||||||
|
Fields
|
||||||
|
</DropdownMenuHeader>
|
||||||
|
<StyledDropdownMenuSeparator />
|
||||||
|
{}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</StyledDropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -4,8 +4,8 @@ import { useRecoilValue, useSetRecoilState } from 'recoil';
|
|||||||
|
|
||||||
import { actionBarOpenState } from '@/ui/action-bar/states/actionBarIsOpenState';
|
import { actionBarOpenState } from '@/ui/action-bar/states/actionBarIsOpenState';
|
||||||
import { contextMenuPositionState } from '@/ui/context-menu/states/contextMenuPositionState';
|
import { contextMenuPositionState } from '@/ui/context-menu/states/contextMenuPositionState';
|
||||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
|
|
||||||
import { contextMenuEntriesState } from '../states/contextMenuEntriesState';
|
import { contextMenuEntriesState } from '../states/contextMenuEntriesState';
|
||||||
@ -60,11 +60,11 @@ export function ContextMenu({ selectedIds }: OwnProps) {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<StyledContainerContextMenu ref={wrapperRef} position={position}>
|
<StyledContainerContextMenu ref={wrapperRef} position={position}>
|
||||||
<DropdownMenu>
|
<StyledDropdownMenu>
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{contextMenuEntries}
|
{contextMenuEntries}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
</StyledContainerContextMenu>
|
</StyledContainerContextMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
|
import { useEffect, useRef } from 'react';
|
||||||
import { Keys } from 'react-hotkeys-hook';
|
import { Keys } from 'react-hotkeys-hook';
|
||||||
import styled from '@emotion/styled';
|
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 { 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 { useDropdownButton } from '../hooks/useDropdownButton';
|
||||||
|
import { dropdownButtonCustomHotkeyScopeScopedFamilyState } from '../states/dropdownButtonCustomHotkeyScopeScopedFamilyState';
|
||||||
|
import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
||||||
|
|
||||||
import { HotkeyEffect } from './HotkeyEffect';
|
import { HotkeyEffect } from './HotkeyEffect';
|
||||||
|
|
||||||
@ -16,38 +22,74 @@ const StyledContainer = styled.div`
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
buttonComponents: JSX.Element | JSX.Element[];
|
buttonComponents: JSX.Element | JSX.Element[];
|
||||||
dropdownComponents: JSX.Element | JSX.Element[];
|
dropdownComponents: JSX.Element | JSX.Element[];
|
||||||
|
dropdownKey: string;
|
||||||
hotkey?: {
|
hotkey?: {
|
||||||
key: Keys;
|
key: Keys;
|
||||||
scope: string;
|
scope: string;
|
||||||
};
|
};
|
||||||
dropdownScopeToSet?: HotkeyScope;
|
dropdownHotkeyScope?: HotkeyScope;
|
||||||
|
dropdownPlacement?: Placement;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DropdownButton({
|
export function DropdownButton({
|
||||||
buttonComponents,
|
buttonComponents,
|
||||||
dropdownComponents,
|
dropdownComponents,
|
||||||
|
dropdownKey,
|
||||||
hotkey,
|
hotkey,
|
||||||
dropdownScopeToSet,
|
dropdownHotkeyScope,
|
||||||
|
dropdownPlacement = 'bottom-end',
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton();
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const { isDropdownButtonOpen, toggleDropdownButton, closeDropdownButton } =
|
||||||
|
useDropdownButton({
|
||||||
|
key: dropdownKey,
|
||||||
|
});
|
||||||
|
|
||||||
const { refs, floatingStyles } = useFloating({
|
const { refs, floatingStyles } = useFloating({
|
||||||
placement: 'bottom-end',
|
placement: dropdownPlacement,
|
||||||
middleware: [flip(), offset()],
|
middleware: [flip(), offset()],
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleButtonClick() {
|
function handleHotkeyTriggered() {
|
||||||
toggleDropdownButton(dropdownScopeToSet);
|
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 (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer ref={containerRef}>
|
||||||
{hotkey && (
|
{hotkey && (
|
||||||
<HotkeyEffect hotkey={hotkey} onHotkeyTriggered={handleButtonClick} />
|
<HotkeyEffect
|
||||||
|
hotkey={hotkey}
|
||||||
|
onHotkeyTriggered={handleHotkeyTriggered}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<div ref={refs.setReference} onClick={handleButtonClick}>
|
<div ref={refs.setReference}>{buttonComponents}</div>
|
||||||
{buttonComponents}
|
|
||||||
</div>
|
|
||||||
{isDropdownButtonOpen && (
|
{isDropdownButtonOpen && (
|
||||||
<div ref={refs.setFloating} style={floatingStyles}>
|
<div ref={refs.setFloating} style={floatingStyles}>
|
||||||
{dropdownComponents}
|
{dropdownComponents}
|
||||||
|
|||||||
@ -44,15 +44,19 @@ type DropdownMenuHeaderProps = ComponentProps<'li'> & {
|
|||||||
endIcon?: ReactElement;
|
endIcon?: ReactElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DropdownMenuHeader = ({
|
export function DropdownMenuHeader({
|
||||||
children,
|
children,
|
||||||
startIcon,
|
startIcon,
|
||||||
endIcon,
|
endIcon,
|
||||||
...props
|
...props
|
||||||
}: DropdownMenuHeaderProps) => (
|
}: DropdownMenuHeaderProps) {
|
||||||
<StyledHeader {...props}>
|
return (
|
||||||
{startIcon && <StyledStartIconWrapper>{startIcon}</StyledStartIconWrapper>}
|
<StyledHeader {...props}>
|
||||||
{children}
|
{startIcon && (
|
||||||
{endIcon && <StyledEndIconWrapper>{endIcon}</StyledEndIconWrapper>}
|
<StyledStartIconWrapper>{startIcon}</StyledStartIconWrapper>
|
||||||
</StyledHeader>
|
)}
|
||||||
);
|
{children}
|
||||||
|
{endIcon && <StyledEndIconWrapper>{endIcon}</StyledEndIconWrapper>}
|
||||||
|
</StyledHeader>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -58,22 +58,24 @@ export type DropdownMenuItemProps = ComponentProps<'li'> & {
|
|||||||
accent?: DropdownMenuItemAccent;
|
accent?: DropdownMenuItemAccent;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DropdownMenuItem = ({
|
export function DropdownMenuItem({
|
||||||
actions,
|
actions,
|
||||||
children,
|
children,
|
||||||
accent = 'regular',
|
accent = 'regular',
|
||||||
...props
|
...props
|
||||||
}: DropdownMenuItemProps) => (
|
}: DropdownMenuItemProps) {
|
||||||
<StyledItem {...props} accent={accent}>
|
return (
|
||||||
{children}
|
<StyledItem {...props} accent={accent}>
|
||||||
{actions && (
|
{children}
|
||||||
<StyledActions
|
{actions && (
|
||||||
className={styledIconButtonGroupClassName}
|
<StyledActions
|
||||||
variant="transparent"
|
className={styledIconButtonGroupClassName}
|
||||||
size="small"
|
variant="transparent"
|
||||||
>
|
size="small"
|
||||||
{actions}
|
>
|
||||||
</StyledActions>
|
{actions}
|
||||||
)}
|
</StyledActions>
|
||||||
</StyledItem>
|
)}
|
||||||
);
|
</StyledItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable twenty/styled-components-prefixed-with-styled */
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const DropdownMenu = styled.div<{
|
export const StyledDropdownMenu = styled.div<{
|
||||||
disableBlur?: boolean;
|
disableBlur?: boolean;
|
||||||
width?: number;
|
width?: number;
|
||||||
}>`
|
}>`
|
||||||
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable twenty/styled-components-prefixed-with-styled */
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const DropdownMenuItemsContainer = styled.div<{
|
export const StyledDropdownMenuItemsContainer = styled.div<{
|
||||||
hasMaxHeight?: boolean;
|
hasMaxHeight?: boolean;
|
||||||
}>`
|
}>`
|
||||||
--padding: ${({ theme }) => theme.spacing(1)};
|
--padding: ${({ theme }) => theme.spacing(1)};
|
||||||
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable twenty/styled-components-prefixed-with-styled */
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const DropdownMenuSeparator = styled.div`
|
export const StyledDropdownMenuSeparator = styled.div`
|
||||||
background-color: ${({ theme }) => theme.border.color.light};
|
background-color: ${({ theme }) => theme.border.color.light};
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable twenty/styled-components-prefixed-with-styled */
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const DropdownMenuSubheader = styled.div`
|
export const StyledDropdownMenuSubheader = styled.div`
|
||||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
font-size: ${({ theme }) => theme.font.size.xxs};
|
font-size: ${({ theme }) => theme.font.size.xxs};
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
type StyledDropdownButtonProps = {
|
||||||
|
isUnfolded?: boolean;
|
||||||
|
isActive?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StyledHeaderDropdownButton = styled.div<StyledDropdownButtonProps>`
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -8,19 +8,19 @@ import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/
|
|||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
import { DropdownMenu } from '../DropdownMenu';
|
|
||||||
import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem';
|
import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem';
|
||||||
import { DropdownMenuHeader } from '../DropdownMenuHeader';
|
import { DropdownMenuHeader } from '../DropdownMenuHeader';
|
||||||
import { DropdownMenuInput } from '../DropdownMenuInput';
|
import { DropdownMenuInput } from '../DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '../DropdownMenuItem';
|
import { DropdownMenuItem } from '../DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '../DropdownMenuItemsContainer';
|
|
||||||
import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem';
|
import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem';
|
||||||
import { DropdownMenuSeparator } from '../DropdownMenuSeparator';
|
import { StyledDropdownMenu } from '../StyledDropdownMenu';
|
||||||
import { DropdownMenuSubheader } from '../DropdownMenuSubheader';
|
import { StyledDropdownMenuItemsContainer } from '../StyledDropdownMenuItemsContainer';
|
||||||
|
import { StyledDropdownMenuSeparator } from '../StyledDropdownMenuSeparator';
|
||||||
|
import { StyledDropdownMenuSubheader } from '../StyledDropdownMenuSubheader';
|
||||||
|
|
||||||
const meta: Meta<typeof DropdownMenu> = {
|
const meta: Meta<typeof StyledDropdownMenu> = {
|
||||||
title: 'UI/Dropdown/DropdownMenu',
|
title: 'UI/Dropdown/DropdownMenu',
|
||||||
component: DropdownMenu,
|
component: StyledDropdownMenu,
|
||||||
decorators: [ComponentDecorator],
|
decorators: [ComponentDecorator],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
as: { table: { disable: true } },
|
as: { table: { disable: true } },
|
||||||
@ -29,7 +29,7 @@ const meta: Meta<typeof DropdownMenu> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof DropdownMenu>;
|
type Story = StoryObj<typeof StyledDropdownMenu>;
|
||||||
|
|
||||||
const FakeContentBelow = () => (
|
const FakeContentBelow = () => (
|
||||||
<div style={{ position: 'absolute' }}>
|
<div style={{ position: 'absolute' }}>
|
||||||
@ -156,9 +156,9 @@ const FakeCheckableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
|
|||||||
|
|
||||||
export const Empty: Story = {
|
export const Empty: Story = {
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<StyledFakeMenuContent />
|
<StyledFakeMenuContent />
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -179,60 +179,60 @@ export const WithContentBelow: Story = {
|
|||||||
export const SimpleMenuItem: Story = {
|
export const SimpleMenuItem: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{mockSelectArray.map(({ name }) => (
|
{mockSelectArray.map(({ name }) => (
|
||||||
<DropdownMenuItem>{name}</DropdownMenuItem>
|
<DropdownMenuItem>{name}</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithHeaders: Story = {
|
export const WithHeaders: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<DropdownMenuHeader>Header</DropdownMenuHeader>
|
<DropdownMenuHeader>Header</DropdownMenuHeader>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<DropdownMenuSubheader>Subheader 1</DropdownMenuSubheader>
|
<StyledDropdownMenuSubheader>Subheader 1</StyledDropdownMenuSubheader>
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{mockSelectArray.slice(0, 3).map(({ name }) => (
|
{mockSelectArray.slice(0, 3).map(({ name }) => (
|
||||||
<DropdownMenuItem>{name}</DropdownMenuItem>
|
<DropdownMenuItem>{name}</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<DropdownMenuSubheader>Subheader 2</DropdownMenuSubheader>
|
<StyledDropdownMenuSubheader>Subheader 2</StyledDropdownMenuSubheader>
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{mockSelectArray.slice(3).map(({ name }) => (
|
{mockSelectArray.slice(3).map(({ name }) => (
|
||||||
<DropdownMenuItem>{name}</DropdownMenuItem>
|
<DropdownMenuItem>{name}</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithIcons: Story = {
|
export const WithIcons: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{mockSelectArray.map(({ name }) => (
|
{mockSelectArray.map(({ name }) => (
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<IconUser size={16} />
|
<IconUser size={16} />
|
||||||
{name}
|
{name}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithActions: Story = {
|
export const WithActions: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{mockSelectArray.map(({ name }, index) => (
|
{mockSelectArray.map(({ name }, index) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className={index === 0 ? 'hover' : undefined}
|
className={index === 0 ? 'hover' : undefined}
|
||||||
@ -244,8 +244,8 @@ export const WithActions: Story = {
|
|||||||
{name}
|
{name}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
parameters: {
|
parameters: {
|
||||||
pseudo: { hover: ['.hover'] },
|
pseudo: { hover: ['.hover'] },
|
||||||
@ -255,71 +255,71 @@ export const WithActions: Story = {
|
|||||||
export const LoadingMenu: Story = {
|
export const LoadingMenu: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: () => (
|
render: () => (
|
||||||
<DropdownMenu>
|
<StyledDropdownMenu>
|
||||||
<DropdownMenuInput value={'query'} autoFocus />
|
<DropdownMenuInput value={'query'} autoFocus />
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
<DropdownMenuSkeletonItem />
|
<DropdownMenuSkeletonItem />
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Search: Story = {
|
export const Search: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<DropdownMenuInput />
|
<DropdownMenuInput />
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{mockSelectArray.map(({ name }) => (
|
{mockSelectArray.map(({ name }) => (
|
||||||
<DropdownMenuItem>{name}</DropdownMenuItem>
|
<DropdownMenuItem>{name}</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SelectableMenuItem: Story = {
|
export const SelectableMenuItem: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
<FakeSelectableMenuItemList />
|
<FakeSelectableMenuItemList />
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SelectableMenuItemWithAvatar: Story = {
|
export const SelectableMenuItemWithAvatar: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
<FakeSelectableMenuItemList hasAvatar />
|
<FakeSelectableMenuItemList hasAvatar />
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CheckableMenuItem: Story = {
|
export const CheckableMenuItem: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
<FakeCheckableMenuItemList />
|
<FakeCheckableMenuItemList />
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CheckableMenuItemWithAvatar: Story = {
|
export const CheckableMenuItemWithAvatar: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
<FakeCheckableMenuItemList hasAvatar />
|
<FakeCheckableMenuItemList hasAvatar />
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,17 +1,27 @@
|
|||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { useRecoilScopedFamilyState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedFamilyState';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
|
||||||
|
|
||||||
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 {
|
const {
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
goBackToPreviousHotkeyScope,
|
goBackToPreviousHotkeyScope,
|
||||||
} = usePreviousHotkeyScope();
|
} = usePreviousHotkeyScope();
|
||||||
|
|
||||||
const [isDropdownButtonOpen, setIsDropdownButtonOpen] = useRecoilScopedState(
|
const [isDropdownButtonOpen, setIsDropdownButtonOpen] =
|
||||||
isDropdownButtonOpenScopedState,
|
useRecoilScopedFamilyState(
|
||||||
|
isDropdownButtonOpenScopedFamilyState,
|
||||||
|
key,
|
||||||
|
DropdownRecoilScopeContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [dropdownButtonCustomHotkeyScope] = useRecoilScopedFamilyState(
|
||||||
|
dropdownButtonCustomHotkeyScopeScopedFamilyState,
|
||||||
|
key,
|
||||||
|
DropdownRecoilScopeContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
function closeDropdownButton() {
|
function closeDropdownButton() {
|
||||||
@ -19,22 +29,22 @@ export function useDropdownButton() {
|
|||||||
setIsDropdownButtonOpen(false);
|
setIsDropdownButtonOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDropdownButton(hotkeyScopeToSet?: HotkeyScope) {
|
function openDropdownButton() {
|
||||||
setIsDropdownButtonOpen(true);
|
setIsDropdownButtonOpen(true);
|
||||||
|
|
||||||
if (hotkeyScopeToSet) {
|
if (dropdownButtonCustomHotkeyScope) {
|
||||||
setHotkeyScopeAndMemorizePreviousScope(
|
setHotkeyScopeAndMemorizePreviousScope(
|
||||||
hotkeyScopeToSet.scope,
|
dropdownButtonCustomHotkeyScope.scope,
|
||||||
hotkeyScopeToSet.customScopes,
|
dropdownButtonCustomHotkeyScope.customScopes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleDropdownButton(hotkeyScopeToSet?: HotkeyScope) {
|
function toggleDropdownButton() {
|
||||||
if (isDropdownButtonOpen) {
|
if (isDropdownButtonOpen) {
|
||||||
closeDropdownButton();
|
closeDropdownButton();
|
||||||
} else {
|
} else {
|
||||||
openDropdownButton(hotkeyScopeToSet);
|
openDropdownButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
});
|
||||||
@ -1,6 +1,9 @@
|
|||||||
import { atomFamily } from 'recoil';
|
import { atomFamily } from 'recoil';
|
||||||
|
|
||||||
export const isDropdownButtonOpenScopedState = atomFamily<boolean, string>({
|
export const isDropdownButtonOpenScopedFamilyState = atomFamily<
|
||||||
|
boolean,
|
||||||
|
string
|
||||||
|
>({
|
||||||
key: 'isDropdownButtonOpenScopedState',
|
key: 'isDropdownButtonOpenScopedState',
|
||||||
default: false,
|
default: false,
|
||||||
});
|
});
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export const DropdownRecoilScopeContext = createContext<string | null>(null);
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { type HTMLAttributes, useRef } from 'react';
|
import { type HTMLAttributes, useRef } from 'react';
|
||||||
import styled from '@emotion/styled';
|
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';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
|
|
||||||
const StyledDropdownMenuContainer = styled.ul<{
|
const StyledDropdownMenuContainer = styled.ul<{
|
||||||
@ -38,7 +38,7 @@ export function DropdownMenuContainer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDropdownMenuContainer data-select-disable {...props} anchor={anchor}>
|
<StyledDropdownMenuContainer data-select-disable {...props} anchor={anchor}>
|
||||||
<DropdownMenu ref={dropdownRef}>{children}</DropdownMenu>
|
<StyledDropdownMenu ref={dropdownRef}>{children}</StyledDropdownMenu>
|
||||||
</StyledDropdownMenuContainer>
|
</StyledDropdownMenuContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Context } from 'react';
|
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 { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ export function FilterDropdownEntitySelect({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<RecoilScope>
|
<RecoilScope>
|
||||||
{filterDefinitionUsedInDropdown.entitySelectComponent}
|
{filterDefinitionUsedInDropdown.entitySelectComponent}
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Context } from 'react';
|
import { Context } from 'react';
|
||||||
|
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||||
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
@ -41,7 +41,7 @@ export function FilterDropdownFilterSelect({
|
|||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{availableFilters.map((availableFilter, index) => (
|
{availableFilters.map((availableFilter, index) => (
|
||||||
<DropdownMenuSelectableItem
|
<DropdownMenuSelectableItem
|
||||||
key={`select-filter-${index}`}
|
key={`select-filter-${index}`}
|
||||||
@ -63,6 +63,6 @@ export function FilterDropdownFilterSelect({
|
|||||||
{availableFilter.label}
|
{availableFilter.label}
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Context } from 'react';
|
import { Context } from 'react';
|
||||||
|
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
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 { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
|
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
|
||||||
@ -62,7 +62,7 @@ export function FilterDropdownOperandSelect({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{operandsForFilterType.map((filterOperand, index) => (
|
{operandsForFilterType.map((filterOperand, index) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={`select-filter-operand-${index}`}
|
key={`select-filter-operand-${index}`}
|
||||||
@ -73,6 +73,6 @@ export function FilterDropdownOperandSelect({
|
|||||||
{getOperandLabel(filterOperand)}
|
{getOperandLabel(filterOperand)}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Context, useCallback, useState } from 'react';
|
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 { filterDefinitionUsedInDropdownScopedState } from '@/ui/filter-n-sort/states/filterDefinitionUsedInDropdownScopedState';
|
||||||
import { filterDropdownSearchInputScopedState } from '@/ui/filter-n-sort/states/filterDropdownSearchInputScopedState';
|
import { filterDropdownSearchInputScopedState } from '@/ui/filter-n-sort/states/filterDropdownSearchInputScopedState';
|
||||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||||
@ -119,7 +119,7 @@ export function MultipleFiltersDropdownButton({
|
|||||||
selectedOperandInDropdown && (
|
selectedOperandInDropdown && (
|
||||||
<>
|
<>
|
||||||
<FilterDropdownOperandButton context={context} />
|
<FilterDropdownOperandButton context={context} />
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
{filterDefinitionUsedInDropdown.type === 'text' && (
|
{filterDefinitionUsedInDropdown.type === 'text' && (
|
||||||
<FilterDropdownTextSearchInput context={context} />
|
<FilterDropdownTextSearchInput context={context} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { selectedOperandInDropdownScopedState } from '@/ui/filter-n-sort/states/
|
|||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
|
import { StyledHeaderDropdownButton } from '../../dropdown/components/StyledHeaderDropdownButton';
|
||||||
import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
|
import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
|
||||||
import { filtersScopedState } from '../states/filtersScopedState';
|
import { filtersScopedState } from '../states/filtersScopedState';
|
||||||
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
|
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
|
||||||
@ -27,29 +28,6 @@ const StyledDropdownButtonContainer = styled.div`
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type StyledDropdownButtonProps = {
|
|
||||||
isUnfolded: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledDropdownButton = styled.div<StyledDropdownButtonProps>`
|
|
||||||
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({
|
export function SingleEntityFilterDropdownButton({
|
||||||
context,
|
context,
|
||||||
HotkeyScope,
|
HotkeyScope,
|
||||||
@ -109,7 +87,7 @@ export function SingleEntityFilterDropdownButton({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDropdownButtonContainer>
|
<StyledDropdownButtonContainer>
|
||||||
<StyledDropdownButton
|
<StyledHeaderDropdownButton
|
||||||
isUnfolded={isUnfolded}
|
isUnfolded={isUnfolded}
|
||||||
onClick={() => handleIsUnfoldedChange(!isUnfolded)}
|
onClick={() => handleIsUnfoldedChange(!isUnfolded)}
|
||||||
>
|
>
|
||||||
@ -119,7 +97,7 @@ export function SingleEntityFilterDropdownButton({
|
|||||||
'Filter'
|
'Filter'
|
||||||
)}
|
)}
|
||||||
<IconChevronDown size={theme.icon.size.md} />
|
<IconChevronDown size={theme.icon.size.md} />
|
||||||
</StyledDropdownButton>
|
</StyledHeaderDropdownButton>
|
||||||
{isUnfolded && (
|
{isUnfolded && (
|
||||||
<DropdownMenuContainer onClose={() => handleIsUnfoldedChange(false)}>
|
<DropdownMenuContainer onClose={() => handleIsUnfoldedChange(false)}>
|
||||||
<FilterDropdownEntitySearchInput context={context} />
|
<FilterDropdownEntitySearchInput context={context} />
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import { useTheme } from '@emotion/react';
|
|||||||
import { IconChevronDown } from '@tabler/icons-react';
|
import { IconChevronDown } from '@tabler/icons-react';
|
||||||
|
|
||||||
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
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 { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ export function SortDropdownButton<SortField>({
|
|||||||
HotkeyScope={HotkeyScope}
|
HotkeyScope={HotkeyScope}
|
||||||
>
|
>
|
||||||
{isOptionUnfolded ? (
|
{isOptionUnfolded ? (
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{options.map((option, index) => (
|
{options.map((option, index) => (
|
||||||
<DropdownMenuSelectableItem
|
<DropdownMenuSelectableItem
|
||||||
key={index}
|
key={index}
|
||||||
@ -93,7 +93,7 @@ export function SortDropdownButton<SortField>({
|
|||||||
{option === 'asc' ? 'Ascending' : 'Descending'}
|
{option === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
@ -102,9 +102,9 @@ export function SortDropdownButton<SortField>({
|
|||||||
>
|
>
|
||||||
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
|
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{availableSorts.map((sort, index) => (
|
{availableSorts.map((sort, index) => (
|
||||||
<DropdownMenuSelectableItem
|
<DropdownMenuSelectableItem
|
||||||
key={index}
|
key={index}
|
||||||
@ -114,7 +114,7 @@ export function SortDropdownButton<SortField>({
|
|||||||
<OverflowingTextWithTooltip text={sort.label} />
|
<OverflowingTextWithTooltip text={sort.label} />
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
|
|
||||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
|
||||||
import { DropdownMenuCheckableItem } from '@/ui/dropdown/components/DropdownMenuCheckableItem';
|
import { DropdownMenuCheckableItem } from '@/ui/dropdown/components/DropdownMenuCheckableItem';
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||||
@ -72,14 +72,14 @@ export function MultipleEntitySelect<
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu ref={containerRef}>
|
<StyledDropdownMenu ref={containerRef}>
|
||||||
<DropdownMenuInput
|
<DropdownMenuInput
|
||||||
value={searchFilter}
|
value={searchFilter}
|
||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{entitiesInDropdown?.map((entity) => (
|
{entitiesInDropdown?.map((entity) => (
|
||||||
<DropdownMenuCheckableItem
|
<DropdownMenuCheckableItem
|
||||||
key={entity.id}
|
key={entity.id}
|
||||||
@ -101,7 +101,7 @@ export function MultipleEntitySelect<
|
|||||||
{entitiesInDropdown?.length === 0 && (
|
{entitiesInDropdown?.length === 0 && (
|
||||||
<DropdownMenuItem>No result</DropdownMenuItem>
|
<DropdownMenuItem>No result</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
import { IconPlus } from '@/ui/icon';
|
import { IconPlus } from '@/ui/icon';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
@ -55,7 +55,7 @@ export function SingleEntitySelect<
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu
|
<StyledDropdownMenu
|
||||||
disableBlur={disableBackgroundBlur}
|
disableBlur={disableBackgroundBlur}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
width={width}
|
width={width}
|
||||||
@ -65,7 +65,7 @@ export function SingleEntitySelect<
|
|||||||
onChange={handleSearchFilterChange}
|
onChange={handleSearchFilterChange}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<SingleEntitySelectBase
|
<SingleEntitySelectBase
|
||||||
entities={entities}
|
entities={entities}
|
||||||
onEntitySelected={onEntitySelected}
|
onEntitySelected={onEntitySelected}
|
||||||
@ -73,15 +73,15 @@ export function SingleEntitySelect<
|
|||||||
/>
|
/>
|
||||||
{showCreateButton && (
|
{showCreateButton && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
<DropdownMenuItem onClick={onCreate}>
|
<DropdownMenuItem onClick={onCreate}>
|
||||||
<IconPlus size={theme.icon.size.md} />
|
<IconPlus size={theme.icon.size.md} />
|
||||||
Add New
|
Add New
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DropdownMenu>
|
</StyledDropdownMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { useRef } from 'react';
|
|||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||||
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
@ -73,7 +73,7 @@ export function SingleEntitySelectBase<
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItemsContainer ref={containerRef} hasMaxHeight>
|
<StyledDropdownMenuItemsContainer ref={containerRef} hasMaxHeight>
|
||||||
{entities.loading ? (
|
{entities.loading ? (
|
||||||
<DropdownMenuSkeletonItem />
|
<DropdownMenuSkeletonItem />
|
||||||
) : entitiesInDropdown.length === 0 ? (
|
) : entitiesInDropdown.length === 0 ? (
|
||||||
@ -97,6 +97,6 @@ export function SingleEntitySelectBase<
|
|||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
/* eslint-disable twenty/styled-components-prefixed-with-styled */
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const TextInputDisplay = styled.div`
|
const StyledTextInputDisplay = styled.div`
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export type TextInputDisplayProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TextInputDisplay({ children }: TextInputDisplayProps) {
|
||||||
|
return <StyledTextInputDisplay>{children}</StyledTextInputDisplay>;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable twenty/styled-components-prefixed-with-styled */
|
import { ReactElement } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
export const ShowPageContainer = styled.div`
|
export const StyledShowPageContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: ${() => (useIsMobile() ? 'column' : 'row')};
|
flex-direction: ${() => (useIsMobile() ? 'column' : 'row')};
|
||||||
gap: ${({ theme }) => (useIsMobile() ? theme.spacing(3) : '0')};
|
gap: ${({ theme }) => (useIsMobile() ? theme.spacing(3) : '0')};
|
||||||
@ -11,3 +11,11 @@ export const ShowPageContainer = styled.div`
|
|||||||
overflow-x: ${() => (useIsMobile() ? 'hidden' : 'auto')};
|
overflow-x: ${() => (useIsMobile() ? 'hidden' : 'auto')};
|
||||||
width: ${() => (useIsMobile() ? `calc(100% - 2px);` : '100%')};
|
width: ${() => (useIsMobile() ? `calc(100% - 2px);` : '100%')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export type ShowPageContainerProps = {
|
||||||
|
children: ReactElement[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ShowPageContainer({ children }: ShowPageContainerProps) {
|
||||||
|
return <StyledShowPageContainer>{children} </StyledShowPageContainer>;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable twenty/styled-components-prefixed-with-styled */
|
import { ReactElement } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
export const ShowPageLeftContainer = styled.div`
|
const StyledShowPageLeftContainer = styled.div`
|
||||||
background: ${({ theme }) => theme.background.secondary};
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
border-bottom-left-radius: 8px;
|
border-bottom-left-radius: 8px;
|
||||||
border-right: 1px solid
|
border-right: 1px solid
|
||||||
@ -24,3 +24,13 @@ export const ShowPageLeftContainer = styled.div`
|
|||||||
|
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export type ShowPageLeftContainerProps = {
|
||||||
|
children: ReactElement[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ShowPageLeftContainer({
|
||||||
|
children,
|
||||||
|
}: ShowPageLeftContainerProps) {
|
||||||
|
return <StyledShowPageLeftContainer>{children} </StyledShowPageLeftContainer>;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable twenty/styled-components-prefixed-with-styled */
|
import { ReactElement } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
export const ShowPageRightContainer = styled.div`
|
export const StyledShowPageRightContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 0 0;
|
flex: 1 0 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -15,3 +15,15 @@ export const ShowPageRightContainer = styled.div`
|
|||||||
return isMobile ? `calc(100% - ${theme.spacing(6)})` : 'auto';
|
return isMobile ? `calc(100% - ${theme.spacing(6)})` : 'auto';
|
||||||
}};
|
}};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export type ShowPageRightContainerProps = {
|
||||||
|
children: ReactElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ShowPageRightContainer({
|
||||||
|
children,
|
||||||
|
}: ShowPageRightContainerProps) {
|
||||||
|
return (
|
||||||
|
<StyledShowPageRightContainer>{children} </StyledShowPageRightContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -4,15 +4,15 @@ import styled from '@emotion/styled';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
import { IconButton } from '@/ui/button/components/IconButton';
|
||||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
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 { IconPlus } from '@/ui/icon';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
|
|
||||||
import { hiddenTableColumnsState } from '../states/tableColumnsState';
|
import { hiddenTableColumnsState } from '../states/tableColumnsState';
|
||||||
|
|
||||||
const StyledColumnMenu = styled(DropdownMenu)`
|
const StyledColumnMenu = styled(StyledDropdownMenu)`
|
||||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ export const EntityTableColumnMenu = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumnMenu {...props} ref={ref}>
|
<StyledColumnMenu {...props} ref={ref}>
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{hiddenColumns.map((column) => (
|
{hiddenColumns.map((column) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={column.id}
|
key={column.id}
|
||||||
@ -56,7 +56,7 @@ export const EntityTableColumnMenu = ({
|
|||||||
{column.columnLabel}
|
{column.columnLabel}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledColumnMenu>
|
</StyledColumnMenu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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<ViewFieldMetadata>[]) => void;
|
||||||
|
onViewsChange?: (views: TableView[]) => void;
|
||||||
|
onImport?: () => void;
|
||||||
|
customHotkeyScope: HotkeyScope;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TableOptionsDropdown({
|
||||||
|
onColumnsChange,
|
||||||
|
onViewsChange,
|
||||||
|
onImport,
|
||||||
|
customHotkeyScope,
|
||||||
|
}: TableOptionsDropdownProps) {
|
||||||
|
return (
|
||||||
|
<DropdownButton
|
||||||
|
buttonComponents={<TableOptionsDropdownButton />}
|
||||||
|
dropdownHotkeyScope={customHotkeyScope}
|
||||||
|
dropdownKey="options"
|
||||||
|
dropdownComponents={
|
||||||
|
<TableOptionsDropdownContent
|
||||||
|
onColumnsChange={onColumnsChange}
|
||||||
|
onImport={onImport}
|
||||||
|
onViewsChange={onViewsChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,273 +1,17 @@
|
|||||||
import {
|
import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton';
|
||||||
type FormEvent,
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
export function TableOptionsDropdownButton() {
|
||||||
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
key: 'options',
|
||||||
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<ViewFieldMetadata>[]) => 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<Option | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const viewEditInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const [columns, setColumns] = useRecoilState(tableColumnsState);
|
|
||||||
const [viewEditMode, setViewEditMode] = useRecoilState(
|
|
||||||
tableViewEditModeState,
|
|
||||||
);
|
|
||||||
const [views, setViews] = useRecoilScopedState(
|
|
||||||
tableViewsState,
|
|
||||||
TableRecoilScopeContext,
|
|
||||||
);
|
|
||||||
const visibleColumns = useRecoilValue(visibleTableColumnsState);
|
|
||||||
const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
|
|
||||||
const viewsById = useRecoilScopedValue(
|
|
||||||
tableViewsByIdState,
|
|
||||||
TableRecoilScopeContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
goBackToPreviousHotkeyScope,
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
|
||||||
} = usePreviousHotkeyScope();
|
|
||||||
|
|
||||||
const handleColumnVisibilityChange = useCallback(
|
|
||||||
(columnId: string, nextIsVisible: boolean) => {
|
|
||||||
const nextColumns = columns.map((column) =>
|
|
||||||
column.id === columnId
|
|
||||||
? { ...column, isVisible: nextIsVisible }
|
|
||||||
: column,
|
|
||||||
);
|
|
||||||
|
|
||||||
(onColumnsChange ?? setColumns)(nextColumns);
|
|
||||||
},
|
|
||||||
[columns, onColumnsChange, setColumns],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderFieldActions = useCallback(
|
|
||||||
(column: ViewFieldDefinition<ViewFieldMetadata>) =>
|
|
||||||
// Do not allow hiding last visible column
|
|
||||||
!column.isVisible || visibleColumns.length > 1 ? (
|
|
||||||
<IconButton
|
|
||||||
icon={
|
|
||||||
column.isVisible ? (
|
|
||||||
<IconMinus size={theme.icon.size.sm} />
|
|
||||||
) : (
|
|
||||||
<IconPlus size={theme.icon.size.sm} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={() =>
|
|
||||||
handleColumnVisibilityChange(column.id, !column.isVisible)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : undefined,
|
|
||||||
[handleColumnVisibilityChange, theme.icon.size.sm, visibleColumns.length],
|
|
||||||
);
|
|
||||||
|
|
||||||
const resetViewEditMode = useCallback(() => {
|
|
||||||
setViewEditMode({ mode: undefined, viewId: undefined });
|
|
||||||
|
|
||||||
if (viewEditInputRef.current) {
|
|
||||||
viewEditInputRef.current.value = '';
|
|
||||||
}
|
|
||||||
}, [setViewEditMode]);
|
|
||||||
|
|
||||||
const handleViewNameSubmit = useCallback(
|
|
||||||
(event?: FormEvent) => {
|
|
||||||
event?.preventDefault();
|
|
||||||
|
|
||||||
if (viewEditMode.mode && viewEditInputRef.current?.value) {
|
|
||||||
const name = viewEditInputRef.current.value;
|
|
||||||
const nextViews =
|
|
||||||
viewEditMode.mode === 'create'
|
|
||||||
? [...views, { id: v4(), name }]
|
|
||||||
: views.map((view) =>
|
|
||||||
view.id === viewEditMode.viewId ? { ...view, name } : view,
|
|
||||||
);
|
|
||||||
|
|
||||||
(onViewsChange ?? setViews)(nextViews);
|
|
||||||
}
|
|
||||||
|
|
||||||
resetViewEditMode();
|
|
||||||
},
|
|
||||||
[
|
|
||||||
onViewsChange,
|
|
||||||
resetViewEditMode,
|
|
||||||
setViews,
|
|
||||||
viewEditMode.mode,
|
|
||||||
viewEditMode.viewId,
|
|
||||||
views,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSelectOption = useCallback(
|
|
||||||
(option: Option) => {
|
|
||||||
handleViewNameSubmit();
|
|
||||||
setIsUnfolded(true);
|
|
||||||
setSelectedOption(option);
|
|
||||||
},
|
|
||||||
[handleViewNameSubmit],
|
|
||||||
);
|
|
||||||
|
|
||||||
const resetSelectedOption = useCallback(() => {
|
|
||||||
setSelectedOption(undefined);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleUnfoldedChange = useCallback(
|
|
||||||
(nextIsUnfolded: boolean) => {
|
|
||||||
setIsUnfolded(nextIsUnfolded);
|
|
||||||
|
|
||||||
if (!nextIsUnfolded) {
|
|
||||||
handleViewNameSubmit();
|
|
||||||
resetSelectedOption();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleViewNameSubmit, resetSelectedOption],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
isUnfolded || viewEditMode.mode
|
|
||||||
? setHotkeyScopeAndMemorizePreviousScope(HotkeyScope)
|
|
||||||
: goBackToPreviousHotkeyScope();
|
|
||||||
}, [
|
|
||||||
HotkeyScope,
|
|
||||||
goBackToPreviousHotkeyScope,
|
|
||||||
isUnfolded,
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
|
||||||
viewEditMode.mode,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownButton
|
<StyledHeaderDropdownButton
|
||||||
label="Options"
|
isUnfolded={isDropdownButtonOpen}
|
||||||
isActive={false}
|
onClick={toggleDropdownButton}
|
||||||
isUnfolded={isUnfolded || !!viewEditMode.mode}
|
|
||||||
onIsUnfoldedChange={handleUnfoldedChange}
|
|
||||||
HotkeyScope={HotkeyScope}
|
|
||||||
>
|
>
|
||||||
{!selectedOption && (
|
Options
|
||||||
<>
|
</StyledHeaderDropdownButton>
|
||||||
{!!viewEditMode.mode ? (
|
|
||||||
<DropdownMenuInput
|
|
||||||
ref={viewEditInputRef}
|
|
||||||
autoFocus
|
|
||||||
placeholder={
|
|
||||||
viewEditMode.mode === 'create' ? 'New view' : 'View name'
|
|
||||||
}
|
|
||||||
defaultValue={
|
|
||||||
viewEditMode.viewId
|
|
||||||
? viewsById[viewEditMode.viewId]?.name
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<DropdownMenuHeader>View settings</DropdownMenuHeader>
|
|
||||||
)}
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItemsContainer>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleSelectOption(Option.Properties)}
|
|
||||||
>
|
|
||||||
<IconTag size={theme.icon.size.md} />
|
|
||||||
Properties
|
|
||||||
</DropdownMenuItem>
|
|
||||||
{onImport && (
|
|
||||||
<DropdownMenuItem onClick={onImport}>
|
|
||||||
<IconFileImport size={theme.icon.size.md} />
|
|
||||||
Import
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
</DropdownMenuItemsContainer>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{selectedOption === Option.Properties && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuHeader
|
|
||||||
startIcon={<IconChevronLeft size={theme.icon.size.md} />}
|
|
||||||
onClick={resetSelectedOption}
|
|
||||||
>
|
|
||||||
Properties
|
|
||||||
</DropdownMenuHeader>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<TableOptionsDropdownSection
|
|
||||||
renderActions={renderFieldActions}
|
|
||||||
title="Visible"
|
|
||||||
columns={visibleColumns}
|
|
||||||
/>
|
|
||||||
{hiddenColumns.length > 0 && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<TableOptionsDropdownSection
|
|
||||||
renderActions={renderFieldActions}
|
|
||||||
title="Hidden"
|
|
||||||
columns={hiddenColumns}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DropdownButton>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@ -0,0 +1,250 @@
|
|||||||
|
import { type FormEvent, useCallback, useRef, useState } from 'react';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
import { Key } from 'ts-key-enum';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
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 { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
|
import type {
|
||||||
|
ViewFieldDefinition,
|
||||||
|
ViewFieldMetadata,
|
||||||
|
} from '@/ui/editable-field/types/ViewField';
|
||||||
|
import {
|
||||||
|
IconChevronLeft,
|
||||||
|
IconFileImport,
|
||||||
|
IconMinus,
|
||||||
|
IconPlus,
|
||||||
|
IconTag,
|
||||||
|
} from '@/ui/icon';
|
||||||
|
import {
|
||||||
|
hiddenTableColumnsState,
|
||||||
|
tableColumnsState,
|
||||||
|
visibleTableColumnsState,
|
||||||
|
} from '@/ui/table/states/tableColumnsState';
|
||||||
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
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<ViewFieldMetadata>[]) => void;
|
||||||
|
onViewsChange?: (views: TableView[]) => void;
|
||||||
|
onImport?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Option {
|
||||||
|
Properties = 'Properties',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TableOptionsDropdownContent({
|
||||||
|
onColumnsChange,
|
||||||
|
onViewsChange,
|
||||||
|
onImport,
|
||||||
|
}: TableOptionsDropdownButtonProps) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { closeDropdownButton } = useDropdownButton({ key: 'options' });
|
||||||
|
|
||||||
|
const [selectedOption, setSelectedOption] = useState<Option | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const viewEditInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const [columns, setColumns] = useRecoilState(tableColumnsState);
|
||||||
|
const [viewEditMode, setViewEditMode] = useRecoilState(
|
||||||
|
tableViewEditModeState,
|
||||||
|
);
|
||||||
|
const [views, setViews] = useRecoilScopedState(
|
||||||
|
tableViewsState,
|
||||||
|
TableRecoilScopeContext,
|
||||||
|
);
|
||||||
|
const visibleColumns = useRecoilValue(visibleTableColumnsState);
|
||||||
|
const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
|
||||||
|
const viewsById = useRecoilScopedValue(
|
||||||
|
tableViewsByIdState,
|
||||||
|
TableRecoilScopeContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleColumnVisibilityChange = useCallback(
|
||||||
|
(columnId: string, nextIsVisible: boolean) => {
|
||||||
|
const nextColumns = columns.map((column) =>
|
||||||
|
column.id === columnId
|
||||||
|
? { ...column, isVisible: nextIsVisible }
|
||||||
|
: column,
|
||||||
|
);
|
||||||
|
|
||||||
|
(onColumnsChange ?? setColumns)(nextColumns);
|
||||||
|
},
|
||||||
|
[columns, onColumnsChange, setColumns],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderFieldActions = useCallback(
|
||||||
|
(column: ViewFieldDefinition<ViewFieldMetadata>) =>
|
||||||
|
// Do not allow hiding last visible column
|
||||||
|
!column.isVisible || visibleColumns.length > 1 ? (
|
||||||
|
<IconButton
|
||||||
|
icon={
|
||||||
|
column.isVisible ? (
|
||||||
|
<IconMinus size={theme.icon.size.sm} />
|
||||||
|
) : (
|
||||||
|
<IconPlus size={theme.icon.size.sm} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={() =>
|
||||||
|
handleColumnVisibilityChange(column.id, !column.isVisible)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : undefined,
|
||||||
|
[handleColumnVisibilityChange, theme.icon.size.sm, visibleColumns.length],
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetViewEditMode = useCallback(() => {
|
||||||
|
setViewEditMode({ mode: undefined, viewId: undefined });
|
||||||
|
|
||||||
|
if (viewEditInputRef.current) {
|
||||||
|
viewEditInputRef.current.value = '';
|
||||||
|
}
|
||||||
|
}, [setViewEditMode]);
|
||||||
|
|
||||||
|
const handleViewNameSubmit = useCallback(
|
||||||
|
(event?: FormEvent) => {
|
||||||
|
event?.preventDefault();
|
||||||
|
|
||||||
|
if (viewEditMode.mode && viewEditInputRef.current?.value) {
|
||||||
|
const name = viewEditInputRef.current.value;
|
||||||
|
const nextViews =
|
||||||
|
viewEditMode.mode === 'create'
|
||||||
|
? [...views, { id: v4(), name }]
|
||||||
|
: views.map((view) =>
|
||||||
|
view.id === viewEditMode.viewId ? { ...view, name } : view,
|
||||||
|
);
|
||||||
|
|
||||||
|
(onViewsChange ?? setViews)(nextViews);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetViewEditMode();
|
||||||
|
},
|
||||||
|
[
|
||||||
|
onViewsChange,
|
||||||
|
resetViewEditMode,
|
||||||
|
setViews,
|
||||||
|
viewEditMode.mode,
|
||||||
|
viewEditMode.viewId,
|
||||||
|
views,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelectOption = useCallback(
|
||||||
|
(option: Option) => {
|
||||||
|
handleViewNameSubmit();
|
||||||
|
setSelectedOption(option);
|
||||||
|
},
|
||||||
|
[handleViewNameSubmit],
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetSelectedOption = useCallback(() => {
|
||||||
|
setSelectedOption(undefined);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
Key.Escape,
|
||||||
|
() => {
|
||||||
|
closeDropdownButton();
|
||||||
|
},
|
||||||
|
TableOptionsHotkeyScope.Dropdown,
|
||||||
|
);
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
Key.Enter,
|
||||||
|
() => {
|
||||||
|
handleViewNameSubmit();
|
||||||
|
resetSelectedOption();
|
||||||
|
closeDropdownButton();
|
||||||
|
},
|
||||||
|
TableOptionsHotkeyScope.Dropdown,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledDropdownMenu>
|
||||||
|
{!selectedOption && (
|
||||||
|
<>
|
||||||
|
{!!viewEditMode.mode ? (
|
||||||
|
<DropdownMenuInput
|
||||||
|
ref={viewEditInputRef}
|
||||||
|
autoFocus
|
||||||
|
placeholder={
|
||||||
|
viewEditMode.mode === 'create' ? 'New view' : 'View name'
|
||||||
|
}
|
||||||
|
defaultValue={
|
||||||
|
viewEditMode.viewId
|
||||||
|
? viewsById[viewEditMode.viewId]?.name
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DropdownMenuHeader>View settings</DropdownMenuHeader>
|
||||||
|
)}
|
||||||
|
<StyledDropdownMenuSeparator />
|
||||||
|
<StyledDropdownMenuItemsContainer>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => handleSelectOption(Option.Properties)}
|
||||||
|
>
|
||||||
|
<IconTag size={theme.icon.size.md} />
|
||||||
|
Properties
|
||||||
|
</DropdownMenuItem>
|
||||||
|
{onImport && (
|
||||||
|
<DropdownMenuItem onClick={onImport}>
|
||||||
|
<IconFileImport size={theme.icon.size.md} />
|
||||||
|
Import
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</StyledDropdownMenuItemsContainer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{selectedOption === Option.Properties && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuHeader
|
||||||
|
startIcon={<IconChevronLeft size={theme.icon.size.md} />}
|
||||||
|
onClick={resetSelectedOption}
|
||||||
|
>
|
||||||
|
Properties
|
||||||
|
</DropdownMenuHeader>
|
||||||
|
<StyledDropdownMenuSeparator />
|
||||||
|
<TableOptionsDropdownSection
|
||||||
|
renderActions={renderFieldActions}
|
||||||
|
title="Visible"
|
||||||
|
columns={visibleColumns}
|
||||||
|
/>
|
||||||
|
{hiddenColumns.length > 0 && (
|
||||||
|
<>
|
||||||
|
<StyledDropdownMenuSeparator />
|
||||||
|
<TableOptionsDropdownSection
|
||||||
|
renderActions={renderFieldActions}
|
||||||
|
title="Hidden"
|
||||||
|
columns={hiddenColumns}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</StyledDropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -5,8 +5,8 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuItemProps,
|
DropdownMenuItemProps,
|
||||||
} from '@/ui/dropdown/components/DropdownMenuItem';
|
} from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { DropdownMenuSubheader } from '@/ui/dropdown/components/DropdownMenuSubheader';
|
import { StyledDropdownMenuSubheader } from '@/ui/dropdown/components/StyledDropdownMenuSubheader';
|
||||||
import {
|
import {
|
||||||
ViewFieldDefinition,
|
ViewFieldDefinition,
|
||||||
ViewFieldMetadata,
|
ViewFieldMetadata,
|
||||||
@ -20,17 +20,17 @@ type TableOptionsDropdownSectionProps = {
|
|||||||
columns: ViewFieldDefinition<ViewFieldMetadata>[];
|
columns: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TableOptionsDropdownSection = ({
|
export function TableOptionsDropdownSection({
|
||||||
renderActions,
|
renderActions,
|
||||||
title,
|
title,
|
||||||
columns,
|
columns,
|
||||||
}: TableOptionsDropdownSectionProps) => {
|
}: TableOptionsDropdownSectionProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSubheader>{title}</DropdownMenuSubheader>
|
<StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader>
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{columns.map((column) => (
|
{columns.map((column) => (
|
||||||
<DropdownMenuItem key={column.id} actions={renderActions(column)}>
|
<DropdownMenuItem key={column.id} actions={renderActions(column)}>
|
||||||
{column.columnIcon &&
|
{column.columnIcon &&
|
||||||
@ -40,7 +40,7 @@ export const TableOptionsDropdownSection = ({
|
|||||||
{column.columnLabel}
|
{column.columnLabel}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { Key } from 'ts-key-enum';
|
|||||||
import { Button, ButtonSize } from '@/ui/button/components/Button';
|
import { Button, ButtonSize } from '@/ui/button/components/Button';
|
||||||
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { DropdownMenuContainer } from '@/ui/filter-n-sort/components/DropdownMenuContainer';
|
import { DropdownMenuContainer } from '@/ui/filter-n-sort/components/DropdownMenuContainer';
|
||||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||||
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
|
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
|
||||||
@ -110,12 +110,12 @@ export const TableUpdateViewButtonGroup = ({
|
|||||||
|
|
||||||
{isDropdownOpen && (
|
{isDropdownOpen && (
|
||||||
<StyledDropdownMenuContainer onClose={handleDropdownClose}>
|
<StyledDropdownMenuContainer onClose={handleDropdownClose}>
|
||||||
<DropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuItem onClick={handleCreateViewButtonClick}>
|
<DropdownMenuItem onClick={handleCreateViewButtonClick}>
|
||||||
<IconPlus size={theme.icon.size.md} />
|
<IconPlus size={theme.icon.size.md} />
|
||||||
Create view
|
Create view
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledDropdownMenuContainer>
|
</StyledDropdownMenuContainer>
|
||||||
)}
|
)}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|||||||
@ -5,8 +5,9 @@ import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
|||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
import { IconButton } from '@/ui/button/components/IconButton';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
|
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
|
||||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||||
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
|
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
|
||||||
@ -34,7 +35,9 @@ import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoi
|
|||||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||||
import { TableViewsHotkeyScope } from '../../types/TableViewsHotkeyScope';
|
import { TableViewsHotkeyScope } from '../../types/TableViewsHotkeyScope';
|
||||||
|
|
||||||
const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
|
const StyledBoldDropdownMenuItemsContainer = styled(
|
||||||
|
StyledDropdownMenuItemsContainer,
|
||||||
|
)`
|
||||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -66,6 +69,10 @@ export const TableViewsDropdownButton = ({
|
|||||||
|
|
||||||
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
||||||
|
|
||||||
|
const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({
|
||||||
|
key: 'options',
|
||||||
|
});
|
||||||
|
|
||||||
const currentView = useRecoilScopedValue(
|
const currentView = useRecoilScopedValue(
|
||||||
currentTableViewState,
|
currentTableViewState,
|
||||||
TableRecoilScopeContext,
|
TableRecoilScopeContext,
|
||||||
@ -105,8 +112,9 @@ export const TableViewsDropdownButton = ({
|
|||||||
|
|
||||||
const handleAddViewButtonClick = useCallback(() => {
|
const handleAddViewButtonClick = useCallback(() => {
|
||||||
setViewEditMode({ mode: 'create', viewId: undefined });
|
setViewEditMode({ mode: 'create', viewId: undefined });
|
||||||
|
openOptionsDropdownButton();
|
||||||
setIsUnfolded(false);
|
setIsUnfolded(false);
|
||||||
}, [setViewEditMode]);
|
}, [setViewEditMode, openOptionsDropdownButton]);
|
||||||
|
|
||||||
const handleEditViewButtonClick = useCallback(
|
const handleEditViewButtonClick = useCallback(
|
||||||
(event: MouseEvent<HTMLButtonElement>, viewId: string) => {
|
(event: MouseEvent<HTMLButtonElement>, viewId: string) => {
|
||||||
@ -184,13 +192,13 @@ export const TableViewsDropdownButton = ({
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledBoldDropdownMenuItemsContainer>
|
||||||
<DropdownMenuItem onClick={handleAddViewButtonClick}>
|
<DropdownMenuItem onClick={handleAddViewButtonClick}>
|
||||||
<IconPlus size={theme.icon.size.md} />
|
<IconPlus size={theme.icon.size.md} />
|
||||||
Add view
|
Add view
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledBoldDropdownMenuItemsContainer>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
||||||
import type {
|
import type {
|
||||||
ViewFieldDefinition,
|
ViewFieldDefinition,
|
||||||
ViewFieldMetadata,
|
ViewFieldMetadata,
|
||||||
@ -10,10 +11,11 @@ import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownBu
|
|||||||
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
||||||
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
|
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
|
||||||
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
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 { 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 { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
|
import { TableOptionsDropdown } from '../../options/components/TableOptionsDropdown';
|
||||||
import { TableUpdateViewButtonGroup } from '../../options/components/TableUpdateViewButtonGroup';
|
import { TableUpdateViewButtonGroup } from '../../options/components/TableUpdateViewButtonGroup';
|
||||||
import { TableViewsDropdownButton } from '../../options/components/TableViewsDropdownButton';
|
import { TableViewsDropdownButton } from '../../options/components/TableViewsDropdownButton';
|
||||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||||
@ -60,54 +62,56 @@ export function TableHeader<SortField>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TopBar
|
<RecoilScope SpecificContext={DropdownRecoilScopeContext}>
|
||||||
leftComponent={
|
<TopBar
|
||||||
<TableViewsDropdownButton
|
leftComponent={
|
||||||
defaultViewName={viewName}
|
<TableViewsDropdownButton
|
||||||
onViewsChange={onViewsChange}
|
defaultViewName={viewName}
|
||||||
HotkeyScope={TableViewsHotkeyScope.ListDropdown}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
displayBottomBorder={false}
|
|
||||||
rightComponent={
|
|
||||||
<>
|
|
||||||
<FilterDropdownButton
|
|
||||||
context={TableRecoilScopeContext}
|
|
||||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
|
||||||
isPrimaryButton
|
|
||||||
/>
|
|
||||||
<SortDropdownButton<SortField>
|
|
||||||
context={TableRecoilScopeContext}
|
|
||||||
isSortSelected={sorts.length > 0}
|
|
||||||
availableSorts={availableSorts || []}
|
|
||||||
onSortSelect={sortSelect}
|
|
||||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
|
||||||
isPrimaryButton
|
|
||||||
/>
|
|
||||||
<TableOptionsDropdownButton
|
|
||||||
onImport={onImport}
|
|
||||||
onColumnsChange={onColumnsChange}
|
|
||||||
onViewsChange={onViewsChange}
|
onViewsChange={onViewsChange}
|
||||||
HotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
HotkeyScope={TableViewsHotkeyScope.ListDropdown}
|
||||||
/>
|
/>
|
||||||
</>
|
}
|
||||||
}
|
displayBottomBorder={false}
|
||||||
bottomComponent={
|
rightComponent={
|
||||||
<SortAndFilterBar
|
<>
|
||||||
context={TableRecoilScopeContext}
|
<FilterDropdownButton
|
||||||
sorts={sorts}
|
context={TableRecoilScopeContext}
|
||||||
onRemoveSort={sortUnselect}
|
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||||
onCancelClick={() => setSorts([])}
|
isPrimaryButton
|
||||||
hasFilterButton
|
|
||||||
rightComponent={
|
|
||||||
<TableUpdateViewButtonGroup
|
|
||||||
onViewSubmit={onViewSubmit}
|
|
||||||
HotkeyScope={TableViewsHotkeyScope.CreateDropdown}
|
|
||||||
/>
|
/>
|
||||||
}
|
<SortDropdownButton<SortField>
|
||||||
/>
|
context={TableRecoilScopeContext}
|
||||||
}
|
isSortSelected={sorts.length > 0}
|
||||||
/>
|
availableSorts={availableSorts || []}
|
||||||
|
onSortSelect={sortSelect}
|
||||||
|
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||||
|
isPrimaryButton
|
||||||
|
/>
|
||||||
|
<TableOptionsDropdown
|
||||||
|
onImport={onImport}
|
||||||
|
onColumnsChange={onColumnsChange}
|
||||||
|
onViewsChange={onViewsChange}
|
||||||
|
customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
bottomComponent={
|
||||||
|
<SortAndFilterBar
|
||||||
|
context={TableRecoilScopeContext}
|
||||||
|
sorts={sorts}
|
||||||
|
onRemoveSort={sortUnselect}
|
||||||
|
onCancelClick={() => setSorts([])}
|
||||||
|
hasFilterButton
|
||||||
|
rightComponent={
|
||||||
|
<TableUpdateViewButtonGroup
|
||||||
|
onViewSubmit={onViewSubmit}
|
||||||
|
HotkeyScope={TableViewsHotkeyScope.CreateDropdown}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</RecoilScope>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable twenty/styled-components-prefixed-with-styled */
|
import { PlacesType, PositionStrategy, Tooltip } from 'react-tooltip';
|
||||||
import { Tooltip } from 'react-tooltip';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { rgba } from '../theme/constants/colors';
|
import { rgba } from '../theme/constants/colors';
|
||||||
@ -11,7 +10,7 @@ export enum TooltipPosition {
|
|||||||
Bottom = 'bottom',
|
Bottom = 'bottom',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppTooltip = styled(Tooltip)`
|
const StyledAppTooltip = styled(Tooltip)`
|
||||||
backdrop-filter: ${({ theme }) => theme.blur.strong};
|
backdrop-filter: ${({ theme }) => theme.blur.strong};
|
||||||
background-color: ${({ theme }) => rgba(theme.color.gray80, 0.8)};
|
background-color: ${({ theme }) => rgba(theme.color.gray80, 0.8)};
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
@ -31,3 +30,19 @@ export const AppTooltip = styled(Tooltip)`
|
|||||||
|
|
||||||
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
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 <StyledAppTooltip {...props} />;
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { Context, useContext } from 'react';
|
||||||
|
import { RecoilState, useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { RecoilScopeContext } from '../states/RecoilScopeContext';
|
||||||
|
|
||||||
|
export function useRecoilScopedFamilyState<StateType>(
|
||||||
|
recoilState: (param: string) => RecoilState<StateType>,
|
||||||
|
stateKey: string,
|
||||||
|
SpecificContext?: Context<string | null>,
|
||||||
|
) {
|
||||||
|
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<StateType>(recoilState(recoilScopeId + stateKey));
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import { EntityBoard } from '@/ui/board/components/EntityBoard';
|
|||||||
import { EntityBoardActionBar } from '@/ui/board/components/EntityBoardActionBar';
|
import { EntityBoardActionBar } from '@/ui/board/components/EntityBoardActionBar';
|
||||||
import { EntityBoardContextMenu } from '@/ui/board/components/EntityBoardContextMenu';
|
import { EntityBoardContextMenu } from '@/ui/board/components/EntityBoardContextMenu';
|
||||||
import { BoardOptionsContext } from '@/ui/board/contexts/BoardOptionsContext';
|
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 { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
|
||||||
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
|
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
|
||||||
import { IconTargetArrow } from '@/ui/icon';
|
import { IconTargetArrow } from '@/ui/icon';
|
||||||
@ -72,7 +73,7 @@ export function Opportunities() {
|
|||||||
title="Opportunities"
|
title="Opportunities"
|
||||||
icon={<IconTargetArrow size={theme.icon.size.md} />}
|
icon={<IconTargetArrow size={theme.icon.size.md} />}
|
||||||
>
|
>
|
||||||
<RecoilScope>
|
<RecoilScope SpecificContext={DropdownRecoilScopeContext}>
|
||||||
<PipelineAddButton />
|
<PipelineAddButton />
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|||||||
Reference in New Issue
Block a user