diff --git a/README.md b/README.md index 3d54b8d1..0c6ce576 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ - [__UI__](./docs/UI.md) - [`useAudio`](./docs/useAudio.md) — plays audio and exposes its controls. + - [`useSpeech`](./docs/useSpeech.md) — synthesizes speech from a text string. - [__Animations__](./docs/Animations.md) diff --git a/docs/UI.md b/docs/UI.md new file mode 100644 index 00000000..c938a430 --- /dev/null +++ b/docs/UI.md @@ -0,0 +1,3 @@ +# UI + +*"UI Hooks"* allow you to control and subscribe to state changes of UI elements. diff --git a/docs/useAudio.md b/docs/useAudio.md index 24fe7475..206681a7 100644 --- a/docs/useAudio.md +++ b/docs/useAudio.md @@ -8,11 +8,9 @@ Creates `` element, tracks its state and exposes playback conrols. ```jsx import {useAudio} from 'react-use'; -const src = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3'; - const Demo = () => { const [audio, state, controls, ref] = useAudio({ - src, + src: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3', autoPlay: true, }); diff --git a/docs/useSpeech.md b/docs/useSpeech.md new file mode 100644 index 00000000..f94628fe --- /dev/null +++ b/docs/useSpeech.md @@ -0,0 +1,20 @@ +# `useSpeech` + +React UI hook that synthesizes human voice that speaks a given string. + + +## Usage + +```jsx +import {useSpeech} from 'react-use'; + +const Demo = () => { + const state = useSpeech('Hello world!'); + + return ( + + {JSON.stringify(state, null, 2)} + + ); +}; +``` diff --git a/package.json b/package.json index 7b97deb2..9ae6f789 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "1.6.0", + "version": "1.7.0", "description": "Collection of React Hooks", "main": "lib/index.js", "files": [ diff --git a/src/__stories__/useAudio.story.tsx b/src/__stories__/useAudio.story.tsx index c6aa1160..3ab58630 100644 --- a/src/__stories__/useAudio.story.tsx +++ b/src/__stories__/useAudio.story.tsx @@ -2,11 +2,9 @@ import * as React from 'react'; import {storiesOf} from '@storybook/react'; import {useAudio} from '..'; -const src = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3'; - const Demo = () => { - const [audio, state, controls] = useAudio({ - src, + const [audio, state, controls, ref] = useAudio({ + src: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3', autoPlay: true, }); diff --git a/src/__stories__/useSpeech.story.tsx b/src/__stories__/useSpeech.story.tsx new file mode 100644 index 00000000..c507d214 --- /dev/null +++ b/src/__stories__/useSpeech.story.tsx @@ -0,0 +1,18 @@ +import {storiesOf} from '@storybook/react'; +import * as React from 'react'; +import {useSpeech} from '..'; + +const Demo = () => { + const state = useSpeech('Hello world!'); + + return ( + + {JSON.stringify(state, null, 2)} + + ); +}; + +storiesOf('useSpeech', module) + .add('Example', () => + + ) diff --git a/src/index.ts b/src/index.ts index cd2a00b0..e9cf92a5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,7 @@ import useOrientation from './useOrientation'; import useRaf from './useRaf'; import useSetState from './useSetState'; import useSize from './useSize'; +import useSpeech from './useSpeech'; import useSpring from './useSpring'; import useTimeout from './useTimeout'; import useTitle from './useTitle'; @@ -55,6 +56,7 @@ export { useRaf, useSetState, useSize, + useSpeech, useSpring, useTimeout, useTitle, diff --git a/src/react.ts b/src/react.ts index 69defde9..de817490 100644 --- a/src/react.ts +++ b/src/react.ts @@ -12,3 +12,6 @@ export const useRef: UseRef = (React as any).useRef; export type UseCallback = any)>(callback: T, args: any[]) => T; export const useCallback: UseCallback = (React as any).useCallback; + +export type UseMemo = (fn: Function, args: any[]) => T; +export const useMemo: UseMemo = (React as any).useMemo; diff --git a/src/useLifecycles.ts b/src/useLifecycles.ts index b5c7e69b..841ccd47 100644 --- a/src/useLifecycles.ts +++ b/src/useLifecycles.ts @@ -1,6 +1,6 @@ import {useEffect} from './react'; -const useLifecycles = (mount, unmount) => { +const useLifecycles = (mount, unmount?) => { useEffect(() => { if (mount) mount(); return () => { diff --git a/src/useMount.ts b/src/useMount.ts index 24e72fe5..7ff2edc4 100644 --- a/src/useMount.ts +++ b/src/useMount.ts @@ -1,9 +1,5 @@ import {useEffect} from './react'; -const useMount = (mount) => { - useEffect(() => { - if (mount) mount(); - }, []); -}; +const useMount = (mount) => useEffect(mount, []); export default useMount; diff --git a/src/useSpeech.ts b/src/useSpeech.ts new file mode 100644 index 00000000..a7c8a7c3 --- /dev/null +++ b/src/useSpeech.ts @@ -0,0 +1,40 @@ +import {useRef} from './react'; +import useSetState from './useSetState'; +import useMount from './useMount'; + +export interface SpeechState { + isPlaying: boolean; + volume: number; +} + +export interface SpeechOptions { + lang?: any; + pitch?: number; + rate?: number; + voice?: any; + volume?: number; +} + +const useSpeech = (text: string, opts: SpeechOptions = {}): SpeechState => { + const [state, setState] = useSetState({ + isPlaying: false, + volume: opts.volume || 1, + }); + + const uterranceRef = useRef(null); + + useMount(() => { + const utterance = new SpeechSynthesisUtterance(text); + utterance.volume = opts.volume || 1; + utterance.onstart = () => setState({isPlaying: true}); + utterance.onresume = () => setState({isPlaying: true}); + utterance.onend = () => setState({isPlaying: false}); + utterance.onpause = () => setState({isPlaying: false}); + uterranceRef.current = utterance; + window.speechSynthesis.speak(uterranceRef.current); + }); + + return state; +}; + +export default useSpeech;
+ {JSON.stringify(state, null, 2)} +