Skip to content

Commit

Permalink
✨ feat(chat): add ChatItem and ChatList components
Browse files Browse the repository at this point in the history
This commit updates the `packages/dumi-theme-lobehub/package.json` file by removing unused dependencies, updating the version of some packages, and adding a dependency on `"@floating-ui/react": "^0"`. In addition, it adds two new demo files in the `src/ChatItem/demos` folder. The first demo file is `Alert.tsx`, which creates an alert message using the `ChatItem` component with props passed via `useControls` hook, while the second is `index.tsx` which exports the `ChatItem` component used in the previous demo file.
  • Loading branch information
canisminor1990 committed Jun 11, 2023
1 parent 9c6a8db commit e3bacd8
Show file tree
Hide file tree
Showing 17 changed files with 413 additions and 25 deletions.
22 changes: 7 additions & 15 deletions packages/dumi-theme-lobehub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,20 @@
},
"dependencies": {
"@ant-design/icons": "^5",
"@floating-ui/react": "^0.17",
"@floating-ui/react": "^0",
"@lobehub/ui": "latest",
"chalk": "^4",
"fast-deep-equal": "^3",
"rc-footer": "^0.6"
"rc-footer": "^0"
},
"devDependencies": {
"@emotion/babel-plugin": "^11",
"@emotion/react": "^11",
"@testing-library/react": "^13",
"@umijs/lint": "^4",
"@vitest/coverage-c8": "latest",
"concurrently": "^7",
"cross-env": "^7",
"father-plugin-dumi-theme": "^1.0.0-rc.1",
"history": "^5",
"jsdom": "^21"
"father-plugin-dumi-theme": "latest",
"history": "^5"
},
"peerDependencies": {
"dumi": "^2.0.0",
"react": ">=16.8",
"react-dom": ">=16.8"
"dumi": ">=2",
"react": ">=18",
"react-dom": ">=18"
},
"publishConfig": {
"access": "public",
Expand Down
14 changes: 10 additions & 4 deletions src/Avatar/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@ export const useStyles = createStyles(
avatar: css`
cursor: pointer;
font-size: ${size * 0.5}px;
font-weight: 700;
line-height: 1;
color: ${color};
display: flex;
align-items: center;
justify-content: center;
background: ${backgroundColor};
border: 1px solid ${background ? 'transparent' : token.colorSplit};
> .ant-avatar-string {
font-size: ${size * 0.5}px;
font-weight: 700;
line-height: 1 !important;
color: ${color};
}
> * {
cursor: pointer;
}
Expand Down
22 changes: 22 additions & 0 deletions src/ChatItem/demos/Alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ChatItem, ChatItemProps, StroyBook, useControls, useCreateStore } from '@lobehub/ui';

export default () => {
const store = useCreateStore();
const control: ChatItemProps['alert'] | any = useControls(
{
message: 'Error',
description: '',
type: {
value: 'error',
options: ['success', 'info', 'warning', 'error'],
},
},
{ store },
);

return (
<StroyBook levaStore={store}>
<ChatItem alert={control} avatar="😅" />
</StroyBook>
);
};
34 changes: 34 additions & 0 deletions src/ChatItem/demos/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ChatItem, ChatItemProps, StroyBook, useControls, useCreateStore } from '@lobehub/ui';

export default () => {
const store = useCreateStore();
const control: ChatItemProps | any = useControls(
{
avatar: '😅',
name: '',
message: {
value:
"要使用 dayjs 的 fromNow 函数,需要先安装 dayjs 库并在代码中引入它。然后,可以使用以下语法来获取当前时间与给定时间之间的相对时间:\n\n```javascript\ndayjs().fromNow();\ndayjs('2021-05-01').fromNow();\n```",
rows: true,
},
primary: false,
placement: {
value: 'left',
options: ['left', 'right'],
},
type: {
value: 'block',
options: ['block', 'pure'],
},
borderSpacing: true,
loading: false,
},
{ store },
);

return (
<StroyBook levaStore={store}>
<ChatItem {...control} />
</StroyBook>
);
};
18 changes: 18 additions & 0 deletions src/ChatItem/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
nav: Components
group: Chat
title: ChatItem
description: ChatItem is a React component that represents a single item in a chat conversation. It displays the user's avatar, name, and message. It can also display a loading indicator if the message is still being sent.
---

## Default

<code src="./demos/index.tsx" nopadding></code>

## Alert

<code src="./demos/Alert.tsx" nopadding></code>

## APIs

<API></API>
97 changes: 97 additions & 0 deletions src/ChatItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Alert, type AlertProps } from 'antd';
import { Loader2 } from 'lucide-react';
import { memo } from 'react';

import { Avatar, Icon, Markdown } from '@/index';
import type { DivProps } from '@/types';

import { useStyles } from './style';

const AVATAR_SIZE = 40;

export interface ChatItemProps extends DivProps {
/**
* @description Weather to show alert and alert config
*/
alert?: AlertProps;
/**
* @description URL of the avatar image
*/
avatar?: string;
/**
* @description Whether to add spacing between chat items
* @default true
*/
borderSpacing?: boolean;
/**
* @description Whether to show a loading spinner
* @default false
*/
loading?: boolean;
/**
* @description The message to be displayed
*/
message?: string;
/**
* @description The name of the chat item
*/
name?: string;
/**
* @description The placement of the chat item
* @default 'left'
*/
placement?: 'left' | 'right';
/**
* @description Whether the chat item is primary
* @default false
*/
primary?: boolean;
/**
* @description The type of chat item
* @default 'block'
*/
type?: 'block' | 'pure';
}

const ChatItem = memo<ChatItemProps>(
({
className,
name,
primary,
borderSpacing = true,
loading,
message,
placement = 'left',
type = 'block',
avatar,
alert,
...props
}) => {
const { cx, styles } = useStyles({ placement, type, name, primary, avatarSize: AVATAR_SIZE });
return (
<div className={cx(styles.container, className)} {...props}>
<div className={styles.avatarContainer}>
<Avatar avatar={avatar} size={AVATAR_SIZE} />
{loading && (
<div className={styles.loading}>
<Icon icon={Loader2} size={{ fontSize: 12, strokeWidth: 3 }} spin />
</div>
)}
</div>
<div className={styles.messageContainer}>
{name && <div className={styles.name}>{name}</div>}
{alert ? (
<Alert showIcon {...alert} />
) : (
<div className={styles.message}>
<Markdown>{String(message || '...')}</Markdown>
</div>
)}
</div>
{borderSpacing && <div style={{ width: AVATAR_SIZE, flex: 'none' }} />}
</div>
);
},
);

export default ChatItem;
114 changes: 114 additions & 0 deletions src/ChatItem/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { createStyles } from 'antd-style';

export const useStyles = createStyles(
(
{ cx, css, token },
{
placement,
type,
name,
primary,
avatarSize,
}: {
avatarSize: number;
name?: string;
placement?: 'left' | 'right';
primary?: boolean;
type?: 'block' | 'pure';
},
) => {
const blockStylish = css`
padding: 8px 12px;
background-color: ${primary ? token.colorFillSecondary : token.colorFillTertiary};
border-radius: ${token.borderRadiusLG}px;
transition: background-color 100ms ${token.motionEaseOut};
&:active {
background-color: ${primary ? token.colorFill : token.colorFillSecondary};
}
`;

const pureStylish = css`
padding-top: ${name ? 0 : '6px'};
`;

const pureContainerStylish = css`
border-radius: ${token.borderRadiusLG}px;
transition: background-color 100ms ${token.motionEaseOut};
&:hover {
background-color: ${token.colorFillTertiary};
}
&:active {
background-color: ${token.colorFillSecondary};
}
`;

const typeStylish = type === 'block' ? blockStylish : pureStylish;

return {
container: cx(
type === 'pure' && pureContainerStylish,
css`
position: relative;
display: flex;
flex-direction: ${placement === 'left' ? 'row' : 'row-reverse'};
gap: 12px;
align-items: flex-start;
justify-content: revert;
width: 100%;
padding: 12px;
`,
),
loading: css`
position: absolute;
right: ${placement === 'left' ? '-4px' : 'unset'};
bottom: 0;
left: ${placement === 'right' ? '-4px' : 'unset'};
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
color: ${token.colorBgLayout};
background: ${token.colorPrimary};
border-radius: 50%;
`,
avatarContainer: css`
position: relative;
flex: none;
width: ${avatarSize}px;
height: ${avatarSize}px;
`,
messageContainer: css`
position: relative;
.ant-alert-with-description {
padding-block: 12px;
padding-inline: 12px;
}
`,
name: css`
margin-bottom: 6px;
font-size: 12px;
line-height: 1;
color: ${token.colorTextDescription};
text-align: ${placement === 'left' ? 'left' : 'right'};
`,
message: cx(
typeStylish,
css`
position: relative;
`,
),
};
},
);
13 changes: 13 additions & 0 deletions src/ChatList/demos/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ChatMessage } from '@/Chat';

export const data: ChatMessage[] = [
{
role: 'user',
content: 'dayjs 如何使用 fromNow',
},
{
role: 'assistant',
content:
'要使用 dayjs 的 fromNow 函数,需要先安装 dayjs 库并在代码中引入它。然后,可以使用以下语法来获取当前时间与给定时间之间的相对时间:\n\n```javascript\ndayjs().fromNow(); // 获取当前时间的相对时间\ndayjs(\'2021-05-01\').fromNow(); // 获取给定时间的相对时间\n```\n\n第一个示例将返回类似于 "几秒前"、"一分钟前"、"2 天前" 的相对时间字符串,表示当前时间与调用 fromNow 方法时的时间差。第二个示例将返回给定时间与当前时间的相对时间字符串。',
},
];
22 changes: 22 additions & 0 deletions src/ChatList/demos/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ChatList, ChatListProps, StroyBook, useControls, useCreateStore } from '@lobehub/ui';

import { data } from './data';

export default () => {
const store = useCreateStore();
const control: ChatListProps | any = useControls(
{
type: {
value: 'chat',
options: ['doc', 'chat'],
},
},
{ store },
);

return (
<StroyBook levaStore={store}>
<ChatList data={data} {...control} />
</StroyBook>
);
};
Loading

0 comments on commit e3bacd8

Please sign in to comment.