Last active
September 23, 2022 17:08
-
-
Save caleb/6e20d9c1ec218d05d917795822d9540c to your computer and use it in GitHub Desktop.
This file contains 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 { FloatingTransition, useFloatingTransition} from "./floating-transition" | |
export default function App() { | |
let [isShowing, setIsShowing] = React.useState(true) | |
let tranState = useFloatingTransition(isShowing, {placement: "bottom-start"}) | |
const {floating, reference} = tranState | |
const ref = React.useRef(null) | |
return ( | |
<div ref={ref} className="flex flex-col items-center py-16"> | |
<div className="h-32 w-32"> | |
{tranState.visible ? | |
<FloatingTransition | |
state={tranState} | |
enter="transform transition duration-[400ms]" | |
enterFrom="opacity-0 rotate-[-120deg] scale-50" | |
enterTo="opacity-100 rotate-0 scale-100" | |
leave="transform duration-200 transition ease-in-out" | |
leaveFrom="opacity-100 rotate-0 scale-100 " | |
leaveTo="opacity-0 scale-95 " | |
> | |
<div ref={floating} | |
className="absolute h-16 w-16 rounded-md bg-white shadow-lg" /> | |
</FloatingTransition> : null} | |
</div> | |
<button | |
ref={reference} | |
onClick={(e) => { e.preventDefault(); setIsShowing((s) => !s) }} | |
className="backface-visibility-hidden mt-8 flex items-center rounded-full bg-black bg-opacity-20 px-3 py-2 text-sm font-medium text-white" | |
> | |
<svg viewBox="0 0 20 20" fill="none" className="h-5 w-5 opacity-70"> | |
<path | |
d="M14.9497 14.9498C12.2161 17.6835 7.78392 17.6835 5.05025 14.9498C2.31658 12.2162 2.31658 7.784 5.05025 5.05033C7.78392 2.31666 12.2161 2.31666 14.9497 5.05033C15.5333 5.63385 15.9922 6.29475 16.3266 7M16.9497 2L17 7H16.3266M12 7L16.3266 7" | |
stroke="currentColor" | |
strokeWidth="1.5" | |
/> | |
</svg> | |
<span className="ml-3">Click to transition</span> | |
</button> | |
</div> | |
) | |
} |
This file contains 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 { Transition } from "@headlessui/react" | |
import React from "react" | |
import { computePosition, autoUpdate } from '@floating-ui/dom'; | |
import { deepEqual } from './utils/deepEqual'; | |
function defaultPositionFloating(reference, floating, { x, y, strategy }) { | |
if (floating) { | |
floating.style.top = `${y ?? 0}px` | |
floating.style.left = `${x ?? 0}px` | |
floating.style.position = strategy | |
} | |
} | |
export function useFloating({ | |
middleware = [], | |
placement = 'bottom', | |
strategy = 'absolute', | |
positionFloating = defaultPositionFloating, | |
} = {}) { | |
const [latestMiddleware, setLatestMiddleware] = React.useState(middleware); | |
if (!deepEqual( | |
latestMiddleware?.map(({ name, options }) => ({ name, options })), | |
middleware?.map(({ name, options }) => ({ name, options })))) { | |
setLatestMiddleware(middleware); | |
} | |
const reference = React.useRef(null); | |
const floating = React.useRef(null); | |
const update = React.useCallback(() => { | |
if (!reference.current || !floating.current) { | |
return; | |
} | |
computePosition(reference.current, floating.current, { | |
middleware: latestMiddleware, | |
placement, | |
strategy, | |
}).then((data) => { | |
positionFloating(reference.current, floating.current, data) | |
}); | |
}, [latestMiddleware, placement, strategy, positionFloating]); | |
return React.useMemo( | |
() => ({ | |
update, | |
reference, | |
floating, | |
}), | |
[update, floating, reference] | |
); | |
} | |
export function useFloatingTransition(show, useFloatOptions = {}) { | |
const [showing, updateShowing] = React.useState(false) | |
const [isTransitioning, updateIsTransitioning] = React.useState(show) | |
const [transitioningTo, updateTransitioningTo] = React.useState(show) | |
const afterLeave = React.useCallback(function () { | |
updateIsTransitioning(false) | |
}, []) | |
const { reference, floating, update } = useFloating(useFloatOptions) | |
React.useLayoutEffect(function () { | |
updateIsTransitioning(true) | |
updateTransitioningTo(show) | |
}, [show]); | |
React.useLayoutEffect(function () { | |
update() | |
let cleanup | |
if (reference.current && floating.current && transitioningTo) { | |
cleanup = autoUpdate(reference.current, floating.current, update) | |
} else { | |
cleanup = function() {} | |
} | |
updateShowing(transitioningTo) | |
return function() { | |
cleanup() | |
} | |
}, [floating, reference, transitioningTo, update]); | |
return { | |
visible: showing || isTransitioning, | |
show: showing, | |
updateShowing, | |
afterLeave, | |
reference, | |
floating | |
} | |
} | |
export function FloatingTransition({ appear, | |
state, | |
children, | |
transitioning, | |
...props }) { | |
if (state.visible) { | |
return ( | |
<Transition show={state.show} | |
unmount={false} | |
as={React.Fragment} | |
afterLeave={state.afterLeave} | |
{...props} | |
> | |
{children} | |
</Transition>); | |
} else { | |
return null; | |
} | |
} |
This file contains 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
// Fork of `fast-deep-equal` that only does the comparisons we need and compares | |
// functions | |
export function deepEqual(a: any, b: any) { | |
if (a === b) { | |
return true; | |
} | |
if (typeof a !== typeof b) { | |
return false; | |
} | |
if (typeof a === 'function' && a.toString() === b.toString()) { | |
return true; | |
} | |
let length, i, keys; | |
if (a && b && typeof a == 'object') { | |
if (Array.isArray(a)) { | |
length = a.length; | |
if (length != b.length) return false; | |
for (i = length; i-- !== 0; ) { | |
if (!deepEqual(a[i], b[i])) { | |
return false; | |
} | |
} | |
return true; | |
} | |
keys = Object.keys(a); | |
length = keys.length; | |
if (length !== Object.keys(b).length) { | |
return false; | |
} | |
for (i = length; i-- !== 0; ) { | |
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) { | |
return false; | |
} | |
} | |
for (i = length; i-- !== 0; ) { | |
const key = keys[i]; | |
if (key === '_owner' && a.$$typeof) { | |
continue; | |
} | |
if (!deepEqual(a[key], b[key])) { | |
return false; | |
} | |
} | |
return true; | |
} | |
return a !== a && b !== b; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment