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(chat): ability to provide template as chat title #2920

Merged
merged 8 commits into from
Nov 17, 2021
6 changes: 6 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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 {}