-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(select): ✨ add select v2 based on reakit combobox
* feat(combobox): ✨ add combobox from reakit * fix(combobox): 🐛 remove popover list focus * feat(select): ✨ new select from combobox followup * fix(select): 🐛 update select with typeahead * refactor(select): ♻️ better state return values * feat(select): ✨ add typeahead in the listbox * feat(select): ✨ add dynamic values * docs(select): 📝 add multiple examples * refactor(select): ♻️ remove combobox & add final select * chore(reakit): ⬆️ update reakit to latest version * refactor(select): ♻️ update utils, types & extract onCharacterPress * refactor(select): ♻️ remove keys from helpers * chore(select): 🏷️ update composite types in base state * refactor(select): 🏷️ support removal of generics types * refactor(select): ♻️ remove generics from popover state
- Loading branch information
1 parent
7e45f06
commit d7766f8
Showing
45 changed files
with
1,598 additions
and
994 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,119 @@ | ||
/* eslint-disable react-hooks/exhaustive-deps */ | ||
import React from "react"; | ||
import { SelectStateReturn } from "./SelectState"; | ||
import { BoxHTMLProps, useBox } from "reakit/Box"; | ||
import { createHook, createComponent } from "reakit-system"; | ||
import { | ||
PopoverDisclosureHTMLProps, | ||
PopoverDisclosureOptions, | ||
usePopoverDisclosure, | ||
} from "reakit"; | ||
import * as React from "react"; | ||
import { useShortcut } from "@chakra-ui/hooks"; | ||
import { useLiveRef } from "reakit-utils/useLiveRef"; | ||
import { createComponent, createHook } from "reakit-system"; | ||
import { callAllHandlers, getNextItemFromSearch } from "@chakra-ui/utils"; | ||
|
||
import { SELECT_KEYS } from "./__keys"; | ||
import { SelectStateReturn } from "./SelectState"; | ||
|
||
export type SelectOptions = Pick<SelectStateReturn, "selected"> & { | ||
onChange?: (value: any) => void; | ||
}; | ||
|
||
const useSelect = createHook<SelectOptions, BoxHTMLProps>({ | ||
export const useSelect = createHook<SelectOptions, SelectHTMLProps>({ | ||
name: "Select", | ||
compose: useBox, | ||
compose: usePopoverDisclosure, | ||
keys: SELECT_KEYS, | ||
|
||
useProps({ selected, onChange }, { ...htmlProps }) { | ||
React.useEffect(() => { | ||
onChange?.(selected); | ||
}, [selected]); | ||
useOptions({ menuRole = "listbox", hideOnEsc = true, ...options }) { | ||
return { menuRole, hideOnEsc, ...options }; | ||
}, | ||
|
||
useProps(options, { onKeyDown: htmlOnKeyDown, ...htmlProps }) { | ||
const onKeyDownRef = useLiveRef(htmlOnKeyDown); | ||
|
||
// Reference: | ||
// https://github.com/chakra-ui/chakra-ui/blob/83eec5b140bd9a69821d8e4df3e69bff0768dcca/packages/menu/src/use-menu.ts#L228-L253 | ||
const onCharacterPress = useShortcut({ | ||
preventDefault: event => event.key !== " ", | ||
}); | ||
|
||
const onKeyDown = React.useCallback( | ||
(event: React.KeyboardEvent) => { | ||
onKeyDownRef.current?.(event); | ||
if (event.defaultPrevented) return; | ||
|
||
// setTimeout on show prevents scroll jump on ArrowUp & ArrowDown | ||
const first = () => { | ||
if (!options.visible) options.show && setTimeout(options.show); | ||
if (!options.selectedValue) options.first?.(); | ||
}; | ||
const last = () => { | ||
if (!options.visible) options.show && setTimeout(options.show); | ||
if (!options.selectedValue) options.last?.(); | ||
}; | ||
|
||
return htmlProps; | ||
const keyMap = { | ||
Enter: first, | ||
" ": first, | ||
ArrowUp: last, | ||
ArrowDown: first, | ||
}; | ||
|
||
const action = keyMap[event.key as keyof typeof keyMap]; | ||
action?.(); | ||
}, | ||
[ | ||
options.visible, | ||
options.show, | ||
options.last, | ||
options.first, | ||
options.values, | ||
options.selectedValue, | ||
options.setSelectedValue, | ||
], | ||
); | ||
|
||
return { | ||
"aria-haspopup": options.menuRole, | ||
onKeyDown: callAllHandlers( | ||
onKeyDown, | ||
onCharacterPress(handleCharacterPress(options)), | ||
), | ||
...htmlProps, | ||
}; | ||
}, | ||
}); | ||
|
||
export const Select = createComponent({ | ||
as: "div", | ||
as: "button", | ||
memo: true, | ||
useHook: useSelect, | ||
}); | ||
|
||
const handleCharacterPress = (options: SelectOptions) => ( | ||
character: string, | ||
) => { | ||
/** | ||
* Typeahead: Based on current character pressed, | ||
* find the next item to be selected | ||
*/ | ||
const selectedValue = options.values.find(value => | ||
options.selectedValue?.includes(value), | ||
); | ||
|
||
const nextItem = getNextItemFromSearch( | ||
options.values, | ||
character, | ||
item => item ?? "", | ||
selectedValue, | ||
); | ||
|
||
if (nextItem) options.setSelectedValue(nextItem); | ||
}; | ||
|
||
export type SelectOptions = PopoverDisclosureOptions & | ||
SelectStateReturn & { | ||
/** | ||
* When enabled, user can hide the select popover by pressing | ||
* `esc` while focusing on the select input. | ||
* @default true | ||
*/ | ||
hideOnEsc?: boolean; | ||
}; | ||
|
||
export type SelectHTMLProps = PopoverDisclosureHTMLProps; | ||
|
||
export type SelectProps = SelectOptions & SelectHTMLProps; |
Oops, something went wrong.