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

Feature/slb 295 breadcrumbs #241

Closed
wants to merge 14 commits into from
24 changes: 24 additions & 0 deletions packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { FrameQuery, OperationExecutor } from '@custom/schema';
import { Meta } from '@storybook/react';
import React from 'react';

import { Default as FrameStory } from '../Routes/Frame.stories';
import { BreadCrumbs } from './Breadcrumbs';

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>
);
},
};
54 changes: 54 additions & 0 deletions packages/ui/src/components/Molecules/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Link } from '@custom/schema';
import { ChevronRightIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
import React from 'react';

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

export function BreadCrumbs({ className }: { className?: string }) {
const breadcrumbs = useBreadcrumbs();

if (!breadcrumbs.length) {
return null;
}

return (
<nav className={clsx('pt-5', className)} aria-label="Breadcrumb">
<ol className={'rounded-lg inline-block py-2.5'}>
{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}
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',
)}
>
{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">{title}</span>
</Link>
</li>
))}
</ol>
</nav>
);
}
6 changes: 5 additions & 1 deletion packages/ui/src/components/Organisms/PageDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';

import { isTruthy } from '../../utils/isTruthy';
import { UnreachableCaseError } from '../../utils/unreachable-case-error';
import { BreadCrumbs } from '../Molecules/Breadcrumbs';
import { PageTransition } from '../Molecules/PageTransition';
import { BlockCta } from './PageContent/BlockCta';
import { BlockForm } from './PageContent/BlockForm';
Expand All @@ -16,7 +17,10 @@ export function PageDisplay(page: PageFragment) {
return (
<PageTransition>
<div>
{page.hero ? <PageHero {...page.hero} /> : null}
{!page.hero && (
<BreadCrumbs className="pt-5 mx-auto max-w-screen-xl px-3.5" />
)}
{page.hero && <PageHero {...page.hero} />}
<div className="bg-white pt-5 pb-12 lg:px-8">
<div className="text-base leading-7 text-gray-700">
{page?.content?.filter(isTruthy).map((block, index) => {
Expand Down
147 changes: 80 additions & 67 deletions packages/ui/src/components/Organisms/PageHero.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Image, Link, PageFragment } from '@custom/schema';
import React from 'react';

import { BreadCrumbs } from '../Molecules/Breadcrumbs';
import { BlockForm } from './PageContent/BlockForm';

export function PageHero(props: NonNullable<PageFragment['hero']>) {
Expand All @@ -15,47 +16,54 @@ export function PageHero(props: NonNullable<PageFragment['hero']>) {

function DefaultHero(props: NonNullable<PageFragment['hero']>) {
return (
<section className="relative isolate overflow-hidden bg-gray-900 pt-12 pb-24 min-h-[20rem] lg:min-h-[33rem]">
{props.image ? (
<Image
alt={props.image.alt}
source={props.image.source}
priority={true}
className="absolute inset-0 -z-10 h-full w-full object-cover"
data-test-id={'hero-image'}
/>
) : null}
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl lg:mx-0">
<h1 className="text-5xl font-extrabold tracking-tight leading-tight text-white drop-shadow-md">
{props.headline}
</h1>
{props.lead ? (
<p className="mt-6 text-lg leading-8 text-gray-300">{props.lead}</p>
) : null}
{props.ctaText && props.ctaUrl ? (
<Link
href={props.ctaUrl}
className="mt-7 px-5 py-2.5 text-sm font-medium text-white inline-flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
<svg
className="mr-2 -ml-1 w-4 h-4"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
<>
<section className="relative isolate overflow-hidden bg-gray-900 pt-12 pb-24 min-h-[20rem] lg:min-h-[33rem]">
{props.image ? (
<Image
alt={props.image.alt}
source={props.image.source}
priority={true}
className="absolute inset-0 -z-10 h-full w-full object-cover"
data-test-id={'hero-image'}
/>
) : null}
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl lg:mx-0">
<h1 className="text-5xl font-extrabold tracking-tight leading-tight text-white drop-shadow-md">
{props.headline}
</h1>
{props.lead ? (
<p className="mt-6 text-lg leading-8 text-gray-300">
{props.lead}
</p>
) : null}
{props.ctaText && props.ctaUrl ? (
<Link
href={props.ctaUrl}
className="mt-7 px-5 py-2.5 text-sm font-medium text-white inline-flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
<path
fillRule="evenodd"
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
clipRule="evenodd"
></path>
</svg>
{props.ctaText}
</Link>
) : null}
<svg
className="mr-2 -ml-1 w-4 h-4"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
clipRule="evenodd"
></path>
</svg>
{props.ctaText}
</Link>
) : null}
</div>
</div>
</section>
<div className="mx-auto max-w-screen-xl px-3.5">
<BreadCrumbs />
</div>
</section>
</>
);
}

Expand Down Expand Up @@ -118,35 +126,40 @@ function FormHero(props: NonNullable<PageFragment['hero']>) {

function NoImageHero(props: NonNullable<PageFragment['hero']>) {
return (
<section className="relative isolate overflow-hidden pt-12 sm:pt-20 px-6 lg:px-8">
<div className="mx-auto max-w-3xl">
<h1 className="text-4xl font-extrabold tracking-tight leading-tight">
{props.headline}
</h1>
{props.lead ? (
<p className="mt-4 text-lg leading-8 text-gray-500">{props.lead}</p>
) : null}
{props.ctaText && props.ctaUrl ? (
<Link
href={props.ctaUrl}
className="mt-5 px-5 py-2.5 text-sm font-medium text-white inline-flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
<svg
className="mr-2 -ml-1 w-4 h-4"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
clipRule="evenodd"
></path>
</svg>
{props.ctaText}
</Link>
) : null}
<>
<div className="mx-auto max-w-screen-xl px-3.5">
<BreadCrumbs />
</div>
</section>
<section className="relative isolate overflow-hidden pt-12 sm:pt-20 px-6 lg:px-8">
<div className="mx-auto max-w-3xl">
<h1 className="text-4xl font-extrabold tracking-tight leading-tight">
{props.headline}
</h1>
{props.lead ? (
<p className="mt-4 text-lg leading-8 text-gray-500">{props.lead}</p>
) : null}
{props.ctaText && props.ctaUrl ? (
<Link
href={props.ctaUrl}
className="mt-5 px-5 py-2.5 text-sm font-medium text-white inline-flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
<svg
className="mr-2 -ml-1 w-4 h-4"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
clipRule="evenodd"
></path>
</svg>
{props.ctaText}
</Link>
) : null}
</div>
</section>
</>
);
}
93 changes: 93 additions & 0 deletions packages/ui/src/components/Routes/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { FrameQuery, NavigationItem, Url, useLocation } from '@custom/schema';
import { useIntl } from 'react-intl';

import { useOperation } from '../../utils/operation';

export type MenuNameType = 'main' | 'footer';

export function useMenus() {
const intl = useIntl();
const locale = intl.locale;
const settings = useOperation(FrameQuery).data;

return {
main: settings?.mainNavigation
?.filter((nav) => nav?.locale === locale)
.pop(),
footer: settings?.footerNavigation
?.filter((nav) => nav?.locale === locale)
.pop(),
};
}

export function useCurrentPath() {
const [loc] = useLocation();
return loc.pathname;
}

export function useMenuItem(path: string, menuName: MenuNameType) {
const menus = useMenus();

return (
menus &&
menus[menuName]?.items.find((menuItem) => menuItem?.target === path)
);
}

export function useMenuChildren(path: string, menuName: MenuNameType) {
const menus = useMenus();
const menuItemFromPath = useMenuItem(path, menuName);
return (
menus &&
menus[menuName]?.items.filter(
(menuItem) => menuItem?.parent === menuItemFromPath?.id,
)
);
}

export function useCurrentMenuItem(menuName: MenuNameType) {
const currentPath = useCurrentPath();
return useMenuItem(currentPath || '', menuName);
}

export function useCurrentMenuChildren(menuName: MenuNameType) {
const currentPath = useCurrentPath();
return useMenuChildren(currentPath || '', menuName);
}

export function useMenuAncestors(path: string, menuName: MenuNameType) {
const menus = useMenus();
const menuItemFromPath = useMenuItem(path, menuName);
let processingMenuItem = menuItemFromPath;
const ancestors: Array<NavigationItem> = [];

// Set current page breadcrumb
if (typeof processingMenuItem !== 'undefined') {
// If not home path then only push into breadcrumbs array
processingMenuItem.target !== '/' && ancestors.push(processingMenuItem);
}

while (
typeof processingMenuItem !== 'undefined' &&
processingMenuItem?.parent
) {
processingMenuItem =
menus &&
menus[menuName]?.items.find(
(menuItem) => menuItem?.id === processingMenuItem?.parent,
);
if (typeof processingMenuItem !== 'undefined') {
ancestors.push(processingMenuItem);
}
}
if (ancestors.length > 0) {
ancestors.push({ id: '_', target: '/' as Url, title: 'Home' });
}

return ancestors.reverse();
}

export const useBreadcrumbs = (menuName?: MenuNameType, path?: string) => {
const currentPath = useCurrentPath();
return useMenuAncestors(path || currentPath || '', menuName || 'main');
};
Loading
Loading