Skip to content

Instantly share code, notes, and snippets.

@caleb
Last active September 23, 2022 17:08
Show Gist options
  • Save caleb/6e20d9c1ec218d05d917795822d9540c to your computer and use it in GitHub Desktop.
Save caleb/6e20d9c1ec218d05d917795822d9540c to your computer and use it in GitHub Desktop.
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>
)
}
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;
}
}
// 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