From 55eff2b7a21bacb0fb2015a2e84e06cca14312d2 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 5 May 2023 12:15:41 +0200 Subject: [PATCH] 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 --- front/package.json | 3 +- .../table/editable-cell/EditableChip.tsx | 69 +++++++++++++++++++ .../table/editable-cell/EditableText.tsx | 4 +- .../__stories__/EditableChip.stories.tsx | 33 +++++++++ .../__tests__/EditableChip.test.tsx | 34 +++++++++ .../table/table-header/SortOrFilterChip.tsx | 4 ++ .../HorizontalyAlignedContainer.tsx | 16 ----- front/src/layout/navbar/NavItem.tsx | 1 + front/src/pages/companies/companies-table.tsx | 34 ++++++--- 9 files changed, 168 insertions(+), 30 deletions(-) create mode 100644 front/src/components/table/editable-cell/EditableChip.tsx create mode 100644 front/src/components/table/editable-cell/__stories__/EditableChip.stories.tsx create mode 100644 front/src/components/table/editable-cell/__tests__/EditableChip.test.tsx delete mode 100644 front/src/layout/containers/HorizontalyAlignedContainer.tsx diff --git a/front/package.json b/front/package.json index 2afa4ec46..393c2c771 100644 --- a/front/package.json +++ b/front/package.json @@ -55,7 +55,8 @@ "coveragePathIgnorePatterns": [ ".stories.tsx$", "graphql.tsx$", - "apollo.tsx$" + "apollo.tsx$", + "src/index.tsx$" ], "coverageThreshold": { "global": { diff --git a/front/src/components/table/editable-cell/EditableChip.tsx b/front/src/components/table/editable-cell/EditableChip.tsx new file mode 100644 index 000000000..fe4c42132 --- /dev/null +++ b/front/src/components/table/editable-cell/EditableChip.tsx @@ -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` + 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(null); + const [inputValue, setInputValue] = useState(value); + const [isEditMode, setIsEditMode] = useState(false); + + const onEditModeChange = (isEditMode: boolean) => { + setIsEditMode(isEditMode); + }; + + return ( + + {isEditMode ? ( + ) => { + setInputValue(event.target.value); + changeHandler(event.target.value); + }} + /> + ) : ( + + )} + + ); +} + +export default EditableChip; diff --git a/front/src/components/table/editable-cell/EditableText.tsx b/front/src/components/table/editable-cell/EditableText.tsx index 798e7486e..f689ae1dc 100644 --- a/front/src/components/table/editable-cell/EditableText.tsx +++ b/front/src/components/table/editable-cell/EditableText.tsx @@ -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; diff --git a/front/src/components/table/editable-cell/__stories__/EditableChip.stories.tsx b/front/src/components/table/editable-cell/__stories__/EditableChip.stories.tsx new file mode 100644 index 000000000..bb051e07c --- /dev/null +++ b/front/src/components/table/editable-cell/__stories__/EditableChip.stories.tsx @@ -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 = (args: EditableChipProps) => { + return ( + +
+ +
+
+ ); +}; + +export const EditableChipStory = Template.bind({}); +EditableChipStory.args = { + ChipComponent: CompanyChip, + placeholder: 'Test', + value: 'Test', + picture: 'https://picsum.photos/200', + changeHandler: () => { + console.log('changed'); + }, +}; diff --git a/front/src/components/table/editable-cell/__tests__/EditableChip.test.tsx b/front/src/components/table/editable-cell/__tests__/EditableChip.test.tsx new file mode 100644 index 000000000..1649aca74 --- /dev/null +++ b/front/src/components/table/editable-cell/__tests__/EditableChip.test.tsx @@ -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( + , + ); + + 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'); +}); diff --git a/front/src/components/table/table-header/SortOrFilterChip.tsx b/front/src/components/table/table-header/SortOrFilterChip.tsx index 974481799..b898d3725 100644 --- a/front/src/components/table/table-header/SortOrFilterChip.tsx +++ b/front/src/components/table/table-header/SortOrFilterChip.tsx @@ -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` diff --git a/front/src/layout/containers/HorizontalyAlignedContainer.tsx b/front/src/layout/containers/HorizontalyAlignedContainer.tsx deleted file mode 100644 index c8e242611..000000000 --- a/front/src/layout/containers/HorizontalyAlignedContainer.tsx +++ /dev/null @@ -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 {children}; -} - -export default HorizontalyAlignedContainer; diff --git a/front/src/layout/navbar/NavItem.tsx b/front/src/layout/navbar/NavItem.tsx index 529a787cb..33e4802ea 100644 --- a/front/src/layout/navbar/NavItem.tsx +++ b/front/src/layout/navbar/NavItem.tsx @@ -15,6 +15,7 @@ type StyledItemProps = { const StyledItem = styled.button` display: flex; + align-items: center; border: none; font-size: ${(props) => props.theme.fontSizeMedium}; cursor: pointer; diff --git a/front/src/pages/companies/companies-table.tsx b/front/src/pages/companies/companies-table.tsx index 70fe26d75..51403cdaa 100644 --- a/front/src/pages/companies/companies-table.tsx +++ b/front/src/pages/companies/companies-table.tsx @@ -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(); export const companiesColumns = [ + columnHelper.accessor('id', { + header: () => ( + + ), + cell: (props) => ( + + ), + }), columnHelper.accessor('name', { header: () => } />, cell: (props) => ( - - - - + { + const company = props.row.original; + company.name = value; + updateCompany(company).catch((error) => console.error(error)); + }} + ChipComponent={CompanyChip} + /> ), }), columnHelper.accessor('employees', {