Components
Caption
Caption
Add captions to your blocks.
Installation
npx shadcx@latest add caption -r plate-ui
Examples
import React from 'react';
import { cn, withRef } from '@udecode/cn';
import { withHOC } from '@udecode/plate-common/react';
import { Image, ImagePlugin, useMediaState } from '@udecode/plate-media/react';
import { ResizableProvider, useResizableStore } from '@udecode/plate-resizable';
import { Caption, CaptionTextarea } from './caption';
import { MediaPopover } from './media-popover';
import { PlateElement } from './plate-element';
import {
Resizable,
ResizeHandle,
mediaResizeHandleVariants,
} from './resizable';
export const ImageElement = withHOC(
ResizableProvider,
withRef<typeof PlateElement>(
({ children, className, nodeProps, ...props }, ref) => {
const { align = 'center', focused, readOnly, selected } = useMediaState();
const width = useResizableStore().get.width();
return (
<MediaPopover plugin={ImagePlugin}>
<PlateElement
ref={ref}
className={cn('py-2.5', className)}
{...props}
>
<figure className="group relative m-0" contentEditable={false}>
<Resizable
align={align}
options={{
align,
readOnly,
}}
>
<ResizeHandle
className={mediaResizeHandleVariants({ direction: 'left' })}
options={{ direction: 'left' }}
/>
<Image
className={cn(
'block w-full max-w-full cursor-pointer object-cover px-0',
'rounded-sm',
focused && selected && 'ring-2 ring-ring ring-offset-2'
)}
alt=""
{...nodeProps}
/>
<ResizeHandle
className={mediaResizeHandleVariants({
direction: 'right',
})}
options={{ direction: 'right' }}
/>
</Resizable>
<Caption style={{ width }} align={align}>
<CaptionTextarea
readOnly={readOnly}
placeholder="Write a caption..."
/>
</Caption>
</figure>
{children}
</PlateElement>
</MediaPopover>
);
}
)
);
import React from 'react';
import LiteYouTubeEmbed from 'react-lite-youtube-embed';
import { Tweet } from 'react-tweet';
import { cn, withRef } from '@udecode/cn';
import { withHOC } from '@udecode/plate-common/react';
import { parseTwitterUrl, parseVideoUrl } from '@udecode/plate-media';
import { MediaEmbedPlugin, useMediaState } from '@udecode/plate-media/react';
import { ResizableProvider, useResizableStore } from '@udecode/plate-resizable';
import { Caption, CaptionTextarea } from './caption';
import { MediaPopover } from './media-popover';
import { PlateElement } from './plate-element';
import {
Resizable,
ResizeHandle,
mediaResizeHandleVariants,
} from './resizable';
export const MediaEmbedElement = withHOC(
ResizableProvider,
withRef<typeof PlateElement>(({ children, className, ...props }, ref) => {
const {
align = 'center',
embed,
focused,
isTweet,
isVideo,
isYoutube,
readOnly,
selected,
} = useMediaState({
urlParsers: [parseTwitterUrl, parseVideoUrl],
});
const width = useResizableStore().get.width();
const provider = embed?.provider;
return (
<MediaPopover plugin={MediaEmbedPlugin}>
<PlateElement
ref={ref}
className={cn('relative py-2.5', className)}
{...props}
>
<figure className="group relative m-0 w-full" contentEditable={false}>
<Resizable
align={align}
options={{
align,
maxWidth: isTweet ? 550 : '100%',
minWidth: isTweet ? 300 : 100,
}}
>
<ResizeHandle
className={mediaResizeHandleVariants({ direction: 'left' })}
options={{ direction: 'left' }}
/>
{isVideo ? (
isYoutube ? (
<LiteYouTubeEmbed
id={embed!.id!}
title="youtube"
wrapperClass={cn(
'rounded-sm',
focused && selected && 'ring-2 ring-ring ring-offset-2',
'relative block cursor-pointer bg-black bg-cover bg-center [contain:content]',
'[&.lyt-activated]:before:absolute [&.lyt-activated]:before:top-0 [&.lyt-activated]:before:h-[60px] [&.lyt-activated]:before:w-full [&.lyt-activated]:before:bg-top [&.lyt-activated]:before:bg-repeat-x [&.lyt-activated]:before:pb-[50px] [&.lyt-activated]:before:[transition:all_0.2s_cubic-bezier(0,_0,_0.2,_1)]',
'[&.lyt-activated]:before:bg-[url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==)]',
'after:block after:pb-[var(--aspect-ratio)] after:content-[""]',
'[&_>_iframe]:absolute [&_>_iframe]:left-0 [&_>_iframe]:top-0 [&_>_iframe]:size-full',
'[&_>_.lty-playbtn]:z-[1] [&_>_.lty-playbtn]:h-[46px] [&_>_.lty-playbtn]:w-[70px] [&_>_.lty-playbtn]:rounded-[14%] [&_>_.lty-playbtn]:bg-[#212121] [&_>_.lty-playbtn]:opacity-80 [&_>_.lty-playbtn]:[transition:all_0.2s_cubic-bezier(0,_0,_0.2,_1)]',
'[&:hover_>_.lty-playbtn]:bg-[red] [&:hover_>_.lty-playbtn]:opacity-100',
'[&_>_.lty-playbtn]:before:border-y-[11px] [&_>_.lty-playbtn]:before:border-l-[19px] [&_>_.lty-playbtn]:before:border-r-0 [&_>_.lty-playbtn]:before:border-[transparent_transparent_transparent_#fff] [&_>_.lty-playbtn]:before:content-[""]',
'[&_>_.lty-playbtn]:absolute [&_>_.lty-playbtn]:left-1/2 [&_>_.lty-playbtn]:top-1/2 [&_>_.lty-playbtn]:[transform:translate3d(-50%,-50%,0)]',
'[&_>_.lty-playbtn]:before:absolute [&_>_.lty-playbtn]:before:left-1/2 [&_>_.lty-playbtn]:before:top-1/2 [&_>_.lty-playbtn]:before:[transform:translate3d(-50%,-50%,0)]',
'[&.lyt-activated]:cursor-[unset]',
'[&.lyt-activated]:before:pointer-events-none [&.lyt-activated]:before:opacity-0',
'[&.lyt-activated_>_.lty-playbtn]:pointer-events-none [&.lyt-activated_>_.lty-playbtn]:!opacity-0'
)}
/>
) : (
<div
className={cn(
provider === 'vimeo' && 'pb-[75%]',
provider === 'youku' && 'pb-[56.25%]',
provider === 'dailymotion' && 'pb-[56.0417%]',
provider === 'coub' && 'pb-[51.25%]'
)}
>
<iframe
className={cn(
'absolute left-0 top-0 size-full rounded-sm',
isVideo && 'border-0',
focused && selected && 'ring-2 ring-ring ring-offset-2'
)}
title="embed"
src={embed!.url}
allowFullScreen
/>
</div>
)
) : null}
{isTweet && (
<div
className={cn(
'[&_.react-tweet-theme]:my-0',
!readOnly &&
selected &&
'[&_.react-tweet-theme]:ring-2 [&_.react-tweet-theme]:ring-ring [&_.react-tweet-theme]:ring-offset-2'
)}
>
<Tweet id={embed!.id!} />
</div>
)}
<ResizeHandle
className={mediaResizeHandleVariants({ direction: 'right' })}
options={{ direction: 'right' }}
/>
</Resizable>
<Caption style={{ width }} align={align}>
<CaptionTextarea placeholder="Write a caption..." />
</Caption>
</figure>
{children}
</PlateElement>
</MediaPopover>
);
})
);
Plus
In Plate Plus, we have enhanced the caption user experience:
Use the media toolbar to add captions when hovering over the image.
Try it out by hovering over the image in the example below.
Build your editor even faster
Complete, deployable AI-powered template with backend.
All components included.
Customizable and extensible.
Get all-accessCustomizable and extensible.