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