Create a component

Uniform makes it easy to use your front-end and design system components with Canvas. Your front-end development works with Uniform with little change in process or code.

In the previous lesson, you used components in Canvas. Now you'll walk through creating a new component in the front end project and register it for use with Canvas.

For this exercise you'll add a new video player component to the project so users can add JavaDrip related videos to your application. During the lesson you will:

Goals

  1. Create a new React component that isn't from the CSK (a video player).
  2. Test your new component using Storybook.
  3. Review how the component source is registered with Uniform.
  4. Define the component in the Uniform component library.
  5. Learn about exposing component props as Canvas parameters a business user can control.
  6. Add the new component to a composition (page).
  7. Learn how to enable inline editing with your component.
  1. In your source code, add the following directory and file:

    src/canvas/VideoPlayer/VideoPlayer.tsx

    /* eslint-disable */ // @ts-nocheck import { FC } from 'react'; import classNames from 'classnames'; import { getTextClass } from '@/utilities/styling'; import { VideoPlayerProps } from '.'; export const VideoPlayer: FC<VideoPlayerProps> = ({ title, description, source, id, }) => ( <div className={classNames('hero min-h-[500px] relative', 'text-secondary-content')}> <div className={classNames('hero-content text-center p-0')}> <div className={classNames('flex flex-col mx-1 md:mx-10')}> <h1 className={classNames('font-bold', getTextClass('h5'))}> {title} </h1> <div className={classNames('py-6')}> {description} </div> <div className='border-dashed border-4 border-gray-300 p-5 text-gray-300'> {source === 'YouTube' && <iframe width="560" height="315" src={`https://www.youtube.com/embed/${id}`} title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowFullScreen></iframe> } {source === 'Loom' && <iframe width="640" height="360" src={`https://www.loom.com/embed/${id}`} frameborder="0" webkitallowfullscreen mozallowfullscreen allowFullScreen></iframe> } {!source && <div className=' text-gray-300'>Select a video source</div> } </div> </div> </div> </div> );
  2. In your source code, add the following file:

    src/canvas/VideoPlayer/index.ts

    import { ComponentProps, registerUniformComponent, } from '@uniformdev/canvas-react'; import { VideoPlayer } from './VideoPlayer'; export type VideoPlayerProps = ComponentProps<{ id: string; source: string; title: string; description: string; }>; registerUniformComponent({ type: 'videoPlayer', component: VideoPlayer }); export default VideoPlayer;
  3. Update the following file to make the video player available to the front-end application:

    src/canvas/index.ts

    import './Accordion'; import './AccordionItem'; import './AddToCart'; import './Banner'; import './Breadcrumbs'; import './Button'; import './CallToAction'; import './Card'; import './Countdown'; import './VideoPlayer'; import './Tabs'; import './Tab'; import './CardBlock'; import './Carousel'; import './_containers/Container'; import './_containers/Grid'; import './_containers/GridItem'; import './ContentBlock'; import './Divider'; import './Feature'; import './FeaturedCallout'; import './Hero'; import './Image'; import './Table'; import './Modal'; import './_navigation/Footer'; import './_navigation/FooterSection'; import './_navigation/Header'; import './_navigation/IconLink'; import './_navigation/NavLink'; import './_navigation/_mega-menu/NavigationMenu'; import './_navigation/_mega-menu/NavigationMenuSection'; import './_navigation/_mega-menu/NavigationMenuSectionLink'; import './_navigation/_mega-menu/NavigationOneColumnMenu'; import './_navigation/_mega-menu/NavigationOneColumnMenuLink'; import './_navigation/_mega-menu/NavigationTwoColumnMenu'; import './Price'; import './ProductDetails'; import './ProductInfo'; import './ImageGallery'; import './Review'; import './RichText'; import './Spacer'; import './Testimonial'; import './Video'; export { default as Accordion } from './Accordion'; export { default as AddToCart } from './AddToCart'; export type { AddToCartProps } from './AddToCart'; export { default as Banner, BannerVariant } from './Banner'; export type { BannerProps } from './Banner'; export { default as Button } from './Button'; export type { ButtonProps } from './Button'; export { default as CallToAction, CallToActionVariant } from './CallToAction'; export type { CallToActionProps } from './CallToAction'; export { default as ProductInfo } from './ProductInfo'; export type { ProductInfoProps } from './ProductInfo'; export { default as Breadcrumbs } from './Breadcrumbs'; export { default as Card, CardVariants } from './Card'; export type { CardProps } from './Card'; export { default as CardBlock, CardBlockVariants } from './CardBlock'; export type { CardBlockProps } from './CardBlock'; export { default as Carousel, CarouselVariants } from './Carousel'; export { default as Container } from './_containers/Container'; export type { ContainerProps } from './_containers/Container'; export { default as ImageGallery } from './ImageGallery'; export type { BaseImageGalleryProps } from './ImageGallery'; export { default as ContentBlock } from './ContentBlock'; export type { ContentBlockProps } from './ContentBlock'; export { default as Divider } from './Divider'; export { default as FeaturedCallout, FeaturedCalloutVariant } from './FeaturedCallout'; export type { FeaturedCalloutProps } from './FeaturedCallout'; export { default as Grid } from './_containers/Grid'; export type { GridProps } from './_containers/Grid'; export { default as Hero, HeroVariant } from './Hero'; export type { HeroProps } from './Hero'; export { default as UniformImage } from './Image'; export type { ImageProps as UniformImageProps } from './Image'; export { default as Modal } from './Modal'; export type { ModalProps } from './Modal'; export { default as Price } from './Price'; export type { PriceProps } from './Price'; export { default as Review, ReviewVariant } from './Review'; export { default as Testimonial, TestimonialVariant } from './Testimonial'; export { default as RichText } from './RichText'; export { default as Video } from './Video'; export type { VideoProps } from './Video'; export { default as Tabs } from './Tabs'; export { TabsVariant } from './Tabs'; export type { TabsProps } from './Tabs'; export { default as Table } from './Table'; export { TableVariant } from './Table'; export type { TableProps } from './Table'; export { default as Countdown } from './Countdown'; export { CountdownVariant } from './Countdown'; export type { CountdownProps } from './Countdown'; export { default as VideoPlayer } from './VideoPlayer'; export type { VideoPlayerProps } from './VideoPlayer';
  1. In your source code, add the following file:

    src/stories/canvas/VideoPlayer.stories.tsx

    /* eslint-disable */ import type { Meta, StoryObj } from '@storybook/react'; import { UniformComposition } from '@uniformdev/canvas-react'; import VideoPlayer, { VideoPlayerProps } from '@/canvas/VideoPlayer'; import { createFakeCompositionData } from '../utils'; const meta: Meta<typeof VideoPlayer> = { title: 'VideoPlayer', component: VideoPlayer, }; export default meta; type Story = StoryObj<typeof VideoPlayer>; const renderStory = (args: VideoPlayerProps) => { const fakeComposition = createFakeCompositionData('videoPlayer', args, {}); return ( <UniformComposition data={fakeComposition}> <VideoPlayer {...args} /> </UniformComposition> ); }; export const YouTube: Story = { name: 'YouTube', args: { title: 'YouTube video', description: 'This is a sample video from YouTube', id: 'a2KL5o-_xuk', source: 'YouTube', }, render: renderStory, }; export const Loom: Story = { args: { title: 'Loom video', description: 'This is a sample video from Loom', id: '9edb010ba5344833b48ef281380cedaa', source: 'Loom', }, render: renderStory, };
  2. Open a new terminal in the root folder of your web application.
  3. Enter the following command:
    npm run storybook
  4. A new browser tab opens with Storybook loaded. Navigate to the story VideoPlayer to interact with the rendered component.
    youtube-story
    The Storybook webpage after selecting Video Player

Before you can use the component in Uniform, you must add a component to the Uniform component library to represent the front-end component.

  1. In Uniform, navigate to Experience > Components.
  2. Click Add component.
  3. Enter the following values:
    FieldValue
    Component NameVideo Player
    Public IDvideoPlayer
    Component IconFilm

    About this step

    The public ID must match the value that was used in the call to registerUniformComponent in the front-end component logic.

  4. Add the following parameters:
    TypeNamePublic IDOptions
    TextTitletitle
    TextDescriptiondescription
    TextIdid
    Dropdown ListSourcesourceYouTube|Loom
  5. Save the component.

Only certain components are allowed to be added to the slot on your composition. Now you will add your new component to the list of allowed components.

  1. In Uniform, open the component Video Player.
  2. In the General tab, click Allow in slot.
  3. From the Component dropdown, select Page.
  4. From the Slot dropdown, select Page Content.
  5. Click Save.

Now you will add the new component to your composition.

  1. In Uniform, open the project map.
  2. Navigate to Home > Articles > The Golden Ratio of Coffee.
  3. Under the Breadcrumbs component, add the component Video Player.

    About this step

    Once the component is added, you'll notice that the preview window shows a message saying that Uniform is unable to find a component that is registered under the name videoPlayer. You can still set parameter values on the Uniform component. After you do that, you will register the video player component so it is properly displayed.

  4. Enter the following values:
    ParameterValue
    TitleA Magical Cup of Coffee
    DescriptionHow do you create magic in a cup? This is how!
    IdlpHAIp60mXs
    SourceYouTube

Uniform supports inline editing for text-type component parameters. Now you will enable inline editing for the title and description parameters on the video player component.

  1. In the component code, make the following changes:

    src/canvas/VideoPlayer/VideoPlayer.tsx

    /* eslint-disable */ // @ts-nocheck import { FC } from 'react'; import classNames from 'classnames'; import { UniformText } from '@uniformdev/canvas-react'; import { getTextClass } from '@/utilities/styling'; import { VideoPlayerProps } from '.'; export const VideoPlayer: FC<VideoPlayerProps> = ({ title, description, source, id, }) => ( <div className={classNames('hero min-h-[500px] relative', 'text-secondary-content')}> <div className={classNames('hero-content text-center p-0')}> <div className={classNames('flex flex-col mx-1 md:mx-10')}> <h1 className={classNames('font-bold', getTextClass('h5'))}> <UniformText parameterId='title' /> </h1> <div className={classNames('py-6')}> <UniformText parameterId='description' /> </div> <div className='border-dashed border-4 border-gray-300 p-5 text-gray-300'> {source === 'YouTube' && <iframe width="560" height="315" src={`https://www.youtube.com/embed/${id}`} title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowFullScreen></iframe> } {source === 'Loom' && <iframe width="640" height="360" src={`https://www.loom.com/embed/${id}`} frameborder="0" webkitallowfullscreen mozallowfullscreen allowFullScreen></iframe> } {!source && <div className=' text-gray-300'>Select a video source</div> } </div> </div> </div> </div> );
  2. In Canvas, the title and description parameters can be edited inline.

    inline-editing-enabled
    The borders around the title and description indicate these values can be edited inline.