Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: lobeUI ChatItem & EditableMessage #17

Merged
merged 1 commit into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@
"react-layout-kit": "^1",
"shiki": "^1.10.3",
"swr": "^2.2.5",
"url-join": "^5.0.0"
"url-join": "^5.0.0",
"use-merge-value": "^1.2.0"
},
"devDependencies": {
"@testing-library/react": "^14",
Expand Down
151 changes: 115 additions & 36 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions src/ChatItem/components/MessageContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { MarkdownProps } from '@lobehub/ui';
import { useResponsive } from 'antd-style';
import { type ReactNode, memo } from 'react';
import { Flexbox } from 'react-layout-kit';

import EditableMessage from '@/EditableMessage';

import { useStyles } from '../style';
import { ChatItemProps } from '../type';

export interface MessageContentProps {
editing?: ChatItemProps['editing'];
fontSize?: number;
message?: ReactNode;
messageExtra?: ChatItemProps['messageExtra'];
onChange?: ChatItemProps['onChange'];
onDoubleClick?: ChatItemProps['onDoubleClick'];
onEditingChange?: ChatItemProps['onEditingChange'];
placement?: ChatItemProps['placement'];
primary?: ChatItemProps['primary'];
renderMessage?: ChatItemProps['renderMessage'];
text?: ChatItemProps['text'];
type?: ChatItemProps['type'];
markdownProps?: MarkdownProps;
markdownClassname?: string;
}

const MessageContent = memo<MessageContentProps>(
({
editing,
onChange,
onEditingChange,
text,
message,
placement,
messageExtra,
renderMessage,
type,
primary,
onDoubleClick,
fontSize,
markdownProps,
markdownClassname,
}) => {
const { cx, styles } = useStyles({ editing, placement, primary, type });
const { mobile } = useResponsive();

const content = (
<EditableMessage
classNames={{ input: styles.editingInput, markdown: markdownClassname }}
editButtonSize={'small'}
editing={editing}
fontSize={fontSize}
fullFeaturedCodeBlock
markdownProps={markdownProps}
onChange={onChange}
onEditingChange={onEditingChange}
openModal={mobile ? editing : undefined}
text={text}
value={message ? String(message).trim() : ''}
/>
);
const messageContent = renderMessage ? renderMessage(content) : content;

return (
<Flexbox
className={cx(styles.message, editing && styles.editingContainer)}
onDoubleClick={onDoubleClick}
>
{messageContent}
{messageExtra && !editing ? (
<div className={styles.messageExtra}>{messageExtra}</div>
) : null}
</Flexbox>
);
}
);

export default MessageContent;
47 changes: 47 additions & 0 deletions src/ChatItem/demos/Alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Highlighter, StoryBook, useControls, useCreateStore } from '@lobehub/ui';

import ChatItem, { ChatItemProps } from '..';
import { avatar } from './data';

const demoError = {
details: {
exception:
'Validation filter failedId-f5aab7304f6c754804f70000Id-f5aab7304f6c754804f70000Id-f5aab7304f6c754804f70000Id-f5aab7304f6c754804f70000Id-f5aab7304f6c754804f70000Id-f5aab7304f6c754804f70000',
msgId:
'Id-f5aab7304f6c754804f70000Id-f5aab7304f6c754804f70000Id-f5aab7304f6c754804f70000Id-f5aab7304f6c754804f70000Id-f5aab7304f6c754804f70000',
},
reasons: [
{
language: 'en',
message: 'Validation filter failed',
},
],
};
export default () => {
const store = useCreateStore();
const control: ChatItemProps['error'] | any = useControls(
{
description: '',
message: 'Error',
type: {
options: ['success', 'info', 'warning', 'error'],
value: 'error',
},
},
{ store }
);

return (
<StoryBook levaStore={store}>
<ChatItem
avatar={avatar}
error={control}
errorMessage={
<Highlighter copyButtonSize={'small'} language={'json'} type={'pure'}>
{JSON.stringify(demoError, null, 2)}
</Highlighter>
}
/>
</StoryBook>
);
};
61 changes: 61 additions & 0 deletions src/ChatItem/demos/Tmarkdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { ActionIconGroup, StoryBook, useControls, useCreateStore } from '@lobehub/ui';
import { useState } from 'react';

import ChatItem, { ChatItemProps } from '..';
import { content } from '../../EditableMessage/demos/data';
import { avatar, dropdownMenu, items } from './data';

export default () => {
const [edit, setEdit] = useState(false);
const store = useCreateStore();
const control: ChatItemProps | any = useControls(
{
loading: false,
message: {
rows: true,
value: content,
},
placement: {
options: ['left', 'right'],
value: 'left',
},
primary: false,
showTitle: false,
time: 1_686_538_950_084,
type: {
options: ['block', 'pure'],
value: 'block',
},
markdownProps: {
fontSize: 15,
lineHeight: 1.625,
headerMultiple: 0,
},
markdownClassname: 'customMarkdown',
},
{ store }
);

return (
<StoryBook levaStore={store}>
<ChatItem
{...control}
actions={
<ActionIconGroup
dropdownMenu={dropdownMenu}
items={items}
onActionClick={action => {
if (action.key === 'edit') {
setEdit(true);
}
}}
type="ghost"
/>
}
avatar={avatar}
editing={edit}
onEditingChange={setEdit}
/>
</StoryBook>
);
};
37 changes: 37 additions & 0 deletions src/ChatItem/demos/data.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { type ActionIconGroupProps, MetaData } from '@lobehub/ui';
import { Copy, Edit, RotateCw, Trash } from 'lucide-react';

export const avatar: MetaData = {
avatar: '😎',
backgroundColor: '#E8DA5A',
title: 'Advertiser',
};

export const items: ActionIconGroupProps['items'] = [
{
icon: Edit,
key: 'edit',
label: 'Edit',
},
];

export const dropdownMenu: ActionIconGroupProps['dropdownMenu'] = [
{
icon: Copy,
key: 'copy',
label: 'Copy',
},
{
icon: RotateCw,
key: 'regenerate',
label: 'Regenerate',
},
{
type: 'divider',
},
{
icon: Trash,
key: 'delete',
label: 'Delete',
},
];
55 changes: 55 additions & 0 deletions src/ChatItem/demos/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ActionIconGroup, StoryBook, useControls, useCreateStore } from '@lobehub/ui';
import { useState } from 'react';

import ChatItem, { ChatItemProps } from '..';
import { avatar, dropdownMenu, items } from './data';

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

return (
<StoryBook levaStore={store}>
<ChatItem
{...control}
actions={
<ActionIconGroup
dropdownMenu={dropdownMenu}
items={items}
onActionClick={action => {
if (action.key === 'edit') {
setEdit(true);
}
}}
type="ghost"
/>
}
avatar={avatar}
editing={edit}
onEditingChange={setEdit}
/>
</StoryBook>
);
};
22 changes: 22 additions & 0 deletions src/ChatItem/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
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>

## Markdown

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

## Alert

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

## APIs

<API></API>
Loading
Loading