sanity-plugin-media
sanity-plugin-media copied to clipboard
(feat) Option to insert multiple images
In my projects I always use the sanity-plugin-media, so thanks for creating it! In many of these projects I also run into that I want to insert multiple images at once in an image array (ie for a carousel or gallery). Searching around, I found that more people have this issue, see the Github issues below or the Sanity Slack:
- https://github.com/sanity-io/sanity/issues/4483
- https://github.com/sanity-io/sanity/issues/1804
- https://github.com/sanity-io/sanity-plugin-media/issues/85
In this pull request, I add the option to insert multiple images at once. By adding the selectionType
'multiple' option, you can determine that the asset source should pass multiple assets. This is not officially supported, but I figured that is where Sanity wishes to go with the selectionType
property, since it currently only supports "single"
.
In this PR I support "multiple"
, without breaking- or changing any of the current functionality. If you do not pass the selectionType="multiple"
it will work the same.
To make this work in a project, you have to create a custom component that creates a AssetSourceComponent with the property selectionType
set to multiple
. I will pass mine as an example so you can see how this works:
'use client';
import { AssetFromSource, StringInputProps, set, useFormBuilder } from 'sanity';
import { Button, Stack, Text } from '@sanity/ui';
import { FaRegImages } from 'react-icons/fa';
import React, { Fragment, MouseEventHandler, useCallback, useMemo, useState } from 'react';
export const MultipleImagesInputComponent = (properties: StringInputProps) => {
const { onChange, value, renderDefault } = properties;
const formBuilder = useFormBuilder();
const [showAssetSource, setShowAssetSource] = useState(false);
// I would love for this not to use internal but it is currently the only way I think.
const AssetSourceComponent = useMemo(
() => formBuilder?.__internal?.image?.assetSources[0]?.component,
[formBuilder?.__internal?.image?.assetSources]
);
const handleAssetSourceClosed = useCallback(() => {
setShowAssetSource(false);
}, []);
const handleSelectAssetFromSource = useCallback(
(assetsFromSource: AssetFromSource[]) => {
try {
const assetReferences = assetsFromSource.map((asset) => ({
_key: randomKey(),
_type: 'image',
asset: {
_ref: asset.value,
_type: 'reference',
},
}));
const newValue = [...(value || []), ...assetReferences];
onChange(set(newValue));
} catch (error) {
console.error(error);
} finally {
setShowAssetSource(false);
}
},
[onChange, value]
);
const onMultipleImagesClick: MouseEventHandler<HTMLButtonElement> = useCallback(() => {
setShowAssetSource(true);
}, []);
return (
<Fragment>
<Stack space={4}>
{renderDefault(properties)}
<Text size={2} muted align="center">
💡 To upload multiple images, simply drag them on the list above.
</Text>
<Button
onClick={onMultipleImagesClick}
mode="ghost"
icon={FaRegImages}
text="Add multiple images from media"
/>
</Stack>
{showAssetSource && !!AssetSourceComponent ? (
<AssetSourceComponent
accept="image/*"
assetType="image"
onClose={handleAssetSourceClosed}
onSelect={handleSelectAssetFromSource}
selectedAssets={[]}
selectionType="multiple" // This is where the magic happens
/>
) : undefined}
</Fragment>
);
};
function randomKey() {
return Math.random().toString(36).slice(2);
}
I hope you will find this functionality useful. If there is a better way to achieve what I'm trying to do I love to hear that as well. Searching around, I found many people with this wish, but no solution, therefore I created this PR.