@nyaruka/temba-components 0.62.3 → 0.63.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/demo/index.html +0 -11
  3. package/dist/{2e129758.js → bb49b9c8.js} +423 -256
  4. package/dist/index.js +423 -256
  5. package/dist/static/svg/index.svg +1 -1
  6. package/dist/sw.js +1 -1
  7. package/dist/sw.js.map +1 -1
  8. package/dist/templates/components-body.html +1 -1
  9. package/dist/templates/components-head.html +1 -1
  10. package/out-tsc/src/RapidElement.js +6 -0
  11. package/out-tsc/src/RapidElement.js.map +1 -1
  12. package/out-tsc/src/compose/Compose.js +189 -88
  13. package/out-tsc/src/compose/Compose.js.map +1 -1
  14. package/out-tsc/src/contacts/ContactChat.js +4 -5
  15. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  16. package/out-tsc/src/interfaces.js.map +1 -1
  17. package/out-tsc/src/lightbox/Lightbox.js +1 -0
  18. package/out-tsc/src/lightbox/Lightbox.js.map +1 -1
  19. package/out-tsc/src/select/Select.js +1 -1
  20. package/out-tsc/src/select/Select.js.map +1 -1
  21. package/out-tsc/src/tabpane/Tab.js.map +1 -1
  22. package/out-tsc/src/tabpane/TabPane.js +155 -25
  23. package/out-tsc/src/tabpane/TabPane.js.map +1 -1
  24. package/out-tsc/src/textinput/TextInput.js +1 -0
  25. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  26. package/out-tsc/src/thumbnail/Thumbnail.js +121 -0
  27. package/out-tsc/src/thumbnail/Thumbnail.js.map +1 -0
  28. package/out-tsc/src/utils/index.js +6 -0
  29. package/out-tsc/src/utils/index.js.map +1 -1
  30. package/out-tsc/src/vectoricon/index.js +2 -1
  31. package/out-tsc/src/vectoricon/index.js.map +1 -1
  32. package/out-tsc/temba-modules.js +2 -0
  33. package/out-tsc/temba-modules.js.map +1 -1
  34. package/out-tsc/test/temba-compose.test.js +2 -1
  35. package/out-tsc/test/temba-compose.test.js.map +1 -1
  36. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  37. package/package.json +1 -1
  38. package/screenshots/truth/compose/attachments-and-send-button.png +0 -0
  39. package/screenshots/truth/compose/attachments-no-send-button.png +0 -0
  40. package/screenshots/truth/compose/attachments-with-all-files-and-click-send.png +0 -0
  41. package/screenshots/truth/compose/attachments-with-all-files.png +0 -0
  42. package/screenshots/truth/compose/attachments-with-failure-files.png +0 -0
  43. package/screenshots/truth/compose/attachments-with-success-files-and-click-send.png +0 -0
  44. package/screenshots/truth/compose/attachments-with-success-files.png +0 -0
  45. package/screenshots/truth/compose/chatbox-attachments-counter-and-send-button.png +0 -0
  46. package/screenshots/truth/compose/chatbox-attachments-counter-no-send-button.png +0 -0
  47. package/screenshots/truth/compose/chatbox-attachments-no-counter-and-send-button.png +0 -0
  48. package/screenshots/truth/compose/chatbox-attachments-no-counter-no-send-button.png +0 -0
  49. package/screenshots/truth/compose/chatbox-counter-and-send-button.png +0 -0
  50. package/screenshots/truth/compose/chatbox-counter-no-send-button.png +0 -0
  51. package/screenshots/truth/compose/chatbox-no-counter-and-send-button.png +0 -0
  52. package/screenshots/truth/compose/chatbox-no-counter-no-send-button.png +0 -0
  53. package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files-and-click-send.png +0 -0
  54. package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files.png +0 -0
  55. package/screenshots/truth/compose/chatbox-no-text-attachments-with-failure-files.png +0 -0
  56. package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files-and-click-send.png +0 -0
  57. package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files.png +0 -0
  58. package/screenshots/truth/compose/chatbox-with-text-and-click-send.png +0 -0
  59. package/screenshots/truth/compose/chatbox-with-text-and-hit-enter.png +0 -0
  60. package/screenshots/truth/compose/chatbox-with-text-and-spaces.png +0 -0
  61. package/screenshots/truth/compose/chatbox-with-text-and-url.png +0 -0
  62. package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-click-send.png +0 -0
  63. package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-hit-enter.png +0 -0
  64. package/screenshots/truth/compose/chatbox-with-text-attachments-no-files.png +0 -0
  65. package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-click-send.png +0 -0
  66. package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-hit-enter.png +0 -0
  67. package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files.png +0 -0
  68. package/screenshots/truth/compose/chatbox-with-text-attachments-with-failure-files.png +0 -0
  69. package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-click-send.png +0 -0
  70. package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-hit-enter.png +0 -0
  71. package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files.png +0 -0
  72. package/screenshots/truth/compose/chatbox-with-text-no-spaces.png +0 -0
  73. package/screenshots/truth/compose/chatbox-with-text.png +0 -0
  74. package/screenshots/truth/contacts/compose-attachments-no-text-failure.png +0 -0
  75. package/screenshots/truth/contacts/compose-attachments-no-text-success.png +0 -0
  76. package/screenshots/truth/contacts/compose-text-and-attachments-failure-attachments.png +0 -0
  77. package/screenshots/truth/contacts/compose-text-and-attachments-failure-generic.png +0 -0
  78. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text-and-attachments.png +0 -0
  79. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text.png +0 -0
  80. package/screenshots/truth/contacts/compose-text-and-attachments-success.png +0 -0
  81. package/screenshots/truth/contacts/compose-text-no-attachments-failure.png +0 -0
  82. package/screenshots/truth/contacts/compose-text-no-attachments-success.png +0 -0
  83. package/screenshots/truth/contacts/contact-active-default.png +0 -0
  84. package/screenshots/truth/contacts/contact-active-show-chatbox.png +0 -0
  85. package/screenshots/truth/contacts/contact-archived-hide-chatbox.png +0 -0
  86. package/screenshots/truth/contacts/contact-blocked-hide-chatbox.png +0 -0
  87. package/screenshots/truth/contacts/contact-stopped-hide-chatbox.png +0 -0
  88. package/src/RapidElement.ts +7 -0
  89. package/src/compose/Compose.ts +199 -104
  90. package/src/contacts/ContactChat.ts +4 -5
  91. package/src/interfaces.ts +9 -0
  92. package/src/lightbox/Lightbox.ts +1 -0
  93. package/src/select/Select.ts +1 -1
  94. package/src/tabpane/Tab.ts +1 -1
  95. package/src/tabpane/TabPane.ts +154 -26
  96. package/src/textinput/TextInput.ts +1 -0
  97. package/src/thumbnail/Thumbnail.ts +119 -0
  98. package/src/utils/index.ts +8 -1
  99. package/src/vectoricon/index.ts +2 -1
  100. package/static/svg/index.svg +1 -1
  101. package/static/svg/work/traced/dotpoints-01.svg +1 -0
  102. package/static/svg/work/used/dotpoints-01.svg +3 -0
  103. package/temba-modules.ts +2 -0
  104. package/test/temba-compose.test.ts +8 -2
  105. package/test/temba-contact-chat.test.ts +2 -2
@@ -12,11 +12,12 @@ export class TabPane extends RapidElement {
12
12
  display: flex;
13
13
  flex-direction: column;
14
14
  min-height: 0;
15
- z-index: 0;
15
+ flex-grow: 1;
16
16
  }
17
17
 
18
18
  .tabs {
19
19
  display: flex;
20
+ align-items: stretch;
20
21
  }
21
22
 
22
23
  .tab {
@@ -34,7 +35,15 @@ export class TabPane extends RapidElement {
34
35
  color: var(--color-text-dark);
35
36
  --icon-color: var(--color-text-dark);
36
37
  white-space: nowrap;
37
- transition: all 100ms ease-in-out;
38
+ transition: all 100ms linear;
39
+ }
40
+
41
+ .focusedname .tab .name {
42
+ transition: all 0s linear !important;
43
+ }
44
+
45
+ .focusedname .tab.selected .name {
46
+ transition: all 200ms linear !important;
38
47
  }
39
48
 
40
49
  .tab.hidden {
@@ -71,6 +80,22 @@ export class TabPane extends RapidElement {
71
80
  }
72
81
  }
73
82
 
83
+ .focusedname .tab.selected {
84
+ transform: none;
85
+ }
86
+
87
+ .focusedname .tab .name {
88
+ max-width: 0px;
89
+ margin: 0;
90
+ transition: max-width 200ms linear, margin 200ms linear;
91
+ }
92
+
93
+ .focusedname .tab.selected .name {
94
+ margin-left: 0.4em;
95
+ max-width: 200px;
96
+ margin-right: 0.4em;
97
+ }
98
+
74
99
  .tab {
75
100
  transform: scale(0.9) translate(0em, -0.05em);
76
101
  --icon-color: #aaa;
@@ -78,20 +103,21 @@ export class TabPane extends RapidElement {
78
103
  }
79
104
 
80
105
  .tab.selected {
81
- z-index: 2 !important;
82
106
  }
83
107
 
84
108
  .tab.selected,
85
109
  .tab.selected:hover {
86
110
  cursor: default;
87
111
  box-shadow: 0px -3px 3px 1px rgba(0, 0, 0, 0.02);
88
- background: #fff;
112
+ background: var(--focused-tab-color, #fff);
89
113
  transform: scale(1) translateY(0em);
90
- z-index: 0;
91
114
  --icon-color: #666;
92
115
  color: #666;
93
116
  }
94
117
 
118
+ .bottom .tab.selected {
119
+ }
120
+
95
121
  .tab:hover {
96
122
  --icon-color: #666;
97
123
  color: #666;
@@ -102,12 +128,11 @@ export class TabPane extends RapidElement {
102
128
  display: flex;
103
129
  flex-direction: column;
104
130
  flex-grow: 1;
105
- background: #fff;
131
+ background: var(--focused-tab-color, #fff);
106
132
  border-radius: var(--curvature);
107
133
  box-shadow: 2px 5px 12px 2px rgba(0, 0, 0, 0.09),
108
134
  3px 3px 2px 1px rgba(0, 0, 0, 0.05);
109
135
  min-height: 0;
110
- z-index: 1;
111
136
  }
112
137
 
113
138
  .pane.first {
@@ -133,30 +158,82 @@ export class TabPane extends RapidElement {
133
158
  background: var(--color-alert);
134
159
  color: #fff;
135
160
  }
161
+
162
+ .bottom.tabs .tab {
163
+ border-radius: 0em;
164
+ }
165
+
166
+ .bottom.pane {
167
+ border-radius: 0em;
168
+ }
169
+
170
+ .bottom.pane.first {
171
+ border-bottom-left-radius: 0px;
172
+ }
173
+
174
+ .bottom .tab.first {
175
+ border-bottom-left-radius: var(--curvature);
176
+ }
177
+
178
+ .embedded.pane {
179
+ box-shadow: none;
180
+ margin: 0;
181
+ }
182
+
183
+ .embedded.tabs {
184
+ margin: 0;
185
+ }
186
+
187
+ .embedded .tab {
188
+ }
189
+
190
+ .embedded.tabs .tab.selected {
191
+ box-shadow: none !important;
192
+ }
193
+
194
+ .embedded.pane {
195
+ // padding: 0.3em;
196
+ }
136
197
  `;
137
198
  }
138
199
 
200
+ @property({ type: Boolean })
201
+ embedded = false;
202
+
139
203
  @property({ type: Boolean })
140
204
  collapses = false;
141
205
 
206
+ // are the tabs on the bottom of the pane?
207
+ @property({ type: Boolean })
208
+ bottom = false;
209
+
210
+ // Only shows the name if the tab is focused
211
+ @property({ type: Boolean })
212
+ focusedName = false;
213
+
142
214
  @property({ type: Number })
143
- index = 0;
215
+ index = -1;
216
+
217
+ @property({ type: String })
218
+ refresh = '';
144
219
 
145
220
  private handleTabClick(event: MouseEvent): void {
146
221
  this.index = parseInt(
147
222
  (event.currentTarget as HTMLDivElement).dataset.index
148
223
  );
224
+ event.preventDefault();
225
+ event.stopPropagation();
149
226
  this.requestUpdate('index');
150
227
  }
151
228
 
152
229
  public updated(changedProperties: Map<string, any>) {
153
230
  super.updated(changedProperties);
154
231
  if (changedProperties.has('index')) {
155
- if (this.children.length > this.index) {
156
- for (let i = 0; i < this.children.length; i++) {
157
- const tab = this.children[i] as Tab;
232
+ const tabs = this.getTabs();
233
+ if (tabs.length > this.index) {
234
+ for (let i = 0; i < tabs.length; i++) {
235
+ const tab = tabs[i];
158
236
  tab.selected = i == this.index;
159
-
160
237
  if (tab.selected) {
161
238
  tab.style.display = 'flex';
162
239
  } else {
@@ -168,30 +245,67 @@ export class TabPane extends RapidElement {
168
245
  }
169
246
 
170
247
  // if our current tab is hidden, select the first visible one
171
- if (this.getTab(this.index).hidden) {
172
- for (let i = 0; i < this.children.length; i++) {
173
- const tab = this.getTab(i);
174
- if (!tab.hidden) {
175
- this.index = i;
176
- return;
248
+ if (this.index > -1) {
249
+ const tabs = this.getTabs();
250
+ if (this.getTab(this.index).hidden) {
251
+ for (let i = 0; i < tabs.length; i++) {
252
+ const tab = this.getTab(i);
253
+ if (!tab.hidden) {
254
+ this.index = i;
255
+ return;
256
+ }
177
257
  }
178
258
  }
179
259
  }
180
260
  }
181
261
 
262
+ public getCurrentTab(): Tab {
263
+ return this.getTabs()[this.index];
264
+ }
265
+
182
266
  public getTab(index: number): Tab {
183
- return this.children.item(index) as Tab;
267
+ return this.getTabs()[index];
184
268
  }
185
269
 
186
- public render(): TemplateResult {
270
+ public handleTabContentChanged() {
271
+ this.requestUpdate();
272
+ }
273
+
274
+ public getTabs(): Tab[] {
187
275
  const tabs: Tab[] = [];
188
- for (const tab of this.children) {
189
- tabs.push(tab as Tab);
276
+ for (const t of this.children) {
277
+ if (t.tagName === 'TEMBA-TAB') {
278
+ const tab = t as Tab;
279
+ tabs.push(tab);
280
+ }
190
281
  }
282
+ return tabs;
283
+ }
284
+
285
+ public render(): TemplateResult {
286
+ const tabs = this.getTabs();
191
287
 
192
288
  return html`
289
+ ${this.bottom
290
+ ? html`<div
291
+ class="pane ${getClasses({
292
+ first: this.index == 0,
293
+ embedded: this.embedded,
294
+ bottom: this.bottom,
295
+ })}"
296
+ >
297
+ <slot></slot>
298
+ </div>`
299
+ : null}
300
+
193
301
  <div
194
- class="tabs ${getClasses({ tabs: true, collapses: this.collapses })}"
302
+ class="tabs ${getClasses({
303
+ tabs: true,
304
+ bottom: this.bottom,
305
+ collapses: this.collapses,
306
+ embedded: this.embedded,
307
+ focusedname: this.focusedName,
308
+ })}"
195
309
  >
196
310
  ${tabs.map(
197
311
  (tab, index) => html`
@@ -200,6 +314,7 @@ export class TabPane extends RapidElement {
200
314
  data-index=${index}
201
315
  class="${getClasses({
202
316
  tab: true,
317
+ first: index == 0,
203
318
  selected: index == this.index,
204
319
  hidden: tab.hidden,
205
320
  notify: tab.notify,
@@ -226,10 +341,23 @@ export class TabPane extends RapidElement {
226
341
  </div>
227
342
  `
228
343
  )}
344
+
345
+ <div style="flex-grow:1"></div>
346
+ <div style="display:flex; align-items:center">
347
+ <slot name="tab-right"></slot>
348
+ </div>
229
349
  </div>
230
- <div class="pane ${this.index === 0 ? 'first' : null}">
231
- <slot></slot>
232
- </div>
350
+ ${!this.bottom
351
+ ? html`<div
352
+ class="pane ${getClasses({
353
+ first: this.index == 0,
354
+ embedded: this.embedded,
355
+ bottom: this.bottom,
356
+ })}"
357
+ >
358
+ <slot></slot>
359
+ </div>`
360
+ : null}
233
361
  `;
234
362
  }
235
363
  }
@@ -52,6 +52,7 @@ export class TextInput extends FormElement {
52
52
 
53
53
  textarea {
54
54
  height: var(--textarea-height);
55
+ min-height: var(--textarea-min-height, var(--textarea-height));
55
56
  }
56
57
 
57
58
  .textinput {
@@ -0,0 +1,119 @@
1
+ import { css, html } from 'lit';
2
+ import { RapidElement } from '../RapidElement';
3
+ import { property } from 'lit/decorators.js';
4
+ import { getClasses } from '../utils';
5
+ import { Lightbox } from '../lightbox/Lightbox';
6
+ import { styleMap } from 'lit-html/directives/style-map.js';
7
+
8
+ export class Thumbnail extends RapidElement {
9
+ static get styles() {
10
+ return css`
11
+ :host {
12
+ display: inline-block;
13
+ }
14
+
15
+ .zooming.wrapper {
16
+ padding: 0 !important;
17
+ border-radius: 0;
18
+ }
19
+
20
+ .zooming .thumb {
21
+ border-radius: 0;
22
+ }
23
+
24
+ .wrapper {
25
+ padding: var(--thumb-padding, 0.4em);
26
+ background: #fff;
27
+ border-radius: var(--curvature);
28
+ box-shadow: var(--widget-box-shadow);
29
+ }
30
+
31
+ .thumb {
32
+ }
33
+ `;
34
+ }
35
+
36
+ @property({ type: String })
37
+ url: string;
38
+
39
+ @property({ type: String })
40
+ label: string;
41
+
42
+ @property({ type: Boolean })
43
+ zoom = true;
44
+
45
+ @property({ type: Boolean })
46
+ zooming = false;
47
+
48
+ public handleClick(evt: MouseEvent) {
49
+ window.setTimeout(() => {
50
+ const lightbox = document.querySelector('temba-lightbox') as Lightbox;
51
+ lightbox.showElement(this);
52
+ }, 0);
53
+ }
54
+
55
+ public render() {
56
+ if (this.zooming) {
57
+ return html`
58
+ <div
59
+ class="${getClasses({ wrapper: true })}"
60
+ style=${styleMap({
61
+ background: 'red',
62
+ borderRadius: '0',
63
+ boxShadow: 'var(--widget-box-shadow)',
64
+ })}
65
+ >
66
+ <div
67
+ class="thumb"
68
+ style=${styleMap({
69
+ backgroundImage: `url(${this.url})`,
70
+ backgroundSize: 'contain',
71
+ backgroundPosition: 'center',
72
+ backgroundRepeat: 'no-repeat',
73
+ maxHeight: 'var(--thumb-size, 4em)',
74
+ height: 'var(--thumb-size, 4em)',
75
+ width: 'var(--thumb-size, 4em)',
76
+ borderRadius: '0',
77
+
78
+ display: 'flex',
79
+ alignItems: 'center',
80
+ justifyContent: 'center',
81
+ fontWeight: '400',
82
+ color: '#bbb',
83
+ })}
84
+ >
85
+ ${this.label}
86
+ </div>
87
+ </div>
88
+ `;
89
+ } else {
90
+ return html`
91
+ <div class="${getClasses({ wrapper: true })}" style=${styleMap({
92
+ padding: 'var(--thumb-padding, 0.4em)',
93
+ background: '#fff',
94
+ borderRadius: 'var(--curvature)',
95
+ boxShadow: 'var(--widget-box-shadow)',
96
+ })}">
97
+
98
+ <div class="thumb" style=${styleMap({
99
+ backgroundImage: `url(${this.url})`,
100
+ backgroundSize: 'cover',
101
+ backgroundPosition: 'center',
102
+ maxHeight: 'var(--thumb-size, 4em)',
103
+ height: 'var(--thumb-size, 4em)',
104
+ width: 'var(--thumb-size, 4em)',
105
+ borderRadius: 'var(--curvature)',
106
+
107
+ display: 'flex',
108
+ alignItems: 'center',
109
+ justifyContent: 'center',
110
+ fontWeight: '400',
111
+ color: '#bbb',
112
+ })}>
113
+ ${this.label}
114
+ </div>
115
+ </div>
116
+ `;
117
+ }
118
+ }
119
+ }
@@ -2,7 +2,7 @@
2
2
  import { html, TemplateResult } from 'lit-html';
3
3
  import { Button } from '../button/Button';
4
4
  import { Dialog } from '../dialog/Dialog';
5
- import { ContactField, Ticket, User } from '../interfaces';
5
+ import { Attachment, ContactField, Ticket, User } from '../interfaces';
6
6
  import ColorHash from 'color-hash';
7
7
 
8
8
  export const DEFAULT_MEDIA_ENDPOINT = '/api/v2/media.json';
@@ -644,6 +644,13 @@ export const formatFileSize = (bytes: number, decimalPoint: number): string => {
644
644
  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
645
645
  };
646
646
 
647
+ export const isImageAttachment = (attachment: Attachment) => {
648
+ if (attachment) {
649
+ return attachment.content_type.split('/')[0] === 'image';
650
+ }
651
+ return false;
652
+ };
653
+
647
654
  export const stopEvent = (event: Event) => {
648
655
  if (event) {
649
656
  event.stopPropagation();
@@ -1,5 +1,5 @@
1
1
  // for cache busting we dynamically generate a fingerprint, use yarn svg to update
2
- export const SVG_FINGERPRINT = '2894cf52370eeff9b466593695609d09';
2
+ export const SVG_FINGERPRINT = '9c84101e8549effb3e17279445ff41e3';
3
3
 
4
4
  // only icons below are included in the sprite sheet
5
5
  export enum Icon {
@@ -144,6 +144,7 @@ export enum Icon {
144
144
  prometheus = 'prometheus',
145
145
  progress_spinner = 'refresh-cw-04',
146
146
  featured = 'star-01',
147
+ quick_replies = 'dotpoints-01',
147
148
  recording = 'microphone-01',
148
149
  referral = 'user-right-01',
149
150
  resend = 'refresh-cw-05',