From 3341539eb2d85bf24f123d1fa900f4e9870bc2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Mon, 12 Jun 2023 17:52:16 +0200 Subject: [PATCH] Add arrow up/down+enter navigation to select relation (#275) * Add arrow up/down+enter navigation to select relation --- .../editable-cell/types/EditableRelation.tsx | 79 ++++++++++++++++++- .../__stories__/People.inputs.stories.tsx | 55 +++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/front/src/modules/ui/components/editable-cell/types/EditableRelation.tsx b/front/src/modules/ui/components/editable-cell/types/EditableRelation.tsx index 3f023305e4..9254d014ec 100644 --- a/front/src/modules/ui/components/editable-cell/types/EditableRelation.tsx +++ b/front/src/modules/ui/components/editable-cell/types/EditableRelation.tsx @@ -1,4 +1,5 @@ import { ChangeEvent, ComponentType, useEffect, useState } from 'react'; +import { useHotkeys } from 'react-hotkeys-hook'; import { FaPlus } from 'react-icons/fa'; import styled from '@emotion/styled'; import { useRecoilState } from 'recoil'; @@ -58,12 +59,21 @@ const StyledEditModeResults = styled.div` padding-right: ${(props) => props.theme.spacing(1)}; `; -const StyledEditModeResultItem = styled.div` +type StyledEditModeResultItemProps = { + isSelected: boolean; +}; + +const StyledEditModeResultItem = styled.div` height: 32px; display: flex; align-items: center; cursor: pointer; user-select: none; + ${(props) => + props.isSelected && + ` + background-color: ${props.theme.tertiaryBackground}; +`} `; const StyledCreateButtonIcon = styled.div` @@ -139,6 +149,72 @@ export function EditableRelation< setIsSomeInputInEditMode(false); } + const [selectedIndex, setSelectedIndex] = useState(0); + useHotkeys( + 'down', + () => { + setSelectedIndex((prevSelectedIndex) => + Math.min( + prevSelectedIndex + 1, + (filterSearchResults.results?.length ?? 0) - 1, + ), + ); + }, + { + enableOnContentEditable: true, + enableOnFormTags: true, + preventDefault: true, + }, + [setSelectedIndex, filterSearchResults.results], + ); + + useHotkeys( + 'up', + () => { + setSelectedIndex((prevSelectedIndex) => + Math.max(prevSelectedIndex - 1, 0), + ); + }, + { + enableOnContentEditable: true, + enableOnFormTags: true, + preventDefault: true, + }, + [setSelectedIndex], + ); + + useHotkeys( + 'enter', + () => { + if (isEditMode) { + if ( + filterSearchResults.results && + selectedIndex < filterSearchResults.results.length + ) { + const selectedResult = filterSearchResults.results[selectedIndex]; + onChange(selectedResult.value); + closeEditMode(); + } else if (canCreate && isNonEmptyString(searchInput)) { + onCreate(searchInput); + closeEditMode(); + } + } + }, + { + enableOnContentEditable: true, + enableOnFormTags: true, + }, + [ + filterSearchResults.results, + selectedIndex, + onChange, + closeEditMode, + canCreate, + searchInput, + onCreate, + ], + ); + return ( <> ( { onChange(result.value); closeEditMode(); diff --git a/front/src/pages/people/__stories__/People.inputs.stories.tsx b/front/src/pages/people/__stories__/People.inputs.stories.tsx index 4dd07b6838..441beeb61b 100644 --- a/front/src/pages/people/__stories__/People.inputs.stories.tsx +++ b/front/src/pages/people/__stories__/People.inputs.stories.tsx @@ -151,3 +151,58 @@ export const EditRelation: Story = { ], }, }; + +export const SelectRelationWithKeys: Story = { + render, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const thirdRowCompanyCell = await canvas.findByText( + mockedPeopleData[2].company.name, + ); + + await userEvent.click(thirdRowCompanyCell); + + const relationInput = await canvas.findByPlaceholderText('Company'); + + await userEvent.type(relationInput, 'Air', { + delay: 200, + }); + + await userEvent.type(relationInput, '{arrowdown}'); + await userEvent.type(relationInput, '{arrowdown}'); + await userEvent.type(relationInput, '{arrowup}'); + await userEvent.type(relationInput, '{arrowdown}'); + await userEvent.type(relationInput, '{enter}'); + + const newThirdRowCompanyCell = await canvas.findByText('Aircall'); + await userEvent.click(newThirdRowCompanyCell); + }, + parameters: { + actions: {}, + msw: [ + ...graphqlMocks.filter((graphqlMock) => { + return graphqlMock.info.operationName !== 'UpdatePeople'; + }), + ...[ + graphql.mutation('UpdatePeople', (req, res, ctx) => { + return res( + ctx.data({ + updateOnePerson: { + ...fetchOneFromData(mockedPeopleData, req.variables.id), + ...{ + company: { + id: req.variables.companyId, + name: 'Aircall', + domainName: 'aircall.io', + __typename: 'Company', + } satisfies GraphqlQueryCompany, + }, + }, + }), + ); + }), + ], + ], + }, +};