mirror of
https://github.com/StanGirard/quivr.git
synced 2024-11-23 21:22:35 +03:00
[ShareableBrain] User email and role inputs form (#608)
* feat: add invitation emails form * test(ShareBrain): add tests
This commit is contained in:
parent
cef45ea712
commit
783f8dea76
@ -1,28 +1,16 @@
|
||||
import { UUID } from "crypto";
|
||||
import { MdDelete } from "react-icons/md";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
|
||||
import { ShareBrain } from "./ShareBrain";
|
||||
import { DeleteBrain, ShareBrain } from "./components";
|
||||
|
||||
type BrainActionsProps = {
|
||||
brainId: UUID;
|
||||
};
|
||||
|
||||
export const BrainActions = ({ brainId }: BrainActionsProps): JSX.Element => {
|
||||
const { deleteBrain } = useBrainContext();
|
||||
|
||||
return (
|
||||
<div className="absolute right-0 flex flex-row">
|
||||
<ShareBrain brainId={brainId} />
|
||||
<Button
|
||||
className="group-hover:visible invisible hover:text-red-500 transition-[colors,opacity] p-1"
|
||||
onClick={() => void deleteBrain(brainId)}
|
||||
variant={"tertiary"}
|
||||
>
|
||||
<MdDelete className="text-xl" />
|
||||
</Button>
|
||||
<DeleteBrain brainId={brainId} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,47 +0,0 @@
|
||||
import { UUID } from "crypto";
|
||||
import { MdContentPaste, MdShare } from "react-icons/md";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Modal from "@/lib/components/ui/Modal";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
|
||||
type ShareBrainModalProps = {
|
||||
brainId: UUID;
|
||||
};
|
||||
|
||||
export const ShareBrain = ({ brainId }: ShareBrainModalProps): JSX.Element => {
|
||||
const { publish } = useToast();
|
||||
|
||||
const baseUrl = window.location.origin;
|
||||
const brainShareLink = `${baseUrl}/brain_subscription_invitation=${brainId}`;
|
||||
|
||||
const handleCopyInvitationLink = async () => {
|
||||
await navigator.clipboard.writeText(brainShareLink);
|
||||
publish({
|
||||
variant: "success",
|
||||
text: "Copied to clipboard",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
Trigger={
|
||||
<Button
|
||||
className="group-hover:visible invisible hover:text-red-500 transition-[colors,opacity] p-1"
|
||||
onClick={() => void 0}
|
||||
variant={"tertiary"}
|
||||
>
|
||||
<MdShare className="text-xl" />
|
||||
</Button>
|
||||
}
|
||||
title="Share brain"
|
||||
>
|
||||
<div className="flex flex-row align-center my-5">
|
||||
<p>{brainShareLink}</p>
|
||||
<Button onClick={() => void handleCopyInvitationLink()}>
|
||||
<MdContentPaste />
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
import { UUID } from "crypto";
|
||||
import { MdDelete } from "react-icons/md";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
|
||||
export const DeleteBrain = ({ brainId }: { brainId: UUID }): JSX.Element => {
|
||||
const { deleteBrain } = useBrainContext();
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="group-hover:visible invisible hover:text-red-500 transition-[colors,opacity] p-1"
|
||||
onClick={() => void deleteBrain(brainId)}
|
||||
variant={"tertiary"}
|
||||
>
|
||||
<MdDelete className="text-xl" />
|
||||
</Button>
|
||||
);
|
||||
};
|
@ -0,0 +1,97 @@
|
||||
"use client";
|
||||
|
||||
import { UUID } from "crypto";
|
||||
import { ImUserPlus } from "react-icons/im";
|
||||
import { MdContentPaste, MdShare } from "react-icons/md";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Modal from "@/lib/components/ui/Modal";
|
||||
|
||||
import { InvitedUserRow } from "./components/InvitedUserRow";
|
||||
import { useShareBrain } from "./hooks/useShareBrain";
|
||||
|
||||
type ShareBrainModalProps = {
|
||||
brainId: UUID;
|
||||
};
|
||||
|
||||
export const ShareBrain = ({ brainId }: ShareBrainModalProps): JSX.Element => {
|
||||
const {
|
||||
roleAssignations,
|
||||
brainShareLink,
|
||||
handleCopyInvitationLink,
|
||||
updateRoleAssignation,
|
||||
removeRoleAssignation,
|
||||
inviteUsers,
|
||||
addNewRoleAssignationRole,
|
||||
} = useShareBrain(brainId);
|
||||
|
||||
const canAddNewRow =
|
||||
roleAssignations.length === 0 ||
|
||||
roleAssignations.filter((invitingUser) => invitingUser.email === "")
|
||||
.length === 0;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
Trigger={
|
||||
<Button
|
||||
className="group-hover:visible invisible hover:text-red-500 transition-[colors,opacity] p-1"
|
||||
onClick={() => void 0}
|
||||
variant={"tertiary"}
|
||||
data-testId="share-brain-button"
|
||||
>
|
||||
<MdShare className="text-xl" />
|
||||
</Button>
|
||||
}
|
||||
CloseTrigger={<div />}
|
||||
title="Share brain"
|
||||
>
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
void inviteUsers();
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div className="flex flex-row align-center my-5">
|
||||
<div className="flex bg-gray-100 p-3 rounded flex-1 flex-row border-b border-gray-200 dark:border-gray-700 justify-space-between align-center">
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<p className="flex-1 color-gray-500">{brainShareLink}</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => void handleCopyInvitationLink()}
|
||||
>
|
||||
<MdContentPaste />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-100 h-0.5 mb-5 border-gray-200 dark:border-gray-700" />
|
||||
|
||||
{roleAssignations.map((roleAssignation, index) => (
|
||||
<InvitedUserRow
|
||||
key={roleAssignation.id}
|
||||
onChange={updateRoleAssignation(index)}
|
||||
removeCurrentInvitation={removeRoleAssignation(index)}
|
||||
roleAssignation={roleAssignation}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
className="my-5"
|
||||
onClick={addNewRoleAssignationRole}
|
||||
disabled={!canAddNewRow}
|
||||
data-testid="add-new-row-role-button"
|
||||
>
|
||||
<ImUserPlus />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 flex flex-row justify-end">
|
||||
<Button disabled={roleAssignations.length === 0} type="submit">
|
||||
Share
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
@ -0,0 +1,50 @@
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { ShareBrain } from "../ShareBrain";
|
||||
|
||||
describe("ShareBrain", () => {
|
||||
it("should render ShareBrain component properly", () => {
|
||||
const { getByTestId } = render(
|
||||
<ShareBrain brainId="cf9bb422-b1b6-4fd7-abc1-01bd395d2318" />
|
||||
);
|
||||
const shareButton = getByTestId("share-brain-button");
|
||||
expect(shareButton).toBeDefined();
|
||||
});
|
||||
|
||||
it("should render open share modal when share button is clicked", () => {
|
||||
const { getByText, getByTestId } = render(
|
||||
<ShareBrain brainId="cf9bb422-b1b6-4fd7-abc1-01bd395d2318" />
|
||||
);
|
||||
const shareButton = getByTestId("share-brain-button");
|
||||
fireEvent.click(shareButton);
|
||||
expect(getByText("Share brain")).toBeDefined();
|
||||
});
|
||||
|
||||
it('shoud add new user row when "Add new user" button is clicked and only where there is no empty field', async () => {
|
||||
const { getByTestId, findAllByTestId } = render(
|
||||
<ShareBrain brainId="cf9bb422-b1b6-4fd7-abc1-01bd395d2318" />
|
||||
);
|
||||
const shareButton = getByTestId("share-brain-button");
|
||||
fireEvent.click(shareButton);
|
||||
|
||||
let assignationRows = await findAllByTestId("assignation-row");
|
||||
|
||||
expect(assignationRows.length).toBe(1);
|
||||
|
||||
const firstAssignationRowEmailInput = (
|
||||
await findAllByTestId("role-assignation-email-input")
|
||||
)[0];
|
||||
|
||||
fireEvent.change(firstAssignationRowEmailInput, {
|
||||
target: { value: "user@quivr.app" },
|
||||
});
|
||||
|
||||
const addNewRoleAssignationButton = getByTestId("add-new-row-role-button");
|
||||
fireEvent.click(addNewRoleAssignationButton);
|
||||
|
||||
assignationRows = await findAllByTestId("assignation-row");
|
||||
|
||||
expect(assignationRows.length).toBe(2);
|
||||
});
|
||||
});
|
@ -0,0 +1,69 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { MdOutlineRemoveCircle } from "react-icons/md";
|
||||
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
import { Select } from "@/lib/components/ui/Select";
|
||||
|
||||
import { BrainRoleAssignation, BrainRoleType } from "../../../types";
|
||||
|
||||
type AddUserRowProps = {
|
||||
onChange: (newRole: BrainRoleAssignation) => void;
|
||||
removeCurrentInvitation?: () => void;
|
||||
roleAssignation: BrainRoleAssignation;
|
||||
};
|
||||
|
||||
type SelectOptionsProps = {
|
||||
label: string;
|
||||
value: BrainRoleType;
|
||||
};
|
||||
const SelectOptions: SelectOptionsProps[] = [
|
||||
{ label: "Viewer", value: "viewer" },
|
||||
{ label: "Editor", value: "editor" },
|
||||
];
|
||||
|
||||
export const InvitedUserRow = ({
|
||||
onChange,
|
||||
removeCurrentInvitation,
|
||||
roleAssignation,
|
||||
}: AddUserRowProps): JSX.Element => {
|
||||
const [selectedRole, setSelectedRole] = useState<BrainRoleType>(
|
||||
roleAssignation.role
|
||||
);
|
||||
const [email, setEmail] = useState(roleAssignation.email);
|
||||
|
||||
useEffect(() => {
|
||||
onChange({
|
||||
...roleAssignation,
|
||||
email,
|
||||
role: selectedRole,
|
||||
});
|
||||
}, [email, selectedRole]);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="assignation-row"
|
||||
className="flex flex-row align-center my-2 gap-3 items-center"
|
||||
>
|
||||
<div className="cursor-pointer" onClick={removeCurrentInvitation}>
|
||||
<MdOutlineRemoveCircle />
|
||||
</div>
|
||||
<div className="flex flex-1">
|
||||
<Field
|
||||
name="email"
|
||||
required
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
value={email}
|
||||
onBlur={() => email === "" && removeCurrentInvitation?.()}
|
||||
data-testid="role-assignation-email-input"
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
onChange={setSelectedRole}
|
||||
value={selectedRole}
|
||||
options={SelectOptions}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from "./InvitedUserRow";
|
@ -0,0 +1,83 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { useToast } from "@/lib/hooks";
|
||||
|
||||
import { BrainRoleAssignation } from "../../../types";
|
||||
import { generateBrainAssignation } from "../utils/generateBrainAssignation";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useShareBrain = (brainId: string) => {
|
||||
const baseUrl = window.location.origin;
|
||||
const { publish } = useToast();
|
||||
|
||||
const brainShareLink = `${baseUrl}/brain_subscription_invitation=${brainId}`;
|
||||
|
||||
const [roleAssignations, setRoleAssignation] = useState<
|
||||
BrainRoleAssignation[]
|
||||
>([generateBrainAssignation()]);
|
||||
|
||||
const handleCopyInvitationLink = async () => {
|
||||
await navigator.clipboard.writeText(brainShareLink);
|
||||
publish({
|
||||
variant: "success",
|
||||
text: "Copied to clipboard",
|
||||
});
|
||||
};
|
||||
|
||||
const removeRoleAssignation = (assignationIndex: number) => () => {
|
||||
if (roleAssignations.length === 1) {
|
||||
return;
|
||||
}
|
||||
setRoleAssignation(
|
||||
roleAssignations.filter((_, index) => index !== assignationIndex)
|
||||
);
|
||||
};
|
||||
|
||||
const updateRoleAssignation =
|
||||
(rowIndex: number) => (data: BrainRoleAssignation) => {
|
||||
const concernedRow = roleAssignations[rowIndex];
|
||||
|
||||
if (concernedRow !== undefined) {
|
||||
setRoleAssignation(
|
||||
roleAssignations.map((row, index) => {
|
||||
if (index === rowIndex) {
|
||||
return data;
|
||||
}
|
||||
|
||||
return row;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
setRoleAssignation([...roleAssignations, data]);
|
||||
}
|
||||
};
|
||||
|
||||
const inviteUsers = (): void => {
|
||||
const inviteUsersPayload = roleAssignations
|
||||
.filter(({ email }) => email !== "")
|
||||
.map((assignation) => ({
|
||||
email: assignation.email,
|
||||
role: assignation.role,
|
||||
}));
|
||||
|
||||
alert(
|
||||
`You will soon be able to invite ${JSON.stringify(
|
||||
inviteUsersPayload
|
||||
)}. Wait a bit`
|
||||
);
|
||||
};
|
||||
|
||||
const addNewRoleAssignationRole = () => {
|
||||
setRoleAssignation([...roleAssignations, generateBrainAssignation()]);
|
||||
};
|
||||
|
||||
return {
|
||||
roleAssignations,
|
||||
brainShareLink,
|
||||
handleCopyInvitationLink,
|
||||
updateRoleAssignation,
|
||||
removeRoleAssignation,
|
||||
inviteUsers,
|
||||
addNewRoleAssignationRole,
|
||||
};
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from "./ShareBrain";
|
@ -0,0 +1,9 @@
|
||||
import { BrainRoleAssignation } from "../../../types";
|
||||
|
||||
export const generateBrainAssignation = (): BrainRoleAssignation => {
|
||||
return {
|
||||
email: "",
|
||||
role: "viewer",
|
||||
id: Math.random().toString(),
|
||||
};
|
||||
};
|
@ -0,0 +1,2 @@
|
||||
export * from "./DeleteBrain";
|
||||
export * from "./ShareBrain";
|
@ -0,0 +1,9 @@
|
||||
export const roles = ["viewer", "editor"];
|
||||
|
||||
export type BrainRoleType = (typeof roles)[number];
|
||||
|
||||
export type BrainRoleAssignation = {
|
||||
email: string;
|
||||
role: BrainRoleType;
|
||||
id: string;
|
||||
};
|
96
frontend/lib/components/ui/Select.tsx
Normal file
96
frontend/lib/components/ui/Select.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { BsCheckCircleFill } from "react-icons/bs";
|
||||
|
||||
import Popover from "@/lib/components/ui/Popover";
|
||||
|
||||
type SelectOptionProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
options: SelectOptionProps[];
|
||||
value?: SelectOptionProps["value"];
|
||||
onChange: (option: SelectOptionProps["value"]) => void;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
const selectedStyle = "rounded-lg bg-black text-white";
|
||||
|
||||
export const Select = ({
|
||||
onChange,
|
||||
options,
|
||||
value,
|
||||
label,
|
||||
}: SelectProps): JSX.Element => {
|
||||
const selectedValueLabel = options.find(
|
||||
(option) => option.value === value
|
||||
)?.label;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{label !== undefined && (
|
||||
<label
|
||||
id="listbox-label"
|
||||
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className="relative">
|
||||
<Popover
|
||||
Trigger={
|
||||
<button
|
||||
type="button"
|
||||
className="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:text-sm sm:leading-6"
|
||||
aria-haspopup="listbox"
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<span className="ml-3 block truncate">
|
||||
{selectedValueLabel ?? label ?? "Select"}
|
||||
</span>
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2">
|
||||
<svg
|
||||
className="h-5 w-5 text-gray-400"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
CloseTrigger={<div />}
|
||||
>
|
||||
<ul role="listbox">
|
||||
{options.map((option) => (
|
||||
<li
|
||||
className="text-gray-900 relative cursor-pointer select-none py-2"
|
||||
id="listbox-option-0"
|
||||
key={option.value}
|
||||
onClick={() => onChange(option.value)}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center px-3 py-2 ${
|
||||
value === option.value && selectedStyle
|
||||
}`}
|
||||
>
|
||||
<span className="font-bold block truncate mr-2">
|
||||
{option.label}
|
||||
</span>
|
||||
{value === option.value && <BsCheckCircleFill />}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -22,6 +22,7 @@
|
||||
"@june-so/analytics-next": "^2.0.0",
|
||||
"@radix-ui/react-dialog": "^1.0.3",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
"@radix-ui/react-select": "^1.2.2",
|
||||
"@radix-ui/react-toast": "^1.1.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
"@sentry/nextjs": "^7.57.0",
|
||||
|
@ -742,6 +742,13 @@
|
||||
picocolors "^1.0.0"
|
||||
tslib "^2.5.0"
|
||||
|
||||
"@radix-ui/number@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.1.tgz#644161a3557f46ed38a042acf4a770e826021674"
|
||||
integrity sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/primitive@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd"
|
||||
@ -803,6 +810,13 @@
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
|
||||
"@radix-ui/react-direction@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b"
|
||||
integrity sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-dismissable-layer@1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.4.tgz#883a48f5f938fa679427aa17fcba70c5494c6978"
|
||||
@ -904,6 +918,34 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
|
||||
"@radix-ui/react-select@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-1.2.2.tgz#caa981fa0d672cf3c1b2a5240135524e69b32181"
|
||||
integrity sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/number" "1.0.1"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-collection" "1.0.3"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-direction" "1.0.1"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.4"
|
||||
"@radix-ui/react-focus-guards" "1.0.1"
|
||||
"@radix-ui/react-focus-scope" "1.0.3"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-popper" "1.1.2"
|
||||
"@radix-ui/react-portal" "1.0.3"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
"@radix-ui/react-use-previous" "1.0.1"
|
||||
"@radix-ui/react-visually-hidden" "1.0.3"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
|
||||
"@radix-ui/react-slot@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab"
|
||||
@ -980,6 +1022,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-previous@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz#b595c087b07317a4f143696c6a01de43b0d0ec66"
|
||||
integrity sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-rect@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2"
|
||||
|
Loading…
Reference in New Issue
Block a user