diff --git a/packages/chat/src/components/Launcher/Launcher.story.tsx b/packages/chat/src/components/Launcher/Launcher.story.tsx index 73ae5eb84..9cf30667e 100644 --- a/packages/chat/src/components/Launcher/Launcher.story.tsx +++ b/packages/chat/src/components/Launcher/Launcher.story.tsx @@ -22,14 +22,23 @@ export default meta; const CollapsableLauncher = (props: any) => { const [isOpen, setIsOpen] = useState(false); + const [counter, setCounter] = useState(0); + const [isDisabled, setIsDisabled] = useState(false); return ( { setIsOpen((prev) => !prev); - props.onClick?.(); + + setCounter((prev) => prev + 1); + + if (counter % 3 === 0) return; + + setIsDisabled(!isDisabled); }} /> ); @@ -51,23 +60,7 @@ export const Loading: Story = { render: () => export const DisabledAndLoading: Story = { render: () => { - const [counter, setCounter] = useState(0); - const [isDisabled, setIsDisabled] = useState(true); - - return ( - { - setCounter((prev) => prev + 1); - - if (counter % 3 === 0) return; - - setIsDisabled(!isDisabled); - }} - /> - ); + return ; }, }; diff --git a/packages/chat/src/components/Launcher/LauncherWithLabel/index.tsx b/packages/chat/src/components/Launcher/LauncherWithLabel/index.tsx index d810d13b3..b3705591c 100644 --- a/packages/chat/src/components/Launcher/LauncherWithLabel/index.tsx +++ b/packages/chat/src/components/Launcher/LauncherWithLabel/index.tsx @@ -5,10 +5,19 @@ import React from 'react'; import { Button } from '@/components/Button'; import { ClassName } from '@/constants'; +import { LoadingSpinner } from '../../LoadingSpinner/LoadingSpinner'; import { ChevronIcon } from '../ChevronIcon'; import { DEFAULT_ICON } from '../constant'; import { PhoneIcon } from '../PhoneIcon'; -import { closeChevron, imageIconStyle, imageIconWrapper, launcherLabelStyles, launcherStyles } from './styles.css'; +import { + closeChevron, + containerLoaderStyles, + imageIconStyle, + imageIconWrapper, + launcherLabelStyles, + launcherStyles, + loadingSpinnerStyles, +} from './styles.css'; export interface LauncherProps { /** @@ -43,6 +52,16 @@ export interface LauncherProps { * Flag to use image. */ withIcon?: boolean; + + /** + * Flag to show loader in the launcher. + */ + isLoading?: boolean; + + /** + * Flag to disable the launcher. + */ + isDisabled?: boolean; } /** @@ -50,14 +69,34 @@ export interface LauncherProps { * * @see {@link https://voiceflow.github.io/react-chat/?path=/story/components-launcher--default} */ -export const LauncherWithLabel: React.FC = ({ isVoice, withIcon, image, isOpen, label, onClick }) => { +export const LauncherWithLabel: React.FC = ({ + isVoice, + withIcon, + image, + isOpen, + label, + onClick, + isLoading, + isDisabled, +}) => { const showDefaultPhoneIcon = !image && isVoice; + const loader = ( +
+ +
+ ); + return ( - ); diff --git a/packages/chat/src/components/Launcher/LauncherWithLabel/styles.css.ts b/packages/chat/src/components/Launcher/LauncherWithLabel/styles.css.ts index 435eec5da..d0dcf3b7c 100644 --- a/packages/chat/src/components/Launcher/LauncherWithLabel/styles.css.ts +++ b/packages/chat/src/components/Launcher/LauncherWithLabel/styles.css.ts @@ -1,4 +1,4 @@ -import { keyframes, style } from '@vanilla-extract/css'; +import { keyframes, style, styleVariants } from '@vanilla-extract/css'; import { recipe } from '@vanilla-extract/recipes'; import { fadeInSlideUp } from '@/components/UserResponse/styles.css'; @@ -9,6 +9,14 @@ import { transition } from '@/styles/transitions'; const LAUNCHER_WITH_LABEL_SIZE = 40; const BEZIER = 'cubic-bezier(0.4, 0, 0.2, 1)'; +const loadingVariant = styleVariants({ + true: {}, +}); + +const noImageVariant = styleVariants({ + true: {}, +}); + export const launcherStyles = recipe({ base: { borderRadius: '9999px', @@ -68,7 +76,23 @@ export const launcherStyles = recipe({ padding: '8px 16px 8px 12px', }, }, - noImage: { true: {} }, + isDisabled: { + true: { + backgroundColor: THEME.colors[300], + + ':hover': { + transform: 'none', + backgroundColor: THEME.colors[300], + }, + ':active': { + transform: 'none', + backgroundColor: THEME.colors[300], + }, + }, + }, + + noImage: noImageVariant, + isLoading: loadingVariant, }, compoundVariants: [ { @@ -90,6 +114,12 @@ export const launcherLabelStyles = style({ textAlign: 'left', padding: '3px 0 1px 0', transition: `all ${duration.mid} ${BEZIER}`, + + selectors: { + [`${loadingVariant.true}${noImageVariant.true} &`]: { + opacity: 0, + }, + }, }); export const twistInAnimation = keyframes({ @@ -116,12 +146,18 @@ export const twistOutAnimation = keyframes({ export const closeChevron = recipe({ base: { transform: 'rotate(0deg)', - transition: transition(['width']), + transition: transition(['width', 'opacity']), position: 'absolute', width: '32px', height: '32px', left: 0, opacity: 0, + + selectors: { + [`${loadingVariant.true} &`]: { + opacity: '0 !important', + }, + }, }, variants: { isOpen: { @@ -148,6 +184,12 @@ export const imageIconStyle = recipe({ flexShrink: 0, transition: transition(['opacity']), + + selectors: { + [`${loadingVariant.true} &`]: { + opacity: 0, + }, + }, }, variants: { isOpen: { @@ -202,3 +244,19 @@ export const imageIconWrapper = recipe({ }, ], }); + +export const loadingSpinnerStyles = style({ + color: 'white', + height: '24px', + width: '24px', +}); + +export const containerLoaderStyles = style({ + position: 'absolute', + top: '50%', + left: '50%', + + height: '24px', + + transform: 'translate(-50%, -50%)', +}); diff --git a/packages/chat/src/components/Launcher/index.tsx b/packages/chat/src/components/Launcher/index.tsx index dfb07bf8b..2c3c4716c 100644 --- a/packages/chat/src/components/Launcher/index.tsx +++ b/packages/chat/src/components/Launcher/index.tsx @@ -96,6 +96,8 @@ export const Launcher: React.FC = ({ onClick={onClick} isVoice={isVoice} withIcon={withIcon} + isLoading={isLoading} + isDisabled={isDisabled} /> ); }