Skip to content

Commit

Permalink
[Sparkle] Super breadcrumbs (#6946)
Browse files Browse the repository at this point in the history
* [Sparkle] Super breadcrumbs

Description
---
Part of task #6906

Super breadcrumbs:
- Shows up to 5 elements;
- if more than 5 elements, shows the 2 first & 2 last elements, ellipsis in the middle;
- clic on ellipsis shows dropdown
- cuts middle elements to 15 chars, last element to 30 chars

Risk
---
na

Deploy
---
deploy front

* review
  • Loading branch information
philipperolet authored Aug 28, 2024
1 parent 8e4bcad commit 40f7c18
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 42 deletions.
4 changes: 2 additions & 2 deletions sparkle/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sparkle/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dust-tt/sparkle",
"version": "0.2.214",
"version": "0.2.215",
"scripts": {
"build": "rm -rf dist && rollup -c",
"build:with-tw-base": "rollup -c --environment INCLUDE_TW_BASE:true",
Expand Down
135 changes: 98 additions & 37 deletions sparkle/src/components/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,117 @@
import type { ComponentType } from "react";
import React from "react";

import { SparkleContextLinkType } from "@sparkle/context";
import { ChevronRightIcon, Icon, SparkleContext } from "@sparkle/index";
import { noHrefLink, SparkleContextLinkType } from "@sparkle/context";
import {
ChevronRightIcon,
DropdownMenu,
Icon,
SparkleContext,
Tooltip,
} from "@sparkle/index";

const LABEL_TRUNCATE_LENGTH_MIDDLE = 15;
const LABEL_TRUNCATE_LENGTH_END = 30;
const ELLIPSIS_STRING = "...";

type BreadcrumbItem = {
label: string;
icon?: ComponentType<{ className?: string }>;
href?: string;
};

type BreadcrumbProps = {
items: {
label: string;
icon?: ComponentType<{ className?: string }>;
href?: string;
}[];
items: BreadcrumbItem[];
};

type BreadcrumbsAccumulator = {
itemsShown: BreadcrumbItem[];
itemsHidden: BreadcrumbItem[];
};

export function Breadcrumbs({ items }: BreadcrumbProps) {
const { components } = React.useContext(SparkleContext);

const Link: SparkleContextLinkType = components.link;
const { itemsShown, itemsHidden } = items.reduce(
(acc: BreadcrumbsAccumulator, item, index) => {
if (items.length <= 5 || index < 2 || index >= items.length - 2) {
acc.itemsShown.push(item);
} else if (index === 2) {
acc.itemsShown.push({ label: ELLIPSIS_STRING });
} else {
acc.itemsHidden.push(item);
}
return acc;
},
{ itemsShown: [], itemsHidden: [] }
);

return (
<div className="gap-2 s-flex s-flex-row s-items-center">
{items.map((item, index) => (
<div key={index} className="s-flex s-flex-row s-items-center s-gap-1">
<Icon visual={item.icon} className="s-text-brand" />
<div>
{item.href ? (
<Link
href={item.href}
className={
index === items.length - 1
? "s-text-element-900"
: "s-text-element-700"
}
>
{item.label}
</Link>
{itemsShown.map((item, index) => {
return (
<div
key={`breadcrumbs-${index}`}
className="s-flex s-flex-row s-items-center s-gap-1"
>
<Icon visual={item.icon} className="s-text-brand" />
{item.label === ELLIPSIS_STRING ? (
<DropdownMenu>
<DropdownMenu.Button>${ELLIPSIS_STRING}</DropdownMenu.Button>
<DropdownMenu.Items origin="topLeft">
{itemsHidden.map((item, index) => (
<DropdownMenu.Item
key={`breadcrumbs-hidden-${index}`}
icon={item.icon}
label={item.label}
>
{getLinkForItem(item, false, components.link)}
</DropdownMenu.Item>
))}
</DropdownMenu.Items>
</DropdownMenu>
) : (
<span
className={
index === items.length - 1
? "s-text-element-900"
: "s-text-element-700"
}
>
{item.label}
</span>
<div>
{getLinkForItem(
item,
index === itemsShown.length - 1,
components.link
)}
</div>
)}
{index === itemsShown.length - 1 ? null : (
<ChevronRightIcon className="s-text-element-500" />
)}
</div>
{index === items.length - 1 ? null : (
<ChevronRightIcon className="s-text-element-500" />
)}
</div>
))}
);
})}
</div>
);
}

function getLinkForItem(
item: BreadcrumbItem,
isLast: boolean,
link: SparkleContextLinkType
) {
const Link: SparkleContextLinkType = item.href ? link : noHrefLink;

return (
<Link
href={item.href || "#"}
className={isLast ? "s-text-element-900" : "s-text-element-700"}
>
{isLast
? truncateWithTooltip(item.label, LABEL_TRUNCATE_LENGTH_END)
: truncateWithTooltip(item.label, LABEL_TRUNCATE_LENGTH_MIDDLE)}
</Link>
);
}

function truncateWithTooltip(text: string, length: number) {
return text.length > length ? (
<Tooltip label={text}>{`${text.substring(0, length - 1)}…`}</Tooltip>
) : (
text
);
}
52 changes: 50 additions & 2 deletions sparkle/src/stories/Breadcrumbs.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { Meta } from "@storybook/react";
import React from "react";

import { Breadcrumbs, CompanyIcon, HomeIcon } from "../index_with_tw_base";
import {
Breadcrumbs,
CompanyIcon,
FolderIcon,
HomeIcon,
} from "../index_with_tw_base";

const meta = {
title: "Components/Breadcrumbs",
Expand All @@ -20,12 +25,55 @@ export const BreadcrumbsExample = () => {
{ label: "Home", href: "#", icon: HomeIcon },
{ label: "Vaults", href: "#", icon: CompanyIcon },
{ label: "My Vault", href: "#" },
{ label: "loooong name in the end, like very very long long" },
];

const items3 = [
{ label: "Home", href: "#", icon: HomeIcon },
{
label: "Middle long name, oh very looong folder name in the middle",
href: "#",
icon: CompanyIcon,
},
{ label: "My Vault", href: "#" },
{ label: "Data Sources" },
{ label: "Folder1", href: "#", icon: FolderIcon },
{ label: "With ellipsis", href: "#" },
];

const items4 = [
{ label: "Home", href: "#", icon: HomeIcon },
{
label: "Middle long name, oh very looong folder name in the middle",
href: "#",
icon: CompanyIcon,
},
{ label: "My Vault", href: "#" },
{ label: "Data Sources" },
{ label: "Folder1", href: "#", icon: FolderIcon },
{ label: "Folder2", href: "#", icon: FolderIcon },
{ label: "Folder3", href: "#", icon: FolderIcon },
{ label: "With ellipsis", href: "#" },
];

const items5 = [
{ label: "Home", href: "#", icon: HomeIcon },
{
label: "Long, oh very looong folder name in the middle",
href: "#",
icon: CompanyIcon,
},
{ label: "My Vault", href: "#" },
{ label: "Data Sources" },
];

return (
<div className="s-flex s-flex-col s-gap-4">
<div className="s-flex s-flex-col s-gap-4 s-pb-8">
<Breadcrumbs items={items1} />
<Breadcrumbs items={items2} />
<Breadcrumbs items={items4} />
<Breadcrumbs items={items3} />
<Breadcrumbs items={items5} />
</div>
);
};

0 comments on commit 40f7c18

Please sign in to comment.