diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index f19ee9c5..53ff6bed 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -4,12 +4,14 @@ on: branches: - master - beta + - theming-v2 pull_request: branches: - master - beta + - theming-v2 jobs: - test: + workflow: runs-on: ubuntu-latest strategy: matrix: @@ -55,6 +57,8 @@ jobs: GH_TOKEN: ${{ secrets.GH_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release + - name: Copy docs from stream-chat-css + run: npm run copy-css-docs - name: Generate docs run: | npm run generate-docs diff --git a/.gitignore b/.gitignore index c111caf4..ffc4ddf3 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,8 @@ testem.log temp-service-docs temp-component-docs docusaurus/docs/Angular/services/*.mdx +docusaurus/docs/Angular/theming/* +docusaurus/docs/Angular/assets/stream-chat-css* # System Files .DS_Store diff --git a/copy-css-docs.sh b/copy-css-docs.sh new file mode 100755 index 00000000..e3a6cdc8 --- /dev/null +++ b/copy-css-docs.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +usage() { + echo "Missing path to stream-chat-css directory" + echo "Usage: $(basename $0) " +} + +main() { + if [ $# -eq 0 ]; then + usage + exit 0 + fi + + STREAM_CHAT_CSS_DOCS_PATH=$1; + cp -r "$STREAM_CHAT_CSS_DOCS_PATH"/* ./docusaurus/docs/Angular; +} + + +main $* +exit 0 diff --git a/docusaurus/docs/Angular/assets/attachment-preview-list-screenshot.png b/docusaurus/docs/Angular/assets/attachment-preview-list-screenshot.png index d20028e4..0a148e08 100644 Binary files a/docusaurus/docs/Angular/assets/attachment-preview-list-screenshot.png and b/docusaurus/docs/Angular/assets/attachment-preview-list-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/attachments-screenshot.png b/docusaurus/docs/Angular/assets/attachments-screenshot.png index 88eb3075..f12f8d52 100644 Binary files a/docusaurus/docs/Angular/assets/attachments-screenshot.png and b/docusaurus/docs/Angular/assets/attachments-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/channel-header-screenshot.png b/docusaurus/docs/Angular/assets/channel-header-screenshot.png index adcea80b..e4f7c3bd 100644 Binary files a/docusaurus/docs/Angular/assets/channel-header-screenshot.png and b/docusaurus/docs/Angular/assets/channel-header-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/channel-header-with-menu-screenshot.png b/docusaurus/docs/Angular/assets/channel-header-with-menu-screenshot.png new file mode 100644 index 00000000..24973cd0 Binary files /dev/null and b/docusaurus/docs/Angular/assets/channel-header-with-menu-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/channel-invites-screenshot.png b/docusaurus/docs/Angular/assets/channel-invites-screenshot.png index c87cfd6c..3a15dde9 100644 Binary files a/docusaurus/docs/Angular/assets/channel-invites-screenshot.png and b/docusaurus/docs/Angular/assets/channel-invites-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/chat-ui-layout-screenshot.png b/docusaurus/docs/Angular/assets/chat-ui-layout-screenshot.png new file mode 100644 index 00000000..edf1655a Binary files /dev/null and b/docusaurus/docs/Angular/assets/chat-ui-layout-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/emoji-picker-screenshot.png b/docusaurus/docs/Angular/assets/emoji-picker-screenshot.png index bed6bb82..893b6a1c 100644 Binary files a/docusaurus/docs/Angular/assets/emoji-picker-screenshot.png and b/docusaurus/docs/Angular/assets/emoji-picker-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/invite-button-screenshot.png b/docusaurus/docs/Angular/assets/invite-button-screenshot.png index 20fefa63..4452360f 100644 Binary files a/docusaurus/docs/Angular/assets/invite-button-screenshot.png and b/docusaurus/docs/Angular/assets/invite-button-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/invite-modal1-screenshot.png b/docusaurus/docs/Angular/assets/invite-modal1-screenshot.png index aa85ba66..6ff9d2bf 100644 Binary files a/docusaurus/docs/Angular/assets/invite-modal1-screenshot.png and b/docusaurus/docs/Angular/assets/invite-modal1-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/invite-modal2-screenshot.png b/docusaurus/docs/Angular/assets/invite-modal2-screenshot.png index 57db6ca7..2c417ed3 100644 Binary files a/docusaurus/docs/Angular/assets/invite-modal2-screenshot.png and b/docusaurus/docs/Angular/assets/invite-modal2-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/message-actions-screenshot.png b/docusaurus/docs/Angular/assets/message-actions-screenshot.png index 7f062e4f..f688bda8 100644 Binary files a/docusaurus/docs/Angular/assets/message-actions-screenshot.png and b/docusaurus/docs/Angular/assets/message-actions-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/message-reactions-screenshot.png b/docusaurus/docs/Angular/assets/message-reactions-screenshot.png index f5643bdb..ec51fb92 100644 Binary files a/docusaurus/docs/Angular/assets/message-reactions-screenshot.png and b/docusaurus/docs/Angular/assets/message-reactions-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/message-reactions-selector-screenshot.png b/docusaurus/docs/Angular/assets/message-reactions-selector-screenshot.png index b4418c7e..82c2a427 100644 Binary files a/docusaurus/docs/Angular/assets/message-reactions-selector-screenshot.png and b/docusaurus/docs/Angular/assets/message-reactions-selector-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/message-screenshot.png b/docusaurus/docs/Angular/assets/message-screenshot.png index 4981181d..9f453159 100644 Binary files a/docusaurus/docs/Angular/assets/message-screenshot.png and b/docusaurus/docs/Angular/assets/message-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/messages-with-grouping-screenshot.png b/docusaurus/docs/Angular/assets/messages-with-grouping-screenshot.png index d2805d86..8e2d590e 100644 Binary files a/docusaurus/docs/Angular/assets/messages-with-grouping-screenshot.png and b/docusaurus/docs/Angular/assets/messages-with-grouping-screenshot.png differ diff --git a/docusaurus/docs/Angular/assets/messages-without-groups-screenshot.png b/docusaurus/docs/Angular/assets/messages-without-groups-screenshot.png index 9b7d454b..408fcb6d 100644 Binary files a/docusaurus/docs/Angular/assets/messages-without-groups-screenshot.png and b/docusaurus/docs/Angular/assets/messages-without-groups-screenshot.png differ diff --git a/docusaurus/docs/Angular/basics/upgrade-v3.mdx b/docusaurus/docs/Angular/basics/upgrade-v3.mdx new file mode 100644 index 00000000..ed588fcb --- /dev/null +++ b/docusaurus/docs/Angular/basics/upgrade-v3.mdx @@ -0,0 +1,48 @@ +--- +id: upgrade-v3 +title: Upgrade from v3 +--- + +## About version 4 + +Version 4 of stream-chat-angular brings a new and improved theming system. To use the new system please refer to our new [theming guide](../theming/introduction.mdx). + +## Theme-v1 + +The old theme can still be used with the latest SDK versions however, you might need to update your custom CSS as some changes were introduced to theme-v1. + +Updating from theme-v1 to theme-v2 will require to rewrite your custom CSS code as the new theme has a new variable system, and all the components were restructured. + +Theme-v1 is now deprecated and will be removed in a future release. + +## Theard layout + +Previously you had to provide a selector for the message list and message input component projected inside the [thread component](../components/ThreadComponent.mdx). + +```html + + + + +``` + +You don't need to provide those selectors anymore, anything inside the content part of `stream-thread` HTML element will be projected inside the thread component: + +```html + + + + +``` + +Providing the selector won't cause a problem, but it's unnecessary. + +## `device-width` module was removed + +The `device-width` utility module was removed. If you rely on this code inside your chat application, you'll have to update your code. diff --git a/docusaurus/docs/Angular/code-examples/_category_.json b/docusaurus/docs/Angular/code-examples/_category_.json index 5f766450..ada6e1cc 100644 --- a/docusaurus/docs/Angular/code-examples/_category_.json +++ b/docusaurus/docs/Angular/code-examples/_category_.json @@ -1,4 +1,4 @@ { "label": "Code examples", - "position": 6 + "position": 8 } diff --git a/docusaurus/docs/Angular/code-examples/channel-invites.mdx b/docusaurus/docs/Angular/code-examples/channel-invites.mdx index 7609d435..0cf8c747 100644 --- a/docusaurus/docs/Angular/code-examples/channel-invites.mdx +++ b/docusaurus/docs/Angular/code-examples/channel-invites.mdx @@ -50,7 +50,7 @@ We create a simplistic UI with an "Invite users" button that opens a [modal](../ ### Styling -We are using stream-chat theme variables to match the default chat theme. You can read more about theme variables in our [themeing guide](../concepts/themeing-and-css.mdx). +We are using stream-chat theme variables to match the default chat theme. You can read more about theme variables in our [theming guide](../theming/introduction.mdx). ```scss .modal-content { @@ -71,8 +71,13 @@ We are using stream-chat theme variables to match the default chat theme. You ca width: 200px; padding: 10px; border: none; - border-radius: var(--border-radius-md); - background-color: var(--grey-whisper); + background-color: var(--str-chat__message-textarea-background-color); + color: var(--str-chat__message-textarea-color); + border-radius: var(--str-chat__message-textarea-border-radius); + border-block-start: var(--str-chat__message-textarea-border-block-start); + border-block-end: var(--str-chat__message-textarea-border-block-end); + border-inline-start: var(--str-chat__message-textarea-border-inline-start); + border-inline-end: var(--str-chat__message-textarea-border-inline-end); } .invited-users { @@ -89,10 +94,10 @@ We are using stream-chat theme variables to match the default chat theme. You ca } button { - background-color: var(--primary-color); + background-color: var(--str-chat__cta-button-background-color); border: none; - border-radius: var(--border-radius-md); - color: white; + border-radius: var(--str-chat__cta-button-border-radius); + color: var(--str-chat__cta-button-color); padding: 10px; cursor: pointer; } @@ -391,13 +396,12 @@ Add this to your `app.component.html` file: Add a reference to the template in your `app.component.ts`: ```typescript -@ViewChild('inviteTemplate') private inviteTemplate!: TemplateRef<{ - channel: Channel | ChannelResponse; +@ViewChild('inviteTemplate') private inviteTemplate!: TemplateRef<{channel: Channel | ChannelResponse}>; ``` #### Displaying the invitations -The `pendingInvites$` Observable on the [`ChatClientService`](../services/ChatClientService.mdx) can notify us about the pending invitations of the current user. Let's subscribe to this Observable and display the invites in the `ngOnInit` method of the `app.component.ts` +The `pendingInvites$` Observable on the [`ChatClientService`](../services/ChatClientService.mdx) can notify us about the pending invitations of the current user. Let's subscribe to this Observable and [display the invites](../services/NotificationService.mdx) in the `ngOnInit` method of the `app.component.ts` ```typescript ngOnInit(): void { diff --git a/docusaurus/docs/Angular/code-examples/emoji-picker.mdx b/docusaurus/docs/Angular/code-examples/emoji-picker.mdx index 91136d4c..d9798a2c 100644 --- a/docusaurus/docs/Angular/code-examples/emoji-picker.mdx +++ b/docusaurus/docs/Angular/code-examples/emoji-picker.mdx @@ -65,26 +65,52 @@ Your emoji picker component should have an input with the type `Subject` We also defined an `isOpened` property that tells if the emoji picker should be opened or closed. +The emoji picker will close on outside clicks. + ```typescript -import { Component, Input, OnInit } from "@angular/core"; -import { Subject } from "rxjs"; +import { Component, ElementRef, Input, ViewChild } from "@angular/core"; +import { Observable, Subject } from "rxjs"; +import { ThemeService } from "stream-chat-angular"; @Component({ selector: "app-emoji-picker", templateUrl: "./emoji-picker.component.html", styleUrls: ["./emoji-picker.component.scss"], }) -export class EmojiPickerComponent implements OnInit { +export class EmojiPickerComponent { isOpened = false; + theme$: Observable; @Input() emojiInput$: Subject | undefined; + @ViewChild("container") container: ElementRef | undefined; - constructor() {} - - ngOnInit(): void {} + constructor(themeService: ThemeService) { + this.theme$ = themeService.theme$; + } emojiSelected(event: any) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access this.emojiInput$?.next(event.emoji.native); } + + eventHandler = (event: Event) => { + // Watching for outside clicks + if (!this.container?.nativeElement.contains(event.target as Node)) { + this.isOpened = false; + window.removeEventListener("click", this.eventHandler); + } + }; + + toggled() { + if (!this.container) { + return; + } + this.isOpened = !this.isOpened; + if (this.isOpened) { + window.addEventListener("click", this.eventHandler); + } else { + window.removeEventListener("click", this.eventHandler); + } + } } ``` @@ -94,59 +120,81 @@ We create a button that can be used to toggle the emoji picker. The [`emoji-mart`](https://www.npmjs.com/package/@ctrl/ngx-emoji-mart) component has a lot of configuration options, feel free to explore those. -We set the `color` input to the `primary-color` defined in the [stream-chat-css theme](https://github.com/GetStream/stream-chat-css/blob/develop/src/styles/_variables.scss). +We set the `color` input to `--str-chat__primary-color` defined in the [stream-chat-css theme](../theming/global-variables.mdx). The [`ngIf`](https://angular.io/api/common/NgIf) directive is used to hide/show the emoji picker. The `emojiSelect` event is fired when a user selects an emoji, we emit the selected emoji using the `emojiSelected` method. ```html - - - +
+ + + +
``` 5. Component styles -```scss -button { - background-color: transparent; - border: none; - cursor: pointer; - padding: 0; -} +If you want to match the color of the emoji picker toggle button to the other tool buttons in the message input, you can use the `--str-chat__message-input-tools-color` to do that as defined in the [stream-chat-css theme](../theming/global-variables.mdx) -.picker { - position: absolute; - bottom: 100%; - left: 0; - transform: scale(0.8); - transform-origin: 0% 100%; -} +```scss +.emoji-picker-container { + position: relative; + width: 24px; + height: 24px; + + button { + background-color: transparent; + border: none; + cursor: pointer; + padding: 0; + margin: 0; + + svg { + width: 24px; + height: 24px; + + path { + fill: var(--str-chat__message-input-tools-color); + } + } + } -@media only screen and (min-device-width: 1024px) { .picker { - transform: scale(1); + z-index: 3; + position: absolute; + bottom: 100%; + transform: scale(0.8); + right: 0; + transform-origin: bottom right; + } + + @media only screen and (min-device-width: 1024px) { + .picker { + transform: scale(1); + } } } ``` @@ -230,3 +278,31 @@ export class AppComponent implements AfterViewInit { This is how our emoji picker looks like: + +## Dark and light mode + +If your application supports dark and light themes, here how you can toggle the theme on the emoji input component: + +Add this to you emoji picker component class: + +```typescript +theme$: Observable; + +constructor(themeService: ThemeService) { + this.theme$ = themeService.theme$; +} +``` + +And set the `darkMode` input on the `emoji-mart` component: + +```html + +``` diff --git a/docusaurus/docs/Angular/code-examples/responsive-layout.mdx b/docusaurus/docs/Angular/code-examples/responsive-layout.mdx new file mode 100644 index 00000000..4f616672 --- /dev/null +++ b/docusaurus/docs/Angular/code-examples/responsive-layout.mdx @@ -0,0 +1,214 @@ +--- +id: responsive-layout +title: Responsive layout +--- + +import ChatUILayoutScreenshot from "../assets/chat-ui-layout-screenshot.png"; +import ChannelHeaderWithMenu from "../assets/channel-header-with-menu-screenshot.png"; + +:::caution +This example is only applicable if you're using [theme-v2](../theming/introduction.mdx). +::: + +Our SDK gives you maximum control over the layout of your chat application. This tutorial shows you a simple example of creating a layout. + +## Channel list, channel, and thread layout + +Let's create a simple chat application UI: + +```html +
+ + + + + + + + + + + +
+``` + +Let's add the layout for the [channel list](../components/ChannelListComponent.mdx), [channel](../components/ChannelComponent.mdx) and [thread](../components/ThreadComponent.mdx) components: + +```scss +#root { + display: flex; + height: 100%; + + .channel { + width: 100%; + } + + .channel-list { + width: 30%; + } + + .thread { + width: 45%; + } +} +``` + +This how our chat application looks like: + + + +This layout works fine on bigger screens, but we should create a separate layout for mobile devices. + +## Mobile layout + +On mobile screens, the thread and channel list components will overlay the channel component filling the whole screen. + +Let's start with the thread component: + +```scss +#root { + display: flex; + height: 100%; + + .channel { + width: 100%; + } + + // Overlay for thread + .thread { + width: 100%; + height: 100%; + position: fixed; + z-index: 2; + } + + @media screen and (min-width: 768px) { + .channel-list { + width: 30%; + } + + .thread { + width: 45%; + position: initial; + z-index: auto; + } + } +} +``` + +For the channel list component, we should add a menu button to toggle the channel list. + +The [ChannelHeader](../components/ChannelHeaderComponent.mdx) component has a slot where you can inject your own' menu button template: + +```html + + + +``` + +```scss +.menu-button { + border: none; + background-color: transparent; + cursor: pointer; + width: 50px; + height: 50px; +} +``` + +In your component class: + +```typescript +isMenuOpen = true; +``` + +This is what the channel header looks like: + + + +Let's add a class to the [ChannelList component](../components/ChannelListComponent.mdx) based on the menu state: + +```html + +``` + +Provide the layout based on the menu state and hide the menu on bigger screens: + +```scss +#root { + display: flex; + height: 100%; + + .menu-open { + width: 100%; + height: 100%; + position: fixed; + z-index: 1; + } + + .menu-close { + width: 0; + } + + .menu-button { + display: block; + } + + .channel { + min-width: 0; + width: 100%; + } + + .thread { + width: 100%; + height: 100%; + position: fixed; + z-index: 2; + } + + @media screen and (min-width: 768px) { + .menu-button { + display: none; + } + + .channel-list { + width: 30%; + max-width: 420px; + position: initial; + z-index: auto; + } + + .thread { + width: 45%; + position: initial; + z-index: auto; + } + } +} +``` + +Lastly, implement autoclose behavior for the channel list: + +```html + +``` diff --git a/docusaurus/docs/Angular/components/ChannelComponent.mdx b/docusaurus/docs/Angular/components/ChannelComponent.mdx index fcff94c5..3740cf97 100644 --- a/docusaurus/docs/Angular/components/ChannelComponent.mdx +++ b/docusaurus/docs/Angular/components/ChannelComponent.mdx @@ -9,22 +9,17 @@ The `Channel` component is a container component that displays the [`ChannelHead - + - - + + + ``` diff --git a/docusaurus/docs/Angular/components/ChannelHeaderComponent.mdx b/docusaurus/docs/Angular/components/ChannelHeaderComponent.mdx index c727e6ad..3e8211f4 100644 --- a/docusaurus/docs/Angular/components/ChannelHeaderComponent.mdx +++ b/docusaurus/docs/Angular/components/ChannelHeaderComponent.mdx @@ -43,5 +43,9 @@ If you want to create your own channel component check out our [customization gu By default no channel action is displayed, but it's possible to [add action buttons to the channel header](../services/CustomTemplatesService.mdx/#channelactionstemplate) component. You can follow our [code example](../code-examples/channel-invites.mdx) that implements the invite action (you can implement other kind of actions as well, for example edit). +## Menu button + +You can provide a menu button template to the channel header, the [responsive layout guide](../code-examples/responsive-layout.mdx) shows how you can use this to toggle the channel list on mobile screens. + [//]: # "Start of generated content" [//]: # "End of generated content" diff --git a/docusaurus/docs/Angular/components/NotificationListComponent.mdx b/docusaurus/docs/Angular/components/NotificationListComponent.mdx index d4e2402f..f596651b 100644 --- a/docusaurus/docs/Angular/components/NotificationListComponent.mdx +++ b/docusaurus/docs/Angular/components/NotificationListComponent.mdx @@ -10,9 +10,9 @@ You can use the `NotificationList` component to display the active notifications - + ``` diff --git a/docusaurus/docs/Angular/components/ThreadComponent.mdx b/docusaurus/docs/Angular/components/ThreadComponent.mdx index d7becfdf..c8c6c1ba 100644 --- a/docusaurus/docs/Angular/components/ThreadComponent.mdx +++ b/docusaurus/docs/Angular/components/ThreadComponent.mdx @@ -2,23 +2,12 @@ The `Thread` component represents a [message thread](https://getstream.io/chat/d ## Usage -The `Thread` component have empty slots for message list and message input components where you can inject content with the following names: - -- `thread-message-list` for the message list -- `thread-message-input` for the message input - -The names determine the location of the template within the thread layout. +The `Thread` component displays the thread header (built inside the thread), the message list and message input components: ```html - - + + ``` diff --git a/docusaurus/docs/Angular/concepts/_category_.json b/docusaurus/docs/Angular/concepts/_category_.json index 283b47d1..0f9ccce0 100644 --- a/docusaurus/docs/Angular/concepts/_category_.json +++ b/docusaurus/docs/Angular/concepts/_category_.json @@ -1,4 +1,4 @@ { "label": "Concepts", - "position": 6 + "position": 7 } diff --git a/docusaurus/docs/Angular/concepts/customization.mdx b/docusaurus/docs/Angular/concepts/customization.mdx index a28f3bc6..bdd871e6 100644 --- a/docusaurus/docs/Angular/concepts/customization.mdx +++ b/docusaurus/docs/Angular/concepts/customization.mdx @@ -3,7 +3,7 @@ id: customization title: Customization --- -Apart from customizing the [themeing and CSS](./themeing-and-css.mdx) of UI components it's also possible to provide your own template to completely override parts of the UI to further improve the flexibility of our SDK. This guide shows you the different custom templates you can provide. +Apart from customizing the [theming and CSS](../theming/introduction.mdx) of UI components it's also possible to provide your own template to completely override parts of the UI to further improve the flexibility of our SDK. This guide shows you the different custom templates you can provide. For code examples to the different customizations see our [customizations example application](https://github.com/GetStream/stream-chat-angular/tree/master/projects/customizations-example), specifically the [AppComponent](https://github.com/GetStream/stream-chat-angular/tree/master/projects/customizations-example/src/app) (see [README](https://github.com/GetStream/stream-chat-angular/blob/master/README.md#customization-examples) for instructions on how to start the application). @@ -17,22 +17,16 @@ This is how our initial chat UI looks like: - - - + + + ``` This template lives in your application so you can freely replace any component with your own custom component, for example if you want to use your own channel header you can just replace `stream-channel-header` with your own component. diff --git a/docusaurus/docs/Angular/concepts/themeing-and-css.mdx b/docusaurus/docs/Angular/concepts/theming-and-css.mdx similarity index 92% rename from docusaurus/docs/Angular/concepts/themeing-and-css.mdx rename to docusaurus/docs/Angular/concepts/theming-and-css.mdx index 56919b2e..52205976 100644 --- a/docusaurus/docs/Angular/concepts/themeing-and-css.mdx +++ b/docusaurus/docs/Angular/concepts/theming-and-css.mdx @@ -1,7 +1,7 @@ --- id: themeing sidebar_position: 1 -title: Themeing and CSS +title: Theming and CSS (deprecated) --- import LightThemeScreenshot from "../assets/light-theme-screenshot.png"; @@ -9,6 +9,10 @@ import DarkThemeScreenshot from "../assets/dark-theme-screenshot.png"; import CustomLightThemeScreenshot from "../assets/custom-light-theme-screenshot.png"; import CustomDarkThemeScreenshot from "../assets/custom-dark-theme-screenshot.png"; +:::caution +This page contains information about the old theming system of the chat UI, this is now deprecated and will be removed in a future release. Please refer to our [new theming guide](../theming/introduction.mdx). +::: + ## Overriding CSS To override pre-defined library styles, follow this simple process: diff --git a/package-lock.json b/package-lock.json index 7ff4813c..738cd996 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,12 +20,14 @@ "@ctrl/ngx-emoji-mart": "^6.2.0", "@ngx-translate/core": "^13.0.0", "@ngx-translate/http-loader": "^6.0.0", - "@stream-io/stream-chat-css": "2.9.0", + "@popperjs/core": "^2.11.5", + "@stream-io/stream-chat-css": "3.0.0-theming2.6", "@stream-io/transliterate": "^1.5.2", "angular-mentions": "^1.4.0", "dayjs": "^1.10.7", "dotenv": "^10.0.0", "emoji-regex": "^10.0.0", + "ngx-popperjs": "^12.2.1", "pretty-bytes": "^5.6.0", "rxjs": "~6.6.0", "stream-chat": "^6.4.0", @@ -429,6 +431,22 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/core/node_modules/ajv": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", + "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@angular-devkit/schematics": { "version": "12.2.5", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.5.tgz", @@ -2685,22 +2703,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.11.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", @@ -2725,12 +2727,6 @@ "node": ">= 4" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -3267,6 +3263,15 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "node_modules/@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/plugin-commonjs": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-20.0.0.tgz", @@ -3994,9 +3999,9 @@ "dev": true }, "node_modules/@stream-io/stream-chat-css": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@stream-io/stream-chat-css/-/stream-chat-css-2.9.0.tgz", - "integrity": "sha512-o5fK5bPD2h+WYEkLZoWjUw+7dvHWcPyUPoOp65Aer0ElTPRryaWD00kG7TDKTsPFjQwJptolNnfrw9Yj7r9Z/Q==" + "version": "3.0.0-theming2.6", + "resolved": "https://registry.npmjs.org/@stream-io/stream-chat-css/-/stream-chat-css-3.0.0-theming2.6.tgz", + "integrity": "sha512-qJxu6Cr7FZeOQDy8ncoGe7lEXZAVoKwoEP35ibwu/zAOWhUljwfSgPooEefzACjlRfDzvRTFIyrMM6/Yg8kXDA==" }, "node_modules/@stream-io/transliterate": { "version": "1.5.2", @@ -4682,14 +4687,14 @@ } }, "node_modules/ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" }, "funding": { @@ -4723,6 +4728,22 @@ } } }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -4732,6 +4753,12 @@ "ajv": "^6.9.1" } }, + "node_modules/ajv/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/alphanum-sort": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", @@ -6517,22 +6544,6 @@ "webpack": "^5.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/copy-webpack-plugin/node_modules/glob-parent": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.1.tgz", @@ -6545,12 +6556,6 @@ "node": ">=10.13.0" } }, - "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/copy-webpack-plugin/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7158,28 +7163,6 @@ } } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/css-minimizer-webpack-plugin/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -9005,22 +8988,6 @@ "@babel/highlight": "^7.10.4" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/eslint/node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -9095,12 +9062,6 @@ "node": ">= 4" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/eslint/node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -10460,28 +10421,6 @@ "node": ">=6" } }, - "node_modules/har-validator/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/har-validator/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -13463,28 +13402,6 @@ "webpack": "^5.0.0" } }, - "node_modules/mini-css-extract-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -13858,6 +13775,22 @@ "typescript": "~4.2.3 || ~4.3.2" } }, + "node_modules/ng-packagr/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ng-packagr/node_modules/commander": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.2.0.tgz", @@ -13867,6 +13800,20 @@ "node": ">= 12" } }, + "node_modules/ngx-popperjs": { + "version": "12.2.1", + "resolved": "https://registry.npmjs.org/ngx-popperjs/-/ngx-popperjs-12.2.1.tgz", + "integrity": "sha512-AIsVgn2Q1/LdcOV68VgTZKm8hShMKot28b6fT1Z/iQ3ZeR3euA08wXZDg3oBc3yibos6O3Ny3c1BubM8/9Eraw==", + "hasInstallScript": true, + "dependencies": { + "tslib": "^2.2.0" + }, + "peerDependencies": { + "@angular/common": "^12.1.3", + "@angular/core": "^12.1.3", + "@popperjs/core": "^2.9.2" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -23413,28 +23360,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -25007,6 +24932,22 @@ "node": ">=10.0.0" } }, + "node_modules/table/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -25203,28 +25144,6 @@ "webpack": "^5.1.0" } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/terser-webpack-plugin/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -26280,28 +26199,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -26375,22 +26272,6 @@ } } }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack-dev-server/node_modules/ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -26686,12 +26567,6 @@ "node": ">=0.10.0" } }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/webpack-dev-server/node_modules/locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -27083,28 +26958,6 @@ } } }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -27631,6 +27484,20 @@ "magic-string": "0.25.7", "rxjs": "6.6.7", "source-map": "0.7.3" + }, + "dependencies": { + "ajv": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", + "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + } } }, "@angular-devkit/schematics": { @@ -29236,18 +29103,6 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "globals": { "version": "13.11.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", @@ -29263,12 +29118,6 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -29715,6 +29564,11 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" + }, "@rollup/plugin-commonjs": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-20.0.0.tgz", @@ -30250,9 +30104,9 @@ "dev": true }, "@stream-io/stream-chat-css": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@stream-io/stream-chat-css/-/stream-chat-css-2.9.0.tgz", - "integrity": "sha512-o5fK5bPD2h+WYEkLZoWjUw+7dvHWcPyUPoOp65Aer0ElTPRryaWD00kG7TDKTsPFjQwJptolNnfrw9Yj7r9Z/Q==" + "version": "3.0.0-theming2.6", + "resolved": "https://registry.npmjs.org/@stream-io/stream-chat-css/-/stream-chat-css-3.0.0-theming2.6.tgz", + "integrity": "sha512-qJxu6Cr7FZeOQDy8ncoGe7lEXZAVoKwoEP35ibwu/zAOWhUljwfSgPooEefzACjlRfDzvRTFIyrMM6/Yg8kXDA==" }, "@stream-io/transliterate": { "version": "1.5.2", @@ -30819,15 +30673,23 @@ } }, "ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "dependencies": { + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } } }, "ajv-errors": { @@ -30844,6 +30706,20 @@ "dev": true, "requires": { "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + } } }, "ajv-keywords": { @@ -32254,18 +32130,6 @@ "serialize-javascript": "^6.0.0" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "glob-parent": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.1.tgz", @@ -32275,12 +32139,6 @@ "is-glob": "^4.0.1" } }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -32749,24 +32607,6 @@ "source-map": "^0.6.1" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -34049,18 +33889,6 @@ "@babel/highlight": "^7.10.4" } }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -34110,12 +33938,6 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -35326,26 +35148,6 @@ "requires": { "ajv": "^6.12.3", "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - } } }, "hard-rejection": { @@ -37680,24 +37482,6 @@ "schema-utils": "^3.1.0" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -37994,6 +37778,18 @@ "stylus": "^0.54.8" }, "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, "commander": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.2.0.tgz", @@ -38002,6 +37798,14 @@ } } }, + "ngx-popperjs": { + "version": "12.2.1", + "resolved": "https://registry.npmjs.org/ngx-popperjs/-/ngx-popperjs-12.2.1.tgz", + "integrity": "sha512-AIsVgn2Q1/LdcOV68VgTZKm8hShMKot28b6fT1Z/iQ3ZeR3euA08wXZDg3oBc3yibos6O3Ny3c1BubM8/9Eraw==", + "requires": { + "tslib": "^2.2.0" + } + }, "nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -45140,26 +44944,6 @@ "@types/json-schema": "^7.0.5", "ajv": "^6.12.4", "ajv-keywords": "^3.5.2" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - } } }, "select-hose": { @@ -46443,6 +46227,20 @@ "slice-ansi": "^4.0.0", "string-width": "^4.2.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + } } }, "tapable": { @@ -46595,24 +46393,6 @@ "terser": "^5.7.0" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -47375,24 +47155,6 @@ "webpack-sources": "^3.2.0" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -47443,24 +47205,6 @@ "schema-utils": "^3.0.0" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -47515,18 +47259,6 @@ "yargs": "^13.3.2" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -47772,12 +47504,6 @@ } } }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", diff --git a/package.json b/package.json index d7105836..779baa9c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "prestart:customizations-example": "npm run config:dev", "start:customizations-example": "ng serve customizations-example", "prestart:dev": "npm run config:dev", - "start:dev": "rm -rf dist & npm run watch & (wait-on dist && ng serve sample-app)", + "start:dev": "rm -rf dist & npm run watch & (wait-on dist && ng serve sample-app --host 0.0.0.0)", "prestart:dev:customizations-example": "npm run config:dev", "start:dev:customizations-example": "rm -rf dist & npm run watch & (wait-on dist && ng serve customizations-example)", "build": "ng build", @@ -30,11 +30,12 @@ "build:sample-app": "ng build --stats-json --project sample-app", "preanalyze:sample-app": "npm run build:sample-app", "analyze:sample-app": "webpack-bundle-analyzer dist/sample-app/stats.json", - "generate-docs": "npm run typedoc:services && npm run typedoc:components && npm run copy-docs", + "generate-docs": "npm run typedoc:services && npm run typedoc:components && npm run copy-docs && npm run copy-css-docs", "typedoc:services": "typedoc --cleanOutputDir true --excludeConstructors true --hideBreadcrumbs true --hideInPageTOC true --excludePrivate true --out temp-service-docs --exclude '!**/*service.ts' --excludeNotDocumented --tsconfig projects/stream-chat-angular/tsconfig.lib.json projects/stream-chat-angular/src/public-api.ts", "typedoc:components": "typedoc --cleanOutputDir true --excludeConstructors true --sort source-order --hideBreadcrumbs true --hideInPageTOC true --excludePrivate true --excludeNotDocumented --out temp-component-docs --exclude '!**/*component.ts' --tsconfig projects/stream-chat-angular/tsconfig.lib.json projects/stream-chat-angular/src/public-api.ts", "copy-docs": "ts-node copy-generated-service-docs.ts & (ts-node remove-generated-component-docs-content && ts-node copy-generated-component-docs.ts)", - "copy-css": "copyfiles --up 4 \"node_modules/@stream-io/stream-chat-css/dist/**/*\" projects/stream-chat-angular/src/assets/styles" + "copy-css": "rm -rf projects/stream-chat-angular/src/assets/styles && copyfiles --up 4 \"node_modules/@stream-io/stream-chat-css/dist/**/*\" projects/stream-chat-angular/src/assets/styles", + "copy-css-docs": "./copy-css-docs.sh node_modules/@stream-io/stream-chat-css/docs" }, "lint-staged": { "**/*": [ @@ -49,6 +50,10 @@ { "name": "beta", "prerelease": true + }, + { + "name": "theming-v2", + "prerelease": true } ], "dryRun": false, @@ -111,12 +116,14 @@ "@ctrl/ngx-emoji-mart": "^6.2.0", "@ngx-translate/core": "^13.0.0", "@ngx-translate/http-loader": "^6.0.0", - "@stream-io/stream-chat-css": "2.9.0", + "@popperjs/core": "^2.11.5", + "@stream-io/stream-chat-css": "3.0.0-theming2.6", "@stream-io/transliterate": "^1.5.2", "angular-mentions": "^1.4.0", "dayjs": "^1.10.7", "dotenv": "^10.0.0", "emoji-regex": "^10.0.0", + "ngx-popperjs": "^12.2.1", "pretty-bytes": "^5.6.0", "rxjs": "~6.6.0", "stream-chat": "^6.4.0", diff --git a/projects/customizations-example/src/app/app.component.html b/projects/customizations-example/src/app/app.component.html index 4552b7bd..a81dd4f7 100644 --- a/projects/customizations-example/src/app/app.component.html +++ b/projects/customizations-example/src/app/app.component.html @@ -1,27 +1,13 @@
- - + + - + - - + +
@@ -32,13 +18,6 @@
@@ -55,11 +34,6 @@ >
- {{ - user?.id || user?.name - }} + {{ user?.id || user?.name }}
@@ -113,31 +83,15 @@ -
- {{ item.autocompleteLabel }} - {{ item.role }} -
+
{{ item.autocompleteLabel }} - {{ item.role }}
-
- {{ item.autocompleteLabel | uppercase }} ({{ item.description }}) -
+
{{ item.autocompleteLabel | uppercase }} ({{ item.description }})
- + - Custom attachments -
+
{{ type | uppercase }}
diff --git a/projects/customizations-example/src/app/app.component.ts b/projects/customizations-example/src/app/app.component.ts index 1fbbaf7a..9a104202 100644 --- a/projects/customizations-example/src/app/app.component.ts +++ b/projects/customizations-example/src/app/app.component.ts @@ -140,7 +140,7 @@ export class AppComponent implements AfterViewInit { this.messageActionItemTemplate ); this.customTemplatesService.messageReactionsTemplate$.next( - this.messageReactonsTemplate + this.messageReactionsTemplate ); this.customTemplatesService.modalTemplate$.next(this.modalTemplate); this.customTemplatesService.notificationTemplate$.next( diff --git a/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.html b/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.html index 2c945ab2..44feadac 100644 --- a/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.html +++ b/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.html @@ -1,23 +1,26 @@ - +
+ - + +
diff --git a/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.scss b/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.scss index 3d965cf0..bc94210b 100644 --- a/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.scss +++ b/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.scss @@ -1,20 +1,37 @@ -button { - background-color: transparent; - border: none; - cursor: pointer; - padding: 0; -} +.emoji-picker-container { + position: relative; -.picker { - position: absolute; - bottom: 100%; - left: 0; - transform: scale(0.8); - transform-origin: 0% 100%; -} + button { + background-color: transparent; + border: none; + cursor: pointer; + padding: 0; + margin: 0; + width: 24px; + height: 24px; + + svg { + width: 24px; + height: 24px; + + path { + fill: var(--str-chat__message-input-tools-color); + } + } + } -@media only screen and (min-device-width: 1024px) { .picker { - transform: scale(1); + z-index: 3; + position: absolute; + bottom: 100%; + transform: scale(0.8); + right: 0; + transform-origin: bottom right; + } + + @media only screen and (min-device-width: 1024px) { + .picker { + transform: scale(1); + } } } diff --git a/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.ts b/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.ts index 9246bcc7..c141ae1e 100644 --- a/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.ts +++ b/projects/customizations-example/src/app/emoji-picker/emoji-picker.component.ts @@ -1,5 +1,6 @@ -import { Component, Input } from '@angular/core'; -import { Subject } from 'rxjs'; +import { Component, ElementRef, Input, ViewChild } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { ThemeService } from 'stream-chat-angular'; @Component({ selector: 'app-emoji-picker', @@ -8,12 +9,36 @@ import { Subject } from 'rxjs'; }) export class EmojiPickerComponent { isOpened = false; + theme$: Observable; @Input() emojiInput$: Subject | undefined; + @ViewChild('container') container: ElementRef | undefined; - constructor() {} + constructor(themeService: ThemeService) { + this.theme$ = themeService.theme$; + } emojiSelected(event: any) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access this.emojiInput$?.next(event.emoji.native); } + + eventHandler = (event: Event) => { + // Watching for outside clicks + if (!this.container?.nativeElement.contains(event.target as Node)) { + this.isOpened = false; + window.removeEventListener('click', this.eventHandler); + } + }; + + toggled() { + if (!this.container) { + return; + } + this.isOpened = !this.isOpened; + if (this.isOpened) { + window.addEventListener('click', this.eventHandler); + } else { + window.removeEventListener('click', this.eventHandler); + } + } } diff --git a/projects/customizations-example/src/app/icon/icon.component.scss b/projects/customizations-example/src/app/icon/icon.component.scss index feb392c4..e69de29b 100644 --- a/projects/customizations-example/src/app/icon/icon.component.scss +++ b/projects/customizations-example/src/app/icon/icon.component.scss @@ -1,3 +0,0 @@ -.material-icons { - color: #33691e; -} diff --git a/projects/customizations-example/src/app/icon/icon.component.ts b/projects/customizations-example/src/app/icon/icon.component.ts index 516cbcf6..0b19a4dd 100644 --- a/projects/customizations-example/src/app/icon/icon.component.ts +++ b/projects/customizations-example/src/app/icon/icon.component.ts @@ -30,6 +30,13 @@ export class IconComponent { 'reply-in-thread': 'reply', retry: 'replay', send: 'send', + attach: 'upload_file', + 'unspecified-filetype': 'draft', + download: 'download_for_offline', + error: 'error', + 'arrow-up': 'keyboard_arrow_up', + 'arrow-down': 'keyboard_arrow_down', + 'chat-bubble': 'chat', }; return map[this.icon!]; diff --git a/projects/customizations-example/src/styles.scss b/projects/customizations-example/src/styles.scss index 7b1328b4..95487961 100644 --- a/projects/customizations-example/src/styles.scss +++ b/projects/customizations-example/src/styles.scss @@ -1,45 +1,26 @@ -@import "../../stream-chat-angular/src/assets/styles/scss/index.scss"; +@import "../../stream-chat-angular/src/assets/styles/v2/scss/index.scss"; @import "~@ctrl/ngx-emoji-mart/picker"; @import url(https://fonts.googleapis.com/icon?family=Material+Icons); -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; +html { + height: 100%; } -.str-chat__thread { - min-width: 100%; - max-width: 100%; -} - -@media only screen and (min-device-width: 768px) { - .str-chat__thread { - min-width: 35%; - max-width: 35%; - } -} - -.str-chat-channel-list { - max-height: 100vh; - overflow-y: auto; +body { + margin: 0; + height: 100%; } -.str-chat__reaction-list { - background-image: none !important; - background-color: #f1f8e9 !important; - border-radius: var(--border-radius-md); - padding: 0 10px; +#root { + display: flex; + height: 100%; - &::before { - background-image: none !important; + stream-channel-list { + width: 30%; } - &::after { - background-image: none !important; + stream-channel { + width: 70%; } } diff --git a/projects/sample-app/src/_variables.scss b/projects/sample-app/src/_variables.scss new file mode 100644 index 00000000..b24d476f --- /dev/null +++ b/projects/sample-app/src/_variables.scss @@ -0,0 +1 @@ +$stream-chat-theme-version: "2"; diff --git a/projects/sample-app/src/app/app.component.html b/projects/sample-app/src/app/app.component.html index 0e188859..b2bf74aa 100644 --- a/projects/sample-app/src/app/app.component.html +++ b/projects/sample-app/src/app/app.component.html @@ -1,19 +1,38 @@
- - - + + + + + - - - + + +
diff --git a/projects/sample-app/src/app/app.component.scss b/projects/sample-app/src/app/app.component.scss index e69de29b..7cb080f9 100644 --- a/projects/sample-app/src/app/app.component.scss +++ b/projects/sample-app/src/app/app.component.scss @@ -0,0 +1,5 @@ +.menu-button { + border: none; + background-color: transparent; + cursor: pointer; +} diff --git a/projects/sample-app/src/app/app.component.ts b/projects/sample-app/src/app/app.component.ts index 99afc1ff..b2883f43 100644 --- a/projects/sample-app/src/app/app.component.ts +++ b/projects/sample-app/src/app/app.component.ts @@ -1,8 +1,18 @@ -import { Component } from '@angular/core'; +import { + AfterViewInit, + Component, + TemplateRef, + ViewChild, +} from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { ChatClientService, ChannelService, StreamI18nService, + EmojiPickerContext, + CustomTemplatesService, + ThemeService, } from 'stream-chat-angular'; import { environment } from '../environments/environment'; @@ -11,11 +21,20 @@ import { environment } from '../environments/environment'; templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) -export class AppComponent { +export class AppComponent implements AfterViewInit { + isMenuOpen = false; + isThreadOpen = false; + @ViewChild('emojiPickerTemplate') + emojiPickerTemplate!: TemplateRef; + themeVersion: '1' | '2'; + theme$: Observable; + constructor( private chatService: ChatClientService, private channelService: ChannelService, - private streamI18nService: StreamI18nService + private streamI18nService: StreamI18nService, + private customTemplateService: CustomTemplatesService, + themeService: ThemeService ) { void this.chatService.init( environment.apiKey, @@ -27,5 +46,20 @@ export class AppComponent { members: { $in: [environment.userId] }, }); this.streamI18nService.setTranslation(); + this.channelService.activeParentMessage$ + .pipe(map((m) => !!m)) + .subscribe((isThreadOpen) => (this.isThreadOpen = isThreadOpen)); + this.themeVersion = themeService.themeVersion; + this.theme$ = themeService.theme$; + } + + ngAfterViewInit(): void { + this.customTemplateService.emojiPickerTemplate$.next( + this.emojiPickerTemplate + ); + } + + closeMenu() { + this.isMenuOpen = false; } } diff --git a/projects/sample-app/src/app/emoji-picker/emoji-picker.component.html b/projects/sample-app/src/app/emoji-picker/emoji-picker.component.html index 029d595e..44feadac 100644 --- a/projects/sample-app/src/app/emoji-picker/emoji-picker.component.html +++ b/projects/sample-app/src/app/emoji-picker/emoji-picker.component.html @@ -1,23 +1,26 @@ - +
+ - + +
diff --git a/projects/sample-app/src/app/emoji-picker/emoji-picker.component.scss b/projects/sample-app/src/app/emoji-picker/emoji-picker.component.scss index 3d965cf0..ab5e5a43 100644 --- a/projects/sample-app/src/app/emoji-picker/emoji-picker.component.scss +++ b/projects/sample-app/src/app/emoji-picker/emoji-picker.component.scss @@ -1,20 +1,76 @@ -button { - background-color: transparent; - border: none; - cursor: pointer; - padding: 0; -} +@import "../../variables"; -.picker { - position: absolute; - bottom: 100%; - left: 0; - transform: scale(0.8); - transform-origin: 0% 100%; -} +.emoji-picker-container { + position: relative; + + button { + background-color: transparent; + border: none; + cursor: pointer; + padding: 0; + margin: 0; + @if $stream-chat-theme-version == "2" { + width: 24px; + height: 24px; + } @else { + width: 20px; + height: 20px; + } + + svg { + @if $stream-chat-theme-version == "2" { + width: 24px; + height: 24px; + } @else { + width: 20px; + height: 20px; + } + + path { + fill: var(--str-chat__message-input-tools-color); + } + } + } + + @if $stream-chat-theme-version == "2" { + .emoji-picker-container { + width: 24px; + height: 24px; + + svg { + width: 24px; + height: 24px; + } + } + } @else { + .emoji-picker-container { + width: 20px; + height: 20px; + + svg { + width: 20px; + height: 20px; + } + } + } -@media only screen and (min-device-width: 1024px) { .picker { - transform: scale(1); + z-index: 3; + position: absolute; + bottom: 100%; + transform: scale(0.8); + @if $stream-chat-theme-version == "2" { + right: 0; + transform-origin: bottom right; + } @else { + left: 0; + transform-origin: bottom left; + } + } + + @media only screen and (min-device-width: 1024px) { + .picker { + transform: scale(1); + } } } diff --git a/projects/sample-app/src/app/emoji-picker/emoji-picker.component.ts b/projects/sample-app/src/app/emoji-picker/emoji-picker.component.ts index 9246bcc7..c141ae1e 100644 --- a/projects/sample-app/src/app/emoji-picker/emoji-picker.component.ts +++ b/projects/sample-app/src/app/emoji-picker/emoji-picker.component.ts @@ -1,5 +1,6 @@ -import { Component, Input } from '@angular/core'; -import { Subject } from 'rxjs'; +import { Component, ElementRef, Input, ViewChild } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { ThemeService } from 'stream-chat-angular'; @Component({ selector: 'app-emoji-picker', @@ -8,12 +9,36 @@ import { Subject } from 'rxjs'; }) export class EmojiPickerComponent { isOpened = false; + theme$: Observable; @Input() emojiInput$: Subject | undefined; + @ViewChild('container') container: ElementRef | undefined; - constructor() {} + constructor(themeService: ThemeService) { + this.theme$ = themeService.theme$; + } emojiSelected(event: any) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access this.emojiInput$?.next(event.emoji.native); } + + eventHandler = (event: Event) => { + // Watching for outside clicks + if (!this.container?.nativeElement.contains(event.target as Node)) { + this.isOpened = false; + window.removeEventListener('click', this.eventHandler); + } + }; + + toggled() { + if (!this.container) { + return; + } + this.isOpened = !this.isOpened; + if (this.isOpened) { + window.addEventListener('click', this.eventHandler); + } else { + window.removeEventListener('click', this.eventHandler); + } + } } diff --git a/projects/sample-app/src/styles.scss b/projects/sample-app/src/styles.scss index 972fb5a2..fdcb403f 100644 --- a/projects/sample-app/src/styles.scss +++ b/projects/sample-app/src/styles.scss @@ -1,28 +1,121 @@ -@import "../../stream-chat-angular/src/assets/styles/scss/index.scss"; +@import "variables"; +@import "../../stream-chat-angular/src/assets/styles/v2/scss/index.scss"; @import "~@ctrl/ngx-emoji-mart/picker"; +html { + height: 100%; +} + body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + height: 100%; } -.str-chat__thread { - min-width: 100%; - max-width: 100%; -} +@if $stream-chat-theme-version == "2" { + #root { + display: flex; + height: 100%; + + .menu-open { + width: 100%; + height: 100%; + position: fixed; + z-index: 1; + } + + .menu-close { + width: 0; + } + + .menu-button { + display: block; + } + + .channel { + min-width: 0; + width: 100%; + } + + .thread { + width: 100%; + height: 100%; + position: fixed; + z-index: 2; + } + + @media screen and (min-width: 768px) { + .channel-list.thread-open { + &.menu-open { + width: 30%; + height: 100%; + position: fixed; + z-index: 1; + } + + &.menu-close { + width: 0; + } + + & + .channel .menu-button { + display: block; + } + } + + .channel-list.thread-close { + width: 30%; + max-width: 420px; + position: initial; + z-index: auto; + + & + .channel .menu-button { + display: none; + } + } + + .thread { + width: 55%; + position: initial; + z-index: auto; + } + } + + @media screen and (min-width: 1024px) { + .menu-button { + display: none; + } + + .channel-list { + width: 30%; + max-width: 420px; + position: initial; + z-index: auto; + } + + .thread { + width: 45%; + position: initial; + z-index: auto; + } + } + } +} @else { + .menu-button { + display: none; + } + .str-chat-channel-list { + max-height: 100vh; + overflow-y: auto; + } -@media only screen and (min-device-width: 768px) { .str-chat__thread { - min-width: 35%; - max-width: 35%; + min-width: 100%; + max-width: 100%; } -} -.str-chat-channel-list { - max-height: 100vh; - overflow-y: auto; + @media only screen and (min-device-width: 768px) { + .str-chat__thread { + min-width: 35%; + max-width: 35%; + } + } } diff --git a/projects/stream-chat-angular/ng-package.json b/projects/stream-chat-angular/ng-package.json index 36bd65a4..2f0bae6c 100644 --- a/projects/stream-chat-angular/ng-package.json +++ b/projects/stream-chat-angular/ng-package.json @@ -9,8 +9,10 @@ "angular-mentions", "dayjs", "@stream-io/transliterate", + "@popperjs/core", "uuidv4", "pretty-bytes", - "emoji-regex" + "emoji-regex", + "ngx-popperjs" ] } diff --git a/projects/stream-chat-angular/package.json b/projects/stream-chat-angular/package.json index ccc8ecc7..3540e56a 100644 --- a/projects/stream-chat-angular/package.json +++ b/projects/stream-chat-angular/package.json @@ -1,6 +1,6 @@ { "name": "stream-chat-angular", - "version": "3.8.2", + "version": "4.0.0-theming-v2.13", "description": "Angular components to create chat conversations or livestream style chat", "author": "GetStream", "homepage": "https://getstream.io/chat/", @@ -17,8 +17,10 @@ "dependencies": { "angular-mentions": "^1.4.0", "@stream-io/transliterate": "^1.5.2", + "@popperjs/core": "^2.11.5", "dayjs": "^1.10.7", "emoji-regex": "^10.0.0", + "ngx-popperjs": "^12.2.1", "pretty-bytes": "^5.6.0", "tslib": "^2.3.0", "uuidv4": "^6.2.12" diff --git a/projects/stream-chat-angular/src/assets/i18n/en.ts b/projects/stream-chat-angular/src/assets/i18n/en.ts index 38c26e11..d3ba4567 100644 --- a/projects/stream-chat-angular/src/assets/i18n/en.ts +++ b/projects/stream-chat-angular/src/assets/i18n/en.ts @@ -17,7 +17,7 @@ export const en = { 'Empty message...': 'Empty message...', 'Error adding flag': 'Error adding flag', 'Error connecting to chat, refresh the page to try again.': - 'Error connecting to chat, refresh the page to try again.', + 'Error connecting to chat, refresh the page to try again', 'Error deleting message': 'Error deleting message', 'Error muting a user ...': 'Error muting a user ...', 'Error pinning message': 'Error pinning message', @@ -26,13 +26,13 @@ export const en = { 'Error uploading file': 'Error uploading file', 'Error uploading image': 'Error uploading image', 'Error deleting attachment': 'Error deleting attachment', - 'Error · Unsent': 'Error · Unsent', + 'Error · Unsent': "Message couldn't be sent", 'Error: {{ errorMessage }}': 'Error: {{ errorMessage }}', Flag: 'Flag', 'Message Failed': 'Message Failed', - 'Message Failed · Unauthorized': 'Message Failed · Unauthorized', + 'Message Failed · Unauthorized': 'Unauthorized to send message', 'Message Failed · Click to try again': - 'Message Failed · Click to try again', + "Message couldn't be sent, click to try again", 'Message deleted': 'Message deleted', 'Message has been successfully flagged': 'Message has been successfully flagged', @@ -58,7 +58,7 @@ export const en = { 'Slow Mode ON': 'Slow Mode ON', 'Start of a new thread': 'Start of a new thread', 'This message was deleted...': 'This message was deleted...', - Thread: 'Thread', + Thread: 'Thread reply', 'Type your message': 'Type your message', Unmute: 'Unmute', Unpin: 'Unpin', @@ -95,5 +95,9 @@ export const en = { "You can't send thread replies in this channel": "You can't send thread replies in this channel", 'Unsupported file type: {{type}}': 'Unsupported file type: {{type}}', + 'No chats here yet…': 'No chats here yet…', + 'user is typing': '{{ user }} is typing', + 'users are typing': '{{ users }} are typing', + 'Error loading channels': 'Error loading channels', }, }; diff --git a/projects/stream-chat-angular/src/assets/version.ts b/projects/stream-chat-angular/src/assets/version.ts index 2f567211..e5281abe 100644 --- a/projects/stream-chat-angular/src/assets/version.ts +++ b/projects/stream-chat-angular/src/assets/version.ts @@ -1 +1 @@ -export const version = '3.8.2'; +export const version = '4.0.0-theming-v2.13'; diff --git a/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.html b/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.html index bd95d500..8a36f40e 100644 --- a/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.html +++ b/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.html @@ -1,191 +1,228 @@ - -
+ - -
+ -
-
- {{ title }} +
+
+
+ {{ title }} +
+
+ {{ unreadCount }} +
{ let queryAvatar: () => AvatarPlaceholderComponent; let queryTitle: () => HTMLElement | null; let queryLatestMessage: () => HTMLElement | null; + let queryUnreadBadge: () => HTMLElement | null; beforeEach(() => { channelServiceMock = mockChannelService(); @@ -56,6 +57,8 @@ describe('ChannelPreviewComponent', () => { .componentInstance as AvatarPlaceholderComponent; queryLatestMessage = () => nativeElement.querySelector('[data-testid="latest-message"]'); + queryUnreadBadge = () => + nativeElement.querySelector('[data-testid="unread-badge"]'); }); it('should apply active class if channel is active', () => { @@ -80,7 +83,7 @@ describe('ChannelPreviewComponent', () => { expect(queryContainer()?.classList.contains(activeClass)).toBeFalse(); }); - it('should apply unread class, if channel has unread messages', () => { + it('should apply unread class and display unread badge, if channel has unread messages', () => { const channels = generateMockChannels(); const channel = channels[0]; component.channel = channel; @@ -91,6 +94,7 @@ describe('ChannelPreviewComponent', () => { fixture.detectChanges(); expect(container?.classList.contains(unreadClass)).toBeFalse(); + expect(queryUnreadBadge()).toBeNull(); countUnreadSpy.and.returnValue(1); const newMessage = mockMessage(); @@ -99,6 +103,8 @@ describe('ChannelPreviewComponent', () => { fixture.detectChanges(); expect(container?.classList.contains(unreadClass)).toBeTrue(); + expect(component.unreadCount).toBe(1); + expect(queryUnreadBadge()?.innerHTML).toContain('1'); }); it(`shouldn't apply unread class, if user doesn't have 'read-events' capabilities`, () => { @@ -114,25 +120,43 @@ describe('ChannelPreviewComponent', () => { fixture.detectChanges(); expect(container?.classList.contains(unreadClass)).toBeFalse(); + expect(queryUnreadBadge()).toBeNull(); }); - it('should remove unread class, if user marked channel as read', () => { + it('should remove unread class and badge, if user marked channel as read', () => { const channels = generateMockChannels(); const channel = channels[0]; component.channel = channel; - let undreadCount = 1; + let undreadCount = 3; spyOn(channel, 'countUnread').and.callFake(() => undreadCount); const unreadClass = 'str-chat__channel-preview-messenger--unread'; const container = queryContainer(); fixture.detectChanges(); expect(container?.classList.contains(unreadClass)).toBeTrue(); + expect(component.unreadCount).toBe(3); + expect(queryUnreadBadge()?.innerHTML).toContain('3'); undreadCount = 0; channel.handleEvent('message.read', {}); fixture.detectChanges(); expect(container?.classList.contains(unreadClass)).toBeFalse(); + expect(queryUnreadBadge()).toBeNull(); + }); + + it(`shouldn't set unread state for active channels`, () => { + const channels = generateMockChannels(); + const channel = channels[0]; + const countUnreadSpy = spyOn(channel, 'countUnread'); + countUnreadSpy.and.returnValue(1); + component.channel = channel; + channelServiceMock.activeChannel$.next(channel); + component.channel = channel; + component.ngOnInit(); + + expect(component.isUnread).toBe(false); + expect(component.unreadCount).toBe(0); }); it('should set channel as active', () => { diff --git a/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.ts b/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.ts index 50ddc01b..20c1f327 100644 --- a/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.ts +++ b/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.ts @@ -26,6 +26,7 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { @Input() channel: Channel | undefined; isActive = false; isUnread = false; + unreadCount: number | undefined; latestMessage: string = 'streamChat.Nothing yet...'; private subscriptions: (Subscription | { unsubscribe: () => void })[] = []; private canSendReadEvents = true; @@ -47,7 +48,7 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { if (messages && messages.length > 0) { this.setLatestMessage(messages[messages.length - 1]); } - this.isUnread = !!this.channel!.countUnread() && this.canSendReadEvents; + this.updateUnreadState(); const capabilities = (this.channel?.data?.own_capabilities as string[]) || []; this.canSendReadEvents = capabilities.indexOf('read-events') !== -1; @@ -66,8 +67,7 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { this.subscriptions.push( this.channel!.on('message.read', () => this.ngZone.run(() => { - this.isUnread = - !!this.channel!.countUnread() && this.canSendReadEvents; + this.updateUnreadState(); }) ) ); @@ -113,7 +113,7 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { return; } this.setLatestMessage(event.message); - this.isUnread = !!this.channel.countUnread() && this.canSendReadEvents; + this.updateUnreadState(); }); } @@ -126,4 +126,14 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { this.latestMessage = 'streamChat.🏙 Attachment...'; } } + + private updateUnreadState() { + if (this.isActive || !this.canSendReadEvents) { + this.unreadCount = 0; + this.isUnread = false; + return; + } + this.unreadCount = this.channel!.countUnread(); + this.isUnread = !!this.unreadCount; + } } diff --git a/projects/stream-chat-angular/src/lib/channel.service.spec.ts b/projects/stream-chat-angular/src/lib/channel.service.spec.ts index 4103a08c..24787359 100644 --- a/projects/stream-chat-angular/src/lib/channel.service.spec.ts +++ b/projects/stream-chat-angular/src/lib/channel.service.spec.ts @@ -19,6 +19,7 @@ import { mockCurrentUser, mockMessage, } from './mocks'; +import { NotificationService } from './notification.service'; import { AttachmentUpload, DefaultStreamChatGenerics, @@ -136,13 +137,22 @@ describe('ChannelService', () => { }); it('should return the result of the init', async () => { + const notificationService = TestBed.inject(NotificationService); + const notificationSpy = jasmine.createSpy(); + notificationService.notifications$.subscribe(notificationSpy); + notificationSpy.calls.reset(); const expectedResult = generateMockChannels(); const result = await init(expectedResult); expect(result as any as MockChannel[]).toEqual(expectedResult); + expect(notificationSpy).not.toHaveBeenCalled(); }); it('should return the result of the init - error', async () => { + const notificationService = TestBed.inject(NotificationService); + const notificationSpy = jasmine.createSpy(); + notificationService.notifications$.subscribe(notificationSpy); + notificationSpy.calls.reset(); const error = 'there was an error'; await expectAsync( @@ -150,6 +160,15 @@ describe('ChannelService', () => { mockChatClient.queryChannels.and.rejectWith(error) ) ).toBeRejectedWith(error); + + expect(notificationSpy).toHaveBeenCalledWith( + jasmine.arrayContaining([ + jasmine.objectContaining({ + type: 'error', + text: 'streamChat.Error loading channels', + }), + ]) + ); }); it('should not set active channel if #shouldSetActiveChannel is false', async () => { diff --git a/projects/stream-chat-angular/src/lib/channel.service.ts b/projects/stream-chat-angular/src/lib/channel.service.ts index 1c1cb65b..2a5eb9b7 100644 --- a/projects/stream-chat-angular/src/lib/channel.service.ts +++ b/projects/stream-chat-angular/src/lib/channel.service.ts @@ -24,6 +24,7 @@ import { } from 'stream-chat'; import { ChatClientService, ClientEvent } from './chat-client.service'; import { createMessagePreview } from './message-preview'; +import { NotificationService } from './notification.service'; import { getReadBy } from './read-by'; import { AttachmentUpload, @@ -291,7 +292,8 @@ export class ChannelService< constructor( private chatClientService: ChatClientService, - private ngZone: NgZone + private ngZone: NgZone, + private notificationService: NotificationService ) { this.channels$ = this.channelsSubject.asObservable(); this.activeChannel$ = this.activeChannelSubject.asObservable(); @@ -489,11 +491,19 @@ export class ChannelService< }; this.sort = sort || { last_message_at: -1, updated_at: -1 }; this.shouldSetActiveChannel = shouldSetActiveChannel; - const result = await this.queryChannels(this.shouldSetActiveChannel); - this.clientEventsSubscription = this.chatClientService.events$.subscribe( - (notification) => void this.handleNotification(notification) - ); - return result; + try { + const result = await this.queryChannels(this.shouldSetActiveChannel); + this.clientEventsSubscription = this.chatClientService.events$.subscribe( + (notification) => void this.handleNotification(notification) + ); + return result; + } catch (error) { + this.notificationService.addPermanentNotification( + 'streamChat.Error loading channels', + 'error' + ); + throw error; + } } /** diff --git a/projects/stream-chat-angular/src/lib/channel/channel.component.html b/projects/stream-chat-angular/src/lib/channel/channel.component.html index fc464a6c..a78cc789 100644 --- a/projects/stream-chat-angular/src/lib/channel/channel.component.html +++ b/projects/stream-chat-angular/src/lib/channel/channel.component.html @@ -1,8 +1,17 @@
-
+
@@ -11,4 +20,73 @@ select='[name="thread"]' >
+ +
+ +

+ {{ "streamChat.No chats here yet…" | translate }} +

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/stream-chat-angular/src/lib/channel/channel.component.ts b/projects/stream-chat-angular/src/lib/channel/channel.component.ts index 051acf1d..9b16753a 100644 --- a/projects/stream-chat-angular/src/lib/channel/channel.component.ts +++ b/projects/stream-chat-angular/src/lib/channel/channel.component.ts @@ -2,6 +2,7 @@ import { Component } from '@angular/core'; import { Observable, of, Subscription } from 'rxjs'; import { catchError, map, startWith } from 'rxjs/operators'; import { ChannelService } from '../channel.service'; +import { ThemeService } from '../theme.service'; /** * The `Channel` component is a container component that displays the [`ChannelHeader`](./ChannelHeaderComponent.mdx), [`MessageList`](./MessageListComponent.mdx), [`NotificationList`](./NotificationListComponent.mdx) and [`MessageInput`](./MessageInputComponent.mdx) components. You can also provide the [`Thread`](./ThreadComponent.mdx) component to use message [threads](https://getstream.io/chat/docs/javascript/threads/?language=javascript). @@ -15,9 +16,14 @@ export class ChannelComponent { isError$: Observable; isInitializing$: Observable; isActiveThread$: Observable; + isActiveChannel$: Observable; subscriptions: Subscription[] = []; + theme$: Observable; - constructor(private channelService: ChannelService) { + constructor( + private channelService: ChannelService, + private themeService: ThemeService + ) { this.isError$ = this.channelService.channels$.pipe( map(() => false), catchError(() => of(true)), @@ -30,5 +36,9 @@ export class ChannelComponent { this.isActiveThread$ = this.channelService.activeParentMessageId$.pipe( map((id) => !!id) ); + this.theme$ = this.themeService.theme$; + this.isActiveChannel$ = this.channelService.activeChannel$.pipe( + map((c) => !!c) + ); } } diff --git a/projects/stream-chat-angular/src/lib/chat-client.service.spec.ts b/projects/stream-chat-angular/src/lib/chat-client.service.spec.ts index fb2f0f82..5b70c372 100644 --- a/projects/stream-chat-angular/src/lib/chat-client.service.spec.ts +++ b/projects/stream-chat-angular/src/lib/chat-client.service.spec.ts @@ -63,6 +63,33 @@ describe('ChatClientService', () => { expect(mockChatClient.setGuestUser).toHaveBeenCalledWith({ id: userId }); }); + it(`should notify if connection wasn't successful`, async () => { + const notificationService = TestBed.inject(NotificationService); + const spy = jasmine.createSpy(); + notificationService.notifications$.subscribe(spy); + spy.calls.reset(); + + await service.init(apiKey, userId, userToken); + + expect(spy).not.toHaveBeenCalled(); + + const error = new Error('error'); + mockChatClient.connectUser.and.rejectWith(error); + + await expectAsync(service.init(apiKey, userId, userToken)).toBeRejectedWith( + error + ); + + expect(spy).toHaveBeenCalledWith( + jasmine.arrayContaining([ + jasmine.objectContaining({ + type: 'error', + text: 'streamChat.Error connecting to chat, refresh the page to try again.', + }), + ]) + ); + }); + it('should disconnect user', async () => { const pendingInvitesSpy = jasmine.createSpy(); const eventsSpy = jasmine.createSpy(); diff --git a/projects/stream-chat-angular/src/lib/chat-client.service.ts b/projects/stream-chat-angular/src/lib/chat-client.service.ts index 29cc42bb..67480341 100644 --- a/projects/stream-chat-angular/src/lib/chat-client.service.ts +++ b/projects/stream-chat-angular/src/lib/chat-client.service.ts @@ -95,10 +95,18 @@ export class ChatClientService< let result; await this.ngZone.runOutsideAngular(async () => { const user = typeof userOrId === 'string' ? { id: userOrId } : userOrId; - result = - userTokenOrProvider === 'guest' - ? await this.chatClient.setGuestUser(user) - : await this.chatClient.connectUser(user, userTokenOrProvider); + try { + result = + userTokenOrProvider === 'guest' + ? await this.chatClient.setGuestUser(user) + : await this.chatClient.connectUser(user, userTokenOrProvider); + } catch (error) { + this.notificationService.addPermanentNotification( + 'streamChat.Error connecting to chat, refresh the page to try again.', + 'error' + ); + throw error; + } this.userSubject.next(this.chatClient.user); const sdkPrefix = 'stream-chat-angular'; if (!this.chatClient.getUserAgent().includes(sdkPrefix)) { diff --git a/projects/stream-chat-angular/src/lib/device-width.ts b/projects/stream-chat-angular/src/lib/device-width.ts deleted file mode 100644 index 6097e736..00000000 --- a/projects/stream-chat-angular/src/lib/device-width.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type DeviceWidth = { - device: 'mobile' | 'tablet' | 'full'; - width: number; -}; - -export const getDeviceWidth = (): DeviceWidth => { - const width = window.innerWidth; - if (width < 768) return { device: 'mobile', width }; - if (width < 1024) return { device: 'tablet', width }; - return { device: 'full', width }; -}; diff --git a/projects/stream-chat-angular/src/lib/icon/icon.component.html b/projects/stream-chat-angular/src/lib/icon/icon.component.html index fb8b3137..980c23cc 100644 --- a/projects/stream-chat-angular/src/lib/icon/icon.component.html +++ b/projects/stream-chat-angular/src/lib/icon/icon.component.html @@ -13,17 +13,21 @@ + streamChat.Send - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/stream-chat-angular/src/lib/icon/icon.component.spec.ts b/projects/stream-chat-angular/src/lib/icon/icon.component.spec.ts index b7a2209f..b000593c 100644 --- a/projects/stream-chat-angular/src/lib/icon/icon.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/icon/icon.component.spec.ts @@ -138,6 +138,41 @@ describe('IconComponent', () => { expect(queryIcon('arrow-down')).not.toBeNull(); }); + it('should display chat-bubble icon', () => { + component.icon = 'chat-bubble'; + fixture.detectChanges(); + + expect(queryIcon('chat-bubble')).not.toBeNull(); + }); + + it('should display attach icon', () => { + component.icon = 'attach'; + fixture.detectChanges(); + + expect(queryIcon('attach')).not.toBeNull(); + }); + + it('should display unspecified-filetype icon', () => { + component.icon = 'unspecified-filetype'; + fixture.detectChanges(); + + expect(queryIcon('unspecified-filetype')).not.toBeNull(); + }); + + it('should display download icon', () => { + component.icon = 'download'; + fixture.detectChanges(); + + expect(queryIcon('download')).not.toBeNull(); + }); + + it('should display error icon', () => { + component.icon = 'error'; + fixture.detectChanges(); + + expect(queryIcon('error')).not.toBeNull(); + }); + it('should not display anything if #icon is not provided', () => { expect(nativeElement.innerHTML).not.toContain('svg'); }); diff --git a/projects/stream-chat-angular/src/lib/icon/icon.component.ts b/projects/stream-chat-angular/src/lib/icon/icon.component.ts index 0e7e74bd..142b172a 100644 --- a/projects/stream-chat-angular/src/lib/icon/icon.component.ts +++ b/projects/stream-chat-angular/src/lib/icon/icon.component.ts @@ -17,7 +17,12 @@ export type Icon = | 'arrow-right' | 'menu' | 'arrow-up' - | 'arrow-down'; + | 'arrow-down' + | 'chat-bubble' + | 'attach' + | 'unspecified-filetype' + | 'download' + | 'error'; /** * The `Icon` component can be used to display different icons (i. e. message delivered icon). diff --git a/projects/stream-chat-angular/src/lib/loading-indicator-placeholder/loading-indicator-placeholder.component.spec.ts b/projects/stream-chat-angular/src/lib/loading-indicator-placeholder/loading-indicator-placeholder.component.spec.ts index a6181dee..2a411a15 100644 --- a/projects/stream-chat-angular/src/lib/loading-indicator-placeholder/loading-indicator-placeholder.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/loading-indicator-placeholder/loading-indicator-placeholder.component.spec.ts @@ -32,7 +32,10 @@ describe('LoadingIndicatorPlaceholderComponent', () => { By.directive(LoadingIndicatorComponent) ).componentInstance as LoadingIndicatorComponent; - expect(loadingIndicatorComponent.color).toBe('#006CFF'); + expect(loadingIndicatorComponent.color).toBe( + `var(--str-chat__loading-indicator-color, var(--str-chat__primary-color, '#006CFF'))` + ); + expect(loadingIndicatorComponent.size).toBe(15); component.color = 'red'; diff --git a/projects/stream-chat-angular/src/lib/loading-indicator-placeholder/loading-indicator-placeholder.component.ts b/projects/stream-chat-angular/src/lib/loading-indicator-placeholder/loading-indicator-placeholder.component.ts index e64c444c..f850118e 100644 --- a/projects/stream-chat-angular/src/lib/loading-indicator-placeholder/loading-indicator-placeholder.component.ts +++ b/projects/stream-chat-angular/src/lib/loading-indicator-placeholder/loading-indicator-placeholder.component.ts @@ -18,7 +18,9 @@ export class LoadingIndicatorPlaceholderComponent { /** * The color of the indicator */ - @Input() color = '#006CFF'; + @Input() + color = `var(--str-chat__loading-indicator-color, var(--str-chat__primary-color, '#006CFF'))`; + constructor(public customTemplatesService: CustomTemplatesService) {} getLoadingIndicatorContext(): LoadingIndicatorContext { diff --git a/projects/stream-chat-angular/src/lib/loading-indicator/loading-indicator.component.html b/projects/stream-chat-angular/src/lib/loading-indicator/loading-indicator.component.html index dcdc03ca..aa142396 100644 --- a/projects/stream-chat-angular/src/lib/loading-indicator/loading-indicator.component.html +++ b/projects/stream-chat-angular/src/lib/loading-indicator/loading-indicator.component.html @@ -7,7 +7,13 @@ xmlns="http://www.w3.org/2000/svg" > - + diff --git a/projects/stream-chat-angular/src/lib/loading-indicator/loading-indicator.component.ts b/projects/stream-chat-angular/src/lib/loading-indicator/loading-indicator.component.ts index 914ab8e9..6c754345 100644 --- a/projects/stream-chat-angular/src/lib/loading-indicator/loading-indicator.component.ts +++ b/projects/stream-chat-angular/src/lib/loading-indicator/loading-indicator.component.ts @@ -1,4 +1,5 @@ import { Component, Input } from '@angular/core'; +import { v4 as uuidv4 } from 'uuid'; /** * The `LoadingIndicator` component displays a spinner to indicate that an action is in progress. @@ -16,7 +17,10 @@ export class LoadingIndicatorComponent { /** * The color of the indicator */ - @Input() color = '#006CFF'; + @Input() + color = `var(--str-chat__loading-indicator-color, var(--str-chat__primary-color, '#006CFF'))`; + + linearGradientId = uuidv4(); constructor() {} } diff --git a/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.html b/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.html index 40e505ea..6898a0fe 100644 --- a/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.html +++ b/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.html @@ -1,8 +1,8 @@
    -
diff --git a/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.spec.ts b/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.spec.ts index 21e98507..bcc853ec 100644 --- a/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.spec.ts @@ -15,6 +15,7 @@ import { AutocompleteTextareaComponent } from './autocomplete-textarea.component import { Subject } from 'rxjs'; import { EmojiInputService } from '../emoji-input.service'; import { DefaultStreamChatGenerics } from '../../types'; +import { ThemeService } from '../../theme.service'; describe('AutocompleteTextareaComponent', () => { let component: AutocompleteTextareaComponent; @@ -50,6 +51,10 @@ describe('AutocompleteTextareaComponent', () => { provide: EmojiInputService, useValue: { emojiInput$ }, }, + { + provide: ThemeService, + useValue: { themeVersion: '2' }, + }, ], }).compileComponents(); fixture = TestBed.createComponent(AutocompleteTextareaComponent); @@ -137,6 +142,27 @@ describe('AutocompleteTextareaComponent', () => { expect(spy).not.toHaveBeenCalled(); }); + it('should increase and decrease textarea height with text input', () => { + const textarea = queryTextarea(); + textarea!.value = 'This is my message'; + fixture.detectChanges(); + const initialHeight = textarea!.offsetHeight; + textarea!.value = 'This is my message \n'; + textarea?.dispatchEvent( + new KeyboardEvent('input', { key: 'Enter', shiftKey: true }) + ); + fixture.detectChanges(); + const newHeight = textarea!.offsetHeight; + + expect(newHeight).toBeGreaterThan(initialHeight); + + component.value = ''; + component.ngOnChanges({ value: {} as SimpleChange }); + fixture.detectChanges(); + + expect(textarea!.offsetHeight).toBeLessThan(newHeight); + }); + it('should add channel members to autocomplete config', () => { expect(component.autocompleteConfig.mentions![0].items).toEqual([ { @@ -408,4 +434,15 @@ describe('AutocompleteTextareaComponent', () => { expect(textarea.value).toEqual('Emoji here: 🥑!'); expect(spy).toHaveBeenCalledWith('Emoji here: 🥑!'); }); + + it('should set initial height of the textarea based on value received', () => { + const textarea = queryTextarea(); + textarea!.value = 'This is my \n multiline message'; + component.ngAfterViewInit(); + fixture.detectChanges(); + + const height = parseInt(textarea?.style.height?.replace('px', '') || ''); + + expect(height).toBeGreaterThan(0); + }); }); diff --git a/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.ts b/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.ts index 6846c08a..0584a363 100644 --- a/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.ts +++ b/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.ts @@ -1,4 +1,5 @@ import { + AfterViewInit, Component, ElementRef, EventEmitter, @@ -26,6 +27,7 @@ import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { TransliterationService } from '../../transliteration.service'; import { EmojiInputService } from '../emoji-input.service'; import { CustomTemplatesService } from '../../custom-templates.service'; +import { ThemeService } from '../../theme.service'; /** * The `AutocompleteTextarea` component is used by the [`MessageInput`](./MessageInputComponent.mdx) component to display the input HTML element where users can type their message. @@ -36,13 +38,18 @@ import { CustomTemplatesService } from '../../custom-templates.service'; styles: [], }) export class AutocompleteTextareaComponent - implements TextareaInterface, OnChanges + implements TextareaInterface, OnChanges, AfterViewInit { - @HostBinding() class = 'str-chat__textarea'; + @HostBinding() class = + 'str-chat__textarea str-chat__message-textarea-angular-host'; /** * The value of the input HTML element. */ @Input() value = ''; + /** + * Placeholder of the textarea + */ + @Input() placeholder = ''; /** * If true, users can mention other users in messages. You can also set this input on the [`MessageInput`](./MessageInputComponent.mdx/#inputs-and-outputs) component. */ @@ -69,6 +76,7 @@ export class AutocompleteTextareaComponent commandAutocompleteItemTemplate: | TemplateRef | undefined; + themeVersion: '1' | '2'; private readonly autocompleteKey = 'autocompleteLabel'; private readonly mentionTriggerChar = '@'; private readonly commandTriggerChar = '/'; @@ -109,7 +117,8 @@ export class AutocompleteTextareaComponent private chatClientService: ChatClientService, private transliterationService: TransliterationService, private emojiInputService: EmojiInputService, - private customTemplatesService: CustomTemplatesService + private customTemplatesService: CustomTemplatesService, + private themeService: ThemeService ) { this.searchTerm$ .pipe(debounceTime(300), distinctUntilChanged()) @@ -157,6 +166,7 @@ export class AutocompleteTextareaComponent this.userMentionConfig, this.slashCommandConfig, ]; + this.themeVersion = this.themeService.themeVersion; } ngOnChanges(changes: SimpleChanges): void { @@ -175,6 +185,15 @@ export class AutocompleteTextareaComponent if (changes.mentionScope) { void this.updateMentionOptions(this.searchTerm$.getValue()); } + if (changes.value && !this.value && this.messageInput) { + this.messageInput.nativeElement.style.height = 'auto'; + } + } + + ngAfterViewInit(): void { + if (this.messageInput.nativeElement.scrollHeight > 0) { + this.adjustTextareaHeight(); + } } filter(searchString: string, items: { autocompleteLabel: string }[]) { @@ -210,6 +229,7 @@ export class AutocompleteTextareaComponent inputChanged() { this.valueChange.emit(this.messageInput.nativeElement.value); + this.adjustTextareaHeight(); } inputLeft() { @@ -222,6 +242,13 @@ export class AutocompleteTextareaComponent this.send.next(); } + private adjustTextareaHeight() { + if (this.themeVersion === '2') { + this.messageInput.nativeElement.style.height = ''; + this.messageInput.nativeElement.style.height = `${this.messageInput.nativeElement.scrollHeight}px`; + } + } + private transliterate(s: string) { if (this.transliterationService) { return this.transliterationService.transliterate(s); diff --git a/projects/stream-chat-angular/src/lib/message-input/message-input.component.html b/projects/stream-chat-angular/src/lib/message-input/message-input.component.html index 051a0521..a8f42211 100644 --- a/projects/stream-chat-angular/src/lib/message-input/message-input.component.html +++ b/projects/stream-chat-angular/src/lib/message-input/message-input.component.html @@ -1,4 +1,5 @@
-
+
{{ "streamChat.Reply to Message" | translate }}
@@ -80,10 +77,7 @@ " >
- +
- {{ cooldown$ | async }} -
- - + + + +