@sqrzro/ui 4.0.0-alpha.54 → 4.0.0-alpha.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/forms/components/Form/index.d.ts +2 -1
- package/dist/forms/components/Form/index.js +4 -3
- package/dist/forms/components/FormField/index.d.ts +3 -2
- package/dist/forms/components/FormField/index.js +4 -2
- package/dist/forms/components/FormFields/index.d.ts +3 -0
- package/dist/forms/components/FormFields/index.js +6 -0
- package/dist/forms/components/ModalForm/index.d.ts +3 -2
- package/dist/forms/components/ModalForm/index.js +3 -2
- package/dist/forms/components/Switch/index.d.ts +8 -2
- package/dist/forms/components/Switch/index.js +3 -2
- package/dist/forms/hooks/useForm.d.ts +2 -1
- package/dist/forms/hooks/useForm.js +10 -2
- package/dist/forms/hooks/useModalForm.js +8 -1
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ export interface FormProps extends ClassNameProps<FormClassNames> {
|
|
|
8
8
|
id?: string;
|
|
9
9
|
onSubmit?: React.FormEventHandler<HTMLFormElement>;
|
|
10
10
|
ref?: React.Ref<HTMLFormElement>;
|
|
11
|
+
uncaughtErrors: Record<string, string> | null;
|
|
11
12
|
}
|
|
12
|
-
declare function Form({ action, children, classNames, classNameProps, id, onSubmit, ref, }: Readonly<FormProps>): React.ReactElement;
|
|
13
|
+
declare function Form({ action, children, classNames, classNameProps, id, onSubmit, uncaughtErrors, ref, }: Readonly<FormProps>): React.ReactElement;
|
|
13
14
|
export default Form;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useClassNames } from '../../../styles/context';
|
|
4
4
|
import tw from '../../../styles/classnames/utility/tw';
|
|
5
|
-
|
|
5
|
+
import { InfoPanel } from '../../../components';
|
|
6
|
+
function Form({ action, children, classNames, classNameProps, id, onSubmit, uncaughtErrors, ref, }) {
|
|
6
7
|
const componentClassNames = useClassNames('form', { props: classNameProps }, classNames);
|
|
7
|
-
return (
|
|
8
|
+
return (_jsxs("form", { ref: ref, action: action, className: tw(componentClassNames?.root), id: id, onSubmit: onSubmit, children: [uncaughtErrors && Object.keys(uncaughtErrors).length ? (_jsx(InfoPanel, { variant: "error", children: Object.values(uncaughtErrors).map((item, index) => (_jsx("p", { children: item }, index))) })) : null, children] }));
|
|
8
9
|
}
|
|
9
10
|
export default Form;
|
|
@@ -11,9 +11,10 @@ export interface FormFieldClassNames {
|
|
|
11
11
|
}
|
|
12
12
|
export interface FormFieldProps<T, V extends T> extends ClassNameProps<FormFieldClassNames>, InputProps<T, V> {
|
|
13
13
|
action?: SimpleActionObject | null;
|
|
14
|
-
details?:
|
|
14
|
+
details?: React.ReactNode;
|
|
15
15
|
error?: Record<string, string> | null;
|
|
16
16
|
hasAssistiveError?: boolean;
|
|
17
|
+
hasAssistiveDetails?: boolean;
|
|
17
18
|
hasAssistiveLabel?: boolean;
|
|
18
19
|
isContentOnly?: boolean;
|
|
19
20
|
isOptional?: boolean;
|
|
@@ -21,5 +22,5 @@ export interface FormFieldProps<T, V extends T> extends ClassNameProps<FormField
|
|
|
21
22
|
onChange?: (event: InputEvent<T>) => void;
|
|
22
23
|
render: (props: InputProps<T>) => React.ReactElement | null;
|
|
23
24
|
}
|
|
24
|
-
declare function FormField<T, V extends T>({ action, classNameProps, classNames, details, error, hasAssistiveError, hasAssistiveLabel, id, isContentOnly, isDisabled, isOptional, label, name, onChange, onKeyDown, render, value, }: Readonly<FormFieldProps<T, V>>): React.ReactElement | null;
|
|
25
|
+
declare function FormField<T, V extends T>({ action, classNameProps, classNames, details, error, hasAssistiveError, hasAssistiveDetails, hasAssistiveLabel, id, isContentOnly, isDisabled, isOptional, label, name, onChange, onKeyDown, render, value, }: Readonly<FormFieldProps<T, V>>): React.ReactElement | null;
|
|
25
26
|
export default FormField;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { useClassNames } from '../../../styles/context';
|
|
5
|
+
import tw from '../../../styles/classnames/utility/tw';
|
|
5
6
|
import FormError from '../FormError';
|
|
6
7
|
import FormLabel from '../FormLabel';
|
|
7
8
|
import ActionButton from '../../../components/buttons/ActionButton';
|
|
@@ -10,7 +11,7 @@ function checkHasError(error) {
|
|
|
10
11
|
Object.keys(error).length > 0 &&
|
|
11
12
|
Object.values(error).some((item) => typeof item === 'string'));
|
|
12
13
|
}
|
|
13
|
-
function FormField({ action, classNameProps, classNames, details, error, hasAssistiveError, hasAssistiveLabel, id, isContentOnly, isDisabled, isOptional, label, name, onChange, onKeyDown, render, value, }) {
|
|
14
|
+
function FormField({ action, classNameProps, classNames, details, error, hasAssistiveError, hasAssistiveDetails, hasAssistiveLabel, id, isContentOnly, isDisabled, isOptional, label, name, onChange, onKeyDown, render, value, }) {
|
|
14
15
|
const componentClassNames = useClassNames('formField', { props: classNameProps, states: { isError: checkHasError(error) } }, classNames);
|
|
15
16
|
const inputId = id || `ff_${name}`;
|
|
16
17
|
const [inputError, setInputError] = useState(null);
|
|
@@ -19,6 +20,7 @@ function FormField({ action, classNameProps, classNames, details, error, hasAssi
|
|
|
19
20
|
}
|
|
20
21
|
const renderProps = {
|
|
21
22
|
classNameProps,
|
|
23
|
+
details,
|
|
22
24
|
error,
|
|
23
25
|
hasError: checkHasError(error),
|
|
24
26
|
id: inputId,
|
|
@@ -34,6 +36,6 @@ function FormField({ action, classNameProps, classNames, details, error, hasAssi
|
|
|
34
36
|
if (isContentOnly) {
|
|
35
37
|
return render(renderProps);
|
|
36
38
|
}
|
|
37
|
-
return (_jsxs("div", { className: componentClassNames?.root, children: [label ? (_jsx(FormLabel, { classNameProps: classNameProps, htmlFor: inputId, isAssistive: hasAssistiveLabel, isOptional: isOptional, children: label })) : null, details && label ? (_jsx("div", { className: componentClassNames?.details, children: details })) : null, _jsx("div", { className: componentClassNames?.field, children: render(renderProps) }), action ? (_jsx("div", { children: _jsx(ActionButton, { ...action, isDisabled: Boolean(isDisabled || action.isDisabled) }) })) : null, inputError || error?.[name] ? (_jsx(FormError, { classNameProps: classNameProps, id: inputId, isAssistive: hasAssistiveError, children: inputError || error?.[name] })) : null] }));
|
|
39
|
+
return (_jsxs("div", { className: componentClassNames?.root, children: [label ? (_jsx(FormLabel, { classNameProps: classNameProps, htmlFor: inputId, isAssistive: hasAssistiveLabel, isOptional: isOptional, children: label })) : null, details && label ? (_jsx("div", { className: tw(componentClassNames?.details, hasAssistiveDetails ? 'sr-only' : null), children: details })) : null, _jsx("div", { className: componentClassNames?.field, children: render(renderProps) }), action ? (_jsx("div", { children: _jsx(ActionButton, { ...action, isDisabled: Boolean(isDisabled || action.isDisabled) }) })) : null, inputError || error?.[name] ? (_jsx(FormError, { classNameProps: classNameProps, id: inputId, isAssistive: hasAssistiveError, children: inputError || error?.[name] })) : null] }));
|
|
38
40
|
}
|
|
39
41
|
export default FormField;
|
|
@@ -4,6 +4,7 @@ import type { DropdownComponentProps } from '../Dropdown';
|
|
|
4
4
|
import type { PointsInputComponentProps } from '../PointsInput';
|
|
5
5
|
import type { NumberInputComponentProps } from '../NumberInput';
|
|
6
6
|
import type { PasswordInputComponentProps } from '../PasswordInput';
|
|
7
|
+
import type { SwitchComponentProps } from '../Switch';
|
|
7
8
|
import type { TextAreaComponentProps } from '../TextArea';
|
|
8
9
|
import type { TextInputComponentProps } from '../TextInput';
|
|
9
10
|
export type AutocompleteFormFieldProps<T> = FormFieldComponentProps<T | null> & AutocompleteComponentProps<T>;
|
|
@@ -18,6 +19,8 @@ export type NumberFormFieldProps = FormFieldComponentProps<number> & NumberInput
|
|
|
18
19
|
export declare function NumberFormField(props: Readonly<NumberFormFieldProps>): React.ReactElement;
|
|
19
20
|
export type PasswordFormFieldProps = FormFieldComponentProps<string> & PasswordInputComponentProps;
|
|
20
21
|
export declare function PasswordFormField(props: Readonly<PasswordFormFieldProps>): React.ReactElement;
|
|
22
|
+
export type SwitchFormFieldProps = FormFieldComponentProps<boolean> & SwitchComponentProps;
|
|
23
|
+
export declare function SwitchFormField(props: SwitchFormFieldProps): React.ReactElement;
|
|
21
24
|
export type TextFormFieldProps = FormFieldComponentProps<string> & TextInputComponentProps;
|
|
22
25
|
export declare function TextFormField(props: Readonly<TextFormFieldProps>): React.ReactElement;
|
|
23
26
|
export type TextAreaFormFieldProps = FormFieldComponentProps<string> & TextAreaComponentProps;
|
|
@@ -8,6 +8,7 @@ import FormField from '../FormField';
|
|
|
8
8
|
import PointsInput from '../PointsInput';
|
|
9
9
|
import NumberInput from '../NumberInput';
|
|
10
10
|
import PasswordInput from '../PasswordInput';
|
|
11
|
+
import Switch from '../Switch';
|
|
11
12
|
import TextArea from '../TextArea';
|
|
12
13
|
import TextInput from '../TextInput';
|
|
13
14
|
export function AutocompleteFormField(props) {
|
|
@@ -40,6 +41,11 @@ export function PasswordFormField(props) {
|
|
|
40
41
|
const renderInput = useCallback((renderProps) => (_jsx(PasswordInput, { ...renderProps, ...inputProps })), [inputProps]);
|
|
41
42
|
return _jsx(FormField, { ...fieldProps, render: renderInput });
|
|
42
43
|
}
|
|
44
|
+
export function SwitchFormField(props) {
|
|
45
|
+
const { fieldProps, inputProps } = extractInputProps(props);
|
|
46
|
+
const renderInput = useCallback((renderProps) => (_jsx(Switch, { ...renderProps, ...inputProps })), [inputProps]);
|
|
47
|
+
return _jsx(FormField, { ...fieldProps, hasAssistiveLabel: true, hasAssistiveDetails: true, render: renderInput });
|
|
48
|
+
}
|
|
43
49
|
export function TextFormField(props) {
|
|
44
50
|
const { fieldProps, inputProps } = extractInputProps(props);
|
|
45
51
|
const renderInput = useCallback((renderProps) => (_jsx(TextInput, { ...renderProps, ...inputProps })), [inputProps]);
|
|
@@ -3,11 +3,12 @@ import type { FormProps } from '../Form';
|
|
|
3
3
|
export interface ModalFormProps {
|
|
4
4
|
children: React.ReactNode;
|
|
5
5
|
formProps: Omit<FormProps, 'children'>;
|
|
6
|
-
hasServerError?: boolean;
|
|
7
6
|
hasSubmit?: boolean;
|
|
8
7
|
isDisabled?: boolean;
|
|
9
8
|
modalProps: Omit<ModalProps, 'children'>;
|
|
9
|
+
serverError?: string | null;
|
|
10
10
|
submitLabel?: string;
|
|
11
|
+
validationErrors?: string[];
|
|
11
12
|
}
|
|
12
|
-
declare function ModalForm({ children, formProps,
|
|
13
|
+
declare function ModalForm({ children, formProps, hasSubmit, isDisabled, modalProps, serverError, submitLabel, validationErrors, }: Readonly<ModalFormProps>): React.ReactElement;
|
|
13
14
|
export default ModalForm;
|
|
@@ -4,12 +4,13 @@ import Modal from '../../../components/modals/Modal';
|
|
|
4
4
|
import ModalActions from '../../../components/modals/ModalActions';
|
|
5
5
|
import useSearchParamsHref from '../../../hooks/useSearchParamsHref';
|
|
6
6
|
import Form from '../Form';
|
|
7
|
-
|
|
7
|
+
import { InfoPanel } from '../../../components';
|
|
8
|
+
function ModalForm({ children, formProps, hasSubmit = true, isDisabled, modalProps, serverError, submitLabel, validationErrors, }) {
|
|
8
9
|
const { setSearchParamsHref } = useSearchParamsHref();
|
|
9
10
|
function handleCancel() {
|
|
10
11
|
setSearchParamsHref('action', null);
|
|
11
12
|
}
|
|
12
|
-
return (_jsx(Modal, { ...modalProps, children: _jsxs(Form, { ...formProps, children: [children,
|
|
13
|
+
return (_jsx(Modal, { ...modalProps, children: _jsxs(Form, { ...formProps, children: [serverError ? _jsx(InfoPanel, { variant: "error", children: serverError }) : null, validationErrors?.length ? (_jsx(InfoPanel, { variant: "error", children: JSON.stringify(validationErrors) })) : null, children, _jsx(ModalActions, { actions: [
|
|
13
14
|
{ label: 'Cancel', onClick: handleCancel },
|
|
14
15
|
...(modalProps.actions || []),
|
|
15
16
|
hasSubmit
|
|
@@ -5,7 +5,13 @@ export interface SwitchClassNames {
|
|
|
5
5
|
control: string;
|
|
6
6
|
input: CheckableClassName;
|
|
7
7
|
icon: CheckableClassName;
|
|
8
|
+
label: string;
|
|
9
|
+
details: string;
|
|
8
10
|
}
|
|
9
|
-
export type
|
|
10
|
-
|
|
11
|
+
export type SwitchComponentProps = {
|
|
12
|
+
details?: React.ReactNode;
|
|
13
|
+
label?: React.ReactNode;
|
|
14
|
+
};
|
|
15
|
+
export type SwitchProps = ClassNameProps<SwitchClassNames> & InputProps<boolean> & SwitchComponentProps;
|
|
16
|
+
declare function Switch({ classNameProps, classNames, details, id, isDisabled, label, name, onChange, value, }: Readonly<SwitchProps>): React.ReactElement;
|
|
11
17
|
export default Switch;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Fragment } from 'react';
|
|
4
|
+
import Assistive from '../../../components/utility/Assistive';
|
|
4
5
|
import { useClassNames } from '../../../styles/context';
|
|
5
6
|
import tw from '../../../styles/classnames/utility/tw';
|
|
6
|
-
function Switch({ classNameProps, classNames, id, isDisabled, name, onChange, value, }) {
|
|
7
|
+
function Switch({ classNameProps, classNames, details, id, isDisabled, label, name, onChange, value, }) {
|
|
7
8
|
const componentClassNames = useClassNames('switch', { props: classNameProps, states: { isChecked: Boolean(value) } }, classNames);
|
|
8
9
|
function handleChange(event) {
|
|
9
10
|
if (onChange) {
|
|
@@ -13,6 +14,6 @@ function Switch({ classNameProps, classNames, id, isDisabled, name, onChange, va
|
|
|
13
14
|
onChange(inputEvent);
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
return (_jsxs(Fragment, { children: [_jsx("input", { name: name, type: "hidden", value: "false" }),
|
|
17
|
+
return (_jsxs(Fragment, { children: [_jsx("input", { name: name, type: "hidden", value: "false" }), _jsxs("div", { className: tw('block', componentClassNames?.root), children: [_jsxs("div", { className: tw('relative', componentClassNames?.control), children: [_jsx("input", { "aria-checked": Boolean(value), checked: Boolean(value), className: tw('appearance-none', componentClassNames?.input), disabled: isDisabled, id: id || name, name: name, onChange: handleChange, type: "checkbox", value: "true" }), _jsx("i", { className: componentClassNames?.icon })] }), label ? (_jsxs("label", { htmlFor: id || name, children: [_jsx("span", { className: tw('', componentClassNames?.label), children: label }), details ? (_jsx("small", { className: tw('', componentClassNames?.details), children: details })) : null] })) : (_jsx(Assistive, { children: "Yes?" }))] })] }));
|
|
17
18
|
}
|
|
18
19
|
export default Switch;
|
|
@@ -24,7 +24,7 @@ export interface UseFormArgs<Request, Response> extends UseSuccessArgs<Response>
|
|
|
24
24
|
defaults?: Default<Request>;
|
|
25
25
|
onError?: (message: string) => void;
|
|
26
26
|
onSubmit?: (formData: Request) => FormResponse<Response>;
|
|
27
|
-
onValidation?: (errors: Record<string, string>) => void;
|
|
27
|
+
onValidation?: (errors: Record<string, string>, uncaughtErrors?: Record<string, string>) => void;
|
|
28
28
|
toasts?: ToastsArgs | false;
|
|
29
29
|
}
|
|
30
30
|
export interface UseFormReturn<Request> {
|
|
@@ -38,6 +38,7 @@ export interface UseFormReturn<Request> {
|
|
|
38
38
|
setFormData: <K extends keyof Request>(key: K, value: Request[K]) => void;
|
|
39
39
|
submitForm: () => void;
|
|
40
40
|
}
|
|
41
|
+
export declare const DEFAULT_TOAST_MESSAGES: Record<keyof ToastsArgs, string>;
|
|
41
42
|
/**
|
|
42
43
|
* ## Overview
|
|
43
44
|
*
|
|
@@ -4,7 +4,7 @@ import { formatTitle } from '@sqrzro/utility';
|
|
|
4
4
|
import useDeepCompareEffect from 'use-deep-compare-effect';
|
|
5
5
|
import useSuccess from '../../hooks/useSuccess';
|
|
6
6
|
import useToast from '../../hooks/useToast';
|
|
7
|
-
const DEFAULT_TOAST_MESSAGES = {
|
|
7
|
+
export const DEFAULT_TOAST_MESSAGES = {
|
|
8
8
|
server: 'There was a problem submitting your request. Please try again later.',
|
|
9
9
|
success: 'Your request was submitted successfully.',
|
|
10
10
|
validation: 'There was a problem with your submission. Please check the form and try again.',
|
|
@@ -121,9 +121,11 @@ function getToastMessage(key, toasts) {
|
|
|
121
121
|
*/
|
|
122
122
|
function useForm({ defaults = {}, onError, onSubmit, onSuccess, onValidation, redirectOnSuccess, refreshOnSuccess, toasts, }) {
|
|
123
123
|
const ref = useRef(null);
|
|
124
|
+
const usedFields = useRef(new Set());
|
|
124
125
|
const { handleSuccess } = useSuccess({ onSuccess, redirectOnSuccess, refreshOnSuccess });
|
|
125
126
|
const [isLoading] = useTransition();
|
|
126
127
|
const [errors, setErrors] = useState(null);
|
|
128
|
+
const [uncaughtErrors, setUncaughtErrors] = useState(null);
|
|
127
129
|
const [data, setData] = useState(defaults);
|
|
128
130
|
const { toastError, toastSuccess } = useToast();
|
|
129
131
|
function setFormData(key, value) {
|
|
@@ -141,8 +143,12 @@ function useForm({ defaults = {}, onError, onSubmit, onSuccess, onValidation, re
|
|
|
141
143
|
toastError(getToastMessage('server', toasts));
|
|
142
144
|
}
|
|
143
145
|
function handleFormValidation(messages) {
|
|
144
|
-
|
|
146
|
+
const uncaughtMessages = Object.keys(messages)
|
|
147
|
+
.filter((key) => !usedFields.current.has(key))
|
|
148
|
+
.reduce((acc, key) => ({ ...acc, [key]: messages[key] }), {});
|
|
149
|
+
onValidation?.(messages, uncaughtMessages);
|
|
145
150
|
setErrors(messages);
|
|
151
|
+
setUncaughtErrors(uncaughtMessages);
|
|
146
152
|
toastError(getToastMessage('validation', toasts));
|
|
147
153
|
}
|
|
148
154
|
async function handleSubmit() {
|
|
@@ -162,6 +168,7 @@ function useForm({ defaults = {}, onError, onSubmit, onSuccess, onValidation, re
|
|
|
162
168
|
}
|
|
163
169
|
}
|
|
164
170
|
function fieldProps(name, label) {
|
|
171
|
+
usedFields.current.add(name);
|
|
165
172
|
return {
|
|
166
173
|
error: getErrorsForField(errors, name),
|
|
167
174
|
label: getLabel(name, label),
|
|
@@ -186,6 +193,7 @@ function useForm({ defaults = {}, onError, onSubmit, onSuccess, onValidation, re
|
|
|
186
193
|
formProps: {
|
|
187
194
|
action: handleSubmit,
|
|
188
195
|
ref,
|
|
196
|
+
uncaughtErrors,
|
|
189
197
|
},
|
|
190
198
|
isLoading,
|
|
191
199
|
resetForm,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import { useRouter } from 'next/navigation';
|
|
3
4
|
import useSearchParamsHref from '../../hooks/useSearchParamsHref';
|
|
4
|
-
import useForm from './useForm';
|
|
5
|
+
import useForm, { DEFAULT_TOAST_MESSAGES } from './useForm';
|
|
5
6
|
function useModalForm({ actions, icon, submitLabel, title, ...useFormArgs }) {
|
|
7
|
+
const [serverError, setServerError] = useState(null);
|
|
6
8
|
const { setSearchParamsHref } = useSearchParamsHref();
|
|
7
9
|
const router = useRouter();
|
|
8
10
|
function handleSuccess(response) {
|
|
@@ -14,6 +16,10 @@ function useModalForm({ actions, icon, submitLabel, title, ...useFormArgs }) {
|
|
|
14
16
|
}
|
|
15
17
|
const useFormReturn = useForm({
|
|
16
18
|
...useFormArgs,
|
|
19
|
+
onError: (message) => {
|
|
20
|
+
setServerError(DEFAULT_TOAST_MESSAGES.server);
|
|
21
|
+
useFormArgs.onError?.(message);
|
|
22
|
+
},
|
|
17
23
|
onSuccess: handleSuccess,
|
|
18
24
|
toasts: false,
|
|
19
25
|
});
|
|
@@ -22,6 +28,7 @@ function useModalForm({ actions, icon, submitLabel, title, ...useFormArgs }) {
|
|
|
22
28
|
formProps: {
|
|
23
29
|
formProps: useFormReturn.formProps,
|
|
24
30
|
modalProps: { actions, icon, title },
|
|
31
|
+
serverError,
|
|
25
32
|
submitLabel,
|
|
26
33
|
},
|
|
27
34
|
};
|