Skip to content

Commit

Permalink
Add Tooltips to Form Fields (#1427)
Browse files Browse the repository at this point in the history
* example tooltip next to focal species form field on survey form

* permits and survey participation

* tooltipping  ctd

* tooltip ctd1

* more tooltipping

* tooltip testing

* helpbutton for typography stack

* help button stack for box items

* fixing help button formatting

* tooltipping on edit survey page

* make fix

* make fix

* make fix

* wip: update text

* move tooltips into autocompletes and add helptext

* update survey form tooltips

* Fix app tests

---------

Co-authored-by: Macgregor Aubertin-Young <[email protected]>
Co-authored-by: Macgregor Aubertin-Young <[email protected]>
Co-authored-by: Nick Phura <[email protected]>
  • Loading branch information
4 people authored Dec 11, 2024
1 parent 66b47fc commit 4c79a59
Show file tree
Hide file tree
Showing 30 changed files with 317 additions and 325 deletions.
19 changes: 19 additions & 0 deletions app/src/components/buttons/HelpButtonStack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Stack, { StackProps } from '@mui/material/Stack';
import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip';
import { PropsWithChildren } from 'react';

interface IHelpButtonStackProps extends StackProps {
helpText: string;
}

const HelpButtonStack = (props: PropsWithChildren<IHelpButtonStackProps>) => {
const { helpText, children, ...stackProps } = props;
return (
<Stack flexDirection="row" alignItems="center" gap={0.75} flexGrow={1} mt={-1} {...stackProps}>
{children}
<HelpButtonTooltip content={helpText} />
</Stack>
);
};

export default HelpButtonStack;
35 changes: 13 additions & 22 deletions app/src/components/buttons/HelpButtonTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import Zoom from '@mui/material/Zoom';
import { ReactNode } from 'react';
import { useState } from 'react';

interface HelpButtonTooltipProps {
content: string;
children?: ReactNode;
iconSx?: object;
}

Expand All @@ -19,31 +18,23 @@ interface HelpButtonTooltipProps {
* @param {HelpButtonTooltipProps}
* @return {*}
*/
//TODO: Update positioning of the tooltip to be more dynamic (Add Animal form)
const HelpButtonTooltip = ({ content, children, iconSx }: HelpButtonTooltipProps) => {
const HelpButtonTooltip = ({ content, iconSx }: HelpButtonTooltipProps) => {
const [renderTooltip, setRenderTooltip] = useState(false);

return (
<Box
sx={{
position: 'relative',
'& input': {
pr: 7,
overflow: 'hidden',
textOverflow: 'ellipsis'
},
'& .MuiSelect-select': {
pr: '80px !important',
overflow: 'hidden',
textOverflow: 'ellipsis'
},
'& .MuiSelect-icon': {
right: '52px'
}
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
{children}
{/* Tooltip should always be there, but only show when hovering */}
<Tooltip
arrow
title={content}
placement={'right-start'}
placement="right-start"
open={renderTooltip}
TransitionComponent={Zoom}
PopperProps={{
sx: {
Expand All @@ -58,11 +49,11 @@ const HelpButtonTooltip = ({ content, children, iconSx }: HelpButtonTooltipProps
}
}
}}>
{/* IconButton is always displayed */}
<IconButton
onMouseEnter={() => setRenderTooltip(true)}
onMouseLeave={() => setRenderTooltip(false)}
sx={{
position: 'absolute',
top: '8px',
right: '8px',
color: '#38598A',
...iconSx
}}>
Expand Down
4 changes: 4 additions & 0 deletions app/src/components/fields/AutocompleteField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CircularProgress from '@mui/material/CircularProgress';
import grey from '@mui/material/colors/grey';
import TextField, { TextFieldProps } from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip';
import { useFormikContext } from 'formik';
import get from 'lodash-es/get';
import { SyntheticEvent } from 'react';
Expand All @@ -27,6 +28,7 @@ export interface IAutocompleteField<T extends string | number> {
showValue?: boolean;
disableClearable?: boolean;
optionFilter?: 'value' | 'label'; // used to filter existing/ set data for the AutocompleteField, defaults to value in getExistingValue function
helpText?: string;
getOptionDisabled?: (option: IAutocompleteFieldOption<T>) => boolean;
onChange?: (event: SyntheticEvent<Element, Event>, option: IAutocompleteFieldOption<T> | null) => void;
renderOption?: (params: React.HTMLAttributes<HTMLLIElement>, option: IAutocompleteFieldOption<T>) => React.ReactNode;
Expand Down Expand Up @@ -64,6 +66,7 @@ const AutocompleteField = <T extends string | number>(props: IAutocompleteField<
blurOnSelect
handleHomeEndKeys
id={props.id}
fullWidth
data-testid={props.id}
value={getExistingValue(get(values, props.name))}
options={props.options}
Expand Down Expand Up @@ -137,6 +140,7 @@ const AutocompleteField = <T extends string | number>(props: IAutocompleteField<
endAdornment: (
<>
{props.loading ? <CircularProgress color="inherit" size={20} /> : null}
{props.helpText && <HelpButtonTooltip content={props.helpText} />}
{params.InputProps.endAdornment}
</>
)
Expand Down
34 changes: 32 additions & 2 deletions app/src/components/fields/CustomTextField.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import InputAdornment from '@mui/material/InputAdornment';
import TextField from '@mui/material/TextField';
import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip';
import { useFormikContext } from 'formik';
import get from 'lodash-es/get';
export interface ICustomTextField {
Expand Down Expand Up @@ -30,6 +32,13 @@ export interface ICustomTextField {
* @memberof ICustomTextField
*/
maxLength?: number;
/**
* Optional help text to be displayed in a tooltip
*
* @type {string}
* @memberof ICustomTextField
*/
helpText?: string;
/*
* TODO: Needed fix: Add correct hardcoded type
* Note: TextFieldProps causes build compile issue
Expand All @@ -41,22 +50,43 @@ export interface ICustomTextField {
const CustomTextField = (props: React.PropsWithChildren<ICustomTextField>) => {
const { touched, errors, values, handleChange, handleBlur } = useFormikContext<any>();

const { name, label, other, placeholder } = props;
const { name, label, other, placeholder, helpText } = props;

return (
<TextField
name={name}
label={label}
id={name}
placeholder={placeholder}
inputProps={{ 'data-testid': name, maxLength: props.maxLength || undefined }} // targets the internal input rather than the react component
inputProps={{
'data-testid': name,
maxLength: props.maxLength || undefined
}}
InputProps={{
endAdornment: helpText && (
<InputAdornment position="start">
<HelpButtonTooltip content={helpText} />
</InputAdornment>
)
}}
onChange={handleChange}
onBlur={handleBlur}
variant="outlined"
value={get(values, name) ?? ''}
fullWidth={true}
error={get(touched, name) && Boolean(get(errors, name))}
helperText={get(touched, name) && get(errors, name)}
sx={{
'& .MuiInputAdornment-root': {
mr: 0,
height: '100%',
alignSelf: 'flex-start',
position: 'absolute',
top: 12,
right: 12
},
...other?.sx
}}
{...other}
/>
);
Expand Down
11 changes: 11 additions & 0 deletions app/src/components/fields/MultiAutocompleteField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Checkbox from '@mui/material/Checkbox';
import Chip from '@mui/material/Chip';
import ListItemText from '@mui/material/ListItemText';
import TextField from '@mui/material/TextField';
import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip';
import { useFormikContext } from 'formik';
import get from 'lodash-es/get';
import { useEffect, useState } from 'react';
Expand All @@ -27,6 +28,7 @@ export interface IMultiAutocompleteField {
selectedOptions?: IMultiAutocompleteFieldOption[];
required?: boolean;
filterLimit?: number;
helpText?: string;
chipVisible?: boolean;
onChange?: (
_event: React.ChangeEvent<any>,
Expand Down Expand Up @@ -157,6 +159,15 @@ const MultiAutocompleteField: React.FC<IMultiAutocompleteField> = (props) => {
label={props.label}
variant="outlined"
fullWidth
InputProps={{
...params.InputProps,
endAdornment: (
<>
{props.helpText && <HelpButtonTooltip content={props.helpText} iconSx={{ mr: -1 }} />}
{params.InputProps.endAdornment}
</>
)
}}
placeholder="Type to start searching"
error={get(touched, props.id) && Boolean(get(errors, props.id))}
helperText={get(touched, props.id) && get(errors, props.id)}
Expand Down
11 changes: 11 additions & 0 deletions app/src/components/fields/MultiAutocompleteFieldVariableSize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Checkbox from '@mui/material/Checkbox';
import ListSubheader from '@mui/material/ListSubheader';
import TextField from '@mui/material/TextField';
import { FilterOptionsState } from '@mui/material/useAutocomplete';
import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip';
import { useFormikContext } from 'formik';
import { DebouncedFunc } from 'lodash-es';
import get from 'lodash-es/get';
Expand Down Expand Up @@ -44,6 +45,7 @@ export type IMultiAutocompleteField = {
label: string;
required?: boolean;
filterLimit?: number;
helpText?: string;
} & (ApiSearchTypeParam | defaultTypeParam);

function renderRow(props: ListChildComponentProps) {
Expand Down Expand Up @@ -290,6 +292,15 @@ const MultiAutocompleteFieldVariableSize: React.FC<IMultiAutocompleteField> = (p
label={props.label}
variant="outlined"
fullWidth
InputProps={{
...params.InputProps,
endAdornment: (
<>
{props.helpText && <HelpButtonTooltip content={props.helpText} iconSx={{ mr: -1 }} />}
{params.InputProps.endAdornment}
</>
)
}}
placeholder="Type to start searching"
error={get(touched, props.id) && Boolean(get(errors, props.id))}
helperText={get(touched, props.id) && get(errors, props.id)}
Expand Down
13 changes: 11 additions & 2 deletions app/src/components/fields/StartEndDateFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ const StartEndDateFields: React.FC<IStartEndDateFieldsProps> = (props) => {

return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Grid container item spacing={3}>
<Grid
container
item
spacing={3}
sx={{
'& .MuiInputAdornment-root.MuiInputAdornment-positionStart': { mr: -1 },
'& .MuiInputAdornment-root': { mr: 1 }
}}>
<Grid item xs={6}>
<DatePicker
slots={{
Expand All @@ -68,11 +75,13 @@ const StartEndDateFields: React.FC<IStartEndDateFieldsProps> = (props) => {
inputProps: {
'data-testid': 'start_date'
},

InputLabelProps: {
shrink: true
},
fullWidth: true
}
},
popper: { placement: 'bottom-end' }
}}
label="Start Date"
format={DATE_FORMAT.ShortDateFormat}
Expand Down
49 changes: 39 additions & 10 deletions app/src/components/fields/SystemUserAutocompleteField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import grey from '@mui/material/colors/grey';
import TextField from '@mui/material/TextField';
import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip';
import UserCard from 'components/user/UserCard';
import { useBiohubApi } from 'hooks/useBioHubApi';
import useIsMounted from 'hooks/useIsMounted';
Expand All @@ -27,6 +28,13 @@ interface ISystemUserAutocompleteFieldProps {
* @memberof ISystemUserAutocompleteFieldProps
*/
label: string;
/**
* Users to filter from the options because they have already been selected
*
* @type {number[]}
* @memberof ISystemUserAutocompleteFieldProps
*/
selectedUsers?: number[];
/**
* Callback fired on option selection.
*
Expand Down Expand Up @@ -61,6 +69,13 @@ interface ISystemUserAutocompleteFieldProps {
* @memberof ISystemUserAutocompleteFieldProps
*/
clearOnSelect?: boolean;
/**
* Optional help text to be displayed in a tooltip
*
* @type {string}
* @memberof ISystemUserAutocompleteFieldProps
*/
helpText?: string;
/**
* Whether to show start adornment magnifying glass or not
* Defaults to false
Expand All @@ -85,7 +100,18 @@ interface ISystemUserAutocompleteFieldProps {
* @return {*}
*/
export const SystemUserAutocompleteField = (props: ISystemUserAutocompleteFieldProps) => {
const { formikFieldName, disabled, label, showStartAdornment, placeholder, onSelect, onClear, clearOnSelect } = props;
const {
formikFieldName,
disabled,
label,
showStartAdornment,
placeholder,
onSelect,
onClear,
clearOnSelect,
helpText,
selectedUsers
} = props;

const biohubApi = useBiohubApi();
const isMounted = useIsMounted();
Expand Down Expand Up @@ -115,15 +141,18 @@ export const SystemUserAutocompleteField = (props: ISystemUserAutocompleteFieldP
disabled={disabled}
data-testid={formikFieldName}
filterSelectedOptions
noOptionsText={inputValue.length > 2 ? 'No matching options' : 'Enter at least 3 letters'}
noOptionsText={inputValue && inputValue.length > 2 ? 'No matching options' : 'Enter at least 3 letters'}
options={options}
getOptionLabel={(option) => option.display_name}
isOptionEqualToValue={(option, value) => {
return option.system_user_id === value.system_user_id;
getOptionLabel={(option) => option.display_name || ''}
value={null} // Always set value to null to prevent a selected value from showing
isOptionEqualToValue={(option, value) => option.system_user_id === value.system_user_id}
filterOptions={(options) => {
if (selectedUsers) {
return options.filter((item) => !selectedUsers.includes(item.system_user_id));
}
return options;
}}
filterOptions={(item) => item}
inputValue={inputValue}
// Text field value changed
inputValue={inputValue || ''} // Control the text field value separately
onInputChange={(_, value, reason) => {
if (clearOnSelect && reason === 'reset') {
setInputValue('');
Expand Down Expand Up @@ -154,11 +183,10 @@ export const SystemUserAutocompleteField = (props: ISystemUserAutocompleteFieldP
if (!isMounted()) {
return;
}
setOptions(() => newOptions);
setOptions(newOptions);
setIsLoading(false);
});
}}
// Option selected from dropdown
onChange={(_, option) => {
if (!option) {
onClear?.();
Expand Down Expand Up @@ -211,6 +239,7 @@ export const SystemUserAutocompleteField = (props: ISystemUserAutocompleteFieldP
endAdornment: (
<>
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
{helpText && <HelpButtonTooltip content={helpText} />}
{params.InputProps.endAdornment}
</>
)
Expand Down
Loading

0 comments on commit 4c79a59

Please sign in to comment.