11import classNames from "classnames" ;
2+ import { useOnBlur } from "utils/hooks/useOnBlur" ;
23import { useTranslation } from "react-i18next" ;
34import { ReactComponent as CheckDoneIcon } from "assets/icons/check-done.svg" ;
45import { ReactComponent as CloseIcon } from "assets/icons/close.svg" ;
56import { TextArea } from "components/TextArea/TextArea" ;
67import { MiniMenu , MiniMenuItem } from "components/MiniMenu/MiniMenu" ;
7- import { Dispatch , SetStateAction , useRef , useState , FocusEvent } from "react" ;
8- import "./ColumnConfiguratorColumnNameDetails.scss" ;
8+ import { Dispatch , SetStateAction , useRef , useState } from "react" ;
99import { MAX_COLUMN_DESCRIPTION_LENGTH } from "constants/misc" ;
10+ import "./ColumnConfiguratorColumnNameDetails.scss" ;
1011
1112export type OpenState = "closed" | "visualFeedback" | "nameFirst" | "descriptionFirst" ;
1213
@@ -26,52 +27,53 @@ export type ColumnConfiguratorColumnNameDetailsProps = {
2627export const ColumnConfiguratorColumnNameDetails = ( props : ColumnConfiguratorColumnNameDetailsProps ) => {
2728 const { t} = useTranslation ( ) ;
2829
29- const nameWrapperRef = useRef < HTMLDivElement > ( null ) ;
30-
3130 // temporary state for name and description text as the changes have to be confirmed before applying
3231 const [ name , setName ] = useState ( props . name ) ;
3332 const [ description , setDescription ] = useState ( props . description ) ;
3433
3534 const isEditing = props . openState === "nameFirst" || props . openState === "descriptionFirst" ;
3635
36+ const nameInputRef = useRef < HTMLInputElement > ( null ) ;
37+
38+ const cancelChanges = ( ) => {
39+ props . setOpenState ( "closed" ) ;
40+ nameInputRef . current ?. blur ( ) ; // leave input (or we can keep typing inside it)
41+ } ;
42+
43+ const saveChanges = ( ) => {
44+ props . updateColumnTitle ( name , description ) ;
45+ // show visual feedback for 2s before displaying menu options again
46+ nameInputRef . current ?. blur ( ) ; // leave input (or we can keep typing inside it)
47+ props . setOpenState ( "visualFeedback" ) ;
48+ setTimeout ( ( ) => {
49+ props . setOpenState ( "closed" ) ;
50+ } , 2000 ) ;
51+ } ;
52+
53+ // if we leave the wrapper, reset and close
54+ const handleBlurNameWrapperContents = ( ) => {
55+ props . setOpenState ( "closed" ) ;
56+ setName ( props . name ) ;
57+ setDescription ( props . description ) ;
58+ } ;
59+
60+ const nameWrapperRef = useOnBlur < HTMLDivElement > ( handleBlurNameWrapperContents ) ;
61+
3762 const descriptionConfirmMiniMenu : MiniMenuItem [ ] = [
3863 {
3964 className : "mini-menu-item--cancel" ,
4065 element : < CloseIcon /> ,
4166 label : t ( "Templates.ColumnsConfiguratorColumn.cancel" ) ,
42- onClick ( ) : void {
43- props . setOpenState ( "closed" ) ;
44- ( document . activeElement as HTMLElement ) ?. blur ( ) ; // leave input (or we can keep typing inside it)
45- } ,
67+ onClick : cancelChanges ,
4668 } ,
4769 {
4870 className : "mini-menu-item--save" ,
4971 element : < CheckDoneIcon /> ,
5072 label : t ( "Templates.ColumnsConfiguratorColumn.save" ) ,
51- onClick ( ) : void {
52- props . updateColumnTitle ( name , description ) ;
53- // show visual feedback for 2s before displaying menu options again
54- props . setOpenState ( "visualFeedback" ) ;
55- setTimeout ( ( ) => {
56- props . setOpenState ( "closed" ) ;
57- } , 2000 ) ;
58- } ,
73+ onClick : saveChanges ,
5974 } ,
6075 ] ;
6176
62- // if we leave the wrapper close, otherwise leave open
63- const handleBlurNameWrapperContents = ( e : FocusEvent < HTMLInputElement | HTMLTextAreaElement > ) => {
64- const isFocusInsideTitleHeaderWrapper = nameWrapperRef . current ?. contains ( e . relatedTarget ) ;
65-
66- if ( ! isFocusInsideTitleHeaderWrapper ) {
67- props . setOpenState ( "closed" ) ;
68-
69- // reset name and description to actual
70- setName ( props . name ) ;
71- setDescription ( props . description ) ;
72- }
73- } ;
74-
7577 const openDescriptionWithCurrentValue = ( ) => {
7678 setDescription ( props . description ) ;
7779 props . setOpenState ( "descriptionFirst" ) ;
@@ -80,13 +82,25 @@ export const ColumnConfiguratorColumnNameDetails = (props: ColumnConfiguratorCol
8082 return (
8183 < div className = { classNames ( props . className , "column-configurator-column-name-details__name-wrapper" ) } ref = { nameWrapperRef } >
8284 < input
85+ ref = { nameInputRef }
8386 className = { classNames ( "column-configurator-column-name-details__name" , { "column-configurator-column-name-details__name--editing" : isEditing } ) }
8487 value = { name }
8588 placeholder = { t ( "Templates.ColumnsConfiguratorColumn.namePlaceholder" ) }
8689 onInput = { ( e ) => setName ( e . currentTarget . value ) }
8790 onFocus = { ( ) => props . setOpenState ( "nameFirst" ) }
88- onBlur = { handleBlurNameWrapperContents }
8991 autoComplete = "off"
92+ onKeyDown = { ( e ) => {
93+ // handle Enter key submission
94+ if ( e . key === "Enter" ) {
95+ e . preventDefault ( ) ;
96+ saveChanges ( ) ;
97+ }
98+ // escape to cancel
99+ else if ( e . key === "Escape" ) {
100+ e . preventDefault ( ) ;
101+ cancelChanges ( ) ;
102+ }
103+ } }
90104 />
91105 { isEditing ? (
92106 < div className = "column-configurator-column-name-details__description-wrapper" >
@@ -98,8 +112,9 @@ export const ColumnConfiguratorColumnNameDetails = (props: ColumnConfiguratorCol
98112 embedded
99113 fitted
100114 autoFocus = { props . openState === "descriptionFirst" }
101- onBlur = { handleBlurNameWrapperContents }
102115 maxLength = { MAX_COLUMN_DESCRIPTION_LENGTH }
116+ onSubmit = { saveChanges }
117+ onCancel = { cancelChanges }
103118 />
104119 < MiniMenu className = "column-configurator-column-name-details__description-mini-menu" items = { descriptionConfirmMiniMenu } small transparent />
105120 </ div >
0 commit comments