Skip to content
Draft
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
40 changes: 40 additions & 0 deletions apps/react-storybook/stories/chat/Chat.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,46 @@ export const ControlledMode: Story = {
}
}

export const ClearButton: Story = {
args: {
showClearButton: true,
},
argTypes: {
showClearButton: {
control: 'boolean',
},
},
render: ({ showClearButton }) => {
const [messages, setMessages] = useState<ChatTypes.Message[]>([...initialMessages]);

const onMessageEntered = useCallback(({ message }: ChatTypes.MessageEnteredEvent) => {
setMessages((prev) => [...prev, message]);
}, []);

const onClearButtonClick = useCallback(() => {
setMessages([]);
}, []);

return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}>
<Chat
width={400}
height={500}
items={messages}
user={secondAuthor}
onMessageEntered={onMessageEntered}
showClearButton={showClearButton}
onClearButtonClick={onClearButtonClick}
/>
</div>
);
},
};

export const SendButtonOptions: Story = {
args: {
action: 'send',
Expand Down
21 changes: 21 additions & 0 deletions packages/devextreme-angular/src/ui/chat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,19 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges
}


/**
* [descr:dxChatOptions.showClearButton]

*/
@Input()
get showClearButton(): boolean {
return this._getOption('showClearButton');
}
set showClearButton(value: boolean) {
this._setOption('showClearButton', value);
}


/**
* [descr:dxChatOptions.showDayHeaders]

Expand Down Expand Up @@ -784,6 +797,13 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges
*/
@Output() showAvatarChange: EventEmitter<boolean>;

/**

* This member supports the internal infrastructure and is not intended to be used directly from your code.

*/
@Output() showClearButtonChange: EventEmitter<boolean>;

/**

* This member supports the internal infrastructure and is not intended to be used directly from your code.
Expand Down Expand Up @@ -903,6 +923,7 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges
{ emit: 'rtlEnabledChange' },
{ emit: 'sendButtonOptionsChange' },
{ emit: 'showAvatarChange' },
{ emit: 'showClearButtonChange' },
{ emit: 'showDayHeadersChange' },
{ emit: 'showMessageTimestampChange' },
{ emit: 'showUserNameChange' },
Expand Down
3 changes: 3 additions & 0 deletions packages/devextreme-vue/src/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ type AccessibleOptions = Pick<Properties,
"rtlEnabled" |
"sendButtonOptions" |
"showAvatar" |
"showClearButton" |
"showDayHeaders" |
"showMessageTimestamp" |
"showUserName" |
Expand Down Expand Up @@ -181,6 +182,7 @@ const componentConfig = {
rtlEnabled: Boolean,
sendButtonOptions: Object as PropType<SendButtonProperties | Record<string, any>>,
showAvatar: Boolean,
showClearButton: Boolean,
showDayHeaders: Boolean,
showMessageTimestamp: Boolean,
showUserName: Boolean,
Expand Down Expand Up @@ -231,6 +233,7 @@ const componentConfig = {
"update:rtlEnabled": null,
"update:sendButtonOptions": null,
"update:showAvatar": null,
"update:showClearButton": null,
"update:showDayHeaders": null,
"update:showMessageTimestamp": null,
"update:showUserName": null,
Expand Down
33 changes: 33 additions & 0 deletions packages/devextreme/js/__internal/ui/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { getGlobalFormatByDataType } from '@ts/core/m_global_format_config';
import { invokeConditionally } from '@ts/core/utils/conditional_invoke';
import type { OptionChanged } from '@ts/core/widget/types';
import Widget from '@ts/core/widget/widget';
import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types';
import AlertList from '@ts/ui/chat/alertlist';
import ConfirmationPopup from '@ts/ui/chat/confirmationpopup';
import type {
Expand All @@ -45,6 +46,8 @@ import MessageList from '@ts/ui/chat/messagelist';
import Suggestions, { type SuggestionsOptions } from '@ts/ui/chat/suggestions';
import type { DataChange } from '@ts/ui/collection/collection_widget.base';

import Toolbar from '../toolbar/toolbar';

const CHAT_CLASS = 'dx-chat';
const TEXTEDITOR_INPUT_CLASS = 'dx-texteditor-input';

Expand All @@ -53,6 +56,8 @@ class Chat extends Widget<ChatProperties> {

_messageList!: MessageList;

_toolbar?: Toolbar;

_alertList!: AlertList;

_suggestions?: Suggestions;
Expand Down Expand Up @@ -130,6 +135,8 @@ class Chat extends Widget<ChatProperties> {
onTypingStart: undefined,
onAttachmentDownloadClick: undefined,
onInputFieldTextChanged: undefined,
showClearButton: false,
onClearButtonClick: undefined,
};
}

Expand Down Expand Up @@ -184,10 +191,15 @@ class Chat extends Widget<ChatProperties> {
}

_initMarkup(): void {
const { showClearButton } = this.option();
$(this.element()).addClass(CHAT_CLASS);

super._initMarkup();

if (showClearButton) {
this._renderToolbar();
}

this._renderMessageList();
this._renderAlertList();
this._renderSuggestions();
Expand Down Expand Up @@ -504,6 +516,27 @@ class Chat extends Widget<ChatProperties> {
return config;
}

_getClearButtonConfig(): ToolbarItem {
const { onClearButtonClick } = this.option();
return {
widget: 'dxButton',
location: 'after',
options: {
icon: 'trash',
onClick: (): void => {
onClearButtonClick?.();
},
},
} as ToolbarItem;
}

_renderToolbar(): void {
// @ts-expect-error Toolbar typings?
this._toolbar = new Toolbar(this.$element(), {
items: [this._getClearButtonConfig()],
});
}

_renderSuggestions(): void {
this._suggestions = new Suggestions(this.$element(), this._getSuggestionsConfiguration());
}
Expand Down
14 changes: 14 additions & 0 deletions packages/devextreme/js/ui/chat.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,20 @@ export interface dxChatOptions extends WidgetOptions<dxChat> {
* @public
*/
onTypingStart?: ((e: TypingStartEvent) => void) | undefined;
/**
* @docid
* @default undefined
* @public
*/
showClearButton?: boolean;
/**
* @docid
* @default undefined
* @type_function_param1 undefined
* @action
* @public
*/
onClearButtonClick?: (() => void) | undefined;
}

/**
Expand Down
5 changes: 3 additions & 2 deletions packages/devextreme/playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
<title>DevExtreme Playground</title>
</head>
<body class="dx-viewport">
<div id="app">
<div class="demo-container">
<select id="theme-selector"></select>
<div id="widget-container"></div>
<div id="user-chat"></div>
<div id="support-chat"></div>
</div>
<script type="module" src="./index.ts"></script>
</body>
Expand Down
137 changes: 113 additions & 24 deletions packages/devextreme/playground/index.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,121 @@
import '../js/__internal/integration/jquery';
import '../js/ui/card_view';
import '../js/ui/chat';
import $ from 'jquery';
import { setupThemeSelector } from './themeSelector.ts';

const customers = [
{ ID: 1, Company: 'Super Mart of the West', Address: '702 SW 8th Street', City: 'Bentonville', State: 'Arkansas', Zipcode: 72716, Phone: '(800) 555-2797' },
{ ID: 2, Company: 'Electronics Depot', Address: '2455 Paces Ferry Road NW', City: 'Atlanta', State: 'Georgia', Zipcode: 30339, Phone: '(800) 595-3232' },
{ ID: 3, Company: 'K&S Music', Address: '1000 Nicllet Mall', City: 'Minneapolis', State: 'Minnesota', Zipcode: 55403, Phone: '(612) 304-6073' },
{ ID: 4, Company: "Tom's Club", Address: '999 Lake Drive', City: 'Issaquah', State: 'Washington', Zipcode: 98027, Phone: '(800) 955-2292' },
{ ID: 5, Company: 'E-Mart', Address: '3333 Beverly Rd', City: 'Hoffman Estates', State: 'Illinois', Zipcode: 60179, Phone: '(847) 286-2500' },
{ ID: 6, Company: 'Walters', Address: '200 Wilmot Rd', City: 'Deerfield', State: 'Illinois', Zipcode: 60015, Phone: '(847) 940-2500' },
{ ID: 7, Company: 'StereoShack', Address: '400 Commerce S', City: 'Fort Worth', State: 'Texas', Zipcode: 76102, Phone: '(817) 820-0741' },
{ ID: 8, Company: 'Circuit Town', Address: '2200 Kensington Court', City: 'Oak Brook', State: 'Illinois', Zipcode: 60523, Phone: '(800) 955-2929' },
{ ID: 9, Company: 'Premier Buy', Address: '7601 Penn Avenue South', City: 'Richfield', State: 'Minnesota', Zipcode: 55423, Phone: '(612) 291-1000' },
{ ID: 10, Company: 'ElectrixMax', Address: '263 Shuman Blvd', City: 'Naperville', State: 'Illinois', Zipcode: 60563, Phone: '(630) 438-7800' },
{ ID: 11, Company: 'Video Emporium', Address: '1201 Elm Street', City: 'Dallas', State: 'Texas', Zipcode: 75270, Phone: '(214) 854-3000' },
{ ID: 12, Company: 'Screen Shop', Address: '1000 Lowes Blvd', City: 'Mooresville', State: 'North Carolina', Zipcode: 28117, Phone: '(800) 445-6937' },
//data.js
const getTimestamp = function (date, offsetMinutes = 0) {
return date.getTime() + offsetMinutes * 60000;
};

const date = new Date();
date.setHours(0, 0, 0, 0);

const currentUser = {
id: 'c94c0e76-fb49-4b9b-8f07-9f93ed93b4f3',
name: 'John Doe',
};

const supportAgent = {
id: 'd16d1a4c-5c67-4e20-b70e-2991c22747c3',
name: 'Support Agent',
avatarUrl: 'images/petersmith.png',
};

let initialMessages = [
{
timestamp: getTimestamp(date, -9),
author: supportAgent,
text: 'Hello, John!\nHow can I assist you today?',
},
{
timestamp: getTimestamp(date, -7),
author: currentUser,
text: "Hi, I'm having trouble accessing my account.",
},
{
timestamp: getTimestamp(date, -7),
author: currentUser,
text: 'It says my password is incorrect.',
},
{
timestamp: getTimestamp(date, -7),
author: supportAgent,
text: 'I can help you with that. Can you please confirm your UserID for security purposes?',
},
{
timestamp: getTimestamp(date, 1),
author: currentUser,
text: 'john.doe1357',
},
{
timestamp: getTimestamp(date, 1),
author: supportAgent,
text: '✅ Instructions to restore access have been sent to the email address associated with your account.',
},
];

window.addEventListener('load', () =>
const initialMessagesForSupport = initialMessages.slice();




function onWindowLoad() {
$(() => {
let userChat;

function onMessageEntered({ message }) {
userChat.renderMessage(message);
supportChat.renderMessage(message);
}

function userChatTypingStart() {
supportChat.option('typingUsers', [currentUser]);
}

function userChatTypingEnd() {
supportChat.option('typingUsers', []);
}

function supportChatTypingStart() {
userChat.option('typingUsers', [supportAgent]);
}

function supportChatTypingEnd() {
userChat.option('typingUsers', []);
}

const removeMessages = () => {
initialMessages = [];
userChat.option('items', []);
}


userChat = $('#user-chat').dxChat({
items: initialMessages,
user: currentUser,
speechToTextEnabled: true,
onMessageEntered,
onTypingStart: userChatTypingStart,
onTypingEnd: userChatTypingEnd,
showClearButton: true,
onClearButtonClick: removeMessages,
}).dxChat('instance');

const supportChat = $('#support-chat').dxChat({
items: initialMessagesForSupport,
user: supportAgent,
speechToTextEnabled: true,
onMessageEntered,
onTypingStart: supportChatTypingStart,
onTypingEnd: supportChatTypingEnd,
}).dxChat('instance');
});

}

window.addEventListener('load', () => {
setupThemeSelector('theme-selector')
.catch((err) => console.error('Theme loading failed:', err))
.then(() => {
$('#widget-container').dxCardView({
dataSource: customers,
keyExpr: 'ID',
cardsPerRow: 'auto',
cardMinWidth: 320,
columns: ['Company', 'Address', 'City', 'State', 'Zipcode', 'Phone'],
});
}));
.then(() => onWindowLoad())
});
8 changes: 8 additions & 0 deletions packages/devextreme/ts/dx.all.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11851,6 +11851,14 @@ declare module DevExpress.ui {
onTypingStart?:
| ((e: DevExpress.ui.dxChat.TypingStartEvent) => void)
| undefined;
/**
* [descr:dxChatOptions.showClearButton]
*/
showClearButton?: boolean;
/**
* [descr:dxChatOptions.onClearButtonClick]
*/
onClearButtonClick?: (() => void) | undefined;
}
/**
* [descr:dxCheckBox]
Expand Down
Loading