Refacto/abstract inplace input (#530)
* Move code to new folder * Deduplicate code, remove dependancy on table * Remove more table dependency * Move close logic to input * Migrate editable text cell * Rename EditableTextInput * Fix component test id
This commit is contained in:
@ -4,7 +4,7 @@ import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFiltere
|
|||||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { EntityForSelect } from '@/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/relation-picker/types/EntityForSelect';
|
||||||
import { Entity } from '@/relation-picker/types/EntityTypeForSelect';
|
import { Entity } from '@/relation-picker/types/EntityTypeForSelect';
|
||||||
import { useEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
|
import { useInplaceInput } from '@/ui/components/inplace-input/hooks/useCloseInplaceInput';
|
||||||
import {
|
import {
|
||||||
Company,
|
Company,
|
||||||
User,
|
User,
|
||||||
@ -28,7 +28,7 @@ export function CompanyAccountOwnerPicker({ company }: OwnProps) {
|
|||||||
);
|
);
|
||||||
const [updateCompany] = useUpdateCompanyMutation();
|
const [updateCompany] = useUpdateCompanyMutation();
|
||||||
|
|
||||||
const { closeEditableCell } = useEditableCell();
|
const { closeInplaceInput } = useInplaceInput();
|
||||||
|
|
||||||
const companies = useFilteredSearchEntityQuery({
|
const companies = useFilteredSearchEntityQuery({
|
||||||
queryHook: useSearchUserQuery,
|
queryHook: useSearchUserQuery,
|
||||||
@ -52,7 +52,7 @@ export function CompanyAccountOwnerPicker({ company }: OwnProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
closeEditableCell();
|
closeInplaceInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import CompanyChip from '@/companies/components/CompanyChip';
|
import CompanyChip from '@/companies/components/CompanyChip';
|
||||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { EditableCell } from '@/ui/components/editable-cell/EditableCell';
|
import { EditableCell } from '@/ui/components/editable-cell/EditableCell';
|
||||||
import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
|
import { isCreateModeScopedState } from '@/ui/components/inplace-input/states/isCreateModeScopedState';
|
||||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||||
import { Company, Person } from '~/generated/graphql';
|
import { Company, Person } from '~/generated/graphql';
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { v4 } from 'uuid';
|
|||||||
|
|
||||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
|
import { isCreateModeScopedState } from '@/ui/components/inplace-input/states/isCreateModeScopedState';
|
||||||
import { DoubleTextInput } from '@/ui/components/inputs/DoubleTextInput';
|
import { DoubleTextInput } from '@/ui/components/inputs/DoubleTextInput';
|
||||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||||
import { logError } from '@/utils/logs/logError';
|
import { logError } from '@/utils/logs/logError';
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState'
|
|||||||
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
||||||
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { useEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
|
import { useInplaceInput } from '@/ui/components/inplace-input/hooks/useCloseInplaceInput';
|
||||||
import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
|
import { isCreateModeScopedState } from '@/ui/components/inplace-input/states/isCreateModeScopedState';
|
||||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||||
import {
|
import {
|
||||||
CommentableType,
|
CommentableType,
|
||||||
@ -25,7 +25,7 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
|
|||||||
);
|
);
|
||||||
const [updatePeople] = useUpdatePeopleMutation();
|
const [updatePeople] = useUpdatePeopleMutation();
|
||||||
|
|
||||||
const { closeEditableCell } = useEditableCell();
|
const { closeInplaceInput } = useInplaceInput();
|
||||||
|
|
||||||
const companies = useFilteredSearchEntityQuery({
|
const companies = useFilteredSearchEntityQuery({
|
||||||
queryHook: useSearchCompanyQuery,
|
queryHook: useSearchCompanyQuery,
|
||||||
@ -50,7 +50,7 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
closeEditableCell();
|
closeInplaceInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCreate() {
|
function handleCreate() {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { IconCurrencyDollar } from '@tabler/icons-react';
|
|||||||
|
|
||||||
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
|
||||||
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
|
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
|
||||||
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
|
import { InplaceTextInput } from '@/ui/components/inplace-input/types/InplaceTextInput';
|
||||||
import { CellContext } from '@/ui/tables/states/CellContext';
|
import { CellContext } from '@/ui/tables/states/CellContext';
|
||||||
import { RowContext } from '@/ui/tables/states/RowContext';
|
import { RowContext } from '@/ui/tables/states/RowContext';
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ export function CompanyBoardCard({
|
|||||||
<span>
|
<span>
|
||||||
<IconCurrencyDollar size={theme.icon.size.md} />
|
<IconCurrencyDollar size={theme.icon.size.md} />
|
||||||
<HackScope>
|
<HackScope>
|
||||||
<EditableText
|
<InplaceTextInput
|
||||||
content={pipelineProgress.amount?.toString() || ''}
|
content={pipelineProgress.amount?.toString() || ''}
|
||||||
placeholder="Opportunity amount"
|
placeholder="Opportunity amount"
|
||||||
changeHandler={(value) =>
|
changeHandler={(value) =>
|
||||||
|
|||||||
@ -1,26 +1,9 @@
|
|||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
import { InplaceInput } from '../inplace-input/InplaceInput';
|
||||||
|
|
||||||
import { useEditableCell } from './hooks/useCloseEditableCell';
|
|
||||||
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
|
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
|
||||||
import { useSetSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
|
import { useSetSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
|
||||||
import { isEditModeScopedState } from './states/isEditModeScopedState';
|
|
||||||
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
|
||||||
import { EditableCellEditMode } from './EditableCellEditMode';
|
|
||||||
import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode';
|
|
||||||
|
|
||||||
export const CellBaseContainer = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
height: 32px;
|
|
||||||
position: relative;
|
|
||||||
user-select: none;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
editModeContent: ReactElement;
|
editModeContent: ReactElement;
|
||||||
@ -35,43 +18,16 @@ export function EditableCell({
|
|||||||
editModeContent,
|
editModeContent,
|
||||||
nonEditModeContent,
|
nonEditModeContent,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [isEditMode] = useRecoilScopedState(isEditModeScopedState);
|
|
||||||
|
|
||||||
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
|
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
|
||||||
|
|
||||||
const { closeEditableCell, openEditableCell } = useEditableCell();
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOnOutsideClick() {
|
|
||||||
closeEditableCell();
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CellBaseContainer onClick={handleOnClick}>
|
<InplaceInput
|
||||||
{isEditMode ? (
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
<EditableCellEditMode
|
editModeVerticalPosition={editModeVerticalPosition}
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeContent={editModeContent}
|
||||||
editModeVerticalPosition={editModeVerticalPosition}
|
nonEditModeContent={nonEditModeContent}
|
||||||
onOutsideClick={handleOnOutsideClick}
|
setSoftFocusOnCurrentInplaceInput={setSoftFocusOnCurrentCell}
|
||||||
>
|
hasSoftFocus={!!hasSoftFocus}
|
||||||
{editModeContent}
|
/>
|
||||||
</EditableCellEditMode>
|
|
||||||
) : hasSoftFocus ? (
|
|
||||||
<EditableCellSoftFocusMode>
|
|
||||||
{nonEditModeContent}
|
|
||||||
</EditableCellSoftFocusMode>
|
|
||||||
) : (
|
|
||||||
<EditableCellDisplayMode>{nonEditModeContent}</EditableCellDisplayMode>
|
|
||||||
)}
|
|
||||||
</CellBaseContainer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
export const HoverableMenuItem = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
background: ${({ theme }) => theme.background.primary};
|
|
||||||
border-radius: 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
transition: background 0.1s ease;
|
|
||||||
user-select: none;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: ${({ theme }) => theme.background.transparent.light};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { InplaceTextInput } from '../../inplace-input/types/InplaceTextInput';
|
||||||
|
import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell';
|
||||||
|
import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
placeholder?: string;
|
||||||
|
content: string;
|
||||||
|
changeHandler: (updated: string) => void;
|
||||||
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
|
};
|
||||||
|
|
||||||
|
export function EditableTextCell({
|
||||||
|
editModeHorizontalAlign = 'left',
|
||||||
|
content,
|
||||||
|
changeHandler,
|
||||||
|
placeholder,
|
||||||
|
}: OwnProps) {
|
||||||
|
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
|
||||||
|
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||||
|
return (
|
||||||
|
<InplaceTextInput
|
||||||
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
|
content={content}
|
||||||
|
changeHandler={changeHandler}
|
||||||
|
placeholder={placeholder}
|
||||||
|
setSoftFocusOnCurrentInplaceInput={setSoftFocusOnCurrentCell}
|
||||||
|
hasSoftFocus={hasSoftFocus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
import { ReactElement } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
|
import { useInplaceInput } from './hooks/useCloseInplaceInput';
|
||||||
|
import { isEditModeScopedState } from './states/isEditModeScopedState';
|
||||||
|
import { InplaceInputDisplayMode } from './InplaceInputDisplayMode';
|
||||||
|
import { InplaceInputEditMode } from './InplaceInputEditMode';
|
||||||
|
import { InplaceInputSoftFocusMode } from './InplaceInputSoftFocusMode';
|
||||||
|
|
||||||
|
export const InplaceInputBaseContainer = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
height: 32px;
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
editModeContent: ReactElement;
|
||||||
|
nonEditModeContent: ReactElement;
|
||||||
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
|
editModeVerticalPosition?: 'over' | 'below';
|
||||||
|
setSoftFocusOnCurrentInplaceInput?: () => void;
|
||||||
|
hasSoftFocus?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function InplaceInput({
|
||||||
|
editModeHorizontalAlign = 'left',
|
||||||
|
editModeVerticalPosition = 'over',
|
||||||
|
editModeContent,
|
||||||
|
nonEditModeContent,
|
||||||
|
setSoftFocusOnCurrentInplaceInput,
|
||||||
|
hasSoftFocus,
|
||||||
|
}: OwnProps) {
|
||||||
|
const [isEditMode] = useRecoilScopedState(isEditModeScopedState);
|
||||||
|
|
||||||
|
const { closeInplaceInput, openInplaceInput } = useInplaceInput();
|
||||||
|
|
||||||
|
// TODO: we might have silent problematic behavior because of the setTimeout in openInplaceInput, investigate
|
||||||
|
// Maybe we could build a switchInplaceInput to handle the case where we go from one InplaceInput to another.
|
||||||
|
// See https://github.com/twentyhq/twenty/issues/446
|
||||||
|
function handleOnClick() {
|
||||||
|
openInplaceInput();
|
||||||
|
setSoftFocusOnCurrentInplaceInput && setSoftFocusOnCurrentInplaceInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOnOutsideClick() {
|
||||||
|
closeInplaceInput();
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<InplaceInputBaseContainer onClick={handleOnClick}>
|
||||||
|
{isEditMode ? (
|
||||||
|
<InplaceInputEditMode
|
||||||
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
|
editModeVerticalPosition={editModeVerticalPosition}
|
||||||
|
onOutsideClick={handleOnOutsideClick}
|
||||||
|
>
|
||||||
|
{editModeContent}
|
||||||
|
</InplaceInputEditMode>
|
||||||
|
) : hasSoftFocus ? (
|
||||||
|
<InplaceInputSoftFocusMode>
|
||||||
|
{nonEditModeContent}
|
||||||
|
</InplaceInputSoftFocusMode>
|
||||||
|
) : (
|
||||||
|
<InplaceInputDisplayMode hasSoftFocus={!!hasSoftFocus}>
|
||||||
|
{nonEditModeContent}
|
||||||
|
</InplaceInputDisplayMode>
|
||||||
|
)}
|
||||||
|
</InplaceInputBaseContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,12 +1,10 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
softFocus: boolean;
|
softFocus: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EditableCellNormalModeOuterContainer = styled.div<Props>`
|
export const InplaceInputNormalModeOuterContainer = styled.div<Props>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -24,7 +22,7 @@ export const EditableCellNormalModeOuterContainer = styled.div<Props>`
|
|||||||
: ''}
|
: ''}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const EditableCellNormalModeInnerContainer = styled.div`
|
export const InplaceInputNormalModeInnerContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -32,16 +30,17 @@ export const EditableCellNormalModeInnerContainer = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function EditableCellDisplayMode({
|
export function InplaceInputDisplayMode({
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren<unknown>) {
|
hasSoftFocus,
|
||||||
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
}: React.PropsWithChildren & {
|
||||||
|
hasSoftFocus: boolean;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<EditableCellNormalModeOuterContainer softFocus={hasSoftFocus}>
|
<InplaceInputNormalModeOuterContainer softFocus={hasSoftFocus}>
|
||||||
<EditableCellNormalModeInnerContainer>
|
<InplaceInputNormalModeInnerContainer>
|
||||||
{children}
|
{children}
|
||||||
</EditableCellNormalModeInnerContainer>
|
</InplaceInputNormalModeInnerContainer>
|
||||||
</EditableCellNormalModeOuterContainer>
|
</InplaceInputNormalModeOuterContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -6,9 +6,9 @@ import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsid
|
|||||||
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
|
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
|
||||||
import { overlayBackground } from '@/ui/themes/effects';
|
import { overlayBackground } from '@/ui/themes/effects';
|
||||||
|
|
||||||
import { useEditableCell } from './hooks/useCloseEditableCell';
|
import { useInplaceInput } from './hooks/useCloseInplaceInput';
|
||||||
|
|
||||||
export const EditableCellEditModeContainer = styled.div<OwnProps>`
|
export const InplaceInputEditModeContainer = styled.div<OwnProps>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid ${({ theme }) => theme.border.color.light};
|
border: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
@ -34,7 +34,7 @@ type OwnProps = {
|
|||||||
onOutsideClick?: () => void;
|
onOutsideClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditableCellEditMode({
|
export function InplaceInputEditMode({
|
||||||
editModeHorizontalAlign,
|
editModeHorizontalAlign,
|
||||||
editModeVerticalPosition,
|
editModeVerticalPosition,
|
||||||
children,
|
children,
|
||||||
@ -42,7 +42,7 @@ export function EditableCellEditMode({
|
|||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const wrapperRef = useRef(null);
|
const wrapperRef = useRef(null);
|
||||||
|
|
||||||
const { closeEditableCell } = useEditableCell();
|
const { closeInplaceInput } = useInplaceInput();
|
||||||
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
||||||
|
|
||||||
useListenClickOutsideArrayOfRef([wrapperRef], () => {
|
useListenClickOutsideArrayOfRef([wrapperRef], () => {
|
||||||
@ -52,7 +52,7 @@ export function EditableCellEditMode({
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'enter',
|
'enter',
|
||||||
() => {
|
() => {
|
||||||
closeEditableCell();
|
closeInplaceInput();
|
||||||
moveDown();
|
moveDown();
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -60,26 +60,26 @@ export function EditableCellEditMode({
|
|||||||
enableOnFormTags: true,
|
enableOnFormTags: true,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
[closeEditableCell],
|
[closeInplaceInput],
|
||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'esc',
|
'esc',
|
||||||
() => {
|
() => {
|
||||||
closeEditableCell();
|
closeInplaceInput();
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enableOnContentEditable: true,
|
enableOnContentEditable: true,
|
||||||
enableOnFormTags: true,
|
enableOnFormTags: true,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
[closeEditableCell],
|
[closeInplaceInput],
|
||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'tab',
|
'tab',
|
||||||
() => {
|
() => {
|
||||||
closeEditableCell();
|
closeInplaceInput();
|
||||||
moveRight();
|
moveRight();
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -87,13 +87,13 @@ export function EditableCellEditMode({
|
|||||||
enableOnFormTags: true,
|
enableOnFormTags: true,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
[closeEditableCell, moveRight],
|
[closeInplaceInput, moveRight],
|
||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'shift+tab',
|
'shift+tab',
|
||||||
() => {
|
() => {
|
||||||
closeEditableCell();
|
closeInplaceInput();
|
||||||
moveLeft();
|
moveLeft();
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -101,17 +101,17 @@ export function EditableCellEditMode({
|
|||||||
enableOnFormTags: true,
|
enableOnFormTags: true,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
[closeEditableCell, moveRight],
|
[closeInplaceInput, moveRight],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellEditModeContainer
|
<InplaceInputEditModeContainer
|
||||||
data-testid="editable-cell-edit-mode-container"
|
data-testid="inplace-input-edit-mode-container"
|
||||||
ref={wrapperRef}
|
ref={wrapperRef}
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeVerticalPosition={editModeVerticalPosition}
|
editModeVerticalPosition={editModeVerticalPosition}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</EditableCellEditModeContainer>
|
</InplaceInputEditModeContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -5,13 +5,13 @@ import { useRecoilState } from 'recoil';
|
|||||||
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
|
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
|
||||||
import { isNonTextWritingKey } from '@/utils/hotkeys/isNonTextWritingKey';
|
import { isNonTextWritingKey } from '@/utils/hotkeys/isNonTextWritingKey';
|
||||||
|
|
||||||
import { useEditableCell } from './hooks/useCloseEditableCell';
|
import { useInplaceInput } from './hooks/useCloseInplaceInput';
|
||||||
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
import { InplaceInputDisplayMode } from './InplaceInputDisplayMode';
|
||||||
|
|
||||||
export function EditableCellSoftFocusMode({
|
export function InplaceInputSoftFocusMode({
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren<unknown>) {
|
}: React.PropsWithChildren<unknown>) {
|
||||||
const { closeEditableCell, openEditableCell } = useEditableCell();
|
const { closeInplaceInput, openInplaceInput } = useInplaceInput();
|
||||||
const [captureHotkeyTypeInFocus] = useRecoilState(
|
const [captureHotkeyTypeInFocus] = useRecoilState(
|
||||||
captureHotkeyTypeInFocusState,
|
captureHotkeyTypeInFocusState,
|
||||||
);
|
);
|
||||||
@ -19,14 +19,14 @@ export function EditableCellSoftFocusMode({
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'enter',
|
'enter',
|
||||||
() => {
|
() => {
|
||||||
openEditableCell();
|
openInplaceInput();
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enableOnContentEditable: true,
|
enableOnContentEditable: true,
|
||||||
enableOnFormTags: true,
|
enableOnFormTags: true,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
[closeEditableCell],
|
[closeInplaceInput],
|
||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
@ -44,7 +44,7 @@ export function EditableCellSoftFocusMode({
|
|||||||
if (captureHotkeyTypeInFocus) {
|
if (captureHotkeyTypeInFocus) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
openEditableCell();
|
openInplaceInput();
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enableOnContentEditable: true,
|
enableOnContentEditable: true,
|
||||||
@ -53,5 +53,7 @@ export function EditableCellSoftFocusMode({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return <EditableCellDisplayMode>{children}</EditableCellDisplayMode>;
|
return (
|
||||||
|
<InplaceInputDisplayMode hasSoftFocus>{children}</InplaceInputDisplayMode>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@ -1,14 +1,15 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
// TODO: Remove dependancy to table
|
||||||
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
|
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
|
||||||
|
|
||||||
import { isEditModeScopedState } from '../states/isEditModeScopedState';
|
import { isEditModeScopedState } from '../states/isEditModeScopedState';
|
||||||
|
|
||||||
export function useEditableCell() {
|
export function useInplaceInput() {
|
||||||
const [, setIsEditMode] = useRecoilScopedState(isEditModeScopedState);
|
const [, setIsEditMode] = useRecoilScopedState(isEditModeScopedState);
|
||||||
|
|
||||||
const closeEditableCell = useRecoilCallback(
|
const closeInplaceInput = useRecoilCallback(
|
||||||
({ set }) =>
|
({ set }) =>
|
||||||
async () => {
|
async () => {
|
||||||
setIsEditMode(false);
|
setIsEditMode(false);
|
||||||
@ -20,7 +21,7 @@ export function useEditableCell() {
|
|||||||
[setIsEditMode],
|
[setIsEditMode],
|
||||||
);
|
);
|
||||||
|
|
||||||
const openEditableCell = useRecoilCallback(
|
const openInplaceInput = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
() => {
|
() => {
|
||||||
const isSomeInputInEditMode = snapshot
|
const isSomeInputInEditMode = snapshot
|
||||||
@ -37,7 +38,7 @@ export function useEditableCell() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
closeEditableCell,
|
closeInplaceInput,
|
||||||
openEditableCell,
|
openInplaceInput,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -3,13 +3,15 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { textInputStyle } from '@/ui/themes/effects';
|
import { textInputStyle } from '@/ui/themes/effects';
|
||||||
|
|
||||||
import { EditableCell } from '../EditableCell';
|
import { InplaceInput } from '../InplaceInput';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
content: string;
|
content: string;
|
||||||
changeHandler: (updated: string) => void;
|
changeHandler: (updated: string) => void;
|
||||||
editModeHorizontalAlign?: 'left' | 'right';
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
|
setSoftFocusOnCurrentInplaceInput?: () => void;
|
||||||
|
hasSoftFocus?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: refactor
|
// TODO: refactor
|
||||||
@ -26,17 +28,19 @@ const StyledNoEditText = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function EditableText({
|
export function InplaceTextInput({
|
||||||
content,
|
content,
|
||||||
placeholder,
|
placeholder,
|
||||||
changeHandler,
|
changeHandler,
|
||||||
editModeHorizontalAlign,
|
editModeHorizontalAlign,
|
||||||
|
setSoftFocusOnCurrentInplaceInput,
|
||||||
|
hasSoftFocus,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [inputValue, setInputValue] = useState(content);
|
const [inputValue, setInputValue] = useState(content);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<InplaceInput
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<StyledInplaceInput
|
<StyledInplaceInput
|
||||||
@ -50,7 +54,9 @@ export function EditableText({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
setSoftFocusOnCurrentInplaceInput={setSoftFocusOnCurrentInplaceInput}
|
||||||
|
hasSoftFocus={hasSoftFocus}
|
||||||
nonEditModeContent={<StyledNoEditText>{inputValue}</StyledNoEditText>}
|
nonEditModeContent={<StyledNoEditText>{inputValue}</StyledNoEditText>}
|
||||||
></EditableCell>
|
></InplaceInput>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@ import { createColumnHelper } from '@tanstack/react-table';
|
|||||||
import { CompanyAccountOwnerCell } from '@/companies/components/CompanyAccountOwnerCell';
|
import { CompanyAccountOwnerCell } from '@/companies/components/CompanyAccountOwnerCell';
|
||||||
import { CompanyEditableNameChipCell } from '@/companies/components/CompanyEditableNameCell';
|
import { CompanyEditableNameChipCell } from '@/companies/components/CompanyEditableNameCell';
|
||||||
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
|
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
|
||||||
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
|
import { EditableTextCell } from '@/ui/components/editable-cell/types/EditableTextCell';
|
||||||
import { ColumnHead } from '@/ui/components/table/ColumnHead';
|
import { ColumnHead } from '@/ui/components/table/ColumnHead';
|
||||||
import {
|
import {
|
||||||
IconBuildingSkyscraper,
|
IconBuildingSkyscraper,
|
||||||
@ -44,7 +44,7 @@ export const useCompaniesColumns = () => {
|
|||||||
<ColumnHead viewName="URL" viewIcon={<IconLink size={16} />} />
|
<ColumnHead viewName="URL" viewIcon={<IconLink size={16} />} />
|
||||||
),
|
),
|
||||||
cell: (props) => (
|
cell: (props) => (
|
||||||
<EditableText
|
<EditableTextCell
|
||||||
content={props.row.original.domainName || ''}
|
content={props.row.original.domainName || ''}
|
||||||
placeholder="Domain name"
|
placeholder="Domain name"
|
||||||
changeHandler={(value) => {
|
changeHandler={(value) => {
|
||||||
@ -66,7 +66,7 @@ export const useCompaniesColumns = () => {
|
|||||||
<ColumnHead viewName="Employees" viewIcon={<IconUsers size={16} />} />
|
<ColumnHead viewName="Employees" viewIcon={<IconUsers size={16} />} />
|
||||||
),
|
),
|
||||||
cell: (props) => (
|
cell: (props) => (
|
||||||
<EditableText
|
<EditableTextCell
|
||||||
content={props.row.original.employees?.toString() || ''}
|
content={props.row.original.employees?.toString() || ''}
|
||||||
placeholder="Employees"
|
placeholder="Employees"
|
||||||
changeHandler={(value) => {
|
changeHandler={(value) => {
|
||||||
@ -89,7 +89,7 @@ export const useCompaniesColumns = () => {
|
|||||||
<ColumnHead viewName="Address" viewIcon={<IconMap size={16} />} />
|
<ColumnHead viewName="Address" viewIcon={<IconMap size={16} />} />
|
||||||
),
|
),
|
||||||
cell: (props) => (
|
cell: (props) => (
|
||||||
<EditableText
|
<EditableTextCell
|
||||||
content={props.row.original.address || ''}
|
content={props.row.original.address || ''}
|
||||||
placeholder="Address"
|
placeholder="Address"
|
||||||
changeHandler={(value) => {
|
changeHandler={(value) => {
|
||||||
|
|||||||
@ -39,13 +39,13 @@ export const InteractWithManyRows: Story = {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
canvas.queryByTestId('editable-cell-edit-mode-container'),
|
canvas.queryByTestId('inplace-input-edit-mode-container'),
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
|
|
||||||
await userEvent.click(firstRowEmailCell);
|
await userEvent.click(firstRowEmailCell);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
canvas.queryByTestId('editable-cell-edit-mode-container'),
|
canvas.queryByTestId('inplace-input-edit-mode-container'),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(secondRowEmailCell);
|
await userEvent.click(secondRowEmailCell);
|
||||||
@ -57,7 +57,7 @@ export const InteractWithManyRows: Story = {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
canvas.queryByTestId('editable-cell-edit-mode-container'),
|
canvas.queryByTestId('inplace-input-edit-mode-container'),
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
|
|
||||||
await userEvent.click(secondRowEmailCellFocused);
|
await userEvent.click(secondRowEmailCellFocused);
|
||||||
@ -65,7 +65,7 @@ export const InteractWithManyRows: Story = {
|
|||||||
await sleep(25);
|
await sleep(25);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
canvas.queryByTestId('editable-cell-edit-mode-container'),
|
canvas.queryByTestId('inplace-input-edit-mode-container'),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { EditablePeopleFullName } from '@/people/components/EditablePeopleFullNa
|
|||||||
import { PeopleCompanyCell } from '@/people/components/PeopleCompanyCell';
|
import { PeopleCompanyCell } from '@/people/components/PeopleCompanyCell';
|
||||||
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
|
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
|
||||||
import { EditablePhone } from '@/ui/components/editable-cell/types/EditablePhone';
|
import { EditablePhone } from '@/ui/components/editable-cell/types/EditablePhone';
|
||||||
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
|
import { EditableTextCell } from '@/ui/components/editable-cell/types/EditableTextCell';
|
||||||
import { ColumnHead } from '@/ui/components/table/ColumnHead';
|
import { ColumnHead } from '@/ui/components/table/ColumnHead';
|
||||||
import {
|
import {
|
||||||
IconBuildingSkyscraper,
|
IconBuildingSkyscraper,
|
||||||
@ -55,7 +55,7 @@ export const usePeopleColumns = () => {
|
|||||||
<ColumnHead viewName="Email" viewIcon={<IconMail size={16} />} />
|
<ColumnHead viewName="Email" viewIcon={<IconMail size={16} />} />
|
||||||
),
|
),
|
||||||
cell: (props) => (
|
cell: (props) => (
|
||||||
<EditableText
|
<EditableTextCell
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
content={props.row.original.email || ''}
|
content={props.row.original.email || ''}
|
||||||
changeHandler={async (value: string) => {
|
changeHandler={async (value: string) => {
|
||||||
@ -137,7 +137,7 @@ export const usePeopleColumns = () => {
|
|||||||
<ColumnHead viewName="City" viewIcon={<IconMap size={16} />} />
|
<ColumnHead viewName="City" viewIcon={<IconMap size={16} />} />
|
||||||
),
|
),
|
||||||
cell: (props) => (
|
cell: (props) => (
|
||||||
<EditableText
|
<EditableTextCell
|
||||||
editModeHorizontalAlign="right"
|
editModeHorizontalAlign="right"
|
||||||
placeholder="City"
|
placeholder="City"
|
||||||
content={props.row.original.city || ''}
|
content={props.row.original.city || ''}
|
||||||
|
|||||||
Reference in New Issue
Block a user