Fix tab focusability

This commit is contained in:
Gregory Schier
2026-01-15 07:17:25 -08:00
parent de47ee19ec
commit 9ddaafb79f

View File

@@ -11,7 +11,16 @@ import {
} from '@dnd-kit/core'; } from '@dnd-kit/core';
import classNames from 'classnames'; import classNames from 'classnames';
import type { ReactNode, Ref } from 'react'; import type { ReactNode, Ref } from 'react';
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import {
forwardRef,
memo,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react';
import { useKeyValue } from '../../../hooks/useKeyValue'; import { useKeyValue } from '../../../hooks/useKeyValue';
import { computeSideForDragMove } from '../../../lib/dnd'; import { computeSideForDragMove } from '../../../lib/dnd';
import { DropMarker } from '../../DropMarker'; import { DropMarker } from '../../DropMarker';
@@ -65,19 +74,22 @@ interface Props {
activeTabKey?: string; activeTabKey?: string;
} }
export const Tabs = forwardRef<TabsRef, Props>(function Tabs({ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
defaultValue, {
onChangeValue: onChangeValueProp, defaultValue,
label, onChangeValue: onChangeValueProp,
children, label,
tabs: originalTabs, children,
className, tabs: originalTabs,
tabListClassName, className,
addBorders, tabListClassName,
layout = 'vertical', addBorders,
storageKey, layout = 'vertical',
activeTabKey, storageKey,
}: Props, forwardedRef: Ref<TabsRef>) { activeTabKey,
}: Props,
forwardedRef: Ref<TabsRef>,
) {
const ref = useRef<HTMLDivElement | null>(null); const ref = useRef<HTMLDivElement | null>(null);
const reorderable = !!storageKey; const reorderable = !!storageKey;
@@ -92,7 +104,7 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs({
// Migrate old format (string[]) to new format (TabsStorage) // Migrate old format (string[]) to new format (TabsStorage)
const storage: TabsStorage = Array.isArray(rawStorage) const storage: TabsStorage = Array.isArray(rawStorage)
? { order: rawStorage, activeTabs: {} } ? { order: rawStorage, activeTabs: {} }
: rawStorage ?? { order: [], activeTabs: {} }; : (rawStorage ?? { order: [], activeTabs: {} });
const savedOrder = storage.order; const savedOrder = storage.order;
@@ -127,11 +139,15 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs({
); );
// Expose imperative methods via ref // Expose imperative methods via ref
useImperativeHandle(forwardedRef, () => ({ useImperativeHandle(
setActiveTab: (value: string) => { forwardedRef,
onChangeValue(value); () => ({
}, setActiveTab: (value: string) => {
}), [onChangeValue]); onChangeValue(value);
},
}),
[onChangeValue],
);
// Helper to save order // Helper to save order
const setSavedOrder = useCallback( const setSavedOrder = useCallback(
@@ -285,13 +301,10 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs({
items.push( items.push(
<div <div
key={`marker-${t.value}`} key={`marker-${t.value}`}
className={classNames( className={classNames('relative', layout === 'vertical' ? 'w-0' : 'h-0')}
'relative',
layout === 'vertical' ? 'w-0' : 'h-0',
)}
> >
<DropMarker orientation={layout === 'vertical' ? 'vertical' : 'horizontal'} /> <DropMarker orientation={layout === 'vertical' ? 'vertical' : 'horizontal'} />
</div> </div>,
); );
} }
@@ -305,7 +318,7 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs({
reorderable={reorderable} reorderable={reorderable}
isDragging={isDragging?.value === t.value} isDragging={isDragging?.value === t.value}
onChangeValue={onChangeValue} onChangeValue={onChangeValue}
/> />,
); );
}); });
return items; return items;
@@ -334,12 +347,7 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs({
> >
{tabButtons} {tabButtons}
{hoveredIndex === tabs.length && ( {hoveredIndex === tabs.length && (
<div <div className={classNames('relative', layout === 'vertical' ? 'w-0' : 'h-0')}>
className={classNames(
'relative',
layout === 'vertical' ? 'w-0' : 'h-0',
)}
>
<DropMarker orientation={layout === 'vertical' ? 'vertical' : 'horizontal'} /> <DropMarker orientation={layout === 'vertical' ? 'vertical' : 'horizontal'} />
</div> </div>
)} )}
@@ -420,6 +428,8 @@ function TabButton({
} = useDraggable({ } = useDraggable({
id: tab.value, id: tab.value,
disabled: !reorderable, disabled: !reorderable,
// The button inside handles focus
attributes: { tabIndex: -1 },
}); });
const { setNodeRef: setDroppableRef } = useDroppable({ const { setNodeRef: setDroppableRef } = useDroppable({
id: tab.value, id: tab.value,
@@ -501,11 +511,7 @@ function TabButton({
); );
} }
return ( return (
<Button <Button leftSlot={tab.leftSlot} rightSlot={tab.rightSlot} {...btnProps}>
leftSlot={tab.leftSlot}
rightSlot={tab.rightSlot}
{...btnProps}
>
{'label' in tab && tab.label ? tab.label : tab.value} {'label' in tab && tab.label ? tab.label : tab.value}
</Button> </Button>
); );