mirror of
https://github.com/prymitive/karma
synced 2026-05-19 04:26:41 +00:00
Merge pull request #52 from prymitive/animations
feat(ui): animate modals and dropdowns
This commit is contained in:
33
ui/src/Components/Animations/DropdownSlide/index.css
Normal file
33
ui/src/Components/Animations/DropdownSlide/index.css
Normal file
@@ -0,0 +1,33 @@
|
||||
.components-animation-slide-enter,
|
||||
.components-animation-slide-appear {
|
||||
opacity: 0.01;
|
||||
overflow: hidden;
|
||||
max-height: 0px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.components-animation-slide-enter-active,
|
||||
.components-animation-slide-appear-active {
|
||||
max-height: 500px; /* can't use auto here */
|
||||
opacity: 1;
|
||||
transition-property: max-height, opacity;
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.components-animation-slide-exit {
|
||||
max-height: 500px; /* can't use auto here */
|
||||
opacity: 1;
|
||||
}
|
||||
.components-animation-slide-exit-active {
|
||||
opacity: 0.01;
|
||||
overflow: hidden;
|
||||
max-height: 0px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
transition-property: max-height, opacity;
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
24
ui/src/Components/Animations/DropdownSlide/index.js
Normal file
24
ui/src/Components/Animations/DropdownSlide/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const DropdownSlide = ({ children, duration, ...props }) => (
|
||||
<CSSTransition
|
||||
in={true}
|
||||
classNames="components-animation-slide"
|
||||
timeout={150}
|
||||
appear={true}
|
||||
exit={true}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</CSSTransition>
|
||||
);
|
||||
DropdownSlide.propTypes = {
|
||||
children: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
export { DropdownSlide };
|
||||
23
ui/src/Components/Animations/MountFade/index.css
Normal file
23
ui/src/Components/Animations/MountFade/index.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.components-animation-fade-appear {
|
||||
opacity: 0.01;
|
||||
}
|
||||
.components-animation-fade-appear-active {
|
||||
opacity: 1;
|
||||
transition: opacity 0.15s ease-in;
|
||||
}
|
||||
|
||||
.components-animation-fade-enter {
|
||||
opacity: 0.01;
|
||||
}
|
||||
.components-animation-fade-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity 0.15s ease-in;
|
||||
}
|
||||
|
||||
.components-animation-fade-exit {
|
||||
opacity: 1;
|
||||
}
|
||||
.components-animation-fade-exit-active {
|
||||
opacity: 0.01;
|
||||
transition: opacity 0.15s ease-in;
|
||||
}
|
||||
25
ui/src/Components/Animations/MountFade/index.js
Normal file
25
ui/src/Components/Animations/MountFade/index.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const MountFade = ({ children, duration, ...props }) => (
|
||||
<CSSTransition
|
||||
in={true}
|
||||
classNames="components-animation-fade"
|
||||
timeout={150}
|
||||
appear={true}
|
||||
enter={true}
|
||||
exit={true}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</CSSTransition>
|
||||
);
|
||||
MountFade.propTypes = {
|
||||
children: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
export { MountFade };
|
||||
@@ -16,6 +16,7 @@ import { faCaretDown } from "@fortawesome/free-solid-svg-icons/faCaretDown";
|
||||
import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
|
||||
|
||||
import { FetchPauser } from "Components/FetchPauser";
|
||||
import { DropdownSlide } from "Components/Animations/DropdownSlide";
|
||||
|
||||
const onSilenceClick = (silenceFormStore, group, alert) => {
|
||||
silenceFormStore.data.resetProgress();
|
||||
@@ -127,7 +128,7 @@ const AlertMenu = observer(
|
||||
</span>
|
||||
)}
|
||||
</Reference>
|
||||
{this.collapse.value ? null : (
|
||||
<DropdownSlide in={!this.collapse.value} unmountOnExit>
|
||||
<Popper
|
||||
placement="bottom-start"
|
||||
modifiers={{
|
||||
@@ -149,7 +150,7 @@ const AlertMenu = observer(
|
||||
/>
|
||||
)}
|
||||
</Popper>
|
||||
)}
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
|
||||
|
||||
import { FormatAPIFilterQuery } from "Stores/AlertStore";
|
||||
import { QueryOperators, StaticLabels, FormatQuery } from "Common/Query";
|
||||
import { DropdownSlide } from "Components/Animations/DropdownSlide";
|
||||
import { FetchPauser } from "Components/FetchPauser";
|
||||
|
||||
const onSilenceClick = (silenceFormStore, group) => {
|
||||
@@ -123,7 +124,7 @@ const GroupMenu = observer(
|
||||
</a>
|
||||
)}
|
||||
</Reference>
|
||||
{this.collapse.value ? null : (
|
||||
<DropdownSlide in={!this.collapse.value} unmountOnExit>
|
||||
<Popper
|
||||
placement="bottom-start"
|
||||
modifiers={{
|
||||
@@ -146,7 +147,7 @@ const GroupMenu = observer(
|
||||
/>
|
||||
)}
|
||||
</Popper>
|
||||
)}
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
.components-grid-alertgrid-alertgroup-fade-enter {
|
||||
opacity: 0.01;
|
||||
}
|
||||
|
||||
.components-grid-alertgrid-alertgroup-fade-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity 0.4s ease-in;
|
||||
}
|
||||
|
||||
.components-grid-alertgrid-alertgroup-fade-exit {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.components-grid-alertgrid-alertgroup-fade-exit-active {
|
||||
opacity: 0.01;
|
||||
transition: opacity 0.4s ease-in;
|
||||
}
|
||||
|
||||
.components-grid-alertgrid-alertgroup-fade-appear {
|
||||
opacity: 0.01;
|
||||
}
|
||||
|
||||
.components-grid-alertgrid-alertgroup-fade-appear-active {
|
||||
opacity: 1;
|
||||
transition: opacity 0.4s ease-in;
|
||||
}
|
||||
@@ -6,20 +6,17 @@ import { observable, action, toJS } from "mobx";
|
||||
|
||||
import hash from "object-hash";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
|
||||
import { faMinus } from "@fortawesome/free-solid-svg-icons/faMinus";
|
||||
|
||||
import { MountFade } from "Components/Animations/MountFade";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { GroupHeader } from "./GroupHeader";
|
||||
import { Alert } from "./Alert";
|
||||
import { GroupFooter } from "./GroupFooter";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const LoadButton = ({ icon, action }) => {
|
||||
return (
|
||||
<button type="button" className="btn btn-sm py-0 bg-white" onClick={action}>
|
||||
@@ -153,15 +150,7 @@ const AlertGroup = observer(
|
||||
}
|
||||
|
||||
return (
|
||||
<CSSTransition
|
||||
key={group.id}
|
||||
in={true}
|
||||
classNames="components-grid-alertgrid-alertgroup-fade"
|
||||
timeout={400}
|
||||
appear={true}
|
||||
enter={true}
|
||||
exit={true}
|
||||
>
|
||||
<MountFade>
|
||||
<div className="components-grid-alertgrid-alertgroup p-1">
|
||||
<div className="card">
|
||||
<div
|
||||
@@ -214,7 +203,7 @@ const AlertGroup = observer(
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
</MountFade>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { faCog } from "@fortawesome/free-solid-svg-icons/faCog";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { MountFade } from "Components/Animations/MountFade";
|
||||
import { MainModalContent } from "./MainModalContent";
|
||||
|
||||
const MainModal = observer(
|
||||
@@ -49,13 +50,13 @@ const MainModal = observer(
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</a>
|
||||
</li>
|
||||
{this.toggle.show ? (
|
||||
<MountFade in={this.toggle.show} unmountOnExit>
|
||||
<MainModalContent
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
onHide={this.toggle.hide}
|
||||
/>
|
||||
) : null}
|
||||
</MountFade>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
@@ -9,17 +9,15 @@ import { MainModal } from ".";
|
||||
let alertStore;
|
||||
let settingsStore;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
settingsStore = new Settings();
|
||||
});
|
||||
|
||||
const ShallowMainModal = () => {
|
||||
return shallow(
|
||||
<MainModal alertStore={alertStore} settingsStore={settingsStore} />
|
||||
);
|
||||
};
|
||||
|
||||
const MountedMainModal = () => {
|
||||
return mount(
|
||||
<MainModal alertStore={alertStore} settingsStore={settingsStore} />
|
||||
@@ -28,33 +26,46 @@ const MountedMainModal = () => {
|
||||
|
||||
describe("<MainModal />", () => {
|
||||
it("only renders FontAwesomeIcon when modal is not shown", () => {
|
||||
const tree = ShallowMainModal();
|
||||
expect(tree.text()).toBe("<FontAwesomeIcon />");
|
||||
const tree = MountedMainModal();
|
||||
expect(tree.find("FontAwesomeIcon")).toHaveLength(1);
|
||||
expect(tree.find("MainModalContent")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("renders the modal when it is shown", () => {
|
||||
const tree = ShallowMainModal();
|
||||
const tree = MountedMainModal();
|
||||
const toggle = tree.find(".nav-link");
|
||||
toggle.simulate("click");
|
||||
expect(tree.text()).toBe("<FontAwesomeIcon /><MainModalContent />");
|
||||
expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0);
|
||||
expect(tree.find("MainModalContent")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("hides the modal when toggle() is called twice", () => {
|
||||
const tree = ShallowMainModal();
|
||||
const tree = MountedMainModal();
|
||||
const toggle = tree.find(".nav-link");
|
||||
|
||||
toggle.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
tree.update();
|
||||
expect(tree.find("MainModalContent")).toHaveLength(1);
|
||||
|
||||
toggle.simulate("click");
|
||||
expect(tree.text()).toBe("<FontAwesomeIcon />");
|
||||
jest.runOnlyPendingTimers();
|
||||
tree.update();
|
||||
expect(tree.find("MainModalContent")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("hides the modal when hide() is called", () => {
|
||||
const tree = ShallowMainModal();
|
||||
const tree = MountedMainModal();
|
||||
const toggle = tree.find(".nav-link");
|
||||
|
||||
toggle.simulate("click");
|
||||
expect(tree.text()).toBe("<FontAwesomeIcon /><MainModalContent />");
|
||||
expect(tree.find("MainModalContent")).toHaveLength(1);
|
||||
|
||||
const instance = tree.instance();
|
||||
instance.toggle.hide();
|
||||
expect(tree.text()).toBe("<FontAwesomeIcon />");
|
||||
jest.runOnlyPendingTimers();
|
||||
tree.update();
|
||||
expect(tree.find("MainModalContent")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("'modal-open' class is appended to body node when modal is visible", () => {
|
||||
|
||||
@@ -19,6 +19,7 @@ import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { DropdownSlide } from "Components/Animations/DropdownSlide";
|
||||
import { HistoryLabel } from "Components/Labels/HistoryLabel";
|
||||
|
||||
import "./History.css";
|
||||
@@ -249,7 +250,7 @@ const History = observer(
|
||||
</button>
|
||||
)}
|
||||
</Reference>
|
||||
{this.collapse.value ? null : (
|
||||
<DropdownSlide in={!this.collapse.value} unmountOnExit>
|
||||
<Popper
|
||||
modifiers={{
|
||||
arrow: { enabled: false }
|
||||
@@ -270,7 +271,7 @@ const History = observer(
|
||||
/>
|
||||
)}
|
||||
</Popper>
|
||||
)}
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { observer } from "mobx-react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
|
||||
|
||||
import { MountFade } from "Components/Animations/MountFade";
|
||||
import { SilenceModalContent } from "./SilenceModalContent";
|
||||
|
||||
import "./index.css";
|
||||
@@ -53,14 +54,14 @@ const SilenceModal = observer(
|
||||
<FontAwesomeIcon icon={faBellSlash} />
|
||||
</a>
|
||||
</li>
|
||||
{silenceFormStore.toggle.visible ? (
|
||||
<MountFade in={silenceFormStore.toggle.visible} unmountOnExit>
|
||||
<SilenceModalContent
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
settingsStore={settingsStore}
|
||||
onHide={this.toggleModal}
|
||||
/>
|
||||
) : null}
|
||||
</MountFade>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
@@ -12,6 +12,7 @@ let settingsStore;
|
||||
let silenceFormStore;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
fetch.mockResponse(JSON.stringify([]));
|
||||
});
|
||||
|
||||
@@ -21,16 +22,6 @@ beforeEach(() => {
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
});
|
||||
|
||||
const ShallowSilenceModal = () => {
|
||||
return shallow(
|
||||
<SilenceModal
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
settingsStore={settingsStore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const MountedSilenceModal = () => {
|
||||
return mount(
|
||||
<SilenceModal
|
||||
@@ -43,32 +34,47 @@ const MountedSilenceModal = () => {
|
||||
|
||||
describe("<SilenceModal />", () => {
|
||||
it("only renders FontAwesomeIcon when modal is not shown", () => {
|
||||
const tree = ShallowSilenceModal();
|
||||
expect(tree.text()).toBe("<FontAwesomeIcon />");
|
||||
const tree = MountedSilenceModal();
|
||||
expect(tree.find("FontAwesomeIcon")).toHaveLength(1);
|
||||
expect(tree.find("SilenceModalContent")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("renders the modal when it is shown", () => {
|
||||
const tree = ShallowSilenceModal();
|
||||
const tree = MountedSilenceModal();
|
||||
const toggle = tree.find(".nav-link");
|
||||
toggle.simulate("click");
|
||||
expect(tree.text()).toBe("<FontAwesomeIcon /><SilenceModalContent />");
|
||||
expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0);
|
||||
expect(tree.find("SilenceModalContent")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("hides the modal when toggle() is called twice", () => {
|
||||
const tree = ShallowSilenceModal();
|
||||
const tree = MountedSilenceModal();
|
||||
const toggle = tree.find(".nav-link");
|
||||
|
||||
toggle.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
tree.update();
|
||||
expect(tree.find("SilenceModalContent")).toHaveLength(1);
|
||||
|
||||
toggle.simulate("click");
|
||||
expect(tree.text()).toBe("<FontAwesomeIcon />");
|
||||
jest.runOnlyPendingTimers();
|
||||
tree.update();
|
||||
expect(tree.find("SilenceModalContent")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("hides the modal when hide() is called", () => {
|
||||
const tree = ShallowSilenceModal();
|
||||
const tree = MountedSilenceModal();
|
||||
const toggle = tree.find(".nav-link");
|
||||
|
||||
toggle.simulate("click");
|
||||
expect(tree.text()).toBe("<FontAwesomeIcon /><SilenceModalContent />");
|
||||
jest.runOnlyPendingTimers();
|
||||
tree.update();
|
||||
expect(tree.find("SilenceModalContent")).toHaveLength(1);
|
||||
|
||||
silenceFormStore.toggle.hide();
|
||||
expect(tree.text()).toBe("<FontAwesomeIcon />");
|
||||
jest.runOnlyPendingTimers();
|
||||
tree.update();
|
||||
expect(tree.find("SilenceModalContent")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("'modal-open' class is appended to body node when modal is visible", () => {
|
||||
|
||||
Reference in New Issue
Block a user