From e3bacd85514faae6486a39cd632b63f0e7684f9e Mon Sep 17 00:00:00 2001 From: canisminor1990 Date: Sun, 11 Jun 2023 18:48:36 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(chat):=20add=20ChatItem=20and?= =?UTF-8?q?=20ChatList=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- packages/dumi-theme-lobehub/package.json | 22 ++-- src/Avatar/style.ts | 14 ++- src/ChatItem/demos/Alert.tsx | 22 ++++ src/ChatItem/demos/index.tsx | 34 ++++++ src/ChatItem/index.md | 18 +++ src/ChatItem/index.tsx | 97 +++++++++++++++ src/ChatItem/style.ts | 114 ++++++++++++++++++ src/ChatList/demos/data.ts | 13 ++ src/ChatList/demos/index.tsx | 22 ++++ src/ChatList/index.md | 14 +++ src/ChatList/index.tsx | 40 ++++++ src/ChatList/style.ts | 9 ++ src/Highlighter/demos/index.tsx | 2 +- src/Highlighter/index.tsx | 2 +- src/Highlighter/style.ts | 6 +- src/index.ts | 2 + .../algorithms/generateCustomStylish.ts | 7 +- 17 files changed, 413 insertions(+), 25 deletions(-) create mode 100644 src/ChatItem/demos/Alert.tsx create mode 100644 src/ChatItem/demos/index.tsx create mode 100644 src/ChatItem/index.md create mode 100644 src/ChatItem/index.tsx create mode 100644 src/ChatItem/style.ts create mode 100644 src/ChatList/demos/data.ts create mode 100644 src/ChatList/demos/index.tsx create mode 100644 src/ChatList/index.md create mode 100644 src/ChatList/index.tsx create mode 100644 src/ChatList/style.ts diff --git a/packages/dumi-theme-lobehub/package.json b/packages/dumi-theme-lobehub/package.json index b04a90ff..840365c2 100644 --- a/packages/dumi-theme-lobehub/package.json +++ b/packages/dumi-theme-lobehub/package.json @@ -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", diff --git a/src/Avatar/style.ts b/src/Avatar/style.ts index 3590a21e..ce44b17f 100644 --- a/src/Avatar/style.ts +++ b/src/Avatar/style.ts @@ -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; } diff --git a/src/ChatItem/demos/Alert.tsx b/src/ChatItem/demos/Alert.tsx new file mode 100644 index 00000000..150ef6f2 --- /dev/null +++ b/src/ChatItem/demos/Alert.tsx @@ -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 ( + + + + ); +}; diff --git a/src/ChatItem/demos/index.tsx b/src/ChatItem/demos/index.tsx new file mode 100644 index 00000000..04dd2f8b --- /dev/null +++ b/src/ChatItem/demos/index.tsx @@ -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 ( + + + + ); +}; diff --git a/src/ChatItem/index.md b/src/ChatItem/index.md new file mode 100644 index 00000000..3db5886e --- /dev/null +++ b/src/ChatItem/index.md @@ -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 + + + +## Alert + + + +## APIs + + diff --git a/src/ChatItem/index.tsx b/src/ChatItem/index.tsx new file mode 100644 index 00000000..f899d5c1 --- /dev/null +++ b/src/ChatItem/index.tsx @@ -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( + ({ + 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 ( +
+
+ + {loading && ( +
+ +
+ )} +
+
+ {name &&
{name}
} + {alert ? ( + + ) : ( +
+ {String(message || '...')} +
+ )} +
+ {borderSpacing &&
} +
+ ); + }, +); + +export default ChatItem; diff --git a/src/ChatItem/style.ts b/src/ChatItem/style.ts new file mode 100644 index 00000000..71b54f3d --- /dev/null +++ b/src/ChatItem/style.ts @@ -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; + `, + ), + }; + }, +); diff --git a/src/ChatList/demos/data.ts b/src/ChatList/demos/data.ts new file mode 100644 index 00000000..d07173cd --- /dev/null +++ b/src/ChatList/demos/data.ts @@ -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 方法时的时间差。第二个示例将返回给定时间与当前时间的相对时间字符串。', + }, +]; diff --git a/src/ChatList/demos/index.tsx b/src/ChatList/demos/index.tsx new file mode 100644 index 00000000..fcbee0b7 --- /dev/null +++ b/src/ChatList/demos/index.tsx @@ -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 ( + + + + ); +}; diff --git a/src/ChatList/index.md b/src/ChatList/index.md new file mode 100644 index 00000000..113f1bb7 --- /dev/null +++ b/src/ChatList/index.md @@ -0,0 +1,14 @@ +--- +nav: Components +group: Chat +title: ChatList +description: ChatList is a component used to display a list of chat messages. It takes in an array of ChatMessage objects as the data prop and an optional type prop which can be either 'docs' or 'chat'. The component is memoized for better performance. +--- + +## Default + + + +## APIs + + diff --git a/src/ChatList/index.tsx b/src/ChatList/index.tsx new file mode 100644 index 00000000..75b6f6bf --- /dev/null +++ b/src/ChatList/index.tsx @@ -0,0 +1,40 @@ +import { memo } from 'react'; + +import { ChatMessage } from '@/Chat'; +import { ChatItem } from '@/index'; +import type { DivProps } from '@/types'; + +import { useStyles } from './style'; + +export interface ChatListProps extends DivProps { + /** + * @description Data of chat messages to be displayed + */ + data: ChatMessage[]; + /** + * @description Type of chat list + * @default 'chat' + */ + type?: 'docs' | 'chat'; +} + +const ChatList = memo(({ className, data, type = 'chat', ...props }) => { + const { cx, styles } = useStyles(); + + return ( +
+ {data.map((item, index) => ( + + ))} +
+ ); +}); + +export default ChatList; diff --git a/src/ChatList/style.ts b/src/ChatList/style.ts new file mode 100644 index 00000000..e684626a --- /dev/null +++ b/src/ChatList/style.ts @@ -0,0 +1,9 @@ +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(({ css }) => { + return { + container: css` + position: relative; + `, + }; +}); diff --git a/src/Highlighter/demos/index.tsx b/src/Highlighter/demos/index.tsx index 846467cb..ef6ba434 100644 --- a/src/Highlighter/demos/index.tsx +++ b/src/Highlighter/demos/index.tsx @@ -23,7 +23,7 @@ export default () => { language: 'tsx', type: { value: 'block', - options: ['ghost', 'block', 'prue'], + options: ['ghost', 'block', 'pure'], }, copyable: true, showLanguage: true, diff --git a/src/Highlighter/index.tsx b/src/Highlighter/index.tsx index 58aba5dc..511bbf5c 100644 --- a/src/Highlighter/index.tsx +++ b/src/Highlighter/index.tsx @@ -36,7 +36,7 @@ export interface HighlighterProps extends DivProps { * @description The type of the code block * @default 'block' */ - type?: 'ghost' | 'block' | 'prue'; + type?: 'ghost' | 'block' | 'pure'; } export const Highlighter = memo( diff --git a/src/Highlighter/style.ts b/src/Highlighter/style.ts index 9583f9ab..6c399ffb 100644 --- a/src/Highlighter/style.ts +++ b/src/Highlighter/style.ts @@ -1,7 +1,7 @@ import { createStyles } from 'antd-style'; export const useStyles = createStyles( - ({ token, css, cx, prefixCls }, type: 'ghost' | 'block' | 'prue') => { + ({ token, css, cx, prefixCls }, type: 'ghost' | 'block' | 'pure') => { const prefix = `${prefixCls}-highlighter`; const buttonHoverCls = `${prefix}-hover-btn`; const langHoverCls = `${prefix}-hover-lang`; @@ -11,7 +11,7 @@ export const useStyles = createStyles( border: 1px solid ${type === 'block' ? 'transparent' : token.colorBorder}; &:hover { - background-color: ${type === 'prue' ? 'transparent' : token.colorFillTertiary}; + background-color: ${type === 'pure' ? 'transparent' : token.colorFillTertiary}; } `; @@ -41,7 +41,7 @@ export const useStyles = createStyles( pre { margin: 0 !important; - padding: ${type === 'prue' ? 0 : `16px 24px`} !important; + padding: ${type === 'pure' ? 0 : `16px 24px`} !important; background: none !important; } diff --git a/src/index.ts b/src/index.ts index 1730fc61..2e79c84f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ export { default as ActionIcon, type ActionIconProps, type ActionIconSize } from './ActionIcon'; export { default as Avatar, type AvatarProps } from './Avatar'; export type { ChatMessage, MessageRoleType } from './Chat'; +export { default as ChatItem, type ChatItemProps } from './ChatItem'; +export { default as ChatList, type ChatListProps } from './ChatList'; export { default as ColorScales, type ColorScalesProps } from './ColorScales'; export { default as ContextMenu, type ContextMenuProps } from './ContextMenu'; export { default as Conversation, type ConversationProps } from './Conversation'; diff --git a/src/styles/algorithms/generateCustomStylish.ts b/src/styles/algorithms/generateCustomStylish.ts index e530b7ed..ea454fff 100644 --- a/src/styles/algorithms/generateCustomStylish.ts +++ b/src/styles/algorithms/generateCustomStylish.ts @@ -24,13 +24,18 @@ export const generateCustomStylish: GetCustomStylish = ({ } p { - margin: 20px auto; + margin-block-start: 0; + margin-block-end: 0; font-size: 14px; line-height: 1.8; color: ${token.colorText}; text-align: justify; word-wrap: break-word; + + + * { + margin-block-end: 0.5em; + } } blockquote {