From 92d727b2e904f0cda7a4512f7c7a53815a8ed2a9 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Thu, 9 May 2024 14:27:54 +0300 Subject: [PATCH] add button widget features --- src/WidgetDev.tsx | 4 +- src/widgets/button/ButtonWidget.tsx | 37 ++++----- src/widgets/button/OpenQuizButton.tsx | 107 +++++++++++++++++++++++--- src/widgets/shared/RunningStripe.tsx | 2 +- 4 files changed, 120 insertions(+), 30 deletions(-) diff --git a/src/WidgetDev.tsx b/src/WidgetDev.tsx index 901713c..98f50df 100644 --- a/src/WidgetDev.tsx +++ b/src/WidgetDev.tsx @@ -1,11 +1,12 @@ import lightTheme from "@/utils/themes/light"; import { Box, ThemeProvider, Typography } from "@mui/material"; import { useEffect, useRef } from "react"; -import { PopupWidget as Widget } from "./widgets"; +import { ButtonWidget as Widget } from "./widgets"; const widgetProps: ConstructorParameters[0] = { quizId: "3c49550d-8c77-4788-bc2d-42586a261514", + selector: "#widget-button", }; export default function WidgetDev() { @@ -28,6 +29,7 @@ export default function WidgetDev() { }} > + diff --git a/src/widgets/button/ButtonWidget.tsx b/src/widgets/button/ButtonWidget.tsx index 5482d18..a432749 100644 --- a/src/widgets/button/ButtonWidget.tsx +++ b/src/widgets/button/ButtonWidget.tsx @@ -1,25 +1,28 @@ +import { ComponentPropsWithoutRef } from "react"; import { Root, createRoot } from "react-dom/client"; +import { pollForSelector } from "../shared/pollForSelector"; import OpenQuizButton from "./OpenQuizButton"; import { createPortal } from "react-dom"; -import { pollForSelector } from "../shared/pollForSelector"; +type Props = ComponentPropsWithoutRef; + export class ButtonWidget { root: Root | undefined; - element = document.createElement("div"); - constructor({ quizId, selector, selectorPollingTimeLimit = 60 }: { - quizId: string; + constructor(props: Props & { selector: string; /** * In seconds, null - polling disabled */ selectorPollingTimeLimit?: number | null; }) { + const { selector, selectorPollingTimeLimit = 60 } = props; + const element = document.querySelector(selector); if (element) { this.root = createRoot(element); - this.root.render(); + this.render(props); return; } @@ -31,13 +34,16 @@ export class ButtonWidget { pollForSelector(selector, selectorPollingTimeLimit, (element) => { this.root = createRoot(element); - this.root.render(); + this.render(props); }); } + render(props: Props) { + this.root?.render(); + } + destroy() { if (this.root) this.root.unmount(); - this.element.remove(); } } @@ -45,22 +51,17 @@ export class ButtonWidgetFixed { root: Root | undefined; element = document.createElement("div"); - constructor({ quizId, side }: { - quizId: string; - side: "left" | "right"; - }) { + constructor(props: Props) { this.element.style.setProperty("display", "none"); document.body.appendChild(this.element); this.root = createRoot(this.element); - this.root.render(createPortal( - , - document.body - )); + this.render(props); + } + + render(props: Props) { + this.root?.render(createPortal(, document.body)); } destroy() { diff --git a/src/widgets/button/OpenQuizButton.tsx b/src/widgets/button/OpenQuizButton.tsx index 08b35a8..72cc093 100644 --- a/src/widgets/button/OpenQuizButton.tsx +++ b/src/widgets/button/OpenQuizButton.tsx @@ -1,26 +1,108 @@ import lightTheme from "@/utils/themes/light"; -import { Button, ThemeProvider } from "@mui/material"; -import { useState } from "react"; +import { Button, ThemeProvider, useMediaQuery } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; import QuizDialog from "../shared/QuizDialog"; +import RunningStripe from "../shared/RunningStripe"; +import { useQuizCompletionStatus } from "../shared/useQuizCompletionStatus"; +const WIDGET_DEFAULT_WIDTH = "600px"; +const WIDGET_DEFAULT_HEIGHT = "80%"; + interface Props { - fixedSide?: "left" | "right"; quizId: string; + fixedSide?: "left" | "right"; + dimensions?: { width: string; height: string; }; + /** + * Открыть квиз через X секунд + */ + autoShowQuizTime?: number; + hideOnMobile?: boolean; + openOnLeaveAttempt?: boolean; + buttonFlash?: boolean; + withShadow?: boolean; + rounded?: boolean; + buttonText?: string; + buttonTextColor?: string; + buttonBackgroundColor?: string; } -export default function OpenQuizButton({ quizId, fixedSide }: Props) { - const [isQuizDialogOpen, setIsQuizDialogOpen] = useState(false); +export default function OpenQuizButton({ + quizId, + fixedSide, + autoShowQuizTime = 0, + dimensions, + hideOnMobile, + openOnLeaveAttempt, + buttonFlash = false, + withShadow = false, + rounded = false, + buttonText = "Пройти квиз", + buttonTextColor, + buttonBackgroundColor, +}: Props) { + const isMobile = useMediaQuery("(max-width: 600px)"); + const [isQuizShown, setIsQuizShown] = useState(false); + const isQuizCompleted = useQuizCompletionStatus(quizId); + const [isFlashEnabled, setIsFlashEnabled] = useState(buttonFlash); + const preventQuizAutoShowRef = useRef(false); + const preventOpenOnLeaveAttemptRef = useRef(false); + + useEffect(function setAutoShowQuizTimer() { + if (!autoShowQuizTime || openOnLeaveAttempt) return; + + const timeout = setTimeout(() => { + setIsQuizShown(true); + }, autoShowQuizTime * 1000); + + return () => { + clearTimeout(timeout); + }; + }, [autoShowQuizTime, openOnLeaveAttempt]); + + useEffect(function attachLeaveListener() { + if (!openOnLeaveAttempt) return; + + const handleMouseLeave = () => { + if (!preventOpenOnLeaveAttemptRef.current) { + preventOpenOnLeaveAttemptRef.current = true; + setIsQuizShown(true); + } + }; + + document.addEventListener("mouseleave", handleMouseLeave); + + return () => { + document.removeEventListener("mouseleave", handleMouseLeave); + }; + }, [openOnLeaveAttempt]); + + function openQuiz() { + preventQuizAutoShowRef.current = true; + setIsQuizShown(true); + setIsFlashEnabled(false); + } + + if (hideOnMobile && isMobile) return null; return ( setIsQuizDialogOpen(false)} + onClose={() => setIsQuizShown(false)} + paperSx={{ + width: dimensions?.width ?? WIDGET_DEFAULT_WIDTH, + height: dimensions?.height ?? WIDGET_DEFAULT_HEIGHT, + }} /> ); diff --git a/src/widgets/shared/RunningStripe.tsx b/src/widgets/shared/RunningStripe.tsx index 7b526c4..0faf39a 100644 --- a/src/widgets/shared/RunningStripe.tsx +++ b/src/widgets/shared/RunningStripe.tsx @@ -20,7 +20,7 @@ export default function RunningStripe({ sx = [] }: Props) { transform: "rotate(-60deg)", "@keyframes runningStripe": { "0%": { - left: "-20%", + left: "-150px", opacity: 1, }, "25%, 100%": {