mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
refactor(gitops): migrate git form to react [EE-4849] (#8268)
This commit is contained in:
parent
afe6cd6df0
commit
273a3f9a10
130 changed files with 3194 additions and 1190 deletions
|
@ -2,6 +2,7 @@ import clsx from 'clsx';
|
|||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
|
||||
import { ButtonGroup, Size } from '@@/buttons/ButtonGroup';
|
||||
import { Button } from '@@/buttons';
|
||||
|
||||
import styles from './ButtonSelector.module.css';
|
||||
|
||||
|
@ -59,10 +60,12 @@ function OptionItem({
|
|||
readOnly,
|
||||
}: PropsWithChildren<OptionItemProps>) {
|
||||
return (
|
||||
<label
|
||||
className={clsx('btn btn-primary', {
|
||||
<Button
|
||||
color="light"
|
||||
as="label"
|
||||
disabled={disabled || readOnly}
|
||||
className={clsx({
|
||||
active: selected,
|
||||
disabled: readOnly || disabled,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
|
@ -73,6 +76,6 @@ function OptionItem({
|
|||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</label>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
import { ComponentProps, PropsWithChildren, ReactNode } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { Tooltip } from '@@/Tip/Tooltip';
|
||||
|
@ -11,11 +11,12 @@ export interface Props {
|
|||
inputId?: string;
|
||||
label: ReactNode;
|
||||
size?: Size;
|
||||
tooltip?: string;
|
||||
tooltip?: ComponentProps<typeof Tooltip>['message'];
|
||||
setTooltipHtmlMessage?: ComponentProps<typeof Tooltip>['setHtmlMessage'];
|
||||
children: ReactNode;
|
||||
errors?: ReactNode;
|
||||
required?: boolean;
|
||||
setTooltipHtmlMessage?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function FormControl({
|
||||
|
@ -25,12 +26,14 @@ export function FormControl({
|
|||
tooltip = '',
|
||||
children,
|
||||
errors,
|
||||
required,
|
||||
className,
|
||||
required = false,
|
||||
setTooltipHtmlMessage,
|
||||
}: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
className,
|
||||
'form-group',
|
||||
'after:clear-both after:table after:content-[""]' // to fix issues with float
|
||||
)}
|
||||
|
@ -50,12 +53,7 @@ export function FormControl({
|
|||
|
||||
<div className={sizeClassChildren(size)}>
|
||||
{children}
|
||||
|
||||
{errors && (
|
||||
<span className="help-block">
|
||||
<FormError>{errors}</FormError>
|
||||
</span>
|
||||
)}
|
||||
{errors && <FormError>{errors}</FormError>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,9 @@ interface Props {
|
|||
|
||||
export function FormError({ children, className }: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<p className={clsx(`text-muted small vertical-center`, className)}>
|
||||
<p
|
||||
className={clsx(`text-muted small vertical-center help-block`, className)}
|
||||
>
|
||||
<Icon icon={AlertTriangle} className="icon-warning" />
|
||||
<span className="text-warning">{children}</span>
|
||||
</p>
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
import clsx from 'clsx';
|
||||
import { InputHTMLAttributes } from 'react';
|
||||
import { forwardRef, InputHTMLAttributes, Ref } from 'react';
|
||||
|
||||
export const InputWithRef = forwardRef<
|
||||
HTMLInputElement,
|
||||
InputHTMLAttributes<HTMLInputElement>
|
||||
>(
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
(props, ref) => <Input {...props} mRef={ref} />
|
||||
);
|
||||
|
||||
export function Input({
|
||||
className,
|
||||
mRef: ref,
|
||||
...props
|
||||
}: InputHTMLAttributes<HTMLInputElement>) {
|
||||
}: InputHTMLAttributes<HTMLInputElement> & { mRef?: Ref<HTMLInputElement> }) {
|
||||
return (
|
||||
<input
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={clsx('form-control', className)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1 +1 @@
|
|||
export { InputList } from './InputList';
|
||||
export { InputList, type ItemProps } from './InputList';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import clsx from 'clsx';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
|
||||
|
@ -13,14 +14,14 @@ export interface Props {
|
|||
onChange(value: boolean): void;
|
||||
|
||||
name?: string;
|
||||
tooltip?: string;
|
||||
tooltip?: ComponentProps<typeof Tooltip>['message'];
|
||||
setTooltipHtmlMessage?: ComponentProps<typeof Tooltip>['setHtmlMessage'];
|
||||
labelClass?: string;
|
||||
switchClass?: string;
|
||||
fieldClass?: string;
|
||||
dataCy?: string;
|
||||
disabled?: boolean;
|
||||
featureId?: FeatureId;
|
||||
setTooltipHtmlMessage?: boolean;
|
||||
}
|
||||
|
||||
export function SwitchField({
|
||||
|
|
27
app/react/components/form-components/useCachedTest.ts
Normal file
27
app/react/components/form-components/useCachedTest.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { useRef } from 'react';
|
||||
import { TestContext, TestFunction } from 'yup';
|
||||
|
||||
function cacheTest<T, TContext>(
|
||||
asyncValidate: TestFunction<T, TContext>
|
||||
): TestFunction<T, TContext> {
|
||||
let valid = true;
|
||||
let value: T | undefined;
|
||||
|
||||
return async (newValue: T, context: TestContext<TContext>) => {
|
||||
if (newValue !== value) {
|
||||
value = newValue;
|
||||
|
||||
const response = await asyncValidate.call(context, newValue, context);
|
||||
valid = !!response;
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
}
|
||||
|
||||
export function useCachedValidation<T, TContext>(
|
||||
test: TestFunction<T, TContext>
|
||||
) {
|
||||
const ref = useRef(cacheTest(test));
|
||||
|
||||
return ref.current;
|
||||
}
|
26
app/react/components/form-components/useCaretPosition.ts
Normal file
26
app/react/components/form-components/useCaretPosition.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { useRef, useState, useCallback, useEffect } from 'react';
|
||||
|
||||
export function useCaretPosition<
|
||||
T extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement
|
||||
>() {
|
||||
const node = useRef<T>(null);
|
||||
const [start, setStart] = useState(0);
|
||||
const [end, setEnd] = useState(0);
|
||||
|
||||
const updateCaret = useCallback(() => {
|
||||
if (node.current) {
|
||||
const { selectionStart, selectionEnd } = node.current;
|
||||
|
||||
setStart(selectionStart || 0);
|
||||
setEnd(selectionEnd || 0);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (node.current) {
|
||||
node.current.setSelectionRange(start, end);
|
||||
}
|
||||
});
|
||||
|
||||
return { start, end, ref: node, updateCaret };
|
||||
}
|
19
app/react/components/form-components/validate-form.ts
Normal file
19
app/react/components/form-components/validate-form.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { yupToFormErrors } from 'formik';
|
||||
import { SchemaOf } from 'yup';
|
||||
|
||||
export async function validateForm<T>(
|
||||
schemaBuilder: () => SchemaOf<T>,
|
||||
formValues: T
|
||||
) {
|
||||
const validationSchema = schemaBuilder();
|
||||
|
||||
try {
|
||||
await validationSchema.validate(formValues, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
});
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
return yupToFormErrors<T>(error);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue