From 9dd89b006d7325cd3a534e1886ea30ad0b4cf13a Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Wed, 12 Jun 2024 16:41:40 +0200 Subject: [PATCH 01/11] refactor: extract `ImageInput->renderPreview` into FC --- .../inputs/files/ImageInput/ImageInput.tsx | 62 +++--------- .../files/ImageInput/ImageInputPreview.tsx | 98 +++++++++++++++++++ .../form/inputs/files/ImageInput/types.ts | 18 ++++ 3 files changed, 131 insertions(+), 47 deletions(-) create mode 100644 packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputPreview.tsx create mode 100644 packages/sanity/src/core/form/inputs/files/ImageInput/types.ts diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx index 33fa9aaae7b..d626b8f462a 100644 --- a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx @@ -1,13 +1,10 @@ -/* eslint-disable react/jsx-no-bind */ -/* eslint-disable import/no-unresolved,react/jsx-handler-names, react/display-name, react/no-this-in-sfc */ - +/* eslint-disable react/jsx-handler-names */ import {isImageSource} from '@sanity/asset-utils' import {type SanityClient} from '@sanity/client' import {ChevronDownIcon, ImageIcon, SearchIcon} from '@sanity/icons' import { type AssetFromSource, type AssetSource, - type Image as BaseImage, type ImageAsset, type ImageSchemaType, type Path, @@ -50,15 +47,11 @@ import {UploadWarning} from '../common/UploadWarning' import {ImageToolInput} from '../ImageToolInput' import {type ImageUrlBuilder} from '../types' import {ImageActionsMenu, ImageActionsMenuWaitPlaceholder} from './ImageActionsMenu' -import {ImagePreview} from './ImagePreview' +import {ImageInputPreview} from './ImageInputPreview' import {InvalidImageWarning} from './InvalidImageWarning' +import {type BaseImageInputValue, type FileInfo} from './types' -/** - * @hidden - * @beta */ -export interface BaseImageInputValue extends Partial { - _upload?: UploadState -} +export {BaseImageInputValue} /** * @hidden @@ -74,18 +67,6 @@ export interface BaseImageInputProps t: (key: string, values?: Record) => string } -const getDevicePixelRatio = () => { - if (typeof window === 'undefined' || !window.devicePixelRatio) { - return 1 - } - return Math.round(Math.max(1, window.devicePixelRatio)) -} - -type FileInfo = { - type: string // mime type - kind: string // 'file' or 'string' -} - interface BaseImageInputState { isUploading: boolean selectedAssetSource: AssetSource | null @@ -101,7 +82,7 @@ function passThrough({children}: {children?: ReactNode}) { return children } -const ASSET_FIELD_PATH = ['asset'] +const ASSET_FIELD_PATH = ['asset'] as const const ASSET_IMAGE_MENU_POPOVER: MenuButtonProps['popover'] = {portal: true} @@ -422,34 +403,21 @@ export class BaseImageInput extends PureComponent { - const {value, schemaType, readOnly, directUploads, imageUrlBuilder, t, resolveUploader} = + const {value, schemaType, readOnly, directUploads, imageUrlBuilder, resolveUploader} = this.props - - if (!value || !isImageSource(value)) { - return null - } - const {hoveringFiles} = this.state - - const acceptedFiles = hoveringFiles.filter((file) => resolveUploader(schemaType, file)) - - const rejectedFilesCount = hoveringFiles.length - acceptedFiles.length - const imageUrl = imageUrlBuilder - .width(2000) - .fit('max') - .image(value) - .dpr(getDevicePixelRatio()) - .auto('format') - .url() + const {handleOpenDialog} = this return ( - 0} - isRejected={rejectedFilesCount > 0 || !directUploads} + ) } diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputPreview.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputPreview.tsx new file mode 100644 index 00000000000..d0d29c1079d --- /dev/null +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputPreview.tsx @@ -0,0 +1,98 @@ +/* eslint-disable react/jsx-handler-names */ +import {isImageSource} from '@sanity/asset-utils' +import {type ImageSchemaType} from '@sanity/types' +import {memo, useMemo} from 'react' +import {useDevicePixelRatio} from 'use-device-pixel-ratio' + +import {useTranslation} from '../../../../i18n' +import {type UploaderResolver} from '../../../studio/uploads/types' +import {type ImageUrlBuilder} from '../types' +import {ImagePreview} from './ImagePreview' +import {type BaseImageInputValue, type FileInfo} from './types' + +export const ImageInputPreview = memo(function ImageInputPreviewComponent(props: { + directUploads: boolean | undefined + handleOpenDialog: () => void + hoveringFiles: FileInfo[] + imageUrlBuilder: ImageUrlBuilder + readOnly: boolean | undefined + resolveUploader: UploaderResolver + schemaType: ImageSchemaType + value: BaseImageInputValue | undefined +}) { + const { + directUploads, + handleOpenDialog, + hoveringFiles, + imageUrlBuilder, + readOnly, + resolveUploader, + schemaType, + value, + } = props + + const isValueImageSource = useMemo(() => isImageSource(value), [value]) + if (!value || !isValueImageSource) { + return null + } + + return ( + + ) +}) + +function RenderImageInputPreview(props: { + directUploads: boolean | undefined + handleOpenDialog: () => void + hoveringFiles: FileInfo[] + imageUrlBuilder: ImageUrlBuilder + readOnly: boolean | undefined + resolveUploader: UploaderResolver + schemaType: ImageSchemaType + value: BaseImageInputValue +}) { + const { + directUploads, + handleOpenDialog, + hoveringFiles, + imageUrlBuilder, + readOnly, + resolveUploader, + schemaType, + value, + } = props + + const {t} = useTranslation() + const acceptedFiles = useMemo( + () => hoveringFiles.filter((file) => resolveUploader(schemaType, file)), + [hoveringFiles, resolveUploader, schemaType], + ) + const rejectedFilesCount = useMemo( + () => hoveringFiles.length - acceptedFiles.length, + [acceptedFiles, hoveringFiles], + ) + const dpr = useDevicePixelRatio() + const imageUrl = useMemo( + () => imageUrlBuilder.width(2000).fit('max').image(value).dpr(dpr).auto('format').url(), + [dpr, imageUrlBuilder, value], + ) + return ( + 0} + isRejected={rejectedFilesCount > 0 || !directUploads} + readOnly={readOnly} + src={imageUrl} + alt={t('inputs.image.preview-uploaded-image')} + /> + ) +} diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/types.ts b/packages/sanity/src/core/form/inputs/files/ImageInput/types.ts new file mode 100644 index 00000000000..b5255d79cff --- /dev/null +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/types.ts @@ -0,0 +1,18 @@ +import {type Image as BaseImage, type UploadState} from '@sanity/types' + +/** + * @hidden + * @internal + */ +export type FileInfo = { + type: string // mime type + kind: string // 'file' or 'string' +} + +/** + * @hidden + * @beta + */ +export interface BaseImageInputValue extends Partial { + _upload?: UploadState +} From b4f7c53225884e39a21ef1211c144fc3e36751db Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Wed, 12 Jun 2024 20:00:27 +0200 Subject: [PATCH 02/11] refactor: extract `ImageInput-> renderHotspotInput ` into FC --- .../inputs/files/ImageInput/ImageInput.tsx | 82 ++++--------------- .../ImageInput/ImageInputHotspotInput.tsx | 52 ++++++++++++ .../form/inputs/files/ImageInput/types.ts | 28 ++++++- 3 files changed, 94 insertions(+), 68 deletions(-) create mode 100644 packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputHotspotInput.tsx diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx index d626b8f462a..54ef40597a7 100644 --- a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx @@ -1,31 +1,15 @@ /* eslint-disable react/jsx-handler-names */ import {isImageSource} from '@sanity/asset-utils' -import {type SanityClient} from '@sanity/client' import {ChevronDownIcon, ImageIcon, SearchIcon} from '@sanity/icons' -import { - type AssetFromSource, - type AssetSource, - type ImageAsset, - type ImageSchemaType, - type Path, - type UploadState, -} from '@sanity/types' +import {type AssetFromSource, type AssetSource, type Path, type UploadState} from '@sanity/types' import {Box, Card, Menu, Stack, type ToastParams} from '@sanity/ui' import {get, startCase} from 'lodash' import {type FocusEvent, PureComponent, type ReactNode} from 'react' -import {type Observable, type Subscription} from 'rxjs' +import {type Subscription} from 'rxjs' -import { - Button, - Dialog, - MenuButton, - type MenuButtonProps, - MenuItem, -} from '../../../../../ui-components' +import {Button, MenuButton, type MenuButtonProps, MenuItem} from '../../../../../ui-components' import {ChangeIndicator} from '../../../../changeIndicators' import {ImperativeToast} from '../../../../components' -import {type FIXME} from '../../../../FIXME' -import {PresenceOverlay} from '../../../../presence' import {FormInput} from '../../../components' import {MemberField, MemberFieldError, MemberFieldSet} from '../../../members' import {type PatchEvent, setIfMissing, unset} from '../../../patch' @@ -33,10 +17,9 @@ import {type FieldMember} from '../../../store' import { type ResolvedUploader, type Uploader, - type UploaderResolver, type UploadOptions, } from '../../../studio/uploads/types' -import {type InputProps, type ObjectInputProps} from '../../../types' +import {type InputProps} from '../../../types' import {WithReferencedAsset} from '../../../utils/WithReferencedAsset' import {ActionsMenu} from '../common/ActionsMenu' import {handleSelectAssetFromSource} from '../common/assetSource' @@ -44,28 +27,13 @@ import {FileTarget} from '../common/styles' import {UploadPlaceholder} from '../common/UploadPlaceholder' import {UploadProgress} from '../common/UploadProgress' import {UploadWarning} from '../common/UploadWarning' -import {ImageToolInput} from '../ImageToolInput' -import {type ImageUrlBuilder} from '../types' import {ImageActionsMenu, ImageActionsMenuWaitPlaceholder} from './ImageActionsMenu' +import {ImageInputHotspotInput} from './ImageInputHotspotInput' import {ImageInputPreview} from './ImageInputPreview' import {InvalidImageWarning} from './InvalidImageWarning' -import {type BaseImageInputValue, type FileInfo} from './types' - -export {BaseImageInputValue} - -/** - * @hidden - * @beta */ -export interface BaseImageInputProps - extends ObjectInputProps { - assetSources: AssetSource[] - directUploads?: boolean - imageUrlBuilder: ImageUrlBuilder - observeAsset: (documentId: string) => Observable - resolveUploader: UploaderResolver - client: SanityClient - t: (key: string, values?: Record) => string -} +import {type BaseImageInputProps, type BaseImageInputValue, type FileInfo} from './types' + +export {BaseImageInputProps, BaseImageInputValue} interface BaseImageInputState { isUploading: boolean @@ -371,34 +339,14 @@ export class BaseImageInput extends PureComponent) => { - const {value, changed, id, imageUrlBuilder, t} = this.props - - const withImageTool = this.isImageToolEnabled() && value && value.asset - + renderHotspotInput = (inputProps: Omit) => { return ( - - - - {withImageTool && value?.asset && ( - - )} - - - + ) } diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputHotspotInput.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputHotspotInput.tsx new file mode 100644 index 00000000000..ea087c35e81 --- /dev/null +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputHotspotInput.tsx @@ -0,0 +1,52 @@ +import {Stack} from '@sanity/ui' +import {memo, useMemo} from 'react' + +import {Dialog} from '../../../../../ui-components' +import {type FIXME} from '../../../../FIXME' +import {useTranslation} from '../../../../i18n' +import {PresenceOverlay} from '../../../../presence' +import {type InputProps} from '../../../types' +import {ImageToolInput} from '../ImageToolInput' +import {type BaseImageInputProps} from './types' + +export const ImageInputHotspotInput = memo(function ImageInputHotspotInputComponent(props: { + handleCloseDialog: () => void + inputProps: Omit + imageInputProps: BaseImageInputProps + isImageToolEnabled: boolean +}) { + const {handleCloseDialog, inputProps, imageInputProps, isImageToolEnabled} = props + const {t} = useTranslation() + const {changed, id, imageUrlBuilder, value} = imageInputProps + + const withImageTool = isImageToolEnabled && value && value.asset + const imageUrl = useMemo( + () => (value?.asset ? imageUrlBuilder.image(value.asset).url() : ''), + [imageUrlBuilder, value?.asset], + ) + + return ( + + + + {withImageTool && value?.asset && ( + + )} + + + + ) +}) diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/types.ts b/packages/sanity/src/core/form/inputs/files/ImageInput/types.ts index b5255d79cff..8e5fa7632e5 100644 --- a/packages/sanity/src/core/form/inputs/files/ImageInput/types.ts +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/types.ts @@ -1,4 +1,16 @@ -import {type Image as BaseImage, type UploadState} from '@sanity/types' +import {type SanityClient} from '@sanity/client' +import { + type AssetSource, + type Image as BaseImage, + type ImageAsset, + type ImageSchemaType, + type UploadState, +} from '@sanity/types' +import {type Observable} from 'rxjs' + +import {type UploaderResolver} from '../../../studio/uploads/types' +import {type ObjectInputProps} from '../../../types' +import {type ImageUrlBuilder} from '../types' /** * @hidden @@ -16,3 +28,17 @@ export type FileInfo = { export interface BaseImageInputValue extends Partial { _upload?: UploadState } + +/** + * @hidden + * @beta */ +export interface BaseImageInputProps + extends ObjectInputProps { + assetSources: AssetSource[] + directUploads?: boolean + imageUrlBuilder: ImageUrlBuilder + observeAsset: (documentId: string) => Observable + resolveUploader: UploaderResolver + client: SanityClient + t: (key: string, values?: Record) => string +} From 70a7529398efe3eddbe7d170f340a0ab20b8c8fd Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Wed, 12 Jun 2024 22:27:52 +0200 Subject: [PATCH 03/11] refactor: extract `ImageInput-> renderAsset ` into FC --- .../inputs/files/ImageInput/ImageInput.tsx | 84 ++++++----------- .../files/ImageInput/ImageInputAsset.tsx | 92 +++++++++++++++++++ .../files/ImageInput/ImageInputPreview.tsx | 1 - .../form/inputs/files/ImageInput/constants.ts | 13 +++ 4 files changed, 133 insertions(+), 57 deletions(-) create mode 100644 packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAsset.tsx create mode 100644 packages/sanity/src/core/form/inputs/files/ImageInput/constants.ts diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx index 54ef40597a7..c6b78fc3c4d 100644 --- a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx @@ -1,14 +1,16 @@ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable react/no-this-in-sfc */ +/* eslint-disable react/display-name */ /* eslint-disable react/jsx-handler-names */ import {isImageSource} from '@sanity/asset-utils' import {ChevronDownIcon, ImageIcon, SearchIcon} from '@sanity/icons' import {type AssetFromSource, type AssetSource, type Path, type UploadState} from '@sanity/types' -import {Box, Card, Menu, Stack, type ToastParams} from '@sanity/ui' +import {Card, Menu, Stack, type ToastParams} from '@sanity/ui' import {get, startCase} from 'lodash' import {type FocusEvent, PureComponent, type ReactNode} from 'react' import {type Subscription} from 'rxjs' -import {Button, MenuButton, type MenuButtonProps, MenuItem} from '../../../../../ui-components' -import {ChangeIndicator} from '../../../../changeIndicators' +import {Button, MenuButton, MenuItem} from '../../../../../ui-components' import {ImperativeToast} from '../../../../components' import {FormInput} from '../../../components' import {MemberField, MemberFieldError, MemberFieldSet} from '../../../members' @@ -23,11 +25,11 @@ import {type InputProps} from '../../../types' import {WithReferencedAsset} from '../../../utils/WithReferencedAsset' import {ActionsMenu} from '../common/ActionsMenu' import {handleSelectAssetFromSource} from '../common/assetSource' -import {FileTarget} from '../common/styles' import {UploadPlaceholder} from '../common/UploadPlaceholder' import {UploadProgress} from '../common/UploadProgress' -import {UploadWarning} from '../common/UploadWarning' +import {ASSET_FIELD_PATH, ASSET_IMAGE_MENU_POPOVER} from './constants' import {ImageActionsMenu, ImageActionsMenuWaitPlaceholder} from './ImageActionsMenu' +import {ImageInputAsset} from './ImageInputAsset' import {ImageInputHotspotInput} from './ImageInputHotspotInput' import {ImageInputPreview} from './ImageInputPreview' import {InvalidImageWarning} from './InvalidImageWarning' @@ -50,10 +52,6 @@ function passThrough({children}: {children?: ReactNode}) { return children } -const ASSET_FIELD_PATH = ['asset'] as const - -const ASSET_IMAGE_MENU_POPOVER: MenuButtonProps['popover'] = {portal: true} - /** @internal */ export class BaseImageInput extends PureComponent { _previewElement: HTMLDivElement | null = null @@ -343,7 +341,7 @@ export class BaseImageInput extends PureComponent @@ -354,12 +352,11 @@ export class BaseImageInput extends PureComponent } - // todo: convert this to a functional component and use this with useCallback - // it currently has to return a new function on every render in order to pick up state from this component return (inputProps: Omit) => ( - <> - {isStale && ( - - - - )} - - - {value?._upload ? ( - this.renderUploadState(value._upload) - ) : ( - 0} - sizing="border" - radius={2} - > - {!value?.asset && this.renderUploadPlaceholder()} - {!value?._upload && value?.asset && ( -
- {this.renderPreview()} - {this.renderAssetMenu()} -
- )} -
- )} -
- + ) } diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAsset.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAsset.tsx new file mode 100644 index 00000000000..c5627594ac9 --- /dev/null +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAsset.tsx @@ -0,0 +1,92 @@ +import {type UploadState} from '@sanity/types' +import {Box, type CardTone} from '@sanity/ui' +import {type FocusEvent, forwardRef, memo, useMemo} from 'react' + +import {ChangeIndicator} from '../../../../changeIndicators' +import {type InputProps} from '../../../types' +import {FileTarget} from '../common/styles' +import {UploadWarning} from '../common/UploadWarning' +import {ASSET_FIELD_PATH} from './constants' +import {type BaseImageInputProps, type BaseImageInputValue, type FileInfo} from './types' + +function ImageInputAssetComponent( + props: { + elementProps: BaseImageInputProps['elementProps'] + handleClearUploadState: () => void + handleFilesOut: () => void + handleFilesOver: (hoveringFiles: FileInfo[]) => void + handleFileTargetFocus: (event: FocusEvent) => void + handleSelectFiles: (files: File[]) => void + hoveringFiles: FileInfo[] + inputProps: Omit + isStale: boolean + readOnly: boolean | undefined + renderAssetMenu(): JSX.Element | null + renderPreview: () => JSX.Element + renderUploadPlaceholder(): JSX.Element + renderUploadState(uploadState: UploadState): JSX.Element + tone: CardTone + value: BaseImageInputValue | undefined + }, + forwardedRef: React.ForwardedRef, +) { + const { + elementProps, + handleClearUploadState, + handleFilesOut, + handleFilesOver, + handleFileTargetFocus, + handleSelectFiles, + hoveringFiles, + inputProps, + isStale, + readOnly, + renderAssetMenu, + renderPreview, + renderUploadPlaceholder, + renderUploadState, + tone, + value, + } = props + + const hasValueOrUpload = Boolean(value?._upload || value?.asset) + const path = useMemo(() => inputProps.path.concat(ASSET_FIELD_PATH), [inputProps.path]) + + return ( + <> + {isStale && ( + + + + )} + + {value?._upload ? ( + renderUploadState(value._upload) + ) : ( + 0} + sizing="border" + radius={2} + > + {!value?.asset && renderUploadPlaceholder()} + {!value?._upload && value?.asset && ( +
+ {renderPreview()} + {renderAssetMenu()} +
+ )} +
+ )} +
+ + ) +} +export const ImageInputAsset = memo(forwardRef(ImageInputAssetComponent)) diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputPreview.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputPreview.tsx index d0d29c1079d..a2fcfb618de 100644 --- a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputPreview.tsx +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputPreview.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-handler-names */ import {isImageSource} from '@sanity/asset-utils' import {type ImageSchemaType} from '@sanity/types' import {memo, useMemo} from 'react' diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/constants.ts b/packages/sanity/src/core/form/inputs/files/ImageInput/constants.ts new file mode 100644 index 00000000000..ef0706c4655 --- /dev/null +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/constants.ts @@ -0,0 +1,13 @@ +import {type MenuButtonProps} from '../../../../../ui-components' + +/** + * @internal + * @hidden + */ +export const ASSET_FIELD_PATH = ['asset'] as const + +/** + * @internal + * @hidden + */ +export const ASSET_IMAGE_MENU_POPOVER: MenuButtonProps['popover'] = {portal: true} as const From 68400b1c20dc2aad7fad84af8171376f1455d553 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Wed, 12 Jun 2024 22:47:28 +0200 Subject: [PATCH 04/11] refactor: extract `ImageInput-> renderUploadPlaceholder ` into FC --- .../inputs/files/ImageInput/ImageInput.tsx | 41 ++++--------- .../ImageInputUploadPlaceholder.tsx | 60 +++++++++++++++++++ 2 files changed, 71 insertions(+), 30 deletions(-) create mode 100644 packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputUploadPlaceholder.tsx diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx index c6b78fc3c4d..e8534853ed5 100644 --- a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx @@ -5,7 +5,7 @@ import {isImageSource} from '@sanity/asset-utils' import {ChevronDownIcon, ImageIcon, SearchIcon} from '@sanity/icons' import {type AssetFromSource, type AssetSource, type Path, type UploadState} from '@sanity/types' -import {Card, Menu, Stack, type ToastParams} from '@sanity/ui' +import {Menu, Stack, type ToastParams} from '@sanity/ui' import {get, startCase} from 'lodash' import {type FocusEvent, PureComponent, type ReactNode} from 'react' import {type Subscription} from 'rxjs' @@ -25,13 +25,13 @@ import {type InputProps} from '../../../types' import {WithReferencedAsset} from '../../../utils/WithReferencedAsset' import {ActionsMenu} from '../common/ActionsMenu' import {handleSelectAssetFromSource} from '../common/assetSource' -import {UploadPlaceholder} from '../common/UploadPlaceholder' import {UploadProgress} from '../common/UploadProgress' import {ASSET_FIELD_PATH, ASSET_IMAGE_MENU_POPOVER} from './constants' import {ImageActionsMenu, ImageActionsMenuWaitPlaceholder} from './ImageActionsMenu' import {ImageInputAsset} from './ImageInputAsset' import {ImageInputHotspotInput} from './ImageInputHotspotInput' import {ImageInputPreview} from './ImageInputPreview' +import {ImageInputUploadPlaceholder} from './ImageInputUploadPlaceholder' import {InvalidImageWarning} from './InvalidImageWarning' import {type BaseImageInputProps, type BaseImageInputValue, type FileInfo} from './types' @@ -527,37 +527,18 @@ export class BaseImageInput extends PureComponent resolveUploader(schemaType, file)) - const rejectedFilesCount = hoveringFiles.length - acceptedFiles.length - - const accept = get(schemaType, 'options.accept', 'image/*') - return ( -
- - - -
+ ) } diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputUploadPlaceholder.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputUploadPlaceholder.tsx new file mode 100644 index 00000000000..440c2bdf4fc --- /dev/null +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputUploadPlaceholder.tsx @@ -0,0 +1,60 @@ +import {Card} from '@sanity/ui' +import {get} from 'lodash' +import {memo, useMemo} from 'react' + +import {UploadPlaceholder} from '../common/UploadPlaceholder' +import {type BaseImageInputProps, type FileInfo} from './types' + +function ImageInputUploadPlaceholderComponent(props: { + directUploads: boolean | undefined + handleSelectFiles: (files: File[]) => void + hoveringFiles: FileInfo[] + readOnly: boolean | undefined + renderBrowser(): JSX.Element | null + resolveUploader: BaseImageInputProps['resolveUploader'] + schemaType: BaseImageInputProps['schemaType'] +}) { + const { + directUploads, + handleSelectFiles, + hoveringFiles, + readOnly, + renderBrowser, + resolveUploader, + schemaType, + } = props + + const acceptedFiles = useMemo( + () => hoveringFiles.filter((file) => resolveUploader(schemaType, file)), + [hoveringFiles, resolveUploader, schemaType], + ) + const accept = useMemo(() => get(schemaType, 'options.accept', 'image/*'), [schemaType]) + + const rejectedFilesCount = hoveringFiles.length - acceptedFiles.length + + return ( +
+ + + +
+ ) +} +export const ImageInputUploadPlaceholder = memo(ImageInputUploadPlaceholderComponent) From f500d8c456dcb3bb1c3c2d8a71a68e818b743e16 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Wed, 12 Jun 2024 23:09:47 +0200 Subject: [PATCH 05/11] refactor: extract `ImageInput->renderAssetSource` into FC --- .../inputs/files/ImageInput/ImageInput.tsx | 41 ++++--------- .../ImageInput/ImageInputAssetSource.tsx | 57 +++++++++++++++++++ 2 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetSource.tsx diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx index e8534853ed5..70613224149 100644 --- a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx @@ -24,11 +24,12 @@ import { import {type InputProps} from '../../../types' import {WithReferencedAsset} from '../../../utils/WithReferencedAsset' import {ActionsMenu} from '../common/ActionsMenu' -import {handleSelectAssetFromSource} from '../common/assetSource' +import {handleSelectAssetFromSource as _handleSelectAssetFromSource} from '../common/assetSource' import {UploadProgress} from '../common/UploadProgress' import {ASSET_FIELD_PATH, ASSET_IMAGE_MENU_POPOVER} from './constants' import {ImageActionsMenu, ImageActionsMenuWaitPlaceholder} from './ImageActionsMenu' import {ImageInputAsset} from './ImageInputAsset' +import {ImageInputAssetSource} from './ImageInputAssetSource' import {ImageInputHotspotInput} from './ImageInputHotspotInput' import {ImageInputPreview} from './ImageInputPreview' import {ImageInputUploadPlaceholder} from './ImageInputUploadPlaceholder' @@ -263,7 +264,7 @@ export class BaseImageInput extends PureComponent { const {onChange, schemaType, resolveUploader} = this.props - handleSelectAssetFromSource({ + _handleSelectAssetFromSource({ assetFromSource, onChange, type: schemaType, @@ -560,37 +561,15 @@ export class BaseImageInput extends PureComponent - {(imageAsset) => ( - - )} - - ) - } return ( - ) } diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetSource.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetSource.tsx new file mode 100644 index 00000000000..cf9765e4028 --- /dev/null +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetSource.tsx @@ -0,0 +1,57 @@ +import {type AssetFromSource, type AssetSource} from '@sanity/types' +import {get} from 'lodash' +import {memo, useMemo} from 'react' + +import {WithReferencedAsset} from '../../../utils/WithReferencedAsset' +import {type BaseImageInputProps} from './types' + +function ImageInputAssetSourceComponent( + props: Pick & { + selectedAssetSource: AssetSource | null + handleAssetSourceClosed: () => void + handleSelectAssetFromSource: (assetFromSource: AssetFromSource[]) => void + }, +) { + const { + handleAssetSourceClosed, + handleSelectAssetFromSource, + observeAsset, + schemaType, + selectedAssetSource, + value, + } = props + const accept = useMemo(() => get(schemaType, 'options.accept', 'image/*'), [schemaType]) + + if (!selectedAssetSource) { + return null + } + const {component: Component} = selectedAssetSource + + if (value && value.asset) { + return ( + + {(imageAsset) => ( + + )} + + ) + } + return ( + + ) +} +export const ImageInputAssetSource = memo(ImageInputAssetSourceComponent) From a321d5a79b34fec388a9e5a370de0c852d36c6f1 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Wed, 12 Jun 2024 23:57:08 +0200 Subject: [PATCH 06/11] refactor: extract `ImageInput->renderAssetMenu` into FC --- dev/test-next-studio/package.json | 1 + package.json | 3 +- .../inputs/files/ImageInput/ImageInput.tsx | 108 +++---------- .../files/ImageInput/ImageInputAssetMenu.tsx | 142 ++++++++++++++++++ pnpm-lock.yaml | 13 +- 5 files changed, 176 insertions(+), 91 deletions(-) create mode 100644 packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetMenu.tsx diff --git a/dev/test-next-studio/package.json b/dev/test-next-studio/package.json index 547802019b4..753ca3766d7 100644 --- a/dev/test-next-studio/package.json +++ b/dev/test-next-studio/package.json @@ -16,6 +16,7 @@ "next": "15.0.0-rc.0", "react": "19.0.0-rc-38e3b23483-20240529", "react-dom": "19.0.0-rc-38e3b23483-20240529", + "react-is": "19.0.0-rc-38e3b23483-20240529", "sanity": "workspace:*", "sanity-test-studio": "workspace:*", "styled-components": "^6.1.11", diff --git a/package.json b/package.json index af55bb08fb6..e8a29d4281e 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,8 @@ "peerDependencyRules": { "allowAny": [ "react", - "react-dom" + "react-dom", + "react-is" ] }, "overrides": { diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx index 70613224149..b63569bed77 100644 --- a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx @@ -22,13 +22,11 @@ import { type UploadOptions, } from '../../../studio/uploads/types' import {type InputProps} from '../../../types' -import {WithReferencedAsset} from '../../../utils/WithReferencedAsset' -import {ActionsMenu} from '../common/ActionsMenu' import {handleSelectAssetFromSource as _handleSelectAssetFromSource} from '../common/assetSource' import {UploadProgress} from '../common/UploadProgress' import {ASSET_FIELD_PATH, ASSET_IMAGE_MENU_POPOVER} from './constants' -import {ImageActionsMenu, ImageActionsMenuWaitPlaceholder} from './ImageActionsMenu' import {ImageInputAsset} from './ImageInputAsset' +import {ImageInputAssetMenu} from './ImageInputAssetMenu' import {ImageInputAssetSource} from './ImageInputAssetSource' import {ImageInputHotspotInput} from './ImageInputHotspotInput' import {ImageInputPreview} from './ImageInputPreview' @@ -370,98 +368,34 @@ export class BaseImageInput extends PureComponent { - this.setState({isMenuOpen: false}) - this.handleSelectImageFromAssetSource(assetSources[0]) - }} - disabled={readOnly} - data-testid="file-input-browse-button" - /> - ) - if (assetSources && assetSources.length > 1) { - browseMenuItem = assetSources.map((assetSource) => { - return ( - { - this.setState({isMenuOpen: false}) - this.handleSelectImageFromAssetSource(assetSource) - }} - icon={assetSource.icon || ImageIcon} - data-testid={`file-input-browse-button-${assetSource.name}`} - disabled={readOnly} - /> - ) - }) - } - return ( - } - > - {({_id, originalFilename, extension}) => { - let copyUrl: string | undefined - let downloadUrl: string | undefined - - if (isImageSource(value)) { - const filename = originalFilename || `download.${extension}` - downloadUrl = imageUrlBuilder.image(_id).forceDownload(filename).url() - copyUrl = imageUrlBuilder.image(_id).url() - } - - return ( - this.setState({isMenuOpen: isOpen})} - setHotspotButtonElement={this.setHotspotButtonElement} - setMenuButtonElement={this.setMenuButtonElement} - showEdit={showAdvancedEditButton} - > - - - ) - }} - + readOnly={readOnly} + schemaType={schemaType} + setHotspotButtonElement={this.setHotspotButtonElement.bind(this)} + setMenuButtonElement={this.setMenuButtonElement.bind(this)} + setMenuOpen={(isOpen) => this.setState({isMenuOpen: isOpen})} + value={value} + /> ) } diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetMenu.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetMenu.tsx new file mode 100644 index 00000000000..53279d15148 --- /dev/null +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetMenu.tsx @@ -0,0 +1,142 @@ +import {isImageSource} from '@sanity/asset-utils' +import {ImageIcon, SearchIcon} from '@sanity/icons' +import {type AssetSource} from '@sanity/types' +import {get, startCase} from 'lodash' +import {memo, type ReactNode, useMemo} from 'react' + +import {MenuItem} from '../../../../../ui-components' +import {useTranslation} from '../../../../i18n' +import {WithReferencedAsset} from '../../../utils/WithReferencedAsset' +import {ActionsMenu} from '../common/ActionsMenu' +import {ImageActionsMenu, ImageActionsMenuWaitPlaceholder} from './ImageActionsMenu' +import {type BaseImageInputProps} from './types' + +function ImageInputAssetMenuComponent( + props: Pick< + BaseImageInputProps, + | 'assetSources' + | 'directUploads' + | 'imageUrlBuilder' + | 'observeAsset' + | 'readOnly' + | 'schemaType' + | 'value' + > & { + handleOpenDialog: () => void + handleRemoveButtonClick: () => void + handleSelectFiles: (files: File[]) => void + handleSelectImageFromAssetSource: (source: AssetSource) => void + isImageToolEnabled: boolean + isMenuOpen: boolean + setHotspotButtonElement: (el: HTMLButtonElement | null) => void + setMenuButtonElement: (el: HTMLButtonElement | null) => void + setMenuOpen: (isOpen: boolean) => void + }, +) { + const { + assetSources, + directUploads, + handleOpenDialog, + handleRemoveButtonClick, + handleSelectFiles, + handleSelectImageFromAssetSource, + imageUrlBuilder, + isImageToolEnabled, + isMenuOpen, + observeAsset, + readOnly, + schemaType, + setHotspotButtonElement, + setMenuButtonElement, + setMenuOpen, + value, + } = props + const {t} = useTranslation() + + const accept = useMemo(() => get(schemaType, 'options.accept', 'image/*'), [schemaType]) + const asset = value?.asset + + const showAdvancedEditButton = value && asset && isImageToolEnabled + + if (!asset) { + return null + } + + let browseMenuItem: ReactNode = + assetSources && assetSources.length === 0 ? null : ( + { + setMenuOpen(false) + handleSelectImageFromAssetSource(assetSources[0]) + }} + disabled={readOnly} + data-testid="file-input-browse-button" + /> + ) + if (assetSources && assetSources.length > 1) { + browseMenuItem = assetSources.map((assetSource) => { + return ( + { + setMenuOpen(false) + handleSelectImageFromAssetSource(assetSource) + }} + icon={assetSource.icon || ImageIcon} + data-testid={`file-input-browse-button-${assetSource.name}`} + disabled={readOnly} + /> + ) + }) + } + + return ( + } + > + {({_id, originalFilename, extension}) => { + let copyUrl: string | undefined + let downloadUrl: string | undefined + + if (isImageSource(value)) { + const filename = originalFilename || `download.${extension}` + downloadUrl = imageUrlBuilder.image(_id).forceDownload(filename).url() + copyUrl = imageUrlBuilder.image(_id).url() + } + + return ( + + + + ) + }} + + ) +} +export const ImageInputAssetMenu = memo(ImageInputAssetMenuComponent) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4dba75664a..6b8bd562c60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -406,6 +406,9 @@ importers: react-dom: specifier: 19.0.0-rc-38e3b23483-20240529 version: 19.0.0-rc-38e3b23483-20240529(react@19.0.0-rc-38e3b23483-20240529) + react-is: + specifier: 19.0.0-rc-38e3b23483-20240529 + version: 19.0.0-rc-38e3b23483-20240529 sanity: specifier: workspace:* version: link:../../packages/sanity @@ -7358,7 +7361,7 @@ packages: peerDependencies: react: '*' react-dom: '*' - react-is: ^18 + react-is: '*' styled-components: ^5.2 || ^6 dependencies: '@floating-ui/react-dom': 2.1.0(react-dom@18.3.1)(react@18.3.1) @@ -17650,6 +17653,10 @@ packages: /react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + /react-is@19.0.0-rc-38e3b23483-20240529: + resolution: {integrity: sha512-lbE0KBiS81mr3nIozqDEWqIXcBOKfdaLFoi76Fyj1Dp5WLhzD4Vd7xQpmlsU1iFPSQ70zU5Frz5Gf3z3WB6fAg==} + dev: false + /react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} dev: false @@ -18344,7 +18351,7 @@ packages: engines: {node: '>=18'} peerDependencies: react: '*' - react-is: ^18 + react-is: '*' sanity: ^3.42.0 styled-components: ^5 || ^6 dependencies: @@ -19207,7 +19214,7 @@ packages: peerDependencies: react: '*' react-dom: '*' - react-is: '>= 16.8.0' + react-is: '*' dependencies: '@babel/helper-module-imports': 7.24.7(supports-color@5.5.0) '@babel/traverse': 7.24.7(supports-color@5.5.0) From 85b7d209804294480bae16bea21d9096970f30d0 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Thu, 13 Jun 2024 00:33:20 +0200 Subject: [PATCH 07/11] refactor: use hooks instead of HOC --- package.json | 3 +- .../files/ImageInput/ImageInputAssetMenu.tsx | 136 +++++++++++++----- pnpm-lock.yaml | 69 ++++----- 3 files changed, 136 insertions(+), 72 deletions(-) diff --git a/package.json b/package.json index e8a29d4281e..3b0f87d9ac8 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,8 @@ }, "overrides": { "@typescript-eslint/eslint-plugin": "$@typescript-eslint/eslint-plugin", - "@typescript-eslint/parser": "$@typescript-eslint/parser" + "@typescript-eslint/parser": "$@typescript-eslint/parser", + "@sanity/ui": "2.3.4-canary.0" } }, "isSanityMonorepo": true diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetMenu.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetMenu.tsx index 53279d15148..678edbbf2a7 100644 --- a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetMenu.tsx +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputAssetMenu.tsx @@ -1,12 +1,13 @@ import {isImageSource} from '@sanity/asset-utils' import {ImageIcon, SearchIcon} from '@sanity/icons' -import {type AssetSource} from '@sanity/types' +import {type AssetSource, type ImageAsset, type Reference} from '@sanity/types' import {get, startCase} from 'lodash' import {memo, type ReactNode, useMemo} from 'react' +import {useObservable} from 'react-rx' +import {type Observable} from 'rxjs' import {MenuItem} from '../../../../../ui-components' import {useTranslation} from '../../../../i18n' -import {WithReferencedAsset} from '../../../utils/WithReferencedAsset' import {ActionsMenu} from '../common/ActionsMenu' import {ImageActionsMenu, ImageActionsMenuWaitPlaceholder} from './ImageActionsMenu' import {type BaseImageInputProps} from './types' @@ -99,44 +100,105 @@ function ImageInputAssetMenuComponent( } return ( - } - > - {({_id, originalFilename, extension}) => { - let copyUrl: string | undefined - let downloadUrl: string | undefined + schemaType={schemaType} + setHotspotButtonElement={setHotspotButtonElement} + setMenuButtonElement={setMenuButtonElement} + setMenuOpen={setMenuOpen} + showAdvancedEditButton={!!showAdvancedEditButton} + value={value} + /> + ) +} +export const ImageInputAssetMenu = memo(ImageInputAssetMenuComponent) + +function ImageInputAssetMenuWithReferenceAssetComponent( + props: Pick< + BaseImageInputProps, + 'directUploads' | 'imageUrlBuilder' | 'observeAsset' | 'readOnly' | 'schemaType' | 'value' + > & { + accept: string + browseMenuItem: ReactNode + handleOpenDialog: () => void + handleRemoveButtonClick: () => void + handleSelectFiles: (files: File[]) => void + isMenuOpen: boolean + observeAsset: (assetId: string) => Observable + reference: Reference + setHotspotButtonElement: (el: HTMLButtonElement | null) => void + setMenuButtonElement: (el: HTMLButtonElement | null) => void + setMenuOpen: (isOpen: boolean) => void + showAdvancedEditButton: boolean + }, +) { + const { + accept, + browseMenuItem, + directUploads, + handleOpenDialog, + handleRemoveButtonClick, + handleSelectFiles, + imageUrlBuilder, + isMenuOpen, + observeAsset, + readOnly, + reference, + setHotspotButtonElement, + setMenuButtonElement, + setMenuOpen, + showAdvancedEditButton, + value, + } = props + + const documentId = reference?._ref + const observable = useMemo(() => observeAsset(documentId), [documentId, observeAsset]) + const asset = useObservable(observable) + + if (!documentId || !asset) { + return + } + + const {_id, originalFilename, extension} = asset + let copyUrl: string | undefined + let downloadUrl: string | undefined - if (isImageSource(value)) { - const filename = originalFilename || `download.${extension}` - downloadUrl = imageUrlBuilder.image(_id).forceDownload(filename).url() - copyUrl = imageUrlBuilder.image(_id).url() - } + if (isImageSource(value)) { + const filename = originalFilename || `download.${extension}` + downloadUrl = imageUrlBuilder.image(_id).forceDownload(filename).url() + copyUrl = imageUrlBuilder.image(_id).url() + } - return ( - - - - ) - }} - + return ( + + + ) } -export const ImageInputAssetMenu = memo(ImageInputAssetMenuComponent) +const ImageInputAssetMenuWithReferenceAsset = memo(ImageInputAssetMenuWithReferenceAssetComponent) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b8bd562c60..b415d61b74f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,7 @@ settings: overrides: '@typescript-eslint/eslint-plugin': ^7.11.0 '@typescript-eslint/parser': ^7.11.0 + '@sanity/ui': 2.3.4-canary.0 importers: @@ -238,8 +239,8 @@ importers: specifier: ^3.0.0 version: 3.2.0(react@18.3.1) '@sanity/ui': - specifier: ^2.3.3 - version: 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + specifier: 2.3.4-canary.0 + version: 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) react: specifier: ^18.3.1 version: 18.3.1 @@ -256,8 +257,8 @@ importers: dev/embedded-studio: dependencies: '@sanity/ui': - specifier: ^2.3.3 - version: 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + specifier: 2.3.4-canary.0 + version: 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) react: specifier: ^18.3.1 version: 18.3.1 @@ -365,8 +366,8 @@ importers: specifier: ^3.0.0 version: 3.2.0(react@18.3.1) '@sanity/ui': - specifier: ^2.3.3 - version: 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + specifier: 2.3.4-canary.0 + version: 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@sanity/vision': specifier: 3.46.0 version: 3.46.0(@babel/runtime@7.24.7)(@codemirror/lint@6.8.0)(@codemirror/theme-one-dark@6.1.2)(@lezer/common@1.2.1)(codemirror@6.0.1)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) @@ -494,11 +495,11 @@ importers: specifier: workspace:* version: link:../../packages/@sanity/types '@sanity/ui': - specifier: ^2.3.3 - version: 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + specifier: 2.3.4-canary.0 + version: 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@sanity/ui-workshop': specifier: ^1.0.0 - version: 1.2.11(@sanity/icons@3.2.0)(@sanity/ui@2.3.3)(@types/node@18.19.31)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.1.11) + version: 1.2.11(@sanity/icons@3.2.0)(@sanity/ui@2.3.4-canary.0)(@types/node@18.19.31)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@sanity/util': specifier: workspace:* version: link:../../packages/@sanity/util @@ -564,7 +565,7 @@ importers: version: link:../../packages/sanity sanity-plugin-hotspot-array: specifier: ^2.0.0 - version: 2.0.0(@sanity/ui@2.3.3)(react-dom@18.3.1)(react@18.3.1)(sanity@packages+sanity)(styled-components@6.1.11) + version: 2.0.0(@sanity/ui@2.3.4-canary.0)(react-dom@18.3.1)(react@18.3.1)(sanity@packages+sanity)(styled-components@6.1.11) sanity-plugin-mux-input: specifier: ^2.2.1 version: 2.3.6(@types/react@18.3.3)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(sanity@packages+sanity)(styled-components@6.1.11) @@ -618,8 +619,8 @@ importers: specifier: 3.46.1 version: link:../../packages/@sanity/cli '@sanity/ui': - specifier: ^2.3.1 - version: 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + specifier: 2.3.4-canary.0 + version: 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) react: specifier: ^18.3.1 version: 18.3.1 @@ -1178,8 +1179,8 @@ importers: specifier: ^3.1.1 version: 3.1.1 '@sanity/ui': - specifier: ^2.3.1 - version: 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + specifier: 2.3.4-canary.0 + version: 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@testing-library/react': specifier: ^13.4.0 version: 13.4.0(react-dom@18.3.1)(react@18.3.1) @@ -1394,8 +1395,8 @@ importers: specifier: ^3.0.0 version: 3.2.0(react@18.3.1) '@sanity/ui': - specifier: ^2.3.1 - version: 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + specifier: 2.3.4-canary.0 + version: 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@uiw/react-codemirror': specifier: ^4.11.4 version: 4.21.25(@babel/runtime@7.24.7)(@codemirror/autocomplete@6.16.2)(@codemirror/language@6.10.2)(@codemirror/lint@6.8.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.28.0)(codemirror@6.0.1)(react-dom@18.3.1)(react@18.3.1) @@ -1566,8 +1567,8 @@ importers: specifier: 3.46.1 version: link:../@sanity/types '@sanity/ui': - specifier: ^2.3.1 - version: 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + specifier: 2.3.4-canary.0 + version: 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@sanity/util': specifier: 3.46.1 version: link:../@sanity/util @@ -1847,7 +1848,7 @@ importers: version: 1.0.72(@types/node@18.19.31)(debug@4.3.4)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(sanity@packages+sanity)(styled-components@6.1.11) '@sanity/ui-workshop': specifier: ^1.2.11 - version: 1.2.11(@sanity/icons@3.2.0)(@sanity/ui@2.3.3)(@types/node@18.19.31)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.1.11) + version: 1.2.11(@sanity/icons@3.2.0)(@sanity/ui@2.3.4-canary.0)(@types/node@18.19.31)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@testing-library/jest-dom': specifier: ^6.2.0 version: 6.4.2(@jest/globals@29.7.0)(jest@29.7.0) @@ -6691,7 +6692,7 @@ packages: '@sanity/icons': 2.11.8(react@18.3.1) '@sanity/incompatible-plugin': 1.0.4(react-dom@18.3.1)(react@18.3.1) '@sanity/mutator': link:packages/@sanity/mutator - '@sanity/ui': 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + '@sanity/ui': 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) date-fns: 3.6.0 lodash: 4.17.21 lodash-es: 4.17.21 @@ -6838,7 +6839,7 @@ packages: dependencies: '@sanity/icons': 2.11.8(react@18.3.1) '@sanity/incompatible-plugin': 1.0.4(react-dom@18.3.1)(react@18.3.1) - '@sanity/ui': 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + '@sanity/ui': 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) lodash: 4.17.21 react: 18.3.1 sanity: link:packages/sanity @@ -7104,7 +7105,7 @@ packages: '@sanity/client': 6.20.0(debug@4.3.4) '@sanity/icons': 3.2.0(react@18.3.1) '@sanity/preview-url-secret': 1.6.17(@sanity/client@6.20.0) - '@sanity/ui': 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + '@sanity/ui': 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@sanity/uuid': 3.0.2 '@types/lodash.isequal': 4.5.8 fast-deep-equal: 3.1.3 @@ -7197,7 +7198,7 @@ packages: '@sanity/color': 3.0.6 '@sanity/icons': 3.2.0(react@18.3.1) '@sanity/pkg-utils': 6.9.3(@types/node@18.19.31)(debug@4.3.4)(typescript@5.4.5) - '@sanity/ui': 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + '@sanity/ui': 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@types/cpx': 1.5.5 '@vitejs/plugin-react': 4.3.1(vite@5.2.13) cac: 6.7.14 @@ -7258,7 +7259,7 @@ packages: '@sanity/color': 3.0.6 '@sanity/icons': 3.2.0(react@18.3.1) '@sanity/pkg-utils': 6.9.3(@types/node@18.19.31)(typescript@5.4.5) - '@sanity/ui': 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + '@sanity/ui': 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@types/cpx': 1.5.5 '@vitejs/plugin-react': 4.3.1(vite@5.2.13) cac: 6.7.14 @@ -7316,18 +7317,18 @@ packages: - debug dev: false - /@sanity/ui-workshop@1.2.11(@sanity/icons@3.2.0)(@sanity/ui@2.3.3)(@types/node@18.19.31)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.1.11): + /@sanity/ui-workshop@1.2.11(@sanity/icons@3.2.0)(@sanity/ui@2.3.4-canary.0)(@types/node@18.19.31)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.1.11): resolution: {integrity: sha512-vzj7upIF7wq2W1HEA0D5VSkR8axaH4Rt07yNTAaas7CLgjSE9r2d+Gnkrq4FIbIuN1GYhhCD+D3/s60GaZrpQw==} hasBin: true peerDependencies: '@sanity/icons': ^2 - '@sanity/ui': ^1 + '@sanity/ui': 2.3.4-canary.0 react: '*' react-dom: '*' styled-components: ^5.2 || ^6 dependencies: '@sanity/icons': 3.2.0(react@18.3.1) - '@sanity/ui': 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + '@sanity/ui': 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@vitejs/plugin-react': 4.3.1(vite@4.5.3) axe-core: 4.9.0 cac: 6.7.14 @@ -7355,8 +7356,8 @@ packages: - supports-color - terser - /@sanity/ui@2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11): - resolution: {integrity: sha512-TuAUDvqFbO+A4OU78I4gFnGfh+oPugsTKj7Kf0FwdQO8xWn26PepGt5BkKEh8RKC36dX39bdhUPKPIkIrsKd9A==} + /@sanity/ui@2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11): + resolution: {integrity: sha512-/EsEA5qlSGj4bSWb7hA8+7juyI/jNP+PWeP2HvsvaBkcR36vqlLTygkhHhl0EtzHI/MCB6NbL5L0fYGbmFCKWg==} engines: {node: '>=14.0.0'} peerDependencies: react: '*' @@ -7426,7 +7427,7 @@ packages: '@rexxars/react-split-pane': 0.1.93(react-dom@18.3.1)(react@18.3.1) '@sanity/color': 3.0.6 '@sanity/icons': 3.2.0(react@18.3.1) - '@sanity/ui': 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + '@sanity/ui': 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@uiw/react-codemirror': 4.21.25(@babel/runtime@7.24.7)(@codemirror/autocomplete@6.16.2)(@codemirror/language@6.10.2)(@codemirror/lint@6.8.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.28.0)(codemirror@6.0.1)(react-dom@18.3.1)(react@18.3.1) is-hotkey-esm: 1.0.0 json-2-csv: 5.5.1 @@ -18321,11 +18322,11 @@ packages: '@sanity/diff-match-patch': 3.1.1 dev: false - /sanity-plugin-hotspot-array@2.0.0(@sanity/ui@2.3.3)(react-dom@18.3.1)(react@18.3.1)(sanity@packages+sanity)(styled-components@6.1.11): + /sanity-plugin-hotspot-array@2.0.0(@sanity/ui@2.3.4-canary.0)(react-dom@18.3.1)(react@18.3.1)(sanity@packages+sanity)(styled-components@6.1.11): resolution: {integrity: sha512-y+FP4JgRaIKO17cBMyzCCVcxwl3fh7DXEp99QlvZSWUFi3NJJg2ZXFIXc2Om66HNkprfH2ORzEmEZMuDShtlTg==} engines: {node: '>=18'} peerDependencies: - '@sanity/ui': ^2.0.0 + '@sanity/ui': 2.3.4-canary.0 react: '*' sanity: ^3.0.0 styled-components: ^6.1 @@ -18333,7 +18334,7 @@ packages: '@sanity/asset-utils': 1.3.0 '@sanity/image-url': 1.0.2 '@sanity/incompatible-plugin': 1.0.4(react-dom@18.3.1)(react@18.3.1) - '@sanity/ui': 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + '@sanity/ui': 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@sanity/util': 3.46.1 '@types/lodash-es': 4.17.12 framer-motion: 11.0.8(react-dom@18.3.1)(react@18.3.1) @@ -18359,7 +18360,7 @@ packages: '@mux/upchunk': 3.4.0 '@sanity/icons': 3.2.0(react@18.3.1) '@sanity/incompatible-plugin': 1.0.4(react-dom@18.3.1)(react@18.3.1) - '@sanity/ui': 2.3.3(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) + '@sanity/ui': 2.3.4-canary.0(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.11) '@sanity/uuid': 3.0.2 iso-639-1: 3.1.2 jsonwebtoken-esm: 1.0.5 From 3419d63b8e570e36ec12853dc9282f21482e8b7f Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Thu, 13 Jun 2024 13:21:53 +0200 Subject: [PATCH 08/11] refactor: extract `ImageInput->renderBrowser` into FC --- dev/design-studio/package.json | 2 +- dev/embedded-studio/package.json | 2 +- dev/studio-e2e-testing/package.json | 2 +- dev/test-studio/package.json | 2 +- examples/ecommerce-studio/package.json | 2 +- package.json | 3 +- .../@sanity/portable-text-editor/package.json | 2 +- packages/@sanity/vision/package.json | 2 +- packages/sanity/package.json | 2 +- .../inputs/files/ImageInput/ImageInput.tsx | 71 ++++--------------- .../files/ImageInput/ImageInputBrowser.tsx | 67 +++++++++++++++++ pnpm-lock.yaml | 69 +++++++++--------- 12 files changed, 122 insertions(+), 104 deletions(-) create mode 100644 packages/sanity/src/core/form/inputs/files/ImageInput/ImageInputBrowser.tsx diff --git a/dev/design-studio/package.json b/dev/design-studio/package.json index 4a90fdd136d..32e3ab29391 100644 --- a/dev/design-studio/package.json +++ b/dev/design-studio/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@sanity/icons": "^3.0.0", - "@sanity/ui": "^2.3.3", + "@sanity/ui": "^2.3.5", "react": "^18.3.1", "react-dom": "^18.3.1", "sanity": "workspace:*", diff --git a/dev/embedded-studio/package.json b/dev/embedded-studio/package.json index 8b9aabaf4c0..4f32ec51fff 100644 --- a/dev/embedded-studio/package.json +++ b/dev/embedded-studio/package.json @@ -8,7 +8,7 @@ "preview": "vite preview" }, "dependencies": { - "@sanity/ui": "^2.3.3", + "@sanity/ui": "^2.3.5", "react": "^18.3.1", "react-dom": "^18.3.1", "sanity": "workspace:*", diff --git a/dev/studio-e2e-testing/package.json b/dev/studio-e2e-testing/package.json index 326286dbeb2..cfd9245ef1c 100644 --- a/dev/studio-e2e-testing/package.json +++ b/dev/studio-e2e-testing/package.json @@ -16,7 +16,7 @@ "dependencies": { "@sanity/google-maps-input": "^4.0.0", "@sanity/icons": "^3.0.0", - "@sanity/ui": "^2.3.3", + "@sanity/ui": "^2.3.5", "@sanity/vision": "3.46.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/dev/test-studio/package.json b/dev/test-studio/package.json index 89a56748dba..2c6302babb9 100644 --- a/dev/test-studio/package.json +++ b/dev/test-studio/package.json @@ -39,7 +39,7 @@ "@sanity/react-loader": "^1.8.3", "@sanity/tsdoc": "1.0.72", "@sanity/types": "workspace:*", - "@sanity/ui": "^2.3.3", + "@sanity/ui": "^2.3.5", "@sanity/ui-workshop": "^1.0.0", "@sanity/util": "workspace:*", "@sanity/uuid": "^3.0.1", diff --git a/examples/ecommerce-studio/package.json b/examples/ecommerce-studio/package.json index cfedc348ae3..c5caedd865b 100644 --- a/examples/ecommerce-studio/package.json +++ b/examples/ecommerce-studio/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@sanity/cli": "3.46.1", - "@sanity/ui": "^2.3.1", + "@sanity/ui": "^2.3.5", "react": "^18.3.1", "react-barcode": "^1.4.1", "react-dom": "^18.3.1", diff --git a/package.json b/package.json index 3b0f87d9ac8..e8a29d4281e 100644 --- a/package.json +++ b/package.json @@ -181,8 +181,7 @@ }, "overrides": { "@typescript-eslint/eslint-plugin": "$@typescript-eslint/eslint-plugin", - "@typescript-eslint/parser": "$@typescript-eslint/parser", - "@sanity/ui": "2.3.4-canary.0" + "@typescript-eslint/parser": "$@typescript-eslint/parser" } }, "isSanityMonorepo": true diff --git a/packages/@sanity/portable-text-editor/package.json b/packages/@sanity/portable-text-editor/package.json index 2f260054294..e88a23889da 100644 --- a/packages/@sanity/portable-text-editor/package.json +++ b/packages/@sanity/portable-text-editor/package.json @@ -74,7 +74,7 @@ "@portabletext/toolkit": "^2.0.15", "@repo/package.config": "workspace:*", "@sanity/diff-match-patch": "^3.1.1", - "@sanity/ui": "^2.3.1", + "@sanity/ui": "^2.3.5", "@testing-library/react": "^13.4.0", "@types/debug": "^4.1.5", "@types/express": "^4.17.21", diff --git a/packages/@sanity/vision/package.json b/packages/@sanity/vision/package.json index a74819f0b50..b508aacd019 100644 --- a/packages/@sanity/vision/package.json +++ b/packages/@sanity/vision/package.json @@ -63,7 +63,7 @@ "@rexxars/react-split-pane": "^0.1.93", "@sanity/color": "^3.0.0", "@sanity/icons": "^3.0.0", - "@sanity/ui": "^2.3.1", + "@sanity/ui": "^2.3.5", "@uiw/react-codemirror": "^4.11.4", "is-hotkey-esm": "^1.0.0", "json-2-csv": "^5.5.1", diff --git a/packages/sanity/package.json b/packages/sanity/package.json index f0a350a4467..ef78e82f7d6 100644 --- a/packages/sanity/package.json +++ b/packages/sanity/package.json @@ -166,7 +166,7 @@ "@sanity/schema": "3.46.1", "@sanity/telemetry": "^0.7.7", "@sanity/types": "3.46.1", - "@sanity/ui": "^2.3.1", + "@sanity/ui": "^2.3.5", "@sanity/util": "3.46.1", "@sanity/uuid": "^3.0.1", "@tanstack/react-table": "^8.16.0", diff --git a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx index b63569bed77..dd5083737b5 100644 --- a/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx +++ b/packages/sanity/src/core/form/inputs/files/ImageInput/ImageInput.tsx @@ -3,14 +3,12 @@ /* eslint-disable react/display-name */ /* eslint-disable react/jsx-handler-names */ import {isImageSource} from '@sanity/asset-utils' -import {ChevronDownIcon, ImageIcon, SearchIcon} from '@sanity/icons' import {type AssetFromSource, type AssetSource, type Path, type UploadState} from '@sanity/types' -import {Menu, Stack, type ToastParams} from '@sanity/ui' -import {get, startCase} from 'lodash' +import {Stack, type ToastParams} from '@sanity/ui' +import {get} from 'lodash' import {type FocusEvent, PureComponent, type ReactNode} from 'react' import {type Subscription} from 'rxjs' -import {Button, MenuButton, MenuItem} from '../../../../../ui-components' import {ImperativeToast} from '../../../../components' import {FormInput} from '../../../components' import {MemberField, MemberFieldError, MemberFieldSet} from '../../../members' @@ -24,10 +22,11 @@ import { import {type InputProps} from '../../../types' import {handleSelectAssetFromSource as _handleSelectAssetFromSource} from '../common/assetSource' import {UploadProgress} from '../common/UploadProgress' -import {ASSET_FIELD_PATH, ASSET_IMAGE_MENU_POPOVER} from './constants' +import {ASSET_FIELD_PATH} from './constants' import {ImageInputAsset} from './ImageInputAsset' import {ImageInputAssetMenu} from './ImageInputAssetMenu' import {ImageInputAssetSource} from './ImageInputAssetSource' +import {ImageInputBrowser} from './ImageInputBrowser' import {ImageInputHotspotInput} from './ImageInputHotspotInput' import {ImageInputPreview} from './ImageInputPreview' import {ImageInputUploadPlaceholder} from './ImageInputUploadPlaceholder' @@ -400,62 +399,16 @@ export class BaseImageInput extends PureComponent 1 && !readOnly && directUploads) { - return ( - - } - menu={ - - {assetSources.map((assetSource) => { - return ( - { - this.setState({isMenuOpen: false}) - this.handleSelectImageFromAssetSource(assetSource) - }} - icon={assetSource.icon || ImageIcon} - disabled={readOnly} - data-testid={`file-input-browse-button-${assetSource.name}`} - /> - ) - })} - - } - popover={ASSET_IMAGE_MENU_POPOVER} - /> - ) - } + const {assetSources, readOnly, directUploads, id} = this.props return ( -