Skip to content

Commit 016404e

Browse files
author
Pierre Poupin
authored
Merge pull request #113 from bamlab/feat/add-is-root-active-mechanism
feat(core): add isRootActive mechanism
2 parents 57c6e1c + 4b56297 commit 016404e

8 files changed

Lines changed: 108 additions & 46 deletions

File tree

docs/api.md

Lines changed: 42 additions & 32 deletions
Large diffs are not rendered by default.

packages/example/src/design-system/components/Button.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ ButtonContent.displayName = 'ButtonContent';
2626
export const Button = ({ label, onSelect }: ButtonProps) => {
2727
return (
2828
<SpatialNavigationFocusableView onSelect={onSelect}>
29-
{({ isFocused }) => <ButtonContent label={label} isFocused={isFocused} />}
29+
{({ isFocused, isRootActive }) => (
30+
<ButtonContent label={label} isFocused={isFocused && isRootActive} />
31+
)}
3032
</SpatialNavigationFocusableView>
3133
);
3234
};

packages/example/src/modules/program/view/ProgramNode.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ export const ProgramNode = forwardRef<SpatialNavigationNodeRef, Props>(
2222
viewProps={{ accessibilityLabel: programInfo.title }}
2323
ref={ref}
2424
>
25-
{({ isFocused }) => (
26-
<Program isFocused={isFocused} programInfo={programInfo} label={label} />
25+
{({ isFocused, isRootActive }) => (
26+
<Program isFocused={isFocused && isRootActive} programInfo={programInfo} label={label} />
2727
)}
2828
</SpatialNavigationFocusableView>
2929
);

packages/example/src/modules/program/view/ProgramNodeLandscape.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ type Props = {
1212
export const ProgramNodeLandscape = ({ programInfo, onSelect, label }: Props) => {
1313
return (
1414
<SpatialNavigationFocusableView onSelect={onSelect}>
15-
{({ isFocused }) => (
16-
<ProgramLandscape isFocused={isFocused} programInfo={programInfo} label={label} />
15+
{({ isFocused, isRootActive }) => (
16+
<ProgramLandscape
17+
isFocused={isFocused && isRootActive}
18+
programInfo={programInfo}
19+
label={label}
20+
/>
1721
)}
1822
</SpatialNavigationFocusableView>
1923
);

packages/lib/src/spatial-navigation/components/FocusableView.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,16 @@ type FocusableViewProps = {
99
style?: ViewStyle;
1010
children:
1111
| React.ReactElement
12-
| ((props: { isFocused: boolean; isActive: boolean }) => React.ReactElement);
12+
| ((props: {
13+
/** Returns whether the root is focused or not. */
14+
isFocused: boolean;
15+
/** Returns whether the root is active or not. An active node is active if one of its children is focused. */
16+
isActive: boolean;
17+
/** Returns whether the root is active or not.
18+
* This is very handy if you want to hide the focus on your page elements when
19+
* the side-menu is focused (since it is a different root navigator) */
20+
isRootActive: boolean;
21+
}) => React.ReactElement);
1322
viewProps?: ViewProps & {
1423
onMouseEnter?: () => void;
1524
};
@@ -49,13 +58,14 @@ export const SpatialNavigationFocusableView = forwardRef<SpatialNavigationNodeRe
4958

5059
return (
5160
<SpatialNavigationNode isFocusable {...props} ref={nodeRef}>
52-
{({ isFocused, isActive }) => (
61+
{({ isFocused, isActive, isRootActive }) => (
5362
<InnerFocusableView
5463
viewProps={viewProps}
5564
webProps={webProps}
5665
style={style}
5766
isActive={isActive}
5867
isFocused={isFocused}
68+
isRootActive={isRootActive}
5969
>
6070
{children}
6171
</InnerFocusableView>
@@ -78,10 +88,11 @@ type InnerFocusableViewProps = FocusableViewProps & {
7888
};
7989
isActive: boolean;
8090
isFocused: boolean;
91+
isRootActive: boolean;
8192
};
8293

8394
const InnerFocusableView = forwardRef<View, InnerFocusableViewProps>(
84-
({ viewProps, webProps, children, isActive, isFocused, style }, ref) => {
95+
({ viewProps, webProps, children, isActive, isFocused, isRootActive, style }, ref) => {
8596
const accessibilityProps = useSpatialNavigatorFocusableAccessibilityProps();
8697
const accessibilityState = useMemo(() => ({ selected: isFocused }), [isFocused]);
8798

@@ -94,7 +105,9 @@ const InnerFocusableView = forwardRef<View, InnerFocusableViewProps>(
94105
{...viewProps}
95106
{...webProps}
96107
>
97-
{typeof children === 'function' ? children({ isFocused, isActive }) : children}
108+
{typeof children === 'function'
109+
? children({ isFocused, isActive, isRootActive })
110+
: children}
98111
</View>
99112
);
100113
},

packages/lib/src/spatial-navigation/components/Node.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,33 @@ import { useUniqueId } from '../hooks/useUniqueId';
88
import { NodeOrientation } from '../types/orientation';
99
import { NodeIndexRange } from '@bam.tech/lrud';
1010
import { SpatialNavigationNodeRef } from '../types/SpatialNavigationNodeRef';
11+
import { useIsRootActive } from '../context/IsRootActiveContext';
1112

1213
type FocusableProps = {
1314
isFocusable: true;
14-
children: (props: { isFocused: boolean; isActive: boolean }) => React.ReactElement;
15+
children: (props: {
16+
/** Returns whether the root is focused or not. */
17+
isFocused: boolean;
18+
/** Returns whether the root is active or not. An active node is active if one of its children is focused. */
19+
isActive: boolean;
20+
/** Returns whether the root is active or not.
21+
* This is very handy if you want to hide the focus on your page elements when
22+
* the side-menu is focused (since it is a different root navigator) */
23+
isRootActive: boolean;
24+
}) => React.ReactElement;
1525
};
1626
type NonFocusableProps = {
1727
isFocusable?: false;
18-
children: React.ReactElement | ((props: { isActive: boolean }) => React.ReactElement);
28+
children:
29+
| React.ReactElement
30+
| ((props: {
31+
/** Returns whether the root is active or not. An active node is active if one of its children is focused. */
32+
isActive: boolean;
33+
/** Returns whether the root is active or not.
34+
* This is very handy if you want to hide the focus on your page elements when
35+
* the side-menu is focused (since it is a different root navigator) */
36+
isRootActive: boolean;
37+
}) => React.ReactElement);
1938
};
2039
type DefaultProps = {
2140
onFocus?: () => void;
@@ -95,6 +114,7 @@ export const SpatialNavigationNode = forwardRef<SpatialNavigationNodeRef, Props>
95114
) => {
96115
const spatialNavigator = useSpatialNavigator();
97116
const parentId = useParentId();
117+
const isRootActive = useIsRootActive();
98118
const [isFocused, setIsFocused] = useState(false);
99119
const [isActive, setIsActive] = useState(false);
100120
// If parent changes, we have to re-register the Node + all children -> adding the parentId to the nodeId makes the children re-register.
@@ -180,7 +200,7 @@ export const SpatialNavigationNode = forwardRef<SpatialNavigationNodeRef, Props>
180200
return (
181201
<ParentIdContext.Provider value={id}>
182202
{typeof children === 'function'
183-
? bindRefToChild(children({ isFocused, isActive }))
203+
? bindRefToChild(children({ isFocused, isActive, isRootActive }))
184204
: children}
185205
</ParentIdContext.Provider>
186206
);

packages/lib/src/spatial-navigation/components/Root.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useCreateSpatialNavigator } from '../hooks/useCreateSpatialNavigator';
55
import { useRemoteControl } from '../hooks/useRemoteControl';
66
import { OnDirectionHandledWithoutMovement } from '../SpatialNavigator';
77
import { LockSpatialNavigationContext, useIsLocked } from '../context/LockSpatialNavigationContext';
8+
import { IsRootActiveContext } from '../context/IsRootActiveContext';
89

910
const ROOT_ID = 'root';
1011

@@ -13,6 +14,8 @@ type Props = {
1314
* Determines if the spatial navigation is active.
1415
* If false, the spatial navigation will be locked, and no nodes can be focused.
1516
* This is useful to handle a multi page app: you can disable the non-focused pages' spatial navigation roots.
17+
*
18+
* Note: this is a little redundant with the lock system, but it's useful to have a way to disable the spatial navigation from above AND from below.
1619
*/
1720
isActive?: boolean;
1821
/**
@@ -45,7 +48,8 @@ export const SpatialNavigationRoot = ({
4548

4649
const { isLocked, lockActions } = useIsLocked();
4750

48-
useRemoteControl({ spatialNavigator, isActive: isActive && !isLocked });
51+
const isRootActive = isActive && !isLocked;
52+
useRemoteControl({ spatialNavigator, isActive: isRootActive });
4953

5054
useEffect(() => {
5155
spatialNavigator.registerNode(ROOT_ID, { orientation: 'vertical' });
@@ -55,7 +59,9 @@ export const SpatialNavigationRoot = ({
5559
return (
5660
<SpatialNavigatorContext.Provider value={spatialNavigator}>
5761
<LockSpatialNavigationContext.Provider value={lockActions}>
58-
<ParentIdContext.Provider value={ROOT_ID}>{children}</ParentIdContext.Provider>
62+
<IsRootActiveContext.Provider value={isRootActive}>
63+
<ParentIdContext.Provider value={ROOT_ID}>{children}</ParentIdContext.Provider>
64+
</IsRootActiveContext.Provider>
5965
</LockSpatialNavigationContext.Provider>
6066
</SpatialNavigatorContext.Provider>
6167
);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createContext, useContext } from 'react';
2+
3+
export const IsRootActiveContext = createContext<boolean>(true);
4+
5+
export const useIsRootActive = () => {
6+
return useContext(IsRootActiveContext);
7+
};

0 commit comments

Comments
 (0)