reakeys
    TypeScript icon, indicating that this package has built-in type declarations

    1.2.2 • Public • Published

    ⌨️ reakeys


    React Hook for Mousetrap Hotkeys


    🚀 Quick Links

    📦 Usage

    Install via NPM:

    yarn add reakeys
    

    Then in your component, just add the useHotkeys hook and specify your keys like:

    import React, { FC } from 'react';
    import { useHotkeys } from 'reakeys';
    
    export const YourComponent: FC = () => {
      useHotkeys([
        {
          name: 'Dashboard',
          keys: 'mod+shift+d',
          category: 'Navigation',
          callback: event => {
            event.preventDefault();
            history.push('/dashboard');
          }
        }
      ]);
    };

    Below are the options you can set in the hook array:

    type HotkeyShortcuts = {
      name: string;
      category?: string;
      description?: string;
      keys: string | string[];
      ref?: any;
      hidden?: boolean;
      disabled?: boolean;
      callback: (e: ExtendedKeyboardEvent, combo: string) => void;
      action?: 'keypress'| 'keydown'| 'keyup';
    };

    You can also get all the hotkeys that are registered by just calling the useHotkeys hook and it will return the current hotkeys.

    const hotkeys = useHotkeys();

    This is useful for creating a dialog to present the user with all the options. Below is an example of how to make a dialog using realayers:

    import React, { useState, FC, useCallback, useMemo } from 'react';
    import { Dialog } from 'shared/Dialog';
    import { useHotkeys } from 'reakeys';
    import groupBy from 'lodash/groupBy';
    import sortBy from 'lodash/sortBy';
    
    const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
    
    export const HotkeyCombos: FC = () => {
      // useHotkeys returns the same object if the hotkeys haven't changed, meaning
      // that you can use useMemo to avoid expensive recalculation in that case.
      //
      // Note that the object will change if another component passes a different
      // object to their useHotkeys, even if that component doesn't actually change
      // anything. In React <18, it will cause two re-renders in a row.
      //
      // There is another long comment at the bottom of this example explaining why
      // useMemo is important.
    
      const hotkeys = useHotkeys();
      const categories = useMemo(() => groupBy(hotkeys, 'category'), [hotkeys]);
    
      const sorted = useMemo(() => Object.keys(categories).reduce((prev, cur) => {
        const category = sortBy(categories[cur], 'name');
        const label = cur === 'undefined' ? 'General' : cur;
    
        return {
          ...prev,
          [label]: category.filter(k => !k.hidden)
        };
      }, {}), [categories]);
    
      const { General, ...rest } = sorted as any;
      const others = sortBy(Object.keys(rest || {}));
    
      const renderKeyCode = useCallback(keyCode => {
        const wrapped = Array.isArray(keyCode) ? keyCode : [keyCode];
        const formatted = wrapped.map(k => k.replace('mod', isMac ? '⌘' : 'CTRL'));
    
        return (
          <div className={css.keyComboContainer}>
            {formatted.map((k, i) => (
              <kbd key={i} className={css.keyCombo}>
                {k}
              </kbd>
            ))}
          </div>
        );
      }, []);
    
      const renderGroups = useCallback(
        group => {
          if (!sorted[group]) {
            return null;
          }
    
          return (
            <div key={group}>
              <h3>{group}</h3>
              <ul className={css.list}>
                {sorted[group].map(kk => (
                  <li key={kk.name} className={css.listItem}>
                    <label>{kk.name}</label>
                    {renderKeyCode(kk.keys)}
                    {kk.description && <p>{kk.description}</p>}
                  </li>
                ))}
              </ul>
            </div>
          );
        },
        [renderKeyCode, sorted]
      );
    
      return (
        <div className={css.groups}>
          {renderGroups('General')}
          {others.map(renderGroups)}
        </div>
      );
    };
    
    export const HotkeyDialog: FC = () => {
      const [visible, setVisible] = useState<boolean>(false);
      const openDialog = useCallback(() => setVisible(true), [setVisible]);
      const closeDialog = useCallback(() => setVisible(false), [setVisible]);
    
      // If your hotkeys haven't changed, it's important to provide the same object
      // to useHotkeys, or else it will remove and replace your hotkeys.
      //
      // That isn't always a bad thing, and works perfectly fine, but it would cause
      // unnecessary updates if other components also call useHotkeys() to retrieve
      // the list of hotkeys, because this component would update it every render.
      //
      // Ideally, you should only change the object passed to useHotkeys when the
      // actual hotkeys have changed (name for instance, when using i18n).
      // useMemo is good for this.
    
      useHotkeys(useMemo(() => [
        {
          name: 'Hotkey Dialog',
          keys: 'SHIFT+?',
          hidden: true,
          callback: openDialog
        }
      ], [openDialog]));
    
      const combosRenderer = useCallback(() => <HotkeyCombos />, [HotkeyCombos]);
    
      return (
        <Dialog
          size="800px"
          header="Hotkeys"
          open={visible}
          onClose={closeDialog}
        >
          {combosRenderer}
        </Dialog>
      );
    };

    Install

    npm i reakeys

    DownloadsWeekly Downloads

    2,994

    Version

    1.2.2

    License

    Apache-2.0

    Unpacked Size

    27 kB

    Total Files

    12

    Last publish

    Collaborators

    • amcdnl