diff --git a/resources/css/components/magicPopup.css b/resources/css/components/magicPopup.css new file mode 100644 index 0000000..26a43fc --- /dev/null +++ b/resources/css/components/magicPopup.css @@ -0,0 +1,104 @@ +@import url('./../variables.css'); +@import '@fortawesome/fontawesome-free/css/all.css'; + +/* Анимация таймера popup, по истечению которого он скроется */ +@keyframes popupTimer { + from{ + width: 100%; + } + to { + width: 0; + } +} + +.magic-popup-container{ + padding: 5px; + margin-bottom: 15px; + border-radius: 5px; + display: flex; + box-shadow: 0px 3px 4px 1px #918787; + background: var(--color_graphite_main); + width: 500px; + transition: 0.3s; + opacity: 0; + transform: translateY(-10px); + + &.hide{ + opacity: 0; + } + + &.show{ + opacity: 1; + transform: translateY(0px); + } + + &.hidePopup{ + transform: translateY(10px); + opacity: 0; + } + + &>.popup__icon-block{ + flex-basis: 10%; + align-self: center; + + &>i{ + font-size: 4rem; + color: var(--color_purple_main); + + &.fa-circle-check{ + color: var(--color_emerald_main); + } + &.fa-circle-xmark{ + color: var(--color_ruby_main); + } + &.fa-circle-exclamation{ + color: orange; + } + } + } + + &>.popup__content-block{ + flex-basis: 90%; + border-left: 2px solid white; + + &>.popup__content__text-block{ + color: white; + padding: 5px; + text-align: center; + font-size: 1.2rem; + } + + &>.popup__button-block{ + margin: 10px 0; + + &>button{ + font-size: 1rem; + margin: auto; + opacity: 0.5; + background: var(--color_purple_main); + transition: 0.3s; + + &:hover{ + opacity: 1; + } + } + } + + .popup__timer-block__timer{ + + &.popup__timer-block__timer { + height: 5px; + margin: 0 5px; + border-radius: 10px; + + &.timerProgress{ + background: #63707a; + animation: popupTimer; + animation-duration: 3s; + animation-fill-mode: forwards; + animation-timing-function: linear; + } + } + } + } +} diff --git a/resources/js/components/MagicPopup.tsx b/resources/js/components/MagicPopup.tsx new file mode 100644 index 0000000..7825335 --- /dev/null +++ b/resources/js/components/MagicPopup.tsx @@ -0,0 +1,108 @@ +import React, { useEffect, useState } from "react"; +import './../../css/components/magicPopup.css'; +import { Button } from '@SharePoint/rencredit_uikit'; + +/** + * Компонент всплывающего окна + */ + +/** + * Варианты типов для всплывающих окон: info - стандартный вид, error - произошла ошибка, attention - обратить внимание, success - успешное событие + */ +type PopupType = 'info' | 'success' | 'error' | 'attention'; + +//Интерфейс для передаваемых пропсов объекта всплывающего окна +interface MagicPopupProps { + key: number, + id: number, //Уникальный идентификатор, по котором всплывающее окно будет в том числе размонтироваться + message: string, //Текст всплывающего окна + renderIndex: number, //Индекс рендеринга компонента. Так как может отрисовываться сразу несколько popup, чтобы корректно их отрисовывать с анимацией с задержкой по времени, нужно передавать индекс компонента в наборе. Передавать "вручную" не нужно, он сам рассчитывается в компоненте MagicPopupContainer + type: PopupType, //Тип всплывающего окна, определяющий его внешний вид + timeOut?: boolean, //Если передается true, для элемента будет запущен стандартный таймер исчезновения. ВАЖНО! для всплывающих окон типа error таймер не будет активироваться, даже если передается. Пользователь должен гарантированно ознакомиться с сообщением и сам его закрыть + onClose?: (id: number) => void //Колбэк для полного размонтирования компонента со страницы +} + +/** + * Компонент всплывающих окон + * @param props + * @returns + */ +export default function MagicPopup (props: MagicPopupProps) +{ + //Объект с классами для иконок font-awesome в зависимости от переданного типа всплывающего окна. При изменении, необходимо поменять названия классов в magicPopup.css! + const iconNameObj = { + info: 'fa-circle-info', + success: 'fa-circle-check', + error: 'fa-circle-xmark', + attention: 'fa-circle-exclamation' + } + //С помощью деструктуризации указываем значение типа окна по умолчанию + const {message, timeOut = true, onClose} = props; + //Задержка исчезновения popup + const popupDelay = 300; + //Итоговая длительность таймера перед закрытием конкретного popup, учитывая его положение в наборе передаваемых попапов + const hideTimeOut: number = 3000 + popupDelay; + //Стейт для размонтирования попапа + const [popupVisible, setPopupVisible] = useState(true) + //Стейт для плавного сокрытия popup. Класс плавного сокрытия присваивается за пол секунды до полного исчезновения через popupVisible, чтобы сначала плавно сокрыть блок из интерфейса, а затем полностью удалить его из DOM + const [popupHideClass, setPopupHideClass] = useState('popup-visible') + //Стейт плавного появления popup. Каждый следующий popup в передаваемом наборе должен появляться с небольшой задержкой + const [popupShowClass, setPopupFadeinClass] = useState(''); + //Стейт запуска анимированного таймера исчезновения popup + const [popupTimer, setPopupTimer] = useState(''); + //Функция сокрытия popup. Сначала присваиваем класс, который анимированно скрывает popup, а через небольшую задержку размонтируем компонент + function hidePopupBlock (): void + { + setPopupHideClass('hidePopup'); + setTimeout ( () => { + setPopupVisible(false); + }, 300) + //Вызываем метод удаления экземпляра компонента из набора. Так как попапы отрисовываются на основе компонента PopupContainer с набором компонентов MagicPopup, то после их размонтирования, их необходимо и удалить из набора в PopupContainer. Без их удаления, при вызове новых попапов, весь набор с уже просмотренными попапами будет снова отрисовываться. + onClose && onClose(props.id) + }; + + //При рендеринге сразу же запускается таймер сокрытия popup, если аргумент timeOut = true и тип окна != error + useEffect( () => { + if (timeOut && props.type !== 'error') { + setTimeout( () => { + //Задержка для отображения таймера нужна, так как при вызове рендеринга нескольких окон, каждое последующее появляется с задержкой. Без задержки ниже, таймер будет запускаться для всех popup одновременно и когда появится последний popup, его таймер уже может закончиться + setPopupTimer('timerProgress') + }, popupDelay) + //Через небольшую задержку после сокрытия компонента срабатывает таймер на размонтирование компонента и удаления его из набора + setTimeout( () => { + hidePopupBlock() + }, hideTimeOut) + } + //Появление popup при рендеринге + setTimeout ( () => { + setPopupFadeinClass('show') + }, popupDelay) + }, []) + + return ( + popupVisible ? + //
Раз
+
+
+ +
+
+
+ {message} +
+
+
+
+
+
+
+
+ : null + ); +};