fix(analysis): preserve tab panel state

This commit is contained in:
2026-07-09 13:59:01 +08:00
parent 701c5a949d
commit 694f7629ee
24 changed files with 1119 additions and 281 deletions
@@ -0,0 +1,55 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
export const useControllableState = <T,>(
externalValue: T | undefined,
onExternalChange: ((value: T) => void) | undefined,
defaultValue: T,
) => {
const [internalValue, setInternalValue] = useState<T>(defaultValue);
const value = externalValue !== undefined ? externalValue : internalValue;
const valueRef = useRef(value);
useEffect(() => {
valueRef.current = value;
}, [value]);
const setValue = useCallback(
(next: T | ((previous: T) => T)) => {
const nextValue =
typeof next === "function"
? (next as (previous: T) => T)(valueRef.current)
: next;
valueRef.current = nextValue;
if (externalValue === undefined) {
setInternalValue(nextValue);
}
onExternalChange?.(nextValue);
},
[externalValue, onExternalChange],
);
return [value, setValue] as const;
};
export const useControllableObjectState = <T extends object>(
externalValue: T | undefined,
onExternalChange: ((value: T) => void) | undefined,
defaultValue: T,
) => {
const [value, setValue] = useControllableState(
externalValue,
onExternalChange,
defaultValue,
);
const setField = useCallback(
<K extends keyof T>(key: K, nextValue: T[K]) => {
setValue((previous) => ({ ...previous, [key]: nextValue }));
},
[setValue],
);
return [value, setValue, setField] as const;
};