From bf4c67b68a4dc7448f96d08ae92b48ba11fca7c9 Mon Sep 17 00:00:00 2001 From: phantomjinx Date: Wed, 6 Sep 2023 17:33:38 +0100 Subject: [PATCH] feature: Ability to add custom toolbar items to main page nav bar * core.ts * Adds optional toolbarItems property to plugin interface * HawtioHeader.tsx * Picks up the plugins from the context and find the plugin that is currently being displayed. If this plugin has a toolbar item property then pick up the components into a custom toolbar group --- app/src/examples/example3/Example3.tsx | 7 +- app/src/examples/example3/ToolbarItemComp.tsx | 91 +++++++++++++++++++ app/src/examples/example3/index.ts | 2 + packages/hawtio/src/core/core.ts | 3 + packages/hawtio/src/ui/page/HawtioHeader.tsx | 23 ++++- 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 app/src/examples/example3/ToolbarItemComp.tsx diff --git a/app/src/examples/example3/Example3.tsx b/app/src/examples/example3/Example3.tsx index 475dfd94a..450088681 100644 --- a/app/src/examples/example3/Example3.tsx +++ b/app/src/examples/example3/Example3.tsx @@ -5,7 +5,12 @@ export const Example3: React.FunctionComponent = () => ( Example 3 - This is yet another example plugin. + + This is another example plugin that also demonstrates the addition of custom components + to the main header toolbar. + Components should be added in the Plugin structure using the `headerItems` array. + Toolbar components should be created as single FunctionComponents and added to the array. + ) diff --git a/app/src/examples/example3/ToolbarItemComp.tsx b/app/src/examples/example3/ToolbarItemComp.tsx new file mode 100644 index 000000000..70966fe9f --- /dev/null +++ b/app/src/examples/example3/ToolbarItemComp.tsx @@ -0,0 +1,91 @@ +import React from 'react' +import { Button, Dropdown, DropdownItem, DropdownSeparator, DropdownToggle, Modal } from '@patternfly/react-core' + +export const ToolbarItemComp1: React.FunctionComponent = () => { + const [isModalOpen, setIsModalOpen] = React.useState(false) + + const handleModalToggle = () => { + setIsModalOpen(!isModalOpen) + } + + return ( + + + + + Confirm + , + , + ]} + > + Hello World! + + + ) +} + +export const ToolbarItemComp2: React.FunctionComponent = () => { + const [isOpen, setIsOpen] = React.useState(false) + + const onToggle = (isOpen: boolean) => { + setIsOpen(isOpen) + } + + const onFocus = () => { + const element = document.getElementById('toggle-basic') + element?.focus() + } + + const onSelect = () => { + setIsOpen(false) + onFocus() + } + + const dropdownItems = [ + + Link + , + + Action + , + + Disabled link + , + + Disabled action + , + , + Separated link, + + Separated action + , + ] + + return ( + + Dropdown + + } + isOpen={isOpen} + dropdownItems={dropdownItems} + /> + ) +} diff --git a/app/src/examples/example3/index.ts b/app/src/examples/example3/index.ts index c154a57dc..10a56a2b6 100644 --- a/app/src/examples/example3/index.ts +++ b/app/src/examples/example3/index.ts @@ -1,5 +1,6 @@ import { hawtio, HawtioPlugin } from '@hawtio/react' import { Example3 } from './Example3' +import { ToolbarItemComp1, ToolbarItemComp2 } from './ToolbarItemComp' export const registerExample3: HawtioPlugin = () => { hawtio.addPlugin({ @@ -7,6 +8,7 @@ export const registerExample3: HawtioPlugin = () => { title: 'Example 3', path: '/example3', component: Example3, + headerItems: [ToolbarItemComp1, ToolbarItemComp2], isActive: async () => true, }) } diff --git a/packages/hawtio/src/core/core.ts b/packages/hawtio/src/core/core.ts index 025588e5e..e3accaa2e 100644 --- a/packages/hawtio/src/core/core.ts +++ b/packages/hawtio/src/core/core.ts @@ -13,6 +13,9 @@ export interface Plugin { // eslint-disable-next-line @typescript-eslint/no-explicit-any component: React.ComponentType + // eslint-disable-next-line @typescript-eslint/no-explicit-any + headerItems?: React.ComponentType[] + /** * Returns if this plugin should be activated. * This method needs to return a promise as the process of resolving if a plugin diff --git a/packages/hawtio/src/ui/page/HawtioHeader.tsx b/packages/hawtio/src/ui/page/HawtioHeader.tsx index e96a2d4aa..657a4068d 100644 --- a/packages/hawtio/src/ui/page/HawtioHeader.tsx +++ b/packages/hawtio/src/ui/page/HawtioHeader.tsx @@ -1,5 +1,5 @@ import { PUBLIC_USER, userService } from '@hawtiosrc/auth' -import { DEFAULT_APP_NAME, useHawtconfig } from '@hawtiosrc/core' +import { DEFAULT_APP_NAME, useHawtconfig, Plugin } from '@hawtiosrc/core' import { hawtioLogo, userAvatar } from '@hawtiosrc/img' import { preferencesService } from '@hawtiosrc/preferences/preferences-service' import { HawtioAbout } from '@hawtiosrc/ui/about' @@ -22,7 +22,7 @@ import { } from '@patternfly/react-core' import { BarsIcon, HelpIcon } from '@patternfly/react-icons' import React, { useContext, useState } from 'react' -import { Link } from 'react-router-dom' +import { Link, useLocation } from 'react-router-dom' import './HawtioHeader.css' import { PageContext } from './context' @@ -72,7 +72,8 @@ const HawtioBrand: React.FunctionComponent = () => { } const HawtioHeaderToolbar: React.FunctionComponent = () => { - const { username } = useContext(PageContext) + const { username, plugins } = useContext(PageContext) + const location = useLocation() const isPublic = username === PUBLIC_USER @@ -104,9 +105,25 @@ const HawtioHeaderToolbar: React.FunctionComponent = () => { userItems.pop() } + /* + * Determine which plugin is currently displaying + * based on the path of the current location + */ + const pluginFromLocation = (): Plugin | null => { + const path = location.pathname + return plugins.find(plugin => path.startsWith(plugin.path)) ?? null + } + + const plugin = pluginFromLocation() + return ( + + {plugin?.headerItems?.map((comp, index) => ( + {React.createElement(comp)} + ))} +