Created
November 24, 2021 09:05
-
-
Save jniac/4112b44d521c55535fe2293c810ff9c9 to your computer and use it in GitHub Desktop.
React TSX Gist
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import * as Animation from 'utils/Animation' | |
import * as MathUtils from 'math/utils' | |
import { Observable } from 'utils/Observable' | |
import { useComplexEffects } from 'utils/react' | |
import './Switch.css' | |
export const SimpleSwitch: React.FC<{ | |
index?: number | |
paths?: React.ElementType[] | |
duration?: number | |
}> = ({ | |
index = 0, | |
paths = [], | |
duration = 0.8, | |
}) => { | |
const ref1 = React.useRef<HTMLDivElement>(null) | |
const ref2 = React.useRef<HTMLDivElement>(null) | |
const indexObs = React.useMemo(() => new Observable(-1), []) | |
const inverseObs = React.useMemo(() => new Observable(false), []) | |
indexObs.setValue(index) | |
const hasChanged = indexObs.hasChanged && indexObs.valueOld !== -1 | |
if (hasChanged) { | |
inverseObs.setValue(!inverseObs.value) | |
} | |
const [transition, setTransition] = React.useState(false) | |
useComplexEffects(function* () { | |
yield indexObs.onChange(() => { | |
setTransition(true) | |
const inverse = inverseObs.value | |
const [entering, leaving] = inverse ? [ref1, ref2] : [ref2, ref1] | |
entering.current?.classList.add('entering') | |
leaving.current?.classList.add('leaving') | |
Animation.duringWithTarget(indexObs, { duration, immediate: true }, ({ progress: t }) => { | |
if (leaving.current) { | |
const t1 = MathUtils.inverseLerp(0, 0.6, t) | |
leaving.current.style.opacity = Animation.easing.in4((1 - t1)).toFixed(2) | |
} | |
if (entering.current) { | |
entering.current.style.opacity = MathUtils.inout(t, 3, .3).toFixed(2) | |
} | |
}).onComplete(() => { | |
entering.current?.classList.remove('entering') | |
setTransition(false) | |
}) | |
}) | |
}, []) | |
// NOTE: | |
// When inverse === true, Content2 is entering / displayed, otherwise it's Content1 | |
const inverse = inverseObs.value | |
const render1 = !inverse || transition | |
const render2 = inverse || transition | |
const index1 = !inverse ? indexObs.value : indexObs.valueOld | |
const index2 = inverse ? indexObs.value : indexObs.valueOld | |
const Content1 = paths[index1] | |
const Content2 = paths[index2] | |
return ( | |
<> | |
{(render1 && Content1) && ( | |
<div | |
ref={ref1} | |
className="Switch Path" | |
style={{ opacity: `${!inverse && transition ? 0 : 1}` }} | |
> | |
<Content1 /> | |
</div> | |
)} | |
{(render2 && Content2) && ( | |
<div | |
ref={ref2} | |
className="Switch Path" | |
style={{ opacity: `${inverse && transition ? 0 : 1}` }} | |
> | |
<Content2 /> | |
</div> | |
)} | |
</> | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import * as Animation from 'utils/Animation' | |
import * as MathUtils from 'math/utils' | |
import { Observable } from 'utils/Observable' | |
import { useComplexEffects } from 'utils/react' | |
import './DivSwitch.css' | |
export interface SwitchChildProps { | |
entering?: boolean | |
} | |
export interface SwitchProps<T> { | |
index?: number | |
paths?: React.ElementType[] | |
duration?: number | |
onTransition?: (entering: T|null, leaving: T|null, progress: number) => void | |
} | |
export const GenericSwitch = <T extends unknown>({ | |
index = 0, | |
paths = [], | |
duration = 0.8, | |
onTransition, | |
}: SwitchProps<T>) => { | |
const ref1 = React.useRef<T>(null) | |
const ref2 = React.useRef<T>(null) | |
const indexObs = React.useMemo(() => new Observable(-1), []) | |
const inverseObs = React.useMemo(() => new Observable(false), []) | |
indexObs.setValue(index) | |
const hasChanged = indexObs.hasChanged && indexObs.valueOld !== -1 | |
if (hasChanged) { | |
inverseObs.setValue(!inverseObs.value) | |
} | |
const [transition, setTransition] = React.useState(false) | |
useComplexEffects(function* () { | |
yield indexObs.onChange(() => { | |
setTransition(true) | |
const inverse = inverseObs.value | |
const [entering, leaving] = inverse ? [ref1, ref2] : [ref2, ref1] | |
Animation.duringWithTarget(indexObs, { duration, immediate: true }, ({ progress }) => { | |
onTransition?.(entering.current, leaving.current, progress) | |
}).onComplete(() => { | |
setTransition(false) | |
}) | |
}) | |
}, []) | |
// NOTE: | |
// When inverse === true, Content2 is entering / displayed, otherwise it's Content1 | |
const inverse = inverseObs.value | |
const render1 = !inverse || transition | |
const render2 = inverse || transition | |
const index1 = !inverse ? indexObs.value : indexObs.valueOld | |
const index2 = inverse ? indexObs.value : indexObs.valueOld | |
const Content1 = paths[index1] | |
const Content2 = paths[index2] | |
return ( | |
<> | |
{(render1 && Content1) && ( | |
<Content1 ref={ref1} entering={!inverse && transition}/> | |
)} | |
{(render2 && Content2) && ( | |
<Content2 ref={ref2} entering={inverse && transition}/> | |
)} | |
</> | |
) | |
} | |
export const DivSwitch: React.FC<{ | |
index?: number | |
paths?: React.ElementType[] | |
duration?: number | |
}> = ({ | |
index = 0, | |
paths = [], | |
duration = 0.8, | |
}) => { | |
return ( | |
<GenericSwitch<HTMLDivElement> | |
index={index} | |
duration={duration} | |
paths={paths.map(Item => React.forwardRef<HTMLDivElement, SwitchChildProps>(({ entering }, ref) => ( | |
<div ref={ref} className="DivSwitch Path" style={{ opacity: `${entering ? 0 : 1}` }}> | |
<Item /> | |
</div> | |
)))} | |
onTransition={(entering, leaving, t) => { | |
if (t < 1) { | |
entering?.classList.add('entering') | |
leaving?.classList.add('leaving') | |
} | |
if (t === 1) { | |
entering?.classList.remove('entering') | |
} | |
if (leaving) { | |
const t1 = MathUtils.inverseLerp(0, 0.6, t) | |
leaving.style.opacity = Animation.easing.in4((1 - t1)).toFixed(2) | |
} | |
if (entering) { | |
entering.style.opacity = MathUtils.inout(t, 3, .3).toFixed(2) | |
} | |
}} | |
/> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment