mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 17:05:18 +00:00
Refine and improve policy and query editing interface (#4004)
This commit is contained in:
parent
2084b7d310
commit
14d36d8e4d
1
changes/issue-3099-3195-improve-query-editing
Normal file
1
changes/issue-3099-3195-improve-query-editing
Normal file
@ -0,0 +1 @@
|
||||
* Refine and improve query and policy editing interface
|
@ -0,0 +1,49 @@
|
||||
import React, { KeyboardEvent } from "react";
|
||||
import { Meta, Story } from "@storybook/react";
|
||||
import { noop } from "lodash";
|
||||
|
||||
// @ts-ignore
|
||||
import AutoSizeInputField from ".";
|
||||
|
||||
import "../../../../index.scss";
|
||||
|
||||
interface IAutoSizeInputFieldProps {
|
||||
name: string;
|
||||
placeholder: string;
|
||||
value: string;
|
||||
inputClassName?: string;
|
||||
hasError?: boolean;
|
||||
isDisabled?: boolean;
|
||||
isFocused?: boolean;
|
||||
onFocus: () => void;
|
||||
onBlur: () => void;
|
||||
onChange: (newSelectedValue: string) => void;
|
||||
onKeyPress: (event: KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||
}
|
||||
|
||||
export default {
|
||||
component: AutoSizeInputField,
|
||||
title: "Components/FormFields/Input",
|
||||
args: {
|
||||
autofocus: false,
|
||||
disabled: false,
|
||||
isFocused: false,
|
||||
error: "",
|
||||
inputClassName: "",
|
||||
inputWrapperClass: "",
|
||||
inputOptions: "",
|
||||
name: "",
|
||||
placeholder: "Type here...",
|
||||
type: "",
|
||||
value: "",
|
||||
onFocus: noop,
|
||||
onChange: noop,
|
||||
onKeyPress: noop,
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
const Template: Story<IAutoSizeInputFieldProps> = (props) => (
|
||||
<AutoSizeInputField {...props} />
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
@ -0,0 +1,102 @@
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
KeyboardEvent,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
interface IAutoSizeInputFieldProps {
|
||||
name: string;
|
||||
placeholder: string;
|
||||
value: string;
|
||||
inputClassName?: string;
|
||||
hasError?: boolean;
|
||||
isDisabled?: boolean;
|
||||
isFocused?: boolean;
|
||||
onFocus: () => void;
|
||||
onBlur: () => void;
|
||||
onChange: (newSelectedValue: string) => void;
|
||||
onKeyPress: (event: KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||
}
|
||||
|
||||
const baseClass = "component__auto-size-input-field";
|
||||
|
||||
const TeamsDropdown = ({
|
||||
name,
|
||||
placeholder,
|
||||
value,
|
||||
inputClassName,
|
||||
hasError,
|
||||
isDisabled,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onChange,
|
||||
onKeyPress,
|
||||
isFocused,
|
||||
}: IAutoSizeInputFieldProps): JSX.Element => {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
|
||||
const inputClasses = classnames(baseClass, inputClassName, "no-hover", {
|
||||
[`${baseClass}--disabled`]: isDisabled,
|
||||
[`${baseClass}--error`]: hasError,
|
||||
[`${baseClass}__textarea`]: true,
|
||||
});
|
||||
|
||||
const inputElement = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
onChange(inputValue);
|
||||
}, [inputValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused && inputElement.current) {
|
||||
inputElement.current.focus();
|
||||
inputElement.current.selectionStart = inputValue.length;
|
||||
inputElement.current.selectionEnd = inputValue.length;
|
||||
}
|
||||
}, [isFocused]);
|
||||
|
||||
const onInputChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setInputValue(event.currentTarget.value);
|
||||
};
|
||||
|
||||
const onInputFocus = () => {
|
||||
isFocused = true;
|
||||
onFocus();
|
||||
};
|
||||
|
||||
const onInputBlur = () => {
|
||||
isFocused = false;
|
||||
onBlur();
|
||||
};
|
||||
|
||||
const onInputKeyPress = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
onKeyPress(event);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<label className="input-sizer" data-value={inputValue} htmlFor={name}>
|
||||
<textarea
|
||||
name={name}
|
||||
id={name}
|
||||
onChange={onInputChange}
|
||||
placeholder={placeholder}
|
||||
value={inputValue}
|
||||
className={inputClasses}
|
||||
cols={12}
|
||||
rows={1}
|
||||
tabIndex={0}
|
||||
onFocus={onInputFocus}
|
||||
onBlur={onInputBlur}
|
||||
onKeyPress={onInputKeyPress}
|
||||
ref={inputElement}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamsDropdown;
|
@ -0,0 +1,42 @@
|
||||
.component__auto-size-input-field {
|
||||
box-sizing: border-box;
|
||||
color: $core-fleet-black;
|
||||
|
||||
&::placeholder {
|
||||
color: $ui-fleet-black-50;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $core-vibrant-blue;
|
||||
}
|
||||
|
||||
.input-sizer {
|
||||
display: inline-grid;
|
||||
vertical-align: top;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&::after,
|
||||
input,
|
||||
textarea {
|
||||
width: auto;
|
||||
grid-area: 1 / 2;
|
||||
resize: none;
|
||||
background: none;
|
||||
appearance: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: attr(data-value) " ";
|
||||
visibility: hidden;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { default } from "./AutoSizeInputField";
|
@ -1,9 +1,10 @@
|
||||
/* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role */
|
||||
/* eslint-disable jsx-a11y/interactive-supports-focus */
|
||||
import React, { useState, useContext } from "react";
|
||||
import React, { useState, useContext, KeyboardEvent } from "react";
|
||||
import { IAceEditor } from "react-ace/lib/types";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import { isUndefined } from "lodash";
|
||||
import classnames from "classnames";
|
||||
|
||||
import { addGravatarUrlToResource } from "fleet/helpers";
|
||||
// @ts-ignore
|
||||
@ -18,6 +19,7 @@ import FleetAce from "components/FleetAce";
|
||||
import Button from "components/buttons/Button";
|
||||
import Checkbox from "components/forms/fields/Checkbox";
|
||||
import Spinner from "components/Spinner";
|
||||
import AutoSizeInputField from "components/forms/fields/AutoSizeInputField";
|
||||
// @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import NewPolicyModal from "../NewPolicyModal";
|
||||
@ -52,7 +54,7 @@ const PolicyForm = ({
|
||||
onOpenSchemaSidebar,
|
||||
renderLiveQueryWarning,
|
||||
}: IPolicyFormProps): JSX.Element => {
|
||||
const [errors, setErrors] = useState<{ [key: string]: string }>({});
|
||||
const [errors, setErrors] = useState<{ [key: string]: any }>({});
|
||||
const [isNewPolicyModalOpen, setIsNewPolicyModalOpen] = useState<boolean>(
|
||||
false
|
||||
);
|
||||
@ -87,7 +89,6 @@ const PolicyForm = ({
|
||||
} = useContext(PolicyContext);
|
||||
|
||||
const {
|
||||
currentTeam,
|
||||
currentUser,
|
||||
isTeamObserver,
|
||||
isGlobalObserver,
|
||||
@ -150,6 +151,16 @@ const PolicyForm = ({
|
||||
setLastEditedQueryBody(sqlString);
|
||||
};
|
||||
|
||||
const onInputKeypress = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (event.key.toLowerCase() === "enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.blur();
|
||||
setIsEditingName(false);
|
||||
setIsEditingDescription(false);
|
||||
setIsEditingResolution(false);
|
||||
}
|
||||
};
|
||||
|
||||
const promptSavePolicy = (forceNew = false) => (
|
||||
evt: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
@ -230,41 +241,44 @@ const PolicyForm = ({
|
||||
);
|
||||
};
|
||||
|
||||
const policyNameClasses = classnames("policy-name-wrapper", {
|
||||
[`${baseClass}--editing`]: isEditingName,
|
||||
});
|
||||
|
||||
const policyDescriptionClasses = classnames("policy-description-wrapper", {
|
||||
[`${baseClass}--editing`]: isEditingDescription,
|
||||
});
|
||||
|
||||
const policyResolutionClasses = classnames("policy-resolution-wrapper", {
|
||||
[`${baseClass}--editing`]: isEditingResolution,
|
||||
});
|
||||
|
||||
const renderName = () => {
|
||||
if (isEditMode) {
|
||||
if (isEditingName) {
|
||||
return (
|
||||
<InputField
|
||||
id="policy-name"
|
||||
type="textarea"
|
||||
name="policy-name"
|
||||
error={errors.name}
|
||||
value={lastEditedQueryName}
|
||||
placeholder="Add name here"
|
||||
inputClassName={`${baseClass}__policy-name`}
|
||||
onChange={setLastEditedQueryName}
|
||||
inputOptions={{
|
||||
autoFocus: true,
|
||||
onFocus: (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
// sets cursor to end of inputfield
|
||||
const val = e.target.value;
|
||||
e.target.value = "";
|
||||
e.target.value = val;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<h1
|
||||
role="button"
|
||||
className={`${baseClass}__policy-name`}
|
||||
onClick={() => setIsEditingName(true)}
|
||||
>
|
||||
{lastEditedQueryName}
|
||||
<img alt="Edit name" src={PencilIcon} />
|
||||
</h1>
|
||||
<>
|
||||
<div className={policyNameClasses}>
|
||||
<AutoSizeInputField
|
||||
name="policy-name"
|
||||
placeholder="Add name here"
|
||||
value={lastEditedQueryName}
|
||||
hasError={errors && errors.name}
|
||||
inputClassName={`${baseClass}__policy-name`}
|
||||
onChange={setLastEditedQueryName}
|
||||
onFocus={() => setIsEditingName(true)}
|
||||
onBlur={() => setIsEditingName(false)}
|
||||
onKeyPress={onInputKeypress}
|
||||
isFocused={isEditingName}
|
||||
/>
|
||||
<a className="edit-link" onClick={() => setIsEditingName(true)}>
|
||||
<img
|
||||
className={`edit-icon ${isEditingName && "hide"}`}
|
||||
alt="Edit name"
|
||||
src={PencilIcon}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -273,38 +287,32 @@ const PolicyForm = ({
|
||||
|
||||
const renderDescription = () => {
|
||||
if (isEditMode) {
|
||||
if (isEditingDescription) {
|
||||
return (
|
||||
<InputField
|
||||
id="policy-description"
|
||||
type="textarea"
|
||||
name="policy-description"
|
||||
value={lastEditedQueryDescription}
|
||||
placeholder="Add description here."
|
||||
inputClassName={`${baseClass}__policy-description`}
|
||||
onChange={setLastEditedQueryDescription}
|
||||
inputOptions={{
|
||||
autoFocus: true,
|
||||
onFocus: (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
// sets cursor to end of inputfield
|
||||
const val = e.target.value;
|
||||
e.target.value = "";
|
||||
e.target.value = val;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
role="button"
|
||||
className={`${baseClass}__policy-description`}
|
||||
onClick={() => setIsEditingDescription(true)}
|
||||
>
|
||||
{lastEditedQueryDescription || "Add description here."}
|
||||
<img alt="Edit description" src={PencilIcon} />
|
||||
</span>
|
||||
<>
|
||||
<div className={policyDescriptionClasses}>
|
||||
<AutoSizeInputField
|
||||
name="policy-description"
|
||||
placeholder="Add description here."
|
||||
value={lastEditedQueryDescription}
|
||||
inputClassName={`${baseClass}__policy-description`}
|
||||
onChange={setLastEditedQueryDescription}
|
||||
onFocus={() => setIsEditingDescription(true)}
|
||||
onBlur={() => setIsEditingDescription(false)}
|
||||
onKeyPress={onInputKeypress}
|
||||
isFocused={isEditingDescription}
|
||||
/>
|
||||
<a
|
||||
className="edit-link"
|
||||
onClick={() => setIsEditingDescription(true)}
|
||||
>
|
||||
<img
|
||||
className={`edit-icon ${isEditingDescription && "hide"}`}
|
||||
alt="Edit name"
|
||||
src={PencilIcon}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -313,52 +321,33 @@ const PolicyForm = ({
|
||||
|
||||
const renderResolution = () => {
|
||||
if (isEditMode) {
|
||||
if (isEditingResolution) {
|
||||
return (
|
||||
<div className={`${baseClass}__policy-resolve`}>
|
||||
{" "}
|
||||
<b>Resolve:</b> <br />
|
||||
<InputField
|
||||
id="policy-resolution"
|
||||
type="textarea"
|
||||
name="policy-resolution"
|
||||
value={lastEditedQueryResolution}
|
||||
placeholder="Add resolution here."
|
||||
inputClassName={`${baseClass}__policy-resolution`}
|
||||
onChange={setLastEditedQueryResolution}
|
||||
inputOptions={{
|
||||
autoFocus: true,
|
||||
onFocus: (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
// sets cursor to end of inputfield
|
||||
const val = e.target.value;
|
||||
e.target.value = "";
|
||||
e.target.value = val;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="resolve-text-wrapper">
|
||||
<b>Resolve:</b>{" "}
|
||||
<span
|
||||
role="button"
|
||||
className={`${baseClass}__policy-resolution`}
|
||||
<p className="resolve-title">
|
||||
<strong>Resolve:</strong>
|
||||
</p>
|
||||
<div className={policyResolutionClasses}>
|
||||
<AutoSizeInputField
|
||||
name="policy-resolution"
|
||||
placeholder="Add resolution here."
|
||||
value={lastEditedQueryResolution}
|
||||
inputClassName={`${baseClass}__policy-resolution`}
|
||||
onChange={setLastEditedQueryResolution}
|
||||
onFocus={() => setIsEditingResolution(true)}
|
||||
onBlur={() => setIsEditingResolution(false)}
|
||||
onKeyPress={onInputKeypress}
|
||||
isFocused={isEditingResolution}
|
||||
/>
|
||||
<a
|
||||
className="edit-link"
|
||||
onClick={() => setIsEditingResolution(true)}
|
||||
>
|
||||
<img alt="Edit resolution" src={PencilIcon} />
|
||||
</span>
|
||||
<br />
|
||||
<span
|
||||
role="button"
|
||||
className={`${baseClass}__policy-resolution`}
|
||||
onClick={() => setIsEditingResolution(true)}
|
||||
>
|
||||
{lastEditedQueryResolution || "Add resolution here."}
|
||||
</span>
|
||||
<img
|
||||
className={`edit-icon ${isEditingResolution && "hide"}`}
|
||||
alt="Edit name"
|
||||
src={PencilIcon}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -39,27 +39,63 @@
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.edit-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.name-description-resolve {
|
||||
flex-grow: 1;
|
||||
margin-right: 24px;
|
||||
.policy-form__policy-name {
|
||||
margin: $pad-large 0;
|
||||
line-height: 2rem;
|
||||
height: 2rem;
|
||||
margin: $pad-medium 24px 0 0;
|
||||
.policy-name-wrapper,
|
||||
.policy-description-wrapper,
|
||||
.policy-resolution-wrapper {
|
||||
display: flex;
|
||||
&:not(.policy-form--editing) {
|
||||
textarea:hover {
|
||||
cursor: pointer;
|
||||
color: $core-vibrant-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
.policy-name-wrapper {
|
||||
.edit-icon {
|
||||
position: relative;
|
||||
left: 3px;
|
||||
top: 13px;
|
||||
}
|
||||
.policy-form__policy-name,
|
||||
.input-sizer::after {
|
||||
font-size: $large;
|
||||
}
|
||||
.component__auto-size-input-field {
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 2.3rem;
|
||||
padding-top: 2px;
|
||||
}
|
||||
}
|
||||
.policy-description-wrapper {
|
||||
padding-top: $pad-small;
|
||||
.edit-icon {
|
||||
position: relative;
|
||||
left: 3px;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.edit-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s;
|
||||
&.hide {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.policy-form__policy-resolve,
|
||||
.resolve-text-wrapper {
|
||||
margin-top: $pad-large;
|
||||
}
|
||||
#policy-description {
|
||||
height: 38px;
|
||||
}
|
||||
textarea.policy-form__policy-description {
|
||||
margin-top: 0.8125rem;
|
||||
}
|
||||
img {
|
||||
height: 14px;
|
||||
padding-left: $pad-small;
|
||||
.resolve-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +151,6 @@
|
||||
}
|
||||
|
||||
&__policy-name {
|
||||
margin-top: $pad-large;
|
||||
font-size: $large;
|
||||
|
||||
&.input-field--error {
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React, { useState, useContext, useEffect } from "react";
|
||||
import React, { useState, useContext, useEffect, KeyboardEvent } from "react";
|
||||
import { IAceEditor } from "react-ace/lib/types";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import { size } from "lodash";
|
||||
import { useDebouncedCallback } from "use-debounce/lib";
|
||||
import classnames from "classnames";
|
||||
|
||||
import { addGravatarUrlToResource } from "fleet/helpers";
|
||||
// @ts-ignore
|
||||
@ -13,12 +14,14 @@ import { QueryContext } from "context/query";
|
||||
import { IQuery, IQueryFormData } from "interfaces/query";
|
||||
|
||||
import Avatar from "components/Avatar";
|
||||
import FleetAce from "components/FleetAce"; // @ts-ignore
|
||||
import FleetAce from "components/FleetAce";
|
||||
// @ts-ignore
|
||||
import validateQuery from "components/forms/validators/validate_query";
|
||||
import Button from "components/buttons/Button";
|
||||
import Checkbox from "components/forms/fields/Checkbox";
|
||||
import Spinner from "components/Spinner"; // @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import Spinner from "components/Spinner";
|
||||
// @ts-ignore
|
||||
import AutoSizeInputField from "components/forms/fields/AutoSizeInputField";
|
||||
import NewQueryModal from "../NewQueryModal";
|
||||
import PlatformCompatibility from "../PlatformCompatibility";
|
||||
import InfoIcon from "../../../../../../assets/images/icon-info-purple-14x14@2x.png";
|
||||
@ -154,6 +157,15 @@ const QueryForm = ({
|
||||
setLastEditedQueryBody(sqlString);
|
||||
};
|
||||
|
||||
const onInputKeypress = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (event.key.toLowerCase() === "enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.blur();
|
||||
setIsEditingName(false);
|
||||
setIsEditingDescription(false);
|
||||
}
|
||||
};
|
||||
|
||||
const promptSaveQuery = (forceNew = false) => (
|
||||
evt: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
@ -233,48 +245,41 @@ const QueryForm = ({
|
||||
return <PlatformCompatibility compatiblePlatforms={compatiblePlatforms} />;
|
||||
};
|
||||
|
||||
const queryNameClasses = classnames("query-name-wrapper", {
|
||||
[`${baseClass}--editing`]: isEditingName,
|
||||
});
|
||||
|
||||
const queryDescriptionClasses = classnames("query-description-wrapper", {
|
||||
[`${baseClass}--editing`]: isEditingDescription,
|
||||
});
|
||||
|
||||
const renderName = () => {
|
||||
if (isEditMode) {
|
||||
if (isEditingName) {
|
||||
return (
|
||||
<InputField
|
||||
id="query-name"
|
||||
type="textarea"
|
||||
name="query-name"
|
||||
error={errors.name}
|
||||
value={lastEditedQueryName}
|
||||
placeholder="Add name here"
|
||||
inputClassName={`${baseClass}__query-name`}
|
||||
onChange={setLastEditedQueryName}
|
||||
inputOptions={{
|
||||
autoFocus: true,
|
||||
onFocus: (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
// sets cursor to end of inputfield
|
||||
const val = e.target.value;
|
||||
e.target.value = "";
|
||||
e.target.value = val;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/* eslint-disable */
|
||||
// eslint complains about the button role
|
||||
// applied to H1 - this is needed to avoid
|
||||
// using a real button
|
||||
// prettier-ignore
|
||||
return (
|
||||
<h1
|
||||
role="button"
|
||||
className={`${baseClass}__query-name`}
|
||||
onClick={() => setIsEditingName(true)}
|
||||
>
|
||||
{lastEditedQueryName}
|
||||
<img alt="Edit name" src={PencilIcon} />
|
||||
</h1>
|
||||
<>
|
||||
<div className={queryNameClasses}>
|
||||
<AutoSizeInputField
|
||||
name="query-name"
|
||||
placeholder="Add name here"
|
||||
value={lastEditedQueryName}
|
||||
hasError={errors && errors.name}
|
||||
inputClassName={`${baseClass}__query-name`}
|
||||
onChange={setLastEditedQueryName}
|
||||
onFocus={() => setIsEditingName(true)}
|
||||
onBlur={() => setIsEditingName(false)}
|
||||
onKeyPress={onInputKeypress}
|
||||
isFocused={isEditingName}
|
||||
/>
|
||||
<a className="edit-link" onClick={() => setIsEditingName(true)}>
|
||||
<img
|
||||
className={`edit-icon ${isEditingName && "hide"}`}
|
||||
alt="Edit name"
|
||||
src={PencilIcon}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
return <h1 className={`${baseClass}__query-name no-hover`}>New query</h1>;
|
||||
@ -282,47 +287,34 @@ const QueryForm = ({
|
||||
|
||||
const renderDescription = () => {
|
||||
if (isEditMode) {
|
||||
if (isEditingDescription) {
|
||||
return (
|
||||
<InputField
|
||||
id="query-description"
|
||||
type="textarea"
|
||||
name="query-description"
|
||||
value={lastEditedQueryDescription}
|
||||
placeholder="Add description here."
|
||||
inputClassName={`${baseClass}__query-description`}
|
||||
onChange={setLastEditedQueryDescription}
|
||||
inputOptions={{
|
||||
autoFocus: true,
|
||||
onFocus: (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
// sets cursor to end of inputfield
|
||||
const val = e.target.value;
|
||||
e.target.value = "";
|
||||
e.target.value = val;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/* eslint-disable */
|
||||
// eslint complains about the button role
|
||||
// applied to span - this is needed to avoid
|
||||
// using a real button
|
||||
// prettier-ignore
|
||||
return (
|
||||
<span
|
||||
role="button"
|
||||
className={`${baseClass}__query-description`}
|
||||
onClick={() => setIsEditingDescription(true)}
|
||||
>
|
||||
{lastEditedQueryDescription}
|
||||
<img alt="Edit description" src={PencilIcon} />
|
||||
</span>
|
||||
<>
|
||||
<div className={queryDescriptionClasses}>
|
||||
<AutoSizeInputField
|
||||
name="query-description"
|
||||
placeholder="Add description here."
|
||||
value={lastEditedQueryDescription}
|
||||
inputClassName={`${baseClass}__query-description`}
|
||||
onChange={setLastEditedQueryDescription}
|
||||
onFocus={() => setIsEditingDescription(true)}
|
||||
onBlur={() => setIsEditingDescription(false)}
|
||||
onKeyPress={onInputKeypress}
|
||||
isFocused={isEditingDescription}
|
||||
/>
|
||||
<a
|
||||
className="edit-link"
|
||||
onClick={() => setIsEditingDescription(true)}
|
||||
>
|
||||
<img
|
||||
className={`edit-icon ${isEditingDescription && "hide"}`}
|
||||
alt="Edit name"
|
||||
src={PencilIcon}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
@ -39,24 +39,59 @@
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.edit-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.name-description {
|
||||
flex-grow: 1;
|
||||
margin-right: 24px;
|
||||
.query-form__query-name {
|
||||
margin-top: $pad-large;
|
||||
line-height: 2rem;
|
||||
height: 2rem;
|
||||
margin: $pad-medium 24px 0 0;
|
||||
.query-name-wrapper {
|
||||
display: flex;
|
||||
&:not(.query-form--editing) {
|
||||
textarea:hover {
|
||||
cursor: pointer;
|
||||
color: $core-vibrant-blue;
|
||||
}
|
||||
}
|
||||
.edit-icon {
|
||||
position: relative;
|
||||
left: 3px;
|
||||
top: 13px;
|
||||
}
|
||||
.query-form__query-name,
|
||||
.input-sizer::after {
|
||||
font-size: $large;
|
||||
}
|
||||
.component__auto-size-input-field {
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 2.3rem;
|
||||
padding-top: 2px;
|
||||
}
|
||||
}
|
||||
.query-form__query-description:not(textarea) {
|
||||
margin: 0.25rem 0 1rem;
|
||||
.query-description-wrapper {
|
||||
display: flex;
|
||||
padding-top: $pad-small;
|
||||
&:not(.query-form--editing) {
|
||||
textarea:hover {
|
||||
cursor: pointer;
|
||||
color: $core-vibrant-blue;
|
||||
}
|
||||
}
|
||||
.edit-icon {
|
||||
position: relative;
|
||||
left: 3px;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
textarea.query-form__query-description {
|
||||
margin-top: 0.8125rem;
|
||||
}
|
||||
img {
|
||||
transform: scale(0.5);
|
||||
position: relative;
|
||||
top: 6px;
|
||||
.edit-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s;
|
||||
&.hide {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +138,7 @@
|
||||
}
|
||||
|
||||
&__query-name {
|
||||
margin-top: $pad-large;
|
||||
margin-top: 0;
|
||||
font-size: $large;
|
||||
|
||||
&.input-field--error {
|
||||
|
Loading…
Reference in New Issue
Block a user