Optimize table loading (#866)

* wip

* wip

* Ok

* Deleted unused code

* Fixed lint

* Minor fixes

* Minor fixes

* Minor Fixes

* Minor merge fixes

* Ok

* Fix storybook tests

* Removed console.log

* Fix login

* asd

* Fixed storybook

* Added await

* Fixed await

* Added sleep for failing test

* Fix sleep

* Fix test

* Fix tests

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-07-25 20:00:15 +02:00
committed by GitHub
parent c2d6abde65
commit a2ccb643ff
85 changed files with 846 additions and 904 deletions

View File

@ -1,7 +1,7 @@
import { ChangeEvent, useMemo, useState } from 'react';
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
import { debounce } from '~/utils/debounce';
import { BoardCardEditableField } from './BoardCardEditableField';
@ -29,7 +29,7 @@ export function BoardCardEditableFieldText({
<BoardCardEditableField
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<InplaceInputTextEditMode
<StyledInput
placeholder={placeholder || ''}
autoFocus
value={internalValue}

View File

@ -0,0 +1,42 @@
import { Profiler } from 'react';
import { Interaction } from 'scheduler/tracing';
type OwnProps = {
id: string;
children: React.ReactNode;
};
export function TimingProfiler({ id, children }: OwnProps) {
function handleRender(
id: string,
phase: 'mount' | 'update',
actualDuration: number,
baseDuration: number,
startTime: number,
commitTime: number,
interactions: Set<Interaction>,
) {
console.debug(
'TimingProfiler',
JSON.stringify(
{
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions,
},
null,
2,
),
);
}
return (
<Profiler id={id} onRender={handleRender}>
{children}
</Profiler>
);
}

View File

@ -1,5 +1,4 @@
import { Context, useCallback, useState } from 'react';
import { Key } from 'ts-key-enum';
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
import { filterDefinitionUsedInDropdownScopedState } from '@/ui/filter-n-sort/states/filterDefinitionUsedInDropdownScopedState';
@ -7,10 +6,8 @@ import { filterDropdownSearchInputScopedState } from '@/ui/filter-n-sort/states/
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/ui/filter-n-sort/states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '@/ui/filter-n-sort/states/selectedOperandInDropdownScopedState';
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
@ -83,15 +80,6 @@ export function FilterDropdownButton({
}
}
useScopedHotkeys(
[Key.Escape],
() => {
handleIsUnfoldedChange(false);
},
RelationPickerHotkeyScope.RelationPicker,
[handleIsUnfoldedChange],
);
return (
<DropdownButton
label="Filter"

View File

@ -36,8 +36,14 @@ export function FilterDropdownEntitySearchSelect({
const filterCurrentlyEdited = useFilterCurrentlyEdited(context);
function handleUserSelected(selectedEntity: EntityForSelect) {
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) {
function handleUserSelected(
selectedEntity: EntityForSelect | null | undefined,
) {
if (
!filterDefinitionUsedInDropdown ||
!selectedOperandInDropdown ||
!selectedEntity
) {
return;
}

View File

@ -0,0 +1,12 @@
import { useLocation } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { currentPageLocationState } from '../states/currentPageLocationState';
export function useIsPageLoading() {
const currentLocation = useLocation().pathname;
const currentPageLocation = useRecoilValue(currentPageLocationState);
return currentLocation !== currentPageLocation;
}

View File

@ -1,30 +0,0 @@
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { currentHotkeyScopeState } from '@/ui/hotkey/states/internal/currentHotkeyScopeState';
import { AppHotkeyScope } from '../../types/AppHotkeyScope';
import { useHotkeyScopes } from './useHotkeyScopes';
export function useHotkeyScopeAutoSync() {
const { setHotkeyScopes } = useHotkeyScopes();
const currentHotkeyScope = useRecoilValue(currentHotkeyScopeState);
useEffect(() => {
const scopesToSet: string[] = [];
if (currentHotkeyScope.customScopes?.commandMenu) {
scopesToSet.push(AppHotkeyScope.CommandMenu);
}
if (currentHotkeyScope?.customScopes?.goto) {
scopesToSet.push(AppHotkeyScope.Goto);
}
scopesToSet.push(currentHotkeyScope.scope);
setHotkeyScopes(scopesToSet);
}, [setHotkeyScopes, currentHotkeyScope]);
}

View File

@ -0,0 +1,40 @@
import { Hotkey } from 'react-hotkeys-hook/dist/types';
import { useRecoilCallback } from 'recoil';
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
export function useScopedHotkeyCallback() {
return useRecoilCallback(
({ snapshot }) =>
({
callback,
hotkeysEvent,
keyboardEvent,
scope,
preventDefault = true,
}: {
keyboardEvent: KeyboardEvent;
hotkeysEvent: Hotkey;
callback: (keyboardEvent: KeyboardEvent, hotkeysEvent: Hotkey) => void;
scope: string;
preventDefault?: boolean;
}) => {
const currentHotkeyScopes = snapshot
.getLoadable(internalHotkeysEnabledScopesState)
.valueOrThrow();
if (!currentHotkeyScopes.includes(scope)) {
return;
}
if (preventDefault) {
keyboardEvent.stopPropagation();
keyboardEvent.preventDefault();
keyboardEvent.stopImmediatePropagation();
}
return callback(keyboardEvent, hotkeysEvent);
},
[],
);
}

View File

@ -1,6 +1,5 @@
import { useHotkeys } from 'react-hotkeys-hook';
import {
Hotkey,
HotkeyCallback,
Keys,
Options,
@ -10,6 +9,8 @@ import { useRecoilState } from 'recoil';
import { pendingHotkeyState } from '../states/internal/pendingHotkeysState';
import { useScopedHotkeyCallback } from './useScopedHotkeyCallback';
export function useScopedHotkeys(
keys: Keys,
callback: HotkeyCallback,
@ -23,21 +24,29 @@ export function useScopedHotkeys(
) {
const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState);
function callbackIfDirectKey(
keyboardEvent: KeyboardEvent,
hotkeysEvent: Hotkey,
) {
if (!pendingHotkey) {
callback(keyboardEvent, hotkeysEvent);
return;
}
setPendingHotkey(null);
}
const callScopedHotkeyCallback = useScopedHotkeyCallback();
return useHotkeys(
keys,
callbackIfDirectKey,
{ ...options, scopes: [scope] },
(keyboardEvent, hotkeysEvent) => {
callScopedHotkeyCallback({
keyboardEvent,
hotkeysEvent,
callback: () => {
if (!pendingHotkey) {
callback(keyboardEvent, hotkeysEvent);
return;
}
setPendingHotkey(null);
},
scope,
preventDefault: !!options.preventDefault,
});
},
{
enableOnContentEditable: options.enableOnContentEditable,
enableOnFormTags: options.enableOnFormTags,
},
dependencies,
);
}

View File

@ -4,10 +4,12 @@ import { useRecoilState } from 'recoil';
import { pendingHotkeyState } from '../states/internal/pendingHotkeysState';
import { useScopedHotkeyCallback } from './useScopedHotkeyCallback';
export function useSequenceHotkeys(
firstKey: Keys,
secondKey: Keys,
callback: () => void,
sequenceCallback: () => void,
scope: string,
options: Options = {
enableOnContentEditable: true,
@ -18,25 +20,57 @@ export function useSequenceHotkeys(
) {
const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState);
const callScopedHotkeyCallback = useScopedHotkeyCallback();
useHotkeys(
firstKey,
() => {
setPendingHotkey(firstKey);
(keyboardEvent, hotkeysEvent) => {
callScopedHotkeyCallback({
keyboardEvent,
hotkeysEvent,
callback: () => {
setPendingHotkey(firstKey);
},
scope,
preventDefault: !!options.preventDefault,
});
},
{ ...options, scopes: [scope] },
[setPendingHotkey],
{
enableOnContentEditable: options.enableOnContentEditable,
enableOnFormTags: options.enableOnFormTags,
},
[setPendingHotkey, scope],
);
useHotkeys(
secondKey,
() => {
if (pendingHotkey !== firstKey) {
return;
}
setPendingHotkey(null);
callback();
(keyboardEvent, hotkeysEvent) => {
callScopedHotkeyCallback({
keyboardEvent,
hotkeysEvent,
callback: () => {
if (pendingHotkey !== firstKey) {
return;
}
setPendingHotkey(null);
if (!!options.preventDefault) {
keyboardEvent.stopImmediatePropagation();
keyboardEvent.stopPropagation();
keyboardEvent.preventDefault();
}
sequenceCallback();
},
scope,
preventDefault: false,
});
},
{ ...options, scopes: [scope] },
[pendingHotkey, setPendingHotkey, ...deps],
{
enableOnContentEditable: options.enableOnContentEditable,
enableOnFormTags: options.enableOnFormTags,
},
[pendingHotkey, setPendingHotkey, scope, ...deps],
);
}

View File

@ -4,7 +4,10 @@ import { isDefined } from '~/utils/isDefined';
import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants';
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
import { AppHotkeyScope } from '../types/AppHotkeyScope';
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
import { HotkeyScope } from '../types/HotkeyScope';
function isCustomScopesEqual(
customScopesA: CustomHotkeyScopes | undefined,
@ -46,13 +49,27 @@ export function useSetHotkeyScope() {
}
}
set(currentHotkeyScopeState, {
const newHotkeyScope: HotkeyScope = {
scope: hotkeyScopeToSet,
customScopes: {
commandMenu: customScopes?.commandMenu ?? true,
goto: customScopes?.goto ?? false,
},
});
};
const scopesToSet: string[] = [];
if (newHotkeyScope.customScopes?.commandMenu) {
scopesToSet.push(AppHotkeyScope.CommandMenu);
}
if (newHotkeyScope?.customScopes?.goto) {
scopesToSet.push(AppHotkeyScope.Goto);
}
scopesToSet.push(newHotkeyScope.scope);
set(internalHotkeysEnabledScopesState, scopesToSet);
},
[],
);

View File

@ -1,7 +1,7 @@
import { ChangeEvent } from 'react';
import styled from '@emotion/styled';
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
type OwnProps = {
firstValue: string;
@ -31,7 +31,7 @@ export function InplaceInputDoubleText({
}: OwnProps) {
return (
<StyledContainer>
<InplaceInputTextEditMode
<StyledInput
autoFocus
placeholder={firstValuePlaceholder}
value={firstValue}
@ -39,7 +39,7 @@ export function InplaceInputDoubleText({
onChange(event.target.value, secondValue);
}}
/>
<InplaceInputTextEditMode
<StyledInput
placeholder={secondValuePlaceholder}
value={secondValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {

View File

@ -1,9 +1,58 @@
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { textInputStyle } from '@/ui/themes/effects';
export const InplaceInputTextEditMode = styled.input`
import { useRegisterCloseCellHandlers } from '../../table/editable-cell/hooks/useRegisterCloseCellHandlers';
export const StyledInput = styled.input`
margin: 0;
width: 100%;
${textInputStyle}
`;
type OwnProps = {
placeholder?: string;
autoFocus?: boolean;
value: string;
onSubmit: (newText: string) => void;
};
export function InplaceInputTextEditMode({
placeholder,
autoFocus,
value,
onSubmit,
}: OwnProps) {
const [internalText, setInternalText] = useState(value);
const wrapperRef = useRef(null);
function handleSubmit() {
onSubmit(internalText);
}
function handleCancel() {
setInternalText(value);
}
function handleChange(event: ChangeEvent<HTMLInputElement>) {
setInternalText(event.target.value);
}
useEffect(() => {
setInternalText(value);
}, [value]);
useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel);
return (
<StyledInput
ref={wrapperRef}
placeholder={placeholder}
onChange={handleChange}
autoFocus={autoFocus}
value={internalText}
/>
);
}

View File

@ -11,7 +11,6 @@ import { MOBILE_VIEWPORT } from '@/ui/themes/themes';
import { AppNavbar } from '~/AppNavbar';
import { CompaniesMockMode } from '~/pages/companies/CompaniesMockMode';
import { useAutoNavigateOnboarding } from '../hooks/useAutoNavigateOnboarding';
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
const StyledLayout = styled.div`
@ -39,12 +38,10 @@ const MainContainer = styled.div`
`;
type OwnProps = {
children: JSX.Element;
children: React.ReactNode;
};
export function DefaultLayout({ children }: OwnProps) {
useAutoNavigateOnboarding();
const onboardingStatus = useOnboardingStatus();
return (

View File

@ -1,52 +0,0 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useIsMatchingLocation } from '../../../../hooks/useIsMatchingLocation';
import { useOnboardingStatus } from '../../../auth/hooks/useOnboardingStatus';
import { OnboardingStatus } from '../../../auth/utils/getOnboardingStatus';
import { AppPath } from '../../../types/AppPath';
export function useAutoNavigateOnboarding() {
const navigate = useNavigate();
const isMatchingLocation = useIsMatchingLocation();
const onboardingStatus = useOnboardingStatus();
useEffect(() => {
const isMachinOngoingUserCreationRoute =
isMatchingLocation(AppPath.SignUp) ||
isMatchingLocation(AppPath.SignIn) ||
isMatchingLocation(AppPath.Invite) ||
isMatchingLocation(AppPath.Verify);
const isMatchingOnboardingRoute =
isMatchingLocation(AppPath.SignUp) ||
isMatchingLocation(AppPath.SignIn) ||
isMatchingLocation(AppPath.Invite) ||
isMatchingLocation(AppPath.Verify) ||
isMatchingLocation(AppPath.CreateWorkspace) ||
isMatchingLocation(AppPath.CreateProfile);
if (
onboardingStatus === OnboardingStatus.OngoingUserCreation &&
!isMachinOngoingUserCreationRoute
) {
navigate(AppPath.SignIn);
} else if (
onboardingStatus === OnboardingStatus.OngoingWorkspaceCreation &&
!isMatchingLocation(AppPath.CreateWorkspace)
) {
navigate(AppPath.CreateWorkspace);
} else if (
onboardingStatus === OnboardingStatus.OngoingProfileCreation &&
!isMatchingLocation(AppPath.CreateProfile)
) {
navigate(AppPath.CreateProfile);
} else if (
onboardingStatus === OnboardingStatus.Completed &&
isMatchingOnboardingRoute
) {
navigate('/');
}
}, [onboardingStatus, navigate, isMatchingLocation]);
}

View File

@ -35,7 +35,7 @@ export function SingleEntitySelect<
onCancel?: () => void;
onCreate?: () => void;
entities: EntitiesForSingleEntitySelect<CustomEntityForSelect>;
onEntitySelected: (entity: CustomEntityForSelect) => void;
onEntitySelected: (entity: CustomEntityForSelect | null | undefined) => void;
disableBackgroundBlur?: boolean;
}) {
const containerRef = useRef<HTMLDivElement>(null);
@ -48,7 +48,11 @@ export function SingleEntitySelect<
useListenClickOutside({
refs: [containerRef],
callback: () => {
callback: (event) => {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
onCancel?.();
},
});

View File

@ -32,7 +32,7 @@ export function SingleEntitySelectBase<
onCancel,
}: {
entities: EntitiesForSingleEntitySelect<CustomEntityForSelect>;
onEntitySelected: (entity: CustomEntityForSelect) => void;
onEntitySelected: (entity: CustomEntityForSelect | null | undefined) => void;
onCancel?: () => void;
}) {
const containerRef = useRef<HTMLDivElement>(null);

View File

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

View File

@ -5,7 +5,9 @@ import { TableColumn } from '@/people/table/components/peopleColumns';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
import { useIsPageLoading } from '../../hooks/useIsPageLoading';
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
import { TableHeader } from '../table-header/components/TableHeader';
import { EntityTableBody } from './EntityTableBody';
@ -88,6 +90,8 @@ export function EntityTable<SortField>({
}: OwnProps<SortField>) {
const tableBodyRef = React.useRef<HTMLDivElement>(null);
useMapKeyboardToSoftFocus();
const leaveTableFocus = useLeaveTableFocus();
useListenClickOutside({
@ -97,6 +101,12 @@ export function EntityTable<SortField>({
},
});
const isPageLoading = useIsPageLoading();
if (isPageLoading) {
return null;
}
return (
<StyledTableWithHeader>
<TableHeader

View File

@ -2,10 +2,10 @@ import { useRecoilValue } from 'recoil';
import { TableColumn } from '@/people/table/components/peopleColumns';
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState';
import { RowContext } from '../states/RowContext';
import { RowIdContext } from '../states/RowIdContext';
import { RowIndexContext } from '../states/RowIndexContext';
import { tableRowIdsState } from '../states/tableRowIdsState';
import { EntityTableRow } from './EntityTableRow';
@ -19,15 +19,19 @@ export function EntityTableBody({ columns }: { columns: Array<TableColumn> }) {
isFetchingEntityTableDataState,
);
if (isFetchingEntityTableData || isNavbarSwitchingSize) {
return null;
}
return (
<tbody>
{!isFetchingEntityTableData && !isNavbarSwitchingSize
? rowIds.map((rowId, index) => (
<RecoilScope SpecificContext={RowContext} key={rowId}>
<EntityTableRow columns={columns} rowId={rowId} index={index} />
</RecoilScope>
))
: null}
{rowIds.map((rowId, index) => (
<RowIdContext.Provider value={rowId} key={rowId}>
<RowIndexContext.Provider value={index}>
<EntityTableRow columns={columns} rowId={rowId} />
</RowIndexContext.Provider>
</RowIdContext.Provider>
))}
</tbody>
);
}

View File

@ -1,33 +1,19 @@
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { RecoilScope } from '../../recoil-scope/components/RecoilScope';
import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
import { CellContext } from '../states/CellContext';
import { ColumnIndexContext } from '../states/ColumnIndexContext';
import { contextMenuPositionState } from '../states/contextMenuPositionState';
import { currentColumnNumberScopedState } from '../states/currentColumnNumberScopedState';
export function EntityTableCell({
rowId,
cellIndex,
children,
size,
}: {
size: number;
rowId: string;
cellIndex: number;
children: React.ReactNode;
}) {
const [, setCurrentColumnNumber] = useRecoilScopedState(
currentColumnNumberScopedState,
CellContext,
);
useEffect(() => {
setCurrentColumnNumber(cellIndex);
}, [cellIndex, setCurrentColumnNumber]);
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
const { setCurrentRowSelected } = useCurrentRowSelected();
@ -44,15 +30,19 @@ export function EntityTableCell({
}
return (
<td
onContextMenu={(event) => handleContextMenu(event)}
style={{
width: size,
minWidth: size,
maxWidth: size,
}}
>
{children}
</td>
<RecoilScope>
<ColumnIndexContext.Provider value={cellIndex}>
<td
onContextMenu={(event) => handleContextMenu(event)}
style={{
width: size,
minWidth: size,
maxWidth: size,
}}
>
{children}
</td>
</ColumnIndexContext.Provider>
</RecoilScope>
);
}

View File

@ -1,16 +1,6 @@
import { useEffect } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { TableColumn } from '@/people/table/components/peopleColumns';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { CellContext } from '../states/CellContext';
import { currentRowEntityIdScopedState } from '../states/currentRowEntityIdScopedState';
import { currentRowNumberScopedState } from '../states/currentRowNumberScopedState';
import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState';
import { RowContext } from '../states/RowContext';
import { CheckboxCell } from './CheckboxCell';
import { EntityTableCell } from './EntityTableCell';
@ -23,56 +13,24 @@ const StyledRow = styled.tr<{ selected: boolean }>`
export function EntityTableRow({
columns,
rowId,
index,
}: {
columns: TableColumn[];
rowId: string;
index: number;
}) {
const [currentRowEntityId, setCurrentRowEntityId] = useRecoilScopedState(
currentRowEntityIdScopedState,
RowContext,
);
const isCurrentRowSelected = useRecoilValue(isRowSelectedFamilyState(rowId));
const [, setCurrentRowNumber] = useRecoilScopedState(
currentRowNumberScopedState,
RowContext,
);
useEffect(() => {
if (currentRowEntityId !== rowId) {
setCurrentRowEntityId(rowId);
}
}, [rowId, setCurrentRowEntityId, currentRowEntityId]);
useEffect(() => {
setCurrentRowNumber(index);
}, [index, setCurrentRowNumber]);
return (
<StyledRow
key={rowId}
data-testid={`row-id-${rowId}`}
selected={isCurrentRowSelected}
>
<StyledRow data-testid={`row-id-${rowId}`} selected={false}>
<td>
<CheckboxCell />
</td>
{columns.map((column, columnIndex) => {
return (
<RecoilScope SpecificContext={CellContext} key={column.id.toString()}>
<RecoilScope>
<EntityTableCell
rowId={rowId}
size={column.size}
cellIndex={columnIndex}
>
{column.cellComponent}
</EntityTableCell>
</RecoilScope>
</RecoilScope>
<EntityTableCell
key={column.id}
size={column.size}
cellIndex={columnIndex}
>
{column.cellComponent}
</EntityTableCell>
);
})}
<td></td>

View File

@ -1,25 +0,0 @@
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useInitializeEntityTable } from '../hooks/useInitializeEntityTable';
import { useInitializeEntityTableFilters } from '../hooks/useInitializeEntityTableFilters';
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
export function HooksEntityTable({
numberOfColumns,
availableFilters,
}: {
numberOfColumns: number;
availableFilters: FilterDefinition[];
}) {
useMapKeyboardToSoftFocus();
useInitializeEntityTable({
numberOfColumns,
});
useInitializeEntityTableFilters({
availableFilters,
});
return <></>;
}

View File

@ -3,9 +3,10 @@ import styled from '@emotion/styled';
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode';
import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell';
import { useRegisterEditableCell } from '../hooks/useRegisterEditableCell';
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
import { EditableCellEditMode } from './EditableCellEditMode';
@ -34,6 +35,10 @@ type OwnProps = {
onCancel?: () => void;
};
const DEFAULT_CELL_SCOPE: HotkeyScope = {
scope: TableHotkeyScope.CellEditMode,
};
export function EditableCell({
editModeHorizontalAlign = 'left',
editModeVerticalPosition = 'over',
@ -42,35 +47,35 @@ export function EditableCell({
editHotkeyScope,
transparent = false,
maxContentWidth,
onSubmit,
onCancel,
}: OwnProps) {
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
useRegisterEditableCell(editHotkeyScope);
return (
<CellBaseContainer>
{isCurrentCellInEditMode ? (
<EditableCellEditMode
maxContentWidth={maxContentWidth}
transparent={transparent}
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
onSubmit={onSubmit}
onCancel={onCancel}
>
{editModeContent}
</EditableCellEditMode>
) : hasSoftFocus ? (
<EditableCellSoftFocusMode>
{nonEditModeContent}
</EditableCellSoftFocusMode>
) : (
<EditableCellDisplayMode>{nonEditModeContent}</EditableCellDisplayMode>
)}
</CellBaseContainer>
<CellHotkeyScopeContext.Provider
value={editHotkeyScope ?? DEFAULT_CELL_SCOPE}
>
<CellBaseContainer>
{isCurrentCellInEditMode ? (
<EditableCellEditMode
maxContentWidth={maxContentWidth}
transparent={transparent}
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
>
{editModeContent}
</EditableCellEditMode>
) : hasSoftFocus ? (
<EditableCellSoftFocusMode>
{nonEditModeContent}
</EditableCellSoftFocusMode>
) : (
<EditableCellDisplayMode>
{nonEditModeContent}
</EditableCellDisplayMode>
)}
</CellBaseContainer>
</CellHotkeyScopeContext.Provider>
);
}

View File

@ -1,10 +1,8 @@
import { ReactElement, useRef } from 'react';
import { ReactElement } from 'react';
import styled from '@emotion/styled';
import { overlayBackground } from '@/ui/themes/effects';
import { useRegisterCloseCellHandlers } from '../hooks/useRegisterCloseCellHandlers';
export const EditableCellEditModeContainer = styled.div<OwnProps>`
align-items: center;
border: ${({ transparent, theme }) =>
@ -36,30 +34,21 @@ type OwnProps = {
maxContentWidth?: number;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
onOutsideClick?: () => void;
onCancel?: () => void;
onSubmit?: () => void;
initialValue?: string;
};
export function EditableCellEditMode({
editModeHorizontalAlign,
editModeVerticalPosition,
children,
onCancel,
onSubmit,
transparent = false,
maxContentWidth,
}: OwnProps) {
const wrapperRef = useRef(null);
useRegisterCloseCellHandlers(wrapperRef, onSubmit, onCancel);
return (
<EditableCellEditModeContainer
maxContentWidth={maxContentWidth}
transparent={transparent}
data-testid="editable-cell-edit-mode-container"
ref={wrapperRef}
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
>

View File

@ -4,6 +4,7 @@ import { userEvent, within } from '@storybook/testing-library';
import { CellPositionDecorator } from '~/testing/decorators/CellPositionDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { sleep } from '~/testing/sleep';
import { EditableCellText } from '../../types/EditableCellText';
@ -28,13 +29,12 @@ export const SoftFocusMode: Story = {
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
await step('Click once', () =>
userEvent.click(canvas.getByText('Content')),
);
const content = await canvas.findByText('Content');
await step('Escape', () => {
userEvent.keyboard('{esc}');
});
await userEvent.click(content);
await userEvent.keyboard('{esc}');
await sleep(10);
await step('Has soft focus mode', () => {
expect(canvas.getByTestId('editable-cell-soft-focus-mode')).toBeDefined();
@ -47,7 +47,7 @@ export const EditMode: Story = {
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
const click = async () => userEvent.click(canvas.getByText('Content'));
const click = () => userEvent.click(canvas.getByText('Content'));
await step('Click once', click);

View File

@ -1,23 +1,12 @@
import { useMemo } from 'react';
import { useContext, useMemo } from 'react';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { CellContext } from '../../states/CellContext';
import { currentColumnNumberScopedState } from '../../states/currentColumnNumberScopedState';
import { currentRowNumberScopedState } from '../../states/currentRowNumberScopedState';
import { RowContext } from '../../states/RowContext';
import { ColumnIndexContext } from '../../states/ColumnIndexContext';
import { RowIndexContext } from '../../states/RowIndexContext';
import { CellPosition } from '../../types/CellPosition';
export function useCurrentCellPosition() {
const [currentRowNumber] = useRecoilScopedState(
currentRowNumberScopedState,
RowContext,
);
const [currentColumnNumber] = useRecoilScopedState(
currentColumnNumberScopedState,
CellContext,
);
const currentRowNumber = useContext(RowIndexContext);
const currentColumnNumber = useContext(ColumnIndexContext);
const currentCellPosition: CellPosition = useMemo(
() => ({

View File

@ -1,15 +1,13 @@
import { useContext } from 'react';
import { useRecoilCallback } from 'recoil';
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { useContextScopeId } from '../../../recoil-scope/hooks/useContextScopeId';
import { getSnapshotScopedState } from '../../../recoil-scope/utils/getSnapshotScopedState';
import { useCloseCurrentCellInEditMode } from '../../hooks/useClearCellInEditMode';
import { CellContext } from '../../states/CellContext';
import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext';
import { isSomeInputInEditModeState } from '../../states/isSomeInputInEditModeState';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { customCellHotkeyScopeScopedState } from '../states/customCellHotkeyScopeScopedState';
import { useCurrentCellEditMode } from './useCurrentCellEditMode';
@ -24,7 +22,7 @@ export function useEditableCell() {
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
const cellContextId = useContextScopeId(CellContext);
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
function closeEditableCell() {
closeCurrentCellInEditMode();
@ -38,12 +36,6 @@ export function useEditableCell() {
.getLoadable(isSomeInputInEditModeState)
.valueOrThrow();
const customCellHotkeyScope = getSnapshotScopedState({
snapshot,
state: customCellHotkeyScopeScopedState,
contextScopeId: cellContextId,
});
if (!isSomeInputInEditMode) {
set(isSomeInputInEditModeState, true);
@ -62,7 +54,7 @@ export function useEditableCell() {
}
}
},
[setCurrentCellInEditMode, setHotkeyScope, cellContextId],
[setCurrentCellInEditMode, setHotkeyScope, customCellHotkeyScope],
);
return {

View File

@ -14,6 +14,7 @@ export function useRegisterCloseCellHandlers(
) {
const { closeEditableCell } = useEditableCell();
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
useListenClickOutside({
refs: [wrapperRef],
callback: (event) => {
@ -26,6 +27,7 @@ export function useRegisterCloseCellHandlers(
}
},
});
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
useScopedHotkeys(

View File

@ -1,23 +0,0 @@
import { useEffect } from 'react';
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { useRecoilScopedState } from '../../../recoil-scope/hooks/useRecoilScopedState';
import { CellContext } from '../../states/CellContext';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { customCellHotkeyScopeScopedState } from '../states/customCellHotkeyScopeScopedState';
const DEFAULT_CELL_SCOPE: HotkeyScope = {
scope: TableHotkeyScope.CellEditMode,
};
export function useRegisterEditableCell(cellHotkeyScope?: HotkeyScope) {
const [, setCustomCellHotkeyScope] = useRecoilScopedState(
customCellHotkeyScopeScopedState,
CellContext,
);
useEffect(() => {
setCustomCellHotkeyScope(cellHotkeyScope ?? DEFAULT_CELL_SCOPE);
}, [cellHotkeyScope, setCustomCellHotkeyScope]);
}

View File

@ -1,50 +1,29 @@
import { useMemo } from 'react';
import { useRecoilCallback } from 'recoil';
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { useSetSoftFocusPosition } from '../../hooks/useSetSoftFocusPosition';
import { CellContext } from '../../states/CellContext';
import { currentColumnNumberScopedState } from '../../states/currentColumnNumberScopedState';
import { currentRowNumberScopedState } from '../../states/currentRowNumberScopedState';
import { isSoftFocusActiveState } from '../../states/isSoftFocusActiveState';
import { RowContext } from '../../states/RowContext';
import { CellPosition } from '../../types/CellPosition';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentCellPosition } from './useCurrentCellPosition';
export function useSetSoftFocusOnCurrentCell() {
const setSoftFocusPosition = useSetSoftFocusPosition();
const [currentRowNumber] = useRecoilScopedState(
currentRowNumberScopedState,
RowContext,
);
const [currentColumnNumber] = useRecoilScopedState(
currentColumnNumberScopedState,
CellContext,
);
const currentTablePosition: CellPosition = useMemo(
() => ({
column: currentColumnNumber,
row: currentRowNumber,
}),
[currentColumnNumber, currentRowNumber],
);
const currentCellPosition = useCurrentCellPosition();
const setHotkeyScope = useSetHotkeyScope();
return useRecoilCallback(
({ set }) =>
() => {
setSoftFocusPosition(currentTablePosition);
setSoftFocusPosition(currentCellPosition);
set(isSoftFocusActiveState, true);
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
},
[setHotkeyScope, currentTablePosition, setSoftFocusPosition],
[setHotkeyScope, currentCellPosition, setSoftFocusPosition],
);
}

View File

@ -1,11 +0,0 @@
import { atomFamily } from 'recoil';
import { HotkeyScope } from '../../../hotkey/types/HotkeyScope';
export const customCellHotkeyScopeScopedState = atomFamily<
HotkeyScope | null,
string
>({
key: 'customCellHotkeyScopeScopedState',
default: null,
});

View File

@ -1,9 +1,11 @@
import { useRef } from 'react';
import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate';
import { useListenClickOutside } from '../../../hooks/useListenClickOutside';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useEditableCell } from '../hooks/useEditableCell';
@ -38,8 +40,21 @@ export function EditableCellDateEditMode({
[closeEditableCell],
);
const containerRef = useRef(null);
useListenClickOutside({
refs: [containerRef],
callback: (event) => {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
closeEditableCell();
},
});
return (
<EditableCellDateEditModeContainer>
<EditableCellDateEditModeContainer ref={containerRef}>
<InplaceInputDate onChange={handleDateChange} value={value} />
</EditableCellDateEditModeContainer>
);

View File

@ -1,4 +1,4 @@
import { ReactElement, useEffect, useState } from 'react';
import { ReactElement } from 'react';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { CellSkeleton } from '../components/CellSkeleton';
@ -12,8 +12,7 @@ type OwnProps = {
firstValuePlaceholder: string;
secondValuePlaceholder: string;
nonEditModeContent: ReactElement;
onChange: (firstValue: string, secondValue: string) => void;
onSubmit?: () => void;
onSubmit?: (firstValue: string, secondValue: string) => void;
onCancel?: () => void;
loading?: boolean;
};
@ -23,36 +22,21 @@ export function EditableCellDoubleText({
secondValue,
firstValuePlaceholder,
secondValuePlaceholder,
onChange,
onSubmit,
onCancel,
nonEditModeContent,
loading,
}: OwnProps) {
const [firstInternalValue, setFirstInternalValue] = useState(firstValue);
const [secondInternalValue, setSecondInternalValue] = useState(secondValue);
useEffect(() => {
setFirstInternalValue(firstValue);
setSecondInternalValue(secondValue);
}, [firstValue, secondValue]);
function handleOnChange(firstValue: string, secondValue: string): void {
setFirstInternalValue(firstValue);
setSecondInternalValue(secondValue);
onChange(firstValue, secondValue);
}
return (
<EditableCell
editHotkeyScope={{ scope: TableHotkeyScope.CellDoubleTextInput }}
editModeContent={
<EditableCellDoubleTextEditMode
firstValue={firstInternalValue}
secondValue={secondInternalValue}
firstValue={firstValue}
secondValue={secondValue}
firstValuePlaceholder={firstValuePlaceholder}
secondValuePlaceholder={secondValuePlaceholder}
onChange={handleOnChange}
onSubmit={onSubmit}
onCancel={onCancel}
/>

View File

@ -1,21 +1,22 @@
import { ChangeEvent, useRef, useState } from 'react';
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useEditableCell } from '../hooks/useEditableCell';
import { useRegisterCloseCellHandlers } from '../hooks/useRegisterCloseCellHandlers';
type OwnProps = {
firstValue: string;
secondValue: string;
firstValuePlaceholder: string;
secondValuePlaceholder: string;
onChange: (firstValue: string, secondValue: string) => void;
onSubmit?: () => void;
onChange?: (firstValue: string, secondValue: string) => void;
onSubmit?: (firstValue: string, secondValue: string) => void;
onCancel?: () => void;
};
@ -39,6 +40,19 @@ export function EditableCellDoubleTextEditMode({
onSubmit,
onCancel,
}: OwnProps) {
const [firstInternalValue, setFirstInternalValue] = useState(firstValue);
const [secondInternalValue, setSecondInternalValue] = useState(secondValue);
useEffect(() => {
setFirstInternalValue(firstValue);
setSecondInternalValue(secondValue);
}, [firstValue, secondValue]);
function handleOnChange(firstValue: string, secondValue: string): void {
setFirstInternalValue(firstValue);
setSecondInternalValue(secondValue);
}
const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left');
const firstValueInputRef = useRef<HTMLInputElement>(null);
@ -52,12 +66,23 @@ export function EditableCellDoubleTextEditMode({
closeEditableCell();
}
function handleCancel() {
setFirstInternalValue(firstValue);
setSecondInternalValue(secondValue);
onCancel?.();
}
function handleSubmit() {
onSubmit?.(firstInternalValue, secondInternalValue);
}
useScopedHotkeys(
Key.Enter,
() => {
closeCell();
moveDown();
onSubmit?.();
handleSubmit();
},
TableHotkeyScope.CellDoubleTextInput,
[closeCell],
@ -66,7 +91,7 @@ export function EditableCellDoubleTextEditMode({
useScopedHotkeys(
Key.Escape,
() => {
onCancel?.();
handleCancel();
closeCell();
},
TableHotkeyScope.CellDoubleTextInput,
@ -80,7 +105,8 @@ export function EditableCellDoubleTextEditMode({
setFocusPosition('right');
secondValueInputRef.current?.focus();
} else {
onSubmit?.();
handleSubmit();
closeCell();
moveRight();
}
@ -96,7 +122,7 @@ export function EditableCellDoubleTextEditMode({
setFocusPosition('left');
firstValueInputRef.current?.focus();
} else {
onSubmit?.();
handleSubmit();
closeCell();
moveLeft();
}
@ -105,23 +131,27 @@ export function EditableCellDoubleTextEditMode({
[closeCell, moveRight, focusPosition],
);
const wrapperRef = useRef(null);
useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel);
return (
<StyledContainer>
<InplaceInputTextEditMode
<StyledContainer ref={wrapperRef}>
<StyledInput
autoFocus
placeholder={firstValuePlaceholder}
ref={firstValueInputRef}
value={firstValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value, secondValue);
handleOnChange(event.target.value, secondValue);
}}
/>
<InplaceInputTextEditMode
<StyledInput
placeholder={secondValuePlaceholder}
ref={secondValueInputRef}
value={secondValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(firstValue, event.target.value);
handleOnChange(firstValue, event.target.value);
}}
/>
</StyledContainer>

View File

@ -1,5 +1,3 @@
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import { InplaceInputPhoneDisplayMode } from '@/ui/display/component/InplaceInputPhoneDisplayMode';
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
@ -8,42 +6,21 @@ import { EditableCell } from '../components/EditableCell';
type OwnProps = {
placeholder?: string;
value: string;
onChange: (updated: string) => void;
onSubmit?: () => void;
onCancel?: () => void;
onSubmit?: (newText: string) => void;
};
export function EditableCellPhone({
value,
placeholder,
onChange,
onSubmit,
onCancel,
}: OwnProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [inputValue, setInputValue] = useState(value);
useEffect(() => {
setInputValue(value);
}, [value]);
export function EditableCellPhone({ value, placeholder, onSubmit }: OwnProps) {
return (
<EditableCell
editModeContent={
<InplaceInputTextEditMode
autoFocus
placeholder={placeholder || ''}
ref={inputRef}
value={inputValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
onChange(event.target.value);
}}
value={value}
onSubmit={(newText) => onSubmit?.(newText)}
/>
}
nonEditModeContent={<InplaceInputPhoneDisplayMode value={inputValue} />}
onSubmit={onSubmit}
onCancel={onCancel}
nonEditModeContent={<InplaceInputPhoneDisplayMode value={value} />}
/>
);
}

View File

@ -1,5 +1,3 @@
import { ChangeEvent, useEffect, useState } from 'react';
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
@ -9,10 +7,10 @@ import { EditableCell } from '../components/EditableCell';
type OwnProps = {
placeholder?: string;
value: string;
onChange: (newValue: string) => void;
onChange?: (newValue: string) => void;
editModeHorizontalAlign?: 'left' | 'right';
loading?: boolean;
onSubmit?: () => void;
onSubmit?: (newText: string) => void;
onCancel?: () => void;
};
@ -25,12 +23,6 @@ export function EditableCellText({
onCancel,
onSubmit,
}: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
setInternalValue(value);
}, [value]);
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
@ -38,22 +30,15 @@ export function EditableCellText({
<InplaceInputTextEditMode
placeholder={placeholder || ''}
autoFocus
value={internalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setInternalValue(event.target.value);
onChange(event.target.value);
}}
value={value}
onSubmit={(newText) => onSubmit?.(newText)}
/>
}
onSubmit={onSubmit}
onCancel={onCancel}
nonEditModeContent={
loading ? (
<CellSkeleton />
) : (
<InplaceInputTextDisplayMode>
{internalValue}
</InplaceInputTextDisplayMode>
<InplaceInputTextDisplayMode>{value}</InplaceInputTextDisplayMode>
)
}
></EditableCell>

View File

@ -1,5 +1,3 @@
import { ChangeEvent, useEffect, useState } from 'react';
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
import { RawLink } from '../../../link/components/RawLink';
@ -9,53 +7,40 @@ import { EditableCell } from '../components/EditableCell';
type OwnProps = {
placeholder?: string;
url: string;
onChange: (newURL: string) => void;
onChange?: (newURL: string) => void;
editModeHorizontalAlign?: 'left' | 'right';
loading?: boolean;
onSubmit?: () => void;
onSubmit?: (newURL: string) => void;
onCancel?: () => void;
};
export function EditableCellURL({
url,
placeholder,
onChange,
editModeHorizontalAlign,
loading,
onCancel,
onSubmit,
}: OwnProps) {
const [internalValue, setInternalValue] = useState(url);
useEffect(() => {
setInternalValue(url);
}, [url]);
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<InplaceInputTextEditMode
placeholder={placeholder || ''}
placeholder={placeholder}
autoFocus
value={internalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setInternalValue(event.target.value);
onChange(event.target.value);
}}
value={url}
onSubmit={(newURL) => onSubmit?.(newURL)}
/>
}
onSubmit={onSubmit}
onCancel={onCancel}
nonEditModeContent={
loading ? (
<CellSkeleton />
) : (
<RawLink
onClick={(e) => e.stopPropagation()}
href={internalValue ? 'https://' + internalValue : ''}
href={url ? 'https://' + url : ''}
>
{internalValue}
{url}
</RawLink>
)
}

View File

@ -1,30 +1,21 @@
import { ChangeEvent, ReactNode, useEffect, useRef, useState } from 'react';
import { ReactNode, useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { textInputStyle } from '@/ui/themes/effects';
import { InplaceInputTextEditMode } from '../../../inplace-input/components/InplaceInputTextEditMode';
import { EditableCell } from '../components/EditableCell';
export type EditableChipProps = {
placeholder?: string;
value: string;
changeHandler: (updated: string) => void;
editModeHorizontalAlign?: 'left' | 'right';
ChipComponent: React.ReactNode;
commentThreadCount?: number;
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
rightEndContents?: ReactNode[];
onSubmit?: () => void;
onSubmit?: (newValue: string) => void;
onCancel?: () => void;
};
// TODO: refactor
const StyledInplaceInput = styled.input`
width: 100%;
${textInputStyle}
`;
const NoEditModeContainer = styled.div`
align-items: center;
display: flex;
@ -40,14 +31,11 @@ const RightContainer = styled.div`
export function EditableCellChip({
value,
placeholder,
changeHandler,
editModeHorizontalAlign,
ChipComponent,
rightEndContents,
onSubmit,
onCancel,
}: EditableChipProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [inputValue, setInputValue] = useState(value);
useEffect(() => {
@ -64,19 +52,13 @@ export function EditableCellChip({
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<StyledInplaceInput
<InplaceInputTextEditMode
placeholder={placeholder || ''}
autoFocus
ref={inputRef}
value={inputValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
changeHandler(event.target.value);
}}
onSubmit={(newValue) => onSubmit?.(newValue)}
/>
}
onSubmit={onSubmit}
onCancel={onCancel}
nonEditModeContent={
<NoEditModeContainer>
{ChipComponent}

View File

@ -1,7 +1,6 @@
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
import { useContext } from 'react';
import { currentRowEntityIdScopedState } from '../states/currentRowEntityIdScopedState';
import { RowContext } from '../states/RowContext';
import { RowIdContext } from '../states/RowIdContext';
export type TableDimensions = {
numberOfColumns: number;
@ -9,10 +8,7 @@ export type TableDimensions = {
};
export function useCurrentRowEntityId() {
const currentRowEntityIdScoped = useRecoilScopedValue(
currentRowEntityIdScopedState,
RowContext,
);
const currentEntityId = useContext(RowIdContext);
return currentRowEntityIdScoped;
return currentEntityId;
}

View File

@ -1,11 +1,11 @@
import { useContext } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState';
import { useCurrentRowEntityId } from './useCurrentEntityId';
import { RowIdContext } from '../states/RowIdContext';
export function useCurrentRowSelected() {
const currentRowId = useCurrentRowEntityId();
const currentRowId = useContext(RowIdContext);
const [isRowSelected] = useRecoilState(
isRowSelectedFamilyState(currentRowId ?? ''),

View File

@ -45,8 +45,6 @@ export function useLeaveTableFocus() {
closeCurrentCellInEditMode();
disableSoftFocus();
setHotkeyScope(TableHotkeyScope.Table, { goto: true });
},
[setHotkeyScope, closeCurrentCellInEditMode, disableSoftFocus],
);

View File

@ -1,3 +0,0 @@
import { createContext } from 'react';
export const CellContext = createContext<string | null>(null);

View File

@ -0,0 +1,5 @@
import { createContext } from 'react';
import { HotkeyScope } from '../../hotkey/types/HotkeyScope';
export const CellHotkeyScopeContext = createContext<HotkeyScope | null>(null);

View File

@ -0,0 +1,3 @@
import { createContext } from 'react';
export const ColumnIndexContext = createContext<number>(0);

View File

@ -1,3 +0,0 @@
import { createContext } from 'react';
export const RowContext = createContext<string | null>(null);

View File

@ -0,0 +1,3 @@
import { createContext } from 'react';
export const RowIdContext = createContext<string | null>(null);

View File

@ -0,0 +1,3 @@
import { createContext } from 'react';
export const RowIndexContext = createContext<number>(0);

View File

@ -3,11 +3,10 @@ import { userEvent, within } from '@storybook/testing-library';
import { IconList } from '@/ui/icon/index';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { companiesFilters } from '~/pages/companies/companies-filters';
import { availableSorts } from '~/pages/companies/companies-sorts';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { HooksEntityTable } from '../../../components/HooksEntityTable';
import { ComponentWithRouterDecorator } from '../../../../../../testing/decorators/ComponentWithRouterDecorator';
import { CompanyEntityTableDataMocked } from '../../../../../companies/table/components/CompanyEntityTableDataMocked';
import { TableContext } from '../../../states/TableContext';
import { TableHeader } from '../TableHeader';
@ -17,15 +16,11 @@ const meta: Meta<typeof TableHeader> = {
decorators: [
(Story) => (
<RecoilScope SpecificContext={TableContext}>
{/* TODO: add company mocked loader <CompanyEntityTableData */}
<HooksEntityTable
availableFilters={companiesFilters}
numberOfColumns={5}
/>
<CompanyEntityTableDataMocked />
<Story />
</RecoilScope>
),
ComponentDecorator,
ComponentWithRouterDecorator,
],
argTypes: { viewIcon: { control: false } },
args: {