mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-25 20:15:01 +01:00
Pair validation
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import classnames from 'classnames';
|
||||
import type { FormEvent } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
||||
import { useSendRequest } from '../hooks/useSendRequest';
|
||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||
@@ -20,7 +20,6 @@ export const UrlBar = memo(function UrlBar({ request, className }: Props) {
|
||||
const handleMethodChange = useCallback((method: string) => updateRequest.mutate({ method }), []);
|
||||
const handleUrlChange = useCallback((url: string) => updateRequest.mutate({ url }), []);
|
||||
const loading = useIsResponseLoading(request.id);
|
||||
const useEditor = useMemo(() => ({ useTemplating: true, contentType: 'url' }), []);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (e: FormEvent) => {
|
||||
@@ -35,7 +34,8 @@ export const UrlBar = memo(function UrlBar({ request, className }: Props) {
|
||||
<Input
|
||||
key={request.id}
|
||||
hideLabel
|
||||
useEditor={useEditor}
|
||||
useTemplating
|
||||
contentType="url"
|
||||
className="px-0"
|
||||
name="url"
|
||||
label="Enter URL"
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import classnames from 'classnames';
|
||||
import { useMemo, useState } from 'react';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import type { EditorProps } from './Editor';
|
||||
import { Editor } from './Editor';
|
||||
import { HStack, VStack } from './Stacks';
|
||||
|
||||
type Props = Omit<HTMLAttributes<HTMLInputElement>, 'onChange' | 'onFocus'> & {
|
||||
name: string;
|
||||
label: string;
|
||||
hideLabel?: boolean;
|
||||
labelClassName?: string;
|
||||
containerClassName?: string;
|
||||
onChange?: (value: string) => void;
|
||||
onFocus?: () => void;
|
||||
useEditor?: Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocomplete'>;
|
||||
defaultValue?: string;
|
||||
leftSlot?: ReactNode;
|
||||
rightSlot?: ReactNode;
|
||||
size?: 'sm' | 'md';
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
autoFocus?: boolean;
|
||||
};
|
||||
type Props = Omit<HTMLAttributes<HTMLInputElement>, 'onChange' | 'onFocus'> &
|
||||
Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocomplete'> & {
|
||||
name: string;
|
||||
label: string;
|
||||
hideLabel?: boolean;
|
||||
labelClassName?: string;
|
||||
containerClassName?: string;
|
||||
onChange?: (value: string) => void;
|
||||
onFocus?: () => void;
|
||||
defaultValue?: string;
|
||||
leftSlot?: ReactNode;
|
||||
rightSlot?: ReactNode;
|
||||
size?: 'sm' | 'md';
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
autoFocus?: boolean;
|
||||
validate?: (v: string) => boolean;
|
||||
require?: boolean;
|
||||
};
|
||||
|
||||
export function Input({
|
||||
label,
|
||||
@@ -31,13 +34,15 @@ export function Input({
|
||||
onChange,
|
||||
placeholder,
|
||||
size = 'md',
|
||||
useEditor,
|
||||
name,
|
||||
leftSlot,
|
||||
rightSlot,
|
||||
defaultValue,
|
||||
validate,
|
||||
require,
|
||||
...props
|
||||
}: Props) {
|
||||
const [currentValue, setCurrentValue] = useState(defaultValue ?? '');
|
||||
const id = `input-${name}`;
|
||||
const inputClassName = classnames(
|
||||
className,
|
||||
@@ -46,6 +51,17 @@ export function Input({
|
||||
!!rightSlot && '!pr-0.5',
|
||||
);
|
||||
|
||||
const isValid = useMemo(() => {
|
||||
if (require && !validateRequire(currentValue)) return false;
|
||||
if (validate && !validate(currentValue)) return false;
|
||||
return true;
|
||||
}, [currentValue, validate, require]);
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
setCurrentValue(value);
|
||||
onChange?.(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<VStack>
|
||||
<label
|
||||
@@ -64,34 +80,27 @@ export function Input({
|
||||
containerClassName,
|
||||
'relative w-full rounded-md text-gray-900',
|
||||
'border border-gray-200 focus-within:border-focus',
|
||||
!isValid && 'border-invalid',
|
||||
size === 'md' && 'h-9',
|
||||
size === 'sm' && 'h-7',
|
||||
)}
|
||||
>
|
||||
{leftSlot}
|
||||
{useEditor ? (
|
||||
<Editor
|
||||
id={id}
|
||||
singleLine
|
||||
defaultValue={defaultValue}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
className={inputClassName}
|
||||
{...props}
|
||||
{...useEditor}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
id={id}
|
||||
onChange={(e) => onChange?.(e.currentTarget.value)}
|
||||
placeholder={placeholder}
|
||||
defaultValue={defaultValue}
|
||||
className={inputClassName}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
<Editor
|
||||
id={id}
|
||||
singleLine
|
||||
defaultValue={defaultValue}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
className={inputClassName}
|
||||
{...props}
|
||||
/>
|
||||
{rightSlot}
|
||||
</HStack>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
function validateRequire(v: string) {
|
||||
return v.length > 0;
|
||||
}
|
||||
|
||||
@@ -200,16 +200,6 @@ const FormRow = memo(function FormRow({
|
||||
[onChange, pairContainer.pair.name, pairContainer.pair.enabled],
|
||||
);
|
||||
|
||||
const nameEditorConfig = useMemo(
|
||||
() => ({ useTemplating: true, autocomplete: nameAutocomplete }),
|
||||
[nameAutocomplete],
|
||||
);
|
||||
|
||||
const valueEditorConfig = useMemo(
|
||||
() => ({ useTemplating: true, autocomplete: valueAutocomplete?.(pairContainer.pair.name) }),
|
||||
[valueAutocomplete, pairContainer.pair.name],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(() => onFocus?.(pairContainer), [onFocus, pairContainer]);
|
||||
const handleDelete = useCallback(() => onDelete?.(pairContainer), [onDelete, pairContainer]);
|
||||
|
||||
@@ -263,13 +253,15 @@ const FormRow = memo(function FormRow({
|
||||
<span className="w-1" />
|
||||
)}
|
||||
<Checkbox
|
||||
disabled={isLast}
|
||||
checked={!!pairContainer.pair.enabled}
|
||||
onChange={handleChangeEnabled}
|
||||
disabled={isLast || !pairContainer.pair.name}
|
||||
checked={isLast || !pairContainer.pair.name ? false : !!pairContainer.pair.enabled}
|
||||
className={isLast ? '!opacity-disabled' : undefined}
|
||||
onChange={handleChangeEnabled}
|
||||
/>
|
||||
<Input
|
||||
hideLabel
|
||||
require={!isLast && !!pairContainer.pair.enabled && !!pairContainer.pair.value}
|
||||
useTemplating
|
||||
containerClassName={classnames(isLast && 'border-dashed')}
|
||||
defaultValue={pairContainer.pair.name}
|
||||
label="Name"
|
||||
@@ -277,7 +269,7 @@ const FormRow = memo(function FormRow({
|
||||
onChange={handleChangeName}
|
||||
onFocus={handleFocus}
|
||||
placeholder={namePlaceholder ?? 'name'}
|
||||
useEditor={nameEditorConfig}
|
||||
autocomplete={nameAutocomplete}
|
||||
/>
|
||||
<Input
|
||||
hideLabel
|
||||
@@ -288,7 +280,8 @@ const FormRow = memo(function FormRow({
|
||||
onChange={handleChangeValue}
|
||||
onFocus={handleFocus}
|
||||
placeholder={valuePlaceholder ?? 'value'}
|
||||
useEditor={valueEditorConfig}
|
||||
useTemplating
|
||||
autocomplete={valueAutocomplete?.(pairContainer.pair.name)}
|
||||
/>
|
||||
{onDelete ? (
|
||||
<IconButton
|
||||
|
||||
@@ -26,6 +26,7 @@ module.exports = {
|
||||
},
|
||||
colors: {
|
||||
focus: "hsl(var(--color-blue-500) / 0.6)",
|
||||
invalid: "hsl(var(--color-red-500))",
|
||||
highlight: "hsl(var(--color-gray-200) / 0.3)",
|
||||
transparent: "transparent",
|
||||
white: "hsl(0 100% 100% / <alpha-value>)",
|
||||
|
||||
Reference in New Issue
Block a user