From 6929601890ebaeb07d59e04e31873c88c2a56e4a Mon Sep 17 00:00:00 2001 From: Orlando Valverde <4467518+septum@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:56:55 -0600 Subject: [PATCH 1/3] frontend: Override padding removal in Wizard container (#3175) --- frontend/packages/wizard/src/wizard.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/packages/wizard/src/wizard.tsx b/frontend/packages/wizard/src/wizard.tsx index 17de8ec39e..282fc99ba2 100644 --- a/frontend/packages/wizard/src/wizard.tsx +++ b/frontend/packages/wizard/src/wizard.tsx @@ -73,7 +73,9 @@ const Header = styled(Grid)<{ $orientation: MuiStepperProps["orientation"] }>( const Container = styled(MuiContainer)<{ $width: ContainerProps["width"] }>( ({ theme }: { theme: Theme }) => ({ - padding: theme.clutch.layout.gutter, + padding: theme.clutch.useWorkflowLayout + ? theme.spacing("none", "md") + : theme.clutch.layout.gutter, height: "100%", }), props => ({ From 1cc0a3220999fef23aeefc05118e3a39cc19e1ba Mon Sep 17 00:00:00 2001 From: Orlando Valverde <4467518+septum@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:13:22 -0600 Subject: [PATCH 2/3] frontend: Improve breadcrumbs UX (#3176) --- .../packages/core/src/AppProvider/index.tsx | 2 +- .../core/src/AppProvider/workflow.tsx | 4 +- .../core/src/WorkflowLayout/index.tsx | 17 ++---- .../src/utils/generateBreadcrumbsEntries.tsx | 57 +++++++++++++++---- 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/frontend/packages/core/src/AppProvider/index.tsx b/frontend/packages/core/src/AppProvider/index.tsx index 0ea18eb2fc..fa4626212e 100644 --- a/frontend/packages/core/src/AppProvider/index.tsx +++ b/frontend/packages/core/src/AppProvider/index.tsx @@ -217,7 +217,7 @@ const ClutchApp = ({ : workflow.displayName; const workflowLayoutProps: LayoutProps = { - workflow, + workflowsInPath: workflows.filter(w => w.path === workflow.path), title: heading, subtitle: route.description, variant: diff --git a/frontend/packages/core/src/AppProvider/workflow.tsx b/frontend/packages/core/src/AppProvider/workflow.tsx index 9ae6d29957..82a5d61287 100644 --- a/frontend/packages/core/src/AppProvider/workflow.tsx +++ b/frontend/packages/core/src/AppProvider/workflow.tsx @@ -55,7 +55,7 @@ interface WorkflowLayoutConfiguration { /** * (Optional) property to pass the defined layout properties to all of its defined routes */ - defaultLayoutProps?: Omit; + defaultLayoutProps?: Omit; } export interface Workflow @@ -105,7 +105,7 @@ export interface Route { /** * (Optional) property to define layout properties for a single route */ - layoutProps?: Omit; + layoutProps?: Omit; } export interface ConfiguredRoute extends Route { diff --git a/frontend/packages/core/src/WorkflowLayout/index.tsx b/frontend/packages/core/src/WorkflowLayout/index.tsx index 3fb0f8bd53..63c3c86052 100644 --- a/frontend/packages/core/src/WorkflowLayout/index.tsx +++ b/frontend/packages/core/src/WorkflowLayout/index.tsx @@ -1,12 +1,11 @@ import React from "react"; -import { matchPath, useParams } from "react-router-dom"; import type { Interpolation } from "@emotion/styled"; import type { CSSObject, Theme } from "@mui/material"; import { alpha } from "@mui/material"; import type { Workflow } from "../AppProvider/workflow"; import Breadcrumbs from "../Breadcrumbs"; -import { useLocation } from "../navigation"; +import { useLocation, useParams } from "../navigation"; import styled from "../styled"; import { Typography } from "../typography"; import { generateBreadcrumbsEntries } from "../utils"; @@ -14,7 +13,7 @@ import { generateBreadcrumbsEntries } from "../utils"; export type LayoutVariant = "standard" | "wizard"; export type LayoutProps = { - workflow: Workflow; + workflowsInPath: Array; variant?: LayoutVariant | null; title?: string | ((params: Record) => string); subtitle?: string; @@ -88,7 +87,7 @@ const Subtitle = styled(Typography)(({ theme }: { theme: Theme }) => ({ })); const WorkflowLayout = ({ - workflow, + workflowsInPath, variant = null, title = null, subtitle = null, @@ -99,22 +98,18 @@ const WorkflowLayout = ({ const params = useParams(); const location = useLocation(); + const entries = generateBreadcrumbsEntries(workflowsInPath, location); + if (variant === null) { return <>{children}; } - const workflowPaths = workflow.routes.map(({ path }) => `/${workflow.path}/${path}`); - const breadcrumbsEntries = generateBreadcrumbsEntries( - location, - url => !!workflowPaths.find(path => !!matchPath({ path }, url)) - ); - return ( {!hideHeader && ( - + {!breadcrumbsOnly && (title || subtitle) && ( diff --git a/frontend/packages/core/src/utils/generateBreadcrumbsEntries.tsx b/frontend/packages/core/src/utils/generateBreadcrumbsEntries.tsx index 099e1e213b..e2d0887b01 100644 --- a/frontend/packages/core/src/utils/generateBreadcrumbsEntries.tsx +++ b/frontend/packages/core/src/utils/generateBreadcrumbsEntries.tsx @@ -1,23 +1,56 @@ -import type { Location } from "react-router-dom"; +import { Location, matchPath } from "react-router-dom"; +import type { Workflow } from "../AppProvider/workflow"; import type { BreadcrumbEntry } from "../Breadcrumbs"; -const generateBreadcrumbsEntries = (location: Location, validateUrl: (url: string) => boolean) => { - const labels = decodeURIComponent(location.pathname) +const HOME_ENTRY = { label: "Home", url: "/" }; + +const generateBreadcrumbsEntries = (workflowsInPath: Array, location: Location) => { + // The first workflow in the will contain + // the same path and displayName as the others + const firstWorkflow = workflowsInPath[0]; + + if (!firstWorkflow) { + return [HOME_ENTRY]; + } + + // Get a single level list of the routes available + const allRoutes = workflowsInPath.flatMap(w => w.routes); + + // Add to every item in the routes list the workflow path prefix + const fullPaths = allRoutes.map(({ path }) => `/${firstWorkflow.path}/${path}`); + + // Generate a list of path segments from the location + const pathSegments = decodeURIComponent(location.pathname) .split("/") - .slice(1, location.pathname.endsWith("/") ? -1 : undefined); + .slice(1, location.pathname.endsWith("/") ? -1 : undefined); // in case of a trailing `/` + + const entries: Array = [HOME_ENTRY].concat( + pathSegments.map((segment, index) => { + const nextIndex = index + 1; + const url = `/${pathSegments.slice(0, nextIndex).join("/")}`; - const entries: Array = [{ label: "Home", url: "/" }].concat( - labels.map((label, index) => { - let url = `/${labels.slice(0, index + 1).join("/")}`; + const path = fullPaths.find(p => !!matchPath(p, url)); - if (!validateUrl(url)) { - url = undefined; - } + // If there is a matched path, it's used to find the route that contains its displayName + const route = path + ? allRoutes.find(r => + r.path.startsWith("/") + ? r.path + : // Done in case of an empty path or missing a leading `/` + `/${r.path}` === `/${path.split("/").slice(2).join("/")}` + ) + : null; return { - label, - url, + // For the label: + // - Prioritize the display name + // - Handle the case of a single route with an unusual long name + // - Default to the path segment + label: + route?.displayName || (allRoutes.length === 1 && firstWorkflow.displayName) || segment, + // Set a null url if there is no path or for the last segment + url: !!path && pathSegments.length !== nextIndex ? url : null, }; }) ); From edd2f15b143214e945a5d6aa3f499e785b1ca5a6 Mon Sep 17 00:00:00 2001 From: Orlando Valverde <4467518+septum@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:42:35 -0600 Subject: [PATCH 3/3] frontend: Set workflow layout title as either route or workflow display names (#3174) --- frontend/packages/core/src/AppProvider/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/packages/core/src/AppProvider/index.tsx b/frontend/packages/core/src/AppProvider/index.tsx index fa4626212e..d67dca2cf7 100644 --- a/frontend/packages/core/src/AppProvider/index.tsx +++ b/frontend/packages/core/src/AppProvider/index.tsx @@ -218,7 +218,7 @@ const ClutchApp = ({ const workflowLayoutProps: LayoutProps = { workflowsInPath: workflows.filter(w => w.path === workflow.path), - title: heading, + title: route.displayName || workflow.displayName, subtitle: route.description, variant: route.layoutProps?.variant === null ||