Fleet UI: Update the read-only SQL editor to appear non-interactive (#37764)

This commit is contained in:
RachelElysia 2025-12-31 09:38:12 -05:00 committed by GitHub
parent e171c02a03
commit 360a426224
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 119 additions and 9 deletions

View file

@ -0,0 +1 @@
- Fleet UI: Adjust the read-only SQL editor to appear non-interactive

View file

@ -19,6 +19,10 @@ import {
sqlKeyWords,
} from "utilities/sql_tools";
import { stringToClipboard } from "utilities/copy_text";
import Button from "components/buttons/Button";
import Icon from "components/Icon";
import "./mode";
import "./theme";
@ -45,6 +49,7 @@ export interface ISQLEditorProps {
onChange?: (value: string) => void;
handleSubmit?: () => void;
disabled?: boolean;
enableCopy?: boolean;
}
const baseClass = "sql-editor";
@ -71,13 +76,29 @@ const SQLEditor = ({
onChange,
handleSubmit = noop,
disabled = false,
/** Combine with readOnly to remove ability to select text */
enableCopy = false,
}: ISQLEditorProps): JSX.Element => {
const editorRef = useRef<ReactAce>(null);
const [copied, setCopied] = React.useState(false);
/** Keeps label actions clickable and removes all mouse/keyboard access/hover states of editor */
const isReadonlyCopy = _readOnly && enableCopy && !disabled;
const wrapperClass = classnames(className, wrapperClassName, baseClass, {
[`${baseClass}__wrapper--error`]: !!error,
[`${baseClass}__wrapper--disabled`]: disabled,
// This is for read only that has a copy button so we disallow selecting the text
[`${baseClass}__wrapper--readonly-copy`]: !!isReadonlyCopy,
});
const onClickCopy = () => {
stringToClipboard(value || "").then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
};
const fixHotkeys = (editor: IAceEditor) => {
editor.commands.removeCommand("gotoline");
editor.commands.removeCommand("find");
@ -219,6 +240,25 @@ const SQLEditor = ({
readOnly: true,
});
if (isReadonlyCopy) {
// keep Ace read-only and remove any selection
editor.setOption("readOnly", true);
editor.selection.on("changeSelection", () => {
editor.clearSelection();
});
// make the internal textarea unfocusable via keyboard
const textarea = editor.textInput?.getElement?.();
if (textarea) {
textarea.setAttribute("tabindex", "-1");
}
// if something still manages to focus it, immediately blur
editor.on("focus", () => {
editor.blur();
});
}
onLoad && onLoad(editor);
};
@ -237,23 +277,44 @@ const SQLEditor = ({
};
const renderLabel = useCallback(() => {
const labelText = error || label;
const labelClassName = classnames(`${baseClass}__label`, {
[`${baseClass}__label--error`]: !!error,
[`${baseClass}__label--with-action`]: !!labelActionComponent,
});
if (!label) {
return <></>;
}
const labelText = error || label;
const labelClassName = classnames(`${baseClass}__label`, {
[`${baseClass}__label--error`]: !!error,
[`${baseClass}__label--with-action`]:
!!labelActionComponent || enableCopy,
});
return (
<div className={labelClassName}>
{labelText}
{labelActionComponent}
<span className={`${baseClass}__label-text`}>{labelText}</span>
<div className={`${baseClass}__label-actions`}>
{labelActionComponent}
{enableCopy && (
<div className={`${baseClass}__copy-wrapper`}>
{copied && (
<span className={`${baseClass}__copied-confirmation`}>
Copied!
</span>
)}
<Button
variant="text-icon"
onClick={onClickCopy}
size="small"
iconStroke
>
Copy <Icon name="copy" />
</Button>
</div>
)}
</div>
</div>
);
}, [error, label, labelActionComponent]);
}, [error, label, labelActionComponent, enableCopy, copied]);
const renderHelpText = () => {
if (helpText) {

View file

@ -7,11 +7,34 @@
&--error {
color: $core-vibrant-red;
}
&--with-action {
display: flex;
justify-content: space-between;
align-items: center;
.sql-editor__label-text {
flex: 1 1 auto;
margin-right: $pad-small;
}
.sql-editor__label-actions {
display: flex;
align-items: center;
gap: $pad-xsmall;
flex: 0 0 auto;
}
.sql-editor__copy-wrapper {
display: flex;
align-items: center;
gap: $pad-xxsmall;
}
.sql-editor__copied-confirmation {
@include copy-message;
}
button {
height: initial; // aligning space between label and textarea
margin: -$pad-small 0;
@ -33,6 +56,29 @@
&--disabled {
@include disabled;
}
&--readonly-copy {
cursor: default;
.ace-fleet:hover {
border: 1px solid $ui-blue-gray;
}
// remove interaction on both code + gutter
.ace_scroller,
.ace_gutter {
pointer-events: auto;
cursor: not-allowed;
user-select: none;
}
// keep label actions (copy, etc.) usable
.sql-editor__label-actions,
.sql-editor__label-actions * {
pointer-events: auto;
cursor: pointer;
}
}
}
.ace_scroller {

View file

@ -140,6 +140,7 @@ const DynamicLabelForm = ({
onLoad={onLoad}
wrapperClassName={`${baseClass}__text-editor-wrapper form-field`}
wrapEnabled
enableCopy={isEditing}
/>
<PlatformField
platform={platform}

View file

@ -207,6 +207,7 @@ $max-width: 2560px;
border: solid 1px #e2e4ea;
border-radius: 10px;
padding: 2px 6px;
margin: -4px 0;
}
@mixin color-contrasted-sections {