Implement a shared Banner component. (#31266)

* feat: Create composer `Banner` shared component.

* fix: Yarn resolution issues corrupting package store.

* chore: Revert "fix: Yarn resolution issues corrupting package store."

This reverts commit 2c1335420331e9cf086cad5c68f7c7432af4b3a5.

* fix: Revert lockfile changes.

* chore: Resolve linting errors.

* chore: Update playwright screenshots.
This commit is contained in:
Skye Elliot
2025-11-26 15:13:55 +00:00
committed by GitHub
parent 16e71ffd58
commit fd152c9c7e
16 changed files with 610 additions and 40 deletions

View File

@@ -46,6 +46,7 @@
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
},
"dependencies": {
"@vector-im/compound-design-tokens": "^6.3.0",
"classnames": "^2.5.1",
"counterpart": "^0.18.6",
"lodash": "^4.17.21",
@@ -88,7 +89,6 @@
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
"peerDependencies": {
"@vector-im/compound-design-tokens": "^6.0.0",
"@vector-im/compound-web": "^8.2.5"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
:root {
--cpd-color-gradient-critical-linear: linear-gradient(
180deg,
var(--cpd-color-alpha-red-500) 0%,
var(--cpd-color-alpha-red-400) 20%,
var(--cpd-color-alpha-red-300) 40%,
var(--cpd-color-alpha-red-200) 60%,
var(--cpd-color-alpha-red-100) 80%,
var(--cpd-color-transparent) 100%
);
}
.banner {
container-type: inline-size;
container-name: banner;
display: flex;
align-items: center;
justify-content: start;
gap: var(--cpd-space-3x);
padding: var(--cpd-space-4x);
border-top: 1px solid var(--cpd-color-gray-400);
white-space: nowrap;
}
.banner[data-type="success"] {
background: var(--cpd-color-gradient-subtle-linear);
border-color: var(--cpd-color-green-900);
}
.banner[data-type="critical"] {
background: var(--cpd-color-gradient-critical-linear);
border-color: var(--cpd-color-border-critical-primary);
}
.banner[data-type="info"] {
background: var(--cpd-color-gradient-info-linear);
border-color: var(--cpd-color-blue-900);
}
.banner[data-type="info"] :is(svg) {
color: var(--cpd-color-blue-900);
}
.banner[data-type="success"] :is(.content, svg) {
color: var(--cpd-color-green-900);
}
.banner[data-type="critical"] :is(.content, svg) {
color: var(--cpd-color-red-900);
}
.banner p {
margin: 0;
}
.icon {
/* lock icon dimensions */
min-width: 32px;
min-height: 32px;
max-width: 32px;
max-height: 32px;
margin: 4px;
/* centre svg icons, as they are not full width */
flex: 0;
display: flex;
align-items: center;
justify-content: center;
}
.icon img {
border-radius: 50%;
}
.actions {
margin-left: auto;
flex: 0;
display: flex;
flex-direction: row;
gap: var(--cpd-space-1x);
align-self: center;
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { fn } from "storybook/test";
import { type Meta, type StoryObj } from "@storybook/react-vite";
import { Button } from "@vector-im/compound-web";
import { Banner } from "./Banner";
import { _t } from "../../utils/i18n";
const meta = {
title: "room/Banner",
component: Banner,
tags: ["autodocs"],
args: {
children: <p>Hello! This is a status banner.</p>,
onClose: fn(),
},
} satisfies Meta<typeof Banner>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Info: Story = {
args: {
type: "info",
},
};
export const Success: Story = {
args: {
type: "success",
},
};
export const Critical: Story = {
args: {
type: "critical",
},
};
export const WithAction: Story = {
args: {
children: (
<p>
{_t(
"encryption|pinned_identity_changed",
{ displayName: "Alice", userId: "@alice:example.org" },
{
a: (sub) => <a href="https://example.org">{sub}</a>,
b: (sub) => <b>{sub}</b>,
},
)}
</p>
),
actions: <Button kind="primary">{_t("encryption|withdraw_verification_action")}</Button>,
},
};
export const WithAvatarImage: Story = {
args: {
avatar: <img alt="Example" src="https://picsum.photos/32/32" />,
},
};

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render } from "jest-matrix-react";
import { composeStories } from "@storybook/react-vite";
import * as stories from "./Banner.stories.tsx";
const { Default, Info, Success, WithAction, WithAvatarImage, Critical } = composeStories(stories);
describe("AvatarWithDetails", () => {
it("renders a default banner", () => {
const { container } = render(<Default />);
expect(container).toMatchSnapshot();
});
it("renders a info banner", () => {
const { container } = render(<Info />);
expect(container).toMatchSnapshot();
});
it("renders a success banner", () => {
const { container } = render(<Success />);
expect(container).toMatchSnapshot();
});
it("renders a critical banner", () => {
const { container } = render(<Critical />);
expect(container).toMatchSnapshot();
});
it("renders a banner with an action", () => {
const { container } = render(<WithAction />);
expect(container).toMatchSnapshot();
});
it("renders a banner with an avatar iamge", () => {
const { container } = render(<WithAvatarImage />);
expect(container).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import classNames from "classnames";
import React, {
type MouseEventHandler,
type ReactElement,
type ReactNode,
type PropsWithChildren,
useMemo,
} from "react";
import { Button } from "@vector-im/compound-web";
import CheckCircleIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle";
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
import styles from "./Banner.module.css";
import { _t } from "../../utils/i18n";
interface BannerProps {
/**
* The type of the status banner.
*/
type?: "success" | "info" | "critical";
/**
* The banner avatar.
*/
avatar?: React.ReactNode;
className?: string;
/**
* Actions presented to the user in the right-hand side of the banner alongside the dismiss button.
*/
actions?: ReactNode;
/**
* Called when the user presses the "dismiss" button.
*/
onClose: MouseEventHandler<HTMLButtonElement>;
}
/**
* A banner component used for displaying user-facing information above the message composer.
*
* @example
* ```tsx
* <Banner onClose={onCloseHandler} />
* ```
*/
export function Banner({
type,
children,
avatar,
className,
actions,
onClose,
...props
}: PropsWithChildren<BannerProps>): ReactElement {
const classes = classNames(styles.banner, className);
const icon = useMemo(() => {
switch (type) {
case "critical":
return <ErrorIcon fontSize={24} {...props} />;
case "info":
return <InfoIcon fontSize={24} {...props} />;
case "success":
return <CheckCircleIcon fontSize={24} {...props} />;
default:
return <InfoIcon fontSize={24} {...props} />;
}
}, [type, props]);
return (
<div {...props} className={classes} data-type={type}>
<div className={styles.icon}>{avatar ?? icon}</div>
<span className={styles.content}>{children}</span>
<div className={styles.actions}>
{actions}
<Button kind="secondary" size="sm" onClick={onClose}>
{_t("action|dismiss")}
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,290 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`AvatarWithDetails renders a banner with an action 1`] = `
<div>
<div
class="banner"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
/>
<path
clip-rule="evenodd"
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
fill-rule="evenodd"
/>
</svg>
</div>
<span
class="content"
>
<p>
encryption|pinned_identity_changed
</p>
</span>
<div
class="actions"
>
<button
class="_button_vczzf_8"
data-kind="primary"
data-size="lg"
role="button"
tabindex="0"
>
encryption|withdraw_verification_action
</button>
<button
class="_button_vczzf_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;
exports[`AvatarWithDetails renders a banner with an avatar iamge 1`] = `
<div>
<div
class="banner"
>
<div
class="icon"
>
<img
alt="Example"
src="https://picsum.photos/32/32"
/>
</div>
<span
class="content"
>
<p>
Hello! This is a status banner.
</p>
</span>
<div
class="actions"
>
<button
class="_button_vczzf_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;
exports[`AvatarWithDetails renders a critical banner 1`] = `
<div>
<div
class="banner"
data-type="critical"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
/>
</svg>
</div>
<span
class="content"
>
<p>
Hello! This is a status banner.
</p>
</span>
<div
class="actions"
>
<button
class="_button_vczzf_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;
exports[`AvatarWithDetails renders a default banner 1`] = `
<div>
<div
class="banner"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
/>
<path
clip-rule="evenodd"
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
fill-rule="evenodd"
/>
</svg>
</div>
<span
class="content"
>
<p>
Hello! This is a status banner.
</p>
</span>
<div
class="actions"
>
<button
class="_button_vczzf_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;
exports[`AvatarWithDetails renders a info banner 1`] = `
<div>
<div
class="banner"
data-type="info"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
/>
<path
clip-rule="evenodd"
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
fill-rule="evenodd"
/>
</svg>
</div>
<span
class="content"
>
<p>
Hello! This is a status banner.
</p>
</span>
<div
class="actions"
>
<button
class="_button_vczzf_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;
exports[`AvatarWithDetails renders a success banner 1`] = `
<div>
<div
class="banner"
data-type="success"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m10.6 13.8-2.15-2.15a.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275.95.95 0 0 0-.275.7q0 .425.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65a.95.95 0 0 0 .275-.7.95.95 0 0 0-.275-.7.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275zM12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20"
/>
</svg>
</div>
<span
class="content"
>
<p>
Hello! This is a status banner.
</p>
</span>
<div
class="actions"
>
<button
class="_button_vczzf_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;

View File

@@ -0,0 +1,8 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
export * from "./Banner";

View File

@@ -11,6 +11,7 @@ export * from "./audio/Clock";
export * from "./audio/PlayPauseButton";
export * from "./audio/SeekBar";
export * from "./avatar/AvatarWithDetails";
export * from "./composer/Banner";
export * from "./event-tiles/TextualEventView";
export * from "./message-body/MediaBody";
export * from "./pill-input/Pill";

View File

@@ -1959,6 +1959,11 @@
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777"
integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==
"@vector-im/compound-design-tokens@^6.3.0":
version "6.4.0"
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-6.4.0.tgz#2e51f39f79ebda985a2f6cf80d567b9307aff03a"
integrity sha512-93nYQZMgUt6apjCwwnMhMxN8VYQXN3GYOnwovwJjavImwsCGwI/e853BV/DstrWumYh6k5pZsP9e6AF+nz3SIQ==
"@vitest/expect@3.2.4":
version "3.2.4"
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433"

View File

@@ -1579,15 +1579,8 @@
yaml "^2.7.0"
"@element-hq/web-shared-components@link:packages/shared-components":
version "0.0.0-test.8"
dependencies:
classnames "^2.5.1"
counterpart "^0.18.6"
lodash "^4.17.21"
matrix-web-i18n "^3.4.0"
patch-package "^8.0.1"
react-merge-refs "^3.0.2"
temporal-polyfill "^0.3.0"
version "0.0.0"
uid ""
"@emnapi/core@^1.4.3", "@emnapi/core@^1.5.0":
version "1.7.0"
@@ -4143,6 +4136,11 @@
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-6.0.0.tgz#a07975ee46307fc31c2ec64a216b6be2b3b27fb3"
integrity sha512-Jk0NsLPCvdcuZi6an1cfyf4MDcIuoPlvja5ZWgJcORyGQZV1eLMHPYKShq9gj+EYk/BXZoPvQ1d6/T+/LSCNPA==
"@vector-im/compound-design-tokens@^6.3.0":
version "6.4.1"
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-6.4.1.tgz#b3356300136b974104b4fb818969350c7686f5ae"
integrity sha512-JhrxnzohxGILrc+IZWoMXcpGHinnJlR2HSCKfypEjPDDF5TOB8HQYTqd5ALAPlob8QZU3N2ghnCF7d0f2LmTxg==
"@vector-im/compound-web@^8.1.2":
version "8.2.4"
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-8.2.4.tgz#1109537365fe49368b13e05c5a32f23956e71fe8"
@@ -4160,13 +4158,14 @@
"@vector-im/matrix-wysiwyg-wasm@link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm":
version "0.0.0"
uid ""
"@vector-im/matrix-wysiwyg@2.40.0":
version "2.40.0"
resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.40.0.tgz#53c9ca5ea907d91e4515da64f20a82e5586b882c"
integrity sha512-8LRFLs5PEKYs4lOL7aJ4lL/hGCrvEvOYkCR3JggXYXDVMtX4LmfdlKYucSAe98pCmqAAbLRvlRcR1bTOYvM8ug==
dependencies:
"@vector-im/matrix-wysiwyg-wasm" "link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm"
"@vector-im/matrix-wysiwyg-wasm" "link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm"
"@vitest/expect@3.2.4":
version "3.2.4"
@@ -9603,7 +9602,7 @@ matrix-events-sdk@0.0.1:
jwt-decode "^4.0.0"
loglevel "^1.9.2"
matrix-events-sdk "0.0.1"
matrix-widget-api "^1.10.0"
matrix-widget-api "^1.14.0"
oidc-client-ts "^3.0.1"
p-retry "7"
sdp-transform "^3.0.0"
@@ -12499,16 +12498,7 @@ string-length@^4.0.2:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -12616,14 +12606,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -13917,16 +13900,7 @@ word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^6.2.0, wrap-ansi@^7.0.0, wrap-ansi@^8.1.0, wrap-ansi@^9.0.0, "wrap-ansi@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^6.2.0, wrap-ansi@^7.0.0, wrap-ansi@^8.1.0, wrap-ansi@^9.0.0, "wrap-ansi@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==