diff --git a/demo/FormExamples.jsx b/demo/FormExamples.jsx index b815a31..5a7c9a9 100644 --- a/demo/FormExamples.jsx +++ b/demo/FormExamples.jsx @@ -14,6 +14,8 @@ import { FormGroupAutocompleteTag, Table, FormAutocompleteTag, + FormGroupTable, + Dialog, // eslint-disable-next-line import/no-unresolved } from '../dist/main'; @@ -95,6 +97,14 @@ export function FormExamples() { }, }, ], + formTable2: [ + { + message: 'Must have more than 2 itens', + validate(value) { + return value?.length > 2; + }, + }, + ], }), [changeCustomValidation] ); @@ -761,6 +771,12 @@ export function FormExamples() { menuClassName="p-4 w-100" /> +
FormTable
+
+ + +
+ @@ -861,3 +877,82 @@ function FormAutocompleteTagsWithCustomLabel() { ); } + +function FormGroupTable1() { + return ( + console.log('formTable', args)} + tableProps={{ + actionLabel: 'Actions', + columns: [ + { + attribute: 'name', + label: 'Name', + }, + { + attribute: 'number', + label: 'Number', + }, + ], + }} + getRemoveComponent={(removeItem) => removeItem()}>} + getAddItemComponent={(addItem) => } + /> + ); +} +function FormGroupTable2() { + return ( + removeItem()}>} + getAddItemComponent={(addItem) => } + /> + ); +} + +function AddFormGroupTableItem({ addItem }) { + return ( + ( +
{ + console.info('submit', data); + addItem(data); + close(); + }} + onCancel={() => { + console.warn('cancel'); + close(); + }} + > + + + + )} + > + +
+ ); +} diff --git a/demo/UncontrolledFormExamples.jsx b/demo/UncontrolledFormExamples.jsx index b49eaab..1d83ff3 100644 --- a/demo/UncontrolledFormExamples.jsx +++ b/demo/UncontrolledFormExamples.jsx @@ -14,6 +14,8 @@ import { UncontrolledFormGroupAutocomplete, UncontrolledFormGroupDropdown, UncontrolledFormGroupRadio, + UncontrolledFormGroupTable, + Dialog, } from '../dist/main'; export function UncontrolledFormExamples() { @@ -84,6 +86,14 @@ export function UncontrolledFormExamples() { }, }, ], + formTable2: [ + { + message: 'Must have more than 2 itens', + validate(value) { + return value?.length > 2; + }, + }, + ], }} >
Form configuration:
@@ -426,6 +436,12 @@ export function UncontrolledFormExamples() { + +
FormTable
+
+ + +
); @@ -1095,3 +1111,82 @@ function FormTextareaSetValueTeste2({}) { ); } + +function FormUncontrolledFormGroupTable1() { + return ( + console.log('formTable', args)} + tableProps={{ + actionLabel: 'Actions', + columns: [ + { + attribute: 'name', + label: 'Name', + }, + { + attribute: 'number', + label: 'Number', + }, + ], + }} + getRemoveComponent={(removeItem) => removeItem()}>} + getAddItemComponent={(addItem) => } + /> + ); +} +function FormUncontrolledFormGroupTable2() { + return ( + removeItem()}>} + getAddItemComponent={(addItem) => } + /> + ); +} + +function AddFormGroupTableItem({ addItem }) { + return ( + ( + { + console.info('submit', data); + addItem(data); + close(); + }} + onCancel={() => { + console.warn('cancel'); + close(); + }} + > + + + + )} + > + + + ); +} diff --git a/src/forms/FormTable.jsx b/src/forms/FormTable.jsx new file mode 100644 index 0000000..8026baf --- /dev/null +++ b/src/forms/FormTable.jsx @@ -0,0 +1,193 @@ +import React, { useCallback, useMemo } from 'react'; +import PropTypes from 'prop-types'; + +import { isFunction } from 'js-var-type'; + +import { formatClasses } from '../utils/attributes'; + +import { Table } from '../table/Table'; + +import { useFormControl } from './helpers/useFormControl'; +import { booleanOrFunction } from './helpers/form-helpers'; +import { FormGroup } from './FormGroup'; + +/** + * FormTable + * + * This component allows you to control an array of itens in an Form. + * This array will be shown as a table. + * @param {object} param0 + * @param {name} param0.name - Name of the field + * @param {boolean|Function=} param0.required - Defines whether the field is required for the form or not. If it's a function, the first parameter is the formData + * @param {boolean|Function=} param0.disabled - Defines whether the field is disabled for the form or not. If it's a function, the first parameter is the formData + * @param {Function=} param0.afterChange - Function that will run after an update on the field + * @param {object} param0.tableProps - Props of the "Table" component + * @param {Function=} param0.getCustomActions - Function that recieves "item" and "index", and must return an array of Table actions. + * @param {Function} param0.getAddItemComponent - Function that recieves the "item to be added" and must return the element that will be used to add new itens on the Table + * @param {Function} param0.getRemoveComponent - Function that recieves the "remove item function" and must return the element that will be used to remove the item + * @returns {JSX.Element} + * + * @example + * + * ```jsx + * function FormUncontrolledFormGroupTable() { + * return ( + * console.log('formTable', args)} + * tableProps={{ + * actionLabel: 'Actions', + * columns: [ + * { + * attribute: 'name', + * label: 'Name', + * }, + * { + * attribute: 'number', + * label: 'Number', + * }, + * ], + * }} + * getRemoveComponent={(removeItem) => removeItem()}>} + * getAddItemComponent={(addItem) => } + * /> + * ); + *} + * + *function AddFormGroupTableItem({ addItem }) { + * // Form to generate new itens + * + * return ( + * ( + *
{ + * addItem(data); + * close(); + * }} + * onCancel={() => { + * close(); + * }} + * > + * + * + * + * )} + * > + * + *
+ * ); + *} + * ``` + */ + +export function FormTable({ + name, + required: _required, + disabled: _disabled, + afterChange, + tableProps, + getCustomActions, + getAddItemComponent, + getRemoveComponent, +}) { + const { getValue, register, getFormData, setValue } = useFormControl(name, 'array'); + const registerRef = useCallback(register, [register]); + const disabled = booleanOrFunction(_disabled, getFormData()); + const required = booleanOrFunction(_required, getFormData()); + + const value = useMemo(() => getValue() || [], [getValue]); + + const removeItem = useCallback( + (doc, index) => { + const newValue = value?.filter?.((_, i) => i !== index) ?? []; + + setValue(newValue); + + if (isFunction(afterChange)) { + afterChange(newValue); + } + }, + [afterChange, setValue, value] + ); + const addItem = useCallback( + (item) => { + const newValue = [...(value || []), item]; + + setValue(newValue); + + if (isFunction(afterChange)) { + afterChange(newValue); + } + }, + [afterChange, setValue, value] + ); + + const addItemComponent = useMemo(() => getAddItemComponent?.(addItem) ?? <>, [addItem, getAddItemComponent]); + + const attrs = { + disabled, + name, + required, + }; + + return ( + <> + 0 ? '1' : ''} //for required validation + /> + {addItemComponent} + [ + ...(getCustomActions?.(doc, index) ?? []), + { + content: getRemoveComponent(() => removeItem(doc, index)), + }, + ]} + docs={value} + {...tableProps} + >
+ + ); +} + +FormTable.defaultProps = { + tableProps: {}, +}; + +const formTableProps = { + afterChange: PropTypes.func, + disabled: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), + name: PropTypes.string.isRequired, + required: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), + tableProps: PropTypes.shape(Table.propTypes), + getCustomActions: PropTypes.func, + getRemoveComponent: PropTypes.func, + getAddItemComponent: PropTypes.func, +}; + +FormTable.propTypes = formTableProps; + +export function FormGroupTable(props) { + return ( + + + + ); +} + +FormGroupTable.propTypes = { + ...formTableProps, + help: PropTypes.node, + label: PropTypes.node.isRequired, +}; diff --git a/src/forms/index.js b/src/forms/index.js index 2d67b6d..547b1ae 100644 --- a/src/forms/index.js +++ b/src/forms/index.js @@ -9,6 +9,7 @@ export * from './FormInput'; export * from './FormRadio'; export * from './FormSelect'; export * from './FormSwitch'; +export * from './FormTable'; export * from './FormTextarea'; export * from './helpers/useFormControl'; export * from './helpers/useFormData'; diff --git a/src/uncontrolled-forms/UncontrolledFormTable.jsx b/src/uncontrolled-forms/UncontrolledFormTable.jsx new file mode 100644 index 0000000..2c2cb3c --- /dev/null +++ b/src/uncontrolled-forms/UncontrolledFormTable.jsx @@ -0,0 +1,199 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import PropTypes from 'prop-types'; + +import { isFunction } from 'js-var-type'; + +import { Table } from '../table/Table'; +import { formatClasses } from '../utils/attributes'; + +import { booleanOrFunction } from './helpers/form-helpers'; + +import { useUncontrolledFormControl } from './helpers/useUncontrolledFormControl'; +import { UncontrolledFormGroup } from './UncontrolledFormGroup'; + +/** + * UncontrolledFormTable + * + * This component allows you to control an array of itens in an UncontrolledForm. + * This array will be shown as a table. + * @param {object} param0 + * @param {name} param0.name - Name of the field + * @param {boolean|Function=} param0.required - Defines whether the field is required for the form or not. If it's a function, the first parameter is the formData + * @param {boolean|Function=} param0.disabled - Defines whether the field is disabled for the form or not. If it's a function, the first parameter is the formData + * @param {Function=} param0.afterChange - Function that will run after an update on the field + * @param {object} param0.tableProps - Props of the "Table" component + * @param {Function=} param0.getCustomActions - Function that recieves "item" and "index", and must return an array of Table actions. + * @param {Function} param0.getAddItemComponent - Function that recieves the "item to be added" and must return the element that will be used to add new itens on the Table + * @param {Function} param0.getRemoveComponent - Function that recieves the "remove item function" and must return the element that will be used to remove the item + * @returns {JSX.Element} + * + * @example + * + * ```jsx + * function FormUncontrolledFormGroupTable() { + * return ( + * console.log('formTable', args)} + * tableProps={{ + * actionLabel: 'Actions', + * columns: [ + * { + * attribute: 'name', + * label: 'Name', + * }, + * { + * attribute: 'number', + * label: 'Number', + * }, + * ], + * }} + * getRemoveComponent={(removeItem) => removeItem()}>} + * getAddItemComponent={(addItem) => } + * /> + * ); + *} + * + *function AddFormGroupTableItem({ addItem }) { + * // Form to generate new itens + * + * return ( + * ( + * { + * addItem(data); + * close(); + * }} + * onCancel={() => { + * close(); + * }} + * > + * + * + * + * )} + * > + * + * + * ); + *} + * ``` + */ + +export function UncontrolledFormTable({ + name, + required: _required, + disabled: _disabled, + afterChange, + tableProps, + getCustomActions, + getAddItemComponent, + getRemoveComponent, +}) { + const state = useState([]); + + const { getValue, getFormData, registerInputRef, setValue } = useUncontrolledFormControl(name, 'array', { + state, + }); + + const value = useMemo(() => getValue(), [getValue]); + + const removeItem = useCallback( + (doc, index) => { + const newValue = value?.filter?.((_, i) => i !== index) ?? []; + + setValue(newValue); + + if (isFunction(afterChange)) { + afterChange(newValue); + } + }, + [afterChange, setValue, value] + ); + const addItem = useCallback( + (item) => { + const newValue = [...(value || []), item]; + + setValue(newValue); + + if (isFunction(afterChange)) { + afterChange(newValue); + } + }, + [afterChange, setValue, value] + ); + + const addItemComponent = useMemo(() => getAddItemComponent?.(addItem) ?? <>, [addItem, getAddItemComponent]); + + const disabled = booleanOrFunction(_disabled, getFormData()); + const required = booleanOrFunction(_required, getFormData()); + + const attrs = { + disabled, + name, + required, + }; + + /** The input is for the default form validation, and will not be shown */ + + return ( + <> + 0 ? '1' : ''} //for required validation + /> + {addItemComponent} + [ + ...(getCustomActions?.(doc, index) ?? []), + { + content: getRemoveComponent(() => removeItem(doc, index)), + }, + ]} + docs={value} + {...tableProps} + >
+ + ); +} + +UncontrolledFormTable.defaultProps = { + tableProps: {}, +}; + +const formTableProps = { + afterChange: PropTypes.func, + disabled: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), + name: PropTypes.string.isRequired, + required: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), + tableProps: PropTypes.shape(Table.propTypes), + getCustomActions: PropTypes.func, + getRemoveComponent: PropTypes.func, + getAddItemComponent: PropTypes.func, +}; + +UncontrolledFormTable.propTypes = formTableProps; + +export function UncontrolledFormGroupTable(props) { + return ( + + + + ); +} + +UncontrolledFormGroupTable.propTypes = { + ...formTableProps, + help: PropTypes.node, + label: PropTypes.node.isRequired, +}; diff --git a/src/uncontrolled-forms/helpers/useUncontrolledFormHelper.jsx b/src/uncontrolled-forms/helpers/useUncontrolledFormHelper.jsx index f44c01e..f599e88 100644 --- a/src/uncontrolled-forms/helpers/useUncontrolledFormHelper.jsx +++ b/src/uncontrolled-forms/helpers/useUncontrolledFormHelper.jsx @@ -120,6 +120,10 @@ export function useUncontrolledFormHelper(initialValues, { debounceWait, transfo if (formControl) { formControl.setValue(value); } + + if (validations) { + this.validateForm(); + } }, setFormControl(name, formControl) { formHelper.current.setFormControl(name, formControl); diff --git a/src/uncontrolled-forms/index.jsx b/src/uncontrolled-forms/index.jsx index fb79daa..a3d410d 100644 --- a/src/uncontrolled-forms/index.jsx +++ b/src/uncontrolled-forms/index.jsx @@ -9,6 +9,7 @@ export * from './UncontrolledFormInputMask'; export * from './UncontrolledFormRadio'; export * from './UncontrolledFormSelect'; export * from './UncontrolledFormSwitch'; +export * from './UncontrolledFormTable'; export * from './UncontrolledFormTextarea'; export * from './helpers/useUncontrolledFormControl'; export * from './helpers/useUncontrolledFormEffect';