Feat/better hotkeys scope (#526)

* Working version

* fix

* Fixed console log

* Fix lint

* wip

* Fix

* Fix

* consolelog

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-07-08 03:53:05 +02:00
committed by GitHub
parent 611cda1f41
commit 66dcc9b2e1
77 changed files with 1240 additions and 454 deletions

View File

@ -1,12 +1,16 @@
import { ReactElement } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveState';
import { useEditableCell } from './hooks/useCloseEditableCell';
import { useCurrentCellEditMode } from './hooks/useCurrentCellEditMode';
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
import { useSetSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
import { isEditModeScopedState } from './states/isEditModeScopedState';
import { useSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
import { EditableCellEditMode } from './EditableCellEditMode';
import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode';
@ -27,6 +31,7 @@ type OwnProps = {
nonEditModeContent: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
editHotkeysScope?: HotkeysScopeStackItem;
};
export function EditableCell({
@ -34,39 +39,51 @@ export function EditableCell({
editModeVerticalPosition = 'over',
editModeContent,
nonEditModeContent,
editHotkeysScope,
}: OwnProps) {
const [isEditMode] = useRecoilScopedState(isEditModeScopedState);
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
const setSoftFocusOnCurrentCell = useSoftFocusOnCurrentCell();
const { closeEditableCell, openEditableCell } = useEditableCell();
const { openEditableCell } = useEditableCell();
const isSoftFocusActive = useRecoilValue(isSoftFocusActiveState);
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
// TODO: we might have silent problematic behavior because of the setTimeout in openEditableCell, investigate
// Maybe we could build a switchEditableCell to handle the case where we go from one cell to another.
// See https://github.com/twentyhq/twenty/issues/446
function handleOnClick() {
openEditableCell();
setSoftFocusOnCurrentCell();
}
if (isCurrentCellInEditMode) {
return;
}
function handleOnOutsideClick() {
closeEditableCell();
if (isSoftFocusActive) {
openEditableCell();
addToHotkeysScopeStack(
editHotkeysScope ?? {
scope: InternalHotkeysScope.CellEditMode,
},
);
}
setSoftFocusOnCurrentCell();
}
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
return (
<CellBaseContainer onClick={handleOnClick}>
{isEditMode ? (
{isCurrentCellInEditMode ? (
<EditableCellEditMode
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
onOutsideClick={handleOnOutsideClick}
>
{editModeContent}
</EditableCellEditMode>
) : hasSoftFocus ? (
<EditableCellSoftFocusMode>
<EditableCellSoftFocusMode editHotkeysScope={editHotkeysScope}>
{nonEditModeContent}
</EditableCellSoftFocusMode>
) : (

View File

@ -1,7 +1,8 @@
import { ReactElement, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import styled from '@emotion/styled';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
import { overlayBackground } from '@/ui/themes/effects';
@ -38,7 +39,6 @@ export function EditableCellEditMode({
editModeHorizontalAlign,
editModeVerticalPosition,
children,
onOutsideClick,
}: OwnProps) {
const wrapperRef = useRef(null);
@ -46,61 +46,45 @@ export function EditableCellEditMode({
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
useListenClickOutsideArrayOfRef([wrapperRef], () => {
onOutsideClick?.();
closeEditableCell();
});
useHotkeys(
useScopedHotkeys(
'enter',
() => {
closeEditableCell();
moveDown();
},
{
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
InternalHotkeysScope.CellEditMode,
[closeEditableCell],
);
useHotkeys(
useScopedHotkeys(
'esc',
() => {
closeEditableCell();
},
{
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
InternalHotkeysScope.CellEditMode,
[closeEditableCell],
);
useHotkeys(
useScopedHotkeys(
'tab',
() => {
closeEditableCell();
moveRight();
},
{
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
InternalHotkeysScope.CellEditMode,
[closeEditableCell, moveRight],
);
useHotkeys(
useScopedHotkeys(
'shift+tab',
() => {
closeEditableCell();
moveLeft();
},
{
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
InternalHotkeysScope.CellEditMode,
[closeEditableCell, moveRight],
);

View File

@ -1,8 +1,9 @@
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useRecoilState } from 'recoil';
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { isNonTextWritingKey } from '@/utils/hotkeys/isNonTextWritingKey';
import { useEditableCell } from './hooks/useCloseEditableCell';
@ -10,26 +11,26 @@ import { EditableCellDisplayMode } from './EditableCellDisplayMode';
export function EditableCellSoftFocusMode({
children,
}: React.PropsWithChildren<unknown>) {
editHotkeysScope,
}: React.PropsWithChildren<{ editHotkeysScope?: HotkeysScopeStackItem }>) {
const { closeEditableCell, openEditableCell } = useEditableCell();
const [captureHotkeyTypeInFocus] = useRecoilState(
captureHotkeyTypeInFocusState,
);
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
useHotkeys(
useScopedHotkeys(
'enter',
() => {
openEditableCell();
addToHotkeysScopeStack(
editHotkeysScope ?? {
scope: InternalHotkeysScope.CellEditMode,
},
);
},
{
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
InternalHotkeysScope.TableSoftFocus,
[closeEditableCell],
);
useHotkeys(
useScopedHotkeys(
'*',
(keyboardEvent) => {
const isWritingText =
@ -41,14 +42,16 @@ export function EditableCellSoftFocusMode({
return;
}
if (captureHotkeyTypeInFocus) {
return;
}
openEditableCell();
addToHotkeysScopeStack(
editHotkeysScope ?? {
scope: InternalHotkeysScope.CellEditMode,
},
);
},
InternalHotkeysScope.TableSoftFocus,
[openEditableCell, addToHotkeysScopeStack, editHotkeysScope],
{
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: false,
},
);

View File

@ -1,24 +1,24 @@
import { useRecoilCallback } from 'recoil';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { useRemoveHighestHotkeysScopeStackItem } from '@/hotkeys/hooks/useRemoveHighestHotkeysScopeStackItem';
import { useCloseCurrentCellInEditMode } from '@/ui/tables/hooks/useClearCellInEditMode';
import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveState';
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
import { isEditModeScopedState } from '../states/isEditModeScopedState';
import { useCurrentCellEditMode } from './useCurrentCellEditMode';
export function useEditableCell() {
const [, setIsEditMode] = useRecoilScopedState(isEditModeScopedState);
const { setCurrentCellInEditMode } = useCurrentCellEditMode();
const closeEditableCell = useRecoilCallback(
({ set }) =>
async () => {
setIsEditMode(false);
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
await new Promise((resolve) => setTimeout(resolve, 20));
const removeHighestHotkeysScopedStackItem =
useRemoveHighestHotkeysScopeStackItem();
set(isSomeInputInEditModeState, false);
},
[setIsEditMode],
);
function closeEditableCell() {
closeCurrentCellInEditMode();
removeHighestHotkeysScopedStackItem();
}
const openEditableCell = useRecoilCallback(
({ snapshot, set }) =>
@ -29,11 +29,12 @@ export function useEditableCell() {
if (!isSomeInputInEditMode) {
set(isSomeInputInEditModeState, true);
set(isSoftFocusActiveState, false);
setIsEditMode(true);
setCurrentCellInEditMode();
}
},
[setIsEditMode],
[setCurrentCellInEditMode],
);
return {

View File

@ -0,0 +1,23 @@
import { useCallback } from 'react';
import { useRecoilState } from 'recoil';
import { useMoveEditModeToCellPosition } from '@/ui/tables/hooks/useMoveEditModeToCellPosition';
import { isCellInEditModeFamilyState } from '@/ui/tables/states/isCellInEditModeFamilyState';
import { useCurrentCellPosition } from './useCurrentCellPosition';
export function useCurrentCellEditMode() {
const moveEditModeToCellPosition = useMoveEditModeToCellPosition();
const currentCellPosition = useCurrentCellPosition();
const [isCurrentCellInEditMode] = useRecoilState(
isCellInEditModeFamilyState(currentCellPosition),
);
const setCurrentCellInEditMode = useCallback(() => {
moveEditModeToCellPosition(currentCellPosition);
}, [currentCellPosition, moveEditModeToCellPosition]);
return { isCurrentCellInEditMode, setCurrentCellInEditMode };
}

View File

@ -0,0 +1,30 @@
import { useMemo } from 'react';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { CellContext } from '@/ui/tables/states/CellContext';
import { currentColumnNumberScopedState } from '@/ui/tables/states/currentColumnNumberScopedState';
import { currentRowNumberScopedState } from '@/ui/tables/states/currentRowNumberScopedState';
import { RowContext } from '@/ui/tables/states/RowContext';
import { CellPosition } from '@/ui/tables/types/CellPosition';
export function useCurrentCellPosition() {
const [currentRowNumber] = useRecoilScopedState(
currentRowNumberScopedState,
RowContext,
);
const [currentColumnNumber] = useRecoilScopedState(
currentColumnNumberScopedState,
CellContext,
);
const currentCellPosition: CellPosition = useMemo(
() => ({
column: currentColumnNumber,
row: currentRowNumber,
}),
[currentColumnNumber, currentRowNumber],
);
return currentCellPosition;
}

View File

@ -1,35 +1,14 @@
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { CellContext } from '@/ui/tables/states/CellContext';
import { currentColumnNumberScopedState } from '@/ui/tables/states/currentColumnNumberScopedState';
import { currentRowNumberScopedState } from '@/ui/tables/states/currentRowNumberScopedState';
import { isSoftFocusOnCellFamilyState } from '@/ui/tables/states/isSoftFocusOnCellFamilyState';
import { RowContext } from '@/ui/tables/states/RowContext';
import { TablePosition } from '@/ui/tables/types/TablePosition';
import { useCurrentCellPosition } from './useCurrentCellPosition';
export function useIsSoftFocusOnCurrentCell() {
const [currentRowNumber] = useRecoilScopedState(
currentRowNumberScopedState,
RowContext,
);
const [currentColumnNumber] = useRecoilScopedState(
currentColumnNumberScopedState,
CellContext,
);
const currentTablePosition: TablePosition = useMemo(
() => ({
column: currentColumnNumber,
row: currentRowNumber,
}),
[currentColumnNumber, currentRowNumber],
);
const currentCellPosition = useCurrentCellPosition();
const isSoftFocusOnCell = useRecoilValue(
isSoftFocusOnCellFamilyState(currentTablePosition),
isSoftFocusOnCellFamilyState(currentCellPosition),
);
return isSoftFocusOnCell;

View File

@ -1,14 +1,18 @@
import { useCallback, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { useSetSoftFocusPosition } from '@/ui/tables/hooks/useSetSoftFocusPosition';
import { CellContext } from '@/ui/tables/states/CellContext';
import { currentColumnNumberScopedState } from '@/ui/tables/states/currentColumnNumberScopedState';
import { currentRowNumberScopedState } from '@/ui/tables/states/currentRowNumberScopedState';
import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveState';
import { RowContext } from '@/ui/tables/states/RowContext';
import { TablePosition } from '@/ui/tables/types/TablePosition';
import { CellPosition } from '@/ui/tables/types/CellPosition';
export function useSetSoftFocusOnCurrentCell() {
export function useSoftFocusOnCurrentCell() {
const setSoftFocusPosition = useSetSoftFocusPosition();
const [currentRowNumber] = useRecoilScopedState(
currentRowNumberScopedState,
@ -20,7 +24,7 @@ export function useSetSoftFocusOnCurrentCell() {
CellContext,
);
const currentTablePosition: TablePosition = useMemo(
const currentTablePosition: CellPosition = useMemo(
() => ({
column: currentColumnNumber,
row: currentRowNumber,
@ -28,7 +32,18 @@ export function useSetSoftFocusOnCurrentCell() {
[currentColumnNumber, currentRowNumber],
);
const [, setIsSoftFocusActive] = useRecoilState(isSoftFocusActiveState);
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
return useCallback(() => {
setSoftFocusPosition(currentTablePosition);
}, [setSoftFocusPosition, currentTablePosition]);
setIsSoftFocusActive(true);
addToHotkeysScopeStack({ scope: InternalHotkeysScope.TableSoftFocus });
}, [
setSoftFocusPosition,
currentTablePosition,
setIsSoftFocusActive,
addToHotkeysScopeStack,
]);
}

View File

@ -1,6 +0,0 @@
import { atomFamily } from 'recoil';
export const isEditModeScopedState = atomFamily<boolean, string>({
key: 'isEditModeScopedState',
default: false,
});

View File

@ -1,10 +1,11 @@
import { ChangeEvent, ReactElement, useRef } from 'react';
import styled from '@emotion/styled';
import { ReactElement } from 'react';
import { textInputStyle } from '@/ui/themes/effects';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { EditableCell } from '../EditableCell';
import { EditableDoubleTextEditMode } from './EditableDoubleTextEditMode';
type OwnProps = {
firstValue: string;
secondValue: string;
@ -14,57 +15,25 @@ type OwnProps = {
onChange: (firstValue: string, secondValue: string) => void;
};
const StyledContainer = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
& > input:last-child {
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
padding-left: ${({ theme }) => theme.spacing(2)};
}
`;
const StyledEditInplaceInput = styled.input`
height: 18px;
margin: 0;
width: 45%;
${textInputStyle}
`;
export function EditableDoubleText({
firstValue,
secondValue,
firstValuePlaceholder,
secondValuePlaceholder,
nonEditModeContent,
onChange,
nonEditModeContent,
}: OwnProps) {
const firstValueInputRef = useRef<HTMLInputElement>(null);
return (
<EditableCell
editHotkeysScope={{ scope: InternalHotkeysScope.CellDoubleTextInput }}
editModeContent={
<StyledContainer>
<StyledEditInplaceInput
autoFocus
placeholder={firstValuePlaceholder}
ref={firstValueInputRef}
value={firstValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value, secondValue);
}}
/>
<StyledEditInplaceInput
placeholder={secondValuePlaceholder}
ref={firstValueInputRef}
value={secondValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(firstValue, event.target.value);
}}
/>
</StyledContainer>
<EditableDoubleTextEditMode
firstValue={firstValue}
secondValue={secondValue}
firstValuePlaceholder={firstValuePlaceholder}
secondValuePlaceholder={secondValuePlaceholder}
onChange={onChange}
/>
}
nonEditModeContent={nonEditModeContent}
></EditableCell>

View File

@ -0,0 +1,129 @@
import { ChangeEvent, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
import { textInputStyle } from '@/ui/themes/effects';
import { useEditableCell } from '../hooks/useCloseEditableCell';
type OwnProps = {
firstValue: string;
secondValue: string;
firstValuePlaceholder: string;
secondValuePlaceholder: string;
onChange: (firstValue: string, secondValue: string) => void;
};
const StyledContainer = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
& > input:last-child {
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
padding-left: ${({ theme }) => theme.spacing(2)};
}
`;
const StyledEditInplaceInput = styled.input`
height: 18px;
margin: 0;
width: 45%;
${textInputStyle}
`;
export function EditableDoubleTextEditMode({
firstValue,
secondValue,
firstValuePlaceholder,
secondValuePlaceholder,
onChange,
}: OwnProps) {
const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left');
const firstValueInputRef = useRef<HTMLInputElement>(null);
const secondValueInputRef = useRef<HTMLInputElement>(null);
const { closeEditableCell } = useEditableCell();
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
function closeCell() {
setFocusPosition('left');
closeEditableCell();
}
useScopedHotkeys(
Key.Enter,
() => {
closeCell();
moveDown();
},
InternalHotkeysScope.CellDoubleTextInput,
[closeCell],
);
useScopedHotkeys(
Key.Escape,
() => {
closeCell();
},
InternalHotkeysScope.CellDoubleTextInput,
[closeCell],
);
useScopedHotkeys(
'tab',
async (keyboardEvent, hotkeyEvent) => {
if (focusPosition === 'left') {
setFocusPosition('right');
secondValueInputRef.current?.focus();
} else {
closeCell();
moveRight();
}
},
InternalHotkeysScope.CellDoubleTextInput,
[closeCell, moveRight, focusPosition],
);
useScopedHotkeys(
'shift+tab',
() => {
if (focusPosition === 'right') {
setFocusPosition('left');
firstValueInputRef.current?.focus();
} else {
closeCell();
moveLeft();
}
},
InternalHotkeysScope.CellDoubleTextInput,
[closeCell, moveRight, focusPosition],
);
return (
<StyledContainer>
<StyledEditInplaceInput
autoFocus
placeholder={firstValuePlaceholder}
ref={firstValueInputRef}
value={firstValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value, secondValue);
}}
/>
<StyledEditInplaceInput
placeholder={secondValuePlaceholder}
ref={secondValueInputRef}
value={secondValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(firstValue, event.target.value);
}}
/>
</StyledContainer>
);
}

View File

@ -13,6 +13,8 @@ import {
SortType,
} from '@/filters-and-sorts/interfaces/sorts/interface';
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
import { useLeaveTableFocus } from '@/ui/tables/hooks/useLeaveTableFocus';
import { RowContext } from '@/ui/tables/states/RowContext';
import { currentRowSelectionState } from '../../tables/states/rowSelectionState';
@ -103,6 +105,8 @@ export function EntityTable<TData extends { id: string }, SortField>({
availableSorts,
onSortsUpdate,
}: OwnProps<TData, SortField>) {
const tableBodyRef = React.useRef<HTMLDivElement>(null);
const [currentRowSelection, setCurrentRowSelection] = useRecoilState(
currentRowSelectionState,
);
@ -119,6 +123,12 @@ export function EntityTable<TData extends { id: string }, SortField>({
getRowId: (row) => row.id,
});
const leaveTableFocus = useLeaveTableFocus();
useListenClickOutsideArrayOfRef([tableBodyRef], () => {
leaveTableFocus();
});
return (
<StyledTableWithHeader>
<TableHeader
@ -127,7 +137,7 @@ export function EntityTable<TData extends { id: string }, SortField>({
availableSorts={availableSorts}
onSortsUpdate={onSortsUpdate}
/>
<StyledTableScrollableContainer>
<StyledTableScrollableContainer ref={tableBodyRef}>
<StyledTable>
<thead>
{table.getHeaderGroups().map((headerGroup) => (

View File

@ -1,8 +1,9 @@
import { ReactNode, useRef } from 'react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { IconChevronDown } from '@/ui/icons/index';
import { overlayBackground, textInputStyle } from '@/ui/themes/effects';
@ -13,7 +14,7 @@ type OwnProps = {
isActive: boolean;
children?: ReactNode;
isUnfolded?: boolean;
setIsUnfolded?: React.Dispatch<React.SetStateAction<boolean>>;
onIsUnfoldedChange?: (newIsUnfolded: boolean) => void;
resetState?: () => void;
};
@ -158,22 +159,23 @@ function DropdownButton({
isActive,
children,
isUnfolded = false,
setIsUnfolded,
resetState,
onIsUnfoldedChange,
}: OwnProps) {
const [, setCaptureHotkeyTypeInFocus] = useRecoilState(
captureHotkeyTypeInFocusState,
useScopedHotkeys(
[Key.Enter, Key.Escape],
() => {
onIsUnfoldedChange?.(false);
},
InternalHotkeysScope.TableHeaderDropdownButton,
[onIsUnfoldedChange],
);
const onButtonClick = () => {
setIsUnfolded && setIsUnfolded(!isUnfolded);
setCaptureHotkeyTypeInFocus((isPreviousUnfolded) => !isPreviousUnfolded);
onIsUnfoldedChange?.(!isUnfolded);
};
const onOutsideClick = () => {
setCaptureHotkeyTypeInFocus(false);
setIsUnfolded && setIsUnfolded(false);
resetState && resetState();
onIsUnfoldedChange?.(false);
};
const dropdownRef = useRef(null);

View File

@ -5,6 +5,8 @@ import { filterDropdownSearchInputScopedState } from '@/filters-and-sorts/states
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/filters-and-sorts/states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '@/filters-and-sorts/states/selectedOperandInDropdownScopedState';
import { tableFilterDefinitionUsedInDropdownScopedState } from '@/filters-and-sorts/states/tableFilterDefinitionUsedInDropdownScopedState';
import { useHotkeysScopeOnBooleanState } from '@/hotkeys/hooks/useHotkeysScopeOnBooleanState';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { TableContext } from '@/ui/tables/states/TableContext';
@ -21,6 +23,11 @@ import { FilterDropdownTextSearchInput } from './FilterDropdownTextSearchInput';
export function FilterDropdownButton() {
const [isUnfolded, setIsUnfolded] = useState(false);
useHotkeysScopeOnBooleanState(
{ scope: InternalHotkeysScope.TableHeaderDropdownButton },
isUnfolded,
);
const [
isFilterDropdownOperandSelectUnfolded,
setIsFilterDropdownOperandSelectUnfolded,
@ -64,13 +71,21 @@ export function FilterDropdownButton() {
const isFilterSelected = (activeTableFilters?.length ?? 0) > 0;
function handleIsUnfoldedChange(newIsUnfolded: boolean) {
if (newIsUnfolded) {
setIsUnfolded(true);
} else {
setIsUnfolded(false);
resetState();
}
}
return (
<DropdownButton
label="Filter"
isActive={isFilterSelected}
isUnfolded={isUnfolded}
setIsUnfolded={setIsUnfolded}
resetState={resetState}
onIsUnfoldedChange={handleIsUnfoldedChange}
>
{!tableFilterDefinitionUsedInDropdown ? (
<FilterDropdownFilterSelect />

View File

@ -1,11 +1,9 @@
import { useCallback, useState } from 'react';
import { useRecoilState } from 'recoil';
import {
SelectedSortType,
SortType,
} from '@/filters-and-sorts/interfaces/sorts/interface';
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
import DropdownButton from './DropdownButton';
@ -23,9 +21,6 @@ export function SortDropdownButton<SortField>({
onSortSelect,
}: OwnProps<SortField>) {
const [isUnfolded, setIsUnfolded] = useState(false);
const [, setCaptureHotkeyTypeInFocus] = useRecoilState(
captureHotkeyTypeInFocusState,
);
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
@ -41,17 +36,24 @@ export function SortDropdownButton<SortField>({
const resetState = useCallback(() => {
setIsOptionUnfolded(false);
setCaptureHotkeyTypeInFocus(false);
setSelectedSortDirection('asc');
}, [setCaptureHotkeyTypeInFocus]);
}, []);
function handleIsUnfoldedChange(newIsUnfolded: boolean) {
if (newIsUnfolded) {
setIsUnfolded(true);
} else {
setIsUnfolded(false);
resetState();
}
}
return (
<DropdownButton
label="Sort"
isActive={isSortSelected}
isUnfolded={isUnfolded}
setIsUnfolded={setIsUnfolded}
resetState={resetState}
onIsUnfoldedChange={handleIsUnfoldedChange}
>
{isOptionUnfolded
? options.map((option, index) => (
@ -59,7 +61,6 @@ export function SortDropdownButton<SortField>({
key={index}
onClick={() => {
setSelectedSortDirection(option);
setCaptureHotkeyTypeInFocus(false);
setIsOptionUnfolded(false);
}}
>
@ -80,7 +81,6 @@ export function SortDropdownButton<SortField>({
key={index + 1}
onClick={() => {
setIsUnfolded(false);
setCaptureHotkeyTypeInFocus(false);
onSortItemSelect(sort);
}}
>