Merge pull request #52 from prymitive/animations

feat(ui): animate modals and dropdowns
This commit is contained in:
Łukasz Mierzwa
2018-09-22 15:44:05 +01:00
committed by GitHub
13 changed files with 176 additions and 86 deletions

View 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;
}

View 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 };

View 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;
}

View 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 };

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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;
}

View File

@@ -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>
);
}
}

View File

@@ -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>
);
}

View File

@@ -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", () => {

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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", () => {