) => void;
}
export type InputVariant = 'filled' | 'outline';
@@ -112,6 +114,7 @@ export function BaseInput, E extends HTMLElement>(
id: idProp,
width,
innerRef,
+ inputWrapperRef,
inputRef,
fullWidth = false,
adornmentGap = 0,
@@ -144,6 +147,7 @@ export function BaseInput
, E extends HTMLElement>(
)}
(ref: Ref, item: T) {
+export function setRef(ref: Ref, item: T, onSet?: (item: T) => void) {
+ if (onSet) {
+ onSet(item);
+ }
if (typeof ref === 'function') {
ref(item);
} else if (ref) {
diff --git a/src/utils/useBlur.ts b/src/utils/useBlur.ts
new file mode 100644
index 0000000..546bf3a
--- /dev/null
+++ b/src/utils/useBlur.ts
@@ -0,0 +1,23 @@
+import { useEffect, useRef } from 'react';
+
+const useBlur = (handler: (target: HTMLElement | null) => void) => {
+ const clickedElement = useRef(null);
+ const handleMouseDown = (e: MouseEvent) => {
+ clickedElement.current = e.target as HTMLElement;
+ };
+ useEffect(() => {
+ document.addEventListener('mousedown', handleMouseDown);
+ return () => {
+ document.removeEventListener('mousedown', handleMouseDown);
+ };
+ }, []);
+
+ return () => {
+ const target = clickedElement.current;
+ clickedElement.current = null;
+
+ handler(target);
+ };
+};
+
+export { useBlur };
diff --git a/src/utils/useForwardRef.ts b/src/utils/useForwardRef.ts
index d543c66..7ab8b12 100644
--- a/src/utils/useForwardRef.ts
+++ b/src/utils/useForwardRef.ts
@@ -1,4 +1,13 @@
-import { MutableRefObject, Ref, RefObject, useCallback, useMemo, useRef, useState } from 'react';
+import {
+ MutableRefObject,
+ Ref,
+ RefObject,
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
import { setRef } from './setRef';
export function useForwardRef(outsideRef: Ref): [RefObject, (newValue: T) => void] {
@@ -14,15 +23,26 @@ export function useForwardRef(outsideRef: Ref): [RefObject, (newValue:
return useMemo(() => [innerRef, setInnerRef], [innerRef, setInnerRef]);
}
-export function useReRenderForwardRef(outsideRef: Ref): [T | null, (newValue: T) => void] {
- const [innerRef, setInnerRefValue] = useState(null);
+export function useReRenderForwardRef(
+ outsideRef: Ref,
+ override?: T
+): [T | null, (newValue: T) => void] {
+ const [innerRef, setInnerRefValue] = useState(override || null);
const setInnerRef = useCallback(
(value) => {
- setInnerRefValue(value);
- setRef(outsideRef, value);
+ if (override === undefined || override === null) {
+ setInnerRefValue(value);
+ setRef(outsideRef, value);
+ }
},
- [outsideRef]
+ [outsideRef, override]
);
+ useEffect(() => {
+ if (override !== undefined && override !== null) {
+ setInnerRefValue(override || null);
+ }
+ }, [override, setInnerRef]);
+
return useMemo(() => [innerRef, setInnerRef], [innerRef, setInnerRef]);
}
diff --git a/src/utils/useKeyboardNavigation.ts b/src/utils/useKeyboardNavigation.ts
new file mode 100644
index 0000000..1d66ef7
--- /dev/null
+++ b/src/utils/useKeyboardNavigation.ts
@@ -0,0 +1,61 @@
+import { useCallback, useEffect, useState } from 'react';
+
+const useKeyboardNavigation = (
+ items: T[],
+ onSelect: (item: T) => void,
+ active: boolean = true
+) => {
+ const [selectedIndex, setSelectedIndex] = useState(-1);
+
+ const handleKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ switch (e.key) {
+ case 'ArrowDown':
+ e.preventDefault();
+ setSelectedIndex((prev) => (prev >= items.length - 1 ? 0 : prev + 1));
+ break;
+ case 'ArrowUp':
+ e.preventDefault();
+ setSelectedIndex((prev) => {
+ return prev <= 0 ? items.length - 1 : prev - 1;
+ });
+ break;
+ case 'Enter':
+ e.preventDefault();
+ e.stopPropagation();
+ if (selectedIndex >= 0) {
+ const selectedItem = items[selectedIndex];
+ if (selectedItem) {
+ onSelect(selectedItem);
+ }
+ }
+ break;
+ default:
+ setSelectedIndex(-1);
+ }
+ },
+ [items, onSelect, selectedIndex]
+ );
+
+ // Reset index when items change
+ useEffect(() => {
+ setSelectedIndex(-1);
+ }, [items]);
+
+ useEffect(() => {
+ if (active) {
+ document.addEventListener('keydown', handleKeyDown, true);
+ return () => {
+ document.removeEventListener('keydown', handleKeyDown, true);
+ };
+ }
+ setSelectedIndex(-1);
+ return () => {};
+ }, [handleKeyDown, active]);
+
+ return {
+ selectedIndex,
+ };
+};
+
+export { useKeyboardNavigation };
diff --git a/tsconfig.json b/tsconfig.json
index 0a3fbbf..844bfcd 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -15,7 +15,8 @@
"strictNullChecks": true,
"skipLibCheck": true,
"esModuleInterop": true,
- "noUnusedLocals": true
+ "noUnusedLocals": true,
+ "types": ["vitest/globals", "@testing-library/jest-dom/vitest"],
},
"include": ["src", "testUtils", "emotion.d.ts"],
"exclude": ["node_modules", "build", "scripts", "acceptance-tests", "webpack", "jest"]
diff --git a/vite.config.js b/vite.config.js
index a44ce48..1603124 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -23,7 +23,7 @@ const config = defineConfig({
test: {
environment: 'jsdom',
globals: true,
- setupFiles: './vitestSetup.js', // assuming the test folder is in the root of our project
+ setupFiles: './vitestSetup.ts', // assuming the test folder is in the root of our project
},
})
diff --git a/vitestSetup.js b/vitestSetup.ts
similarity index 100%
rename from vitestSetup.js
rename to vitestSetup.ts
diff --git a/yarn.lock b/yarn.lock
index ca41b83..a730cdf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1566,6 +1566,11 @@
dependencies:
"@babel/runtime" "^7.12.5"
+"@testing-library/user-event@^14.6.1":
+ version "14.6.1"
+ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149"
+ integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==
+
"@tootallnate/quickjs-emscripten@^0.23.0":
version "0.23.0"
resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c"