admin/components/ui/AppTabs.tsx
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

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