Skip to content

Commit

Permalink
Tabs: update indicator more reactively (#66207)
Browse files Browse the repository at this point in the history
Co-authored-by: ciampo <[email protected]>
Co-authored-by: tyxla <[email protected]>
Co-authored-by: DaniGuardiola <[email protected]>
  • Loading branch information
4 people authored Oct 18, 2024
1 parent 240180a commit fa12080
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 13 deletions.
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- `RadioGroup`: Fix arrow key navigation in RTL ([#66202](https://github.com/WordPress/gutenberg/pull/66202)).
- `Tabs` and `TabPanel`: Fix arrow key navigation in RTL ([#66201](https://github.com/WordPress/gutenberg/pull/66201)).
- `Tabs`: override tablist's tabindex only when necessary ([#66209](https://github.com/WordPress/gutenberg/pull/66209)).
- `Tabs`: update indicator more reactively ([#66207](https://github.com/WordPress/gutenberg/pull/66207)).

### Enhancements

Expand Down
32 changes: 21 additions & 11 deletions packages/components/src/tabs/tablist.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* External dependencies
*/
import { useStoreState } from '@ariakit/react';
import * as Ariakit from '@ariakit/react';
import clsx from 'clsx';

/**
* WordPress dependencies
Expand All @@ -14,11 +15,10 @@ import { useMergeRefs } from '@wordpress/compose';
* Internal dependencies
*/
import type { TabListProps } from './types';
import { useTabsContext } from './context';
import { StyledTabList } from './styles';
import type { WordPressComponentProps } from '../context';
import clsx from 'clsx';
import type { ElementOffsetRect } from '../utils/element-rect';
import { useTabsContext } from './context';
import { StyledTabList } from './styles';
import { useTrackElementOffsetRect } from '../utils/element-rect';
import { useTrackOverflow } from './use-track-overflow';
import { useAnimatedOffsetRect } from '../utils/hooks/use-animated-offset-rect';
Expand Down Expand Up @@ -62,15 +62,25 @@ export const TabList = forwardRef<
>( function TabList( { children, ...otherProps }, ref ) {
const { store } = useTabsContext() ?? {};

const selectedId = useStoreState( store, 'selectedId' );
const activeId = useStoreState( store, 'activeId' );
const selectOnMove = useStoreState( store, 'selectOnMove' );
const items = useStoreState( store, 'items' );
const selectedId = Ariakit.useStoreState( store, 'selectedId' );
const activeId = Ariakit.useStoreState( store, 'activeId' );
const selectOnMove = Ariakit.useStoreState( store, 'selectOnMove' );
const items = Ariakit.useStoreState( store, 'items' );
const [ parent, setParent ] = useState< HTMLElement >();
const refs = useMergeRefs( [ ref, setParent ] );
const selectedRect = useTrackElementOffsetRect(
store?.item( selectedId )?.element
);

const selectedItem = store?.item( selectedId );
const renderedItems = Ariakit.useStoreState( store, 'renderedItems' );

const selectedItemIndex =
renderedItems && selectedItem
? renderedItems.indexOf( selectedItem )
: -1;
// Use selectedItemIndex as a dependency to force recalculation when the
// selected item index changes (elements are swapped / added / removed).
const selectedRect = useTrackElementOffsetRect( selectedItem?.element, [
selectedItemIndex,
] );

// Track overflow to show scroll hints.
const overflow = useTrackOverflow( parent, {
Expand Down
17 changes: 15 additions & 2 deletions packages/components/src/utils/element-rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,17 @@ const POLL_RATE = 100;
* milliseconds until it succeeds.
*/
export function useTrackElementOffsetRect(
targetElement: HTMLElement | undefined | null
targetElement: HTMLElement | undefined | null,
deps: unknown[] = []
) {
const [ indicatorPosition, setIndicatorPosition ] =
useState< ElementOffsetRect >( NULL_ELEMENT_OFFSET_RECT );
const intervalRef = useRef< ReturnType< typeof setInterval > >();

const measure = useEvent( () => {
if ( targetElement ) {
// Check that the targetElement is still attached to the DOM, in case
// it was removed since the last `measure` call.
if ( targetElement && targetElement.isConnected ) {
const elementOffsetRect = getElementOffsetRect( targetElement );
if ( elementOffsetRect ) {
setIndicatorPosition( elementOffsetRect );
Expand Down Expand Up @@ -171,6 +174,16 @@ export function useTrackElementOffsetRect(
}
}, [ setElement, targetElement ] );

// Escape hatch to force a remeasurement when something else changes rather
// than the target elements' ref or size (for example, the target element
// can change its position within the tablist).
useLayoutEffect( () => {
measure();
// `measure` is a stable function, so it's safe to omit it from the deps array.
// deps can't be statically analyzed by ESLint
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps );

return indicatorPosition;
}

Expand Down

0 comments on commit fa12080

Please sign in to comment.