@backstage/plugin-app 0.4.3-next.2 → 0.4.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 +43 -0
- package/dist/apis/DefaultDialogApi.esm.js +69 -15
- package/dist/apis/DefaultDialogApi.esm.js.map +1 -1
- package/dist/components/Toast/Toast.esm.js +1 -2
- package/dist/components/Toast/Toast.esm.js.map +1 -1
- package/dist/components/Toast/ToastContainer.esm.js +2 -2
- package/dist/components/Toast/ToastContainer.esm.js.map +1 -1
- package/dist/components/Toast/ToastDisplay.esm.js +1 -1
- package/dist/components/Toast/ToastDisplay.esm.js.map +1 -1
- package/dist/extensions/AppLanguageApi.esm.js +4 -5
- package/dist/extensions/AppLanguageApi.esm.js.map +1 -1
- package/dist/extensions/AppRoutes.esm.js +8 -9
- package/dist/extensions/AppRoutes.esm.js.map +1 -1
- package/dist/extensions/DialogDisplay.esm.js +1 -15
- package/dist/extensions/DialogDisplay.esm.js.map +1 -1
- package/dist/extensions/elements.esm.js +10 -8
- package/dist/extensions/elements.esm.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/plugins/app/package.json.esm.js +3 -4
- package/dist/plugins/app/package.json.esm.js.map +1 -1
- package/package.json +19 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @backstage/plugin-app
|
|
2
2
|
|
|
3
|
+
## 0.4.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 23fb582: Migrated React Aria imports from individual packages (`@react-aria/toast`, `@react-aria/button`, `@react-stately/toast`) to the monopackages (`react-aria`, `react-stately`).
|
|
8
|
+
- 6b60bd7: Replaced old config schema values from existing extensions and blueprints.
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- @backstage/ui@0.14.1
|
|
11
|
+
- @backstage/frontend-plugin-api@0.16.1
|
|
12
|
+
|
|
13
|
+
## 0.4.3
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- effa7bf: Migrated `AppLanguageApi` extension to use the new `configSchema` option.
|
|
18
|
+
- e5baa20: Added support for configuring URL redirects on the `app/routes` extension. Redirects can be configured through `app-config` as an array of `{from, to}` path pairs, which will cause navigation to the `from` path to be redirected to the `to` path.
|
|
19
|
+
|
|
20
|
+
For example:
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
app:
|
|
24
|
+
extensions:
|
|
25
|
+
- app/routes:
|
|
26
|
+
config:
|
|
27
|
+
redirects:
|
|
28
|
+
- from: /old-path
|
|
29
|
+
to: /new-path
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- 9244b70: The default auth implementation now checks for a `logoutUrl` in the logout response body. If the auth provider returns one (e.g. Auth0 federated logout), the browser is redirected to that URL to clear the provider's session cookies. This is backward compatible — providers that return an empty response are unaffected.
|
|
33
|
+
- e4804ab: Updated the default `DialogApi` implementation to support the new `open` method. The dialog display layer no longer renders any dialog chrome — callers provide their own dialog component. The deprecated `show` and `showModal` methods now use `open` internally with a Material UI dialog wrapper for backward compatibility.
|
|
34
|
+
- d66a3ec: Updated the `PageLayout` swap to pass a clickable `titleLink` on the `PluginHeader`, resolved from the plugin's root route ref.
|
|
35
|
+
- Updated dependencies
|
|
36
|
+
- @backstage/ui@0.14.0
|
|
37
|
+
- @backstage/theme@0.7.3
|
|
38
|
+
- @backstage/frontend-plugin-api@0.16.0
|
|
39
|
+
- @backstage/core-components@0.18.9
|
|
40
|
+
- @backstage/filter-predicates@0.1.2
|
|
41
|
+
- @backstage/plugin-permission-react@0.5.0
|
|
42
|
+
- @backstage/core-plugin-api@1.12.5
|
|
43
|
+
- @backstage/integration-react@1.2.17
|
|
44
|
+
- @backstage/plugin-app-react@0.2.2
|
|
45
|
+
|
|
3
46
|
## 0.4.3-next.2
|
|
4
47
|
|
|
5
48
|
### Patch Changes
|
|
@@ -1,26 +1,80 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import Dialog from '@material-ui/core/Dialog';
|
|
3
|
+
|
|
1
4
|
class DefaultDialogApi {
|
|
2
|
-
#
|
|
3
|
-
|
|
4
|
-
if (!this.#
|
|
5
|
+
#onOpen;
|
|
6
|
+
open(elementOrComponent) {
|
|
7
|
+
if (!this.#onOpen) {
|
|
5
8
|
throw new Error("Dialog API has not been connected");
|
|
6
9
|
}
|
|
7
|
-
return this.#
|
|
8
|
-
component: typeof elementOrComponent === "function" ? elementOrComponent : () => elementOrComponent
|
|
9
|
-
modal: false
|
|
10
|
+
return this.#onOpen({
|
|
11
|
+
component: typeof elementOrComponent === "function" ? elementOrComponent : () => elementOrComponent
|
|
10
12
|
});
|
|
11
13
|
}
|
|
14
|
+
/** @deprecated Use {@link DefaultDialogApi.open} instead */
|
|
15
|
+
show(elementOrComponent) {
|
|
16
|
+
console.warn(
|
|
17
|
+
"DialogApi.show() is deprecated and will be removed in a future release. Use DialogApi.open() instead."
|
|
18
|
+
);
|
|
19
|
+
const innerDialog = this.open(({ dialog }) => /* @__PURE__ */ jsx(
|
|
20
|
+
DeprecatedMuiDialogWrapper,
|
|
21
|
+
{
|
|
22
|
+
dialog,
|
|
23
|
+
content: elementOrComponent,
|
|
24
|
+
modal: false
|
|
25
|
+
}
|
|
26
|
+
));
|
|
27
|
+
return wrapDialogHandle(innerDialog, false);
|
|
28
|
+
}
|
|
29
|
+
/** @deprecated Use {@link DefaultDialogApi.open} instead */
|
|
12
30
|
showModal(elementOrComponent) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
console.warn(
|
|
32
|
+
"DialogApi.showModal() is deprecated and will be removed in a future release. Use DialogApi.open() instead."
|
|
33
|
+
);
|
|
34
|
+
const innerDialog = this.open(({ dialog }) => /* @__PURE__ */ jsx(
|
|
35
|
+
DeprecatedMuiDialogWrapper,
|
|
36
|
+
{
|
|
37
|
+
dialog,
|
|
38
|
+
content: elementOrComponent,
|
|
39
|
+
modal: true
|
|
40
|
+
}
|
|
41
|
+
));
|
|
42
|
+
return wrapDialogHandle(innerDialog, true);
|
|
43
|
+
}
|
|
44
|
+
connect(onOpen) {
|
|
45
|
+
this.#onOpen = onOpen;
|
|
20
46
|
}
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
}
|
|
48
|
+
function DeprecatedMuiDialogWrapper({
|
|
49
|
+
dialog,
|
|
50
|
+
content,
|
|
51
|
+
modal
|
|
52
|
+
}) {
|
|
53
|
+
if (typeof content === "function") {
|
|
54
|
+
const Content = content;
|
|
55
|
+
return /* @__PURE__ */ jsx(Dialog, { open: true, onClose: modal ? void 0 : () => dialog.close(), children: /* @__PURE__ */ jsx(Content, { dialog }) });
|
|
23
56
|
}
|
|
57
|
+
return /* @__PURE__ */ jsx(Dialog, { open: true, onClose: modal ? void 0 : () => dialog.close(), children: content });
|
|
58
|
+
}
|
|
59
|
+
function wrapDialogHandle(innerDialog, modal) {
|
|
60
|
+
return {
|
|
61
|
+
close(...args) {
|
|
62
|
+
innerDialog.close(...args);
|
|
63
|
+
},
|
|
64
|
+
result() {
|
|
65
|
+
return innerDialog.result();
|
|
66
|
+
},
|
|
67
|
+
update(newContent) {
|
|
68
|
+
innerDialog.update(({ dialog }) => /* @__PURE__ */ jsx(
|
|
69
|
+
DeprecatedMuiDialogWrapper,
|
|
70
|
+
{
|
|
71
|
+
dialog,
|
|
72
|
+
content: newContent,
|
|
73
|
+
modal
|
|
74
|
+
}
|
|
75
|
+
));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
24
78
|
}
|
|
25
79
|
|
|
26
80
|
export { DefaultDialogApi };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultDialogApi.esm.js","sources":["../../src/apis/DefaultDialogApi.
|
|
1
|
+
{"version":3,"file":"DefaultDialogApi.esm.js","sources":["../../src/apis/DefaultDialogApi.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 { DialogApi, DialogApiDialog } from '@backstage/frontend-plugin-api';\nimport Dialog from '@material-ui/core/Dialog';\n\nexport type OnOpenDialog = (options: {\n component: (props: { dialog: DialogApiDialog<any> }) => JSX.Element;\n}) => DialogApiDialog<unknown>;\n\n/**\n * Default implementation for the {@link DialogApi}.\n * @internal\n */\nexport class DefaultDialogApi implements DialogApi {\n #onOpen?: OnOpenDialog;\n\n open<TResult = void>(\n elementOrComponent:\n | JSX.Element\n | ((props: { dialog: DialogApiDialog<TResult> }) => JSX.Element),\n ): DialogApiDialog<TResult> {\n if (!this.#onOpen) {\n throw new Error('Dialog API has not been connected');\n }\n return this.#onOpen({\n component:\n typeof elementOrComponent === 'function'\n ? elementOrComponent\n : () => elementOrComponent,\n }) as DialogApiDialog<TResult>;\n }\n\n /** @deprecated Use {@link DefaultDialogApi.open} instead */\n show<TResult = void>(\n elementOrComponent:\n | JSX.Element\n | ((props: {\n dialog: DialogApiDialog<TResult | undefined>;\n }) => JSX.Element),\n ): DialogApiDialog<TResult | undefined> {\n // eslint-disable-next-line no-console\n console.warn(\n 'DialogApi.show() is deprecated and will be removed in a future release. Use DialogApi.open() instead.',\n );\n const innerDialog = this.open<TResult | undefined>(({ dialog }) => (\n <DeprecatedMuiDialogWrapper\n dialog={dialog}\n content={elementOrComponent}\n modal={false}\n />\n ));\n return wrapDialogHandle(innerDialog, false);\n }\n\n /** @deprecated Use {@link DefaultDialogApi.open} instead */\n showModal<TResult = void>(\n elementOrComponent:\n | JSX.Element\n | ((props: { dialog: DialogApiDialog<TResult> }) => JSX.Element),\n ): DialogApiDialog<TResult> {\n // eslint-disable-next-line no-console\n console.warn(\n 'DialogApi.showModal() is deprecated and will be removed in a future release. Use DialogApi.open() instead.',\n );\n const innerDialog = this.open<TResult>(({ dialog }) => (\n <DeprecatedMuiDialogWrapper\n dialog={dialog}\n content={elementOrComponent}\n modal\n />\n ));\n return wrapDialogHandle(innerDialog, true);\n }\n\n connect(onOpen: OnOpenDialog): void {\n this.#onOpen = onOpen;\n }\n}\n\nfunction DeprecatedMuiDialogWrapper({\n dialog,\n content,\n modal,\n}: {\n dialog: DialogApiDialog<any>;\n content:\n | JSX.Element\n | ((props: { dialog: DialogApiDialog<any> }) => JSX.Element);\n modal: boolean;\n}) {\n if (typeof content === 'function') {\n const Content = content;\n return (\n <Dialog open onClose={modal ? undefined : () => dialog.close()}>\n <Content dialog={dialog} />\n </Dialog>\n );\n }\n return (\n <Dialog open onClose={modal ? undefined : () => dialog.close()}>\n {content}\n </Dialog>\n );\n}\n\nfunction wrapDialogHandle<TResult>(\n innerDialog: DialogApiDialog<TResult>,\n modal: boolean,\n): DialogApiDialog<TResult> {\n return {\n close(...args: any[]) {\n (innerDialog.close as any)(...args);\n },\n result() {\n return innerDialog.result();\n },\n update(newContent: any) {\n innerDialog.update(({ dialog }: { dialog: DialogApiDialog<TResult> }) => (\n <DeprecatedMuiDialogWrapper\n dialog={dialog}\n content={newContent}\n modal={modal}\n />\n ));\n },\n };\n}\n"],"names":[],"mappings":";;;AA2BO,MAAM,gBAAA,CAAsC;AAAA,EACjD,OAAA;AAAA,EAEA,KACE,kBAAA,EAG0B;AAC1B,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,MAAM,IAAI,MAAM,mCAAmC,CAAA;AAAA,IACrD;AACA,IAAA,OAAO,KAAK,OAAA,CAAQ;AAAA,MAClB,SAAA,EACE,OAAO,kBAAA,KAAuB,UAAA,GAC1B,qBACA,MAAM;AAAA,KACb,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,KACE,kBAAA,EAKsC;AAEtC,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN;AAAA,KACF;AACA,IAAA,MAAM,cAAc,IAAA,CAAK,IAAA,CAA0B,CAAC,EAAE,QAAO,qBAC3D,GAAA;AAAA,MAAC,0BAAA;AAAA,MAAA;AAAA,QACC,MAAA;AAAA,QACA,OAAA,EAAS,kBAAA;AAAA,QACT,KAAA,EAAO;AAAA;AAAA,KAEV,CAAA;AACD,IAAA,OAAO,gBAAA,CAAiB,aAAa,KAAK,CAAA;AAAA,EAC5C;AAAA;AAAA,EAGA,UACE,kBAAA,EAG0B;AAE1B,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN;AAAA,KACF;AACA,IAAA,MAAM,cAAc,IAAA,CAAK,IAAA,CAAc,CAAC,EAAE,QAAO,qBAC/C,GAAA;AAAA,MAAC,0BAAA;AAAA,MAAA;AAAA,QACC,MAAA;AAAA,QACA,OAAA,EAAS,kBAAA;AAAA,QACT,KAAA,EAAK;AAAA;AAAA,KAER,CAAA;AACD,IAAA,OAAO,gBAAA,CAAiB,aAAa,IAAI,CAAA;AAAA,EAC3C;AAAA,EAEA,QAAQ,MAAA,EAA4B;AAClC,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,EACjB;AACF;AAEA,SAAS,0BAAA,CAA2B;AAAA,EAClC,MAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAMG;AACD,EAAA,IAAI,OAAO,YAAY,UAAA,EAAY;AACjC,IAAA,MAAM,OAAA,GAAU,OAAA;AAChB,IAAA,uBACE,GAAA,CAAC,MAAA,EAAA,EAAO,IAAA,EAAI,IAAA,EAAC,SAAS,KAAA,GAAQ,MAAA,GAAY,MAAM,MAAA,CAAO,KAAA,EAAM,EAC3D,QAAA,kBAAA,GAAA,CAAC,OAAA,EAAA,EAAQ,QAAgB,CAAA,EAC3B,CAAA;AAAA,EAEJ;AACA,EAAA,uBACE,GAAA,CAAC,MAAA,EAAA,EAAO,IAAA,EAAI,IAAA,EAAC,OAAA,EAAS,KAAA,GAAQ,MAAA,GAAY,MAAM,MAAA,CAAO,KAAA,EAAM,EAC1D,QAAA,EAAA,OAAA,EACH,CAAA;AAEJ;AAEA,SAAS,gBAAA,CACP,aACA,KAAA,EAC0B;AAC1B,EAAA,OAAO;AAAA,IACL,SAAS,IAAA,EAAa;AACpB,MAAC,WAAA,CAAY,KAAA,CAAc,GAAG,IAAI,CAAA;AAAA,IACpC,CAAA;AAAA,IACA,MAAA,GAAS;AACP,MAAA,OAAO,YAAY,MAAA,EAAO;AAAA,IAC5B,CAAA;AAAA,IACA,OAAO,UAAA,EAAiB;AACtB,MAAA,WAAA,CAAY,MAAA,CAAO,CAAC,EAAE,MAAA,EAAO,qBAC3B,GAAA;AAAA,QAAC,0BAAA;AAAA,QAAA;AAAA,UACC,MAAA;AAAA,UACA,OAAA,EAAS,UAAA;AAAA,UACT;AAAA;AAAA,OAEH,CAAA;AAAA,IACH;AAAA,GACF;AACF;;;;"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { forwardRef, useRef, useState, useLayoutEffect } from 'react';
|
|
3
|
-
import { useToast } from '
|
|
4
|
-
import { useButton } from '@react-aria/button';
|
|
3
|
+
import { useToast, useButton } from 'react-aria';
|
|
5
4
|
import { motion } from 'motion/react';
|
|
6
5
|
import { Box } from '@backstage/ui';
|
|
7
6
|
import { RiCloseLine, RiInformationLine, RiAlertLine, RiErrorWarningLine, RiCheckLine } from '@remixicon/react';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.esm.js","sources":["../../../src/components/Toast/Toast.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 { forwardRef, Ref, useRef, useLayoutEffect, useState } from 'react';\nimport { useToast } from '@react-aria/toast';\nimport { useButton } from '@react-aria/button';\nimport { motion } from 'motion/react';\nimport { Box } from '@backstage/ui';\nimport {\n RiInformationLine,\n RiCheckLine,\n RiErrorWarningLine,\n RiAlertLine,\n RiCloseLine,\n} from '@remixicon/react';\nimport type { ToastApiMessageProps } from './types';\nimport styles from './Toast.module.css';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { BgReset } from '../../../../../packages/ui/src/hooks/useBg';\n\n// Track which toasts are being manually closed (vs auto-timeout)\n// This allows different exit animations for each case\nconst manuallyClosingToasts = new Set<string>();\n\n/**\n * A Toast displays a brief, temporary notification of actions, errors, or other events in an application.\n *\n * @remarks\n * The Toast component is used internally by ToastContainer and managed by a ToastQueue.\n * It supports multiple status variants (neutral, info, success, warning, danger) and can display\n * a title and description. Toasts can be dismissed manually or automatically.\n *\n * @internal\n */\nexport const Toast = forwardRef(\n (props: ToastApiMessageProps, ref: Ref<HTMLDivElement>) => {\n const {\n toast,\n state,\n index = 0,\n isExpanded = false,\n onClose,\n status,\n expandedY: expandedYProp = 0,\n collapsedHeight,\n naturalHeight,\n onHeightChange,\n } = props;\n\n // Use internal ref if none provided\n const internalRef = useRef<HTMLDivElement>(null);\n const toastRef = (ref as React.RefObject<HTMLDivElement>) || internalRef;\n\n // Get ARIA props from useToast hook\n const { toastProps, titleProps, closeButtonProps } = useToast(\n { toast },\n state,\n toastRef,\n );\n\n // Close button ref for useButton hook\n const closeButtonRef = useRef<HTMLButtonElement>(null);\n\n // Extract only ARIA and accessibility props from toastProps to avoid\n // conflicts with motion.div's event handler types (motion has its own drag API)\n const ariaProps = {\n role: toastProps.role,\n tabIndex: toastProps.tabIndex,\n 'aria-label': toastProps['aria-label'],\n 'aria-labelledby': toastProps['aria-labelledby'],\n 'aria-describedby': toastProps['aria-describedby'],\n 'aria-posinset': toastProps['aria-posinset'],\n 'aria-setsize': toastProps['aria-setsize'],\n };\n\n // Track whether we've measured this toast's natural height\n const [hasMeasured, setHasMeasured] = useState(false);\n // Store the measured natural height locally to avoid re-measurement issues\n const naturalHeightRef = useRef<number | null>(null);\n\n // Measure this toast's natural height on mount (before paint)\n // Using useLayoutEffect ensures we measure before the browser paints\n useLayoutEffect(() => {\n if (!onHeightChange) return;\n if (naturalHeightRef.current) return; // Already measured\n\n const element = toastRef.current;\n if (!element) return;\n\n // Measure immediately - useLayoutEffect runs before paint\n const height = element.getBoundingClientRect().height;\n if (height > 0) {\n naturalHeightRef.current = height;\n onHeightChange(toast.key, height);\n setHasMeasured(true);\n }\n }, [toast.key, onHeightChange, toastRef]);\n\n // Close button handler\n const handleClose = () => {\n // Mark this toast as manually closed for exit animation\n manuallyClosingToasts.add(toast.key);\n onClose?.();\n state.close(toast.key);\n };\n\n // Get button props from useButton hook\n const { buttonProps } = useButton(\n {\n 'aria-label': closeButtonProps['aria-label'],\n onPress: handleClose,\n },\n closeButtonRef,\n );\n\n // Get content from toast\n const content = toast.content;\n const finalStatus = status || content.status || 'info';\n\n // Determine which icon to render based on status\n const getStatusIcon = () => {\n switch (finalStatus) {\n case 'neutral':\n // Neutral status has no icon\n return null;\n case 'success':\n return <RiCheckLine aria-hidden=\"true\" />;\n case 'warning':\n return <RiErrorWarningLine aria-hidden=\"true\" />;\n case 'danger':\n return <RiAlertLine aria-hidden=\"true\" />;\n case 'info':\n default:\n return <RiInformationLine aria-hidden=\"true\" />;\n }\n };\n\n const statusIcon = getStatusIcon();\n\n // Calculate stacking values based on index\n // Collapsed: each toast behind scales down 5% and peeks up 12px\n const collapsedScale = Math.max(0.85, 1 - index * 0.05);\n const collapsedY = -index * 12;\n\n // Use expanded or collapsed values based on hover state\n // expandedYProp is pre-calculated based on actual toast heights\n const animateY = isExpanded ? expandedYProp : collapsedY;\n const animateScale = isExpanded ? 1 : collapsedScale;\n const stackZIndex = 1000 - index;\n\n // Check if this toast is being manually closed\n const isManualClose = manuallyClosingToasts.has(toast.key);\n\n // Different exit animations for manual close vs auto-timeout\n // Manual close: slide down from front, stay on top\n // Auto-timeout: fade out in place, stay in stack position\n const exitAnimation = isManualClose\n ? { opacity: 0, y: 100, scale: 1, zIndex: 2000 }\n : {\n opacity: 0,\n y: animateY + 50,\n scale: animateScale,\n zIndex: stackZIndex,\n };\n\n // Height animation for back toasts:\n // - Front toast (index 0): never set height, uses natural CSS height\n // - Back toasts: animate between collapsedHeight and their own naturalHeight\n const measuredHeight = naturalHeight || naturalHeightRef.current;\n const isBackToast = index > 0;\n const hasValidMeasurements =\n hasMeasured && collapsedHeight && measuredHeight;\n\n // For back toasts with valid measurements, calculate target height\n // Otherwise, let CSS handle it naturally\n const animateProps: {\n opacity: number;\n y: number;\n scale: number;\n zIndex: number;\n height?: number;\n } = {\n opacity: 1,\n y: animateY,\n scale: animateScale,\n zIndex: stackZIndex,\n ...(isBackToast && hasValidMeasurements\n ? { height: isExpanded ? measuredHeight : collapsedHeight }\n : {}),\n };\n\n const shouldClipContent =\n isBackToast && hasValidMeasurements && !isExpanded;\n\n return (\n <motion.div\n {...ariaProps}\n ref={toastRef}\n className={styles.toast}\n style={\n {\n '--toast-index': index,\n overflow: shouldClipContent ? 'hidden' : undefined,\n } as React.CSSProperties\n }\n initial={{ opacity: 0, y: 100, scale: 1 }}\n animate={animateProps}\n exit={exitAnimation}\n onAnimationComplete={definition => {\n // Clean up the manual close tracking after exit animation\n if (definition === 'exit') {\n manuallyClosingToasts.delete(toast.key);\n }\n }}\n transition={{ type: 'spring', stiffness: 400, damping: 35 }}\n data-status={finalStatus}\n >\n <BgReset>\n <Box className={styles.surface}>\n <div className={styles.wrapper}>\n {statusIcon && <div className={styles.icon}>{statusIcon}</div>}\n <div className={styles.content}>\n <div {...titleProps} className={styles.title}>\n {content.title}\n </div>\n {content.description && (\n <div className={styles.description}>\n {content.description}\n </div>\n )}\n {content.links && content.links.length > 0 && (\n <div className={styles.links}>\n {content.links.map(link => (\n <a key={link.href} href={link.href}>\n {link.label}\n </a>\n ))}\n </div>\n )}\n </div>\n </div>\n {/* eslint-disable-next-line react/forbid-elements */}\n <button\n {...buttonProps}\n ref={closeButtonRef}\n className={styles.closeButton}\n >\n <RiCloseLine aria-hidden=\"true\" />\n </button>\n </Box>\n </BgReset>\n </motion.div>\n );\n },\n);\n\nToast.displayName = 'Toast';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Toast.esm.js","sources":["../../../src/components/Toast/Toast.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 { forwardRef, Ref, useRef, useLayoutEffect, useState } from 'react';\nimport { useToast, useButton } from 'react-aria';\nimport { motion } from 'motion/react';\nimport { Box } from '@backstage/ui';\nimport {\n RiInformationLine,\n RiCheckLine,\n RiErrorWarningLine,\n RiAlertLine,\n RiCloseLine,\n} from '@remixicon/react';\nimport type { ToastApiMessageProps } from './types';\nimport styles from './Toast.module.css';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { BgReset } from '../../../../../packages/ui/src/hooks/useBg';\n\n// Track which toasts are being manually closed (vs auto-timeout)\n// This allows different exit animations for each case\nconst manuallyClosingToasts = new Set<string>();\n\n/**\n * A Toast displays a brief, temporary notification of actions, errors, or other events in an application.\n *\n * @remarks\n * The Toast component is used internally by ToastContainer and managed by a ToastQueue.\n * It supports multiple status variants (neutral, info, success, warning, danger) and can display\n * a title and description. Toasts can be dismissed manually or automatically.\n *\n * @internal\n */\nexport const Toast = forwardRef(\n (props: ToastApiMessageProps, ref: Ref<HTMLDivElement>) => {\n const {\n toast,\n state,\n index = 0,\n isExpanded = false,\n onClose,\n status,\n expandedY: expandedYProp = 0,\n collapsedHeight,\n naturalHeight,\n onHeightChange,\n } = props;\n\n // Use internal ref if none provided\n const internalRef = useRef<HTMLDivElement>(null);\n const toastRef = (ref as React.RefObject<HTMLDivElement>) || internalRef;\n\n // Get ARIA props from useToast hook\n const { toastProps, titleProps, closeButtonProps } = useToast(\n { toast },\n state,\n toastRef,\n );\n\n // Close button ref for useButton hook\n const closeButtonRef = useRef<HTMLButtonElement>(null);\n\n // Extract only ARIA and accessibility props from toastProps to avoid\n // conflicts with motion.div's event handler types (motion has its own drag API)\n const ariaProps = {\n role: toastProps.role,\n tabIndex: toastProps.tabIndex,\n 'aria-label': toastProps['aria-label'],\n 'aria-labelledby': toastProps['aria-labelledby'],\n 'aria-describedby': toastProps['aria-describedby'],\n 'aria-posinset': toastProps['aria-posinset'],\n 'aria-setsize': toastProps['aria-setsize'],\n };\n\n // Track whether we've measured this toast's natural height\n const [hasMeasured, setHasMeasured] = useState(false);\n // Store the measured natural height locally to avoid re-measurement issues\n const naturalHeightRef = useRef<number | null>(null);\n\n // Measure this toast's natural height on mount (before paint)\n // Using useLayoutEffect ensures we measure before the browser paints\n useLayoutEffect(() => {\n if (!onHeightChange) return;\n if (naturalHeightRef.current) return; // Already measured\n\n const element = toastRef.current;\n if (!element) return;\n\n // Measure immediately - useLayoutEffect runs before paint\n const height = element.getBoundingClientRect().height;\n if (height > 0) {\n naturalHeightRef.current = height;\n onHeightChange(toast.key, height);\n setHasMeasured(true);\n }\n }, [toast.key, onHeightChange, toastRef]);\n\n // Close button handler\n const handleClose = () => {\n // Mark this toast as manually closed for exit animation\n manuallyClosingToasts.add(toast.key);\n onClose?.();\n state.close(toast.key);\n };\n\n // Get button props from useButton hook\n const { buttonProps } = useButton(\n {\n 'aria-label': closeButtonProps['aria-label'],\n onPress: handleClose,\n },\n closeButtonRef,\n );\n\n // Get content from toast\n const content = toast.content;\n const finalStatus = status || content.status || 'info';\n\n // Determine which icon to render based on status\n const getStatusIcon = () => {\n switch (finalStatus) {\n case 'neutral':\n // Neutral status has no icon\n return null;\n case 'success':\n return <RiCheckLine aria-hidden=\"true\" />;\n case 'warning':\n return <RiErrorWarningLine aria-hidden=\"true\" />;\n case 'danger':\n return <RiAlertLine aria-hidden=\"true\" />;\n case 'info':\n default:\n return <RiInformationLine aria-hidden=\"true\" />;\n }\n };\n\n const statusIcon = getStatusIcon();\n\n // Calculate stacking values based on index\n // Collapsed: each toast behind scales down 5% and peeks up 12px\n const collapsedScale = Math.max(0.85, 1 - index * 0.05);\n const collapsedY = -index * 12;\n\n // Use expanded or collapsed values based on hover state\n // expandedYProp is pre-calculated based on actual toast heights\n const animateY = isExpanded ? expandedYProp : collapsedY;\n const animateScale = isExpanded ? 1 : collapsedScale;\n const stackZIndex = 1000 - index;\n\n // Check if this toast is being manually closed\n const isManualClose = manuallyClosingToasts.has(toast.key);\n\n // Different exit animations for manual close vs auto-timeout\n // Manual close: slide down from front, stay on top\n // Auto-timeout: fade out in place, stay in stack position\n const exitAnimation = isManualClose\n ? { opacity: 0, y: 100, scale: 1, zIndex: 2000 }\n : {\n opacity: 0,\n y: animateY + 50,\n scale: animateScale,\n zIndex: stackZIndex,\n };\n\n // Height animation for back toasts:\n // - Front toast (index 0): never set height, uses natural CSS height\n // - Back toasts: animate between collapsedHeight and their own naturalHeight\n const measuredHeight = naturalHeight || naturalHeightRef.current;\n const isBackToast = index > 0;\n const hasValidMeasurements =\n hasMeasured && collapsedHeight && measuredHeight;\n\n // For back toasts with valid measurements, calculate target height\n // Otherwise, let CSS handle it naturally\n const animateProps: {\n opacity: number;\n y: number;\n scale: number;\n zIndex: number;\n height?: number;\n } = {\n opacity: 1,\n y: animateY,\n scale: animateScale,\n zIndex: stackZIndex,\n ...(isBackToast && hasValidMeasurements\n ? { height: isExpanded ? measuredHeight : collapsedHeight }\n : {}),\n };\n\n const shouldClipContent =\n isBackToast && hasValidMeasurements && !isExpanded;\n\n return (\n <motion.div\n {...ariaProps}\n ref={toastRef}\n className={styles.toast}\n style={\n {\n '--toast-index': index,\n overflow: shouldClipContent ? 'hidden' : undefined,\n } as React.CSSProperties\n }\n initial={{ opacity: 0, y: 100, scale: 1 }}\n animate={animateProps}\n exit={exitAnimation}\n onAnimationComplete={definition => {\n // Clean up the manual close tracking after exit animation\n if (definition === 'exit') {\n manuallyClosingToasts.delete(toast.key);\n }\n }}\n transition={{ type: 'spring', stiffness: 400, damping: 35 }}\n data-status={finalStatus}\n >\n <BgReset>\n <Box className={styles.surface}>\n <div className={styles.wrapper}>\n {statusIcon && <div className={styles.icon}>{statusIcon}</div>}\n <div className={styles.content}>\n <div {...titleProps} className={styles.title}>\n {content.title}\n </div>\n {content.description && (\n <div className={styles.description}>\n {content.description}\n </div>\n )}\n {content.links && content.links.length > 0 && (\n <div className={styles.links}>\n {content.links.map(link => (\n <a key={link.href} href={link.href}>\n {link.label}\n </a>\n ))}\n </div>\n )}\n </div>\n </div>\n {/* eslint-disable-next-line react/forbid-elements */}\n <button\n {...buttonProps}\n ref={closeButtonRef}\n className={styles.closeButton}\n >\n <RiCloseLine aria-hidden=\"true\" />\n </button>\n </Box>\n </BgReset>\n </motion.div>\n );\n },\n);\n\nToast.displayName = 'Toast';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { forwardRef, useRef, useState, useCallback, useMemo } from 'react';
|
|
3
|
-
import { useToastRegion } from '
|
|
4
|
-
import { useToastQueue } from '
|
|
3
|
+
import { useToastRegion } from 'react-aria';
|
|
4
|
+
import { useToastQueue } from 'react-stately';
|
|
5
5
|
import { AnimatePresence } from 'motion/react';
|
|
6
6
|
import { useInvertedThemeMode } from '../../hooks/useInvertedThemeMode.esm.js';
|
|
7
7
|
import { Toast } from './Toast.esm.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastContainer.esm.js","sources":["../../../src/components/Toast/ToastContainer.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 { forwardRef, Ref, useState, useRef, useCallback, useMemo } from 'react';\nimport { useToastRegion } from '
|
|
1
|
+
{"version":3,"file":"ToastContainer.esm.js","sources":["../../../src/components/Toast/ToastContainer.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 { forwardRef, Ref, useState, useRef, useCallback, useMemo } from 'react';\nimport { useToastRegion } from 'react-aria';\nimport { useToastQueue } from 'react-stately';\nimport { AnimatePresence } from 'motion/react';\nimport type { ToastApiMessageContainerProps } from './types';\nimport { useInvertedThemeMode } from '../../hooks/useInvertedThemeMode';\nimport { Toast } from './Toast';\nimport styles from './Toast.module.css';\n\n/**\n * A ToastContainer displays one or more toast notifications in the bottom-center of the screen.\n *\n * @remarks\n * The ToastContainer component should typically be placed once at the root of your application.\n * It manages the display and stacking of all toast notifications added to its queue.\n * Toasts appear in the bottom-center with deep stacking when multiple are visible.\n * Toast containers are ARIA landmark regions that can be navigated using F6 (forward) and\n * Shift+F6 (backward) for keyboard accessibility.\n *\n * @internal\n */\nexport const ToastContainer = forwardRef(\n (props: ToastApiMessageContainerProps, ref: Ref<HTMLDivElement>) => {\n const { queue, className } = props;\n\n // Subscribe to the toast queue state\n const state = useToastQueue(queue);\n\n // Use internal ref if none provided\n const internalRef = useRef<HTMLDivElement>(null);\n const containerRef =\n (ref as React.RefObject<HTMLDivElement>) || internalRef;\n\n // Get ARIA props for the toast region\n const { regionProps } = useToastRegion({}, state, containerRef);\n\n // Track hover state for expanding/collapsing the stack\n const [isHovered, setIsHovered] = useState(false);\n\n // Lock expanded state after close to prevent stack collapse during exit animation\n const [isHoverLocked, setIsHoverLocked] = useState(false);\n const unlockTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Toasts are expanded when hovered, focused, or locked\n const isExpanded = isHovered || isHoverLocked;\n\n // Track heights of all toasts by their key\n const [toastHeights, setToastHeights] = useState<Record<string, number>>(\n {},\n );\n\n // Callback for toasts to report their height\n const handleHeightChange = useCallback((key: string, height: number) => {\n setToastHeights(prev => {\n if (prev[key] === height) return prev;\n return { ...prev, [key]: height };\n });\n }, []);\n\n // Calculate expanded Y positions and get front toast height\n const { expandedYPositions, frontToastHeight } = useMemo(() => {\n const gap = 8;\n const positions: Record<string, number> = {};\n let frontHeight = 60; // Default fallback\n\n // visibleToasts[0] is the front toast (newest)\n const toasts = state.visibleToasts;\n\n if (toasts.length > 0 && toastHeights[toasts[0].key]) {\n frontHeight = toastHeights[toasts[0].key];\n }\n\n // Calculate cumulative Y position for each toast when expanded\n // Position is negative Y (moving up from bottom)\n let cumulativeY = 0;\n for (let i = 0; i < toasts.length; i++) {\n positions[toasts[i].key] = -cumulativeY;\n const height = toastHeights[toasts[i].key] || 60;\n cumulativeY += height + gap;\n }\n\n return { expandedYPositions: positions, frontToastHeight: frontHeight };\n }, [state.visibleToasts, toastHeights]);\n\n // Get inverted theme mode for toasts (light when app is dark, dark when app is light)\n const invertedThemeMode = useInvertedThemeMode();\n\n const handleClose = () => {\n // Lock the expanded state while toast is being removed\n setIsHoverLocked(true);\n\n // Clear any pending unlock\n if (unlockTimerRef.current) {\n clearTimeout(unlockTimerRef.current);\n }\n\n // Unlock after a short delay to allow exit animation to complete\n unlockTimerRef.current = setTimeout(() => {\n setIsHoverLocked(false);\n }, 500);\n };\n\n return (\n <div\n {...regionProps}\n ref={containerRef}\n className={className || styles.container}\n data-theme-mode={invertedThemeMode}\n data-hover-locked={isHoverLocked ? '' : undefined}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n onFocus={() => setIsHovered(true)}\n onBlur={() => setIsHovered(false)}\n >\n <AnimatePresence>\n {state.visibleToasts.map((toast, index) => (\n <Toast\n key={toast.key}\n toast={toast}\n state={state}\n index={index}\n isExpanded={isExpanded}\n onClose={handleClose}\n expandedY={expandedYPositions[toast.key] ?? 0}\n collapsedHeight={index > 0 ? frontToastHeight : undefined}\n naturalHeight={toastHeights[toast.key]}\n onHeightChange={handleHeightChange}\n />\n ))}\n </AnimatePresence>\n </div>\n );\n },\n);\n\nToastContainer.displayName = 'ToastContainer';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import { useApi, alertApiRef } from '@backstage/core-plugin-api';
|
|
4
|
-
import { ToastQueue } from '
|
|
4
|
+
import { ToastQueue } from 'react-stately';
|
|
5
5
|
import { ToastContainer } from './ToastContainer.esm.js';
|
|
6
6
|
import 'zen-observable';
|
|
7
7
|
import { toastApiForwarderRef } from '../../apis/toastApiForwarderRef.esm.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastDisplay.esm.js","sources":["../../../src/components/Toast/ToastDisplay.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 { useEffect, useState } from 'react';\nimport { alertApiRef, useApi } from '@backstage/core-plugin-api';\nimport { ToastQueue } from '
|
|
1
|
+
{"version":3,"file":"ToastDisplay.esm.js","sources":["../../../src/components/Toast/ToastDisplay.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 { useEffect, useState } from 'react';\nimport { alertApiRef, useApi } from '@backstage/core-plugin-api';\nimport { ToastQueue } from 'react-stately';\nimport { ToastContainer } from './ToastContainer';\nimport { toastApiForwarderRef } from '../../apis';\nimport type {\n ToastApiMessageDisplayProps,\n ToastApiMessageContent,\n} from './types';\n\n/**\n * Maps AlertApi severity to Toast status.\n * AlertApi uses 'error' while Toast uses 'danger' for the same semantic meaning.\n */\nfunction mapSeverity(\n severity: 'success' | 'info' | 'warning' | 'error' | undefined,\n): ToastApiMessageContent['status'] {\n if (severity === 'error') {\n return 'danger';\n }\n return severity ?? 'success';\n}\n\n/**\n * ToastDisplay bridges both the ToastApi and AlertApi with the Toast notification system.\n *\n * @remarks\n * This component provides a migration bridge between the deprecated AlertApi and the new ToastApi.\n * During the migration period, it subscribes to both APIs simultaneously, allowing plugins to\n * migrate incrementally without breaking existing functionality.\n *\n * **Subscriptions:**\n * - `toastApi.toast$()` - New toast notifications with full features (title, description, links, icons)\n * - `alertApi.alert$()` - Deprecated alerts for backward compatibility (message maps to title only)\n *\n * **ToastApi (recommended):**\n * - Uses toast content directly (title, description, status, icon, links)\n * - Uses the provided timeout from the toast message\n * - Supports programmatic dismiss via the returned `close()` handle\n *\n * **AlertApi (deprecated - please migrate to ToastApi):**\n * - `alert.message` → `toast.title`\n * - `alert.severity` → `toast.status` ('error' maps to 'danger')\n * - `alert.display` → `timeout` (transient gets default timeout, permanent stays until dismissed)\n *\n * @example\n * ```tsx\n * // In your app root element extension\n * <ToastDisplay transientTimeoutMs={5000} />\n *\n * // Using the new ToastApi (recommended):\n * import { toastApiRef, useApi } from '@backstage/frontend-plugin-api';\n * const toastApi = useApi(toastApiRef);\n * const { close } = toastApi.post({\n * title: 'Entity saved',\n * description: 'Your changes have been saved successfully.',\n * status: 'success',\n * timeout: 5000,\n * });\n * // Later: close() to dismiss programmatically\n *\n * // Using the deprecated AlertApi (migrate to ToastApi):\n * import { alertApiRef, useApi } from '@backstage/core-plugin-api';\n * const alertApi = useApi(alertApiRef);\n * alertApi.post({ message: 'Saved!', severity: 'success', display: 'transient' });\n * ```\n *\n * @public\n */\nexport function ToastDisplay(props: ToastApiMessageDisplayProps) {\n const alertApi = useApi(alertApiRef);\n const toastApi = useApi(toastApiForwarderRef);\n const { transientTimeoutMs = 5000 } = props;\n\n // Create toast queue once per component instance\n const [toastQueue] = useState(\n () => new ToastQueue<ToastApiMessageContent>({ maxVisibleToasts: 4 }),\n );\n\n // Subscribe to ToastApi\n useEffect(() => {\n const subscription = toastApi.toast$().subscribe(toast => {\n const content: ToastApiMessageContent = {\n title: toast.title,\n description: toast.description,\n status: toast.status ?? 'success',\n links: toast.links,\n };\n\n // Use the timeout from the toast message if provided\n const options = toast.timeout ? { timeout: toast.timeout } : {};\n\n const queueKey = toastQueue.add(content, options);\n\n // When the toast is programmatically closed, remove it from the queue\n toast.onClose(() => toastQueue.close(queueKey));\n });\n\n return () => subscription.unsubscribe();\n }, [toastApi, toastQueue]);\n\n // Subscribe to AlertApi (deprecated - provides backward compatibility during migration)\n // This subscription will be removed when AlertApi is fully deprecated\n useEffect(() => {\n const subscription = alertApi.alert$().subscribe(alert => {\n const content: ToastApiMessageContent = {\n title: alert.message,\n status: mapSeverity(alert.severity),\n };\n\n // Transient alerts auto-dismiss after timeout, permanent alerts stay until dismissed\n const options =\n alert.display === 'transient' ? { timeout: transientTimeoutMs } : {};\n\n toastQueue.add(content, options);\n });\n\n return () => subscription.unsubscribe();\n }, [alertApi, transientTimeoutMs, toastQueue]);\n\n return <ToastContainer queue={toastQueue} />;\n}\n"],"names":[],"mappings":";;;;;;;;AA8BA,SAAS,YACP,QAAA,EACkC;AAClC,EAAA,IAAI,aAAa,OAAA,EAAS;AACxB,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,OAAO,QAAA,IAAY,SAAA;AACrB;AAgDO,SAAS,aAAa,KAAA,EAAoC;AAC/D,EAAA,MAAM,QAAA,GAAW,OAAO,WAAW,CAAA;AACnC,EAAA,MAAM,QAAA,GAAW,OAAO,oBAAoB,CAAA;AAC5C,EAAA,MAAM,EAAE,kBAAA,GAAqB,GAAA,EAAK,GAAI,KAAA;AAGtC,EAAA,MAAM,CAAC,UAAU,CAAA,GAAI,QAAA;AAAA,IACnB,MAAM,IAAI,UAAA,CAAmC,EAAE,gBAAA,EAAkB,GAAG;AAAA,GACtE;AAGA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,YAAA,GAAe,QAAA,CAAS,MAAA,EAAO,CAAE,UAAU,CAAA,KAAA,KAAS;AACxD,MAAA,MAAM,OAAA,GAAkC;AAAA,QACtC,OAAO,KAAA,CAAM,KAAA;AAAA,QACb,aAAa,KAAA,CAAM,WAAA;AAAA,QACnB,MAAA,EAAQ,MAAM,MAAA,IAAU,SAAA;AAAA,QACxB,OAAO,KAAA,CAAM;AAAA,OACf;AAGA,MAAA,MAAM,OAAA,GAAU,MAAM,OAAA,GAAU,EAAE,SAAS,KAAA,CAAM,OAAA,KAAY,EAAC;AAE9D,MAAA,MAAM,QAAA,GAAW,UAAA,CAAW,GAAA,CAAI,OAAA,EAAS,OAAO,CAAA;AAGhD,MAAA,KAAA,CAAM,OAAA,CAAQ,MAAM,UAAA,CAAW,KAAA,CAAM,QAAQ,CAAC,CAAA;AAAA,IAChD,CAAC,CAAA;AAED,IAAA,OAAO,MAAM,aAAa,WAAA,EAAY;AAAA,EACxC,CAAA,EAAG,CAAC,QAAA,EAAU,UAAU,CAAC,CAAA;AAIzB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,YAAA,GAAe,QAAA,CAAS,MAAA,EAAO,CAAE,UAAU,CAAA,KAAA,KAAS;AACxD,MAAA,MAAM,OAAA,GAAkC;AAAA,QACtC,OAAO,KAAA,CAAM,OAAA;AAAA,QACb,MAAA,EAAQ,WAAA,CAAY,KAAA,CAAM,QAAQ;AAAA,OACpC;AAGA,MAAA,MAAM,OAAA,GACJ,MAAM,OAAA,KAAY,WAAA,GAAc,EAAE,OAAA,EAAS,kBAAA,KAAuB,EAAC;AAErE,MAAA,UAAA,CAAW,GAAA,CAAI,SAAS,OAAO,CAAA;AAAA,IACjC,CAAC,CAAA;AAED,IAAA,OAAO,MAAM,aAAa,WAAA,EAAY;AAAA,EACxC,CAAA,EAAG,CAAC,QAAA,EAAU,kBAAA,EAAoB,UAAU,CAAC,CAAA;AAE7C,EAAA,uBAAO,GAAA,CAAC,cAAA,EAAA,EAAe,KAAA,EAAO,UAAA,EAAY,CAAA;AAC5C;;;;"}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { AppLanguageSelector } from '../packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.esm.js';
|
|
2
2
|
import { ApiBlueprint, appLanguageApiRef } from '@backstage/frontend-plugin-api';
|
|
3
|
+
import { z } from 'zod/v4';
|
|
3
4
|
|
|
4
5
|
const AppLanguageApi = ApiBlueprint.makeWithOverrides({
|
|
5
6
|
name: "app-language",
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
availableLanguages: (z) => z.array(z.string()).optional()
|
|
10
|
-
}
|
|
7
|
+
configSchema: {
|
|
8
|
+
defaultLanguage: z.string().optional(),
|
|
9
|
+
availableLanguages: z.array(z.string()).optional()
|
|
11
10
|
},
|
|
12
11
|
factory(originalFactory, { config }) {
|
|
13
12
|
return originalFactory(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppLanguageApi.esm.js","sources":["../../src/extensions/AppLanguageApi.ts"],"sourcesContent":["/*\n * Copyright 2024 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// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppLanguageSelector } from '../../../../packages/core-app-api/src/apis/implementations/AppLanguageApi';\nimport { appLanguageApiRef } from '@backstage/frontend-plugin-api';\nimport { ApiBlueprint } from '@backstage/frontend-plugin-api';\n\nexport const AppLanguageApi = ApiBlueprint.makeWithOverrides({\n name: 'app-language',\n
|
|
1
|
+
{"version":3,"file":"AppLanguageApi.esm.js","sources":["../../src/extensions/AppLanguageApi.ts"],"sourcesContent":["/*\n * Copyright 2024 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// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppLanguageSelector } from '../../../../packages/core-app-api/src/apis/implementations/AppLanguageApi';\nimport { appLanguageApiRef } from '@backstage/frontend-plugin-api';\nimport { ApiBlueprint } from '@backstage/frontend-plugin-api';\nimport { z } from 'zod/v4';\n\nexport const AppLanguageApi = ApiBlueprint.makeWithOverrides({\n name: 'app-language',\n configSchema: {\n defaultLanguage: z.string().optional(),\n availableLanguages: z.array(z.string()).optional(),\n },\n factory(originalFactory, { config }) {\n return originalFactory(defineParams =>\n defineParams({\n api: appLanguageApiRef,\n deps: {},\n factory: () =>\n AppLanguageSelector.createWithStorage({\n defaultLanguage: config.defaultLanguage,\n availableLanguages: config.availableLanguages,\n }),\n }),\n );\n },\n});\n"],"names":[],"mappings":";;;;AAsBO,MAAM,cAAA,GAAiB,aAAa,iBAAA,CAAkB;AAAA,EAC3D,IAAA,EAAM,cAAA;AAAA,EACN,YAAA,EAAc;AAAA,IACZ,eAAA,EAAiB,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IACrC,oBAAoB,CAAA,CAAE,KAAA,CAAM,EAAE,MAAA,EAAQ,EAAE,QAAA;AAAS,GACnD;AAAA,EACA,OAAA,CAAQ,eAAA,EAAiB,EAAE,MAAA,EAAO,EAAG;AACnC,IAAA,OAAO,eAAA;AAAA,MAAgB,kBACrB,YAAA,CAAa;AAAA,QACX,GAAA,EAAK,iBAAA;AAAA,QACL,MAAM,EAAC;AAAA,QACP,OAAA,EAAS,MACP,mBAAA,CAAoB,iBAAA,CAAkB;AAAA,UACpC,iBAAiB,MAAA,CAAO,eAAA;AAAA,UACxB,oBAAoB,MAAA,CAAO;AAAA,SAC5B;AAAA,OACJ;AAAA,KACH;AAAA,EACF;AACF,CAAC;;;;"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
2
3
|
import { createExtension, coreExtensionData, createExtensionInput, NotFoundErrorPage } from '@backstage/frontend-plugin-api';
|
|
3
4
|
import { useRoutes, Navigate } from 'react-router-dom';
|
|
4
5
|
|
|
@@ -12,15 +13,13 @@ const AppRoutes = createExtension({
|
|
|
12
13
|
coreExtensionData.reactElement
|
|
13
14
|
])
|
|
14
15
|
},
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
z.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
).optional()
|
|
23
|
-
}
|
|
16
|
+
configSchema: {
|
|
17
|
+
redirects: z.array(
|
|
18
|
+
z.object({
|
|
19
|
+
from: z.string(),
|
|
20
|
+
to: z.string()
|
|
21
|
+
})
|
|
22
|
+
).optional()
|
|
24
23
|
},
|
|
25
24
|
output: [coreExtensionData.reactElement],
|
|
26
25
|
factory({ inputs, config }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppRoutes.esm.js","sources":["../../src/extensions/AppRoutes.tsx"],"sourcesContent":["/*\n * Copyright 2023 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 {\n createExtension,\n coreExtensionData,\n createExtensionInput,\n NotFoundErrorPage,\n} from '@backstage/frontend-plugin-api';\nimport { Navigate, useRoutes } from 'react-router-dom';\n\nexport const AppRoutes = createExtension({\n name: 'routes',\n attachTo: { id: 'app/layout', input: 'content' },\n inputs: {\n routes: createExtensionInput([\n coreExtensionData.routePath,\n coreExtensionData.routeRef.optional(),\n coreExtensionData.reactElement,\n ]),\n },\n
|
|
1
|
+
{"version":3,"file":"AppRoutes.esm.js","sources":["../../src/extensions/AppRoutes.tsx"],"sourcesContent":["/*\n * Copyright 2023 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 { z } from 'zod/v4';\nimport {\n createExtension,\n coreExtensionData,\n createExtensionInput,\n NotFoundErrorPage,\n} from '@backstage/frontend-plugin-api';\nimport { Navigate, useRoutes } from 'react-router-dom';\n\nexport const AppRoutes = createExtension({\n name: 'routes',\n attachTo: { id: 'app/layout', input: 'content' },\n inputs: {\n routes: createExtensionInput([\n coreExtensionData.routePath,\n coreExtensionData.routeRef.optional(),\n coreExtensionData.reactElement,\n ]),\n },\n configSchema: {\n redirects: z\n .array(\n z.object({\n from: z.string(),\n to: z.string(),\n }),\n )\n .optional(),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs, config }) {\n const redirects = config.redirects ?? [];\n\n const Routes = () => {\n const element = useRoutes([\n ...redirects.map(redirect => ({\n path:\n redirect.from === '/'\n ? redirect.from\n : `${redirect.from.replace(/\\/$/, '')}/*`,\n element: <Navigate to={redirect.to} replace />,\n })),\n ...inputs.routes.map(route => {\n const routePath = route.get(coreExtensionData.routePath);\n\n return {\n path:\n routePath === '/'\n ? routePath\n : `${routePath.replace(/\\/$/, '')}/*`,\n\n element: route.get(coreExtensionData.reactElement),\n };\n }),\n {\n path: '*',\n element: <NotFoundErrorPage />,\n },\n ]);\n\n return element;\n };\n\n return [coreExtensionData.reactElement(<Routes />)];\n },\n});\n"],"names":[],"mappings":";;;;;AAyBO,MAAM,YAAY,eAAA,CAAgB;AAAA,EACvC,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,EAAE,EAAA,EAAI,YAAA,EAAc,OAAO,SAAA,EAAU;AAAA,EAC/C,MAAA,EAAQ;AAAA,IACN,QAAQ,oBAAA,CAAqB;AAAA,MAC3B,iBAAA,CAAkB,SAAA;AAAA,MAClB,iBAAA,CAAkB,SAAS,QAAA,EAAS;AAAA,MACpC,iBAAA,CAAkB;AAAA,KACnB;AAAA,GACH;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,WAAW,CAAA,CACR,KAAA;AAAA,MACC,EAAE,MAAA,CAAO;AAAA,QACP,IAAA,EAAM,EAAE,MAAA,EAAO;AAAA,QACf,EAAA,EAAI,EAAE,MAAA;AAAO,OACd;AAAA,MAEF,QAAA;AAAS,GACd;AAAA,EACA,MAAA,EAAQ,CAAC,iBAAA,CAAkB,YAAY,CAAA;AAAA,EACvC,OAAA,CAAQ,EAAE,MAAA,EAAQ,MAAA,EAAO,EAAG;AAC1B,IAAA,MAAM,SAAA,GAAY,MAAA,CAAO,SAAA,IAAa,EAAC;AAEvC,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,MAAM,UAAU,SAAA,CAAU;AAAA,QACxB,GAAG,SAAA,CAAU,GAAA,CAAI,CAAA,QAAA,MAAa;AAAA,UAC5B,IAAA,EACE,QAAA,CAAS,IAAA,KAAS,GAAA,GACd,QAAA,CAAS,IAAA,GACT,CAAA,EAAG,QAAA,CAAS,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,EAAA,CAAA;AAAA,UACzC,yBAAS,GAAA,CAAC,QAAA,EAAA,EAAS,IAAI,QAAA,CAAS,EAAA,EAAI,SAAO,IAAA,EAAC;AAAA,SAC9C,CAAE,CAAA;AAAA,QACF,GAAG,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,CAAA,KAAA,KAAS;AAC5B,UAAA,MAAM,SAAA,GAAY,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,SAAS,CAAA;AAEvD,UAAA,OAAO;AAAA,YACL,IAAA,EACE,cAAc,GAAA,GACV,SAAA,GACA,GAAG,SAAA,CAAU,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,EAAA,CAAA;AAAA,YAErC,OAAA,EAAS,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,YAAY;AAAA,WACnD;AAAA,QACF,CAAC,CAAA;AAAA,QACD;AAAA,UACE,IAAA,EAAM,GAAA;AAAA,UACN,OAAA,sBAAU,iBAAA,EAAA,EAAkB;AAAA;AAC9B,OACD,CAAA;AAED,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAEA,IAAA,OAAO,CAAC,iBAAA,CAAkB,YAAA,iBAAa,GAAA,CAAC,MAAA,EAAA,EAAO,CAAE,CAAC,CAAA;AAAA,EACpD;AACF,CAAC;;;;"}
|
|
@@ -2,7 +2,6 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { Fragment, useState, useEffect } from 'react';
|
|
3
3
|
import { AppRootElementBlueprint, dialogApiRef } from '@backstage/frontend-plugin-api';
|
|
4
4
|
import { createDeferred } from '@backstage/types';
|
|
5
|
-
import Dialog from '@material-ui/core/Dialog';
|
|
6
5
|
|
|
7
6
|
let dialogId = 0;
|
|
8
7
|
function getDialogId() {
|
|
@@ -19,7 +18,6 @@ function DialogDisplay({
|
|
|
19
18
|
const deferred = createDeferred();
|
|
20
19
|
const dialog = {
|
|
21
20
|
id,
|
|
22
|
-
modal: options.modal,
|
|
23
21
|
close(result) {
|
|
24
22
|
deferred.resolve(result);
|
|
25
23
|
setDialogs((ds) => ds.filter((d) => d.dialog.id !== id));
|
|
@@ -40,19 +38,7 @@ function DialogDisplay({
|
|
|
40
38
|
});
|
|
41
39
|
}, [dialogApi]);
|
|
42
40
|
if (dialogs.length > 0) {
|
|
43
|
-
|
|
44
|
-
return /* @__PURE__ */ jsx(
|
|
45
|
-
Dialog,
|
|
46
|
-
{
|
|
47
|
-
open: true,
|
|
48
|
-
onClose: () => {
|
|
49
|
-
if (!lastDialog.dialog.modal) {
|
|
50
|
-
lastDialog.dialog.close();
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
children: lastDialog.element
|
|
54
|
-
}
|
|
55
|
-
);
|
|
41
|
+
return dialogs[dialogs.length - 1].element;
|
|
56
42
|
}
|
|
57
43
|
return null;
|
|
58
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DialogDisplay.esm.js","sources":["../../src/extensions/DialogDisplay.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 { Fragment, useEffect, useState } from 'react';\nimport {\n AppRootElementBlueprint,\n DialogApi,\n DialogApiDialog,\n dialogApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { createDeferred } from '@backstage/types';\nimport {
|
|
1
|
+
{"version":3,"file":"DialogDisplay.esm.js","sources":["../../src/extensions/DialogDisplay.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 { Fragment, useEffect, useState } from 'react';\nimport {\n AppRootElementBlueprint,\n DialogApi,\n DialogApiDialog,\n dialogApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { createDeferred } from '@backstage/types';\nimport { OnOpenDialog } from '../apis/DefaultDialogApi';\n\nlet dialogId = 0;\nfunction getDialogId() {\n dialogId += 1;\n return dialogId.toString(36);\n}\n\ntype DialogState = DialogApiDialog<unknown> & {\n id: string;\n};\n\n/**\n * The other half of the default implementation of the {@link DialogApi}.\n *\n * This component is responsible for rendering the dialogs in the React tree\n * and managing a stack of dialogs. It renders only the most recently opened\n * dialog, without any dialog chrome — the caller is expected to provide their\n * own dialog component (overlay, backdrop, surface, etc.).\n *\n * It expects the implementation of the {@link DialogApi} to be the\n * `DefaultDialogApi`. If one is replaced the other must be too.\n *\n * @internal\n */\nfunction DialogDisplay({\n dialogApi,\n}: {\n dialogApi: DialogApi & { connect(onOpen: OnOpenDialog): void };\n}) {\n const [dialogs, setDialogs] = useState<\n { dialog: DialogState; element: React.JSX.Element }[]\n >([]);\n\n useEffect(() => {\n dialogApi.connect(options => {\n const id = getDialogId();\n const deferred = createDeferred<unknown>();\n const dialog: DialogState = {\n id,\n close(result) {\n deferred.resolve(result);\n setDialogs(ds => ds.filter(d => d.dialog.id !== id));\n },\n update(ElementOrComponent) {\n const element =\n typeof ElementOrComponent === 'function' ? (\n <ElementOrComponent dialog={dialog} />\n ) : (\n ElementOrComponent\n );\n setDialogs(ds =>\n ds.map(d => (d.dialog.id === id ? { dialog, element } : d)),\n );\n },\n async result() {\n return deferred;\n },\n };\n const element = <options.component dialog={dialog} />;\n setDialogs(ds => [...ds, { dialog, element }]);\n return dialog;\n });\n }, [dialogApi]);\n\n if (dialogs.length > 0) {\n return dialogs[dialogs.length - 1].element;\n }\n\n return null;\n}\n\nexport const dialogDisplayAppRootElement =\n AppRootElementBlueprint.makeWithOverrides({\n name: 'dialog-display',\n factory(originalFactory, { apis }) {\n const dialogApi = apis.get(dialogApiRef);\n if (!isInternalDialogApi(dialogApi)) {\n return originalFactory({\n element: <Fragment />,\n });\n }\n return originalFactory({\n element: <DialogDisplay dialogApi={dialogApi} />,\n });\n },\n });\n\nfunction isInternalDialogApi(\n dialogApi?: DialogApi,\n): dialogApi is DialogApi & { connect(onOpen: OnOpenDialog): void } {\n if (!dialogApi) {\n return false;\n }\n return 'connect' in dialogApi;\n}\n"],"names":["element"],"mappings":";;;;;AA0BA,IAAI,QAAA,GAAW,CAAA;AACf,SAAS,WAAA,GAAc;AACrB,EAAA,QAAA,IAAY,CAAA;AACZ,EAAA,OAAO,QAAA,CAAS,SAAS,EAAE,CAAA;AAC7B;AAmBA,SAAS,aAAA,CAAc;AAAA,EACrB;AACF,CAAA,EAEG;AACD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,QAAA,CAE5B,EAAE,CAAA;AAEJ,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,SAAA,CAAU,QAAQ,CAAA,OAAA,KAAW;AAC3B,MAAA,MAAM,KAAK,WAAA,EAAY;AACvB,MAAA,MAAM,WAAW,cAAA,EAAwB;AACzC,MAAA,MAAM,MAAA,GAAsB;AAAA,QAC1B,EAAA;AAAA,QACA,MAAM,MAAA,EAAQ;AACZ,UAAA,QAAA,CAAS,QAAQ,MAAM,CAAA;AACvB,UAAA,UAAA,CAAW,CAAA,EAAA,KAAM,GAAG,MAAA,CAAO,CAAA,CAAA,KAAK,EAAE,MAAA,CAAO,EAAA,KAAO,EAAE,CAAC,CAAA;AAAA,QACrD,CAAA;AAAA,QACA,OAAO,kBAAA,EAAoB;AACzB,UAAA,MAAMA,WACJ,OAAO,kBAAA,KAAuB,6BAC5B,GAAA,CAAC,kBAAA,EAAA,EAAmB,QAAgB,CAAA,GAEpC,kBAAA;AAEJ,UAAA,UAAA;AAAA,YAAW,CAAA,EAAA,KACT,EAAA,CAAG,GAAA,CAAI,CAAA,CAAA,KAAM,CAAA,CAAE,MAAA,CAAO,EAAA,KAAO,EAAA,GAAK,EAAE,MAAA,EAAQ,OAAA,EAAAA,QAAAA,KAAY,CAAE;AAAA,WAC5D;AAAA,QACF,CAAA;AAAA,QACA,MAAM,MAAA,GAAS;AACb,UAAA,OAAO,QAAA;AAAA,QACT;AAAA,OACF;AACA,MAAA,MAAM,OAAA,mBAAU,GAAA,CAAC,OAAA,CAAQ,SAAA,EAAR,EAAkB,MAAA,EAAgB,CAAA;AACnD,MAAA,UAAA,CAAW,CAAA,EAAA,KAAM,CAAC,GAAG,EAAA,EAAI,EAAE,MAAA,EAAQ,OAAA,EAAS,CAAC,CAAA;AAC7C,MAAA,OAAO,MAAA;AAAA,IACT,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,CAAC,SAAS,CAAC,CAAA;AAEd,EAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,IAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA,CAAE,OAAA;AAAA,EACrC;AAEA,EAAA,OAAO,IAAA;AACT;AAEO,MAAM,2BAAA,GACX,wBAAwB,iBAAA,CAAkB;AAAA,EACxC,IAAA,EAAM,gBAAA;AAAA,EACN,OAAA,CAAQ,eAAA,EAAiB,EAAE,IAAA,EAAK,EAAG;AACjC,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,YAAY,CAAA;AACvC,IAAA,IAAI,CAAC,mBAAA,CAAoB,SAAS,CAAA,EAAG;AACnC,MAAA,OAAO,eAAA,CAAgB;AAAA,QACrB,OAAA,sBAAU,QAAA,EAAA,EAAS;AAAA,OACpB,CAAA;AAAA,IACH;AACA,IAAA,OAAO,eAAA,CAAgB;AAAA,MACrB,OAAA,kBAAS,GAAA,CAAC,aAAA,EAAA,EAAc,SAAA,EAAsB;AAAA,KAC/C,CAAA;AAAA,EACH;AACF,CAAC;AAEH,SAAS,oBACP,SAAA,EACkE;AAClE,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,SAAA,IAAa,SAAA;AACtB;;;;"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { OAuthRequestDialog } from '@backstage/core-components';
|
|
3
3
|
import { AppRootElementBlueprint } from '@backstage/frontend-plugin-api';
|
|
4
|
+
import { z } from 'zod/v4';
|
|
4
5
|
import { ToastDisplay } from '../components/Toast/ToastDisplay.esm.js';
|
|
5
6
|
import '../components/Toast/ToastContainer.esm.js';
|
|
6
7
|
|
|
@@ -12,14 +13,15 @@ const oauthRequestDialogAppRootElement = AppRootElementBlueprint.make({
|
|
|
12
13
|
});
|
|
13
14
|
const alertDisplayAppRootElement = AppRootElementBlueprint.makeWithOverrides({
|
|
14
15
|
name: "alert-display",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
configSchema: {
|
|
17
|
+
transientTimeoutMs: z.number().default(5e3),
|
|
18
|
+
anchorOrigin: z.object({
|
|
19
|
+
vertical: z.enum(["top", "bottom"]).default("top"),
|
|
20
|
+
horizontal: z.enum(["left", "center", "right"]).default("center")
|
|
21
|
+
}).default({
|
|
22
|
+
vertical: "top",
|
|
23
|
+
horizontal: "center"
|
|
24
|
+
})
|
|
23
25
|
},
|
|
24
26
|
factory: (originalFactory, { config }) => {
|
|
25
27
|
return originalFactory({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elements.esm.js","sources":["../../src/extensions/elements.tsx"],"sourcesContent":["/*\n * Copyright 2023 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 { OAuthRequestDialog } from '@backstage/core-components';\nimport { AppRootElementBlueprint } from '@backstage/frontend-plugin-api';\nimport { ToastDisplay } from '../components/Toast';\n\nexport const oauthRequestDialogAppRootElement = AppRootElementBlueprint.make({\n name: 'oauth-request-dialog',\n params: {\n element: <OAuthRequestDialog />,\n },\n});\n\nexport const alertDisplayAppRootElement =\n AppRootElementBlueprint.makeWithOverrides({\n name: 'alert-display',\n
|
|
1
|
+
{"version":3,"file":"elements.esm.js","sources":["../../src/extensions/elements.tsx"],"sourcesContent":["/*\n * Copyright 2023 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 { OAuthRequestDialog } from '@backstage/core-components';\nimport { AppRootElementBlueprint } from '@backstage/frontend-plugin-api';\nimport { z } from 'zod/v4';\nimport { ToastDisplay } from '../components/Toast';\n\nexport const oauthRequestDialogAppRootElement = AppRootElementBlueprint.make({\n name: 'oauth-request-dialog',\n params: {\n element: <OAuthRequestDialog />,\n },\n});\n\nexport const alertDisplayAppRootElement =\n AppRootElementBlueprint.makeWithOverrides({\n name: 'alert-display',\n configSchema: {\n transientTimeoutMs: z.number().default(5000),\n anchorOrigin: z\n .object({\n vertical: z.enum(['top', 'bottom']).default('top'),\n horizontal: z.enum(['left', 'center', 'right']).default('center'),\n })\n .default({\n vertical: 'top',\n horizontal: 'center',\n }),\n },\n factory: (originalFactory, { config }) => {\n return originalFactory({\n element: (\n <ToastDisplay transientTimeoutMs={config.transientTimeoutMs} />\n ),\n });\n },\n });\n"],"names":[],"mappings":";;;;;;;AAqBO,MAAM,gCAAA,GAAmC,wBAAwB,IAAA,CAAK;AAAA,EAC3E,IAAA,EAAM,sBAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,OAAA,sBAAU,kBAAA,EAAA,EAAmB;AAAA;AAEjC,CAAC;AAEM,MAAM,0BAAA,GACX,wBAAwB,iBAAA,CAAkB;AAAA,EACxC,IAAA,EAAM,eAAA;AAAA,EACN,YAAA,EAAc;AAAA,IACZ,kBAAA,EAAoB,CAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,GAAI,CAAA;AAAA,IAC3C,YAAA,EAAc,EACX,MAAA,CAAO;AAAA,MACN,QAAA,EAAU,EAAE,IAAA,CAAK,CAAC,OAAO,QAAQ,CAAC,CAAA,CAAE,OAAA,CAAQ,KAAK,CAAA;AAAA,MACjD,UAAA,EAAY,CAAA,CAAE,IAAA,CAAK,CAAC,MAAA,EAAQ,UAAU,OAAO,CAAC,CAAA,CAAE,OAAA,CAAQ,QAAQ;AAAA,KACjE,EACA,OAAA,CAAQ;AAAA,MACP,QAAA,EAAU,KAAA;AAAA,MACV,UAAA,EAAY;AAAA,KACb;AAAA,GACL;AAAA,EACA,OAAA,EAAS,CAAC,eAAA,EAAiB,EAAE,QAAO,KAAM;AACxC,IAAA,OAAO,eAAA,CAAgB;AAAA,MACrB,OAAA,kBACE,GAAA,CAAC,YAAA,EAAA,EAAa,kBAAA,EAAoB,OAAO,kBAAA,EAAoB;AAAA,KAEhE,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -485,16 +485,16 @@ declare const appPlugin: _backstage_frontend_plugin_api.OverridableFrontendPlugi
|
|
|
485
485
|
config: {
|
|
486
486
|
transientTimeoutMs: number;
|
|
487
487
|
anchorOrigin: {
|
|
488
|
-
horizontal: "center" | "left" | "right";
|
|
489
488
|
vertical: "top" | "bottom";
|
|
489
|
+
horizontal: "center" | "left" | "right";
|
|
490
490
|
};
|
|
491
491
|
};
|
|
492
492
|
configInput: {
|
|
493
|
+
transientTimeoutMs?: number | undefined;
|
|
493
494
|
anchorOrigin?: {
|
|
494
|
-
horizontal?: "center" | "left" | "right" | undefined;
|
|
495
495
|
vertical?: "top" | "bottom" | undefined;
|
|
496
|
+
horizontal?: "center" | "left" | "right" | undefined;
|
|
496
497
|
} | undefined;
|
|
497
|
-
transientTimeoutMs?: number | undefined;
|
|
498
498
|
};
|
|
499
499
|
output: _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}>;
|
|
500
500
|
inputs: {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "@backstage/plugin-app";
|
|
2
|
-
var version = "0.4.
|
|
2
|
+
var version = "0.4.4";
|
|
3
3
|
var backstage = {
|
|
4
4
|
role: "frontend-plugin",
|
|
5
5
|
pluginId: "app",
|
|
@@ -64,12 +64,11 @@ var dependencies = {
|
|
|
64
64
|
"@material-ui/core": "^4.9.13",
|
|
65
65
|
"@material-ui/icons": "^4.9.1",
|
|
66
66
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
|
67
|
-
"@react-aria/button": "^3.14.3",
|
|
68
|
-
"@react-aria/toast": "^3.0.9",
|
|
69
67
|
"@react-hookz/web": "^24.0.0",
|
|
70
|
-
"@react-stately/toast": "^3.1.2",
|
|
71
68
|
"@remixicon/react": "^4.6.0",
|
|
72
69
|
motion: "^12.0.0",
|
|
70
|
+
"react-aria": "^3.48.0",
|
|
71
|
+
"react-stately": "^3.46.0",
|
|
73
72
|
"react-use": "^17.2.4",
|
|
74
73
|
"zen-observable": "^0.10.0",
|
|
75
74
|
zod: "^3.25.76 || ^4.0.0"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package.json.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"package.json.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-app",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "frontend-plugin",
|
|
6
6
|
"pluginId": "app",
|
|
@@ -63,36 +63,35 @@
|
|
|
63
63
|
"test": "backstage-cli package test"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@backstage/core-components": "0.18.9
|
|
67
|
-
"@backstage/core-plugin-api": "1.12.5
|
|
68
|
-
"@backstage/filter-predicates": "0.1.2
|
|
69
|
-
"@backstage/frontend-plugin-api": "0.16.
|
|
70
|
-
"@backstage/integration-react": "1.2.17
|
|
71
|
-
"@backstage/plugin-app-react": "0.2.2
|
|
72
|
-
"@backstage/plugin-permission-react": "0.
|
|
73
|
-
"@backstage/theme": "0.7.3
|
|
74
|
-
"@backstage/types": "1.2.2",
|
|
75
|
-
"@backstage/ui": "0.14.
|
|
76
|
-
"@backstage/version-bridge": "1.0.12",
|
|
66
|
+
"@backstage/core-components": "^0.18.9",
|
|
67
|
+
"@backstage/core-plugin-api": "^1.12.5",
|
|
68
|
+
"@backstage/filter-predicates": "^0.1.2",
|
|
69
|
+
"@backstage/frontend-plugin-api": "^0.16.1",
|
|
70
|
+
"@backstage/integration-react": "^1.2.17",
|
|
71
|
+
"@backstage/plugin-app-react": "^0.2.2",
|
|
72
|
+
"@backstage/plugin-permission-react": "^0.5.0",
|
|
73
|
+
"@backstage/theme": "^0.7.3",
|
|
74
|
+
"@backstage/types": "^1.2.2",
|
|
75
|
+
"@backstage/ui": "^0.14.1",
|
|
76
|
+
"@backstage/version-bridge": "^1.0.12",
|
|
77
77
|
"@material-ui/core": "^4.9.13",
|
|
78
78
|
"@material-ui/icons": "^4.9.1",
|
|
79
79
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
|
80
|
-
"@react-aria/button": "^3.14.3",
|
|
81
|
-
"@react-aria/toast": "^3.0.9",
|
|
82
80
|
"@react-hookz/web": "^24.0.0",
|
|
83
|
-
"@react-stately/toast": "^3.1.2",
|
|
84
81
|
"@remixicon/react": "^4.6.0",
|
|
85
82
|
"motion": "^12.0.0",
|
|
83
|
+
"react-aria": "^3.48.0",
|
|
84
|
+
"react-stately": "^3.46.0",
|
|
86
85
|
"react-use": "^17.2.4",
|
|
87
86
|
"zen-observable": "^0.10.0",
|
|
88
87
|
"zod": "^3.25.76 || ^4.0.0"
|
|
89
88
|
},
|
|
90
89
|
"devDependencies": {
|
|
91
|
-
"@backstage/cli": "0.36.1
|
|
92
|
-
"@backstage/dev-utils": "1.1.22
|
|
93
|
-
"@backstage/frontend-defaults": "0.5.1
|
|
94
|
-
"@backstage/frontend-test-utils": "0.5.2
|
|
95
|
-
"@backstage/test-utils": "1.7.17
|
|
90
|
+
"@backstage/cli": "^0.36.1",
|
|
91
|
+
"@backstage/dev-utils": "^1.1.22",
|
|
92
|
+
"@backstage/frontend-defaults": "^0.5.1",
|
|
93
|
+
"@backstage/frontend-test-utils": "^0.5.2",
|
|
94
|
+
"@backstage/test-utils": "^1.7.17",
|
|
96
95
|
"@testing-library/jest-dom": "^6.0.0",
|
|
97
96
|
"@testing-library/react": "^16.0.0",
|
|
98
97
|
"@testing-library/user-event": "^14.0.0",
|