@zen_flow/react-tinacms-inline
TypeScript icon, indicating that this package has built-in type declarations

0.36.1-zenflow.0 • Public • Published

react-tinacms-inline

This package provides the components and helpers for Inline Editing.

Install

yarn add react-tinacms-inline

For specific steps on setting up Inline Editing on a page, refer to the Inline Editing documentation.

Inline Form

The InlineForm component provides an inline editing context. To use, wrap the component where you want to render inline fields and pass the form.

Usage

export function Page(props) {
  const [, form] = useForm(props.data)
  usePlugin(form)

  return (
    <InlineForm form={form}>
      <main>
        {
            //...
        }
      </main>
    </InlineForm>
  )
}

Interface

interface InlineFormProps {
  form: Form
  children: React.ReactElement | React.ReactElement[] | InlineFormRenderChild
}

interface InlineFormRenderChild {
  (props: InlineFormRenderChildOptions):
    | React.ReactElement
    | React.ReactElement[]
}

type InlineFormRenderChildOptions = InlineFormState &
  Omit<FormRenderProps<any>, 'form'>

/**
 * This state is available via context
*/
interface InlineFormState {
  form: Form
  focussedField: string
  setFocussedField(field: string): void
}
Key Description
form A Tina Form.
children Child components to render — Either React Elements or Render Props Children that receive the form state and other Form Render Props

useInlineForm

useInlineForm(): InlineFormState

The useInlineForm hook can be used to access the inline editing context. It must be used within a child component of InlineForm.

Inline Field

Inline Fields should provide basic inputs for editing data on the page and account for both enabled / disabled CMS states. All Inline Fields must be children of an InlineForm.

Interface

export interface InlineFieldProps {
  name: string
  children(fieldProps: InlineFieldRenderProps): React.ReactElement
}

export interface InlineFieldRenderProps<V = any>
  extends FieldRenderProps<V>,
    InlineFormState {}
Key Description
name The path to the editable data.
children Child components to render — React Elements that receive the form state and Field Render Props.

Available Inline Fields

See the full list of inline fields or learn how to make custom inline fields.

Below is a list of fields provided by the react-tinacms-inline package:

Inline Field Styles

Styles are stripped as much as possible to prevent interference with base site styling. When toggling between enabled / disabled states, the default inline fields will switch between rendering child elements with any additional editing UI and just passing the child elements alone.

For example with InlineText:

export function InlineText({
  name,
  className,
  focusRing = true,
}: InlineTextProps) {
  const cms: CMS = useCMS()

  return (
    <InlineField name={name}>
      {({ input }) => {
        /**
        * If the cms is enabled, render the input
        * with the focus ring
        */
        if (cms.enabled) {
          if (!focusRing) {
            return <Input type="text" {...input} className={className} />
          }

          return (
            <FocusRing name={name} options={focusRing}>
              <Input type="text" {...input} className={className} />
            </FocusRing>
          )
        }
        /**
        * Otherwise, pass the input value
        */
        return <>{input.value}</>
      }}
    </InlineField>
  )
}

Input is a styled-component with some base styling aimed at making this component mesh well with the surrounding site. If you ever need to override default Inline Field styles, read about this workaround to extend styles.

Focus Ring

The common UI element on all Inline Fields is the FocusRing. The focus ring provides context to which field is active / available to edit.

Interface

interface FocusRingProps {
  name?: string
  children: React.ReactNode | ((active: boolean) => React.ReactNode)
  options?: boolean | FocusRingOptions
}

interface FocusRingOptions {
  offset?: number | { x: number; y: number }
  borderRadius?: number
  nestedFocus?: boolean
}

Focus Ring Props

You would only use these options if you were creating custom inline fields and working with the FocusRing directly.

Key Description
children Optional: Child elements to render.
name Optional: This value is used to set the focused / active field.
options Optional: The FocusRingOptions outlined below.

Focus Ring Children

FocusRing optionally accepts render prop patterned children, which receive the active state and can be used to conditionally render elements based on whether the FocusRing currently has focus.

<FocusRing>
 {active => {
   if (active) {
     return <ComplicatedEditableComponent />
   }
   return <SimpleDisplayComponent />
 }}
</FocusRing>

Focus Ring Options

These options are passed to default inline fields or inline block fields via the focusRing prop on most default inline fields. The options are configurable by the developer setting up the inline form & fields. Refer to individual inline field documentation for additional examples.

Key Description
offset Optional: Sets the distance from the focus ring to the edge of the child element(s). It can either be a number (in pixels) for both x & y offsets, or individual x & y offset values passed in an object.
borderRadius Optional: Determines (in pixels) the rounded corners of the focus ring.
nestedFocus Optional: Disables pointer-events (clicking) and hides blocks empty state.

Inline Blocks

Inline Blocks consist of an array of Blocks to render in an Inline Form. Refer to the Inline Blocks Documentation or Guide for code examples on creating Inline Blocks.

Interface

export interface InlineBlocksProps {
  name: string
  blocks: {
    [key: string]: Block
  }
  className?: string
  direction?: 'vertical' | 'horizontal'
  itemProps?: {
    [key: string]: any
  }
  min?: number
  max?: number
  components?: {
    Container?: React.FunctionComponent<BlocksContainerProps>
  }
}
Key Purpose
name The path to the source data for the blocks.
blocks An object composed of individual Blocks.
className Optional — To set styles directly on the input or extend via styled components.
direction Optional — Sets the orientation of the AddBlock button position.
itemProps Optional — An object that passes additional props to every block child element.
min Optional — Controls the minimum number of blocks. Once reached, blocks won't be able to be removed. (Optional)
max Optional — Controls the maximum number of blocks allowed. Once reached, blocks won't be able to be added. (Optional)

Actions

The Inline Blocks Actions are used by the Inline Blocks Controls. Use these if you are building your own custom Inline Block Field Controls. These actions are avaiable in the Inline Blocks Context.

interface InlineBlocksActions {
  count: number
  insert(index: number, data: any): void
  move(from: number, to: number): void
  remove(index: number): void
  blocks: {
    [key: string]: Block
  }
  activeBlock: number | null
  setActiveBlock: any
  direction: 'vertical' | 'horizontal'
  min?: number
  max?: number
}

useInlineBlocks

useInlineBlocks(): InlineBlocksActions

useInlineBlocks is a hook that can be used to access the Inline Blocks Context when creating custom controls.

Inline Block Field Controls

Editors can add / delete / rearrange blocks with the blocks controls. They can also access additional fields in a Settings Modal.

Interface

interface BlocksControlsProps {
  index: number
  insetControls?: boolean
  focusRing?: false | FocusRingProps
  customActions?: BlocksControlActionItem[]
  label?: boolean
  children: React.ReactChild
}

interface FocusRingProps {
  offset?: number | { x: number; y: number }
  borderRadius?: number
}

export interface BlocksControlActionItem {
  icon: React.ReactNode
  onClick: () => void
}
Key Description
index The index of the block associated with these controls.
insetControls A boolean to denote whether the group controls display within or outside the group.
focusRing Either an object to style the focus ring or false, which hides the focus ring entirely. For styles, offset (in pixels) controls the distance from the ring to the edge of the group; borderRadius(in pixels) controls the rounding edge of the focus ring.
label A boolean to control whether or not a block label is rendered.
children Any child components, typically inline field(s).
customActions An array of objects containing custom block action configuration. icon is the component to render in the toolbar. onClick handles the action behavior.

Block Definition

A block is made of two parts: a component that renders in edit mode, and a template to configure fields, defaults and other required data.

Interface

interface Block {
  Component: React.FC<BlockComponentProps>
  template: BlockTemplate
}

interface BlockComponentProps {
  name: string
  index: number
  data: any
}

interface BlockTemplate {
  label: string
  defaultItem?: object | (() => object)
  fields?: Field[]
}

Block Component

Key Purpose
name A unique identifier and pseudo-path to the block from the parent blocks array. e.g. the first child would be 'blocks.0'
index Position in the block array.
data The source data.

Block Template

Key Purpose
label A human readable label.
defaultItem Optional — Populates new blocks with default data.
fields Optional — Populates fields in the Settings Modal.

Examples

Block Components can render Inline Fields to expose the data for editing. When using Inline Fields within a block, the name property should be relative to the block object in the source data.

For example:

import { useForm } from 'tinacms'
import { InlineForm, InlineBlocks, BlocksControls, InlineTextarea } from 'react-tinacms-inline'

function FeaturePage({ data }) {
  const [ , form ] = useForm({
    id: 'my-features-id',
    label: 'Edit Features',
    fields: [],
    initialValues: data,
  })

  return (
    <InlineForm form={form}>
      <div className="wrapper">
        <InlineBlocks name="features_blocks" blocks={FEATURE_BLOCKS} />
      </div>
    </InlineForm>
  )
}

function Feature({ index }) {
  return (
    <BlocksControls index={index}>
      <div className="feature">
        <h3>
        {/**
        * The `name` property is relative to individual
        * `features_blocks` array items (blocks). The full path
        * in the source file (example below) would be
        *  `features_blocks[index].heading`
        */}
          <InlineTextarea name="heading" focusRing={false} />
        </h3>
        <p>
          <InlineTextarea name="supporting_copy" focusRing={false} />
        </p>
      </div>
    </BlocksControls>
  )
}

const featureBlock = {
  Component: Feature,
  template: {
    label: 'Feature',
    defaultItem: {
      _template: 'feature',
      heading: 'Marie Skłodowska Curie',
      supporting_copy:
        'Muse about vastness.',
    },
    fields: [],
  },
}

const FEATURE_BLOCKS = {
    featureBlock
}

Example JSON data

{
    "features_blocks": [
        {
            "_template": "feature",
            "heading": "Drake Equation",
            "supporting_copy": "Light years gathered by gravity Rig Veda.."
        },
        {
            "_template": "feature",
            "heading": "Jean-François Champollion",
            "supporting_copy": "Not a sunrise but a galaxyrise."
        },
        {
            "_template": "feature",
            "heading": "Sea of Tranquility",
            "supporting_copy": "Bits of moving fluff take root and flourish."
        }
    ]
}

Below is another example using InlineBlocks with multiple block definitions:

import { useJsonForm } from 'next-tinacms-json'
import { InlineForm, InlineBlocks, BlocksControls, InlineTextarea } from 'react-tinacms-inline'

export default function PageBlocks({ jsonFile }) {
  const [, form] = useJsonForm(jsonFile)

  return (
    <InlineForm form={form}>
      <InlineBlocks name="my_blocks" blocks={PAGE_BLOCKS} />
    </InlineForm>
  )
}

/** Example Heading Block Definition
 * Component + template
*/
function Heading({ index }) {
  return (
    <BlocksControls index={index}>
      <InlineTextarea name="text" />
    </BlocksControls>
  )
}

const heading_template = {
  label: 'Heading',
  defaultItem: {
    text: 'At vero eos et accusamus',
  },
  fields: [],
}

const headingBlock = {
    Component: Heading,
    template: heading_template
}

/**
 * Example Paragraph Block
 * Component + template
*/

function Paragraph({ index }) {
  return (
    <BlocksControls index={index} focusRing={{ offset: 0 }} insetControls>
      <div className="paragraph__background">
        <div className="wrapper wrapper--narrow">
          <p className="paragraph__text">
            <InlineTextarea name="text" focusRing={false} />
          </p>
        </div>
      </div>
    </BlocksControls>
  );
}

const paragraphBlock = {
  Component: Paragraph,
  // template defined inline
  template: {
    label: 'Paragraph',
    defaultItem: {
      text:
        'Take root and flourish quis nostrum exercitationem ullam',
    },
    fields: [],
  },
};

/**
 * Available blocks passed to InlineBlocks to render
*/

const PAGE_BLOCKS = {
    headingBlock,
    paragraphBlock
}

Configuring the Container

InlineBlocks wraps your blocks with a <div> element by default. This can be an issue if your styles require direct inheritance, such as a flexbox grid:

<div class="row">
  <div class="column">
  </div>
</div>

To handle this, you can pass a custom container component to InlineBlocks.

Interface

interface BlocksContainerProps {s
  className?: string
  children?: React.ReactNode
}

Example

import { useJsonForm } from 'next-tinacms-json'
import { InlineForm, InlineBlocks, BlocksControls, InlineTextarea } from 'react-tinacms-inline'

const MyBlocksContainer = (children}) => (
  <div>
    {children}
  </div>
)

export default function PageBlocks({ jsonFile }) {
  const [, form] = useJsonForm(jsonFile)

  return (
    <InlineForm form={form}>
      <InlineBlocks
        name="my_blocks"
        blocks={PAGE_BLOCKS}
        components={{
          Container: MyBlocksContainer
        }}
      />
    </InlineForm>
  )
}

Checkout this guide to learn more on using Inline Blocks.

Readme

Keywords

none

Package Sidebar

Install

npm i @zen_flow/react-tinacms-inline

Weekly Downloads

4

Version

0.36.1-zenflow.0

License

Apache-2.0

Unpacked Size

107 kB

Total Files

33

Last publish

Collaborators

  • zen_flow