The InlineImage
field represents an image input. This field supports drag and drop upload, or via clicking on the image to select media from the local filesystem.
Below is a simple example of how InlineImage
could be implemented in in an Inline Form.
import ReactMarkdown from 'react-markdown'
import { useForm, usePlugin } from 'tinacms'
import { InlineForm, InlineImage } from 'react-tinacms-inline'
// Example 'Page' Component
export function Page(props) {
const [data, form] = useForm(props.data)
usePlugin(form)
return (
<InlineForm form={form}>
<InlineImage
name="hero_image"
parse={media => media.id}
uploadDir={() => '/public/images/'}
alt="hero-image"
/>
</InlineForm>
)
}
There are two ways to use InlineImage
, with and without children. If no children are passed, InlineImage
will render a default img
element. However, you may want more control over the image behavior, in which case you can pass children to InlineImage
.
Key | Description |
---|---|
name | The path to some value in the data being edited. |
parse | Defines how the actual front matter or data value gets populated. The name of the file gets passed as an argument, and one can set the path this image as defined by the uploadDir property. |
uploadDir | Optional Defines the upload directory for the image. All of the post data is passed in, fileRelativePath is most useful in defining the upload directory, but you can also statically define the upload directory. |
previewSrc | Optional Defines the path for the src attribute on the image preview. If using gatsby-image, the path to the childImageSharp.fluid.src needs to be provided. This defaults to the MediaStore's previewSrc method. |
focusRing | Optional Either an object to style the focus ring or a boolean to show/hide the focus ring. Defaults to true which displays the focus ring with default styles. For style options, offset (in pixels) sets the distance from the ring to the edge of the component, and borderRadius (in pixels) controls the rounded corners of the focus ring. |
className | Optional A string associated with a CSS class to extend the inline image container styled component. |
alt | Optional A string to pass for the alt attribute of the img element when not using the Render Props pattern. |
children | Optional Children need to be passed through the Render Props pattern to access src . |
interface InlineImageProps {
name: string
parse(media: Media): string
uploadDir?(form: Form): string
previewSrc?(
src: string,
fieldPath?: string,
formValues?: any
): Promise<string> | string
focusRing?: boolean | FocusRingProps
className?: string
alt?: string
children?: ImageRenderChildren
}
interface FocusRingProps {
offset?: number | { x: number; y: number }
borderRadius?: number
}
interface ImageRenderChildrenProps {
src?: string
}
type ImageRenderChildren = (props: ImageRenderChildrenProps) => React.ReactNode
The parse
function handles how the path gets written in the source data when a new image is uploaded. parse
is passed the media object for the newly uploaded image. A simple return value could look something like this:
parse: media => media.id
The path depends on where images live in your project structure. uploadDir
sets where those new images should live:
uploadDir: () => `/example-dir`
The previewSrc
provides a path for the image when inline editing is active (a.k.a when the CMS is enabled). When inline editing is not active (cms.enabled === false
), the image will reference the path in the source data.
previewSrc
is passed the current field value, the field path, and current form values. The return value should be the entire path to the image from the source data — e.g. /example-dir/image.jpg
.
You can extend styles of the
InlineImage
container via styled-components or with aclassName
.
Below is an example of an Inline Image field used in an Inline Block in a Next.js project.
import { useCMS } from 'tinacms'
import { BlocksControls, InlineImage } from 'react-tinacms-inline'
export function Image({ data, index }) {
const cms = useCMS()
return (
<div className="block">
<BlocksControls
index={index}
focusRing={{ offset: { x: 0, y: 0 }, borderRadius: 0 }}
insetControls
>
<InlineImage
name="src"
previewSrc={fieldValue => cms.media.previewSrc(`public${fieldValue}`)}
parse={media => `/img/${media.filename}`}
uploadDir={() => '/public/img/'}
className="img--wrap"
alt={data.alt}
focusRing={false}
/>
</BlocksControls>
</div>
)
}
The className
will apply corresponding styles to the Inline Image container div. In this case, the focusRing
is false because the
Below is the same example, refactored to use the Render Props pattern:
import { useCMS } from 'tinacms'
import { BlocksControls, InlineImage } from 'react-tinacms-inline'
export function Image({ data, index }) {
const cms = useCMS()
return (
<div className="block">
<BlocksControls
index={index}
focusRing={{ offset: { x: 0, y: 0 }, borderRadius: 0 }}
insetControls
>
<InlineImage
name="src"
previewSrc={fieldValue => cms.media.previewSrc(`public${fieldValue}`)}
parse={media => `/img/${media.filename}`}
uploadDir={() => '/public/img/'}
className="img--wrap"
focusRing={false}
>
{props => <img src={props.src} alt={data.alt} />}
</InlineImage>
</BlocksControls>
</div>
)
}
With this pattern, the src
is passed as a prop to the render children. The value of src
will either be the return value from the previewSrc
function (when the CMS is enabled) or the value stored in the source data (when the CMS is disabled).
Below is an example of how you could pass children as a to InlineImage
to work with Gatsby Image. Notice how children need to be passed via render props.
You'll notice that this component looks a bit different with its handling of src
. Since with Gatsby Image, the path to the image is transformed, you'll need to provide a fallback src
that references the transformed path from childImageSharp
.
Also, the previewSrc
function uses the third argument, formValues
to access the entire form and retrieve the transformed, childImageSharp
path.
Read more on proper image paths in Gatsby to get context on the parse
& uploadDir
configuration.
import { InlineForm, InlineImage } from 'react-tinacms-inline'
import { useRemarkForm } from 'gatsby-tinacms-remark'
import { usePlugin, useCMS } from 'tinacms'
import Img from 'gatsby-image'
// Using InlineImage with Gatsby Image
export function Hero({ data }) {
const cms = useCMS()
const [post, form] = useRemarkForm(data.markdownRemark)
usePlugin(form)
return (
<InlineForm form={form}>
<InlineImage
name="rawFrontmatter.thumbnail"
parse={media => (media.filename ? `./${filename}` : null)}
uploadDir={blogPost => {
const postPathParts = blogPost.initialValues.fileRelativePath.split(
'/'
)
const postDirectory = postPathParts
.splice(0, postPathParts.length - 1)
.join('/')
return postDirectory
}}
previewSrc={(fieldValue, fieldPath, formValues) =>
formValues.frontmatter.thumbnail.childImageSharp.fluid.src
}
>
{props => (
<Img
fluid={
cms.enabled
? props.src
: post.frontmatter.thumbnail.childImageSharp.fluid
}
alt="Gatsby can't find me"
{...props}
/>
)}
</InlineImage>
</InlineForm>
)
}
Note that the same principles for Gatsby Image can be applied to the next/image component. This gives you the benefits of automatic image optimisation, but comes with some caveats. You can read more about how this works on the Next.js docs.
Below is an example of how you could pass children as a to InlineImage
to work with next/image. Notice how children need to be passed via render props. You can read more about the next/image component on the Next.js API reference docs.
You can optionally pass specific widths to the image element, but the example below shows how you could use padding to create a container with an aspect ratio (in this case a square).
Don't forget to update your next.config.js
if you're planning on passing in sources from external websites.
import { InlineForm, InlineImage } from 'react-tinacms-inline'
import { useGithubJsonForm } from "react-tinacms-github";
import { usePlugin, useCMS } from 'tinacms'
import Image from '@next/image'
// Using InlineImage with next/image
export function Hero({ file, formConfig }) {
const cms = useCMS()
// Note for simplicity's sake, the below assumes you're passing file and formConfig props.
const [data, form] = useGithubJsonForm(file, formConfig);
usePlugin(form)
return (
<InlineForm form={form}>
<InlineImage
name="image"
parse={(media: any) => {
return `/${media.id}`;
}}
uploadDir={() => "/images"}
alt="Some descriptive alt text here"
>
{(props) => (
<div
style={{
height: 0,
paddingBottom: "100%"
}}
>
<Image
src={props.src}
alt={props.alt}
layout="fill"
objectFit="cover"
/>
</div>
)}
</InlineImage>
</InlineForm>
)
}
Last Edited: September 24, 2020