Updates
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Border from "./Border";
|
||||
import Stack from "../layout/Stack";
|
||||
import Row from "../layout/Row";
|
||||
import twuiSlugify from "../utils/slugify";
|
||||
|
||||
export type TWUITabsObject = {
|
||||
title: string;
|
||||
value?: string;
|
||||
content?: React.ReactNode;
|
||||
defaultActive?: boolean;
|
||||
};
|
||||
|
||||
export type TWUI_TOGGLE_PROPS = React.ComponentProps<typeof Stack> & {
|
||||
tabsContentArray: (TWUITabsObject | TWUITabsObject[] | undefined | null)[];
|
||||
tabsBorderProps?: React.ComponentProps<typeof Border>;
|
||||
tabsButtonsWrapperProps?: React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>;
|
||||
centered?: boolean;
|
||||
debounce?: number;
|
||||
/**
|
||||
* React Component to display when switching
|
||||
*/
|
||||
switchComponent?: ReactNode;
|
||||
setActiveValue?: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
changeHandler?: (value: TWUITabsObject) => void;
|
||||
defaultValue?: string | null;
|
||||
hrefUpdate?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Tabs Component
|
||||
* @className twui-tabs-wrapper
|
||||
* @className twui-tab-buttons
|
||||
* @className twui-tab-button-active
|
||||
* @className twui-tab-buttons-wrapper
|
||||
* @className twui-tab-buttons-container
|
||||
* @className twui-tabs-border
|
||||
*/
|
||||
export default function Tabs({
|
||||
tabsContentArray,
|
||||
tabsBorderProps,
|
||||
tabsButtonsWrapperProps,
|
||||
centered,
|
||||
debounce = 100,
|
||||
switchComponent,
|
||||
setActiveValue: existingSetActiveValue,
|
||||
changeHandler,
|
||||
defaultValue,
|
||||
hrefUpdate,
|
||||
...props
|
||||
}: TWUI_TOGGLE_PROPS) {
|
||||
const finalTabsContentArray = tabsContentArray
|
||||
.flat()
|
||||
.filter((ct) => Boolean(ct?.title)) as TWUITabsObject[];
|
||||
|
||||
const values = finalTabsContentArray.map(
|
||||
(obj) => obj.value || twuiSlugify(obj.title),
|
||||
);
|
||||
|
||||
const defaultActiveObj = finalTabsContentArray.find(
|
||||
(ctn) => ctn.defaultActive,
|
||||
);
|
||||
|
||||
const [activeValue, setActiveValue] = React.useState(
|
||||
defaultValue
|
||||
? defaultValue
|
||||
: defaultActiveObj
|
||||
? defaultActiveObj?.value || twuiSlugify(defaultActiveObj.title)
|
||||
: values[0] || undefined,
|
||||
);
|
||||
const [ready, setReady] = React.useState(false);
|
||||
|
||||
const targetContent = finalTabsContentArray.find(
|
||||
(ctn) =>
|
||||
ctn.value == activeValue || twuiSlugify(ctn.title) == activeValue,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!ready) return;
|
||||
existingSetActiveValue?.(activeValue);
|
||||
if (targetContent && activeValue) {
|
||||
changeHandler?.(targetContent);
|
||||
|
||||
if (hrefUpdate) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("tab", activeValue);
|
||||
window.history.pushState({}, "", url);
|
||||
}
|
||||
}
|
||||
}, [activeValue]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (hrefUpdate) {
|
||||
const url = new URL(window.location.href);
|
||||
|
||||
const activeTab = url.searchParams.get("tab");
|
||||
|
||||
if (activeTab && activeValue !== activeTab) {
|
||||
setActiveValue(undefined);
|
||||
setActiveValue(activeTab);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setReady(true);
|
||||
}, 500);
|
||||
} else {
|
||||
setReady(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
{...props}
|
||||
className={twMerge("w-full", "twui-tabs-wrapper", props.className)}
|
||||
>
|
||||
<div
|
||||
{...tabsButtonsWrapperProps}
|
||||
className={twMerge(
|
||||
"w-full",
|
||||
"twui-tab-buttons-wrapper",
|
||||
tabsButtonsWrapperProps?.className,
|
||||
)}
|
||||
>
|
||||
<Border
|
||||
className="p-0 w-full overflow-hidden twui-tabs-border"
|
||||
{...tabsBorderProps}
|
||||
>
|
||||
<Row
|
||||
className={twMerge(
|
||||
"gap-0 items-stretch w-full flex-nowrap overflow-x-auto",
|
||||
centered && "justify-center",
|
||||
"twui-tab-buttons-container",
|
||||
)}
|
||||
>
|
||||
{values.map((value, index) => {
|
||||
const targetObject = finalTabsContentArray.find(
|
||||
(ctn) =>
|
||||
ctn.value == value ||
|
||||
twuiSlugify(ctn.title) == value,
|
||||
);
|
||||
|
||||
const isActive = value == activeValue;
|
||||
|
||||
return (
|
||||
<span
|
||||
className={twMerge(
|
||||
"px-6 py-2 rounded-default -ml-[1px] whitespace-nowrap",
|
||||
isActive
|
||||
? "bg-primary dark:bg-primary-dark text-white outline-none twui-tab-button-active"
|
||||
: "text-slate-400 dark:text-white/40 hover:text-slate-800 dark:hover:text-white" +
|
||||
" cursor-pointer",
|
||||
"twui-tab-buttons",
|
||||
)}
|
||||
onClick={() => {
|
||||
setActiveValue(undefined);
|
||||
setTimeout(() => {
|
||||
setActiveValue(value);
|
||||
}, debounce);
|
||||
}}
|
||||
key={index}
|
||||
>
|
||||
{targetObject?.title}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Border>
|
||||
</div>
|
||||
{activeValue ? targetContent?.content : switchComponent || null}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user