diff --git a/README.md b/README.md index 42a0e24..b90c02b 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ Available options in useEpg | `isSidebar` | `boolean` | optional | Show/hide sidebar | | `isTimeline` | `boolean` | optional | Show/hide timeline | | `isLine` | `boolean` | optional | Show/hide line | +| `isRTL` | `boolean` | optional | Change direction to RTL or LTR. Default value is false | | `theme` | `object` | optional | Object with theme schema | #### Note about width and height props @@ -412,6 +413,76 @@ function App() { export default App; ``` +## renderProgram - RTL direction + +Below is an example that allows you to render your custom Program component with RTL direction using Plaby's style components. + +```tsx +... +const Item = ({ program, ...rest }: ProgramItem) => { + const { + isRTL, + isLive, + isMinWidth, + formatTime, + styles, + set12HoursTimeFormat, + getRTLSinceTime, + getRTLTillTime, + } = useProgram({ + program, + ...rest + }); + + const { data } = program; + const { image, title, since, till } = data; + + const sinceTime = formatTime( + getRTLSinceTime(since), + set12HoursTimeFormat() + ).toLowerCase(); + const tillTime = formatTime( + getRTLTillTime(till), + set12HoursTimeFormat() + ).toLowerCase(); + + return ( + + + + {isLive && isMinWidth && } + + {title} + + {sinceTime} - {tillTime} + + + + + + ); +}; + +function App() { + + ... + + const { + getEpgProps, + getLayoutProps, +} = useEpg({ + epg, + channels, + isBaseTimeFormat: true, + startDate: '2022/02/02', // or 2022-02-02T00:00:00 +}); + +... +} + +export default App; +``` + ## renderChannel Below is an example that allows you to render your custom Channel component using Plaby's style components. @@ -561,6 +632,60 @@ function App() { export default App; ``` +## renderTimeline - RTL direction + +Below is an example that allows you to render your custom Timeline component using Plaby's style components. + +```tsx +import { + TimelineWrapper, + TimelineBox, + TimelineTime, + TimelineDivider, + TimelineDividers, + useTimeline, +} from 'planby'; + +interface TimelineProps { + isRTL: boolean; + isBaseTimeFormat: boolean; + isSidebar: boolean; + dayWidth: number; + hourWidth: number; + numberOfHoursInDay: number; + offsetStartHoursRange: number; + sidebarWidth: number; +} + +export function Timeline({ + isRTL, + isBaseTimeFormat, + isSidebar, + dayWidth, + hourWidth, + numberOfHoursInDay, + offsetStartHoursRange, + sidebarWidth, +}: TimelineProps) { + const { time, dividers, formatTime } = useTimeline( + numberOfHoursInDay, + isBaseTimeFormat + ); + + const renderTime = (index: number) => ( + + + {formatTime(index + offsetStartHoursRange).toLowerCase()} + + {renderDividers()} + + ); + + ... +} + +``` + ## Theme ### Schema diff --git a/package.json b/package.json index 79a53fb..810ba68 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planby", "author": "Karol Kozer", - "version": "0.3.0", + "version": "0.4.0", "license": "MIT", "repository": { "type": "git", diff --git a/src/Epg/Epg.tsx b/src/Epg/Epg.tsx index f1b02e0..325cc59 100644 --- a/src/Epg/Epg.tsx +++ b/src/Epg/Epg.tsx @@ -16,6 +16,7 @@ import { Loader } from "./components"; interface EpgProps { width?: number; height?: number; + isRTL?: boolean; isSidebar: boolean; isTimeline?: boolean; isLoading?: boolean; @@ -35,6 +36,7 @@ export const Epg = React.forwardRef( height, theme, sidebarWidth, + isRTL = false, isSidebar = true, isTimeline = true, isLoading = false, @@ -57,6 +59,7 @@ export const Epg = React.forwardRef( {isSidebar && isTimeline && ( boolean; channels: ChannelWithPosiiton[]; scrollY: number; sidebarWidth: number; - isTimeline: boolean; - isChannelVisible: (position: any) => boolean; renderChannel?: (v: { channel: ChannelWithPosiiton }) => React.ReactNode; } @@ -21,7 +22,7 @@ const { Box } = ChannelsStyled; export function Channels(props: ChannelsProps) { const { channels, scrollY, sidebarWidth, renderChannel } = props; - const { isTimeline, isChannelVisible } = props; + const { isRTL, isTimeline, isChannelVisible } = props; const renderChannels = (channel: ChannelWithPosiiton) => { const isVisible = isChannelVisible(channel.position); @@ -35,8 +36,9 @@ export function Channels(props: ChannelsProps) { return ( {channels.map(renderChannels)} diff --git a/src/Epg/components/Layout.tsx b/src/Epg/components/Layout.tsx index a8accc1..71cfa17 100644 --- a/src/Epg/components/Layout.tsx +++ b/src/Epg/components/Layout.tsx @@ -20,12 +20,13 @@ import { EpgStyled } from "../styles"; import { Timeline, Channels, Program, Line } from "../components"; interface RenderTimeline { + isBaseTimeFormat: BaseTimeFormat; + isSidebar: boolean; + isRTL: boolean; sidebarWidth: number; hourWidth: number; numberOfHoursInDay: number; offsetStartHoursRange: number; - isBaseTimeFormat: BaseTimeFormat; - isSidebar: boolean; dayWidth: number; } @@ -44,6 +45,7 @@ interface LayoutProps { onScroll: ( e: React.UIEvent & { target: Element } ) => void; + isRTL?: boolean; isBaseTimeFormat?: BaseTimeFormat; isSidebar?: boolean; isTimeline?: boolean; @@ -52,6 +54,7 @@ interface LayoutProps { isChannelVisible: (position: Pick) => boolean; renderProgram?: (v: { program: ProgramItem; + isRTL: boolean; isBaseTimeFormat: BaseTimeFormat; }) => React.ReactNode; renderChannel?: (v: { channel: ChannelWithPosiiton }) => React.ReactNode; @@ -70,6 +73,7 @@ export const Layout = React.forwardRef( isTimeline = true, isLine = true, isBaseTimeFormat = false, + isRTL = false, } = props; const { @@ -97,11 +101,13 @@ export const Layout = React.forwardRef( if (renderProgram) return renderProgram({ program: options, + isRTL, isBaseTimeFormat, }); return ( @@ -112,9 +118,10 @@ export const Layout = React.forwardRef( const renderTopbar = () => { const props = { + sidebarWidth, + isSidebar, + isRTL, dayWidth, - sidebarWidth: sidebarWidth, - isSidebar: isSidebar, numberOfHoursInDay, }; const timeProps = { @@ -130,7 +137,7 @@ export const Layout = React.forwardRef( }; return ( - + {isLine && isFuture && ( ( {isTimeline && renderTopbar()} {isSidebar && ( { - program: T; + isRTL?: boolean; isBaseTimeFormat: BaseTimeFormat; + program: T; onClick?: (v: ProgramType) => void; } @@ -31,19 +32,21 @@ const { export function Program({ program, - isBaseTimeFormat, onClick, ...rest }: ProgramProps) { const { + isRTL, + isLive, + isMinWidth, styles, formatTime, set12HoursTimeFormat, - isLive, - isMinWidth, + getRTLSinceTime, + getRTLTillTime, } = useProgram({ program, - isBaseTimeFormat, + ...rest, }); const { data } = program; @@ -51,6 +54,15 @@ export function Program({ const handleOnContentClick = () => onClick?.(data); + const sinceTime = formatTime( + getRTLSinceTime(since), + set12HoursTimeFormat() + ).toLowerCase(); + const tillTime = formatTime( + getRTLTillTime(till), + set12HoursTimeFormat() + ).toLowerCase(); + return ( ({ > {isLive && isMinWidth && } - + {title} - {formatTime(since, set12HoursTimeFormat())} -{" "} - {formatTime(till, set12HoursTimeFormat())} + {sinceTime} - {tillTime} diff --git a/src/Epg/components/Timeline.tsx b/src/Epg/components/Timeline.tsx index d38dd07..f5e655a 100644 --- a/src/Epg/components/Timeline.tsx +++ b/src/Epg/components/Timeline.tsx @@ -18,6 +18,7 @@ const { } = TimelineStyled; interface TimelineProps { + isRTL?: boolean; isBaseTimeFormat: BaseTimeFormat; isSidebar: boolean; dayWidth: number; @@ -28,6 +29,7 @@ interface TimelineProps { } export function Timeline({ + isRTL, isBaseTimeFormat, isSidebar, dayWidth, @@ -43,7 +45,9 @@ export function Timeline({ const renderTime = (index: number) => ( - {formatTime(index + offsetStartHoursRange)} + + {formatTime(index + offsetStartHoursRange)} + {renderDividers()} ); diff --git a/src/Epg/hooks/__tests__/useProgram.test.tsx b/src/Epg/hooks/__tests__/useProgram.test.tsx index fd21d74..8716535 100644 --- a/src/Epg/hooks/__tests__/useProgram.test.tsx +++ b/src/Epg/hooks/__tests__/useProgram.test.tsx @@ -11,8 +11,11 @@ const defaultState = ({ overrides, styles }: DefaultState = {}) => { return { formatTime: expect.any(Function), set12HoursTimeFormat: expect.any(Function), + getRTLSinceTime: expect.any(Function), + getRTLTillTime: expect.any(Function), isLive: false, isMinWidth: true, + isRTL: false, styles, ...overrides, }; @@ -41,7 +44,11 @@ test("should return generated props from useTimeline", () => { test("should specify an initial state in useTimeline", () => { const program = buildProgramWithPosition(); - const props = { program, isBaseTimeFormat: true, minWidth: 800 }; + const props = { + program, + isBaseTimeFormat: true, + minWidth: 800, + }; const { result } = renderHook(() => useProgram(props)); const options = { ...getStyles(program), diff --git a/src/Epg/hooks/useEpg.tsx b/src/Epg/hooks/useEpg.tsx index 2d04fdd..cd6a23d 100644 --- a/src/Epg/hooks/useEpg.tsx +++ b/src/Epg/hooks/useEpg.tsx @@ -42,6 +42,7 @@ interface useEpgProps { isBaseTimeFormat?: BaseTimeFormat; isSidebar?: boolean; isTimeline?: boolean; + isRTL?: boolean; isLine?: boolean; theme?: Theme; dayWidth?: number; @@ -57,6 +58,7 @@ export function useEpg({ epg, startDate: startDateInput = defaultStartDateTime, endDate: endDateInput = "", + isRTL = false, isBaseTimeFormat = false, isSidebar = true, isTimeline = true, @@ -93,8 +95,13 @@ export function useEpg({ }); const { scrollX, scrollY, layoutWidth, layoutHeight } = layoutProps; - const { onScroll, onScrollToNow, onScrollTop, onScrollLeft, onScrollRight } = - layoutProps; + const { + onScroll, + onScrollToNow, + onScrollTop, + onScrollLeft, + onScrollRight, + } = layoutProps; //-------- Variables -------- const channels = React.useMemo( @@ -140,11 +147,12 @@ export function useEpg({ ); const getEpgProps = () => ({ - width, - height, + isRTL, isSidebar, isLine, isTimeline, + width, + height, sidebarWidth, ref: containerRef, theme, @@ -157,6 +165,7 @@ export function useEpg({ endDate, scrollY, onScroll, + isRTL, isBaseTimeFormat, isSidebar, isTimeline, diff --git a/src/Epg/hooks/useProgram.tsx b/src/Epg/hooks/useProgram.tsx index 3934bc0..c518780 100644 --- a/src/Epg/hooks/useProgram.tsx +++ b/src/Epg/hooks/useProgram.tsx @@ -12,13 +12,15 @@ import { useInterval } from "./useInterval"; interface useProgramProps { program: T; + isRTL?: boolean; isBaseTimeFormat: BaseTimeFormat; minWidth?: number; } export function useProgram({ - program, + isRTL = false, isBaseTimeFormat, + program, minWidth = 200, }: useProgramProps) { const { data, position } = program; @@ -41,6 +43,11 @@ export function useProgram({ return TIME_FORMAT.HOURS_MIN; }; + const getRTLSinceTime = (since: string | number | Date) => + isRTL ? till : since; + const getRTLTillTime = (till: string | number | Date) => + isRTL ? since : till; + useInterval(() => { const status = getLiveStatus(since, till); setIsLive(status); @@ -49,10 +56,13 @@ export function useProgram({ const isMinWidth = width > minWidth; return { - formatTime, - set12HoursTimeFormat, isLive, isMinWidth, + isRTL, + formatTime, + set12HoursTimeFormat, + getRTLSinceTime, + getRTLTillTime, styles: { width, position: newPosition }, }; } diff --git a/src/Epg/index.ts b/src/Epg/index.ts index 7c91447..7e3828b 100644 --- a/src/Epg/index.ts +++ b/src/Epg/index.ts @@ -14,6 +14,7 @@ export type Channel = ChannelWithPosiiton; export type Program = ProgramItemType; export type ProgramItem = { program: ProgramItemType; + isRTL: boolean; isBaseTimeFormat: BaseTimeFormatType; }; diff --git a/src/Epg/styles/Channels.styles.ts b/src/Epg/styles/Channels.styles.ts index 754848d..e6208a7 100644 --- a/src/Epg/styles/Channels.styles.ts +++ b/src/Epg/styles/Channels.styles.ts @@ -2,8 +2,9 @@ import styled from "@emotion/styled/macro"; import { Theme } from "../helpers"; export const Box = styled.div<{ - width: number; isTimeline: boolean; + isRTL: boolean; + width: number; bottom: number; theme?: Theme; }>` @@ -14,4 +15,6 @@ export const Box = styled.div<{ left: 0; z-index: 100; background-color: ${({ theme }) => theme.primary[900]}; + + ${({ isRTL }) => isRTL && `transform: scale(-1,1)`}; `; diff --git a/src/Epg/styles/Epg.styles.ts b/src/Epg/styles/Epg.styles.ts index bffaac7..7bcf6d7 100644 --- a/src/Epg/styles/Epg.styles.ts +++ b/src/Epg/styles/Epg.styles.ts @@ -20,7 +20,7 @@ export const Wrapper = styled.div` overflow: hidden; `; -export const ScrollBox = styled.div<{ theme?: Theme }>` +export const ScrollBox = styled.div<{ theme?: Theme; isRTL?: boolean }>` height: 100%; width: 100%; position: relative; @@ -28,6 +28,8 @@ export const ScrollBox = styled.div<{ theme?: Theme }>` scroll-behavior: smooth; background: ${({ theme }) => theme.primary[900]}; + ${({ isRTL }) => isRTL && `transform: scale(-1,1)`}; + ::-webkit-scrollbar { width: 10px; height: 10px; @@ -54,6 +56,7 @@ export const ScrollBox = styled.div<{ theme?: Theme }>` `; export const Box = styled.div<{ + isRTL?: boolean; width: number; height: number; left?: number; @@ -64,9 +67,10 @@ export const Box = styled.div<{ height: ${({ height }) => height}px; width: ${({ width }) => width}px; top: ${({ top = 0 }) => top}px; - left: ${({ left = 0 }) => left}px; background: ${({ theme }) => theme.primary[900]}; z-index: 900; + + ${({ isRTL, left = 0 }) => (isRTL ? `right:0px;` : ` left: ${left}px`)}; `; export const Content = styled.div<{ diff --git a/src/Epg/styles/Program.styles.ts b/src/Epg/styles/Program.styles.ts index ee5b816..9a47231 100644 --- a/src/Epg/styles/Program.styles.ts +++ b/src/Epg/styles/Program.styles.ts @@ -72,6 +72,12 @@ export const ProgramImage = styled.img` width: 100px; `; -export const ProgramStack = styled.div` +export const ProgramStack = styled.div<{ isRTL?: boolean }>` overflow: hidden; + ${({ isRTL }) => + isRTL && + `transform: scale(-1,1); + display: flex; + flex-direction: column; + align-items: flex-end`}; `; diff --git a/src/Epg/styles/Timeline.styles.ts b/src/Epg/styles/Timeline.styles.ts index 893a10f..77ebabf 100644 --- a/src/Epg/styles/Timeline.styles.ts +++ b/src/Epg/styles/Timeline.styles.ts @@ -4,11 +4,18 @@ import { Theme } from "../helpers"; // Import heleprs import { ITEM_HEIGHT } from "../helpers"; -export const TimelineTime = styled.span<{ theme?: Theme }>` +export const TimelineTime = styled.span<{ + theme?: Theme; + isBaseTimeFormat?: boolean; + isRTL?: boolean; +}>` color: ${({ theme }) => theme.text.grey[300]}; position: absolute; top: 18px; - left: -18px; + left: ${({ isRTL, isBaseTimeFormat }) => + isRTL && isBaseTimeFormat ? "-32" : "-18"}px; + + ${({ isRTL }) => isRTL && `transform: scale(-1,1)`}; `; export const TimelineDividers = styled.div`