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 @@