summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOliver <oliver@mohlin.dev>2025-09-30 15:10:40 +0200
committerTobias Järvelöv <tobias.jarvelov@mullvad.net>2025-10-07 11:35:59 +0200
commit08ff6065aa848b428683031389bd75c44a27672e (patch)
treeb809901d7ce0b8591c825dbc366fee73c646c34b
parent93c2133fe37d18a5114b0cb5fef60c6dc8a9465f (diff)
downloadmullvadvpn-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.tsx42
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/listbox/components/listbox-options/hooks/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/hooks/index.ts5
-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.ts59
-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