mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +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,
|
IN_OPERATORS_FREETEXT,
|
||||||
Input,
|
Input,
|
||||||
} from '../useConstraintInput/useConstraintInput';
|
} from '../useConstraintInput/useConstraintInput';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
interface IResolveInputProps {
|
interface IResolveInputProps {
|
||||||
contextDefinition: IUnleashContextDefinition;
|
contextDefinition: IUnleashContextDefinition;
|
||||||
@ -81,7 +82,7 @@ export const ResolveInput = ({
|
|||||||
type="number"
|
type="number"
|
||||||
legalValues={
|
legalValues={
|
||||||
contextDefinition.legalValues?.filter(
|
contextDefinition.legalValues?.filter(
|
||||||
(value: string) => Number(value)
|
legalValue => Number(legalValue.value)
|
||||||
) || []
|
) || []
|
||||||
}
|
}
|
||||||
error={error}
|
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 { useCommonStyles } from 'themes/commonStyles';
|
||||||
import ConditionallyRender from 'component/common/ConditionallyRender';
|
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 { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch';
|
||||||
import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
|
import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
|
||||||
|
import { ILegalValue } from 'interfaces/context';
|
||||||
|
import {
|
||||||
|
LegalValueLabel,
|
||||||
|
filterLegalValues,
|
||||||
|
} from '../LegalValueLabel/LegalValueLabel';
|
||||||
|
|
||||||
// Parent component
|
|
||||||
interface IRestrictiveLegalValuesProps {
|
interface IRestrictiveLegalValuesProps {
|
||||||
legalValues: string[];
|
legalValues: ILegalValue[];
|
||||||
values: string[];
|
values: string[];
|
||||||
setValues: (values: string[]) => void;
|
setValues: (values: string[]) => void;
|
||||||
beforeValues?: JSX.Element;
|
beforeValues?: JSX.Element;
|
||||||
@ -36,6 +40,8 @@ export const RestrictiveLegalValues = ({
|
|||||||
setError,
|
setError,
|
||||||
}: IRestrictiveLegalValuesProps) => {
|
}: IRestrictiveLegalValuesProps) => {
|
||||||
const [filter, setFilter] = useState('');
|
const [filter, setFilter] = useState('');
|
||||||
|
const filteredValues = filterLegalValues(legalValues, filter);
|
||||||
|
|
||||||
// Lazily initialise the values because there might be a lot of them.
|
// Lazily initialise the values because there might be a lot of them.
|
||||||
const [valuesMap, setValuesMap] = useState(() => createValuesMap(values));
|
const [valuesMap, setValuesMap] = useState(() => createValuesMap(values));
|
||||||
const styles = useCommonStyles();
|
const styles = useCommonStyles();
|
||||||
@ -63,12 +69,20 @@ export const RestrictiveLegalValues = ({
|
|||||||
Select values from a predefined set
|
Select values from a predefined set
|
||||||
</ConstraintFormHeader>
|
</ConstraintFormHeader>
|
||||||
<ConstraintValueSearch filter={filter} setFilter={setFilter} />
|
<ConstraintValueSearch filter={filter} setFilter={setFilter} />
|
||||||
<LegalValueOptions
|
{filteredValues.map(match => (
|
||||||
legalValues={legalValues}
|
<LegalValueLabel
|
||||||
filter={filter}
|
key={match.value}
|
||||||
onChange={onChange}
|
legal={match}
|
||||||
valuesMap={valuesMap}
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={Boolean(valuesMap[match.value])}
|
||||||
|
onChange={() => onChange(match.value)}
|
||||||
|
name={match.value}
|
||||||
|
color="primary"
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(error)}
|
condition={Boolean(error)}
|
||||||
show={<p className={styles.error}>{error}</p>}
|
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 { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
|
||||||
import {
|
import { FormControl, RadioGroup, Radio } from '@material-ui/core';
|
||||||
FormControl,
|
|
||||||
FormLabel,
|
|
||||||
FormControlLabel,
|
|
||||||
RadioGroup,
|
|
||||||
Radio,
|
|
||||||
} from '@material-ui/core';
|
|
||||||
import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch';
|
import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch';
|
||||||
import ConditionallyRender from 'component/common/ConditionallyRender';
|
import ConditionallyRender from 'component/common/ConditionallyRender';
|
||||||
import { useCommonStyles } from 'themes/commonStyles';
|
import { useCommonStyles } from 'themes/commonStyles';
|
||||||
|
import { ILegalValue } from 'interfaces/context';
|
||||||
// Parent component
|
import {
|
||||||
|
LegalValueLabel,
|
||||||
|
filterLegalValues,
|
||||||
|
} from '../LegalValueLabel/LegalValueLabel';
|
||||||
|
|
||||||
interface ISingleLegalValueProps {
|
interface ISingleLegalValueProps {
|
||||||
setValue: (value: string) => void;
|
setValue: (value: string) => void;
|
||||||
value?: string;
|
value?: string;
|
||||||
type: string;
|
type: string;
|
||||||
legalValues: string[];
|
legalValues: ILegalValue[];
|
||||||
error: string;
|
error: string;
|
||||||
setError: React.Dispatch<React.SetStateAction<string>>;
|
setError: React.Dispatch<React.SetStateAction<string>>;
|
||||||
}
|
}
|
||||||
@ -32,21 +29,18 @@ export const SingleLegalValue = ({
|
|||||||
}: ISingleLegalValueProps) => {
|
}: ISingleLegalValueProps) => {
|
||||||
const [filter, setFilter] = useState('');
|
const [filter, setFilter] = useState('');
|
||||||
const styles = useCommonStyles();
|
const styles = useCommonStyles();
|
||||||
|
const filteredValues = filterLegalValues(legalValues, filter);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConstraintFormHeader>
|
<ConstraintFormHeader>
|
||||||
Add a single {type.toLowerCase()} value
|
Add a single {type.toLowerCase()} value
|
||||||
</ConstraintFormHeader>
|
</ConstraintFormHeader>
|
||||||
|
|
||||||
<ConstraintValueSearch filter={filter} setFilter={setFilter} />
|
<ConstraintValueSearch filter={filter} setFilter={setFilter} />
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(legalValues.length)}
|
condition={Boolean(legalValues.length)}
|
||||||
show={
|
show={
|
||||||
<FormControl component="fieldset">
|
<FormControl component="fieldset">
|
||||||
<FormLabel component="legend">
|
|
||||||
Available values
|
|
||||||
</FormLabel>
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
aria-label="selected-value"
|
aria-label="selected-value"
|
||||||
name="selected"
|
name="selected"
|
||||||
@ -56,10 +50,13 @@ export const SingleLegalValue = ({
|
|||||||
setValue(e.target.value);
|
setValue(e.target.value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RadioOptions
|
{filteredValues.map(match => (
|
||||||
legalValues={legalValues}
|
<LegalValueLabel
|
||||||
filter={filter}
|
key={match.value}
|
||||||
|
legal={match}
|
||||||
|
control={<Radio />}
|
||||||
/>
|
/>
|
||||||
|
))}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</FormControl>
|
</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: {
|
tagContainer: {
|
||||||
display: 'flex',
|
display: 'grid',
|
||||||
alignItems: 'flex-start',
|
gridTemplateColumns: '1fr auto',
|
||||||
|
gap: '0.5rem',
|
||||||
marginBottom: '1rem',
|
marginBottom: '1rem',
|
||||||
},
|
},
|
||||||
tagInput: {
|
tagInput: {
|
||||||
width: '75%',
|
gridColumn: 1,
|
||||||
marginRight: 'auto',
|
|
||||||
},
|
},
|
||||||
tagValue: {
|
tagButton: {
|
||||||
marginRight: '3px',
|
gridColumn: 2,
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
},
|
||||||
buttonContainer: {
|
buttonContainer: {
|
||||||
marginTop: 'auto',
|
marginTop: 'auto',
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
import Input from 'component/common/Input/Input';
|
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 { useStyles } from './ContextForm.styles';
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Add } from '@material-ui/icons';
|
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 {
|
interface IContextForm {
|
||||||
contextName: string;
|
contextName: string;
|
||||||
contextDesc: string;
|
contextDesc: string;
|
||||||
legalValues: Array<string>;
|
legalValues: ILegalValue[];
|
||||||
stickiness: boolean;
|
stickiness: boolean;
|
||||||
setContextName: React.Dispatch<React.SetStateAction<string>>;
|
setContextName: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setContextDesc: React.Dispatch<React.SetStateAction<string>>;
|
setContextDesc: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setStickiness: React.Dispatch<React.SetStateAction<boolean>>;
|
setStickiness: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setLegalValues: React.Dispatch<React.SetStateAction<string[]>>;
|
setLegalValues: React.Dispatch<React.SetStateAction<ILegalValue[]>>;
|
||||||
handleSubmit: (e: any) => void;
|
handleSubmit: (e: any) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
errors: { [key: string]: string };
|
errors: { [key: string]: string };
|
||||||
mode: 'Create' | 'Edit';
|
mode: 'Create' | 'Edit';
|
||||||
clearErrors: () => void;
|
clearErrors: (key?: string) => void;
|
||||||
validateContext?: () => void;
|
validateContext?: () => void;
|
||||||
setErrors: React.Dispatch<React.SetStateAction<Object>>;
|
setErrors: React.Dispatch<React.SetStateAction<Object>>;
|
||||||
}
|
}
|
||||||
@ -45,54 +47,64 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const [value, setValue] = useState('');
|
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();
|
event.preventDefault();
|
||||||
if (focused) return;
|
|
||||||
handleSubmit(event);
|
handleSubmit(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (event: React.KeyboardEvent) => {
|
const onKeyDown = (event: React.KeyboardEvent) => {
|
||||||
if (event.key === ENTER && focused) {
|
if (event.key === ENTER) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (valueFocused) {
|
||||||
addLegalValue();
|
addLegalValue();
|
||||||
return;
|
} else {
|
||||||
} else if (event.key === ENTER) {
|
|
||||||
handleSubmit(event);
|
handleSubmit(event);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortIgnoreCase = (a: string, b: string) => {
|
const sortLegalValues = (a: ILegalValue, b: ILegalValue) => {
|
||||||
a = a.toLowerCase();
|
return a.value.toLowerCase().localeCompare(b.value.toLowerCase());
|
||||||
b = b.toLowerCase();
|
|
||||||
if (a === b) return 0;
|
|
||||||
if (a > b) return 1;
|
|
||||||
return -1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addLegalValue = () => {
|
const addLegalValue = () => {
|
||||||
clearErrors();
|
const next: ILegalValue = {
|
||||||
if (!value) {
|
value: value.trim(),
|
||||||
return;
|
description: valueDesc.trim(),
|
||||||
}
|
|
||||||
|
|
||||||
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) => {
|
if (next.value && !isDuplicateValue) {
|
||||||
const filteredValues = legalValues.filter((_, i) => i !== index);
|
setValue('');
|
||||||
setLegalValues([...filteredValues]);
|
setValueDesc('');
|
||||||
|
setLegalValues(prev => [...prev, next].sort(sortLegalValues));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeLegalValue = (value: ILegalValue) => {
|
||||||
|
setLegalValues(prev => prev.filter(p => p.value !== value.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={submit} className={styles.form}>
|
<form onSubmit={onSubmit} className={styles.form}>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<p className={styles.inputDescription}>
|
<p className={styles.inputDescription}>
|
||||||
What is your context name?
|
What is your context name?
|
||||||
@ -102,10 +114,10 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
label="Context name"
|
label="Context name"
|
||||||
value={contextName}
|
value={contextName}
|
||||||
disabled={mode === 'Edit'}
|
disabled={mode === 'Edit'}
|
||||||
onChange={e => setContextName(trim(e.target.value))}
|
onChange={e => setContextName(e.target.value.trim())}
|
||||||
error={Boolean(errors.name)}
|
error={Boolean(errors.name)}
|
||||||
errorText={errors.name}
|
errorText={errors.name}
|
||||||
onFocus={() => clearErrors()}
|
onFocus={() => clearErrors('name')}
|
||||||
onBlur={validateContext}
|
onBlur={validateContext}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
@ -119,25 +131,15 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
multiline
|
multiline
|
||||||
maxRows={4}
|
maxRows={4}
|
||||||
value={contextDesc}
|
value={contextDesc}
|
||||||
|
size="small"
|
||||||
onChange={e => setContextDesc(e.target.value)}
|
onChange={e => setContextDesc(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<p className={styles.inputDescription}>
|
<p className={styles.inputDescription}>
|
||||||
Which values do you want to allow?
|
Which values do you want to allow?
|
||||||
</p>
|
</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}>
|
<div className={styles.tagContainer}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Value (optional)"
|
label="Legal value (optional)"
|
||||||
name="value"
|
name="value"
|
||||||
className={styles.tagInput}
|
className={styles.tagInput}
|
||||||
value={value}
|
value={value}
|
||||||
@ -145,20 +147,47 @@ export const ContextForm: React.FC<IContextForm> = ({
|
|||||||
helperText={errors.tag}
|
helperText={errors.tag}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
onChange={e => setValue(trim(e.target.value))}
|
onChange={e => setValue(e.target.value)}
|
||||||
onKeyPress={e => handleKeyDown(e)}
|
onKeyPress={e => onKeyDown(e)}
|
||||||
onBlur={e => setFocused(false)}
|
onBlur={() => setValueFocused(false)}
|
||||||
onFocus={e => setFocused(true)}
|
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
|
<Button
|
||||||
|
className={styles.tagButton}
|
||||||
startIcon={<Add />}
|
startIcon={<Add />}
|
||||||
onClick={addLegalValue}
|
onClick={addLegalValue}
|
||||||
variant="contained"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
disabled={!value.trim() || isDuplicateValue}
|
||||||
>
|
>
|
||||||
Add
|
Add
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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 className={styles.inputHeader}>Custom stickiness</p>
|
||||||
<p>
|
<p>
|
||||||
By enabling stickiness on this context field you can use it
|
By enabling stickiness on this context field you can use it
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi';
|
import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi';
|
||||||
|
import { ILegalValue } from 'interfaces/context';
|
||||||
|
|
||||||
export const useContextForm = (
|
export const useContextForm = (
|
||||||
initialcontextName = '',
|
initialContextName = '',
|
||||||
initialcontextDesc = '',
|
initialContextDesc = '',
|
||||||
initialLegalValues = [] as string[],
|
initialLegalValues = [] as ILegalValue[],
|
||||||
initialStickiness = false
|
initialStickiness = false
|
||||||
) => {
|
) => {
|
||||||
const [contextName, setContextName] = useState(initialcontextName);
|
const [contextName, setContextName] = useState(initialContextName);
|
||||||
const [contextDesc, setContextDesc] = useState(initialcontextDesc);
|
const [contextDesc, setContextDesc] = useState(initialContextDesc);
|
||||||
const [legalValues, setLegalValues] = useState(initialLegalValues);
|
const [legalValues, setLegalValues] = useState(initialLegalValues);
|
||||||
const [stickiness, setStickiness] = useState(initialStickiness);
|
const [stickiness, setStickiness] = useState(initialStickiness);
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
const { validateContextName } = useContextsApi();
|
const { validateContextName } = useContextsApi();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setContextName(initialcontextName);
|
setContextName(initialContextName);
|
||||||
}, [initialcontextName]);
|
}, [initialContextName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setContextDesc(initialcontextDesc);
|
setContextDesc(initialContextDesc);
|
||||||
}, [initialcontextDesc]);
|
}, [initialContextDesc]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLegalValues(initialLegalValues);
|
setLegalValues(initialLegalValues);
|
||||||
@ -66,8 +67,12 @@ export const useContextForm = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearErrors = () => {
|
const clearErrors = (key?: string) => {
|
||||||
|
if (key) {
|
||||||
|
setErrors(prev => ({ ...prev, [key]: undefined }));
|
||||||
|
} else {
|
||||||
setErrors({});
|
setErrors({});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -4,5 +4,10 @@ export interface IUnleashContextDefinition {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
stickiness: boolean;
|
stickiness: boolean;
|
||||||
legalValues?: string[];
|
legalValues?: ILegalValue[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILegalValue {
|
||||||
|
value: string;
|
||||||
|
description?: string;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user