Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ export const RegionSelect = <
return (
<StyledAutocompleteContainer sx={{ width }}>
<Autocomplete<Region, false, DisableClearable>
enableNativeSelectOnMobile={{
getOptionValue: (option) => option.id,
optionMatcher: (option, value) => option.id === value,
transformSelection: (option) => option,
}}
getOptionLabel={(region) =>
isGeckoLAEnabled ? region.label : `${region.label} (${region.id})`
}
Expand Down
63 changes: 63 additions & 0 deletions packages/ui/src/components/Autocomplete/Autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import type { TextFieldProps } from '../TextField';
import type {
AutocompleteProps,
AutocompleteRenderInputParams,
AutocompleteValue,
} from '@mui/material/Autocomplete';
import { NativeSelect } from '@mui/material';
import { FormHelperText } from '../FormHelperText';
import { useIsMobileDevice } from '../../utilities/useIsMobileDevice';
import { Stack } from '../Stack';

export interface EnhancedAutocompleteProps<
T extends { label: string },
Expand All @@ -34,6 +39,15 @@ export interface EnhancedAutocompleteProps<
errorText?: string;
/** Provides a hint with normal styling to assist users. */
helperText?: TextFieldProps['helperText'];
/** Enable native select on mobile */
enableNativeSelectOnMobile?: {
optionMatcher: (option: T, value: string) => boolean;
getOptionValue: (option: T) => string;
transformSelection: (
option: T,
event: React.ChangeEvent<HTMLSelectElement>
) => AutocompleteValue<T, Multiple, DisableClearable, FreeSolo>;
};
/** A required label for the Autocomplete to ensure accessibility. */
label: string;
/** Removes the top margin from the input label, if desired. */
Expand Down Expand Up @@ -76,6 +90,7 @@ export const Autocomplete = <
defaultValue,
disablePortal = true,
disableSelectAll = false,
enableNativeSelectOnMobile = false,
errorText = '',
helperText,
label,
Expand All @@ -96,6 +111,7 @@ export const Autocomplete = <
value,
...rest
} = props;
const { isMobileDevice } = useIsMobileDevice();

const isSelectAllActive =
multiple && Array.isArray(value) && value.length === options.length;
Expand All @@ -106,6 +122,53 @@ export const Autocomplete = <

const optionsWithSelectAll = [selectAllOption, ...options] as T[];

if (isMobileDevice && enableNativeSelectOnMobile && !multiple) {
return (
<Stack direction="column">
<NativeSelect
onChange={(e) => {
const selectedOption = options.find((option) =>
enableNativeSelectOnMobile.optionMatcher(option, e.target.value)
);
if (selectedOption && onChange) {
const transformedValue = enableNativeSelectOnMobile.transformSelection(
selectedOption,
e
);
onChange(e, transformedValue, 'selectOption');
}
}}
sx={{
width: '100%',
mt: 1,
}}
error={!!errorText}
placeholder={placeholder}
required={textFieldProps?.InputProps?.required}
>
<option value="">{placeholder}</option>
{options.map((option) => {
return (
<option
key={option.label}
value={enableNativeSelectOnMobile.getOptionValue(option)}
>
{option.label}
</option>
);
})}
</NativeSelect>
{errorText && (
<FormHelperText
sx={(theme) => ({ color: theme.tokens.action.Negative.Active })}
>
{errorText}
</FormHelperText>
)}
</Stack>
);
}

return (
<MuiAutocomplete
options={
Expand Down
23 changes: 23 additions & 0 deletions packages/ui/src/utilities/useIsMobileDevice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useState } from 'react';

export const useIsMobileDevice = () => {
const [isMobileDevice, setIsMobileDevice] = useState(false);

useEffect(() => {
const checkMobile = () => {
setIsMobileDevice(
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
window.matchMedia('(hover: none) and (pointer: coarse)').matches
);
};

checkMobile();
// eslint-disable-next-line scanjs-rules/call_addEventListener
window.addEventListener('resize', checkMobile);

return () => window.removeEventListener('resize', checkMobile);
}, []);

return { isMobileDevice };
};
Loading