mirror of https://github.com/buster-so/buster.git
update breadcrumb
This commit is contained in:
parent
fb20eb2759
commit
c3d6f9ea67
|
@ -0,0 +1,60 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Breadcrumb } from './Breadcrumb';
|
||||
import { BusterAppRoutes } from '@/routes/busterRoutes/busterAppRoutes';
|
||||
|
||||
const meta: Meta<typeof Breadcrumb> = {
|
||||
title: 'Base/Breadcrumb',
|
||||
component: Breadcrumb,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Breadcrumb>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
items: [
|
||||
{ label: 'Home', route: { route: BusterAppRoutes.APP_ROOT } },
|
||||
{ label: 'Datasets', route: { route: BusterAppRoutes.APP_DATASETS } },
|
||||
{ label: 'Current Dataset' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
export const WithDropdown: Story = {
|
||||
args: {
|
||||
items: [
|
||||
{ label: 'Home', route: { route: BusterAppRoutes.APP_ROOT } },
|
||||
{
|
||||
label: null,
|
||||
dropdown: [
|
||||
{ label: 'Dataset A', route: { route: BusterAppRoutes.APP_DATASETS } },
|
||||
{ label: 'Dataset B', route: { route: BusterAppRoutes.APP_DATASETS } },
|
||||
{ label: 'Dataset C', route: { route: BusterAppRoutes.APP_DATASETS } }
|
||||
]
|
||||
},
|
||||
{ label: 'Current Dataset' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
export const CustomActiveIndex: Story = {
|
||||
args: {
|
||||
items: [
|
||||
{ label: 'Home', route: { route: BusterAppRoutes.APP_ROOT } },
|
||||
{ label: 'Datasets', route: { route: BusterAppRoutes.APP_DATASETS } },
|
||||
{ label: 'Settings', route: { route: BusterAppRoutes.SETTINGS_GENERAL } },
|
||||
{ label: 'Profile' }
|
||||
],
|
||||
activeIndex: 2
|
||||
}
|
||||
};
|
||||
|
||||
export const SingleItem: Story = {
|
||||
args: {
|
||||
items: [{ label: 'Home' }]
|
||||
}
|
||||
};
|
|
@ -0,0 +1,108 @@
|
|||
import React, { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
Breadcrumb as BreadcrumbBase,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis
|
||||
} from './BreadcrumbBase';
|
||||
import { createBusterRoute } from '@/routes/busterRoutes';
|
||||
import { Dropdown, DropdownItem } from '../dropdown/Dropdown';
|
||||
import Link from 'next/link';
|
||||
|
||||
type CreateBusterRouteParams = Parameters<typeof createBusterRoute>[0];
|
||||
|
||||
interface BreadcrumbItem {
|
||||
label: string | null; //if null, it will be an ellipsis
|
||||
route?: CreateBusterRouteParams;
|
||||
dropdown?: { label: string; route: CreateBusterRouteParams }[];
|
||||
}
|
||||
|
||||
interface BreadcrumbProps {
|
||||
items: BreadcrumbItem[];
|
||||
activeIndex?: number; //default will be the last item
|
||||
}
|
||||
|
||||
export const Breadcrumb = React.memo(
|
||||
React.forwardRef<HTMLElement, BreadcrumbProps>(({ items, activeIndex }, ref) => {
|
||||
const chosenIndex = activeIndex ?? items.length - 1;
|
||||
const lastItemIndex = items.length - 1;
|
||||
|
||||
console.log('chosenIndex', chosenIndex);
|
||||
|
||||
return (
|
||||
<BreadcrumbBase>
|
||||
<BreadcrumbList>
|
||||
{items.map((item, index) => (
|
||||
<BreadcrumbItemSelector
|
||||
key={index}
|
||||
item={item}
|
||||
isActive={chosenIndex === index}
|
||||
isLast={index === lastItemIndex}
|
||||
/>
|
||||
))}
|
||||
</BreadcrumbList>
|
||||
</BreadcrumbBase>
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
const BreadcrumbItemSelector: React.FC<{
|
||||
item: BreadcrumbItem;
|
||||
isActive: boolean;
|
||||
isLast: boolean;
|
||||
}> = ({ item, isActive, isLast }) => {
|
||||
const ChosenComponent = useMemo(() => {
|
||||
if (item.dropdown) {
|
||||
return <BreadcrumbDropdown items={item.dropdown} />;
|
||||
}
|
||||
if (isActive) {
|
||||
return <BreadcrumbPage>{item.label}</BreadcrumbPage>;
|
||||
}
|
||||
|
||||
return (
|
||||
<BreadcrumbLink asChild>
|
||||
{item.route ? (
|
||||
<Link href={createBusterRoute(item.route)}>{item.label}</Link>
|
||||
) : (
|
||||
<span>{item.label}</span>
|
||||
)}
|
||||
</BreadcrumbLink>
|
||||
);
|
||||
}, [isActive, item.label, item.route, item.dropdown]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<BreadcrumbItem>{ChosenComponent}</BreadcrumbItem>
|
||||
{!isLast && <BreadcrumbSeparator />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const BreadcrumbDropdown: React.FC<{
|
||||
items: { label: string; route: CreateBusterRouteParams }[];
|
||||
}> = ({ items }) => {
|
||||
const dropdownItems: DropdownItem[] = useMemo(() => {
|
||||
return items.map((item) => {
|
||||
const route = createBusterRoute(item.route);
|
||||
return {
|
||||
label: <Link href={route}>{item.label}</Link>,
|
||||
value: route
|
||||
};
|
||||
});
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<Dropdown items={dropdownItems}>
|
||||
<div className="flex cursor-pointer items-center">
|
||||
<BreadcrumbEllipsis className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
Breadcrumb.displayName = 'Breadcrumb';
|
|
@ -0,0 +1,101 @@
|
|||
import * as React from 'react';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { ChevronRight, Dots } from '../icons';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Breadcrumb = React.forwardRef<
|
||||
HTMLElement,
|
||||
React.ComponentPropsWithoutRef<'nav'> & {
|
||||
separator?: React.ReactNode;
|
||||
}
|
||||
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
|
||||
Breadcrumb.displayName = 'Breadcrumb';
|
||||
|
||||
const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<'ol'>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<ol
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-gray-dark flex flex-wrap items-center gap-1.5 text-base break-words sm:gap-2.5',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
BreadcrumbList.displayName = 'BreadcrumbList';
|
||||
|
||||
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<'li'>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<li ref={ref} className={cn('inline-flex items-center gap-1.5', className)} {...props} />
|
||||
)
|
||||
);
|
||||
BreadcrumbItem.displayName = 'BreadcrumbItem';
|
||||
|
||||
const BreadcrumbLink = React.forwardRef<
|
||||
HTMLAnchorElement,
|
||||
React.ComponentPropsWithoutRef<'a'> & {
|
||||
asChild?: boolean;
|
||||
}
|
||||
>(({ asChild, children, className, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : 'a';
|
||||
|
||||
return (
|
||||
<Comp ref={ref} className={cn('hover:text-foreground transition-colors', className)} {...props}>
|
||||
{children}
|
||||
</Comp>
|
||||
);
|
||||
});
|
||||
BreadcrumbLink.displayName = 'BreadcrumbLink';
|
||||
|
||||
const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<'span'>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<span
|
||||
ref={ref}
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
className={cn('text-foreground font-normal', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
BreadcrumbPage.displayName = 'BreadcrumbPage';
|
||||
|
||||
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<'li'>) => (
|
||||
<li
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn('text-icon-size [&>svg]:h-2.5 [&>svg]:w-2.5', className)}
|
||||
{...props}>
|
||||
{children ?? <ChevronRight />}
|
||||
</li>
|
||||
);
|
||||
BreadcrumbSeparator.displayName = 'BreadcrumbSeparator';
|
||||
|
||||
const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<'span'>) => (
|
||||
<span
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn('hover:text-foreground flex h-9 w-9 items-center justify-center', className)}
|
||||
{...props}>
|
||||
<div className="text-icon-size flex">
|
||||
<Dots />
|
||||
</div>
|
||||
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
);
|
||||
BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis';
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis
|
||||
};
|
Loading…
Reference in New Issue