UNPKG

@spaced-out/ui-design-system

Version:
223 lines (188 loc) 5.34 kB
// @flow strict import * as React from 'react'; import classify from '../../utils/classify'; import type {AvatarProps} from '../Avatar'; import {Avatar} from '../Avatar'; import {Icon, ICON_SIZE, ICON_TYPE} from '../Icon'; import type {BaseTooltipProps} from '../Tooltip'; import {Tooltip} from '../Tooltip'; import css from './ChatBubble.module.css'; const getNamedComponent = ( childrenArray: Array<React.Node>, componentDisplayName: string, ): ?React.Node | React.Node[] => { const nodes: React.Node[] = []; for (const child of childrenArray) { if ( // $FlowFixMe child?.type?.displayName === componentDisplayName ) { nodes.push(child); } } return nodes.length > 1 ? nodes : nodes[0] || null; }; export type ClassNames = $ReadOnly<{wrapper?: string}>; export const CHAT_BUBBLE_ORIENTATION = Object.freeze({ left: 'left', right: 'right ', }); export type ChatBubbleOrientation = $Values<typeof CHAT_BUBBLE_ORIENTATION>; export type ChatBubbleProps = { classNames?: ClassNames, orientation?: ChatBubbleOrientation, children: React.Node, }; export const ChatBubble: React$AbstractComponent< ChatBubbleProps, HTMLDivElement, > = React.forwardRef<ChatBubbleProps, HTMLDivElement>( ({classNames, orientation = 'left', children}: ChatBubbleProps, ref) => { const isLeftAligned = orientation === 'left'; const childrenArray: React.Node[] = React.Children.toArray(children); const anchorComponent = getNamedComponent(childrenArray, 'ChatAnchor'); const chatComponent = getNamedComponent(childrenArray, 'ChatContent'); return ( <div ref={ref} data-testid="ChatBubble" className={classify( css.chatBubbleWrapper, { [css.chatBubbleWrapperRight]: !isLeftAligned, }, classNames?.wrapper, )} > {anchorComponent} {chatComponent} </div> ); }, ); export type ChatAnchorProps = { isAI?: boolean, tooltip?: BaseTooltipProps, classNames?: ClassNames, avatarProps?: AvatarProps, }; export const ChatAnchor = ({ isAI = true, tooltip, classNames, avatarProps, }: ChatAnchorProps): React.Node => ( <div data-testid="ChatAnchor" className={classify(css.chatAnchorWrapper, classNames?.wrapper)} > <Tooltip {...tooltip} hidden={!tooltip}> {isAI ? ( <Icon name="sparkle" color="information" type={ICON_TYPE.solid} size={ICON_SIZE.large} /> ) : ( <Avatar {...avatarProps} /> )} </Tooltip> </div> ); ChatAnchor.displayName = 'ChatAnchor'; export type ChatContentProps = {classNames?: ClassNames, children?: React.Node}; export const ChatContent = ({ classNames, children, }: ChatContentProps): React.Node => ( <div data-testid="ChatContent" className={classify(css.chatContentWrapper, classNames?.wrapper)} > {children} </div> ); ChatContent.displayName = 'ChatContent'; export type ChatBodyClassNames = $ReadOnly<{wrapper?: string, loader: string}>; export type ChatBodyProps = { withBgColor?: boolean, isLoading?: boolean, loadingText?: string, children?: React.Node, classNames?: ClassNames, }; export const ChatBody = ({ withBgColor = true, isLoading, children, loadingText = 'Generating', classNames, }: ChatBodyProps): React.Node => ( <div data-testid="ChatBody" className={classify( css.chatBodyWrapper, { [css.chatBodyWrapperLoading]: isLoading || !withBgColor, [css.chatBodyWrapperBgColor]: withBgColor, }, classNames?.wrapper, )} > {isLoading ? ( <div className={classify(css.loadingContainer, classNames?.wrapper)}> <span className={css.loadingText}>{loadingText}</span> <span className={css.loader}> <span className={css.dot} /> <span className={css.dot} /> <span className={css.dot} /> </span> </div> ) : ( children )} </div> ); ChatBody.displayName = 'ChatBody'; export type ChatFooterProps = {classNames?: ClassNames, children?: React.Node}; export const ChatFooter = ({ children, classNames, }: ChatFooterProps): React.Node => { const childrenArray: React.Node[] = React.Children.toArray(children); const leftSlot = getNamedComponent(childrenArray, 'ChatFooterLeftSlot'); const rightSlot = getNamedComponent(childrenArray, 'ChatFooterRightSlot'); return ( <div data-testid="ChatFooter" className={classify(css.chatFooterWrapper, classNames?.wrapper)} > {leftSlot} {rightSlot} </div> ); }; ChatFooter.displayName = 'ChatFooter'; export type ChatFooterRightSlotProps = { children?: React.Node, classNames?: ClassNames, }; export const ChatFooterRightSlot = ({ children, classNames, }: ChatFooterRightSlotProps): React.Node => ( <div className={classNames?.wrapper}>{children}</div> ); ChatFooterRightSlot.displayName = 'ChatFooterRightSlot'; export type ChatFooterLeftSlotProps = { children?: React.Node, classNames?: ClassNames, }; export const ChatFooterLeftSlot = ({ children, classNames, }: ChatFooterLeftSlotProps): React.Node => ( <div className={classNames?.wrapper}>{children}</div> ); ChatFooterLeftSlot.displayName = 'ChatFooterLeftSlot';