add button widget features
This commit is contained in:
parent
cd972e493b
commit
92d727b2e9
@ -1,11 +1,12 @@
|
|||||||
import lightTheme from "@/utils/themes/light";
|
import lightTheme from "@/utils/themes/light";
|
||||||
import { Box, ThemeProvider, Typography } from "@mui/material";
|
import { Box, ThemeProvider, Typography } from "@mui/material";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { PopupWidget as Widget } from "./widgets";
|
import { ButtonWidget as Widget } from "./widgets";
|
||||||
|
|
||||||
|
|
||||||
const widgetProps: ConstructorParameters<typeof Widget>[0] = {
|
const widgetProps: ConstructorParameters<typeof Widget>[0] = {
|
||||||
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||||
|
selector: "#widget-button",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function WidgetDev() {
|
export default function WidgetDev() {
|
||||||
@ -28,6 +29,7 @@ export default function WidgetDev() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Lorem />
|
<Lorem />
|
||||||
|
<Box id="widget-button"></Box>
|
||||||
<Lorem />
|
<Lorem />
|
||||||
<Lorem />
|
<Lorem />
|
||||||
<Lorem />
|
<Lorem />
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
|
import { ComponentPropsWithoutRef } from "react";
|
||||||
import { Root, createRoot } from "react-dom/client";
|
import { Root, createRoot } from "react-dom/client";
|
||||||
|
import { pollForSelector } from "../shared/pollForSelector";
|
||||||
import OpenQuizButton from "./OpenQuizButton";
|
import OpenQuizButton from "./OpenQuizButton";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { pollForSelector } from "../shared/pollForSelector";
|
|
||||||
|
|
||||||
|
|
||||||
|
type Props = ComponentPropsWithoutRef<typeof OpenQuizButton>;
|
||||||
|
|
||||||
export class ButtonWidget {
|
export class ButtonWidget {
|
||||||
root: Root | undefined;
|
root: Root | undefined;
|
||||||
element = document.createElement("div");
|
|
||||||
|
|
||||||
constructor({ quizId, selector, selectorPollingTimeLimit = 60 }: {
|
constructor(props: Props & {
|
||||||
quizId: string;
|
|
||||||
selector: string;
|
selector: string;
|
||||||
/**
|
/**
|
||||||
* In seconds, null - polling disabled
|
* In seconds, null - polling disabled
|
||||||
*/
|
*/
|
||||||
selectorPollingTimeLimit?: number | null;
|
selectorPollingTimeLimit?: number | null;
|
||||||
}) {
|
}) {
|
||||||
|
const { selector, selectorPollingTimeLimit = 60 } = props;
|
||||||
|
|
||||||
const element = document.querySelector(selector);
|
const element = document.querySelector(selector);
|
||||||
if (element) {
|
if (element) {
|
||||||
this.root = createRoot(element);
|
this.root = createRoot(element);
|
||||||
this.root.render(<OpenQuizButton quizId={quizId} />);
|
this.render(props);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -31,13 +34,16 @@ export class ButtonWidget {
|
|||||||
|
|
||||||
pollForSelector(selector, selectorPollingTimeLimit, (element) => {
|
pollForSelector(selector, selectorPollingTimeLimit, (element) => {
|
||||||
this.root = createRoot(element);
|
this.root = createRoot(element);
|
||||||
this.root.render(<OpenQuizButton quizId={quizId} />);
|
this.render(props);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render(props: Props) {
|
||||||
|
this.root?.render(<OpenQuizButton {...props} />);
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.root) this.root.unmount();
|
if (this.root) this.root.unmount();
|
||||||
this.element.remove();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,22 +51,17 @@ export class ButtonWidgetFixed {
|
|||||||
root: Root | undefined;
|
root: Root | undefined;
|
||||||
element = document.createElement("div");
|
element = document.createElement("div");
|
||||||
|
|
||||||
constructor({ quizId, side }: {
|
constructor(props: Props) {
|
||||||
quizId: string;
|
|
||||||
side: "left" | "right";
|
|
||||||
}) {
|
|
||||||
this.element.style.setProperty("display", "none");
|
this.element.style.setProperty("display", "none");
|
||||||
document.body.appendChild(this.element);
|
document.body.appendChild(this.element);
|
||||||
|
|
||||||
this.root = createRoot(this.element);
|
this.root = createRoot(this.element);
|
||||||
|
|
||||||
this.root.render(createPortal(
|
this.render(props);
|
||||||
<OpenQuizButton
|
}
|
||||||
fixedSide={side}
|
|
||||||
quizId={quizId}
|
render(props: Props) {
|
||||||
/>,
|
this.root?.render(createPortal(<OpenQuizButton {...props} />, document.body));
|
||||||
document.body
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
@ -1,26 +1,108 @@
|
|||||||
import lightTheme from "@/utils/themes/light";
|
import lightTheme from "@/utils/themes/light";
|
||||||
import { Button, ThemeProvider } from "@mui/material";
|
import { Button, ThemeProvider, useMediaQuery } from "@mui/material";
|
||||||
import { useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import QuizDialog from "../shared/QuizDialog";
|
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 {
|
interface Props {
|
||||||
fixedSide?: "left" | "right";
|
|
||||||
quizId: string;
|
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) {
|
export default function OpenQuizButton({
|
||||||
const [isQuizDialogOpen, setIsQuizDialogOpen] = useState<boolean>(false);
|
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<boolean>(false);
|
||||||
|
const isQuizCompleted = useQuizCompletionStatus(quizId);
|
||||||
|
const [isFlashEnabled, setIsFlashEnabled] = useState<boolean>(buttonFlash);
|
||||||
|
const preventQuizAutoShowRef = useRef<boolean>(false);
|
||||||
|
const preventOpenOnLeaveAttemptRef = useRef<boolean>(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 (
|
return (
|
||||||
<ThemeProvider theme={lightTheme}>
|
<ThemeProvider theme={lightTheme}>
|
||||||
<Button
|
<Button
|
||||||
className="pena-quiz-widget-button"
|
className="pena-quiz-widget-button"
|
||||||
onClick={() => setIsQuizDialogOpen(p => !p)}
|
onClick={openQuiz}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
disableFocusRipple
|
||||||
sx={[
|
sx={[
|
||||||
{
|
{
|
||||||
// normal styles
|
overflow: "hidden",
|
||||||
|
color: buttonTextColor,
|
||||||
|
backgroundColor: buttonBackgroundColor,
|
||||||
|
},
|
||||||
|
withShadow && {
|
||||||
|
boxShadow: "0px 0px 8px 0px rgba(0, 0, 0, 0.7)",
|
||||||
|
},
|
||||||
|
!rounded && {
|
||||||
|
borderRadius: 0,
|
||||||
},
|
},
|
||||||
Boolean(fixedSide) && {
|
Boolean(fixedSide) && {
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
@ -38,12 +120,17 @@ export default function OpenQuizButton({ quizId, fixedSide }: Props) {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Пройти квиз
|
{!isQuizCompleted && isFlashEnabled && <RunningStripe />}
|
||||||
|
{buttonText}
|
||||||
</Button>
|
</Button>
|
||||||
<QuizDialog
|
<QuizDialog
|
||||||
open={isQuizDialogOpen}
|
open={isQuizShown}
|
||||||
quizId={quizId}
|
quizId={quizId}
|
||||||
onClose={() => setIsQuizDialogOpen(false)}
|
onClose={() => setIsQuizShown(false)}
|
||||||
|
paperSx={{
|
||||||
|
width: dimensions?.width ?? WIDGET_DEFAULT_WIDTH,
|
||||||
|
height: dimensions?.height ?? WIDGET_DEFAULT_HEIGHT,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
@ -20,7 +20,7 @@ export default function RunningStripe({ sx = [] }: Props) {
|
|||||||
transform: "rotate(-60deg)",
|
transform: "rotate(-60deg)",
|
||||||
"@keyframes runningStripe": {
|
"@keyframes runningStripe": {
|
||||||
"0%": {
|
"0%": {
|
||||||
left: "-20%",
|
left: "-150px",
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
},
|
},
|
||||||
"25%, 100%": {
|
"25%, 100%": {
|
||||||
|
Loading…
Reference in New Issue
Block a user