mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: add date type input field for constraints. (#9864)
Adds a date input method for editable constraints. Uses a modified version of `frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/DateSingleValue/DateSingleValue.tsx`, which has been marked as deprecated. <img width="971" alt="image" src="https://github.com/user-attachments/assets/3c6f6e1f-6156-444c-9a73-e0c9c1c52ad6" /> Wraps when necessary <img width="471" alt="image" src="https://github.com/user-attachments/assets/786be9d0-e62e-4bc2-884d-ef6f4aaf6b51" /> Additionally, because I noticed how the old date input sets the error, I've switched to using the standard way of setting input errors in Unleash (and presumably for MUI) <img width="359" alt="image" src="https://github.com/user-attachments/assets/31e6ce7c-ad5d-4432-a89f-b4d9d491bd99" />
This commit is contained in:
		
							parent
							
								
									0e1ab236c9
								
							
						
					
					
						commit
						44b4ba7f60
					
				@ -21,6 +21,10 @@ const StyledWrapper = styled('div')(({ theme }) => ({
 | 
			
		||||
    gap: theme.spacing(1),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @deprecated use `component/feature/FeatureStrategy/FeatureStrategyConstraints/ConstraintDateInput.tsx`
 | 
			
		||||
 * Remove with flag `addEditStrategy`
 | 
			
		||||
 */
 | 
			
		||||
export const DateSingleValue = ({
 | 
			
		||||
    setValue,
 | 
			
		||||
    value,
 | 
			
		||||
 | 
			
		||||
@ -22,12 +22,6 @@ const InputRow = styled('div')(({ theme }) => ({
 | 
			
		||||
    width: '100%',
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const ErrorMessage = styled('div')(({ theme }) => ({
 | 
			
		||||
    color: theme.palette.error.main,
 | 
			
		||||
    fontSize: theme.typography.caption.fontSize,
 | 
			
		||||
    marginBottom: theme.spacing(1),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export type OnAddActions = {
 | 
			
		||||
    setError: (error: string) => void;
 | 
			
		||||
    clearInput: () => void;
 | 
			
		||||
@ -93,7 +87,6 @@ export const AddValuesPopover: FC<AddValuesProps> = ({
 | 
			
		||||
                    }
 | 
			
		||||
                }}
 | 
			
		||||
            >
 | 
			
		||||
                {error && <ErrorMessage>{error}</ErrorMessage>}
 | 
			
		||||
                <InputRow>
 | 
			
		||||
                    <ScreenReaderOnly>
 | 
			
		||||
                        <label htmlFor={inputId}>Constraint Value</label>
 | 
			
		||||
@ -111,6 +104,8 @@ export const AddValuesPopover: FC<AddValuesProps> = ({
 | 
			
		||||
                        fullWidth
 | 
			
		||||
                        inputRef={inputRef}
 | 
			
		||||
                        autoFocus
 | 
			
		||||
                        error={!!error}
 | 
			
		||||
                        helperText={error}
 | 
			
		||||
                    />
 | 
			
		||||
                    <Button
 | 
			
		||||
                        variant='text'
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,95 @@
 | 
			
		||||
import Input from 'component/common/Input/Input';
 | 
			
		||||
import { parseDateValue, parseValidDate } from 'component/common/util';
 | 
			
		||||
 | 
			
		||||
import { useId, useMemo, useState } from 'react';
 | 
			
		||||
import { styled } from '@mui/material';
 | 
			
		||||
import TimezoneCountries from 'countries-and-timezones';
 | 
			
		||||
import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly';
 | 
			
		||||
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
 | 
			
		||||
 | 
			
		||||
interface IDateSingleValueProps {
 | 
			
		||||
    setValue: (value: string) => void;
 | 
			
		||||
    value?: string;
 | 
			
		||||
    error: string;
 | 
			
		||||
    setError: React.Dispatch<React.SetStateAction<string>>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const StyledInput = styled(Input)({
 | 
			
		||||
    border: 'none',
 | 
			
		||||
    '*': {
 | 
			
		||||
        border: 'none',
 | 
			
		||||
        padding: 0,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const Container = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    flexFlow: 'row wrap',
 | 
			
		||||
    alignItems: 'center',
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const ConstraintDateInput = ({
 | 
			
		||||
    setValue,
 | 
			
		||||
    value,
 | 
			
		||||
    error,
 | 
			
		||||
    setError,
 | 
			
		||||
}: IDateSingleValueProps) => {
 | 
			
		||||
    const timezones = Object.values(
 | 
			
		||||
        TimezoneCountries.getAllTimezones({ deprecated: false }) as {
 | 
			
		||||
            [timezone: string]: { name: string; utcOffsetStr: string };
 | 
			
		||||
        },
 | 
			
		||||
    ).map((timezone) => ({
 | 
			
		||||
        key: timezone.name,
 | 
			
		||||
        label: `${timezone.name}`,
 | 
			
		||||
        utcOffset: timezone.utcOffsetStr,
 | 
			
		||||
    }));
 | 
			
		||||
    const { timeZone: localTimezoneName } =
 | 
			
		||||
        Intl.DateTimeFormat().resolvedOptions();
 | 
			
		||||
    const [pickedDate, setPickedDate] = useState(value || '');
 | 
			
		||||
    const inputId = useId();
 | 
			
		||||
    const helpId = useId();
 | 
			
		||||
 | 
			
		||||
    const timezoneText = useMemo<string>(() => {
 | 
			
		||||
        const localTimezone = timezones.find(
 | 
			
		||||
            (t) => t.key === localTimezoneName,
 | 
			
		||||
        );
 | 
			
		||||
        if (localTimezone != null) {
 | 
			
		||||
            return `${localTimezone.key} (UTC ${localTimezone.utcOffset})`;
 | 
			
		||||
        } else {
 | 
			
		||||
            return 'The time shown is in your local time zone according to your browser.';
 | 
			
		||||
        }
 | 
			
		||||
    }, [timezones, localTimezoneName]);
 | 
			
		||||
 | 
			
		||||
    if (!value) return null;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Container>
 | 
			
		||||
            <label htmlFor={inputId}>
 | 
			
		||||
                <ScreenReaderOnly>Date</ScreenReaderOnly>
 | 
			
		||||
            </label>
 | 
			
		||||
            <StyledInput
 | 
			
		||||
                id={inputId}
 | 
			
		||||
                aria-describedby={helpId}
 | 
			
		||||
                hiddenLabel
 | 
			
		||||
                label=''
 | 
			
		||||
                size='small'
 | 
			
		||||
                type='datetime-local'
 | 
			
		||||
                value={parseDateValue(pickedDate)}
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                    setError('');
 | 
			
		||||
                    const parsedDate = parseValidDate(e.target.value);
 | 
			
		||||
                    const dateString = parsedDate?.toISOString();
 | 
			
		||||
                    dateString && setPickedDate(dateString);
 | 
			
		||||
                    dateString && setValue(dateString);
 | 
			
		||||
                }}
 | 
			
		||||
                InputLabelProps={{
 | 
			
		||||
                    shrink: true,
 | 
			
		||||
                }}
 | 
			
		||||
                error={Boolean(error)}
 | 
			
		||||
                errorText={error}
 | 
			
		||||
                required
 | 
			
		||||
            />
 | 
			
		||||
            <HelpIcon htmlTooltip tooltip={<p id={helpId}>{timezoneText}</p>} />
 | 
			
		||||
        </Container>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@ -30,6 +30,7 @@ import { ResolveInput } from 'component/common/NewConstraintAccordion/Constraint
 | 
			
		||||
import { ReactComponent as EqualsIcon } from 'assets/icons/constraint-equals.svg';
 | 
			
		||||
import { ReactComponent as NotEqualsIcon } from 'assets/icons/constraint-not-equals.svg';
 | 
			
		||||
import { AddSingleValueWidget } from './AddSingleValueWidget';
 | 
			
		||||
import { ConstraintDateInput } from './ConstraintDateInput';
 | 
			
		||||
 | 
			
		||||
const Container = styled('article')(({ theme }) => ({
 | 
			
		||||
    '--padding': theme.spacing(2),
 | 
			
		||||
@ -208,6 +209,7 @@ export const EditableConstraint: FC<Props> = ({
 | 
			
		||||
    const showSingleValueButton = SINGLE_VALUE_OPERATORS.includes(input);
 | 
			
		||||
    const showAddValuesButton =
 | 
			
		||||
        OPERATORS_WITH_ADD_VALUES_WIDGET.includes(input);
 | 
			
		||||
    const showDateInput = input.includes('DATE');
 | 
			
		||||
    const showInputField = input.includes('LEGAL_VALUES');
 | 
			
		||||
 | 
			
		||||
    /* We need a special case to handle the currentTime context field. Since
 | 
			
		||||
@ -362,6 +364,14 @@ export const EditableConstraint: FC<Props> = ({
 | 
			
		||||
                            currentValue={localConstraint.value}
 | 
			
		||||
                        />
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                    {showDateInput ? (
 | 
			
		||||
                        <ConstraintDateInput
 | 
			
		||||
                            setValue={setValue}
 | 
			
		||||
                            value={localConstraint.value}
 | 
			
		||||
                            error={error}
 | 
			
		||||
                            setError={setError}
 | 
			
		||||
                        />
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                </ConstraintDetails>
 | 
			
		||||
                <ButtonPlaceholder />
 | 
			
		||||
                <HtmlTooltip title='Delete constraint' arrow>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user