@red-hat-developer-hub/backstage-plugin-global-floating-action-button 0.0.2 → 0.0.4

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-global-floating-action-button
2
2
 
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - d783f6f: created dynamic fab to be integrated in the RHDH app
8
+
9
+ ## 0.0.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 01c8d1c: update global floating action button plugin readme
14
+ - 3d2f8a3: update fab content
15
+
3
16
  ## 0.0.2
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -10,7 +10,59 @@ This plugin has been added to the example app in this workspace, meaning it can
10
10
 
11
11
  ### Installation
12
12
 
13
- #### Procedure
13
+ #### Installing as a dynamic plugin?
14
+
15
+ The sections below are relevant for static plugins. If the plugin is expected to be installed as a dynamic one:
16
+
17
+ - Follow https://github.com/redhat-developer/rhdh/blob/main/docs/dynamic-plugins/installing-plugins.md
18
+ - Add content of `app-config.dynamic.yaml` into `app-config.local.yaml`.
19
+ - To configure a plugin as a Floating Action Button (FAB), you need to specify the `global.floatingactionbutton/component` mount point in your plugin configuration, as shown below using the bulk-import plugin as an example:
20
+
21
+ ```yaml
22
+ dynamicPlugins:
23
+ frontend:
24
+ red-hat-developer-hub.backstage-plugin-bulk-import:
25
+ # start of fab config
26
+ mountPoints:
27
+ - mountPoint: global.floatingactionbutton/component
28
+ importName: BulkImportPage # It is necessary to specify an importName because mount point without an associated component is not allowed.
29
+ config:
30
+ slot: 'page-end'
31
+ icon: bulkImportIcon
32
+ label: 'Bulk import'
33
+ toolTip: 'Register multiple repositories in bulk'
34
+ to: /bulk-import/repositories
35
+ # end of fab config
36
+ appIcons:
37
+ - name: bulkImportIcon
38
+ importName: BulkImportIcon
39
+ dynamicRoutes:
40
+ - path: /bulk-import/repositories
41
+ importName: BulkImportPage
42
+ menuItem:
43
+ icon: bulkImportIcon
44
+ text: Bulk import
45
+ ```
46
+
47
+ - To configure a Floating Action Button (FAB) that opens an external link, specify the `global.floatingactionbutton/component` mount point in the `backstage-plugin-global-floating-action-button` plugin, as shown below:
48
+
49
+ ```yaml
50
+ dynamicPlugins:
51
+ frontend:
52
+ red-hat-developer-hub.backstage-plugin-global-floating-action-button:
53
+ mountPoints:
54
+ - mountPoint: application/listener
55
+ importName: DynamicGlobalFloatingActionButton
56
+ - mountPoint: global.floatingactionbutton/component
57
+ importName: NullComponent # It is necessary to specify an importName because mount point without an associated component is not allowed.
58
+ config:
59
+ icon: github
60
+ label: 'Git'
61
+ toolTip: 'Github'
62
+ to: https://github.com/redhat-developer/rhdh-plugins
63
+ ```
64
+
65
+ #### Static Installation
14
66
 
15
67
  1. Install the Global floating action button plugin using the following command:
16
68
 
@@ -18,19 +70,20 @@ This plugin has been added to the example app in this workspace, meaning it can
18
70
  yarn workspace app add @red-hat-developer-hub/backstage-plugin-global-floating-action-button
19
71
  ```
20
72
 
21
- 2. Add **Bulk import** Sidebar Item in `packages/app/src/components/Root/Root.tsx`:
73
+ 2. Add **GlobalFloatingActionButton** component to `packages/app/src/components/Root/Root.tsx` with the desired actions you want to associate with your floating buttons:
22
74
 
23
75
  ```tsx title="packages/app/src/components/Root/Root.tsx"
24
76
  /* highlight-add-next-line */
25
77
  import {
26
- GlobalFloatingButton,
78
+ GlobalFloatingActionButton,
27
79
  Slot,
28
80
  } from '@red-hat-developer-hub/backstage-plugin-global-floating-action-button';
29
81
 
30
82
  export const Root = ({ children }: PropsWithChildren<{}>) => (
31
83
  <SidebarPage>
32
- ... /* highlight-add-start */
33
- <GlobalFloatingButton
84
+ {/* ... */}
85
+ {/* highlight-add-start */}
86
+ <GlobalFloatingActionButton
34
87
  floatingButtons={[
35
88
  {
36
89
  color: 'success',
@@ -40,7 +93,7 @@ This plugin has been added to the example app in this workspace, meaning it can
40
93
  to: '/create',
41
94
  },
42
95
  {
43
- slot: Slot.BOTTOM_CENTER,
96
+ slot: Slot.BOTTOM_LEFT,
44
97
  icon: <LibraryBooks />,
45
98
  label: 'Docs',
46
99
  toolTip: 'Docs',
@@ -48,7 +101,29 @@ This plugin has been added to the example app in this workspace, meaning it can
48
101
  },
49
102
  ]}
50
103
  />
51
- /* highlight-add-end */ ...
104
+ {/* highlight-add-end */}
105
+ {/* ... */}
52
106
  </SidebarPage>
53
107
  );
54
108
  ```
109
+
110
+ #### Floating Action Button Parameters
111
+
112
+ | Name | Type | Description | Notes |
113
+ | ------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
114
+ | **slot** | `enum` | The position where the fab will be placed. Valid values: `PAGE_END`, `BOTTOM_LEFT`. | [optional] default to `PAGE_END`. |
115
+ | **label** | `String` | A name for your action button. | required |
116
+ | **icon** | `String`<br>`React.ReactElement` | An icon for your floating button. Recommended to use **filled** icons from the [Material Design library](https://fonts.google.com/icons) | required |
117
+ | **showLabel** | `Boolean` | To display the label next to your icon. | optional |
118
+ | **size** | `'small'`<br>`'medium'`<br>`'large'` | A name for your action button. | [optional] default to `'medium'` |
119
+ | **color** | `'default'`<br>`'error'`<br>`'info'`<br>`'inherit'`<br>`'primary'`<br>`'secondary'`<br>`'success'`<br>`'warning'` | The color of the component. It supports both default and custom theme colors, which can be added as shown in the [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors). | [optional] default to `'default'`. |
120
+ | **onClick** | `React.MouseEventHandler` | the action to be performed on `onClick`. | optional |
121
+ | **to** | `String` | Specify an href if the action button should open a internal/external link. | optional |
122
+ | **toolTip** | `String` | The text to appear on hover. | optional |
123
+ | **priority** | `number` | When multiple sub-menu actions are displayed, the button can be prioritized to position either at the top or the bottom. | optional |
124
+ | **visibleOnPaths** | `string[]` | The action button will appear only on the specified paths and will remain hidden on all other paths. | [optional] default to displaying on all paths. |
125
+ | **excludeOnPaths** | `string[]` | The action button will be hidden only on the specified paths and will appear on all other paths. | [optional] default to displaying on all paths. |
126
+
127
+ **NOTE**
128
+
129
+ If multiple floating button actions are assigned to the same `Slot`, they will appear as sub-menu options within the floating button.
@@ -0,0 +1,17 @@
1
+ import React__default from 'react';
2
+ import { useFabMountPoints } from '../hooks/useFabMountPoints.esm.js';
3
+ import { GlobalFloatingActionButton } from './GlobalFloatingActionButton.esm.js';
4
+
5
+ const DynamicGlobalFloatingActionButton = () => {
6
+ const allFabMountPoints = useFabMountPoints();
7
+ const floatingButtons = (allFabMountPoints || []).map(
8
+ (fab) => fab?.config
9
+ );
10
+ if (floatingButtons?.length === 0) {
11
+ return null;
12
+ }
13
+ return /* @__PURE__ */ React__default.createElement(GlobalFloatingActionButton, { floatingButtons });
14
+ };
15
+
16
+ export { DynamicGlobalFloatingActionButton };
17
+ //# sourceMappingURL=DynamicGlobalFloatingActionButton.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DynamicGlobalFloatingActionButton.esm.js","sources":["../../src/components/DynamicGlobalFloatingActionButton.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 React from 'react';\n\nimport { useFabMountPoints } from '../hooks/useFabMountPoints';\nimport { GlobalFloatingActionButton } from './GlobalFloatingActionButton';\nimport { FloatingActionButton } from '../types';\n\n/**\n * Dynamic Global Floating Action Button\n *\n * @public\n */\nexport const DynamicGlobalFloatingActionButton = () => {\n const allFabMountPoints = useFabMountPoints();\n\n const floatingButtons = (allFabMountPoints || []).map(\n fab => fab?.config,\n ) as FloatingActionButton[];\n\n if (floatingButtons?.length === 0) {\n return null;\n }\n\n return <GlobalFloatingActionButton floatingButtons={floatingButtons} />;\n};\n"],"names":["React"],"mappings":";;;;AA2BO,MAAM,oCAAoC,MAAM;AACrD,EAAA,MAAM,oBAAoB,iBAAkB,EAAA;AAE5C,EAAM,MAAA,eAAA,GAAA,CAAmB,iBAAqB,IAAA,EAAI,EAAA,GAAA;AAAA,IAChD,SAAO,GAAK,EAAA;AAAA,GACd;AAEA,EAAI,IAAA,eAAA,EAAiB,WAAW,CAAG,EAAA;AACjC,IAAO,OAAA,IAAA;AAAA;AAGT,EAAO,uBAAAA,cAAA,CAAA,aAAA,CAAC,8BAA2B,eAAkC,EAAA,CAAA;AACvE;;;;"}
@@ -1,39 +1,107 @@
1
1
  import * as React from 'react';
2
2
  import { useNavigate } from 'react-router-dom';
3
+ import { makeStyles } from '@mui/styles';
3
4
  import Fab from '@mui/material/Fab';
4
5
  import Tooltip from '@mui/material/Tooltip';
5
6
  import Typography from '@mui/material/Typography';
7
+ import OpenInNewIcon from '@mui/icons-material/OpenInNew';
6
8
  import { FabIcon } from './FabIcon.esm.js';
7
9
  import { Slot } from '../types.esm.js';
10
+ import { slotOptions } from '../utils.esm.js';
8
11
 
12
+ const useStyles = makeStyles(() => ({
13
+ openInNew: { paddingBottom: "5px", paddingTop: "3px" }
14
+ }));
15
+ const FABLabel = ({
16
+ label,
17
+ slot,
18
+ showExternalIcon,
19
+ icon,
20
+ order
21
+ }) => {
22
+ const styles = useStyles();
23
+ const marginStyle = slotOptions[slot].margin;
24
+ return /* @__PURE__ */ React.createElement(Typography, { sx: { display: "flex" } }, showExternalIcon && /* @__PURE__ */ React.createElement(
25
+ OpenInNewIcon,
26
+ {
27
+ className: styles.openInNew,
28
+ sx: { ...marginStyle, order: order.externalIcon }
29
+ }
30
+ ), label && /* @__PURE__ */ React.createElement(
31
+ Typography,
32
+ {
33
+ sx: {
34
+ ...marginStyle,
35
+ color: "#151515",
36
+ order: 2
37
+ }
38
+ },
39
+ label
40
+ ), /* @__PURE__ */ React.createElement(Typography, { sx: { mb: -1, order: order.icon } }, /* @__PURE__ */ React.createElement(FabIcon, { icon })));
41
+ };
9
42
  const FAB = ({
10
43
  actionButton,
11
- size
44
+ size,
45
+ className
12
46
  }) => {
13
47
  const navigate = useNavigate();
14
48
  const isExternalUri = (uri) => /^([a-z+.-]+):/.test(uri);
15
- const external = isExternalUri(actionButton.to);
16
- const newWindow = external && !!/^https?:/.exec(actionButton.to);
17
- const navigateTo = () => actionButton.to && !external ? navigate(actionButton.to) : "";
49
+ const isExternal = isExternalUri(actionButton.to);
50
+ const newWindow = isExternal && !!/^https?:/.exec(actionButton.to);
51
+ const navigateTo = () => actionButton.to && !isExternal ? navigate(actionButton.to) : "";
52
+ if (!actionButton.label) {
53
+ console.warn(
54
+ "Label is missing from your FAB component. A label is required for the aria-label attribute.",
55
+ actionButton
56
+ );
57
+ }
58
+ const labelText = (actionButton.label || "").length > 20 ? `${actionButton.label.slice(0, actionButton.label.length)}...` : actionButton.label;
59
+ if (!actionButton.icon) {
60
+ console.warn(
61
+ "Icon is missing from your FAB component. An icon is required to render a FAB button.",
62
+ actionButton
63
+ );
64
+ return null;
65
+ }
66
+ const getColor = () => {
67
+ if (actionButton.color) {
68
+ return actionButton.color;
69
+ }
70
+ if (!className) {
71
+ return "info";
72
+ }
73
+ return void 0;
74
+ };
75
+ const displayOnRight = actionButton.slot === Slot.PAGE_END || !actionButton.slot;
18
76
  return /* @__PURE__ */ React.createElement(
19
77
  Tooltip,
20
78
  {
21
79
  title: actionButton.toolTip,
22
- placement: Slot.PAGE_END ? "left" : "right"
80
+ placement: slotOptions[actionButton.slot || Slot.PAGE_END].tooltipDirection
23
81
  },
24
- /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
82
+ /* @__PURE__ */ React.createElement("div", { className }, /* @__PURE__ */ React.createElement(
25
83
  Fab,
26
84
  {
27
85
  ...newWindow ? { target: "_blank", rel: "noopener" } : {},
28
- variant: actionButton.showLabel || !actionButton.icon ? "extended" : "circular",
86
+ style: { color: "#1f1f1f" },
87
+ variant: actionButton.showLabel || isExternal ? "extended" : "circular",
29
88
  size: size || actionButton.size || "medium",
30
- color: actionButton.color || "default",
89
+ color: getColor(),
31
90
  "aria-label": actionButton.label,
91
+ "data-testid": (actionButton.label || "").replace(" ", "-").toLocaleLowerCase("en-US"),
32
92
  onClick: actionButton.onClick || navigateTo,
33
- ...external ? { href: actionButton.to } : {}
93
+ ...isExternal ? { href: actionButton.to } : {}
34
94
  },
35
- actionButton.icon && /* @__PURE__ */ React.createElement(FabIcon, { icon: actionButton.icon }),
36
- (actionButton.showLabel || !actionButton.icon) && /* @__PURE__ */ React.createElement(Typography, { sx: actionButton.icon ? { ml: 1 } : {} }, actionButton.label)
95
+ /* @__PURE__ */ React.createElement(
96
+ FABLabel,
97
+ {
98
+ showExternalIcon: isExternal,
99
+ icon: actionButton.icon,
100
+ label: actionButton.showLabel ? labelText : "",
101
+ order: displayOnRight ? { externalIcon: isExternal ? 1 : -1, icon: 3 } : { externalIcon: isExternal ? 3 : -1, icon: 1 },
102
+ slot: actionButton.slot || Slot.PAGE_END
103
+ }
104
+ )
37
105
  ))
38
106
  );
39
107
  };
@@ -1 +1 @@
1
- {"version":3,"file":"FAB.esm.js","sources":["../../src/components/FAB.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\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 * as React from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport Fab from '@mui/material/Fab';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport { FabIcon } from './FabIcon';\nimport { FloatingActionButton, Slot } from '../types';\n\nexport const FAB = ({\n actionButton,\n size,\n}: {\n actionButton: FloatingActionButton;\n size?: 'small' | 'medium' | 'large';\n}) => {\n const navigate = useNavigate();\n const isExternalUri = (uri: string) => /^([a-z+.-]+):/.test(uri);\n const external = isExternalUri(actionButton.to!);\n const newWindow = external && !!/^https?:/.exec(actionButton.to!);\n const navigateTo = () =>\n actionButton.to && !external ? navigate(actionButton.to) : '';\n return (\n <Tooltip\n title={actionButton.toolTip}\n placement={Slot.PAGE_END ? 'left' : 'right'}\n >\n <div>\n <Fab\n {...(newWindow ? { target: '_blank', rel: 'noopener' } : {})}\n variant={\n actionButton.showLabel || !actionButton.icon\n ? 'extended'\n : 'circular'\n }\n size={size || actionButton.size || 'medium'}\n color={actionButton.color || 'default'}\n aria-label={actionButton.label}\n onClick={actionButton.onClick || navigateTo}\n {...(external ? { href: actionButton.to } : {})}\n >\n {actionButton.icon && <FabIcon icon={actionButton.icon} />}\n {(actionButton.showLabel || !actionButton.icon) && (\n <Typography sx={actionButton.icon ? { ml: 1 } : {}}>\n {actionButton.label}\n </Typography>\n )}\n </Fab>\n </div>\n </Tooltip>\n );\n};\n"],"names":[],"mappings":";;;;;;;;AAwBO,MAAM,MAAM,CAAC;AAAA,EAClB,YAAA;AAAA,EACA;AACF,CAGM,KAAA;AACJ,EAAA,MAAM,WAAW,WAAY,EAAA;AAC7B,EAAA,MAAM,aAAgB,GAAA,CAAC,GAAgB,KAAA,eAAA,CAAgB,KAAK,GAAG,CAAA;AAC/D,EAAM,MAAA,QAAA,GAAW,aAAc,CAAA,YAAA,CAAa,EAAG,CAAA;AAC/C,EAAA,MAAM,YAAY,QAAY,IAAA,CAAC,CAAC,UAAW,CAAA,IAAA,CAAK,aAAa,EAAG,CAAA;AAChE,EAAM,MAAA,UAAA,GAAa,MACjB,YAAa,CAAA,EAAA,IAAM,CAAC,QAAW,GAAA,QAAA,CAAS,YAAa,CAAA,EAAE,CAAI,GAAA,EAAA;AAC7D,EACE,uBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,OAAO,YAAa,CAAA,OAAA;AAAA,MACpB,SAAA,EAAW,IAAK,CAAA,QAAA,GAAW,MAAS,GAAA;AAAA,KAAA;AAAA,wCAEnC,KACC,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,MAAC,GAAA;AAAA,MAAA;AAAA,QACE,GAAI,YAAY,EAAE,MAAA,EAAQ,UAAU,GAAK,EAAA,UAAA,KAAe,EAAC;AAAA,QAC1D,SACE,YAAa,CAAA,SAAA,IAAa,CAAC,YAAA,CAAa,OACpC,UACA,GAAA,UAAA;AAAA,QAEN,IAAA,EAAM,IAAQ,IAAA,YAAA,CAAa,IAAQ,IAAA,QAAA;AAAA,QACnC,KAAA,EAAO,aAAa,KAAS,IAAA,SAAA;AAAA,QAC7B,cAAY,YAAa,CAAA,KAAA;AAAA,QACzB,OAAA,EAAS,aAAa,OAAW,IAAA,UAAA;AAAA,QAChC,GAAI,QAAW,GAAA,EAAE,MAAM,YAAa,CAAA,EAAA,KAAO;AAAC,OAAA;AAAA,MAE5C,aAAa,IAAQ,oBAAA,KAAA,CAAA,aAAA,CAAC,OAAQ,EAAA,EAAA,IAAA,EAAM,aAAa,IAAM,EAAA,CAAA;AAAA,MAAA,CACtD,aAAa,SAAa,IAAA,CAAC,YAAa,CAAA,IAAA,yCACvC,UAAW,EAAA,EAAA,EAAA,EAAI,YAAa,CAAA,IAAA,GAAO,EAAE,EAAI,EAAA,CAAA,KAAM,EAAC,EAAA,EAC9C,aAAa,KAChB;AAAA,KAGN;AAAA,GACF;AAEJ;;;;"}
1
+ {"version":3,"file":"FAB.esm.js","sources":["../../src/components/FAB.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 * as React from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { makeStyles } from '@mui/styles';\nimport Fab from '@mui/material/Fab';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport OpenInNewIcon from '@mui/icons-material/OpenInNew';\nimport { FabIcon } from './FabIcon';\nimport { FloatingActionButton, Slot } from '../types';\nimport { slotOptions } from '../utils';\n\nconst useStyles = makeStyles(() => ({\n openInNew: { paddingBottom: '5px', paddingTop: '3px' },\n}));\n\nconst FABLabel = ({\n label,\n slot,\n showExternalIcon,\n icon,\n order,\n}: {\n label: string;\n slot: Slot;\n showExternalIcon: boolean;\n icon: string | React.ReactElement;\n order: { externalIcon?: number; icon?: number };\n}) => {\n const styles = useStyles();\n const marginStyle = slotOptions[slot].margin;\n return (\n <Typography sx={{ display: 'flex' }}>\n {showExternalIcon && (\n <OpenInNewIcon\n className={styles.openInNew}\n sx={{ ...marginStyle, order: order.externalIcon }}\n />\n )}\n {label && (\n <Typography\n sx={{\n ...marginStyle,\n color: '#151515',\n order: 2,\n }}\n >\n {label}\n </Typography>\n )}\n <Typography sx={{ mb: -1, order: order.icon }}>\n <FabIcon icon={icon} />\n </Typography>\n </Typography>\n );\n};\n\nexport const FAB = ({\n actionButton,\n size,\n className,\n}: {\n actionButton: FloatingActionButton;\n size?: 'small' | 'medium' | 'large';\n className?: string;\n}) => {\n const navigate = useNavigate();\n const isExternalUri = (uri: string) => /^([a-z+.-]+):/.test(uri);\n const isExternal = isExternalUri(actionButton.to!);\n const newWindow = isExternal && !!/^https?:/.exec(actionButton.to!);\n const navigateTo = () =>\n actionButton.to && !isExternal ? navigate(actionButton.to) : '';\n\n if (!actionButton.label) {\n // eslint-disable-next-line no-console\n console.warn(\n 'Label is missing from your FAB component. A label is required for the aria-label attribute.',\n actionButton,\n );\n }\n\n const labelText =\n (actionButton.label || '').length > 20\n ? `${actionButton.label.slice(0, actionButton.label.length)}...`\n : actionButton.label;\n\n if (!actionButton.icon) {\n // eslint-disable-next-line no-console\n console.warn(\n 'Icon is missing from your FAB component. An icon is required to render a FAB button.',\n actionButton,\n );\n return null;\n }\n\n const getColor = () => {\n if (actionButton.color) {\n return actionButton.color;\n }\n if (!className) {\n return 'info';\n }\n return undefined;\n };\n\n const displayOnRight =\n actionButton.slot === Slot.PAGE_END || !actionButton.slot;\n\n return (\n <Tooltip\n title={actionButton.toolTip}\n placement={\n slotOptions[actionButton.slot || Slot.PAGE_END].tooltipDirection\n }\n >\n <div className={className}>\n <Fab\n {...(newWindow ? { target: '_blank', rel: 'noopener' } : {})}\n style={{ color: '#1f1f1f' }}\n variant={\n actionButton.showLabel || isExternal ? 'extended' : 'circular'\n }\n size={size || actionButton.size || 'medium'}\n color={getColor()}\n aria-label={actionButton.label}\n data-testid={(actionButton.label || '')\n .replace(' ', '-')\n .toLocaleLowerCase('en-US')}\n onClick={actionButton.onClick || navigateTo}\n {...(isExternal ? { href: actionButton.to } : {})}\n >\n <FABLabel\n showExternalIcon={isExternal}\n icon={actionButton.icon}\n label={actionButton.showLabel ? labelText : ''}\n order={\n displayOnRight\n ? { externalIcon: isExternal ? 1 : -1, icon: 3 }\n : { externalIcon: isExternal ? 3 : -1, icon: 1 }\n }\n slot={actionButton.slot || Slot.PAGE_END}\n />\n </Fab>\n </div>\n </Tooltip>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;AA2BA,MAAM,SAAA,GAAY,WAAW,OAAO;AAAA,EAClC,SAAW,EAAA,EAAE,aAAe,EAAA,KAAA,EAAO,YAAY,KAAM;AACvD,CAAE,CAAA,CAAA;AAEF,MAAM,WAAW,CAAC;AAAA,EAChB,KAAA;AAAA,EACA,IAAA;AAAA,EACA,gBAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAMM,KAAA;AACJ,EAAA,MAAM,SAAS,SAAU,EAAA;AACzB,EAAM,MAAA,WAAA,GAAc,WAAY,CAAA,IAAI,CAAE,CAAA,MAAA;AACtC,EAAA,2CACG,UAAW,EAAA,EAAA,EAAA,EAAI,EAAE,OAAS,EAAA,MAAA,MACxB,gBACC,oBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,aAAA;AAAA,IAAA;AAAA,MACC,WAAW,MAAO,CAAA,SAAA;AAAA,MAClB,IAAI,EAAE,GAAG,WAAa,EAAA,KAAA,EAAO,MAAM,YAAa;AAAA;AAAA,KAGnD,KACC,oBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,EAAI,EAAA;AAAA,QACF,GAAG,WAAA;AAAA,QACH,KAAO,EAAA,SAAA;AAAA,QACP,KAAO,EAAA;AAAA;AACT,KAAA;AAAA,IAEC;AAAA,GAGL,kBAAA,KAAA,CAAA,aAAA,CAAC,UAAW,EAAA,EAAA,EAAA,EAAI,EAAE,EAAI,EAAA,CAAA,CAAA,EAAI,KAAO,EAAA,KAAA,CAAM,MACrC,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,OAAQ,EAAA,EAAA,IAAA,EAAY,CACvB,CACF,CAAA;AAEJ,CAAA;AAEO,MAAM,MAAM,CAAC;AAAA,EAClB,YAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAIM,KAAA;AACJ,EAAA,MAAM,WAAW,WAAY,EAAA;AAC7B,EAAA,MAAM,aAAgB,GAAA,CAAC,GAAgB,KAAA,eAAA,CAAgB,KAAK,GAAG,CAAA;AAC/D,EAAM,MAAA,UAAA,GAAa,aAAc,CAAA,YAAA,CAAa,EAAG,CAAA;AACjD,EAAA,MAAM,YAAY,UAAc,IAAA,CAAC,CAAC,UAAW,CAAA,IAAA,CAAK,aAAa,EAAG,CAAA;AAClE,EAAM,MAAA,UAAA,GAAa,MACjB,YAAa,CAAA,EAAA,IAAM,CAAC,UAAa,GAAA,QAAA,CAAS,YAAa,CAAA,EAAE,CAAI,GAAA,EAAA;AAE/D,EAAI,IAAA,CAAC,aAAa,KAAO,EAAA;AAEvB,IAAQ,OAAA,CAAA,IAAA;AAAA,MACN,6FAAA;AAAA,MACA;AAAA,KACF;AAAA;AAGF,EAAA,MAAM,aACH,YAAa,CAAA,KAAA,IAAS,EAAI,EAAA,MAAA,GAAS,KAChC,CAAG,EAAA,YAAA,CAAa,KAAM,CAAA,KAAA,CAAM,GAAG,YAAa,CAAA,KAAA,CAAM,MAAM,CAAC,QACzD,YAAa,CAAA,KAAA;AAEnB,EAAI,IAAA,CAAC,aAAa,IAAM,EAAA;AAEtB,IAAQ,OAAA,CAAA,IAAA;AAAA,MACN,sFAAA;AAAA,MACA;AAAA,KACF;AACA,IAAO,OAAA,IAAA;AAAA;AAGT,EAAA,MAAM,WAAW,MAAM;AACrB,IAAA,IAAI,aAAa,KAAO,EAAA;AACtB,MAAA,OAAO,YAAa,CAAA,KAAA;AAAA;AAEtB,IAAA,IAAI,CAAC,SAAW,EAAA;AACd,MAAO,OAAA,MAAA;AAAA;AAET,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AAEA,EAAA,MAAM,iBACJ,YAAa,CAAA,IAAA,KAAS,IAAK,CAAA,QAAA,IAAY,CAAC,YAAa,CAAA,IAAA;AAEvD,EACE,uBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,OAAO,YAAa,CAAA,OAAA;AAAA,MACpB,WACE,WAAY,CAAA,YAAA,CAAa,IAAQ,IAAA,IAAA,CAAK,QAAQ,CAAE,CAAA;AAAA,KAAA;AAAA,oBAGlD,KAAA,CAAA,aAAA,CAAC,SAAI,SACH,EAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,MAAC,GAAA;AAAA,MAAA;AAAA,QACE,GAAI,YAAY,EAAE,MAAA,EAAQ,UAAU,GAAK,EAAA,UAAA,KAAe,EAAC;AAAA,QAC1D,KAAA,EAAO,EAAE,KAAA,EAAO,SAAU,EAAA;AAAA,QAC1B,OACE,EAAA,YAAA,CAAa,SAAa,IAAA,UAAA,GAAa,UAAa,GAAA,UAAA;AAAA,QAEtD,IAAA,EAAM,IAAQ,IAAA,YAAA,CAAa,IAAQ,IAAA,QAAA;AAAA,QACnC,OAAO,QAAS,EAAA;AAAA,QAChB,cAAY,YAAa,CAAA,KAAA;AAAA,QACzB,aAAA,EAAA,CAAc,aAAa,KAAS,IAAA,EAAA,EACjC,QAAQ,GAAK,EAAA,GAAG,CAChB,CAAA,iBAAA,CAAkB,OAAO,CAAA;AAAA,QAC5B,OAAA,EAAS,aAAa,OAAW,IAAA,UAAA;AAAA,QAChC,GAAI,UAAa,GAAA,EAAE,MAAM,YAAa,CAAA,EAAA,KAAO;AAAC,OAAA;AAAA,sBAE/C,KAAA,CAAA,aAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,gBAAkB,EAAA,UAAA;AAAA,UAClB,MAAM,YAAa,CAAA,IAAA;AAAA,UACnB,KAAA,EAAO,YAAa,CAAA,SAAA,GAAY,SAAY,GAAA,EAAA;AAAA,UAC5C,OACE,cACI,GAAA,EAAE,YAAc,EAAA,UAAA,GAAa,IAAI,CAAI,CAAA,EAAA,IAAA,EAAM,CAAE,EAAA,GAC7C,EAAE,YAAc,EAAA,UAAA,GAAa,CAAI,GAAA,CAAA,CAAA,EAAI,MAAM,CAAE,EAAA;AAAA,UAEnD,IAAA,EAAM,YAAa,CAAA,IAAA,IAAQ,IAAK,CAAA;AAAA;AAAA;AAClC,KAEJ;AAAA,GACF;AAEJ;;;;"}
@@ -1,17 +1,29 @@
1
1
  import * as React from 'react';
2
2
  import { useLocation } from 'react-router-dom';
3
+ import { makeStyles } from '@mui/styles';
3
4
  import Fab from '@mui/material/Fab';
4
5
  import Tooltip from '@mui/material/Tooltip';
5
6
  import { useTheme } from '@mui/material/styles';
6
7
  import CloseIcon from '@mui/icons-material/Close';
7
8
  import MenuIcon from '@mui/icons-material/Menu';
8
- import Slide from '@mui/material/Slide';
9
+ import Collapse from '@mui/material/Collapse';
9
10
  import { FAB } from './FAB.esm.js';
11
+ import { slotOptions } from '../utils.esm.js';
10
12
 
13
+ const useStyles = makeStyles((theme) => ({
14
+ button: {
15
+ paddingTop: "10px",
16
+ color: theme && Object.keys(theme).length > 0 ? theme.palette.grey[500] : "#9e9e9e"
17
+ },
18
+ menuButtonStyle: {
19
+ color: "#1f1f1f"
20
+ }
21
+ }));
11
22
  const FABWithSubmenu = ({
12
23
  fabs,
13
- ref
24
+ slot
14
25
  }) => {
26
+ const styles = useStyles();
15
27
  const theme = useTheme();
16
28
  const { pathname } = useLocation();
17
29
  const [isMenuOpen, setIsMenuOpen] = React.useState(false);
@@ -21,29 +33,42 @@ const FABWithSubmenu = ({
21
33
  };
22
34
  }, [pathname]);
23
35
  const handleClick = () => {
24
- setIsMenuOpen(!isMenuOpen);
36
+ setIsMenuOpen((prev) => !prev);
25
37
  };
26
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tooltip, { title: "Menu", placement: "left" }, /* @__PURE__ */ React.createElement(
38
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tooltip, { title: "Menu", placement: slotOptions[slot].tooltipDirection }, /* @__PURE__ */ React.createElement(
27
39
  Fab,
28
40
  {
29
41
  size: "medium",
30
- color: "default",
42
+ color: "info",
31
43
  onClick: handleClick,
32
44
  "aria-label": "Menu",
33
- variant: "circular"
45
+ variant: "circular",
46
+ "data-testid": "fab-with-submenu"
34
47
  },
35
- isMenuOpen ? /* @__PURE__ */ React.createElement(CloseIcon, null) : /* @__PURE__ */ React.createElement(MenuIcon, null)
36
- )), isMenuOpen && /* @__PURE__ */ React.createElement(
37
- Slide,
48
+ isMenuOpen ? /* @__PURE__ */ React.createElement(CloseIcon, { className: styles.menuButtonStyle }) : /* @__PURE__ */ React.createElement(MenuIcon, { className: styles.menuButtonStyle })
49
+ )), /* @__PURE__ */ React.createElement(
50
+ Collapse,
38
51
  {
39
- container: ref,
52
+ style: { textAlign: slotOptions[slot].textAlign },
53
+ in: isMenuOpen,
54
+ mountOnEnter: true,
55
+ unmountOnExit: true,
56
+ orientation: "vertical",
40
57
  easing: {
41
58
  enter: theme.transitions.easing.easeOut,
42
59
  exit: theme.transitions.easing.sharp
43
60
  }
44
61
  },
45
62
  /* @__PURE__ */ React.createElement(React.Fragment, null, fabs?.map((fb) => {
46
- return /* @__PURE__ */ React.createElement(FAB, { actionButton: fb, size: "medium", key: fb.label });
63
+ return /* @__PURE__ */ React.createElement(
64
+ FAB,
65
+ {
66
+ actionButton: fb,
67
+ size: "medium",
68
+ key: fb.label,
69
+ className: styles.button
70
+ }
71
+ );
47
72
  }))
48
73
  ));
49
74
  };
@@ -1 +1 @@
1
- {"version":3,"file":"FABWithSubmenu.esm.js","sources":["../../src/components/FABWithSubmenu.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\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 * as React from 'react';\nimport { useLocation } from 'react-router-dom';\nimport Fab from '@mui/material/Fab';\nimport Tooltip from '@mui/material/Tooltip';\nimport { useTheme } from '@mui/material/styles';\nimport CloseIcon from '@mui/icons-material/Close';\nimport MenuIcon from '@mui/icons-material/Menu';\nimport Slide from '@mui/material/Slide';\nimport { FloatingActionButton } from '../types';\nimport { FAB } from './FAB';\n\nexport const FABWithSubmenu = ({\n fabs,\n ref,\n}: {\n fabs: FloatingActionButton[];\n ref: HTMLDivElement | null;\n}) => {\n const theme = useTheme();\n const { pathname } = useLocation();\n const [isMenuOpen, setIsMenuOpen] = React.useState(false);\n\n React.useEffect(() => {\n return () => {\n setIsMenuOpen(false);\n };\n }, [pathname]);\n\n const handleClick = () => {\n setIsMenuOpen(!isMenuOpen);\n };\n return (\n <>\n <Tooltip title=\"Menu\" placement=\"left\">\n <Fab\n size=\"medium\"\n color=\"default\"\n onClick={handleClick}\n aria-label=\"Menu\"\n variant=\"circular\"\n >\n {isMenuOpen ? <CloseIcon /> : <MenuIcon />}\n </Fab>\n </Tooltip>\n {isMenuOpen && (\n <Slide\n container={ref}\n easing={{\n enter: theme.transitions.easing.easeOut,\n exit: theme.transitions.easing.sharp,\n }}\n >\n <>\n {fabs?.map(fb => {\n return <FAB actionButton={fb} size=\"medium\" key={fb.label} />;\n })}\n </>\n </Slide>\n )}\n </>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;AA2BO,MAAM,iBAAiB,CAAC;AAAA,EAC7B,IAAA;AAAA,EACA;AACF,CAGM,KAAA;AACJ,EAAA,MAAM,QAAQ,QAAS,EAAA;AACvB,EAAM,MAAA,EAAE,QAAS,EAAA,GAAI,WAAY,EAAA;AACjC,EAAA,MAAM,CAAC,UAAY,EAAA,aAAa,CAAI,GAAA,KAAA,CAAM,SAAS,KAAK,CAAA;AAExD,EAAA,KAAA,CAAM,UAAU,MAAM;AACpB,IAAA,OAAO,MAAM;AACX,MAAA,aAAA,CAAc,KAAK,CAAA;AAAA,KACrB;AAAA,GACF,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,cAAc,MAAM;AACxB,IAAA,aAAA,CAAc,CAAC,UAAU,CAAA;AAAA,GAC3B;AACA,EAAA,iFAEK,KAAA,CAAA,aAAA,CAAA,OAAA,EAAA,EAAQ,KAAM,EAAA,MAAA,EAAO,WAAU,MAC9B,EAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,IAAK,EAAA,QAAA;AAAA,MACL,KAAM,EAAA,SAAA;AAAA,MACN,OAAS,EAAA,WAAA;AAAA,MACT,YAAW,EAAA,MAAA;AAAA,MACX,OAAQ,EAAA;AAAA,KAAA;AAAA,IAEP,UAAa,mBAAA,KAAA,CAAA,aAAA,CAAC,SAAU,EAAA,IAAA,CAAA,uCAAM,QAAS,EAAA,IAAA;AAAA,GAE5C,GACC,UACC,oBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAW,EAAA,GAAA;AAAA,MACX,MAAQ,EAAA;AAAA,QACN,KAAA,EAAO,KAAM,CAAA,WAAA,CAAY,MAAO,CAAA,OAAA;AAAA,QAChC,IAAA,EAAM,KAAM,CAAA,WAAA,CAAY,MAAO,CAAA;AAAA;AACjC,KAAA;AAAA,oBAEA,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EACG,IAAM,EAAA,GAAA,CAAI,CAAM,EAAA,KAAA;AACf,MAAO,uBAAA,KAAA,CAAA,aAAA,CAAC,OAAI,YAAc,EAAA,EAAA,EAAI,MAAK,QAAS,EAAA,GAAA,EAAK,GAAG,KAAO,EAAA,CAAA;AAAA,KAC5D,CACH;AAAA,GAGN,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"FABWithSubmenu.esm.js","sources":["../../src/components/FABWithSubmenu.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 * as React from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { makeStyles } from '@mui/styles';\nimport Fab from '@mui/material/Fab';\nimport Tooltip from '@mui/material/Tooltip';\nimport { useTheme } from '@mui/material/styles';\nimport CloseIcon from '@mui/icons-material/Close';\nimport MenuIcon from '@mui/icons-material/Menu';\nimport Collapse from '@mui/material/Collapse';\nimport { FAB } from './FAB';\nimport { slotOptions } from '../utils';\nimport { FloatingActionButton, Slot } from '../types';\n\nconst useStyles = makeStyles(theme => ({\n button: {\n paddingTop: '10px',\n color:\n theme && Object.keys(theme).length > 0\n ? theme.palette.grey[500]\n : '#9e9e9e',\n },\n menuButtonStyle: {\n color: '#1f1f1f',\n },\n}));\n\nexport const FABWithSubmenu = ({\n fabs,\n slot,\n}: {\n fabs: FloatingActionButton[];\n slot: Slot;\n}) => {\n const styles = useStyles();\n const theme = useTheme();\n const { pathname } = useLocation();\n const [isMenuOpen, setIsMenuOpen] = React.useState(false);\n\n React.useEffect(() => {\n return () => {\n setIsMenuOpen(false);\n };\n }, [pathname]);\n\n const handleClick = () => {\n setIsMenuOpen(prev => !prev);\n };\n return (\n <>\n <Tooltip title=\"Menu\" placement={slotOptions[slot].tooltipDirection}>\n <Fab\n size=\"medium\"\n color=\"info\"\n onClick={handleClick}\n aria-label=\"Menu\"\n variant=\"circular\"\n data-testid=\"fab-with-submenu\"\n >\n {isMenuOpen ? (\n <CloseIcon className={styles.menuButtonStyle} />\n ) : (\n <MenuIcon className={styles.menuButtonStyle} />\n )}\n </Fab>\n </Tooltip>\n <Collapse\n style={{ textAlign: slotOptions[slot].textAlign }}\n in={isMenuOpen}\n mountOnEnter\n unmountOnExit\n orientation=\"vertical\"\n easing={{\n enter: theme.transitions.easing.easeOut,\n exit: theme.transitions.easing.sharp,\n }}\n >\n <>\n {fabs?.map(fb => {\n return (\n <FAB\n actionButton={fb}\n size=\"medium\"\n key={fb.label}\n className={styles.button}\n />\n );\n })}\n </>\n </Collapse>\n </>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;AA6BA,MAAM,SAAA,GAAY,WAAW,CAAU,KAAA,MAAA;AAAA,EACrC,MAAQ,EAAA;AAAA,IACN,UAAY,EAAA,MAAA;AAAA,IACZ,KACE,EAAA,KAAA,IAAS,MAAO,CAAA,IAAA,CAAK,KAAK,CAAA,CAAE,MAAS,GAAA,CAAA,GACjC,KAAM,CAAA,OAAA,CAAQ,IAAK,CAAA,GAAG,CACtB,GAAA;AAAA,GACR;AAAA,EACA,eAAiB,EAAA;AAAA,IACf,KAAO,EAAA;AAAA;AAEX,CAAE,CAAA,CAAA;AAEK,MAAM,iBAAiB,CAAC;AAAA,EAC7B,IAAA;AAAA,EACA;AACF,CAGM,KAAA;AACJ,EAAA,MAAM,SAAS,SAAU,EAAA;AACzB,EAAA,MAAM,QAAQ,QAAS,EAAA;AACvB,EAAM,MAAA,EAAE,QAAS,EAAA,GAAI,WAAY,EAAA;AACjC,EAAA,MAAM,CAAC,UAAY,EAAA,aAAa,CAAI,GAAA,KAAA,CAAM,SAAS,KAAK,CAAA;AAExD,EAAA,KAAA,CAAM,UAAU,MAAM;AACpB,IAAA,OAAO,MAAM;AACX,MAAA,aAAA,CAAc,KAAK,CAAA;AAAA,KACrB;AAAA,GACF,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,cAAc,MAAM;AACxB,IAAc,aAAA,CAAA,CAAA,IAAA,KAAQ,CAAC,IAAI,CAAA;AAAA,GAC7B;AACA,EACE,uBAAA,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,sCACG,OAAQ,EAAA,EAAA,KAAA,EAAM,QAAO,SAAW,EAAA,WAAA,CAAY,IAAI,CAAA,CAAE,gBACjD,EAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,IAAK,EAAA,QAAA;AAAA,MACL,KAAM,EAAA,MAAA;AAAA,MACN,OAAS,EAAA,WAAA;AAAA,MACT,YAAW,EAAA,MAAA;AAAA,MACX,OAAQ,EAAA,UAAA;AAAA,MACR,aAAY,EAAA;AAAA,KAAA;AAAA,IAEX,UAAA,mBACE,KAAA,CAAA,aAAA,CAAA,SAAA,EAAA,EAAU,SAAW,EAAA,MAAA,CAAO,eAAiB,EAAA,CAAA,mBAE7C,KAAA,CAAA,aAAA,CAAA,QAAA,EAAA,EAAS,SAAW,EAAA,MAAA,CAAO,eAAiB,EAAA;AAAA,GAGnD,CACA,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,OAAO,EAAE,SAAA,EAAW,WAAY,CAAA,IAAI,EAAE,SAAU,EAAA;AAAA,MAChD,EAAI,EAAA,UAAA;AAAA,MACJ,YAAY,EAAA,IAAA;AAAA,MACZ,aAAa,EAAA,IAAA;AAAA,MACb,WAAY,EAAA,UAAA;AAAA,MACZ,MAAQ,EAAA;AAAA,QACN,KAAA,EAAO,KAAM,CAAA,WAAA,CAAY,MAAO,CAAA,OAAA;AAAA,QAChC,IAAA,EAAM,KAAM,CAAA,WAAA,CAAY,MAAO,CAAA;AAAA;AACjC,KAAA;AAAA,oBAEA,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EACG,IAAM,EAAA,GAAA,CAAI,CAAM,EAAA,KAAA;AACf,MACE,uBAAA,KAAA,CAAA,aAAA;AAAA,QAAC,GAAA;AAAA,QAAA;AAAA,UACC,YAAc,EAAA,EAAA;AAAA,UACd,IAAK,EAAA,QAAA;AAAA,UACL,KAAK,EAAG,CAAA,KAAA;AAAA,UACR,WAAW,MAAO,CAAA;AAAA;AAAA,OACpB;AAAA,KAEH,CACH;AAAA,GAEJ,CAAA;AAEJ;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"FabIcon.esm.js","sources":["../../src/components/FabIcon.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\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 */\nimport * as React from 'react';\nimport { useApp } from '@backstage/core-plugin-api';\n\nimport MuiIcon from '@mui/material/Icon';\n\nexport const FabIcon = ({ icon }: { icon: string | React.ReactElement }) => {\n const app = useApp();\n\n if (!icon) {\n return null;\n }\n\n if (React.isValidElement(icon)) {\n return icon;\n }\n\n const strIcon = icon as string;\n\n const SystemIcon = app.getSystemIcon(strIcon);\n\n if (SystemIcon) {\n return <SystemIcon />;\n }\n\n if (strIcon.startsWith('<svg')) {\n const svgDataUri = `data:image/svg+xml;base64,${btoa(strIcon)}`;\n return (\n <MuiIcon>\n <img src={svgDataUri} alt=\"\" />\n </MuiIcon>\n );\n }\n\n if (\n strIcon.startsWith('https://') ||\n strIcon.startsWith('http://') ||\n strIcon.startsWith('/')\n ) {\n return (\n <MuiIcon baseClassName=\"material-icons-outlined\">\n <img src={strIcon} alt=\"\" />\n </MuiIcon>\n );\n }\n\n return <MuiIcon baseClassName=\"material-icons-outlined\">{strIcon}</MuiIcon>;\n};\n"],"names":[],"mappings":";;;;AAoBO,MAAM,OAAU,GAAA,CAAC,EAAE,IAAA,EAAkD,KAAA;AAC1E,EAAA,MAAM,MAAM,MAAO,EAAA;AAEnB,EAAA,IAAI,CAAC,IAAM,EAAA;AACT,IAAO,OAAA,IAAA;AAAA;AAGT,EAAI,IAAA,KAAA,CAAM,cAAe,CAAA,IAAI,CAAG,EAAA;AAC9B,IAAO,OAAA,IAAA;AAAA;AAGT,EAAA,MAAM,OAAU,GAAA,IAAA;AAEhB,EAAM,MAAA,UAAA,GAAa,GAAI,CAAA,aAAA,CAAc,OAAO,CAAA;AAE5C,EAAA,IAAI,UAAY,EAAA;AACd,IAAA,2CAAQ,UAAW,EAAA,IAAA,CAAA;AAAA;AAGrB,EAAI,IAAA,OAAA,CAAQ,UAAW,CAAA,MAAM,CAAG,EAAA;AAC9B,IAAA,MAAM,UAAa,GAAA,CAAA,0BAAA,EAA6B,IAAK,CAAA,OAAO,CAAC,CAAA,CAAA;AAC7D,IACE,uBAAA,KAAA,CAAA,aAAA,CAAC,+BACE,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAI,KAAK,UAAY,EAAA,GAAA,EAAI,IAAG,CAC/B,CAAA;AAAA;AAIJ,EACE,IAAA,OAAA,CAAQ,UAAW,CAAA,UAAU,CAC7B,IAAA,OAAA,CAAQ,UAAW,CAAA,SAAS,CAC5B,IAAA,OAAA,CAAQ,UAAW,CAAA,GAAG,CACtB,EAAA;AACA,IACE,uBAAA,KAAA,CAAA,aAAA,CAAC,OAAQ,EAAA,EAAA,aAAA,EAAc,yBACrB,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,SAAI,GAAK,EAAA,OAAA,EAAS,GAAI,EAAA,EAAA,EAAG,CAC5B,CAAA;AAAA;AAIJ,EAAA,uBAAQ,KAAA,CAAA,aAAA,CAAA,OAAA,EAAA,EAAQ,aAAc,EAAA,yBAAA,EAAA,EAA2B,OAAQ,CAAA;AACnE;;;;"}
1
+ {"version":3,"file":"FabIcon.esm.js","sources":["../../src/components/FabIcon.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 */\nimport * as React from 'react';\nimport { useApp } from '@backstage/core-plugin-api';\n\nimport MuiIcon from '@mui/material/Icon';\n\nexport const FabIcon = ({ icon }: { icon: string | React.ReactElement }) => {\n const app = useApp();\n\n if (!icon) {\n return null;\n }\n\n if (React.isValidElement(icon)) {\n return icon;\n }\n\n const strIcon = icon as string;\n\n const SystemIcon = app.getSystemIcon(strIcon);\n\n if (SystemIcon) {\n return <SystemIcon />;\n }\n\n if (strIcon.startsWith('<svg')) {\n const svgDataUri = `data:image/svg+xml;base64,${btoa(strIcon)}`;\n return (\n <MuiIcon>\n <img src={svgDataUri} alt=\"\" />\n </MuiIcon>\n );\n }\n\n if (\n strIcon.startsWith('https://') ||\n strIcon.startsWith('http://') ||\n strIcon.startsWith('/')\n ) {\n return (\n <MuiIcon baseClassName=\"material-icons-outlined\">\n <img src={strIcon} alt=\"\" />\n </MuiIcon>\n );\n }\n\n return <MuiIcon baseClassName=\"material-icons-outlined\">{strIcon}</MuiIcon>;\n};\n"],"names":[],"mappings":";;;;AAoBO,MAAM,OAAU,GAAA,CAAC,EAAE,IAAA,EAAkD,KAAA;AAC1E,EAAA,MAAM,MAAM,MAAO,EAAA;AAEnB,EAAA,IAAI,CAAC,IAAM,EAAA;AACT,IAAO,OAAA,IAAA;AAAA;AAGT,EAAI,IAAA,KAAA,CAAM,cAAe,CAAA,IAAI,CAAG,EAAA;AAC9B,IAAO,OAAA,IAAA;AAAA;AAGT,EAAA,MAAM,OAAU,GAAA,IAAA;AAEhB,EAAM,MAAA,UAAA,GAAa,GAAI,CAAA,aAAA,CAAc,OAAO,CAAA;AAE5C,EAAA,IAAI,UAAY,EAAA;AACd,IAAA,2CAAQ,UAAW,EAAA,IAAA,CAAA;AAAA;AAGrB,EAAI,IAAA,OAAA,CAAQ,UAAW,CAAA,MAAM,CAAG,EAAA;AAC9B,IAAA,MAAM,UAAa,GAAA,CAAA,0BAAA,EAA6B,IAAK,CAAA,OAAO,CAAC,CAAA,CAAA;AAC7D,IACE,uBAAA,KAAA,CAAA,aAAA,CAAC,+BACE,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAI,KAAK,UAAY,EAAA,GAAA,EAAI,IAAG,CAC/B,CAAA;AAAA;AAIJ,EACE,IAAA,OAAA,CAAQ,UAAW,CAAA,UAAU,CAC7B,IAAA,OAAA,CAAQ,UAAW,CAAA,SAAS,CAC5B,IAAA,OAAA,CAAQ,UAAW,CAAA,GAAG,CACtB,EAAA;AACA,IACE,uBAAA,KAAA,CAAA,aAAA,CAAC,OAAQ,EAAA,EAAA,aAAA,EAAc,yBACrB,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,SAAI,GAAK,EAAA,OAAA,EAAS,GAAI,EAAA,EAAA,EAAG,CAC5B,CAAA;AAAA;AAIJ,EAAA,uBAAQ,KAAA,CAAA,aAAA,CAAA,OAAA,EAAA,EAAQ,aAAc,EAAA,yBAAA,EAAA,EAA2B,OAAQ,CAAA;AACnE;;;;"}
@@ -7,22 +7,21 @@ import { FAB } from './FAB.esm.js';
7
7
  import { filterAndSortButtons } from '../utils.esm.js';
8
8
 
9
9
  const useStyles = makeStyles((theme) => ({
10
- button: {
10
+ fabContainer: {
11
11
  zIndex: 200,
12
12
  display: "flex",
13
13
  position: "fixed",
14
- maxWidth: "150px",
15
14
  gap: "10px"
16
15
  },
17
16
  "page-end": {
18
- bottom: theme.spacing(4),
19
- right: theme.spacing(4),
17
+ bottom: theme && Object.keys(theme).length > 0 ? theme?.spacing(2) : "16px",
18
+ right: theme && Object.keys(theme).length > 0 ? theme?.spacing(2) : "16px",
20
19
  alignItems: "end"
21
20
  },
22
- "bottom-center": {
23
- bottom: theme.spacing(4),
24
- left: "50%",
25
- alignItems: "center"
21
+ "bottom-left": {
22
+ bottom: theme && Object.keys(theme).length > 0 ? theme?.spacing(2) : "16px",
23
+ paddingLeft: theme && Object.keys(theme).length > 0 ? theme?.spacing(2) : "16px",
24
+ alignItems: "start"
26
25
  }
27
26
  }));
28
27
  const FloatingButton = ({
@@ -30,7 +29,6 @@ const FloatingButton = ({
30
29
  slot
31
30
  }) => {
32
31
  const { pathname } = useLocation();
33
- const subMenuRef = React.useRef(null);
34
32
  const [subMenuDirection, setSubMenuDirection] = React.useState("column");
35
33
  const fabButton = useStyles();
36
34
  React.useEffect(() => {
@@ -55,15 +53,14 @@ const FloatingButton = ({
55
53
  return /* @__PURE__ */ React.createElement(
56
54
  "div",
57
55
  {
58
- className: classnames(fabButton.button, fabButton[slot]),
56
+ className: classnames(fabButton.fabContainer, fabButton[slot]),
59
57
  style: {
60
58
  flexDirection: subMenuDirection
61
59
  },
62
60
  id: "floating-button",
63
- "data-testId": "floating-button",
64
- ref: subMenuRef
61
+ "data-testId": "floating-button"
65
62
  },
66
- fabs.length > 1 ? /* @__PURE__ */ React.createElement(FABWithSubmenu, { fabs, ref: subMenuRef.current }) : /* @__PURE__ */ React.createElement(FAB, { actionButton: fabs[0] })
63
+ fabs.length > 1 ? /* @__PURE__ */ React.createElement(FABWithSubmenu, { fabs, slot }) : /* @__PURE__ */ React.createElement(FAB, { actionButton: fabs[0] })
67
64
  );
68
65
  };
69
66
 
@@ -1 +1 @@
1
- {"version":3,"file":"FloatingButton.esm.js","sources":["../../src/components/FloatingButton.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\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 * as React from 'react';\nimport { useLocation } from 'react-router-dom';\nimport classnames from 'classnames';\n\nimport { makeStyles } from '@mui/styles';\nimport { FABWithSubmenu } from './FABWithSubmenu';\nimport { FAB } from './FAB';\nimport { FloatingActionButton, Slot } from '../types';\nimport { filterAndSortButtons } from '../utils';\n\nconst useStyles = makeStyles(theme => ({\n button: {\n zIndex: 200,\n display: 'flex',\n position: 'fixed',\n maxWidth: '150px',\n gap: '10px',\n },\n 'page-end': {\n bottom: theme.spacing(4),\n right: theme.spacing(4),\n alignItems: 'end',\n },\n 'bottom-center': {\n bottom: theme.spacing(4),\n left: '50%',\n alignItems: 'center',\n },\n}));\n\nexport const FloatingButton = ({\n floatingButtons,\n slot,\n}: {\n floatingButtons: FloatingActionButton[];\n slot: Slot;\n}) => {\n const { pathname } = useLocation();\n const subMenuRef = React.useRef<HTMLDivElement>(null);\n const [subMenuDirection, setSubMenuDirection] = React.useState<\n 'column' | 'column-reverse'\n >('column');\n const fabButton = useStyles();\n\n React.useEffect(() => {\n const floatingButtonElement = document.getElementById('floating-button');\n const screenHeight = window.innerHeight;\n if (floatingButtonElement) {\n const { top } = floatingButtonElement?.getBoundingClientRect();\n if (top < screenHeight / 2) {\n setSubMenuDirection('column');\n } else {\n setSubMenuDirection('column-reverse');\n }\n }\n }, [pathname]);\n\n const fabs = React.useMemo(\n () => filterAndSortButtons(floatingButtons, pathname),\n [floatingButtons, pathname],\n );\n\n if (fabs?.length === 0) {\n return null;\n }\n return (\n <div\n className={classnames(fabButton.button, fabButton[slot])}\n style={{\n flexDirection: subMenuDirection,\n }}\n id=\"floating-button\"\n data-testId=\"floating-button\"\n ref={subMenuRef}\n >\n {fabs.length > 1 ? (\n <FABWithSubmenu fabs={fabs} ref={subMenuRef.current} />\n ) : (\n <FAB actionButton={fabs[0]} />\n )}\n </div>\n );\n};\n"],"names":[],"mappings":";;;;;;;;AA0BA,MAAM,SAAA,GAAY,WAAW,CAAU,KAAA,MAAA;AAAA,EACrC,MAAQ,EAAA;AAAA,IACN,MAAQ,EAAA,GAAA;AAAA,IACR,OAAS,EAAA,MAAA;AAAA,IACT,QAAU,EAAA,OAAA;AAAA,IACV,QAAU,EAAA,OAAA;AAAA,IACV,GAAK,EAAA;AAAA,GACP;AAAA,EACA,UAAY,EAAA;AAAA,IACV,MAAA,EAAQ,KAAM,CAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,IACvB,KAAA,EAAO,KAAM,CAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,IACtB,UAAY,EAAA;AAAA,GACd;AAAA,EACA,eAAiB,EAAA;AAAA,IACf,MAAA,EAAQ,KAAM,CAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,IACvB,IAAM,EAAA,KAAA;AAAA,IACN,UAAY,EAAA;AAAA;AAEhB,CAAE,CAAA,CAAA;AAEK,MAAM,iBAAiB,CAAC;AAAA,EAC7B,eAAA;AAAA,EACA;AACF,CAGM,KAAA;AACJ,EAAM,MAAA,EAAE,QAAS,EAAA,GAAI,WAAY,EAAA;AACjC,EAAM,MAAA,UAAA,GAAa,KAAM,CAAA,MAAA,CAAuB,IAAI,CAAA;AACpD,EAAA,MAAM,CAAC,gBAAkB,EAAA,mBAAmB,CAAI,GAAA,KAAA,CAAM,SAEpD,QAAQ,CAAA;AACV,EAAA,MAAM,YAAY,SAAU,EAAA;AAE5B,EAAA,KAAA,CAAM,UAAU,MAAM;AACpB,IAAM,MAAA,qBAAA,GAAwB,QAAS,CAAA,cAAA,CAAe,iBAAiB,CAAA;AACvE,IAAA,MAAM,eAAe,MAAO,CAAA,WAAA;AAC5B,IAAA,IAAI,qBAAuB,EAAA;AACzB,MAAA,MAAM,EAAE,GAAA,EAAQ,GAAA,qBAAA,EAAuB,qBAAsB,EAAA;AAC7D,MAAI,IAAA,GAAA,GAAM,eAAe,CAAG,EAAA;AAC1B,QAAA,mBAAA,CAAoB,QAAQ,CAAA;AAAA,OACvB,MAAA;AACL,QAAA,mBAAA,CAAoB,gBAAgB,CAAA;AAAA;AACtC;AACF,GACF,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,OAAO,KAAM,CAAA,OAAA;AAAA,IACjB,MAAM,oBAAqB,CAAA,eAAA,EAAiB,QAAQ,CAAA;AAAA,IACpD,CAAC,iBAAiB,QAAQ;AAAA,GAC5B;AAEA,EAAI,IAAA,IAAA,EAAM,WAAW,CAAG,EAAA;AACtB,IAAO,OAAA,IAAA;AAAA;AAET,EACE,uBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,WAAW,UAAW,CAAA,SAAA,CAAU,MAAQ,EAAA,SAAA,CAAU,IAAI,CAAC,CAAA;AAAA,MACvD,KAAO,EAAA;AAAA,QACL,aAAe,EAAA;AAAA,OACjB;AAAA,MACA,EAAG,EAAA,iBAAA;AAAA,MACH,aAAY,EAAA,iBAAA;AAAA,MACZ,GAAK,EAAA;AAAA,KAAA;AAAA,IAEJ,IAAK,CAAA,MAAA,GAAS,CACb,mBAAA,KAAA,CAAA,aAAA,CAAC,kBAAe,IAAY,EAAA,GAAA,EAAK,UAAW,CAAA,OAAA,EAAS,oBAEpD,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA,EAAI,YAAc,EAAA,IAAA,CAAK,CAAC,CAAG,EAAA;AAAA,GAEhC;AAEJ;;;;"}
1
+ {"version":3,"file":"FloatingButton.esm.js","sources":["../../src/components/FloatingButton.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 * as React from 'react';\nimport { useLocation } from 'react-router-dom';\nimport classnames from 'classnames';\n\nimport { makeStyles } from '@mui/styles';\nimport { FABWithSubmenu } from './FABWithSubmenu';\nimport { FAB } from './FAB';\nimport { FloatingActionButton, Slot } from '../types';\nimport { filterAndSortButtons } from '../utils';\n\nconst useStyles = makeStyles(theme => ({\n fabContainer: {\n zIndex: 200,\n display: 'flex',\n position: 'fixed',\n gap: '10px',\n },\n 'page-end': {\n bottom: theme && Object.keys(theme).length > 0 ? theme?.spacing(2) : '16px',\n right: theme && Object.keys(theme).length > 0 ? theme?.spacing(2) : '16px',\n alignItems: 'end',\n },\n 'bottom-left': {\n bottom: theme && Object.keys(theme).length > 0 ? theme?.spacing(2) : '16px',\n paddingLeft:\n theme && Object.keys(theme).length > 0 ? theme?.spacing(2) : '16px',\n alignItems: 'start',\n },\n}));\n\nexport const FloatingButton = ({\n floatingButtons,\n slot,\n}: {\n floatingButtons: FloatingActionButton[];\n slot: Slot;\n}) => {\n const { pathname } = useLocation();\n const [subMenuDirection, setSubMenuDirection] = React.useState<\n 'column' | 'column-reverse'\n >('column');\n const fabButton = useStyles();\n\n React.useEffect(() => {\n const floatingButtonElement = document.getElementById('floating-button');\n const screenHeight = window.innerHeight;\n if (floatingButtonElement) {\n const { top } = floatingButtonElement?.getBoundingClientRect();\n if (top < screenHeight / 2) {\n setSubMenuDirection('column');\n } else {\n setSubMenuDirection('column-reverse');\n }\n }\n }, [pathname]);\n\n const fabs = React.useMemo(\n () => filterAndSortButtons(floatingButtons, pathname),\n [floatingButtons, pathname],\n );\n\n if (fabs?.length === 0) {\n return null;\n }\n return (\n <div\n className={classnames(fabButton.fabContainer, fabButton[slot])}\n style={{\n flexDirection: subMenuDirection,\n }}\n id=\"floating-button\"\n data-testId=\"floating-button\"\n >\n {fabs.length > 1 ? (\n <FABWithSubmenu fabs={fabs} slot={slot} />\n ) : (\n <FAB actionButton={fabs[0]} />\n )}\n </div>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"GlobalFloatingActionButton.esm.js","sources":["../../src/components/GlobalFloatingActionButton.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\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 */\nimport * as React from 'react';\nimport { FloatingActionButton } from '../types';\nimport { evaluateFloatingButtonsWithPositions } from '../utils';\nimport { FloatingButton } from './FloatingButton';\n\nexport const GlobalFloatingActionButton = ({\n floatingButtons,\n}: {\n floatingButtons: FloatingActionButton[];\n}) => {\n const floatingButtonMap =\n evaluateFloatingButtonsWithPositions(floatingButtons);\n\n return (\n <>\n {floatingButtonMap.map(fb => (\n <FloatingButton\n key={fb.slot}\n slot={fb.slot}\n floatingButtons={fb.actions}\n />\n ))}\n </>\n );\n};\n"],"names":[],"mappings":";;;;AAoBO,MAAM,6BAA6B,CAAC;AAAA,EACzC;AACF,CAEM,KAAA;AACJ,EAAM,MAAA,iBAAA,GACJ,qCAAqC,eAAe,CAAA;AAEtD,EACE,uBAAA,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EACG,iBAAkB,CAAA,GAAA,CAAI,CACrB,EAAA,qBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,cAAA;AAAA,IAAA;AAAA,MACC,KAAK,EAAG,CAAA,IAAA;AAAA,MACR,MAAM,EAAG,CAAA,IAAA;AAAA,MACT,iBAAiB,EAAG,CAAA;AAAA;AAAA,GAEvB,CACH,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"GlobalFloatingActionButton.esm.js","sources":["../../src/components/GlobalFloatingActionButton.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 */\nimport * as React from 'react';\nimport { FloatingActionButton } from '../types';\nimport { evaluateFloatingButtonsWithPositions } from '../utils';\nimport { FloatingButton } from './FloatingButton';\n\nexport const GlobalFloatingActionButton = ({\n floatingButtons,\n}: {\n floatingButtons: FloatingActionButton[];\n}) => {\n const floatingButtonMap =\n evaluateFloatingButtonsWithPositions(floatingButtons);\n\n return (\n <>\n {floatingButtonMap.map(fb => (\n <FloatingButton\n key={fb.slot}\n slot={fb.slot}\n floatingButtons={fb.actions}\n />\n ))}\n </>\n );\n};\n"],"names":[],"mappings":";;;;AAoBO,MAAM,6BAA6B,CAAC;AAAA,EACzC;AACF,CAEM,KAAA;AACJ,EAAM,MAAA,iBAAA,GACJ,qCAAqC,eAAe,CAAA;AAEtD,EACE,uBAAA,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EACG,iBAAkB,CAAA,GAAA,CAAI,CACrB,EAAA,qBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,cAAA;AAAA,IAAA;AAAA,MACC,KAAK,EAAG,CAAA,IAAA;AAAA,MACR,MAAM,EAAG,CAAA,IAAA;AAAA,MACT,iBAAiB,EAAG,CAAA;AAAA;AAAA,GAEvB,CACH,CAAA;AAEJ;;;;"}
@@ -0,0 +1,7 @@
1
+ const NullComponent = () => {
2
+ console.info("NullComponent rendered.");
3
+ return null;
4
+ };
5
+
6
+ export { NullComponent };
7
+ //# sourceMappingURL=NullComponent.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NullComponent.esm.js","sources":["../../src/components/NullComponent.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\n/**\n * Null Component\n *\n * Mount point without a component is not considered. So adding this that doesn't return anything.\n * @public\n */\n\nexport const NullComponent = () => {\n // eslint-disable-next-line no-console\n console.info('NullComponent rendered.');\n return null;\n};\n"],"names":[],"mappings":"AAuBO,MAAM,gBAAgB,MAAM;AAEjC,EAAA,OAAA,CAAQ,KAAK,yBAAyB,CAAA;AACtC,EAAO,OAAA,IAAA;AACT;;;;"}
@@ -0,0 +1,10 @@
1
+ import { useScalprum } from '@scalprum/react-core';
2
+
3
+ const useFabMountPoints = () => {
4
+ const scalprum = useScalprum();
5
+ const fabMountPoints = scalprum?.api?.dynamicRootConfig?.mountPoints?.["global.floatingactionbutton/component"];
6
+ return fabMountPoints;
7
+ };
8
+
9
+ export { useFabMountPoints };
10
+ //# sourceMappingURL=useFabMountPoints.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFabMountPoints.esm.js","sources":["../../src/hooks/useFabMountPoints.ts"],"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 { useScalprum } from '@scalprum/react-core';\n\nimport { FABMountPoint } from '../types';\n\ninterface ScalprumState {\n api?: {\n dynamicRootConfig?: {\n mountPoints?: {\n 'global.floatingactionbutton/component': FABMountPoint[];\n };\n };\n };\n}\n\nexport const useFabMountPoints = (): FABMountPoint[] | undefined => {\n const scalprum = useScalprum<ScalprumState>();\n const fabMountPoints =\n scalprum?.api?.dynamicRootConfig?.mountPoints?.[\n 'global.floatingactionbutton/component'\n ];\n\n return fabMountPoints;\n};\n"],"names":[],"mappings":";;AA8BO,MAAM,oBAAoB,MAAmC;AAClE,EAAA,MAAM,WAAW,WAA2B,EAAA;AAC5C,EAAA,MAAM,cACJ,GAAA,QAAA,EAAU,GAAK,EAAA,iBAAA,EAAmB,cAChC,uCACF,CAAA;AAEF,EAAO,OAAA,cAAA;AACT;;;;"}
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ declare enum Slot {
15
15
  /**
16
16
  * Positions the floating action button at the bottom center of the page
17
17
  */
18
- BOTTOM_CENTER = "bottom-center"
18
+ BOTTOM_LEFT = "bottom-left"
19
19
  }
20
20
  /**
21
21
  * Floating Action Button
@@ -26,7 +26,7 @@ type FloatingActionButton = {
26
26
  slot?: Slot;
27
27
  label: string;
28
28
  showLabel?: boolean;
29
- icon?: string | React.ReactElement;
29
+ icon: string | React.ReactElement;
30
30
  size?: 'small' | 'medium' | 'large';
31
31
  color?: 'default' | 'error' | 'info' | 'inherit' | 'primary' | 'secondary' | 'success' | 'warning';
32
32
  onClick?: React.MouseEventHandler;
@@ -45,6 +45,14 @@ type FloatingActionButtonWithPositions = Array<{
45
45
  slot: Slot;
46
46
  actions: FloatingActionButton[];
47
47
  }>;
48
+ /**
49
+ * FAB Mount Point
50
+ *
51
+ * @public
52
+ */
53
+ type FABMountPoint = {
54
+ config?: FloatingActionButton;
55
+ };
48
56
 
49
57
  /**
50
58
  * Global Floating Action Button Plugin
@@ -52,6 +60,12 @@ type FloatingActionButtonWithPositions = Array<{
52
60
  * @public
53
61
  */
54
62
  declare const globalFloatingActionButtonPlugin: _backstage_core_plugin_api.BackstagePlugin<{}, {}, {}>;
63
+ /**
64
+ * Dynamic Global Floating Action Button Plugin
65
+ *
66
+ * @public
67
+ */
68
+ declare const dynamicGlobalFloatingActionButtonPlugin: _backstage_core_plugin_api.BackstagePlugin<{}, {}, {}>;
55
69
  /**
56
70
  * Global Floating Action Button
57
71
  *
@@ -60,5 +74,17 @@ declare const globalFloatingActionButtonPlugin: _backstage_core_plugin_api.Backs
60
74
  declare const GlobalFloatingActionButton: ({ floatingButtons, }: {
61
75
  floatingButtons: FloatingActionButton[];
62
76
  }) => react.JSX.Element;
77
+ /**
78
+ * Dynamic Global Floating Action Button
79
+ *
80
+ * @public
81
+ */
82
+ declare const DynamicGlobalFloatingActionButton: React.ComponentType;
83
+ /**
84
+ * Null Component
85
+ *
86
+ * @public
87
+ */
88
+ declare const NullComponent: React.ComponentType;
63
89
 
64
- export { type FloatingActionButton, type FloatingActionButtonWithPositions, GlobalFloatingActionButton, Slot, globalFloatingActionButtonPlugin };
90
+ export { DynamicGlobalFloatingActionButton, type FABMountPoint, type FloatingActionButton, type FloatingActionButtonWithPositions, GlobalFloatingActionButton, NullComponent, Slot, dynamicGlobalFloatingActionButtonPlugin, globalFloatingActionButtonPlugin };
package/dist/index.esm.js CHANGED
@@ -1,3 +1,3 @@
1
- export { GlobalFloatingActionButton, globalFloatingActionButtonPlugin } from './plugin.esm.js';
1
+ export { DynamicGlobalFloatingActionButton, GlobalFloatingActionButton, NullComponent, dynamicGlobalFloatingActionButtonPlugin, globalFloatingActionButtonPlugin } from './plugin.esm.js';
2
2
  export { Slot } from './types.esm.js';
3
3
  //# sourceMappingURL=index.esm.js.map
@@ -3,6 +3,9 @@ import { createPlugin, createComponentExtension } from '@backstage/core-plugin-a
3
3
  const globalFloatingActionButtonPlugin = createPlugin({
4
4
  id: "global-floating-action-button"
5
5
  });
6
+ const dynamicGlobalFloatingActionButtonPlugin = createPlugin({
7
+ id: "dynamic-global-floating-action-button"
8
+ });
6
9
  const GlobalFloatingActionButton = globalFloatingActionButtonPlugin.provide(
7
10
  createComponentExtension({
8
11
  name: "GlobalFloatingActionButton",
@@ -13,6 +16,24 @@ const GlobalFloatingActionButton = globalFloatingActionButtonPlugin.provide(
13
16
  }
14
17
  })
15
18
  );
19
+ const DynamicGlobalFloatingActionButton = dynamicGlobalFloatingActionButtonPlugin.provide(
20
+ createComponentExtension({
21
+ name: "DynamicGlobalFloatingActionButton",
22
+ component: {
23
+ lazy: () => import('./components/DynamicGlobalFloatingActionButton.esm.js').then(
24
+ (m) => m.DynamicGlobalFloatingActionButton
25
+ )
26
+ }
27
+ })
28
+ );
29
+ const NullComponent = dynamicGlobalFloatingActionButtonPlugin.provide(
30
+ createComponentExtension({
31
+ name: "NullComponent",
32
+ component: {
33
+ lazy: () => import('./components/NullComponent.esm.js').then((m) => m.NullComponent)
34
+ }
35
+ })
36
+ );
16
37
 
17
- export { GlobalFloatingActionButton, globalFloatingActionButtonPlugin };
38
+ export { DynamicGlobalFloatingActionButton, GlobalFloatingActionButton, NullComponent, dynamicGlobalFloatingActionButtonPlugin, globalFloatingActionButtonPlugin };
18
39
  //# sourceMappingURL=plugin.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\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 */\nimport {\n createComponentExtension,\n createPlugin,\n} from '@backstage/core-plugin-api';\n\n/**\n * Global Floating Action Button Plugin\n *\n * @public\n */\nexport const globalFloatingActionButtonPlugin = createPlugin({\n id: 'global-floating-action-button',\n});\n\n/**\n * Global Floating Action Button\n *\n * @public\n */\nexport const GlobalFloatingActionButton =\n globalFloatingActionButtonPlugin.provide(\n createComponentExtension({\n name: 'GlobalFloatingActionButton',\n component: {\n lazy: () =>\n import('./components/GlobalFloatingActionButton').then(\n m => m.GlobalFloatingActionButton,\n ),\n },\n }),\n );\n"],"names":[],"mappings":";;AAyBO,MAAM,mCAAmC,YAAa,CAAA;AAAA,EAC3D,EAAI,EAAA;AACN,CAAC;AAOM,MAAM,6BACX,gCAAiC,CAAA,OAAA;AAAA,EAC/B,wBAAyB,CAAA;AAAA,IACvB,IAAM,EAAA,4BAAA;AAAA,IACN,SAAW,EAAA;AAAA,MACT,IAAM,EAAA,MACJ,OAAO,gDAAyC,CAAE,CAAA,IAAA;AAAA,QAChD,OAAK,CAAE,CAAA;AAAA;AACT;AACJ,GACD;AACH;;;;"}
1
+ {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"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 {\n createComponentExtension,\n createPlugin,\n} from '@backstage/core-plugin-api';\n\n/**\n * Global Floating Action Button Plugin\n *\n * @public\n */\nexport const globalFloatingActionButtonPlugin = createPlugin({\n id: 'global-floating-action-button',\n});\n\n/**\n * Dynamic Global Floating Action Button Plugin\n *\n * @public\n */\nexport const dynamicGlobalFloatingActionButtonPlugin = createPlugin({\n id: 'dynamic-global-floating-action-button',\n});\n\n/**\n * Global Floating Action Button\n *\n * @public\n */\nexport const GlobalFloatingActionButton =\n globalFloatingActionButtonPlugin.provide(\n createComponentExtension({\n name: 'GlobalFloatingActionButton',\n component: {\n lazy: () =>\n import('./components/GlobalFloatingActionButton').then(\n m => m.GlobalFloatingActionButton,\n ),\n },\n }),\n );\n\n/**\n * Dynamic Global Floating Action Button\n *\n * @public\n */\nexport const DynamicGlobalFloatingActionButton: React.ComponentType =\n dynamicGlobalFloatingActionButtonPlugin.provide(\n createComponentExtension({\n name: 'DynamicGlobalFloatingActionButton',\n component: {\n lazy: () =>\n import('./components/DynamicGlobalFloatingActionButton').then(\n m => m.DynamicGlobalFloatingActionButton,\n ),\n },\n }),\n );\n\n/**\n * Null Component\n *\n * @public\n */\nexport const NullComponent: React.ComponentType =\n dynamicGlobalFloatingActionButtonPlugin.provide(\n createComponentExtension({\n name: 'NullComponent',\n component: {\n lazy: () =>\n import('./components/NullComponent').then(m => m.NullComponent),\n },\n }),\n );\n"],"names":[],"mappings":";;AA0BO,MAAM,mCAAmC,YAAa,CAAA;AAAA,EAC3D,EAAI,EAAA;AACN,CAAC;AAOM,MAAM,0CAA0C,YAAa,CAAA;AAAA,EAClE,EAAI,EAAA;AACN,CAAC;AAOM,MAAM,6BACX,gCAAiC,CAAA,OAAA;AAAA,EAC/B,wBAAyB,CAAA;AAAA,IACvB,IAAM,EAAA,4BAAA;AAAA,IACN,SAAW,EAAA;AAAA,MACT,IAAM,EAAA,MACJ,OAAO,gDAAyC,CAAE,CAAA,IAAA;AAAA,QAChD,OAAK,CAAE,CAAA;AAAA;AACT;AACJ,GACD;AACH;AAOK,MAAM,oCACX,uCAAwC,CAAA,OAAA;AAAA,EACtC,wBAAyB,CAAA;AAAA,IACvB,IAAM,EAAA,mCAAA;AAAA,IACN,SAAW,EAAA;AAAA,MACT,IAAM,EAAA,MACJ,OAAO,uDAAgD,CAAE,CAAA,IAAA;AAAA,QACvD,OAAK,CAAE,CAAA;AAAA;AACT;AACJ,GACD;AACH;AAOK,MAAM,gBACX,uCAAwC,CAAA,OAAA;AAAA,EACtC,wBAAyB,CAAA;AAAA,IACvB,IAAM,EAAA,eAAA;AAAA,IACN,SAAW,EAAA;AAAA,MACT,IAAA,EAAM,MACJ,OAAO,mCAA4B,EAAE,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,aAAa;AAAA;AAClE,GACD;AACH;;;;"}
package/dist/types.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  var Slot = /* @__PURE__ */ ((Slot2) => {
2
2
  Slot2["PAGE_END"] = "page-end";
3
- Slot2["BOTTOM_CENTER"] = "bottom-center";
3
+ Slot2["BOTTOM_LEFT"] = "bottom-left";
4
4
  return Slot2;
5
5
  })(Slot || {});
6
6
 
@@ -1 +1 @@
1
- {"version":3,"file":"types.esm.js","sources":["../src/types.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\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\n/**\n * Slot\n *\n * @public\n */\nexport enum Slot {\n /**\n * Positions the floating action button in the bottom-right corner of the page\n */\n PAGE_END = 'page-end',\n /**\n * Positions the floating action button at the bottom center of the page\n */\n BOTTOM_CENTER = 'bottom-center',\n}\n\n/**\n * Floating Action Button\n *\n * @public\n */\nexport type FloatingActionButton = {\n slot?: Slot;\n label: string;\n showLabel?: boolean;\n icon?: string | React.ReactElement;\n size?: 'small' | 'medium' | 'large';\n color?:\n | 'default'\n | 'error'\n | 'info'\n | 'inherit'\n | 'primary'\n | 'secondary'\n | 'success'\n | 'warning';\n onClick?: React.MouseEventHandler;\n to?: string;\n toolTip?: string;\n priority?: number;\n visibleOnPaths?: string[];\n excludeOnPaths?: string[];\n};\n\n/**\n * Floating Action Button With Positions\n *\n * @public\n */\nexport type FloatingActionButtonWithPositions = Array<{\n slot: Slot;\n actions: FloatingActionButton[];\n}>;\n"],"names":["Slot"],"mappings":"AAqBY,IAAA,IAAA,qBAAAA,KAAL,KAAA;AAIL,EAAAA,MAAA,UAAW,CAAA,GAAA,UAAA;AAIX,EAAAA,MAAA,eAAgB,CAAA,GAAA,eAAA;AARN,EAAAA,OAAAA,KAAAA;AAAA,CAAA,EAAA,IAAA,IAAA,EAAA;;;;"}
1
+ {"version":3,"file":"types.esm.js","sources":["../src/types.ts"],"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\n/**\n * Slot\n *\n * @public\n */\nexport enum Slot {\n /**\n * Positions the floating action button in the bottom-right corner of the page\n */\n PAGE_END = 'page-end',\n /**\n * Positions the floating action button at the bottom center of the page\n */\n BOTTOM_LEFT = 'bottom-left',\n}\n\n/**\n * Floating Action Button\n *\n * @public\n */\nexport type FloatingActionButton = {\n slot?: Slot;\n label: string;\n showLabel?: boolean;\n icon: string | React.ReactElement;\n size?: 'small' | 'medium' | 'large';\n color?:\n | 'default'\n | 'error'\n | 'info'\n | 'inherit'\n | 'primary'\n | 'secondary'\n | 'success'\n | 'warning';\n onClick?: React.MouseEventHandler;\n to?: string;\n toolTip?: string;\n priority?: number;\n visibleOnPaths?: string[];\n excludeOnPaths?: string[];\n};\n\n/**\n * Floating Action Button With Positions\n *\n * @public\n */\nexport type FloatingActionButtonWithPositions = Array<{\n slot: Slot;\n actions: FloatingActionButton[];\n}>;\n\n/**\n * FAB Mount Point\n *\n * @public\n */\nexport type FABMountPoint = {\n config?: FloatingActionButton;\n};\n"],"names":["Slot"],"mappings":"AAqBY,IAAA,IAAA,qBAAAA,KAAL,KAAA;AAIL,EAAAA,MAAA,UAAW,CAAA,GAAA,UAAA;AAIX,EAAAA,MAAA,aAAc,CAAA,GAAA,aAAA;AARJ,EAAAA,OAAAA,KAAAA;AAAA,CAAA,EAAA,IAAA,IAAA,EAAA;;;;"}
package/dist/utils.esm.js CHANGED
@@ -44,6 +44,18 @@ const filterAndSortButtons = (floatingButtons, pathname) => {
44
44
  const sortedButtons = sortButtonsWithPriority(filteredButtons);
45
45
  return sortedButtons;
46
46
  };
47
+ const slotOptions = {
48
+ [Slot.BOTTOM_LEFT]: {
49
+ tooltipDirection: "right",
50
+ textAlign: "left",
51
+ margin: { ml: 1 }
52
+ },
53
+ [Slot.PAGE_END]: {
54
+ tooltipDirection: "left",
55
+ textAlign: "right",
56
+ margin: { mr: 1 }
57
+ }
58
+ };
47
59
 
48
- export { evaluateFloatingButtonsWithPositions, filterAndSortButtons, sortButtonsWithPriority };
60
+ export { evaluateFloatingButtonsWithPositions, filterAndSortButtons, slotOptions, sortButtonsWithPriority };
49
61
  //# sourceMappingURL=utils.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.esm.js","sources":["../src/utils.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\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 */\nimport {\n FloatingActionButton,\n FloatingActionButtonWithPositions,\n Slot,\n} from './types';\n\nexport const evaluateFloatingButtonsWithPositions = (\n floatingButtons: FloatingActionButton[],\n): FloatingActionButtonWithPositions =>\n floatingButtons.reduce(\n (acc: FloatingActionButtonWithPositions, fb: FloatingActionButton) => {\n const slot = fb.slot ?? Slot.PAGE_END;\n const slotWithActions = acc.find(a => a.slot === slot);\n if (slotWithActions) {\n slotWithActions.actions.push(fb);\n } else {\n acc.push({\n slot,\n actions: [fb],\n });\n }\n return acc;\n },\n [],\n );\n\nexport const sortButtonsWithPriority = (\n floatingButtons: FloatingActionButton[],\n) => {\n const buttons = [...floatingButtons];\n return buttons.sort((fb1, fb2) => {\n if ((fb2.priority || 0) > (fb1.priority || 0)) {\n return 1;\n }\n if ((fb2.priority || 0) < (fb1.priority || 0)) {\n return -1;\n }\n return 0;\n });\n};\n\nexport const filterAndSortButtons = (\n floatingButtons: FloatingActionButton[],\n pathname: string,\n) => {\n const filteredButtons = floatingButtons.filter(fb => {\n if (fb.excludeOnPaths?.includes(pathname)) {\n return false;\n }\n if (fb.visibleOnPaths && fb.visibleOnPaths.length > 0) {\n if (fb.visibleOnPaths?.includes(pathname)) {\n return true;\n }\n return false;\n }\n return true;\n });\n const sortedButtons = sortButtonsWithPriority(filteredButtons);\n return sortedButtons;\n};\n"],"names":[],"mappings":";;AAqBa,MAAA,oCAAA,GAAuC,CAClD,eAAA,KAEA,eAAgB,CAAA,MAAA;AAAA,EACd,CAAC,KAAwC,EAA6B,KAAA;AACpE,IAAM,MAAA,IAAA,GAAO,EAAG,CAAA,IAAA,IAAQ,IAAK,CAAA,QAAA;AAC7B,IAAA,MAAM,kBAAkB,GAAI,CAAA,IAAA,CAAK,CAAK,CAAA,KAAA,CAAA,CAAE,SAAS,IAAI,CAAA;AACrD,IAAA,IAAI,eAAiB,EAAA;AACnB,MAAgB,eAAA,CAAA,OAAA,CAAQ,KAAK,EAAE,CAAA;AAAA,KAC1B,MAAA;AACL,MAAA,GAAA,CAAI,IAAK,CAAA;AAAA,QACP,IAAA;AAAA,QACA,OAAA,EAAS,CAAC,EAAE;AAAA,OACb,CAAA;AAAA;AAEH,IAAO,OAAA,GAAA;AAAA,GACT;AAAA,EACA;AACF;AAEW,MAAA,uBAAA,GAA0B,CACrC,eACG,KAAA;AACH,EAAM,MAAA,OAAA,GAAU,CAAC,GAAG,eAAe,CAAA;AACnC,EAAA,OAAO,OAAQ,CAAA,IAAA,CAAK,CAAC,GAAA,EAAK,GAAQ,KAAA;AAChC,IAAA,IAAA,CAAK,GAAI,CAAA,QAAA,IAAY,CAAM,KAAA,GAAA,CAAI,YAAY,CAAI,CAAA,EAAA;AAC7C,MAAO,OAAA,CAAA;AAAA;AAET,IAAA,IAAA,CAAK,GAAI,CAAA,QAAA,IAAY,CAAM,KAAA,GAAA,CAAI,YAAY,CAAI,CAAA,EAAA;AAC7C,MAAO,OAAA,CAAA,CAAA;AAAA;AAET,IAAO,OAAA,CAAA;AAAA,GACR,CAAA;AACH;AAEa,MAAA,oBAAA,GAAuB,CAClC,eAAA,EACA,QACG,KAAA;AACH,EAAM,MAAA,eAAA,GAAkB,eAAgB,CAAA,MAAA,CAAO,CAAM,EAAA,KAAA;AACnD,IAAA,IAAI,EAAG,CAAA,cAAA,EAAgB,QAAS,CAAA,QAAQ,CAAG,EAAA;AACzC,MAAO,OAAA,KAAA;AAAA;AAET,IAAA,IAAI,EAAG,CAAA,cAAA,IAAkB,EAAG,CAAA,cAAA,CAAe,SAAS,CAAG,EAAA;AACrD,MAAA,IAAI,EAAG,CAAA,cAAA,EAAgB,QAAS,CAAA,QAAQ,CAAG,EAAA;AACzC,QAAO,OAAA,IAAA;AAAA;AAET,MAAO,OAAA,KAAA;AAAA;AAET,IAAO,OAAA,IAAA;AAAA,GACR,CAAA;AACD,EAAM,MAAA,aAAA,GAAgB,wBAAwB,eAAe,CAAA;AAC7D,EAAO,OAAA,aAAA;AACT;;;;"}
1
+ {"version":3,"file":"utils.esm.js","sources":["../src/utils.ts"],"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 */\nimport {\n FloatingActionButton,\n FloatingActionButtonWithPositions,\n Slot,\n} from './types';\n\nexport const evaluateFloatingButtonsWithPositions = (\n floatingButtons: FloatingActionButton[],\n): FloatingActionButtonWithPositions =>\n floatingButtons.reduce(\n (acc: FloatingActionButtonWithPositions, fb: FloatingActionButton) => {\n const slot = fb.slot ?? Slot.PAGE_END;\n const slotWithActions = acc.find(a => a.slot === slot);\n if (slotWithActions) {\n slotWithActions.actions.push(fb);\n } else {\n acc.push({\n slot,\n actions: [fb],\n });\n }\n return acc;\n },\n [],\n );\n\nexport const sortButtonsWithPriority = (\n floatingButtons: FloatingActionButton[],\n) => {\n const buttons = [...floatingButtons];\n return buttons.sort((fb1, fb2) => {\n if ((fb2.priority || 0) > (fb1.priority || 0)) {\n return 1;\n }\n if ((fb2.priority || 0) < (fb1.priority || 0)) {\n return -1;\n }\n return 0;\n });\n};\n\nexport const filterAndSortButtons = (\n floatingButtons: FloatingActionButton[],\n pathname: string,\n) => {\n const filteredButtons = floatingButtons.filter(fb => {\n if (fb.excludeOnPaths?.includes(pathname)) {\n return false;\n }\n if (fb.visibleOnPaths && fb.visibleOnPaths.length > 0) {\n if (fb.visibleOnPaths?.includes(pathname)) {\n return true;\n }\n return false;\n }\n return true;\n });\n const sortedButtons = sortButtonsWithPriority(filteredButtons);\n return sortedButtons;\n};\n\nexport const slotOptions = {\n [Slot.BOTTOM_LEFT]: {\n tooltipDirection: 'right',\n textAlign: 'left',\n margin: { ml: 1 },\n },\n [Slot.PAGE_END]: {\n tooltipDirection: 'left',\n textAlign: 'right',\n margin: { mr: 1 },\n },\n} as const;\n"],"names":[],"mappings":";;AAqBa,MAAA,oCAAA,GAAuC,CAClD,eAAA,KAEA,eAAgB,CAAA,MAAA;AAAA,EACd,CAAC,KAAwC,EAA6B,KAAA;AACpE,IAAM,MAAA,IAAA,GAAO,EAAG,CAAA,IAAA,IAAQ,IAAK,CAAA,QAAA;AAC7B,IAAA,MAAM,kBAAkB,GAAI,CAAA,IAAA,CAAK,CAAK,CAAA,KAAA,CAAA,CAAE,SAAS,IAAI,CAAA;AACrD,IAAA,IAAI,eAAiB,EAAA;AACnB,MAAgB,eAAA,CAAA,OAAA,CAAQ,KAAK,EAAE,CAAA;AAAA,KAC1B,MAAA;AACL,MAAA,GAAA,CAAI,IAAK,CAAA;AAAA,QACP,IAAA;AAAA,QACA,OAAA,EAAS,CAAC,EAAE;AAAA,OACb,CAAA;AAAA;AAEH,IAAO,OAAA,GAAA;AAAA,GACT;AAAA,EACA;AACF;AAEW,MAAA,uBAAA,GAA0B,CACrC,eACG,KAAA;AACH,EAAM,MAAA,OAAA,GAAU,CAAC,GAAG,eAAe,CAAA;AACnC,EAAA,OAAO,OAAQ,CAAA,IAAA,CAAK,CAAC,GAAA,EAAK,GAAQ,KAAA;AAChC,IAAA,IAAA,CAAK,GAAI,CAAA,QAAA,IAAY,CAAM,KAAA,GAAA,CAAI,YAAY,CAAI,CAAA,EAAA;AAC7C,MAAO,OAAA,CAAA;AAAA;AAET,IAAA,IAAA,CAAK,GAAI,CAAA,QAAA,IAAY,CAAM,KAAA,GAAA,CAAI,YAAY,CAAI,CAAA,EAAA;AAC7C,MAAO,OAAA,CAAA,CAAA;AAAA;AAET,IAAO,OAAA,CAAA;AAAA,GACR,CAAA;AACH;AAEa,MAAA,oBAAA,GAAuB,CAClC,eAAA,EACA,QACG,KAAA;AACH,EAAM,MAAA,eAAA,GAAkB,eAAgB,CAAA,MAAA,CAAO,CAAM,EAAA,KAAA;AACnD,IAAA,IAAI,EAAG,CAAA,cAAA,EAAgB,QAAS,CAAA,QAAQ,CAAG,EAAA;AACzC,MAAO,OAAA,KAAA;AAAA;AAET,IAAA,IAAI,EAAG,CAAA,cAAA,IAAkB,EAAG,CAAA,cAAA,CAAe,SAAS,CAAG,EAAA;AACrD,MAAA,IAAI,EAAG,CAAA,cAAA,EAAgB,QAAS,CAAA,QAAQ,CAAG,EAAA;AACzC,QAAO,OAAA,IAAA;AAAA;AAET,MAAO,OAAA,KAAA;AAAA;AAET,IAAO,OAAA,IAAA;AAAA,GACR,CAAA;AACD,EAAM,MAAA,aAAA,GAAgB,wBAAwB,eAAe,CAAA;AAC7D,EAAO,OAAA,aAAA;AACT;AAEO,MAAM,WAAc,GAAA;AAAA,EACzB,CAAC,IAAK,CAAA,WAAW,GAAG;AAAA,IAClB,gBAAkB,EAAA,OAAA;AAAA,IAClB,SAAW,EAAA,MAAA;AAAA,IACX,MAAA,EAAQ,EAAE,EAAA,EAAI,CAAE;AAAA,GAClB;AAAA,EACA,CAAC,IAAK,CAAA,QAAQ,GAAG;AAAA,IACf,gBAAkB,EAAA,MAAA;AAAA,IAClB,SAAW,EAAA,OAAA;AAAA,IACX,MAAA,EAAQ,EAAE,EAAA,EAAI,CAAE;AAAA;AAEpB;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/backstage-plugin-global-floating-action-button",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "main": "dist/index.esm.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -39,6 +39,7 @@
39
39
  "@mui/icons-material": "^5.15.17",
40
40
  "@mui/material": "^5.15.17",
41
41
  "@mui/styles": "5.16.13",
42
+ "@scalprum/react-core": "0.9.3",
42
43
  "classnames": "^2.5.1",
43
44
  "react-use": "^17.2.4"
44
45
  },
@@ -49,6 +50,7 @@
49
50
  "devDependencies": {
50
51
  "@backstage/cli": "^0.28.0",
51
52
  "@backstage/dev-utils": "^1.1.2",
53
+ "@openshift/dynamic-plugin-sdk": "5.0.1",
52
54
  "@testing-library/jest-dom": "^6.0.0",
53
55
  "@testing-library/react": "^14.0.0",
54
56
  "react": "^16.13.1 || ^17.0.0 || ^18.0.0"