Skip to content

Commit

Permalink
Merge pull request #389 from nyaruka/notifications
Browse files Browse the repository at this point in the history
Add notification component
  • Loading branch information
ericnewcomer authored Jan 10, 2024
2 parents 4829781 + d4d7b0a commit 4b90e71
Show file tree
Hide file tree
Showing 61 changed files with 338 additions and 65 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screenshots/truth/compose/chatbox-counter-and-send-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screenshots/truth/compose/chatbox-counter-no-send-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screenshots/truth/compose/chatbox-no-counter-and-send-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screenshots/truth/compose/chatbox-no-counter-no-send-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screenshots/truth/compose/chatbox-with-text-and-click-send.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screenshots/truth/compose/chatbox-with-text-and-hit-enter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screenshots/truth/compose/chatbox-with-text-and-spaces.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screenshots/truth/compose/chatbox-with-text-and-url.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screenshots/truth/compose/chatbox-with-text-no-spaces.png
Binary file modified screenshots/truth/compose/chatbox-with-text.png
Binary file modified screenshots/truth/contacts/contact-active-default.png
Binary file modified screenshots/truth/contacts/contact-active-show-chatbox.png
Binary file modified screenshots/truth/contacts/contact-archived-hide-chatbox.png
Binary file modified screenshots/truth/contacts/contact-blocked-hide-chatbox.png
Binary file modified screenshots/truth/contacts/contact-stopped-hide-chatbox.png
Binary file removed screenshots/truth/contacts/details.png
Diff not rendered.
Binary file removed screenshots/truth/contacts/fields-updated.png
Diff not rendered.
Binary file removed screenshots/truth/contacts/fields.png
Diff not rendered.
2 changes: 1 addition & 1 deletion src/compose/Compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class Compose extends FormElement {
--compose-curvature,
var(--curvature) var(--curvature) 0px 0px
);
--textarea-min-height: 4em;
--textarea-min-height: var(--textarea-min-height, 4em);
--widget-box-shadow: none;
padding: var(--compose-padding, 0px);
}
Expand Down
10 changes: 9 additions & 1 deletion src/contacts/ContactChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,24 @@ export class ContactChat extends ContactStoreElement {
}
temba-contact-history {
border-bottom: 2px solid #f6f6f6;
border-bottom: 1px solid #f6f6f6;
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.chatbox {
box-shadow: 0px -5px 1rem 0rem rgba(0, 0, 0, 0.07);
display: flex;
flex-direction: column;
--textarea-min-height: 1em;
--textarea-height: 1.2em;
--widget-box-shadow-focused: none;
}
.chatbox:focus-within {
--textarea-height: 4em;
}
.chatbox.full {
Expand Down
2 changes: 1 addition & 1 deletion src/contacts/ContactTickets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ export class ContactTickets extends StoreElement {
: html`
<div>
<temba-dropdown
drop_align="right"
right
arrowsize="8"
arrowoffset="-44"
offsety="8"
Expand Down
1 change: 0 additions & 1 deletion src/contacts/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export const getEventStyles = () => {
cursor: pointer;
width: 100%;
opacity: 1;
z-index: 1;
}
.event-count temba-icon {
Expand Down
58 changes: 48 additions & 10 deletions src/dropdown/Dropdown.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { css, html, TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import { RapidElement } from '../RapidElement';
import { getClasses } from '../utils';

export class Dropdown extends RapidElement {
static get styles() {
Expand Down Expand Up @@ -66,20 +67,34 @@ export class Dropdown extends RapidElement {
opacity: 0;
transition: opacity var(--transition-speed) ease-in-out;
pointer-events: none;
z-index: 1;
}
.mask.open {
opacity: 1;
pointer-events: auto;
}
.right {
right: 0;
}
`;
}

@property({ type: Boolean })
open = false;

@property({ type: String, attribute: 'drop_align' })
dropAlign = 'left';
@property({ type: Boolean })
top = false;

@property({ type: Boolean })
bottom = false;

@property({ type: Boolean })
left = false;

@property({ type: Boolean })
right = false;

@property({ type: Number })
arrowSize = 6;
Expand Down Expand Up @@ -113,7 +128,7 @@ export class Dropdown extends RapidElement {
'.dropdown'
) as HTMLDivElement;

dropdown.addEventListener('blur', (event: any) => {
dropdown.addEventListener('blur', () => {
// we nest this to deal with clicking the toggle to close
// as we don't want it to toggle an immediate open, probably
// a better way to deal with this
Expand All @@ -127,18 +142,17 @@ export class Dropdown extends RapidElement {

public updated(changedProperties: Map<string, any>) {
super.updated(changedProperties);
const dropdown = this.shadowRoot.querySelector(
'.dropdown'
) as HTMLDivElement;

if (changedProperties.has('offsetY') || changedProperties.has('offsetX')) {
const dropdown = this.shadowRoot.querySelector(
'.dropdown'
) as HTMLDivElement;

dropdown.style.marginTop = this.offsetY + 'px';
if (dropdown.offsetLeft + dropdown.clientWidth > window.outerWidth) {
dropdown.style.marginLeft =
'-' + (dropdown.clientWidth - this.clientWidth - this.offsetX) + 'px';
} else {
if (this.dropAlign === 'right') {
if (this.right) {
dropdown.style.marginRight = this.offsetX + 'px';
} else {
dropdown.style.marginLeft = this.offsetX + 'px';
Expand All @@ -152,9 +166,28 @@ export class Dropdown extends RapidElement {
} else {
this.classList.remove('open');
}

this.ensureOnScreen();
}
}

public ensureOnScreen() {
window.setTimeout(() => {
const dropdown = this.shadowRoot.querySelector(
'.dropdown'
) as HTMLDivElement;

if (dropdown) {
// dropdown will go off the screen, let's push it up
if (dropdown.getBoundingClientRect().bottom > window.innerHeight) {
const toggle = this.querySelector('div[slot="toggle"]');
dropdown.style.bottom =
this.offsetY + toggle.clientHeight + 10 + 'px';
}
}
}, 100);
}

public handleToggleClicked(event: MouseEvent): void {
event.preventDefault();
event.stopPropagation();
Expand Down Expand Up @@ -187,10 +220,15 @@ export class Dropdown extends RapidElement {
@click=${this.handleToggleClicked}
></slot>
<div
class="dropdown"
class="${getClasses({
dropdown: true,
right: this.right,
left: this.left,
top: this.top,
bottom: this.bottom,
})}"
tabindex="0"
@mousedown=${this.handleDropdownMouseDown}
style="${this.dropAlign == 'right' ? 'right:0' : ''}"
>
<div class="arrow"></div>
<div class="dropdown-wrapper">
Expand Down
154 changes: 154 additions & 0 deletions src/list/NotificationList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { css, html, TemplateResult } from 'lit';
import { TembaList } from './TembaList';
import { StyleInfo } from 'lit-html/directives/style-map.js';
import { Options } from '../options/Options';
import { Icon } from '../vectoricon';

interface Notification {
created_on: string;
type: string;
target_url: string;
is_seen: boolean;
export?: {
type: string;
};
import?: {
type: string;
num_records: number;
};
incident?: {
type: string;
started_on: string;
ended_on?: string;
};
}

const getNotification = (notification: Notification) => {
let icon = null;
let body = null;
let color = '#333';

if (notification.type === 'incident:started') {
color = 'tomato';
if (notification.incident.type === 'org:flagged') {
icon = Icon.incidents;
body =
'Your workspace was flagged, please contact support for assistance.';
} else if (notification.incident.type === 'org:suspended') {
icon = Icon.incidents;
body =
'Your workspace was suspended, please contact support for assistance.';
} else if (notification.incident.type === 'channel:disconnected') {
icon = Icon.channel;
body = 'Your android channel is not connected';
} else if (notification.incident.type === 'webhooks:unhealthy') {
icon = Icon.webhook;
body = 'Your webhook calls are not working properly.';
}
} else if (notification.type === 'import:finished') {
if (notification.import.type === 'contact') {
icon = Icon.contact_import;
body = `Imported ${notification.import.num_records.toLocaleString()} contacts`;
}
} else if (notification.type === 'export:finished') {
if (notification.export.type === 'contact') {
icon = Icon.contact_export;
body = 'Contacts exported';
}
} else if (notification.type === 'tickets:activity') {
icon = Icon.tickets;
body = 'New ticket activity';
} else if (notification.type === 'tickets:opened') {
icon = Icon.tickets;
body = 'New unassigned ticket';
}
return html`<div
style="color:${color};display:flex;align-items:flex-start;flex-direction:row;font-weight:${notification.is_seen
? 300
: 400}"
>
${icon
? html`<div style="margin-right:0.6em">
<temba-icon name="${icon}"></temba-icon>
</div>`
: null}
<div style="display:flex;flex-direction:column">
<div style="line-height:1.1em">${body}</div>
<temba-date
style="font-size:80%"
value=${notification.created_on}
display="duration"
></temba-date>
</div>
</div>`;
};

export class NotificationList extends TembaList {
reverseRefresh = false;
internalFocusDisabled = true;
static get styles() {
return css`
:host {
--option-hover-bg: #f9f9f9;
}
.header {
padding: 0.25em 1em;
background: #f9f9f9;
border-top-left-radius: var(--curvature);
border-top-right-radius: var(--curvature);
display: flex;
color: #999;
border-bottom: 1px solid #f3f3f3;
}
.header temba-icon {
margin-right: 0.35em;
}
.footer {
background: #f9f9f9;
}
.title {
font-weight: normal;
}
`;
}

constructor() {
super();
this.valueKey = 'target_url';
this.renderOption = (notification: Notification): TemplateResult => {
const styles: StyleInfo = {
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'flex-start',
};

if (!notification.is_seen) {
styles['fontWeight'] = '400';
}
return html` ${getNotification(notification)} `;
};
}

public renderHeader(): TemplateResult {
return html`<div class="header">
<temba-icon name="notification"></temba-icon>
<div class="title">Notifications</div>
</div>`;
}

protected handleSelection(event: CustomEvent) {
super.handleSelected(event);
}

public scrollToTop(): void {
// scroll back to the top
window.setTimeout(() => {
const options = this.shadowRoot.querySelector('temba-options') as Options;
options.scrollToTop();
}, 1000);
}
}
10 changes: 9 additions & 1 deletion src/list/TembaList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export class TembaList extends RapidElement {
@property({ type: Boolean })
paused = false;

@property({ type: Boolean })
internalFocusDisabled = false;

@property({ attribute: false })
getNextRefresh: (firstOption: any) => any;

Expand Down Expand Up @@ -148,6 +151,10 @@ export class TembaList extends RapidElement {
this.handleSelected(this.selected);
}
}

if (changedProperties.has('items')) {
//
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -440,7 +447,7 @@ export class TembaList extends RapidElement {
return '';
}

private handleSelection(event: CustomEvent) {
protected handleSelection(event: CustomEvent) {
const { selected, index } = event.detail;

this.selected = selected;
Expand All @@ -460,6 +467,7 @@ export class TembaList extends RapidElement {
?hideShadow=${this.hideShadow}
?collapsed=${this.collapsed}
?loading=${this.loading}
?internalFocusDisabled=${this.internalFocusDisabled}
.renderOption=${this.renderOption}
.renderOptionDetail=${this.renderOptionDetail}
@temba-scroll-threshold=${this.handleScrollThreshold}
Expand Down
Loading

0 comments on commit 4b90e71

Please sign in to comment.