Quellcode durchsuchen

@uppy/react: remove `useUppy` & reintroduce `useUppyState` (#5059)

Merlijn Vos vor 1 Jahr
Ursprung
Commit
915a563da9

+ 1 - 1
bin/build-lib.js

@@ -10,7 +10,7 @@ const { mkdir, stat, writeFile } = fs.promises
 const PACKAGE_JSON_IMPORT = /^\..*\/package.json$/
 const SOURCE = 'packages/{*,@uppy/*}/src/**/*.{js,ts}?(x)'
 // Files not to build (such as tests)
-const IGNORE = /\.test\.[jt]s$|__mocks__|svelte|angular|companion\//
+const IGNORE = /\.test\.jsx?$|\.test\.tsx?$|__mocks__|svelte|angular|companion\//;
 // Files that should trigger a rebuild of everything on change
 const META_FILES = [
   'babel.config.js',

+ 1 - 6
docs/framework-integrations/react.mdx

@@ -61,9 +61,6 @@ The following components are exported from `@uppy/react`:
 - `<ProgressBar />` renders [`@uppy/progress-bar`](/docs/progress-bar)
 - `<StatusBar />` renders [`@uppy/status-bar`](/docs/status-bar)
 
-{/* prettier-ignore */}
-{/* Commented out until the hook is live
-
 ### Hooks
 
 `useUppyState(uppy, selector)`
@@ -86,12 +83,10 @@ const metaFields = useUppyState(
 ```
 
 You can see all the values you can access on the
-[`State`](https://github.com/transloadit/uppy/blob/main/packages/%40uppy/core/types/index.d.ts#L190)
+[`State`](https://github.com/transloadit/uppy/blob/c45407d099d87e25cecaf03c5d9ce59c582ca0dc/packages/%40uppy/core/src/Uppy.ts#L155-L181)
 type. If you are accessing plugin state, you would have to look at the types of
 the plugin.
 
-\*/}
-
 ## Examples
 
 ### Example: basic component

+ 20 - 9
e2e/clients/react/App.jsx

@@ -10,24 +10,35 @@ import '@uppy/core/dist/style.css'
 import '@uppy/dashboard/dist/style.css'
 import '@uppy/drag-drop/dist/style.css'
 
-export default function App () {
-  const RemoteSourcesOptions = {
-    companionUrl: 'http://companion.uppy.io',
-    sources: ['GoogleDrive', 'OneDrive', 'Unsplash', 'Zoom', 'Url'],
-  }
-  const uppyDashboard = new Uppy({ id: 'dashboard' }).use(RemoteSources, { ...RemoteSourcesOptions })
-  const uppyModal = new Uppy({ id: 'modal' })
-  const uppyDragDrop = new Uppy({ id: 'drag-drop' }).use(ThumbnailGenerator)
+const uppyDashboard = new Uppy({ id: 'dashboard' }).use(RemoteSources, {
+  companionUrl: 'http://companion.uppy.io',
+  sources: ['GoogleDrive', 'OneDrive', 'Unsplash', 'Zoom', 'Url'],
+})
+const uppyModal = new Uppy({ id: 'modal' })
+const uppyDragDrop = new Uppy({ id: 'drag-drop' }).use(ThumbnailGenerator)
+
+export default function App() {
   const [open, setOpen] = useState(false)
+  // TODO: Parcel is having a bad time resolving React inside @uppy/react for some reason.
+  // We are using Parcel in an odd way and I don't think there is an easy fix.
+  // const files = useUppyState(uppyDashboard, (state) => state.files)
 
   // drag-drop has no visual output so we test it via the uppy instance
   window.uppy = uppyDragDrop
 
   return (
-    <div style={{ maxWidth: '30em', margin: '5em 0', display: 'grid', gridGap: '2em' }}>
+    <div
+      style={{
+        maxWidth: '30em',
+        margin: '5em 0',
+        display: 'grid',
+        gridGap: '2em',
+      }}
+    >
       <button type="button" id="open" onClick={() => setOpen(!open)}>
         Open Modal
       </button>
+      {/* <p>Dashboard file count: {Object.keys(files).length}</p> */}
 
       <Dashboard id="dashboard" uppy={uppyDashboard} />
       <DashboardModal id="modal" open={open} uppy={uppyModal} />

+ 1 - 1
package.json

@@ -168,7 +168,7 @@
   },
   "resolutions": {
     "@types/eslint@^7.2.13": "^8.2.0",
-    "@types/react": "^17",
+    "@types/react": "^18",
     "@types/webpack-dev-server": "^4",
     "@vitest/utils": "patch:@vitest/utils@npm%3A1.2.1#./.yarn/patches/@vitest-utils-npm-1.2.1-3028846845.patch",
     "p-queue": "patch:p-queue@npm%3A7.4.1#./.yarn/patches/p-queue-npm-7.4.1-e0cf0a6f17.patch",

+ 8 - 3
packages/@uppy/react/package.json

@@ -21,11 +21,16 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "workspace:^"
+    "@uppy/utils": "workspace:^",
+    "use-sync-external-store": "^1.2.0"
   },
   "devDependencies": {
+    "@testing-library/jest-dom": "^6.4.2",
+    "@testing-library/react": "^14.2.2",
     "@types/react": "^18.0.8",
-    "react": "^18.1.0"
+    "@types/use-sync-external-store": "^0.0.6",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0"
   },
   "peerDependencies": {
     "@uppy/core": "workspace:^",
@@ -34,7 +39,7 @@
     "@uppy/file-input": "workspace:^",
     "@uppy/progress-bar": "workspace:^",
     "@uppy/status-bar": "workspace:^",
-    "react": "^16.0.0 || ^17.0.0 || ^18.0.0"
+    "react": "^18.0.0"
   },
   "peerDependenciesMeta": {
     "@uppy/dashboard": {

+ 1 - 1
packages/@uppy/react/src/index.ts

@@ -4,4 +4,4 @@ export { default as DragDrop } from './DragDrop.ts'
 export { default as ProgressBar } from './ProgressBar.ts'
 export { default as StatusBar } from './StatusBar.ts'
 export { default as FileInput } from './FileInput.ts'
-export { default as useUppy } from './useUppy.ts'
+export { default as useUppyState } from './useUppyState.ts'

+ 0 - 36
packages/@uppy/react/src/useUppy.ts

@@ -1,36 +0,0 @@
-import { useEffect, useRef } from 'react'
-import { Uppy as UppyCore } from '@uppy/core'
-import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
-
-/**
- * @deprecated Initialize Uppy outside of the component.
- */
-export default function useUppy<M extends Meta, B extends Body>(
-  factory: () => UppyCore<M, B>,
-): UppyCore<M, B> | undefined {
-  if (typeof factory !== 'function') {
-    throw new TypeError(
-      'useUppy: expected a function that returns a new Uppy instance',
-    )
-  }
-
-  const uppy = useRef<UppyCore<M, B> | undefined>(undefined)
-  if (uppy.current === undefined) {
-    uppy.current = factory()
-
-    if (!(uppy.current instanceof UppyCore)) {
-      throw new TypeError(
-        `useUppy: factory function must return an Uppy instance, got ${typeof uppy.current}`,
-      )
-    }
-  }
-
-  useEffect(() => {
-    return () => {
-      uppy.current?.close({ reason: 'unmount' })
-      uppy.current = undefined
-    }
-  }, [uppy])
-
-  return uppy.current
-}

+ 46 - 0
packages/@uppy/react/src/useUppyState.test.tsx

@@ -0,0 +1,46 @@
+/* eslint-disable react/react-in-jsx-scope */
+/* eslint-disable import/no-extraneous-dependencies */
+import React from 'react'
+import { describe, expect, expectTypeOf, it } from 'vitest'
+import { renderHook, render, act } from '@testing-library/react'
+import Uppy from '@uppy/core'
+
+import useUppyState from './useUppyState.ts'
+
+describe('useUppyState', () => {
+  it('should return and update value with the correct type', () => {
+    const uppy = new Uppy()
+    const { result, rerender } = renderHook(() =>
+      useUppyState(uppy, (state) => state.totalProgress),
+    )
+    expectTypeOf(result.current).toEqualTypeOf<number>()
+    expect(result.current).toBe(0)
+    act(() => uppy.setState({ totalProgress: 50 }))
+    rerender()
+    expect(result.current).toBe(50)
+    rerender()
+    expect(result.current).toBe(50)
+  })
+
+  it('does not re-render unnecessarily', () => {
+    const uppy = new Uppy()
+    let renderCount = 0
+
+    const Component = React.memo((props: { uppy: Uppy<any, any> }) => {
+      const files = useUppyState(props.uppy, (state) =>
+        Object.values(state.files),
+      )
+      renderCount++
+      return <div>{files.length}</div>
+    })
+
+    const { rerender } = render(<Component uppy={uppy} />)
+    expect(renderCount).toBe(1)
+    // Re-render without updating Uppy's state
+    rerender(<Component uppy={uppy} />)
+    // It should have been memoized
+    expect(renderCount).toBe(1)
+    act(() => uppy.addFile({ name: 'file', data: new Blob() }))
+    expect(renderCount).toBe(2)
+  })
+})

+ 23 - 0
packages/@uppy/react/src/useUppyState.ts

@@ -0,0 +1,23 @@
+import type { Uppy, State } from '@uppy/core'
+import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
+import { useMemo, useCallback } from 'react'
+import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector'
+
+export default function useUppyState<
+  M extends Meta = Meta,
+  B extends Body = Body,
+  T = any,
+>(uppy: Uppy<M, B>, selector: (state: State<M, B>) => T): T {
+  const subscribe = useMemo(
+    () => uppy.store.subscribe.bind(uppy.store),
+    [uppy.store],
+  )
+  const getSnapshot = useCallback(() => uppy.store.getState(), [uppy.store])
+
+  return useSyncExternalStoreWithSelector(
+    subscribe,
+    getSnapshot,
+    null,
+    selector,
+  )
+}

+ 3 - 1
packages/@uppy/react/tsconfig.build.json

@@ -3,6 +3,8 @@
   "compilerOptions": {
     "noImplicitAny": false,
     "outDir": "./lib",
+    "jsxImportSource": "react",
+    "jsx": "react-jsx",
     "paths": {
       "@uppy/utils/lib/*": ["../utils/src/*"],
       "@uppy/core": ["../core/src/index.js"],
@@ -23,7 +25,7 @@
     "skipLibCheck": true
   },
   "include": ["./src/**/*.*"],
-  "exclude": ["./src/**/*.test.ts"],
+  "exclude": ["./src/**/*.test.tsx"],
   "references": [
     {
       "path": "../utils/tsconfig.build.json"

+ 2 - 0
packages/@uppy/react/tsconfig.json

@@ -3,6 +3,8 @@
   "compilerOptions": {
     "emitDeclarationOnly": false,
     "noEmit": true,
+    "jsxImportSource": "react",
+    "jsx": "react-jsx",
     "paths": {
       "@uppy/utils/lib/*": ["../utils/src/*"],
       "@uppy/core": ["../core/src/index.js"],

+ 176 - 26
yarn.lock

@@ -12,6 +12,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@adobe/css-tools@npm:^4.3.2":
+  version: 4.3.3
+  resolution: "@adobe/css-tools@npm:4.3.3"
+  checksum: d21f3786b84911fee59c995a146644a85c98692979097b26484ffa9e442fb1a92ccd68ce984e3e7cf8d5933c3560fbc0ad3e3cd1de50b9a723d1c012e793bbcb
+  languageName: node
+  linkType: hard
+
 "@ampproject/remapping@npm:2.3.0":
   version: 2.3.0
   resolution: "@ampproject/remapping@npm:2.3.0"
@@ -3833,6 +3840,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@babel/runtime@npm:^7.12.5":
+  version: 7.24.1
+  resolution: "@babel/runtime@npm:7.24.1"
+  dependencies:
+    regenerator-runtime: ^0.14.0
+  checksum: 5c8f3b912ba949865f03b3cf8395c60e1f4ebd1033fbd835bdfe81b6cac8a87d85bc3c7aded5fcdf07be044c9ab8c818f467abe0deca50020c72496782639572
+  languageName: node
+  linkType: hard
+
 "@babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2":
   version: 7.24.4
   resolution: "@babel/runtime@npm:7.24.4"
@@ -7594,6 +7610,69 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@testing-library/dom@npm:^9.0.0":
+  version: 9.3.4
+  resolution: "@testing-library/dom@npm:9.3.4"
+  dependencies:
+    "@babel/code-frame": ^7.10.4
+    "@babel/runtime": ^7.12.5
+    "@types/aria-query": ^5.0.1
+    aria-query: 5.1.3
+    chalk: ^4.1.0
+    dom-accessibility-api: ^0.5.9
+    lz-string: ^1.5.0
+    pretty-format: ^27.0.2
+  checksum: dfd6fb0d6c7b4dd716ba3c47309bc9541b4a55772cb61758b4f396b3785efe2dbc75dc63423545c039078c7ffcc5e4b8c67c2db1b6af4799580466036f70026f
+  languageName: node
+  linkType: hard
+
+"@testing-library/jest-dom@npm:^6.4.2":
+  version: 6.4.2
+  resolution: "@testing-library/jest-dom@npm:6.4.2"
+  dependencies:
+    "@adobe/css-tools": ^4.3.2
+    "@babel/runtime": ^7.9.2
+    aria-query: ^5.0.0
+    chalk: ^3.0.0
+    css.escape: ^1.5.1
+    dom-accessibility-api: ^0.6.3
+    lodash: ^4.17.15
+    redent: ^3.0.0
+  peerDependencies:
+    "@jest/globals": ">= 28"
+    "@types/bun": "*"
+    "@types/jest": ">= 28"
+    jest: ">= 28"
+    vitest: ">= 0.32"
+  peerDependenciesMeta:
+    "@jest/globals":
+      optional: true
+    "@types/bun":
+      optional: true
+    "@types/jest":
+      optional: true
+    jest:
+      optional: true
+    vitest:
+      optional: true
+  checksum: 631aeadbf4e738080ae095242cf1a29a0b4ee2f09c8bdd0d3f00a923707da64c1617e088ba9a961d098481afabdc1d19149fb7ef98edf15132348eb222f345ae
+  languageName: node
+  linkType: hard
+
+"@testing-library/react@npm:^14.2.2":
+  version: 14.2.2
+  resolution: "@testing-library/react@npm:14.2.2"
+  dependencies:
+    "@babel/runtime": ^7.12.5
+    "@testing-library/dom": ^9.0.0
+    "@types/react-dom": ^18.0.0
+  peerDependencies:
+    react: ^18.0.0
+    react-dom: ^18.0.0
+  checksum: cb73df588592d9101429f057eaa6f320fc12524d5eb2acc8a16002c1ee2d9422a49e44841003bba42974c9ae1ced6b134f0d647826eca42ab8f19e4592971b16
+  languageName: node
+  linkType: hard
+
 "@tootallnate/once@npm:2":
   version: 2.0.0
   resolution: "@tootallnate/once@npm:2.0.0"
@@ -7646,6 +7725,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/aria-query@npm:^5.0.1":
+  version: 5.0.4
+  resolution: "@types/aria-query@npm:5.0.4"
+  checksum: ad8b87e4ad64255db5f0a73bc2b4da9b146c38a3a8ab4d9306154334e0fc67ae64e76bfa298eebd1e71830591fb15987e5de7111bdb36a2221bdc379e3415fb0
+  languageName: node
+  linkType: hard
+
 "@types/babel__core@npm:^7.1.14":
   version: 7.1.19
   resolution: "@types/babel__core@npm:7.1.19"
@@ -8189,14 +8275,22 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/react@npm:^17":
-  version: 17.0.49
-  resolution: "@types/react@npm:17.0.49"
+"@types/react-dom@npm:^18.0.0":
+  version: 18.2.23
+  resolution: "@types/react-dom@npm:18.2.23"
+  dependencies:
+    "@types/react": "*"
+  checksum: ebed13ffc393f96b3dbe2f959c857c3124c20e84718cded13016eaa2e3b650590557ea75584c4d3d3f226b0aae206167c9c81e971632c1f24bbd258f1d295b96
+  languageName: node
+  linkType: hard
+
+"@types/react@npm:^18":
+  version: 18.2.75
+  resolution: "@types/react@npm:18.2.75"
   dependencies:
     "@types/prop-types": "*"
-    "@types/scheduler": "*"
     csstype: ^3.0.2
-  checksum: e2e8001bb16fdf52dc92dbc2f64c8e49b1c6ab5d5da4725c0f27e3d925939e42b835554fecc306ca6b0951801eb784288b63c924bd703834662bbaccf89cb68a
+  checksum: f77322720744beba6650462c0d75ccc0be27c176bd7b2aff32039ea30e498a78fe6dbce4a1c3fdbf592ccc01286059f332a294a3b6975dbb0d8d5999718ece38
   languageName: node
   linkType: hard
 
@@ -8260,13 +8354,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/scheduler@npm:*":
-  version: 0.16.2
-  resolution: "@types/scheduler@npm:0.16.2"
-  checksum: b6b4dcfeae6deba2e06a70941860fb1435730576d3689225a421280b7742318d1548b3d22c1f66ab68e414f346a9542f29240bc955b6332c5b11e561077583bc
-  languageName: node
-  linkType: hard
-
 "@types/semver@npm:^7.3.12":
   version: 7.3.13
   resolution: "@types/semver@npm:7.3.13"
@@ -8375,6 +8462,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/use-sync-external-store@npm:^0.0.6":
+  version: 0.0.6
+  resolution: "@types/use-sync-external-store@npm:0.0.6"
+  checksum: a95ce330668501ad9b1c5b7f2b14872ad201e552a0e567787b8f1588b22c7040c7c3d80f142cbb9f92d13c4ea41c46af57a20f2af4edf27f224d352abcfe4049
+  languageName: node
+  linkType: hard
+
 "@types/webpack@npm:^5.28.0":
   version: 5.28.0
   resolution: "@types/webpack@npm:5.28.0"
@@ -9604,9 +9698,14 @@ __metadata:
   version: 0.0.0-use.local
   resolution: "@uppy/react@workspace:packages/@uppy/react"
   dependencies:
+    "@testing-library/jest-dom": ^6.4.2
+    "@testing-library/react": ^14.2.2
     "@types/react": ^18.0.8
+    "@types/use-sync-external-store": ^0.0.6
     "@uppy/utils": "workspace:^"
-    react: ^18.1.0
+    react: ^18.2.0
+    react-dom: ^18.2.0
+    use-sync-external-store: ^1.2.0
   peerDependencies:
     "@uppy/core": "workspace:^"
     "@uppy/dashboard": "workspace:^"
@@ -9614,7 +9713,7 @@ __metadata:
     "@uppy/file-input": "workspace:^"
     "@uppy/progress-bar": "workspace:^"
     "@uppy/status-bar": "workspace:^"
-    react: ^16.0.0 || ^17.0.0 || ^18.0.0
+    react: ^18.0.0
   peerDependenciesMeta:
     "@uppy/dashboard":
       optional: true
@@ -10893,16 +10992,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"aria-query@npm:5.3.0, aria-query@npm:^5.3.0":
-  version: 5.3.0
-  resolution: "aria-query@npm:5.3.0"
-  dependencies:
-    dequal: ^2.0.3
-  checksum: 305bd73c76756117b59aba121d08f413c7ff5e80fa1b98e217a3443fcddb9a232ee790e24e432b59ae7625aebcf4c47cb01c2cac872994f0b426f5bdfcd96ba9
-  languageName: node
-  linkType: hard
-
-"aria-query@npm:^5.1.3":
+"aria-query@npm:5.1.3, aria-query@npm:^5.1.3":
   version: 5.1.3
   resolution: "aria-query@npm:5.1.3"
   dependencies:
@@ -10911,6 +11001,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"aria-query@npm:5.3.0, aria-query@npm:^5.0.0, aria-query@npm:^5.3.0":
+  version: 5.3.0
+  resolution: "aria-query@npm:5.3.0"
+  dependencies:
+    dequal: ^2.0.3
+  checksum: 305bd73c76756117b59aba121d08f413c7ff5e80fa1b98e217a3443fcddb9a232ee790e24e432b59ae7625aebcf4c47cb01c2cac872994f0b426f5bdfcd96ba9
+  languageName: node
+  linkType: hard
+
 "arr-diff@npm:^4.0.0":
   version: 4.0.0
   resolution: "arr-diff@npm:4.0.0"
@@ -13387,6 +13486,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"css.escape@npm:^1.5.1":
+  version: 1.5.1
+  resolution: "css.escape@npm:1.5.1"
+  checksum: f6d38088d870a961794a2580b2b2af1027731bb43261cfdce14f19238a88664b351cc8978abc20f06cc6bbde725699dec8deb6fe9816b139fc3f2af28719e774
+  languageName: node
+  linkType: hard
+
 "cssesc@npm:^3.0.0":
   version: 3.0.0
   resolution: "cssesc@npm:3.0.0"
@@ -14096,6 +14202,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"dom-accessibility-api@npm:^0.5.9":
+  version: 0.5.16
+  resolution: "dom-accessibility-api@npm:0.5.16"
+  checksum: 005eb283caef57fc1adec4d5df4dd49189b628f2f575af45decb210e04d634459e3f1ee64f18b41e2dcf200c844bc1d9279d80807e686a30d69a4756151ad248
+  languageName: node
+  linkType: hard
+
+"dom-accessibility-api@npm:^0.6.3":
+  version: 0.6.3
+  resolution: "dom-accessibility-api@npm:0.6.3"
+  checksum: c325b5144bb406df23f4affecffc117dbaec9af03daad9ee6b510c5be647b14d28ef0a4ea5ca06d696d8ab40bb777e5fed98b985976fdef9d8790178fa1d573f
+  languageName: node
+  linkType: hard
+
 "dom-serialize@npm:^2.2.1":
   version: 2.2.1
   resolution: "dom-serialize@npm:2.2.1"
@@ -21327,6 +21447,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"lz-string@npm:^1.5.0":
+  version: 1.5.0
+  resolution: "lz-string@npm:1.5.0"
+  bin:
+    lz-string: bin/bin.js
+  checksum: 1ee98b4580246fd90dd54da6e346fb1caefcf05f677c686d9af237a157fdea3fd7c83a4bc58f858cd5b10a34d27afe0fdcbd0505a47e0590726a873dc8b8f65d
+  languageName: node
+  linkType: hard
+
 "magic-string@npm:0.30.8, magic-string@npm:^0.30.7":
   version: 0.30.8
   resolution: "magic-string@npm:0.30.8"
@@ -25739,6 +25868,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"pretty-format@npm:^27.0.2":
+  version: 27.5.1
+  resolution: "pretty-format@npm:27.5.1"
+  dependencies:
+    ansi-regex: ^5.0.1
+    ansi-styles: ^5.0.0
+    react-is: ^17.0.1
+  checksum: cf610cffcb793885d16f184a62162f2dd0df31642d9a18edf4ca298e909a8fe80bdbf556d5c9573992c102ce8bf948691da91bf9739bee0ffb6e79c8a8a6e088
+  languageName: node
+  linkType: hard
+
 "pretty-format@npm:^29.5.0":
   version: 29.5.0
   resolution: "pretty-format@npm:29.5.0"
@@ -26126,7 +26266,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"react-dom@npm:^18.0.0, react-dom@npm:^18.1.0":
+"react-dom@npm:^18.0.0, react-dom@npm:^18.1.0, react-dom@npm:^18.2.0":
   version: 18.2.0
   resolution: "react-dom@npm:18.2.0"
   dependencies:
@@ -26282,7 +26422,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"react@npm:^18.0.0, react@npm:^18.1.0":
+"react@npm:^18.0.0, react@npm:^18.1.0, react@npm:^18.2.0":
   version: 18.2.0
   resolution: "react@npm:18.2.0"
   dependencies:
@@ -26480,6 +26620,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"redent@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "redent@npm:3.0.0"
+  dependencies:
+    indent-string: ^4.0.0
+    strip-indent: ^3.0.0
+  checksum: fa1ef20404a2d399235e83cc80bd55a956642e37dd197b4b612ba7327bf87fa32745aeb4a1634b2bab25467164ab4ed9c15be2c307923dd08b0fe7c52431ae6b
+  languageName: node
+  linkType: hard
+
 "redent@npm:^4.0.0":
   version: 4.0.0
   resolution: "redent@npm:4.0.0"