This commit is contained in:
@@ -1,139 +1,111 @@
|
|||||||
'use client';
|
"use client";
|
||||||
|
|
||||||
import React, { ReactElement, ReactNode, useCallback, useState } from 'react'
|
import { glassSurfaceNav } from "@/lib/styles/glassSurface";
|
||||||
import HorizontalScrollView from '../listview/HorizontalScrollView'
|
import clsx from "clsx";
|
||||||
import { TabHeader, TabItemProps } from './TabHeader';
|
import Image from "next/image";
|
||||||
import clsx from 'clsx';
|
import React, { ReactElement, ReactNode, useCallback, useState } from "react";
|
||||||
import TabHeaderRenderer from './TabHeaderRenderer';
|
import HorizontalScrollView from "../listview/HorizontalScrollView";
|
||||||
import Image from 'next/image';
|
import { TabHeader, TabItemProps } from "./TabHeader";
|
||||||
import { glassSurfaceNav } from '@/lib/styles/glassSurface';
|
import TabHeaderRenderer from "./TabHeaderRenderer";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
itemRenderer?: (child: ReactNode, index: number) => ReactElement;
|
itemRenderer?: (child: ReactNode, index: number) => ReactElement;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
className?: TabContainerClassName;
|
className?: TabContainerClassName;
|
||||||
changeType?: TabContainerRenderType;
|
changeType?: TabContainerRenderType;
|
||||||
defaultIndex?: number;
|
defaultIndex?: number;
|
||||||
selectedTab?: number;
|
selectedTab?: number;
|
||||||
onTabChange?: (index: number) => void;
|
onTabChange?: (index: number) => void;
|
||||||
} & Omit<React.OlHTMLAttributes<HTMLUListElement>, 'className'>;
|
} & Omit<React.OlHTMLAttributes<HTMLUListElement>, "className">;
|
||||||
|
|
||||||
export enum TabContainerRenderType {
|
export enum TabContainerRenderType {
|
||||||
RENDERER,
|
RENDERER,
|
||||||
VISIBILITY,
|
VISIBILITY,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TabContainerClassName = {
|
export type TabContainerClassName = {
|
||||||
wrapper?: string;
|
wrapper?: string;
|
||||||
scrollView?: string;
|
scrollView?: string;
|
||||||
header?: string;
|
header?: string;
|
||||||
headerDeactive?: string;
|
headerDeactive?: string;
|
||||||
headerActive?: string;
|
headerActive?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
iconDeactive?: string;
|
iconDeactive?: string;
|
||||||
iconActive?: string;
|
iconActive?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
titleDeactive?: string;
|
titleDeactive?: string;
|
||||||
titleActive?: string
|
titleActive?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const TabContainerClassNames: TabContainerClassName = {
|
export const TabContainerClassNames: TabContainerClassName = {
|
||||||
wrapper: 'h-full',
|
wrapper: "h-full",
|
||||||
scrollView: glassSurfaceNav('h-[72px] gradient-border !pt-3 !pb-2 w-full overflow-y-hidden justify-center rounded-[32px] inline-flex items-center gap-10'),
|
scrollView: glassSurfaceNav("h-[72px] gradient-border !pt-3 !pb-2 w-full overflow-y-hidden justify-center rounded-[32px] inline-flex items-center gap-10 !shadow-none"),
|
||||||
header: '',
|
header: "",
|
||||||
headerDeactive: '',
|
headerDeactive: "",
|
||||||
headerActive: '',
|
headerActive: "",
|
||||||
icon: 'text-xs transition-all duration-150',
|
icon: "text-xs transition-all duration-150",
|
||||||
iconDeactive: 'stroke-disabled-text',
|
iconDeactive: "stroke-disabled-text",
|
||||||
iconActive: 'stroke-primary dark:stroke-foreground',
|
iconActive: "stroke-primary dark:stroke-foreground",
|
||||||
title: 'text-xs transition-all duration-150',
|
title: "text-xs transition-all duration-150",
|
||||||
titleDeactive: 'text-disabled-text',
|
titleDeactive: "text-disabled-text",
|
||||||
titleActive: 'text-primary dark:text-foreground'
|
titleActive: "text-primary dark:text-foreground",
|
||||||
|
};
|
||||||
|
|
||||||
|
function TabContainer({
|
||||||
|
className = TabContainerClassNames,
|
||||||
|
changeType = TabContainerRenderType.RENDERER,
|
||||||
|
defaultIndex = 0,
|
||||||
|
selectedTab: controlledSelectedTab,
|
||||||
|
onTabChange,
|
||||||
|
itemRenderer,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: Props) {
|
||||||
|
const [internalSelectedTab, setInternalSelectedTab] = useState(defaultIndex);
|
||||||
|
const selectedTab = controlledSelectedTab !== undefined ? controlledSelectedTab : internalSelectedTab;
|
||||||
|
|
||||||
|
const updateTab = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
if (onTabChange) {
|
||||||
|
onTabChange(index);
|
||||||
|
} else {
|
||||||
|
setInternalSelectedTab(index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onTabChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = React.Children.toArray(children).filter((child): child is ReactElement<TabItemProps> => React.isValidElement(child) && child.type === TabHeader);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className.wrapper}>
|
||||||
|
<HorizontalScrollView className={className.scrollView} itemRenderer={itemRenderer} {...restProps}>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
const { title, imageSrc, icon }: TabItemProps = item.props;
|
||||||
|
return (
|
||||||
|
<TabHeaderRenderer key={index} className={clsx(className.header, index === selectedTab ? className.headerActive : className.headerDeactive)} onClick={() => updateTab(index)}>
|
||||||
|
{imageSrc && <Image priority src={imageSrc} width={24} height={24} alt="category image" />}
|
||||||
|
{icon && <span className={clsx(className.icon, index === selectedTab ? className.iconActive : className.iconDeactive)}>{icon}</span>}
|
||||||
|
<h6 className={clsx(className.title, index === selectedTab ? className.titleActive : className.titleDeactive)}>{title}</h6>
|
||||||
|
</TabHeaderRenderer>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
{changeType === TabContainerRenderType.RENDERER && <div>{items[selectedTab]?.props?.viewRenderer ?? <div></div>}</div>}
|
||||||
|
{changeType === TabContainerRenderType.VISIBILITY && (
|
||||||
|
<>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className={clsx(index === selectedTab ? "visible" : "hidden")}>
|
||||||
|
{item.props.viewRenderer}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabContainer({ className = TabContainerClassNames, changeType = TabContainerRenderType.RENDERER, defaultIndex = 0, selectedTab: controlledSelectedTab, onTabChange, itemRenderer, children, ...restProps }: Props) {
|
export default TabContainer;
|
||||||
|
|
||||||
const [internalSelectedTab, setInternalSelectedTab] = useState(defaultIndex);
|
|
||||||
const selectedTab = controlledSelectedTab !== undefined ? controlledSelectedTab : internalSelectedTab;
|
|
||||||
|
|
||||||
const updateTab = useCallback((index: number) => {
|
|
||||||
if (onTabChange) {
|
|
||||||
onTabChange(index);
|
|
||||||
} else {
|
|
||||||
setInternalSelectedTab(index);
|
|
||||||
}
|
|
||||||
}, [onTabChange]);
|
|
||||||
|
|
||||||
const items = React.Children.toArray(children).filter(
|
|
||||||
(child): child is ReactElement<TabItemProps> =>
|
|
||||||
React.isValidElement(child) && child.type === TabHeader
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className.wrapper}>
|
|
||||||
<HorizontalScrollView
|
|
||||||
className={className.scrollView}
|
|
||||||
itemRenderer={itemRenderer}
|
|
||||||
{...restProps}>
|
|
||||||
{items.map((item, index) => {
|
|
||||||
const { title, imageSrc, icon }: TabItemProps = item.props;
|
|
||||||
return (
|
|
||||||
<TabHeaderRenderer
|
|
||||||
key={index}
|
|
||||||
className={clsx(
|
|
||||||
className.header,
|
|
||||||
index === selectedTab ? className.headerActive : className.headerDeactive
|
|
||||||
)}
|
|
||||||
onClick={() => updateTab(index)}
|
|
||||||
>
|
|
||||||
{imageSrc &&
|
|
||||||
<Image
|
|
||||||
priority
|
|
||||||
src={imageSrc}
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
alt="category image"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{icon &&
|
|
||||||
<span className={clsx(
|
|
||||||
className.icon,
|
|
||||||
index === selectedTab ? className.iconActive : className.iconDeactive
|
|
||||||
)}>
|
|
||||||
{icon}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
<h6 className={clsx(
|
|
||||||
className.title,
|
|
||||||
index === selectedTab ? className.titleActive : className.titleDeactive
|
|
||||||
)}>
|
|
||||||
{title}
|
|
||||||
</h6>
|
|
||||||
</TabHeaderRenderer>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</HorizontalScrollView>
|
|
||||||
|
|
||||||
{changeType === TabContainerRenderType.RENDERER &&
|
|
||||||
<div>
|
|
||||||
{items[selectedTab]?.props?.viewRenderer ?? <div></div>}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{changeType === TabContainerRenderType.VISIBILITY &&
|
|
||||||
<>
|
|
||||||
{items.map((item, index) => {
|
|
||||||
return (
|
|
||||||
<div key={index} className={clsx(
|
|
||||||
index === selectedTab ? 'visible' : 'hidden'
|
|
||||||
)}>
|
|
||||||
{item.props.viewRenderer}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TabContainer
|
|
||||||
|
|||||||
Reference in New Issue
Block a user