New pass on toast

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2026-01-27 16:13:21 +00:00
committed by Patrik Oldsberg
parent cae33447ed
commit 69a323b9c4
8 changed files with 278 additions and 74 deletions
+2
View File
@@ -47,9 +47,11 @@
},
"dependencies": {
"@backstage/version-bridge": "workspace:^",
"@react-aria/toast": "^3.0.9",
"@remixicon/react": "^4.6.0",
"@tanstack/react-table": "^8.21.3",
"clsx": "^2.1.1",
"motion": "^12.29.2",
"react-aria-components": "^1.14.0"
},
"devDependencies": {
@@ -193,17 +193,6 @@
}
}
/* Entering and exiting states */
.bui-Toast[data-entering] {
transform: translateY(calc(100% + 2rem)) scale(1);
opacity: 0;
}
.bui-Toast[data-exiting] {
transform: translateY(calc(100% + 2rem)) scale(var(--toast-scale));
opacity: 0;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.bui-Toast {
+40 -26
View File
@@ -14,13 +14,10 @@
* limitations under the License.
*/
import { forwardRef, Ref, isValidElement, ReactElement } from 'react';
import {
UNSTABLE_Toast as RAToast,
UNSTABLE_ToastContent as RAToastContent,
Text,
Button as RAButton,
} from 'react-aria-components';
import { forwardRef, Ref, isValidElement, ReactElement, useRef } from 'react';
import { motion } from 'motion/react';
import { useToast } from '@react-aria/toast';
import { useButton } from 'react-aria';
import {
RiInformationLine,
RiCheckLine,
@@ -31,6 +28,8 @@ import {
import type { ToastProps } from './types';
import { useDefinition } from '../../hooks/useDefinition';
import { ToastDefinition } from './definition';
import type { ToastState } from 'react-stately';
import type { ToastContent } from './types';
/**
* A Toast displays a brief, temporary notification of actions, errors, or other events in an application.
@@ -66,12 +65,17 @@ import { ToastDefinition } from './definition';
* @public
*/
export const Toast = forwardRef(
(props: ToastProps, ref: Ref<HTMLDivElement>) => {
const { ownProps, restProps, dataAttributes } = useDefinition(
ToastDefinition,
props,
);
const { classes, toast, index = 0, status, icon } = ownProps;
(props: ToastProps, _forwardedRef: Ref<HTMLDivElement>) => {
const { ownProps, dataAttributes } = useDefinition(ToastDefinition, props);
const { classes, toast, state, index = 0, status, icon } = ownProps;
const ref = useRef<HTMLDivElement>(null);
const closeButtonRef = useRef<HTMLButtonElement>(null);
const { toastProps, titleProps, descriptionProps, closeButtonProps } =
useToast({ toast }, state, ref);
const { buttonProps } = useButton(closeButtonProps, closeButtonRef);
// Get content from toast
const content = toast.content;
@@ -112,36 +116,46 @@ export const Toast = forwardRef(
const statusIcon = getStatusIcon();
return (
<RAToast
toast={toast}
className={classes.root}
<motion.div
ref={ref}
className={classes.root}
style={
{
'--toast-index': index,
} as React.CSSProperties
}
{...toastProps}
{...dataAttributes}
data-status={finalStatus}
{...restProps}
initial={{ y: '150%', opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: '150%', opacity: 0 }}
transition={{
duration: 0.4,
ease: [0.22, 1, 0.36, 1],
}}
>
<RAToastContent className={classes.content}>
<div className={classes.content}>
{statusIcon && <div className={classes.icon}>{statusIcon}</div>}
<div>
<Text slot="title" className={classes.title}>
<div {...titleProps} className={classes.title}>
{content.title}
</Text>
</div>
{content.description && (
<Text slot="description" className={classes.description}>
<div {...descriptionProps} className={classes.description}>
{content.description}
</Text>
</div>
)}
</div>
</RAToastContent>
<RAButton slot="close" className={classes.closeButton}>
</div>
<button
{...buttonProps}
ref={closeButtonRef}
className={classes.closeButton}
>
<RiCloseLine aria-hidden="true" />
</RAButton>
</RAToast>
</button>
</motion.div>
);
},
);
@@ -43,5 +43,5 @@ import type { ToastContent } from './types';
* @public
*/
export const toastQueue = new RAToastQueue<ToastContent>({
maxVisibleToasts: 3,
maxVisibleToasts: 5,
});
@@ -14,9 +14,11 @@
* limitations under the License.
*/
import { forwardRef, Ref, useEffect, useState } from 'react';
import { UNSTABLE_ToastRegion as RAToastRegion } from 'react-aria-components';
import type { ToastRegionProps } from './types';
import { forwardRef, Ref, useEffect, useState, useRef } from 'react';
import { useToastRegion } from '@react-aria/toast';
import { AnimatePresence } from 'motion/react';
import type { QueuedToast, ToastState } from 'react-stately';
import type { ToastRegionProps, ToastContent } from './types';
import { useDefinition } from '../../hooks/useDefinition';
import { ToastRegionDefinition } from './definition';
import { Toast } from './Toast';
@@ -51,41 +53,57 @@ import { Toast } from './Toast';
* @public
*/
export const ToastRegion = forwardRef(
(props: ToastRegionProps, ref: Ref<HTMLDivElement>) => {
(props: ToastRegionProps, _ref: Ref<HTMLDivElement>) => {
const { ownProps, restProps, dataAttributes } = useDefinition(
ToastRegionDefinition,
props,
);
const { classes, queue, className } = ownProps;
// Track visible toast keys to determine index
const [visibleToasts, setVisibleToasts] = useState<string[]>([]);
const [, forceUpdate] = useState({});
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const updateToasts = () => {
setVisibleToasts(queue.visibleToasts.map(t => t.key));
};
// Subscribe to queue changes
const unsubscribe = queue.subscribe(updateToasts);
updateToasts(); // Initial update
// Subscribe to queue changes to trigger re-renders
const unsubscribe = queue.subscribe(() => forceUpdate({}));
return unsubscribe;
}, [queue]);
const state: ToastState<ToastContent> = {
visibleToasts: queue.visibleToasts,
add: (content: ToastContent, options?: any) =>
queue.add(content, options),
close: (key: string) => queue.close(key),
pauseAll: () => queue.pauseAll(),
resumeAll: () => queue.resumeAll(),
};
const { regionProps } = useToastRegion({}, state, ref);
return (
<RAToastRegion
<div
{...regionProps}
ref={ref}
queue={queue}
className={className || classes.region}
{...dataAttributes}
{...restProps}
>
{({ toast }) => {
const index = visibleToasts.indexOf(toast.key);
return <Toast toast={toast} index={index >= 0 ? index : 0} />;
}}
</RAToastRegion>
<ol style={{ margin: 0, padding: 0, listStyleType: 'none' }}>
<AnimatePresence mode="popLayout">
{queue.visibleToasts.map(
(toast: QueuedToast<ToastContent>, arrayIndex: number) => {
// Reverse index so newest toast (at end of array) gets index 0
const index = queue.visibleToasts.length - 1 - arrayIndex;
return (
<li key={toast.key} style={{ display: 'contents' }}>
<Toast toast={toast} index={index} state={state} />
</li>
);
},
)}
</AnimatePresence>
</ol>
</div>
);
},
);
@@ -35,6 +35,7 @@ export const ToastDefinition = defineComponent<ToastOwnProps>()({
surface: 'container',
propDefs: {
toast: {},
state: {},
index: {},
status: { dataAttribute: true },
icon: {},
+7 -7
View File
@@ -16,6 +16,7 @@
import type { ReactElement, ReactNode } from 'react';
import type { UNSTABLE_ToastQueue as RAToastQueue } from 'react-aria-components';
import type { ToastState, QueuedToast } from 'react-stately';
import type { Responsive } from '../../types';
/**
@@ -35,16 +36,15 @@ export interface ToastContent {
/**
* Own props for the Toast component
* @public
* @internal
*/
export type ToastOwnProps = {
/** Toast object from the queue */
toast: {
key: string;
content: ToastContent;
};
toast: QueuedToast<ToastContent>;
/** Toast state for hooks */
state: ToastState<ToastContent>;
/** Index of the toast in the stack (0 = frontmost) */
index?: number;
index: number;
/** Override status from content */
status?: Responsive<'info' | 'success' | 'warning' | 'danger'>;
/** Override icon from content */
@@ -53,7 +53,7 @@ export type ToastOwnProps = {
/**
* Properties for {@link Toast}
* @public
* @internal
*/
export interface ToastProps extends ToastOwnProps {}
+188 -8
View File
@@ -8114,6 +8114,7 @@ __metadata:
dependencies:
"@backstage/cli": "workspace:^"
"@backstage/version-bridge": "workspace:^"
"@react-aria/toast": "npm:^3.0.9"
"@remixicon/react": "npm:^4.6.0"
"@tanstack/react-table": "npm:^8.21.3"
"@types/react": "npm:^18.0.0"
@@ -8122,6 +8123,7 @@ __metadata:
eslint-plugin-storybook: "npm:^10.3.0-alpha.1"
glob: "npm:^11.0.1"
globals: "npm:^15.11.0"
motion: "npm:^12.29.2"
react: "npm:^18.0.2"
react-aria-components: "npm:^1.14.0"
react-dom: "npm:^18.0.2"
@@ -22931,6 +22933,19 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.52.0":
version: 8.52.0
resolution: "@typescript-eslint/project-service@npm:8.52.0"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.52.0"
"@typescript-eslint/types": "npm:^8.52.0"
debug: "npm:^4.4.3"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/bfa786007ed4a603fb8f31c6e354f0ba0cca576b03e402584ae3cf0d674f07adfbde9e976a5bf165fa44c484d4b4f310bd18b34d1b0e75b4210253edbdaabb87
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.54.0":
version: 8.54.0
resolution: "@typescript-eslint/project-service@npm:8.54.0"
@@ -22954,6 +22969,16 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.52.0":
version: 8.52.0
resolution: "@typescript-eslint/scope-manager@npm:8.52.0"
dependencies:
"@typescript-eslint/types": "npm:8.52.0"
"@typescript-eslint/visitor-keys": "npm:8.52.0"
checksum: 10/89d9c04cd2567e6aa9adcbe85e2eab24fbc64bde5a33c688764e7c896e9a02c06aad2ec88e8bdc4d5bfabadbc510906a0cb4f3e0b73a5b80d10218f7a6a4ea27
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.54.0, @typescript-eslint/scope-manager@npm:^8.51.0":
version: 8.54.0
resolution: "@typescript-eslint/scope-manager@npm:8.54.0"
@@ -22964,6 +22989,15 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.52.0, @typescript-eslint/tsconfig-utils@npm:^8.52.0":
version: 8.52.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.52.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/5b26227ab549e20a6b15725a4f8373acb70ae1c83570c8d670e242bfcd22ac0c9111d4d28ea16ee3939572caacce50e113388ce943f238fc2ca17f6c5a040cd2
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.54.0, @typescript-eslint/tsconfig-utils@npm:^8.54.0":
version: 8.54.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.54.0"
@@ -22996,6 +23030,13 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.52.0, @typescript-eslint/types@npm:^8.52.0":
version: 8.52.0
resolution: "@typescript-eslint/types@npm:8.52.0"
checksum: 10/05a630c5d25cce74d1bfa51027f1232f2e8a97a8f483ce0274e928373b4633cdf713be53eca39926f0372d52a3335f13786c7910d2edfd546a0cf1d66b3bcf51
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.54.0, @typescript-eslint/types@npm:^8.54.0":
version: 8.54.0
resolution: "@typescript-eslint/types@npm:8.54.0"
@@ -23022,6 +23063,25 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.52.0":
version: 8.52.0
resolution: "@typescript-eslint/typescript-estree@npm:8.52.0"
dependencies:
"@typescript-eslint/project-service": "npm:8.52.0"
"@typescript-eslint/tsconfig-utils": "npm:8.52.0"
"@typescript-eslint/types": "npm:8.52.0"
"@typescript-eslint/visitor-keys": "npm:8.52.0"
debug: "npm:^4.4.3"
minimatch: "npm:^9.0.5"
semver: "npm:^7.7.3"
tinyglobby: "npm:^0.2.15"
ts-api-utils: "npm:^2.4.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/4e699f44a05e9c487531557a1eaf6412a97f370ec946a03596c8d445f584c3d17e9aa34cde5ce8998ae9d6908c1daffb2c9b523cb07e5988cf249eae6dea50fd
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.54.0, @typescript-eslint/typescript-estree@npm:^8.23.0":
version: 8.54.0
resolution: "@typescript-eslint/typescript-estree@npm:8.54.0"
@@ -23041,7 +23101,7 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.54.0, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.48.0, @typescript-eslint/utils@npm:^8.51.0":
"@typescript-eslint/utils@npm:8.54.0, @typescript-eslint/utils@npm:^8.48.0":
version: 8.54.0
resolution: "@typescript-eslint/utils@npm:8.54.0"
dependencies:
@@ -23056,6 +23116,21 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.51.0":
version: 8.52.0
resolution: "@typescript-eslint/utils@npm:8.52.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.9.1"
"@typescript-eslint/scope-manager": "npm:8.52.0"
"@typescript-eslint/types": "npm:8.52.0"
"@typescript-eslint/typescript-estree": "npm:8.52.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/11a02ab0fd26bb1284dfa8c02d40c54cabd3aa795e82ab26b060ea3839998c28a41822b075f9d23fb51e291e465147213166c8ddaf3c8d5807e70b0a4345d967
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:^7.0.0":
version: 7.18.0
resolution: "@typescript-eslint/utils@npm:7.18.0"
@@ -23080,6 +23155,16 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.52.0":
version: 8.52.0
resolution: "@typescript-eslint/visitor-keys@npm:8.52.0"
dependencies:
"@typescript-eslint/types": "npm:8.52.0"
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10/4d841402cc65e876382ede464b68cf167c7d24905b15225c472516bb759140abbef02f250c6335ca35327f7328975ff3b28c3249a5183319cfd01f1d5541e3c1
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.54.0":
version: 8.54.0
resolution: "@typescript-eslint/visitor-keys@npm:8.54.0"
@@ -29250,9 +29335,9 @@ __metadata:
linkType: hard
"detect-libc@npm:^2.0.0":
version: 2.1.2
resolution: "detect-libc@npm:2.1.2"
checksum: 10/b736c8d97d5d46164c0d1bed53eb4e6a3b1d8530d460211e2d52f1c552875e706c58a5376854e4e54f8b828c9cada58c855288c968522eb93ac7696d65970766
version: 2.0.3
resolution: "detect-libc@npm:2.0.3"
checksum: 10/b4ea018d623e077bd395f168a9e81db77370dde36a5b01d067f2ad7989924a81d31cb547ff764acb2aa25d50bb7fdde0b0a93bec02212b0cb430621623246d39
languageName: node
linkType: hard
@@ -32169,7 +32254,17 @@ __metadata:
languageName: node
linkType: hard
"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.11, follow-redirects@npm:^1.15.6":
"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.6":
version: 1.15.6
resolution: "follow-redirects@npm:1.15.6"
peerDependenciesMeta:
debug:
optional: true
checksum: 10/70c7612c4cab18e546e36b991bbf8009a1a41cf85354afe04b113d1117569abf760269409cb3eb842d9f7b03d62826687086b081c566ea7b1e6613cf29030bf7
languageName: node
linkType: hard
"follow-redirects@npm:^1.15.11":
version: 1.15.11
resolution: "follow-redirects@npm:1.15.11"
peerDependenciesMeta:
@@ -32374,6 +32469,28 @@ __metadata:
languageName: node
linkType: hard
"framer-motion@npm:^12.29.2":
version: 12.29.2
resolution: "framer-motion@npm:12.29.2"
dependencies:
motion-dom: "npm:^12.29.2"
motion-utils: "npm:^12.29.2"
tslib: "npm:^2.4.0"
peerDependencies:
"@emotion/is-prop-valid": "*"
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@emotion/is-prop-valid":
optional: true
react:
optional: true
react-dom:
optional: true
checksum: 10/13ea43efa814e4df1f3a0bf7ee6c1736f3f01b96cb677e560b62d58db8768f00a53c17156bb5bf6bf06b786370468495dbb6b0a83e3b5b5289e5235046ccb03b
languageName: node
linkType: hard
"framer-motion@npm:^6.5.1":
version: 6.5.1
resolution: "framer-motion@npm:6.5.1"
@@ -34956,7 +35073,7 @@ __metadata:
languageName: node
linkType: hard
"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.1":
"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.0, is-core-module@npm:^2.16.1":
version: 2.16.1
resolution: "is-core-module@npm:2.16.1"
dependencies:
@@ -39787,6 +39904,43 @@ __metadata:
languageName: node
linkType: hard
"motion-dom@npm:^12.29.2":
version: 12.29.2
resolution: "motion-dom@npm:12.29.2"
dependencies:
motion-utils: "npm:^12.29.2"
checksum: 10/d1eeb6840363cc2b4d1e1c2d9a91becc117bb64747482b1a99f7f2d4da709f049c9544c3a566c641cada368037019c0fafd3e7cf56f0477e88d65e9886bcdc40
languageName: node
linkType: hard
"motion-utils@npm:^12.29.2":
version: 12.29.2
resolution: "motion-utils@npm:12.29.2"
checksum: 10/ae5f9be58c07939af72334894ed1a18653d724946182a718dc3d11268ef26e63804c3f16dee5a6110596d4406b539c4513822b74f86adebef9488601c34b18b7
languageName: node
linkType: hard
"motion@npm:^12.29.2":
version: 12.29.2
resolution: "motion@npm:12.29.2"
dependencies:
framer-motion: "npm:^12.29.2"
tslib: "npm:^2.4.0"
peerDependencies:
"@emotion/is-prop-valid": "*"
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@emotion/is-prop-valid":
optional: true
react:
optional: true
react-dom:
optional: true
checksum: 10/0480a984192dd0f884709a007c1d28f31ff5fcdecb6674517edbe11ac0e318efc845373f35800e0606899613e6617ec87b1c09d5ecf3f5a5aeb3eb25fef22c7d
languageName: node
linkType: hard
"mri@npm:1.1.4":
version: 1.1.4
resolution: "mri@npm:1.1.4"
@@ -45405,7 +45559,20 @@ __metadata:
languageName: node
linkType: hard
"resolve@npm:^1.1.6, resolve@npm:^1.1.7, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2":
"resolve@npm:^1.1.6, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2":
version: 1.22.10
resolution: "resolve@npm:1.22.10"
dependencies:
is-core-module: "npm:^2.16.0"
path-parse: "npm:^1.0.7"
supports-preserve-symlinks-flag: "npm:^1.0.0"
bin:
resolve: bin/resolve
checksum: 10/0a398b44da5c05e6e421d70108822c327675febb880eebe905587628de401854c61d5df02866ff34fc4cb1173a51c9f0e84a94702738df3611a62e2acdc68181
languageName: node
linkType: hard
"resolve@npm:^1.1.7":
version: 1.22.11
resolution: "resolve@npm:1.22.11"
dependencies:
@@ -45444,7 +45611,20 @@ __metadata:
languageName: node
linkType: hard
"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.1.7#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin<compat/resolve>":
"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin<compat/resolve>":
version: 1.22.10
resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin<compat/resolve>::version=1.22.10&hash=c3c19d"
dependencies:
is-core-module: "npm:^2.16.0"
path-parse: "npm:^1.0.7"
supports-preserve-symlinks-flag: "npm:^1.0.0"
bin:
resolve: bin/resolve
checksum: 10/d4d878bfe3702d215ea23e75e0e9caf99468e3db76f5ca100d27ebdc527366fee3877e54bce7d47cc72ca8952fc2782a070d238bfa79a550eeb0082384c3b81a
languageName: node
linkType: hard
"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin<compat/resolve>":
version: 1.22.11
resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin<compat/resolve>::version=1.22.11&hash=c3c19d"
dependencies: