diff --git a/.gitignore b/.gitignore index 5d68f75..0a42158 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ node_modules dist dist-package dist-ssr -widget +/widget *.local # Editor directories and files diff --git a/lib/model/widget/banner.ts b/lib/model/widget/banner.ts new file mode 100644 index 0000000..7fc6732 --- /dev/null +++ b/lib/model/widget/banner.ts @@ -0,0 +1,22 @@ +export interface BannerWidgetComponentProps { + quizId: string; + position: "topleft" | "topright" | "bottomleft" | "bottomright"; + onWidgetClose?: () => void; + appealText?: string; + quizHeaderText?: string; + buttonTextColor?: string; + buttonBackgroundColor?: string; + /** + * Открыть квиз через X секунд, 0 - сразу + */ + autoShowQuizTime?: number | null; + openOnLeaveAttempt?: boolean; + buttonFlash?: boolean; + hideOnMobile?: boolean; + withShadow?: boolean; + rounded?: boolean; + bannerFullWidth?: boolean; + pulsation?: boolean; +} + +export type BannerWidgetParams = Omit; diff --git a/lib/model/widget/button.ts b/lib/model/widget/button.ts new file mode 100644 index 0000000..962002b --- /dev/null +++ b/lib/model/widget/button.ts @@ -0,0 +1,29 @@ +export interface ButtonWidgetComponentProps { + quizId: string; + fixedSide?: "left" | "right"; + dialogDimensions?: { width: string; height: string; }; + /** + * Открыть квиз через X секунд, 0 - сразу + */ + autoShowQuizTime?: number | null; + hideOnMobile?: boolean; + openOnLeaveAttempt?: boolean; + buttonFlash?: boolean; + withShadow?: boolean; + rounded?: boolean; + buttonText?: string; + buttonTextColor?: string; + buttonBackgroundColor?: string; +} + +export type ButtonWidgetParams = Omit & { + selector: string; + /** + * In seconds, null - polling disabled + */ + selectorPollingTimeLimit?: number | null; +}; + +export type ButtonWidgetFixedParams = Omit & { + fixedSide: "left" | "right"; +}; diff --git a/lib/model/widget/container.ts b/lib/model/widget/container.ts new file mode 100644 index 0000000..759e5fe --- /dev/null +++ b/lib/model/widget/container.ts @@ -0,0 +1,15 @@ +import { ButtonWidgetComponentProps } from "./button"; + +export type ContainerWidgetComponentProps = ButtonWidgetComponentProps & { + quizId: string; + showButtonOnMobile?: boolean; + dimensions?: { width: string; height: string; }; +}; + +export type ContainerWidgetParams = ContainerWidgetComponentProps & { + selector: string; + /** + * In seconds, null - polling disabled + */ + selectorPollingTimeLimit?: number | null; +}; diff --git a/lib/model/widget/popup.ts b/lib/model/widget/popup.ts new file mode 100644 index 0000000..1fa8feb --- /dev/null +++ b/lib/model/widget/popup.ts @@ -0,0 +1,12 @@ +export interface PopupWidgetComponentProps { + quizId: string; + dialogDimensions?: { width: string; height: string; }; + /** + * Открыть квиз через X секунд, 0 - сразу + */ + autoShowQuizTime?: number | null; + hideOnMobile?: boolean; + openOnLeaveAttempt?: boolean; +} + +export type PopupWidgetParams = PopupWidgetComponentProps; diff --git a/lib/model/widget/side.ts b/lib/model/widget/side.ts new file mode 100644 index 0000000..3e07f3a --- /dev/null +++ b/lib/model/widget/side.ts @@ -0,0 +1,20 @@ +export interface SideWidgetComponentProps { + quizId: string; + position: "left" | "right"; + buttonBackgroundColor?: string; + buttonTextColor?: string; + dialogDimensions?: { width: string; height: string; }; + fullScreen?: boolean; + buttonFlash?: boolean; + /** + * Скрывать виджет первые X секунд + */ + autoOpenTime?: number; + /** + * Открыть квиз через X секунд, 0 - сразу + */ + autoShowQuizTime?: number | null; + hideOnMobile?: boolean; +} + +export type SideWidgetParams = SideWidgetComponentProps; diff --git a/src/widgets/banner/BannerWidget.tsx b/src/widgets/banner/BannerWidget.tsx index f2e4ed1..5e67f28 100644 --- a/src/widgets/banner/BannerWidget.tsx +++ b/src/widgets/banner/BannerWidget.tsx @@ -1,15 +1,13 @@ +import { BannerWidgetParams } from "@/model/widget/banner"; import { Root, createRoot } from "react-dom/client"; import QuizBanner from "./QuizBanner"; -import { ComponentPropsWithoutRef } from "react"; -type Props = Omit, "onClose">; - export class BannerWidget { root: Root | undefined; element = document.createElement("div"); - constructor(props: Props) { + constructor(props: BannerWidgetParams) { this.element.style.setProperty("display", "none"); document.body.appendChild(this.element); @@ -18,7 +16,7 @@ export class BannerWidget { this.render(props); } - render(props: Props) { + render(props: BannerWidgetParams) { this.root?.render( void; - appealText?: string; - quizHeaderText?: string; - buttonTextColor?: string; - buttonBackgroundColor?: string; - /** - * Открыть квиз через X секунд, 0 - сразу - */ - autoShowQuizTime?: number | null; - openOnLeaveAttempt?: boolean; - buttonFlash?: boolean; - hideOnMobile?: boolean; - withShadow?: boolean; - rounded?: boolean; - bannerFullWidth?: boolean; - pulsation?: boolean; -} - export default function QuizBanner({ quizId, position, @@ -47,7 +27,7 @@ export default function QuizBanner({ rounded = false, bannerFullWidth = false, pulsation = false, -}: Props) { +}: BannerWidgetComponentProps) { const isMobile = useMediaQuery("(max-width: 600px)"); const [isQuizShown, setIsQuizShown] = useState(false); const [isFlashEnabled, setIsFlashEnabled] = useState(buttonFlash); diff --git a/src/widgets/button/ButtonWidget.tsx b/src/widgets/button/ButtonWidget.tsx index d322a3e..d087a54 100644 --- a/src/widgets/button/ButtonWidget.tsx +++ b/src/widgets/button/ButtonWidget.tsx @@ -1,22 +1,14 @@ -import { ComponentPropsWithoutRef } from "react"; +import { ButtonWidgetFixedParams, ButtonWidgetParams } from "@/model/widget/button"; +import { createPortal } from "react-dom"; import { Root, createRoot } from "react-dom/client"; import { pollForSelector } from "../shared/pollForSelector"; import OpenQuizButton from "./OpenQuizButton"; -import { createPortal } from "react-dom"; -type ButtonWidgetProps = Omit, "fixedSide">; - export class ButtonWidget { root: Root | undefined; - constructor(props: ButtonWidgetProps & { - selector: string; - /** - * In seconds, null - polling disabled - */ - selectorPollingTimeLimit?: number | null; - }) { + constructor(props: ButtonWidgetParams) { const { selector, selectorPollingTimeLimit = 60 } = props; const element = document.querySelector(selector); @@ -38,7 +30,7 @@ export class ButtonWidget { }); } - render(props: ButtonWidgetProps) { + render(props: Omit) { this.root?.render(); } @@ -47,15 +39,11 @@ export class ButtonWidget { } } -type ButtonWidgetFixedProps = Omit, "selector"> & { - fixedSide: "left" | "right"; -}; - export class ButtonWidgetFixed { root: Root | undefined; element = document.createElement("div"); - constructor(props: ButtonWidgetFixedProps) { + constructor(props: ButtonWidgetFixedParams) { this.element.style.setProperty("display", "none"); document.body.appendChild(this.element); @@ -64,7 +52,7 @@ export class ButtonWidgetFixed { this.render(props); } - render(props: ButtonWidgetFixedProps) { + render(props: ButtonWidgetFixedParams) { this.root?.render(createPortal(, document.body)); } diff --git a/src/widgets/button/OpenQuizButton.tsx b/src/widgets/button/OpenQuizButton.tsx index f60ba3a..71f204f 100644 --- a/src/widgets/button/OpenQuizButton.tsx +++ b/src/widgets/button/OpenQuizButton.tsx @@ -1,3 +1,4 @@ +import { ButtonWidgetComponentProps } from "@/model/widget/button"; import lightTheme from "@/utils/themes/light"; import { Button, ThemeProvider, useMediaQuery } from "@mui/material"; import { useEffect, useRef, useState } from "react"; @@ -9,24 +10,6 @@ import { useQuizCompletionStatus } from "../shared/useQuizCompletionStatus"; const WIDGET_DEFAULT_WIDTH = "600px"; const WIDGET_DEFAULT_HEIGHT = "80%"; -interface Props { - quizId: string; - fixedSide?: "left" | "right"; - dialogDimensions?: { width: string; height: string; }; - /** - * Открыть квиз через X секунд, 0 - сразу - */ - autoShowQuizTime?: number | null; - hideOnMobile?: boolean; - openOnLeaveAttempt?: boolean; - buttonFlash?: boolean; - withShadow?: boolean; - rounded?: boolean; - buttonText?: string; - buttonTextColor?: string; - buttonBackgroundColor?: string; -} - export default function OpenQuizButton({ quizId, fixedSide, @@ -40,7 +23,7 @@ export default function OpenQuizButton({ buttonText = "Пройти квиз", buttonTextColor, buttonBackgroundColor, -}: Props) { +}: ButtonWidgetComponentProps) { const isMobile = useMediaQuery("(max-width: 600px)"); const [isQuizShown, setIsQuizShown] = useState(false); const isQuizCompleted = useQuizCompletionStatus(quizId); diff --git a/src/widgets/container/ContainerWidget.tsx b/src/widgets/container/ContainerWidget.tsx index 8efc41d..260cef3 100644 --- a/src/widgets/container/ContainerWidget.tsx +++ b/src/widgets/container/ContainerWidget.tsx @@ -1,21 +1,13 @@ -import { ComponentPropsWithoutRef } from "react"; +import { ContainerWidgetParams } from "@/model/widget/container"; import { Root, createRoot } from "react-dom/client"; import { pollForSelector } from "../shared/pollForSelector"; import QuizContainer from "./QuizContainer"; -type Props = ComponentPropsWithoutRef; - export class ContainerWidget { root: Root | undefined; - constructor(props: Props & { - selector: string; - /** - * In seconds, null - polling disabled - */ - selectorPollingTimeLimit?: number | null; - }) { + constructor(props: ContainerWidgetParams) { const { selector, selectorPollingTimeLimit = 60 } = props; const element = document.querySelector(selector); @@ -37,7 +29,7 @@ export class ContainerWidget { }); } - render(props: Props) { + render(props: Omit) { this.root?.render(); } diff --git a/src/widgets/container/QuizContainer.tsx b/src/widgets/container/QuizContainer.tsx index 3d6f267..45dda99 100644 --- a/src/widgets/container/QuizContainer.tsx +++ b/src/widgets/container/QuizContainer.tsx @@ -1,16 +1,10 @@ import QuizAnswerer from "@/components/QuizAnswerer"; +import { ContainerWidgetComponentProps } from "@/model/widget/container"; import { Box, useMediaQuery } from "@mui/material"; -import { ComponentPropsWithoutRef } from "react"; import OpenQuizButton from "../button/OpenQuizButton"; -type Props = ComponentPropsWithoutRef & { - quizId: string; - showButtonOnMobile?: boolean; - dimensions?: { width: string; height: string; }; -}; - -export default function QuizContainer(props: Props) { +export default function QuizContainer(props: ContainerWidgetComponentProps) { const { quizId, dimensions, showButtonOnMobile = false } = props; const isMobile = useMediaQuery("(max-width: 600px)"); diff --git a/src/widgets/popup/PopupWidget.tsx b/src/widgets/popup/PopupWidget.tsx index d238eaa..92e18e6 100644 --- a/src/widgets/popup/PopupWidget.tsx +++ b/src/widgets/popup/PopupWidget.tsx @@ -1,15 +1,13 @@ +import { PopupWidgetParams } from "@/model/widget/popup"; import { Root, createRoot } from "react-dom/client"; -import { ComponentPropsWithoutRef } from "react"; import QuizPopup from "./QuizPopup"; -type Props = ComponentPropsWithoutRef; - export class PopupWidget { root: Root | undefined; element = document.createElement("div"); - constructor(props: Props) { + constructor(props: PopupWidgetParams) { this.element.style.setProperty("display", "none"); document.body.appendChild(this.element); @@ -18,7 +16,7 @@ export class PopupWidget { this.render(props); } - render(props: Props) { + render(props: PopupWidgetParams) { this.root?.render(); } diff --git a/src/widgets/popup/QuizPopup.tsx b/src/widgets/popup/QuizPopup.tsx index 681c844..808b84a 100644 --- a/src/widgets/popup/QuizPopup.tsx +++ b/src/widgets/popup/QuizPopup.tsx @@ -2,29 +2,19 @@ import { useEffect, useRef, useState } from "react"; import QuizDialog from "../shared/QuizDialog"; import { useQuizCompletionStatus } from "../shared/useQuizCompletionStatus"; import { useMediaQuery } from "@mui/material"; +import { PopupWidgetComponentProps } from "@/model/widget/popup"; const WIDGET_DEFAULT_WIDTH = "600px"; const WIDGET_DEFAULT_HEIGHT = "80%"; -interface Props { - quizId: string; - dialogDimensions?: { width: string; height: string; }; - /** - * Открыть квиз через X секунд, 0 - сразу - */ - autoShowQuizTime?: number | null; - hideOnMobile?: boolean; - openOnLeaveAttempt?: boolean; -} - export default function QuizPopup({ quizId, dialogDimensions, autoShowQuizTime = null, hideOnMobile = false, openOnLeaveAttempt = false, -}: Props) { +}: PopupWidgetComponentProps) { const initialIsQuizShown = (autoShowQuizTime !== null || openOnLeaveAttempt) ? false : true; const [isQuizShown, setIsQuizShown] = useState(initialIsQuizShown); diff --git a/src/widgets/side/QuizSideButton.tsx b/src/widgets/side/QuizSideButton.tsx index aaa78a4..67b2c5c 100644 --- a/src/widgets/side/QuizSideButton.tsx +++ b/src/widgets/side/QuizSideButton.tsx @@ -6,31 +6,13 @@ import QuizDialog from "../shared/QuizDialog"; import RunningStripe from "../shared/RunningStripe"; import { useAutoOpenTimer } from "../shared/useAutoOpenTimer"; import { useQuizCompletionStatus } from "../shared/useQuizCompletionStatus"; +import { SideWidgetComponentProps } from "@/model/widget/side"; const PADDING = 10; const WIDGET_DEFAULT_WIDTH = "600px"; const WIDGET_DEFAULT_HEIGHT = "800px"; -interface Props { - quizId: string; - position: "left" | "right"; - buttonBackgroundColor?: string; - buttonTextColor?: string; - dialogDimensions?: { width: string; height: string; }; - fullScreen?: boolean; - buttonFlash?: boolean; - /** - * Скрывать виджет первые X секунд - */ - autoOpenTime?: number; - /** - * Открыть квиз через X секунд, 0 - сразу - */ - autoShowQuizTime?: number | null; - hideOnMobile?: boolean; -} - export default function QuizSideButton({ quizId, position, @@ -42,7 +24,7 @@ export default function QuizSideButton({ autoOpenTime = 0, autoShowQuizTime = null, hideOnMobile = false, -}: Props) { +}: SideWidgetComponentProps) { const [isQuizShown, setIsQuizShown] = useState(false); const isMobile = useMediaQuery("(max-width: 600px)"); const isQuizCompleted = useQuizCompletionStatus(quizId); diff --git a/src/widgets/side/SideWidget.tsx b/src/widgets/side/SideWidget.tsx index 2c7c4d3..c1c1863 100644 --- a/src/widgets/side/SideWidget.tsx +++ b/src/widgets/side/SideWidget.tsx @@ -1,15 +1,13 @@ +import { SideWidgetParams } from "@/model/widget/side"; import { Root, createRoot } from "react-dom/client"; import QuizSideButton from "./QuizSideButton"; -import { ComponentPropsWithoutRef } from "react"; -type Props = ComponentPropsWithoutRef; - export class SideWidget { root: Root | undefined; element = document.createElement("div"); - constructor(props: Props) { + constructor(props: SideWidgetParams) { this.element.style.setProperty("display", "none"); document.body.appendChild(this.element); @@ -18,7 +16,7 @@ export class SideWidget { this.render(props); } - render(props: Props) { + render(props: SideWidgetParams) { this.root?.render(); }