⚡️ feat: create tabs component
This commit is contained in:
parent
b5eef95d15
commit
d0263f547e
53
packages/frontend/src/components/shared/Tabs/Tabs.theme.ts
Normal file
53
packages/frontend/src/components/shared/Tabs/Tabs.theme.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
|
||||
export type TabsVariants = VariantProps<typeof tabsTheme>;
|
||||
|
||||
export const tabsTheme = tv({
|
||||
slots: {
|
||||
root: ['flex', 'flex-col', 'w-full'],
|
||||
triggerWrapper: [
|
||||
'px-1',
|
||||
'pb-5',
|
||||
'text-elements-low-em',
|
||||
'border-b-2',
|
||||
'border-transparent',
|
||||
'hover:border-border-interactive/10',
|
||||
'hover:text-elements-mid-em',
|
||||
'focus-within:border-border-interactive/10',
|
||||
'data-[state=active]:font-medium',
|
||||
'data-[state=active]:text-elements-high-em',
|
||||
'data-[state=active]:border-elements-high-em',
|
||||
],
|
||||
trigger: [
|
||||
'flex',
|
||||
'gap-1.5',
|
||||
'cursor-default',
|
||||
'select-none',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'outline-none',
|
||||
'leading-none',
|
||||
'tracking-[-0.006em]',
|
||||
'rounded-md',
|
||||
'focus-ring',
|
||||
],
|
||||
triggerList: [
|
||||
'flex',
|
||||
'shrink-0',
|
||||
'gap-5',
|
||||
'border-b',
|
||||
'border-border-interactive/10',
|
||||
],
|
||||
content: ['text-elements-high-em', 'grow', 'outline-none', 'tab-content'],
|
||||
},
|
||||
variants: {
|
||||
fillWidth: {
|
||||
true: {
|
||||
trigger: ['flex-1'],
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
fillWidth: false,
|
||||
},
|
||||
});
|
42
packages/frontend/src/components/shared/Tabs/Tabs.tsx
Normal file
42
packages/frontend/src/components/shared/Tabs/Tabs.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React, { type ComponentPropsWithoutRef } from 'react';
|
||||
import { Root as TabsRoot } from '@radix-ui/react-tabs';
|
||||
|
||||
import { tabsTheme } from './Tabs.theme';
|
||||
import TabsContent from './TabsContent';
|
||||
import TabsList from './TabsList';
|
||||
import TabsTrigger from './TabsTrigger';
|
||||
import TabsProvider, { TabsProviderProps } from './TabsProvider';
|
||||
|
||||
export interface TabsProps extends ComponentPropsWithoutRef<typeof TabsRoot> {
|
||||
/**
|
||||
* The configuration for the tabs component.
|
||||
*/
|
||||
config?: TabsProviderProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that allows users to switch between different tabs.
|
||||
* @returns JSX element representing the tabs component.
|
||||
*/
|
||||
export const Tabs = ({ config, className, ...props }: TabsProps) => {
|
||||
const { root } = tabsTheme(config);
|
||||
|
||||
return (
|
||||
<TabsProvider {...config}>
|
||||
<TabsRoot {...props} className={root({ className })} />
|
||||
</TabsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Assigns the TabsTrigger class to the Trigger property of the Tabs object.
|
||||
*/
|
||||
Tabs.Trigger = TabsTrigger;
|
||||
/**
|
||||
* Assigns the TabsList object to the List property of the Tabs object.
|
||||
*/
|
||||
Tabs.List = TabsList;
|
||||
/**
|
||||
* Assigns the TabsContent component to the Content property of the Tabs component.
|
||||
*/
|
||||
Tabs.Content = TabsContent;
|
@ -0,0 +1,26 @@
|
||||
import React, {
|
||||
forwardRef,
|
||||
type ComponentPropsWithoutRef,
|
||||
type ElementRef,
|
||||
} from 'react';
|
||||
import { Content } from '@radix-ui/react-tabs';
|
||||
|
||||
import { tabsTheme } from '../Tabs.theme';
|
||||
|
||||
export interface TabsContentProps
|
||||
extends ComponentPropsWithoutRef<typeof Content> {}
|
||||
|
||||
/**
|
||||
* A component that represents the content of the tabs component.
|
||||
*/
|
||||
const TabsContent = forwardRef<ElementRef<typeof Content>, TabsContentProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
const { content } = tabsTheme();
|
||||
return <Content ref={ref} className={content({ className })} {...props} />;
|
||||
},
|
||||
);
|
||||
|
||||
// Assigns the display name to the TabsContent component.
|
||||
TabsContent.displayName = 'TabsContent';
|
||||
|
||||
export { TabsContent };
|
@ -0,0 +1,3 @@
|
||||
import { TabsContent } from './TabsContent';
|
||||
|
||||
export default TabsContent;
|
@ -0,0 +1,25 @@
|
||||
import React, {
|
||||
forwardRef,
|
||||
type ComponentPropsWithoutRef,
|
||||
type ElementRef,
|
||||
} from 'react';
|
||||
import { List } from '@radix-ui/react-tabs';
|
||||
|
||||
import { tabsTheme } from 'components/shared/Tabs/Tabs.theme';
|
||||
|
||||
export interface TabsListProps extends ComponentPropsWithoutRef<typeof List> {}
|
||||
|
||||
/**
|
||||
* A component that represents the list of tabs.
|
||||
*/
|
||||
const TabsList = forwardRef<ElementRef<typeof List>, TabsListProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
const { triggerList } = tabsTheme({ className });
|
||||
return <List ref={ref} className={triggerList({ className })} {...props} />;
|
||||
},
|
||||
);
|
||||
|
||||
// Assigns the display name to the TabsList component.
|
||||
TabsList.displayName = 'TabsList';
|
||||
|
||||
export { TabsList };
|
@ -0,0 +1,3 @@
|
||||
import { TabsList } from './TabsList';
|
||||
|
||||
export default TabsList;
|
@ -0,0 +1,43 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
type PropsWithChildren,
|
||||
} from 'react';
|
||||
import { TabsVariants } from './Tabs.theme';
|
||||
|
||||
export interface TabsProviderProps extends Partial<TabsVariants> {}
|
||||
|
||||
type TabsProviderContext = ReturnType<typeof useTabsValues>;
|
||||
|
||||
const TabsContext = createContext<Partial<TabsProviderContext>>({});
|
||||
|
||||
// For inferring return type
|
||||
const useTabsValues = (props: TabsProviderProps) => {
|
||||
return props;
|
||||
};
|
||||
|
||||
/**
|
||||
* A provider component that allows users to switch between different tabs.
|
||||
* @returns JSX element representing the tabs provider component.
|
||||
*/
|
||||
export const TabsProvider = ({
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<TabsProviderProps>): JSX.Element => {
|
||||
const values = useTabsValues(props);
|
||||
return <TabsContext.Provider value={values}>{children}</TabsContext.Provider>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the context of the tabs provider.
|
||||
* @returns The context of the tabs provider.
|
||||
*/
|
||||
export const useTabs = () => {
|
||||
const context = useContext(TabsContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useTabs was used outside of its Provider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export default TabsProvider;
|
@ -0,0 +1,49 @@
|
||||
import React, {
|
||||
forwardRef,
|
||||
type ComponentPropsWithoutRef,
|
||||
type ElementRef,
|
||||
type PropsWithChildren,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import { Trigger } from '@radix-ui/react-tabs';
|
||||
|
||||
import { tabsTheme } from 'components/shared/Tabs/Tabs.theme';
|
||||
import { useTabs } from 'components/shared/Tabs/TabsProvider';
|
||||
|
||||
export interface TabsTriggerProps
|
||||
extends ComponentPropsWithoutRef<typeof Trigger> {
|
||||
/**
|
||||
* The icon to display in the trigger.
|
||||
*/
|
||||
icon?: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that represents the trigger for the tabs component.
|
||||
*/
|
||||
const TabsTrigger = forwardRef<
|
||||
ElementRef<typeof Trigger>,
|
||||
PropsWithChildren<TabsTriggerProps>
|
||||
>(({ className, icon, children, ...props }, ref) => {
|
||||
const config = useTabs();
|
||||
const { triggerWrapper, trigger } = tabsTheme(config);
|
||||
|
||||
return (
|
||||
<Trigger
|
||||
ref={ref}
|
||||
tabIndex={-1}
|
||||
className={triggerWrapper({ className })}
|
||||
{...props}
|
||||
>
|
||||
{/* Need to add button in the trigger children because there's focus state inside the children */}
|
||||
<button className={trigger()}>
|
||||
{children}
|
||||
{icon}
|
||||
</button>
|
||||
</Trigger>
|
||||
);
|
||||
});
|
||||
|
||||
TabsTrigger.displayName = 'TabsTrigger';
|
||||
|
||||
export { TabsTrigger };
|
@ -0,0 +1,3 @@
|
||||
import { TabsTrigger } from './TabsTrigger';
|
||||
|
||||
export default TabsTrigger;
|
1
packages/frontend/src/components/shared/Tabs/index.ts
Normal file
1
packages/frontend/src/components/shared/Tabs/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Tabs';
|
Loading…
Reference in New Issue
Block a user