From d33ac8c588eafb5514f388637e47b31172f644e5 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Thu, 30 Dec 2021 17:46:12 +0200 Subject: [PATCH] refactor(app): create a composed header component [EE-2329] (#6326) * refactor(app): create a composed header component refactor(app): support single child breadcrumbs fix(app): fix breadcrumbs warning * refactor(app): import breadcrumbs * refactor(app): support object breadcrumbs * chore(app): write tests for header components --- .eslintrc.yml | 2 + .../Header/Breadcrumbs/Breadcrumbs.tsx | 20 --------- .../Breadcrumbs/Breadcrumbs.css | 0 .../Breadcrumbs/Breadcrumbs.stories.tsx | 22 +++++----- .../Breadcrumbs/Breadcrumbs.test.tsx | 15 +++++++ .../PageHeader/Breadcrumbs/Breadcrumbs.tsx | 39 +++++++++++++++++ .../PageHeader/Breadcrumbs/index.tsx | 1 + .../HeaderContainer.css} | 0 .../HeaderContainer.html} | 0 .../HeaderContainer.stories.tsx} | 27 ++++++------ .../HeaderContainer.tsx} | 8 ++-- .../HeaderContent.controller.js | 0 .../{Header => PageHeader}/HeaderContent.html | 4 +- .../HeaderContent.module.css | 0 .../PageHeader/HeaderContent.test.tsx | 35 ++++++++++++++++ .../{Header => PageHeader}/HeaderContent.tsx | 2 +- .../HeaderTitle.controller.js | 0 .../{Header => PageHeader}/HeaderTitle.html | 0 .../PageHeader/HeaderTitle.test.tsx | 34 +++++++++++++++ .../{Header => PageHeader}/HeaderTitle.tsx | 2 +- .../PageHeader/PageHeader.stories.tsx | 42 +++++++++++++++++++ .../components/PageHeader/PageHeader.test.tsx | 22 ++++++++++ .../components/PageHeader/PageHeader.tsx | 33 +++++++++++++++ .../__snapshots__/HeaderContent.test.tsx.snap | 3 ++ .../__snapshots__/HeaderTitle.test.tsx.snap | 3 ++ .../{Header => PageHeader}/index.ts | 6 ++- app/portainer/components/index.js | 2 +- package.json | 4 +- 28 files changed, 270 insertions(+), 56 deletions(-) delete mode 100644 app/portainer/components/Header/Breadcrumbs/Breadcrumbs.tsx rename app/portainer/components/{Header => PageHeader}/Breadcrumbs/Breadcrumbs.css (100%) rename app/portainer/components/{Header => PageHeader}/Breadcrumbs/Breadcrumbs.stories.tsx (50%) create mode 100644 app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.test.tsx create mode 100644 app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.tsx create mode 100644 app/portainer/components/PageHeader/Breadcrumbs/index.tsx rename app/portainer/components/{Header/Header.css => PageHeader/HeaderContainer.css} (100%) rename app/portainer/components/{Header/Header.html => PageHeader/HeaderContainer.html} (100%) rename app/portainer/components/{Header/Header.stories.tsx => PageHeader/HeaderContainer.stories.tsx} (59%) rename app/portainer/components/{Header/Header.tsx => PageHeader/HeaderContainer.tsx} (71%) rename app/portainer/components/{Header => PageHeader}/HeaderContent.controller.js (100%) rename app/portainer/components/{Header => PageHeader}/HeaderContent.html (72%) rename app/portainer/components/{Header => PageHeader}/HeaderContent.module.css (100%) create mode 100644 app/portainer/components/PageHeader/HeaderContent.test.tsx rename app/portainer/components/{Header => PageHeader}/HeaderContent.tsx (96%) rename app/portainer/components/{Header => PageHeader}/HeaderTitle.controller.js (100%) rename app/portainer/components/{Header => PageHeader}/HeaderTitle.html (100%) create mode 100644 app/portainer/components/PageHeader/HeaderTitle.test.tsx rename app/portainer/components/{Header => PageHeader}/HeaderTitle.tsx (93%) create mode 100644 app/portainer/components/PageHeader/PageHeader.stories.tsx create mode 100644 app/portainer/components/PageHeader/PageHeader.test.tsx create mode 100644 app/portainer/components/PageHeader/PageHeader.tsx create mode 100644 app/portainer/components/PageHeader/__snapshots__/HeaderContent.test.tsx.snap create mode 100644 app/portainer/components/PageHeader/__snapshots__/HeaderTitle.test.tsx.snap rename app/portainer/components/{Header => PageHeader}/index.ts (62%) diff --git a/.eslintrc.yml b/.eslintrc.yml index 9e8fc7528..80295fbb1 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -94,3 +94,5 @@ overrides: - 'plugin:jest/style' env: 'jest/globals': true + rules: + 'react/jsx-no-constructed-context-values': off diff --git a/app/portainer/components/Header/Breadcrumbs/Breadcrumbs.tsx b/app/portainer/components/Header/Breadcrumbs/Breadcrumbs.tsx deleted file mode 100644 index 54a6e2566..000000000 --- a/app/portainer/components/Header/Breadcrumbs/Breadcrumbs.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { ReactNode } from 'react'; - -import './Breadcrumbs.css'; - -interface Props { - children: ReactNode[]; -} - -export function Breadcrumbs({ children }: Props) { - return ( -
- {children.map((child, index) => ( - <> - {child} - {index !== children.length - 1 ? ' > ' : ''} - - ))} -
- ); -} diff --git a/app/portainer/components/Header/Breadcrumbs/Breadcrumbs.css b/app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.css similarity index 100% rename from app/portainer/components/Header/Breadcrumbs/Breadcrumbs.css rename to app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.css diff --git a/app/portainer/components/Header/Breadcrumbs/Breadcrumbs.stories.tsx b/app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.stories.tsx similarity index 50% rename from app/portainer/components/Header/Breadcrumbs/Breadcrumbs.stories.tsx rename to app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.stories.tsx index b934a442f..b018b9b26 100644 --- a/app/portainer/components/Header/Breadcrumbs/Breadcrumbs.stories.tsx +++ b/app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.stories.tsx @@ -1,12 +1,10 @@ import { Meta } from '@storybook/react'; import { UIRouter, pushStateLocationPlugin } from '@uirouter/react'; -import { Link } from '@/portainer/components/Link'; - import { Breadcrumbs } from './Breadcrumbs'; const meta: Meta = { - title: 'Components/Header/Breadcrumbs', + title: 'Components/PageHeader/Breadcrumbs', component: Breadcrumbs, }; @@ -17,13 +15,17 @@ export { Example }; function Example() { return ( - - Environments - - endpointName - - String item - + ); } diff --git a/app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.test.tsx b/app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.test.tsx new file mode 100644 index 000000000..cb128b903 --- /dev/null +++ b/app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.test.tsx @@ -0,0 +1,15 @@ +import { render } from '@/react-tools/test-utils'; + +import { Breadcrumbs } from './Breadcrumbs'; + +test('should display a Breadcrumbs, breadcrumbs should be separated by >', async () => { + const breadcrumbs = [ + { label: 'bread1' }, + { label: 'bread2' }, + { label: 'bread3' }, + ]; + const { queryByText } = render(); + + const heading = queryByText(breadcrumbs.map((b) => b.label).join(' > ')); + expect(heading).toBeVisible(); +}); diff --git a/app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.tsx b/app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.tsx new file mode 100644 index 000000000..197023a6b --- /dev/null +++ b/app/portainer/components/PageHeader/Breadcrumbs/Breadcrumbs.tsx @@ -0,0 +1,39 @@ +import { Fragment } from 'react'; + +import { Link } from '@/portainer/components/Link'; + +import './Breadcrumbs.css'; + +export interface Crumb { + label: string; + link?: string; + linkParams?: Record; +} +interface Props { + breadcrumbs: Crumb[]; +} + +export function Breadcrumbs({ breadcrumbs }: Props) { + return ( +
+ {breadcrumbs.map((crumb, index) => ( + + {renderCrumb(crumb)} + {index !== breadcrumbs.length - 1 ? ' > ' : ''} + + ))} +
+ ); +} + +function renderCrumb(crumb: Crumb) { + if (crumb.link) { + return ( + + {crumb.label} + + ); + } + + return crumb.label; +} diff --git a/app/portainer/components/PageHeader/Breadcrumbs/index.tsx b/app/portainer/components/PageHeader/Breadcrumbs/index.tsx new file mode 100644 index 000000000..28140a257 --- /dev/null +++ b/app/portainer/components/PageHeader/Breadcrumbs/index.tsx @@ -0,0 +1 @@ +export { Breadcrumbs } from './Breadcrumbs'; diff --git a/app/portainer/components/Header/Header.css b/app/portainer/components/PageHeader/HeaderContainer.css similarity index 100% rename from app/portainer/components/Header/Header.css rename to app/portainer/components/PageHeader/HeaderContainer.css diff --git a/app/portainer/components/Header/Header.html b/app/portainer/components/PageHeader/HeaderContainer.html similarity index 100% rename from app/portainer/components/Header/Header.html rename to app/portainer/components/PageHeader/HeaderContainer.html diff --git a/app/portainer/components/Header/Header.stories.tsx b/app/portainer/components/PageHeader/HeaderContainer.stories.tsx similarity index 59% rename from app/portainer/components/Header/Header.stories.tsx rename to app/portainer/components/PageHeader/HeaderContainer.stories.tsx index 45b662bf1..8b3bc7faf 100644 --- a/app/portainer/components/Header/Header.stories.tsx +++ b/app/portainer/components/PageHeader/HeaderContainer.stories.tsx @@ -1,18 +1,17 @@ import { Meta, Story } from '@storybook/react'; import { useMemo } from 'react'; -import { Link } from '@/portainer/components/Link'; import { UserContext } from '@/portainer/hooks/useUser'; import { UserViewModel } from '@/portainer/models/user'; -import { Header } from './Header'; -import { Breadcrumbs } from './Breadcrumbs/Breadcrumbs'; - -import { HeaderContent, HeaderTitle } from '.'; +import { HeaderContainer } from './HeaderContainer'; +import { Breadcrumbs } from './Breadcrumbs'; +import { HeaderTitle } from './HeaderTitle'; +import { HeaderContent } from './HeaderContent'; export default { - component: Header, - title: 'Components/Header', + component: HeaderContainer, + title: 'Components/PageHeader/HeaderContainer', } as Meta; interface StoryProps { @@ -27,15 +26,17 @@ function Template({ title }: StoryProps) { return ( -
+ - - Container instances - Add container - + -
+
); } diff --git a/app/portainer/components/Header/Header.tsx b/app/portainer/components/PageHeader/HeaderContainer.tsx similarity index 71% rename from app/portainer/components/Header/Header.tsx rename to app/portainer/components/PageHeader/HeaderContainer.tsx index 65e08e833..5cb7ae55b 100644 --- a/app/portainer/components/Header/Header.tsx +++ b/app/portainer/components/PageHeader/HeaderContainer.tsx @@ -1,6 +1,6 @@ import { PropsWithChildren, createContext, useContext } from 'react'; -import './Header.css'; +import './HeaderContainer.css'; const Context = createContext(null); @@ -8,11 +8,11 @@ export function useHeaderContext() { const context = useContext(Context); if (context == null) { - throw new Error('Should be nested inside a Header component'); + throw new Error('Should be nested inside a HeaderContainer component'); } } -export function Header({ children }: PropsWithChildren) { +export function HeaderContainer({ children }: PropsWithChildren) { return (
@@ -27,5 +27,5 @@ export function Header({ children }: PropsWithChildren) { export const HeaderAngular = { transclude: true, - templateUrl: './Header.html', + templateUrl: './HeaderContainer.html', }; diff --git a/app/portainer/components/Header/HeaderContent.controller.js b/app/portainer/components/PageHeader/HeaderContent.controller.js similarity index 100% rename from app/portainer/components/Header/HeaderContent.controller.js rename to app/portainer/components/PageHeader/HeaderContent.controller.js diff --git a/app/portainer/components/Header/HeaderContent.html b/app/portainer/components/PageHeader/HeaderContent.html similarity index 72% rename from app/portainer/components/Header/HeaderContent.html rename to app/portainer/components/PageHeader/HeaderContent.html index b7eb182c9..2b5d569e5 100644 --- a/app/portainer/components/Header/HeaderContent.html +++ b/app/portainer/components/PageHeader/HeaderContent.html @@ -1,10 +1,10 @@