@mui/material#FormControl TypeScript Examples

The following examples show how to use @mui/material#FormControl. 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: OptionsCheck.tsx    From ui-schema with MIT License 6 votes vote down vote up
OptionsCheck: React.ComponentType<WidgetProps<MuiWidgetBinding> & OptionsCheckRendererProps> = (
    {
        schema, storeKeys, showValidity, valid, required, errors,
        row, widgets,
    }
) => {
    const oneOfVal = schema.getIn(['items', 'oneOf'])
    if (!oneOfVal) return null
    const InfoRenderer = widgets?.InfoRenderer
    return <FormControl
        required={required} error={!valid && showValidity} component="fieldset" fullWidth
        size={schema.getIn(['view', 'dense']) ? 'small' : undefined}
        disabled={schema.get('readOnly') as boolean}
    >
        <FormLabel component="legend" style={{width: '100%'}}>
            <TransTitle schema={schema} storeKeys={storeKeys}/>
            {InfoRenderer && schema?.get('info') ?
                <InfoRenderer
                    schema={schema} variant={'icon'} openAs={'modal'}
                    storeKeys={storeKeys} valid={valid} errors={errors}
                    align={'right'} dense
                /> :
                undefined}
        </FormLabel>
        <FormGroup row={row}>
            <OptionsCheckValue
                oneOfValues={oneOfVal as List<OrderedMap<string, string>>} storeKeys={storeKeys}
                required={required} schema={schema}
                disabled={schema.get('readOnly') as boolean}
            />
        </FormGroup>

        <ValidityHelperText errors={errors} showValidity={showValidity} schema={schema}/>
    </FormControl>
}
Example #2
Source File: RadioGroup.tsx    From Cromwell with MIT License 6 votes vote down vote up
/** @internal */
export function RadioGroup(props: TRadioProps) {
  return (
    <FormControl component="fieldset"
      className={props.className}
      style={props.style}
      id={props.id}
    >
      <MuiRadioGroup
        value={props.value}
        onChange={props.onChange}
        name={props.name}
      >
        {props.options?.map(option => {
          const value = typeof option === 'object' ? option.value : option;
          if (!value) return <></>;
          const label = (typeof option === 'object' ? option.label : option) ?? value;
          return (
            <FormControlLabel
              key={value}
              value={value}
              control={<Radio color="primary" />}
              label={label}
            />
          )
        })}
      </MuiRadioGroup>
    </FormControl>
  )
}
Example #3
Source File: BlockEditor.tsx    From NekoMaid with MIT License 6 votes vote down vote up
BlockSelector: React.FC<{ worlds: string[] }> = ({ worlds }) => {
  const his = useHistory()
  const [world, setWorld] = useState(worlds[0])
  const [x, setX] = useState('0')
  const [y, setY] = useState('0')
  const [z, setZ] = useState('0')
  return <Grid container>
    <Grid item xs={6}>
      <FormControl variant='standard' fullWidth>
        <InputLabel id='nekomaid-block-editor-world'>{lang.world}</InputLabel>
        <Select
          labelId='nekomaid-block-editor-world'
          value={world}
          label={lang.world}
          onChange={e => setWorld(e.target.value)}
        >
          {worlds.map(it => <MenuItem key={it} value={it}>{it}</MenuItem>)}
        </Select>
      </FormControl>
    </Grid>
    <Grid item xs={2}><TextField variant='standard' label='X' type='number' fullWidth value={x} onChange={e => setX(e.target.value)} /></Grid>
    <Grid item xs={2}><TextField variant='standard' label='Y' type='number' fullWidth value={y} onChange={e => setY(e.target.value)} /></Grid>
    <Grid item xs={2}><TextField variant='standard' label='Z' type='number' fullWidth value={z} onChange={e => setZ(e.target.value)} /></Grid>
    <Grid item xs={12} sx={{ marginTop: 3, textAlign: 'center' }}>
      <Button
        variant='contained'
        onClick={() => his.push(`/NekoMaid/block/${world}/${parseFloat(x) | 0}/${parseFloat(y) | 0}/${parseFloat(z) | 0}`)}
      >{minecraft['gui.done']}</Button>
    </Grid>
  </Grid>
}
Example #4
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 #5
Source File: Select.tsx    From Cromwell with MIT License 6 votes vote down vote up
/** @internal */
export function Select(props: TSelectProps & MuiSelectProps<string | number>) {
  const { className, style, variant = 'filled' } = props;
  return (
    <FormControl
      fullWidth={props.fullWidth}
      style={props.style}
      className={props.className}
    >
      {props.label && (
        <InputLabel style={{
          marginTop: '8px',
        }}>{props.label}</InputLabel>
      )}
      <MuiSelect
        {...props}
        variant={variant}
        className={className}
        style={style}
      >
        {props.options?.map((option) => {
          const label = typeof option === 'object' ? option.label : option;
          const value = typeof option === 'object' ? option.value : option;
          return (
            <MenuItem value={value} key={value + ''}>{label}</MenuItem>
          )
        })}
      </MuiSelect>
    </FormControl>
  )
}
Example #6
Source File: SelectCoordinator.tsx    From frontend with MIT License 6 votes vote down vote up
export default function SelectCoordinator({ name = 'coordinatorId' }) {
  const { t } = useTranslation()
  const [field] = useField(name)
  const { data: personList } = usePersonList()
  const { data: coordinatorList } = useCoordinatorsList()

  return (
    <FormControl fullWidth>
      <InputLabel>Избери</InputLabel>
      <Select fullWidth defaultValue="" label={t('campaigns:campaign.type')} {...field}>
        {personList
          ?.filter((person) => {
            return !coordinatorList?.find((c) => c.personId === person.id)
          })
          ?.map((person) => {
            return (
              <MenuItem key={person.id} value={person.id}>
                {person.firstName} {person.lastName}
              </MenuItem>
            )
          })}
      </Select>
    </FormControl>
  )
}
Example #7
Source File: Importer.tsx    From your_spotify with GNU General Public License v3.0 5 votes vote down vote up
export default function Importer() {
  const dispatch = useAppDispatch();
  const imports = useSelector(selectImportStates);
  const [importType, setImportType] = useState<ImporterStateTypes>(ImporterStateTypes.privacy);

  const fetch = useCallback(
    async (force = false) => {
      dispatch(getImports(force));
    },
    [dispatch],
  );

  useEffect(() => {
    fetch();
  }, [fetch]);

  const running = useMemo(() => imports?.find((st) => st.status === 'progress'), [imports]);
  const Component = useMemo(
    () => (importType ? ImportTypeToComponent[importType] : null),
    [importType],
  );

  if (!imports) {
    return <CircularProgress />;
  }

  return (
    <div>
      <div>
        {running && (
          <div>
            <Text>Importing...</Text>
            <div className={s.progress}>
              <Text>
                {running.current} / {running.total}
              </Text>
            </div>
            <LinearProgress
              style={{ width: '100%' }}
              variant="determinate"
              value={(running.current / running.total) * 100}
            />
          </div>
        )}
      </div>
      {!running && (
        <div>
          <FormControl className={s.selectimport}>
            <InputLabel id="import-type-select">Import type</InputLabel>
            <Select
              labelId="import-type-select"
              value={importType}
              label="Import type"
              onChange={(ev) => setImportType(ev.target.value as ImporterStateTypes)}>
              {Object.values(ImporterStateTypes).map((typ) => (
                <MenuItem value={typ} key={typ}>
                  {typ}
                </MenuItem>
              ))}
            </Select>
          </FormControl>
          {Component && <Component />}
        </div>
      )}
      {imports.length > 0 && <ImportHistory />}
    </div>
  );
}
Example #8
Source File: TextFilter.tsx    From Tachidesk-WebUI with Mozilla Public License 2.0 5 votes vote down vote up
export default function TextFilter(props: Props) {
    const {
        state,
        name,
        position,
        group,
        updateFilterValue,
        update,
    } = props;
    const [Search, setsearch] = React.useState(state || '');
    let typingTimer: NodeJS.Timeout;

    function doneTyping(e: React.ChangeEvent<HTMLInputElement>) {
        const upd = update.filter((el: {
            position: number; group: number | undefined;
        }) => !(position === el.position && group === el.group));
        updateFilterValue([...upd, { position, state: e.target.value, group }]);
    }

    function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
        setsearch(e.target.value);

        clearTimeout(typingTimer);
        typingTimer = setTimeout(() => { doneTyping(e); }, 2500);
    }

    if (state !== undefined) {
        return (
            <Box key={`${name}`} sx={{ display: 'flex', flexDirection: 'row', minWidth: 120 }}>
                <>
                    <SearchIcon
                        sx={{
                            margin: 'auto',
                        }}
                    />
                    <FormControl fullWidth>
                        <InputLabel sx={{ margin: '10px 0 10px 0' }}>
                            {name}
                        </InputLabel>
                        <Input
                            name={name}
                            value={Search || ''}
                            onChange={handleChange}
                            sx={{ margin: '10px 0 10px 0' }}
                        />
                    </FormControl>
                </>
            </Box>
        );
    }
    return (<></>);
}
Example #9
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 #10
Source File: CategorySelect.tsx    From mojito_pdm with Creative Commons Attribution Share Alike 4.0 International 5 votes vote down vote up
CategorySelect: React.FC = () => {
    const theme = useTheme()
    const [category, setCategory] = useRecoilState(CarState.categorySearch)

    const handleChange = (event: SelectChangeEvent) => {
        setCategory(event.target.value)
    };

    return (
        <>
            <div>
                <FormControl variant="outlined" sx={{margin: theme.spacing(1), minWidth: 240}} color="error">
                    <InputLabel sx={{color: "white"}}>Category</InputLabel>
                    <Select sx={{color: "white"}}
                            labelId="demo-simple-select-outlined-label"
                            id="demo-simple-select-outlined"
                            value={category}
                            onChange={handleChange}
                            label="Category"
                    >
                        <MenuItem value="">
                            <em>All</em>
                        </MenuItem>
                        <MenuItem value={"Sports"}>Sports</MenuItem>
                        <MenuItem value={"Compacts"}>Compacts</MenuItem>
                        <MenuItem value={"Muscle"}>Muscle</MenuItem>
                        <MenuItem value={"Sedan"}>Sedan</MenuItem>
                        <MenuItem value={"Coupe"}>Coupé</MenuItem>
                        <MenuItem value={"Super"}>Super</MenuItem>
                        <MenuItem value={"SUV"}>SUV</MenuItem>
                        <MenuItem value={"Vans"}>Vans</MenuItem>
                        <MenuItem value={"Offroad"}>Offroad</MenuItem>
                        <MenuItem value={"Sports Classics"}>Sports Classics</MenuItem>
                        <MenuItem value={"Motorcycles"}>Motorcycles</MenuItem>
                    </Select>
                </FormControl>
            </div>
        </>
    )
}
Example #11
Source File: MultiSelect.tsx    From multi-downloader-nx with MIT License 5 votes vote down vote up
MultiSelect: React.FC<MultiSelectProps> = (props) => {
  const theme = useTheme();

  return <div>
    <FormControl sx={{ m: 1, width: 300 }}>
      <InputLabel id="multi-select-label">{props.title}</InputLabel>
      <Select
        labelId="multi-select-label"
        id="multi-select"
        multiple
        value={(props.selected ?? [])}
        onChange={e => {
          const val = typeof e.target.value === "string" ? e.target.value.split(",") : e.target.value;
          if (props.allOption && val.includes('all')) {
            if (props.values.length === val.length - 1)
              props.onChange([]);
            else 
              props.onChange(props.values);
          } else {
            props.onChange(val);
          }
        }}
        input={<OutlinedInput id="select-multiple-chip" label={props.title} />}
        renderValue={(selected) => (
          selected.join(', ')
        )}
        MenuProps={MenuProps}
      >
        {props.values.concat(props.allOption ? 'all' : []).map((name) => (
          <MenuItem
            key={`${props.title}_${name}`}
            value={name}
            style={getStyles(name, props.selected, theme)}
          >
            {name}
          </MenuItem>
        ))}
      </Select>
    </FormControl>
  </div>
}
Example #12
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 #13
Source File: AutocompleteWrapper.tsx    From console with GNU Affero General Public License v3.0 5 votes vote down vote up
AutocompleteWrapper = ({
  classes,
  id,
  name,
  onChange,
  options,
  label,
  tooltip = "",
  value,
  disabled = false,
}: SelectProps) => {
  const [internalValue, setInternalValue] = useState<selectorTypes>(options[0]);

  const executeOnSelect = (_: any, selectedValue: any) => {
    if (selectedValue) {
      onChange(selectedValue.value);
      setInternalValue(selectedValue);
    }
  };

  return (
    <React.Fragment>
      <Grid item xs={12} className={classes.fieldContainer}>
        {label !== "" && (
          <InputLabel htmlFor={id} className={classes.inputLabel}>
            <span>{label}</span>
            {tooltip !== "" && (
              <div className={classes.tooltipContainer}>
                <Tooltip title={tooltip} placement="top-start">
                  <div className={classes.tooltip}>
                    <HelpIcon />
                  </div>
                </Tooltip>
              </div>
            )}
          </InputLabel>
        )}
        <FormControl fullWidth>
          <Autocomplete
            id={id}
            options={options}
            getOptionLabel={(option) => option.label}
            isOptionEqualToValue={(option) => option.value === value}
            disabled={disabled}
            renderInput={(params) => <InputField {...params} name={name} />}
            value={internalValue}
            onChange={executeOnSelect}
            autoHighlight
          />
        </FormControl>
      </Grid>
    </React.Fragment>
  );
}
Example #14
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 #15
Source File: index.tsx    From ExpressLRS-Configurator with GNU General Public License v3.0 5 votes vote down vote up
FlashingMethodOptions: FunctionComponent<FlashingMethodsListProps> = (
  props
) => {
  const { onChange, currentTarget, currentDevice, firmwareVersionData } = props;
  const targetMappingsSorted = useMemo(
    () => sortDeviceTargets(currentDevice?.targets ?? []),
    [currentDevice?.targets]
  );

  const onFlashingMethodChange = (
    _event: React.ChangeEvent<HTMLInputElement>,
    value: string
  ) => {
    const target = targetMappingsSorted?.find((item) => {
      return item.id === value;
    });
    onChange(target ?? null);
  };

  const flashingMethodRadioOption = useCallback(
    (targetMapping: Target) => {
      const label = (
        <>
          {!targetMapping.flashingMethod
            ? targetMapping.name
            : targetMapping.flashingMethod}
          {targetMapping.flashingMethod !== null && (
            <FlashingMethodDescription
              flashingMethod={targetMapping.flashingMethod}
              deviceWikiUrl={currentDevice?.wikiUrl ?? null}
              firmwareVersionData={firmwareVersionData}
            />
          )}
        </>
      );

      return (
        <FormControlLabel
          key={targetMapping.id}
          value={targetMapping.id}
          sx={styles.radioControl}
          control={<Radio sx={styles.radio} color="primary" />}
          label={label}
        />
      );
    },
    [currentDevice?.wikiUrl, firmwareVersionData]
  );

  return (
    <Box sx={styles.root}>
      <Typography variant="h6" sx={styles.categoryTitle}>
        Flashing Method
      </Typography>
      <FormControl component="fieldset" sx={styles.flashingMethods}>
        <RadioGroup
          row
          value={currentTarget?.id ?? null}
          onChange={onFlashingMethodChange}
          defaultValue="top"
        >
          {targetMappingsSorted?.map((item) => {
            return flashingMethodRadioOption(item);
          })}
        </RadioGroup>
      </FormControl>
    </Box>
  );
}
Example #16
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 #17
Source File: Sort.tsx    From Cromwell with MIT License 5 votes vote down vote up
export default function Sort<TEntity>(props: {
    options: TSortOption<TEntity>[];
    onChange: (key: keyof TEntity, order: 'ASC' | 'DESC') => any;
}) {
    const [orderByKey, setOrderByKey] = useState<keyof TEntity | null>(null);
    const [order, setOrder] = useState<'ASC' | 'DESC'>('DESC');

    const handleChangeOrderByKey = (key: keyof TEntity) => {
        setOrderByKey(key);
        props.onChange(key, order);
    }
    const toggleOrder = () => {
        setOrder(prev => {
            const newOrder = prev === 'ASC' ? 'DESC' : 'ASC';
            props.onChange(orderByKey, newOrder);
            return newOrder;
        });
    }
    return (
        <FormControl className={styles.wrapper}>
            <InputLabel>Sort</InputLabel>
            <Select
                style={{ minWidth: '60px', maxWidth: '100px' }}
                size="small"
                value={orderByKey ?? 'id'}
                label="Sort"
                onChange={event => handleChangeOrderByKey(event.target.value as any)}
                classes={{
                    icon: styles.selectIcon,
                    select: styles.muiSelect,
                }}
            >
                {props.options.map(sort => (
                    <MenuItem key={sort.key + ''} value={sort.key + ''}>{sort.label}</MenuItem>
                ))}
            </Select>
            <div onClick={toggleOrder} className={styles.orderButton}>
                <ArrowDropDownIcon
                    style={{
                        transform: order === 'ASC' ? 'rotate(180deg)' : undefined,
                        transition: '0.3s',
                    }}
                />
            </div>
        </FormControl>
    )
}
Example #18
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 #19
Source File: SettingsView.tsx    From react-flight-tracker with MIT License 4 votes vote down vote up
SettingsView: React.FC<Props> = (props) => {

  // Fields
  const contextName: string = ViewKeys.SettingsView;

  // Contexts
  const systemContext = useContext(SystemContext);
  const appContext = useContext(AppContext);

  const getSetting = (key: string, type: string) => {

    const value = systemContext.getSetting(key)
    if (typeof (value) === type)
      return value;

    return false;
  };

  const handleChange = (e: SelectChangeEvent) => {
    appContext.changeTheme(e.target.value);
  };

  const handleSettingsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    systemContext.storeSetting(e.target.name, e.target.checked);
  };

  const renderAppSettings = () => {

    return (
      <Card>

        <CardContent>

          <Typography
            variant={'h6'}
            gutterBottom={true}>
            {'App settings'}
          </Typography>

          <FormGroup>
            <FormControl
              color='secondary'
              variant="filled"
              sx={{ m: 1, minWidth: 120 }}>

              <InputLabel
                id="demo-simple-select-filled-label">
                Theme change
              </InputLabel>
              <Select
                labelId="demo-simple-select-filled-label"
                id="demo-simple-select-filled"
                value={appContext.activeThemeName}
                onChange={handleChange}>

                <MenuItem
                  value={ThemeKeys.DarkTheme}>
                  {ThemeKeys.DarkTheme}
                </MenuItem>
                <MenuItem
                  value={ThemeKeys.LightTheme}>
                  {ThemeKeys.LightTheme}
                </MenuItem>
                <MenuItem
                  value={ThemeKeys.PineappleTheme}>
                  {ThemeKeys.PineappleTheme}
                </MenuItem>
              </Select>
            </FormControl>

          </FormGroup>
        </CardContent>
      </Card>
    );
  };

  const renderMapSettings = () => {

    return (
      <Card>

        <CardContent>

          <Typography
            variant={'h6'}
            gutterBottom={true}>
            {'Map settings'}
          </Typography>

          <FormGroup>
            <FormControlLabel
              control={
                <Switch
                  color='secondary'
                  name={SettingKeys.ShowDataOverlayOnMap}
                  checked={getSetting(SettingKeys.ShowDataOverlayOnMap, 'boolean')}
                  onChange={handleSettingsChange} />
              }
              label="Show data overlay on map"
            />
            <FormControlLabel
              control={
                <Switch
                  color='secondary'
                  name={SettingKeys.ShowLogOverlayOnMap}
                  checked={getSetting(SettingKeys.ShowLogOverlayOnMap, 'boolean')}
                  onChange={handleSettingsChange} />
              }
              label="Show log overlay on map"
            />
          </FormGroup>
        </CardContent>
      </Card>
    );
  };

  return (

    <ViewContainer
      isScrollLocked={true}>

      {renderAppSettings()}

      <Box sx={{ height: (theme) => theme.spacing(1) }} />

      {renderMapSettings()}
    </ViewContainer>
  );
}
Example #20
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 #21
Source File: Form.tsx    From frontend with MIT License 4 votes vote down vote up
export default function EditForm() {
  const [type, setType] = useState('donation')
  const [status, setStatus] = useState('initial')
  const [provider, setProvider] = useState('none')
  const [currency, setCurrency] = useState('')
  const [vault, setVault] = useState('')
  const router = useRouter()
  const { t } = useTranslation()

  let id = router.query.id

  const vaults = useVaultsList().data

  let initialValues: DonationInput = {
    type: 'donation',
    status: 'initial',
    provider: 'none',
    currency: '',
    amount: 0,
    targetVaultId: '',
    extCustomerId: '',
    extPaymentIntentId: '',
    extPaymentMethodId: '',
  }

  if (id) {
    id = String(id)
    const { data }: UseQueryResult<DonationResponse> = useDonation(id)

    if (data) {
      initialValues = {
        type: data?.type.toString(),
        status: data?.status.toString(),
        provider: data?.provider.toString(),
        currency: data?.currency.toString(),
        amount: data?.amount,
        targetVaultId: data?.targetVaultId,
        extCustomerId: data?.extCustomerId,
        extPaymentIntentId: data?.extPaymentIntentId,
        extPaymentMethodId: data?.extPaymentMethodId,
      }
    }
  }

  const mutationFn = id ? useEditDonation(id) : useCreateDonation()

  const mutation = useMutation<
    AxiosResponse<DonationResponse>,
    AxiosError<ApiErrors>,
    DonationInput
  >({
    mutationFn,
    onError: () => AlertStore.show(t('donations:alerts:error'), 'error'),
    onSuccess: () => {
      AlertStore.show(id ? t('donations:alerts:edit') : t('donations:alerts:create'), 'success')
      router.push(routes.admin.donations.index)
    },
  })

  async function onSubmit(data: DonationInput) {
    type ? (data.type = type) : ''
    status ? (data.status = status) : ''
    provider ? (data.provider = provider) : ''
    currency ? (data.currency = currency) : ''
    vault ? (data.targetVaultId = vault) : ''

    mutation.mutate(data)
  }

  return (
    <GenericForm
      onSubmit={onSubmit}
      initialValues={initialValues}
      validationSchema={validationSchema}>
      <Box sx={{ marginTop: '5%', height: '62.6vh' }}>
        <Typography variant="h5" component="h2" sx={{ marginBottom: 2, textAlign: 'center' }}>
          {id ? t('donations:edit-form-heading') : t('donations:form-heading')}
        </Typography>
        <Grid container spacing={2} sx={{ width: 600, margin: '0 auto' }}>
          <Grid item xs={6}>
            <FormControl fullWidth size="small">
              <InputLabel id="labelType">{t('donations:type')}</InputLabel>
              <Select
                labelId="labelType"
                label={t('donations:type')}
                id="type"
                name="type"
                value={initialValues.type}
                onChange={(e) => setType(e.target.value)}
                disabled={id ? true : false}>
                {validDonationTypes.map((type) => {
                  return (
                    <MenuItem key={type} value={type}>
                      {type}
                    </MenuItem>
                  )
                })}
              </Select>
            </FormControl>
          </Grid>
          <Grid item xs={6}>
            <FormControl fullWidth size="small">
              <InputLabel id="labelStatus">{t('donations:status')}</InputLabel>
              <Select
                labelId="labelStatus"
                label={t('donations:status')}
                id="status"
                name="status"
                value={initialValues.status}
                onChange={(e) => {
                  setStatus(e.target.value)
                  console.log(e.target.value)
                }}>
                {validDonationStatuses.map((stat) => {
                  return (
                    <MenuItem key={stat} value={stat}>
                      {stat}
                    </MenuItem>
                  )
                })}
              </Select>
            </FormControl>
          </Grid>
          <Grid item xs={6}>
            <FormControl fullWidth size="small">
              <InputLabel id="labelProvider">{t('donations:provider')}</InputLabel>
              <Select
                labelId="labelProvider"
                label={t('donations:provider')}
                id="provider"
                name="provider"
                value={initialValues.provider}
                onChange={(e) => setProvider(e.target.value)}
                disabled={id ? true : false}>
                {validProviders.map((prov) => {
                  return (
                    <MenuItem key={prov} value={prov}>
                      {prov}
                    </MenuItem>
                  )
                })}
              </Select>
            </FormControl>
          </Grid>
          <Grid item xs={6}>
            <FormControl fullWidth size="small">
              <InputLabel id="labelVault">{t('donations:vault')}</InputLabel>
              <Select
                labelId="labelVault"
                label={t('donations:vault')}
                id="targetVaultId"
                name="targetVaultId"
                value={initialValues.targetVaultId}
                onChange={(e) => setVault(e.target.value)}
                disabled={id ? true : false}>
                {vaults?.map((vault) => {
                  return (
                    <MenuItem key={vault.id} value={vault.id}>
                      {vault.name}
                    </MenuItem>
                  )
                })}
              </Select>
            </FormControl>
          </Grid>
          <Grid item xs={12}>
            <FormTextField
              type="text"
              label={t('donations:ext-customer-id')}
              name="extCustomerId"
              disabled={id ? true : false}
            />
          </Grid>
          <Grid item xs={6}>
            <FormTextField
              type="text"
              label={t('donations:ext-payment-intent-id')}
              name="extPaymentIntentId"
              disabled={id ? true : false}
            />
          </Grid>
          <Grid item xs={6}>
            <FormTextField
              type="text"
              label={t('donations:ext-payment-method-id')}
              name="extPaymentMethodId"
              disabled={id ? true : false}
            />
          </Grid>
          <Grid item xs={6}>
            <FormTextField
              type="number"
              label={t('donations:amount')}
              name="amount"
              disabled={id ? true : false}
            />
          </Grid>
          <Grid item xs={6}>
            <FormControl fullWidth size="small">
              <InputLabel id="labelCurrency">{t('donations:currency')}</InputLabel>
              <Select
                labelId="labelCurrency"
                label={t('donations:currency')}
                id="currency"
                name="currency"
                value={initialValues.currency}
                onChange={(e) => setCurrency(e.target.value)}
                disabled={id ? true : false}>
                {validCurrencies.map((currency) => {
                  return (
                    <MenuItem key={currency} value={currency}>
                      {currency}
                    </MenuItem>
                  )
                })}
              </Select>
            </FormControl>
          </Grid>
          <Grid item xs={6}>
            <SubmitButton fullWidth label={t('donations:cta:submit')} />
          </Grid>
          <Grid item xs={6}>
            <Link passHref href={routes.admin.donations.index}>
              <Button>{t('donations:cta:cancel')}</Button>
            </Link>
          </Grid>
        </Grid>
      </Box>
    </GenericForm>
  )
}
Example #22
Source File: ArrayOneOfField.tsx    From firecms with MIT License 4 votes vote down vote up
function ArrayOneOfEntry({
                             name,
                             index,
                             value,
                             typeField,
                             valueField,
                             properties,
                             autoFocus,
                             context
                         }: ArrayOneOfEntryProps) {


    const type = value && value[typeField];
    const [typeInternal, setTypeInternal] = useState<string | undefined>(type ?? undefined);

    useEffect(() => {
        if (type !== typeInternal) {
            setTypeInternal(type);
        }
    }, [type]);

    const property = typeInternal ? properties[typeInternal] : undefined;

    const enumValues: EnumValues = Object.entries(properties).map(([key, property]) => ({ [key]: property.title ?? key })).reduce((a, b) => ({ ...a, ...b }));

    const typeFieldName = `${name}[${typeField}]`;
    const valueFieldName = `${name}[${valueField}]`;

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

            <FastField
                required={true}
                name={typeFieldName}
            >
                {(fieldProps: FormikFieldProps) =>
                    (
                        <FormControl fullWidth>
                            <InputLabel
                                id={`${name}_${index}_select_label`}>
                                <span>Type</span>
                            </InputLabel>
                            <Select
                                fullWidth
                                sx={{ marginBottom: 2 }}
                                labelId={`${name}_${index}_select_label`}
                                value={fieldProps.field.value !== undefined && fieldProps.field.value !== null ? fieldProps.field.value : ""}
                                onChange={(evt: any) => {
                                    const eventValue = evt.target.value;
                                    fieldProps.form.setFieldTouched(typeFieldName);
                                    setTypeInternal(eventValue);
                                    fieldProps.form.setFieldValue(typeFieldName, eventValue);
                                    fieldProps.form.setFieldValue(valueFieldName, null);
                                }}
                                renderValue={(enumKey: any) =>
                                    <EnumValuesChip
                                        enumKey={enumKey}
                                        enumValues={enumValues}
                                        small={true}/>
                                }>
                                {enumToObjectEntries(enumValues)
                                    .map(([enumKey, labelOrConfig]) => {
                                        return (
                                            <MenuItem
                                                key={`select_${name}_${index}_${enumKey}`}
                                                value={enumKey}>
                                                <EnumValuesChip
                                                    enumKey={enumKey}
                                                    enumValues={enumValues}
                                                    small={true}/>
                                            </MenuItem>
                                        );
                                    })}
                            </Select>
                        </FormControl>
                    )
                }
            </FastField>

            {property && (
                <FormControl fullWidth
                             key={`form_control_${name}_${typeInternal}`}>
                    {buildPropertyField({
                        name: valueFieldName,
                        property: property,
                        context: context,
                        autoFocus: autoFocus
                    })}
                </FormControl>
            )}

        </Paper>
    );
}
Example #23
Source File: Info.tsx    From frontend with MIT License 4 votes vote down vote up
export default function Info({ files, setFiles }: Props) {
  const { t } = useTranslation('irregularity')

  return (
    <Grid container spacing={4} justifyContent="center" alignContent="center">
      <Subtitle label={t('steps.info.subtitle')} />
      <Grid container item>
        <Grid item xs={12}>
          <Typography sx={{ fontSize: '18px' }}>{t('steps.info.is-donor')}</Typography>
        </Grid>
        <Grid container item xs={12}>
          <FormControl sx={{ width: '40%', marginTop: '20px' }}>
            <Grid container item xs={12} direction="row">
              <Grid item xs={6}>
                <label>
                  <Field
                    size="medium"
                    type="radio"
                    name="info.notifierType"
                    value={NotifierTypes.BENEFACTOR}
                  />
                  {t('steps.info.yes')}
                </label>
              </Grid>
              <Grid item xs={6}>
                <label>
                  <Field
                    size="medium"
                    type="radio"
                    name="info.notifierType"
                    value={NotifierTypes.OTHER}
                  />
                  {t('steps.info.no')}
                </label>
              </Grid>
            </Grid>
          </FormControl>
        </Grid>
      </Grid>
      <Grid container item>
        <Grid item xs={12}>
          <Typography sx={{ fontSize: '18px', marginBottom: '20px' }}>
            {t('reason.title')}
          </Typography>
        </Grid>
        <Grid item xs={12}>
          <IrregularityReasonSelect />
        </Grid>
      </Grid>
      <Grid container item>
        <Grid item xs={12}>
          <Typography sx={{ fontSize: '18px' }}>{t('steps.info.content')}</Typography>
        </Grid>
        <Grid item xs={12}>
          <CssTextField label="" type="text" multiline rows={6} name="info.description" />
        </Grid>
      </Grid>
      <Grid container item rowSpacing={2}>
        <Grid item xs={12}>
          <Typography sx={{ fontSize: '18px' }}>{t('steps.info.files')}</Typography>
        </Grid>
        <Grid container justifyContent="center">
          <FileUpload
            onUpload={(newFiles) => {
              setFiles((prevFiles: File[]) => [...prevFiles, ...newFiles])
            }}
            buttonLabel={t('cta.upload-files')}
          />
          <FileList
            files={files}
            onDelete={(deletedFile) =>
              setFiles((prevFiles: File[]) =>
                prevFiles.filter((file: File) => file.name !== deletedFile.name),
              )
            }
          />
        </Grid>
      </Grid>
      <Grid item xs={12}>
        <Typography sx={{ fontSize: '15px', textAlign: 'justify' }}>
          {t('steps.info.priority-message')}
        </Typography>
      </Grid>
      <Grid item xs={12}>
        <Typography sx={{ fontSize: '15px', textAlign: 'justify' }}>
          {t('steps.info.share-message')}
        </Typography>
      </Grid>
      <Grid item xs={12}>
        <Typography sx={{ fontSize: '15px', textAlign: 'justify' }}>
          {t('steps.info.thanks-message')}
        </Typography>
      </Grid>
      <Grid item xs={12}>
        <Typography sx={{ fontSize: '15px', textAlign: 'justify' }}>
          {t('steps.info.sign')}
        </Typography>
      </Grid>
    </Grid>
  )
}
Example #24
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>
    );
}
Example #25
Source File: GeneralInfo.tsx    From frontend with MIT License 4 votes vote down vote up
export default function GeneralInfo() {
  const { t } = useTranslation()

  return (
    <Grid container spacing={3} justifyContent="center" direction="column" alignContent="center">
      <Grid item xs={12}>
        <HeaderTypography>{t('support:steps.info.subtitle')}</HeaderTypography>
      </Grid>
      <Grid item xs={12} sm={8}>
        <Grid container spacing={3} justifyContent="center" direction="column">
          <Grid item xs={12}>
            <Grid container spacing={3} justifyContent="center" direction="row">
              <Grid item xs={12} sm={6}>
                <FormTextField
                  autoFocus
                  type="text"
                  name="person.firstName"
                  autoComplete="first-name"
                  label="support:steps.info.first-name"
                />
              </Grid>
              <Grid item xs={12} sm={6}>
                <FormTextField
                  type="text"
                  name="person.lastName"
                  autoComplete="family-name"
                  label="support:steps.info.last-name"
                />
              </Grid>
            </Grid>
          </Grid>
          <Grid item xs={12}>
            <FormTextField
              type="text"
              name="person.email"
              autoComplete="email"
              label="support:steps.info.email"
            />
          </Grid>
          <Grid item xs={12}>
            <FormTextField
              type="text"
              name="person.phone"
              autoComplete="tel"
              label="support:steps.info.phone"
            />
          </Grid>
          <Grid item xs={12}>
            <FormTextField
              multiline
              size="medium"
              type="text"
              name="person.comment"
              autoComplete="comment"
              label="support:steps.info.comment"
            />
          </Grid>
        </Grid>
      </Grid>
      <Grid item xs={12} sm={8}>
        <Grid container direction="column" alignItems="flex-start">
          <Grid item xs={12}>
            <AcceptTermsField name="person.terms" />
          </Grid>
          <Grid item xs={12}>
            <AcceptPrivacyPolicyField name="person.gdpr" />
          </Grid>
          <Grid item xs={12}>
            <FormControl component="fieldset">
              <CheckboxField
                name="person.newsletter"
                label={
                  <Typography variant="body2">{t('support:steps.newsletter.label')}</Typography>
                }
              />
            </FormControl>
          </Grid>
        </Grid>
      </Grid>
    </Grid>
  )
}
Example #26
Source File: StringNumberFilterField.tsx    From firecms with MIT License 4 votes vote down vote up
export function StringNumberFilterField({
                                            name,
                                            value,
                                            setValue,
                                            dataType,
                                            isArray,
                                            enumValues,
                                            title
                                        }: StringNumberFilterFieldProps) {

    const possibleOperations: (keyof typeof operationLabels) [] = isArray
        ? ["array-contains"]
        : ["==", "!=", ">", "<", ">=", "<="];

    if (enumValues)
        isArray
            ? possibleOperations.push("array-contains-any")
            : possibleOperations.push("in");

    const [fieldOperation, fieldValue] = value || [possibleOperations[0], undefined];
    const [operation, setOperation] = useState<TableWhereFilterOp>(fieldOperation);
    const [internalValue, setInternalValue] = useState<string | number | string[] | number[] | undefined>(fieldValue);

    function updateFilter(op: TableWhereFilterOp, val: string | number | string[] | number[] | undefined) {
        let newValue = val;
        const prevOpIsArray = multipleSelectOperations.includes(operation);
        const newOpIsArray = multipleSelectOperations.includes(op);
        if (prevOpIsArray !== newOpIsArray) {
            // @ts-ignore
            newValue = newOpIsArray ? (typeof val === "string" || typeof val === "number" ? [val] : []) : "";
        }

        if (typeof newValue === "number" && isNaN(newValue))
            newValue = undefined;

        setOperation(op);
        setInternalValue(newValue);

        const hasNewValue = newValue !== null && Array.isArray(newValue)
            ? newValue.length > 0
            : newValue !== undefined;
        if (op && hasNewValue) {
            setValue(
                [op, newValue]
            );
        } else {
            setValue(
                undefined
            );
        }
    }

    const multiple = multipleSelectOperations.includes(operation);
    return (

        <Box display={"flex"} width={340} alignItems={"center"}>
            <Box width={80}>
                <FormControl fullWidth>
                    <MuiSelect value={operation}
                               fullWidth
                               onChange={(evt: any) => {
                                   updateFilter(evt.target.value, internalValue);
                               }}>
                        {possibleOperations.map((op) =>
                            <MenuItem
                                key={`filter_op_${name}_${op}`}
                                value={op}>{operationLabels[op]}</MenuItem>
                        )}

                    </MuiSelect>
                </FormControl>
            </Box>

            <Box flexGrow={1} ml={1}>

                <FormControl fullWidth>
                    {!enumValues && <OutlinedInput
                        fullWidth
                        key={`filter_${name}`}
                        type={dataType === "number" ? "number" : undefined}
                        value={internalValue !== undefined ? internalValue : ""}
                        onChange={(evt) => {
                            const val = dataType === "number"
                                ? parseFloat(evt.target.value)
                                : evt.target.value;
                            updateFilter(operation, val);
                        }}
                    />}

                    {enumValues &&
                    <MuiSelect
                        fullWidth
                        key={`filter-select-${multiple}-${name}`}
                        multiple={multiple}
                        value={internalValue !== undefined ? internalValue : isArray ? [] : ""}
                        onChange={(evt: any) => updateFilter(operation, dataType === "number" ? parseInt(evt.target.value) : evt.target.value)}
                        renderValue={multiple
? (selected: any) =>
                            (
                                <div>
                                    {selected.map((enumKey: any) => {
                                        return <EnumValuesChip
                                            key={`select_value_${name}_${enumKey}`}
                                            enumKey={enumKey}
                                            enumValues={enumValues}
                                            small={true}/>;
                                    })}
                                </div>
                            )
: undefined}>
                        {Object.entries(enumValues).map(([enumKey, labelOrConfig]) => {
                            return (
                                <MenuItem
                                    key={`select_${name}_${enumKey}`}
                                    value={enumKey}>
                                    <EnumValuesChip
                                        enumKey={enumKey}
                                        enumValues={enumValues}
                                        small={true}/>
                                </MenuItem>
                            );
                        })}
                    </MuiSelect>}
                </FormControl>
            </Box>

            {internalValue !== undefined && <Box ml={1}>
                <IconButton
                    onClick={(e) => updateFilter(operation, undefined)}
                    size={"small"}>
                    <Tooltip title={`Clear ${title}`}>
                        <ClearIcon fontSize={"small"}/>
                    </Tooltip>
                </IconButton>
            </Box>}

        </Box>
    );

}
Example #27
Source File: Deposit.tsx    From wrap.scrt.network with MIT License 4 votes vote down vote up
export default function Deposit({
  token,
  secretAddress,
  onSuccess,
  onFailure,
}: {
  token: Token;
  secretAddress: string;
  onSuccess: (txhash: string) => any;
  onFailure: (error: any) => any;
}) {
  const [sourceAddress, setSourceAddress] = useState<string>("");
  const [availableBalance, setAvailableBalance] = useState<string>("");
  const [loadingTx, setLoading] = useState<boolean>(false);
  const [sourceCosmJs, setSourceCosmJs] =
    useState<SigningStargateClient | null>(null);
  const [selectedChainIndex, setSelectedChainIndex] = useState<number>(0);
  const [fetchBalanceInterval, setFetchBalanceInterval] = useState<any>(null);
  const inputRef = useRef<any>();
  const maxButtonRef = useRef<any>();

  const sourceChain =
    chains[token.deposits[selectedChainIndex].source_chain_name];
  const targetChain = chains["Secret Network"];

  const fetchSourceBalance = async (sourceAddress: string) => {
    const url = `${
      chains[token.deposits[selectedChainIndex].source_chain_name].lcd
    }/bank/balances/${sourceAddress}`;
    try {
      const response = await fetch(url);
      const result: {
        height: string;
        result: Array<{ denom: string; amount: string }>;
      } = await response.json();

      const balance =
        result.result.find(
          (c) => c.denom === token.deposits[selectedChainIndex].from_denom
        )?.amount || "0";

      setAvailableBalance(balance);
    } catch (e) {
      console.error(`Error while trying to query ${url}:`, e);
      setAvailableBalance("Error");
    }
  };

  useEffect(() => {
    setAvailableBalance("");

    if (!sourceAddress) {
      return;
    }

    if (fetchBalanceInterval) {
      clearInterval(fetchBalanceInterval);
    }

    fetchSourceBalance(sourceAddress);
    const interval = setInterval(
      () => fetchSourceBalance(sourceAddress),
      10_000
    );
    setFetchBalanceInterval(interval);

    return () => clearInterval(interval);
  }, [sourceAddress]);

  useEffect(() => {
    (async () => {
      while (!window.keplr || !window.getOfflineSignerOnlyAmino) {
        await sleep(100);
      }

      if (["LUNA", "UST"].includes(token.name.toUpperCase())) {
        await suggestTerraToKeplr(window.keplr);
      }
      // Initialize cosmjs on the target chain, because it has sendIbcTokens()
      const { chain_id, rpc, bech32_prefix } =
        chains[token.deposits[selectedChainIndex].source_chain_name];
      await window.keplr.enable(chain_id);
      const sourceOfflineSigner = window.getOfflineSignerOnlyAmino(chain_id);
      const depositFromAccounts = await sourceOfflineSigner.getAccounts();
      setSourceAddress(depositFromAccounts[0].address);
      const cosmjs = await SigningStargateClient.connectWithSigner(
        rpc,
        sourceOfflineSigner,
        { prefix: bech32_prefix, broadcastPollIntervalMs: 10_000 }
      );
      setSourceCosmJs(cosmjs);
    })();
  }, [selectedChainIndex]);

  return (
    <>
      <div style={{ padding: "1.5em" }}>
        <div
          style={{
            display: "flex",
            placeItems: "center",
            gap: token.deposits.length === 1 ? "0.3em" : "0.5em",
          }}
        >
          <Typography>
            Deposit <strong>{token.name}</strong> from
          </Typography>
          <If condition={token.deposits.length === 1}>
            <Then>
              <Typography>
                <strong>
                  {token.deposits[selectedChainIndex].source_chain_name}
                </strong>
              </Typography>
            </Then>
            <Else>
              <FormControl>
                <Select
                  value={selectedChainIndex}
                  onChange={(e) =>
                    setSelectedChainIndex(Number(e.target.value))
                  }
                >
                  {token.deposits.map((chain, index) => (
                    <MenuItem value={index} key={index}>
                      <div
                        style={{
                          display: "flex",
                          gap: "0.5em",
                          placeItems: "center",
                        }}
                      >
                        <Avatar
                          src={chains[chain.source_chain_name].chain_image}
                          sx={{
                            marginLeft: "0.3em",
                            width: "1em",
                            height: "1em",
                            boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
                          }}
                        />
                        <strong>{chain.source_chain_name}</strong>
                      </div>
                    </MenuItem>
                  ))}
                </Select>
              </FormControl>
            </Else>
          </If>
          <Typography>
            to <strong>Secret Network</strong>
          </Typography>
        </div>
        <br />
        <div
          style={{
            display: "flex",
            placeContent: "space-between",
            placeItems: "center",
            gap: "1em",
          }}
        >
          <Typography sx={{ fontWeight: "bold" }}>From:</Typography>
          <CopyableAddress
            address={sourceAddress}
            explorerPrefix={sourceChain.explorer_account}
          />
        </div>
        <div
          style={{
            display: "flex",
            placeContent: "space-between",
            placeItems: "center",
            gap: "1em",
          }}
        >
          <Typography sx={{ fontWeight: "bold" }}>To:</Typography>
          <CopyableAddress
            address={secretAddress}
            explorerPrefix={targetChain.explorer_account}
          />
        </div>
        <br />
        <div
          style={{
            display: "flex",
            placeItems: "center",
            gap: "0.3em",
            marginBottom: "0.8em",
          }}
        >
          <Typography sx={{ fontSize: "0.8em", fontWeight: "bold" }}>
            Available to Deposit:
          </Typography>
          <Typography
            sx={{
              fontSize: "0.8em",
              opacity: 0.8,
              cursor: "pointer",
            }}
            onClick={() => {
              maxButtonRef.current.click();
            }}
          >
            {(() => {
              if (availableBalance === "") {
                return <CircularProgress size="0.6em" />;
              }

              const prettyBalance = new BigNumber(availableBalance)
                .dividedBy(`1e${token.decimals}`)
                .toFormat();

              if (prettyBalance === "NaN") {
                return "Error";
              }

              return `${prettyBalance} ${token.name}`;
            })()}
          </Typography>
        </div>
        <FormControl sx={{ width: "100%" }} variant="standard">
          <InputLabel htmlFor="Amount to Deposit">Amount to Deposit</InputLabel>
          <Input
            autoFocus
            id="Amount to Deposit"
            fullWidth
            type="text"
            inputRef={inputRef}
            startAdornment={
              <InputAdornment position="start">
                <Avatar
                  src={token.image}
                  sx={{
                    width: "1em",
                    height: "1em",
                    boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
                  }}
                />
              </InputAdornment>
            }
            endAdornment={
              <InputAdornment position="end">
                <Button
                  ref={maxButtonRef}
                  style={{
                    padding: "0.1em 0.5em",
                    minWidth: 0,
                  }}
                  onClick={() => {
                    if (availableBalance === "") {
                      return;
                    }

                    const prettyBalance = new BigNumber(availableBalance)
                      .dividedBy(`1e${token.decimals}`)
                      .toFormat();

                    if (prettyBalance === "NaN") {
                      return;
                    }

                    inputRef.current.value = prettyBalance;
                  }}
                >
                  MAX
                </Button>
              </InputAdornment>
            }
          />
        </FormControl>
      </div>
      <div
        style={{
          display: "flex",
          placeContent: "center",
          marginBottom: "0.4em",
        }}
      >
        <LoadingButton
          variant="contained"
          sx={{
            padding: "0.5em 0",
            width: "10em",
            fontWeight: "bold",
            fontSize: "1.2em",
          }}
          loading={loadingTx}
          onClick={async () => {
            if (!sourceCosmJs) {
              console.error("No cosmjs");
              return;
            }

            if (!inputRef?.current?.value) {
              console.error("Empty deposit");
              return;
            }

            const normalizedAmount = (inputRef.current.value as string).replace(
              /,/g,
              ""
            );

            if (!(Number(normalizedAmount) > 0)) {
              console.error(`${normalizedAmount} not bigger than 0`);
              return;
            }

            setLoading(true);

            const amount = new BigNumber(normalizedAmount)
              .multipliedBy(`1e${token.decimals}`)
              .toFixed(0, BigNumber.ROUND_DOWN);

            const { deposit_channel_id, deposit_gas } =
              chains[token.deposits[selectedChainIndex].source_chain_name];
            try {
              const { transactionHash } = await sourceCosmJs.sendIbcTokens(
                sourceAddress,
                secretAddress,
                {
                  amount,
                  denom: token.deposits[selectedChainIndex].from_denom,
                },
                "transfer",
                deposit_channel_id,
                undefined,
                Math.floor(Date.now() / 1000) + 15 * 60, // 15 minute timeout
                gasToFee(deposit_gas)
              );
              inputRef.current.value = "";
              onSuccess(transactionHash);
            } catch (e) {
              onFailure(e);
            } finally {
              setLoading(false);
            }
          }}
        >
          Deposit
        </LoadingButton>
      </div>
    </>
  );
}
Example #28
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 #29
Source File: index.tsx    From metaplex with Apache License 2.0 4 votes vote down vote up
Settings = ({ narrow }: { narrow: boolean }) => {
  const { disconnect, publicKey } = useWallet();
  const { setEndpoint, env, endpoint } = useConnectionConfig();
  const { setVisible } = useWalletModal();
  const open = React.useCallback(() => setVisible(true), [setVisible]);
  const { setModal } = useModal();
  const theme = useTheme();
  const colorModeCtx = useColorMode();

  const handleConnect = React.useCallback(() => {
    setModal(ModalEnum.WALLET);
    setVisible(true);
  }, [setModal, setVisible]);

  const connectedActions = [
    {
      click: async () => {
        if (publicKey) {
          await navigator.clipboard.writeText(publicKey.toBase58());
          notify({
            message: 'Wallet update',
            description: 'Address copied to clipboard',
          });
        }
      },
      innerNarrow: () =>
        `Copy Address (${publicKey && shortenAddress(publicKey.toBase58())})`,
      inner: function ConnectedWalletCopyC() {
        return (
          <React.Fragment>
            <CopyOutlined />
            {publicKey && shortenAddress(publicKey.toBase58())}
          </React.Fragment>
        );
      },
    },
    {
      click: open,
      inner: () => 'Change\u00A0Wallet',
    },
    {
      click: () => disconnect().catch(),
      inner: () => `Disconnect\u00A0(${env})`,
      expandedExtra: {
        // these are interepreted as props. TODO: specific types
        color: 'error' as any,
        variant: 'contained' as any,
      },
    },
  ];

  const [drawerOpen, setDrawerOpen] = React.useState(false);
  const [envCollapseOpen, setEnvCollapseOpen] = React.useState(false);

  const hackySkipSet = 'hackySkipSet';
  const toggleDrawer = open => event => {
    if (
      event.type === 'keydown' &&
      (event.key === 'Tab' || event.key === 'Shift')
    ) {
      return;
    }

    if (event.target.classList.contains(hackySkipSet)) {
      return;
    }

    setDrawerOpen(open);
  };

  const drawerC = inner => {
    return (
      <React.Fragment>
        <Button onClick={toggleDrawer(true)}>
          <AccountBalanceWalletIcon />
        </Button>
        <Drawer anchor="right" open={drawerOpen} onClose={toggleDrawer(false)}>
          <Box
            sx={{ width: 250 }}
            role="presentation"
            onClick={toggleDrawer(false)}
            onKeyDown={toggleDrawer(false)}
          >
            {inner}
          </Box>
        </Drawer>
      </React.Fragment>
    );
  };

  const themeSwitch = (
    <Button
      sx={{ ml: 1 }}
      onClick={colorModeCtx.toggleColorMode}
      color="inherit"
    >
      {theme.palette.mode === 'dark' ? (
        <Brightness7Icon />
      ) : (
        <Brightness4Icon />
      )}
    </Button>
  );

  if (narrow) {
    const listHead = (
      <ListItem>
        <ListItemText
          primary="Wallet"
          primaryTypographyProps={{
            fontSize: '1.2rem',
            fontWeight: 'medium',
            letterSpacing: 0,
          }}
        />
      </ListItem>
    );
    return (
      <React.Fragment>
        {!publicKey &&
          drawerC(
            <List>
              {listHead}
              <Divider />
              <ListItemButton
                onClick={() => setEnvCollapseOpen(!envCollapseOpen)}
                className={hackySkipSet}
              >
                Change Network
                {envCollapseOpen ? <ExpandLess /> : <ExpandMore />}
              </ListItemButton>
              <Collapse in={envCollapseOpen} timeout="auto" unmountOnExit>
                <List component="div" disablePadding>
                  {ENDPOINTS.map(p => (
                    <ListItemButton
                      selected={endpoint === p.endpoint}
                      onClick={() => setEndpoint(p.endpoint)}
                      key={p.name}
                      sx={{ pl: 4 }}
                      className={hackySkipSet}
                    >
                      {p.name}
                    </ListItemButton>
                  ))}
                </List>
              </Collapse>
              <ListItemButton onClick={handleConnect}>Connect</ListItemButton>
            </List>,
          )}
        {publicKey &&
          drawerC(
            <List>
              {listHead}
              <Divider />
              {connectedActions.map((a, idx) => {
                return (
                  <ListItemButton onClick={a.click} key={idx}>
                    {(a.innerNarrow && a.innerNarrow()) || a.inner()}
                  </ListItemButton>
                );
              })}
            </List>,
          )}
        {themeSwitch}
      </React.Fragment>
    );
  } else {
    return (
      <Stack
        direction="row"
        spacing={2}
        sx={{
          display: 'flex',
          justifyContent: 'flex-end',
          alignItems: 'center',
          marginRight: '36px',
        }}
      >
        {!publicKey && (
          <React.Fragment>
            <FormControl variant="standard" style={{ minWidth: '10ch' }}>
              <Select
                id="connected-env-select"
                onChange={e => {
                  setEndpoint(e.target.value);
                }}
                value={endpoint}
              >
                {ENDPOINTS.map(({ name, endpoint }) => (
                  <MenuItem key={name} value={endpoint}>
                    {name}
                  </MenuItem>
                ))}
              </Select>
            </FormControl>
            <Link underline="none">
              <Button variant="contained" onClick={handleConnect}>
                Connect
              </Button>
            </Link>
          </React.Fragment>
        )}
        {publicKey &&
          connectedActions.map((a, idx) => {
            return (
              <Button
                key={idx}
                variant="outlined"
                onClick={a.click}
                {...a.expandedExtra}
              >
                {a.inner()}
              </Button>
            );
          })}
        {themeSwitch}
      </Stack>
    );
  }
}