Custom Image Elements
Learn how to create a Custom Image Component. For this example, we'll modify the ImageBlock
Component by adding a title
attribute to the Image.
Let's start by looking at then modifing the type of the ImageBlockElement
.
Custom Image Type
🌞 Even if you aren't using TypeScript, we recommend reading this section. You can probably follow the meaning of the type declarations (e.g.
originKey: string
means theoriginKey
property takes astring
type value).
Here is the type for the ImageBlockElement
.
export type ImageBlockElement = {
type: "image-block"
originKey: string
originSize: [number, number]
size: [number, number]
children: [{ text: "" }]
}
This Element
has its type
set to "image-block"
. Also, this is a void
Element, so it has children
which is [{ text: "" }]
(a requirement for void
Elements).
It also has three other properties that are meaningful: An originKey
, originSize
and size
. These properties are part of the ImageFileInterface
and are required in order to use the HostedImage
subcomponent which takes care of resizing, and showing the upload progress and error state.
Here is the ImageFileInterface
:
export interface ImageFileInterface {
originKey: string
originSize: [number, number]
size: [number, number]
}
Although the ImageFileInterface
is the minimum requirement for a HostedImage
we can add other properties we desire to the Element
.
Let's create a new Element "titled-image-block"
which renders an <img>
with a title attribute.
Here's an Element
type definition that includes a title
property:
export type TitledImageBlockElement = {
type: "titled-image-block"
title: string // ✅ Add a `title` property for our titled image
originKey: string
originSize: [number, number]
size: [number, number]
children: [{ text: "" }]
}
Custom Image Component
Here's the Preset ImageBlock
Component.
import { RendeElementPropsFor, HostedImage } from "slate-portive"
export function ImageBlock({
attributes,
element,
children,
}: RenderElementPropsFor<ImageBlockElement>) {
return (
<div {...attributes} style={{ margin: "8px 0" }}>
<HostedImage
element={element}
style={{ borderRadius: element.size[0] < 100 ? 0 : 4 }}
/>
{children}
</div>
)
}
It may seem small. This is because the HostedImage
sub-component takes care of most of the hard work:
Resizing with drag handles
Showing the width/height during resize
Showing a progress bar while uploading
Showing an Error when an upload fails
Showing retina images for high DPI devices and normal resolution images for low DPI devices
From the perspective of the ImageBlock
Component, we can treat it just like an img
tag and it can take any img
attributes like a "title"
attribute for example.
Let's modify this to create our Custom TitledImageBlock
Component:
import { RendeElementPropsFor, HostedImage } from "slate-portive"
export function TitledImageBlock({
attributes,
element,
children,
}: // ✅ Change `ImageBlockElement` to `TitledImageBlockElement`
RenderElementPropsFor<TitledImageBlockElement>) {
return (
<div {...attributes} style={{ margin: "8px 0" }}>
{/* ✅ Add `element.title` */}
<HostedImage
element={element}
style={{ borderRadius: element.size[0] < 100 ? 0 : 4 }}
title={element.title}
/>
{children}
</div>
)
}
Now our Custom Image can render the image with the title
attribute.
Customize createImageFileElement
callback
createImageFileElement
callbackWhen a user uploads an image, the createImageFileElement
function passed to withPortive
is called. In Getting Started withPortive
has these options:
const editor = withPortive(reactEditor, {
createImageFileElement: createImageBlock,
// ...
})
Here's the createImageBlock
method passed to the createImageFileElement
option:
export function createImageBlock(
e: CreateImageFileElementEvent
): ImageBlockElement {
return {
type: "image-block",
originKey: e.originKey, // ✅ sets originKey from the event
originSize: e.originSize, // ✅ sets originSize from the event
size: e.initialSize, // ✅ sets size from `initialSize` from the event
children: [{ text: "" }],
}
}
Here's what e
which is of type CreateImageFileElementEvent
looks like:
export type CreateImageFileElementEvent = {
type: "image"
originKey: string
originSize: [number, number]
initialSize: [number, number]
file: File
}
export type CreateImageFileElement = (
e: CreateImageFileElementEvent
) => Element & { originKey: string }
You can learn more about file
by reading the File
MDN web docs.
Let's use the file
object for our TitledImageBlock
:
export function createTitledImageBlock(
e: CreateImageFileElementEvent
// ✅ returns a `TitledImageBlockElement` instead
): TitledImageBlockElement {
return {
type: "titled-image-block",
title: e.file.name, // ✅ set the initial title value to the filename
originKey: e.originKey,
originSize: e.originSize,
size: e.initialSize,
children: [{ text: "" }],
}
}
Now it's just a matter of importing and using our new TitledImageBlock
. Here's the full source code...
import {
CreatedImageFileElementEvent,
RendeElementPropsFor,
HostedImage,
} from "slate-portive"
export type TitledImageBlockElement = {
type: "titled-image-block"
title: string // ✅ Add a `title` property for our titled image
originKey: string
originSize: [number, number]
size: [number, number]
children: [{ text: "" }]
}
export function createTitledImageBlock(
e: CreateImageFileElementEvent
// ✅ returns a `TitledImageBlockElement` instead
): TitledImageBlockElement {
return {
type: "titled-image-block",
title: e.file.name, // ✅ set the initial title value to the filename
originKey: e.originKey,
originSize: e.originSize,
size: e.initialSize,
children: [{ text: "" }],
}
}
export function TitledImageBlock({
attributes,
element,
children,
}: // ✅ Change `ImageBlockElement` to `TitledImageBlockElement`
RenderElementPropsFor<TitledImageBlockElement>) {
return (
<div {...attributes} style={{ margin: "8px 0" }}>
{/* ✅ Add `element.title` */}
<HostedImage
element={element}
style={{ borderRadius: element.size[0] < 100 ? 0 : 4 }}
title={element.title}
/>
{children}
</div>
)
}
Last updated