diff --git a/packages/example/src/modules/program/view/LongProgram.tsx b/packages/example/src/modules/program/view/LongProgram.tsx deleted file mode 100644 index 89c9277f..00000000 --- a/packages/example/src/modules/program/view/LongProgram.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import styled from '@emotion/native'; -import React from 'react'; -import { Animated, Image, View } from 'react-native'; -import { ProgramInfo } from '../domain/programInfo'; -import { useFocusAnimation } from '../../../design-system/helpers/useFocusAnimation'; - -type LongProgramProps = { - isFocused?: boolean; - programInfo: ProgramInfo; -}; - -export const LongProgram = React.forwardRef( - ({ isFocused = false, programInfo }, ref) => { - const imageSource = programInfo.image; - - const scaleAnimation = useFocusAnimation(isFocused); - - return ( - - - - ); - }, -); - -LongProgram.displayName = 'LongProgram'; - -const LongProgramContainer = styled(Animated.View)<{ isFocused: boolean }>( - ({ isFocused, theme }) => ({ - height: theme.sizes.program.long.height, - width: theme.sizes.program.long.width, - overflow: 'hidden', - borderRadius: 20, - borderColor: isFocused ? theme.colors.primary.light : 'transparent', - borderWidth: 3, - }), -); - -const LongProgramImage = styled(Image)({ - height: '100%', - width: '100%', -}); diff --git a/packages/example/src/modules/program/view/Program.tsx b/packages/example/src/modules/program/view/Program.tsx index b24c53db..464a80fe 100644 --- a/packages/example/src/modules/program/view/Program.tsx +++ b/packages/example/src/modules/program/view/Program.tsx @@ -9,6 +9,7 @@ type ProgramProps = { isFocused?: boolean; programInfo: ProgramInfo; label?: string; + variant?: 'portrait' | 'landscape'; }; const Label = React.memo(({ label }: { label: string }) => { @@ -17,33 +18,41 @@ const Label = React.memo(({ label }: { label: string }) => { Label.displayName = 'Label'; export const Program = React.memo( - React.forwardRef(({ isFocused = false, programInfo, label }, ref) => { - const imageSource = programInfo.image; + React.forwardRef( + ({ isFocused = false, programInfo, label, variant = 'portrait' }, ref) => { + const imageSource = programInfo.image; + const scaleAnimation = useFocusAnimation(isFocused); - const scaleAnimation = useFocusAnimation(isFocused); - - return ( - - - {label ? ( - - - ) : null} - - ); - }), + return ( + + + {label ? ( + + + ) : null} + + ); + }, + ), ); Program.displayName = 'Program'; -const ProgramContainer = styled(Animated.View)<{ isFocused: boolean }>(({ isFocused, theme }) => ({ - height: theme.sizes.program.portrait.height, - width: theme.sizes.program.portrait.width, +const ProgramContainer = styled(Animated.View)<{ + isFocused: boolean; + variant: 'portrait' | 'landscape'; +}>(({ isFocused, variant, theme }) => ({ + height: theme.sizes.program.portrait.height, // Height is the same for both variants + width: + variant === 'landscape' + ? theme.sizes.program.landscape.width * 2 + : theme.sizes.program.portrait.width, overflow: 'hidden', borderRadius: 20, borderColor: isFocused ? theme.colors.primary.light : 'transparent', diff --git a/packages/example/src/modules/program/view/ProgramLandscape.tsx b/packages/example/src/modules/program/view/ProgramLandscape.tsx deleted file mode 100644 index 798ba10c..00000000 --- a/packages/example/src/modules/program/view/ProgramLandscape.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import styled from '@emotion/native'; -import React from 'react'; -import { Animated, Image, View } from 'react-native'; -import { ProgramInfo } from '../domain/programInfo'; -import { useFocusAnimation } from '../../../design-system/helpers/useFocusAnimation'; -import { Typography } from '../../../design-system/components/Typography'; - -type ProgramProps = { - isFocused?: boolean; - programInfo: ProgramInfo; - label?: string; -}; - -const Label = ({ label }: { label: string }) => { - return {label}; -}; - -export const ProgramLandscape = React.forwardRef( - ({ isFocused = false, programInfo, label }, ref) => { - const imageSource = programInfo.image; - - const scaleAnimation = useFocusAnimation(isFocused); - - return ( - - - {label ? ( - - - ) : null} - - ); - }, -); - -ProgramLandscape.displayName = 'ProgramSquare'; - -const ProgramContainer = styled(Animated.View)<{ isFocused: boolean }>(({ isFocused, theme }) => ({ - height: theme.sizes.program.portrait.height, - width: theme.sizes.program.landscape.width * 2, - overflow: 'hidden', - borderRadius: 20, - borderColor: isFocused ? theme.colors.primary.light : 'transparent', - borderWidth: 3, -})); - -const ProgramImage = styled(Image)({ - height: '100%', - width: '100%', -}); - -const Overlay = styled.View({ - position: 'absolute', - bottom: 12, - left: 12, -}); diff --git a/packages/example/src/modules/program/view/ProgramList.tsx b/packages/example/src/modules/program/view/ProgramList.tsx index 1ec899cd..d4ea4115 100644 --- a/packages/example/src/modules/program/view/ProgramList.tsx +++ b/packages/example/src/modules/program/view/ProgramList.tsx @@ -21,18 +21,27 @@ const NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN = 7; const WINDOW_SIZE = NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN + 8; const ROW_PADDING = scaledPixels(70); +type ProgramListProps = { + orientation?: 'vertical' | 'horizontal'; + containerStyle?: object; + listRef?: MutableRefObject; + data?: ProgramInfo[]; + variant?: 'normal' | 'variable-size'; +}; + export const ProgramList = ({ - orientation, + orientation = 'horizontal', containerStyle, listRef, data, -}: { - orientation?: 'vertical' | 'horizontal'; - containerStyle?: object; - listRef: MutableRefObject; - data?: ProgramInfo[]; -}) => { + variant = 'normal', +}: ProgramListProps) => { const navigation = useNavigation>(); + const theme = useTheme(); + + const isItemLarge = useCallback((item: ProgramInfo) => { + return parseInt(item.id, 10) % 2 === 0; // Arbitrary condition to decide size + }, []); const renderItem = useCallback( ({ item, index }: { item: ProgramInfo; index: number }) => ( @@ -40,13 +49,16 @@ export const ProgramList = ({ programInfo={item} onSelect={() => navigation.push('ProgramDetail', { programInfo: item })} label={index.toString()} + variant={variant === 'variable-size' && isItemLarge(item) ? 'landscape' : 'portrait'} /> ), - [navigation], + [navigation, isItemLarge, variant], ); - const theme = useTheme(); - const programInfos = useMemo(() => getPrograms(1000), []); + const programInfos = useMemo( + () => data ?? getPrograms(variant === 'variable-size' ? 10 : 1000), + [data, variant], + ); return ( @@ -54,9 +66,16 @@ export const ProgramList = ({ + isItemLarge(item) + ? theme.sizes.program.landscape.width * 2 + 45 + : theme.sizes.program.portrait.width + 30 + : theme.sizes.program.portrait.width + 30 // Default item size for "normal" + } numberOfRenderedItems={WINDOW_SIZE} numberOfItemsVisibleOnScreen={NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN} onEndReachedThresholdItemsNumber={NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN} @@ -75,9 +94,11 @@ export const ProgramList = ({ export const ProgramsRow = ({ containerStyle, listRef, + variant = 'normal', }: { containerStyle?: object; - listRef: MutableRefObject; + listRef?: MutableRefObject; + variant?: 'normal' | 'variable-size'; }) => { const theme = useTheme(); return ( @@ -87,6 +108,7 @@ export const ProgramsRow = ({ height: theme.sizes.program.portrait.height + ROW_PADDING, }} listRef={listRef} + variant={variant} /> ); }; diff --git a/packages/example/src/modules/program/view/ProgramListVariableSize.tsx b/packages/example/src/modules/program/view/ProgramListVariableSize.tsx deleted file mode 100644 index 96495534..00000000 --- a/packages/example/src/modules/program/view/ProgramListVariableSize.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import styled from '@emotion/native'; -import { useTheme } from '@emotion/react'; -import { useNavigation } from '@react-navigation/native'; -import { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { useCallback, useMemo } from 'react'; -import { SpatialNavigationNode, SpatialNavigationVirtualizedList } from 'react-tv-space-navigation'; -import { RootStackParamList } from '../../../../App'; -import { ProgramNode } from './ProgramNode'; -import { scaledPixels } from '../../../design-system/helpers/scaledPixels'; -import { ProgramNodeLandscape } from './ProgramNodeLandscape'; -import { getPrograms } from '../infra/programInfos'; -import { ProgramInfo } from '../domain/programInfo'; -import { LeftArrow, RightArrow } from '../../../design-system/components/Arrows'; -import { StyleSheet } from 'react-native'; -import { theme } from '../../../design-system/theme/theme'; - -const NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN = 7; -const WINDOW_SIZE = NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN + 8; -const ROW_PADDING = scaledPixels(70); - -export const ProgramList = ({ - orientation, - containerStyle, -}: { - orientation?: 'vertical' | 'horizontal'; - containerStyle?: object; -}) => { - const navigation = useNavigation>(); - - const isItemLarge = (item: ProgramInfo) => { - return parseInt(item.id, 10) % 2 === 0; - }; - - const renderItem = useCallback( - ({ item, index }: { item: ProgramInfo; index: number }) => - isItemLarge(item) ? ( - navigation.push('ProgramDetail', { programInfo: item })} - label={index.toString()} - /> - ) : ( - navigation.push('ProgramDetail', { programInfo: item })} - label={index.toString()} - /> - ), - [navigation], - ); - const theme = useTheme(); - - const programInfos = useMemo(() => getPrograms(10), []); - - return ( - - {({ isActive }) => ( - - - isItemLarge(item) - ? theme.sizes.program.portrait.width + 30 - : theme.sizes.program.landscape.width * 2 + 45 - } - numberOfRenderedItems={WINDOW_SIZE} - numberOfItemsVisibleOnScreen={NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN} - onEndReachedThresholdItemsNumber={NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN} - descendingArrow={isActive ? : null} - descendingArrowContainerStyle={styles.leftArrowContainer} - ascendingArrow={isActive ? : null} - ascendingArrowContainerStyle={styles.rightArrowContainer} - /> - - )} - - ); -}; - -export const ProgramsRowVariableSize = ({ containerStyle }: { containerStyle?: object }) => { - const theme = useTheme(); - return ( - - ); -}; - -const Container = styled.View(({ theme }) => ({ - backgroundColor: theme.colors.background.mainHover, - padding: theme.spacings.$8, - borderRadius: scaledPixels(20), - overflow: 'hidden', -})); - -const styles = StyleSheet.create({ - leftArrowContainer: { - width: 120, - height: scaledPixels(260) + 2 * theme.spacings.$8, - position: 'absolute', - top: 0, - justifyContent: 'center', - alignItems: 'center', - left: -theme.spacings.$8, - }, - rightArrowContainer: { - width: 120, - height: scaledPixels(260) + 2 * theme.spacings.$8, - position: 'absolute', - top: 0, - justifyContent: 'center', - alignItems: 'center', - right: -theme.spacings.$8, - }, -}); diff --git a/packages/example/src/modules/program/view/ProgramListWithTitle.tsx b/packages/example/src/modules/program/view/ProgramListWithTitle.tsx index 44ccda9f..ee016bca 100644 --- a/packages/example/src/modules/program/view/ProgramListWithTitle.tsx +++ b/packages/example/src/modules/program/view/ProgramListWithTitle.tsx @@ -3,7 +3,6 @@ import { Box } from '../../../design-system/components/Box'; import { Spacer } from '../../../design-system/components/Spacer'; import { Typography } from '../../../design-system/components/Typography'; import { ProgramsRow } from './ProgramList'; -import { ProgramsRowVariableSize } from './ProgramListVariableSize'; import { SpatialNavigationVirtualizedListRef } from '../../../../../lib/src/spatial-navigation/types/SpatialNavigationVirtualizedListRef'; type Props = { @@ -30,7 +29,7 @@ export const ProgramListWithTitleAndVariableSizes = ({ title }: Props) => { {title} - + ); }; diff --git a/packages/example/src/modules/program/view/ProgramNode.tsx b/packages/example/src/modules/program/view/ProgramNode.tsx index ae9f22d8..f03319a7 100644 --- a/packages/example/src/modules/program/view/ProgramNode.tsx +++ b/packages/example/src/modules/program/view/ProgramNode.tsx @@ -2,7 +2,6 @@ import { SpatialNavigationFocusableView } from 'react-tv-space-navigation'; import { ProgramInfo } from '../domain/programInfo'; import { Program } from './Program'; -import { LongProgram } from './LongProgram'; import { forwardRef } from 'react'; import { SpatialNavigationNodeRef } from '../../../../../lib/src/spatial-navigation/types/SpatialNavigationNodeRef'; @@ -11,10 +10,11 @@ type Props = { onSelect?: () => void; indexRange?: [number, number]; label?: string; + variant?: 'portrait' | 'landscape'; }; export const ProgramNode = forwardRef( - ({ programInfo, onSelect, indexRange, label }: Props, ref) => { + ({ programInfo, onSelect, indexRange, label, variant }: Props, ref) => { return ( ( ref={ref} > {({ isFocused, isRootActive }) => ( - + )} ); }, ); ProgramNode.displayName = 'ProgramNode'; - -export const LongProgramNode = forwardRef( - ({ programInfo, onSelect, indexRange }: Props, ref) => { - return ( - - {({ isFocused }) => } - - ); - }, -); -LongProgramNode.displayName = 'LongProgramNode'; diff --git a/packages/example/src/modules/program/view/ProgramNodeLandscape.tsx b/packages/example/src/modules/program/view/ProgramNodeLandscape.tsx deleted file mode 100644 index 8a117287..00000000 --- a/packages/example/src/modules/program/view/ProgramNodeLandscape.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { SpatialNavigationFocusableView } from 'react-tv-space-navigation'; - -import { ProgramInfo } from '../domain/programInfo'; -import { ProgramLandscape } from './ProgramLandscape'; - -type Props = { - programInfo: ProgramInfo; - onSelect?: () => void; - label?: string; -}; - -export const ProgramNodeLandscape = ({ programInfo, onSelect, label }: Props) => { - return ( - - {({ isFocused, isRootActive }) => ( - - )} - - ); -}; diff --git a/packages/example/src/pages/GridWithLongNodesPage.tsx b/packages/example/src/pages/GridWithLongNodesPage.tsx index 71dac6c2..16c9d6c2 100644 --- a/packages/example/src/pages/GridWithLongNodesPage.tsx +++ b/packages/example/src/pages/GridWithLongNodesPage.tsx @@ -10,7 +10,7 @@ import '../components/configureRemoteControl'; import { programInfos } from '../modules/program/infra/programInfos'; import styled from '@emotion/native'; import { scaledPixels } from '../design-system/helpers/scaledPixels'; -import { LongProgramNode, ProgramNode } from '../modules/program/view/ProgramNode'; +import { ProgramNode } from '../modules/program/view/ProgramNode'; import { theme } from '../design-system/theme/theme'; import { MutableRefObject, forwardRef, useRef } from 'react'; import { StyleSheet } from 'react-native'; @@ -32,7 +32,6 @@ export const GridWithLongNodesPage = () => { } ascendingArrowContainerStyle={styles.bottomArrowContainer} @@ -76,10 +75,15 @@ const FirstRow = forwardRef((_, ref) => { return ( - + - +