fix: made button disabled state more congruent, made edit flow details submit on enter (#8339)
* Changed textarea classes * Changed flowsettingscomponent to use form * changed edit flow settings to use form and to submit on enter * Reset form data on close * Updated disabled state to have lower opacity instead of to have set background * Fixed loading state of button
This commit is contained in:
parent
4cbce28d26
commit
97dd5b361c
6 changed files with 135 additions and 83 deletions
|
|
@ -197,7 +197,10 @@ export const MenuBar = memo((): JSX.Element => {
|
|||
sideOffset={15}
|
||||
>
|
||||
<span className="text-sm font-semibold">Flow Details</span>
|
||||
<FlowSettingsComponent close={() => setOpenSettings(false)} />
|
||||
<FlowSettingsComponent
|
||||
close={() => setOpenSettings(false)}
|
||||
open={openSettings}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import React, { ChangeEvent, useState } from "react";
|
||||
import * as Form from "@radix-ui/react-form";
|
||||
import React, { useState } from "react";
|
||||
import { InputProps } from "../../../types/components";
|
||||
import { cn } from "../../../utils/utils";
|
||||
import { Input } from "../../ui/input";
|
||||
import { Label } from "../../ui/label";
|
||||
import { Textarea } from "../../ui/textarea";
|
||||
|
||||
export const EditFlowSettings: React.FC<InputProps> = ({
|
||||
export const EditFlowSettings: React.FC<
|
||||
InputProps & { submitForm?: () => void }
|
||||
> = ({
|
||||
name,
|
||||
invalidNameList = [],
|
||||
description,
|
||||
|
|
@ -14,13 +16,14 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
minLength = 1,
|
||||
setName,
|
||||
setDescription,
|
||||
}: InputProps): JSX.Element => {
|
||||
submitForm,
|
||||
}: InputProps & { submitForm?: () => void }): JSX.Element => {
|
||||
const [isMaxLength, setIsMaxLength] = useState(false);
|
||||
const [isMaxDescriptionLength, setIsMaxDescriptionLength] = useState(false);
|
||||
const [isMinLength, setIsMinLength] = useState(false);
|
||||
const [isInvalidName, setIsInvalidName] = useState(false);
|
||||
|
||||
const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
if (value.length >= maxLength) {
|
||||
setIsMaxLength(true);
|
||||
|
|
@ -41,16 +44,15 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
invalid = false;
|
||||
}
|
||||
setIsInvalidName(invalid);
|
||||
|
||||
setName!(value);
|
||||
|
||||
if (value.length === 0) {
|
||||
// For empty string, update state but keep isMinLength true
|
||||
setIsMinLength(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const handleDescriptionChange = (
|
||||
event: React.ChangeEvent<HTMLTextAreaElement>,
|
||||
) => {
|
||||
const { value } = event.target;
|
||||
if (value.length >= descriptionMaxLength) {
|
||||
setIsMaxDescriptionLength(true);
|
||||
|
|
@ -60,14 +62,25 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
setDescription!(value);
|
||||
};
|
||||
|
||||
//this function is necessary to select the text when double clicking, this was not working with the onFocus event
|
||||
const handleDescriptionKeyDown = (
|
||||
event: React.KeyboardEvent<HTMLTextAreaElement>,
|
||||
) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
if (submitForm) submitForm();
|
||||
}
|
||||
// else allow default (newline)
|
||||
};
|
||||
|
||||
const handleFocus = (event) => event.target.select();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Label>
|
||||
<Form.Field name="name">
|
||||
<div className="edit-flow-arrangement">
|
||||
<span className="text-mmd font-medium">Name{setName ? "" : ":"}</span>{" "}
|
||||
<Form.Label className="text-mmd font-medium">
|
||||
Name{setName ? "" : ":"}
|
||||
</Form.Label>
|
||||
{isMaxLength && (
|
||||
<span className="edit-flow-span">Character limit reached</span>
|
||||
)}
|
||||
|
|
@ -81,52 +94,63 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
)}
|
||||
</div>
|
||||
{setName ? (
|
||||
<Input
|
||||
className="nopan nodelete nodrag noflow mt-2 font-normal"
|
||||
onChange={handleNameChange}
|
||||
type="text"
|
||||
name="name"
|
||||
value={name ?? ""}
|
||||
placeholder="Flow name"
|
||||
id="name"
|
||||
maxLength={maxLength}
|
||||
minLength={minLength}
|
||||
required={true}
|
||||
onDoubleClickCapture={(event) => {
|
||||
handleFocus(event);
|
||||
}}
|
||||
data-testid="input-flow-name"
|
||||
/>
|
||||
<Form.Control asChild>
|
||||
<Input
|
||||
className="nopan nodelete nodrag noflow mt-2 font-normal"
|
||||
onChange={handleNameChange}
|
||||
type="text"
|
||||
name="name"
|
||||
value={name ?? ""}
|
||||
placeholder="Flow name"
|
||||
id="name"
|
||||
maxLength={maxLength}
|
||||
minLength={minLength}
|
||||
required={true}
|
||||
onDoubleClickCapture={handleFocus}
|
||||
data-testid="input-flow-name"
|
||||
autoFocus
|
||||
/>
|
||||
</Form.Control>
|
||||
) : (
|
||||
<span className="font-normal text-muted-foreground word-break-break-word">
|
||||
{name}
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<Label>
|
||||
<Form.Message match="valueMissing" className="field-invalid">
|
||||
Please enter a name
|
||||
</Form.Message>
|
||||
<Form.Message
|
||||
match={(value) => !!(value && invalidNameList.includes(value))}
|
||||
className="field-invalid"
|
||||
>
|
||||
Flow name already exists
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
<Form.Field name="description">
|
||||
<div className="edit-flow-arrangement mt-3">
|
||||
<span className="text-mmd font-medium">
|
||||
<Form.Label className="text-mmd font-medium">
|
||||
Description{setDescription ? "" : ":"}
|
||||
</span>
|
||||
</Form.Label>
|
||||
{isMaxDescriptionLength && (
|
||||
<span className="edit-flow-span">Character limit reached</span>
|
||||
)}
|
||||
</div>
|
||||
{setDescription ? (
|
||||
<Textarea
|
||||
name="description"
|
||||
id="description"
|
||||
onChange={handleDescriptionChange}
|
||||
value={description!}
|
||||
placeholder="Flow description"
|
||||
data-testid="input-flow-description"
|
||||
className="mt-2 max-h-[250px] resize-none font-normal"
|
||||
rows={5}
|
||||
maxLength={descriptionMaxLength}
|
||||
onDoubleClickCapture={(event) => {
|
||||
handleFocus(event);
|
||||
}}
|
||||
/>
|
||||
<Form.Control asChild>
|
||||
<Textarea
|
||||
name="description"
|
||||
id="description"
|
||||
onChange={handleDescriptionChange}
|
||||
value={description!}
|
||||
placeholder="Flow description"
|
||||
data-testid="input-flow-description"
|
||||
className="mt-2 max-h-[250px] resize-none font-normal"
|
||||
rows={5}
|
||||
maxLength={descriptionMaxLength}
|
||||
onDoubleClickCapture={handleFocus}
|
||||
onKeyDown={handleDescriptionKeyDown}
|
||||
/>
|
||||
</Form.Control>
|
||||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -137,7 +161,10 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
{description === "" ? "No description" : description}
|
||||
</div>
|
||||
)}
|
||||
</Label>
|
||||
<Form.Message match="valueMissing" className="field-invalid">
|
||||
Please enter a description
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,16 +4,19 @@ import useAlertStore from "@/stores/alertStore";
|
|||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import { FlowType } from "@/types/flow";
|
||||
import * as Form from "@radix-ui/react-form";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import EditFlowSettings from "../editFlowSettingsComponent";
|
||||
|
||||
export default function FlowSettingsComponent({
|
||||
flowData,
|
||||
close,
|
||||
open,
|
||||
}: {
|
||||
flowData?: FlowType;
|
||||
close: () => void;
|
||||
open: boolean;
|
||||
}): JSX.Element {
|
||||
const saveFlow = useSaveFlow();
|
||||
const currentFlow = useFlowStore((state) =>
|
||||
|
|
@ -28,13 +31,15 @@ export default function FlowSettingsComponent({
|
|||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [disableSave, setDisableSave] = useState(true);
|
||||
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setName(flow?.name ?? "");
|
||||
setDescription(flow?.description ?? "");
|
||||
}, [flow?.name, flow?.description, flow?.endpoint_name, open]);
|
||||
|
||||
function handleClick(): void {
|
||||
function handleSubmit(event?: React.FormEvent<HTMLFormElement>): void {
|
||||
if (event) event.preventDefault();
|
||||
setIsSaving(true);
|
||||
if (!flow) return;
|
||||
const newFlow = cloneDeep(flow);
|
||||
|
|
@ -58,6 +63,10 @@ export default function FlowSettingsComponent({
|
|||
}
|
||||
}
|
||||
|
||||
const submitForm = () => {
|
||||
formRef.current?.requestSubmit();
|
||||
};
|
||||
|
||||
const [nameLists, setNameList] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -81,36 +90,41 @@ export default function FlowSettingsComponent({
|
|||
}
|
||||
}, [nameLists, flow, description, name]);
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<EditFlowSettings
|
||||
invalidNameList={nameLists}
|
||||
name={name}
|
||||
description={description}
|
||||
setName={setName}
|
||||
setDescription={setDescription}
|
||||
/>
|
||||
<Form.Root onSubmit={handleSubmit} ref={formRef}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<EditFlowSettings
|
||||
invalidNameList={nameLists}
|
||||
name={name}
|
||||
description={description}
|
||||
setName={setName}
|
||||
setDescription={setDescription}
|
||||
submitForm={submitForm}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
data-testid="cancel-flow-settings"
|
||||
type="button"
|
||||
onClick={() => close()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Form.Submit asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
data-testid="save-flow-settings"
|
||||
loading={isSaving}
|
||||
disabled={disableSave}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
data-testid="cancel-flow-settings"
|
||||
onClick={() => close()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
data-testid="save-flow-settings"
|
||||
onClick={handleClick}
|
||||
loading={isSaving}
|
||||
disabled={disableSave}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Root>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { cn } from "../../utils/utils";
|
|||
import ForwardedIconComponent from "../common/genericIconComponent";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"noflow nopan nodelete nodrag inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-100 disabled:disabled-state [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
"noflow nopan nodelete nodrag inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-70 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
|
@ -105,7 +105,14 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||
>
|
||||
{loading ? (
|
||||
<span className="relative flex items-center justify-center">
|
||||
<span className="invisible">{newChildren}</span>
|
||||
<span
|
||||
className={cn(
|
||||
className,
|
||||
"invisible flex items-center justify-center gap-2 !p-0",
|
||||
)}
|
||||
>
|
||||
{newChildren}
|
||||
</span>
|
||||
<span className="absolute inset-0 flex items-center justify-center">
|
||||
<ForwardedIconComponent
|
||||
name={"Loader2"}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export default function FlowSettingsModal({
|
|||
<FlowSettingsComponent
|
||||
flowData={flowData}
|
||||
close={() => setOpen(false)}
|
||||
open={open}
|
||||
/>
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@
|
|||
|
||||
/* The same as primary-input but no-truncate */
|
||||
.textarea-primary {
|
||||
@apply form-input block w-full rounded-md border border-border bg-background px-3 text-left shadow-sm placeholder:text-placeholder-foreground hover:border-muted focus:border-muted focus:placeholder-transparent focus:ring-[0.75px] focus:ring-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-secondary disabled:text-muted disabled:opacity-100 placeholder:disabled:text-muted-foreground sm:text-sm;
|
||||
@apply form-input block w-full rounded-md border border-border bg-background px-3 text-left text-sm placeholder:text-muted-foreground hover:border-muted-foreground focus:border-foreground focus:placeholder-transparent focus:ring-0 focus:ring-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100 placeholder:disabled:text-muted-foreground;
|
||||
}
|
||||
|
||||
.input-edit-node {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue