|
@@ -1,21 +1,38 @@
|
|
-import React, { useRef, useState, useCallback, useEffect } from 'react';
|
|
|
|
-import { CSSTransition } from 'react-transition-group';
|
|
|
|
-import flagLookup from '../../../domain/flagLookup';
|
|
|
|
-import styles from './Dropdown.module.css';
|
|
|
|
|
|
+import React, { useRef, useState, useCallback, useEffect } from "react";
|
|
|
|
+import { CSSTransition } from "react-transition-group";
|
|
|
|
+import flagLookup from "../../../domain/flagLookup";
|
|
|
|
+import styles from "./Dropdown.module.css";
|
|
|
|
|
|
-export const Item = ({ value, display, onSelect, children }) => (
|
|
|
|
- <div className={styles.item} onClick={() => onSelect(value ?? children, display ?? value ?? children)}>
|
|
|
|
- {children ?? display ?? value}
|
|
|
|
- </div>
|
|
|
|
-);
|
|
|
|
|
|
+export const Item = ({ value, display, onSelect, children }) => {
|
|
|
|
+ const onClick = () =>
|
|
|
|
+ onSelect(value ?? children, display ?? value ?? children);
|
|
|
|
+ return (
|
|
|
|
+ <div
|
|
|
|
+ tabIndex="0"
|
|
|
|
+ role="menuitem"
|
|
|
|
+ className={styles.item}
|
|
|
|
+ onClick={onClick}
|
|
|
|
+ onKeyDown={({ key }) => {
|
|
|
|
+ if (key === "Enter") {
|
|
|
|
+ onClick();
|
|
|
|
+ }
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ {children ?? display ?? value}
|
|
|
|
+ </div>
|
|
|
|
+ );
|
|
|
|
+};
|
|
|
|
|
|
export const Dropdown = ({ selected, open, onSelect, onClick, children }) => {
|
|
export const Dropdown = ({ selected, open, onSelect, onClick, children }) => {
|
|
const transitionRef = useRef(null);
|
|
const transitionRef = useRef(null);
|
|
- const [ displayed, setDisplayed ] = useState(null);
|
|
|
|
- const onSelectCallback = useCallback((value, display) => {
|
|
|
|
- setDisplayed(display);
|
|
|
|
- onSelect(value);
|
|
|
|
- }, [onSelect]);
|
|
|
|
|
|
+ const [displayed, setDisplayed] = useState(null);
|
|
|
|
+ const onSelectCallback = useCallback(
|
|
|
|
+ (value, display) => {
|
|
|
|
+ setDisplayed(display);
|
|
|
|
+ onSelect(value);
|
|
|
|
+ },
|
|
|
|
+ [onSelect]
|
|
|
|
+ );
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
if (selected === undefined) {
|
|
if (selected === undefined) {
|
|
@@ -24,7 +41,11 @@ export const Dropdown = ({ selected, open, onSelect, onClick, children }) => {
|
|
|
|
|
|
let found = null;
|
|
let found = null;
|
|
React.Children.toArray(children).forEach(element => {
|
|
React.Children.toArray(children).forEach(element => {
|
|
- if (React.isValidElement(element) && (found === null) && element.props.value === selected) {
|
|
|
|
|
|
+ if (
|
|
|
|
+ React.isValidElement(element) &&
|
|
|
|
+ found === null &&
|
|
|
|
+ element.props.value === selected
|
|
|
|
+ ) {
|
|
const { value, display } = element.props;
|
|
const { value, display } = element.props;
|
|
found = display ?? value;
|
|
found = display ?? value;
|
|
}
|
|
}
|
|
@@ -33,82 +54,157 @@ export const Dropdown = ({ selected, open, onSelect, onClick, children }) => {
|
|
}, [children, selected]);
|
|
}, [children, selected]);
|
|
return (
|
|
return (
|
|
<div className={styles.container}>
|
|
<div className={styles.container}>
|
|
- <div className={styles.button} onClick={onClick}>{displayed}</div>
|
|
|
|
- <CSSTransition nodeRef={transitionRef} in={open} timeout={200} mountOnEnter unmountOnExit classNames={{
|
|
|
|
- enter: styles['list-enter'],
|
|
|
|
- enterActive: styles['list-enter-active'],
|
|
|
|
- exit: styles['list-exit'],
|
|
|
|
- exitActive: styles['list-exit-active'],
|
|
|
|
- }}>
|
|
|
|
- <div className={styles.list} ref={transitionRef}>
|
|
|
|
- {React.Children.toArray(children).map((child, key) => React.cloneElement(child, { onSelect: onSelectCallback, key }))}
|
|
|
|
|
|
+ <div
|
|
|
|
+ className={styles.button}
|
|
|
|
+ role="button"
|
|
|
|
+ tabIndex="0"
|
|
|
|
+ onClick={onClick}
|
|
|
|
+ onKeyDown={({ key }) => {
|
|
|
|
+ if (key === "Enter") {
|
|
|
|
+ onClick();
|
|
|
|
+ }
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ {displayed}
|
|
|
|
+ </div>
|
|
|
|
+ <CSSTransition
|
|
|
|
+ nodeRef={transitionRef}
|
|
|
|
+ in={open}
|
|
|
|
+ timeout={200}
|
|
|
|
+ mountOnEnter
|
|
|
|
+ unmountOnExit
|
|
|
|
+ classNames={{
|
|
|
|
+ enter: styles["list-enter"],
|
|
|
|
+ enterActive: styles["list-enter-active"],
|
|
|
|
+ exit: styles["list-exit"],
|
|
|
|
+ exitActive: styles["list-exit-active"],
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ <div className={styles.list} role="menu" ref={transitionRef}>
|
|
|
|
+ {React.Children.toArray(children).map(child =>
|
|
|
|
+ React.cloneElement(child, {
|
|
|
|
+ onSelect: onSelectCallback,
|
|
|
|
+ key: JSON.stringify(child.props.value),
|
|
|
|
+ })
|
|
|
|
+ )}
|
|
</div>
|
|
</div>
|
|
</CSSTransition>
|
|
</CSSTransition>
|
|
</div>
|
|
</div>
|
|
- )
|
|
|
|
|
|
+ );
|
|
};
|
|
};
|
|
|
|
|
|
-export const CountryDropdown = ({ countryLookup, selected, onSelect, onClick, open }) => {
|
|
|
|
|
|
+export const CountryDropdown = ({
|
|
|
|
+ countryLookup,
|
|
|
|
+ selected,
|
|
|
|
+ onSelect,
|
|
|
|
+ onClick,
|
|
|
|
+ open,
|
|
|
|
+}) => {
|
|
const transitionRef = useRef(null);
|
|
const transitionRef = useRef(null);
|
|
- const [ search, setSearch ] = useState('');
|
|
|
|
|
|
+ const [search, setSearch] = useState("");
|
|
const found = countryLookup(search) ?? [];
|
|
const found = countryLookup(search) ?? [];
|
|
- const onSelectCallback = useCallback(code => {
|
|
|
|
- setSearch('');
|
|
|
|
- onSelect(code);
|
|
|
|
- }, [onSelect]);
|
|
|
|
|
|
+ const onSelectCallback = useCallback(
|
|
|
|
+ code => {
|
|
|
|
+ setSearch("");
|
|
|
|
+ onSelect(code);
|
|
|
|
+ },
|
|
|
|
+ [onSelect]
|
|
|
|
+ );
|
|
|
|
|
|
return (
|
|
return (
|
|
<div className={styles.container}>
|
|
<div className={styles.container}>
|
|
- <div className={styles.button} onClick={onClick}>{flagLookup(selected)}</div>
|
|
|
|
- <CSSTransition nodeRef={transitionRef} in={open} timeout={200} mountOnEnter unmountOnExit classNames={{
|
|
|
|
- enter: styles['list-enter'],
|
|
|
|
- enterActive: styles['list-enter-active'],
|
|
|
|
- exit: styles['list-exit'],
|
|
|
|
- exitActive: styles['list-exit-active'],
|
|
|
|
- }}>
|
|
|
|
- <div className={styles.list} ref={transitionRef}>
|
|
|
|
|
|
+ <div
|
|
|
|
+ className={styles.button}
|
|
|
|
+ role="button"
|
|
|
|
+ tabIndex="0"
|
|
|
|
+ onClick={onClick}
|
|
|
|
+ onKeyDown={({ key }) => {
|
|
|
|
+ if (key === "Enter") {
|
|
|
|
+ onClick();
|
|
|
|
+ }
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ {flagLookup(selected)}
|
|
|
|
+ </div>
|
|
|
|
+ <CSSTransition
|
|
|
|
+ nodeRef={transitionRef}
|
|
|
|
+ in={open}
|
|
|
|
+ timeout={200}
|
|
|
|
+ mountOnEnter
|
|
|
|
+ unmountOnExit
|
|
|
|
+ classNames={{
|
|
|
|
+ enter: styles["list-enter"],
|
|
|
|
+ enterActive: styles["list-enter-active"],
|
|
|
|
+ exit: styles["list-exit"],
|
|
|
|
+ exitActive: styles["list-exit-active"],
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ <div className={styles.list} role="menu" ref={transitionRef}>
|
|
<input
|
|
<input
|
|
className={styles.search}
|
|
className={styles.search}
|
|
autoFocus
|
|
autoFocus
|
|
- type='text'
|
|
|
|
|
|
+ type="text"
|
|
value={search}
|
|
value={search}
|
|
onChange={({ target }) => setSearch(target.value)}
|
|
onChange={({ target }) => setSearch(target.value)}
|
|
onKeyDown={({ key }) => {
|
|
onKeyDown={({ key }) => {
|
|
- if (key === 'Enter') {
|
|
|
|
|
|
+ if (key === "Enter") {
|
|
onSelectCallback(found?.[0]?.item?.alpha2);
|
|
onSelectCallback(found?.[0]?.item?.alpha2);
|
|
- } else if (key === 'Escape') {
|
|
|
|
|
|
+ } else if (key === "Escape") {
|
|
onSelectCallback(selected);
|
|
onSelectCallback(selected);
|
|
}
|
|
}
|
|
}}
|
|
}}
|
|
/>
|
|
/>
|
|
- {
|
|
|
|
- found.map(({ item: { country, alpha2 } }) => (
|
|
|
|
- <div key={alpha2} className={styles.item} onClick={() => onSelectCallback(alpha2)}>
|
|
|
|
- { flagLookup(alpha2) } - { country }
|
|
|
|
- </div>
|
|
|
|
- ))
|
|
|
|
- }
|
|
|
|
- <div className={styles.item} onClick={() => onSelectCallback(null)}>
|
|
|
|
- { flagLookup(null) } - All Countries
|
|
|
|
|
|
+ {found.map(({ item: { country, alpha2 } }) => (
|
|
|
|
+ <div
|
|
|
|
+ role="button"
|
|
|
|
+ tabIndex="0"
|
|
|
|
+ key={alpha2}
|
|
|
|
+ className={styles.item}
|
|
|
|
+ onClick={() => onSelectCallback(alpha2)}
|
|
|
|
+ onKeyDown={({ key }) => {
|
|
|
|
+ if (key === "Enter") {
|
|
|
|
+ onSelectCallback(alpha2);
|
|
|
|
+ }
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ {flagLookup(alpha2)} - {country}
|
|
|
|
+ </div>
|
|
|
|
+ ))}
|
|
|
|
+ <div
|
|
|
|
+ className={styles.item}
|
|
|
|
+ role="menuitem"
|
|
|
|
+ tabIndex="0"
|
|
|
|
+ onClick={() => onSelectCallback(null)}
|
|
|
|
+ onKeyDown={({ key }) => {
|
|
|
|
+ if (key === "Enter") {
|
|
|
|
+ onSelectCallback(null);
|
|
|
|
+ }
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ {flagLookup(null)} - All Countries
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CSSTransition>
|
|
</CSSTransition>
|
|
</div>
|
|
</div>
|
|
- )
|
|
|
|
|
|
+ );
|
|
};
|
|
};
|
|
|
|
|
|
export const DropdownGroup = ({ children }) => {
|
|
export const DropdownGroup = ({ children }) => {
|
|
- const [ open, setOpen ] = useState(null);
|
|
|
|
|
|
+ const [open, setOpen] = useState(null);
|
|
return (
|
|
return (
|
|
<>
|
|
<>
|
|
- {
|
|
|
|
- children.map((child, key) => React.cloneElement(child, {
|
|
|
|
|
|
+ {children.map(child =>
|
|
|
|
+ React.cloneElement(child, {
|
|
open: open === child.props.open,
|
|
open: open === child.props.open,
|
|
- onClick: () => setOpen(o => o === child.props.open ? null : child.props.open),
|
|
|
|
- onSelect: v => { child.props.onSelect(v); setOpen(null); },
|
|
|
|
- key,
|
|
|
|
- }))
|
|
|
|
- }
|
|
|
|
|
|
+ onClick: () =>
|
|
|
|
+ setOpen(o => (o === child.props.open ? null : child.props.open)),
|
|
|
|
+ onSelect: v => {
|
|
|
|
+ child.props.onSelect(v);
|
|
|
|
+ setOpen(null);
|
|
|
|
+ },
|
|
|
|
+ key: child.props.open,
|
|
|
|
+ })
|
|
|
|
+ )}
|
|
</>
|
|
</>
|
|
);
|
|
);
|
|
-}
|
|
|
|
|
|
+};
|