@spaced-out/ui-design-system
Version:
Sense UI components library
223 lines (188 loc) • 5.34 kB
Flow
// @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';