mirror of
https://github.com/prymitive/karma
synced 2026-05-09 03:36:44 +00:00
chore(ui): migrate more code to typescript
This commit is contained in:
committed by
Łukasz Mierzwa
parent
55170f8812
commit
4d4dd111c1
@@ -1,9 +1,12 @@
|
||||
import React from "react";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
const DropdownSlide = ({ children, duration, ...props }) => (
|
||||
const DropdownSlide: FC<{
|
||||
children: ReactNode;
|
||||
duration: number;
|
||||
}> = ({ children, duration, ...props }) => (
|
||||
<CSSTransition
|
||||
classNames="components-animation-slide"
|
||||
timeout={150}
|
||||
@@ -1,9 +1,13 @@
|
||||
import React from "react";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
const MountModal = ({ children, duration, ...props }) => (
|
||||
const MountModal: FC<{
|
||||
children: ReactNode;
|
||||
in: boolean;
|
||||
unmountOnExit?: boolean;
|
||||
}> = ({ children, ...props }) => (
|
||||
<CSSTransition
|
||||
classNames="components-animation-modal"
|
||||
timeout={200}
|
||||
@@ -19,9 +23,12 @@ MountModal.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
const MountModalBackdrop = ({ children, duration, ...props }) => (
|
||||
const MountModalBackdrop: FC<{
|
||||
children: ReactNode;
|
||||
in?: boolean;
|
||||
unmountOnExit?: boolean;
|
||||
}> = ({ children, ...props }) => (
|
||||
<CSSTransition
|
||||
in={true}
|
||||
classNames="components-animation-backdrop"
|
||||
timeout={200}
|
||||
appear={true}
|
||||
@@ -1,10 +1,13 @@
|
||||
import React from "react";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
|
||||
const CenteredMessage = ({ children, className }) => {
|
||||
const CenteredMessage: FC<{
|
||||
children: ReactNode;
|
||||
className: string;
|
||||
}> = ({ children, className }) => {
|
||||
const context = React.useContext(ThemeContext);
|
||||
return (
|
||||
<CSSTransition
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useState, useEffect, FC } from "react";
|
||||
|
||||
import { autorun } from "mobx";
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
@@ -8,7 +7,9 @@ import Favico from "favico.js";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
|
||||
const FaviconBadge = ({ alertStore }) => {
|
||||
const FaviconBadge: FC<{
|
||||
alertStore: AlertStore;
|
||||
}> = ({ alertStore }) => {
|
||||
const [favico] = useState(
|
||||
new Favico({
|
||||
animation: "none",
|
||||
@@ -38,8 +39,5 @@ const FaviconBadge = ({ alertStore }) => {
|
||||
/>
|
||||
));
|
||||
};
|
||||
FaviconBadge.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
};
|
||||
|
||||
export { FaviconBadge };
|
||||
@@ -1,19 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
|
||||
const FetchPauser = ({ children, alertStore }) => {
|
||||
useEffect(() => {
|
||||
alertStore.status.pause();
|
||||
return alertStore.status.resume;
|
||||
}, [alertStore.status]);
|
||||
|
||||
return children;
|
||||
};
|
||||
FetchPauser.propTypes = {
|
||||
children: PropTypes.any,
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
};
|
||||
|
||||
export { FetchPauser };
|
||||
17
ui/src/Components/FetchPauser/index.tsx
Normal file
17
ui/src/Components/FetchPauser/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { FC, ReactElement, useEffect } from "react";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
|
||||
const FetchPauser: FC<{
|
||||
children: ReactElement;
|
||||
alertStore: AlertStore;
|
||||
}> = ({ children, alertStore }) => {
|
||||
useEffect(() => {
|
||||
alertStore.status.pause();
|
||||
return alertStore.status.resume;
|
||||
}, [alertStore.status]);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export { FetchPauser };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useEffect, useRef, FC } from "react";
|
||||
|
||||
import { reaction } from "mobx";
|
||||
|
||||
@@ -8,8 +7,11 @@ import addSeconds from "date-fns/addSeconds";
|
||||
import { AlertStore, AlertStoreStatuses } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
const Fetcher = ({ alertStore, settingsStore }) => {
|
||||
const timer = useRef(null);
|
||||
const Fetcher: FC<{
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
}> = ({ alertStore, settingsStore }) => {
|
||||
const timer = useRef(undefined as number | undefined);
|
||||
|
||||
const getSortSettings = () => {
|
||||
let sortSettings = {
|
||||
@@ -81,7 +83,7 @@ const Fetcher = ({ alertStore, settingsStore }) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => clearInterval(timer.current);
|
||||
return () => window.clearInterval(timer.current);
|
||||
}, []);
|
||||
|
||||
useEffect(
|
||||
@@ -89,7 +91,9 @@ const Fetcher = ({ alertStore, settingsStore }) => {
|
||||
reaction(
|
||||
() =>
|
||||
JSON.stringify({
|
||||
filters: alertStore.filters.values.map((f) => f.raw).join(" "),
|
||||
filters: alertStore.filters.values
|
||||
.map((f: { raw: string }) => f.raw)
|
||||
.join(" "),
|
||||
grid: {
|
||||
sortOrder: settingsStore.gridConfig.config.sortOrder,
|
||||
sortLabel: settingsStore.gridConfig.config.sortLabel,
|
||||
@@ -115,10 +119,10 @@ const Fetcher = ({ alertStore, settingsStore }) => {
|
||||
() => alertStore.status.paused,
|
||||
(paused) => {
|
||||
if (paused) {
|
||||
clearInterval(timer.current);
|
||||
timer.current = null;
|
||||
window.clearInterval(timer.current);
|
||||
timer.current = undefined;
|
||||
} else {
|
||||
timer.current = setInterval(
|
||||
timer.current = window.setInterval(
|
||||
() => window.requestAnimationFrame(fetchIfIdle),
|
||||
1000
|
||||
);
|
||||
@@ -131,9 +135,5 @@ const Fetcher = ({ alertStore, settingsStore }) => {
|
||||
|
||||
return <span />;
|
||||
};
|
||||
Fetcher.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { Fetcher };
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Tab = ({ title, active, onClick }) => (
|
||||
<span
|
||||
className={`nav-item nav-link cursor-pointer mx-1 px-2 ${
|
||||
active ? "active" : "components-tab-inactive"
|
||||
}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
);
|
||||
Tab.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
active: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export { Tab };
|
||||
18
ui/src/Components/Modal/Tab.tsx
Normal file
18
ui/src/Components/Modal/Tab.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React, { FC, MouseEvent } from "react";
|
||||
|
||||
const Tab: FC<{
|
||||
title: string;
|
||||
active?: boolean;
|
||||
onClick: (event: MouseEvent<HTMLElement>) => void;
|
||||
}> = ({ title, active, onClick }) => (
|
||||
<span
|
||||
className={`nav-item nav-link cursor-pointer mx-1 px-2 ${
|
||||
active ? "active" : "components-tab-inactive"
|
||||
}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
);
|
||||
|
||||
export { Tab };
|
||||
@@ -3,7 +3,16 @@ import React from "react";
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { PressKey } from "__mocks__/PressKey";
|
||||
import { Modal } from ".";
|
||||
import { Modal, ModalInner } from ".";
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
document.body.className = "";
|
||||
});
|
||||
|
||||
const fakeToggle = jest.fn();
|
||||
|
||||
@@ -15,12 +24,21 @@ const MountedModal = (isOpen, isUpper) => {
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
document.body.className = "";
|
||||
});
|
||||
describe("<ModalInner />", () => {
|
||||
it("scroll isn't enabled if ref is null", () => {
|
||||
const useRefSpy = jest.spyOn(React, "useRef").mockImplementation(() =>
|
||||
Object.defineProperty({}, "current", {
|
||||
get: () => null,
|
||||
set: () => {},
|
||||
})
|
||||
);
|
||||
const tree = mount(<ModalInner isUpper toggleOpen={fakeToggle} />);
|
||||
tree.setProps({ isUpper: false });
|
||||
tree.setProps({ isUpper: true });
|
||||
tree.setProps({ isUpper: false });
|
||||
expect(useRefSpy).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
describe("<Modal />", () => {
|
||||
it("'modal-open' class is appended to MountModal container", () => {
|
||||
const tree = MountedModal(true);
|
||||
expect(tree.find("div").at(0).hasClass("modal-open")).toBe(true);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import React, { FC, useEffect } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
|
||||
|
||||
@@ -11,18 +10,25 @@ import {
|
||||
MountModalBackdrop,
|
||||
} from "Components/Animations/MountModal";
|
||||
|
||||
const ModalInner = ({ size, isUpper, toggleOpen, children }) => {
|
||||
const ref = useRef(null);
|
||||
const ModalInner: FC<{
|
||||
size: "lg" | "xl";
|
||||
isUpper: boolean;
|
||||
toggleOpen: () => void;
|
||||
}> = ({ size, isUpper, toggleOpen, children }) => {
|
||||
// needed for tests to spy on useRef
|
||||
const ref = React.useRef(null as HTMLDivElement | null);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.classList.add("modal-open");
|
||||
disableBodyScroll(ref.current, { reserveScrollBarGap: true });
|
||||
if (ref.current !== null) {
|
||||
document.body.classList.add("modal-open");
|
||||
disableBodyScroll(ref.current, { reserveScrollBarGap: true });
|
||||
|
||||
let modal = ref.current;
|
||||
return () => {
|
||||
if (!isUpper) document.body.classList.remove("modal-open");
|
||||
enableBodyScroll(modal);
|
||||
};
|
||||
let modal = ref.current;
|
||||
return () => {
|
||||
if (!isUpper) document.body.classList.remove("modal-open");
|
||||
enableBodyScroll(modal);
|
||||
};
|
||||
}
|
||||
}, [isUpper]);
|
||||
|
||||
useHotkeys("esc", toggleOpen);
|
||||
@@ -43,7 +49,19 @@ const ModalInner = ({ size, isUpper, toggleOpen, children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Modal = ({ size, isOpen, isUpper, toggleOpen, children, ...props }) => {
|
||||
const Modal: FC<{
|
||||
size?: "lg" | "xl";
|
||||
isOpen: boolean;
|
||||
isUpper?: boolean;
|
||||
toggleOpen: () => void;
|
||||
}> = ({
|
||||
size = "lg",
|
||||
isOpen,
|
||||
isUpper = false,
|
||||
toggleOpen,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return ReactDOM.createPortal(
|
||||
<React.Fragment>
|
||||
<MountModal in={isOpen} unmountOnExit {...props}>
|
||||
@@ -58,16 +76,5 @@ const Modal = ({ size, isOpen, isUpper, toggleOpen, children, ...props }) => {
|
||||
document.body
|
||||
);
|
||||
};
|
||||
Modal.propTypes = {
|
||||
size: PropTypes.oneOf(["lg", "xl"]),
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
isUpper: PropTypes.bool,
|
||||
toggleOpen: PropTypes.func.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
Modal.defaultProps = {
|
||||
size: "lg",
|
||||
isUpper: false,
|
||||
};
|
||||
|
||||
export { Modal };
|
||||
export { Modal, ModalInner };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||
@@ -12,7 +11,7 @@ import {
|
||||
} from "Components/LabelSetList";
|
||||
import { useFetchGet } from "Hooks/useFetchGet";
|
||||
|
||||
const FetchError = ({ message }) => (
|
||||
const FetchError: FC<{ message: ReactNode }> = ({ message }) => (
|
||||
<div className="text-center">
|
||||
<h2 className="display-2 text-danger">
|
||||
<FontAwesomeIcon icon={faExclamationCircle} />
|
||||
@@ -20,9 +19,6 @@ const FetchError = ({ message }) => (
|
||||
<p className="lead text-muted">{message}</p>
|
||||
</div>
|
||||
);
|
||||
FetchError.propTypes = {
|
||||
message: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
const Placeholder = () => (
|
||||
<div className="jumbotron bg-transparent">
|
||||
@@ -32,7 +28,11 @@ const Placeholder = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const PaginatedAlertList = ({ alertStore, filters, title }) => {
|
||||
const PaginatedAlertList: FC<{
|
||||
alertStore: AlertStore;
|
||||
filters: string[];
|
||||
title: string;
|
||||
}> = ({ alertStore, filters, title }) => {
|
||||
const { response, error, isLoading } = useFetchGet(
|
||||
FormatBackendURI("alerts.json?") + FormatAlertsQ(filters)
|
||||
);
|
||||
@@ -51,10 +51,5 @@ const PaginatedAlertList = ({ alertStore, filters, title }) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
PaginatedAlertList.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
export { PaginatedAlertList };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useState, useEffect, FC } from "react";
|
||||
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
|
||||
@@ -13,16 +12,24 @@ import { faAngleDoubleRight } from "@fortawesome/free-solid-svg-icons/faAngleDou
|
||||
|
||||
import { IsMobile } from "Common/Device";
|
||||
|
||||
const PageSelect = ({
|
||||
type PageCallback = (page: number) => void;
|
||||
|
||||
const PageSelect: FC<{
|
||||
totalItemsCount: number;
|
||||
totalPages: number;
|
||||
maxPerPage: number;
|
||||
initialPage: number;
|
||||
setPageCallback: PageCallback;
|
||||
}> = ({
|
||||
totalItemsCount,
|
||||
totalPages,
|
||||
maxPerPage,
|
||||
initialPage,
|
||||
initialPage = 1,
|
||||
setPageCallback,
|
||||
}) => {
|
||||
const [activePage, setActivePage] = useState(initialPage);
|
||||
|
||||
const onChange = (page) => {
|
||||
const onChange = (page: number) => {
|
||||
setActivePage(page);
|
||||
setPageCallback(page);
|
||||
};
|
||||
@@ -83,15 +90,5 @@ const PageSelect = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
PageSelect.propTypes = {
|
||||
totalPages: PropTypes.number.isRequired,
|
||||
initialPage: PropTypes.number,
|
||||
maxPerPage: PropTypes.number.isRequired,
|
||||
totalItemsCount: PropTypes.number.isRequired,
|
||||
setPageCallback: PropTypes.func.isRequired,
|
||||
};
|
||||
PageSelect.defaultProps = {
|
||||
initialPage: 1,
|
||||
};
|
||||
|
||||
export { PageSelect };
|
||||
@@ -1,4 +1,25 @@
|
||||
const ReactSelectColors = {
|
||||
import { CSSProperties } from "react";
|
||||
import { Styles } from "react-select";
|
||||
|
||||
interface ReactSelectTheme {
|
||||
color: string;
|
||||
singleValueColor: string;
|
||||
backgroundColor: string;
|
||||
borderColor: string;
|
||||
focusedBoxShadow: string;
|
||||
focusedBorderColor: string;
|
||||
menuBackground: string;
|
||||
optionHoverBackground: string;
|
||||
valueContainerBackground: string;
|
||||
disabledValueContainerBackground: string;
|
||||
}
|
||||
|
||||
interface ReactSelectThemes {
|
||||
Light: ReactSelectTheme;
|
||||
Dark: ReactSelectTheme;
|
||||
}
|
||||
|
||||
const ReactSelectColors: ReactSelectThemes = {
|
||||
Light: {
|
||||
color: "#fff",
|
||||
singleValueColor: "#000",
|
||||
@@ -25,8 +46,8 @@ const ReactSelectColors = {
|
||||
},
|
||||
};
|
||||
|
||||
const ReactSelectStyles = (theme) => ({
|
||||
control: (base, state) =>
|
||||
const ReactSelectStyles = (theme: ReactSelectTheme): Styles => ({
|
||||
control: (base: CSSProperties, state: any) =>
|
||||
state.isFocused
|
||||
? {
|
||||
...base,
|
||||
@@ -47,7 +68,7 @@ const ReactSelectStyles = (theme) => ({
|
||||
borderColor: theme.borderColor,
|
||||
"&:hover": { borderColor: theme.borderColor },
|
||||
},
|
||||
valueContainer: (base, state) =>
|
||||
valueContainer: (base: CSSProperties, state: any) =>
|
||||
state.isMulti
|
||||
? {
|
||||
...base,
|
||||
@@ -72,11 +93,11 @@ const ReactSelectStyles = (theme) => ({
|
||||
? theme.disabledValueContainerBackground
|
||||
: theme.valueContainerBackground,
|
||||
},
|
||||
singleValue: (base, state) => ({
|
||||
singleValue: (base: CSSProperties) => ({
|
||||
...base,
|
||||
color: theme.singleValueColor,
|
||||
}),
|
||||
multiValue: (base, state) => ({
|
||||
multiValue: (base: CSSProperties) => ({
|
||||
...base,
|
||||
borderRadius: "4px",
|
||||
backgroundColor: theme.optionHoverBackground,
|
||||
@@ -84,7 +105,7 @@ const ReactSelectStyles = (theme) => ({
|
||||
backgroundColor: theme.optionHoverBackground,
|
||||
},
|
||||
}),
|
||||
multiValueLabel: (base, state) => ({
|
||||
multiValueLabel: (base: CSSProperties) => ({
|
||||
...base,
|
||||
color: theme.color,
|
||||
whiteSpace: "normal",
|
||||
@@ -94,7 +115,7 @@ const ReactSelectStyles = (theme) => ({
|
||||
color: theme.color,
|
||||
},
|
||||
}),
|
||||
multiValueRemove: (base, state) => ({
|
||||
multiValueRemove: (base: CSSProperties) => ({
|
||||
...base,
|
||||
cursor: "pointer",
|
||||
color: theme.color,
|
||||
@@ -107,11 +128,11 @@ const ReactSelectStyles = (theme) => ({
|
||||
opacity: "0.75",
|
||||
},
|
||||
}),
|
||||
input: (base, state) => ({
|
||||
input: (base: CSSProperties) => ({
|
||||
...base,
|
||||
color: "inherit",
|
||||
}),
|
||||
indicatorsContainer: (base, state) => ({
|
||||
indicatorsContainer: (base: CSSProperties, state: any) => ({
|
||||
...base,
|
||||
backgroundColor: state.isDisabled
|
||||
? theme.disabledValueContainerBackground
|
||||
@@ -119,19 +140,19 @@ const ReactSelectStyles = (theme) => ({
|
||||
borderTopRightRadius: "0.25rem",
|
||||
borderBottomRightRadius: "0.25rem",
|
||||
}),
|
||||
dropdownIndicator: (base, state) =>
|
||||
dropdownIndicator: (base: CSSProperties, state: any) =>
|
||||
state.isFocused
|
||||
? {
|
||||
...base,
|
||||
"&:hover": { color: "inherit" },
|
||||
}
|
||||
: { ...base },
|
||||
menu: (base, state) => ({
|
||||
menu: (base: CSSProperties) => ({
|
||||
...base,
|
||||
zIndex: 1500,
|
||||
backgroundColor: theme.menuBackground,
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
option: (base: CSSProperties) => ({
|
||||
...base,
|
||||
color: "inherit",
|
||||
backgroundColor: "inherit",
|
||||
@@ -38,7 +38,19 @@ const Placeholder = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ThemeContext = React.createContext();
|
||||
interface ThemeCtx {
|
||||
isDark: boolean;
|
||||
reactSelectStyles: any;
|
||||
animations: {
|
||||
duration: number;
|
||||
};
|
||||
}
|
||||
|
||||
const ThemeContext = React.createContext({
|
||||
isDark: false,
|
||||
reactSelectStyles: {},
|
||||
animations: { duration: 1000 },
|
||||
} as ThemeCtx);
|
||||
|
||||
const BodyTheme = () => {
|
||||
const context = React.useContext(ThemeContext);
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, ReactNode, FC } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
@@ -8,16 +7,24 @@ import { usePopper } from "react-popper";
|
||||
|
||||
import { useSupportsTouch } from "Hooks/useSupportsTouch";
|
||||
|
||||
const TooltipWrapper = ({ title, children, className }) => {
|
||||
const [referenceElement, setReferenceElement] = useState(null);
|
||||
const [popperElement, setPopperElement] = useState(null);
|
||||
const TooltipWrapper: FC<{
|
||||
title: ReactNode;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}> = ({ title, children, className }) => {
|
||||
const [referenceElement, setReferenceElement] = useState(
|
||||
null as HTMLElement | null
|
||||
);
|
||||
const [popperElement, setPopperElement] = useState(
|
||||
null as HTMLElement | null
|
||||
);
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: "top",
|
||||
modifiers: [
|
||||
{
|
||||
name: "preventOverflow",
|
||||
options: {
|
||||
boundariesElement: "viewport",
|
||||
rootBoundary: "viewport",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -32,22 +39,22 @@ const TooltipWrapper = ({ title, children, className }) => {
|
||||
const hideTooltip = () => setIsHovering(false);
|
||||
|
||||
useEffect(() => {
|
||||
let timerShow;
|
||||
let timerHide;
|
||||
let timerShow: number | undefined;
|
||||
let timerHide: number | undefined;
|
||||
|
||||
if (!isHovering) {
|
||||
if (isVisible) {
|
||||
clearTimeout(timerShow);
|
||||
timerHide = setTimeout(() => setIsVisible(false), 100);
|
||||
window.clearTimeout(timerShow);
|
||||
timerHide = window.setTimeout(() => setIsVisible(false), 100);
|
||||
}
|
||||
setWasClicked(false);
|
||||
} else if (wasClicked) {
|
||||
clearTimeout(timerShow);
|
||||
clearTimeout(timerHide);
|
||||
window.clearTimeout(timerShow);
|
||||
window.clearTimeout(timerHide);
|
||||
setIsVisible(false);
|
||||
} else if (!isVisible && isHovering) {
|
||||
clearTimeout(timerHide);
|
||||
timerShow = setTimeout(() => setIsVisible(true), 1000);
|
||||
timerShow = window.setTimeout(() => setIsVisible(true), 1000);
|
||||
}
|
||||
return () => {
|
||||
clearTimeout(timerShow);
|
||||
@@ -59,11 +66,11 @@ const TooltipWrapper = ({ title, children, className }) => {
|
||||
<React.Fragment>
|
||||
<div
|
||||
onClick={() => setWasClicked(true)}
|
||||
onMouseOver={supportsTouch ? null : showTooltip}
|
||||
onMouseLeave={supportsTouch ? null : hideTooltip}
|
||||
onTouchStart={supportsTouch ? showTooltip : null}
|
||||
onTouchCancel={supportsTouch ? hideTooltip : null}
|
||||
onTouchEnd={supportsTouch ? hideTooltip : null}
|
||||
onMouseOver={supportsTouch ? undefined : showTooltip}
|
||||
onMouseLeave={supportsTouch ? undefined : hideTooltip}
|
||||
onTouchStart={supportsTouch ? showTooltip : undefined}
|
||||
onTouchCancel={supportsTouch ? hideTooltip : undefined}
|
||||
onTouchEnd={supportsTouch ? hideTooltip : undefined}
|
||||
ref={setReferenceElement}
|
||||
className={`${className ? className : ""} tooltip-trigger`}
|
||||
>
|
||||
@@ -94,10 +101,5 @@ const TooltipWrapper = ({ title, children, className }) => {
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
TooltipWrapper.propTypes = {
|
||||
title: PropTypes.node.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export { TooltipWrapper };
|
||||
Reference in New Issue
Block a user