111
common/resources/client/ui/tabs/tab.tsx
Executable file
111
common/resources/client/ui/tabs/tab.tsx
Executable file
@@ -0,0 +1,111 @@
|
||||
import React, {JSXElementConstructor, ReactNode, useContext} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {useFocusManager} from '@react-aria/focus';
|
||||
import {TabContext} from './tabs-context';
|
||||
import {LinkProps} from 'react-router-dom';
|
||||
|
||||
export interface TabProps {
|
||||
className?: string;
|
||||
index?: number;
|
||||
children: ReactNode;
|
||||
isDisabled?: boolean;
|
||||
padding?: string;
|
||||
elementType?: 'button' | 'a' | JSXElementConstructor<any>;
|
||||
to?: LinkProps['to'];
|
||||
relative?: LinkProps['relative'];
|
||||
replace?: LinkProps['replace'];
|
||||
width?: string;
|
||||
}
|
||||
export function Tab({
|
||||
index,
|
||||
className,
|
||||
isDisabled,
|
||||
children,
|
||||
padding: paddingProp,
|
||||
elementType = 'button',
|
||||
to,
|
||||
relative,
|
||||
width = 'min-w-min',
|
||||
}: TabProps) {
|
||||
const {
|
||||
selectedTab,
|
||||
setSelectedTab,
|
||||
tabsRef,
|
||||
size = 'md',
|
||||
id,
|
||||
} = useContext(TabContext);
|
||||
const isSelected = index === selectedTab;
|
||||
const focusManager = useFocusManager();
|
||||
const padding = paddingProp || (size === 'sm' ? 'px-12' : 'px-18');
|
||||
|
||||
const mergedClassname = clsx(
|
||||
'tracking-wide overflow-hidden capitalize text-sm flex items-center justify-center outline-none transition-colors',
|
||||
'focus-visible:ring focus-visible:ring-2 ring-inset rounded whitespace-nowrap cursor-pointer',
|
||||
width,
|
||||
textColor({isDisabled, isSelected}),
|
||||
className,
|
||||
size === 'md' && `${padding} h-48`,
|
||||
size === 'sm' && `${padding} h-32`,
|
||||
isDisabled && 'pointer-events-none',
|
||||
);
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
focusManager?.focusPrevious();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
focusManager?.focusNext();
|
||||
break;
|
||||
case 'Home':
|
||||
focusManager?.focusFirst();
|
||||
break;
|
||||
case 'End':
|
||||
focusManager?.focusLast();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const tabIndex = isSelected ? 0 : -1;
|
||||
const Element = elementType;
|
||||
|
||||
return (
|
||||
<Element
|
||||
disabled={isDisabled}
|
||||
id={`${id}-${index}-tab`}
|
||||
aria-controls={`${id}-${index}-tabpanel`}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={isSelected}
|
||||
tabIndex={isDisabled ? undefined : tabIndex}
|
||||
onKeyDown={onKeyDown}
|
||||
onClick={() => {
|
||||
setSelectedTab(index!);
|
||||
}}
|
||||
to={to}
|
||||
relative={relative}
|
||||
className={mergedClassname}
|
||||
ref={(el: HTMLElement) => {
|
||||
if (tabsRef.current && el) {
|
||||
tabsRef.current[index!] = el;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Element>
|
||||
);
|
||||
}
|
||||
|
||||
interface TextColorProps {
|
||||
isDisabled?: boolean;
|
||||
isSelected: boolean;
|
||||
}
|
||||
function textColor({isDisabled, isSelected}: TextColorProps): string {
|
||||
if (isDisabled) {
|
||||
return 'text-disabled cursor-default';
|
||||
}
|
||||
if (isSelected) {
|
||||
return 'text-primary';
|
||||
}
|
||||
return 'text-muted hover:text-main';
|
||||
}
|
||||
Reference in New Issue
Block a user