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:
Emilien Chauvet
2023-07-07 12:11:57 -07:00
committed by GitHub
parent 26b033abc9
commit c847bca293
19 changed files with 189 additions and 138 deletions

View File

@ -4,7 +4,7 @@ import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFiltere
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/relation-picker/types/EntityForSelect';
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 {
Company,
User,
@ -28,7 +28,7 @@ export function CompanyAccountOwnerPicker({ company }: OwnProps) {
);
const [updateCompany] = useUpdateCompanyMutation();
const { closeEditableCell } = useEditableCell();
const { closeInplaceInput } = useInplaceInput();
const companies = useFilteredSearchEntityQuery({
queryHook: useSearchUserQuery,
@ -52,7 +52,7 @@ export function CompanyAccountOwnerPicker({ company }: OwnProps) {
},
});
closeEditableCell();
closeInplaceInput();
}
return (

View File

@ -1,7 +1,7 @@
import CompanyChip from '@/companies/components/CompanyChip';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
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 { Company, Person } from '~/generated/graphql';

View File

@ -4,7 +4,7 @@ import { v4 } from 'uuid';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
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 { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
import { logError } from '@/utils/logs/logError';

View File

@ -2,8 +2,8 @@ import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState'
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
import { useEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
import { useInplaceInput } from '@/ui/components/inplace-input/hooks/useCloseInplaceInput';
import { isCreateModeScopedState } from '@/ui/components/inplace-input/states/isCreateModeScopedState';
import { getLogoUrlFromDomainName } from '@/utils/utils';
import {
CommentableType,
@ -25,7 +25,7 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
);
const [updatePeople] = useUpdatePeopleMutation();
const { closeEditableCell } = useEditableCell();
const { closeInplaceInput } = useInplaceInput();
const companies = useFilteredSearchEntityQuery({
queryHook: useSearchCompanyQuery,
@ -50,7 +50,7 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
},
});
closeEditableCell();
closeInplaceInput();
}
function handleCreate() {

View File

@ -4,7 +4,7 @@ import { IconCurrencyDollar } from '@tabler/icons-react';
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
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 { RowContext } from '@/ui/tables/states/RowContext';
@ -113,7 +113,7 @@ export function CompanyBoardCard({
<span>
<IconCurrencyDollar size={theme.icon.size.md} />
<HackScope>
<EditableText
<InplaceTextInput
content={pipelineProgress.amount?.toString() || ''}
placeholder="Opportunity amount"
changeHandler={(value) =>

View File

@ -1,26 +1,9 @@
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 { 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 = {
editModeContent: ReactElement;
@ -35,43 +18,16 @@ export function EditableCell({
editModeContent,
nonEditModeContent,
}: OwnProps) {
const [isEditMode] = useRecoilScopedState(isEditModeScopedState);
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();
return (
<CellBaseContainer onClick={handleOnClick}>
{isEditMode ? (
<EditableCellEditMode
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
onOutsideClick={handleOnOutsideClick}
>
{editModeContent}
</EditableCellEditMode>
) : hasSoftFocus ? (
<EditableCellSoftFocusMode>
{nonEditModeContent}
</EditableCellSoftFocusMode>
) : (
<EditableCellDisplayMode>{nonEditModeContent}</EditableCellDisplayMode>
)}
</CellBaseContainer>
<InplaceInput
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
editModeContent={editModeContent}
nonEditModeContent={nonEditModeContent}
setSoftFocusOnCurrentInplaceInput={setSoftFocusOnCurrentCell}
hasSoftFocus={!!hasSoftFocus}
/>
);
}

View File

@ -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};
}
`;

View File

@ -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}
/>
);
}

View File

@ -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>
);
}

View File

@ -1,12 +1,10 @@
import styled from '@emotion/styled';
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
type Props = {
softFocus: boolean;
};
export const EditableCellNormalModeOuterContainer = styled.div<Props>`
export const InplaceInputNormalModeOuterContainer = styled.div<Props>`
align-items: center;
display: flex;
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;
display: flex;
height: 100%;
@ -32,16 +30,17 @@ export const EditableCellNormalModeInnerContainer = styled.div`
width: 100%;
`;
export function EditableCellDisplayMode({
export function InplaceInputDisplayMode({
children,
}: React.PropsWithChildren<unknown>) {
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
hasSoftFocus,
}: React.PropsWithChildren & {
hasSoftFocus: boolean;
}) {
return (
<EditableCellNormalModeOuterContainer softFocus={hasSoftFocus}>
<EditableCellNormalModeInnerContainer>
<InplaceInputNormalModeOuterContainer softFocus={hasSoftFocus}>
<InplaceInputNormalModeInnerContainer>
{children}
</EditableCellNormalModeInnerContainer>
</EditableCellNormalModeOuterContainer>
</InplaceInputNormalModeInnerContainer>
</InplaceInputNormalModeOuterContainer>
);
}

View File

@ -6,9 +6,9 @@ import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsid
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
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;
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
@ -34,7 +34,7 @@ type OwnProps = {
onOutsideClick?: () => void;
};
export function EditableCellEditMode({
export function InplaceInputEditMode({
editModeHorizontalAlign,
editModeVerticalPosition,
children,
@ -42,7 +42,7 @@ export function EditableCellEditMode({
}: OwnProps) {
const wrapperRef = useRef(null);
const { closeEditableCell } = useEditableCell();
const { closeInplaceInput } = useInplaceInput();
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
useListenClickOutsideArrayOfRef([wrapperRef], () => {
@ -52,7 +52,7 @@ export function EditableCellEditMode({
useHotkeys(
'enter',
() => {
closeEditableCell();
closeInplaceInput();
moveDown();
},
{
@ -60,26 +60,26 @@ export function EditableCellEditMode({
enableOnFormTags: true,
preventDefault: true,
},
[closeEditableCell],
[closeInplaceInput],
);
useHotkeys(
'esc',
() => {
closeEditableCell();
closeInplaceInput();
},
{
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
[closeEditableCell],
[closeInplaceInput],
);
useHotkeys(
'tab',
() => {
closeEditableCell();
closeInplaceInput();
moveRight();
},
{
@ -87,13 +87,13 @@ export function EditableCellEditMode({
enableOnFormTags: true,
preventDefault: true,
},
[closeEditableCell, moveRight],
[closeInplaceInput, moveRight],
);
useHotkeys(
'shift+tab',
() => {
closeEditableCell();
closeInplaceInput();
moveLeft();
},
{
@ -101,17 +101,17 @@ export function EditableCellEditMode({
enableOnFormTags: true,
preventDefault: true,
},
[closeEditableCell, moveRight],
[closeInplaceInput, moveRight],
);
return (
<EditableCellEditModeContainer
data-testid="editable-cell-edit-mode-container"
<InplaceInputEditModeContainer
data-testid="inplace-input-edit-mode-container"
ref={wrapperRef}
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
>
{children}
</EditableCellEditModeContainer>
</InplaceInputEditModeContainer>
);
}

View File

@ -5,13 +5,13 @@ import { useRecoilState } from 'recoil';
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
import { isNonTextWritingKey } from '@/utils/hotkeys/isNonTextWritingKey';
import { useEditableCell } from './hooks/useCloseEditableCell';
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
import { useInplaceInput } from './hooks/useCloseInplaceInput';
import { InplaceInputDisplayMode } from './InplaceInputDisplayMode';
export function EditableCellSoftFocusMode({
export function InplaceInputSoftFocusMode({
children,
}: React.PropsWithChildren<unknown>) {
const { closeEditableCell, openEditableCell } = useEditableCell();
const { closeInplaceInput, openInplaceInput } = useInplaceInput();
const [captureHotkeyTypeInFocus] = useRecoilState(
captureHotkeyTypeInFocusState,
);
@ -19,14 +19,14 @@ export function EditableCellSoftFocusMode({
useHotkeys(
'enter',
() => {
openEditableCell();
openInplaceInput();
},
{
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
[closeEditableCell],
[closeInplaceInput],
);
useHotkeys(
@ -44,7 +44,7 @@ export function EditableCellSoftFocusMode({
if (captureHotkeyTypeInFocus) {
return;
}
openEditableCell();
openInplaceInput();
},
{
enableOnContentEditable: true,
@ -53,5 +53,7 @@ export function EditableCellSoftFocusMode({
},
);
return <EditableCellDisplayMode>{children}</EditableCellDisplayMode>;
return (
<InplaceInputDisplayMode hasSoftFocus>{children}</InplaceInputDisplayMode>
);
}

View File

@ -1,14 +1,15 @@
import { useRecoilCallback } from 'recoil';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
// TODO: Remove dependancy to table
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
import { isEditModeScopedState } from '../states/isEditModeScopedState';
export function useEditableCell() {
export function useInplaceInput() {
const [, setIsEditMode] = useRecoilScopedState(isEditModeScopedState);
const closeEditableCell = useRecoilCallback(
const closeInplaceInput = useRecoilCallback(
({ set }) =>
async () => {
setIsEditMode(false);
@ -20,7 +21,7 @@ export function useEditableCell() {
[setIsEditMode],
);
const openEditableCell = useRecoilCallback(
const openInplaceInput = useRecoilCallback(
({ snapshot, set }) =>
() => {
const isSomeInputInEditMode = snapshot
@ -37,7 +38,7 @@ export function useEditableCell() {
);
return {
closeEditableCell,
openEditableCell,
closeInplaceInput,
openInplaceInput,
};
}

View File

@ -3,13 +3,15 @@ import styled from '@emotion/styled';
import { textInputStyle } from '@/ui/themes/effects';
import { EditableCell } from '../EditableCell';
import { InplaceInput } from '../InplaceInput';
type OwnProps = {
placeholder?: string;
content: string;
changeHandler: (updated: string) => void;
editModeHorizontalAlign?: 'left' | 'right';
setSoftFocusOnCurrentInplaceInput?: () => void;
hasSoftFocus?: boolean;
};
// TODO: refactor
@ -26,17 +28,19 @@ const StyledNoEditText = styled.div`
width: 100%;
`;
export function EditableText({
export function InplaceTextInput({
content,
placeholder,
changeHandler,
editModeHorizontalAlign,
setSoftFocusOnCurrentInplaceInput,
hasSoftFocus,
}: OwnProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [inputValue, setInputValue] = useState(content);
return (
<EditableCell
<InplaceInput
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<StyledInplaceInput
@ -50,7 +54,9 @@ export function EditableText({
}}
/>
}
setSoftFocusOnCurrentInplaceInput={setSoftFocusOnCurrentInplaceInput}
hasSoftFocus={hasSoftFocus}
nonEditModeContent={<StyledNoEditText>{inputValue}</StyledNoEditText>}
></EditableCell>
></InplaceInput>
);
}

View File

@ -4,7 +4,7 @@ import { createColumnHelper } from '@tanstack/react-table';
import { CompanyAccountOwnerCell } from '@/companies/components/CompanyAccountOwnerCell';
import { CompanyEditableNameChipCell } from '@/companies/components/CompanyEditableNameCell';
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 {
IconBuildingSkyscraper,
@ -44,7 +44,7 @@ export const useCompaniesColumns = () => {
<ColumnHead viewName="URL" viewIcon={<IconLink size={16} />} />
),
cell: (props) => (
<EditableText
<EditableTextCell
content={props.row.original.domainName || ''}
placeholder="Domain name"
changeHandler={(value) => {
@ -66,7 +66,7 @@ export const useCompaniesColumns = () => {
<ColumnHead viewName="Employees" viewIcon={<IconUsers size={16} />} />
),
cell: (props) => (
<EditableText
<EditableTextCell
content={props.row.original.employees?.toString() || ''}
placeholder="Employees"
changeHandler={(value) => {
@ -89,7 +89,7 @@ export const useCompaniesColumns = () => {
<ColumnHead viewName="Address" viewIcon={<IconMap size={16} />} />
),
cell: (props) => (
<EditableText
<EditableTextCell
content={props.row.original.address || ''}
placeholder="Address"
changeHandler={(value) => {

View File

@ -39,13 +39,13 @@ export const InteractWithManyRows: Story = {
);
expect(
canvas.queryByTestId('editable-cell-edit-mode-container'),
canvas.queryByTestId('inplace-input-edit-mode-container'),
).toBeNull();
await userEvent.click(firstRowEmailCell);
expect(
canvas.queryByTestId('editable-cell-edit-mode-container'),
canvas.queryByTestId('inplace-input-edit-mode-container'),
).toBeInTheDocument();
await userEvent.click(secondRowEmailCell);
@ -57,7 +57,7 @@ export const InteractWithManyRows: Story = {
);
expect(
canvas.queryByTestId('editable-cell-edit-mode-container'),
canvas.queryByTestId('inplace-input-edit-mode-container'),
).toBeNull();
await userEvent.click(secondRowEmailCellFocused);
@ -65,7 +65,7 @@ export const InteractWithManyRows: Story = {
await sleep(25);
expect(
canvas.queryByTestId('editable-cell-edit-mode-container'),
canvas.queryByTestId('inplace-input-edit-mode-container'),
).toBeInTheDocument();
},
parameters: {

View File

@ -5,7 +5,7 @@ import { EditablePeopleFullName } from '@/people/components/EditablePeopleFullNa
import { PeopleCompanyCell } from '@/people/components/PeopleCompanyCell';
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
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 {
IconBuildingSkyscraper,
@ -55,7 +55,7 @@ export const usePeopleColumns = () => {
<ColumnHead viewName="Email" viewIcon={<IconMail size={16} />} />
),
cell: (props) => (
<EditableText
<EditableTextCell
placeholder="Email"
content={props.row.original.email || ''}
changeHandler={async (value: string) => {
@ -137,7 +137,7 @@ export const usePeopleColumns = () => {
<ColumnHead viewName="City" viewIcon={<IconMap size={16} />} />
),
cell: (props) => (
<EditableText
<EditableTextCell
editModeHorizontalAlign="right"
placeholder="City"
content={props.row.original.city || ''}