Create Editable chip to edit cells containing chips (#102)

* Create Editable chip to edit cells containing chips

* Fix icons vertical alignment

* Fix linter

* Fix coverage
This commit is contained in:
Charles Bochet
2023-05-05 12:15:41 +02:00
committed by GitHub
parent 9cd57083f1
commit 55eff2b7a2
9 changed files with 168 additions and 30 deletions

View File

@ -55,7 +55,8 @@
"coveragePathIgnorePatterns": [
".stories.tsx$",
"graphql.tsx$",
"apollo.tsx$"
"apollo.tsx$",
"src/index.tsx$"
],
"coverageThreshold": {
"global": {

View File

@ -0,0 +1,69 @@
import styled from '@emotion/styled';
import { ChangeEvent, ComponentType, useRef, useState } from 'react';
import EditableCellWrapper from './EditableCellWrapper';
export type EditableChipProps = {
placeholder?: string;
value: string;
picture: string;
changeHandler: (updated: string) => void;
shouldAlignRight?: boolean;
ChipComponent: ComponentType<{ name: string; picture: string }>;
};
type StyledEditModeProps = {
isEditMode: boolean;
};
const StyledInplaceInput = styled.input<StyledEditModeProps>`
width: 100%;
border: none;
outline: none;
&::placeholder {
font-weight: ${(props) => (props.isEditMode ? 'bold' : 'normal')};
color: ${(props) =>
props.isEditMode ? props.theme.text20 : 'transparent'};
}
`;
function EditableChip({
value,
placeholder,
changeHandler,
picture,
shouldAlignRight,
ChipComponent,
}: EditableChipProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [inputValue, setInputValue] = useState(value);
const [isEditMode, setIsEditMode] = useState(false);
const onEditModeChange = (isEditMode: boolean) => {
setIsEditMode(isEditMode);
};
return (
<EditableCellWrapper
onEditModeChange={onEditModeChange}
shouldAlignRight={shouldAlignRight}
>
{isEditMode ? (
<StyledInplaceInput
isEditMode={isEditMode}
placeholder={placeholder || ''}
autoFocus
ref={inputRef}
value={inputValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
changeHandler(event.target.value);
}}
/>
) : (
<ChipComponent name={value} picture={picture} />
)}
</EditableCellWrapper>
);
}
export default EditableChip;

View File

@ -29,7 +29,7 @@ const StyledNoEditText = styled.div`
max-width: 200px;
`;
function EditableCell({
function EditableText({
content,
placeholder,
changeHandler,
@ -67,4 +67,4 @@ function EditableCell({
);
}
export default EditableCell;
export default EditableText;

View File

@ -0,0 +1,33 @@
import EditableChip, { EditableChipProps } from '../EditableChip';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes';
import { StoryFn } from '@storybook/react';
import CompanyChip from '../../../chips/CompanyChip';
const component = {
title: 'EditableChip',
component: EditableChip,
};
export default component;
const Template: StoryFn<typeof EditableChip> = (args: EditableChipProps) => {
return (
<ThemeProvider theme={lightTheme}>
<div data-testid="content-editable-parent">
<EditableChip {...args} />
</div>
</ThemeProvider>
);
};
export const EditableChipStory = Template.bind({});
EditableChipStory.args = {
ChipComponent: CompanyChip,
placeholder: 'Test',
value: 'Test',
picture: 'https://picsum.photos/200',
changeHandler: () => {
console.log('changed');
},
};

View File

@ -0,0 +1,34 @@
import { fireEvent, render } from '@testing-library/react';
import { EditableChipStory } from '../__stories__/EditableChip.stories';
import CompanyChip from '../../../chips/CompanyChip';
it('Checks the EditableChip editing event bubbles up', async () => {
const func = jest.fn(() => null);
const { getByTestId } = render(
<EditableChipStory
value="test"
picture="http://"
changeHandler={func}
ChipComponent={CompanyChip}
/>,
);
const parent = getByTestId('content-editable-parent');
const wrapper = parent.querySelector('div');
if (!wrapper) {
throw new Error('Editable input not found');
}
fireEvent.click(wrapper);
const editableInput = parent.querySelector('input');
if (!editableInput) {
throw new Error('Editable input not found');
}
fireEvent.change(editableInput, { target: { value: 'Test' } });
expect(func).toBeCalledWith('Test');
});

View File

@ -23,11 +23,15 @@ const StyledChip = styled.div`
`;
const StyledIcon = styled.div`
margin-right: ${(props) => props.theme.spacing(1)};
display: flex;
align-items: center;
`;
const StyledDelete = styled.div`
margin-left: ${(props) => props.theme.spacing(2)};
cursor: pointer;
display: flex;
align-items: center;
`;
const StyledLabelKey = styled.div`

View File

@ -1,16 +0,0 @@
import styled from '@emotion/styled';
type OwnProps = {
children: JSX.Element[] | JSX.Element;
};
const StyledContainer = styled.div`
display: flex;
align-items: center;
`;
function HorizontalyAlignedContainer({ children }: OwnProps) {
return <StyledContainer>{children}</StyledContainer>;
}
export default HorizontalyAlignedContainer;

View File

@ -15,6 +15,7 @@ type StyledItemProps = {
const StyledItem = styled.button<StyledItemProps>`
display: flex;
align-items: center;
border: none;
font-size: ${(props) => props.theme.fontSizeMedium};
cursor: pointer;

View File

@ -2,7 +2,6 @@ import { createColumnHelper } from '@tanstack/react-table';
import { Company } from '../../interfaces/company.interface';
import { OrderByFields, updateCompany } from '../../services/companies';
import ColumnHead from '../../components/table/ColumnHead';
import HorizontalyAlignedContainer from '../../layout/containers/HorizontalyAlignedContainer';
import Checkbox from '../../components/form/Checkbox';
import CompanyChip from '../../components/chips/CompanyChip';
import EditableText from '../../components/table/editable-cell/EditableText';
@ -19,6 +18,7 @@ import {
import ClickableCell from '../../components/table/ClickableCell';
import PersonChip from '../../components/chips/PersonChip';
import { SortType } from '../../components/table/table-header/interface';
import EditableChip from '../../components/table/editable-cell/EditableChip';
export const sortsAvailable = [
{
@ -35,19 +35,31 @@ export const sortsAvailable = [
const columnHelper = createColumnHelper<Company>();
export const companiesColumns = [
columnHelper.accessor('id', {
header: () => (
<Checkbox id="company-select-all" name="company-select-all" />
),
cell: (props) => (
<Checkbox
id={`company-selected-${props.row.original.id}`}
name={`company-selected-${props.row.original.id}`}
/>
),
}),
columnHelper.accessor('name', {
header: () => <ColumnHead viewName="Name" viewIcon={<FaRegBuilding />} />,
cell: (props) => (
<HorizontalyAlignedContainer>
<Checkbox
id={`company-selected-${props.row.original.id}`}
name={`company-selected-${props.row.original.id}`}
/>
<CompanyChip
name={props.row.original.name}
picture={`https://www.google.com/s2/favicons?domain=${props.row.original.domain_name}&sz=256`}
/>
</HorizontalyAlignedContainer>
<EditableChip
value={props.row.original.name}
placeholder="Name"
picture={`https://www.google.com/s2/favicons?domain=${props.row.original.domain_name}&sz=256`}
changeHandler={(value: string) => {
const company = props.row.original;
company.name = value;
updateCompany(company).catch((error) => console.error(error));
}}
ChipComponent={CompanyChip}
/>
),
}),
columnHelper.accessor('employees', {