Planby

A React based component for a quick implementation of Epg, schedules, live ...

README

Planby logo
npm downloads downloads Support us

Description


Planby is a React based component for a quick implementation of Epg, schedules, live streaming, music events, timelines and many more ideas. It uses a custom virtual view which allows you to operate on a really big number of data. The component has a simple API that you can easily integrate with other third party UI libraries. The component theme is customised to the needs of the application design.

Planby preview
Planby preview
Planby preview

Codesandbox example





Testimonials


JW Player logo

We added Planby as the EPG component of the

open-source
  OTT web app
. The JWP Web App enables developers to quickly build OTT Apps for web platforms, and using Planby developers can easily visualize schedules for 24x7 live streams in their OTT Apps.


Marco van de Veen
Linkedin icon
**Product Manager**
JW Player


Support our activity and help us continue our development -> Open Collective.

Getting Started


Installation


- yarn

  1. ```sh
  2. yarn add planby
  3. ```

- npm

  1. ```sh
  2. npm install planby
  3. ```

Usage


  1. ```tsx
  2. import { useEpg, Epg, Layout } from 'planby';

  3. const channels = React.useMemo(
  4.   () => [
  5.     {
  6.       logo: 'https://via.placeholder.com',
  7.       uuid: '10339a4b-7c48-40ab-abad-f3bcaf95d9fa',
  8.       ...
  9.     },
  10.   ],
  11.   []
  12. );

  13. const epg = React.useMemo(
  14.   () => [
  15.     {
  16.       channelUuid: '30f5ff1c-1346-480a-8047-a999dd908c1e',
  17.       description:
  18.         'Ut anim nisi consequat minim deserunt...',
  19.       id: 'b67ccaa3-3dd2-4121-8256-33dbddc7f0e6',
  20.       image: 'https://via.placeholder.com',
  21.       since: "2022-02-02T23:50:00",
  22.       till: "2022-02-02T00:55:00",
  23.       title: 'Title',
  24.       ...
  25.     },
  26.   ],
  27.   []
  28. );

  29. const {
  30.   getEpgProps,
  31.   getLayoutProps,
  32.   onScrollToNow,
  33.   onScrollLeft,
  34.   onScrollRight,
  35. } = useEpg({
  36.   epg,
  37.   channels,
  38.   startDate: '2022/02/02', // or 2022-02-02T00:00:00
  39. });

  40. return (
  41.   <div>
  42.     <div style={{ height: '600px', width: '1200px' }}>
  43.       <Epg {...getEpgProps()}>
  44.         <Layout
  45.           {...getLayoutProps()}
  46.         />
  47.       </Epg>
  48.     </div>
  49.   </div>
  50. );
  51. ```

or

Custom width and height


  1. ```tsx
  2. const {
  3.   getEpgProps,
  4.   getLayoutProps,
  5.   ...
  6. } = useEpg({
  7.   epg,
  8.   channels,
  9. startDate: '2022/02/02', // or 2022-02-02T00:00:00
  10.   width: 1200,
  11.   height: 600
  12. });

  13. return (
  14.   <div>
  15.      <Epg {...getEpgProps()}>
  16.         <Layout
  17.           {...getLayoutProps()}
  18.         />
  19.       </Epg>
  20.   </div>

  21. ```

or

Time range


  1. ```tsx
  2. const {
  3.   getEpgProps,
  4.   getLayoutProps,
  5.   ...
  6. } = useEpg({
  7.   epg,
  8.   channels,
  9.   startDate: '2022-02-02T10:00:00',
  10.   endDate: '2022-02-02T20:00:00',
  11.   width: 1200,
  12.   height: 600
  13. });

  14. return (
  15.   <div>
  16.      <Epg {...getEpgProps()}>
  17.         <Layout
  18.           {...getLayoutProps()}
  19.         />
  20.       </Epg>
  21.   </div>

  22. ```

API


useEpg


Options


Available options in useEpg

PropertyTypeStatusDescription
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
`channels``array`requiredArray
`epg``array`requiredArray
`width``number`optionalEpg
`height``number`optionalEpg
`sidebarWidth``number`optionalWidth
`itemHeight``number`optionalHeight
`dayWidth``number`optionalWidth
`startDate``string`optionalDate
`endtDate``string`optionalDate
`isBaseTimeFormat``boolean`optionalConvert
`isSidebar``boolean`optionalShow/hide
`isTimeline``boolean`optionalShow/hide
`isLine``boolean`optionalShow/hide
`isRTL``boolean`optionalChange
`theme``object`optionalObject
`globalStyles``string`optionalInject

Note about width and height props


Without declaring the width and length properties, the component takes the dimensions of the parent element.

globalStyles


Inject own custom font and other global styles.

  1. ```tsx
  2. const globalStyles = `
  3.   @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");

  4.   .planby {
  5.     font-family: "Inter", system-ui, -apple-system,
  6.      "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
  7.      "Apple Color Emoji", "Segoe UI Emoji";
  8.   }

  9.   // Other styles
  10.   ...
  11. `;
  12. ```

Instance Properties


Properties returned from useEpg

PropertyTypeDescription
----------------------------------------------------------------------------
`scrollY``number`Current
`scrollX``number`Current
`onScrollLeft``function(value:Default
`onScrollRight``function(value:Default
`onScrollToNow``function()`Scroll
`onScrollTop``function(value:Default

Channel schema


PropertyTypeStatus
------------------------
`logo``string`required
`uuid``string`required

Epg schema


PropertyTypeStatus
-----------------------------
`channelUuid``string`required
`id``string`required
`image``string`required
`since``string`required
`till``string`required
`title``string`required

Epg


Base props


Available props in Epg

PropertyTypeDescriptionStatus
------------------------------------------------------
`isLoading``boolean`Loaderoptional
`loader``Component`Loaderoptional

Layout


Base props


Available props in Layout.

PropertyTypeDescriptionStatus
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
`renderProgram``function({`data`optional
`renderChannel``function({`channel`optional
`renderTimeline``function({sidebarWidth:`sidebarWidth`optional

Render functions


You can use Plaby's style components to develop main features. Moreover, you can integrate with third party UI library eg. Chakra UI, Material UI etc or make custom styles.

renderProgram


Below is an example that allows you to render your custom Program component using Plaby's style components.

  1. ```tsx
  2. import {
  3.   useEpg,
  4.   Epg,
  5.   Layout,
  6.   ProgramBox,
  7.   ProgramContent,
  8.   ProgramFlex,
  9.   ProgramStack,
  10.   ProgramTitle,
  11.   ProgramText,
  12.   ProgramImage,
  13.   useProgram,
  14.   Program,
  15.   ProgramItem
  16. } from "planby";


  17. const Item = ({ program,...rest }: ProgramItem) => {
  18.   const { styles, formatTime, isLive, isMinWidth } = useProgram({ program,...rest });

  19.   const { data } = program;
  20.   const { image, title, since, till } = data;

  21.   const sinceTime = formatTime(since);
  22.   const tillTime = formatTime(till);

  23.   return (
  24.     <ProgramBox width={styles.width} style={styles.position}>
  25.       <ProgramContent
  26.         width={styles.width}
  27.         isLive={isLive}
  28.       >
  29.         <ProgramFlex>
  30.           {isLive && isMinWidth && <ProgramImage src={image} alt="Preview" />}
  31.           <ProgramStack>
  32.             <ProgramTitle>{title}</ProgramTitle>
  33.             <ProgramText>
  34.               {sinceTime} - {tillTime}
  35.             </ProgramText>
  36.           </ProgramStack>
  37.         </ProgramFlex>
  38.       </ProgramContent>
  39.     </ProgramBox>
  40.   );
  41. };

  42. function App() {

  43.   ...

  44. const {
  45.   getEpgProps,
  46.   getLayoutProps,
  47. } = useEpg({
  48.   epg,
  49.   channels,
  50.   startDate: '2022/02/02', // or 2022-02-02T00:00:00
  51. });

  52. return (
  53.   <div>
  54.     <div style={{ height: '600px', width: '1200px' }}>
  55.       <Epg {...getEpgProps()}>
  56.         <Layout
  57.             {...getLayoutProps()}
  58.             renderProgram={({ program,...rest }) => (
  59.               <Item key={program.data.id} program={program} {...rest} />
  60.             )}
  61.           />
  62.       </Epg>
  63.     </div>
  64.   </div>
  65. );
  66. }

  67. export default App;
  68. ```

renderProgram - 12 hours time format


Below is an example that allows you to render your custom Program component with 12 hours time format using Plaby's style components.

  1. ```tsx
  2. ...
  3. const Item = ({ program, ...rest }: ProgramItem) => {
  4.   const {
  5.     styles,
  6.     formatTime,
  7.     set12HoursTimeFormat,
  8.     isLive,
  9.     isMinWidth,
  10.   } = useProgram({
  11.     program,
  12.     ...rest
  13.   });

  14.   const { data } = program;
  15.   const { image, title, since, till } = data;

  16.   const sinceTime = formatTime(since, set12HoursTimeFormat()).toLowerCase();
  17.   const tillTime = formatTime(till, set12HoursTimeFormat()).toLowerCase();

  18.   return (
  19.     <ProgramBox width={styles.width} style={styles.position}>
  20.       <ProgramContent
  21.         width={styles.width}
  22.         isLive={isLive}
  23.       >
  24.         <ProgramFlex>
  25.           {isLive && isMinWidth && <ProgramImage src={image} alt="Preview" />}
  26.           <ProgramStack>
  27.             <ProgramTitle>{title}</ProgramTitle>
  28.             <ProgramText>
  29.               {sinceTime} - {tillTime}
  30.             </ProgramText>
  31.           </ProgramStack>
  32.         </ProgramFlex>
  33.       </ProgramContent>
  34.     </ProgramBox>
  35.   );
  36. };

  37. function App() {

  38.   ...

  39. const {
  40.   getEpgProps,
  41.   getLayoutProps,
  42. } = useEpg({
  43.   epg,
  44.   channels,
  45.   isBaseTimeFormat: true,
  46.   startDate: '2022/02/02', // or 2022-02-02T00:00:00
  47. });

  48. ...
  49. }

  50. export default App;
  51. ```

renderProgram - RTL direction


Below is an example that allows you to render your custom Program component with RTL direction using Plaby's style components.

  1. ```tsx
  2. ...
  3. const Item = ({ program, ...rest }: ProgramItem) => {
  4.   const {
  5.     isRTL,
  6.     isLive,
  7.     isMinWidth,
  8.     formatTime,
  9.     styles,
  10.     set12HoursTimeFormat,
  11.     getRTLSinceTime,
  12.     getRTLTillTime,
  13.   } = useProgram({
  14.     program,
  15.     ...rest
  16.   });

  17.   const { data } = program;
  18.   const { image, title, since, till } = data;

  19.   const sinceTime = formatTime(
  20.     getRTLSinceTime(since),
  21.     set12HoursTimeFormat()
  22.   ).toLowerCase();
  23.   const tillTime = formatTime(
  24.     getRTLTillTime(till),
  25.     set12HoursTimeFormat()
  26.   ).toLowerCase();

  27.   return (
  28.     <ProgramBox width={styles.width} style={styles.position}>
  29.       <ProgramContent width={styles.width} isLive={isLive}>
  30.         <ProgramFlex>
  31.           {isLive && isMinWidth && <ProgramImage src={image} alt="Preview" />}
  32.           <ProgramStack isRTL={isRTL}>
  33.             <ProgramTitle>{title}</ProgramTitle>
  34.             <ProgramText>
  35.               {sinceTime} - {tillTime}
  36.             </ProgramText>
  37.           </ProgramStack>
  38.         </ProgramFlex>
  39.       </ProgramContent>
  40.     </ProgramBox>
  41.   );
  42. };

  43. function App() {

  44.   ...

  45. const {
  46.   getEpgProps,
  47.   getLayoutProps,
  48. } = useEpg({
  49.   epg,
  50.   channels,
  51.   isBaseTimeFormat: true,
  52.   startDate: '2022/02/02', // or 2022-02-02T00:00:00
  53. });

  54. ...
  55. }

  56. export default App;
  57. ```

renderChannel


Below is an example that allows you to render your custom Channel component using Plaby's style components.

  1. ```tsx
  2. import { useEpg, Epg, Layout, ChannelBox, ChannelLogo, Channel } from 'planby';

  3. interface ChannelItemProps {
  4.   channel: Channel;
  5. }

  6. const ChannelItem = ({ channel }: ChannelItemProps) => {
  7.   const { position, logo } = channel;
  8.   return (
  9.     <ChannelBox {...position}>
  10.       <ChannelLogo
  11.         onClick={() => console.log('channel', channel)}
  12.         src={logo}
  13.         alt="Logo"
  14.       />
  15.     </ChannelBox>
  16.   );
  17. };


  18. function App() {

  19.   ...

  20.   const {
  21.     getEpgProps,
  22.     getLayoutProps,
  23.   } = useEpg({
  24.     epg,
  25.     channels,
  26.     startDate: '2022/02/02', // or 2022-02-02T00:00:00
  27.   });

  28.   return (
  29.     <div>
  30.       <div style={{ height: '600px', width: '1200px' }}>
  31.         <Epg {...getEpgProps()}>
  32.           <Layout
  33.               {...getLayoutProps()}
  34.               renderChannel={({ channel }) => (
  35.               <ChannelItem key={channel.uuid} channel={channel} />
  36.             )}
  37.             />
  38.         </Epg>
  39.       </div>
  40.     </div>
  41.   );
  42. }

  43. ```

renderTimeline


Below is an example that allows you to render your custom Timeline component using Plaby's style components.

  1. ```tsx
  2. import {
  3.   TimelineWrapper,
  4.   TimelineBox,
  5.   TimelineTime,
  6.   TimelineDivider,
  7.   TimelineDividers,
  8.   useTimeline,
  9. } from 'planby';

  10. interface TimelineProps {
  11.   isBaseTimeFormat: boolean;
  12.   isSidebar: boolean;
  13.   dayWidth: number;
  14.   hourWidth: number;
  15.   numberOfHoursInDay: number;
  16.   offsetStartHoursRange: number;
  17.   sidebarWidth: number;
  18. }

  19. export function Timeline({
  20.   isBaseTimeFormat,
  21.   isSidebar,
  22.   dayWidth,
  23.   hourWidth,
  24.   numberOfHoursInDay,
  25.   offsetStartHoursRange,
  26.   sidebarWidth,
  27. }: TimelineProps) {
  28.   const { time, dividers, formatTime } = useTimeline(
  29.     numberOfHoursInDay,
  30.     isBaseTimeFormat
  31.   );

  32.   const renderTime = (index: number) => (
  33.     <TimelineBox key={index} width={hourWidth}>
  34.       <TimelineTime>
  35.         {formatTime(index + offsetStartHoursRange).toLowerCase()}
  36.       </TimelineTime>
  37.       <TimelineDividers>{renderDividers()}</TimelineDividers>
  38.     </TimelineBox>
  39.   );

  40.   const renderDividers = () =>
  41.     dividers.map((_, index) => (
  42.       <TimelineDivider key={index} width={hourWidth} />
  43.     ));

  44.   return (
  45.     <TimelineWrapper
  46.       dayWidth={dayWidth}
  47.       sidebarWidth={sidebarWidth}
  48.       isSidebar={isSidebar}
  49.     >
  50.       {time.map((_, index) => renderTime(index))}
  51.     </TimelineWrapper>
  52.   );
  53. }

  54. function App() {

  55.   ...

  56.   const {
  57.     getEpgProps,
  58.     getLayoutProps,
  59.   } = useEpg({
  60.     epg,
  61.     channels,
  62.     startDate: '2022/02/02', // or 2022-02-02T00:00:00
  63.   });

  64.   return (
  65.     <div>
  66.       <div style={{ height: '600px', width: '1200px' }}>
  67.         <Epg {...getEpgProps()}>
  68.           <Layout
  69.               {...getLayoutProps()}
  70.               renderTimeline={(props) => <Timeline {...props} />}
  71.             />
  72.         </Epg>
  73.       </div>
  74.     </div>
  75.   );
  76. }

  77. export default App;
  78. ```

renderTimeline - RTL direction


Below is an example that allows you to render your custom Timeline component using Plaby's style components.

  1. ```tsx
  2. import {
  3.   TimelineWrapper,
  4.   TimelineBox,
  5.   TimelineTime,
  6.   TimelineDivider,
  7.   TimelineDividers,
  8.   useTimeline,
  9. } from 'planby';

  10. interface TimelineProps {
  11.   isRTL: boolean;
  12.   isBaseTimeFormat: boolean;
  13.   isSidebar: boolean;
  14.   dayWidth: number;
  15.   hourWidth: number;
  16.   numberOfHoursInDay: number;
  17.   offsetStartHoursRange: number;
  18.   sidebarWidth: number;
  19. }

  20. export function Timeline({
  21.   isRTL,
  22.   isBaseTimeFormat,
  23.   isSidebar,
  24.   dayWidth,
  25.   hourWidth,
  26.   numberOfHoursInDay,
  27.   offsetStartHoursRange,
  28.   sidebarWidth,
  29. }: TimelineProps) {
  30.   const { time, dividers, formatTime } = useTimeline(
  31.     numberOfHoursInDay,
  32.     isBaseTimeFormat
  33.   );

  34.   const renderTime = (index: number) => (
  35.     <TimelineBox key={index} width={hourWidth}>
  36.       <TimelineTime isBaseTimeFormat={isBaseTimeFormat} isRTL={isRTL}>
  37.         {formatTime(index + offsetStartHoursRange).toLowerCase()}
  38.       </TimelineTime>
  39.       <TimelineDividers>{renderDividers()}</TimelineDividers>
  40.     </TimelineBox>
  41.   );

  42. ...
  43. }

  44. ```

Theme


Schema


Make your theme custom. Below is theme schema that you can pass as one of the options to useEpg hook.

  1. ``` js
  2. const theme = {
  3.   primary: {
  4.     600: '#1a202c',
  5.     900: '#171923',
  6.   },
  7.   grey: { 300: '#d1d1d1' },
  8.   white: '#fff',
  9.   green: {
  10.     300: '#2C7A7B',
  11.   },
  12.   loader: {
  13.     teal: '#5DDADB',
  14.     purple: '#3437A2',
  15.     pink: '#F78EB6',
  16.     bg: '#171923db',
  17.   },
  18.   scrollbar: {
  19.     border: '#ffffff',
  20.     thumb: {
  21.       bg: '#e1e1e1',
  22.     },
  23.   },
  24.   gradient: {
  25.     blue: {
  26.       300: '#002eb3',
  27.       600: '#002360',
  28.       900: '#051937',
  29.     },
  30.   },
  31.   text: {
  32.     grey: {
  33.       300: '#a0aec0',
  34.       500: '#718096',
  35.     },
  36.   },
  37.   timeline: {
  38.     divider: {
  39.       bg: '#718096',
  40.     },
  41.   },
  42. };
  43. ```

All import options


  1. ```tsx
  2. import {
  3.   Epg,
  4.   Layout,
  5.   ChannelBox,
  6.   ChannelLogo,
  7.   ProgramBox,
  8.   ProgramContent,
  9.   ProgramFlex,
  10.   ProgramStack,
  11.   ProgramTitle,
  12.   ProgramText,
  13.   ProgramImage,
  14.   TimelineWrapper,
  15.   TimelineBox,
  16.   TimelineTime,
  17.   TimelineDividers,
  18.   useEpg,
  19.   useProgram,
  20.   useTimeline,
  21.   Program, // Interface
  22.   Channel, // Interface
  23.   ProgramItem, // Interface for program render
  24.   Theme, // Interface
  25. } from 'planby';
  26. ```

License


Distributed under the MIT License. See LICENSE for more information.

Contact


Karol Kozer - @kozerkarol_twitter