diff options
| author | Oliver <oliver@mohlin.dev> | 2025-09-30 15:10:40 +0200 |
|---|---|---|
| committer | Tobias Järvelöv <tobias.jarvelov@mullvad.net> | 2025-10-07 11:35:59 +0200 |
| commit | 08ff6065aa848b428683031389bd75c44a27672e (patch) | |
| tree | b809901d7ce0b8591c825dbc366fee73c646c34b | |
| parent | 93c2133fe37d18a5114b0cb5fef60c6dc8a9465f (diff) | |
| download | mullvadvpn-08ff6065aa848b428683031389bd75c44a27672e.tar.xz mullvadvpn-08ff6065aa848b428683031389bd75c44a27672e.zip | |
Make option focus and keyboard handling hooks reusable
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/ListboxOptions.tsx | 42 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/index.ts | 1 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/hooks/index.ts | 5 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-focus-option-by-index.ts (renamed from desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/useFocusOptionByIndex.ts) | 10 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-get-initial-focus-index.ts (renamed from desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/useGetInitialFocusIndex.ts) | 10 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-handle-keyboard-navigation.ts (renamed from desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/useHandleKeyboardNavigation.ts) | 23 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-options.ts | 59 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-initial-option.ts (renamed from desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-initial-option.ts) | 0 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-is-option-selected.ts (renamed from desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-is-option-selected.ts) | 0 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-options.ts (renamed from desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-options.ts) | 0 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-selected-option-index.ts (renamed from desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-selected-option-index.ts) | 0 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-selected-option.ts (renamed from desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-selected-option.ts) | 0 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/src/renderer/lib/utils/index.ts (renamed from desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/index.ts) | 0 |
13 files changed, 101 insertions, 49 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/ListboxOptions.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/ListboxOptions.tsx index 6af21a10cb..fbb97cd492 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/ListboxOptions.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/ListboxOptions.tsx @@ -1,33 +1,19 @@ import React from 'react'; +import { useOptions } from '../../../../hooks'; import { useListboxContext } from '../../'; -import { useHandleKeyboardNavigation } from './hooks'; -import { getInitialOption, getOptions } from './utils'; export type ListboxOptionsProps = { children: React.ReactNode[]; }; export function ListboxOptions({ children }: ListboxOptionsProps) { - const { labelId, optionsRef, setFocusedIndex } = useListboxContext(); - const [tabIndex, setTabIndex] = React.useState<number>(0); - - const handleFocus = React.useCallback( - (event: React.FocusEvent) => { - if (!optionsRef.current?.isSameNode(event.target)) return; - - const options = getOptions(optionsRef.current); - - const initialOption = getInitialOption(options); - if (initialOption) { - setTabIndex(-1); - initialOption.focus(); - } - }, - [optionsRef], - ); - - const handleKeyboardNavigation = useHandleKeyboardNavigation(); + const { labelId, optionsRef, focusedIndex, setFocusedIndex } = useListboxContext(); + const { handleFocus, handleKeyboardNavigation, handleBlur, tabIndex } = useOptions({ + focusedIndex, + optionsRef, + setFocusedIndex, + }); const onKeyDown = React.useCallback( (event: React.KeyboardEvent) => { @@ -36,20 +22,6 @@ export function ListboxOptions({ children }: ListboxOptionsProps) { [handleKeyboardNavigation], ); - const handleBlur = React.useCallback( - (event: React.FocusEvent<HTMLUListElement>) => { - const container = optionsRef.current; - const nextFocus = event.relatedTarget as Node | null; - - // If focus moves outside the listbox - if (!container || !nextFocus || !container.contains(nextFocus)) { - setFocusedIndex(undefined); - setTabIndex(0); - } - }, - [optionsRef, setFocusedIndex], - ); - return ( <ul ref={optionsRef} diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/index.ts deleted file mode 100644 index 64b446899b..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useHandleKeyboardNavigation'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/index.ts new file mode 100644 index 0000000000..acdfcad376 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/index.ts @@ -0,0 +1,5 @@ +export * from './use-exclusive-task'; +export * from './use-focus-option-by-index'; +export * from './use-get-initial-focus-index'; +export * from './use-handle-keyboard-navigation'; +export * from './use-options'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/useFocusOptionByIndex.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-focus-option-by-index.ts index 8e75d70912..1f3b3a9233 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/useFocusOptionByIndex.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-focus-option-by-index.ts @@ -1,10 +1,14 @@ import React from 'react'; -import { useListboxContext } from '../../../ListboxContext'; import { getOptions } from '../utils'; -export const useFocusOptionByIndex = () => { - const { setFocusedIndex, optionsRef } = useListboxContext(); +export const useFocusOptionByIndex = ({ + optionsRef, + setFocusedIndex, +}: { + optionsRef: React.RefObject<HTMLUListElement | null>; + setFocusedIndex: (index: number) => void; +}) => { return React.useCallback( (index: number) => { const options = getOptions(optionsRef.current); diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/useGetInitialFocusIndex.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-get-initial-focus-index.ts index 43205589cd..fca42ecb7a 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/useGetInitialFocusIndex.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-get-initial-focus-index.ts @@ -1,10 +1,14 @@ import React from 'react'; -import { useListboxContext } from '../../../ListboxContext'; import { getOptions, getSelectedOptionIndex } from '../utils'; -export const useGetInitialFocusIndex = () => { - const { focusedIndex, optionsRef } = useListboxContext(); +export const useGetInitialFocusIndex = ({ + focusedIndex, + optionsRef, +}: { + focusedIndex?: number; + optionsRef: React.RefObject<HTMLUListElement | null>; +}) => { return React.useCallback(() => { const options = getOptions(optionsRef.current); if (focusedIndex !== undefined) { diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/useHandleKeyboardNavigation.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-handle-keyboard-navigation.ts index ba7b61bd67..d5719b6ec8 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/useHandleKeyboardNavigation.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-handle-keyboard-navigation.ts @@ -1,14 +1,23 @@ import React from 'react'; -import { useListboxContext } from '../../../'; import { getOptions } from '../utils'; -import { useFocusOptionByIndex } from './useFocusOptionByIndex'; -import { useGetInitialFocusIndex } from './useGetInitialFocusIndex'; +import { useFocusOptionByIndex } from './use-focus-option-by-index'; +import { useGetInitialFocusIndex } from './use-get-initial-focus-index'; -export const useHandleKeyboardNavigation = () => { - const { optionsRef } = useListboxContext(); - const getInitialFocusIndex = useGetInitialFocusIndex(); - const focusOptionByIndex = useFocusOptionByIndex(); +export const useHandleKeyboardNavigation = ({ + optionsRef, + focusedIndex, + setFocusedIndex, +}: { + optionsRef: React.RefObject<HTMLUListElement | null>; + focusedIndex?: number; + setFocusedIndex: (index: number) => void; +}) => { + const getInitialFocusIndex = useGetInitialFocusIndex({ optionsRef, focusedIndex }); + const focusOptionByIndex = useFocusOptionByIndex({ + optionsRef, + setFocusedIndex, + }); return React.useCallback( (event: React.KeyboardEvent) => { diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-options.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-options.ts new file mode 100644 index 0000000000..aaf62df1f1 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/hooks/use-options.ts @@ -0,0 +1,59 @@ +import React from 'react'; + +import { getInitialOption, getOptions } from '../utils'; +import { useHandleKeyboardNavigation } from './use-handle-keyboard-navigation'; + +export type UseOptionsProps = { + optionsRef: React.RefObject<HTMLUListElement | null>; + focusedIndex?: number; + setFocusedIndex: React.Dispatch<React.SetStateAction<number | undefined>>; +}; + +export type UseOptionsFieldState = { + tabIndex: number; + handleFocus: (event: React.FocusEvent) => void; + optionsRef: React.RefObject<HTMLUListElement | null>; + handleKeyboardNavigation: (event: React.KeyboardEvent) => void; + handleBlur: (event: React.FocusEvent<HTMLUListElement>) => void; +}; + +export function useOptions({ optionsRef, focusedIndex, setFocusedIndex }: UseOptionsProps) { + const [tabIndex, setTabIndex] = React.useState<number>(0); + const handleFocus = React.useCallback( + (event: React.FocusEvent) => { + if (!optionsRef.current?.isSameNode(event.target)) return; + + const options = getOptions(optionsRef.current); + + const initialOption = getInitialOption(options); + if (initialOption) { + // Prevent the container from being tabbable once an option has focus + setTabIndex(-1); + initialOption.focus(); + } + }, + [optionsRef], + ); + + const handleKeyboardNavigation = useHandleKeyboardNavigation({ + optionsRef, + setFocusedIndex, + focusedIndex, + }); + + const handleBlur = React.useCallback( + (event: React.FocusEvent<HTMLUListElement>) => { + const container = optionsRef.current; + const nextFocus = event.relatedTarget as Node | null; + + // If focus moves outside the container + if (!container || !nextFocus || !container.contains(nextFocus)) { + setFocusedIndex(undefined); + setTabIndex(0); + } + }, + [optionsRef, setFocusedIndex], + ); + + return { tabIndex, handleFocus, handleKeyboardNavigation, handleBlur, optionsRef }; +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-initial-option.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-initial-option.ts index e2278b5237..e2278b5237 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-initial-option.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-initial-option.ts diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-is-option-selected.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-is-option-selected.ts index 6433e20232..6433e20232 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-is-option-selected.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-is-option-selected.ts diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-options.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-options.ts index d37647936c..d37647936c 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-options.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-options.ts diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-selected-option-index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-selected-option-index.ts index 2534ef46d1..2534ef46d1 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-selected-option-index.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-selected-option-index.ts diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-selected-option.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-selected-option.ts index dfed49aae7..dfed49aae7 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/get-selected-option.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/get-selected-option.ts diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/index.ts index cbd0929620..cbd0929620 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/utils/index.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/utils/index.ts |
