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