Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slb 390 truncate long breadcrumbs #270

Merged
merged 12 commits into from
Jun 24, 2024
8 changes: 8 additions & 0 deletions packages/ui/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ export const parameters = {
id: 'landmark-unique',
reviewOnFail: true,
},
{
id: 'button-name',
reviewOnFail: true,
},
{
id: 'list',
reviewOnFail: true,
},
],
},
// Axe's options parameter
Expand Down
18 changes: 13 additions & 5 deletions packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FrameQuery, OperationExecutor } from '@custom/schema';
import { Meta } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import React from 'react';

import { Default as FrameStory } from '../Routes/Frame.stories';
Expand All @@ -9,16 +9,24 @@ export default {
component: BreadCrumbs,
parameters: {
layout: 'fullscreen',
location: new URL('local:/gatsby-turbo'),
},
} satisfies Meta<typeof BreadCrumbs>;

export const Default = {
render: () => {
return (
<OperationExecutor executor={FrameStory.args} id={FrameQuery}>
<BreadCrumbs />
</OperationExecutor>
);
},
} satisfies Meta<typeof BreadCrumbs>;

export const Simple = {
parameters: {
location: new URL('local:/gatsby-turbo'),
},
};

export const Truncated: StoryObj<typeof BreadCrumbs> = {
parameters: {
location: new URL('local:/gatsby-turbo-more-more-more'),
},
};
121 changes: 93 additions & 28 deletions packages/ui/src/components/Molecules/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { Link } from '@custom/schema';
import { ChevronRightIcon } from '@heroicons/react/24/outline';
import {
ChevronRightIcon,
EllipsisHorizontalIcon,
} from '@heroicons/react/24/outline';
import clsx from 'clsx';
import React from 'react';
import React, { useEffect, useState } from 'react';

import { isTruthy } from '../../utils/isTruthy';
import { truncateString } from '../../utils/stringTruncation';
import { useBreadcrumbs } from '../Routes/Menu';

export function BreadCrumbs() {
const breadcrumbs = useBreadcrumbs();
const [hideInnerBreadcrumbs, setHideInnerBreadcrumbs] = useState(false);
const [toggleMoreBreadcrumbs, setToggleMoreBreadcrumbs] = useState(false);

useEffect(() => {
breadcrumbs.length > 5 &&
toggleMoreBreadcrumbs === false &&
setHideInnerBreadcrumbs(true);
}, [hideInnerBreadcrumbs, breadcrumbs, toggleMoreBreadcrumbs]);

if (!breadcrumbs.length) {
return null;
Expand All @@ -16,38 +28,91 @@ export function BreadCrumbs() {
return (
<div className="container-page">
<nav className="pt-5 container-content" aria-label="Breadcrumb">
<ol className={'rounded-lg inline-block py-2.5'}>
<ol
className={
'rounded-lg items-center overflow-x-scroll flex py-2.5 container'
}
>
{breadcrumbs?.filter(isTruthy).map(({ title, target, id }, index) => (
<li className="inline-flex items-center" key={id}>
{index > 0 ? (
<div aria-hidden="true">
<ChevronRightIcon className={'w-4 h-4 text-gray-400 mr-4'} />
</div>
) : null}
<Link
href={target}
title={title}
<>
{hideInnerBreadcrumbs === true && index === 1 && (
<>
<div aria-hidden="true">
<ChevronRightIcon
className={
'hidden xl:flex rotate-180 xl:rotate-0 w-4 h-4 text-gray-400 mr-4'
}
/>
</div>
<button
className="mr-4 hidden xl:flex items-center rounded-sm px-1 py-2 bg-gray-100 hover:bg-gray-200 h-2"
onClick={() => {
setHideInnerBreadcrumbs(false);
setToggleMoreBreadcrumbs(true);
}}
>
<EllipsisHorizontalIcon className="w-4 text-gray-900" />
</button>
</>
)}
<li
className={clsx(
'inline-flex items-center text-sm font-medium hover:text-blue-600',
index === breadcrumbs?.length - 1
? 'pointer-events-none text-gray-500'
: 'text-gray-700',
'',
index < breadcrumbs.length - 1
? 'hidden xl:inline-flex xl:items-center'
: 'inline-flex items-center',
)}
key={id}
>
{target === '/' && (
<svg
className="w-4 h-4 mr-4"
{index > 0 ? (
<div
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
className={clsx(
hideInnerBreadcrumbs === true &&
index > 0 &&
index < breadcrumbs.length - 1 &&
'hidden',
)}
>
<path d="m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z" />
</svg>
)}
<span className="mr-4">{title}</span>
</Link>
</li>
<ChevronRightIcon
className={
'rotate-180 xl:rotate-0 w-4 h-4 text-gray-400 mr-4'
}
/>
</div>
) : null}
<Link
href={target}
title={title}
className={clsx(
'inline-flex items-center text-sm font-medium hover:text-blue-600 whitespace-nowrap',
index < breadcrumbs.length - 1 &&
hideInnerBreadcrumbs !== true
? 'hidden xl:inline-flex xl:items-center'
: 'inline-flex items-center',
hideInnerBreadcrumbs === true &&
index > 0 &&
index < breadcrumbs.length - 1 &&
'hidden',
)}
>
{target === '/' && (
<svg
className="w-4 h-4 mr-4"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z" />
</svg>
)}
<span className="mr-4">
{truncateString({ value: title, maxChar: 25 })}
</span>
</Link>
</li>
</>
))}
</ol>
</nav>
Expand Down
14 changes: 13 additions & 1 deletion packages/ui/src/components/Organisms/Header.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,19 @@ export const Idle = {
id: '8',
title: 'Super Gatsby Turbo',
target: '/gatsby-turbo' as Url,
parent: '6',
parent: '7',
},
{
id: '9',
title: 'Drupal Turbo This is a little extra long',
target: '/gatsby-turbo-more-more' as Url,
parent: '8',
},
{
id: '10',
title: 'Drupal Turbo This is a little extra long breadcrumb title',
target: '/gatsby-turbo-more-more-more' as Url,
parent: '9',
},
],
},
Expand Down
4 changes: 3 additions & 1 deletion packages/ui/src/components/Routes/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,11 @@ export function useMenuAncestors(path: string, menuName: MenuNameType) {
}
if (ancestors.length > 0) {
ancestors.push({ id: '_', target: '/' as Url, title: 'Home' });
// Pop off the current path, we dont care about it
ancestors.reverse().pop();
}

return ancestors.reverse();
return ancestors;
}

export const useBreadcrumbs = (menuName?: MenuNameType, path?: string) => {
Expand Down
29 changes: 29 additions & 0 deletions packages/ui/src/utils/stringTruncation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const truncateString = ({
value,
maxChar,
}: {
value: string;
maxChar: number;
}): string => {
if (value.length <= maxChar) {
return value;
}

// Split the string into words
const words = value.split(' ');

let result = '';
words.forEach((word, index) => {
if (index === 0) {
// Skip the first word
result = word;
return;
}
if (result.length + word.length + 1 > maxChar) {
return;
}
result += ' ' + word;
});

return result + '...';
};
Loading