-
Notifications
You must be signed in to change notification settings - Fork 1
303 imagecropper component #1906
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
5b5f04a
feat(ImageCropper): add image cropper
Lisa18289 a9d7848
feat(ImageCropper): add image cropper component
Lisa18289 ee9d6b9
feat(ImageCropper): add image cropper component
Lisa18289 29851ce
feat(ImageCropper): add image cropper component
Lisa18289 ad43d46
feat(ImageCropper): add design tokens
Lisa18289 9d4d372
feat(ImageCropper): add design tokens
Lisa18289 d1cf384
feat(ImageCropper): add docs
Lisa18289 0a940f4
feat(ImageCropper): add docs
Lisa18289 352f77f
feat(ImageCropper): add docs
Lisa18289 fd61368
Merge remote-tracking branch 'origin/main' into 303-imagecropper-comp…
Lisa18289 4f9c491
Merge remote-tracking branch 'refs/remotes/origin/main' into 303-imag…
Lisa18289 6508919
fix(ImageCropper): add translation
Lisa18289 1ce6446
Merge remote-tracking branch 'origin/main' into 303-imagecropper-comp…
Lisa18289 17f32a3
feat(ImageCropper): add tests
Lisa18289 62af44c
feat(ImageCropper): add tests
Lisa18289 639a89d
test: update visual regression screenshots
github-actions[bot] 2c49ee5
merge main
Lisa18289 44825f4
merge main
Lisa18289 cd977d6
Merge remote-tracking branch 'origin/main' into 303-imagecropper-comp…
Lisa18289 77fe6a3
merge main
Lisa18289 29b6242
Merge remote-tracking branch 'origin/main' into 303-imagecropper-comp…
Lisa18289 8ca78ca
update image cropper
Lisa18289 4ad4baf
update image cropper
Lisa18289 2ccce6f
update image cropper
Lisa18289 376a032
test: update visual regression screenshots
github-actions[bot] 4353daa
Merge remote-tracking branch 'origin/main' into 303-imagecropper-comp…
Lisa18289 f3a4097
feat(ImageCropper): add image cropper
Lisa18289 bc6d7b9
feat(ImageCropper): add image cropper
Lisa18289 0685a59
update cropper
Lisa18289 08926a7
update cropper
Lisa18289 8e2912a
test: update visual regression screenshots
github-actions[bot] f176287
Merge branch 'main' into 303-imagecropper-component
Lisa18289 1768c76
update slider
Lisa18289 5d2a613
Merge remote-tracking branch 'origin/303-imagecropper-component' into…
Lisa18289 8085b59
update slider
Lisa18289 b31d150
remove unused disabled
Lisa18289 a24fb44
Merge branch 'main' into 303-imagecropper-component
Lisa18289 9d7b815
test: update visual regression screenshots
github-actions[bot] a4c491c
update cropper
Lisa18289 a662614
Merge remote-tracking branch 'origin/303-imagecropper-component' into…
Lisa18289 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
3 changes: 3 additions & 0 deletions
3
apps/docs/src/content/04-components/upload/image-cropper/develop.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # Properties | ||
|
|
||
| <PropertiesTables /> |
6 changes: 6 additions & 0 deletions
6
apps/docs/src/content/04-components/upload/image-cropper/examples/aspect.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { ImageCropper } from "@mittwald/flow-react-components"; | ||
|
|
||
| <ImageCropper | ||
| image="https://mittwald.github.io/flow/assets/mittwald_logo_rgb.jpg" | ||
| aspectRatio={16 / 9} | ||
| />; |
7 changes: 7 additions & 0 deletions
7
apps/docs/src/content/04-components/upload/image-cropper/examples/crop-shape.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { ImageCropper } from "@mittwald/flow-react-components"; | ||
|
|
||
| <ImageCropper | ||
| image="https://mittwald.github.io/flow/assets/mittwald_logo_rgb.jpg" | ||
| cropShape="round" | ||
| aspectRatio={1} | ||
| />; |
3 changes: 3 additions & 0 deletions
3
apps/docs/src/content/04-components/upload/image-cropper/examples/default.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import { ImageCropper } from "@mittwald/flow-react-components"; | ||
|
|
||
| <ImageCropper image="https://mittwald.github.io/flow/assets/mittwald_logo_rgb.jpg" />; |
86 changes: 86 additions & 0 deletions
86
apps/docs/src/content/04-components/upload/image-cropper/examples/file-drop-zone.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| import { | ||
| Button, | ||
| Heading, | ||
| FileField, | ||
| ImageCropper, | ||
| IconImage, | ||
| Section, | ||
| FileDropZone, | ||
| Text, | ||
| Flex, | ||
| IconClose, | ||
| Action, | ||
| } from "@mittwald/flow-react-components"; | ||
| import { useForm } from "react-hook-form"; | ||
| import { | ||
| Form, | ||
| SubmitButton, | ||
| typedField, | ||
| } from "@mittwald/flow-react-components/react-hook-form"; | ||
| import { sleep } from "@/content/04-components/actions/action/examples/lib"; | ||
|
|
||
| export default () => { | ||
| const form = useForm<{ | ||
| files: FileList | File[] | []; | ||
| }>({ defaultValues: { files: [] } }); | ||
| const Field = typedField(form); | ||
|
|
||
| const watchedFiles = [...form.watch("files")]; | ||
|
|
||
| return ( | ||
| <Section> | ||
| <Form form={form} onSubmit={sleep}> | ||
| {watchedFiles.length === 0 && ( | ||
| <FileDropZone | ||
| accept="image/png" | ||
| onChange={(f) => { | ||
| if (f) { | ||
| form.setValue("files", f); | ||
| } | ||
| }} | ||
| > | ||
| <IconImage /> | ||
| <Heading>Bild ablegen</Heading> | ||
| <Text> | ||
| Bitte wähle ein Bild vom Typ PNG aus. | ||
| </Text> | ||
| <Field | ||
| name="files" | ||
| rules={{ | ||
| required: "Bitte wähle ein Bild aus", | ||
| }} | ||
| > | ||
| <FileField> | ||
| <Button variant="outline" color="dark"> | ||
| Bild auswählen | ||
| </Button> | ||
| </FileField> | ||
| </Field> | ||
| </FileDropZone> | ||
| )} | ||
|
|
||
| {watchedFiles.length > 0 && ( | ||
| <Flex justify="center" align="start" gap="m"> | ||
| <ImageCropper | ||
| width={200} | ||
| height={200} | ||
| image={watchedFiles[0]} | ||
| /> | ||
| <Action | ||
| onAction={() => form.setValue("files", [])} | ||
| > | ||
| <Button | ||
| variant="plain" | ||
| color="secondary" | ||
| aria-label="Bild entfernen" | ||
| > | ||
| <IconClose /> | ||
| </Button> | ||
| </Action> | ||
| </Flex> | ||
| )} | ||
| <SubmitButton>Hochladen</SubmitButton> | ||
| </Form> | ||
| </Section> | ||
| ); | ||
| }; |
7 changes: 7 additions & 0 deletions
7
apps/docs/src/content/04-components/upload/image-cropper/examples/size.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { ImageCropper } from "@mittwald/flow-react-components"; | ||
|
|
||
| <ImageCropper | ||
| image="https://mittwald.github.io/flow/assets/mittwald_logo_rgb.jpg" | ||
| height={200} | ||
| width={200} | ||
| />; |
8 changes: 8 additions & 0 deletions
8
apps/docs/src/content/04-components/upload/image-cropper/index.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| --- | ||
| component: ImageCropper | ||
| description: | ||
| Die ImageCropper Component wird dafür verwendet ein Bild auf ein bestimmtes | ||
| Format zuzuschneiden. | ||
| --- | ||
|
|
||
| <LiveCodeEditor editorDisabled example="size" /> |
37 changes: 37 additions & 0 deletions
37
apps/docs/src/content/04-components/upload/image-cropper/overview.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Playground | ||
|
|
||
| <LiveCodeEditor /> | ||
|
|
||
| --- | ||
|
|
||
| # Seitenverhältnis | ||
|
|
||
| Über das Property `aspect` kann das Seitenverhältnis angegeben werden. | ||
|
|
||
| <LiveCodeEditor example="aspect" editorCollapsed /> | ||
|
|
||
| --- | ||
|
|
||
| # Runder Ausschnitt | ||
|
|
||
| Über das Property `cropShape` kann zwischen einer rechteckigen und runden | ||
| Ansicht des Ausschnitts gewechselt werden. | ||
|
|
||
| <LiveCodeEditor example="crop-shape" editorCollapsed /> | ||
|
|
||
| --- | ||
|
|
||
| # Größe | ||
|
|
||
| Über die `width` und `height` Properties kann die Größe des Croppers angepasst | ||
| werden. | ||
|
|
||
| <LiveCodeEditor example="size" editorCollapsed /> | ||
|
|
||
| --- | ||
|
|
||
| # Kombiniere mit ... | ||
|
|
||
| ## FileDropZone | ||
|
|
||
| <LiveCodeEditor example="file-drop-zone" editorCollapsed /> |
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
packages/components/src/components/ImageCropper/ImageCropper.module.scss
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| .imageCropper { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: var(--image-cropper--spacing); | ||
| max-width: 100%; | ||
|
|
||
| .cropperContainer { | ||
| position: relative; | ||
| width: 100%; | ||
| border-radius: var(--image-cropper--corner-radius); | ||
| border-color: var(--image-cropper--border-color); | ||
| border-style: var(--image-cropper--border-style); | ||
| border-width: var(--image-cropper--border-width); | ||
| } | ||
|
|
||
| :global(.reactEasyCrop_CropArea) { | ||
| color: var(--image-cropper--mask-color); | ||
| border-color: var(--image-cropper--mask-border-color); | ||
| box-sizing: content-box; | ||
| } | ||
|
|
||
| :global(.reactEasyCrop_CropAreaGrid::before) { | ||
| border-color: var(--image-cropper--grid-color); | ||
| border-width: var(--image-cropper--grid-width); | ||
| } | ||
| } |
99 changes: 99 additions & 0 deletions
99
packages/components/src/components/ImageCropper/ImageCropper.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import { | ||
| type CSSProperties, | ||
| type FC, | ||
| useEffect, | ||
| useEffectEvent, | ||
| useState, | ||
| } from "react"; | ||
| import Cropper, { type Area, type CropperProps } from "react-easy-crop"; | ||
| import type { PropsWithClassName } from "@/lib/types/props"; | ||
| import clsx from "clsx"; | ||
| import styles from "./ImageCropper.module.scss"; | ||
| import { Slider } from "@/components/Slider"; | ||
| import { getCroppedImageFile } from "@/components/ImageCropper/lib/getCroppedImageFile"; | ||
| import { useLocalizedStringFormatter } from "react-aria"; | ||
| import locales from "./locales/*.locale.json"; | ||
| import { useImageSrc } from "@/lib/hooks/useImageSrc"; | ||
|
|
||
| export interface ImageCropperProps | ||
| extends PropsWithClassName, Partial<Pick<CropperProps, "cropShape">> { | ||
| /** The image file to crop. */ | ||
| image?: File | string; | ||
| /** Callback on crop complete. */ | ||
| onCropComplete?: (croppedImage: File) => void; | ||
| /** The width of the component. @default 300 */ | ||
| width?: CSSProperties["width"]; | ||
| /** The height of the component. @default 300 */ | ||
| height?: CSSProperties["height"]; | ||
| /** The aspect ratio of the crop shape. */ | ||
| aspectRatio?: number; | ||
| } | ||
|
|
||
| /** @flr-generate all */ | ||
| export const ImageCropper: FC<ImageCropperProps> = (props) => { | ||
| const { | ||
| image, | ||
| className, | ||
| onCropComplete, | ||
| width = 300, | ||
| height = 300, | ||
| aspectRatio, | ||
| ...rest | ||
| } = props; | ||
|
|
||
| const imageSrc = useImageSrc(image); | ||
|
|
||
| const [crop, setCrop] = useState({ x: 0, y: 0 }); | ||
| const [zoom, setZoom] = useState(1); | ||
| const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area>(); | ||
|
|
||
| const rootClassName = clsx(styles.imageCropper, className); | ||
|
|
||
| const stringFormatter = useLocalizedStringFormatter(locales); | ||
|
|
||
| const onCropAreaPixelsChange = useEffectEvent(async () => { | ||
| if (croppedAreaPixels) { | ||
| const croppedImageFile = await getCroppedImageFile( | ||
| imageSrc, | ||
| croppedAreaPixels, | ||
| ); | ||
| if (onCropComplete) { | ||
| onCropComplete(croppedImageFile); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| void onCropAreaPixelsChange(); | ||
| }, [croppedAreaPixels]); | ||
|
mfal marked this conversation as resolved.
|
||
|
|
||
| return ( | ||
| <div className={rootClassName} style={{ width }}> | ||
| <div className={styles.cropperContainer} style={{ height }}> | ||
| <Cropper | ||
| aspect={aspectRatio} | ||
| crop={crop} | ||
| image={imageSrc} | ||
| onCropChange={setCrop} | ||
| zoom={zoom} | ||
| onZoomChange={setZoom} | ||
| onCropComplete={(_, croppedAreaPixels) => | ||
| setCroppedAreaPixels(croppedAreaPixels) | ||
| } | ||
| {...rest} | ||
| /> | ||
| </div> | ||
| <Slider | ||
| minValue={1} | ||
| maxValue={3} | ||
| step={0.01} | ||
| value={zoom} | ||
| sliderOnly | ||
| onChange={(zoom) => setZoom(zoom as number)} | ||
| aria-label={stringFormatter.format("zoom")} | ||
| /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default ImageCropper; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { type ImageCropperProps, ImageCropper } from "./ImageCropper"; | ||
| export { default } from "./ImageCropper"; |
51 changes: 51 additions & 0 deletions
51
packages/components/src/components/ImageCropper/lib/getCroppedImageFile.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import type { Area } from "react-easy-crop"; | ||
|
|
||
| export function getCroppedImageFile( | ||
| imageSrc: string, | ||
| pixelCrop: Area, | ||
| ): Promise<File> { | ||
| return new Promise((resolve, reject) => { | ||
| const image = new Image(); | ||
| image.crossOrigin = "anonymous"; | ||
| image.src = imageSrc; | ||
|
|
||
| image.onload = () => { | ||
| const canvas = document.createElement("canvas"); | ||
| canvas.width = pixelCrop.width; | ||
| canvas.height = pixelCrop.height; | ||
| const ctx = canvas.getContext("2d"); | ||
|
|
||
| if (!ctx) { | ||
| reject(new Error("Failed to get canvas context")); | ||
| return; | ||
| } | ||
|
|
||
| ctx.drawImage( | ||
| image, | ||
| pixelCrop.x, | ||
| pixelCrop.y, | ||
| pixelCrop.width, | ||
| pixelCrop.height, | ||
| 0, | ||
| 0, | ||
| pixelCrop.width, | ||
| pixelCrop.height, | ||
| ); | ||
|
|
||
| canvas.toBlob((blob) => { | ||
| if (!blob) { | ||
| return; | ||
| } | ||
|
|
||
| const file = new File([blob], "cropped-image.png", { | ||
| type: "image/png", | ||
| }); | ||
| resolve(file); | ||
| }, "image/png"); | ||
| }; | ||
|
|
||
| image.onerror = () => { | ||
| reject(new Error("Failed to load image")); | ||
| }; | ||
| }); | ||
| } |
3 changes: 3 additions & 0 deletions
3
packages/components/src/components/ImageCropper/locales/de-DE.locale.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "zoom": "Zoom" | ||
| } |
3 changes: 3 additions & 0 deletions
3
packages/components/src/components/ImageCropper/locales/en-US.locale.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "zoom": "zoom" | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.