Uniformize folder structure (#693)
* Uniformize folder structure * Fix icons * Fix icons * Fix tests * Fix tests
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
export function CellSkeleton() {
|
||||
return (
|
||||
<div style={{ width: '100%', alignItems: 'center' }}>
|
||||
<Skeleton />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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};
|
||||
}
|
||||
`;
|
||||
@ -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);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user