@red-hat-developer-hub/backstage-plugin-global-floating-action-button 0.0.1 → 0.0.3
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 +14 -0
- package/README.md +28 -4
- package/dist/components/FAB.esm.js +66 -11
- package/dist/components/FAB.esm.js.map +1 -1
- package/dist/components/FABWithSubmenu.esm.js +36 -11
- package/dist/components/FABWithSubmenu.esm.js.map +1 -1
- package/dist/components/FabIcon.esm.js.map +1 -1
- package/dist/components/FloatingButton.esm.js +13 -14
- package/dist/components/FloatingButton.esm.js.map +1 -1
- package/dist/components/GlobalFloatingActionButton.esm.js +8 -1
- package/dist/components/GlobalFloatingActionButton.esm.js.map +1 -1
- package/dist/index.d.ts +21 -21
- package/dist/plugin.esm.js.map +1 -1
- package/dist/types.esm.js +1 -1
- package/dist/types.esm.js.map +1 -1
- package/dist/utils.esm.js +16 -4
- package/dist/utils.esm.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-global-floating-action-button
|
|
2
2
|
|
|
3
|
+
## 0.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 01c8d1c: update global floating action button plugin readme
|
|
8
|
+
- 3d2f8a3: update fab content
|
|
9
|
+
|
|
10
|
+
## 0.0.2
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 37af2c4: align page-end floating action buttons to the right page when expanded
|
|
15
|
+
- 09dfb9d: Updated dependency `@mui/styles` to `5.16.13`.
|
|
16
|
+
|
|
3
17
|
## 0.0.1
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -18,16 +18,19 @@ This plugin has been added to the example app in this workspace, meaning it can
|
|
|
18
18
|
yarn workspace app add @red-hat-developer-hub/backstage-plugin-global-floating-action-button
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
2. Add **
|
|
21
|
+
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
22
|
|
|
23
23
|
```tsx title="packages/app/src/components/Root/Root.tsx"
|
|
24
24
|
/* highlight-add-next-line */
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
GlobalFloatingActionButton,
|
|
27
|
+
Slot,
|
|
28
|
+
} from '@red-hat-developer-hub/backstage-plugin-global-floating-action-button';
|
|
26
29
|
|
|
27
30
|
export const Root = ({ children }: PropsWithChildren<{}>) => (
|
|
28
31
|
<SidebarPage>
|
|
29
32
|
... /* highlight-add-start */
|
|
30
|
-
<
|
|
33
|
+
<GlobalFloatingActionButton
|
|
31
34
|
floatingButtons={[
|
|
32
35
|
{
|
|
33
36
|
color: 'success',
|
|
@@ -37,11 +40,11 @@ This plugin has been added to the example app in this workspace, meaning it can
|
|
|
37
40
|
to: '/create',
|
|
38
41
|
},
|
|
39
42
|
{
|
|
43
|
+
slot: Slot.BOTTOM_LEFT,
|
|
40
44
|
icon: <LibraryBooks />,
|
|
41
45
|
label: 'Docs',
|
|
42
46
|
toolTip: 'Docs',
|
|
43
47
|
to: '/docs',
|
|
44
|
-
position: 'bottom-center',
|
|
45
48
|
},
|
|
46
49
|
]}
|
|
47
50
|
/>
|
|
@@ -49,3 +52,24 @@ This plugin has been added to the example app in this workspace, meaning it can
|
|
|
49
52
|
</SidebarPage>
|
|
50
53
|
);
|
|
51
54
|
```
|
|
55
|
+
|
|
56
|
+
#### Floating Action Button Parameters
|
|
57
|
+
|
|
58
|
+
| Name | Type | Description | Notes |
|
|
59
|
+
| ------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
|
60
|
+
| **slot** | `enum` | The position where the fab will be placed. Valid values: `PAGE_END`, `BOTTOM_LEFT`. | [optional] default to `PAGE_END`. |
|
|
61
|
+
| **label** | `String` | A name for your action button. | required |
|
|
62
|
+
| **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 |
|
|
63
|
+
| **showLabel** | `Boolean` | To display the label next to your icon. | optional |
|
|
64
|
+
| **size** | `'small'`<br>`'medium'`<br>`'large'` | A name for your action button. | [optional] default to `'medium'` |
|
|
65
|
+
| **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'`. |
|
|
66
|
+
| **onClick** | `React.MouseEventHandler` | the action to be performed on `onClick`. | optional |
|
|
67
|
+
| **to** | `String` | Specify an href if the action button should open a internal/external link. | optional |
|
|
68
|
+
| **toolTip** | `String` | The text to appear on hover. | optional |
|
|
69
|
+
| **priority** | `number` | When multiple sub-menu actions are displayed, the button can be prioritized to position either at the top or the bottom. | optional |
|
|
70
|
+
| **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. |
|
|
71
|
+
| **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. |
|
|
72
|
+
|
|
73
|
+
**NOTE**
|
|
74
|
+
|
|
75
|
+
If multiple floating button actions are assigned to the same `Slot`, they will appear as sub-menu options within the floating button.
|
|
@@ -1,39 +1,94 @@
|
|
|
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
|
|
16
|
-
const newWindow =
|
|
17
|
-
const navigateTo = () => 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
|
+
const labelText = actionButton.label.length > 20 ? `${actionButton.label.slice(0, actionButton.label.length)}...` : actionButton.label;
|
|
53
|
+
const getColor = () => {
|
|
54
|
+
if (actionButton.color) {
|
|
55
|
+
return actionButton.color;
|
|
56
|
+
}
|
|
57
|
+
if (!className) {
|
|
58
|
+
return "info";
|
|
59
|
+
}
|
|
60
|
+
return void 0;
|
|
61
|
+
};
|
|
62
|
+
const displayOnRight = actionButton.slot === Slot.PAGE_END || !actionButton.slot;
|
|
18
63
|
return /* @__PURE__ */ React.createElement(
|
|
19
64
|
Tooltip,
|
|
20
65
|
{
|
|
21
66
|
title: actionButton.toolTip,
|
|
22
|
-
placement: Slot.PAGE_END
|
|
67
|
+
placement: slotOptions[actionButton.slot || Slot.PAGE_END].tooltipDirection
|
|
23
68
|
},
|
|
24
|
-
/* @__PURE__ */ React.createElement("div",
|
|
69
|
+
/* @__PURE__ */ React.createElement("div", { className }, /* @__PURE__ */ React.createElement(
|
|
25
70
|
Fab,
|
|
26
71
|
{
|
|
27
72
|
...newWindow ? { target: "_blank", rel: "noopener" } : {},
|
|
28
|
-
|
|
73
|
+
style: { color: "#1f1f1f" },
|
|
74
|
+
variant: actionButton.showLabel || isExternal ? "extended" : "circular",
|
|
29
75
|
size: size || actionButton.size || "medium",
|
|
30
|
-
color:
|
|
76
|
+
color: getColor(),
|
|
31
77
|
"aria-label": actionButton.label,
|
|
78
|
+
"data-testid": actionButton.label.replace(" ", "-").toLocaleLowerCase("en-US"),
|
|
32
79
|
onClick: actionButton.onClick || navigateTo,
|
|
33
|
-
...
|
|
80
|
+
...isExternal ? { href: actionButton.to } : {}
|
|
34
81
|
},
|
|
35
|
-
|
|
36
|
-
|
|
82
|
+
/* @__PURE__ */ React.createElement(
|
|
83
|
+
FABLabel,
|
|
84
|
+
{
|
|
85
|
+
showExternalIcon: isExternal,
|
|
86
|
+
icon: actionButton.icon,
|
|
87
|
+
label: actionButton.showLabel ? labelText : "",
|
|
88
|
+
order: displayOnRight ? { externalIcon: isExternal ? 1 : -1, icon: 3 } : { externalIcon: isExternal ? 3 : -1, icon: 1 },
|
|
89
|
+
slot: actionButton.slot || Slot.PAGE_END
|
|
90
|
+
}
|
|
91
|
+
)
|
|
37
92
|
))
|
|
38
93
|
);
|
|
39
94
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FAB.esm.js","sources":["../../src/components/FAB.tsx"],"sourcesContent":["/*\n * Copyright
|
|
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 const labelText =\n actionButton.label.length > 20\n ? `${actionButton.label.slice(0, actionButton.label.length)}...`\n : actionButton.label;\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;AAC/D,EAAA,MAAM,SACJ,GAAA,YAAA,CAAa,KAAM,CAAA,MAAA,GAAS,KACxB,CAAG,EAAA,YAAA,CAAa,KAAM,CAAA,KAAA,CAAM,GAAG,YAAa,CAAA,KAAA,CAAM,MAAM,CAAC,QACzD,YAAa,CAAA,KAAA;AACnB,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,aAAa,KACvB,CAAA,OAAA,CAAQ,KAAK,GAAG,CAAA,CAChB,kBAAkB,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
|
|
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.palette.grey[500]
|
|
17
|
+
},
|
|
18
|
+
menuButtonStyle: {
|
|
19
|
+
color: "#1f1f1f"
|
|
20
|
+
}
|
|
21
|
+
}));
|
|
11
22
|
const FABWithSubmenu = ({
|
|
12
23
|
fabs,
|
|
13
|
-
|
|
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(!
|
|
36
|
+
setIsMenuOpen((prev) => !prev);
|
|
25
37
|
};
|
|
26
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tooltip, { title: "Menu", placement:
|
|
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: "
|
|
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,
|
|
36
|
-
)),
|
|
37
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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: theme.palette.grey[500],\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,KAAO,EAAA,KAAA,CAAM,OAAQ,CAAA,IAAA,CAAK,GAAG;AAAA,GAC/B;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
|
|
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,28 +7,28 @@ import { FAB } from './FAB.esm.js';
|
|
|
7
7
|
import { filterAndSortButtons } from '../utils.esm.js';
|
|
8
8
|
|
|
9
9
|
const useStyles = makeStyles((theme) => ({
|
|
10
|
-
|
|
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(
|
|
19
|
-
right: theme.spacing(
|
|
17
|
+
bottom: theme.spacing(2),
|
|
18
|
+
right: theme.spacing(2),
|
|
19
|
+
alignItems: "end"
|
|
20
20
|
},
|
|
21
|
-
"bottom-
|
|
22
|
-
bottom: theme.spacing(
|
|
23
|
-
|
|
21
|
+
"bottom-left": {
|
|
22
|
+
bottom: theme.spacing(2),
|
|
23
|
+
paddingLeft: theme.spacing(2),
|
|
24
|
+
alignItems: "start"
|
|
24
25
|
}
|
|
25
26
|
}));
|
|
26
27
|
const FloatingButton = ({
|
|
27
28
|
floatingButtons,
|
|
28
|
-
|
|
29
|
+
slot
|
|
29
30
|
}) => {
|
|
30
31
|
const { pathname } = useLocation();
|
|
31
|
-
const subMenuRef = React.useRef(null);
|
|
32
32
|
const [subMenuDirection, setSubMenuDirection] = React.useState("column");
|
|
33
33
|
const fabButton = useStyles();
|
|
34
34
|
React.useEffect(() => {
|
|
@@ -53,15 +53,14 @@ const FloatingButton = ({
|
|
|
53
53
|
return /* @__PURE__ */ React.createElement(
|
|
54
54
|
"div",
|
|
55
55
|
{
|
|
56
|
-
className: classnames(fabButton.
|
|
56
|
+
className: classnames(fabButton.fabContainer, fabButton[slot]),
|
|
57
57
|
style: {
|
|
58
|
-
flexDirection: subMenuDirection
|
|
58
|
+
flexDirection: subMenuDirection
|
|
59
59
|
},
|
|
60
60
|
id: "floating-button",
|
|
61
|
-
"data-testId": "floating-button"
|
|
62
|
-
ref: subMenuRef
|
|
61
|
+
"data-testId": "floating-button"
|
|
63
62
|
},
|
|
64
|
-
fabs.length > 1 ? /* @__PURE__ */ React.createElement(FABWithSubmenu, { fabs,
|
|
63
|
+
fabs.length > 1 ? /* @__PURE__ */ React.createElement(FABWithSubmenu, { fabs, slot }) : /* @__PURE__ */ React.createElement(FAB, { actionButton: fabs[0] })
|
|
65
64
|
);
|
|
66
65
|
};
|
|
67
66
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FloatingButton.esm.js","sources":["../../src/components/FloatingButton.tsx"],"sourcesContent":["/*\n * Copyright
|
|
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.spacing(2),\n right: theme.spacing(2),\n alignItems: 'end',\n },\n 'bottom-left': {\n bottom: theme.spacing(2),\n paddingLeft: theme.spacing(2),\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":";;;;;;;;AA0BA,MAAM,SAAA,GAAY,WAAW,CAAU,KAAA,MAAA;AAAA,EACrC,YAAc,EAAA;AAAA,IACZ,MAAQ,EAAA,GAAA;AAAA,IACR,OAAS,EAAA,MAAA;AAAA,IACT,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,aAAe,EAAA;AAAA,IACb,MAAA,EAAQ,KAAM,CAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,IACvB,WAAA,EAAa,KAAM,CAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,IAC5B,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,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,YAAc,EAAA,SAAA,CAAU,IAAI,CAAC,CAAA;AAAA,MAC7D,KAAO,EAAA;AAAA,QACL,aAAe,EAAA;AAAA,OACjB;AAAA,MACA,EAAG,EAAA,iBAAA;AAAA,MACH,aAAY,EAAA;AAAA,KAAA;AAAA,IAEX,IAAK,CAAA,MAAA,GAAS,CACb,mBAAA,KAAA,CAAA,aAAA,CAAC,cAAe,EAAA,EAAA,IAAA,EAAY,IAAY,EAAA,CAAA,mBAEvC,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA,EAAI,YAAc,EAAA,IAAA,CAAK,CAAC,CAAG,EAAA;AAAA,GAEhC;AAEJ;;;;"}
|
|
@@ -6,7 +6,14 @@ const GlobalFloatingActionButton = ({
|
|
|
6
6
|
floatingButtons
|
|
7
7
|
}) => {
|
|
8
8
|
const floatingButtonMap = evaluateFloatingButtonsWithPositions(floatingButtons);
|
|
9
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, floatingButtonMap.map((fb) => /* @__PURE__ */ React.createElement(
|
|
9
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, floatingButtonMap.map((fb) => /* @__PURE__ */ React.createElement(
|
|
10
|
+
FloatingButton,
|
|
11
|
+
{
|
|
12
|
+
key: fb.slot,
|
|
13
|
+
slot: fb.slot,
|
|
14
|
+
floatingButtons: fb.actions
|
|
15
|
+
}
|
|
16
|
+
)));
|
|
10
17
|
};
|
|
11
18
|
|
|
12
19
|
export { GlobalFloatingActionButton };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GlobalFloatingActionButton.esm.js","sources":["../../src/components/GlobalFloatingActionButton.tsx"],"sourcesContent":["/*\n * Copyright
|
|
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;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,9 @@ import * as react from 'react';
|
|
|
3
3
|
import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @public
|
|
7
6
|
* Slot
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
8
9
|
*/
|
|
9
10
|
declare enum Slot {
|
|
10
11
|
/**
|
|
@@ -14,31 +15,19 @@ declare enum Slot {
|
|
|
14
15
|
/**
|
|
15
16
|
* Positions the floating action button at the bottom center of the page
|
|
16
17
|
*/
|
|
17
|
-
|
|
18
|
+
BOTTOM_LEFT = "bottom-left"
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
|
-
* @public
|
|
21
|
-
* Floating Action Button With Positions
|
|
22
|
-
*/
|
|
23
|
-
type FloatingActionButtonWithPositions = Array<{
|
|
24
|
-
slot: Slot;
|
|
25
|
-
actions: FloatingActionButton[];
|
|
26
|
-
}>;
|
|
27
|
-
/**
|
|
28
|
-
* @public
|
|
29
|
-
* Flex Direction
|
|
30
|
-
*/
|
|
31
|
-
type FlexDirection = 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
|
32
|
-
/**
|
|
33
|
-
* @public
|
|
34
21
|
* Floating Action Button
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
35
24
|
*/
|
|
36
25
|
type FloatingActionButton = {
|
|
26
|
+
slot?: Slot;
|
|
37
27
|
label: string;
|
|
38
28
|
showLabel?: boolean;
|
|
39
|
-
icon
|
|
29
|
+
icon: string | React.ReactElement;
|
|
40
30
|
size?: 'small' | 'medium' | 'large';
|
|
41
|
-
position?: Slot | string;
|
|
42
31
|
color?: 'default' | 'error' | 'info' | 'inherit' | 'primary' | 'secondary' | 'success' | 'warning';
|
|
43
32
|
onClick?: React.MouseEventHandler;
|
|
44
33
|
to?: string;
|
|
@@ -47,18 +36,29 @@ type FloatingActionButton = {
|
|
|
47
36
|
visibleOnPaths?: string[];
|
|
48
37
|
excludeOnPaths?: string[];
|
|
49
38
|
};
|
|
50
|
-
|
|
51
39
|
/**
|
|
40
|
+
* Floating Action Button With Positions
|
|
41
|
+
*
|
|
52
42
|
* @public
|
|
43
|
+
*/
|
|
44
|
+
type FloatingActionButtonWithPositions = Array<{
|
|
45
|
+
slot: Slot;
|
|
46
|
+
actions: FloatingActionButton[];
|
|
47
|
+
}>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
53
50
|
* Global Floating Action Button Plugin
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
54
53
|
*/
|
|
55
54
|
declare const globalFloatingActionButtonPlugin: _backstage_core_plugin_api.BackstagePlugin<{}, {}, {}>;
|
|
56
55
|
/**
|
|
57
|
-
* @public
|
|
58
56
|
* Global Floating Action Button
|
|
57
|
+
*
|
|
58
|
+
* @public
|
|
59
59
|
*/
|
|
60
60
|
declare const GlobalFloatingActionButton: ({ floatingButtons, }: {
|
|
61
61
|
floatingButtons: FloatingActionButton[];
|
|
62
62
|
}) => react.JSX.Element;
|
|
63
63
|
|
|
64
|
-
export { type
|
|
64
|
+
export { type FloatingActionButton, type FloatingActionButtonWithPositions, GlobalFloatingActionButton, Slot, globalFloatingActionButtonPlugin };
|
package/dist/plugin.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright
|
|
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 * 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":";;AA0BO,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;;;;"}
|
package/dist/types.esm.js
CHANGED
package/dist/types.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.esm.js","sources":["../src/types.ts"],"sourcesContent":["/*\n * Copyright
|
|
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"],"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
|
@@ -2,13 +2,13 @@ import { Slot } from './types.esm.js';
|
|
|
2
2
|
|
|
3
3
|
const evaluateFloatingButtonsWithPositions = (floatingButtons) => floatingButtons.reduce(
|
|
4
4
|
(acc, fb) => {
|
|
5
|
-
const
|
|
6
|
-
const slotWithActions = acc.find((a) => a.slot ===
|
|
5
|
+
const slot = fb.slot ?? Slot.PAGE_END;
|
|
6
|
+
const slotWithActions = acc.find((a) => a.slot === slot);
|
|
7
7
|
if (slotWithActions) {
|
|
8
8
|
slotWithActions.actions.push(fb);
|
|
9
9
|
} else {
|
|
10
10
|
acc.push({
|
|
11
|
-
slot
|
|
11
|
+
slot,
|
|
12
12
|
actions: [fb]
|
|
13
13
|
});
|
|
14
14
|
}
|
|
@@ -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
|
package/dist/utils.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.esm.js","sources":["../src/utils.ts"],"sourcesContent":["/*\n * Copyright
|
|
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.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"main": "dist/index.esm.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@backstage/theme": "^0.6.0",
|
|
39
39
|
"@mui/icons-material": "^5.15.17",
|
|
40
40
|
"@mui/material": "^5.15.17",
|
|
41
|
-
"@mui/styles": "5.16.
|
|
41
|
+
"@mui/styles": "5.16.13",
|
|
42
42
|
"classnames": "^2.5.1",
|
|
43
43
|
"react-use": "^17.2.4"
|
|
44
44
|
},
|