Skip to content

Commit

Permalink
feat: Add experimental KeyBinder component
Browse files Browse the repository at this point in the history
  • Loading branch information
diegohaz committed Apr 25, 2019
1 parent 5c50cf9 commit 7eb739a
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/reakit-playground/src/__deps/reakit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export default {
"reakit/Menu/MenuGroup": require("reakit/Menu/MenuGroup"),
"reakit/Menu/MenuDisclosure": require("reakit/Menu/MenuDisclosure"),
"reakit/Menu/Menu": require("reakit/Menu/Menu"),
"reakit/KeyBinder": require("reakit/KeyBinder"),
"reakit/KeyBinder/KeyBinder": require("reakit/KeyBinder/KeyBinder"),
"reakit/Hidden": require("reakit/Hidden"),
"reakit/Hidden/HiddenState": require("reakit/Hidden/HiddenState"),
"reakit/Hidden/HiddenDisclosure": require("reakit/Hidden/HiddenDisclosure"),
Expand Down
1 change: 1 addition & 0 deletions packages/reakit/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
/Portal
/Popover
/Menu
/KeyBinder
/Hidden
/Group
/Form
Expand Down
94 changes: 94 additions & 0 deletions packages/reakit/src/KeyBinder/KeyBinder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as React from "react";
import { unstable_createComponent } from "../utils/createComponent";
import { mergeProps } from "../utils/mergeProps";
import { unstable_useProps } from "../system/useProps";
import { Keys } from "../__utils/types";
import { unstable_useOptions } from "../system";

type KeyMap = {
[key: string]:
| ((event: React.KeyboardEvent<any>) => any)
| null
| false
| undefined;
};

export type unstable_KeyBinderOptions = {
/**
* TODO: Description
*/
keyMap?: KeyMap | ((event: React.KeyboardEvent) => KeyMap);
/**
* TODO: Description
*/
onKey?: (event: React.KeyboardEvent) => any;
/**
* TODO: Description
*/
preventDefault?: boolean | ((event: React.KeyboardEvent) => boolean);
/**
* TODO: Description
*/
stopPropagation?: boolean | ((event: React.KeyboardEvent) => boolean);
};

export type unstable_KeyBinderProps = React.HTMLAttributes<any> &
React.RefAttributes<any>;

export function unstable_useKeyBinder(
{ preventDefault = true, ...options }: unstable_KeyBinderOptions = {},
htmlProps: unstable_KeyBinderProps = {}
) {
let _options: unstable_KeyBinderOptions = { preventDefault, ...options };
_options = unstable_useOptions("KeyBinder", _options, htmlProps);

htmlProps = mergeProps(
{
onKeyDown: event => {
if (!_options.keyMap) return;

const keyMap =
typeof _options.keyMap === "function"
? _options.keyMap(event)
: _options.keyMap;

const shouldPreventDefault =
typeof _options.preventDefault === "function"
? _options.preventDefault(event)
: _options.preventDefault;

const shouldStopPropagation =
typeof _options.stopPropagation === "function"
? _options.stopPropagation(event)
: _options.stopPropagation;

if (event.key in keyMap) {
const action = keyMap[event.key];
if (typeof action === "function") {
if (shouldPreventDefault) event.preventDefault();
if (shouldStopPropagation) event.stopPropagation();
if (_options.onKey) _options.onKey(event);
action(event);
}
}
}
} as typeof htmlProps,
htmlProps
);
htmlProps = unstable_useProps("KeyBinder", options, htmlProps);
return htmlProps;
}

const keys: Keys<unstable_KeyBinderOptions> = [
"keyMap",
"onKey",
"preventDefault",
"stopPropagation"
];

unstable_useKeyBinder.__keys = keys;

export const unstable_KeyBinder = unstable_createComponent({
as: "div",
useHook: unstable_useKeyBinder
});
74 changes: 74 additions & 0 deletions packages/reakit/src/KeyBinder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
path: /docs/key-binder
---

# KeyBinder

> **This is experimental** and may have breaking changes in minor or patch version updates. Issues for this module will have lower priority. Even so, if you use it, feel free to [give us feedback](https://github.com/reakit/reakit/issues/new/choose).
`KeyBinder` is an abstract component that adds key bindings to other components.

## Installation

```sh
npm install reakit
```

Learn more in [Get started](/docs/get-started).

## Usage

```jsx
import React from "react";
import { unstable_KeyBinder as KeyBinder } from "reakit/KeyBinder";

function Example() {
const [count, setCount] = React.useState(0);
return (
<KeyBinder tabIndex={0} keyMap={{ Enter: () => setCount(count + 1) }}>
{count}
</KeyBinder>
);
}
```

### Composing with other components

It's better used in combination with other components:

```jsx
import { unstable_useKeyBinder as useKeyBinder } from "reakit/KeyBinder";
import { Hidden, HiddenDisclosure, useHiddenState } from "reakit/Hidden";

function Example() {
const hidden = useHiddenState();
const keybindings = useKeyBinder({ keyMap: { a: hidden.toggle } });
return (
<>
<HiddenDisclosure {...hidden} {...keybindings}>
{`Press "a" to toggle`}
</HiddenDisclosure>
<Hidden {...hidden}>Yaay!</Hidden>
</>
);
}
```

## Composition

- `KeyBinder` is used by [Menu](/docs/menu) and [MenuDisclosure](/docs/menu).

Learn more in [Composition](/docs/composition#props-hooks).

## Props

<!-- Automatically generated -->

### `KeyBinder`

| Name | Type | Description |
|------|------|-------------|
| <strong><code>keyMap</code>&nbsp;</strong> | <code title="{ [key: string]: false &#124; ((event: KeyboardEvent&#60;any&#62;) =&#62; any) &#124; null &#124; undefined; } &#124; undefined">{&nbsp;[key:&nbsp;string]:&nbsp;false&nbsp;&#124;&nbsp;((...</code> | TODO: Description |
| <strong><code>onKey</code>&nbsp;</strong> | <code title="((event: KeyboardEvent&#60;any&#62;) =&#62; any) &#124; undefined">((event:&nbsp;KeyboardEvent&#60;any&#62;...</code> | TODO: Description |
| <strong><code>preventDefault</code>&nbsp;</strong> | <code>boolean&nbsp;&#124;&nbsp;undefined</code> | TODO: Description |
| <strong><code>stopPropagation</code>&nbsp;</strong> | <code>boolean&nbsp;&#124;&nbsp;undefined</code> | TODO: Description |
15 changes: 15 additions & 0 deletions packages/reakit/src/KeyBinder/__tests__/KeyBinder-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// TODO: Add more tests
import * as React from "react";
import { render } from "react-testing-library";
import { unstable_KeyBinder as KeyBinder } from "../KeyBinder";

test("render", () => {
const { baseElement } = render(<KeyBinder />);
expect(baseElement).toMatchInlineSnapshot(`
<body>
<div>
<div />
</div>
</body>
`);
});
1 change: 1 addition & 0 deletions packages/reakit/src/KeyBinder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./KeyBinder";
1 change: 1 addition & 0 deletions packages/reakit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from "./Dialog";
export * from "./Form";
export * from "./Group";
export * from "./Hidden";
export * from "./KeyBinder";
export * from "./Menu";
export * from "./Popover";
export * from "./Portal";
Expand Down

0 comments on commit 7eb739a

Please sign in to comment.