@red-hat-developer-hub/backstage-plugin-global-header 1.20.3 → 1.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +92 -8
- package/config.d.ts +75 -33
- package/dist/alpha/components/ApplicationLauncherDropdown.esm.js +35 -0
- package/dist/alpha/components/ApplicationLauncherDropdown.esm.js.map +1 -0
- package/dist/alpha/components/GlobalHeader.esm.js +36 -0
- package/dist/alpha/components/GlobalHeader.esm.js.map +1 -0
- package/dist/alpha/components/GlobalHeaderDropdown.esm.js +60 -0
- package/dist/alpha/components/GlobalHeaderDropdown.esm.js.map +1 -0
- package/dist/alpha/components/GlobalHeaderDropdownContent.esm.js +52 -0
- package/dist/alpha/components/GlobalHeaderDropdownContent.esm.js.map +1 -0
- package/dist/alpha/components/GlobalHeaderMenuItem.esm.js +39 -0
- package/dist/alpha/components/GlobalHeaderMenuItem.esm.js.map +1 -0
- package/dist/alpha/components/HelpDropdown.esm.js +32 -0
- package/dist/alpha/components/HelpDropdown.esm.js.map +1 -0
- package/dist/alpha/components/MyProfileMenuItem.esm.js +58 -0
- package/dist/alpha/components/MyProfileMenuItem.esm.js.map +1 -0
- package/dist/alpha/components/ProfileDropdown.esm.js +97 -0
- package/dist/alpha/components/ProfileDropdown.esm.js.map +1 -0
- package/dist/alpha/defaults/index.esm.js +26 -0
- package/dist/alpha/defaults/index.esm.js.map +1 -0
- package/dist/alpha/defaults/menuItemExtensions.esm.js +67 -0
- package/dist/alpha/defaults/menuItemExtensions.esm.js.map +1 -0
- package/dist/alpha/defaults/rhdhLogo.esm.js +7 -0
- package/dist/alpha/defaults/rhdhLogo.esm.js.map +1 -0
- package/dist/alpha/defaults/toolbarExtensions.esm.js +74 -0
- package/dist/alpha/defaults/toolbarExtensions.esm.js.map +1 -0
- package/dist/alpha/extensions/GlobalHeaderContext.esm.js +31 -0
- package/dist/alpha/extensions/GlobalHeaderContext.esm.js.map +1 -0
- package/dist/alpha/extensions/blueprints.esm.js +112 -0
- package/dist/alpha/extensions/blueprints.esm.js.map +1 -0
- package/dist/alpha/extensions/dataRefs.esm.js +11 -0
- package/dist/alpha/extensions/dataRefs.esm.js.map +1 -0
- package/dist/alpha/extensions/globalHeaderModule.esm.js +72 -0
- package/dist/alpha/extensions/globalHeaderModule.esm.js.map +1 -0
- package/dist/alpha/plugin.esm.js +28 -0
- package/dist/alpha/plugin.esm.js.map +1 -0
- package/dist/alpha/utils/menuItemGrouping.esm.js +52 -0
- package/dist/alpha/utils/menuItemGrouping.esm.js.map +1 -0
- package/dist/alpha/utils/readConfigComponents.esm.js +29 -0
- package/dist/alpha/utils/readConfigComponents.esm.js.map +1 -0
- package/dist/alpha/utils/readConfigMenuItems.esm.js +19 -0
- package/dist/alpha/utils/readConfigMenuItems.esm.js.map +1 -0
- package/dist/alpha.d.ts +1255 -0
- package/dist/alpha.esm.js +28 -0
- package/dist/alpha.esm.js.map +1 -0
- package/dist/components/HeaderDropdownComponent/HeaderDropdownComponent.esm.js +23 -15
- package/dist/components/HeaderDropdownComponent/HeaderDropdownComponent.esm.js.map +1 -1
- package/dist/components/HeaderDropdownComponent/MenuSection.esm.js +15 -3
- package/dist/components/HeaderDropdownComponent/MenuSection.esm.js.map +1 -1
- package/dist/components/HeaderIcon/HeaderIcon.esm.js +5 -1
- package/dist/components/HeaderIcon/HeaderIcon.esm.js.map +1 -1
- package/dist/components/HeaderIconButton/HeaderIconButton.esm.js +3 -5
- package/dist/components/HeaderIconButton/HeaderIconButton.esm.js.map +1 -1
- package/dist/components/LogoutButton/LogoutButton.esm.js +4 -2
- package/dist/components/LogoutButton/LogoutButton.esm.js.map +1 -1
- package/dist/components/MenuItemLink/MenuItemLink.esm.js.map +1 -1
- package/dist/defaultMountPoints/defaultMountPoints.esm.js +7 -10
- package/dist/defaultMountPoints/defaultMountPoints.esm.js.map +1 -1
- package/dist/hooks/useDropdownManager.esm.js +10 -5
- package/dist/hooks/useDropdownManager.esm.js.map +1 -1
- package/dist/index.d.ts +7 -66
- package/dist/index.esm.js.map +1 -1
- package/dist/plugin.esm.js +6 -7
- package/dist/plugin.esm.js.map +1 -1
- package/dist/types/index.d-CdKdPbQ2.d.ts +67 -0
- package/package.json +57 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-global-header
|
|
2
2
|
|
|
3
|
+
## 1.21.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 351d260: Fixed the css issue when drawer opens, fixed the validity check bug
|
|
8
|
+
|
|
9
|
+
## 1.21.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- a49a2cc: Added a new frontend system based header
|
|
14
|
+
|
|
3
15
|
## 1.20.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,13 +1,97 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Global Header Plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A configurable and extensible global header for [Red Hat Developer Hub](https://developers.redhat.com/rhdh) (RHDH), built as a [Backstage](https://backstage.io) frontend plugin.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- Sticky header bar with company logo, search, notifications, and user profile
|
|
8
|
+
- Dropdown menus for application launcher, help/support, and user profile
|
|
9
|
+
- Extensible via the **new frontend system** (extension blueprints) or **legacy mount points**
|
|
10
|
+
- Config-driven menu items via `app-config.yaml` (no code required)
|
|
11
|
+
- Full i18n/translation support
|
|
12
|
+
- Themeable (light/dark mode, custom branding)
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
## Installation
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
```bash
|
|
17
|
+
yarn --cwd packages/app add @red-hat-developer-hub/backstage-plugin-global-header
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### New Frontend System
|
|
23
|
+
|
|
24
|
+
Import the plugin and module in your NFS app:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { createApp } from '@backstage/frontend-defaults';
|
|
28
|
+
import globalHeaderPlugin, {
|
|
29
|
+
globalHeaderModule,
|
|
30
|
+
} from '@red-hat-developer-hub/backstage-plugin-global-header/alpha';
|
|
31
|
+
|
|
32
|
+
export default createApp({
|
|
33
|
+
features: [
|
|
34
|
+
// ... other plugins
|
|
35
|
+
globalHeaderModule,
|
|
36
|
+
globalHeaderPlugin,
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Other plugins can contribute toolbar items and dropdown menu items using `GlobalHeaderComponentBlueprint` and `GlobalHeaderMenuItemBlueprint`. See the [New Frontend System documentation](../../docs/new-frontend-system.md) for detailed examples and API reference.
|
|
42
|
+
|
|
43
|
+
### Legacy (Mount Points)
|
|
44
|
+
|
|
45
|
+
For legacy Backstage apps using dynamic plugin mount points, see the [Configuration documentation](../../docs/configuration.md).
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
### Config-driven toolbar buttons and menu items
|
|
50
|
+
|
|
51
|
+
Add toolbar buttons and dropdown menu items directly from `app-config.yaml`:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
globalHeader:
|
|
55
|
+
components:
|
|
56
|
+
- title: Dashboard
|
|
57
|
+
icon: dashboard
|
|
58
|
+
link: /dashboard
|
|
59
|
+
priority: 75
|
|
60
|
+
|
|
61
|
+
menuItems:
|
|
62
|
+
- target: app-launcher
|
|
63
|
+
title: Internal Wiki
|
|
64
|
+
icon: article
|
|
65
|
+
link: https://wiki.internal.example.com
|
|
66
|
+
sectionLabel: Resources
|
|
67
|
+
priority: 80
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Company logo branding
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
app:
|
|
74
|
+
branding:
|
|
75
|
+
fullLogo:
|
|
76
|
+
light: 'data:image/svg+xml;base64,...'
|
|
77
|
+
dark: 'data:image/svg+xml;base64,...'
|
|
78
|
+
fullLogoWidth: 200px
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
|
|
83
|
+
- [New Frontend System Guide](../../docs/new-frontend-system.md) -- Blueprints, building blocks, and integration guide for plugin authors
|
|
84
|
+
- [Configuration](../../docs/configuration.md) -- Dynamic plugin setup for legacy apps
|
|
85
|
+
- [Components](../../docs/components/) -- HeaderButton, HeaderIconButton, Spacer, Divider reference
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cd workspaces/global-header
|
|
91
|
+
yarn install
|
|
92
|
+
yarn start
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
Apache-2.0
|
package/config.d.ts
CHANGED
|
@@ -14,38 +14,80 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* @visibility frontend
|
|
46
|
-
*/
|
|
47
|
-
fullLogoWidth?: string | number;
|
|
48
|
-
};
|
|
17
|
+
export interface Config {
|
|
18
|
+
app?: {
|
|
19
|
+
branding?: {
|
|
20
|
+
/**
|
|
21
|
+
* Base64 URI for the full logo. If the value is a string, it is used as the logo for both themes.
|
|
22
|
+
* @visibility frontend
|
|
23
|
+
*/
|
|
24
|
+
fullLogo?:
|
|
25
|
+
| string
|
|
26
|
+
| {
|
|
27
|
+
/**
|
|
28
|
+
* Base64 URI for the logo in light theme
|
|
29
|
+
* @visibility frontend
|
|
30
|
+
*/
|
|
31
|
+
light: string;
|
|
32
|
+
/**
|
|
33
|
+
* Base64 URI for the logo in dark theme
|
|
34
|
+
* @visibility frontend
|
|
35
|
+
*/
|
|
36
|
+
dark: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Fallback width for the full logo in the global header.
|
|
40
|
+
* Accepts any valid CSS length (e.g. `'200px'`, `'12rem'`).
|
|
41
|
+
* Used only when a `width` prop isn't supplied through the extension configuration.
|
|
42
|
+
* @visibility frontend
|
|
43
|
+
*/
|
|
44
|
+
fullLogoWidth?: string | number;
|
|
49
45
|
};
|
|
50
|
-
}
|
|
46
|
+
};
|
|
47
|
+
globalHeader?: {
|
|
48
|
+
/**
|
|
49
|
+
* Simple icon-button toolbar components that deployers can add without
|
|
50
|
+
* writing a plugin. Each entry renders a clickable icon in the header bar.
|
|
51
|
+
* @visibility frontend
|
|
52
|
+
*/
|
|
53
|
+
components?: Array<{
|
|
54
|
+
/** @visibility frontend */
|
|
55
|
+
title: string;
|
|
56
|
+
/** @visibility frontend */
|
|
57
|
+
titleKey?: string;
|
|
58
|
+
/** @visibility frontend */
|
|
59
|
+
icon: string;
|
|
60
|
+
/** @visibility frontend */
|
|
61
|
+
link: string;
|
|
62
|
+
/** @visibility frontend */
|
|
63
|
+
tooltip?: string;
|
|
64
|
+
/** @visibility frontend */
|
|
65
|
+
priority?: number;
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* Simple link menu items that deployers can add without writing a plugin.
|
|
69
|
+
* Each item is injected into the dropdown identified by `target`.
|
|
70
|
+
* @visibility frontend
|
|
71
|
+
*/
|
|
72
|
+
menuItems?: Array<{
|
|
73
|
+
/** @visibility frontend */
|
|
74
|
+
target: string;
|
|
75
|
+
/** @visibility frontend */
|
|
76
|
+
title: string;
|
|
77
|
+
/** @visibility frontend */
|
|
78
|
+
titleKey?: string;
|
|
79
|
+
/** @visibility frontend */
|
|
80
|
+
icon?: string;
|
|
81
|
+
/** @visibility frontend */
|
|
82
|
+
link: string;
|
|
83
|
+
/** @visibility frontend */
|
|
84
|
+
sectionLabel?: string;
|
|
85
|
+
/** @visibility frontend */
|
|
86
|
+
sectionLink?: string;
|
|
87
|
+
/** @visibility frontend */
|
|
88
|
+
sectionLinkLabel?: string;
|
|
89
|
+
/** @visibility frontend */
|
|
90
|
+
priority?: number;
|
|
91
|
+
}>;
|
|
92
|
+
};
|
|
51
93
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import AppsIcon from '@mui/icons-material/Apps';
|
|
3
|
+
import AppRegistrationIcon from '@mui/icons-material/AppRegistration';
|
|
4
|
+
import { useTranslation } from '../../hooks/useTranslation.esm.js';
|
|
5
|
+
import { DropdownEmptyState } from '../../components/HeaderDropdownComponent/DropdownEmptyState.esm.js';
|
|
6
|
+
import { GlobalHeaderDropdown } from './GlobalHeaderDropdown.esm.js';
|
|
7
|
+
|
|
8
|
+
const ApplicationLauncherDropdown = () => {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
GlobalHeaderDropdown,
|
|
12
|
+
{
|
|
13
|
+
target: "app-launcher",
|
|
14
|
+
isIconButton: true,
|
|
15
|
+
tooltip: t("applicationLauncher.tooltip"),
|
|
16
|
+
buttonContent: /* @__PURE__ */ jsx(AppsIcon, {}),
|
|
17
|
+
emptyState: /* @__PURE__ */ jsx(
|
|
18
|
+
DropdownEmptyState,
|
|
19
|
+
{
|
|
20
|
+
title: t("applicationLauncher.noLinksTitle"),
|
|
21
|
+
subTitle: t("applicationLauncher.noLinksSubtitle"),
|
|
22
|
+
icon: /* @__PURE__ */ jsx(
|
|
23
|
+
AppRegistrationIcon,
|
|
24
|
+
{
|
|
25
|
+
sx: { fontSize: 64, color: "text.disabled" }
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { ApplicationLauncherDropdown };
|
|
35
|
+
//# sourceMappingURL=ApplicationLauncherDropdown.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApplicationLauncherDropdown.esm.js","sources":["../../../src/alpha/components/ApplicationLauncherDropdown.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport AppsIcon from '@mui/icons-material/Apps';\nimport AppRegistrationIcon from '@mui/icons-material/AppRegistration';\n\nimport { useTranslation } from '../../hooks/useTranslation';\nimport { DropdownEmptyState } from '../../components/HeaderDropdownComponent/DropdownEmptyState';\nimport { GlobalHeaderDropdown } from './GlobalHeaderDropdown';\n\n/**\n * Application Launcher dropdown. Collects menu items from the `'app-launcher'`\n * target.\n *\n * @internal\n */\nexport const ApplicationLauncherDropdown = () => {\n const { t } = useTranslation();\n return (\n <GlobalHeaderDropdown\n target=\"app-launcher\"\n isIconButton\n tooltip={t('applicationLauncher.tooltip')}\n buttonContent={<AppsIcon />}\n emptyState={\n <DropdownEmptyState\n title={t('applicationLauncher.noLinksTitle')}\n subTitle={t('applicationLauncher.noLinksSubtitle')}\n icon={\n <AppRegistrationIcon\n sx={{ fontSize: 64, color: 'text.disabled' }}\n />\n }\n />\n }\n />\n );\n};\n"],"names":[],"mappings":";;;;;;;AA6BO,MAAM,8BAA8B,MAAM;AAC/C,EAAM,MAAA,EAAE,CAAE,EAAA,GAAI,cAAe,EAAA;AAC7B,EACE,uBAAA,GAAA;AAAA,IAAC,oBAAA;AAAA,IAAA;AAAA,MACC,MAAO,EAAA,cAAA;AAAA,MACP,YAAY,EAAA,IAAA;AAAA,MACZ,OAAA,EAAS,EAAE,6BAA6B,CAAA;AAAA,MACxC,aAAA,sBAAgB,QAAS,EAAA,EAAA,CAAA;AAAA,MACzB,UACE,kBAAA,GAAA;AAAA,QAAC,kBAAA;AAAA,QAAA;AAAA,UACC,KAAA,EAAO,EAAE,kCAAkC,CAAA;AAAA,UAC3C,QAAA,EAAU,EAAE,qCAAqC,CAAA;AAAA,UACjD,IACE,kBAAA,GAAA;AAAA,YAAC,mBAAA;AAAA,YAAA;AAAA,cACC,EAAI,EAAA,EAAE,QAAU,EAAA,EAAA,EAAI,OAAO,eAAgB;AAAA;AAAA;AAC7C;AAAA;AAEJ;AAAA,GAEJ;AAEJ;;;;"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { ErrorBoundary } from '@backstage/core-components';
|
|
3
|
+
import AppBar from '@mui/material/AppBar';
|
|
4
|
+
import Box from '@mui/material/Box';
|
|
5
|
+
import Toolbar from '@mui/material/Toolbar';
|
|
6
|
+
import { useGlobalHeaderComponents } from '../extensions/GlobalHeaderContext.esm.js';
|
|
7
|
+
|
|
8
|
+
const GlobalHeader = () => {
|
|
9
|
+
const components = useGlobalHeaderComponents();
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
AppBar,
|
|
12
|
+
{
|
|
13
|
+
position: "sticky",
|
|
14
|
+
component: "nav",
|
|
15
|
+
id: "global-header",
|
|
16
|
+
sx: {
|
|
17
|
+
width: "auto",
|
|
18
|
+
marginRight: "var(--docked-drawer-width, 0px)",
|
|
19
|
+
transition: "margin-right 225ms cubic-bezier(0, 0, 0.2, 1)"
|
|
20
|
+
},
|
|
21
|
+
children: /* @__PURE__ */ jsx(
|
|
22
|
+
Toolbar,
|
|
23
|
+
{
|
|
24
|
+
sx: {
|
|
25
|
+
gap: 1,
|
|
26
|
+
color: (theme) => theme.rhdh?.general?.appBarForegroundColor ?? theme.palette.text.primary
|
|
27
|
+
},
|
|
28
|
+
children: components.map((item, index) => /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(Box, { sx: item.layout, children: /* @__PURE__ */ jsx(item.component, {}) }) }, `gh-component-${index}`))
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { GlobalHeader };
|
|
36
|
+
//# sourceMappingURL=GlobalHeader.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GlobalHeader.esm.js","sources":["../../../src/alpha/components/GlobalHeader.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ErrorBoundary } from '@backstage/core-components';\n\nimport AppBar from '@mui/material/AppBar';\nimport Box from '@mui/material/Box';\nimport Toolbar from '@mui/material/Toolbar';\n\nimport { useGlobalHeaderComponents } from '../extensions/GlobalHeaderContext';\n\n/**\n * Global header bar. Reads toolbar items from GlobalHeaderContext\n * and renders them in a sticky AppBar.\n *\n * @alpha\n */\nexport const GlobalHeader = () => {\n const components = useGlobalHeaderComponents();\n\n return (\n <AppBar\n position=\"sticky\"\n component=\"nav\"\n id=\"global-header\"\n sx={{\n width: 'auto',\n marginRight: 'var(--docked-drawer-width, 0px)',\n transition: 'margin-right 225ms cubic-bezier(0, 0, 0.2, 1)',\n }}\n >\n <Toolbar\n sx={{\n gap: 1,\n color: theme =>\n (theme as any).rhdh?.general?.appBarForegroundColor ??\n theme.palette.text.primary,\n }}\n >\n {components.map((item, index) => (\n <ErrorBoundary key={`gh-component-${index}`}>\n <Box sx={item.layout}>\n <item.component />\n </Box>\n </ErrorBoundary>\n ))}\n </Toolbar>\n </AppBar>\n );\n};\n"],"names":[],"mappings":";;;;;;;AA8BO,MAAM,eAAe,MAAM;AAChC,EAAA,MAAM,aAAa,yBAA0B,EAAA;AAE7C,EACE,uBAAA,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,QAAS,EAAA,QAAA;AAAA,MACT,SAAU,EAAA,KAAA;AAAA,MACV,EAAG,EAAA,eAAA;AAAA,MACH,EAAI,EAAA;AAAA,QACF,KAAO,EAAA,MAAA;AAAA,QACP,WAAa,EAAA,iCAAA;AAAA,QACb,UAAY,EAAA;AAAA,OACd;AAAA,MAEA,QAAA,kBAAA,GAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UACC,EAAI,EAAA;AAAA,YACF,GAAK,EAAA,CAAA;AAAA,YACL,KAAA,EAAO,WACJ,KAAc,CAAA,IAAA,EAAM,SAAS,qBAC9B,IAAA,KAAA,CAAM,QAAQ,IAAK,CAAA;AAAA,WACvB;AAAA,UAEC,QAAA,EAAA,UAAA,CAAW,IAAI,CAAC,IAAA,EAAM,0BACpB,GAAA,CAAA,aAAA,EAAA,EACC,8BAAC,GAAI,EAAA,EAAA,EAAA,EAAI,KAAK,MACZ,EAAA,QAAA,kBAAA,GAAA,CAAC,KAAK,SAAL,EAAA,EAAe,GAClB,CAHkB,EAAA,EAAA,CAAA,aAAA,EAAgB,KAAK,CAAA,CAIzC,CACD;AAAA;AAAA;AACH;AAAA,GACF;AAEJ;;;;"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo, useRef, useState, useLayoutEffect } from 'react';
|
|
3
|
+
import { useGlobalHeaderMenuItems } from '../extensions/GlobalHeaderContext.esm.js';
|
|
4
|
+
import { buildDropdownEntries } from '../utils/menuItemGrouping.esm.js';
|
|
5
|
+
import { useDropdownManager } from '../../hooks/useDropdownManager.esm.js';
|
|
6
|
+
import '@backstage/core-plugin-api/alpha';
|
|
7
|
+
import '../../translations/index.esm.js';
|
|
8
|
+
import '@backstage/core-plugin-api';
|
|
9
|
+
import { HeaderDropdownComponent } from '../../components/HeaderDropdownComponent/HeaderDropdownComponent.esm.js';
|
|
10
|
+
import { GlobalHeaderDropdownContent } from './GlobalHeaderDropdownContent.esm.js';
|
|
11
|
+
|
|
12
|
+
const GlobalHeaderDropdown = ({
|
|
13
|
+
target,
|
|
14
|
+
buttonContent,
|
|
15
|
+
buttonProps,
|
|
16
|
+
isIconButton,
|
|
17
|
+
tooltip,
|
|
18
|
+
emptyState,
|
|
19
|
+
trackValidity = false
|
|
20
|
+
}) => {
|
|
21
|
+
const { anchorEl, handleOpen, handleClose } = useDropdownManager();
|
|
22
|
+
const menuItems = useGlobalHeaderMenuItems(target);
|
|
23
|
+
const entries = useMemo(() => buildDropdownEntries(menuItems), [menuItems]);
|
|
24
|
+
const menuListRef = useRef(null);
|
|
25
|
+
const [hasVisibleItems, setHasVisibleItems] = useState(true);
|
|
26
|
+
const isOpen = Boolean(anchorEl);
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
if (!trackValidity || !isOpen || !menuListRef.current) return;
|
|
29
|
+
const found = menuListRef.current.querySelector('[role="menuitem"]') !== null;
|
|
30
|
+
if (found !== hasVisibleItems) {
|
|
31
|
+
setHasVisibleItems(found);
|
|
32
|
+
}
|
|
33
|
+
}, [trackValidity, hasVisibleItems, isOpen]);
|
|
34
|
+
if (menuItems.length === 0 && !emptyState) return null;
|
|
35
|
+
const showEmpty = entries.length === 0 || trackValidity && !hasVisibleItems;
|
|
36
|
+
return /* @__PURE__ */ jsx(
|
|
37
|
+
HeaderDropdownComponent,
|
|
38
|
+
{
|
|
39
|
+
buttonContent,
|
|
40
|
+
buttonProps,
|
|
41
|
+
isIconButton,
|
|
42
|
+
tooltip,
|
|
43
|
+
onOpen: handleOpen,
|
|
44
|
+
onClose: handleClose,
|
|
45
|
+
anchorEl,
|
|
46
|
+
menuListRef: trackValidity ? menuListRef : void 0,
|
|
47
|
+
children: showEmpty ? emptyState : /* @__PURE__ */ jsx(
|
|
48
|
+
GlobalHeaderDropdownContent,
|
|
49
|
+
{
|
|
50
|
+
entries,
|
|
51
|
+
target,
|
|
52
|
+
handleClose
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export { GlobalHeaderDropdown };
|
|
60
|
+
//# sourceMappingURL=GlobalHeaderDropdown.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GlobalHeaderDropdown.esm.js","sources":["../../../src/alpha/components/GlobalHeaderDropdown.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { ComponentProps, ReactNode } from 'react';\nimport type Button from '@mui/material/Button';\n\nimport { useGlobalHeaderMenuItems } from '../extensions/GlobalHeaderContext';\nimport { buildDropdownEntries } from '../utils/menuItemGrouping';\nimport { useDropdownManager } from '../../hooks';\nimport { HeaderDropdownComponent } from '../../components/HeaderDropdownComponent/HeaderDropdownComponent';\nimport { GlobalHeaderDropdownContent } from './GlobalHeaderDropdownContent';\n\n/**\n * Props for {@link GlobalHeaderDropdown}.\n *\n * @alpha\n */\nexport interface GlobalHeaderDropdownProps {\n /** Extension target name that items are collected from (e.g. `'help'`, `'create'`). */\n target: string;\n /** Content rendered inside the trigger button. */\n buttonContent: ReactNode;\n /** MUI Button props forwarded to the trigger. */\n buttonProps?: ComponentProps<typeof Button>;\n /** Render an `IconButton` instead of a regular `Button`. */\n isIconButton?: boolean;\n /** Tooltip shown on hover of the trigger button. */\n tooltip?: string;\n /** Rendered when no menu items are contributed (or all render empty when `trackValidity` is on). */\n emptyState?: ReactNode;\n /**\n * When `true`, the dropdown checks the rendered MenuList for visible\n * `[role=\"menuitem\"]` elements after each render. If none are found,\n * the `emptyState` is shown instead.\n */\n trackValidity?: boolean;\n}\n\n/**\n * High-level dropdown building block for the global header.\n *\n * Collects menu items from a named extension `target`, groups them\n * by section, sorts by priority, and renders them inside a\n * `HeaderDropdownComponent`.\n *\n * @alpha\n */\nexport const GlobalHeaderDropdown = ({\n target,\n buttonContent,\n buttonProps,\n isIconButton,\n tooltip,\n emptyState,\n trackValidity = false,\n}: GlobalHeaderDropdownProps) => {\n const { anchorEl, handleOpen, handleClose } = useDropdownManager();\n const menuItems = useGlobalHeaderMenuItems(target);\n const entries = useMemo(() => buildDropdownEntries(menuItems), [menuItems]);\n\n const menuListRef = useRef<HTMLUListElement>(null);\n const [hasVisibleItems, setHasVisibleItems] = useState(true);\n const isOpen = Boolean(anchorEl);\n\n useLayoutEffect(() => {\n if (!trackValidity || !isOpen || !menuListRef.current) return;\n const found =\n menuListRef.current.querySelector('[role=\"menuitem\"]') !== null;\n if (found !== hasVisibleItems) {\n setHasVisibleItems(found);\n }\n }, [trackValidity, hasVisibleItems, isOpen]);\n\n if (menuItems.length === 0 && !emptyState) return null;\n\n const showEmpty = entries.length === 0 || (trackValidity && !hasVisibleItems);\n\n return (\n <HeaderDropdownComponent\n buttonContent={buttonContent}\n buttonProps={buttonProps}\n isIconButton={isIconButton}\n tooltip={tooltip}\n onOpen={handleOpen}\n onClose={handleClose}\n anchorEl={anchorEl}\n menuListRef={trackValidity ? menuListRef : undefined}\n >\n {showEmpty ? (\n emptyState\n ) : (\n <GlobalHeaderDropdownContent\n entries={entries}\n target={target}\n handleClose={handleClose}\n />\n )}\n </HeaderDropdownComponent>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;AA6DO,MAAM,uBAAuB,CAAC;AAAA,EACnC,MAAA;AAAA,EACA,aAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,UAAA;AAAA,EACA,aAAgB,GAAA;AAClB,CAAiC,KAAA;AAC/B,EAAA,MAAM,EAAE,QAAA,EAAU,UAAY,EAAA,WAAA,KAAgB,kBAAmB,EAAA;AACjE,EAAM,MAAA,SAAA,GAAY,yBAAyB,MAAM,CAAA;AACjD,EAAM,MAAA,OAAA,GAAU,QAAQ,MAAM,oBAAA,CAAqB,SAAS,CAAG,EAAA,CAAC,SAAS,CAAC,CAAA;AAE1E,EAAM,MAAA,WAAA,GAAc,OAAyB,IAAI,CAAA;AACjD,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3D,EAAM,MAAA,MAAA,GAAS,QAAQ,QAAQ,CAAA;AAE/B,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,IAAI,CAAC,aAAiB,IAAA,CAAC,MAAU,IAAA,CAAC,YAAY,OAAS,EAAA;AACvD,IAAA,MAAM,KACJ,GAAA,WAAA,CAAY,OAAQ,CAAA,aAAA,CAAc,mBAAmB,CAAM,KAAA,IAAA;AAC7D,IAAA,IAAI,UAAU,eAAiB,EAAA;AAC7B,MAAA,kBAAA,CAAmB,KAAK,CAAA;AAAA;AAC1B,GACC,EAAA,CAAC,aAAe,EAAA,eAAA,EAAiB,MAAM,CAAC,CAAA;AAE3C,EAAA,IAAI,SAAU,CAAA,MAAA,KAAW,CAAK,IAAA,CAAC,YAAmB,OAAA,IAAA;AAElD,EAAA,MAAM,SAAY,GAAA,OAAA,CAAQ,MAAW,KAAA,CAAA,IAAM,iBAAiB,CAAC,eAAA;AAE7D,EACE,uBAAA,GAAA;AAAA,IAAC,uBAAA;AAAA,IAAA;AAAA,MACC,aAAA;AAAA,MACA,WAAA;AAAA,MACA,YAAA;AAAA,MACA,OAAA;AAAA,MACA,MAAQ,EAAA,UAAA;AAAA,MACR,OAAS,EAAA,WAAA;AAAA,MACT,QAAA;AAAA,MACA,WAAA,EAAa,gBAAgB,WAAc,GAAA,KAAA,CAAA;AAAA,MAE1C,sBACC,UAEA,mBAAA,GAAA;AAAA,QAAC,2BAAA;AAAA,QAAA;AAAA,UACC,OAAA;AAAA,UACA,MAAA;AAAA,UACA;AAAA;AAAA;AACF;AAAA,GAEJ;AAEJ;;;;"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { MenuSection } from '../../components/HeaderDropdownComponent/MenuSection.esm.js';
|
|
3
|
+
|
|
4
|
+
const DropdownItem = ({
|
|
5
|
+
entry,
|
|
6
|
+
isLast,
|
|
7
|
+
handleClose
|
|
8
|
+
}) => {
|
|
9
|
+
if (entry.type === "component") {
|
|
10
|
+
const Comp = entry.item.component;
|
|
11
|
+
if (!Comp) return null;
|
|
12
|
+
return /* @__PURE__ */ jsx(
|
|
13
|
+
Comp,
|
|
14
|
+
{
|
|
15
|
+
handleClose,
|
|
16
|
+
hideDivider: isLast,
|
|
17
|
+
title: entry.item.title,
|
|
18
|
+
titleKey: entry.item.titleKey,
|
|
19
|
+
icon: entry.item.icon,
|
|
20
|
+
link: entry.item.link
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const { sectionLabel, sectionLink, sectionLinkLabel, items } = entry.group;
|
|
25
|
+
return /* @__PURE__ */ jsx(
|
|
26
|
+
MenuSection,
|
|
27
|
+
{
|
|
28
|
+
sectionLabel: sectionLabel || void 0,
|
|
29
|
+
optionalLink: sectionLink,
|
|
30
|
+
optionalLinkLabel: sectionLinkLabel,
|
|
31
|
+
items,
|
|
32
|
+
handleClose,
|
|
33
|
+
hideDivider: isLast || !sectionLabel
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
const GlobalHeaderDropdownContent = ({
|
|
38
|
+
entries,
|
|
39
|
+
target,
|
|
40
|
+
handleClose
|
|
41
|
+
}) => /* @__PURE__ */ jsx(Fragment, { children: entries.map((entry, i) => /* @__PURE__ */ jsx(
|
|
42
|
+
DropdownItem,
|
|
43
|
+
{
|
|
44
|
+
entry,
|
|
45
|
+
isLast: i === entries.length - 1,
|
|
46
|
+
handleClose
|
|
47
|
+
},
|
|
48
|
+
`${target}-${i}`
|
|
49
|
+
)) });
|
|
50
|
+
|
|
51
|
+
export { GlobalHeaderDropdownContent };
|
|
52
|
+
//# sourceMappingURL=GlobalHeaderDropdownContent.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GlobalHeaderDropdownContent.esm.js","sources":["../../../src/alpha/components/GlobalHeaderDropdownContent.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { DropdownEntry } from '../utils/menuItemGrouping';\nimport { MenuSection } from '../../components/HeaderDropdownComponent/MenuSection';\n\n/** Renders a standalone component or a data-driven `MenuSection`. */\nconst DropdownItem = ({\n entry,\n isLast,\n handleClose,\n}: {\n entry: DropdownEntry;\n isLast: boolean;\n handleClose: () => void;\n}) => {\n if (entry.type === 'component') {\n const Comp = entry.item.component;\n if (!Comp) return null;\n return (\n <Comp\n handleClose={handleClose}\n hideDivider={isLast}\n title={entry.item.title}\n titleKey={entry.item.titleKey}\n icon={entry.item.icon}\n link={entry.item.link}\n />\n );\n }\n\n const { sectionLabel, sectionLink, sectionLinkLabel, items } = entry.group;\n return (\n <MenuSection\n sectionLabel={sectionLabel || undefined}\n optionalLink={sectionLink}\n optionalLinkLabel={sectionLinkLabel}\n items={items}\n handleClose={handleClose}\n hideDivider={isLast || !sectionLabel}\n />\n );\n};\n\n/**\n * Renders the menu content for a {@link GlobalHeaderDropdown}.\n *\n * Maps each entry to either a standalone component or a `MenuSection`.\n */\nexport const GlobalHeaderDropdownContent = ({\n entries,\n target,\n handleClose,\n}: {\n entries: DropdownEntry[];\n target: string;\n handleClose: () => void;\n}) => (\n <>\n {entries.map((entry, i) => (\n <DropdownItem\n key={`${target}-${i}`}\n entry={entry}\n isLast={i === entries.length - 1}\n handleClose={handleClose}\n />\n ))}\n </>\n);\n"],"names":[],"mappings":";;;AAoBA,MAAM,eAAe,CAAC;AAAA,EACpB,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAIM,KAAA;AACJ,EAAI,IAAA,KAAA,CAAM,SAAS,WAAa,EAAA;AAC9B,IAAM,MAAA,IAAA,GAAO,MAAM,IAAK,CAAA,SAAA;AACxB,IAAI,IAAA,CAAC,MAAa,OAAA,IAAA;AAClB,IACE,uBAAA,GAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,WAAA;AAAA,QACA,WAAa,EAAA,MAAA;AAAA,QACb,KAAA,EAAO,MAAM,IAAK,CAAA,KAAA;AAAA,QAClB,QAAA,EAAU,MAAM,IAAK,CAAA,QAAA;AAAA,QACrB,IAAA,EAAM,MAAM,IAAK,CAAA,IAAA;AAAA,QACjB,IAAA,EAAM,MAAM,IAAK,CAAA;AAAA;AAAA,KACnB;AAAA;AAIJ,EAAA,MAAM,EAAE,YAAc,EAAA,WAAA,EAAa,gBAAkB,EAAA,KAAA,KAAU,KAAM,CAAA,KAAA;AACrE,EACE,uBAAA,GAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,cAAc,YAAgB,IAAA,KAAA,CAAA;AAAA,MAC9B,YAAc,EAAA,WAAA;AAAA,MACd,iBAAmB,EAAA,gBAAA;AAAA,MACnB,KAAA;AAAA,MACA,WAAA;AAAA,MACA,WAAA,EAAa,UAAU,CAAC;AAAA;AAAA,GAC1B;AAEJ,CAAA;AAOO,MAAM,8BAA8B,CAAC;AAAA,EAC1C,OAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,qBAMK,GAAA,CAAA,QAAA,EAAA,EAAA,QAAA,EAAA,OAAA,CAAQ,GAAI,CAAA,CAAC,OAAO,CACnB,qBAAA,GAAA;AAAA,EAAC,YAAA;AAAA,EAAA;AAAA,IAEC,KAAA;AAAA,IACA,MAAA,EAAQ,CAAM,KAAA,OAAA,CAAQ,MAAS,GAAA,CAAA;AAAA,IAC/B;AAAA,GAAA;AAAA,EAHK,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,CAAC,CAAA;AAIrB,CACD,CACH,EAAA;;;;"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { Link } from '@backstage/core-components';
|
|
3
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
4
|
+
import { MenuItemLink } from '../../components/MenuItemLink/MenuItemLink.esm.js';
|
|
5
|
+
|
|
6
|
+
const GlobalHeaderMenuItem = ({
|
|
7
|
+
to,
|
|
8
|
+
title,
|
|
9
|
+
titleKey,
|
|
10
|
+
subTitle,
|
|
11
|
+
subTitleKey,
|
|
12
|
+
icon,
|
|
13
|
+
tooltip,
|
|
14
|
+
onClick
|
|
15
|
+
}) => /* @__PURE__ */ jsx(
|
|
16
|
+
MenuItem,
|
|
17
|
+
{
|
|
18
|
+
disableRipple: true,
|
|
19
|
+
disableTouchRipple: true,
|
|
20
|
+
onClick,
|
|
21
|
+
sx: { py: 0.5, color: "inherit", textDecoration: "none" },
|
|
22
|
+
...to ? { component: Link, to } : {},
|
|
23
|
+
children: /* @__PURE__ */ jsx(
|
|
24
|
+
MenuItemLink,
|
|
25
|
+
{
|
|
26
|
+
to: to ?? "",
|
|
27
|
+
title,
|
|
28
|
+
titleKey,
|
|
29
|
+
subTitle,
|
|
30
|
+
subTitleKey,
|
|
31
|
+
icon,
|
|
32
|
+
tooltip
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export { GlobalHeaderMenuItem };
|
|
39
|
+
//# sourceMappingURL=GlobalHeaderMenuItem.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GlobalHeaderMenuItem.esm.js","sources":["../../../src/alpha/components/GlobalHeaderMenuItem.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Link } from '@backstage/core-components';\n\nimport MenuItem from '@mui/material/MenuItem';\n\nimport { MenuItemLink } from '../../components/MenuItemLink/MenuItemLink';\n\n/**\n * Props for {@link GlobalHeaderMenuItem}.\n *\n * @alpha\n */\nexport interface GlobalHeaderMenuItemProps {\n /** Navigation URL. When absent the item renders as a plain action button. */\n to?: string;\n /** Display title. */\n title?: string;\n /** Translation key for the title. */\n titleKey?: string;\n /** Secondary text rendered below the title. */\n subTitle?: string;\n /** Translation key for the secondary text. */\n subTitleKey?: string;\n /** Icon identifier passed to `HeaderIcon`. */\n icon?: string;\n /** Tooltip shown on hover. */\n tooltip?: string;\n /** Called when the item is clicked (typically the dropdown's `handleClose`). */\n onClick?: () => void;\n}\n\n/**\n * A complete, self-contained menu item for use inside header dropdowns.\n *\n * Renders a MUI `MenuItem` with optional `Link` navigation and the\n * standard `MenuItemLink` content (icon, title, subtitle, external\n * link indicator). Consumers only need this one component — no manual\n * `MenuItem` or `Link` wrapping required.\n *\n * @example\n * ```tsx\n * const MyHelpItem = ({ handleClose }) => (\n * <GlobalHeaderMenuItem\n * to=\"https://docs.example.com\"\n * title=\"Documentation\"\n * icon=\"menu_book\"\n * onClick={handleClose}\n * />\n * );\n * ```\n *\n * @alpha\n */\nexport const GlobalHeaderMenuItem = ({\n to,\n title,\n titleKey,\n subTitle,\n subTitleKey,\n icon,\n tooltip,\n onClick,\n}: GlobalHeaderMenuItemProps) => (\n <MenuItem\n disableRipple\n disableTouchRipple\n onClick={onClick}\n sx={{ py: 0.5, color: 'inherit', textDecoration: 'none' }}\n {...(to ? { component: Link, to } : {})}\n >\n <MenuItemLink\n to={to ?? ''}\n title={title}\n titleKey={titleKey}\n subTitle={subTitle}\n subTitleKey={subTitleKey}\n icon={icon}\n tooltip={tooltip}\n />\n </MenuItem>\n);\n"],"names":[],"mappings":";;;;;AAoEO,MAAM,uBAAuB,CAAC;AAAA,EACnC,EAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,IAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CACE,qBAAA,GAAA;AAAA,EAAC,QAAA;AAAA,EAAA;AAAA,IACC,aAAa,EAAA,IAAA;AAAA,IACb,kBAAkB,EAAA,IAAA;AAAA,IAClB,OAAA;AAAA,IACA,IAAI,EAAE,EAAA,EAAI,KAAK,KAAO,EAAA,SAAA,EAAW,gBAAgB,MAAO,EAAA;AAAA,IACvD,GAAI,EAAK,GAAA,EAAE,WAAW,IAAM,EAAA,EAAA,KAAO,EAAC;AAAA,IAErC,QAAA,kBAAA,GAAA;AAAA,MAAC,YAAA;AAAA,MAAA;AAAA,QACC,IAAI,EAAM,IAAA,EAAA;AAAA,QACV,KAAA;AAAA,QACA,QAAA;AAAA,QACA,QAAA;AAAA,QACA,WAAA;AAAA,QACA,IAAA;AAAA,QACA;AAAA;AAAA;AACF;AACF;;;;"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
|
3
|
+
import SupportAgentIcon from '@mui/icons-material/SupportAgent';
|
|
4
|
+
import { useTranslation } from '../../hooks/useTranslation.esm.js';
|
|
5
|
+
import { DropdownEmptyState } from '../../components/HeaderDropdownComponent/DropdownEmptyState.esm.js';
|
|
6
|
+
import { GlobalHeaderDropdown } from './GlobalHeaderDropdown.esm.js';
|
|
7
|
+
|
|
8
|
+
const HelpDropdown = () => {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
GlobalHeaderDropdown,
|
|
12
|
+
{
|
|
13
|
+
target: "help",
|
|
14
|
+
trackValidity: true,
|
|
15
|
+
isIconButton: true,
|
|
16
|
+
tooltip: t("help.tooltip"),
|
|
17
|
+
buttonContent: /* @__PURE__ */ jsx(HelpOutlineIcon, {}),
|
|
18
|
+
buttonProps: { color: "inherit" },
|
|
19
|
+
emptyState: /* @__PURE__ */ jsx(
|
|
20
|
+
DropdownEmptyState,
|
|
21
|
+
{
|
|
22
|
+
title: t("help.noSupportLinks"),
|
|
23
|
+
subTitle: t("help.noSupportLinksSubtitle"),
|
|
24
|
+
icon: /* @__PURE__ */ jsx(SupportAgentIcon, { sx: { fontSize: 64, color: "text.disabled" } })
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { HelpDropdown };
|
|
32
|
+
//# sourceMappingURL=HelpDropdown.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HelpDropdown.esm.js","sources":["../../../src/alpha/components/HelpDropdown.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport HelpOutlineIcon from '@mui/icons-material/HelpOutline';\nimport SupportAgentIcon from '@mui/icons-material/SupportAgent';\n\nimport { useTranslation } from '../../hooks/useTranslation';\nimport { DropdownEmptyState } from '../../components/HeaderDropdownComponent/DropdownEmptyState';\nimport { GlobalHeaderDropdown } from './GlobalHeaderDropdown';\n\n/**\n * Help dropdown. Collects menu items from the `'help'` target.\n *\n * @internal\n */\nexport const HelpDropdown = () => {\n const { t } = useTranslation();\n return (\n <GlobalHeaderDropdown\n target=\"help\"\n trackValidity\n isIconButton\n tooltip={t('help.tooltip')}\n buttonContent={<HelpOutlineIcon />}\n buttonProps={{ color: 'inherit' }}\n emptyState={\n <DropdownEmptyState\n title={t('help.noSupportLinks')}\n subTitle={t('help.noSupportLinksSubtitle')}\n icon={\n <SupportAgentIcon sx={{ fontSize: 64, color: 'text.disabled' }} />\n }\n />\n }\n />\n );\n};\n"],"names":[],"mappings":";;;;;;;AA4BO,MAAM,eAAe,MAAM;AAChC,EAAM,MAAA,EAAE,CAAE,EAAA,GAAI,cAAe,EAAA;AAC7B,EACE,uBAAA,GAAA;AAAA,IAAC,oBAAA;AAAA,IAAA;AAAA,MACC,MAAO,EAAA,MAAA;AAAA,MACP,aAAa,EAAA,IAAA;AAAA,MACb,YAAY,EAAA,IAAA;AAAA,MACZ,OAAA,EAAS,EAAE,cAAc,CAAA;AAAA,MACzB,aAAA,sBAAgB,eAAgB,EAAA,EAAA,CAAA;AAAA,MAChC,WAAA,EAAa,EAAE,KAAA,EAAO,SAAU,EAAA;AAAA,MAChC,UACE,kBAAA,GAAA;AAAA,QAAC,kBAAA;AAAA,QAAA;AAAA,UACC,KAAA,EAAO,EAAE,qBAAqB,CAAA;AAAA,UAC9B,QAAA,EAAU,EAAE,6BAA6B,CAAA;AAAA,UACzC,IAAA,sBACG,gBAAiB,EAAA,EAAA,EAAA,EAAI,EAAE,QAAU,EAAA,EAAA,EAAI,KAAO,EAAA,eAAA,EAAmB,EAAA;AAAA;AAAA;AAEpE;AAAA,GAEJ;AAEJ;;;;"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useUserProfile } from '@backstage/plugin-user-settings';
|
|
4
|
+
import { parseEntityRef } from '@backstage/catalog-model';
|
|
5
|
+
import { Link } from '@backstage/core-components';
|
|
6
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
7
|
+
import { MenuItemLinkContent } from '../../components/MenuItemLink/MenuItemLinkContent.esm.js';
|
|
8
|
+
import { useTranslation } from '../../hooks/useTranslation.esm.js';
|
|
9
|
+
|
|
10
|
+
const MyProfileMenuItem = ({
|
|
11
|
+
handleClose
|
|
12
|
+
}) => {
|
|
13
|
+
const { backstageIdentity } = useUserProfile();
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const [profileLink, setProfileLink] = useState(null);
|
|
16
|
+
const [isGuest, setIsGuest] = useState(true);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!backstageIdentity?.userEntityRef) {
|
|
19
|
+
setIsGuest(true);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (backstageIdentity.userEntityRef.includes("/guest")) {
|
|
23
|
+
setIsGuest(true);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const { namespace = "default", name } = parseEntityRef(
|
|
28
|
+
backstageIdentity.userEntityRef
|
|
29
|
+
);
|
|
30
|
+
setProfileLink(`/catalog/${namespace}/user/${name}`);
|
|
31
|
+
setIsGuest(false);
|
|
32
|
+
} catch {
|
|
33
|
+
setIsGuest(true);
|
|
34
|
+
}
|
|
35
|
+
}, [backstageIdentity]);
|
|
36
|
+
if (isGuest || !profileLink) return null;
|
|
37
|
+
return /* @__PURE__ */ jsx(
|
|
38
|
+
MenuItem,
|
|
39
|
+
{
|
|
40
|
+
component: Link,
|
|
41
|
+
to: profileLink,
|
|
42
|
+
onClick: handleClose,
|
|
43
|
+
disableRipple: true,
|
|
44
|
+
disableTouchRipple: true,
|
|
45
|
+
sx: { py: 0.5, color: "inherit", textDecoration: "none" },
|
|
46
|
+
children: /* @__PURE__ */ jsx(
|
|
47
|
+
MenuItemLinkContent,
|
|
48
|
+
{
|
|
49
|
+
icon: "account_circle",
|
|
50
|
+
label: t("profile.myProfile")
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export { MyProfileMenuItem };
|
|
58
|
+
//# sourceMappingURL=MyProfileMenuItem.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MyProfileMenuItem.esm.js","sources":["../../../src/alpha/components/MyProfileMenuItem.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useEffect, useState } from 'react';\n\nimport { useUserProfile } from '@backstage/plugin-user-settings';\nimport { parseEntityRef } from '@backstage/catalog-model';\nimport { Link } from '@backstage/core-components';\n\nimport MenuItem from '@mui/material/MenuItem';\n\nimport { MenuItemLinkContent } from '../../components/MenuItemLink/MenuItemLinkContent';\nimport { useTranslation } from '../../hooks/useTranslation';\n\n/**\n * Custom component for the \"My Profile\" menu item.\n * Dynamically resolves the catalog URL from the user's identity and\n * returns `null` for guest users, matching legacy ProfileDropdown behavior.\n *\n * @internal\n */\nexport const MyProfileMenuItem = ({\n handleClose,\n}: {\n handleClose?: () => void;\n}) => {\n const { backstageIdentity } = useUserProfile();\n const { t } = useTranslation();\n const [profileLink, setProfileLink] = useState<string | null>(null);\n const [isGuest, setIsGuest] = useState(true);\n\n useEffect(() => {\n if (!backstageIdentity?.userEntityRef) {\n setIsGuest(true);\n return;\n }\n if (backstageIdentity.userEntityRef.includes('/guest')) {\n setIsGuest(true);\n return;\n }\n try {\n const { namespace = 'default', name } = parseEntityRef(\n backstageIdentity.userEntityRef,\n );\n setProfileLink(`/catalog/${namespace}/user/${name}`);\n setIsGuest(false);\n } catch {\n setIsGuest(true);\n }\n }, [backstageIdentity]);\n\n if (isGuest || !profileLink) return null;\n\n return (\n <MenuItem\n component={Link}\n to={profileLink}\n onClick={handleClose}\n disableRipple\n disableTouchRipple\n sx={{ py: 0.5, color: 'inherit', textDecoration: 'none' }}\n >\n <MenuItemLinkContent\n icon=\"account_circle\"\n label={t('profile.myProfile')}\n />\n </MenuItem>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;AAkCO,MAAM,oBAAoB,CAAC;AAAA,EAChC;AACF,CAEM,KAAA;AACJ,EAAM,MAAA,EAAE,iBAAkB,EAAA,GAAI,cAAe,EAAA;AAC7C,EAAM,MAAA,EAAE,CAAE,EAAA,GAAI,cAAe,EAAA;AAC7B,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAwB,IAAI,CAAA;AAClE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAE3C,EAAA,SAAA,CAAU,MAAM;AACd,IAAI,IAAA,CAAC,mBAAmB,aAAe,EAAA;AACrC,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA;AAAA;AAEF,IAAA,IAAI,iBAAkB,CAAA,aAAA,CAAc,QAAS,CAAA,QAAQ,CAAG,EAAA;AACtD,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA;AAAA;AAEF,IAAI,IAAA;AACF,MAAA,MAAM,EAAE,SAAA,GAAY,SAAW,EAAA,IAAA,EAAS,GAAA,cAAA;AAAA,QACtC,iBAAkB,CAAA;AAAA,OACpB;AACA,MAAA,cAAA,CAAe,CAAY,SAAA,EAAA,SAAS,CAAS,MAAA,EAAA,IAAI,CAAE,CAAA,CAAA;AACnD,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA,KACV,CAAA,MAAA;AACN,MAAA,UAAA,CAAW,IAAI,CAAA;AAAA;AACjB,GACF,EAAG,CAAC,iBAAiB,CAAC,CAAA;AAEtB,EAAI,IAAA,OAAA,IAAW,CAAC,WAAA,EAAoB,OAAA,IAAA;AAEpC,EACE,uBAAA,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,SAAW,EAAA,IAAA;AAAA,MACX,EAAI,EAAA,WAAA;AAAA,MACJ,OAAS,EAAA,WAAA;AAAA,MACT,aAAa,EAAA,IAAA;AAAA,MACb,kBAAkB,EAAA,IAAA;AAAA,MAClB,IAAI,EAAE,EAAA,EAAI,KAAK,KAAO,EAAA,SAAA,EAAW,gBAAgB,MAAO,EAAA;AAAA,MAExD,QAAA,kBAAA,GAAA;AAAA,QAAC,mBAAA;AAAA,QAAA;AAAA,UACC,IAAK,EAAA,gBAAA;AAAA,UACL,KAAA,EAAO,EAAE,mBAAmB;AAAA;AAAA;AAC9B;AAAA,GACF;AAEJ;;;;"}
|