Skip to content

Commit

Permalink
Merge pull request #639 from szhsin/feat/switch-transition
Browse files Browse the repository at this point in the history
feat: switch transition
  • Loading branch information
szhsin authored Aug 21, 2024
2 parents cf999f2 + 3b6d2b7 commit 21b2707
Show file tree
Hide file tree
Showing 12 changed files with 209 additions and 36 deletions.
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# React-Transition-State

> Zero dependency React transition state machine
**[Live Demo](https://szhsin.github.io/react-transition-state/)**

[![NPM](https://img.shields.io/npm/v/react-transition-state.svg)](https://www.npmjs.com/package/react-transition-state) [![NPM](https://img.shields.io/npm/dm/react-transition-state)](https://www.npmjs.com/package/react-transition-state) [![NPM](https://img.shields.io/bundlephobia/minzip/react-transition-state)](https://bundlephobia.com/package/react-transition-state) [![Known Vulnerabilities](https://snyk.io/test/github/szhsin/react-transition-state/badge.svg)](https://snyk.io/test/github/szhsin/react-transition-state)

## Features
Expand Down Expand Up @@ -126,6 +130,17 @@ export default StyledExample;

<br/>

### Switch transition

You can create switch transition effects using one of the provided hooks,

- `useTransition` if the number of elements participating in the switch transition is static.
- `useTransitionMap` if the number of elements participating in the switch transition is dynamic and only known at runtime.

**[Edit on CodeSandbox](https://codesandbox.io/p/sandbox/react-switch-transition-x87jt8)**

<br/>

### Perform appearing transition when page loads or a component mounts

You can toggle on transition with the `useEffect` hook.
Expand Down Expand Up @@ -208,9 +223,9 @@ The `useTransition` Hook returns a tuple of values in the following order:

3. endTransition: `() => void`

- Call this function to stop transition which will turn state into 'entered' or 'exited'.
- You will normally call this function in the `onAnimationEnd` or `onTransitionEnd` event.
- You need to either call this function explicitly in your code or set a timeout value in Hook options.
- Call this function to stop a transition which will turn the state into 'entered' or 'exited'.
- You don't need to call this function explicitly if a timeout value is provided in the hook options.
- You can call this function explicitly in the `onAnimationEnd` or `onTransitionEnd` event.

<br/>

Expand Down
2 changes: 1 addition & 1 deletion example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions example/src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import BasicExample from './components/BasicExample';
import StyledExample from './components/StyledExample';
import { BasicExample } from './components/BasicExample';
import { StyledExample } from './components/StyledExample';
import { SwitchExample } from './components/SwitchExample';
import './App.css';

function App() {
Expand All @@ -10,6 +11,7 @@ function App() {
</a>
<BasicExample />
<StyledExample />
<SwitchExample />
</div>
);
}
Expand Down
21 changes: 10 additions & 11 deletions example/src/components/BasicExample.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useState } from 'react';
import useTransition from 'react-transition-state';
import { CodeSandbox } from './CodeSandbox';

function BasicExample() {
const BasicExample = () => {
const [unmountOnExit, setUnmountOnExit] = useState(true);
const [{ status: state, isMounted }, toggle] = useTransition({
const [{ status, isMounted }, toggle] = useTransition({
timeout: 500,
initialEntered: true,
preEnter: true,
Expand All @@ -12,9 +13,9 @@ function BasicExample() {

return (
<div className="basic-example">
<h1>Basic example</h1>
<h1>CSS example</h1>
<div className="basic-console">
<div className="basic-state">state: {state}</div>
<div className="basic-status">status: {status}</div>
<label>
Unmount after hiding
<input
Expand All @@ -24,19 +25,17 @@ function BasicExample() {
/>
</label>
<button className="btn" onClick={() => toggle()}>
{state === 'entering' || state === 'entered' ? 'Hide' : 'Show'}
{status === 'entering' || status === 'entered' ? 'Hide' : 'Show'}
</button>
<em className="tips">
Tip: open the browser dev tools to verify that the following message is being moved in and
out of DOM.
</em>
</div>
{isMounted && <div className={`basic-transition ${state}`}>React transition state</div>}
<a className="code-sandbox" href="https://codesandbox.io/s/react-transition-basic-100io">
Edit on CodeSandbox
</a>
{isMounted && <div className={`basic-transition ${status}`}>React transition state</div>}
<CodeSandbox href="https://codesandbox.io/s/react-transition-basic-100io" />
</div>
);
}
};

export default BasicExample;
export { BasicExample };
7 changes: 7 additions & 0 deletions example/src/components/CodeSandbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const CodeSandbox = ({ href }) => (
<div className="code-sandbox">
<a href={href}>Edit on CodeSandbox</a>
</div>
);

export { CodeSandbox };
23 changes: 11 additions & 12 deletions example/src/components/StyledExample.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import styled from 'styled-components';
import { useTransition } from 'react-transition-state';
import { CodeSandbox } from './CodeSandbox';

const Container = styled.div`
margin: 1rem;
margin-top: 200px;
margin-top: 150px;
`;

const Box = styled.div`
Expand All @@ -15,16 +16,16 @@ const Box = styled.div`
margin: 0 auto;
border-radius: 0.5rem;
${({ $state }) =>
($state === 'preEnter' || $state === 'exiting') &&
${({ $status }) =>
($status === 'preEnter' || $status === 'exiting') &&
`
opacity: 0;
transform: scale(0.9);
`}
`;

function StyledExample() {
const [{ status: state, isMounted }, toggle] = useTransition({
const StyledExample = () => {
const [{ status, isMounted }, toggle] = useTransition({
timeout: 500,
mountOnEnter: true,
unmountOnExit: true,
Expand All @@ -40,19 +41,17 @@ function StyledExample() {
</button>
)}
{isMounted && (
<Box $state={state}>
<h2>state: {state}</h2>
<Box $status={status}>
<h2>status: {status}</h2>
<p>This message is being transitioned in and out of the DOM.</p>
<button className="btn" onClick={() => toggle(false)}>
Close
</button>
</Box>
)}
<a className="code-sandbox" href="https://codesandbox.io/s/react-transition-styled-3id7q">
Edit on CodeSandbox
</a>
<CodeSandbox href="https://codesandbox.io/s/react-transition-styled-3id7q" />
</Container>
);
}
};

export default StyledExample;
export { StyledExample };
16 changes: 16 additions & 0 deletions example/src/components/SwitchExample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { SwitchTransition } from './SwitchTransition';
import { SwitchTransitionMap } from './SwitchTransitionMap';
import { CodeSandbox } from './CodeSandbox';

const SwitchExample = () => (
<div className="switch-example">
<h1>Switch Transition example</h1>
<h3>Two elements switching</h3>
<SwitchTransition />
<h3>Any number of elements switching</h3>
<SwitchTransitionMap />
<CodeSandbox href="https://codesandbox.io/p/sandbox/react-switch-transition-x87jt8" />
</div>
);

export { SwitchExample };
43 changes: 43 additions & 0 deletions example/src/components/SwitchTransition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useTransition } from 'react-transition-state';

// Ideal for creating switch transition for a small number of elements
// Use `useTransition` hook once for each element in the switch transition
export const SwitchTransition = () => {
const transitionProps = {
timeout: 300,
mountOnEnter: true,
unmountOnExit: true,
preEnter: true
};

const [state1, toggle1] = useTransition({
...transitionProps,
initialEntered: true
});
const [state2, toggle2] = useTransition(transitionProps);
const toggle = () => {
toggle1();
toggle2();
};

return (
<div className="switch-container">
<SwitchButton state={state1} onClick={toggle}>
Button 1
</SwitchButton>
<SwitchButton state={state2} onClick={toggle}>
Button 2
</SwitchButton>
</div>
);
};

const SwitchButton = ({ state: { status, isMounted }, onClick, children }) => {
if (!isMounted) return null;

return (
<button className={`btn switch ${status}`} onClick={onClick}>
{children}
</button>
);
};
59 changes: 59 additions & 0 deletions example/src/components/SwitchTransitionMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useEffect } from 'react';
import { useTransitionMap } from 'react-transition-state';

// Creating switch transitions for a large number of elements,
// or the number of elements is only known at runtime.
// `useTransition` doesn't suffice as React hook has the limitation that it cannot be called in a loop
const NUMBER_OF_BUTTONS = 5;
const buttonArray = new Array(NUMBER_OF_BUTTONS).fill(0).map((_, i) => `Button ${i + 1}`);

export const SwitchTransitionMap = () => {
const transition = useTransitionMap({
timeout: 300,
mountOnEnter: true,
unmountOnExit: true,
preEnter: true
});

return (
<div className="switch-container">
{buttonArray.map((button, index) => (
<SwitchButton
key={index}
itemKey={index}
nextItemKey={(index + 1) % buttonArray.length}
initialEntered={index === 0}
{...transition}
>
{button}
</SwitchButton>
))}
</div>
);
};

const SwitchButton = ({
itemKey,
nextItemKey,
initialEntered,
children,
stateMap,
toggle,
setItem,
deleteItem
}) => {
useEffect(() => {
setItem(itemKey, { initialEntered });
return () => void deleteItem(itemKey);
}, [setItem, deleteItem, itemKey, initialEntered]);

const { status, isMounted } = stateMap.get(itemKey) || {};

if (!isMounted) return null;

return (
<button className={`btn switch ${status}`} onClick={() => toggle(nextItemKey)}>
{children}
</button>
);
};
41 changes: 37 additions & 4 deletions example/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ body {
-moz-osx-font-smoothing: grayscale;
background-color: #18191a;
color: #cad1d8;
padding-bottom: 500px;
padding-bottom: 300px;
}

code {
Expand All @@ -25,6 +25,10 @@ code {
background-color: #333;
}

h1 {
color: goldenrod;
}

a {
text-decoration: none;
color: #69a6f8;
Expand All @@ -39,7 +43,7 @@ a:hover {
display: block;
font-weight: 800;
padding: 1rem;
margin-top: 2rem;
margin-top: 1rem;
}

.btn {
Expand Down Expand Up @@ -76,14 +80,15 @@ a:hover {
display: flex;
flex-direction: column;
align-items: center;
margin: 3rem 0;
margin: 3rem 0 2rem;
}

.basic-console em {
max-width: 26rem;
margin: 0;
}

.basic-state {
.basic-status {
font-size: 1.25rem;
font-weight: 600;
}
Expand All @@ -96,6 +101,7 @@ a:hover {
font-size: 3rem;
font-weight: 800;
transition: all 0.5s;
color: #bbb;
}

.basic-transition.preEnter,
Expand All @@ -107,3 +113,30 @@ a:hover {
.basic-transition.exited {
display: none;
}

.switch-example {
margin-top: 150px;
}

.switch-container {
display: flex;
justify-content: center;
min-height: 40px;
}

.switch {
transition:
opacity 0.3s,
transform 0.3s;
position: absolute;
}

.switch.preEnter {
opacity: 0;
transform: translateX(-200%);
}

.switch.exiting {
opacity: 0;
transform: translateX(200%);
}
Loading

0 comments on commit 21b2707

Please sign in to comment.