diff --git a/.storybook/addons.js b/.storybook/addons.js index 6aed412d..a77bd590 100644 --- a/.storybook/addons.js +++ b/.storybook/addons.js @@ -1,2 +1,3 @@ -import '@storybook/addon-actions/register'; -import '@storybook/addon-links/register'; +import '@storybook/addon-actions/register' +import '@storybook/addon-links/register' +import '@storybook/addon-knobs/register' diff --git a/README.md b/README.md index ea2d345e..323f19f2 100644 --- a/README.md +++ b/README.md @@ -96,17 +96,23 @@ Currently there are two supported providers: To use HelpScout set the `provider` prop as `helpScout` and set the `providerKey` prop as your Beacon API Key. +You can customise the HelpScout beacon by passing the following props to the +`HelpScout` component: + +- `color`: The background color of the beacon +- `icon`: Choose from `message`, `antenna`, `search`, `question`, `beacon` +- `zIndex`: Changes the CSS index value of how the Beacon relates to other objects +- `horizontalPosition`: Choose from `left` or `right` + #### Intercom To use Intercom set the `provider` as `intercom` and set the `providerKey` props as your Intercom App ID. -### Options - You can customise the color of the Intercom widget by passing a `color` prop to -the `LiveChatLoaderProvider`. +the `Intercom` component. ### Todo -- Add customisation options for HelpScout +- Add further customisation options for HelpScout: `buttonStyle`, `text`, `textAlign` - Add tests diff --git a/package.json b/package.json index 3271a50d..dc462a2c 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,7 @@ "url": "https://github.com/calibreapp/react-live-chat-loader.git" }, "peerDependencies": { - "react": "^16.8.5", - "styled-components": "^4.2.0" + "react": "^16.8.5" }, "devDependencies": { "@babel/cli": "^7.5.0", @@ -31,6 +30,7 @@ "@babel/preset-env": "^7.5.0", "@babel/preset-react": "^7.0.0", "@storybook/addon-actions": "^5.1.9", + "@storybook/addon-knobs": "^5.2.6", "@storybook/addon-links": "^5.1.9", "@storybook/addons": "^5.1.9", "@storybook/react": "^5.1.9", @@ -44,8 +44,7 @@ "eslint-plugin-node": "^9.1.0", "eslint-plugin-react": "^7.14.2", "eslint-plugin-react-hooks": "^1.0.1", - "react": "^16.8.5", - "styled-components": "^4.2.0" + "react": "^16.8.5" }, "files": [ "dist/*" diff --git a/src/components/HelpScout/index.js b/src/components/HelpScout/index.js index a837084d..c97d6c40 100644 --- a/src/components/HelpScout/index.js +++ b/src/components/HelpScout/index.js @@ -1,170 +1,222 @@ -import React from 'react' -import styled, { keyframes } from 'styled-components' +import React, { useEffect, useState } from 'react' import { useChat } from '../../' +import useWindowHeight from '../../hooks/useWindowHeight' import STATES from '../../utils/states' -const animation = keyframes` - from { - transform: scale(0); +const styles = { + wrapper: { + borderRadius: '55px', + height: '60px', + width: '60px', + bottom: '40px', + boxShadow: 'rgba(0, 0, 0, 0.1) 0px 4px 7px', + position: 'fixed', + right: '40px', + top: 'auto', + borderStyle: 'none', + transition: + 'box-shadow 250ms ease 0s, opacity 0.4s ease 0s, scale 1000ms ease-in-out 0s, transform 0.2s ease-in-out 0s' + }, + button: { + appearance: 'none', + alignItems: 'center', + bottom: '0px', + display: 'block', + justifyContent: 'center', + position: 'relative', + userSelect: 'none', + zIndex: '999', + color: 'white', + cursor: 'pointer', + minWidth: '60px', + WebkitTapHighlightColor: 'transparent', + height: '60px', + lineHeight: '60px', + borderRadius: '120px', + margin: '0px', + outline: 'none', + padding: '0px', + borderStyle: 'none', + transition: 'background-color 200ms linear 0s, transform 200ms linear 0s' + }, + icon: { + alignItems: 'center', + color: 'white', + cursor: 'pointer', + display: 'flex', + height: '100%', + WebkitBoxPack: 'center', + justifyContent: 'center', + pointerEvents: 'none', + position: 'absolute', + textIndent: '-99999px', + top: '0px', + width: '60px', + willChange: 'opacity, transform', + left: 'auto', + right: '0px', + opacity: '1 !important', + transition: 'opacity 80ms linear 0s, transform 160ms linear 0s' + }, + close: { + WebkitBoxAlign: 'center', + alignItems: 'center', + color: 'white', + cursor: 'pointer', + display: 'flex', + height: '100%', + WebkitBoxPack: 'center', + justifyContent: 'center', + pointerEvents: 'none', + position: 'absolute', + textIndent: '-99999px', + top: '0px', + width: '60px', + willChange: 'opacity, transform', + left: 'auto', + right: '0px', + transition: 'opacity 80ms linear 0s, transform 160ms linear 0s' } +} - to { - transform: scale(1); - } -` - -const Wrapper = styled.div` - border-radius: 55px; - height: 55px; - transform: scale(1); - width: 105px; - z-index: 1050; // One more than the script Beacon - bottom: 40px; - box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 7px; - position: fixed; - right: 40px; - top: auto; - border-width: initial; - border-style: none; - border-color: initial; - border-image: initial; - transition: box-shadow 250ms ease 0s, opacity 0.4s ease 0s, - scale 1000ms ease-in-out 0s, transform 0.2s ease-in-out 0s; - animation-duration: 250ms; - animation-timing-function: ease; - animation-delay: initial; - animation-iteration-count: initial; - animation-direction: initial; - animation-fill-mode: initial; - animation-play-state: initial; - animation-name: ${animation}; - - @media (max-height: 740px) { - bottom: 10px; - right: 20px; - } -` - -const Button = styled.button` - appearance: none; - align-items: center; - bottom: 0px; - display: block; - justify-content: center; - position: relative; - user-select: none; - z-index: 999; - background-color: rgb(35, 146, 236); - color: white; - cursor: pointer; - min-width: 60px; - -webkit-tap-highlight-color: transparent; - height: 55px; - line-height: 55px; - border-radius: 200px; - margin: 0px; - outline: none; - padding: 0px; - border-width: initial; - border-style: none; - border-color: initial; - border-image: initial; - transition: background-color 200ms linear 0s, transform 200ms linear 0s; - - &:focus, - &:hover { - background-color: rgb(27, 138, 228); - box-shadow: rgba(0, 0, 0, 0.06) 0px 0px 0px 30px inset; +const getIcon = icon => { + switch (icon) { + case 'message': + return ( + + + + ) + case 'antenna': + return ( + + + + ) + case 'search': + return ( + + + + ) + case 'question': + return ( + + + + ) + case 'beacon': + return ( + + + + ) + case 'close': + default: + return ( + + + + ) } -` - -const Icon = styled.span` - align-items: center; - color: white; - cursor: pointer; - display: flex; - height: 100%; - -webkit-box-pack: center; - justify-content: center; - pointer-events: none; - position: absolute; - text-indent: -99999px; - top: 0px; - width: 60px; - will-change: opacity, transform; - left: auto; - right: 0px; - opacity: 1 !important; - transform: ${({ state }) => - state === STATES.INITIAL - ? 'rotate(0deg) scale(1)' - : 'rotate(30deg) scale(0)'}; - transition: opacity 80ms linear 0s, transform 160ms linear 0s; -` +} -const Close = styled.span` - -webkit-box-align: center; - align-items: center; - color: white; - cursor: pointer; - display: flex; - height: 100%; - -webkit-box-pack: center; - justify-content: center; - opacity: ${({ state }) => (state === STATES.INITIAL ? 0 : 1)}; - transform: ${({ state }) => - state === STATES.INITIAL - ? 'rotate(30deg) scale(0)' - : 'rotate(0deg) scale(1)'}; - pointer-events: none; - position: absolute; - text-indent: -99999px; - top: 0px; - width: 60px; - will-change: opacity, transform; - left: auto; - right: 0px; - transition: opacity 80ms linear 0s, transform 160ms linear 0s; -` +const HelpScout = ({ color, icon, zIndex, horizontalPosition }) => { + const [scale, setScale] = useState(0) + const [state, loadChat] = useChat() + const windowHeight = useWindowHeight() -const Text = styled.span` - color: white; - display: block; - font-size: 14px; - font-weight: 600; - white-space: nowrap; - padding: 0px 54px 0px 20px; -` + useEffect(() => { + setScale(1) + }, []) -const HelpScout = () => { - const [state, loadChat] = useChat({ toggle: true }) if (state === STATES.COMPLETE) return null + return ( - - - +
+ +
) } +HelpScout.defaultProps = { + color: '#976ad4', + icon: 'beacon', + zIndex: '1050', + horizontalPosition: 'left' +} + export default HelpScout diff --git a/src/components/Intercom/index.js b/src/components/Intercom/index.js index 41c34946..29f2aac2 100644 --- a/src/components/Intercom/index.js +++ b/src/components/Intercom/index.js @@ -1,180 +1,153 @@ -import React, { useContext } from 'react' -import styled, { keyframes } from 'styled-components' +import React, { useEffect, useState } from 'react' -import { useChat, LiveChatLoaderContext } from '../../' +import { useChat } from '../../' import STATES from '../../utils/states' -const animation = keyframes` - from { - transform: scale(0); +const styles = { + wrapper: { + zIndex: 2147483000, + position: 'fixed', + bottom: '20px', + display: 'block', + right: '20px', + width: '60px', + height: '60px', + borderRadius: '50%', + transition: 'transform 0.3s ease', + transform: 'scale(1)', + boxShadow: + 'rgba(0, 0, 0, 0.0588235) 0px 1px 6px 0px, rgba(0, 0, 0, 0.156863) 0px 2px 32px 0px' + }, + region: { + fontFamily: + "intercom-font, 'Helvetica Neue', 'Apple Color Emoji', Helvetica, Arial, sans-serif", + fontSize: '100%', + fontStyle: 'normal', + letterSpacing: 'normal', + fontStretch: 'normal', + fontVariantLigatures: 'normal', + fontVariantCaps: 'normal', + fontVariantEastAsian: 'normal', + fontVariantPosition: 'normal', + fontWeight: 'normal', + textAlign: 'left', + textDecorationLine: 'none', + textDecorationStyle: 'initial', + textDecorationColor: 'initial', + textDecoration: 'none', + textIndent: '0px', + textShadow: 'none', + textTransform: 'none', + boxSizing: 'content-box', + WebkitTextEmphasisRtyle: 'none', + WebkitTextEmphasisColor: 'initial', + WebkitFontSmoothing: 'antialiased', + lineHeight: 1 + }, + launcher: { + position: 'absolute', + top: '0px', + left: '0px', + width: '60px', + height: '60px', + borderRadius: '50%', + cursor: 'pointer', + transformOriginX: 'center', + transformOriginY: 'center', + overflowX: 'hidden', + overflowY: 'hidden', + WebkitBackfaceVisibility: 'hidden', + WebkitFontSmoothing: 'antialiased' + }, + logo: { + display: 'flex', + WebkitBoxAlign: 'center', + alignItems: 'center', + WebkitBoxPack: 'center', + justifyContent: 'center', + position: 'absolute', + top: '0px', + bottom: '0px', + width: '100%', + transform: 'rotate(0deg) scale(1)', + transition: 'transform 0.16s linear 0s, opacity 0.08s linear 0s' + }, + close: { + display: 'flex', + WebkitBoxAlign: 'center', + alignItems: 'center', + WebkitBoxPack: 'center', + justifyContent: 'center', + position: 'absolute', + top: '0px', + bottom: '0px', + width: '100%', + transition: 'transform 0.16s linear 0s, opacity 0.08s linear 0s' } - - to { - transform: scale(1); - } -` - -const Wrapper = styled.div` - z-index: 2147483001; //One more than real widget - position: fixed; - bottom: 20px; - display: block; - right: 20px; - width: 60px; - height: 60px; - border-top-left-radius: 50%; - border-top-right-radius: 50%; - border-bottom-right-radius: 50%; - border-bottom-left-radius: 50%; - background-image: initial; - background-position-x: initial; - background-position-y: initial; - background-size: initial; - background-repeat-x: initial; - background-repeat-y: initial; - background-attachment: initial; - background-origin: initial; - background-clip: initial; - background-color: ${({ color }) => color}; - animation-duration: 250ms; - animation-timing-function: ease; - animation-delay: initial; - animation-iteration-count: initial; - animation-direction: initial; - animation-fill-mode: initial; - animation-play-state: initial; - animation-name: ${animation}; - transition-duration: 0.3s; - transition-timing-function: initial; - transition-delay: initial; - transition-property: opacity; - box-shadow: rgba(0, 0, 0, 0.0588235) 0px 1px 6px 0px, - rgba(0, 0, 0, 0.156863) 0px 2px 32px 0px; -` -Wrapper.defaultProps = { - color: 'rgb(0, 113, 178)' } -const Region = styled.div` - font-family: intercom-font, 'Helvetica Neue', 'Apple Color Emoji', Helvetica, - Arial, sans-serif; - font-size: 100%; - font-style: normal; - letter-spacing: normal; - font-stretch: normal; - font-variant-ligatures: normal; - font-variant-caps: normal; - font-variant-east-asian: normal; - font-variant-position: normal; - font-weight: normal; - text-align: left; - text-decoration-line: none; - text-decoration-style: initial; - text-decoration-color: initial; - text-decoration: none; - -webkit-text-emphasis-style: none; - -webkit-text-emphasis-color: initial; - text-indent: 0px; - text-shadow: none; - text-transform: none; - box-sizing: content-box; - -webkit-font-smoothing: antialiased; - line-height: 1; -` - -const Launcher = styled.div` - position: absolute; - top: 0px; - left: 0px; - width: 60px; - height: 60px; - border-top-left-radius: 50%; - border-top-right-radius: 50%; - border-bottom-right-radius: 50%; - border-bottom-left-radius: 50%; - cursor: pointer; - transform-origin-x: center; - transform-origin-y: center; - -webkit-backface-visibility: hidden; - overflow-x: hidden; - overflow-y: hidden; - -webkit-font-smoothing: antialiased; -` - -const Logo = styled.div` - display: flex; - -webkit-box-align: center; - align-items: center; - -webkit-box-pack: center; - justify-content: center; - position: absolute; - top: 0px; - bottom: 0px; - width: 100%; - opacity: ${({ state }) => (state === STATES.INITIAL ? 1 : 0)}; - transform: rotate(0deg) scale(1); - transition: transform 0.16s linear 0s, opacity 0.08s linear 0s; - - svg { - height: 28px; - width: 32px; - - path { - fill: rgb(255, 255, 255); - } - } -` - -const Close = styled.div` - display: flex; - -webkit-box-align: center; - align-items: center; - -webkit-box-pack: center; - justify-content: center; - position: absolute; - top: 0px; - bottom: 0px; - width: 100%; - opacity: ${({ state }) => (state === STATES.INITIAL ? 0 : 1)}; - transform: ${({ state }) => - state === STATES.INITIAL ? 'rotate(-30deg)' : 'rotate(0deg)'}; - transition: transform 0.16s linear 0s, opacity 0.08s linear 0s; +const Intercom = ({ color = '#333333' }) => { + const [scale, setScale] = useState(0) + const [state, loadChat] = useChat() - svg { - width: 14px; - height: 14px; - - path { - fill: rgb(255, 255, 255); - } - } -` - -const Intercom = () => { - const [state, loadChat] = useChat({ toggle: true }) - const { color } = useContext(LiveChatLoaderContext) + useEffect(() => { + setScale(1) + }, []) if (state === STATES.COMPLETE) return null return ( - - - - -
+
+
+
+ - - -
+
+ - - - - +
+
+
+
) } diff --git a/src/hooks/useWindowHeight.js b/src/hooks/useWindowHeight.js new file mode 100644 index 00000000..52a930d1 --- /dev/null +++ b/src/hooks/useWindowHeight.js @@ -0,0 +1,18 @@ +import { useState, useEffect } from 'react' + +const useWindowHeight = () => { + const [windowHeight, setWindowHeight] = useState(window.innerHeight) + + const handleResize = () => setWindowHeight(window.innerHeight) + + useEffect(() => { + window.addEventListener('resize', handleResize) + return () => { + window.removeEventListener('resize', handleResize) + } + }, []) + + return windowHeight +} + +export default useWindowHeight diff --git a/stories/index.stories.js b/stories/index.stories.js index 0d47ade4..d367700e 100644 --- a/stories/index.stories.js +++ b/stories/index.stories.js @@ -1,13 +1,9 @@ import React from 'react' import { storiesOf } from '@storybook/react' +import { text } from '@storybook/addon-knobs' -import { - LiveChatLoaderProvider, - HelpScout, - Intercom, - useChat -} from '../src' +import { LiveChatLoaderProvider, HelpScout, Intercom, useChat } from '../src' const Button = () => { const [state, loadChat] = useChat() @@ -24,14 +20,25 @@ const Button = () => { storiesOf('HelpScout', module) .add('Chat', () => ( - - + + )) .add('hook', () => React.createElement(() => { return ( - +