From 053f3190e71d54f277435e7158b1a5228e4ce0b1 Mon Sep 17 00:00:00 2001 From: Josh Slaughter <8338893+jdslaugh@users.noreply.github.com> Date: Fri, 30 Jun 2023 11:00:56 -0700 Subject: [PATCH] frontend: Landing Icons and Single Drawer Item Patch (#2712) --- .../cypress/integration/navigation_spec.js | 54 +++++++++----- .../packages/core/src/AppLayout/drawer.tsx | 17 ++++- .../packages/core/src/AppLayout/utils.tsx | 9 +++ frontend/packages/core/src/card.tsx | 72 +++++++++++-------- frontend/packages/core/src/landing.tsx | 1 + 5 files changed, 105 insertions(+), 48 deletions(-) diff --git a/frontend/packages/app/cypress/integration/navigation_spec.js b/frontend/packages/app/cypress/integration/navigation_spec.js index 6727cba6b8..e876d46879 100644 --- a/frontend/packages/app/cypress/integration/navigation_spec.js +++ b/frontend/packages/app/cypress/integration/navigation_spec.js @@ -2,6 +2,20 @@ const DRAWER = "drawer"; const WORKFLOW_GROUP = "workflowGroup"; const WORKFLOW_GROUP_ITEM = "workflowGroupItem"; +const ifElementExists = (selector, attempt = 0) => { + if (attempt === 5) { + return null; + } + + if (Cypress.$(selector).length === 0) { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(100, { log: false }); + return ifElementExists(selector, attempt + 1); + } + + return cy.get(selector, { log: false }); +}; + describe("Navigation drawer", () => { before(() => { cy.visit("localhost:3000"); @@ -17,11 +31,14 @@ describe("Navigation drawer", () => { it("displays and hides routes", () => { cy.element(WORKFLOW_GROUP).each((_, idx) => { cy.element(WORKFLOW_GROUP).eq(idx).click(); - cy.element(WORKFLOW_GROUP_ITEM).each(link => { - cy.wrap(link).should("have.attr", "href"); - }); - cy.element(WORKFLOW_GROUP).eq(idx).click(); - cy.element(WORKFLOW_GROUP).eq(idx).descendent(WORKFLOW_GROUP_ITEM).should("not.exist"); + const element = ifElementExists(WORKFLOW_GROUP_ITEM); + if (element) { + element.each(link => { + cy.wrap(link).should("have.attr", "href"); + }); + cy.element(WORKFLOW_GROUP).eq(idx).click(); + cy.element(WORKFLOW_GROUP).eq(idx).descendent(WORKFLOW_GROUP_ITEM).should("not.exist"); + } }); }); @@ -29,18 +46,21 @@ describe("Navigation drawer", () => { it("can route correctly", () => { return cy.element(WORKFLOW_GROUP).each((_, groupIdx) => { cy.element(WORKFLOW_GROUP).eq(groupIdx).click(); - cy.element(WORKFLOW_GROUP_ITEM).each((__, linkIdx) => { - cy.element(WORKFLOW_GROUP_ITEM).eq(linkIdx).should("be.visible"); - cy.element(WORKFLOW_GROUP_ITEM) - .eq(linkIdx) - .should("have.attr", "href") - .then(href => { - cy.element(WORKFLOW_GROUP_ITEM).eq(linkIdx).click(); - cy.url().should("include", href); - }); - cy.element(WORKFLOW_GROUP).eq(groupIdx).click(); - // TODO: validate header of workflow here when it's landed - }); + const element = ifElementExists(WORKFLOW_GROUP_ITEM); + if (element) { + cy.element(WORKFLOW_GROUP_ITEM).each((__, linkIdx) => { + cy.element(WORKFLOW_GROUP_ITEM).eq(linkIdx).should("be.visible"); + cy.element(WORKFLOW_GROUP_ITEM) + .eq(linkIdx) + .should("have.attr", "href") + .then(href => { + cy.element(WORKFLOW_GROUP_ITEM).eq(linkIdx).click(); + cy.url().should("include", href); + }); + cy.element(WORKFLOW_GROUP).eq(groupIdx).click(); + // TODO: validate header of workflow here when it's landed + }); + } }); }); }); diff --git a/frontend/packages/core/src/AppLayout/drawer.tsx b/frontend/packages/core/src/AppLayout/drawer.tsx index 728e7a59e3..fd0d412d40 100644 --- a/frontend/packages/core/src/AppLayout/drawer.tsx +++ b/frontend/packages/core/src/AppLayout/drawer.tsx @@ -13,6 +13,7 @@ import _ from "lodash"; import type { WorkflowIcon } from "../AppProvider"; import type { Workflow } from "../AppProvider/workflow"; import { useAppContext } from "../Contexts"; +import { useNavigate } from "../navigation"; import type { PopperItemProps } from "../popper"; import { Popper, PopperItem } from "../popper"; @@ -37,7 +38,7 @@ const GroupList = styled(List)({ padding: "0px", }); -const GroupListItem = styled(ListItemButton)<{ icon: boolean }>( +const GroupListItem = styled(ListItemButton)<{ icon: number }>( { flexDirection: "column", minHeight: "82px", @@ -115,7 +116,9 @@ const Group = ({ closeGroup, children, }: GroupProps) => { + const navigate = useNavigate(); const anchorRef = React.useRef(null); + const singleChild = React.Children.count(children) === 1; const validIcon = icon.path && icon.path.length > 0; // n.b. if a Workflow Grouping has no workflows in it don't display it even if @@ -133,8 +136,11 @@ const Group = ({ ref={anchorRef} aria-controls={open ? "workflow-options" : undefined} aria-haspopup="true" - icon={validIcon} + icon={+validIcon} onClick={() => { + if (singleChild) { + navigate(children[0].props.to); + } updateOpenGroup(heading); }} > @@ -144,7 +150,12 @@ const Group = ({ {heading.charAt(0)} )} {heading} - + {children} diff --git a/frontend/packages/core/src/AppLayout/utils.tsx b/frontend/packages/core/src/AppLayout/utils.tsx index 3b5c315789..2796f7d5c8 100644 --- a/frontend/packages/core/src/AppLayout/utils.tsx +++ b/frontend/packages/core/src/AppLayout/utils.tsx @@ -19,6 +19,7 @@ interface TrendingWorkflow { group: string; description: string; path: string; + icon: string; } const getDisplayName = (workflow: Workflow, route: Route, delimiter: string = ":"): string => { @@ -36,6 +37,13 @@ const getDisplayName = (workflow: Workflow, route: Route, delimiter: string = ": const workflowsByTrending = (workflows: Workflow[]): TrendingWorkflow[] => { const trending = []; + const trendingIcons = {}; + + workflows.forEach(workflow => { + if (workflow?.icon?.path && !trendingIcons[workflow.group]) { + trendingIcons[workflow.group] = workflow.icon.path; + } + }); workflows.forEach(workflow => { workflow.routes.forEach(route => { @@ -45,6 +53,7 @@ const workflowsByTrending = (workflows: Workflow[]): TrendingWorkflow[] => { group: workflow.group, description: route.description, path: `${workflow.path}/${route.path}`, + icon: trendingIcons[workflow.group] ?? "", }); } }); diff --git a/frontend/packages/core/src/card.tsx b/frontend/packages/core/src/card.tsx index e1054a1442..bb66e751fe 100644 --- a/frontend/packages/core/src/card.tsx +++ b/frontend/packages/core/src/card.tsx @@ -258,20 +258,12 @@ const StyledLandingCard = styled(Card)({ lineHeight: "36px", color: "rgba(13, 16, 48, 0.6)", }, - - "& .header .icon .MuiAvatar-root": { - height: "36px", - width: "36px", - marginRight: "8px", - color: "rgba(13, 16, 48, 0.38)", - backgroundColor: "rgba(13, 16, 48, 0.12)", - }, }); const TruncatedText = styled(Typography)({ display: "-webkit-box", overflow: "hidden", - "-webkit-box-orient": "vertical", + WebkitBoxOrient: "vertical", "-webkit-line-clamp": "3", [`@media screen and (max-width: 595px), screen and (min-width: 900px) and (max-width: 950px), @@ -280,31 +272,55 @@ const TruncatedText = styled(Typography)({ }, }); +const IconAvatar = styled(Avatar)({ + height: "36px", + width: "36px", + marginRight: "8px", +}); + +const StyledAvatar = styled(IconAvatar)({ + color: "rgba(13, 16, 48, 0.38)", + backgroundColor: "rgba(13, 16, 48, 0.12)", +}); + export interface LandingCardProps extends Pick { group: string; title: string; description: string; + icon: string; } -export const LandingCard = ({ group, title, description, onClick, ...props }: LandingCardProps) => ( - - - -
-
- {group.charAt(0)} +export const LandingCard = ({ + group, + title, + description, + icon, + onClick, + ...props +}: LandingCardProps) => { + const validIcon = icon && icon.length > 0; + return ( + + + +
+ {validIcon ? ( + {group.charAt(0)} + ) : ( + {group.charAt(0)} + )} + {group}
- {group} -
-
- {title} - - {description} - -
- - - -); +
+ {title} + + {description} + +
+ + + + ); +}; export { Card, CardContent, CardHeader }; diff --git a/frontend/packages/core/src/landing.tsx b/frontend/packages/core/src/landing.tsx index af15832b00..0a5ce86021 100644 --- a/frontend/packages/core/src/landing.tsx +++ b/frontend/packages/core/src/landing.tsx @@ -82,6 +82,7 @@ const Landing: React.FC<{}> = () => { description={workflow.description} onClick={() => navigateTo(workflow.path)} key={workflow.path} + icon={workflow.icon} /> ))}