feat(ui): switch tab on drag over tab button

This commit is contained in:
psychedelicious
2025-06-17 12:09:27 +10:00
parent 2f9ea91896
commit 32aa3e6d48
3 changed files with 79 additions and 27 deletions

View File

@@ -0,0 +1,29 @@
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { dropTargetForExternal } from '@atlaskit/pragmatic-drag-and-drop/external/adapter';
import { useTimeoutCallback } from 'common/hooks/useTimeoutCallback';
import type { RefObject } from 'react';
import { useEffect } from 'react';
export const useCallbackOnDragEnter = (cb: () => void, ref: RefObject<HTMLElement>, delay = 300) => {
const [run, cancel] = useTimeoutCallback(cb, delay);
useEffect(() => {
const element = ref.current;
if (!element) {
return;
}
return combine(
dropTargetForElements({
element,
onDragEnter: run,
onDragLeave: cancel,
}),
dropTargetForExternal({
element,
onDragEnter: run,
onDragLeave: cancel,
})
);
}, [cancel, ref, run]);
};

View File

@@ -0,0 +1,21 @@
import { useCallback, useMemo, useRef } from 'react';
export const useTimeoutCallback = (callback: () => void, delay: number, onCancel?: () => void) => {
const timeoutRef = useRef<number | null>(null);
const cancel = useCallback(() => {
if (timeoutRef.current !== null) {
window.clearTimeout(timeoutRef.current);
timeoutRef.current = null;
onCancel?.();
}
}, [onCancel]);
const callWithTimeout = useCallback(() => {
cancel();
timeoutRef.current = window.setTimeout(() => {
callback();
timeoutRef.current = null;
}, delay);
}, [callback, cancel, delay]);
const api = useMemo(() => [callWithTimeout, cancel] as const, [callWithTimeout, cancel]);
return api;
};

View File

@@ -1,10 +1,12 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { IconButton, Tooltip } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useCallbackOnDragEnter } from 'common/hooks/useCallbackOnDragEnter';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { setActiveTab } from 'features/ui/store/uiSlice';
import type { TabName } from 'features/ui/store/uiTypes';
import { forwardRef, memo, type ReactElement, useCallback } from 'react';
import type { ReactElement } from 'react';
import { memo, useCallback, useRef } from 'react';
const sx: SystemStyleObject = {
'&[data-selected=true]': {
@@ -12,32 +14,32 @@ const sx: SystemStyleObject = {
},
};
export const TabButton = memo(
forwardRef(({ tab, icon, label }: { tab: TabName; icon: ReactElement; label: string }, ref) => {
const dispatch = useAppDispatch();
const activeTabName = useAppSelector(selectActiveTab);
const onClick = useCallback(() => {
dispatch(setActiveTab(tab));
}, [dispatch, tab]);
export const TabButton = memo(({ tab, icon, label }: { tab: TabName; icon: ReactElement; label: string }) => {
const dispatch = useAppDispatch();
const ref = useRef<HTMLDivElement>(null);
const activeTabName = useAppSelector(selectActiveTab);
const selectTab = useCallback(() => {
dispatch(setActiveTab(tab));
}, [dispatch, tab]);
useCallbackOnDragEnter(selectTab, ref, 300);
return (
<Tooltip label={label} placement="end">
<IconButton
ref={ref}
p={0}
onClick={onClick}
icon={icon}
size="md"
fontSize="24px"
variant="appTab"
data-selected={activeTabName === tab}
aria-label={label}
data-testid={label}
sx={sx}
/>
</Tooltip>
);
})
);
return (
<Tooltip label={label} placement="end">
<IconButton
p={0}
ref={ref}
onClick={selectTab}
icon={icon}
size="md"
fontSize="24px"
variant="appTab"
data-selected={activeTabName === tab}
aria-label={label}
data-testid={label}
sx={sx}
/>
</Tooltip>
);
});
TabButton.displayName = 'TabButton';