Skip to content

Commit

Permalink
feat(chat): ability to provide template as chat title (#2920)
Browse files Browse the repository at this point in the history
  • Loading branch information
katebatura committed Nov 17, 2021
1 parent f94fd77 commit 9ccec64
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 35 deletions.
6 changes: 6 additions & 0 deletions src/app/playground-components.ts
Expand Up @@ -480,6 +480,12 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'ChatCustomMessageComponent',
name: 'Chat Custom Message',
},
{
path: 'chat-template-title.component',
link: '/chat/chat-template-title.component',
component: 'ChatTemplateTitleComponent',
name: 'Chat Template Title',
},
],
},
{
Expand Down
96 changes: 96 additions & 0 deletions src/framework/theme/components/chat/chat-title.directive.spec.ts
@@ -0,0 +1,96 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Component } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import { NbThemeModule, NbChatModule, NbChatComponent } from '@nebular/theme';

@Component({
template: `
<nb-chat [title]="title">
<ng-template nbChatTitle [context]="{ text: contextTemplateText }" let-data>
{{ staticTemplateText }} {{ data.text }}
</ng-template>
<nb-chat-message
*ngFor="let msg of messages"
[type]="msg.type"
[message]="msg.text"
[reply]="msg.reply"
[sender]="msg.user.name"
[date]="msg.date"
[avatar]="msg.user.avatar"
[customMessageData]="msg.optionalData"
>
<div *nbCustomMessage="'link'; let data">
<a [href]="data.href">{{ data.label }}</a>
</div>
</nb-chat-message>
<nb-chat-form [dropFiles]="false"> </nb-chat-form>
</nb-chat>
`,
})
export class NbChatTitleTemplateTestComponent {
messages = [
{
reply: false,
type: 'link',
optionalData: {
href: 'https://akveo.github.io/ngx-admin/',
label: 'Visit Akveo Nebular',
},
date: new Date(),
user: {
name: 'Frodo Baggins',
avatar: 'https://i.gifer.com/no.gif',
},
},
{
text: 'Hello, how are you?',
reply: true,
type: 'text',
date: new Date(),
user: {
name: 'Bilbo Baggins',
avatar: '',
},
},
];

title = 'chat title';
staticTemplateText = 'staticTemplateText';
contextTemplateText = 'contextTemplateText';
}

describe('NbChatTitleDirective', () => {
let fixture: ComponentFixture<NbChatTitleTemplateTestComponent>;
let testComponent: NbChatTitleTemplateTestComponent;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule, NbThemeModule.forRoot(), NbChatModule],
declarations: [NbChatTitleTemplateTestComponent],
});

fixture = TestBed.createComponent(NbChatTitleTemplateTestComponent);
testComponent = fixture.componentInstance;
fixture.detectChanges();
});

it('should render title template if provided', () => {
const chatHeaderElement: HTMLElement = fixture.debugElement
.query(By.directive(NbChatComponent))
.query(By.css('.header')).nativeElement;
const expectedText = ` ${testComponent.staticTemplateText} ${testComponent.contextTemplateText} `;

expect(chatHeaderElement.textContent).toEqual(expectedText);
});

it('should not render text title if template title is provided', () => {
const chatHeaderElement: HTMLElement = fixture.debugElement
.query(By.directive(NbChatComponent))
.query(By.css('.header')).nativeElement;

expect(chatHeaderElement.textContent).not.toContain(testComponent.title);
});
});
10 changes: 10 additions & 0 deletions src/framework/theme/components/chat/chat-title.directive.ts
@@ -0,0 +1,10 @@
import { Directive, Input, TemplateRef } from '@angular/core';

@Directive({
selector: `[nbChatTitle]`,
})
export class NbChatTitleDirective {
@Input() context: Object = {};

constructor(public templateRef: TemplateRef<any>) {}
}
18 changes: 17 additions & 1 deletion src/framework/theme/components/chat/chat.component.ts
Expand Up @@ -26,6 +26,7 @@ import { convertToBoolProperty, NbBooleanInput } from '../helpers';
import { NbChatFormComponent } from './chat-form.component';
import { NbChatMessageComponent } from './chat-message.component';
import { NbChatCustomMessageService } from './chat-custom-message.service';
import { NbChatTitleDirective } from './chat-title.directive';

/**
* Conversational UI collection - a set of components for chat-like UI construction.
Expand Down Expand Up @@ -101,6 +102,9 @@ import { NbChatCustomMessageService } from './chat-custom-message.service';
* </nb-chat-message> // chat message, available multiple types
* ```
*
* You could provide a chat title as a template via the `nbChatTitle` directive. It overrides `title` input.
* @stacked-example(Custom title, chat/chat-template-title.component)
*
* Two users conversation showcase:
* @stacked-example(Conversation, chat/chat-conversation-showcase.component)
*
Expand Down Expand Up @@ -237,7 +241,18 @@ import { NbChatCustomMessageService } from './chat-custom-message.service';
selector: 'nb-chat',
styleUrls: ['./chat.component.scss'],
template: `
<div class="header">{{ title }}</div>
<div class="header">
<ng-container
*ngIf="titleTemplate; else textTitleTemplate"
[ngTemplateOutlet]="titleTemplate.templateRef"
[ngTemplateOutletContext]="{ $implicit: titleTemplate.context }"
>
</ng-container>
<ng-template #textTitleTemplate>
{{ title }}
</ng-template>
</div>
<div class="scrollable" #scrollable>
<div class="messages">
<ng-content select="nb-chat-message"></ng-content>
Expand Down Expand Up @@ -283,6 +298,7 @@ export class NbChatComponent implements OnChanges, AfterContentInit, AfterViewIn
@ViewChild('scrollable') scrollable: ElementRef;
@ContentChildren(NbChatMessageComponent) messages: QueryList<NbChatMessageComponent>;
@ContentChild(NbChatFormComponent) chatForm: NbChatFormComponent;
@ContentChild(NbChatTitleDirective) titleTemplate: NbChatTitleDirective;

constructor(protected statusService: NbStatusService) {}

Expand Down
31 changes: 7 additions & 24 deletions src/framework/theme/components/chat/chat.module.ts
Expand Up @@ -21,6 +21,7 @@ import { NbChatMessageMapComponent } from './chat-message-map.component';
import { NbChatOptions } from './chat.options';
import { NbChatAvatarComponent } from './chat-avatar.component';
import { NbChatCustomMessageDirective } from './chat-custom-message.directive';
import { NbChatTitleDirective } from './chat-title.directive';

const NB_CHAT_COMPONENTS = [
NbChatComponent,
Expand All @@ -33,43 +34,25 @@ const NB_CHAT_COMPONENTS = [
NbChatAvatarComponent,
];

const NB_CHAT_DIRECTIVES = [
NbChatCustomMessageDirective,
];
const NB_CHAT_DIRECTIVES = [NbChatCustomMessageDirective, NbChatTitleDirective];

@NgModule({
imports: [
NbSharedModule,
NbIconModule,
NbInputModule,
NbButtonModule,
],
declarations: [
...NB_CHAT_COMPONENTS,
...NB_CHAT_DIRECTIVES,
],
exports: [
...NB_CHAT_COMPONENTS,
...NB_CHAT_DIRECTIVES,
],
imports: [NbSharedModule, NbIconModule, NbInputModule, NbButtonModule],
declarations: [...NB_CHAT_COMPONENTS, ...NB_CHAT_DIRECTIVES],
exports: [...NB_CHAT_COMPONENTS, ...NB_CHAT_DIRECTIVES],
})
export class NbChatModule {

static forRoot(options?: NbChatOptions): ModuleWithProviders<NbChatModule> {
return {
ngModule: NbChatModule,
providers: [
{ provide: NbChatOptions, useValue: options || {} },
],
providers: [{ provide: NbChatOptions, useValue: options || {} }],
};
}

static forChild(options?: NbChatOptions): ModuleWithProviders<NbChatModule> {
return {
ngModule: NbChatModule,
providers: [
{ provide: NbChatOptions, useValue: options || {} },
],
providers: [{ provide: NbChatOptions, useValue: options || {} }],
};
}
}
1 change: 1 addition & 0 deletions src/framework/theme/public_api.ts
Expand Up @@ -115,6 +115,7 @@ export * from './components/chat/chat.options';
export * from './components/chat/chat-avatar.component';
export * from './components/chat/chat-custom-message.directive';
export * from './components/chat/chat-custom-message.service';
export * from './components/chat/chat-title.directive';
export * from './components/spinner/spinner.component';
export * from './components/spinner/spinner.directive';
export * from './components/spinner/spinner.module';
Expand Down
11 changes: 8 additions & 3 deletions src/playground/with-layout/chat/chat-routing.module.ts
Expand Up @@ -5,7 +5,7 @@
*/

import { NgModule } from '@angular/core';
import { RouterModule, Route} from '@angular/router';
import { RouterModule, Route } from '@angular/router';
import { ChatColorsComponent } from './chat-colors.component';
import { ChatConversationShowcaseComponent } from './chat-conversation-showcase.component';
import { ChatDropComponent } from './chat-drop.component';
Expand All @@ -14,6 +14,7 @@ import { ChatShowcaseComponent } from './chat-showcase.component';
import { ChatSizesComponent } from './chat-sizes.component';
import { ChatTestComponent } from './chat-test.component';
import { ChatCustomMessageComponent } from './chat-custom-message.component';
import { ChatTemplateTitleComponent } from './chat-template-title.component';

const routes: Route[] = [
{
Expand Down Expand Up @@ -48,10 +49,14 @@ const routes: Route[] = [
path: 'chat-custom-message.component',
component: ChatCustomMessageComponent,
},
{
path: 'chat-template-title.component',
component: ChatTemplateTitleComponent,
},
];

@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ],
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ChatRoutingModule {}
21 changes: 21 additions & 0 deletions src/playground/with-layout/chat/chat-template-title.component.html
@@ -0,0 +1,21 @@
<nb-chat size="large">
<ng-template nbChatTitle [context]="{ text: 'some text to pass into template' }" let-data>
<div>Chat title content from template. Here is the text provided via context: "{{ data.text }}"</div>
</ng-template>

<nb-chat-message
*ngFor="let msg of messages"
[type]="msg.type"
[message]="msg.text"
[reply]="msg.reply"
[sender]="msg.user.name"
[date]="msg.date"
[files]="msg.files"
[quote]="msg.quote"
[latitude]="msg.latitude"
[longitude]="msg.longitude"
[avatar]="msg.user.avatar"
>
</nb-chat-message>
<nb-chat-form (send)="sendMessage($event)" [dropFiles]="true"> </nb-chat-form>
</nb-chat>
61 changes: 61 additions & 0 deletions src/playground/with-layout/chat/chat-template-title.component.ts
@@ -0,0 +1,61 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { Component } from '@angular/core';
import { ChatShowcaseService } from './chat-showcase.service';

@Component({
templateUrl: './chat-template-title.component.html',
providers: [ChatShowcaseService],
styles: [
`
::ng-deep nb-layout-column {
justify-content: center;
display: flex;
}
nb-chat {
width: 500px;
}
`,
],
})
export class ChatTemplateTitleComponent {
messages: any[];

constructor(protected chatShowcaseService: ChatShowcaseService) {
this.messages = this.chatShowcaseService.loadMessages();
}

sendMessage(event: any) {
const files = !event.files
? []
: event.files.map((file) => {
return {
url: file.src,
type: file.type,
icon: 'file-text-outline',
};
});

this.messages.push({
text: event.message,
date: new Date(),
reply: true,
type: files.length ? 'file' : 'text',
files: files,
user: {
name: 'Jonh Doe',
avatar: 'https://i.gifer.com/no.gif',
},
});
const botReply = this.chatShowcaseService.reply(event.message);
if (botReply) {
setTimeout(() => {
this.messages.push(botReply);
}, 500);
}
}
}
10 changes: 3 additions & 7 deletions src/playground/with-layout/chat/chat.module.ts
Expand Up @@ -18,6 +18,7 @@ import { ChatSizesComponent } from './chat-sizes.component';
import { ChatTestComponent } from './chat-test.component';
import { ChatCustomMessageComponent } from './chat-custom-message.component';
import { ChatCustomMessageTableComponent } from './components/chat-custom-message-table.component';
import { ChatTemplateTitleComponent } from './chat-template-title.component';

@NgModule({
declarations: [
Expand All @@ -30,13 +31,8 @@ import { ChatCustomMessageTableComponent } from './components/chat-custom-messag
ChatTestComponent,
ChatCustomMessageComponent,
ChatCustomMessageTableComponent,
ChatTemplateTitleComponent,
],
imports: [
CommonModule,
NbChatModule.forRoot(),
NbCardModule,
NbButtonModule,
ChatRoutingModule,
],
imports: [CommonModule, NbChatModule.forRoot(), NbCardModule, NbButtonModule, ChatRoutingModule],
})
export class ChatModule {}

0 comments on commit 9ccec64

Please sign in to comment.