mirror of
https://github.com/prymitive/karma
synced 2026-08-28 11:17:28 +00:00
fix(ui): use custom inline edit component
This commit is contained in:
committed by
Łukasz Mierzwa
parent
9cfc4f317b
commit
d3aaa5bff7
Generated
-9
@@ -4,15 +4,6 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@attently/riek": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@attently/riek/-/riek-2.0.1.tgz",
|
||||
"integrity": "sha1-AFJ4WlurHKepkjbxNWJNOeZhkCQ=",
|
||||
"requires": {
|
||||
"debug": "^2.6.8",
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@attently/riek": "2.0.1",
|
||||
"@fortawesome/fontawesome-common-types": "0.2.28",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.28",
|
||||
"@fortawesome/free-regular-svg-icons": "5.13.0",
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { useOnClickOutside } from "Hooks/useOnClickOutside";
|
||||
|
||||
const InlineEdit = ({
|
||||
className,
|
||||
classNameEditing,
|
||||
value,
|
||||
onChange,
|
||||
onEnterEditing,
|
||||
onExitEditing,
|
||||
}) => {
|
||||
const ref = useRef(null);
|
||||
const [editedValue, setEditedValue] = useState(null);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const startEditing = () => {
|
||||
if (onEnterEditing) {
|
||||
onEnterEditing();
|
||||
}
|
||||
setIsEditing(true);
|
||||
};
|
||||
|
||||
const doneEditing = () => {
|
||||
setIsEditing(false);
|
||||
setEditedValue(null);
|
||||
if (onExitEditing) {
|
||||
onExitEditing();
|
||||
}
|
||||
};
|
||||
|
||||
const onInput = (event) => {
|
||||
setEditedValue(event.target.value.trim());
|
||||
};
|
||||
|
||||
const onKeyDown = (event) => {
|
||||
if (event.keyCode === 13) {
|
||||
if (editedValue) {
|
||||
onChange(editedValue);
|
||||
}
|
||||
doneEditing();
|
||||
} else if (event.keyCode === 27) {
|
||||
doneEditing();
|
||||
}
|
||||
};
|
||||
|
||||
useOnClickOutside(ref, doneEditing);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing && ref.current) {
|
||||
ref.current.focus();
|
||||
}
|
||||
}, [isEditing, ref]);
|
||||
|
||||
if (isEditing) {
|
||||
const val = editedValue === null ? value : editedValue;
|
||||
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
type="text"
|
||||
className={classNameEditing}
|
||||
value={val}
|
||||
size={val.length + 1}
|
||||
onChange={onInput}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span tabIndex={0} className={className} onClick={startEditing}>
|
||||
{value}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
InlineEdit.propTypes = {
|
||||
className: PropTypes.string,
|
||||
classNameEditing: PropTypes.string,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onEnterEditing: PropTypes.func,
|
||||
onExitEditing: PropTypes.func,
|
||||
};
|
||||
|
||||
export { InlineEdit };
|
||||
@@ -0,0 +1,141 @@
|
||||
import React from "react";
|
||||
import { act } from "react-dom/test-utils";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { InlineEdit } from ".";
|
||||
|
||||
describe("<InlineEdit />", () => {
|
||||
it("renders span by default", () => {
|
||||
const tree = mount(<InlineEdit value="foo" onChange={jest.fn()} />);
|
||||
expect(tree.html()).toBe('<span tabindex="0">foo</span>');
|
||||
});
|
||||
|
||||
it("renders input after click", () => {
|
||||
const tree = mount(<InlineEdit value="foo" onChange={jest.fn()} />);
|
||||
tree.simulate("click");
|
||||
expect(tree.html()).toBe('<input type="text" size="4" value="foo">');
|
||||
});
|
||||
|
||||
it("edit mode start calls onEnterEditing", () => {
|
||||
const onEnterEditing = jest.fn();
|
||||
const tree = mount(
|
||||
<InlineEdit
|
||||
value="foo"
|
||||
onChange={jest.fn()}
|
||||
onEnterEditing={onEnterEditing}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(onEnterEditing).not.toHaveBeenCalled();
|
||||
|
||||
tree.simulate("click");
|
||||
expect(tree.html()).toBe('<input type="text" size="4" value="foo">');
|
||||
expect(onEnterEditing).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("edit mode finish calls onExitEditing", () => {
|
||||
const onExitEditing = jest.fn();
|
||||
const tree = mount(
|
||||
<div id="root">
|
||||
<button>click me</button>
|
||||
<InlineEdit
|
||||
value="foo"
|
||||
onChange={jest.fn()}
|
||||
onExitEditing={onExitEditing}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
expect(onExitEditing).not.toHaveBeenCalled();
|
||||
|
||||
tree.find("span").simulate("click");
|
||||
expect(onExitEditing).not.toHaveBeenCalled();
|
||||
|
||||
act(() => {
|
||||
document.dispatchEvent(
|
||||
new Event("mousedown", { target: tree.find("button").getDOMNode() })
|
||||
);
|
||||
});
|
||||
expect(tree.html()).not.toMatch(/<input/);
|
||||
expect(onExitEditing).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("cancels edits after click outside", () => {
|
||||
const tree = mount(
|
||||
<div id="root">
|
||||
<button>click me</button>
|
||||
<InlineEdit value="foo" onChange={jest.fn()} />
|
||||
</div>
|
||||
);
|
||||
|
||||
tree.find("span").simulate("click");
|
||||
expect(tree.html()).toMatch(/<input/);
|
||||
|
||||
act(() => {
|
||||
document.dispatchEvent(
|
||||
new Event("mousedown", { target: tree.find("button").getDOMNode() })
|
||||
);
|
||||
});
|
||||
expect(tree.html()).not.toMatch(/<input/);
|
||||
});
|
||||
|
||||
it("typing in the input changes value", () => {
|
||||
const tree = mount(<InlineEdit value="foo" onChange={jest.fn()} />);
|
||||
|
||||
tree.find("span").simulate("click");
|
||||
expect(tree.html()).toMatch(/<input/);
|
||||
|
||||
tree.simulate("change", { target: { value: "bar" } });
|
||||
expect(tree.html()).toBe('<input type="text" size="4" value="bar">');
|
||||
});
|
||||
|
||||
it("enter calls onChange if value was edited", () => {
|
||||
const onChange = jest.fn();
|
||||
const tree = mount(<InlineEdit value="foo" onChange={onChange} />);
|
||||
|
||||
tree.find("span").simulate("click");
|
||||
expect(tree.html()).toMatch(/<input/);
|
||||
|
||||
tree.simulate("change", { target: { value: "bar" } });
|
||||
expect(tree.html()).toBe('<input type="text" size="4" value="bar">');
|
||||
|
||||
tree.simulate("keyDown", { keyCode: 13 });
|
||||
expect(onChange).toHaveBeenCalledWith("bar");
|
||||
});
|
||||
|
||||
it("enter doesn't call onChange if value was not edited", () => {
|
||||
const onChange = jest.fn();
|
||||
const tree = mount(<InlineEdit value="foo" onChange={onChange} />);
|
||||
|
||||
tree.find("span").simulate("click");
|
||||
expect(tree.html()).toMatch(/<input/);
|
||||
|
||||
tree.simulate("keyDown", { keyCode: 13 });
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("esc cancels edit mode", () => {
|
||||
const onChange = jest.fn();
|
||||
const tree = mount(<InlineEdit value="foo" onChange={onChange} />);
|
||||
|
||||
tree.find("span").simulate("click");
|
||||
expect(tree.html()).toMatch(/<input/);
|
||||
|
||||
tree.simulate("keyDown", { keyCode: 27 });
|
||||
expect(tree.html()).not.toMatch(/<input/);
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("unknown keyDown does nothing", () => {
|
||||
const onChange = jest.fn();
|
||||
const tree = mount(<InlineEdit value="foo" onChange={onChange} />);
|
||||
|
||||
tree.find("span").simulate("click");
|
||||
expect(tree.html()).toMatch(/<input/);
|
||||
|
||||
tree.simulate("keyDown", { keyCode: 45 });
|
||||
expect(tree.html()).toMatch(/<input/);
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -3,8 +3,6 @@ import PropTypes from "prop-types";
|
||||
|
||||
import { useObserver } from "mobx-react";
|
||||
|
||||
import { RIEInput } from "@attently/riek";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
|
||||
@@ -14,15 +12,16 @@ import { AlertStore } from "Stores/AlertStore";
|
||||
import { QueryOperators } from "Common/Query";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { GetClassAndStyle } from "Components/Labels/Utils";
|
||||
import { InlineEdit } from "Components/InlineEdit";
|
||||
|
||||
const FilterInputLabel = ({ alertStore, filter }) => {
|
||||
const onChange = ({ raw }) => {
|
||||
const onChange = (val) => {
|
||||
// if filter is empty string then remove it
|
||||
if (raw === "") {
|
||||
if (val === "") {
|
||||
alertStore.filters.removeFilter(filter.raw);
|
||||
}
|
||||
// if not empty replace it
|
||||
alertStore.filters.replaceFilter(filter.raw, raw);
|
||||
alertStore.filters.replaceFilter(filter.raw, val);
|
||||
};
|
||||
|
||||
const cs = GetClassAndStyle(
|
||||
@@ -63,15 +62,13 @@ const FilterInputLabel = ({ alertStore, filter }) => {
|
||||
title="Click to edit this filter"
|
||||
className="components-filteredinputlabel-text flex-grow-1 flex-shrink-1 ml-1"
|
||||
>
|
||||
<RIEInput
|
||||
<InlineEdit
|
||||
className="cursor-text px-1"
|
||||
defaultValue=""
|
||||
classNameEditing="px-1 py-0 border-0 editing rounded"
|
||||
value={filter.raw}
|
||||
propName="raw"
|
||||
change={onChange}
|
||||
classEditing="py-0 border-0 editing rounded"
|
||||
afterStart={alertStore.status.pause}
|
||||
afterFinish={alertStore.status.resume}
|
||||
onChange={onChange}
|
||||
onEnterEditing={alertStore.status.pause}
|
||||
onExitEditing={alertStore.status.resume}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
<FontAwesomeIcon
|
||||
|
||||
@@ -49,8 +49,8 @@ const ValidateOnChange = (newRaw) => {
|
||||
/>
|
||||
);
|
||||
|
||||
const input = tree.find("RIEInput");
|
||||
input.props().change({ raw: newRaw });
|
||||
const input = tree.find("InlineEdit");
|
||||
input.props().onChange(newRaw);
|
||||
|
||||
return tree;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user