Skip to content

Instantly share code, notes, and snippets.

@jasikpark
Created April 25, 2023 18:53
Show Gist options
  • Save jasikpark/739f528ec4e3a8e8e5e05b5617333dc4 to your computer and use it in GitHub Desktop.
Save jasikpark/739f528ec4e3a8e8e5e05b5617333dc4 to your computer and use it in GitHub Desktop.
Example of a polymorphic ariakit Tab component that can be a button or link
import { Tab as AriakitTab, type TabProps } from 'ariakit/tab';
import cn from 'classnames';
import { useLayoutEffect, useRef } from 'react';
import { useRect } from '@hooks/useRect';
import { useAnimatedTabs } from '../context/useAnimatedTabs';
import styles from './Tab.module.css';
/**
* A tab that goes at the top of a tab panel, which a user clicks on to change the UI underneath.
* This is a button by default, but can also be a react-router Link, if the `to` and `as` props are provided.
*/
export function Tab<T extends 'button' | 'a' = 'button'>({ as, ...props }: TabProps<T>) {
// get the state and style changing function from context
const { state, setActiveRect } = useAnimatedTabs();
// measure the size of our element, only listen to rect if active
const ref = useRef<HTMLButtonElement & HTMLAnchorElement>(null);
// @ts-expect-error Unsure how to type-narrow this depending on the value of `as`
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const isSelected = state.selectedId === ref.current?.id || ref.current?.href?.includes(state.selectedId);
const rect = useRect(ref, { observe: isSelected });
// callup to set styles whenever we're active
useLayoutEffect(() => {
if (isSelected) {
setActiveRect(rect);
}
}, [isSelected, rect, setActiveRect]);
return (
// @ts-expect-error generics are hard.
<AriakitTab ref={ref} as={as} className={cn(styles.Tab, { [styles.Tab___isActive]: isSelected })} {...props} />
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment