diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 526822758678e..4706c2c84c672 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -18,6 +18,7 @@ - `DropdownMenu`: migrate Storybook to controls ([47149](https://github.com/WordPress/gutenberg/pull/47149)). - Removed deprecated `@storybook/addon-knobs` dependency from the package ([47152](https://github.com/WordPress/gutenberg/pull/47152)). - `ColorListPicker`: Convert to TypeScript ([#46358](https://github.com/WordPress/gutenberg/pull/46358)). +- `KeyboardShortcuts`: Convert to TypeScript ([#47429](https://github.com/WordPress/gutenberg/pull/47429)). - `ColorPalette`, `BorderControl`, `GradientPicker`: refine types and logic around single vs multiple palettes ([#47384](https://github.com/WordPress/gutenberg/pull/47384)). - `Button`: Convert to TypeScript ([#46997](https://github.com/WordPress/gutenberg/pull/46997)). diff --git a/packages/components/src/keyboard-shortcuts/README.md b/packages/components/src/keyboard-shortcuts/README.md index f78abbbd03260..081e551958259 100644 --- a/packages/components/src/keyboard-shortcuts/README.md +++ b/packages/components/src/keyboard-shortcuts/README.md @@ -41,7 +41,7 @@ The component accepts the following props: Elements to render, upon whom key events are to be monitored. -- Type: `Element` | `Element[]` +- Type: `ReactNode` - Required: No ### shortcuts diff --git a/packages/components/src/keyboard-shortcuts/index.js b/packages/components/src/keyboard-shortcuts/index.js deleted file mode 100644 index b7538b9519793..0000000000000 --- a/packages/components/src/keyboard-shortcuts/index.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * WordPress dependencies - */ -import { useRef, Children } from '@wordpress/element'; -import { useKeyboardShortcut } from '@wordpress/compose'; - -function KeyboardShortcut( { - target, - callback, - shortcut, - bindGlobal, - eventName, -} ) { - useKeyboardShortcut( shortcut, callback, { - bindGlobal, - target, - eventName, - } ); - - return null; -} - -function KeyboardShortcuts( { children, shortcuts, bindGlobal, eventName } ) { - const target = useRef(); - - const element = Object.entries( shortcuts ?? {} ).map( - ( [ shortcut, callback ] ) => ( - - ) - ); - - // Render as non-visual if there are no children pressed. Keyboard - // events will be bound to the document instead. - if ( ! Children.count( children ) ) { - return element; - } - - return ( -
- { element } - { children } -
- ); -} - -export default KeyboardShortcuts; diff --git a/packages/components/src/keyboard-shortcuts/index.tsx b/packages/components/src/keyboard-shortcuts/index.tsx new file mode 100644 index 0000000000000..cc6835c56666d --- /dev/null +++ b/packages/components/src/keyboard-shortcuts/index.tsx @@ -0,0 +1,93 @@ +/** + * WordPress dependencies + */ +import { useRef, Children } from '@wordpress/element'; +import { useKeyboardShortcut } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import type { KeyboardShortcutProps, KeyboardShortcutsProps } from './types'; + +function KeyboardShortcut( { + target, + callback, + shortcut, + bindGlobal, + eventName, +}: KeyboardShortcutProps ) { + useKeyboardShortcut( shortcut, callback, { + bindGlobal, + target, + eventName, + } ); + + return null; +} + +/** + * `KeyboardShortcuts` is a component which handles keyboard sequences during the lifetime of the rendering element. + * + * When passed children, it will capture key events which occur on or within the children. If no children are passed, events are captured on the document. + * + * It uses the [Mousetrap](https://craig.is/killing/mice) library to implement keyboard sequence bindings. + * + * ```jsx + * import { KeyboardShortcuts } from '@wordpress/components'; + * import { useState } from '@wordpress/element'; + * + * const MyKeyboardShortcuts = () => { + * const [ isAllSelected, setIsAllSelected ] = useState( false ); + * const selectAll = () => { + * setIsAllSelected( true ); + * }; + * + * return ( + *
+ * + * [cmd/ctrl + A] Combination pressed? { isAllSelected ? 'Yes' : 'No' } + *
+ * ); + * }; + * ``` + */ +function KeyboardShortcuts( { + children, + shortcuts, + bindGlobal, + eventName, +}: KeyboardShortcutsProps ) { + const target = useRef( null ); + + const element = Object.entries( shortcuts ?? {} ).map( + ( [ shortcut, callback ] ) => ( + + ) + ); + + // Render as non-visual if there are no children pressed. Keyboard + // events will be bound to the document instead. + if ( ! Children.count( children ) ) { + return <>{ element }; + } + + return ( +
+ { element } + { children } +
+ ); +} + +export default KeyboardShortcuts; diff --git a/packages/components/src/keyboard-shortcuts/stories/index.tsx b/packages/components/src/keyboard-shortcuts/stories/index.tsx new file mode 100644 index 0000000000000..9a66bacce9108 --- /dev/null +++ b/packages/components/src/keyboard-shortcuts/stories/index.tsx @@ -0,0 +1,60 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + +/** + * Internal dependencies + */ +import KeyboardShortcuts from '..'; + +const meta: ComponentMeta< typeof KeyboardShortcuts > = { + component: KeyboardShortcuts, + title: 'Components/KeyboardShortcuts', + parameters: { + controls: { expanded: true }, + docs: { source: { state: 'open' } }, + }, +}; +export default meta; + +const Template: ComponentStory< typeof KeyboardShortcuts > = ( props ) => ( + +); + +export const Default = Template.bind( {} ); +Default.args = { + shortcuts: { + // eslint-disable-next-line no-alert + a: () => window.alert( 'You hit "a"!' ), + // eslint-disable-next-line no-alert + b: () => window.alert( 'You hit "b"!' ), + }, + children: ( +
+

{ `Hit the "a" or "b" key in this textarea:` }

+