uppy/examples
Merlijn Vos 850c1cb1c5
Add useImageEditor (#6122)
- Create image editor abstraction in `@uppy/components`
- Refactor `Editor` (Preact) and `ImageEditor` (Uppy plugin) to put all
methods in the Uppy plugin class so they can be reused by the headless
abstraction
- Create framework hooks for React, Vue, Svelte
- Update all examples to showcase the new hook
- Symlink the `cropper.scss` (just regular CSS) from
`@uppy/image-editor` into `@uppy/components`, update `build:css` in all
framework packages to copy that file too into their own `dist/` output.
- cropper-js needs basic CSS to do the rotations and stuff which would
be very tedious to write yourself so we ship that CSS file to consumers
too so they can just import it.
- Fix memory leak where we created the image on each render inside the
original Preact plugin UI

```ts
import type { UppyFile } from '@uppy/core'
import { useImageEditor } from '@uppy/react'

interface ImageEditorProps {
  file: UppyFile<any, any>
  close: () => void
}

export function ImageEditor({ file, close }: ImageEditorProps) {
  const {
    state,
    getImageProps,
    getSaveButtonProps,
    getCancelButtonProps,
    getRotateButtonProps,
    getFlipHorizontalButtonProps,
    getZoomButtonProps,
    getCropSquareButtonProps,
    getCropLandscapeButtonProps,
    getCropPortraitButtonProps,
    getResetButtonProps,
    getRotationSliderProps,
  } = useImageEditor({ file })

  return (
    <div className="p-4 max-w-2xl w-full">
      <div className="flex justify-between items-center mb-4">
        <h2 className="text-xl font-bold">Edit Image</h2>
        <button
          type="button"
          onClick={close}
          className="text-gray-500 hover:text-gray-700"
        >
          ✕
        </button>
      </div>

      <div className="mb-4">
        {/* biome-ignore lint/a11y/useAltText: alt is provided by getImageProps() */}
        <img
          className="w-full max-h-[400px] rounded-lg border-2"
          {...getImageProps()}
        />
      </div>

      <div className="mb-4">
        <label className="block text-sm font-medium mb-2">
          Fine Rotation: {state.angle}°
          <input className="w-full mt-1" {...getRotationSliderProps()} />
        </label>
      </div>

      <div className="flex gap-2 flex-wrap mb-4">
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getRotateButtonProps(-90)}
        >
          ↶ -90°
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getRotateButtonProps(90)}
        >
          ↷ +90°
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getFlipHorizontalButtonProps()}
        >
          ⇆ Flip
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getZoomButtonProps(0.1)}
        >
          + Zoom
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getZoomButtonProps(-0.1)}
        >
          - Zoom
        </button>
      </div>

      <div className="flex gap-2 flex-wrap mb-4">
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getCropSquareButtonProps()}
        >
          1:1
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getCropLandscapeButtonProps()}
        >
          16:9
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getCropPortraitButtonProps()}
        >
          9:16
        </button>
        <button
          className="bg-gray-200 px-3 py-1 rounded disabled:opacity-50"
          {...getResetButtonProps()}
        >
          Reset
        </button>
      </div>

      <div className="flex gap-4 justify-end">
        <button
          className="bg-gray-500 text-white px-4 py-2 rounded-md"
          {...getCancelButtonProps({ onClick: close })}
        >
          Cancel
        </button>
        <button
          className="bg-green-500 text-white px-4 py-2 rounded-md disabled:opacity-50 disabled:bg-green-300"
          {...getSaveButtonProps({ onClick: close })}
        >
          Save
        </button>
      </div>
    </div>
  )
}

export default ImageEditor

```


https://github.com/user-attachments/assets/4b0a99cc-8489-41a2-aa3b-ce88110025bf

---------

Co-authored-by: Prakash <qxprakash@gmail.com>
2026-01-29 11:13:12 +01:00
..
angular build(deps): bump @angular/compiler from 19.2.17 to 19.2.18 (#6128) 2026-01-12 11:13:45 +01:00
aws-companion Fix @uppy/examples (#6099) 2025-12-08 09:48:23 +01:00
aws-nodejs build(deps): bump body-parser from 1.20.3 to 1.20.4 (#6070) 2025-12-02 09:55:11 +01:00
aws-php build(deps): bump aws/aws-sdk-php from 3.288.1 to 3.368.0 in /examples/aws-php (#6112) 2025-12-22 09:24:41 +01:00
cdn Cleanup examples (#5817) 2025-07-10 17:23:58 +02:00
companion Fix @uppy/examples (#6099) 2025-12-08 09:48:23 +01:00
companion-custom-provider build(deps): bump preact from 10.26.9 to 10.26.10 (#6123) 2026-01-08 12:00:59 +01:00
companion-digitalocean-spaces Fix @uppy/examples (#6099) 2025-12-08 09:48:23 +01:00
nextjs build(deps): bump next from 15.5.7 to 15.5.9 (#6104) 2025-12-12 09:38:32 +01:00
react Add useImageEditor (#6122) 2026-01-29 11:13:12 +01:00
reactrouter Fix useless security warnings (#6132) 2026-01-12 11:16:15 +01:00
sveltekit Add useImageEditor (#6122) 2026-01-29 11:13:12 +01:00
transloadit Fix @uppy/examples (#6099) 2025-12-08 09:48:23 +01:00
vue Add useImageEditor (#6122) 2026-01-29 11:13:12 +01:00
xhr-bundle Fix @uppy/examples (#6099) 2025-12-08 09:48:23 +01:00
xhr-node Fix @uppy/examples (#6099) 2025-12-08 09:48:23 +01:00
xhr-php Fix @uppy/examples (#6099) 2025-12-08 09:48:23 +01:00
xhr-python Fix @uppy/examples (#6099) 2025-12-08 09:48:23 +01:00