229 lines
7.7 KiB
TypeScript
229 lines
7.7 KiB
TypeScript
import React, { ComponentProps, ReactNode } from "react";
|
|
import Link from "../layout/Link";
|
|
import Divider from "../layout/Divider";
|
|
import Row from "../layout/Row";
|
|
import lowerToTitleCase from "../utils/lower-to-title-case";
|
|
import { twMerge } from "tailwind-merge";
|
|
import { ChevronLeft } from "lucide-react";
|
|
import Button from "../layout/Button";
|
|
|
|
type LinkObject = {
|
|
title: string;
|
|
path: string;
|
|
};
|
|
|
|
type Props = {
|
|
excludeRegexMatch?: RegExp;
|
|
linkProps?: ComponentProps<typeof Link>;
|
|
currentLinkProps?: ComponentProps<typeof Link>;
|
|
dividerProps?: ComponentProps<typeof Divider>;
|
|
backButtonProps?: ComponentProps<typeof Button>;
|
|
backButton?: boolean;
|
|
pageUrl?: string;
|
|
currentTitle?: string;
|
|
skipHome?: boolean;
|
|
divider?: ReactNode;
|
|
};
|
|
|
|
/**
|
|
* # TWUI Breadcrumbs
|
|
* @className `twui-breadcrumb-link`
|
|
* @className `twui-current-breadcrumb-wrapper`
|
|
* @className `twui-breadcrumbs-divider`
|
|
* @className `twui-breadcrumbs-back-button`
|
|
*/
|
|
export default function Breadcrumbs({
|
|
excludeRegexMatch,
|
|
linkProps,
|
|
currentLinkProps,
|
|
dividerProps,
|
|
backButton,
|
|
backButtonProps,
|
|
pageUrl,
|
|
currentTitle,
|
|
skipHome,
|
|
divider,
|
|
}: Props) {
|
|
const [links, setLinks] = React.useState<LinkObject[] | null>(
|
|
pageUrl
|
|
? twuiBreadcrumbsGenerateLinksFromUrl({ url: pageUrl, skipHome })
|
|
: null
|
|
);
|
|
|
|
React.useEffect(() => {
|
|
if (links) return;
|
|
|
|
let pathname = window.location.pathname;
|
|
|
|
let validPathLinks = twuiBreadcrumbsGenerateLinksFromUrl({
|
|
url: pathname,
|
|
excludeRegexMatch,
|
|
skipHome,
|
|
});
|
|
|
|
setLinks(validPathLinks);
|
|
|
|
return function () {
|
|
setLinks(null);
|
|
};
|
|
}, []);
|
|
|
|
if (!links?.[1]) {
|
|
return <React.Fragment></React.Fragment>;
|
|
}
|
|
|
|
return (
|
|
<nav
|
|
className={twMerge(
|
|
"overflow-x-auto",
|
|
"twui-current-breadcrumb-wrapper"
|
|
)}
|
|
aria-label="Breadcrumb"
|
|
>
|
|
<Row
|
|
className={twMerge(
|
|
"gap-4 flex-nowrap whitespace-nowrap overflow-x-auto overflow-y-hidden w-full"
|
|
)}
|
|
>
|
|
{backButton && (
|
|
<React.Fragment>
|
|
<Button
|
|
variant="ghost"
|
|
color="gray"
|
|
{...backButtonProps}
|
|
className={twMerge(
|
|
"p-1 -my-2 -mx-2",
|
|
"twui-breadcrumbs-back-button",
|
|
backButtonProps?.className
|
|
)}
|
|
onClick={(e) => {
|
|
window.history.back();
|
|
backButtonProps?.onClick?.(e);
|
|
}}
|
|
title="Breadcrumbs Back Button"
|
|
beforeIcon={<ChevronLeft size={20} />}
|
|
/>
|
|
{divider || (
|
|
<Divider
|
|
vertical
|
|
className={twMerge(
|
|
"twui-breadcrumbs-divider",
|
|
dividerProps?.className
|
|
)}
|
|
/>
|
|
)}
|
|
</React.Fragment>
|
|
)}
|
|
|
|
{links.map((linkObject, index, array) => {
|
|
const isTarget = array.length - 1 == index;
|
|
|
|
if (index === links.length - 1) {
|
|
return (
|
|
<Link
|
|
key={index}
|
|
href={linkObject.path}
|
|
{...linkProps}
|
|
{...(isTarget ? currentLinkProps : {})}
|
|
className={twMerge(
|
|
"text-primary-text/50 dark:text-primary-dark-text/50 text-xs",
|
|
"max-w-[200px] text-ellipsis overflow-hidden",
|
|
isTarget ? "current" : "",
|
|
"twui-breadcrumb-link",
|
|
linkProps?.className,
|
|
isTarget && currentLinkProps?.className
|
|
)}
|
|
title={
|
|
currentLinkProps?.title || linkObject.title
|
|
}
|
|
>
|
|
{currentTitle || linkObject.title}
|
|
</Link>
|
|
);
|
|
} else {
|
|
return (
|
|
<React.Fragment key={index}>
|
|
<Link
|
|
href={linkObject.path}
|
|
{...linkProps}
|
|
{...(isTarget ? currentLinkProps : {})}
|
|
className={twMerge(
|
|
"text-xs",
|
|
isTarget ? "current" : "",
|
|
"twui-breadcrumb-link",
|
|
linkProps?.className,
|
|
isTarget && currentLinkProps?.className
|
|
)}
|
|
>
|
|
{currentLinkProps?.title ||
|
|
linkObject.title}
|
|
</Link>
|
|
{divider || (
|
|
<Divider
|
|
vertical
|
|
{...dividerProps}
|
|
className={twMerge(
|
|
"twui-breadcrumbs-divider",
|
|
dividerProps?.className
|
|
)}
|
|
/>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
})}
|
|
</Row>
|
|
</nav>
|
|
);
|
|
////////////////////////////////////////
|
|
////////////////////////////////////////
|
|
////////////////////////////////////////
|
|
}
|
|
|
|
export function twuiBreadcrumbsGenerateLinksFromUrl({
|
|
url,
|
|
excludeRegexMatch,
|
|
skipHome,
|
|
}: {
|
|
url: string;
|
|
excludeRegexMatch?: RegExp;
|
|
skipHome?: boolean;
|
|
}) {
|
|
let pathLinks = url.split("/");
|
|
|
|
let validPathLinks = [];
|
|
|
|
if (!skipHome) {
|
|
validPathLinks.push({
|
|
title: "Home",
|
|
path: url.match(/admin/) ? "/admin" : "/",
|
|
});
|
|
}
|
|
|
|
pathLinks.forEach((linkText, index, array) => {
|
|
if (!linkText?.match(/./)) {
|
|
return;
|
|
}
|
|
|
|
if (excludeRegexMatch && excludeRegexMatch.test(linkText)) return;
|
|
|
|
validPathLinks.push({
|
|
title: lowerToTitleCase(linkText),
|
|
path: (() => {
|
|
let path = "";
|
|
|
|
for (let i = 0; i < array.length; i++) {
|
|
const lnText = array[i];
|
|
if (i > index || !lnText.match(/./)) continue;
|
|
|
|
path += `/${lnText}`;
|
|
}
|
|
|
|
return path;
|
|
})(),
|
|
});
|
|
});
|
|
|
|
return validPathLinks;
|
|
}
|