Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Root/View): Add a hook to know animation transition direction #5567

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
55ebfa1
Add root context to track transition direction
mendrew Aug 1, 2023
83010ff
Prepare transition hooks for Views and Panels
mendrew Aug 2, 2023
ce03ef9
Test strategy to just look at previous isBack value and check for mount
mendrew Aug 3, 2023
7f336ba
Provide transition direction via NavTransitionProvider
mendrew Aug 3, 2023
5ec2f91
Remove unnecessary hooks and contexts from NavTransitionContext
mendrew Aug 3, 2023
0a3cf66
Updated View Readme to mention the new hook
mendrew Aug 3, 2023
317458c
Update View Readme to link to the Root example
mendrew Aug 3, 2023
1fa6649
Remove outdated hook
mendrew Aug 3, 2023
0dccc69
Remove comment about swipe testing
mendrew Aug 3, 2023
cb106e1
Remove `initial` value from direction as it is confusing
mendrew Aug 3, 2023
6293bad
Remove useless RootContext
mendrew Aug 3, 2023
06d6b82
Test navigation direction
mendrew Aug 4, 2023
e08ae15
Update links in Readme
mendrew Aug 4, 2023
3992de3
Remove imports from Readme
mendrew Aug 4, 2023
8659ae4
To make sure that we can track direction when swiping back on iOS
mendrew Aug 4, 2023
5ad8ca0
Highlight hook documentation
mendrew Aug 4, 2023
97343a6
Initialize direction value on panel mount once
mendrew Aug 7, 2023
4d3ae7b
Remove isBack and direction prop from NavTransitionContext
mendrew Aug 7, 2023
8694338
Update documentation
mendrew Aug 7, 2023
60857ad
Test direction detection on swipeBack
mendrew Aug 8, 2023
11fd9c2
Test useNavDirection with and without animations
mendrew Aug 8, 2023
55a4c67
NavTransitionDirectionContext to a separate folder
mendrew Aug 8, 2023
e3eb543
Remove unnecessary import
mendrew Aug 8, 2023
6be1af0
Add NavTransitionDirectionProvider to ViewInfinite
mendrew Aug 8, 2023
bf753c2
Remove empty file
mendrew Aug 8, 2023
c77ae79
Update documentation
mendrew Aug 8, 2023
6f3a034
Remove ref check from example
mendrew Aug 8, 2023
01d12dd
Move useNavDirection() example from Root to View doc
mendrew Aug 8, 2023
49bb2e2
Remove useNavTransition() from the doc
mendrew Aug 8, 2023
2cafbaa
Set swipingback default to undefind in ViewInfinite
mendrew Aug 8, 2023
e83d5af
Ignore test coverage for the ViewInfinite component
mendrew Aug 8, 2023
ddcb8a1
Reword title of useNavDirection
mendrew Aug 8, 2023
763dd51
Move ViewInfinite tests to a separate file
mendrew Aug 8, 2023
af7ddd9
Translate example to russian
mendrew Aug 8, 2023
abf41ed
Remove outdated code from NavTransitionContext
mendrew Aug 8, 2023
dec488a
Fix typo in the Readme
mendrew Aug 10, 2023
6f29126
Use better wording in doc
mendrew Aug 15, 2023
676cd7b
Reword documentation to make it more formal
mendrew Aug 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React from 'react';
import { act, render, screen } from '@testing-library/react';
import { ConfigProvider } from '../ConfigProvider/ConfigProvider';
import { Panel } from '../Panel/Panel';
import { Root } from '../Root/Root';
import { View } from '../View/View';
import { useNavDirection } from './NavTransitionDirectionContext';

function setup({ withAnimationsMode }: { withAnimationsMode: boolean }) {
const TestContent = () => {
const direction = useNavDirection();
return <span>Direction: {direction || 'undefined'}</span>;
};

const TestComponent = ({
activeView,
activePanel = 'v2.1',
}: {
activeView: string;
activePanel?: string;
}) => {
return (
<ConfigProvider transitionMotionEnabled={withAnimationsMode}>
<Root activeView={activeView}>
<View id="v1" key="1" activePanel="v1.1">
<Panel id="v1.1">
<TestContent />
</Panel>
</View>
<View id="v2" key="2" activePanel={activePanel}>
<Panel id="v2.1" key="1">
<TestContent />
</Panel>
<Panel id="v2.2" key="2">
<TestContent />
</Panel>
<Panel id="v2.3" key="3">
<TestContent />
</Panel>
</View>
<View id="v3" key="3" activePanel="v3.1">
<Panel id="v3.1">
<TestContent />
</Panel>
</View>
</Root>
</ConfigProvider>
);
};

return { TestComponent };
}

describe('useNavTransition', () => {
beforeAll(() => jest.useFakeTimers());
afterAll(() => jest.useRealTimers());

it.each([
['properly detects transition direction without animations', false],
['properly detects transition direction with animations', true],
])('%s', (_name, withAnimationsMode) => {
const { TestComponent } = setup({ withAnimationsMode });
// transition between views
const component = render(<TestComponent activeView="v1" />);
expect(screen.queryByText('Direction: undefined')).toBeTruthy();

component.rerender(<TestComponent activeView="v3" />);
expect(screen.queryByText('Direction: forwards')).toBeTruthy();

component.rerender(<TestComponent activeView="v2" />);
expect(screen.queryByText('Direction: backwards')).toBeTruthy();

component.rerender(<TestComponent activeView="v1" />);
act(() => {
jest.runAllTimers();
});
expect(screen.queryByText('Direction: backwards')).toBeTruthy();

component.rerender(<TestComponent activeView="v2" />);
act(() => {
jest.runAllTimers();
});
expect(screen.queryByText('Direction: forwards')).toBeTruthy();

component.rerender(<TestComponent activeView="v3" />);
act(() => {
jest.runAllTimers();
});
expect(screen.queryByText('Direction: forwards')).toBeTruthy();

// transition between panels
component.rerender(<TestComponent activeView="v2" activePanel="v2.1" />);
act(() => {
jest.runAllTimers();
});
expect(screen.queryByText('Direction: backwards')).toBeTruthy();

component.rerender(<TestComponent activeView="v2" activePanel="v2.2" />);
expect(screen.queryByText('Direction: forwards')).toBeTruthy();

component.rerender(<TestComponent activeView="v2" activePanel="v2.1" />);
expect(screen.queryByText('Direction: backwards')).toBeTruthy();

component.rerender(<TestComponent activeView="v2" activePanel="v2.3" />);
expect(screen.queryByText('Direction: forwards')).toBeTruthy();

component.rerender(<TestComponent activeView="v2" activePanel="v2.2" />);
expect(screen.queryByText('Direction: backwards')).toBeTruthy();

// transition to another view
component.rerender(<TestComponent activeView="v3" />);
expect(screen.queryByText('Direction: forwards')).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';

type DirectionContextType = boolean | undefined;

const TransitionDirectionContext = React.createContext<DirectionContextType>(undefined);

export const NavTransitionDirectionProvider = ({
children,
isBack: isBackProp,
}: React.PropsWithChildren<{ isBack: DirectionContextType }>) => {
const parentIsBack = React.useContext(TransitionDirectionContext);
// if local isBack is undefined then transition happend on the parent side (probably Root)
const isBack = isBackProp !== undefined ? isBackProp : parentIsBack;

// 'direction' should always represent the direction state of the panel on mount
// save the on mount value of the panel to the state
// to make sure we don't trigger new re-render for the panel
// due to change in the prop passed to provider
const [isBackOnMount] = React.useState<DirectionContextType>(isBack);
SevereCloud marked this conversation as resolved.
Show resolved Hide resolved

return (
<TransitionDirectionContext.Provider value={isBackOnMount}>
{children}
</TransitionDirectionContext.Provider>
);
};

export type TransitionDirection = undefined | 'forwards' | 'backwards';

export const useNavDirection = (): TransitionDirection => {
const isBack = React.useContext(TransitionDirectionContext);
const transitionDirection = isBack === undefined ? undefined : isBack ? 'backwards' : 'forwards';

return transitionDirection;
};
3 changes: 3 additions & 0 deletions packages/vkui/src/components/Root/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
При смене значения свойства `activeView` происходит плавный переход от одной `View` к другой.
Как только он заканчивается, вызывается свойство-функция `onTransition`.

Чтобы понять был это переход вперёд или назад можно воспользоваться хуком [`useNavDirection()`](#/View?id=usenavdirection_example).
Этот хук работает даже если анимации выключены (`<ConfigProvider transitionMotionEnabled={false}>`).

```jsx
const [activeView, setActiveView] = useState('view1');

Expand Down
23 changes: 13 additions & 10 deletions packages/vkui/src/components/Root/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { warnOnce } from '../../lib/warnOnce';
import { ScrollContext } from '../AppRoot/ScrollContext';
import { useConfigProvider } from '../ConfigProvider/ConfigProviderContext';
import { NavTransitionProvider } from '../NavTransitionContext/NavTransitionContext';
import { NavTransitionDirectionProvider } from '../NavTransitionDirectionContext/NavTransitionDirectionContext';
import { SplitColContext } from '../SplitCol/SplitColContext';
import styles from './Root.module.css';

Expand Down Expand Up @@ -145,16 +146,18 @@ export const Root = ({
transition && viewId === activeView && !isBack && styles['Root__view--show-forward'],
)}
>
<NavTransitionProvider entering={transition && viewId === activeView}>
<div
className={styles['Root__scrollCompensation']}
style={{
marginTop: compensateScroll ? viewId && -(scrolls[viewId] ?? 0) : undefined,
}}
>
{view}
</div>
</NavTransitionProvider>
<NavTransitionDirectionProvider isBack={isBack}>
<NavTransitionProvider entering={transition && viewId === activeView}>
<div
className={styles['Root__scrollCompensation']}
style={{
marginTop: compensateScroll ? viewId && -(scrolls[viewId] ?? 0) : undefined,
}}
>
{view}
</div>
</NavTransitionProvider>
</NavTransitionDirectionProvider>
</div>
);
})}
Expand Down
Loading
Loading