Refactor/inplace input (#541)
* wip * Changed all other components * Removed console log * Console.log * lint * Removed internal state * Fix * Lint
This commit is contained in:
@ -1,55 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { DropResult } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
|
||||
|
||||
export const StyledBoard = styled.div`
|
||||
border-radius: ${({ theme }) => theme.spacing(2)};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: calc(100%);
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export type Column = {
|
||||
id: string;
|
||||
title: string;
|
||||
colorCode?: string;
|
||||
itemKeys: string[];
|
||||
};
|
||||
|
||||
export function getOptimisticlyUpdatedBoard(
|
||||
board: Column[],
|
||||
result: DropResult,
|
||||
) {
|
||||
const newBoard = JSON.parse(JSON.stringify(board));
|
||||
const { destination, source } = result;
|
||||
if (!destination) return;
|
||||
const sourceColumnIndex = newBoard.findIndex(
|
||||
(column: Column) => column.id === source.droppableId,
|
||||
);
|
||||
const sourceColumn = newBoard[sourceColumnIndex];
|
||||
const destinationColumnIndex = newBoard.findIndex(
|
||||
(column: Column) => column.id === destination.droppableId,
|
||||
);
|
||||
const destinationColumn = newBoard[destinationColumnIndex];
|
||||
if (!destinationColumn || !sourceColumn) return;
|
||||
const sourceItems = sourceColumn.itemKeys;
|
||||
const destinationItems = destinationColumn.itemKeys;
|
||||
|
||||
const [removed] = sourceItems.splice(source.index, 1);
|
||||
destinationItems.splice(destination.index, 0, removed);
|
||||
|
||||
const newSourceColumn = {
|
||||
...sourceColumn,
|
||||
itemKeys: sourceItems,
|
||||
};
|
||||
|
||||
const newDestinationColumn = {
|
||||
...destinationColumn,
|
||||
itemKeys: destinationItems,
|
||||
};
|
||||
|
||||
newBoard.splice(sourceColumnIndex, 1, newSourceColumn);
|
||||
newBoard.splice(destinationColumnIndex, 1, newDestinationColumn);
|
||||
return newBoard;
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const StyledColumn = styled.div`
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 200px;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const StyledColumnTitle = styled.h3`
|
||||
color: ${({ color }) => color};
|
||||
font-family: 'Inter';
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
font-style: normal;
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
line-height: ${({ theme }) => theme.text.lineHeight};
|
||||
margin: 0;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
colorCode?: string;
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export function BoardColumn({ colorCode, title, children }: OwnProps) {
|
||||
return (
|
||||
<StyledColumn>
|
||||
<StyledColumnTitle color={colorCode}>• {title}</StyledColumnTitle>
|
||||
{children}
|
||||
</StyledColumn>
|
||||
);
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconPlus } from '@/ui/icons/index';
|
||||
|
||||
const StyledButton = styled.button`
|
||||
align-items: center;
|
||||
align-self: baseline;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border: none;
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
justify-content: center;
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.background.secondary};
|
||||
}
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export function NewButton({ onClick }: OwnProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledButton onClick={onClick}>
|
||||
<IconPlus size={theme.icon.size.md} />
|
||||
New
|
||||
</StyledButton>
|
||||
);
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
import { DropResult } from '@hello-pangea/dnd';
|
||||
|
||||
import { BoardItemKey, getOptimisticlyUpdatedBoard } from '../Board';
|
||||
|
||||
describe('getOptimisticlyUpdatedBoard', () => {
|
||||
it('should return a new board with the updated cell', () => {
|
||||
const initialColumn1: BoardItemKey[] = ['item-1', 'item-2', 'item-3'];
|
||||
const initialColumn2: BoardItemKey[] = ['item-4', 'item-5'];
|
||||
|
||||
const finalColumn1: BoardItemKey[] = ['item-2', 'item-3'];
|
||||
const finalColumn2: BoardItemKey[] = ['item-4', 'item-1', 'item-5'];
|
||||
|
||||
const dropResult = {
|
||||
source: {
|
||||
droppableId: 'column-1',
|
||||
index: 0,
|
||||
},
|
||||
destination: {
|
||||
droppableId: 'column-2',
|
||||
index: 1,
|
||||
},
|
||||
} as DropResult;
|
||||
|
||||
const initialBoard = [
|
||||
{
|
||||
id: 'column-1',
|
||||
title: 'My Column',
|
||||
itemKeys: initialColumn1,
|
||||
},
|
||||
{
|
||||
id: 'column-2',
|
||||
title: 'My Column',
|
||||
itemKeys: initialColumn2,
|
||||
},
|
||||
];
|
||||
|
||||
const updatedBoard = getOptimisticlyUpdatedBoard(initialBoard, dropResult);
|
||||
|
||||
const finalBoard = [
|
||||
{
|
||||
id: 'column-1',
|
||||
title: 'My Column',
|
||||
itemKeys: finalColumn1,
|
||||
},
|
||||
{
|
||||
id: 'column-2',
|
||||
title: 'My Column',
|
||||
itemKeys: finalColumn2,
|
||||
},
|
||||
];
|
||||
|
||||
expect(updatedBoard).toEqual(finalBoard);
|
||||
expect(updatedBoard).not.toBe(initialBoard);
|
||||
});
|
||||
});
|
||||
@ -1,14 +1,13 @@
|
||||
import { ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
|
||||
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
|
||||
import { useEditableCell } from './hooks/useCloseEditableCell';
|
||||
import { useCurrentCellEditMode } from './hooks/useCurrentCellEditMode';
|
||||
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
|
||||
import { useSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
|
||||
import { useSetSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
|
||||
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
||||
import { EditableCellEditMode } from './EditableCellEditMode';
|
||||
import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode';
|
||||
@ -41,14 +40,12 @@ export function EditableCell({
|
||||
}: OwnProps) {
|
||||
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
||||
|
||||
const setSoftFocusOnCurrentCell = useSoftFocusOnCurrentCell();
|
||||
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
|
||||
|
||||
const { openEditableCell } = useEditableCell();
|
||||
|
||||
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||
|
||||
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
|
||||
|
||||
// TODO: we might have silent problematic behavior because of the setTimeout in openEditableCell, investigate
|
||||
// Maybe we could build a switchEditableCell to handle the case where we go from one cell to another.
|
||||
// See https://github.com/twentyhq/twenty/issues/446
|
||||
@ -58,8 +55,7 @@ export function EditableCell({
|
||||
}
|
||||
|
||||
if (hasSoftFocus) {
|
||||
openEditableCell();
|
||||
addToHotkeysScopeStack(
|
||||
openEditableCell(
|
||||
editHotkeysScope ?? {
|
||||
scope: InternalHotkeysScope.CellEditMode,
|
||||
},
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
|
||||
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
|
||||
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
@ -14,20 +13,18 @@ export function EditableCellSoftFocusMode({
|
||||
editHotkeysScope,
|
||||
}: React.PropsWithChildren<{ editHotkeysScope?: HotkeysScopeStackItem }>) {
|
||||
const { closeEditableCell, openEditableCell } = useEditableCell();
|
||||
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
|
||||
|
||||
useScopedHotkeys(
|
||||
'enter',
|
||||
() => {
|
||||
openEditableCell();
|
||||
addToHotkeysScopeStack(
|
||||
openEditableCell(
|
||||
editHotkeysScope ?? {
|
||||
scope: InternalHotkeysScope.CellEditMode,
|
||||
},
|
||||
);
|
||||
},
|
||||
InternalHotkeysScope.TableSoftFocus,
|
||||
[closeEditableCell],
|
||||
[closeEditableCell, editHotkeysScope],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
@ -42,15 +39,14 @@ export function EditableCellSoftFocusMode({
|
||||
return;
|
||||
}
|
||||
|
||||
openEditableCell();
|
||||
addToHotkeysScopeStack(
|
||||
openEditableCell(
|
||||
editHotkeysScope ?? {
|
||||
scope: InternalHotkeysScope.CellEditMode,
|
||||
},
|
||||
);
|
||||
},
|
||||
InternalHotkeysScope.TableSoftFocus,
|
||||
[openEditableCell, addToHotkeysScopeStack, editHotkeysScope],
|
||||
[openEditableCell, editHotkeysScope],
|
||||
{
|
||||
preventDefault: false,
|
||||
},
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
|
||||
import { useRemoveHighestHotkeysScopeStackItem } from '@/hotkeys/hooks/useRemoveHighestHotkeysScopeStackItem';
|
||||
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
|
||||
import { useCloseCurrentCellInEditMode } from '@/ui/tables/hooks/useClearCellInEditMode';
|
||||
import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveState';
|
||||
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
|
||||
@ -10,6 +12,8 @@ import { useCurrentCellEditMode } from './useCurrentCellEditMode';
|
||||
export function useEditableCell() {
|
||||
const { setCurrentCellInEditMode } = useCurrentCellEditMode();
|
||||
|
||||
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
|
||||
|
||||
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
|
||||
|
||||
const removeHighestHotkeysScopedStackItem =
|
||||
@ -22,7 +26,7 @@ export function useEditableCell() {
|
||||
|
||||
const openEditableCell = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
(hotkeysScopeStackItem: HotkeysScopeStackItem) => {
|
||||
const isSomeInputInEditMode = snapshot
|
||||
.getLoadable(isSomeInputInEditModeState)
|
||||
.valueOrThrow();
|
||||
@ -32,9 +36,11 @@ export function useEditableCell() {
|
||||
set(isSoftFocusActiveState, false);
|
||||
|
||||
setCurrentCellInEditMode();
|
||||
|
||||
addToHotkeysScopeStack(hotkeysScopeStackItem);
|
||||
}
|
||||
},
|
||||
[setCurrentCellInEditMode],
|
||||
[setCurrentCellInEditMode, addToHotkeysScopeStack],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -12,7 +12,7 @@ import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveStat
|
||||
import { RowContext } from '@/ui/tables/states/RowContext';
|
||||
import { CellPosition } from '@/ui/tables/types/CellPosition';
|
||||
|
||||
export function useSoftFocusOnCurrentCell() {
|
||||
export function useSetSoftFocusOnCurrentCell() {
|
||||
const setSoftFocusPosition = useSetSoftFocusPosition();
|
||||
const [currentRowNumber] = useRecoilScopedState(
|
||||
currentRowNumberScopedState,
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { InplaceInputDateDisplayMode } from '@/ui/inplace-inputs/components/InplaceInputDateDisplayMode';
|
||||
|
||||
import { EditableCell } from '../EditableCell';
|
||||
|
||||
import { EditableCellDateEditMode } from './EditableCellDateEditMode';
|
||||
|
||||
export type EditableDateProps = {
|
||||
value: Date;
|
||||
onChange: (date: Date) => void;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export function EditableCellDate({
|
||||
value,
|
||||
onChange,
|
||||
editModeHorizontalAlign,
|
||||
}: EditableDateProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<EditableCellDateEditMode onChange={onChange} value={value} />
|
||||
}
|
||||
nonEditModeContent={<InplaceInputDateDisplayMode value={value} />}
|
||||
editHotkeysScope={{ scope: InternalHotkeysScope.CellDateEditMode }}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
import { InplaceInputDateEditMode } from '@/ui/inplace-inputs/components/InplaceInputDateEditMode';
|
||||
|
||||
import { useEditableCell } from '../hooks/useCloseEditableCell';
|
||||
|
||||
export type EditableDateProps = {
|
||||
value: Date;
|
||||
onChange: (date: Date) => void;
|
||||
};
|
||||
|
||||
export function EditableCellDateEditMode({
|
||||
value,
|
||||
onChange,
|
||||
}: EditableDateProps) {
|
||||
const { closeEditableCell } = useEditableCell();
|
||||
|
||||
function handleDateChange(newDate: Date) {
|
||||
onChange(newDate);
|
||||
closeEditableCell();
|
||||
}
|
||||
|
||||
return <InplaceInputDateEditMode onChange={handleDateChange} value={value} />;
|
||||
}
|
||||
@ -4,7 +4,7 @@ import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysSc
|
||||
|
||||
import { EditableCell } from '../EditableCell';
|
||||
|
||||
import { EditableDoubleTextEditMode } from './EditableDoubleTextEditMode';
|
||||
import { EditableCellDoubleTextEditMode } from './EditableCellDoubleTextEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
firstValue: string;
|
||||
@ -15,7 +15,7 @@ type OwnProps = {
|
||||
onChange: (firstValue: string, secondValue: string) => void;
|
||||
};
|
||||
|
||||
export function EditableDoubleText({
|
||||
export function EditableCellDoubleText({
|
||||
firstValue,
|
||||
secondValue,
|
||||
firstValuePlaceholder,
|
||||
@ -27,7 +27,7 @@ export function EditableDoubleText({
|
||||
<EditableCell
|
||||
editHotkeysScope={{ scope: InternalHotkeysScope.CellDoubleTextInput }}
|
||||
editModeContent={
|
||||
<EditableDoubleTextEditMode
|
||||
<EditableCellDoubleTextEditMode
|
||||
firstValue={firstValue}
|
||||
secondValue={secondValue}
|
||||
firstValuePlaceholder={firstValuePlaceholder}
|
||||
@ -4,8 +4,8 @@ import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { InplaceInputTextEditMode } from '@/ui/inplace-inputs/components/InplaceInputTextEditMode';
|
||||
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
|
||||
import { textInputStyle } from '@/ui/themes/effects';
|
||||
|
||||
import { useEditableCell } from '../hooks/useCloseEditableCell';
|
||||
|
||||
@ -28,15 +28,7 @@ const StyledContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledEditInplaceInput = styled.input`
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
width: 45%;
|
||||
|
||||
${textInputStyle}
|
||||
`;
|
||||
|
||||
export function EditableDoubleTextEditMode({
|
||||
export function EditableCellDoubleTextEditMode({
|
||||
firstValue,
|
||||
secondValue,
|
||||
firstValuePlaceholder,
|
||||
@ -77,7 +69,7 @@ export function EditableDoubleTextEditMode({
|
||||
|
||||
useScopedHotkeys(
|
||||
'tab',
|
||||
async (keyboardEvent, hotkeyEvent) => {
|
||||
() => {
|
||||
if (focusPosition === 'left') {
|
||||
setFocusPosition('right');
|
||||
secondValueInputRef.current?.focus();
|
||||
@ -107,7 +99,7 @@ export function EditableDoubleTextEditMode({
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledEditInplaceInput
|
||||
<InplaceInputTextEditMode
|
||||
autoFocus
|
||||
placeholder={firstValuePlaceholder}
|
||||
ref={firstValueInputRef}
|
||||
@ -116,7 +108,7 @@ export function EditableDoubleTextEditMode({
|
||||
onChange(event.target.value, secondValue);
|
||||
}}
|
||||
/>
|
||||
<StyledEditInplaceInput
|
||||
<InplaceInputTextEditMode
|
||||
placeholder={secondValuePlaceholder}
|
||||
ref={secondValueInputRef}
|
||||
value={secondValue}
|
||||
@ -0,0 +1,39 @@
|
||||
import { ChangeEvent, useRef, useState } from 'react';
|
||||
|
||||
import { InplaceInputPhoneDisplayMode } from '@/ui/inplace-inputs/components/InplaceInputPhoneDisplayMode';
|
||||
import { InplaceInputTextEditMode } from '@/ui/inplace-inputs/components/InplaceInputTextEditMode';
|
||||
|
||||
import { EditableCell } from '../EditableCell';
|
||||
|
||||
type OwnProps = {
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
changeHandler: (updated: string) => void;
|
||||
};
|
||||
|
||||
export function EditableCellPhone({
|
||||
value,
|
||||
placeholder,
|
||||
changeHandler,
|
||||
}: OwnProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeContent={
|
||||
<InplaceInputTextEditMode
|
||||
autoFocus
|
||||
placeholder={placeholder || ''}
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(event.target.value);
|
||||
changeHandler(event.target.value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={<InplaceInputPhoneDisplayMode value={inputValue} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const EditableRelationCreateButton = styled.button`
|
||||
export const EditableCellRelationCreateButton = styled.button`
|
||||
align-items: center;
|
||||
background: none;
|
||||
border: none;
|
||||
@ -0,0 +1,39 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
|
||||
import { InplaceInputTextDisplayMode } from '@/ui/inplace-inputs/components/InplaceInputTextDisplayMode';
|
||||
import { InplaceInputTextEditMode } from '@/ui/inplace-inputs/components/InplaceInputTextEditMode';
|
||||
|
||||
import { EditableCell } from '../EditableCell';
|
||||
|
||||
type OwnProps = {
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
onChange: (newValue: string) => void;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export function EditableCellText({
|
||||
value,
|
||||
placeholder,
|
||||
onChange,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<InplaceInputTextEditMode
|
||||
placeholder={placeholder || ''}
|
||||
autoFocus
|
||||
value={value}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(event.target.value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={
|
||||
<InplaceInputTextDisplayMode>{value}</InplaceInputTextDisplayMode>
|
||||
}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -39,7 +39,8 @@ const RightContainer = styled.div`
|
||||
margin-left: ${(props) => props.theme.spacing(1)};
|
||||
`;
|
||||
|
||||
function EditableChip({
|
||||
// TODO: move right end content in EditableCell
|
||||
export function EditableCellChip({
|
||||
value,
|
||||
placeholder,
|
||||
changeHandler,
|
||||
@ -89,5 +90,3 @@ function EditableChip({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditableChip;
|
||||
|
||||
@ -1,81 +0,0 @@
|
||||
import { forwardRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { humanReadableDate } from '@/utils/utils';
|
||||
|
||||
import DatePicker from '../../form/DatePicker';
|
||||
import { EditableCell } from '../EditableCell';
|
||||
|
||||
export type EditableDateProps = {
|
||||
value: Date;
|
||||
changeHandler: (date: Date) => void;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin: 0px ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export type StyledCalendarContainerProps = {
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
const StyledCalendarContainer = styled.div<StyledCalendarContainerProps>`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
border-radius: 8px;
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
left: -10px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
z-index: 1;
|
||||
`;
|
||||
export function EditableDate({
|
||||
value,
|
||||
changeHandler,
|
||||
editModeHorizontalAlign,
|
||||
}: EditableDateProps) {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
|
||||
type DivProps = React.HTMLProps<HTMLDivElement>;
|
||||
|
||||
const DateDisplay = forwardRef<HTMLDivElement, DivProps>(
|
||||
({ value, onClick }, ref) => (
|
||||
<div onClick={onClick} ref={ref}>
|
||||
{value && humanReadableDate(new Date(value as string))}
|
||||
</div>
|
||||
),
|
||||
);
|
||||
|
||||
type DatePickerContainerProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const DatePickerContainer = ({ children }: DatePickerContainerProps) => {
|
||||
return <StyledCalendarContainer>{children}</StyledCalendarContainer>;
|
||||
};
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<StyledContainer>
|
||||
<DatePicker
|
||||
date={inputValue}
|
||||
onChangeHandler={(date: Date) => {
|
||||
changeHandler(date);
|
||||
setInputValue(date);
|
||||
}}
|
||||
customInput={<DateDisplay />}
|
||||
customCalendarContainer={DatePickerContainer}
|
||||
/>
|
||||
</StyledContainer>
|
||||
}
|
||||
nonEditModeContent={
|
||||
<div>{inputValue && humanReadableDate(inputValue)}</div>
|
||||
}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
import { ChangeEvent, MouseEvent, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js';
|
||||
|
||||
import { textInputStyle } from '@/ui/themes/effects';
|
||||
|
||||
import { RawLink } from '../../links/RawLink';
|
||||
import { EditableCell } from '../EditableCell';
|
||||
|
||||
type OwnProps = {
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
changeHandler: (updated: string) => void;
|
||||
};
|
||||
|
||||
const StyledRawLink = styled(RawLink)`
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
// TODO: refactor
|
||||
const StyledEditInplaceInput = styled.input`
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
${textInputStyle}
|
||||
`;
|
||||
|
||||
export function EditablePhone({ value, placeholder, changeHandler }: OwnProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeContent={
|
||||
<StyledEditInplaceInput
|
||||
autoFocus
|
||||
placeholder={placeholder || ''}
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(event.target.value);
|
||||
changeHandler(event.target.value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={
|
||||
<>
|
||||
{isValidPhoneNumber(inputValue) ? (
|
||||
<StyledRawLink
|
||||
href={parsePhoneNumber(inputValue, 'FR')?.getURI()}
|
||||
onClick={(event: MouseEvent<HTMLElement>) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{parsePhoneNumber(inputValue, 'FR')?.formatInternational() ||
|
||||
inputValue}
|
||||
</StyledRawLink>
|
||||
) : (
|
||||
<StyledRawLink href="#">{inputValue}</StyledRawLink>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
import { ChangeEvent, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { textInputStyle } from '@/ui/themes/effects';
|
||||
|
||||
import { EditableCell } from '../EditableCell';
|
||||
|
||||
type OwnProps = {
|
||||
placeholder?: string;
|
||||
content: string;
|
||||
changeHandler: (updated: string) => void;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
// TODO: refactor
|
||||
const StyledInplaceInput = styled.input`
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
${textInputStyle}
|
||||
`;
|
||||
|
||||
const StyledNoEditText = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export function EditableText({
|
||||
content,
|
||||
placeholder,
|
||||
changeHandler,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [inputValue, setInputValue] = useState(content);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<StyledInplaceInput
|
||||
placeholder={placeholder || ''}
|
||||
autoFocus
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(event.target.value);
|
||||
changeHandler(event.target.value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={<StyledNoEditText>{inputValue}</StyledNoEditText>}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user