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.

In this section, you will create a new component and make some code changes. To enhance the experience and enable live reloading, it's recommended to utilize the development mode.

  1. Stop existing production server
  2. Run this in terminal from the root folder of your web application:
    npm run dev
  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 } from '@uniformdev/canvas-react'; import { VideoPlayer } from './VideoPlayer' export type VideoPlayerProps = ComponentProps<{ id: string; source: string; title: string; description: string; }>; export default VideoPlayer;
  3. Update the following file to add item for a video player:

    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, { Props as 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 = { 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:

    NamePublic IDTypeOptions
    TitletitleText
    DescriptiondescriptionText
    IdidText
    SourcesourceDropdown ListYouTube|Loom

    About this step

    When you add your first parameter, if you click the down arrow next to Save you will have the option to save and create another parameter.

  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.
  6. Save and close the composition.

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.
  4. Enter the following values:
    ParameterValue
    TitleA Magical Cup of Coffee
    DescriptionHow do you create magic in a cup? This is how!
    IdlpHAIp60mXs
    SourceYouTube
    Once the component is added, you'll notice that the preview window shows a message saying that Uniform doesn't know which React component to render. Copy the line from the message and keep it handy for use in the next step:
    registerUniformComponent({ type: 'videoPlayer', component: VideoPlayer });
  5. Click Publish.

Now that you have created a component in your local application and in Uniform, you need to connect them so that your application knows

  1. In your text editor, open the src/canvas/VideoPlayer/index.ts file.

  2. Add:

    import { registerUniformComponent } from '@uniformdev/canvas-react';

    to the top of the file with the other import commands to register generic components from Uniform.

  3. Add the specific command from Canvas to the bottom of the file to map the Canvas component to the React component:

    registerUniformComponent({ type: 'videoPlayer', component: VideoPlayer });

    Your file should now look like this:

    src/canvas/VideoPlayer/index.ts

    import { registerUniformComponent, ComponentProps } 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;
  4. Refresh your composition in Canvas and see that the preview of the video player component now works once configured.

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 your source code, update the following file:

    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> );