Skip to content

Commit

Permalink
feat: up auto focus
Browse files Browse the repository at this point in the history
  • Loading branch information
AllanOliveiraM committed Apr 24, 2024
1 parent dac798a commit 5324571
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"editor.formatOnSave": true,
"files.associations": {
Expand Down
18 changes: 18 additions & 0 deletions src/components/molecules/Fields/AsyncSelect/AsyncSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ReactSelectAsync from 'react-select/async'
import { StyleModeContext } from 'contexts/StyleModeContext'
import debounce from 'lodash/debounce'

import { useRegisterFieldFocus } from 'hooks/useRegisterFieldFocus'
import { StyleModes, System } from 'types'
import { slugify } from 'utils'

Expand Down Expand Up @@ -66,6 +67,7 @@ const AsyncSelect = <FormType extends FieldValues>(props: AsyncSelectProps<FormT
reactSelectProps,
reactSelectStyles,
styleMode: localStyleMode,
onKeyDown,
...rest
} = props

Expand Down Expand Up @@ -169,6 +171,8 @@ const AsyncSelect = <FormType extends FieldValues>(props: AsyncSelectProps<FormT
}
}, [isSearchable, loadOptions])

const onKeyDownAuto = useRegisterFieldFocus(name)

return (
<RootContainer
m='0.8rem 0'
Expand Down Expand Up @@ -238,6 +242,13 @@ const AsyncSelect = <FormType extends FieldValues>(props: AsyncSelectProps<FormT
isSearchable={isSearchable}
noOptionsMessage={() => noOptionsMessage}
isLoading={isLoading}
onKeyDown={e => {
onKeyDownAuto(e)

if (typeof onKeyDown === 'function') {
onKeyDown(e as any)
}
}}
/>
)
}
Expand Down Expand Up @@ -279,6 +290,13 @@ const AsyncSelect = <FormType extends FieldValues>(props: AsyncSelectProps<FormT
isSearchable={isSearchable}
noOptionsMessage={() => noOptionsMessage}
isLoading={isLoading}
onKeyDown={e => {
onKeyDownAuto(e)

if (typeof onKeyDown === 'function') {
onKeyDown(e as any)
}
}}
/>
)
}}
Expand Down
17 changes: 17 additions & 0 deletions src/components/molecules/Fields/Field/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { StyleModeContext } from 'contexts/StyleModeContext'

import { Input, InputProps, Label, Span } from 'components/atoms'

import { useRegisterFieldFocus } from 'hooks/useRegisterFieldFocus'
import { StyleModes } from 'types'

import {
Expand Down Expand Up @@ -99,6 +100,8 @@ const Field = forwardRef<HTMLInputElement, FieldProps>((props, ref) => {
setIsFocused(false)
}

const onKeyDown = useRegisterFieldFocus(name)

return (
<RootContainer
m='0.8rem 0'
Expand Down Expand Up @@ -199,6 +202,13 @@ const Field = forwardRef<HTMLInputElement, FieldProps>((props, ref) => {
target.blur()
}
}}
onKeyDown={e => {
onKeyDown(e)

if (typeof props.onKeyDown === 'function') {
props.onKeyDown(e)
}
}}
/>
</>
) : (
Expand Down Expand Up @@ -232,6 +242,13 @@ const Field = forwardRef<HTMLInputElement, FieldProps>((props, ref) => {
target.blur()
}
}}
onKeyDown={e => {
onKeyDown(e)

if (typeof props.onKeyDown === 'function') {
props.onKeyDown(e)
}
}}
/>
)}
{type !== 'file' && (sufixElement || null)}
Expand Down
18 changes: 18 additions & 0 deletions src/components/molecules/Fields/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ReactSelect, { MultiValue, SingleValue } from 'react-select'

import { StyleModeContext } from 'contexts/StyleModeContext'

import { useRegisterFieldFocus } from 'hooks/useRegisterFieldFocus'
import { StyleModes, System } from 'types'
import { slugify } from 'utils'

Expand Down Expand Up @@ -72,6 +73,7 @@ const Select = <FormType extends FieldValues>(props: SelectProps<FormType>) => {
onChange: customOnChange,
reactSelectProps,
reactSelectStyles,
onKeyDown,
...rest
} = props

Expand Down Expand Up @@ -127,6 +129,8 @@ const Select = <FormType extends FieldValues>(props: SelectProps<FormType>) => {
[isMulti, reactSelectStyles, selectedColor, styleMode]
)

const onKeyDownAuto = useRegisterFieldFocus(name)

return (
<RootContainer
m='0.8rem 0'
Expand Down Expand Up @@ -190,6 +194,13 @@ const Select = <FormType extends FieldValues>(props: SelectProps<FormType>) => {
data-cy={resolvedId}
isSearchable={Boolean(enableSearch)}
noOptionsMessage={() => enableSearch?.noOptionsMessage}
onKeyDown={e => {
onKeyDownAuto(e)

if (typeof onKeyDown === 'function') {
onKeyDown(e as any)
}
}}
/>
)
}
Expand Down Expand Up @@ -225,6 +236,13 @@ const Select = <FormType extends FieldValues>(props: SelectProps<FormType>) => {
data-cy={resolvedId}
isSearchable={Boolean(enableSearch)}
noOptionsMessage={() => enableSearch?.noOptionsMessage}
onKeyDown={e => {
onKeyDownAuto(e)

if (typeof onKeyDown === 'function') {
onKeyDown(e as any)
}
}}
/>
)
}}
Expand Down
39 changes: 39 additions & 0 deletions src/contexts/AutoFocusContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react'
import { FieldValues, UseFormSetFocus, UseFormTrigger } from 'react-hook-form'

import { createContext } from '@nexpy/react-easy-context-api'

import { WithChildren } from 'types'

type AutoFocusContextValue<FormValues extends FieldValues = any> = {
trigger: UseFormTrigger<FormValues> | null
setFocus: UseFormSetFocus<FormValues> | null
}

export const AutoFocusContext = createContext<AutoFocusContextValue>({
trigger: null,
setFocus: null,
} as AutoFocusContextValue)

type AutoFocusProviderProps<FormValues extends FieldValues> = WithChildren<{
trigger: UseFormTrigger<FormValues>
setFocus: UseFormSetFocus<FormValues>
}>

export const AutoFocus = <FormValues extends FieldValues>({
children,
trigger,
setFocus,
}: AutoFocusProviderProps<FormValues>) => {
return (
<AutoFocusContext.Provider
value={{
trigger,
setFocus,
}}
>
{children}
</AutoFocusContext.Provider>
)
}
24 changes: 24 additions & 0 deletions src/contexts/FieldNextFocusManagerContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { useRef, MutableRefObject } from 'react'

import { createContext } from '@nexpy/react-easy-context-api'

import { WithChildren } from 'types'

type FieldNextFocusManagerContextValue = {
sequentialFieldNamesRef: MutableRefObject<string[]>
}

export const FieldNextFocusManagerContext =
createContext<FieldNextFocusManagerContextValue>(
{} as FieldNextFocusManagerContextValue
)

export const FieldNextFocusManagerProvider = ({ children }: WithChildren) => {
const sequentialFieldNamesRef = useRef<string[]>([])

return (
<FieldNextFocusManagerContext.Provider value={{ sequentialFieldNamesRef }}>
{children}
</FieldNextFocusManagerContext.Provider>
)
}
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './useOnClickOutside'
export * from './useMask'
export * from './useRegisterFieldFocus'
66 changes: 66 additions & 0 deletions src/hooks/useRegisterFieldFocus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useCallback, useEffect } from 'react'

import { AutoFocusContext } from 'contexts/AutoFocusContext'
import { FieldNextFocusManagerContext } from 'contexts/FieldNextFocusManagerContext'

export const useRegisterFieldFocus = (fieldName?: string | undefined) => {
const sequentialFieldNamesRef = FieldNextFocusManagerContext.useSelector(
state => state.sequentialFieldNamesRef
)

const autoFocusContextValue = AutoFocusContext.useContext()

if (fieldName && autoFocusContextValue?.setFocus) {
sequentialFieldNamesRef.current.push(fieldName)
}

const onKeyDown = useCallback(
(e: any) => {
if (
!autoFocusContextValue?.setFocus ||
!Array.isArray(sequentialFieldNamesRef.current)
) {
return
}

if (e.key === 'Enter') {
e.preventDefault()

const fieldIndex = sequentialFieldNamesRef.current.findIndex(
val => val === fieldName
)

if (typeof fieldIndex === 'number' && fieldIndex !== -1) {
const nextFieldNameIndex = fieldIndex + 1
const nextFieldName = sequentialFieldNamesRef.current[nextFieldNameIndex]

if (nextFieldName) {
autoFocusContextValue.trigger?.(fieldName).then(passed => {
if (passed) {
autoFocusContextValue.setFocus?.(nextFieldName)
}
})
}
}
}
},
[autoFocusContextValue, fieldName, sequentialFieldNamesRef]
)

useEffect(() => {
return () => {
if (!autoFocusContextValue?.setFocus) {
return
}

if (Array.isArray(sequentialFieldNamesRef.current)) {
sequentialFieldNamesRef.current = sequentialFieldNamesRef.current.filter(
val => val !== fieldName
)
}
}
}, [autoFocusContextValue?.setFocus, fieldName, sequentialFieldNamesRef])

return onKeyDown
}
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './hooks'
export * from './theme'
export * from './types'
export * from './utils'
export { AutoFocus } from 'contexts/AutoFocusContext'
23 changes: 13 additions & 10 deletions src/theme/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useMemo, memo, PropsWithChildren } from 'react'

import { ThemeProvider as BaseThemeProvider } from '@xstyled/styled-components'
import { FieldNextFocusManagerProvider } from 'contexts/FieldNextFocusManagerContext'
import { StyleModeProvider } from 'contexts/StyleModeContext'
import merge from 'lodash/merge'

Expand All @@ -27,16 +28,18 @@ const ThemeProviderBase = ({
return (
<BaseThemeProvider theme={mergedTheme}>
<StyleModeProvider defaultStyleMode={defaultStyleMode}>
{renderWithAdaptativeRootRemRefCSS ? (
<AdaptativeGlobalStyle css={renderWithAdaptativeRootRemRefCSS} />
) : (
<GlobalStyle />
)}
{children}

{disablePortalContainer !== true ? (
<div id={ROOT_DESIGN_SYSTEM_PORTALS_CONTAINER_ID} />
) : null}
<FieldNextFocusManagerProvider>
{renderWithAdaptativeRootRemRefCSS ? (
<AdaptativeGlobalStyle css={renderWithAdaptativeRootRemRefCSS} />
) : (
<GlobalStyle />
)}
{children}

{disablePortalContainer !== true ? (
<div id={ROOT_DESIGN_SYSTEM_PORTALS_CONTAINER_ID} />
) : null}
</FieldNextFocusManagerProvider>
</StyleModeProvider>
</BaseThemeProvider>
)
Expand Down

0 comments on commit 5324571

Please sign in to comment.