Last active
January 18, 2020 14:26
-
-
Save khusamov/26b87429c7718cb0399df07ea36d4bb8 to your computer and use it in GitHub Desktop.
Плавающий элемент (React)
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 {ReactNode, ReactPortal} from 'react'; | |
import {createPortal} from 'react-dom'; | |
/** | |
* Создать портал в `document.body`. | |
* @param children | |
* @param key | |
*/ | |
export default function createBodyPortal(children: ReactNode, key?: null | string): ReactPortal { | |
return createPortal(children, document.body, key); | |
} |
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, {forwardRef} from 'react'; | |
import {styled} from '@material-ui/styles'; | |
import {TopProperty, LeftProperty} from 'csstype'; | |
type TLength = string | 0; | |
type TDivProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>; | |
interface IFloatingDivProps { | |
x: number; | |
y: number; | |
} | |
const createFloatingDiv = styled( | |
forwardRef<HTMLDivElement, IFloatingDivProps & TDivProps>(({x, y, ...other}, ref) => ( | |
<div | |
ref={ref} | |
// tabindex="-1" позволяет фокусироваться на элементе только программно. | |
// Клавиша Tab проигнорирует такой элемент, но метод elem.focus() будет действовать. | |
// https://learn.javascript.ru/focus-blur#vklyuchaem-fokusirovku-na-lyubom-elemente-tabindex | |
tabIndex={-1} | |
{...other} | |
/> | |
)) | |
); | |
export const FloatingDiv = ( | |
createFloatingDiv<{}, IFloatingDivProps>(({x, y}) => ({ | |
position: 'absolute', | |
left: x as TopProperty<TLength>, | |
top: y as LeftProperty<TLength>, | |
outline: 'none' | |
}), {name: 'FloatingDiv'}) | |
); |
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, {createRef, useEffect, useState, forwardRef, useImperativeHandle} from 'react'; | |
import {PropsWithChildren, MouseEventHandler, MouseEvent, FocusEvent} from 'react'; | |
import createBodyPortal from '../../functions/createBodyPortal'; | |
import {FloatingDiv} from './Floating.style'; | |
interface IFloatingProps { | |
} | |
export interface IFloatingImperativeHandle { | |
/** | |
* Изменить координаты плавающего элемента на основе события | |
* мышки (берутся свойства event.pageX и event.pageY). | |
*/ | |
setPositionByMouseEvent: MouseEventHandler; | |
/** | |
* Изменить координаты плавающего элемента. | |
* После изменения координат элемент становится видимым. | |
*/ | |
setPosition: (x: number, y: number) => void; | |
} | |
/** | |
* Плавающий элемент. | |
*/ | |
const Floating = ( | |
forwardRef<IFloatingImperativeHandle, PropsWithChildren<IFloatingProps>>( | |
(props, ref) => { | |
const {children} = props; | |
const [visibled, setVisibled] = useState(false); | |
const [x, setX] = useState(0); | |
const [y, setY] = useState(0); | |
const floatingDivRef = createRef<HTMLDivElement>(); | |
const onFloatingDivBlur = (event: FocusEvent<HTMLDivElement>) => { | |
const floatingDiv = floatingDivRef.current; | |
if (!(floatingDiv && event.relatedTarget instanceof Element && floatingDiv.contains(event.relatedTarget))) { | |
setVisibled(false); | |
} | |
}; | |
useEffect(() => { | |
if (floatingDivRef.current) { | |
floatingDivRef.current.focus(); | |
} | |
}); | |
useImperativeHandle(ref, () => ({ | |
setPositionByMouseEvent(event: MouseEvent) { | |
event.preventDefault(); | |
this.setPosition(event.pageX, event.pageY); | |
}, | |
setPosition(x: number, y: number) { | |
setX(x); | |
setY(y); | |
setVisibled(true); | |
} | |
})); | |
return ( | |
createBodyPortal( | |
visibled && <FloatingDiv | |
ref={floatingDivRef} | |
onBlur={onFloatingDivBlur} | |
{...{x, y, children}} | |
/> | |
) | |
); | |
} | |
) | |
); | |
export default Floating; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment