Merge pull request #1641 from robintown/invite-modal

Implement the new invite modal designs
This commit is contained in:
Robin
2023-09-28 09:45:03 -04:00
committed by GitHub
19 changed files with 553 additions and 174 deletions

View File

@@ -87,6 +87,7 @@
"@storybook/react": "^6.5.0-alpha.5",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/content-type": "^1.1.5",
"@types/d3": "^7.4.0",
"@types/dom-screen-wake-lock": "^1.0.1",

View File

@@ -29,7 +29,7 @@
"Continue in browser": "Continue in browser",
"Copied!": "Copied!",
"Copy": "Copy",
"Copy and share this call link": "Copy and share this call link",
"Copy link": "Copy link",
"Create account": "Create account",
"Debug log": "Debug log",
"Debug log request": "Debug log request",
@@ -54,9 +54,12 @@
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.",
"Include debug logs": "Include debug logs",
"Inspector": "Inspector",
"Invite": "Invite",
"Invite to this call": "Invite to this call",
"Join call": "Join call",
"Join call now": "Join call now",
"Join existing call?": "Join existing call?",
"Link copied to clipboard": "Link copied to clipboard",
"Loading…": "Loading…",
"Local volume": "Local volume",
"Logging in…": "Logging in…",
@@ -92,9 +95,7 @@
"Sending debug logs…": "Sending debug logs…",
"Sending…": "Sending…",
"Settings": "Settings",
"Share": "Share",
"Share screen": "Share screen",
"Share this call": "Share this call",
"Sharing screen": "Sharing screen",
"Show call inspector": "Show call inspector",
"Show connection stats": "Show connection stats",

View File

@@ -14,96 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.overlay {
position: fixed;
z-index: 100;
inset: 0;
background: rgba(3, 12, 27, 0.528);
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.dialogOverlay[data-state="open"] {
animation: fade-in 200ms;
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.dialogOverlay[data-state="closed"] {
animation: fade-out 130ms;
}
.modal {
position: fixed;
z-index: 101;
display: flex;
flex-direction: column;
}
.dialog {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
box-sizing: border-box;
inline-size: 520px;
max-inline-size: 90%;
max-block-size: 600px;
}
@keyframes zoom-in {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(80%);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(100%);
}
}
@keyframes zoom-out {
from {
opacity: 1;
transform: translate(-50%, -50%) scale(100%);
}
to {
opacity: 0;
transform: translate(-50%, -50%) scale(80%);
}
}
.dialog[data-state="open"] {
animation: zoom-in 200ms;
}
.dialog[data-state="closed"] {
animation: zoom-out 130ms;
}
@media (prefers-reduced-motion) {
.dialog[data-state="open"] {
animation-name: fade-in;
}
.dialog[data-state="closed"] {
animation-name: fade-out;
}
}
.content {
display: flex;
flex-direction: column;

View File

@@ -32,6 +32,7 @@ import classNames from "classnames";
import { Heading } from "@vector-im/compound-web";
import styles from "./Modal.module.css";
import overlayStyles from "./Overlay.module.css";
import { useMediaQuery } from "./useMediaQuery";
import { Glass } from "./Glass";
@@ -85,9 +86,14 @@ export function Modal({
dismissible={onDismiss !== undefined}
>
<Drawer.Portal>
<Drawer.Overlay className={styles.overlay} />
<Drawer.Overlay className={classNames(overlayStyles.bg)} />
<Drawer.Content
className={classNames(className, styles.modal, styles.drawer)}
className={classNames(
className,
overlayStyles.overlay,
styles.modal,
styles.drawer
)}
{...rest}
>
<div className={styles.content}>
@@ -108,12 +114,18 @@ export function Modal({
<DialogRoot open={open} onOpenChange={onOpenChange}>
<DialogPortal>
<DialogOverlay
className={classNames(styles.overlay, styles.dialogOverlay)}
className={classNames(overlayStyles.bg, overlayStyles.animate)}
/>
<DialogContent asChild {...rest}>
<Glass
frosted
className={classNames(className, styles.modal, styles.dialog)}
className={classNames(
className,
overlayStyles.overlay,
overlayStyles.animate,
styles.modal,
styles.dialog
)}
>
<div className={styles.content}>
<div className={styles.header}>

99
src/Overlay.module.css Normal file
View File

@@ -0,0 +1,99 @@
/*
Copyright 2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.bg {
position: fixed;
z-index: 100;
inset: 0;
background: rgba(3, 12, 27, 0.528);
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.bg.animate[data-state="open"] {
animation: fade-in 200ms;
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.bg.animate[data-state="closed"] {
animation: fade-out 130ms;
}
.overlay {
position: fixed;
z-index: 101;
}
.overlay.animate {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
@keyframes zoom-in {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(80%);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(100%);
}
}
@keyframes zoom-out {
from {
opacity: 1;
transform: translate(-50%, -50%) scale(100%);
}
to {
opacity: 0;
transform: translate(-50%, -50%) scale(80%);
}
}
.overlay.animate[data-state="open"] {
animation: zoom-in 200ms;
}
.overlay.animate[data-state="closed"] {
animation: zoom-out 130ms;
}
@media (prefers-reduced-motion) {
.overlay.animate[data-state="open"] {
animation-name: fade-in;
}
.overlay.animate[data-state="closed"] {
animation-name: fade-out;
}
}

38
src/Toast.module.css Normal file
View File

@@ -0,0 +1,38 @@
/*
Copyright 2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.toast {
color: var(--cpd-color-text-on-solid-primary);
background: var(--cpd-color-alpha-gray-1200);
padding-inline: var(--cpd-space-3x);
padding-block: var(--cpd-space-1x);
border: none;
border-radius: var(--cpd-radius-pill-effect);
box-shadow: var(--small-drop-shadow);
display: flex;
align-items: center;
gap: var(--cpd-space-1x);
}
.toast > h3 {
margin: 0;
}
.toast > svg {
color: var(--cpd-color-icon-on-solid-primary);
flex-shrink: 0;
margin-inline-end: calc(-1 * var(--cpd-space-1x));
}

108
src/Toast.tsx Normal file
View File

@@ -0,0 +1,108 @@
/*
Copyright 2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {
ComponentType,
FC,
SVGAttributes,
useCallback,
useEffect,
} from "react";
import {
Root as DialogRoot,
Portal as DialogPortal,
Overlay as DialogOverlay,
Content as DialogContent,
Close as DialogClose,
Title as DialogTitle,
} from "@radix-ui/react-dialog";
import classNames from "classnames";
import { Text } from "@vector-im/compound-web";
import styles from "./Toast.module.css";
import overlayStyles from "./Overlay.module.css";
interface Props {
/**
* The controlled open state of the toast.
*/
open: boolean;
/**
* Callback for when the user dismisses the toast.
*/
onDismiss: () => void;
/**
* A number of milliseconds after which the toast should be automatically
* dismissed.
*/
autoDismiss?: number;
children: string;
/**
* A supporting icon to display within the toast.
*/
Icon?: ComponentType<SVGAttributes<SVGElement>>;
}
/**
* A temporary message shown in an overlay in the center of the screen.
*/
export const Toast: FC<Props> = ({
open,
onDismiss,
autoDismiss,
children,
Icon,
}) => {
const onOpenChange = useCallback(
(open: boolean) => {
if (!open) onDismiss();
},
[onDismiss]
);
useEffect(() => {
if (open && autoDismiss !== undefined) {
const timeout = setTimeout(onDismiss, autoDismiss);
return () => clearTimeout(timeout);
}
}, [open, autoDismiss, onDismiss]);
return (
<DialogRoot open={open} onOpenChange={onOpenChange}>
<DialogPortal>
<DialogOverlay
className={classNames(overlayStyles.bg, overlayStyles.animate)}
/>
<DialogContent asChild>
<DialogClose
className={classNames(
overlayStyles.overlay,
overlayStyles.animate,
styles.toast
)}
>
<DialogTitle asChild>
<Text as="h3" size="sm" weight="semibold">
{children}
</Text>
</DialogTitle>
{Icon && <Icon width={20} height={20} aria-hidden />}
</DialogClose>
</DialogContent>
</DialogPortal>
</DialogRoot>
);
};

View File

@@ -19,13 +19,13 @@ import { Button } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import UserAddSolidIcon from "@vector-im/compound-design-tokens/icons/user-add-solid.svg?react";
export const ShareButton: FC<
export const InviteButton: FC<
Omit<ComponentPropsWithoutRef<"button">, "children">
> = (props) => {
const { t } = useTranslation();
return (
<Button kind="secondary" size="sm" Icon={UserAddSolidIcon} {...props}>
{t("Share")}
{t("Invite")}
</Button>
);
};

View File

@@ -47,7 +47,7 @@ import { useEnableE2EE } from "../settings/useSetting";
import { useRoomAvatar } from "./useRoomAvatar";
import { useRoomName } from "./useRoomName";
import { useJoinRule } from "./useJoinRule";
import { ShareModal } from "./ShareModal";
import { InviteModal } from "./InviteModal";
declare global {
interface Window {
@@ -267,15 +267,15 @@ export function GroupCallView({
const joinRule = useJoinRule(rtcSession.room);
const [shareModalOpen, setShareModalOpen] = useState(false);
const onDismissShareModal = useCallback(
() => setShareModalOpen(false),
[setShareModalOpen]
const [shareModalOpen, setInviteModalOpen] = useState(false);
const onDismissInviteModal = useCallback(
() => setInviteModalOpen(false),
[setInviteModalOpen]
);
const onShareClickFn = useCallback(
() => setShareModalOpen(true),
[setShareModalOpen]
() => setInviteModalOpen(true),
[setInviteModalOpen]
);
const onShareClick = joinRule === JoinRule.Public ? onShareClickFn : null;
@@ -318,10 +318,10 @@ export function GroupCallView({
}
const shareModal = (
<ShareModal
<InviteModal
room={rtcSession.room}
open={shareModalOpen}
onDismiss={onDismissShareModal}
onDismiss={onDismissInviteModal}
/>
);

View File

@@ -69,7 +69,7 @@ import { useWakeLock } from "../useWakeLock";
import { useMergedRefs } from "../useMergedRefs";
import { MuteStates } from "./MuteStates";
import { MatrixInfo } from "./VideoPreview";
import { ShareButton } from "../button/ShareButton";
import { InviteButton } from "../button/InviteButton";
import { LayoutToggle } from "./LayoutToggle";
import {
ECAddonConnectionState,
@@ -416,7 +416,7 @@ export function InCallView({
</LeftNav>
<RightNav>
{!reducedControls && onShareClick !== null && (
<ShareButton onClick={onShareClick} />
<InviteButton onClick={onShareClick} />
)}
</RightNav>
</Header>

View File

@@ -14,6 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.copyButton {
.url {
text-align: center;
color: var(--cpd-color-text-secondary);
margin-block-end: var(--cpd-space-8x);
}
.button {
width: 100%;
}

84
src/room/InviteModal.tsx Normal file
View File

@@ -0,0 +1,84 @@
/*
Copyright 2022 - 2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { FC, MouseEvent, useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Room } from "matrix-js-sdk";
import { Button, Text } from "@vector-im/compound-web";
import LinkIcon from "@vector-im/compound-design-tokens/icons/link.svg?react";
import CheckIcon from "@vector-im/compound-design-tokens/icons/check.svg?react";
import useClipboard from "react-use-clipboard";
import { Modal } from "../Modal";
import { getAbsoluteRoomUrl } from "../matrix-utils";
import styles from "./InviteModal.module.css";
import { useRoomSharedKey } from "../e2ee/sharedKeyManagement";
import { Toast } from "../Toast";
interface Props {
room: Room;
open: boolean;
onDismiss: () => void;
}
export const InviteModal: FC<Props> = ({ room, open, onDismiss }) => {
const { t } = useTranslation();
const roomSharedKey = useRoomSharedKey(room.roomId);
const url = useMemo(
() =>
getAbsoluteRoomUrl(room.roomId, room.name, roomSharedKey ?? undefined),
[room, roomSharedKey]
);
const [, setCopied] = useClipboard(url);
const [toastOpen, setToastOpen] = useState(false);
const onToastDismiss = useCallback(() => setToastOpen(false), [setToastOpen]);
const onButtonClick = useCallback(
(e: MouseEvent) => {
e.stopPropagation();
setCopied();
onDismiss();
setToastOpen(true);
},
[setCopied, onDismiss]
);
return (
<>
<Modal title={t("Invite to this call")} open={open} onDismiss={onDismiss}>
<Text className={styles.url} size="sm" weight="semibold">
{url}
</Text>
<Button
className={styles.button}
Icon={LinkIcon}
onClick={onButtonClick}
data-testid="modal_inviteLink"
>
{t("Copy link")}
</Button>
</Modal>
<Toast
open={toastOpen}
onDismiss={onToastDismiss}
autoDismiss={2000}
Icon={CheckIcon}
>
{t("Link copied to clipboard")}
</Toast>
</>
);
};

View File

@@ -27,7 +27,7 @@ import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header";
import { useLocationNavigation } from "../useLocationNavigation";
import { MatrixInfo, VideoPreview } from "./VideoPreview";
import { MuteStates } from "./MuteStates";
import { ShareButton } from "../button/ShareButton";
import { InviteButton } from "../button/InviteButton";
import {
HangupButton,
MicButton,
@@ -108,7 +108,7 @@ export const LobbyView: FC<Props> = ({
/>
</LeftNav>
<RightNav>
{onShareClick !== null && <ShareButton onClick={onShareClick} />}
{onShareClick !== null && <InviteButton onClick={onShareClick} />}
</RightNav>
</Header>
)}

View File

@@ -1,51 +0,0 @@
/*
Copyright 2022 - 2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { FC } from "react";
import { useTranslation } from "react-i18next";
import { Room } from "matrix-js-sdk";
import { Modal } from "../Modal";
import { CopyButton } from "../button";
import { getAbsoluteRoomUrl } from "../matrix-utils";
import styles from "./ShareModal.module.css";
import { useRoomSharedKey } from "../e2ee/sharedKeyManagement";
interface Props {
room: Room;
open: boolean;
onDismiss: () => void;
}
export const ShareModal: FC<Props> = ({ room, open, onDismiss }) => {
const { t } = useTranslation();
const roomSharedKey = useRoomSharedKey(room.roomId);
return (
<Modal title={t("Share this call")} open={open} onDismiss={onDismiss}>
<p>{t("Copy and share this call link")}</p>
<CopyButton
className={styles.copyButton}
value={getAbsoluteRoomUrl(
room.roomId,
room.name,
roomSharedKey ?? undefined
)}
data-testid="modal_inviteLink"
/>
</Modal>
);
};

59
test/Toast-test.tsx Normal file
View File

@@ -0,0 +1,59 @@
/*
Copyright 2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { screen, render } from "@testing-library/react";
import { Toast } from "../src/Toast";
import userEvent from "@testing-library/user-event";
import { withFakeTimers } from "./utils";
test("Toast renders", () => {
render(
<Toast open={false} onDismiss={() => {}}>
Hello world!
</Toast>
);
expect(screen.queryByRole("dialog")).toBe(null);
render(
<Toast open={true} onDismiss={() => {}}>
Hello world!
</Toast>
);
expect(screen.getByRole("dialog")).toMatchSnapshot();
});
test("Toast dismisses when clicked", async () => {
const onDismiss = jest.fn();
render(
<Toast open={true} onDismiss={onDismiss}>
Hello world!
</Toast>
);
await userEvent.click(screen.getByRole("dialog"));
expect(onDismiss).toHaveBeenCalled();
});
test("Toast dismisses itself after the specified timeout", async () => {
withFakeTimers(() => {
const onDismiss = jest.fn();
render(
<Toast open={true} onDismiss={onDismiss} autoDismiss={2000}>
Hello world!
</Toast>
);
jest.advanceTimersByTime(2000);
expect(onDismiss).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Toast renders 1`] = `
<button
aria-describedby="radix-:r5:"
aria-labelledby="radix-:r4:"
class="overlay animate toast"
data-state="open"
id="radix-:r3:"
role="dialog"
style="pointer-events: auto;"
tabindex="-1"
type="button"
>
<h3
class="_font-body-sm-semibold_1jx6b_45"
id="radix-:r4:"
>
Hello world!
</h3>
</button>
`;

View File

@@ -18,15 +18,7 @@ import { Mocked, mocked } from "jest-mock";
import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { PosthogAnalytics } from "../../src/analytics/PosthogAnalytics";
import { checkForParallelCalls } from "../../src/room/checkForParallelCalls";
const withFakeTimers = (continuation: () => void) => {
jest.useFakeTimers();
try {
continuation();
} finally {
jest.useRealTimers();
}
};
import { withFakeTimers } from "../utils";
const withMockedPosthog = (
continuation: (posthog: Mocked<PosthogAnalytics>) => void

24
test/utils.ts Normal file
View File

@@ -0,0 +1,24 @@
/*
Copyright 2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export function withFakeTimers(continuation: () => void): void {
jest.useFakeTimers();
try {
continuation();
} finally {
jest.useRealTimers();
}
}

View File

@@ -65,7 +65,28 @@
semver "^5.4.1"
source-map "^0.5.0"
"@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.16.5", "@babel/core@^7.17.10", "@babel/core@^7.21.3", "@babel/core@^7.22.20", "@babel/core@^7.7.5":
"@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.16.5", "@babel/core@^7.17.10", "@babel/core@^7.7.5":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.20.tgz#e3d0eed84c049e2a2ae0a64d27b6a37edec385b7"
integrity sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.22.15"
"@babel/helper-compilation-targets" "^7.22.15"
"@babel/helper-module-transforms" "^7.22.20"
"@babel/helpers" "^7.22.15"
"@babel/parser" "^7.22.16"
"@babel/template" "^7.22.15"
"@babel/traverse" "^7.22.20"
"@babel/types" "^7.22.19"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/core@^7.21.3", "@babel/core@^7.22.20":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.0.tgz#f8259ae0e52a123eb40f552551e647b506a94d83"
integrity sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==
@@ -96,6 +117,16 @@
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/generator@^7.22.15", "@babel/generator@^7.7.2":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339"
integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==
dependencies:
"@babel/types" "^7.22.15"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/generator@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
@@ -106,16 +137,6 @@
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/generator@^7.7.2":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339"
integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==
dependencies:
"@babel/types" "^7.22.15"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882"
@@ -253,6 +274,17 @@
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/helper-validator-identifier" "^7.22.5"
"@babel/helper-module-transforms@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz#da9edc14794babbe7386df438f3768067132f59e"
integrity sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A==
dependencies:
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-module-imports" "^7.22.15"
"@babel/helper-simple-access" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/helper-validator-identifier" "^7.22.20"
"@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e"
@@ -353,6 +385,15 @@
"@babel/traverse" "^7.22.11"
"@babel/types" "^7.22.11"
"@babel/helpers@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1"
integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==
dependencies:
"@babel/template" "^7.22.15"
"@babel/traverse" "^7.22.15"
"@babel/types" "^7.22.15"
"@babel/helpers@^7.23.0":
version "7.23.1"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.1.tgz#44e981e8ce2b9e99f8f0b703f3326a4636c16d15"
@@ -381,7 +422,7 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.14.tgz#c7de58e8de106e88efca42ce17f0033209dfd245"
integrity sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ==
"@babel/parser@^7.14.7":
"@babel/parser@^7.14.7", "@babel/parser@^7.22.16":
version "7.22.16"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95"
integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==
@@ -1362,6 +1403,22 @@
debug "^4.1.0"
globals "^11.1.0"
"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.20.tgz#db572d9cb5c79e02d83e5618b82f6991c07584c9"
integrity sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.22.15"
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.22.5"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.22.16"
"@babel/types" "^7.22.19"
debug "^4.1.0"
globals "^11.1.0"
"@babel/traverse@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53"
@@ -1396,7 +1453,7 @@
"@babel/helper-validator-identifier" "^7.22.5"
to-fast-properties "^2.0.0"
"@babel/types@^7.3.3":
"@babel/types@^7.22.19", "@babel/types@^7.3.3":
version "7.22.19"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684"
integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==
@@ -4333,6 +4390,11 @@
"@testing-library/dom" "^9.0.0"
"@types/react-dom" "^18.0.0"
"@testing-library/user-event@^14.5.1":
version "14.5.1"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.1.tgz#27337d72046d5236b32fd977edee3f74c71d332f"
integrity sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"