Skip to content

Commit

Permalink
Add state flow charts to Gesture Handler's documentation. (#2817)
Browse files Browse the repository at this point in the history
## Description

Add a flow chart to visualise state changes of gesture components.

## Test plan

Run docs locally, go to the `Gesture states & events` article.

### compatible with both Light and Dark modes


https://github.com/software-mansion/react-native-gesture-handler/assets/74246391/6c4794ac-3f02-4990-9d42-e8602c6cb10a

### adaptive layout


https://github.com/software-mansion/react-native-gesture-handler/assets/74246391/3294d39a-0357-41c4-867c-39b9c3d89781

---------

Co-authored-by: Kacper Kapuściak <[email protected]>
Co-authored-by: Patrycja Kalińska <[email protected]>
  • Loading branch information
3 people authored Apr 5, 2024
1 parent d2a464a commit 7340a7f
Show file tree
Hide file tree
Showing 28 changed files with 1,338 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,15 @@ A gesture can be in one of the six possible states:

The most typical flow of state is when a gesture picks up on an initial touch event, then recognizes it, then acknowledges its ending and resets itself back to the initial state.

The flow looks as follows (longer arrows represent that there are possibly more touch events received before the state changes):
The flow looks as follows:

[`UNDETERMINED`](#undetermined) -> [`BEGAN`](#began) ------> [`ACTIVE`](#active) ------> [`END`](#end) -> [`UNDETERMINED`](#undetermined)
import GestureStateFlowExample from '@site/src/examples/GestureStateFlowExample';

Another possible flow is when a handler receives touches that cause a recognition failure:

[`UNDETERMINED`](#undetermined) -> [`BEGAN`](#began) ------> [`FAILED`](#failed) -> [`UNDETERMINED`](#undetermined)

At last, when a handler does properly recognize the gesture but then is interrupted by the touch system the gesture recognition is canceled and the flow looks as follows:

[`UNDETERMINED`](#undetermined) -> [`BEGAN`](#began) ------> [`ACTIVE`](#active) ------> [`CANCELLED`](#cancelled) -> [`UNDETERMINED`](#undetermined)
<InteractiveExample
component={<GestureStateFlowExample />}
label="Drag or long-press the circle"
larger={true}
/>

## Events

Expand Down
6 changes: 3 additions & 3 deletions docs/docs/gestures/state-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ sidebar_position: 15

### `begin()`

Transition the gesture to the [`BEGAN`](/docs/fundamentals/states-events.md#began) state. This method will have no effect if the gesture has already activated or finished.
Transition the gesture to the [`BEGAN`](/docs/fundamentals/states-events#began) state. This method will have no effect if the gesture has already activated or finished.

### `activate()`

Expand All @@ -20,8 +20,8 @@ If the gesture is [`exclusive`](/docs/fundamentals/gesture-composition) with ano

### `end()`

Transition the gesture to the [`END`](/docs/fundamentals/states-events.md#end) state. This method will have no effect if the handler has already finished.
Transition the gesture to the [`END`](/docs/fundamentals/states-events#end) state. This method will have no effect if the handler has already finished.

### `fail()`

Transition the gesture to the [`FAILED`](/docs/fundamentals/states-events.md#failed) state. This method will have no effect if the handler has already finished.
Transition the gesture to the [`FAILED`](/docs/fundamentals/states-events#failed) state. This method will have no effect if the handler has already finished.
40 changes: 40 additions & 0 deletions docs/src/components/AnimableIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useEffect, useState } from 'react';
import styles from './styles.module.css';
import clsx from 'clsx';
import { useColorMode } from '@docusaurus/theme-common';

export const Animation = {
FADE_IN_OUT: styles.iconClicked,
};

interface Props {
icon: JSX.Element;
iconDark?: JSX.Element;
animation: string;
onClick: (actionPerformed, setActionPerformed) => void;
}

const AnimableIcon = ({
icon,
iconDark,
animation = Animation.FADE_IN_OUT,
onClick,
}: Props): JSX.Element => {
const { colorMode } = useColorMode();
const [actionPerformed, setActionPerformed] = useState(false);

useEffect(() => {
const timeout = setTimeout(() => setActionPerformed(false), 1000);
return () => clearTimeout(timeout);
}, [actionPerformed]);

return (
<div
onClick={() => onClick(actionPerformed, setActionPerformed)}
className={clsx(styles.actionIcon, actionPerformed && animation)}>
{colorMode === 'light' ? icon : iconDark || icon}
</div>
);
};

export default AnimableIcon;
27 changes: 27 additions & 0 deletions docs/src/components/AnimableIcon/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.actionIcon {
display: flex;
align-items: center;
justify-content: center;

padding: 0.25em;
cursor: pointer;

border: 1px solid transparent;
border-radius: 3px;
}

.iconClicked {
animation: 1s iconClick;
}

@keyframes iconClick {
0% {
border: 1px solid var(--swm-interactive-copy-button-off);
}
50% {
border: 1px solid var(--swm-interactive-copy-button-on);
}
100% {
border: 1px solid var(--swm-interactive-copy-button-off);
}
}
33 changes: 33 additions & 0 deletions docs/src/components/CollapseButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import styles from './styles.module.css';
import Arrow from '@site/static/img/Arrow.svg';
import ArrowDark from '@site/static/img/Arrow-dark.svg';
import { useColorMode } from '@docusaurus/theme-common';
import clsx from 'clsx';

const CollapseButton: React.FC<{
label: string;
labelCollapsed: string;
collapsed: boolean;
onCollapse: () => void;
className?: string;
}> = ({ label, labelCollapsed, collapsed, onCollapse, className }) => {
const { colorMode } = useColorMode();

return (
<div
className={clsx(styles.collapseButton, className)}
data-collapsed={collapsed}
onClick={() => onCollapse()}>
{colorMode === 'light' ? (
<Arrow className={styles.arrow} />
) : (
<ArrowDark className={styles.arrow} />
)}

<button>{collapsed ? labelCollapsed : label}</button>
</div>
);
};

export default CollapseButton;
29 changes: 29 additions & 0 deletions docs/src/components/CollapseButton/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.collapseButton {
display: flex;
align-items: center;
cursor: pointer;
}

.collapseButton button {
background-color: transparent;
border: none;
padding: 0;

font-family: var(--swm-body-font);
font-size: 16px;
color: var(--ifm-font-color-base);
cursor: pointer;
}

.arrow {
height: 12px;
width: 12px;
margin-right: 1rem;
margin-top: 2px;

transition: var(--swm-expandable-transition);
}

.collapseButton[data-collapsed='false'] .arrow {
transform: rotate(180deg);
}
36 changes: 36 additions & 0 deletions docs/src/components/CollapsibleCode/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { useState } from 'react';
import CodeBlock from '@theme/CodeBlock';
import styles from './styles.module.css';

import CollapseButton from '@site/src/components/CollapseButton';

interface Props {
src: string;
lineBounds: number[];
}

export default function CollapsibleCode({ src, lineBounds }: Props) {
const [collapsed, setCollapsed] = useState(true);

if (!lineBounds) {
return <CodeBlock language="jsx">{src}</CodeBlock>;
}

const [start, end] = lineBounds;

const codeLines = src.split('\n');
const linesToShow = codeLines.slice(start, end + 1).join('\n');

return (
<div className={styles.container}>
<CollapseButton
label="Collapse the full code"
labelCollapsed="Expand the full code"
collapsed={collapsed}
onCollapse={() => setCollapsed(!collapsed)}
className={styles.collapseButton}
/>
<CodeBlock language="jsx">{collapsed ? linesToShow : src}</CodeBlock>
</div>
);
}
15 changes: 15 additions & 0 deletions docs/src/components/CollapsibleCode/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.container {
background-color: var(--swm-off-background);
border-radius: 0;
border: 1px solid var(--swm-border);
margin-bottom: 1em;
}

.container pre,
.container code {
border: none;
}

.collapseButton {
padding: 1em 0 0 1em;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import styles from './styles.module.css';
import InteractiveExampleComponent from '@site/src/components/InteractiveExampleComponent';
import LandingExampleComponent from '@site/src/components/LandingExampleComponent';

interface Props {
title: string;
Expand All @@ -17,7 +17,7 @@ const GestureExampleItem = ({ title, component, idx, href }: Props) => {
{title}
</a>
<div className={styles.interactiveExampleWrapper}>
<InteractiveExampleComponent idx={idx} component={component} />
<LandingExampleComponent idx={idx} component={component} />
</div>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

import BrowserOnly from '@docusaurus/BrowserOnly';
import styles from './styles.module.css';

interface Props {
component: React.ReactNode;
label?: string;
idx?: number;
}

export default function InteractiveExampleComponent({
component,
label,
idx,
}: Props) {
return (
<BrowserOnly fallback={<div>Loading...</div>}>
{() => (
<div className={styles.container}>
<React.Fragment key={idx}>{component}</React.Fragment>
{label && <div className={styles.label}>{label}</div>}
</div>
)}
</BrowserOnly>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.container {
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
contain: content;
align-items: center;
background-color: none;
margin-bottom: var(--ifm-leading);
}

.label {
text-align: center;
font-size: 1rem;
}
Loading

0 comments on commit 7340a7f

Please sign in to comment.