diff --git a/docs/api.md b/docs/api.md index 57e59135..fb5bced7 100644 --- a/docs/api.md +++ b/docs/api.md @@ -51,12 +51,9 @@ The `SpatialNavigationNode` component receives the following props: | `onSelect` | `function` | `undefined` | Callback function to be called when the node is selected. | | `orientation` | `'vertical' \| 'horizontal` | `'vertical'` | Determines the orientation of the node. | | `isFocusable` | `boolean` | `false` | Determines if the node is focusable or not. If it's `true`, the `children` prop must be a function that returns a React element and accepts a parameter with a `isFocused` property. If it's `false` or not provided, `children` can be any valid React node. | - -| `alignInGrid` | `boolean` | `false` | Determines whether child lists should behave like a grid. - -| `indexRange` | `number[]` | `undefined` | Determines the indexes when using long nodes in a grid. If a grid row has one `indexRange`, you should specify each element's `indexRange`. You can check for more details in [`GridWithLongNodesPage`](https://github.com/bamlab/react-tv-space-navigation/blob/31bfe1def4a7e18e9e41f26a520090d1b7a5b149/packages/example/src/pages/GridWithLongNodesPage.tsx) example or in [lrud documentation](https://github.com/bbc/lrud/blob/master/docs/usage.md#indexrange). - -| `children` | `function` or `ReactNode` | `null` | Child elements of the component. It can be a function that returns a React element and accepts a parameter with a `isFocused` property when `isFocusable` is `true`. If `isFocusable` is `false` or not provided, it can be any valid React node. | +| `alignInGrid` | `boolean` | `false` | Determines whether child lists should behave like a grid. | +| `indexRange` | `number[]` | `undefined` | Determines the indexes when using long nodes in a grid. If a grid row has one `indexRange`, you should specify each element's `indexRange`. You can check for more details in [`GridWithLongNodesPage`](https://github.com/bamlab/react-tv-space-navigation/blob/31bfe1def4a7e18e9e41f26a520090d1b7a5b149/packages/example/src/pages/GridWithLongNodesPage.tsx) example or in [lrud documentation](https://github.com/bbc/lrud/blob/master/docs/usage.md#indexrange). | +| `children` | `({ isFocused, isActive }: { isFocused: boolean, isActive: boolean }) => ReactNode` or `ReactNode` | `null` | Child elements of the component. It can be a function that returns a React element and accepts a parameter with a `isFocused` property when `isFocusable` is `true`. If `isFocusable` is `false` or not provided, it can be any valid React node. | ## Usage @@ -71,6 +68,21 @@ The `SpatialNavigationNode` component receives the following props: ``` +```jsx + + {({ isActive }) => ( + // This node is active if one of its nodes is focused + + + )} + +``` + +Note : It is not recommend to use isActive on virtualized focusable nodes, as this can lead to unexpected behaviour. + ## Important note The SpatialNavigationNode will use the ref of your component to handle the scrolling. diff --git a/packages/example/src/design-system/theme/colors.ts b/packages/example/src/design-system/theme/colors.ts index c948494a..de45b475 100644 --- a/packages/example/src/design-system/theme/colors.ts +++ b/packages/example/src/design-system/theme/colors.ts @@ -16,6 +16,7 @@ export const colors = { background: { main: '#111111', mainHover: '#1a1a1a', + mainActive: '#2a2a2a', light: '#EEF0F5', lightHover: '#D5D7DC', contrastText: '#FFFFFF', diff --git a/packages/example/src/modules/program/view/ProgramList.tsx b/packages/example/src/modules/program/view/ProgramList.tsx index 30ded24e..2272eab5 100644 --- a/packages/example/src/modules/program/view/ProgramList.tsx +++ b/packages/example/src/modules/program/view/ProgramList.tsx @@ -3,7 +3,7 @@ import { useTheme } from '@emotion/react'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useCallback, useMemo } from 'react'; -import { SpatialNavigationVirtualizedList } from 'react-tv-space-navigation'; +import { SpatialNavigationNode, SpatialNavigationVirtualizedList } from 'react-tv-space-navigation'; import { RootStackParamList } from '../../../../App'; import { ProgramInfo } from '../domain/programInfo'; import { getPrograms } from '../infra/programInfos'; @@ -37,17 +37,21 @@ export const ProgramList = ({ const programInfos = useMemo(() => getPrograms(), []); return ( - - - + + {({ isActive }) => ( + + + + )} + ); }; @@ -63,8 +67,10 @@ export const ProgramsRow = ({ containerStyle }: { containerStyle?: object }) => ); }; -const Container = styled.View(({ theme }) => ({ - backgroundColor: theme.colors.background.mainHover, +const Container = styled.View<{ isActive: boolean }>(({ isActive, theme }) => ({ + backgroundColor: isActive + ? theme.colors.background.mainActive + : theme.colors.background.mainHover, padding: theme.spacings.$8, borderRadius: scaledPixels(20), overflow: 'hidden', diff --git a/packages/lib/src/spatial-navigation/components/Node.tsx b/packages/lib/src/spatial-navigation/components/Node.tsx index 32d4f3e7..ab34c1aa 100644 --- a/packages/lib/src/spatial-navigation/components/Node.tsx +++ b/packages/lib/src/spatial-navigation/components/Node.tsx @@ -11,11 +11,11 @@ import { NodeIndexRange } from '@bam.tech/lrud'; type FocusableProps = { isFocusable: true; - children: (props: { isFocused: boolean }) => React.ReactElement; + children: (props: { isFocused: boolean; isActive: boolean }) => React.ReactElement; }; type NonFocusableProps = { isFocusable?: false; - children: React.ReactElement; + children: React.ReactElement | ((props: { isActive: boolean }) => React.ReactElement); }; type DefaultProps = { onFocus?: () => void; @@ -79,6 +79,7 @@ export const SpatialNavigationNode = ({ const spatialNavigator = useSpatialNavigator(); const parentId = useParentId(); const [isFocused, setIsFocused] = useState(false); + const [isActive, setIsActive] = useState(false); // If parent changes, we have to re-register the Node + all children -> adding the parentId to the nodeId makes the children re-register. const id = useUniqueId({ prefix: `${parentId}_node_` }); @@ -122,6 +123,8 @@ export const SpatialNavigationNode = ({ orientation, isIndexAlign: alignInGrid, indexRange, + onActive: () => setIsActive(true), + onInactive: () => setIsActive(false), }); return () => spatialNavigator.unregisterNode(id); @@ -135,7 +138,9 @@ export const SpatialNavigationNode = ({ return ( - {typeof children === 'function' ? bindRefToChild(children({ isFocused })) : children} + {typeof children === 'function' + ? bindRefToChild(children({ isFocused, isActive })) + : children} ); };