1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-27 11:02:16 +01:00
unleash.unleash/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/AddValuesWidget.tsx
Thomas Heartman a7118e0c18
chore(1-3639): constraint validation (#9909)
Implements client-side validation of constraint values before you can
add them to a constraint.

I've removed the extra server-side validation that used to happen for
each specific constraint, because the surrounding form itself uses
server side validation to check every constraint every time there's a
change. This is what controls disabling the submit button etc.

I wanna make the next PR a bit of a followup cleanup now that it's
clearer what properties we do and don't need.
<img width="371" alt="image"
src="https://github.com/user-attachments/assets/7c98708f-fcbe-40ca-8590-bb0f5b2ad167"
/>
<img width="361" alt="image"
src="https://github.com/user-attachments/assets/503d4841-d910-4e8e-b0ef-a3d725739534"
/>
2025-05-06 15:21:33 +02:00

93 lines
3.3 KiB
TypeScript

import Add from '@mui/icons-material/Add';
import { styled } from '@mui/material';
import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
import { parseParameterStrings } from 'utils/parseParameter';
import { baseChipStyles } from './ValueList';
import { AddValuesPopover, type OnAddActions } from './AddValuesPopover';
import type { ConstraintValidatorOutput } from 'component/common/NewConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators';
// todo: MUI v6 / v7 upgrade: consider changing this to a Chip to align with the rest of the values and the single value selector. There was a fix introduced in v6 that makes you not lose focus on pressing esc: https://mui.com/material-ui/migration/upgrade-to-v6/#chip talk to Thomas for more info.
const AddValuesButton = styled('button')(({ theme }) => ({
...baseChipStyles(theme),
color: theme.palette.primary.main,
svg: {
fill: theme.palette.primary.main,
height: theme.fontSizes.smallerBody,
width: theme.fontSizes.smallerBody,
},
border: 'none',
borderRadius: theme.shape.borderRadiusExtraLarge,
display: 'flex',
flexFlow: 'row nowrap',
whiteSpace: 'nowrap',
gap: theme.spacing(0.25),
alignItems: 'center',
padding: theme.spacing(0.5, 1.5, 0.5, 1.5),
height: 'auto',
cursor: 'pointer',
}));
interface AddValuesProps {
onAddValues: (newValues: string[]) => void;
helpText?: string;
validator: (...values: string[]) => ConstraintValidatorOutput;
}
export const AddValuesWidget = forwardRef<HTMLButtonElement, AddValuesProps>(
({ onAddValues, helpText, validator }, ref) => {
const [open, setOpen] = useState(false);
const positioningRef = useRef<HTMLButtonElement>(null);
useImperativeHandle(
ref,
() => positioningRef.current as HTMLButtonElement,
);
const handleAdd = (
inputValues: string,
{ setError, clearInput }: OnAddActions,
) => {
const newValues = parseParameterStrings(inputValues);
if (newValues.length === 0) {
setError('Values cannot be empty');
return;
}
if (newValues.some((v) => v.length > 100)) {
setError('Values cannot be longer than 100 characters');
return;
}
const [isValid, errorMessage] = validator(...newValues);
if (isValid) {
onAddValues(newValues);
clearInput();
setError('');
} else {
setError(errorMessage);
}
};
return (
<>
<AddValuesButton
ref={positioningRef}
onClick={() => setOpen(true)}
type='button'
>
<Add />
<span>Add values</span>
</AddValuesButton>
<AddValuesPopover
onAdd={handleAdd}
helpText={helpText}
open={open}
anchorEl={positioningRef.current}
onClose={() => setOpen(false)}
/>
</>
);
},
);