mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: add context value descriptions (#874)
* feat: add context value descriptions * refcator: use ConditionallyRender for ...conditional render * refactor: fix context form enter behaviour * refactor: decrease margin between inputs * refactor: show error on missing value * refactor: disable add button on error * refactor: avoid clearing value error on name focus
This commit is contained in:
		
							parent
							
								
									9200e74c90
								
							
						
					
					
						commit
						cb8add5c30
					
				| @ -0,0 +1,16 @@ | ||||
| import { makeStyles } from '@material-ui/core/styles'; | ||||
| 
 | ||||
| export const useStyles = makeStyles(theme => ({ | ||||
|     container: { | ||||
|         display: 'inline-block', | ||||
|     }, | ||||
|     value: { | ||||
|         lineHeight: 1.33, | ||||
|         fontSize: theme.fontSizes.smallBody, | ||||
|     }, | ||||
|     description: { | ||||
|         lineHeight: 1.33, | ||||
|         fontSize: theme.fontSizes.smallerBody, | ||||
|         color: theme.palette.grey[700], | ||||
|     }, | ||||
| })); | ||||
| @ -0,0 +1,39 @@ | ||||
| import { ILegalValue } from 'interfaces/context'; | ||||
| import { useStyles } from './LegalValueLabel.styles'; | ||||
| import React from 'react'; | ||||
| import { FormControlLabel } from '@material-ui/core'; | ||||
| 
 | ||||
| interface ILegalValueTextProps { | ||||
|     legal: ILegalValue; | ||||
|     control: React.ReactElement; | ||||
| } | ||||
| 
 | ||||
| export const LegalValueLabel = ({ legal, control }: ILegalValueTextProps) => { | ||||
|     const styles = useStyles(); | ||||
| 
 | ||||
|     return ( | ||||
|         <div className={styles.container}> | ||||
|             <FormControlLabel | ||||
|                 value={legal.value} | ||||
|                 control={control} | ||||
|                 label={ | ||||
|                     <> | ||||
|                         <div className={styles.value}>{legal.value}</div> | ||||
|                         <div className={styles.description}> | ||||
|                             {legal.description} | ||||
|                         </div> | ||||
|                     </> | ||||
|                 } | ||||
|             /> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export const filterLegalValues = ( | ||||
|     legalValues: ILegalValue[], | ||||
|     filter: string | ||||
| ): ILegalValue[] => { | ||||
|     return legalValues.filter(legalValue => { | ||||
|         return legalValue.value.includes(filter); | ||||
|     }); | ||||
| }; | ||||
| @ -18,6 +18,7 @@ import { | ||||
|     IN_OPERATORS_FREETEXT, | ||||
|     Input, | ||||
| } from '../useConstraintInput/useConstraintInput'; | ||||
| import React from 'react'; | ||||
| 
 | ||||
| interface IResolveInputProps { | ||||
|     contextDefinition: IUnleashContextDefinition; | ||||
| @ -81,7 +82,7 @@ export const ResolveInput = ({ | ||||
|                             type="number" | ||||
|                             legalValues={ | ||||
|                                 contextDefinition.legalValues?.filter( | ||||
|                                     (value: string) => Number(value) | ||||
|                                     legalValue => Number(legalValue.value) | ||||
|                                 ) || [] | ||||
|                             } | ||||
|                             error={error} | ||||
|  | ||||
| @ -1,13 +1,17 @@ | ||||
| import { Checkbox, FormControlLabel } from '@material-ui/core'; | ||||
| import { Checkbox } from '@material-ui/core'; | ||||
| import { useCommonStyles } from 'themes/commonStyles'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { useEffect, useState } from 'react'; | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch'; | ||||
| import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader'; | ||||
| import { ILegalValue } from 'interfaces/context'; | ||||
| import { | ||||
|     LegalValueLabel, | ||||
|     filterLegalValues, | ||||
| } from '../LegalValueLabel/LegalValueLabel'; | ||||
| 
 | ||||
| // Parent component
 | ||||
| interface IRestrictiveLegalValuesProps { | ||||
|     legalValues: string[]; | ||||
|     legalValues: ILegalValue[]; | ||||
|     values: string[]; | ||||
|     setValues: (values: string[]) => void; | ||||
|     beforeValues?: JSX.Element; | ||||
| @ -36,6 +40,8 @@ export const RestrictiveLegalValues = ({ | ||||
|     setError, | ||||
| }: IRestrictiveLegalValuesProps) => { | ||||
|     const [filter, setFilter] = useState(''); | ||||
|     const filteredValues = filterLegalValues(legalValues, filter); | ||||
| 
 | ||||
|     // Lazily initialise the values because there might be a lot of them.
 | ||||
|     const [valuesMap, setValuesMap] = useState(() => createValuesMap(values)); | ||||
|     const styles = useCommonStyles(); | ||||
| @ -63,12 +69,20 @@ export const RestrictiveLegalValues = ({ | ||||
|                 Select values from a predefined set | ||||
|             </ConstraintFormHeader> | ||||
|             <ConstraintValueSearch filter={filter} setFilter={setFilter} /> | ||||
|             <LegalValueOptions | ||||
|                 legalValues={legalValues} | ||||
|                 filter={filter} | ||||
|                 onChange={onChange} | ||||
|                 valuesMap={valuesMap} | ||||
|             /> | ||||
|             {filteredValues.map(match => ( | ||||
|                 <LegalValueLabel | ||||
|                     key={match.value} | ||||
|                     legal={match} | ||||
|                     control={ | ||||
|                         <Checkbox | ||||
|                             checked={Boolean(valuesMap[match.value])} | ||||
|                             onChange={() => onChange(match.value)} | ||||
|                             name={match.value} | ||||
|                             color="primary" | ||||
|                         /> | ||||
|                     } | ||||
|                 /> | ||||
|             ))} | ||||
|             <ConditionallyRender | ||||
|                 condition={Boolean(error)} | ||||
|                 show={<p className={styles.error}>{error}</p>} | ||||
| @ -76,41 +90,3 @@ export const RestrictiveLegalValues = ({ | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| // Child component
 | ||||
| interface ILegalValueOptionsProps { | ||||
|     legalValues: string[]; | ||||
|     filter: string; | ||||
|     onChange: (legalValue: string) => void; | ||||
|     valuesMap: IValuesMap; | ||||
| } | ||||
| 
 | ||||
| const LegalValueOptions = ({ | ||||
|     legalValues, | ||||
|     filter, | ||||
|     onChange, | ||||
|     valuesMap, | ||||
| }: ILegalValueOptionsProps) => { | ||||
|     return ( | ||||
|         <> | ||||
|             {legalValues | ||||
|                 .filter(legalValue => legalValue.includes(filter)) | ||||
|                 .map(legalValue => { | ||||
|                     return ( | ||||
|                         <FormControlLabel | ||||
|                             key={legalValue} | ||||
|                             control={ | ||||
|                                 <Checkbox | ||||
|                                     checked={Boolean(valuesMap[legalValue])} | ||||
|                                     onChange={() => onChange(legalValue)} | ||||
|                                     color="primary" | ||||
|                                     name={legalValue} | ||||
|                                 /> | ||||
|                             } | ||||
|                             label={legalValue} | ||||
|                         /> | ||||
|                     ); | ||||
|                 })} | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,23 +1,20 @@ | ||||
| import { useState } from 'react'; | ||||
| import React, { useState } from 'react'; | ||||
| import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader'; | ||||
| import { | ||||
|     FormControl, | ||||
|     FormLabel, | ||||
|     FormControlLabel, | ||||
|     RadioGroup, | ||||
|     Radio, | ||||
| } from '@material-ui/core'; | ||||
| import { FormControl, RadioGroup, Radio } from '@material-ui/core'; | ||||
| import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { useCommonStyles } from 'themes/commonStyles'; | ||||
| 
 | ||||
| // Parent component
 | ||||
| import { ILegalValue } from 'interfaces/context'; | ||||
| import { | ||||
|     LegalValueLabel, | ||||
|     filterLegalValues, | ||||
| } from '../LegalValueLabel/LegalValueLabel'; | ||||
| 
 | ||||
| interface ISingleLegalValueProps { | ||||
|     setValue: (value: string) => void; | ||||
|     value?: string; | ||||
|     type: string; | ||||
|     legalValues: string[]; | ||||
|     legalValues: ILegalValue[]; | ||||
|     error: string; | ||||
|     setError: React.Dispatch<React.SetStateAction<string>>; | ||||
| } | ||||
| @ -32,21 +29,18 @@ export const SingleLegalValue = ({ | ||||
| }: ISingleLegalValueProps) => { | ||||
|     const [filter, setFilter] = useState(''); | ||||
|     const styles = useCommonStyles(); | ||||
|     const filteredValues = filterLegalValues(legalValues, filter); | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <ConstraintFormHeader> | ||||
|                 Add a single {type.toLowerCase()} value | ||||
|             </ConstraintFormHeader> | ||||
| 
 | ||||
|             <ConstraintValueSearch filter={filter} setFilter={setFilter} /> | ||||
|             <ConditionallyRender | ||||
|                 condition={Boolean(legalValues.length)} | ||||
|                 show={ | ||||
|                     <FormControl component="fieldset"> | ||||
|                         <FormLabel component="legend"> | ||||
|                             Available values | ||||
|                         </FormLabel> | ||||
|                         <RadioGroup | ||||
|                             aria-label="selected-value" | ||||
|                             name="selected" | ||||
| @ -56,10 +50,13 @@ export const SingleLegalValue = ({ | ||||
|                                 setValue(e.target.value); | ||||
|                             }} | ||||
|                         > | ||||
|                             <RadioOptions | ||||
|                                 legalValues={legalValues} | ||||
|                                 filter={filter} | ||||
|                             /> | ||||
|                             {filteredValues.map(match => ( | ||||
|                                 <LegalValueLabel | ||||
|                                     key={match.value} | ||||
|                                     legal={match} | ||||
|                                     control={<Radio />} | ||||
|                                 /> | ||||
|                             ))} | ||||
|                         </RadioGroup> | ||||
|                     </FormControl> | ||||
|                 } | ||||
| @ -74,28 +71,3 @@ export const SingleLegalValue = ({ | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| // Child components
 | ||||
| interface IRadioOptionsProps { | ||||
|     legalValues: string[]; | ||||
|     filter: string; | ||||
| } | ||||
| 
 | ||||
| const RadioOptions = ({ legalValues, filter }: IRadioOptionsProps) => { | ||||
|     return ( | ||||
|         <> | ||||
|             {legalValues | ||||
|                 .filter(legalValue => legalValue.includes(filter)) | ||||
|                 .map((value, index) => { | ||||
|                     return ( | ||||
|                         <FormControlLabel | ||||
|                             key={`${value}-${index}`} | ||||
|                             value={value} | ||||
|                             control={<Radio />} | ||||
|                             label={value} | ||||
|                         /> | ||||
|                     ); | ||||
|                 })} | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -0,0 +1,36 @@ | ||||
| import { makeStyles } from '@material-ui/core/styles'; | ||||
| 
 | ||||
| export const useStyles = makeStyles(theme => ({ | ||||
|     container: { | ||||
|         display: 'grid', | ||||
|         lineHeight: 1.25, | ||||
|         gridTemplateColumns: '1fr auto', | ||||
|         alignSelf: 'start', | ||||
|         alignItems: 'start', | ||||
|         gap: '0.5rem', | ||||
|         padding: '0.5rem', | ||||
|         background: theme.palette.grey[200], | ||||
|         borderRadius: theme.borders.radius.main, | ||||
|     }, | ||||
|     label: { | ||||
|         fontSize: theme.fontSizes.smallBody, | ||||
|     }, | ||||
|     description: { | ||||
|         fontSize: theme.fontSizes.smallerBody, | ||||
|         color: theme.palette.grey[700], | ||||
|     }, | ||||
|     button: { | ||||
|         all: 'unset', | ||||
|         lineHeight: 0.1, | ||||
|         paddingTop: 1, | ||||
|         display: 'block', | ||||
|         cursor: 'pointer', | ||||
|         '& svg': { | ||||
|             fontSize: '1rem', | ||||
|             opacity: 0.5, | ||||
|         }, | ||||
|         '&:hover svg, &:focus-visible svg': { | ||||
|             opacity: 0.75, | ||||
|         }, | ||||
|     }, | ||||
| })); | ||||
| @ -0,0 +1,34 @@ | ||||
| import { useStyles } from 'component/context/ContectFormChip/ContextFormChip.styles'; | ||||
| import { Cancel } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| 
 | ||||
| interface IContextFormChipProps { | ||||
|     label: string; | ||||
|     description?: string; | ||||
|     onRemove: () => void; | ||||
| } | ||||
| 
 | ||||
| export const ContextFormChip = ({ | ||||
|     label, | ||||
|     description, | ||||
|     onRemove, | ||||
| }: IContextFormChipProps) => { | ||||
|     const styles = useStyles(); | ||||
| 
 | ||||
|     return ( | ||||
|         <li className={styles.container}> | ||||
|             <div> | ||||
|                 <div className={styles.label}>{label}</div> | ||||
|                 <ConditionallyRender | ||||
|                     condition={Boolean(description)} | ||||
|                     show={() => ( | ||||
|                         <div className={styles.description}>{description}</div> | ||||
|                     )} | ||||
|                 /> | ||||
|             </div> | ||||
|             <button onClick={onRemove} className={styles.button}> | ||||
|                 <Cancel titleAccess="Remove" /> | ||||
|             </button> | ||||
|         </li> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,13 @@ | ||||
| import { makeStyles } from '@material-ui/core/styles'; | ||||
| 
 | ||||
| export const useStyles = makeStyles(theme => ({ | ||||
|     container: { | ||||
|         listStyleType: 'none', | ||||
|         display: 'flex', | ||||
|         flexWrap: 'wrap', | ||||
|         gap: '0.5rem', | ||||
|         padding: 0, | ||||
|         margin: 0, | ||||
|         marginBottom: '1rem !important', | ||||
|     }, | ||||
| })); | ||||
| @ -0,0 +1,8 @@ | ||||
| import { useStyles } from 'component/context/ContectFormChip/ContextFormChipList.styles'; | ||||
| import React from 'react'; | ||||
| 
 | ||||
| export const ContextFormChipList: React.FC = ({ children }) => { | ||||
|     const styles = useStyles(); | ||||
| 
 | ||||
|     return <ul className={styles.container}>{children}</ul>; | ||||
| }; | ||||
| @ -20,17 +20,16 @@ export const useStyles = makeStyles(theme => ({ | ||||
|         }, | ||||
|     }, | ||||
|     tagContainer: { | ||||
|         display: 'flex', | ||||
|         alignItems: 'flex-start', | ||||
|         display: 'grid', | ||||
|         gridTemplateColumns: '1fr auto', | ||||
|         gap: '0.5rem', | ||||
|         marginBottom: '1rem', | ||||
|     }, | ||||
|     tagInput: { | ||||
|         width: '75%', | ||||
|         marginRight: 'auto', | ||||
|         gridColumn: 1, | ||||
|     }, | ||||
|     tagValue: { | ||||
|         marginRight: '3px', | ||||
|         marginBottom: '1rem', | ||||
|     tagButton: { | ||||
|         gridColumn: 2, | ||||
|     }, | ||||
|     buttonContainer: { | ||||
|         marginTop: 'auto', | ||||
|  | ||||
| @ -1,24 +1,26 @@ | ||||
| import Input from 'component/common/Input/Input'; | ||||
| import { TextField, Button, Switch, Chip, Typography } from '@material-ui/core'; | ||||
| import { TextField, Button, Switch, Typography } from '@material-ui/core'; | ||||
| import { useStyles } from './ContextForm.styles'; | ||||
| import React, { useState } from 'react'; | ||||
| import React, { useState, useEffect } from 'react'; | ||||
| import { Add } from '@material-ui/icons'; | ||||
| import { trim } from 'component/common/util'; | ||||
| import { ILegalValue } from 'interfaces/context'; | ||||
| import { ContextFormChip } from 'component/context/ContectFormChip/ContextFormChip'; | ||||
| import { ContextFormChipList } from 'component/context/ContectFormChip/ContextFormChipList'; | ||||
| 
 | ||||
| interface IContextForm { | ||||
|     contextName: string; | ||||
|     contextDesc: string; | ||||
|     legalValues: Array<string>; | ||||
|     legalValues: ILegalValue[]; | ||||
|     stickiness: boolean; | ||||
|     setContextName: React.Dispatch<React.SetStateAction<string>>; | ||||
|     setContextDesc: React.Dispatch<React.SetStateAction<string>>; | ||||
|     setStickiness: React.Dispatch<React.SetStateAction<boolean>>; | ||||
|     setLegalValues: React.Dispatch<React.SetStateAction<string[]>>; | ||||
|     setLegalValues: React.Dispatch<React.SetStateAction<ILegalValue[]>>; | ||||
|     handleSubmit: (e: any) => void; | ||||
|     onCancel: () => void; | ||||
|     errors: { [key: string]: string }; | ||||
|     mode: 'Create' | 'Edit'; | ||||
|     clearErrors: () => void; | ||||
|     clearErrors: (key?: string) => void; | ||||
|     validateContext?: () => void; | ||||
|     setErrors: React.Dispatch<React.SetStateAction<Object>>; | ||||
| } | ||||
| @ -45,54 +47,64 @@ export const ContextForm: React.FC<IContextForm> = ({ | ||||
| }) => { | ||||
|     const styles = useStyles(); | ||||
|     const [value, setValue] = useState(''); | ||||
|     const [focused, setFocused] = useState(false); | ||||
|     const [valueDesc, setValueDesc] = useState(''); | ||||
|     const [valueFocused, setValueFocused] = useState(false); | ||||
| 
 | ||||
|     const submit = (event: React.SyntheticEvent) => { | ||||
|     const isMissingValue = valueDesc.trim() && !value.trim(); | ||||
| 
 | ||||
|     const isDuplicateValue = legalValues.some(legalValue => { | ||||
|         return legalValue.value.trim() === value.trim(); | ||||
|     }); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setErrors(prev => ({ | ||||
|             ...prev, | ||||
|             tag: isMissingValue | ||||
|                 ? 'Value cannot be empty' | ||||
|                 : isDuplicateValue | ||||
|                 ? 'Duplicate value' | ||||
|                 : undefined, | ||||
|         })); | ||||
|     }, [setErrors, isMissingValue, isDuplicateValue]); | ||||
| 
 | ||||
|     const onSubmit = (event: React.SyntheticEvent) => { | ||||
|         event.preventDefault(); | ||||
|         if (focused) return; | ||||
|         handleSubmit(event); | ||||
|     }; | ||||
| 
 | ||||
|     const handleKeyDown = (event: React.KeyboardEvent) => { | ||||
|         if (event.key === ENTER && focused) { | ||||
|             addLegalValue(); | ||||
|             return; | ||||
|         } else if (event.key === ENTER) { | ||||
|             handleSubmit(event); | ||||
|     const onKeyDown = (event: React.KeyboardEvent) => { | ||||
|         if (event.key === ENTER) { | ||||
|             event.preventDefault(); | ||||
|             if (valueFocused) { | ||||
|                 addLegalValue(); | ||||
|             } else { | ||||
|                 handleSubmit(event); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const sortIgnoreCase = (a: string, b: string) => { | ||||
|         a = a.toLowerCase(); | ||||
|         b = b.toLowerCase(); | ||||
|         if (a === b) return 0; | ||||
|         if (a > b) return 1; | ||||
|         return -1; | ||||
|     const sortLegalValues = (a: ILegalValue, b: ILegalValue) => { | ||||
|         return a.value.toLowerCase().localeCompare(b.value.toLowerCase()); | ||||
|     }; | ||||
| 
 | ||||
|     const addLegalValue = () => { | ||||
|         clearErrors(); | ||||
|         if (!value) { | ||||
|             return; | ||||
|         const next: ILegalValue = { | ||||
|             value: value.trim(), | ||||
|             description: valueDesc.trim(), | ||||
|         }; | ||||
|         if (next.value && !isDuplicateValue) { | ||||
|             setValue(''); | ||||
|             setValueDesc(''); | ||||
|             setLegalValues(prev => [...prev, next].sort(sortLegalValues)); | ||||
|         } | ||||
| 
 | ||||
|         if (legalValues.indexOf(value) !== -1) { | ||||
|             setErrors(prev => ({ | ||||
|                 ...prev, | ||||
|                 tag: 'Duplicate legal value', | ||||
|             })); | ||||
|             return; | ||||
|         } | ||||
|         setLegalValues(prev => [...prev, trim(value)].sort(sortIgnoreCase)); | ||||
|         setValue(''); | ||||
|     }; | ||||
|     const removeLegalValue = (index: number) => { | ||||
|         const filteredValues = legalValues.filter((_, i) => i !== index); | ||||
|         setLegalValues([...filteredValues]); | ||||
| 
 | ||||
|     const removeLegalValue = (value: ILegalValue) => { | ||||
|         setLegalValues(prev => prev.filter(p => p.value !== value.value)); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <form onSubmit={submit} className={styles.form}> | ||||
|         <form onSubmit={onSubmit} className={styles.form}> | ||||
|             <div className={styles.container}> | ||||
|                 <p className={styles.inputDescription}> | ||||
|                     What is your context name? | ||||
| @ -102,10 +114,10 @@ export const ContextForm: React.FC<IContextForm> = ({ | ||||
|                     label="Context name" | ||||
|                     value={contextName} | ||||
|                     disabled={mode === 'Edit'} | ||||
|                     onChange={e => setContextName(trim(e.target.value))} | ||||
|                     onChange={e => setContextName(e.target.value.trim())} | ||||
|                     error={Boolean(errors.name)} | ||||
|                     errorText={errors.name} | ||||
|                     onFocus={() => clearErrors()} | ||||
|                     onFocus={() => clearErrors('name')} | ||||
|                     onBlur={validateContext} | ||||
|                     autoFocus | ||||
|                 /> | ||||
| @ -119,25 +131,15 @@ export const ContextForm: React.FC<IContextForm> = ({ | ||||
|                     multiline | ||||
|                     maxRows={4} | ||||
|                     value={contextDesc} | ||||
|                     size="small" | ||||
|                     onChange={e => setContextDesc(e.target.value)} | ||||
|                 /> | ||||
|                 <p className={styles.inputDescription}> | ||||
|                     Which values do you want to allow? | ||||
|                 </p> | ||||
|                 {legalValues.map((value, index) => { | ||||
|                     return ( | ||||
|                         <Chip | ||||
|                             key={index + value} | ||||
|                             label={value} | ||||
|                             className={styles.tagValue} | ||||
|                             onDelete={() => removeLegalValue(index)} | ||||
|                             title="Remove value" | ||||
|                         /> | ||||
|                     ); | ||||
|                 })} | ||||
|                 <div className={styles.tagContainer}> | ||||
|                     <TextField | ||||
|                         label="Value (optional)" | ||||
|                         label="Legal value (optional)" | ||||
|                         name="value" | ||||
|                         className={styles.tagInput} | ||||
|                         value={value} | ||||
| @ -145,20 +147,47 @@ export const ContextForm: React.FC<IContextForm> = ({ | ||||
|                         helperText={errors.tag} | ||||
|                         variant="outlined" | ||||
|                         size="small" | ||||
|                         onChange={e => setValue(trim(e.target.value))} | ||||
|                         onKeyPress={e => handleKeyDown(e)} | ||||
|                         onBlur={e => setFocused(false)} | ||||
|                         onFocus={e => setFocused(true)} | ||||
|                         onChange={e => setValue(e.target.value)} | ||||
|                         onKeyPress={e => onKeyDown(e)} | ||||
|                         onBlur={() => setValueFocused(false)} | ||||
|                         onFocus={() => setValueFocused(true)} | ||||
|                         inputProps={{ maxLength: 100 }} | ||||
|                     /> | ||||
|                     <TextField | ||||
|                         label="Value description (optional)" | ||||
|                         className={styles.tagInput} | ||||
|                         value={valueDesc} | ||||
|                         variant="outlined" | ||||
|                         size="small" | ||||
|                         onChange={e => setValueDesc(e.target.value)} | ||||
|                         onKeyPress={e => onKeyDown(e)} | ||||
|                         onBlur={() => setValueFocused(false)} | ||||
|                         onFocus={() => setValueFocused(true)} | ||||
|                         inputProps={{ maxLength: 100 }} | ||||
|                     /> | ||||
|                     <Button | ||||
|                         className={styles.tagButton} | ||||
|                         startIcon={<Add />} | ||||
|                         onClick={addLegalValue} | ||||
|                         variant="contained" | ||||
|                         variant="outlined" | ||||
|                         color="primary" | ||||
|                         disabled={!value.trim() || isDuplicateValue} | ||||
|                     > | ||||
|                         Add | ||||
|                     </Button> | ||||
|                 </div> | ||||
|                 <ContextFormChipList> | ||||
|                     {legalValues.map(legalValue => { | ||||
|                         return ( | ||||
|                             <ContextFormChip | ||||
|                                 key={legalValue.value} | ||||
|                                 label={legalValue.value} | ||||
|                                 description={legalValue.description} | ||||
|                                 onRemove={() => removeLegalValue(legalValue)} | ||||
|                             /> | ||||
|                         ); | ||||
|                     })} | ||||
|                 </ContextFormChipList> | ||||
|                 <p className={styles.inputHeader}>Custom stickiness</p> | ||||
|                 <p> | ||||
|                     By enabling stickiness on this context field you can use it | ||||
|  | ||||
| @ -1,26 +1,27 @@ | ||||
| import { useEffect, useState } from 'react'; | ||||
| import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi'; | ||||
| import { ILegalValue } from 'interfaces/context'; | ||||
| 
 | ||||
| export const useContextForm = ( | ||||
|     initialcontextName = '', | ||||
|     initialcontextDesc = '', | ||||
|     initialLegalValues = [] as string[], | ||||
|     initialContextName = '', | ||||
|     initialContextDesc = '', | ||||
|     initialLegalValues = [] as ILegalValue[], | ||||
|     initialStickiness = false | ||||
| ) => { | ||||
|     const [contextName, setContextName] = useState(initialcontextName); | ||||
|     const [contextDesc, setContextDesc] = useState(initialcontextDesc); | ||||
|     const [contextName, setContextName] = useState(initialContextName); | ||||
|     const [contextDesc, setContextDesc] = useState(initialContextDesc); | ||||
|     const [legalValues, setLegalValues] = useState(initialLegalValues); | ||||
|     const [stickiness, setStickiness] = useState(initialStickiness); | ||||
|     const [errors, setErrors] = useState({}); | ||||
|     const { validateContextName } = useContextsApi(); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setContextName(initialcontextName); | ||||
|     }, [initialcontextName]); | ||||
|         setContextName(initialContextName); | ||||
|     }, [initialContextName]); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setContextDesc(initialcontextDesc); | ||||
|     }, [initialcontextDesc]); | ||||
|         setContextDesc(initialContextDesc); | ||||
|     }, [initialContextDesc]); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setLegalValues(initialLegalValues); | ||||
| @ -66,8 +67,12 @@ export const useContextForm = ( | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const clearErrors = () => { | ||||
|         setErrors({}); | ||||
|     const clearErrors = (key?: string) => { | ||||
|         if (key) { | ||||
|             setErrors(prev => ({ ...prev, [key]: undefined })); | ||||
|         } else { | ||||
|             setErrors({}); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return { | ||||
|  | ||||
| @ -4,5 +4,10 @@ export interface IUnleashContextDefinition { | ||||
|     createdAt: string; | ||||
|     sortOrder: number; | ||||
|     stickiness: boolean; | ||||
|     legalValues?: string[]; | ||||
|     legalValues?: ILegalValue[]; | ||||
| } | ||||
| 
 | ||||
| export interface ILegalValue { | ||||
|     value: string; | ||||
|     description?: string; | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user