aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/web/app/dashboard/tags/page.tsx16
-rw-r--r--apps/web/components/dashboard/bookmarks/TagsEditor.tsx233
-rw-r--r--apps/web/package.json1
-rw-r--r--packages/trpc/routers/tags.ts29
-rw-r--r--pnpm-lock.yaml200
5 files changed, 348 insertions, 131 deletions
diff --git a/apps/web/app/dashboard/tags/page.tsx b/apps/web/app/dashboard/tags/page.tsx
index 08acd968..dec11527 100644
--- a/apps/web/app/dashboard/tags/page.tsx
+++ b/apps/web/app/dashboard/tags/page.tsx
@@ -1,11 +1,8 @@
import Link from "next/link";
import { redirect } from "next/navigation";
import { Separator } from "@/components/ui/separator";
+import { api } from "@/server/api/client";
import { getServerAuthSession } from "@/server/auth";
-import { count, eq } from "drizzle-orm";
-
-import { db } from "@hoarder/db";
-import { bookmarkTags, tagsOnBookmarks } from "@hoarder/db/schema";
function TagPill({ name, count }: { name: string; count: number }) {
return (
@@ -24,16 +21,7 @@ export default async function TagsPage() {
redirect("/");
}
- let tags = await db
- .select({
- id: tagsOnBookmarks.tagId,
- name: bookmarkTags.name,
- count: count(),
- })
- .from(tagsOnBookmarks)
- .where(eq(bookmarkTags.userId, session.user.id))
- .groupBy(tagsOnBookmarks.tagId)
- .innerJoin(bookmarkTags, eq(bookmarkTags.id, tagsOnBookmarks.tagId));
+ let tags = (await api.tags.list()).tags;
// Sort tags by usage desc
tags = tags.sort((a, b) => b.count - a.count);
diff --git a/apps/web/components/dashboard/bookmarks/TagsEditor.tsx b/apps/web/components/dashboard/bookmarks/TagsEditor.tsx
index 12c0dcd0..38f01bdd 100644
--- a/apps/web/components/dashboard/bookmarks/TagsEditor.tsx
+++ b/apps/web/components/dashboard/bookmarks/TagsEditor.tsx
@@ -1,134 +1,137 @@
-import type { KeyboardEvent } from "react";
-import { useEffect, useState } from "react";
-import { Input } from "@/components/ui/input";
+import type { ActionMeta } from "react-select";
import { toast } from "@/components/ui/use-toast";
import { api } from "@/lib/trpc";
import { cn } from "@/lib/utils";
-import { Sparkles, X } from "lucide-react";
+import { Sparkles } from "lucide-react";
+import CreateableSelect from "react-select/creatable";
import type { ZBookmark } from "@hoarder/trpc/types/bookmarks";
import type { ZAttachedByEnum } from "@hoarder/trpc/types/tags";
interface EditableTag {
attachedBy: ZAttachedByEnum;
- id?: string;
- name: string;
-}
-
-function TagAddInput({ addTag }: { addTag: (tag: string) => void }) {
- const onKeyUp = (e: KeyboardEvent<HTMLInputElement>) => {
- if (e.key === "Enter") {
- addTag(e.currentTarget.value);
- e.currentTarget.value = "";
- }
- };
- return (
- <Input
- onKeyUp={onKeyUp}
- className="h-8 w-full border-none bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0"
- />
- );
-}
-
-function TagPill({
- tag,
- deleteCB,
-}: {
- tag: { attachedBy: ZAttachedByEnum; id?: string; name: string };
- deleteCB: () => void;
-}) {
- const isAttachedByAI = tag.attachedBy == "ai";
- return (
- <div
- className={cn(
- "flex min-h-8 space-x-1 rounded px-2",
- isAttachedByAI
- ? "bg-gradient-to-tr from-purple-500 to-purple-400 text-white"
- : "bg-gray-200",
- )}
- >
- {isAttachedByAI && <Sparkles className="m-auto size-4" />}
- <p className="m-auto">{tag.name}</p>
- <button className="m-auto size-4" onClick={deleteCB}>
- <X className="size-4" />
- </button>
- </div>
- );
+ value?: string;
+ label: string;
}
export function TagsEditor({ bookmark }: { bookmark: ZBookmark }) {
- const [tags, setTags] = useState<Map<string, EditableTag>>(new Map());
- useEffect(() => {
- const m = new Map<string, EditableTag>();
- for (const t of bookmark.tags) {
- m.set(t.name, { attachedBy: t.attachedBy, id: t.id, name: t.name });
- }
- setTags(m);
- }, [bookmark.tags]);
-
const bookmarkInvalidationFunction =
api.useUtils().bookmarks.getBookmark.invalidate;
- const { mutate } = api.bookmarks.updateTags.useMutation({
- onSuccess: () => {
- toast({
- description: "Tags has been updated!",
- });
- bookmarkInvalidationFunction({ bookmarkId: bookmark.id });
- // TODO(bug) Invalidate the tag views as well
- },
- onError: () => {
- toast({
- variant: "destructive",
- title: "Something went wrong",
- description: "There was a problem with your request.",
- });
- },
- });
+ const { mutate, isPending: isMutating } =
+ api.bookmarks.updateTags.useMutation({
+ onSuccess: () => {
+ toast({
+ description: "Tags has been updated!",
+ });
+ bookmarkInvalidationFunction({ bookmarkId: bookmark.id });
+ // TODO(bug) Invalidate the tag views as well
+ },
+ onError: () => {
+ toast({
+ variant: "destructive",
+ title: "Something went wrong",
+ description: "There was a problem with your request.",
+ });
+ },
+ });
+
+ const { data: existingTags, isLoading: isExistingTagsLoading } =
+ api.tags.list.useQuery();
+
+ const onChange = (
+ _option: readonly EditableTag[],
+ actionMeta: ActionMeta<EditableTag>,
+ ) => {
+ switch (actionMeta.action) {
+ case "remove-value": {
+ if (actionMeta.removedValue.value) {
+ mutate({
+ bookmarkId: bookmark.id,
+ attach: [],
+ detach: [{ tagId: actionMeta.removedValue.value }],
+ });
+ }
+ break;
+ }
+ case "create-option": {
+ mutate({
+ bookmarkId: bookmark.id,
+ attach: [{ tag: actionMeta.option.label }],
+ detach: [],
+ });
+ break;
+ }
+ case "select-option": {
+ if (actionMeta.option) {
+ mutate({
+ bookmarkId: bookmark.id,
+ attach: [
+ { tag: actionMeta.option.label, tagId: actionMeta.option?.value },
+ ],
+ detach: [],
+ });
+ }
+ break;
+ }
+ }
+ };
return (
- <div className="flex flex-wrap gap-2 rounded border p-2">
- {[...tags.values()].map((t) => (
- <TagPill
- key={t.name}
- tag={t}
- deleteCB={() => {
- setTags((m) => {
- const newMap = new Map(m);
- newMap.delete(t.name);
- if (t.id) {
- mutate({
- bookmarkId: bookmark.id,
- attach: [],
- detach: [{ tagId: t.id }],
- });
- }
- return newMap;
- });
- }}
- />
- ))}
- <div className="flex-1">
- <TagAddInput
- addTag={(val) => {
- setTags((m) => {
- if (m.has(val)) {
- // Tag already exists
- // Do nothing
- return m;
- }
- const newMap = new Map(m);
- newMap.set(val, { attachedBy: "human", name: val });
- mutate({
- bookmarkId: bookmark.id,
- attach: [{ tag: val }],
- detach: [],
- });
- return newMap;
- });
- }}
- />
- </div>
- </div>
+ <CreateableSelect
+ onChange={onChange}
+ options={
+ existingTags?.tags.map((t) => ({
+ label: t.name,
+ value: t.id,
+ attachedBy: "human" as const,
+ })) ?? []
+ }
+ value={bookmark.tags.map((t) => ({
+ label: t.name,
+ value: t.id,
+ attachedBy: t.attachedBy,
+ }))}
+ isMulti
+ closeMenuOnSelect={false}
+ isClearable={false}
+ isLoading={isExistingTagsLoading || isMutating}
+ styles={{
+ multiValueRemove: () => ({
+ "background-color": "transparent",
+ }),
+ valueContainer: (styles) => ({
+ ...styles,
+ padding: "0.5rem",
+ }),
+ }}
+ components={{
+ MultiValueContainer: ({ children, data }) => (
+ <div
+ className={cn(
+ "flex min-h-8 space-x-1 rounded px-2",
+ (data as { attachedBy: string }).attachedBy == "ai"
+ ? "bg-gradient-to-tr from-purple-500 to-purple-400 text-white"
+ : "bg-gray-200",
+ )}
+ >
+ {children}
+ </div>
+ ),
+ MultiValueLabel: ({ children, data }) => (
+ <div className="m-auto flex gap-2">
+ {(data as { attachedBy: string }).attachedBy == "ai" && (
+ <Sparkles className="m-auto size-4" />
+ )}
+ {children}
+ </div>
+ ),
+ }}
+ classNames={{
+ multiValueRemove: () => "my-auto",
+ valueContainer: () => "gap-2",
+ menuList: () => "text-sm",
+ }}
+ />
);
}
diff --git a/apps/web/package.json b/apps/web/package.json
index bfe9a5a2..4b0b5e0d 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -51,6 +51,7 @@
"react-hook-form": "^7.50.1",
"react-markdown": "^9.0.1",
"react-masonry-css": "^1.0.16",
+ "react-select": "^5.8.0",
"superjson": "^2.2.1",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
diff --git a/packages/trpc/routers/tags.ts b/packages/trpc/routers/tags.ts
index af11f34c..61b052d9 100644
--- a/packages/trpc/routers/tags.ts
+++ b/packages/trpc/routers/tags.ts
@@ -1,5 +1,5 @@
import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
-import { and, eq } from "drizzle-orm";
+import { and, count, eq } from "drizzle-orm";
import { z } from "zod";
import { bookmarks, bookmarkTags, tagsOnBookmarks } from "@hoarder/db/schema";
@@ -93,7 +93,32 @@ export const tagsAppRouter = router({
return {
id: res[0].id,
name: res[0].name,
- bookmarks: res.flatMap((t) => t.bookmarkId ? [t.bookmarkId] : []),
+ bookmarks: res.flatMap((t) => (t.bookmarkId ? [t.bookmarkId] : [])),
};
}),
+ list: authedProcedure
+ .output(
+ z.object({
+ tags: z.array(
+ z.object({
+ id: z.string(),
+ name: z.string(),
+ count: z.number(),
+ }),
+ ),
+ }),
+ )
+ .query(async ({ ctx }) => {
+ const tags = await ctx.db
+ .select({
+ id: tagsOnBookmarks.tagId,
+ name: bookmarkTags.name,
+ count: count(),
+ })
+ .from(tagsOnBookmarks)
+ .where(eq(bookmarkTags.userId, ctx.user.id))
+ .groupBy(tagsOnBookmarks.tagId)
+ .innerJoin(bookmarkTags, eq(bookmarkTags.id, tagsOnBookmarks.tagId));
+ return { tags };
+ }),
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 75e23664..72229bc1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -372,6 +372,9 @@ importers:
react-masonry-css:
specifier: ^1.0.16
version: 1.0.16(react@18.2.0)
+ react-select:
+ specifier: ^5.8.0
+ version: 5.8.0(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
superjson:
specifier: ^2.2.1
version: 2.2.1
@@ -2529,6 +2532,95 @@ packages:
react: 18.2.0
dev: false
+ /@emotion/babel-plugin@11.11.0:
+ resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==}
+ dependencies:
+ '@babel/helper-module-imports': 7.22.15
+ '@babel/runtime': 7.23.9
+ '@emotion/hash': 0.9.1
+ '@emotion/memoize': 0.8.1
+ '@emotion/serialize': 1.1.3
+ babel-plugin-macros: 3.1.0
+ convert-source-map: 1.9.0
+ escape-string-regexp: 4.0.0
+ find-root: 1.1.0
+ source-map: 0.5.7
+ stylis: 4.2.0
+ dev: false
+
+ /@emotion/cache@11.11.0:
+ resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==}
+ dependencies:
+ '@emotion/memoize': 0.8.1
+ '@emotion/sheet': 1.2.2
+ '@emotion/utils': 1.2.1
+ '@emotion/weak-memoize': 0.3.1
+ stylis: 4.2.0
+ dev: false
+
+ /@emotion/hash@0.9.1:
+ resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==}
+ dev: false
+
+ /@emotion/memoize@0.8.1:
+ resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
+ dev: false
+
+ /@emotion/react@11.11.4(@types/react@18.2.58)(react@18.2.0):
+ resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: '>=16.8.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@emotion/babel-plugin': 11.11.0
+ '@emotion/cache': 11.11.0
+ '@emotion/serialize': 1.1.3
+ '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
+ '@emotion/utils': 1.2.1
+ '@emotion/weak-memoize': 0.3.1
+ '@types/react': 18.2.58
+ hoist-non-react-statics: 3.3.2
+ react: 18.2.0
+ dev: false
+
+ /@emotion/serialize@1.1.3:
+ resolution: {integrity: sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==}
+ dependencies:
+ '@emotion/hash': 0.9.1
+ '@emotion/memoize': 0.8.1
+ '@emotion/unitless': 0.8.1
+ '@emotion/utils': 1.2.1
+ csstype: 3.1.3
+ dev: false
+
+ /@emotion/sheet@1.2.2:
+ resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==}
+ dev: false
+
+ /@emotion/unitless@0.8.1:
+ resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
+ dev: false
+
+ /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0):
+ resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ dependencies:
+ react: 18.2.0
+ dev: false
+
+ /@emotion/utils@1.2.1:
+ resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==}
+ dev: false
+
+ /@emotion/weak-memoize@0.3.1:
+ resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==}
+ dev: false
+
/@esbuild-kit/core-utils@3.3.2:
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
dependencies:
@@ -5888,6 +5980,10 @@ packages:
dependencies:
undici-types: 5.26.5
+ /@types/parse-json@4.0.2:
+ resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
+ dev: false
+
/@types/prop-types@15.7.11:
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
@@ -5896,6 +5992,12 @@ packages:
dependencies:
'@types/react': 18.2.58
+ /@types/react-transition-group@4.4.10:
+ resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
+ dependencies:
+ '@types/react': 18.2.58
+ dev: false
+
/@types/react@18.2.58:
resolution: {integrity: sha512-TaGvMNhxvG2Q0K0aYxiKfNDS5m5ZsoIBBbtfUorxdH4NGSXIlYvZxLJI+9Dd3KjeB3780bciLyAb7ylO8pLhPw==}
dependencies:
@@ -6909,6 +7011,15 @@ packages:
webpack: 5.90.3
dev: false
+ /babel-plugin-macros@3.1.0:
+ resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
+ engines: {node: '>=10', npm: '>=6'}
+ dependencies:
+ '@babel/runtime': 7.23.9
+ cosmiconfig: 7.1.0
+ resolve: 1.22.8
+ dev: false
+
/babel-plugin-polyfill-corejs2@0.4.8(@babel/core@7.23.9):
resolution: {integrity: sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==}
peerDependencies:
@@ -7830,6 +7941,10 @@ packages:
- supports-color
dev: false
+ /convert-source-map@1.9.0:
+ resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
+ dev: false
+
/convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
requiresBuild: true
@@ -7885,6 +8000,17 @@ packages:
parse-json: 4.0.0
dev: false
+ /cosmiconfig@7.1.0:
+ resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
+ engines: {node: '>=10'}
+ dependencies:
+ '@types/parse-json': 4.0.2
+ import-fresh: 3.3.0
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ yaml: 1.10.2
+ dev: false
+
/cosmiconfig@9.0.0(typescript@5.3.3):
resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==}
engines: {node: '>=14'}
@@ -8314,6 +8440,13 @@ packages:
dependencies:
esutils: 2.0.3
+ /dom-helpers@5.2.1:
+ resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
+ dependencies:
+ '@babel/runtime': 7.23.9
+ csstype: 3.1.3
+ dev: false
+
/dom-serializer@2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
dependencies:
@@ -9912,6 +10045,10 @@ packages:
pkg-dir: 4.2.0
dev: false
+ /find-root@1.1.0:
+ resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
+ dev: false
+
/find-up@3.0.0:
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
engines: {node: '>=6'}
@@ -10492,6 +10629,12 @@ packages:
source-map: 0.7.4
dev: false
+ /hoist-non-react-statics@3.3.2:
+ resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
+ dependencies:
+ react-is: 16.13.1
+ dev: false
+
/hosted-git-info@3.0.8:
resolution: {integrity: sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==}
engines: {node: '>=10'}
@@ -14770,6 +14913,27 @@ packages:
react: 18.2.0
dev: false
+ /react-select@5.8.0(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-TfjLDo58XrhP6VG5M/Mi56Us0Yt8X7xD6cDybC7yoRMUNm7BGO7qk8J0TLQOua/prb8vUOtsfnXZwfm30HGsAA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ '@babel/runtime': 7.23.9
+ '@emotion/cache': 11.11.0
+ '@emotion/react': 11.11.4(@types/react@18.2.58)(react@18.2.0)
+ '@floating-ui/dom': 1.6.3
+ '@types/react-transition-group': 4.4.10
+ memoize-one: 6.0.0
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0)
+ use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.58)(react@18.2.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ dev: false
+
/react-shallow-renderer@16.15.0(react@18.2.0):
resolution: {integrity: sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==}
peerDependencies:
@@ -14797,6 +14961,20 @@ packages:
tslib: 2.6.2
dev: false
+ /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
+ peerDependencies:
+ react: '>=16.6.0'
+ react-dom: '>=16.6.0'
+ dependencies:
+ '@babel/runtime': 7.23.9
+ dom-helpers: 5.2.1
+ loose-envify: 1.4.0
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}
@@ -15846,6 +16024,10 @@ packages:
react: 18.2.0
dev: false
+ /stylis@4.2.0:
+ resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
+ dev: false
+
/sucrase@3.34.0:
resolution: {integrity: sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==}
engines: {node: '>=8'}
@@ -16745,6 +16927,19 @@ packages:
react: 18.2.0
dev: false
+ /use-isomorphic-layout-effect@1.1.2(@types/react@18.2.58)(react@18.2.0):
+ resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.58
+ react: 18.2.0
+ dev: false
+
/use-latest-callback@0.1.9(react@18.2.0):
resolution: {integrity: sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw==}
peerDependencies:
@@ -17577,6 +17772,11 @@ packages:
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+ /yaml@1.10.2:
+ resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
+ engines: {node: '>= 6'}
+ dev: false
+
/yaml@2.4.0:
resolution: {integrity: sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==}
engines: {node: '>= 14'}