Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
'use client'
|
|
|
|
import type { TabsProps } from '@/components/heroui/Tabs'
|
|
import { Tabs } from '@/components/heroui/Tabs'
|
|
import { cn } from '@/lib/cn'
|
|
|
|
export type AppTabsSurface = 'consumer' | 'admin'
|
|
|
|
type TabsClassNames = NonNullable<TabsProps['classNames']>
|
|
|
|
const TAB_SLOTS = ['base', 'tabList', 'tab', 'tabContent', 'cursor', 'panel', 'tabWrapper'] as const
|
|
|
|
const SURFACE_CLASS_NAMES: Record<AppTabsSurface, TabsClassNames> = {
|
|
consumer: {
|
|
base: 'w-full',
|
|
tabWrapper: 'h-11 bg-secondary-50 [&>.tabs__list-container__scroll-prev]:hidden [&>.tabs__list-container__scroll-next]:hidden',
|
|
cursor: 'bg-primary',
|
|
tab: 'group min-w-fit px-4 p-2 text-[14px] font-medium text-black data-[selected=true]:!text-white',
|
|
tabContent: 'flex items-center gap-2 text-inherit',
|
|
},
|
|
admin: {
|
|
base: 'w-full overflow-x-auto rounded-2xl border border-secondary-40 bg-white p-2',
|
|
tabList: 'gap-1 bg-transparent p-0',
|
|
cursor: 'bg-primary-600 shadow-none',
|
|
tab: 'h-10 px-4',
|
|
tabContent: 'font-medium text-secondary-30 group-data-[selected=true]:text-white',
|
|
panel: 'px-0 pt-4',
|
|
},
|
|
}
|
|
|
|
const SURFACE_FULL_WIDTH: Record<AppTabsSurface, boolean> = {
|
|
consumer: true,
|
|
admin: false,
|
|
}
|
|
|
|
const SURFACE_VARIANT: Record<AppTabsSurface, TabsProps['variant']> = {
|
|
consumer: undefined,
|
|
admin: 'light',
|
|
}
|
|
|
|
type AppTabsProps = Omit<TabsProps, 'variant'> & {
|
|
/** Which surface's visual language to render. @default 'consumer' */
|
|
surface?: AppTabsSurface
|
|
}
|
|
|
|
/**
|
|
* Global tabs component — every tab bar in the app, admin or consumer,
|
|
* should render through this instead of `@/components/heroui/Tabs` directly. `surface`
|
|
* picks the visual language (`consumer`: pill tabs with a soft active
|
|
* background; `admin`: sliding-cursor tabs). Any `classNames` slots passed
|
|
* in merge on top of the surface defaults rather than replacing them.
|
|
* Pairs with `useQueryTab` for tabs that persist their selection in the URL.
|
|
*/
|
|
const AppTabs = ({ surface = 'consumer', classNames, fullWidth, ...props }: AppTabsProps) => {
|
|
const surfaceClassNames = SURFACE_CLASS_NAMES[surface]
|
|
|
|
const mergedClassNames = Object.fromEntries(
|
|
TAB_SLOTS.map((slot) => [slot, cn(surfaceClassNames[slot], classNames?.[slot])]).filter(([, value]) => value)
|
|
) as TabsClassNames
|
|
|
|
return (
|
|
<Tabs
|
|
classNames={mergedClassNames}
|
|
fullWidth={fullWidth ?? SURFACE_FULL_WIDTH[surface]}
|
|
variant={SURFACE_VARIANT[surface]}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export default AppTabs
|