Skip to content

Commit

Permalink
* feat(select): ✨ add select v2 based on reakit combobox
Browse files Browse the repository at this point in the history
* 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
navin-moorthy authored Nov 16, 2020
1 parent 7e45f06 commit d7766f8
Show file tree
Hide file tree
Showing 45 changed files with 1,598 additions and 994 deletions.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@
"@react-aria/interactions": "^3.3.0",
"@react-aria/utils": "3.4.0",
"date-fns": "2.16.1",
"reakit": "1.2.5",
"reakit-system": "0.14.5",
"reakit-utils": "0.14.4",
"reakit": "1.3.0",
"reakit-system": "0.15.0",
"reakit-utils": "0.15.0",
"uuid": "8.3.1"
},
"devDependencies": {
Expand Down Expand Up @@ -130,6 +130,7 @@
"react-spring": "8.0.27",
"react-test-renderer": "16.14.0",
"react-transition-group": "4.4.1",
"react-virtual": "^2.3.1",
"reakit-test-utils": "0.14.5",
"rimraf": "3.0.2",
"sort-package-json": "1.46.1",
Expand Down
5 changes: 4 additions & 1 deletion src/accordion/__keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ const ACCORDION_STATE_KEYS = [
"setCurrentId",
"setLoop",
"setWrap",
"setShift",
"reset",
"unstable_setIncludesBaseElement",
"unstable_setHasActiveWidget",
"select",
"unSelect",
Expand All @@ -35,9 +37,10 @@ const ACCORDION_STATE_KEYS = [
"currentId",
"loop",
"wrap",
"shift",
"unstable_moves",
"unstable_angular",
"unstable_hasActiveWidget",
"unstable_includesBaseElement",
"allowMultiple",
"manual",
"allowToggle",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ Object {
"panels": Array [],
"rtl": false,
"selectedId": null,
"unstable_angular": false,
"shift": false,
"unstable_hasActiveWidget": false,
"unstable_idCountRef": Object {
"current": 0,
},
"unstable_includesBaseElement": false,
"unstable_moves": 0,
"unstable_virtual": false,
"wrap": false,
Expand Down
5 changes: 4 additions & 1 deletion src/datepicker/__keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ const DATE_PICKER_STATE_KEYS = [
"currentId",
"loop",
"wrap",
"shift",
"unstable_moves",
"unstable_angular",
"unstable_hasActiveWidget",
"unstable_includesBaseElement",
"registerItem",
"unregisterItem",
"registerGroup",
Expand All @@ -44,7 +45,9 @@ const DATE_PICKER_STATE_KEYS = [
"setCurrentId",
"setLoop",
"setWrap",
"setShift",
"reset",
"unstable_setIncludesBaseElement",
"unstable_setHasActiveWidget",
"visible",
"animated",
Expand Down
5 changes: 4 additions & 1 deletion src/segment/__keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ const SEGMENT_STATE_KEYS = [
"currentId",
"loop",
"wrap",
"shift",
"unstable_moves",
"unstable_angular",
"unstable_hasActiveWidget",
"unstable_includesBaseElement",
"registerItem",
"unregisterItem",
"registerGroup",
Expand All @@ -42,7 +43,9 @@ const SEGMENT_STATE_KEYS = [
"setCurrentId",
"setLoop",
"setWrap",
"setShift",
"reset",
"unstable_setIncludesBaseElement",
"unstable_setHasActiveWidget",
] as const;
export const SEGMENT_KEYS = [
Expand Down
122 changes: 105 additions & 17 deletions src/select/Select.ts
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;
Loading

0 comments on commit d7766f8

Please sign in to comment.