@mui/material#FormHelperText TypeScript Examples

The following examples show how to use @mui/material#FormHelperText. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: FieldDescription.tsx    From firecms with MIT License 6 votes vote down vote up
/**
 * Render the field description for a property
 * @category Form custom fields
 */
export function FieldDescription<T extends CMSType>({ property }: FieldDescriptionPopoverProps<T>) {
    const disabledTooltip: string | undefined = typeof property.disabled === "object" ? property.disabled.disabledMessage : undefined;
    return (

        // <FormHelperText>{disabledTooltip ? disabledTooltip : property.description}</FormHelperText>
        <Box display="flex">

            <Box flexGrow={1}>
                <FormHelperText>{disabledTooltip || property.description}</FormHelperText>
            </Box>

            {property.longDescription &&
            <Tooltip title={
                <Typography
                    variant={"caption"}>{property.longDescription}</Typography>
            }
                     placement="bottom-start"
                     arrow>
                <IconButton
                    edge={"start"}
                    size={"small"}>

                    <InfoIcon color={"disabled"}
                              fontSize={"small"}/>
                </IconButton>
            </Tooltip>}

        </Box>
    );
}
Example #2
Source File: CampaignTypeSelect.tsx    From frontend with MIT License 6 votes vote down vote up
export default function CampaignTypeSelect({ name = 'campaignTypeId' }) {
  const { t } = useTranslation()
  const { data } = useCampaignTypesList()
  const [field, meta] = useField(name)

  const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : ''
  return (
    <FormControl
      fullWidth
      size="small"
      variant="outlined"
      error={Boolean(meta.error) && Boolean(meta.touched)}>
      <InputLabel>{t('campaigns:campaign.type')}</InputLabel>
      <Select fullWidth defaultValue="" label={t('campaigns:campaign.type')} {...field}>
        <MenuItem value="" disabled>
          {t('campaigns:campaign.type')}
        </MenuItem>
        {data?.map((campaignType, index) => (
          <MenuItem key={index} value={campaignType.id}>
            {campaignType.name}
          </MenuItem>
        ))}
      </Select>
      {helperText && <FormHelperText error>{helperText}</FormHelperText>}
    </FormControl>
  )
}
Example #3
Source File: BeneficiarySelect.tsx    From frontend with MIT License 6 votes vote down vote up
export default function BeneficiarySelect({ name = 'beneficiaryId' }) {
  const { t } = useTranslation()
  const { data } = useBeneficiariesList()
  const [field, meta] = useField(name)

  const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : ''
  return (
    <FormControl
      fullWidth
      size="small"
      variant="outlined"
      error={Boolean(meta.error) && Boolean(meta.touched)}>
      <InputLabel>{t('Бенефициент')}</InputLabel>
      <Select fullWidth defaultValue="" label={t('Бенефициент')} {...field}>
        <MenuItem value="" disabled>
          {t('Бенефициент')}
        </MenuItem>
        {data?.map((beneficiary, index) => (
          <MenuItem key={index} value={beneficiary.id}>
            {beneficiary.person
              ? `${beneficiary.person.firstName} ${beneficiary.person.lastName}`
              : `${beneficiary.company?.name}`}
          </MenuItem>
        ))}
      </Select>
      {helperText && <FormHelperText error>{helperText}</FormHelperText>}
    </FormControl>
  )
}
Example #4
Source File: AdditionalQuestions.tsx    From frontend with MIT License 6 votes vote down vote up
Question = ({ question }: QuestionProps) => {
  const { t } = useTranslation()
  const formik = useFormikContext<SupportFormData>()
  if (!question) {
    return null
  }
  return (
    <FormControl fullWidth required error={Boolean(formik.errors.roles)} component="fieldset">
      <FormGroup>
        <FormLabel component="legend">{t(question.title)}</FormLabel>
        {question.options.map((option: Option, index) => (
          <React.Fragment key={index}>
            <CheckboxField label={option.label} name={option.name} />
            {option.textFieldOptions && option.value ? (
              <FormTextField
                type="text"
                name={option.textFieldOptions.name}
                label={option.textFieldOptions.placeholder}
                placeholder={t(option.textFieldOptions.placeholder)}
              />
            ) : null}
          </React.Fragment>
        ))}
      </FormGroup>
      {Boolean(formik.errors[question.key]) && (
        <FormHelperText error>{t(question.errorMessage)}</FormHelperText>
      )}
    </FormControl>
  )
}
Example #5
Source File: SelectCountry.tsx    From frontend with MIT License 6 votes vote down vote up
export default function SelectCountry({ name = 'countryId' }) {
  const { t } = useTranslation()
  const { data } = useCountriesList()
  const [field, meta] = useField(name)

  const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : ''
  return (
    <FormControl
      fullWidth
      size="small"
      variant="outlined"
      error={Boolean(meta.error) && Boolean(meta.touched)}>
      <InputLabel>Държава</InputLabel>
      <Select fullWidth defaultValue="" label="Държава" {...field}>
        <MenuItem value="" disabled>
          Избери държава
        </MenuItem>
        {data?.map((country) => (
          <MenuItem key={country.id} value={country.id}>
            {country.name}
          </MenuItem>
        ))}
      </Select>
      {helperText && <FormHelperText error>{helperText}</FormHelperText>}
    </FormControl>
  )
}
Example #6
Source File: CoordinatorSelect.tsx    From frontend with MIT License 6 votes vote down vote up
export default function CoordinatorSelect({ name = 'coordinatorId' }) {
  const { t } = useTranslation()
  const { data } = useCoordinatorsList()
  const [field, meta] = useField(name)

  const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : ''
  return (
    <FormControl
      fullWidth
      size="small"
      variant="outlined"
      error={Boolean(meta.error) && Boolean(meta.touched)}>
      <InputLabel>{t('Кординатор')}</InputLabel>
      <Select fullWidth defaultValue="" label={t('Кординатор')} {...field}>
        <MenuItem value="" disabled>
          {t('Кординатор')}
        </MenuItem>
        {data?.map((coordinator, index) => (
          <MenuItem key={index} value={coordinator.id}>
            {coordinator.person.firstName} {coordinator.person.lastName}
          </MenuItem>
        ))}
      </Select>
      {helperText && <FormHelperText error>{helperText}</FormHelperText>}
    </FormControl>
  )
}
Example #7
Source File: CheckboxField.tsx    From frontend with MIT License 6 votes vote down vote up
export default function CheckboxField({ name, label }: CheckboxFieldProps) {
  const { t } = useTranslation()
  const [field, meta] = useField(name)
  const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : ''
  return (
    <FormControl required component="fieldset" error={Boolean(meta.error) && Boolean(meta.touched)}>
      <FormControlLabel
        label={typeof label === 'string' ? `${t(label)}` : label}
        control={<Checkbox color="primary" checked={Boolean(field.value)} {...field} />}
      />
      {Boolean(meta.error) && <FormHelperText error>{helperText}</FormHelperText>}
    </FormControl>
  )
}
Example #8
Source File: ReadOnlyField.tsx    From firecms with MIT License 5 votes vote down vote up
/**
 *
 * Simply render the non-editable preview of a field
 *
 * This is one of the internal components that get mapped natively inside forms
 * and tables to the specified properties.
 * @category Form fields
 */
export function ReadOnlyField({
                                  name,
                                                                    value,
                                                                    setValue,
                                                                    error,
                                                                    showError,
                                                                    isSubmitting,
                                                                    touched,
                                                                    tableMode,
                                                                    property,
                                                                    includeDescription,
                                                                    context
                                                                }: FieldProps<any>) {

    return (

        <FormControl fullWidth error={showError}>

            {!tableMode && <FormHelperText filled
                                           required={property.validation?.required}>
                <LabelWithIcon property={property}/>
            </FormHelperText>}

            <Paper
                sx={(theme) => ({
                    minHeight: "64px",
                    elevation: 0,
                    padding: theme.spacing(2),
                    [theme.breakpoints.up("md")]: {
                        padding: theme.spacing(3)
                    }
                })}
                variant={"outlined"}>

                <ErrorBoundary>
                    <PreviewComponent name={name}
                                      value={value}
                                      property={property}
                                      size={"regular"}/>
                </ErrorBoundary>

            </Paper>

            {showError &&
            typeof error === "string" &&
            <FormHelperText>{error}</FormHelperText>}

            {includeDescription &&
            <FieldDescription property={property}/>}

        </FormControl>
    );
}
Example #9
Source File: CheckboxElement.tsx    From react-hook-form-mui with MIT License 5 votes vote down vote up
export default function CheckboxElement({
  name,
  validation = {},
  required,
  parseError,
  label,
  control,
  ...rest
}: CheckboxElementProps): JSX.Element {

  if (required) {
    validation.required = 'This field is required'
  }

  return (
    <Controller
      name={name}
      rules={validation}
      control={control}
      render={({ field: { value, onChange }, fieldState: { invalid, error } }) => {
        const helperText = error ? (typeof parseError === 'function' ? parseError(error) : error.message) : rest.helperText
        return (
          <FormControl required={required} error={invalid}>
            <FormGroup row>
              <FormControlLabel
                label={label || ''}
                control={
                  <Checkbox
                    color={'primary'}
                    sx={{
                      color: invalid ? "error.main" : undefined,
                    }}
                    value={value}
                    checked={!!value}
                    onChange={() => {
                      onChange(!value)
                      //setValue(name, !formValue, { shouldValidate: true })
                    }}
                  />
                }
              />
            </FormGroup>
            {helperText && <FormHelperText error={invalid}>{helperText}</FormHelperText>}
          </FormControl>
        )
      }}
    />
  )
}
Example #10
Source File: SwitchField.tsx    From firecms with MIT License 5 votes vote down vote up
SwitchFieldComponent = React.forwardRef(function({
                                                           name,
                                                           value,
                                                           setValue,
                                                           error,
                                                           showError,
                                                           autoFocus,
                                                           disabled,
                                                           touched,
                                                           property,
                                                           includeDescription,
                                                           shouldAlwaysRerender
                                                       }: SwitchFieldProps, ref) {

    const classes = useStyles();

    useClearRestoreValue({
        property,
        value,
        setValue
    });

    const [focus, setFocus] = useState<boolean>(autoFocus);

    return (
        <>
            <FormControl fullWidth>

                <FormControlLabel
                    className={clsx(classes.formControl,
                        {
                            [classes.focus]: focus
                        })}
                    onClick={(e) => setFocus(true)}
                    labelPlacement={"start"}
                    checked={Boolean(value)}
                    inputRef={ref}
                    control={
                        <Switch
                            type={"checkbox"}
                            color={"secondary"}
                            autoFocus={autoFocus}
                            disabled={disabled}
                            onFocus={(e) => setFocus(true)}
                            onBlur={(e) => setFocus(false)}
                            onChange={(evt) => {
                                setFocus(true);
                                setValue(
                                    evt.target.checked
                                );
                            }}/>
                    }
                    disabled={disabled}
                    label={
                        <Typography color={"textSecondary"}>
                            <LabelWithIcon

                                property={property}/>
                        </Typography>}
                />

                {includeDescription &&
                <FieldDescription property={property}/>}

                {showError && <FormHelperText>{error}</FormHelperText>}

            </FormControl>


        </>

    );
})
Example #11
Source File: CircleCheckboxField.tsx    From frontend with MIT License 5 votes vote down vote up
export default function CircleCheckboxField({ name, label, labelProps }: CircleCheckboxField) {
  const { t } = useTranslation('one-time-donation')
  const [field, meta] = useField(name)
  const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : ''
  return (
    <FormControl required component="fieldset" error={Boolean(meta.error) && Boolean(meta.touched)}>
      <FormControlLabel
        sx={
          field.checked
            ? {
                background: lighten(theme.palette.primary.main, 0.8),
                border: `1px solid ${theme.borders.light}`,
              }
            : undefined
        }
        label={<Typography sx={{ fontWeight: 'bold', ml: 1 }}>{label}</Typography>}
        control={
          <Checkbox
            icon={
              <Icon
                sx={(theme) => ({
                  width: 30,
                  height: 30,
                  border: `1px solid ${theme.palette.primary.dark}`,
                  // @ts-expect-error theme doesn't include overrides
                  borderRadius: theme.borders.round,
                })}
              />
            }
            checkedIcon={
              <CheckIcon
                sx={(theme) => ({
                  width: 30,
                  height: 30,
                  border: `1px solid ${theme.palette.primary.main}`,
                  backgroundColor: theme.palette.primary.main,
                  // @ts-expect-error theme doesn't include overrides
                  borderRadius: theme.borders.round,
                  color: '#fff',
                })}
              />
            }
            checked={Boolean(field.value)}
            {...field}
          />
        }
        {...labelProps}
      />
      {Boolean(meta.error) && (
        <FormHelperText error>{helperText ? t(helperText) : 'General Error'}</FormHelperText>
      )}
    </FormControl>
  )
}
Example #12
Source File: MarkdownField.tsx    From firecms with MIT License 5 votes vote down vote up
/**
 * Render a markdown field that allows edition and seeing the preview.
 *
 * This is one of the internal components that get mapped natively inside forms
 * and tables to the specified properties.
 * @category Form fields
 */
export function MarkdownField({
                                  name,
                                  value,
                                  setValue,
                                  error,
                                  showError,
                                  disabled,
                                  autoFocus,
                                  touched,
                                  property,
                                  tableMode,
                                  includeDescription,
                                  context,
                                  shouldAlwaysRerender
                              }: MarkDownFieldProps) {

    const classes = useStyles();

    useClearRestoreValue({
        property,
        value,
        setValue
    });

    const updateValue = (newValue: string | undefined) => {
        if (!newValue) {
            setValue(
                null
            );
        } else {
            setValue(
                newValue
            );
        }
    };

    return (

        <FormControl
            required={property.validation?.required}
            error={showError}
            fullWidth>

            {!tableMode && <FormHelperText filled
                                           required={property.validation?.required}>
                <LabelWithIcon property={property}/>
            </FormHelperText>}

            <div className={classes.root}>
                <MDEditor
                    value={typeof value === "string" ? value : ""}
                    preview={"edit"}
                    onChange={(value) => updateValue(value)}
                />
            </div>

            <Box display={"flex"}>
                <Box flexGrow={1}>
                    {showError &&
                    typeof error === "string" &&
                    <FormHelperText>{error}</FormHelperText>}
                    {includeDescription &&
                    <FieldDescription property={property}/>}
                </Box>
            </Box>

        </FormControl>
    );

}
Example #13
Source File: RadioButtonGroup.tsx    From frontend with MIT License 5 votes vote down vote up
export default function RadioButtonGroup({
  name,
  options,
  muiRadioGroupProps,
  muiRadioButtonGridProps,
}: RadioButtonGroup) {
  const { t } = useTranslation('one-time-donation')
  const [field, meta, { setValue }] = useField(name)
  const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : ''
  return (
    <FormControl
      fullWidth
      required
      component="fieldset"
      error={Boolean(meta.error) && Boolean(meta.touched)}>
      <RadioGroup
        onChange={(e, v) => {
          setValue(v)
        }}
        value={field.value}
        name={name}
        {...muiRadioGroupProps}>
        <Grid rowSpacing={2} columnSpacing={2} container>
          {options ? (
            <>
              {options.map(({ label: optionLabel, value: optionValue }, index) => (
                <Grid key={index} item xs={12} sm={6} {...muiRadioButtonGridProps}>
                  <PriceRadioButton
                    value={optionValue}
                    checked={optionValue == field.value}
                    label={optionLabel}
                  />
                </Grid>
              ))}
            </>
          ) : (
            <Typography>There are no avaliable choices you can make :(</Typography>
          )}
        </Grid>
      </RadioGroup>
      {Boolean(meta.error) && Boolean(meta.touched) && helperText && (
        <FormHelperText error>{t(helperText)}</FormHelperText>
      )}
    </FormControl>
  )
}
Example #14
Source File: Roles.tsx    From frontend with MIT License 5 votes vote down vote up
export default function Roles() {
  const [, { error }] = useField('roles')
  const { t } = useTranslation()

  return (
    <Grid container spacing={6} justifyContent="center">
      <Grid item xs={12} md={8}>
        <HeaderTypography>{t('support:steps.role.subtitle')}</HeaderTypography>
      </Grid>
      <Grid item xs={12} md={8}>
        <Typography variant="h5" paragraph>
          {t('support:steps.role.first-subtitle')}
        </Typography>
        <Divider />
        <div>
          <BankTransfer />
        </div>
      </Grid>
      <Grid item xs={12} md={8}>
        <Typography variant="h5" paragraph>
          {t('support:steps.role.second-subtitle')}
        </Typography>
        <Divider />
        <FormControl fullWidth required error={!!error} component="fieldset">
          <FormGroup>
            <Role name="roles.benefactor" label={t('support:steps.role.fields.benefactor.title')} />
            <Role label={t('support:steps.role.fields.volunteer.title')} name="roles.volunteer" />
            <Role
              label={t('support:steps.role.fields.associationMember.title')}
              name="roles.associationMember"
            />
            <Role label={t('support:steps.role.fields.partner.title')} name="roles.partner" />
            <Role label={t('support:steps.role.fields.company.title')} name="roles.company" />
          </FormGroup>
          {error && <FormHelperText>{t('validation:select-role')}</FormHelperText>}
        </FormControl>
      </Grid>
    </Grid>
  )
}
Example #15
Source File: SubPropertyField.tsx    From firecms with MIT License 5 votes vote down vote up
CustomField = ({
                         property,
                         value,
                         name,
                         tableMode,
                         error,
                         showError,
                         includeDescription,
                         context,
                         setValue,
                     }: FieldProps<object>) => {
    useEffect(() => {
        if (!value) setValue({});
    }, [value, setValue]);

    return (
        <FormControl fullWidth error={showError}>
            {!tableMode && (
                <FormHelperText filled required={property.validation?.required}>
                    <LabelWithIcon property={property} />
                </FormHelperText>
            )}

            <Paper elevation={0}>
                {buildPropertyField({
                    name: `${name}.sample`,
                    property: {
                        title: "Sample",
                        dataType: "string",
                        validation: {
                            required: true,
                        },
                    },
                    context,
                })}
            </Paper>

            {includeDescription && <FieldDescription property={property} />}

            {showError && typeof error === "string" && (
                <FormHelperText>{error}</FormHelperText>
            )}
        </FormControl>
    );
}
Example #16
Source File: CustomShapedArrayField.tsx    From firecms with MIT License 5 votes vote down vote up
export default function CustomShapedArrayField({
                                                   property,
                                                   name,
                                                   value,
                                                   setValue,
                                                   customProps,
                                                   touched,
                                                   error,
                                                   isSubmitting,
                                                   showError,
                                                   includeDescription,
                                                   context,
                                                   ...props
                                               }: FieldProps<any[], CustomShapedArrayProps>)
    : ReactElement {

    const properties = customProps.properties;

    return (
        <FormControl fullWidth error={showError}>

            <FormHelperText>{property.title ?? name}</FormHelperText>

            <Paper variant={"outlined"}>
                <Box m={2}>
                    {properties.map((property, index) =>
                        <div key={`array_${index}`}>
                            {buildPropertyField({
                                name: `${name}[${index}]`,
                                property,
                                context
                            })}
                        </div>
                    )}
                </Box>
            </Paper>

            {includeDescription &&
            <FieldDescription property={property}/>}

            {showError
            && typeof error === "string"
            && <FormHelperText>{error}</FormHelperText>}

        </FormControl>

    );

}
Example #17
Source File: select.tsx    From Search-Next with GNU General Public License v3.0 4 votes vote down vote up
Select: React.FC<SelectProps> = ({
  label,
  size = 'small',
  options,
  optionsConfig,
  helperText,
  error,
  inputRef,
  ...props
}) => {
  const theme = createTheme();

  return (
    <Box sx={{ minWidth: 120 }}>
      <FormControl fullWidth>
        <InputLabel
          id={`mui-select-label-${label}`}
          className={css`
            &.MuiFormLabel-root {
              transform: translate(14px, 9px) scale(1);
            }
            &.Mui-focused,
            &.MuiFormLabel-filled {
              transform: translate(14px, -9px) scale(0.75);
            }
          `}
        >
          {label}
        </InputLabel>
        <StyledSelect
          {...props}
          labelId={`mui-select-label-${label}`}
          id={`mui-select-${label}`}
          inputRef={inputRef}
          label={label}
          size={size}
          error={error}
          onClose={(e) => e.stopPropagation()}
          MenuProps={{
            sx: {
              '& .MuiPaper-root': {
                backgroundColor: 'rgba(253, 253, 253, 0.8)',
                backdropFilter: 'blur(8px)',
                borderRadius: '4px',
                marginTop: theme.spacing(1),
                minWidth: 140,
                color:
                  theme.palette.mode === 'light'
                    ? 'rgb(55, 65, 81)'
                    : theme.palette.grey[300],
                boxShadow:
                  'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
                '& .MuiMenu-list': {
                  padding: '0 4px',
                },
                '& .MuiMenuItem-root': {
                  borderRadius: '4px',
                  padding: '4px 8px',
                  margin: '4px 0',
                  fontSize: '14px',
                  transition: 'all 0.3s',
                  '& .MuiSvgIcon-root': {
                    fontSize: '14px',
                  },
                  '&:active': {
                    backgroundColor: alpha(
                      theme.palette.primary.main,
                      theme.palette.action.selectedOpacity,
                    ),
                  },
                },
              },
            },
          }}
        >
          {options.map((i) => {
            const label =
              optionsConfig && optionsConfig.label
                ? i[optionsConfig.label]
                : i.label;
            const value =
              optionsConfig && optionsConfig.value
                ? i[optionsConfig.value]
                : i.value;
            return (
              <MenuItem key={value} value={value}>
                {label}
              </MenuItem>
            );
          })}
        </StyledSelect>
        <FormHelperText error={error}>{helperText}</FormHelperText>
      </FormControl>
    </Box>
  );
}
Example #18
Source File: RadioButtonGroup.tsx    From react-hook-form-mui with MIT License 4 votes vote down vote up
export default function RadioButtonGroup({
  helperText,
  options,
  label,
  name,
  parseError,
  labelKey = 'label',
  valueKey = 'id',
  required,
  emptyOptionLabel,
  returnObject,
  row,
  control,
  ...rest
}: RadioButtonGroupProps): JSX.Element {
  const theme = useTheme()
  const { field: { value, onChange }, fieldState: { invalid, error } } = useController({
    name,
    rules: required ? { required: 'This field is required' } : undefined,
    control
  })

  helperText = error ? (typeof parseError === 'function' ? parseError(error) : error.message) : helperText

  const onRadioChange = (event: ChangeEvent<HTMLInputElement>) => {
    const radioValue = (event.target as HTMLInputElement).value
    const returnValue = returnObject
      ? options.find(items => items[valueKey] === radioValue)
      : radioValue
    // setValue(name, returnValue, { shouldValidate: true })
    onChange(returnValue)
    if (typeof rest.onChange === 'function') {
      rest.onChange(returnValue)
    }
  }

  return (
    <FormControl error={invalid}>
      {label && <FormLabel required={required} error={invalid}>{label}</FormLabel>}
      <RadioGroup onChange={onRadioChange}
                  name={name}
                  row={row}
                  value={value || ''}>
        {emptyOptionLabel && (
          <FormControlLabel
            control={<Radio sx={{
              color: invalid ? theme.palette.error.main : undefined
            }} checked={!value} />}
            label={emptyOptionLabel}
            value=""
          />
        )}
        {options.map((option: any) => {
          const optionKey = option[valueKey]
          if (!optionKey) {
            console.error(
              `CheckboxButtonGroup: valueKey ${valueKey} does not exist on option`,
              option
            )
          }
          const isChecked = !!(
            value &&
            (returnObject
              ? value[valueKey] === optionKey
              : value === optionKey)
          )
          return (
            <FormControlLabel
              control={<Radio sx={{
                color: invalid ? theme.palette.error.main : undefined
              }} checked={isChecked} />}
              value={optionKey}
              label={option[labelKey]}
              key={optionKey}
            />
          )
        })}
      </RadioGroup>
      {helperText && <FormHelperText>{helperText}</FormHelperText>}
    </FormControl>
  )
}
Example #19
Source File: MultiSelectElement.tsx    From react-hook-form-mui with MIT License 4 votes vote down vote up
export default function MultiSelectElement({
  menuItems,
  label = '',
  itemKey = '',
  itemValue = '',
  itemLabel = '',
  required = false,
  validation = {},
  parseError,
  name,
  menuMaxHeight = ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
  menuMaxWidth = 250,
  minWidth = 120,
  helperText,
  showChips,
  variant,
  control,
  showCheckbox,
  ...rest
}: MultiSelectElementProps): JSX.Element {

  if (required) {
    validation.required = 'This field is required'
  }

  return (
    <Controller
      name={name}
      rules={validation}
      control={control}
      render={({ field: { value, onChange, onBlur }, fieldState: { invalid, error } }) => {
        helperText = error ? (typeof parseError === 'function' ? parseError(error) : error.message) : helperText
        return (
          <FormControl
            variant={variant}
            style={{ minWidth }}
            fullWidth={rest.fullWidth}
            error={invalid}
          >
            {label && (
              <InputLabel error={invalid} htmlFor={rest.id || `select-multi-select-${name}`} required={required}>
                {label}
              </InputLabel>
            )}
            <Select
              {...rest}
              id={rest.id || `select-multi-select-${name}`}
              multiple
              label={label || undefined}
              error={invalid}
              value={value || []}
              required={required}
              onChange={onChange}
              onBlur={onBlur}
              MenuProps={{
                PaperProps: {
                  style: {
                    maxHeight: menuMaxHeight,
                    width: menuMaxWidth
                  }
                }
              }}
              renderValue={typeof rest.renderValue === 'function' ? rest.renderValue : showChips ? (selected) => (
                <div style={{ display: 'flex', flexWrap: 'wrap' }}>
                  {(selected as any[] || []).map((selectedValue) => (
                    <Chip
                      key={selectedValue}
                      label={selectedValue}
                      style={{ display: 'flex', flexWrap: 'wrap' }}
                      onDelete={() => {
                        onChange(value.filter((i: any) => i !== selectedValue))
                        // setValue(name, formValue.filter((i: any) => i !== value), { shouldValidate: true })
                      }}
                      deleteIcon={<CloseIcon
                        onMouseDown={(ev) => {
                          ev.stopPropagation()
                        }} />
                      }
                    />
                  ))}
                </div>
              ) : (selected) => selected?.join(', ')}
            >
              {menuItems.map((item: any) => {
                const isChecked = value?.includes(item) ?? false
                const key = itemValue || itemKey
                let val = key ? item[key] : item
                return (
                  <MenuItem
                    key={val}
                    value={val}
                    sx={{
                      fontWeight: (theme) => isChecked ? theme.typography.fontWeightBold : theme.typography.fontWeightRegular
                    }}
                  >
                    {showCheckbox && <Checkbox checked={isChecked} />}
                    <ListItemText primary={itemLabel ? item[itemLabel] : item} />
                  </MenuItem>
                )
              })}
            </Select>
            {helperText && <FormHelperText>{helperText}</FormHelperText>}
          </FormControl>
        )
      }}
    />
  )
}
Example #20
Source File: CheckboxButtonGroup.tsx    From react-hook-form-mui with MIT License 4 votes vote down vote up
export default function CheckboxButtonGroup({
  helperText,
  options,
  label,
  name,
  parseError,
  required,
  labelKey = 'label',
  valueKey = 'id',
  returnObject,
  disabled,
  row,
  control,
  checkboxColor,
  ...rest
}: CheckboxButtonGroupProps): JSX.Element {
  const theme = useTheme()
  const { field: { value = [], onChange }, fieldState: { invalid, error } } = useController({
    name,
    rules: required ? { required: 'This field is required' } : undefined,
    control
  })

  helperText = error ? (typeof parseError === 'function' ? parseError(error) : error.message) : helperText

  const handleChange = (index: number | string) => {
    const newArray = [...value]
    const exists =
      value.findIndex((i: any) =>
        returnObject ? i[valueKey] === index : i === index
      ) === -1
    if (exists) {
      newArray.push(
        returnObject ? options.find(i => i[valueKey] === index) : index
      )
    } else {
      newArray.splice(
        value.findIndex((i: any) =>
          returnObject ? i[valueKey] === index : i === index
        ),
        1
      )
    }
    // setValue(name, newArray, { shouldValidate: true })
    onChange(newArray)
    if (typeof rest.onChange === 'function') {
      rest.onChange(newArray)
    }
  }

  return (
    <FormControl error={invalid} required={required}>
      {label && <FormLabel error={invalid}>{label}</FormLabel>}
      <FormGroup row={row}>
        {options.map((option: any) => {
          const optionKey = option[valueKey]
          if (!optionKey) {
            console.error(
              `CheckboxButtonGroup: valueKey ${valueKey} does not exist on option`,
              option
            )
          }
          const isChecked =
            value.findIndex((item: any) =>
              returnObject ? item[valueKey] === optionKey : item === optionKey
            ) !== -1
          return (
            <FormControlLabel
              control={
                <Checkbox
                  sx={{
                    color: invalid ? theme.palette.error.main : undefined
                  }}
                  color={checkboxColor || 'primary'}
                  value={optionKey}
                  checked={isChecked}
                  disabled={disabled}
                  onChange={() => handleChange(optionKey)}
                />
              }
              label={option[labelKey]}
              key={optionKey}
            />
          )
        })}
      </FormGroup>
      {helperText && <FormHelperText>{helperText}</FormHelperText>}
    </FormControl>
  )
}
Example #21
Source File: form_factory.tsx    From firecms with MIT License 4 votes vote down vote up
function FieldInternal<T extends CMSType, M extends { [Key: string]: any }>
({
     component,
     componentProps: {
         name,
         property,
         includeDescription,
         underlyingValueHasChanged,
         tableMode,
         partOfArray,
         autoFocus,
         context,
         disabled,
         shouldAlwaysRerender
     },
     fieldProps

 }:
     {
         component: ComponentType<FieldProps<T>>,
         componentProps: CMSFormFieldProps<M>,
         fieldProps: FormikFieldProps<T>
     }) {

    const customFieldProps: any = property.config?.customProps;
    const value = fieldProps.field.value;
    const initialValue = fieldProps.meta.initialValue;
    const error = getIn(fieldProps.form.errors, name);
    const touched = getIn(fieldProps.form.touched, name);

    const showError: boolean = error &&
        (fieldProps.form.submitCount > 0 || property.validation?.unique) &&
        (!Array.isArray(error) || !!error.filter((e: any) => !!e).length);

    const isSubmitting = fieldProps.form.isSubmitting;

    const [internalValue, setInternalValue] = useState<T | null>(value);
    useEffect(
        () => {
            const handler = setTimeout(() => {
                fieldProps.form.setFieldValue(name, internalValue);
            }, 50);

            return () => {
                clearTimeout(handler);
            };
        },
        [internalValue]
    );

    useEffect(
        () => {
            if (!isEqual(value, internalValue)) {
                setInternalValue(value);
            }
        },
        [value]
    );

    const cmsFieldProps: FieldProps<T> = {
        name,
        value: internalValue as T,
        initialValue,
        setValue: (value: T | null) => {
            fieldProps.form.setFieldTouched(name, true, false);
            setInternalValue(value);
        },
        error,
        touched,
        showError,
        isSubmitting,
        includeDescription: includeDescription ?? true,
        property: property as Property<T>,
        disabled: disabled ?? false,
        underlyingValueHasChanged: underlyingValueHasChanged ?? false,
        tableMode: tableMode ?? false,
        partOfArray: partOfArray ?? false,
        autoFocus: autoFocus ?? false,
        customProps: customFieldProps,
        context,
        shouldAlwaysRerender: shouldAlwaysRerender ?? true
    };

    return (
        <>

            {React.createElement(component, cmsFieldProps)}

            {underlyingValueHasChanged && !isSubmitting &&
            <FormHelperText>
                This value has been updated elsewhere
            </FormHelperText>}

        </>);

}
Example #22
Source File: TextField.tsx    From firecms with MIT License 4 votes vote down vote up
/**
 * Generic text field.
 * This is one of the internal components that get mapped natively inside forms
 * and tables to the specified properties.
 * @category Form fields
 */
export function TextField<T extends string | number>({
                                                         name,
                                                         value,
                                                         setValue,
                                                         error,
                                                         showError,
                                                         disabled,
                                                         autoFocus,
                                                         property,
                                                         includeDescription,
                                                         allowInfinity,
                                                         shouldAlwaysRerender
                                                     }: TextFieldProps<T>) {

    const classes = formStyles();

    let mediaType: MediaType | undefined;
    let multiline: boolean | undefined;
    if (property.dataType === "string") {
        const url = (property as StringProperty).config?.url;
        mediaType = typeof url === "string" ? url : undefined;
        multiline = (property as StringProperty).config?.multiline;
    }

    useClearRestoreValue({
        property,
        value,
        setValue
    });

    const isMultiline = !!multiline;

    const internalValue = value ?? (property.dataType === "string" ? "" : value === 0 ? 0 : "");

    const valueIsInfinity = internalValue === Infinity;
    const inputType = !valueIsInfinity && property.dataType === "number" ? "number" : undefined;

    const updateValue = (newValue: typeof internalValue | undefined) => {

        if (!newValue) {
            setValue(
                null
            );
        } else if (inputType === "number") {
            const numValue = parseFloat(newValue as string);
            setValue(
                numValue as T
            );
        } else {
            setValue(
                newValue
            );
        }
    };

    const filledInput = (
        <FilledInput
            sx={{
                minHeight: "64px"
            }}
            autoFocus={autoFocus}
            type={inputType}
            multiline={isMultiline}
            inputProps={{
                rows: 4
            }}
            value={valueIsInfinity ? "Infinity" : (value ?? "")}
            disabled={disabled}
            onChange={(evt) => {
                updateValue(evt.target.value as T);
            }}
        />
    );

    return (
        <>

            <FormControl
                variant="filled"
                required={property.validation?.required}
                error={showError}
                disabled={valueIsInfinity}
                fullWidth>

                <InputLabel
                    classes={{
                        root: classes.inputLabel,
                        shrink: classes.shrinkInputLabel
                    }}>
                    <LabelWithIcon property={property}/>
                </InputLabel>

                {filledInput}

                <Box display={"flex"}>

                    <Box flexGrow={1}>
                        {showError && <FormHelperText>{error}</FormHelperText>}

                        {includeDescription &&
                        <FieldDescription property={property}/>}
                    </Box>

                    {allowInfinity &&
                    <FormControlLabel
                        checked={valueIsInfinity}
                        style={{ marginRight: 0 }}
                        labelPlacement={"start"}
                        control={
                            <Switch
                                size={"small"}
                                type={"checkbox"}
                                onChange={(evt) => {
                                    updateValue(
                                        evt.target.checked ? Infinity as T : undefined);
                                }}/>
                        }
                        disabled={disabled}
                        label={
                            <Typography variant={"caption"}>
                                Set value to Infinity
                            </Typography>
                        }
                    />
                    }
                </Box>

            </FormControl>

            {mediaType && internalValue &&
            <ErrorBoundary>
                <Box m={1}>
                    <PreviewComponent name={name}
                                      value={internalValue}
                                      property={property}
                                      size={"regular"}/>
                </Box>
            </ErrorBoundary>
            }
        </>
    );

}
Example #23
Source File: StorageUploadField.tsx    From firecms with MIT License 4 votes vote down vote up
/**
 * Field that allows to upload files to Google Cloud Storage.
 *
 * This is one of the internal components that get mapped natively inside forms
 * and tables to the specified properties.
 * @category Form fields
 */
export function StorageUploadField({
                                       name,
                                       value,
                                       setValue,
                                       error,
                                       showError,
                                       autoFocus,
                                       tableMode,
                                       property,
                                       includeDescription,
                                       context,
                                       isSubmitting
                                   }: StorageUploadFieldProps) {

    const multipleFilesSupported = property.dataType === "array";
    const disabled = isReadOnly(property) || !!property.disabled || isSubmitting;

    const internalValue = multipleFilesSupported
        ? (Array.isArray(value) ? value : [])
        : value;

    useClearRestoreValue<string | string[]>({
        property,
        value,
        setValue
    });

    const storageMeta: StorageMeta | undefined = property.dataType === "string"
? property.config?.storageMeta
        : property.dataType === "array" &&
        (property.of as Property).dataType === "string"
? (property.of as StringProperty).config?.storageMeta
            : undefined;

    if (!storageMeta)
        throw Error("Storage meta must be specified");

    const fileNameBuilder = (file: File) => {
        if (storageMeta.fileName) {
            const fileName = storageMeta.fileName({
                entityId: context.entityId,
                values: context.values,
                property,
                file,
                storageMeta,
                name
            });

            if (!fileName || fileName.length === 0) {
                throw Error("You need to return a valid filename");
            }
            return fileName;
        }
        return file.name;
    };

    const storagePathBuilder = (file: File) => {
        if (typeof storageMeta.storagePath === "string")
            return storageMeta.storagePath;

        if (typeof storageMeta.storagePath === "function") {
            const storagePath = storageMeta.storagePath({
                entityId: context.entityId,
                values: context.values,
                property,
                file,
                storageMeta,
                name
            });

            if (!storagePath || storagePath.length === 0) {
                throw Error("You need to return a valid filename");
            }
            return storagePath;
        }
        console.warn("When using a storage property, if you don't specify the storagePath, the root storage is used");
        return "/";
    };

    return (

            <FormControl fullWidth
                         required={property.validation?.required}
                         error={showError}>

                {!tableMode &&
                <FormHelperText filled
                                required={property.validation?.required}>
                    <LabelWithIcon property={property}/>
                </FormHelperText>}

                <StorageUpload
                    value={internalValue}
                    name={name}
                    disabled={disabled}
                    autoFocus={autoFocus}
                    property={property}
                    onChange={(newValue) => {
                        setValue(newValue);
                    }}
                    fileNameBuilder={fileNameBuilder}
                    storagePathBuilder={storagePathBuilder}
                    storageMeta={storageMeta}
                    multipleFilesSupported={multipleFilesSupported}/>

                {includeDescription &&
                <FieldDescription property={property as any}/>}

                {showError && <FormHelperText>{error}</FormHelperText>}

            </FormControl>
    );
}
Example #24
Source File: Select.tsx    From firecms with MIT License 4 votes vote down vote up
/**
 * If `enumValues` are set in the string config, this field renders a select
 * where each option is a colored chip.
 *
 * This is one of the internal components that get mapped natively inside forms
 * and tables to the specified properties.
 * @category Form fields
 */
export function Select<T extends EnumType>({
                                               name,
                                               value,
                                               setValue,
                                               error,
                                               showError,
                                               disabled,
                                               autoFocus,
                                               touched,
                                               property,
                                               includeDescription,
                                               shouldAlwaysRerender
                                           }: SelectProps<T>) {

    const classes = formStyles();
    const enumValues = property.config?.enumValues as EnumValues;

    useClearRestoreValue({
        property,
        value,
        setValue
    });

    return (
        <FormControl
            variant="filled"
            fullWidth
            required={property.validation?.required}
            error={showError}
            disabled={disabled}
        >

            <InputLabel id={`${name}-select-label`}
                        classes={{
                            root: classes.inputLabel,
                            shrink: classes.shrinkInputLabel
                        }}>
                <LabelWithIcon property={property}/>
            </InputLabel>

            <MuiSelect
                sx={{
                    minHeight: "64px"
                }}
                variant={"filled"}
                labelId={`${name}-select-label`}
                autoFocus={autoFocus}
                value={value !== undefined ? value : ""}
                disabled={disabled}
                onChange={(evt: any) => {
                    const eventValue = evt.target.value;
                    const newValue = eventValue
                        ? (property.dataType === "number" ? parseFloat(eventValue) : eventValue)
                        : null;

                    return setValue(newValue);
                }}
                renderValue={(enumKey: any) => {
                    return <EnumValuesChip
                        enumKey={enumKey}
                        enumValues={enumValues}
                        small={false}/>;
                }
                }>

                {enumToObjectEntries(enumValues)
                    .map(([enumKey, labelOrConfig]) => {
                        return (
                            <MenuItem key={`select_${name}_${enumKey}`}
                                      value={enumKey}
                                      disabled={isEnumValueDisabled(labelOrConfig)}>
                                <EnumValuesChip
                                    enumKey={enumKey}
                                    enumValues={enumValues}
                                    small={true}/>
                            </MenuItem>
                        );
                    })}
            </MuiSelect>

            {includeDescription &&
            <FieldDescription property={property}/>}

            {showError && <FormHelperText>{error}</FormHelperText>}

        </FormControl>
    );
}
Example #25
Source File: ReferenceField.tsx    From firecms with MIT License 4 votes vote down vote up
/**
 * Field that opens a reference selection dialog.
 *
 * This is one of the internal components that get mapped natively inside forms
 * and tables to the specified properties.
 * @category Form fields
 */
export function ReferenceField<M extends { [Key: string]: any }>({
                                                                     name,
                                                                     value,
                                                                     setValue,
                                                                     error,
                                                                     showError,
                                                                     disabled,
                                                                     touched,
                                                                     autoFocus,
                                                                     property,
                                                                     includeDescription,
                                                                     context,
                                                                     shouldAlwaysRerender
                                                                 }: FieldProps<EntityReference>) {


    if (typeof property.path !== "string") {
        throw Error("Picked the wrong component ReferenceField");
    }

    useClearRestoreValue({
        property,
        value,
        setValue
    });

    const classes = useStyles();

    const [open, setOpen] = React.useState(autoFocus);
    const sideEntityController = useSideEntityController();

    const navigationContext = useNavigation();
    const collectionResolver: EntityCollectionResolver | undefined = useMemo(() => {
        return navigationContext.getCollectionResolver(property.path as string);
    }, [property.path, navigationContext]);

    if (!collectionResolver) {
        throw Error(`Couldn't find the corresponding collection for the path: ${property.path}`);
    }

    const schemaResolver = collectionResolver.schemaResolver;
    const path = property.path;

    const validValue = value && value instanceof EntityReference;

    const {
        entity,
        dataLoading,
        dataLoadingError
    } = useEntityFetch({
        path: validValue ? value.path : undefined,
        entityId: validValue ? value.id : undefined,
        schema: schemaResolver,
        useCache: true
    });

    const handleEntityClick = (entity: Entity<M>) => {
        if (disabled)
            return;
        setValue(entity ? getReferenceFrom(entity) : null);
        setOpen(false);
    };

    const handleClickOpen = () => {
        setOpen(true);
    };

    const clearValue = (e: React.MouseEvent) => {
        e.stopPropagation();
        setValue(null);
        setOpen(false);
    };

    const seeEntityDetails = (e: React.MouseEvent) => {
        e.stopPropagation();
        if (entity)
            sideEntityController.open({
                entityId: entity.id,
                path,
                overrideSchemaRegistry: false
            });
    };

    const onClose = () => {
        setOpen(false);
    };

    function buildEntityView(schemaResolver?: EntitySchemaResolver) {

        const missingEntity = entity && !entity.values;

        let body: JSX.Element;
        if (!schemaResolver) {
            body = (
                <ErrorView
                    error={"The specified collection does not exist. Check console"}/>
            );
        } else if (missingEntity) {
            body = (
                <Tooltip title={value && value.path}>
                    <Box
                        display={"flex"}
                        alignItems={"center"}
                        p={1}
                        flexGrow={1}>
                        <ErrorIcon fontSize={"small"} color={"error"}/>
                        <Box marginLeft={1}>Missing
                            reference {entity && entity.id}</Box>
                    </Box>
                </Tooltip>
            );
        } else {
            if (validValue) {

                const schema = schemaResolver({});
                const allProperties = Object.keys(schema.properties);
                let listProperties = property.previewProperties?.filter(p => allProperties.includes(p as string));
                if (!listProperties || !listProperties.length) {
                    listProperties = allProperties;
                }
                listProperties = listProperties.slice(0, 3);

                body = (
                    <Box display={"flex"}
                         flexDirection={"column"}
                         flexGrow={1}
                         ml={1}
                         mr={1}>

                        {listProperties && listProperties.map((key, index) => {
                            const property = schema.properties[key as string];
                            if (!property) return null;
                            return (
                                <Box
                                    key={`reference_previews_${key as string}`}
                                    mt={0.5}
                                    mb={0.5}>
                                    <ErrorBoundary>{
                                        entity
                                            ? <PreviewComponent
                                                name={key as string}
                                                value={(entity.values as any)[key]}
                                                property={property as AnyProperty}
                                                size={"tiny"}/>
                                            : <SkeletonComponent
                                                property={property as AnyProperty}
                                                size={"tiny"}/>}
                                    </ErrorBoundary>
                                </Box>
                            );
                        })}
                    </Box>
                );
            } else {
                body = <Box p={1}
                            onClick={disabled ? undefined : handleClickOpen}
                            justifyContent="center"
                            display="flex">
                    <Typography variant={"body2"} sx={(theme) => ({
                        flexGrow: 1,
                        textAlign: "center",
                        color: "#838383",
                        fontWeight: theme.typography.fontWeightMedium
                    })}>No value set</Typography>
                    {!disabled && <Button variant="outlined"
                                          color="primary">
                        Set
                    </Button>}
                </Box>;
            }
        }

        return (
            <Box
                onClick={disabled ? undefined : handleClickOpen}
                display="flex">

                <Box display={"flex"}
                     flexDirection={"column"}
                     flexGrow={1}>

                    <Box display={"flex"}
                         flexDirection={"row"}
                         flexGrow={1}>

                        <Box flexGrow={1}>
                            <FormHelperText filled
                                            required={property.validation?.required}>
                                <LabelWithIcon
                                    property={property}/>
                            </FormHelperText>
                        </Box>

                        {entity &&
                        <Box
                            alignSelf={"center"}
                            m={1}>
                            <Tooltip title={value && value.path}>
                                <Typography variant={"caption"}
                                            className={"mono"}>
                                    {entity.id}
                                </Typography>
                            </Tooltip>
                        </Box>}

                        {!missingEntity && entity && value && <Box>
                            <Tooltip title={`See details for ${entity.id}`}>
                                <span>
                                <IconButton
                                    onClick={seeEntityDetails}
                                    size="large">
                                    <KeyboardTabIcon/>
                                </IconButton>
                                    </span>
                            </Tooltip>
                        </Box>}

                        {value && <Box>
                            <Tooltip title="Clear">
                                <span>
                                <IconButton
                                    disabled={disabled}
                                    onClick={disabled ? undefined : clearValue}
                                    size="large">
                                    <ClearIcon/>
                                </IconButton>
                                </span>
                            </Tooltip>
                        </Box>}

                    </Box>

                    {body}

                </Box>
            </Box>
        );
    }

    return (
        <FormControl error={showError} fullWidth>

            <div
                className={`${classes.root} ${disabled ? classes.disabled : ""}`}>

                {schemaResolver && buildEntityView(schemaResolver)}

                {collectionResolver && <ReferenceDialog open={open}
                                                        collectionResolver={collectionResolver}
                                                        multiselect={false}
                                                        path={path}
                                                        onClose={onClose}
                                                        onSingleEntitySelected={handleEntityClick}
                />}


            </div>

            {includeDescription &&
            <FieldDescription property={property}/>}

            {showError && <FormHelperText>{error}</FormHelperText>}

        </FormControl>
    );
}
Example #26
Source File: MapField.tsx    From firecms with MIT License 4 votes vote down vote up
/**
 * Field that renders the children property fields
 *
 * This is one of the internal components that get mapped natively inside forms
 * and tables to the specified properties.
 * @category Form fields
 */
export function MapField<T extends object>({
                                               name,
                                               value,
                                               showError,
                                               disabled,
                                               property,
                                               setValue,
                                               tableMode,
                                               includeDescription,
                                               underlyingValueHasChanged,
                                               context
                                           }: FieldProps<T>) {


    const pickOnlySomeKeys = property.config?.pickOnlySomeKeys || false;

    if (!property.properties) {
        throw Error(`You need to specify a 'properties' prop (or specify a custom field) in your map property ${name}`);
    }

    let mapProperties: Record<string, Property>;
    if (!pickOnlySomeKeys) {
        mapProperties = property.properties as Properties;
    } else if (value) {
        mapProperties = pick(property.properties as Properties,
            ...Object.keys(value)
                .filter(key => key in property.properties!)
        );
    } else {
        mapProperties = {};
    }

    useClearRestoreValue({
        property,
        value,
        setValue
    });

    function buildPickKeysSelect() {

        const keys = Object.keys(property.properties!)
            .filter((key) => !value || !(key in value));

        const handleAddProperty = (event: SelectChangeEvent) => {
            setValue({
                ...value,
                [event.target.value as string]: null
            });
        };

        if (!keys.length) return <></>;

        return <Box m={1}>
            <FormControl fullWidth>
                <InputLabel>Add property</InputLabel>
                <Select
                    variant={"standard"}
                    value={""}
                    disabled={disabled}
                    onChange={handleAddProperty}>
                    {keys.map((key) => (
                        <MenuItem key={key} value={key}>
                            {(property.properties as Properties)[key].title || key}
                        </MenuItem>
                    ))}
                </Select>
            </FormControl>
        </Box>;
    }

    return (
        <FormControl fullWidth error={showError}>

            {!tableMode && <FormHelperText filled
                                           required={property.validation?.required}>
                <LabelWithIcon property={property}/>
            </FormHelperText>}

            <Paper elevation={0} variant={"outlined"} sx={(theme) => ({
                elevation: 0,
                padding: theme.spacing(2),
                [theme.breakpoints.up("md")]: {
                    padding: theme.spacing(2)
                }
            })}>
                <Grid container spacing={2}>
                    {Object.entries(mapProperties)
                        .filter(([_, property]) => !isHidden(property))
                        .map(([entryKey, childProperty], index) => {
                            return (
                                <Grid item
                                      sm={12}
                                      xs={12}
                                      key={`map-${name}-${index}`}>
                                    {
                                        buildPropertyField<any, T>({
                                            name: `${name}[${entryKey}]`,
                                            disabled,
                                            property: childProperty,
                                            includeDescription,
                                            underlyingValueHasChanged,
                                            context,
                                            tableMode,
                                            partOfArray: false,
                                            autoFocus: false,
                                                shouldAlwaysRerender: false
                                            })
                                        }
                                    </Grid>
                                );
                            }
                        )}
                </Grid>

                {pickOnlySomeKeys && buildPickKeysSelect()}

            </Paper>

            {includeDescription &&
            <FieldDescription property={property}/>}

        </FormControl>
    );
}
Example #27
Source File: ArrayOneOfField.tsx    From firecms with MIT License 4 votes vote down vote up
/**
 * If the `oneOf` property is specified, this fields render each array entry as
 * a `type` select and the corresponding field widget to the selected `type.
 *
 * This is one of the internal components that get mapped natively inside forms
 * and tables to the specified properties.
 * @category Form fields
 */
export function ArrayOneOfField<T extends Array<any>>({
                                                          name,
                                                          value,
                                                          error,
                                                          showError,
                                                          isSubmitting,
                                                          setValue,
                                                          tableMode,
                                                          property,
                                                          includeDescription,
                                                          underlyingValueHasChanged,
                                                          context,
                                                          disabled,
                                                          shouldAlwaysRerender
                                                      }: FieldProps<T>) {

    if (!property.oneOf)
        throw Error("ArrayOneOfField misconfiguration. Property `oneOf` not set");

    useClearRestoreValue({
        property,
        value,
        setValue
    });

    const [lastAddedId, setLastAddedId] = useState<number | undefined>();

    const buildEntry = (index: number, internalId: number) => {
        return <ArrayOneOfEntry
            key={`array_one_of_${index}`}
            name={`${name}[${index}]`}
            index={index}
            value={value[index]}
            typeField={property.oneOf!.typeField ?? "type"}
            valueField={property.oneOf!.valueField ?? "value"}
            properties={property.oneOf!.properties}
            autoFocus={internalId === lastAddedId}
            context={context}/>;
    };

    return (

        <FormControl fullWidth error={showError}>

            {!tableMode && <FormHelperText filled
                                           required={property.validation?.required}>
                <LabelWithIcon property={property}/>
            </FormHelperText>}

            <Paper variant={"outlined"}
                   sx={(theme) => ({
                       elevation: 0,
                       padding: theme.spacing(2),
                       [theme.breakpoints.up("md")]: {
                           padding: theme.spacing(2)
                       }
                   })}>
                <ArrayContainer value={value}
                                name={name}
                                buildEntry={buildEntry}
                                onInternalIdAdded={setLastAddedId}
                                disabled={isSubmitting || Boolean(property.disabled)}
                                includeAddButton={!property.disabled}/>
            </Paper>

            {includeDescription &&
            <FieldDescription property={property}/>}

            {showError &&
            typeof error === "string" &&
            <FormHelperText>{error}</FormHelperText>}

        </FormControl>
    );
}
Example #28
Source File: ArrayOfReferencesField.tsx    From firecms with MIT License 4 votes vote down vote up
/**
 * This field allows selecting multiple references.
 *
 * This is one of the internal components that get mapped natively inside forms
 * and tables to the specified properties.
 * @category Form fields
 */
export function ArrayOfReferencesField({
                                           name,
                                           value,
                                           error,
                                           showError,
                                           isSubmitting,
                                           tableMode,
                                           property,
                                           includeDescription,
                                           setValue
                                       }: ArrayOfReferencesFieldProps) {

    const ofProperty: Property = property.of as Property;
    if (ofProperty.dataType !== "reference") {
        throw Error("ArrayOfReferencesField expected a property containing references");
    }

    const [open, setOpen] = React.useState(false);
    const [onHover, setOnHover] = React.useState(false);
    const selectedIds = value && Array.isArray(value) ? value.map((ref) => ref.id) : [];

    useClearRestoreValue({
        property,
        value,
        setValue
    });

    const navigationContext = useNavigation();
    const collectionResolver: EntityCollectionResolver | undefined = useMemo(() => {
        return ofProperty.path ? navigationContext.getCollectionResolver(ofProperty.path) : undefined;
    }, [ofProperty.path]);

    if (!collectionResolver) {
        throw Error(`Couldn't find the corresponding collection for the path: ${ofProperty.path}`);
    }

    const onEntryClick = () => {
        setOpen(true);
    };

    const onClose = () => {
        setOpen(false);
    };

    const onMultipleEntitiesSelected = (entities: Entity<any>[]) => {
        setValue(entities.map(e => getReferenceFrom(e)));
    };

    const buildEntry = (index: number, internalId: number) => {
        const entryValue = value && value.length > index ? value[index] : undefined;
        if (!entryValue)
            return <div>Internal ERROR</div>;
        return (
            <div
                onMouseEnter={() => setOnHover(true)}
                onMouseMove={() => setOnHover(true)}
                onMouseLeave={() => setOnHover(false)}>
                <ReferencePreview
                    value={entryValue}
                    property={ofProperty}
                    onHover={onHover}
                    size={"regular"}
                    onClick={onEntryClick}/>
            </div>
        );
    };


    return (
        <>
            <FormControl fullWidth error={showError}>

                {!tableMode && <FormHelperText filled
                                               required={property.validation?.required}>
                    <LabelWithIcon property={property}/>
                </FormHelperText>}

                <Paper variant={"outlined"}
                       sx={(theme) => ({
                           elevation: 0,
                           padding: theme.spacing(2),
                           [theme.breakpoints.up("md")]: {
                               padding: theme.spacing(2)
                           }
                       })}>

                    {!collectionResolver && <ErrorView
                        error={"The specified collection does not exist. Check console"}/>}

                    {collectionResolver && <>

                        <ArrayContainer value={value}
                                        name={name}
                                        buildEntry={buildEntry}
                                        disabled={isSubmitting}/>

                        <Box p={1}
                             justifyContent="center"
                             textAlign={"left"}>
                            <Button variant="outlined"
                                    color="primary"
                                    disabled={isSubmitting}
                                    onClick={onEntryClick}>
                                Edit {property.title}
                            </Button>
                        </Box>
                    </>}

                </Paper>

                {includeDescription &&
                <FieldDescription property={property}/>}

                {showError &&
                typeof error === "string" &&
                <FormHelperText>{error}</FormHelperText>}

            </FormControl>

            {collectionResolver && ofProperty.path && <ReferenceDialog open={open}
                                                    multiselect={true}
                                                    collectionResolver={collectionResolver}
                                                    path={ofProperty.path}
                                                    onClose={onClose}
                                                    onMultipleEntitiesSelected={onMultipleEntitiesSelected}
                                                    selectedEntityIds={selectedIds}
            />}
        </>
    );
}
Example #29
Source File: ArrayEnumSelect.tsx    From firecms with MIT License 4 votes vote down vote up
/**
 * This fields renders a dropdown with multiple selection.
 *
 * This is one of the internal components that get mapped natively inside forms
 * and tables to the specified properties.
 * @category Form fields
 */
export function ArrayEnumSelect({
                                    name,
                                    value,
                                    setValue,
                                    error,
                                    showError,
                                    disabled,
                                    property,
                                    includeDescription,
                                    autoFocus
                                }: FieldProps<EnumType[]>) {

    const classes = formStyles();

    if (!property.of) {
        throw Error("Using wrong component ArrayEnumSelect");
    }

    if (property.of.dataType !== "string" && property.of.dataType !== "number") {
        throw Error("Field misconfiguration: array field of type string or number");
    }

    const enumValues = property.of.config?.enumValues;
    if (!enumValues) {
        console.error(property);
        throw Error("Field misconfiguration: array field of type string or number needs to have enumValues");
    }
    useClearRestoreValue({
        property,
        value,
        setValue
    });

    const validValue = !!value && Array.isArray(value);
    return (
        <FormControl
            variant="filled"
            fullWidth
            required={property.validation?.required}
            error={showError}
        >

            <InputLabel id={`${name}-multiselect-label`}
                        classes={{
                            root: classes.inputLabel,
                            shrink: classes.shrinkInputLabel
                        }}>
                <LabelWithIcon property={property}/>
            </InputLabel>

            <MuiSelect
                multiple
                sx={{
                    minHeight: "64px"
                }}
                variant={"filled"}
                labelId={`${name}-multiselect-label`}
                value={validValue ? value.map(v => v.toString()) : []}
                autoFocus={autoFocus}
                disabled={disabled}
                onChange={(evt: any) => {
                    let newValue;
                    if (property.of?.dataType === "number")
                        newValue = evt.target.value ? evt.target.value.map((e: any) => parseFloat(e)) : [];
                    else
                        newValue = evt.target.value;
                    return setValue(
                        newValue
                    );
                }}
                renderValue={(selected: any) => (
                    <ArrayEnumPreview value={selected}
                                      name={name}
                                      enumValues={enumValues}
                                      size={"regular"}/>
                )}>

                {enumToObjectEntries(enumValues)
                    .map(([enumKey, labelOrConfig]) => {
                        const checked = validValue && value.map(v => v.toString()).includes(enumKey.toString());
                        return (
                            <MenuItem key={`form-select-${name}-${enumKey}`}
                                      value={enumKey}
                                      disabled={isEnumValueDisabled(labelOrConfig)}
                                      dense={true}>
                                <Checkbox checked={checked}/>
                                <ListItemText primary={
                                    <EnumValuesChip
                                        enumKey={enumKey}
                                        enumValues={enumValues}
                                        small={true}/>
                                }/>
                            </MenuItem>
                        );
                    })}
            </MuiSelect>

            {includeDescription &&
            <FieldDescription property={property}/>}

            {showError && <FormHelperText>{error}</FormHelperText>}

        </FormControl>
    );
}