Uniformize folder structure (#693)

* Uniformize folder structure

* Fix icons

* Fix icons

* Fix tests

* Fix tests
This commit is contained in:
Charles Bochet
2023-07-16 14:29:28 -07:00
committed by GitHub
parent 900ec5572f
commit 6ced8434bd
462 changed files with 931 additions and 960 deletions

View File

@ -0,0 +1,9 @@
import Skeleton from 'react-loading-skeleton';
export function CellSkeleton() {
return (
<div style={{ width: '100%', alignItems: 'center' }}>
<Skeleton />
</div>
);
}

View File

@ -0,0 +1,67 @@
import { ReactElement } from 'react';
import styled from '@emotion/styled';
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode';
import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell';
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;
nonEditModeContent: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
editHotkeyScope?: HotkeyScope;
onSubmit?: () => void;
onCancel?: () => void;
};
export function EditableCell({
editModeHorizontalAlign = 'left',
editModeVerticalPosition = 'over',
editModeContent,
nonEditModeContent,
editHotkeyScope,
onSubmit,
onCancel,
}: OwnProps) {
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
return (
<CellBaseContainer>
{isCurrentCellInEditMode ? (
<EditableCellEditMode
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
onSubmit={onSubmit}
onCancel={onCancel}
>
{editModeContent}
</EditableCellEditMode>
) : hasSoftFocus ? (
<EditableCellSoftFocusMode editHotkeyScope={editHotkeyScope}>
{nonEditModeContent}
</EditableCellSoftFocusMode>
) : (
<EditableCellDisplayMode>{nonEditModeContent}</EditableCellDisplayMode>
)}
</CellBaseContainer>
);
}

View File

@ -0,0 +1,51 @@
import styled from '@emotion/styled';
import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell';
type Props = {
softFocus?: boolean;
};
export const EditableCellNormalModeOuterContainer = styled.div<Props>`
align-items: center;
display: flex;
height: 100%;
overflow: hidden;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(1)};
width: 100%;
${(props) =>
props.softFocus
? `background: ${props.theme.background.transparent.secondary};
border-radius: ${props.theme.border.radius.md};
box-shadow: inset 0 0 0 1px ${props.theme.font.color.extraLight};`
: ''}
`;
export const EditableCellNormalModeInnerContainer = styled.div`
align-items: center;
display: flex;
height: 100%;
overflow: hidden;
width: 100%;
`;
export function EditableCellDisplayMode({
children,
}: React.PropsWithChildren<unknown>) {
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
function handleOnClick() {
setSoftFocusOnCurrentCell();
}
return (
<EditableCellNormalModeOuterContainer onClick={handleOnClick}>
<EditableCellNormalModeInnerContainer>
{children}
</EditableCellNormalModeInnerContainer>
</EditableCellNormalModeOuterContainer>
);
}

View File

@ -0,0 +1,59 @@
import { ReactElement, useRef } 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: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
left: ${(props) =>
props.editModeHorizontalAlign === 'right' ? 'auto' : '0'};
margin-left: -1px;
margin-top: -1px;
min-height: 100%;
position: absolute;
right: ${(props) =>
props.editModeHorizontalAlign === 'right' ? '0' : 'auto'};
top: ${(props) => (props.editModeVerticalPosition === 'over' ? '0' : '100%')};
width: 100%;
z-index: 1;
${overlayBackground}
`;
type OwnProps = {
children: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
onOutsideClick?: () => void;
onCancel?: () => void;
onSubmit?: () => void;
};
export function EditableCellEditMode({
editModeHorizontalAlign,
editModeVerticalPosition,
children,
onCancel,
onSubmit,
}: OwnProps) {
const wrapperRef = useRef(null);
useRegisterCloseCellHandlers(wrapperRef, onSubmit, onCancel);
return (
<EditableCellEditModeContainer
data-testid="editable-cell-edit-mode-container"
ref={wrapperRef}
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
>
{children}
</EditableCellEditModeContainer>
);
}

View File

@ -0,0 +1,73 @@
import React from 'react';
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { isNonTextWritingKey } from '@/ui/hotkey/utils/isNonTextWritingKey';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useEditableCell } from '../hooks/useEditableCell';
import {
EditableCellNormalModeInnerContainer,
EditableCellNormalModeOuterContainer,
} from './EditableCellDisplayMode';
export function EditableCellSoftFocusMode({
children,
editHotkeyScope,
}: React.PropsWithChildren<{ editHotkeyScope?: HotkeyScope }>) {
const { openEditableCell } = useEditableCell();
function openEditMode() {
openEditableCell(
editHotkeyScope ?? {
scope: TableHotkeyScope.CellEditMode,
},
);
}
useScopedHotkeys(
'enter',
() => {
openEditMode();
},
TableHotkeyScope.TableSoftFocus,
[openEditMode],
);
useScopedHotkeys(
'*',
(keyboardEvent) => {
const isWritingText =
!isNonTextWritingKey(keyboardEvent.key) &&
!keyboardEvent.ctrlKey &&
!keyboardEvent.metaKey;
if (!isWritingText) {
return;
}
openEditMode();
},
TableHotkeyScope.TableSoftFocus,
[openEditMode],
{
preventDefault: false,
},
);
function handleClick() {
openEditMode();
}
return (
<EditableCellNormalModeOuterContainer
onClick={handleClick}
softFocus={true}
>
<EditableCellNormalModeInnerContainer>
{children}
</EditableCellNormalModeInnerContainer>
</EditableCellNormalModeOuterContainer>
);
}

View File

@ -0,0 +1,19 @@
import styled from '@emotion/styled';
export const HoverableMenuItem = styled.div`
align-items: center;
background: ${({ theme }) => theme.background.primary};
border-radius: ${({ theme }) => theme.border.radius.sm};
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,46 @@
import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import {
CellPositionDecorator,
ComponentDecorator,
} from '~/testing/decorators';
import { EditableCellText } from '../../types/EditableCellText';
const meta: Meta<typeof EditableCellText> = {
title: 'UI/EditableCell/EditableCellText',
component: EditableCellText,
decorators: [ComponentDecorator, CellPositionDecorator],
args: {
value: 'Content',
},
};
export default meta;
type Story = StoryObj<typeof EditableCellText>;
export const DisplayMode: Story = {
render: EditableCellText,
};
export const SoftFocusMode: Story = {
...DisplayMode,
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
await step('Click once', () =>
userEvent.click(canvas.getByText('Content')),
);
},
};
export const EditMode: Story = {
...DisplayMode,
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
const click = async () => userEvent.click(canvas.getByText('Content'));
await step('Click once', click);
await step('Click twice', click);
},
};

View File

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

View File

@ -0,0 +1,31 @@
import { 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 { CellPosition } from '../../types/CellPosition';
export function useCurrentCellPosition() {
const [currentRowNumber] = useRecoilScopedState(
currentRowNumberScopedState,
RowContext,
);
const [currentColumnNumber] = useRecoilScopedState(
currentColumnNumberScopedState,
CellContext,
);
const currentCellPosition: CellPosition = useMemo(
() => ({
column: currentColumnNumber,
row: currentRowNumber,
}),
[currentColumnNumber, currentRowNumber],
);
return currentCellPosition;
}

View File

@ -0,0 +1,46 @@
import { useRecoilCallback } from 'recoil';
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { useCloseCurrentCellInEditMode } from '../../hooks/useClearCellInEditMode';
import { isSomeInputInEditModeState } from '../../states/isSomeInputInEditModeState';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentCellEditMode } from './useCurrentCellEditMode';
export function useEditableCell() {
const { setCurrentCellInEditMode } = useCurrentCellEditMode();
const setHotkeyScope = useSetHotkeyScope();
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
function closeEditableCell() {
closeCurrentCellInEditMode();
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
}
const openEditableCell = useRecoilCallback(
({ snapshot, set }) =>
(HotkeyScope: HotkeyScope) => {
const isSomeInputInEditMode = snapshot
.getLoadable(isSomeInputInEditModeState)
.valueOrThrow();
if (!isSomeInputInEditMode) {
set(isSomeInputInEditModeState, true);
setCurrentCellInEditMode();
setHotkeyScope(HotkeyScope.scope);
}
},
[setCurrentCellInEditMode, setHotkeyScope],
);
return {
closeEditableCell,
openEditableCell,
};
}

View File

@ -0,0 +1,15 @@
import { useRecoilValue } from 'recoil';
import { isSoftFocusOnCellFamilyState } from '../../states/isSoftFocusOnCellFamilyState';
import { useCurrentCellPosition } from './useCurrentCellPosition';
export function useIsSoftFocusOnCurrentCell() {
const currentCellPosition = useCurrentCellPosition();
const isSoftFocusOnCell = useRecoilValue(
isSoftFocusOnCellFamilyState(currentCellPosition),
);
return isSoftFocusOnCell;
}

View File

@ -0,0 +1,67 @@
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentCellEditMode } from './useCurrentCellEditMode';
import { useEditableCell } from './useEditableCell';
export function useRegisterCloseCellHandlers(
wrapperRef: React.RefObject<HTMLDivElement>,
onSubmit?: () => void,
onCancel?: () => void,
) {
const { closeEditableCell } = useEditableCell();
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
useListenClickOutsideArrayOfRef([wrapperRef], () => {
if (isCurrentCellInEditMode) {
onSubmit?.();
closeEditableCell();
}
});
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
useScopedHotkeys(
'enter',
() => {
onSubmit?.();
closeEditableCell();
moveDown();
},
TableHotkeyScope.CellEditMode,
[closeEditableCell, onSubmit, moveDown],
);
useScopedHotkeys(
'esc',
() => {
closeEditableCell();
onCancel?.();
},
TableHotkeyScope.CellEditMode,
[closeEditableCell, onCancel],
);
useScopedHotkeys(
'tab',
() => {
onSubmit?.();
closeEditableCell();
moveRight();
},
TableHotkeyScope.CellEditMode,
[closeEditableCell, onSubmit, moveRight],
);
useScopedHotkeys(
'shift+tab',
() => {
onSubmit?.();
closeEditableCell();
moveLeft();
},
TableHotkeyScope.CellEditMode,
[closeEditableCell, onSubmit, moveRight],
);
}

View File

@ -0,0 +1,50 @@
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';
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 setHotkeyScope = useSetHotkeyScope();
return useRecoilCallback(
({ set }) =>
() => {
setSoftFocusPosition(currentTablePosition);
set(isSoftFocusActiveState, true);
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
},
[setHotkeyScope, currentTablePosition, setSoftFocusPosition],
);
}

View File

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

View File

@ -0,0 +1,29 @@
import { InplaceInputDateDisplayMode } from '@/ui/display/component/InplaceInputDateDisplayMode';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { EditableCell } from '../components/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} />}
editHotkeyScope={{ scope: TableHotkeyScope.CellDateEditMode }}
></EditableCell>
);
}

View File

@ -0,0 +1,45 @@
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 { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useEditableCell } from '../hooks/useEditableCell';
const EditableCellDateEditModeContainer = styled.div`
margin-top: -1px;
width: inherit;
`;
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();
}
useScopedHotkeys(
Key.Escape,
() => {
closeEditableCell();
},
TableHotkeyScope.CellDateEditMode,
[closeEditableCell],
);
return (
<EditableCellDateEditModeContainer>
<InplaceInputDate onChange={handleDateChange} value={value} />
</EditableCellDateEditModeContainer>
);
}

View File

@ -0,0 +1,63 @@
import { ReactElement, useEffect, useState } from 'react';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { CellSkeleton } from '../components/CellSkeleton';
import { EditableCell } from '../components/EditableCell';
import { EditableCellDoubleTextEditMode } from './EditableCellDoubleTextEditMode';
type OwnProps = {
firstValue: string;
secondValue: string;
firstValuePlaceholder: string;
secondValuePlaceholder: string;
nonEditModeContent: ReactElement;
onChange: (firstValue: string, secondValue: string) => void;
onSubmit?: () => void;
onCancel?: () => void;
loading?: boolean;
};
export function EditableCellDoubleText({
firstValue,
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}
firstValuePlaceholder={firstValuePlaceholder}
secondValuePlaceholder={secondValuePlaceholder}
onChange={handleOnChange}
onSubmit={onSubmit}
onCancel={onCancel}
/>
}
nonEditModeContent={loading ? <CellSkeleton /> : nonEditModeContent}
></EditableCell>
);
}

View File

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

View File

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

View File

@ -0,0 +1,19 @@
import styled from '@emotion/styled';
export const EditableCellRelationCreateButton = styled.button`
align-items: center;
background: none;
border: none;
border-radius: ${({ theme }) => theme.border.radius.sm};
cursor: pointer;
display: flex;
font-family: 'Inter';
font-size: ${({ theme }) => theme.font.size.md};
gap: ${({ theme }) => theme.spacing(2)};
height: 31px;
padding-bottom: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(1)};
padding-top: ${({ theme }) => theme.spacing(1)};
user-select: none;
width: 100%;
`;

View File

@ -0,0 +1,61 @@
import { ChangeEvent, useEffect, useState } from 'react';
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
import { CellSkeleton } from '../components/CellSkeleton';
import { EditableCell } from '../components/EditableCell';
type OwnProps = {
placeholder?: string;
value: string;
onChange: (newValue: string) => void;
editModeHorizontalAlign?: 'left' | 'right';
loading?: boolean;
onSubmit?: () => void;
onCancel?: () => void;
};
export function EditableCellText({
value,
placeholder,
onChange,
editModeHorizontalAlign,
loading,
onCancel,
onSubmit,
}: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
setInternalValue(value);
}, [value]);
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<InplaceInputTextEditMode
placeholder={placeholder || ''}
autoFocus
value={internalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setInternalValue(event.target.value);
onChange(event.target.value);
}}
/>
}
onSubmit={onSubmit}
onCancel={onCancel}
nonEditModeContent={
loading ? (
<CellSkeleton />
) : (
<InplaceInputTextDisplayMode>
{internalValue}
</InplaceInputTextDisplayMode>
)
}
></EditableCell>
);
}

View File

@ -0,0 +1,112 @@
import {
ChangeEvent,
ComponentType,
ReactNode,
useEffect,
useRef,
useState,
} from 'react';
import styled from '@emotion/styled';
import { textInputStyle } from '@/ui/themes/effects';
import { EditableCell } from '../components/EditableCell';
export type EditableChipProps = {
id: string;
placeholder?: string;
value: string;
picture: string;
changeHandler: (updated: string) => void;
editModeHorizontalAlign?: 'left' | 'right';
ChipComponent: ComponentType<{
id: string;
name: string;
picture: string;
isOverlapped?: boolean;
}>;
commentThreadCount?: number;
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
rightEndContents?: ReactNode[];
onSubmit?: () => void;
onCancel?: () => void;
};
// TODO: refactor
const StyledInplaceInput = styled.input`
width: 100%;
${textInputStyle}
`;
const NoEditModeContainer = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
width: 100%;
`;
const RightContainer = styled.div`
margin-left: ${(props) => props.theme.spacing(1)};
`;
// TODO: move right end content in EditableCell
export function EditableCellChip({
id,
value,
placeholder,
changeHandler,
picture,
editModeHorizontalAlign,
ChipComponent,
rightEndContents,
onSubmit,
onCancel,
}: EditableChipProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [inputValue, setInputValue] = useState(value);
useEffect(() => {
setInputValue(value);
}, [value]);
const handleRightEndContentClick = (
event: React.MouseEvent<HTMLDivElement>,
) => {
event.stopPropagation();
};
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);
}}
/>
}
onSubmit={onSubmit}
onCancel={onCancel}
nonEditModeContent={
<NoEditModeContainer>
<ChipComponent id={id} name={inputValue} picture={picture} />
<RightContainer>
{rightEndContents &&
rightEndContents.length > 0 &&
rightEndContents.map((content, index) => (
<div key={index} onClick={handleRightEndContentClick}>
{content}
</div>
))}
</RightContainer>
</NoEditModeContainer>
}
/>
);
}