@material-ui/core#Radio TypeScript Examples
The following examples show how to use
@material-ui/core#Radio.
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: LabeledRadio.tsx From UsTaxes with GNU Affero General Public License v3.0 | 6 votes |
export function LabeledRadio<A>(props: LabeledRadioProps<A>): ReactElement {
const { label, name, values, useGrid = true, sizes = { xs: 12 } } = props
const classes = useStyles()
const { control } = useFormContext<A>()
return (
<ConditionallyWrap
condition={useGrid}
wrapper={(children) => (
<Grid item {...sizes}>
{children}
</Grid>
)}
>
<Controller
name={name}
render={({ field: { value, onChange } }) => (
<div className={classes.root}>
<FormControl component="fieldset">
<FormLabel>{label}</FormLabel>
<RadioGroup name={name} value={value} onChange={onChange}>
{values.map(([rowLabel, rowValue], i) => (
<FormControlLabel
key={i}
value={rowValue}
control={<Radio color="primary" />}
label={rowLabel}
/>
))}
</RadioGroup>
</FormControl>
</div>
)}
control={control}
/>
</ConditionallyWrap>
)
}
Example #2
Source File: Language.tsx From back-home-safe with GNU General Public License v3.0 | 6 votes |
Language = () => {
const { t } = useTranslation("tutorial");
const { language, setLanguage } = useI18n();
return (
<Wrapper>
<h2>{t("language.name")}</h2>
<StyledRadioGroup
aria-label="language"
name="language"
value={language}
onChange={(event) => {
setLanguage(event.target.value as languageType);
}}
>
<FormControlLabel
value={languageType["ZH-HK"]}
control={<Radio />}
label="繁體中文"
/>
<FormControlLabel
value={languageType.EN}
control={<Radio />}
label="English"
/>
</StyledRadioGroup>
</Wrapper>
);
}
Example #3
Source File: TemplateDemoControls.tsx From clearflask with Apache License 2.0 | 6 votes |
render() {
return (
<RadioGroup
className={this.props.classes.extraControls}
value={this.props.value}
onChange={(e, val) => this.props.onChange(val)}
>
{Object.keys(demoOptions).map(option => (
<FormControlLabel key={option} value={option} control={<Radio color='primary' />}
label={<FormHelperText component='span'>{option}</FormHelperText>} />
))}
</RadioGroup>
);
}
Example #4
Source File: RoadmapControls.tsx From clearflask with Apache License 2.0 | 6 votes |
render() {
return (
<RadioGroup
className={this.props.classes.extraControls}
value={this.state.type}
onChange={(e, val) => {
switch (val) {
case 'development':
this.setState({ type: val });
this.props.templater.demoBoardPreset('development');
break;
case 'funding':
this.setState({ type: val });
this.props.templater.demoBoardPreset('funding');
break;
case 'design':
this.setState({ type: val });
this.props.templater.demoBoardPreset('design');
break;
}
}}
>
<FormControlLabel value='development' control={<Radio color='primary' />} label='Development' />
<FormControlLabel value='funding' control={<Radio color='primary' />} label='Custom' />
{/* <FormControlLabel value='design' control={<Radio color='primary' />} label="Design" /> */}
</RadioGroup>
);
}
Example #5
Source File: PrioritizationControlsCredits.tsx From clearflask with Apache License 2.0 | 6 votes |
render() {
return (
<RadioGroup
className={this.props.classes.extraControls}
value={this.state.fundingType}
onChange={this.handleChangeFundingType.bind(this)}
>
<FormControlLabel value='currency' control={<Radio color='primary' />}
label={<FormHelperText component='span'>Currency</FormHelperText>} />
<FormControlLabel value='time' control={<Radio color='primary' />}
label={<FormHelperText component='span'>{this.props.forContentCreator ? 'Time' : 'Development time'}</FormHelperText>} />
<FormControlLabel value={this.props.forContentCreator ? 'heart' : 'beer'} control={<Radio color='primary' />}
label={<FormHelperText component='span'>Customize</FormHelperText>} />
</RadioGroup>
);
}
Example #6
Source File: Settings.tsx From backstage with Apache License 2.0 | 6 votes |
Settings = () => {
const { type, handleChangeType } = useRandomJoke();
const JOKE_TYPES: JokeType[] = ['any' as JokeType, 'programming' as JokeType];
return (
<FormControl component="fieldset">
<FormLabel component="legend">Joke Type</FormLabel>
<RadioGroup
aria-label="joke type"
value={type}
onChange={e => handleChangeType(e.target.value)}
>
{JOKE_TYPES.map(t => (
<FormControlLabel
key={t}
value={t}
control={<Radio />}
label={upperFirst(t)}
/>
))}
</RadioGroup>
</FormControl>
);
}
Example #7
Source File: SQFormRadioButtonGroupItem.tsx From SQForm with MIT License | 6 votes |
function SQFormRadioButtonGroupItem({
value,
label,
isDisabled = false,
isRowDisplay = false,
InputProps = {},
}: SQFormRadioButtonGroupItemProps): JSX.Element {
const classes = useStyles();
return (
<FormControlLabel
className={`
${classes.radioButton}
${isRowDisplay ? classes.rowDisplay : ''}
`}
value={value}
label={label}
control={<Radio disabled={isDisabled} {...InputProps} />}
/>
);
}
Example #8
Source File: Checkbox.tsx From panvala with Apache License 2.0 | 6 votes |
Checkbox = (props: any) => {
return (
<>
<Wrapper>
<Field name={props.name} required>
{({ field, form }: any) => (
<>
<Radio
checked={field.value.includes(props.value)}
onChange={() => form.setFieldValue(props.name, props.value)}
value={props.value}
name={props.name}
aria-label="D"
classes={{
root: props.classes.root,
checked: props.classes.checked,
}}
/>
<ToggleLabel
onClick={() => form.setFieldValue(props.name, props.value)}
htmlFor={props.name}
>
{props.label}
</ToggleLabel>
</>
)}
</Field>
</Wrapper>
</>
);
}
Example #9
Source File: BooleanFacet.tsx From cognitive-search-static-web-apps-sample-ui with MIT License | 6 votes |
render(): JSX.Element {
const state = this.props.state;
return (
<FacetValuesList component="div" disablePadding>
<FacetValueListItem dense disableGutters>
<Radio edge="start" disableRipple
disabled={this.props.inProgress}
checked={!state.isApplied}
onChange={(evt) => state.value = null}
/>
<ListItemText primary="[ANY]" />
</FacetValueListItem>
<FacetValueListItem dense disableGutters>
<Radio edge="start" disableRipple
disabled={this.props.inProgress}
checked={state.value === true}
onChange={(evt) => state.value = true}
/>
<ListItemText primary={`TRUE(${state.trueCount})`} />
</FacetValueListItem>
<FacetValueListItem dense disableGutters>
<Radio edge="start" disableRipple
disabled={this.props.inProgress}
checked={state.value === false}
onChange={(evt) => state.value = false}
/>
<ListItemText primary={`FALSE(${state.falseCount})`} />
</FacetValueListItem>
</FacetValuesList>
);
}
Example #10
Source File: radio_input.tsx From jupyter-extensions with Apache License 2.0 | 6 votes |
/** Funtional Component for Radio input fields */
export function RadioInput(props: RadioInputProps) {
const { options, ...groupProps } = props;
return (
<ThemeProvider theme={theme}>
<RadioGroup {...groupProps}>
{options &&
options.map((o, i) => (
<FormControlLabel
key={i}
value={o.value}
control={<Radio />}
label={o.text}
className={css.primaryTextColor}
/>
))}
</RadioGroup>
</ThemeProvider>
);
}
Example #11
Source File: RiskCriteria.tsx From listo with MIT License | 5 votes |
RiskCriteria = ({
text,
options,
handleRiskOption,
description,
}: Props) => {
const classes = useStyles({});
if (!options) {
// TODO: this should be moved to pre-validation
return null;
}
const selectedOption = options.find(o => o.selected);
const value = selectedOption ? selectedOption.text : UNSELECTED_KEY;
const ExpansionPanelDetails = withStyles(theme => ({
root: {
padding: theme.spacing(2),
backgroundColor: '#f5f9fe',
},
}))(MuiExpansionPanelDetails);
return (
<React.Fragment>
<Paper className={classes.root}>
<Grid container spacing={2}>
<Grid item xs={12}>
<ExpansionPanel>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1a-content"
>
<Typography>{text}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Typography>{description}</Typography>
</ExpansionPanelDetails>
</ExpansionPanel>
</Grid>
<Grid item xs={12}>
<FormControl>
<RadioGroup onChange={handleRiskOption} value={value}>
{options.map(option => (
<FormControlLabel
key={option.text}
value={option.text}
control={<Radio />}
label={option.text}
/>
))}
<FormControlLabel
value={UNSELECTED_KEY}
control={<Radio />}
style={{ display: 'none' }}
label="Hidden"
/>
</RadioGroup>
</FormControl>
</Grid>
</Grid>
</Paper>
</React.Fragment>
);
}
Example #12
Source File: ShippingMethods.tsx From storefront with MIT License | 5 votes |
ShippingMethods: React.VFC<Props> = ({
availableShippingMethods,
chosenShippingMethods,
onSubmit,
}) => {
const [updateShippingMethod, { loading }] = useUpdateShippingMethodMutation({
refetchQueries: ['Cart'],
});
const handleChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
updateShippingMethod({ variables: { shippingMethods: [ev.target.value] } });
};
return (
<>
<RadioGroup
name="shippingMethod"
value={chosenShippingMethods?.[0] ?? undefined}
onChange={handleChange}
>
<Grid container spacing={2}>
{availableShippingMethods?.[0]?.rates?.map(
(rate) =>
rate != null && (
<Grid key={rate.id} item xs={12} lg={6}>
<Box
component="label"
htmlFor={`shippingMethod-${rate.id}`}
sx={{
alignItems: 'center',
backgroundColor: 'background.paper',
cursor: 'pointer',
display: 'flex',
flexDirection: 'row',
p: 2,
}}
>
<div>
<Radio value={rate.id} id={`shippingMethod-${rate.id}`} disabled={loading} />
</div>
<Box sx={{ flexGrow: 1, ml: 2 }}>
<Typography>{rate.label}</Typography>
<Price>{rate.cost}</Price>
</Box>
</Box>
</Grid>
),
)}
</Grid>
</RadioGroup>
<Box sx={{ mt: 2 }}>
<Button
type="submit"
color="primary"
disabled={chosenShippingMethods == null}
loading={loading}
onClick={onSubmit}
>
Continue to Payment Method
</Button>
</Box>
</>
);
}
Example #13
Source File: StringCollectionFacet.tsx From cognitive-search-static-web-apps-sample-ui with MIT License | 5 votes |
render(): JSX.Element {
const state = this.props.state;
return (<FacetValuesList component="div" disablePadding>
<FacetValueListItem key={state.fieldName} dense disableGutters>
<Radio edge="start" disableRipple
disabled={this.props.inProgress}
checked={state.allSelected}
onChange={(evt) => state.allSelected = evt.target.checked}
/>
<ListItemText primary="[ALL]" />
</FacetValueListItem>
<FacetValueListItem key={state.fieldName + "-or-and"} dense disableGutters>
<Radio edge="start" disableRipple
disabled={this.props.inProgress || state.allSelected}
checked={!state.allSelected && !state.useAndOperator}
onChange={(evt) => state.useAndOperator = false}
/>
<ListItemText primary="[ANY OF]" />
<Radio edge="start" disableRipple
disabled={this.props.inProgress || state.allSelected}
checked={!state.allSelected && state.useAndOperator}
onChange={(evt) => state.useAndOperator = true}
/>
<ListItemText primary="[ALL OF]" />
</FacetValueListItem>
{state.values.map(facetValue => {
return (
<FacetValueListItem key={facetValue.value} dense disableGutters>
<Checkbox edge="start" disableRipple
disabled={this.props.inProgress}
checked={facetValue.isSelected}
onChange={(evt) => facetValue.isSelected = evt.target.checked}
/>
<ListItemText primary={`${facetValue.value} (${facetValue.count})`} />
</FacetValueListItem>
);
})}
</FacetValuesList>);
}
Example #14
Source File: AlertSnoozeForm.tsx From backstage with Apache License 2.0 | 5 votes |
AlertSnoozeForm = forwardRef<
HTMLFormElement,
AlertSnoozeFormProps
>(({ onSubmit, disableSubmit }, ref) => {
const classes = useStyles();
const [duration, setDuration] = useState<Maybe<Duration>>(Duration.P7D);
useEffect(() => disableSubmit(false), [disableSubmit]);
const onFormSubmit: FormEventHandler = e => {
e.preventDefault();
if (duration) {
const repeatInterval = 1;
const today = DateTime.now().toFormat(DEFAULT_DATE_FORMAT);
onSubmit({
intervals: intervalsOf(duration, today, repeatInterval),
});
}
};
const onSnoozeDurationChange = (
_: ChangeEvent<HTMLInputElement>,
value: string,
) => {
setDuration(value as Duration);
};
return (
<form ref={ref} onSubmit={onFormSubmit}>
<FormControl component="fieldset" fullWidth>
<Typography color="textPrimary">
<b>For how long?</b>
</Typography>
<Box mb={1}>
<RadioGroup
name="snooze-alert-options"
value={duration}
onChange={onSnoozeDurationChange}
>
{AlertSnoozeOptions.map(option => (
<FormControlLabel
key={`snooze-alert-option-${option.duration}`}
label={option.label}
value={option.duration}
control={<Radio className={classes.radio} />}
/>
))}
</RadioGroup>
</Box>
</FormControl>
</form>
);
})
Example #15
Source File: VersioningStrategy.tsx From backstage with Apache License 2.0 | 5 votes |
export function VersioningStrategy() {
const navigate = useNavigate();
const { project } = useProjectContext();
const { getParsedQuery, getQueryParamsWithUpdates } = useQueryHandler();
useEffect(() => {
const { parsedQuery } = getParsedQuery();
if (!parsedQuery.versioningStrategy && !project.isProvidedViaProps) {
const { queryParams } = getQueryParamsWithUpdates({
updates: [
{ key: 'versioningStrategy', value: project.versioningStrategy },
],
});
navigate(`?${queryParams}`, { replace: true });
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<FormControl
component="fieldset"
required
disabled={project.isProvidedViaProps}
>
<FormLabel component="legend">Versioning strategy</FormLabel>
<RadioGroup
data-testid={TEST_IDS.form.versioningStrategy.radioGroup}
aria-label="calendar-strategy"
name="calendar-strategy"
value={project.versioningStrategy}
onChange={event => {
const { queryParams } = getQueryParamsWithUpdates({
updates: [{ key: 'versioningStrategy', value: event.target.value }],
});
navigate(`?${queryParams}`, { replace: true });
}}
>
<FormControlLabel
value={VERSIONING_STRATEGIES.semver}
control={<Radio />}
label="Semantic versioning"
/>
<FormControlLabel
value={VERSIONING_STRATEGIES.calver}
control={<Radio />}
label="Calendar versioning"
/>
</RadioGroup>
</FormControl>
);
}
Example #16
Source File: ListReplyTemplate.tsx From glific-frontend with GNU Affero General Public License v3.0 | 5 votes |
ChatTemplate: React.SFC<TemplateProps> = (props) => {
const [showDialog, setShowDialog] = useState(false);
const [checkedItem, setCheckedItem] = useState<any>(null);
const { title, body, globalButtonTitle, items } = props;
let dialog;
if (showDialog) {
const list = items.map((item: any) => {
const { options, title: listItemTitle } = item;
return (
<div className={styles.ListItemContainer} key={listItemTitle}>
<div className={styles.ListItemTitle}>{listItemTitle}</div>
{options.map((option: any) => (
<Button
key={option.title}
className={styles.ListItemChat}
onClick={() => setCheckedItem(option.title)}
>
<div>
<div>{option.title}</div>
<div>{option.description}</div>
</div>
<div>
<Radio
value={option.title}
name="radio-list-item"
size="small"
checked={option.title === checkedItem}
color="primary"
/>
</div>
</Button>
))}
</div>
);
});
dialog = (
<DialogBox
title={globalButtonTitle}
titleAlign="left"
handleOk={() => setShowDialog(false)}
buttonOk="Done"
skipCancel
alwaysOntop
>
<div className={styles.DialogContent}> {list}</div>
</DialogBox>
);
}
return (
<div>
<div className={styles.ChatTemplateBody}>
<p>{title}</p>
<p>{body}</p>
</div>
<div className={styles.ChatTemplateButton}>
<Button
variant="contained"
color="default"
startIcon={<MenuIcon />}
onClick={() => setShowDialog(true)}
className={styles.GlobalButton}
>
{globalButtonTitle}
</Button>
</div>
{dialog}
</div>
);
}
Example #17
Source File: RadioInput.tsx From glific-frontend with GNU Affero General Public License v3.0 | 5 votes |
RadioInput: React.SFC<RadioInputProps> = ({
labelYes = 'Yes',
labelNo = 'No',
row = true,
field,
form: { touched, errors, setFieldValue, values },
radioTitle,
handleChange,
}) => {
const selectedValue = values[field.name];
const isChecked = (value: any) => selectedValue === value;
const handleRadioChange = (value: boolean) => {
setFieldValue(field.name, value);
if (handleChange) {
handleChange(value);
}
};
let radioGroupLabel: any;
if (radioTitle) {
radioGroupLabel = <FormLabel component="legend">{radioTitle}</FormLabel>;
}
return (
<FormControl component="fieldset">
{radioGroupLabel}
<RadioGroup row={row} name="radio-buttons">
<FormControlLabel
value={1}
control={
<Radio
color="primary"
onClick={() => handleRadioChange(true)}
checked={isChecked(true)}
/>
}
label={labelYes}
className={styles.Label}
/>
<FormControlLabel
value={0}
control={
<Radio
color="primary"
onClick={() => handleRadioChange(false)}
checked={isChecked(false)}
/>
}
label={labelNo}
className={styles.Label}
/>
</RadioGroup>
{errors[field.name] && touched[field.name] ? (
<FormHelperText className={styles.DangerText}>{errors[field.name]}</FormHelperText>
) : null}
</FormControl>
);
}
Example #18
Source File: FilterControls.tsx From clearflask with Apache License 2.0 | 5 votes |
FilterControlSelect = (props: {
type: 'radio' | 'check';
name?: string;
labels: Array<Label>;
selected?: Set<String> | string;
onToggle: (value: string) => void;
}) => {
const classes = useStyles();
const Control = props.type === 'check' ? Checkbox : Radio;
return (
<div
key={`group-${props.name || 'noname'}-${props.type}`}
className={classes.group}
>
<FilterControlTitle name={props.name} />
{props.labels.map(label => (
<FormControlLabel
key={`label-${label.label}-${label.value}`}
style={{ color: label.color }}
label={(
<Typography
variant='body2'
component='div'
className={classes.label}>
{label.label}
</Typography>
)}
control={(
<Control
size='small'
color='primary'
checked={typeof props.selected === 'string'
? label.value === props.selected
: !!props.selected?.has(label.value)}
onChange={e => props.onToggle(label.value)}
/>
)}
/>
))}
</div>
);
}
Example #19
Source File: PricingPlan.tsx From clearflask with Apache License 2.0 | 5 votes |
render() {
return (
<Card elevation={0} className={classNames(this.props.className, this.props.classes.box, this.props.selected && this.props.classes.boxSelected)}>
<CardHeader
title={(
<div className={this.props.classes.title}>
{this.props.plan.title}
{this.props.plan.beta && (
<div className={this.props.classes.beta}>
EARLY<br />ACCESS
</div>
)}
</div>
)}
titleTypographyProps={{ align: 'center' }}
/>
<CardContent>
{this.renderPriceTag()}
{(this.props.overridePerks || this.props.plan.perks).map(perk => (
<div key={perk.desc} style={{ display: 'flex', alignItems: 'baseline' }}>
<CheckIcon fontSize='inherit' />
<Typography variant='subtitle1'>
{perk.desc}
{!!perk.terms && (<>
<HelpPopper description={perk.terms} />
</>)}
</Typography>
</div>
))}
</CardContent>
{
!!this.props.actionTitle && (
<CardActions className={this.props.classes.actions}>
{this.props.actionType === 'radio' ? (
<FormControlLabel
label={this.props.actionTitle}
control={(
<Radio
checked={this.props.selected}
color='primary'
onChange={e => this.props.actionOnClick && this.props.actionOnClick()}
disabled={!this.props.actionOnClick}
/>
)}
/>
) : (
<Button
color='primary'
variant='contained'
disableElevation
style={{ fontWeight: 900 }}
onClick={this.props.actionOnClick}
disabled={!this.props.actionOnClick}
{...(this.props.actionTo ? {
component: Link,
to: this.props.actionTo,
} : {})}
{...(this.props.actionToExt ? {
component: 'a',
href: this.props.actionToExt,
} : {})}
>
{this.props.actionTitle}
</Button>
)}
</CardActions>
)
}
{this.props.remark && (
<div className={this.props.classes.remark}>
<Typography variant='caption' component='div' color='textSecondary'>{this.props.remark}</Typography>
</div>
)}
</Card >
);
}
Example #20
Source File: Users.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
Users: React.FC = () => {
const { apiGet, apiPost, apiDelete } = useAuthContext();
const [showModal, setShowModal] = useState<Boolean>(false);
const [selectedRow, setSelectedRow] = useState<number>(0);
const [users, setUsers] = useState<User[]>([]);
const columns: Column<User>[] = [
{
Header: 'Name',
accessor: 'fullName',
width: 200,
disableFilters: true,
id: 'name'
},
{
Header: 'Email',
accessor: 'email',
width: 150,
minWidth: 150,
id: 'email',
disableFilters: true
},
{
Header: 'Organizations',
accessor: ({ roles }) =>
roles &&
roles
.filter((role) => role.approved)
.map((role) => role.organization.name)
.join(', '),
id: 'organizations',
width: 200,
disableFilters: true
},
{
Header: 'User type',
accessor: ({ userType }) =>
userType === 'standard'
? 'Standard'
: userType === 'globalView'
? 'Global View'
: 'Global Admin',
width: 50,
minWidth: 50,
id: 'userType',
disableFilters: true
},
{
Header: 'Date ToU Signed',
accessor: ({ dateAcceptedTerms }) =>
dateAcceptedTerms
? `${formatDistanceToNow(parseISO(dateAcceptedTerms))} ago`
: 'None',
width: 50,
minWidth: 50,
id: 'dateAcceptedTerms',
disableFilters: true
},
{
Header: 'ToU Version',
accessor: 'acceptedTermsVersion',
width: 50,
minWidth: 50,
id: 'acceptedTermsVersion',
disableFilters: true
},
{
Header: 'Last Logged In',
accessor: ({ lastLoggedIn }) =>
lastLoggedIn
? `${formatDistanceToNow(parseISO(lastLoggedIn))} ago`
: 'None',
width: 50,
minWidth: 50,
id: 'lastLoggedIn',
disableFilters: true
},
{
Header: 'Delete',
id: 'delete',
Cell: ({ row }: { row: { index: number } }) => (
<span
onClick={() => {
setShowModal(true);
setSelectedRow(row.index);
}}
>
<FaTimes />
</span>
),
disableFilters: true
}
];
const [errors, setErrors] = useState<Errors>({});
const [values, setValues] = useState<{
firstName: string;
lastName: string;
email: string;
organization?: Organization;
userType: string;
}>({
firstName: '',
lastName: '',
email: '',
userType: ''
});
const fetchUsers = useCallback(async () => {
try {
const rows = await apiGet<User[]>('/users/');
setUsers(rows);
} catch (e) {
console.error(e);
}
}, [apiGet]);
const deleteRow = async (index: number) => {
try {
const row = users[index];
await apiDelete(`/users/${row.id}`, { body: {} });
setUsers(users.filter((user) => user.id !== row.id));
} catch (e) {
setErrors({
global:
e.status === 422 ? 'Unable to delete user' : e.message ?? e.toString()
});
console.log(e);
}
};
const onSubmit: React.FormEventHandler = async (e) => {
e.preventDefault();
try {
const body = {
firstName: values.firstName,
lastName: values.lastName,
email: values.email,
userType: values.userType
};
const user = await apiPost('/users/', {
body
});
setUsers(users.concat(user));
} catch (e) {
setErrors({
global:
e.status === 422
? 'Error when submitting user entry.'
: e.message ?? e.toString()
});
console.log(e);
}
};
const onTextChange: React.ChangeEventHandler<
HTMLInputElement | HTMLSelectElement
> = (e) => onChange(e.target.name, e.target.value);
const onChange = (name: string, value: any) => {
setValues((values) => ({
...values,
[name]: value
}));
};
React.useEffect(() => {
document.addEventListener('keyup', (e) => {
//Escape
if (e.keyCode === 27) {
setShowModal(false);
}
});
}, [apiGet]);
return (
<div className={classes.root}>
<h1>Users</h1>
<Table<User> columns={columns} data={users} fetchData={fetchUsers} />
<h2>Invite a user</h2>
<form onSubmit={onSubmit} className={classes.form}>
{errors.global && <p className={classes.error}>{errors.global}</p>}
<Label htmlFor="firstName">First Name</Label>
<TextInput
required
id="firstName"
name="firstName"
className={classes.textField}
type="text"
value={values.firstName}
onChange={onTextChange}
/>
<Label htmlFor="lastName">Last Name</Label>
<TextInput
required
id="lastName"
name="lastName"
className={classes.textField}
type="text"
value={values.lastName}
onChange={onTextChange}
/>
<Label htmlFor="email">Email</Label>
<TextInput
required
id="email"
name="email"
className={classes.textField}
type="text"
value={values.email}
onChange={onTextChange}
/>
<Label htmlFor="userType">User Type</Label>
<RadioGroup
aria-label="User Type"
name="userType"
value={values.userType}
onChange={onTextChange}
>
<FormControlLabel
value="standard"
control={<Radio color="primary" />}
label="Standard"
/>
<FormControlLabel
value="globalView"
control={<Radio color="primary" />}
label="Global View"
/>
<FormControlLabel
value="globalAdmin"
control={<Radio color="primary" />}
label="Global Administrator"
/>
</RadioGroup>
<br></br>
<Button type="submit">Invite User</Button>
</form>
<ImportExport<
| User
| {
roles: string;
}
>
name="users"
fieldsToExport={['firstName', 'lastName', 'email', 'roles', 'userType']}
onImport={async (results) => {
// TODO: use a batch call here instead.
const createdUsers = [];
for (const result of results) {
const parsedRoles: {
organization: string;
role: string;
}[] = JSON.parse(result.roles as string);
const body: any = result;
// For now, just create role with the first organization
if (parsedRoles.length > 0) {
body.organization = parsedRoles[0].organization;
body.organizationAdmin = parsedRoles[0].role === 'admin';
}
try {
createdUsers.push(
await apiPost('/users/', {
body
})
);
} catch (e) {
// Just continue when an error occurs
console.error(e);
}
}
setUsers(users.concat(...createdUsers));
}}
getDataToExport={() =>
users.map((user) => ({
...user,
roles: JSON.stringify(
user.roles.map((role) => ({
organization: role.organization.id,
role: role.role
}))
)
}))
}
/>
{showModal && (
<div>
<Overlay />
<ModalContainer>
<Modal
actions={
<>
<Button
outline
type="button"
onClick={() => {
setShowModal(false);
}}
>
Cancel
</Button>
<Button
type="button"
onClick={() => {
deleteRow(selectedRow);
setShowModal(false);
}}
>
Delete
</Button>
</>
}
title={<h2>Delete user?</h2>}
>
<p>
Are you sure you would like to delete{' '}
<code>{users[selectedRow].fullName}</code>?
</p>
</Modal>
</ModalContainer>
</div>
)}
</div>
);
}
Example #21
Source File: ListReplyTemplate.tsx From glific-frontend with GNU Affero General Public License v3.0 | 4 votes |
ListReplyTemplateDrawer: React.SFC<ListTemplate> = (props) => {
const { items, drawerTitle, onItemClick, onDrawerClose, disableSend = false } = props;
const [checkedItem, setCheckedItem] = useState<any>(null);
const handleItemClick = () => {
onItemClick(checkedItem);
};
const list =
items.items &&
items.items.map((item: any, index: number) => {
const { options, title: sectionTitle } = item;
if (!sectionTitle) {
return null;
}
return (
<div key={uuidv4()}>
<div className={styles.SectionTitle}>{sectionTitle}</div>
<div className={styles.Options}>
{options
.map((option: any) => {
const payloadObject = {
payload: {
type: 'list_reply',
title: option.title,
id: '',
reply: `${option.title} ${index + 1} `,
postbackText: '',
description: option.description,
},
context: {
id: '',
gsId: items.bspMessageId,
},
};
if (option.title) {
return (
<Button
key={uuidv4()}
className={styles.ListItem}
onClick={() => setCheckedItem(payloadObject)}
>
<div>
<div>{option.title}</div>
<div>{option.description}</div>
</div>
<div>
<Radio
value={option.title}
name="radio-list-item"
size="small"
checked={checkedItem && option.title === checkedItem.payload.title}
color="primary"
/>
</div>
</Button>
);
}
return null;
})
.filter((a: any) => a)}
</div>
</div>
);
});
return (
<div className={styles.Drawer}>
<div className={styles.DrawerHeading}>
<h3>{drawerTitle}</h3>
<ClearIcon onClick={onDrawerClose} className={styles.DrawerCloseIcon} />
</div>
<div className={styles.List}>{list}</div>
<div className={styles.SendButton}>
<Button
variant="contained"
color="primary"
disabled={!checkedItem || disableSend}
onClick={handleItemClick}
>
Send
</Button>
</div>
</div>
);
}
Example #22
Source File: InteractiveOptions.tsx From glific-frontend with GNU Affero General Public License v3.0 | 4 votes |
InteractiveOptions: React.SFC<InteractiveOptionsProps> = ({
isAddButtonChecked,
templateType,
inputFields,
form,
onAddClick,
onRemoveClick,
onTemplateTypeChange,
onInputChange,
onGlobalButtonInputChange,
onListItemAddClick,
onListItemRemoveClick,
disabled = false,
translation,
disabledType,
}) => {
const { values, errors, touched, setFieldValue } = form;
const handleAddClick = (helper: any, type: string) => {
const obj = type === LIST ? { title: '', options: [] } : { value: '' };
helper.push(obj);
onAddClick(true, type);
};
const handleRemoveClick = (helper: any, idx: number) => {
helper.remove(idx);
onRemoveClick(idx);
};
const getButtons = (index: number, arrayHelpers: any) => {
let template: any = null;
if (templateType === LIST) {
template = (
<ListReplyTemplate
translation={translation && translation.items[index]}
key={index}
index={index}
inputFields={inputFields}
form={form}
onListAddClick={() => handleAddClick(arrayHelpers, LIST)}
onListRemoveClick={() => handleRemoveClick(arrayHelpers, index)}
onListItemAddClick={(options: Array<any>) => onListItemAddClick(index, options)}
onListItemRemoveClick={(itemIndex: number) => onListItemRemoveClick(index, itemIndex)}
onInputChange={(value: string, payload: any) =>
onInputChange(LIST, index, value, payload, setFieldValue)
}
/>
);
}
if (templateType === QUICK_REPLY) {
template = (
<QuickReplyTemplate
translation={translation && translation[index]}
key={index}
index={index}
inputFields={inputFields}
form={form}
onInputChange={(value: string, payload: any) =>
onInputChange(QUICK_REPLY, index, value, payload, setFieldValue)
}
onAddClick={() => handleAddClick(arrayHelpers, QUICK_REPLY)}
onRemoveClick={() => handleRemoveClick(arrayHelpers, index)}
/>
);
}
return template;
};
const radioTemplateType = (
<div>
<RadioGroup
aria-label="template-type"
name="template-type"
row
value={templateType}
onChange={(event) => onTemplateTypeChange(event.target.value)}
>
<div className={styles.RadioLabelWrapper}>
<FormControlLabel
value={QUICK_REPLY}
control={
<Radio
disabled={disabledType}
color="primary"
checkedIcon={<ApprovedIcon className={styles.CheckedIcon} />}
size="small"
/>
}
className={templateType === QUICK_REPLY ? styles.SelectedLabel : ''}
classes={{ root: styles.RadioLabel }}
label="Reply buttons"
/>
</div>
<div className={styles.RadioLabelWrapper}>
<FormControlLabel
value={LIST}
control={
<Radio
disabled={disabledType}
color="primary"
checkedIcon={<ApprovedIcon className={styles.CheckedIcon} />}
size="small"
/>
}
className={templateType === LIST ? styles.SelectedLabel : ''}
classes={{ root: styles.RadioLabel }}
label="List message"
/>
</div>
</RadioGroup>
{templateType && templateType === LIST && (
<div className={styles.GlobalButton}>
{translation && <div className={styles.Translation}>{translation.globalButton}</div>}
<FormControl
fullWidth
error={!!(errors.globalButton && touched.globalButton)}
className={styles.FormControl}
>
<TextField
placeholder="List header"
variant="outlined"
label="List header*"
className={styles.TextField}
onChange={(e: any) => {
setFieldValue('globalButton', e.target.value);
onGlobalButtonInputChange(e.target.value);
}}
value={values.globalButton}
error={!!errors.globalButton && touched.globalButton}
/>
{errors.globalButton && touched.globalButton && (
<FormHelperText>{errors.globalButton}</FormHelperText>
)}
</FormControl>
</div>
)}
{templateType && (
<div className={templateType === QUICK_REPLY ? styles.TemplateFields : ''}>
<FieldArray
name="templateButtons"
render={(arrayHelpers) =>
values.templateButtons.map((row: any, index: any) => getButtons(index, arrayHelpers))
}
/>
</div>
)}
</div>
);
return <div>{isAddButtonChecked && !disabled && radioTemplateType}</div>;
}
Example #23
Source File: TemplateOptions.tsx From glific-frontend with GNU Affero General Public License v3.0 | 4 votes |
TemplateOptions: React.SFC<TemplateOptionsProps> = ({
isAddButtonChecked,
templateType,
inputFields,
form: { touched, errors, values },
onAddClick,
onRemoveClick,
onTemplateTypeChange,
onInputChange,
disabled = false,
}) => {
const buttonTitle = 'Button Title';
const buttonValue = 'Button Value';
const buttonTitles: any = {
CALL_TO_ACTION: 'Call to action',
QUICK_REPLY: 'Quick Reply',
};
const handleAddClick = (helper: any, type: boolean) => {
const obj = type ? { type: '', value: '', title: '' } : { value: '' };
helper.push(obj);
onAddClick();
};
const handleRemoveClick = (helper: any, idx: number) => {
helper.remove(idx);
onRemoveClick(idx);
};
const addButton = (helper: any, type: boolean = false) => {
const title = templateType ? buttonTitles[templateType] : '';
const buttonClass =
templateType === QUICK_REPLY ? styles.QuickReplyAddButton : styles.CallToActionAddButton;
return (
<Button
className={buttonClass}
variant="outlined"
color="primary"
onClick={() => handleAddClick(helper, type)}
>
Add {title}
</Button>
);
};
const getButtons = (row: any, index: number, arrayHelpers: any) => {
const { type, title, value }: any = row;
let template: any = null;
const isError = (key: string) =>
!!(
errors.templateButtons &&
touched.templateButtons &&
errors.templateButtons[index] &&
errors.templateButtons[index][key]
);
if (templateType === CALL_TO_ACTION) {
template = (
<div className={styles.CallToActionContainer} key={index.toString()}>
<div className={styles.CallToActionWrapper}>
<div>
<div className={styles.RadioStyles}>
<FormControl fullWidth error={isError('type')} className={styles.FormControl}>
<RadioGroup
aria-label="action-radio-buttons"
name="action-radio-buttons"
row
value={type}
onChange={(e: any) => onInputChange(e, row, index, 'type')}
className={styles.RadioGroup}
>
<FormControlLabel
value="phone_number"
control={
<Radio
color="primary"
disabled={
disabled ||
(index === 0 &&
inputFields.length > 1 &&
inputFields[0].type !== 'phone_number') ||
(index > 0 &&
inputFields[0].type &&
inputFields[0].type === 'phone_number')
}
/>
}
label="Phone number"
/>
<FormControlLabel
value="url"
control={
<Radio
color="primary"
disabled={
disabled ||
(index === 0 &&
inputFields.length > 1 &&
inputFields[0].type !== 'url') ||
(index > 0 && inputFields[0].type && inputFields[0].type === 'url')
}
/>
}
label="URL"
/>
</RadioGroup>
{errors.templateButtons &&
touched.templateButtons &&
touched.templateButtons[index] ? (
<FormHelperText>{errors.templateButtons[index]?.type}</FormHelperText>
) : null}
</FormControl>
</div>
<div>
{inputFields.length > 1 ? (
<DeleteIcon onClick={() => handleRemoveClick(arrayHelpers, index)} />
) : null}
</div>
</div>
<div className={styles.TextFieldWrapper}>
<FormControl fullWidth error={isError('title')} className={styles.FormControl}>
<TextField
disabled={disabled}
title={title}
defaultValue={value}
placeholder={buttonTitle}
variant="outlined"
label={buttonTitle}
onBlur={(e: any) => onInputChange(e, row, index, 'title')}
className={styles.TextField}
error={isError('title')}
/>
{errors.templateButtons &&
touched.templateButtons &&
touched.templateButtons[index] ? (
<FormHelperText>{errors.templateButtons[index]?.title}</FormHelperText>
) : null}
</FormControl>
</div>
<div className={styles.TextFieldWrapper}>
<FormControl fullWidth error={isError('value')} className={styles.FormControl}>
<TextField
title={value}
defaultValue={value}
disabled={disabled}
placeholder={buttonValue}
variant="outlined"
label={buttonValue}
onBlur={(e: any) => onInputChange(e, row, index, 'value')}
className={styles.TextField}
error={isError('value')}
/>
{errors.templateButtons &&
touched.templateButtons &&
touched.templateButtons[index] ? (
<FormHelperText>{errors.templateButtons[index]?.value}</FormHelperText>
) : null}
</FormControl>
</div>
</div>
<div>
{inputFields.length === index + 1 && inputFields.length !== 2
? addButton(arrayHelpers, true)
: null}
</div>
</div>
);
}
if (templateType === QUICK_REPLY) {
template = (
<div className={styles.QuickReplyContainer} key={index.toString()}>
<div className={styles.QuickReplyWrapper}>
<FormControl fullWidth error={isError('value')} className={styles.FormControl}>
<TextField
disabled={disabled}
defaultValue={value}
title={title}
placeholder={`Quick reply ${index + 1} title`}
label={`Quick reply ${index + 1} title`}
variant="outlined"
onBlur={(e: any) => onInputChange(e, row, index, 'value')}
className={styles.TextField}
error={isError('value')}
InputProps={{
endAdornment: inputFields.length > 1 && !disabled && (
<CrossIcon
className={styles.RemoveIcon}
title="Remove"
onClick={() => handleRemoveClick(arrayHelpers, index)}
/>
),
}}
/>
{errors.templateButtons &&
touched.templateButtons &&
touched.templateButtons[index] ? (
<FormHelperText>{errors.templateButtons[index]?.value}</FormHelperText>
) : null}
</FormControl>
</div>
<div>
{inputFields.length === index + 1 && inputFields.length !== 3
? addButton(arrayHelpers)
: null}
</div>
</div>
);
}
return template;
};
const radioTemplateType = (
<div>
<RadioGroup
aria-label="template-type"
name="template-type"
row
value={templateType}
onChange={(event) => onTemplateTypeChange(event.target.value)}
>
<div className={styles.RadioLabelWrapper}>
<FormControlLabel
value={CALL_TO_ACTION}
control={<Radio color="primary" disabled={disabled} />}
label="Call to actions"
classes={{ root: styles.RadioLabel }}
/>
<Tooltip title={GUPSHUP_CALL_TO_ACTION} placement="right" tooltipClass={styles.Tooltip}>
<InfoIcon />
</Tooltip>
</div>
<div className={styles.RadioLabelWrapper}>
<FormControlLabel
value={QUICK_REPLY}
control={<Radio color="primary" disabled={disabled} />}
label="Quick replies"
className={styles.RadioLabel}
/>
<Tooltip title={GUPSHUP_QUICK_REPLY} placement="right" tooltipClass={styles.Tooltip}>
<InfoIcon />
</Tooltip>
</div>
</RadioGroup>
{templateType ? (
<div
className={
templateType === QUICK_REPLY
? styles.QuickTemplateFields
: styles.CallToActionTemplateFields
}
>
<FieldArray
name="templateButtons"
render={(arrayHelpers) =>
values.templateButtons.map((row: any, index: any) =>
getButtons(row, index, arrayHelpers)
)
}
/>
</div>
) : null}
</div>
);
return <div>{isAddButtonChecked && radioTemplateType}</div>;
}
Example #24
Source File: CreateGame.tsx From planning-poker with MIT License | 4 votes |
CreateGame = () => {
const history = useHistory();
const [gameName, setGameName] = useState('Avengers');
const [createdBy, setCreatedBy] = useState('SuperHero');
const [gameType, setGameType] = useState(GameType.Fibonacci);
const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
const game: NewGame = {
name: gameName,
createdBy: createdBy,
gameType: gameType,
createdAt: new Date(),
};
const newGameId = await addNewGame(game);
history.push(`/game/${newGameId}`);
};
return (
<Grow in={true} timeout={1000}>
<form onSubmit={handleSubmit}>
<Card variant='outlined' className='CreateGameCard'>
<CardHeader
className='CreateGameCardHeader'
title='Create New Session'
titleTypographyProps={{ variant: 'h4' }}
/>
<CardContent className='CreateGameCardContent'>
<TextField
className='CreateGameTextField'
required
id='filled-required'
label='Session Name'
placeholder='Enter a session name'
defaultValue={gameName}
variant='outlined'
onChange={(event: ChangeEvent<HTMLInputElement>) => setGameName(event.target.value)}
/>
<TextField
className='CreateGameTextField'
required
id='filled-required'
label='Your Name'
placeholder='Enter your name'
defaultValue={createdBy}
variant='outlined'
onChange={(event: ChangeEvent<HTMLInputElement>) => setCreatedBy(event.target.value)}
/>
<RadioGroup
aria-label='gender'
name='gender1'
value={gameType}
onChange={(
event: ChangeEvent<{
name?: string | undefined;
value: any;
}>
) => setGameType(event.target.value)}
>
<FormControlLabel
value={GameType.Fibonacci}
control={<Radio color='primary' size='small' />}
label='Fibonacci (0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89)'
/>
<FormControlLabel
value={GameType.ShortFibonacci}
control={<Radio color='primary' size='small' />}
label='Short Fibonacci (0, ½, 1, 2, 3, 5, 8, 13, 20, 40, 100)'
/>
<FormControlLabel
value={GameType.TShirt}
control={<Radio color='primary' size='small' />}
label='T-Shirt (XXS, XS, S, M, L, XL, XXL)'
/>
</RadioGroup>
</CardContent>
<CardActions className='CreateGameCardAction'>
<Button type='submit' variant='contained' color='primary' className='CreateGameButton'>
Create
</Button>
</CardActions>
</Card>
</form>
</Grow>
);
}
Example #25
Source File: Settings.tsx From back-home-safe with GNU General Public License v3.0 | 4 votes |
Settings = () => {
const { t } = useTranslation("main_screen");
const { hasCameraSupport } = useCamera();
const { autoRemoveRecordDay, setAutoRemoveRecordDay } = useTravelRecord();
const { incognito, setIncognito, value } = useData();
const [languageOpen, setLanguageOpen] = useState(false);
const { language, setLanguage } = useI18n();
const handleLanguageClick = () => {
setLanguageOpen(!languageOpen);
};
const handleExportData = () => {
const dataStr =
"data:text/json;charset=utf-8," +
encodeURIComponent(JSON.stringify(value));
const downloadAnchorNode = document.createElement("a");
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", "export.json");
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
};
return (
<PageWrapper>
<Header name={t("setting.name")} />
<ContentWrapper>
<StyledList
subheader={
<ListSubheader>{t("setting.section.common")}</ListSubheader>
}
>
{hasCameraSupport ? (
<StyledLink to="/cameraSetting">
<ListItem button>
<ListItemText primary={t("setting.item.camera_setting")} />
</ListItem>
</StyledLink>
) : (
<ListItem button disabled>
<ListItemText primary={t("setting.item.camera_setting")} />
</ListItem>
)}
<StyledLink to="/confirmPageSetting">
<ListItem button>
<ListItemText primary={t("setting.item.confirm_page_setting")} />
</ListItem>
</StyledLink>
<ListItem>
<ListItemText primary={t("setting.item.auto_delete_record")} />
<ListItemSecondaryAction>
<Select
labelId="cameraId"
id="demo-simple-select"
value={autoRemoveRecordDay}
onChange={(e) => {
setAutoRemoveRecordDay(e.target.value as number);
}}
>
{range(1, 100).map((day) => (
<MenuItem value={day} key={day}>
{day}{" "}
{day === 1 ? t("setting.form.day") : t("setting.form.days")}
</MenuItem>
))}
</Select>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemText
primary={t("setting.item.incognito_mode.name")}
secondary={t("setting.item.incognito_mode.explanation")}
/>
<ListItemSecondaryAction>
<Switch
checked={incognito}
onChange={(e) => {
setIncognito(e.target.checked);
}}
color="primary"
/>
</ListItemSecondaryAction>
</ListItem>
<ListItem button onClick={handleLanguageClick}>
<ListItemText primary={t("setting.item.language")} />
{languageOpen ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={languageOpen} timeout="auto" unmountOnExit>
<ListItem>
<RadioGroup
aria-label="language"
name="language"
value={language}
onChange={(event) => {
setLanguage(event.target.value as languageType);
}}
>
<FormControlLabel
value={languageType["ZH-HK"]}
control={<Radio />}
label="繁體中文"
/>
<FormControlLabel
value={languageType.EN}
control={<Radio />}
label="English"
/>
</RadioGroup>
</ListItem>
</Collapse>
</StyledList>
<Divider />
<StyledList
subheader={<ListSubheader>{t("setting.section.lab")}</ListSubheader>}
>
<StyledLink to="/qrGenerator">
<ListItem button>
<ListItemText primary={t("setting.item.qr_generator")} />
</ListItem>
</StyledLink>
<StyledLink to="/vaccinationQRReader">
<ListItem button>
<ListItemText primary={t("setting.item.vaccinationQRReader")} />
</ListItem>
</StyledLink>
<ListItem onClick={handleExportData}>
<ListItemText primary={t("setting.item.export_data")} />
</ListItem>
<ListItem button>
<ListItemText
primary={t("setting.item.reset")}
onClick={clearAllData}
/>
</ListItem>
</StyledList>
<Divider />
<StyledList
subheader={
<ListSubheader>
{t("setting.section.version")}: {__APP_VERSION__}
</ListSubheader>
}
>
<StyledExternalLink
href="https://gitlab.com/codogo-b/back-home-safe"
target="_blank"
>
<ListItem button>
<ListItemText primary={t("setting.item.about_us")} />
</ListItem>
</StyledExternalLink>
<StyledLink to="/disclaimer">
<ListItem button>
<ListItemText primary={t("setting.item.disclaimer")} />
</ListItem>
</StyledLink>
<StyledExternalLink
href="https://gitlab.com/codogo-b/back-home-safe/-/blob/master/CHANGELOG.md"
target="_blank"
>
<ListItem button>
<ListItemText primary={t("setting.item.change_log")} />
</ListItem>
</StyledExternalLink>
<StyledExternalLink
href="https://gitlab.com/codogo-b/back-home-safe/-/issues"
target="_blank"
>
<ListItem button>
<ListItemText primary={t("setting.item.report_issue")} />
</ListItem>
</StyledExternalLink>
</StyledList>
</ContentWrapper>
</PageWrapper>
);
}
Example #26
Source File: PopupContents.tsx From firetable with Apache License 2.0 | 4 votes |
// TODO: Implement infinite scroll here
export default function PopupContents({
value = [],
onChange,
config,
row,
docRef,
}: IPopupContentsProps) {
const url = config.url;
const titleKey = config.titleKey ?? config.primaryKey;
const subtitleKey = config.subtitleKey;
const resultsKey = config.resultsKey;
const primaryKey = config.primaryKey;
const multiple = Boolean(config.multiple);
const classes = useStyles();
// Webservice search query
const [query, setQuery] = useState("");
// Webservice response
const [response, setResponse] = useState<any | null>(null);
const [docData, setDocData] = useState<any | null>(null);
useEffect(() => {
docRef.get().then((d) => setDocData(d.data()));
}, []);
const hits: any["hits"] = _get(response, resultsKey) ?? [];
const [search] = useDebouncedCallback(
async (query: string) => {
if (!docData) return;
if (!url) return;
const uri = new URL(url),
params = { q: query };
Object.keys(params).forEach((key) =>
uri.searchParams.append(key, params[key])
);
const resp = await fetch(uri.toString(), {
method: "POST",
body: JSON.stringify(docData),
headers: { "content-type": "application/json" },
});
const jsonBody = await resp.json();
setResponse(jsonBody);
},
1000,
{ leading: true }
);
useEffect(() => {
search(query);
}, [query, docData]);
if (!response) return <Loading />;
const select = (hit: any) => () => {
if (multiple) onChange([...value, hit]);
else onChange([hit]);
};
const deselect = (hit: any) => () => {
if (multiple)
onChange(value.filter((v) => v[primaryKey] !== hit[primaryKey]));
else onChange([]);
};
const selectedValues = value?.map((item) => _get(item, primaryKey));
const clearSelection = () => onChange([]);
return (
<Grid container direction="column" className={classes.grid}>
<Grid item className={classes.searchRow}>
<TextField
value={query}
onChange={(e) => setQuery(e.target.value)}
fullWidth
variant="filled"
margin="dense"
label="Search items"
className={classes.noMargins}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<SearchIcon />
</InputAdornment>
),
}}
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
/>
</Grid>
<Grid item xs className={classes.listRow}>
<List className={classes.list}>
{hits.map((hit) => {
const isSelected =
selectedValues.indexOf(_get(hit, primaryKey)) !== -1;
console.log(`Selected Values: ${selectedValues}`);
return (
<React.Fragment key={_get(hit, primaryKey)}>
<MenuItem
dense
onClick={isSelected ? deselect(hit) : select(hit)}
>
<ListItemIcon className={classes.checkboxContainer}>
{multiple ? (
<Checkbox
edge="start"
checked={isSelected}
tabIndex={-1}
color="secondary"
className={classes.checkbox}
disableRipple
inputProps={{
"aria-labelledby": `label-${_get(hit, primaryKey)}`,
}}
/>
) : (
<Radio
edge="start"
checked={isSelected}
tabIndex={-1}
color="secondary"
className={classes.checkbox}
disableRipple
inputProps={{
"aria-labelledby": `label-${_get(hit, primaryKey)}`,
}}
/>
)}
</ListItemIcon>
<ListItemText
id={`label-${_get(hit, primaryKey)}`}
primary={_get(hit, titleKey)}
secondary={!subtitleKey ? "" : _get(hit, subtitleKey)}
/>
</MenuItem>
<Divider className={classes.divider} />
</React.Fragment>
);
})}
</List>
</Grid>
{multiple && (
<Grid item className={clsx(classes.footerRow, classes.selectedRow)}>
<Grid
container
direction="row"
justify="space-between"
alignItems="center"
>
<Typography
variant="button"
color="textSecondary"
className={classes.selectedNum}
>
{value?.length} of {hits?.length}
</Typography>
<Button
disabled={!value || value.length === 0}
onClick={clearSelection}
color="primary"
className={classes.selectAllButton}
>
Clear Selection
</Button>
</Grid>
</Grid>
)}
</Grid>
);
}
Example #27
Source File: Organization.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
Organization: React.FC = () => {
const {
apiGet,
apiPut,
apiPost,
user,
setFeedbackMessage
} = useAuthContext();
const { organizationId } = useParams<{ organizationId: string }>();
const [organization, setOrganization] = useState<OrganizationType>();
const [tags, setTags] = useState<AutocompleteType[]>([]);
const [userRoles, setUserRoles] = useState<Role[]>([]);
const [scanTasks, setScanTasks] = useState<ScanTask[]>([]);
const [scans, setScans] = useState<Scan[]>([]);
const [scanSchema, setScanSchema] = useState<ScanSchema>({});
const [newUserValues, setNewUserValues] = useState<{
firstName: string;
lastName: string;
email: string;
organization?: OrganizationType;
role: string;
}>({
firstName: '',
lastName: '',
email: '',
role: ''
});
const classes = useStyles();
const [tagValue, setTagValue] = React.useState<AutocompleteType | null>(null);
const [inputValue, setInputValue] = React.useState('');
const [dialog, setDialog] = React.useState<{
open: boolean;
type?: 'rootDomains' | 'ipBlocks' | 'tags';
label?: string;
}>({ open: false });
const dateAccessor = (date?: string) => {
return !date || new Date(date).getTime() === new Date(0).getTime()
? 'None'
: `${formatDistanceToNow(parseISO(date))} ago`;
};
const userRoleColumns: Column<Role>[] = [
{
Header: 'Name',
accessor: ({ user }) => user.fullName,
width: 200,
disableFilters: true,
id: 'name'
},
{
Header: 'Email',
accessor: ({ user }) => user.email,
width: 150,
minWidth: 150,
id: 'email',
disableFilters: true
},
{
Header: 'Role',
accessor: ({ approved, role, user }) => {
if (approved) {
if (user.invitePending) {
return 'Invite pending';
} else if (role === 'admin') {
return 'Administrator';
} else {
return 'Member';
}
}
return 'Pending approval';
},
width: 50,
minWidth: 50,
id: 'approved',
disableFilters: true
},
{
Header: () => {
return (
<div style={{ justifyContent: 'flex-center' }}>
<Button color="secondary" onClick={() => setDialog({ open: true })}>
<ControlPoint style={{ marginRight: '10px' }}></ControlPoint>
Add member
</Button>
</div>
);
},
id: 'action',
Cell: ({ row }: { row: { index: number } }) => {
const isApproved =
!organization?.userRoles[row.index] ||
organization?.userRoles[row.index].approved;
return (
<>
{isApproved ? (
<Button
onClick={() => {
removeUser(row.index);
}}
color="secondary"
>
<p>Remove</p>
</Button>
) : (
<Button
onClick={() => {
approveUser(row.index);
}}
color="secondary"
>
<p>Approve</p>
</Button>
)}
</>
);
},
disableFilters: true
}
];
const scanColumns: Column<Scan>[] = [
{
Header: 'Name',
accessor: 'name',
width: 150,
id: 'name',
disableFilters: true
},
{
Header: 'Description',
accessor: ({ name }) => scanSchema[name] && scanSchema[name].description,
width: 200,
minWidth: 200,
id: 'description',
disableFilters: true
},
{
Header: 'Mode',
accessor: ({ name }) =>
scanSchema[name] && scanSchema[name].isPassive ? 'Passive' : 'Active',
width: 150,
minWidth: 150,
id: 'mode',
disableFilters: true
},
{
Header: 'Action',
id: 'action',
maxWidth: 100,
Cell: ({ row }: { row: { index: number } }) => {
if (!organization) return;
const enabled = organization.granularScans.find(
(scan) => scan.id === scans[row.index].id
);
return (
<Button
type="button"
onClick={() => {
updateScan(scans[row.index], !enabled);
}}
>
{enabled ? 'Disable' : 'Enable'}
</Button>
);
},
disableFilters: true
}
];
const scanTaskColumns: Column<ScanTask>[] = [
{
Header: 'ID',
accessor: 'id',
disableFilters: true
},
{
Header: 'Status',
accessor: 'status',
disableFilters: true
},
{
Header: 'Type',
accessor: 'type',
disableFilters: true
},
{
Header: 'Name',
accessor: ({ scan }) => scan?.name,
disableFilters: true
},
{
Header: 'Created At',
accessor: ({ createdAt }) => dateAccessor(createdAt),
disableFilters: true,
disableSortBy: true
},
{
Header: 'Requested At',
accessor: ({ requestedAt }) => dateAccessor(requestedAt),
disableFilters: true,
disableSortBy: true
},
{
Header: 'Started At',
accessor: ({ startedAt }) => dateAccessor(startedAt),
disableFilters: true,
disableSortBy: true
},
{
Header: 'Finished At',
accessor: ({ finishedAt }) => dateAccessor(finishedAt),
disableFilters: true,
disableSortBy: true
},
{
Header: 'Output',
accessor: 'output',
disableFilters: true
}
];
const fetchOrganization = useCallback(async () => {
try {
const organization = await apiGet<OrganizationType>(
`/organizations/${organizationId}`
);
organization.scanTasks.sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
setOrganization(organization);
setUserRoles(organization.userRoles);
setScanTasks(organization.scanTasks);
const tags = await apiGet<OrganizationTag[]>(`/organizations/tags`);
setTags(tags);
} catch (e) {
console.error(e);
}
}, [apiGet, setOrganization, organizationId]);
const fetchScans = useCallback(async () => {
try {
const response = await apiGet<{
scans: Scan[];
schema: ScanSchema;
}>('/granularScans/');
let { scans } = response;
const { schema } = response;
if (user?.userType !== 'globalAdmin')
scans = scans.filter(
(scan) =>
scan.name !== 'censysIpv4' && scan.name !== 'censysCertificates'
);
setScans(scans);
setScanSchema(schema);
} catch (e) {
console.error(e);
}
}, [apiGet, user]);
const approveUser = async (user: number) => {
try {
await apiPost(
`/organizations/${organization?.id}/roles/${organization?.userRoles[user].id}/approve`,
{ body: {} }
);
const copy = userRoles.map((role, id) =>
id === user ? { ...role, approved: true } : role
);
setUserRoles(copy);
} catch (e) {
console.error(e);
}
};
const removeUser = async (user: number) => {
try {
await apiPost(
`/organizations/${organization?.id}/roles/${userRoles[user].id}/remove`,
{ body: {} }
);
const copy = userRoles.filter((_, ind) => ind !== user);
setUserRoles(copy);
} catch (e) {
console.error(e);
}
};
const updateOrganization = async (body: any) => {
try {
const org = await apiPut('/organizations/' + organization?.id, {
body: organization
});
setOrganization(org);
setFeedbackMessage({
message: 'Organization successfully updated',
type: 'success'
});
} catch (e) {
setFeedbackMessage({
message:
e.status === 422
? 'Error updating organization'
: e.message ?? e.toString(),
type: 'error'
});
console.error(e);
}
};
const updateScan = async (scan: Scan, enabled: boolean) => {
try {
if (!organization) return;
await apiPost(
`/organizations/${organization?.id}/granularScans/${scan.id}/update`,
{
body: {
enabled
}
}
);
setOrganization({
...organization,
granularScans: enabled
? organization.granularScans.concat([scan])
: organization.granularScans.filter(
(granularScan) => granularScan.id !== scan.id
)
});
} catch (e) {
setFeedbackMessage({
message:
e.status === 422 ? 'Error updating scan' : e.message ?? e.toString(),
type: 'error'
});
console.error(e);
}
};
useEffect(() => {
fetchOrganization();
}, [fetchOrganization]);
const onInviteUserSubmit = async () => {
try {
const body = {
firstName: newUserValues.firstName,
lastName: newUserValues.lastName,
email: newUserValues.email,
organization: organization?.id,
organizationAdmin: newUserValues.role === 'admin'
};
const user: User = await apiPost('/users/', {
body
});
const newRole = user.roles[user.roles.length - 1];
newRole.user = user;
if (userRoles.find((role) => role.user.id === user.id)) {
setUserRoles(
userRoles.map((role) => (role.user.id === user.id ? newRole : role))
);
} else {
setUserRoles(userRoles.concat([newRole]));
}
} catch (e) {
setFeedbackMessage({
message:
e.status === 422 ? 'Error inviting user' : e.message ?? e.toString(),
type: 'error'
});
console.log(e);
}
};
const onInviteUserTextChange: React.ChangeEventHandler<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
> = (e) => onInviteUserChange(e.target.name, e.target.value);
const onInviteUserChange = (name: string, value: any) => {
setNewUserValues((values) => ({
...values,
[name]: value
}));
};
const filter = createFilterOptions<AutocompleteType>();
const ListInput = (props: {
type: 'rootDomains' | 'ipBlocks' | 'tags';
label: string;
}) => {
if (!organization) return null;
const elements: (string | OrganizationTag)[] = organization[props.type];
return (
<div className={classes.headerRow}>
<label>{props.label}</label>
<span>
{elements &&
elements.map((value: string | OrganizationTag, index: number) => (
<Chip
className={classes.chip}
key={index}
label={typeof value === 'string' ? value : value.name}
onDelete={() => {
organization[props.type].splice(index, 1);
setOrganization({ ...organization });
}}
></Chip>
))}
<Chip
label="ADD"
variant="outlined"
color="secondary"
onClick={() => {
setDialog({
open: true,
type: props.type,
label: props.label
});
}}
/>
</span>
</div>
);
};
if (!organization) return null;
const views = [
<Paper className={classes.settingsWrapper} key={0}>
<Dialog
open={dialog.open}
onClose={() => setDialog({ open: false })}
aria-labelledby="form-dialog-title"
maxWidth="xs"
fullWidth
>
<DialogTitle id="form-dialog-title">
Add {dialog.label && dialog.label.slice(0, -1)}
</DialogTitle>
<DialogContent>
{dialog.type === 'tags' ? (
<>
<DialogContentText>
Select an existing tag or add a new one.
</DialogContentText>
<Autocomplete
value={tagValue}
onChange={(event, newValue) => {
if (typeof newValue === 'string') {
setTagValue({
name: newValue
});
} else {
setTagValue(newValue);
}
}}
filterOptions={(options, params) => {
const filtered = filter(options, params);
// Suggest the creation of a new value
if (
params.inputValue !== '' &&
!filtered.find(
(tag) =>
tag.name?.toLowerCase() ===
params.inputValue.toLowerCase()
)
) {
filtered.push({
name: params.inputValue,
title: `Add "${params.inputValue}"`
});
}
return filtered;
}}
selectOnFocus
clearOnBlur
handleHomeEndKeys
options={tags}
getOptionLabel={(option) => {
return option.name ?? '';
}}
renderOption={(option) => {
if (option.title) return option.title;
return option.name ?? '';
}}
fullWidth
freeSolo
renderInput={(params) => (
<TextField {...params} variant="outlined" />
)}
/>
</>
) : (
<TextField
autoFocus
margin="dense"
id="name"
label={dialog.label && dialog.label.slice(0, -1)}
type="text"
fullWidth
onChange={(e) => setInputValue(e.target.value)}
/>
)}
</DialogContent>
<DialogActions>
<Button variant="outlined" onClick={() => setDialog({ open: false })}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
if (dialog.type && dialog.type !== 'tags') {
if (inputValue) {
organization[dialog.type].push(inputValue);
setOrganization({ ...organization });
}
} else {
if (tagValue) {
if (!organization.tags) organization.tags = [];
organization.tags.push(tagValue as any);
setOrganization({ ...organization });
}
}
setDialog({ open: false });
setInputValue('');
setTagValue(null);
}}
>
Add
</Button>
</DialogActions>
</Dialog>
<TextField
value={organization.name}
disabled
variant="filled"
InputProps={{
className: classes.orgName
}}
></TextField>
<ListInput label="Root Domains" type="rootDomains"></ListInput>
<ListInput label="IP Blocks" type="ipBlocks"></ListInput>
<ListInput label="Tags" type="tags"></ListInput>
<div className={classes.headerRow}>
<label>Passive Mode</label>
<span>
<SwitchInput
checked={organization.isPassive}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setOrganization({
...organization,
isPassive: event.target.checked
});
}}
color="primary"
/>
</span>
</div>
<div className={classes.buttons}>
<Link to={`/organizations`}>
<Button
variant="outlined"
style={{ marginRight: '10px', color: '#565C65' }}
>
Cancel
</Button>
</Link>
<Button
variant="contained"
onClick={updateOrganization}
style={{ background: '#565C65', color: 'white' }}
>
Save
</Button>
</div>
</Paper>,
<React.Fragment key={1}>
<Table<Role> columns={userRoleColumns} data={userRoles} />
<Dialog
open={dialog.open}
onClose={() => setDialog({ open: false })}
aria-labelledby="form-dialog-title"
maxWidth="xs"
fullWidth
>
<DialogTitle id="form-dialog-title">Add Member</DialogTitle>
<DialogContent>
<p style={{ color: '#3D4551' }}>
Organization members can view Organization-specific vulnerabilities,
domains, and notes. Organization administrators can additionally
manage members and update the organization.
</p>
<TextField
margin="dense"
id="firstName"
name="firstName"
label="First Name"
type="text"
fullWidth
value={newUserValues.firstName}
onChange={onInviteUserTextChange}
variant="filled"
InputProps={{
className: classes.textField
}}
/>
<TextField
margin="dense"
id="lastName"
name="lastName"
label="Last Name"
type="text"
fullWidth
value={newUserValues.lastName}
onChange={onInviteUserTextChange}
variant="filled"
InputProps={{
className: classes.textField
}}
/>
<TextField
margin="dense"
id="email"
name="email"
label="Email"
type="text"
fullWidth
value={newUserValues.email}
onChange={onInviteUserTextChange}
variant="filled"
InputProps={{
className: classes.textField
}}
/>
<br></br>
<br></br>
<FormLabel component="legend">Role</FormLabel>
<RadioGroup
aria-label="role"
name="role"
value={newUserValues.role}
onChange={onInviteUserTextChange}
>
<FormControlLabel
value="standard"
control={<Radio color="primary" />}
label="Standard"
/>
<FormControlLabel
value="admin"
control={<Radio color="primary" />}
label="Administrator"
/>
</RadioGroup>
</DialogContent>
<DialogActions>
<Button variant="outlined" onClick={() => setDialog({ open: false })}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
onClick={async () => {
onInviteUserSubmit();
setDialog({ open: false });
}}
>
Add
</Button>
</DialogActions>
</Dialog>
</React.Fragment>,
<React.Fragment key={2}>
<OrganizationList parent={organization}></OrganizationList>
</React.Fragment>,
<React.Fragment key={3}>
<Table<Scan> columns={scanColumns} data={scans} fetchData={fetchScans} />
<h2>Organization Scan History</h2>
<Table<ScanTask> columns={scanTaskColumns} data={scanTasks} />
</React.Fragment>
];
let navItems = [
{
title: 'Settings',
path: `/organizations/${organizationId}`,
exact: true
},
{
title: 'Members',
path: `/organizations/${organizationId}/members`
}
];
if (!organization.parent) {
navItems = navItems.concat([
// { title: 'Teams', path: `/organizations/${organizationId}/teams` },
{ title: 'Scans', path: `/organizations/${organizationId}/scans` }
]);
}
return (
<div>
<div className={classes.header}>
<h1 className={classes.headerLabel}>
<Link to="/organizations">Organizations</Link>
{organization.parent && (
<>
<ChevronRight></ChevronRight>
<Link to={'/organizations/' + organization.parent.id}>
{organization.parent.name}
</Link>
</>
)}
<ChevronRight
style={{
verticalAlign: 'middle',
lineHeight: '100%',
fontSize: '26px'
}}
></ChevronRight>
<span style={{ color: '#07648D' }}>{organization.name}</span>
</h1>
<Subnav
items={navItems}
styles={{
background: '#F9F9F9'
}}
></Subnav>
</div>
<div className={classes.root}>
<Switch>
<Route
path="/organizations/:organizationId"
exact
render={() => views[0]}
/>
<Route
path="/organizations/:organizationId/members"
render={() => views[1]}
/>
<Route
path="/organizations/:organizationId/teams"
render={() => views[2]}
/>
<Route
path="/organizations/:organizationId/scans"
render={() => views[3]}
/>
</Switch>
</div>
</div>
);
}
Example #28
Source File: AlertDismissForm.tsx From backstage with Apache License 2.0 | 4 votes |
AlertDismissForm = forwardRef<
HTMLFormElement,
AlertDismissFormProps
>(({ onSubmit, disableSubmit }, ref) => {
const classes = useStyles();
const [other, setOther] = useState<Maybe<string>>(null);
const [feedback, setFeedback] = useState<Maybe<string>>(null);
const [reason, setReason] = useState<AlertDismissReason>(
AlertDismissReason.Resolved,
);
const onFormSubmit: FormEventHandler = e => {
e.preventDefault();
if (reason) {
onSubmit({
other: other,
reason: reason,
feedback: feedback,
});
}
};
const onReasonChange = (_: ChangeEvent<HTMLInputElement>, value: string) => {
if (other) {
setOther(null);
}
setReason(value as AlertDismissReason);
};
const onOtherChange = (e: ChangeEvent<HTMLInputElement>) => {
return e.target.value
? setOther(e.target.value as AlertDismissReason)
: setOther(null);
};
const onFeedbackChange = (e: ChangeEvent<HTMLInputElement>) => {
return e.target.value
? setFeedback(e.target.value as AlertDismissReason)
: setFeedback(null);
};
useEffect(() => {
function validateDismissForm() {
if (reason === AlertDismissReason.Other) {
if (other) {
disableSubmit(false);
} else {
disableSubmit(true);
}
} else if (reason) {
disableSubmit(false);
} else {
disableSubmit(true);
}
}
validateDismissForm();
}, [reason, other, disableSubmit]);
return (
<form ref={ref} onSubmit={onFormSubmit}>
<FormControl component="fieldset" fullWidth>
<Typography color="textPrimary">
<b>Reason for dismissing?</b>
</Typography>
<Box mb={1}>
<RadioGroup
name="dismiss-alert-reasons"
value={reason}
onChange={onReasonChange}
>
{AlertDismissOptions.map(option => (
<FormControlLabel
key={`dismiss-alert-option-${option.reason}`}
label={option.label}
value={option.reason}
control={<Radio className={classes.radio} />}
/>
))}
</RadioGroup>
<Collapse in={reason === AlertDismissReason.Other}>
<Box ml={4}>
<TextField
id="dismiss-alert-option-other"
variant="outlined"
multiline
fullWidth
rows={4}
value={other ?? ''}
onChange={onOtherChange}
/>
</Box>
</Collapse>
</Box>
<Typography gutterBottom>
<b>Any other feedback you can provide?</b>
</Typography>
<TextField
id="dismiss-alert-feedback"
variant="outlined"
multiline
rows={4}
fullWidth
value={feedback ?? ''}
onChange={onFeedbackChange}
/>
</FormControl>
</form>
);
})
Example #29
Source File: CardSettings.tsx From fishbowl with MIT License | 4 votes |
function CardSettings(props: {
cardPlayStyle: GameCardPlayStyleEnum
setCardPlayStyle?: (cardPlayStyle: GameCardPlayStyleEnum) => void
debouncedSetWordList?: (wordList: string) => void
}) {
const { t } = useTranslation()
const currentPlayer = React.useContext(CurrentPlayerContext)
const currentGame = React.useContext(CurrentGameContext)
const [wordList, setWordList] = React.useState("")
const canConfigureSettings = currentPlayer.role === PlayerRole.Host
const wordListLength = parseWordList(wordList).length
return (
<>
<Grid item>
<FormControl component="fieldset" disabled={!canConfigureSettings}>
<RadioGroup
value={props.cardPlayStyle}
onChange={({ target: { value } }) => {
props.setCardPlayStyle &&
props.setCardPlayStyle(value as GameCardPlayStyleEnum)
}}
>
<FormControlLabel
value={GameCardPlayStyleEnum.PlayersSubmitWords}
control={<Radio color="primary"></Radio>}
label={t(
"settings.cards.cardStyle.playersSubmit",
"Players submit words (default)"
)}
></FormControlLabel>
<FormControlLabel
value={GameCardPlayStyleEnum.HostProvidesWords}
control={<Radio color="primary"></Radio>}
label={t(
"settings.cards.cardStyle.hostProvides",
"Host provides words"
)}
></FormControlLabel>
</RadioGroup>
</FormControl>
</Grid>
{props.cardPlayStyle === GameCardPlayStyleEnum.PlayersSubmitWords && (
<>
<Grid item />
<Grid item>
<SubmissionsPerPlayerInput
value={String(currentGame.num_entries_per_player || "")}
/>
</Grid>
<Grid item>
<LetterInput value={currentGame.starting_letter || ""} />
</Grid>
<Grid item>
<ScreenCardsCheckbox value={Boolean(currentGame.screen_cards)} />
</Grid>
</>
)}
{props.cardPlayStyle === GameCardPlayStyleEnum.HostProvidesWords &&
canConfigureSettings && (
<Grid item>
<TextField
autoFocus
value={wordList}
onChange={({ target: { value } }) => {
setWordList(value)
props.debouncedSetWordList && props.debouncedSetWordList(value)
}}
fullWidth
label={t("settings.cards.words.label", "Words")}
multiline
rows={5}
variant="outlined"
placeholder={t(
"settings.cards.words.placeholder",
"Comma separated list of words here..."
)}
></TextField>
<Box
display="flex"
flexDirection="row-reverse"
pt={0.5}
color={grey[600]}
>
{t("settings.cards.words.helper", "{{ count }} word detected", {
count: wordListLength,
defaultValue_plural: "{{ count }} words detected",
})}
</Box>
</Grid>
)}
</>
)
}