Skip to content

Commit

Permalink
Impl [Project setting] Provide validation rules on labels in UI (#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
mavdryk authored Feb 28, 2024
1 parent 8869526 commit d9912f5
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 74 deletions.
23 changes: 20 additions & 3 deletions src/lib/components/FormChipCell/FormChipCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const FormChipCell = ({
shortChips,
validationRules,
validator,
onExitEditModeCallback,
visibleChipsMaxLength
}) => {
const chipsClassName = classnames('chips', className)
Expand Down Expand Up @@ -140,10 +141,10 @@ const FormChipCell = ({
.value()
)
fields.remove(chipIndex)

onExitEditModeCallback && onExitEditModeCallback()
event && event.stopPropagation()
},
[checkChipsList, formState, name]
[checkChipsList, formState, name, onExitEditModeCallback]
)

const handleEditChip = useCallback(
Expand All @@ -163,6 +164,7 @@ const FormChipCell = ({
isValueFocused: false,
isNewChip: false
})
isChipNotEmpty && onExitEditModeCallback && onExitEditModeCallback()
} else if (nameEvent === TAB) {
if (!isChipNotEmpty) {
handleRemoveChip(event, fields, editConfig.chipIndex)
Expand All @@ -171,6 +173,8 @@ const FormChipCell = ({
setEditConfig((prevState) => {
const lastChipSelected = prevState.chipIndex + 1 > fields.value.length - 1

isChipNotEmpty && lastChipSelected && onExitEditModeCallback && onExitEditModeCallback()

return {
chipIndex: lastChipSelected ? null : prevState.chipIndex + 1,
isEdit: !lastChipSelected,
Expand All @@ -187,6 +191,11 @@ const FormChipCell = ({
setEditConfig((prevState) => {
const isPrevChipIndexExists = prevState.chipIndex - 1 < 0

isChipNotEmpty &&
isPrevChipIndexExists &&
onExitEditModeCallback &&
onExitEditModeCallback()

return {
chipIndex: isPrevChipIndexExists ? null : prevState.chipIndex - 1,
isEdit: !isPrevChipIndexExists,
Expand All @@ -200,7 +209,14 @@ const FormChipCell = ({
checkChipsList(get(formState.values, name))
event && event.preventDefault()
},
[editConfig.chipIndex, handleRemoveChip, checkChipsList, formState.values, name]
[
editConfig.chipIndex,
checkChipsList,
formState.values,
name,
onExitEditModeCallback,
handleRemoveChip
]
)

const handleToEditMode = useCallback(
Expand Down Expand Up @@ -296,6 +312,7 @@ const FormChipCell = ({
chipOptions={chipOptions}
chips={chips}
editConfig={editConfig}
formState={formState}
handleAddNewChip={handleAddNewChip}
handleEditChip={handleEditChip}
handleRemoveChip={handleRemoveChip}
Expand Down
10 changes: 8 additions & 2 deletions src/lib/components/FormChipCell/FormChipCellView.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const FormChipCellView = React.forwardRef(
chipOptions,
chips,
editConfig,
formState,
handleAddNewChip,
handleEditChip,
handleRemoveChip,
Expand Down Expand Up @@ -78,7 +79,11 @@ const FormChipCellView = React.forwardRef(
)

return (
<FieldArray name={name} validate={validateFields}>
<FieldArray
name={name}
initialValue={formState.initialValues[name]}
validate={validateFields}
>
{({ fields, meta }) => {
if (
!isEmpty(validationRules) &&
Expand All @@ -94,7 +99,7 @@ const FormChipCellView = React.forwardRef(
{fields.map((contentItem, index) => {
const chipData = fields.value[index]
return (
index < chips.visibleChips.length && (
index < chips.visibleChips?.length && (
<div className="chip-block" key={chipData.id}>
<Tooltip
hidden={editConfig.isEdit}
Expand Down Expand Up @@ -203,6 +208,7 @@ FormChipCellView.propTypes = {
chipOptions: CHIP_OPTIONS,
chips: PropTypes.object.isRequired,
editConfig: PropTypes.object.isRequired,
formState: PropTypes.object.isRequired,
handleAddNewChip: PropTypes.func.isRequired,
handleEditChip: PropTypes.func.isRequired,
handleRemoveChip: PropTypes.func.isRequired,
Expand Down
9 changes: 8 additions & 1 deletion src/lib/components/FormInput/FormInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const FormInput = React.forwardRef(
onBlur,
onChange,
onFocus,
onKeyDown,
pattern,
required,
suggestionList,
Expand Down Expand Up @@ -160,10 +161,15 @@ const FormInput = React.forwardRef(
}
const handleInputFocus = (event) => {
input.onFocus && input.onFocus(event)
onFocus && onFocus()
onFocus && onFocus(event)
setIsFocused(true)
}

const handleInputKeyDown = (event) => {
input.onKeyDown && input.onKeyDown(event)
onKeyDown && onKeyDown(event)
}

const handleScroll = (event) => {
if (inputRef.current && inputRef.current.contains(event.target)) return

Expand Down Expand Up @@ -316,6 +322,7 @@ const FormInput = React.forwardRef(
}}
autoComplete={inputProps.autocomplete ?? 'off'}
onBlur={handleInputBlur}
onKeyDown={handleInputKeyDown}
onFocus={handleInputFocus}
/>
</div>
Expand Down
5 changes: 4 additions & 1 deletion src/lib/components/FormKeyValueTable/FormKeyValueTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const FormKeyValueTable = ({
keyHeader,
keyLabel,
keyOptions,
onExitEditModeCallback,
valueHeader,
valueLabel
}) => {
Expand All @@ -51,7 +52,7 @@ const FormKeyValueTable = ({
editingItem,
enterEditMode,
isCurrentRowEditing
} = useFormTable(formState, exitEditModeTriggerItem)
} = useFormTable(formState, exitEditModeTriggerItem, onExitEditModeCallback)

const uniquenessValidator = (fields, newValue) => {
return !fields.value.some(({ data: { key } }, index) => {
Expand Down Expand Up @@ -184,6 +185,7 @@ FormKeyValueTable.defaultProps = {
keyHeader: 'Key',
keyLabel: 'Key',
keyOptions: null,
onExitEditModeCallback: () => {},
valueHeader: 'Value',
valueLabel: 'Value'
}
Expand All @@ -207,6 +209,7 @@ FormKeyValueTable.propTypes = {
id: PropTypes.string.isRequired
})
),
onExitEditModeCallback: PropTypes.func,
valueHeader: PropTypes.string,
valueLabel: PropTypes.string
}
Expand Down
7 changes: 5 additions & 2 deletions src/lib/components/FormTextarea/FormTextarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ illegal under applicable law, and the grant of the foregoing license
under the Apache 2.0 license is conditioned upon your compliance with
such restriction.
*/
import React, { useEffect, useState } from 'react'
import React, { useEffect, useLayoutEffect, useState } from 'react'
import classnames from 'classnames'
import PropTypes from 'prop-types'
import { Field, useField } from 'react-final-form'
Expand Down Expand Up @@ -65,6 +65,10 @@ const FormTextarea = React.forwardRef(
withoutBorder && 'without-border'
)

useLayoutEffect(() => {
setTextAreaCount(input.value.length)
}, [input.value.length])

useEffect(() => {
if (focused) {
textAreaRef.current.focus()
Expand All @@ -84,7 +88,6 @@ const FormTextarea = React.forwardRef(

const handleInputChange = (event) => {
input.onChange(event)
setTextAreaCount(event.target.value.length)
onChange && onChange(event.target.value)
}

Expand Down
93 changes: 46 additions & 47 deletions src/lib/hooks/useFormTable.hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react
import { get, omit } from 'lodash'
import { ARRAY_ERROR } from 'final-form'

export const useFormTable = (formState, exitEditModeTriggerItem) => {
export const useFormTable = (formState, exitEditModeTriggerItem, onExitEditModeCallback) => {
// `editingItem` should contain the `data` object with all fields that are used in the `formState`.
// Properties that aren't used in the `formState` should be placed directly in the `editingItem` object
// `editingItem` also has an `ui` property which is used internally in this hook
Expand All @@ -44,6 +44,7 @@ export const useFormTable = (formState, exitEditModeTriggerItem) => {
const editingItemErrorsRef = useRef(null)
const formStateRef = useRef(null)
const bottomScrollRef = useRef(null)
const onExitEditModeCallbackRef = useRef(onExitEditModeCallback)

useLayoutEffect(() => {
const tableErrors = get(formState?.errors, editingItem?.ui.fieldsPath, [])
Expand All @@ -54,46 +55,61 @@ export const useFormTable = (formState, exitEditModeTriggerItem) => {
formStateRef.current = formState
}, [formState])

const applyOrDiscardOrDeleteInEffect = useCallback(() => {
if (editingItemRef?.current) {
if (!editingItemErrorsRef.current) {
exitEditMode()
} else {
if (editingItemRef.current?.ui?.isNew) {
const values = get(formStateRef.current.values, editingItemRef.current?.ui.fieldsPath)
useLayoutEffect(() => {
onExitEditModeCallbackRef.current = onExitEditModeCallback
}, [onExitEditModeCallback])

if (values?.length > 1) {
formStateRef.current.form.mutators.remove(
const exitEditMode = () => {
if (editingItemRef.current?.data) {
Object.entries(editingItemRef.current?.data).forEach(([fieldName]) => {
formStateRef.current?.form.mutators.setFieldState(
`${editingItemRef.current?.ui.fieldsPath}[${editingItemRef.current?.ui.index}].data.${fieldName}`,
{
modified: false
}
)
})
}

editingItemRef.current = null
setEditingItem(null)
onExitEditModeCallbackRef?.current && onExitEditModeCallbackRef.current()
}

useEffect(() => {
const applyOrDiscardOrDeleteInEffect = () => {
if (editingItemRef?.current) {
if (!editingItemErrorsRef.current) {
exitEditMode()
} else {
if (editingItemRef.current?.ui?.isNew) {
const values = get(formStateRef.current.values, editingItemRef.current?.ui.fieldsPath)

if (values?.length > 1) {
formStateRef.current.form.mutators.remove(
editingItemRef.current?.ui.fieldsPath,
editingItemRef.current?.ui.index
)
} else {
formStateRef.current.form.change(editingItemRef.current?.ui.fieldsPath, [])
}
} else {
formStateRef.current.form.mutators.update(
editingItemRef.current?.ui.fieldsPath,
editingItemRef.current?.ui.index
editingItemRef.current?.ui.index,
omit(editingItemRef.current, ['ui'])
)
} else {
formStateRef.current.form.change(editingItemRef.current?.ui.fieldsPath, [])
}
} else {
formStateRef.current.form.mutators.update(
editingItemRef.current?.ui.fieldsPath,
editingItemRef.current?.ui.index,
omit(editingItemRef.current, ['ui'])
)
}

exitEditMode()
exitEditMode()
}
}
}
}, [])

useEffect(() => {
if (editingItemRef?.current) {
applyOrDiscardOrDeleteInEffect()
}
}, [applyOrDiscardOrDeleteInEffect, exitEditModeTriggerItem])

useEffect(() => {
return () => {
applyOrDiscardOrDeleteInEffect()
}
}, [applyOrDiscardOrDeleteInEffect])
}, [exitEditModeTriggerItem])

const addNewRow = (event, fields, fieldsPath, newItem) => {
applyOrDiscardOrDelete(event)
Expand Down Expand Up @@ -151,7 +167,6 @@ export const useFormTable = (formState, exitEditModeTriggerItem) => {
}

exitEditMode()

event && event.stopPropagation()
}

Expand Down Expand Up @@ -202,22 +217,6 @@ export const useFormTable = (formState, exitEditModeTriggerItem) => {
})
}

const exitEditMode = () => {
if (editingItemRef.current?.data) {
Object.entries(editingItemRef.current?.data).forEach(([fieldName]) => {
formStateRef.current?.form.mutators.setFieldState(
`${editingItemRef.current?.ui.fieldsPath}[${editingItemRef.current?.ui.index}].data.${fieldName}`,
{
modified: false
}
)
})
}

editingItemRef.current = null
setEditingItem(null)
}

const scrollIntoView = () => {
if (bottomScrollRef.current) {
setTimeout(() => {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/scss/mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,8 @@
width: 100%;

&__label {
display: flex;
align-items: center;
margin-bottom: 5px;
color: $topaz;
font-size: 12px;
Expand Down
Loading

0 comments on commit d9912f5

Please sign in to comment.