Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Commit

Permalink
Scroll through list of results with keyboard (#4294)
Browse files Browse the repository at this point in the history
* feat: scroll through list of results with keyboard

* feat: Add guard pattern and named functions

- Change if statement and add guard
- Add extra comment for specific calculations
- Change functions to named functions

* feat: rename function according to coding standards
  • Loading branch information
hasan-ozaynaci authored and VWSCoronaDashboard25 committed Jun 23, 2022
1 parent 1765c21 commit 304f7b5
Showing 1 changed file with 39 additions and 10 deletions.
49 changes: 39 additions & 10 deletions packages/app/src/components/combo-box/combo-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,53 @@ type TProps<Option extends TOption> = {
* />
* ```
*/
export function ComboBox<Option extends TOption>(props: TProps<Option>) {
export const ComboBox = <Option extends TOption>(props: TProps<Option>) => {
const { options, placeholder, sorter, selectedOption } = props;

const { commonTexts } = useIntl();

const router = useRouter();
const { code } = router.query;
const inputRef = useRef<HTMLInputElement>(null);
const containerRef = useRef<HTMLUListElement>(null);
const [inputValue, setInputValue] = useState<string>('');
const results = useSearchedOptions<Option>(inputValue, options, sorter);
const breakpoints = useBreakpoints();
const isLargeScreen = breakpoints.md;
const hasRegionSelected = !!code;

function handleInputChange(event: React.ChangeEvent<HTMLInputElement>): void {
/**
* Allow keyboard interaction to scroll through a list of results.
*/
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
const container = containerRef.current;

if (event.isDefaultPrevented() || !container) return;

window.requestAnimationFrame(() => {
const element: HTMLInputElement | null = container.querySelector(
'[aria-selected=true]'
);
if (element) {
const top = element.offsetTop - container.scrollTop; // Calculate the space between active element and top of the list
const bottom =
container.scrollTop +
container.clientHeight -
(element.offsetTop + element.clientHeight); // Calculate the space between active element and bottom of the list

if (bottom < 0) container.scrollTop -= bottom;
if (top < 0) container.scrollTop += top;
}
});
};

const handleInputChange = (
event: React.ChangeEvent<HTMLInputElement>
): void => {
setInputValue(event.target.value);
}
};

function handleSelect(name: string): void {
const handleSelect = (name: string): void => {
if (!name) {
return;
}
Expand All @@ -85,7 +113,7 @@ export function ComboBox<Option extends TOption>(props: TProps<Option>) {
setTimeout(() => {
inputRef.current?.blur();
}, 1);
}
};

useEffect(() => {
if (!inputRef.current?.value && isLargeScreen && !hasRegionSelected) {
Expand All @@ -99,11 +127,12 @@ export function ComboBox<Option extends TOption>(props: TProps<Option>) {
<ComboboxInput
ref={inputRef}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder={placeholder}
/>
<ComboboxPopover>
{results.length > 0 ? (
<ComboboxList>
<ComboboxList ref={containerRef}>
{results.map((option, index) => (
<StyledComboboxOption
key={`${index}-${option.name}`}
Expand All @@ -120,13 +149,13 @@ export function ComboBox<Option extends TOption>(props: TProps<Option>) {
<ComboBoxStyles />
</Box>
);
}
};

function useSearchedOptions<Option extends TOption>(
const useSearchedOptions = <Option extends TOption>(
term: string,
options: Option[],
sorter?: (a: Option, b: Option) => number
): Option[] {
): Option[] => {
const throttledTerm = useThrottle(term, 100);

return useMemo(
Expand All @@ -138,7 +167,7 @@ function useSearchedOptions<Option extends TOption>(
}),
[throttledTerm, options, sorter]
);
}
};

const StyledComboboxOption = styled(ComboboxOption)<{
isSelectedOption: boolean;
Expand Down

0 comments on commit 304f7b5

Please sign in to comment.