@nyaruka/temba-components 0.113.0 → 0.114.0

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 (130) hide show
  1. package/CHANGELOG.md +21 -2
  2. package/demo/index.html +1 -1
  3. package/dist/temba-components.js +793 -966
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/aliaseditor/AliasEditor.js.map +1 -1
  6. package/out-tsc/src/button/Button.js +6 -2
  7. package/out-tsc/src/button/Button.js.map +1 -1
  8. package/out-tsc/src/chat/Chat.js +29 -7
  9. package/out-tsc/src/chat/Chat.js.map +1 -1
  10. package/out-tsc/src/compose/Compose.js +10 -5
  11. package/out-tsc/src/compose/Compose.js.map +1 -1
  12. package/out-tsc/src/contacts/ContactChat.js +240 -114
  13. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  14. package/out-tsc/src/contacts/ContactFieldEditor.js.map +1 -1
  15. package/out-tsc/src/contacts/events.js.map +1 -1
  16. package/out-tsc/src/contacts/helpers.js +5 -1
  17. package/out-tsc/src/contacts/helpers.js.map +1 -1
  18. package/out-tsc/src/contactsearch/ContactSearch.js +1 -1
  19. package/out-tsc/src/contactsearch/ContactSearch.js.map +1 -1
  20. package/out-tsc/src/dropdown/Dropdown.js +121 -108
  21. package/out-tsc/src/dropdown/Dropdown.js.map +1 -1
  22. package/out-tsc/src/interfaces.js +2 -0
  23. package/out-tsc/src/interfaces.js.map +1 -1
  24. package/out-tsc/src/list/ContentMenu.js +11 -8
  25. package/out-tsc/src/list/ContentMenu.js.map +1 -1
  26. package/out-tsc/src/list/RunList.js.map +1 -1
  27. package/out-tsc/src/list/TembaList.js +21 -14
  28. package/out-tsc/src/list/TembaList.js.map +1 -1
  29. package/out-tsc/src/list/TembaMenu.js +11 -12
  30. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  31. package/out-tsc/src/list/TicketList.js +10 -0
  32. package/out-tsc/src/list/TicketList.js.map +1 -1
  33. package/out-tsc/src/omnibox/Omnibox.js +33 -90
  34. package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
  35. package/out-tsc/src/options/Options.js +49 -47
  36. package/out-tsc/src/options/Options.js.map +1 -1
  37. package/out-tsc/src/select/PopupSelect.js +57 -0
  38. package/out-tsc/src/select/PopupSelect.js.map +1 -0
  39. package/out-tsc/src/select/Select.js +194 -144
  40. package/out-tsc/src/select/Select.js.map +1 -1
  41. package/out-tsc/src/select/UserSelect.js +67 -0
  42. package/out-tsc/src/select/UserSelect.js.map +1 -0
  43. package/out-tsc/src/store/Store.js +65 -14
  44. package/out-tsc/src/store/Store.js.map +1 -1
  45. package/out-tsc/src/tabpane/TabPane.js +72 -115
  46. package/out-tsc/src/tabpane/TabPane.js.map +1 -1
  47. package/out-tsc/src/textinput/TextInput.js +1 -0
  48. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  49. package/out-tsc/src/user/TembaUser.js +24 -37
  50. package/out-tsc/src/user/TembaUser.js.map +1 -1
  51. package/out-tsc/src/utils/index.js +13 -6
  52. package/out-tsc/src/utils/index.js.map +1 -1
  53. package/out-tsc/temba-modules.js +4 -2
  54. package/out-tsc/temba-modules.js.map +1 -1
  55. package/out-tsc/test/temba-omnibox.test.js +43 -4
  56. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  57. package/out-tsc/test/temba-select.test.js +121 -65
  58. package/out-tsc/test/temba-select.test.js.map +1 -1
  59. package/out-tsc/test/utils.test.js +4 -0
  60. package/out-tsc/test/utils.test.js.map +1 -1
  61. package/package.json +1 -1
  62. package/screenshots/truth/compose/attachments-tab.png +0 -0
  63. package/screenshots/truth/compose/attachments-with-files-focused.png +0 -0
  64. package/screenshots/truth/compose/attachments-with-files.png +0 -0
  65. package/screenshots/truth/compose/intial-text.png +0 -0
  66. package/screenshots/truth/compose/no-counter.png +0 -0
  67. package/screenshots/truth/compose/wraps-text-and-spaces.png +0 -0
  68. package/screenshots/truth/compose/wraps-text-and-url.png +0 -0
  69. package/screenshots/truth/compose/wraps-text-no-spaces.png +0 -0
  70. package/screenshots/truth/contacts/chat-failure.png +0 -0
  71. package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
  72. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  73. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  74. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  75. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  76. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  77. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  78. package/screenshots/truth/content-menu/item-no-buttons.png +0 -0
  79. package/screenshots/truth/content-menu/items-and-buttons.png +0 -0
  80. package/screenshots/truth/omnibox/selected.png +0 -0
  81. package/screenshots/truth/select/enabled-multi-selection.png +0 -0
  82. package/screenshots/truth/select/endpoint-initial-value-updated.png +0 -0
  83. package/screenshots/truth/select/endpoint-initial-value.png +0 -0
  84. package/screenshots/truth/select/expressions.png +0 -0
  85. package/screenshots/truth/select/functions.png +0 -0
  86. package/screenshots/truth/select/initial-value.png +0 -0
  87. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  88. package/screenshots/truth/select/multiple-initial-values.png +0 -0
  89. package/screenshots/truth/select/selected-multi-test.png +0 -0
  90. package/screenshots/truth/select/static-initial-value.png +0 -0
  91. package/screenshots/truth/select/static-initial-via-selected.png +0 -0
  92. package/screenshots/truth/select/value-initial.png +0 -0
  93. package/src/aliaseditor/AliasEditor.ts +1 -1
  94. package/src/button/Button.ts +6 -2
  95. package/src/chat/Chat.ts +28 -6
  96. package/src/compose/Compose.ts +11 -6
  97. package/src/contacts/ContactChat.ts +260 -118
  98. package/src/contacts/ContactFieldEditor.ts +1 -1
  99. package/src/contacts/events.ts +1 -0
  100. package/src/contacts/helpers.ts +8 -1
  101. package/src/contactsearch/ContactSearch.ts +3 -3
  102. package/src/dropdown/Dropdown.ts +142 -103
  103. package/src/interfaces.ts +4 -1
  104. package/src/list/ContentMenu.ts +11 -9
  105. package/src/list/RunList.ts +3 -1
  106. package/src/list/TembaList.ts +24 -14
  107. package/src/list/TembaMenu.ts +14 -15
  108. package/src/list/TicketList.ts +11 -0
  109. package/src/omnibox/Omnibox.ts +34 -95
  110. package/src/options/Options.ts +57 -60
  111. package/src/select/PopupSelect.ts +53 -0
  112. package/src/select/Select.ts +182 -112
  113. package/src/select/UserSelect.ts +71 -0
  114. package/src/store/Store.ts +70 -21
  115. package/src/tabpane/TabPane.ts +79 -113
  116. package/src/textinput/TextInput.ts +1 -0
  117. package/src/user/TembaUser.ts +30 -41
  118. package/src/utils/index.ts +12 -8
  119. package/temba-modules.ts +4 -2
  120. package/test/temba-omnibox.test.ts +56 -4
  121. package/test/temba-select.test.ts +170 -56
  122. package/test/utils.test.ts +5 -0
  123. package/test-assets/select/omnibox.json +55 -0
  124. package/web-test-runner.config.mjs +16 -4
  125. package/out-tsc/src/contacts/ContactTickets.js +0 -462
  126. package/out-tsc/src/contacts/ContactTickets.js.map +0 -1
  127. package/out-tsc/test/temba-contact-tickets.test.js +0 -36
  128. package/out-tsc/test/temba-contact-tickets.test.js.map +0 -1
  129. package/src/contacts/ContactTickets.ts +0 -490
  130. package/test/temba-contact-tickets.test.ts +0 -52
@@ -28,6 +28,7 @@ import { css, html } from 'lit';
28
28
  import { configureLocalization } from '@lit/localize';
29
29
  import { sourceLocale, targetLocales } from '../locales/locale-codes';
30
30
  import { StoreMonitorElement } from './StoreMonitorElement';
31
+ import { getFullName } from '../user/TembaUser';
31
32
 
32
33
  const { setLocale } = configureLocalization({
33
34
  sourceLocale,
@@ -83,9 +84,6 @@ export class Store extends RapidElement {
83
84
  @property({ type: String, attribute: 'workspace' })
84
85
  workspaceEndpoint: string;
85
86
 
86
- @property({ type: String, attribute: 'users' })
87
- usersEndpoint: string;
88
-
89
87
  @property({ type: String, attribute: 'shortcuts' })
90
88
  shortcutsEndpoint: string;
91
89
 
@@ -226,14 +224,6 @@ export class Store extends RapidElement {
226
224
  );
227
225
  }
228
226
 
229
- if (this.usersEndpoint) {
230
- fetches.push(
231
- getAssets(this.usersEndpoint).then((users: any[]) => {
232
- this.users = users;
233
- })
234
- );
235
- }
236
-
237
227
  if (this.shortcutsEndpoint) {
238
228
  fetches.push(this.refreshShortcuts());
239
229
  }
@@ -249,12 +239,6 @@ export class Store extends RapidElement {
249
239
  return this.shortcuts || [];
250
240
  }
251
241
 
252
- public getAssignableUsers() {
253
- return this.users.filter((user: User) =>
254
- ['administrator', 'editor', 'agent'].includes(user.role)
255
- );
256
- }
257
-
258
242
  public firstUpdated() {
259
243
  this.reset();
260
244
  }
@@ -397,7 +381,7 @@ export class Store extends RapidElement {
397
381
  // we treat missing groups as dynamic since the
398
382
  // api excludes initializing groups
399
383
  if (!group) {
400
- console.warn('No group for ' + uuid);
384
+ // console.warn('No group for ' + uuid);
401
385
  }
402
386
 
403
387
  if (!group || group.query) {
@@ -510,6 +494,71 @@ export class Store extends RapidElement {
510
494
  this.cache.delete(url);
511
495
  }
512
496
 
497
+ public resolveUsers(items: any, keys: string[]): Promise<void> {
498
+ return new Promise<void>((resolve) => {
499
+ const emails = new Set<string>();
500
+
501
+ // keys are dot notation paths to user fields
502
+ items.forEach((item) => {
503
+ keys.forEach((key) => {
504
+ const parts = key.split('.');
505
+ let value = item;
506
+ for (let i = 0; i < parts.length; i++) {
507
+ value = value[parts[i]];
508
+ if (!value) {
509
+ break;
510
+ }
511
+ }
512
+ if (value && value.email) {
513
+ emails.add(value.email);
514
+ }
515
+ });
516
+ });
517
+
518
+ const promises = [];
519
+ // we don't want to fetch all users at once so we can benefit from caching
520
+ emails.forEach((email) => {
521
+ promises.push(this.getUrl(`/api/v2/users.json?email=${email}`));
522
+ });
523
+
524
+ // wait for all of our user fetches to complete
525
+ Promise.all(promises).then((promises) => {
526
+ promises.forEach((response: WebResponse) => {
527
+ if (response && response.json) {
528
+ const results = response.json.results;
529
+ if (results && results.length === 1) {
530
+ const user = results[0];
531
+
532
+ items.forEach((item) => {
533
+ // replace each key with a matching user
534
+ keys.forEach((key) => {
535
+ const parts = key.split('.');
536
+ let value = item;
537
+ let last = value;
538
+ for (let i = 0; i < parts.length; i++) {
539
+ last = value;
540
+ value = value[parts[i]];
541
+ if (!value) {
542
+ break;
543
+ }
544
+ }
545
+ if (value && value.email === user.email) {
546
+ // only care about avatars for now
547
+ const orginalUser = last[parts[parts.length - 1]];
548
+ orginalUser.avatar = user.avatar;
549
+ orginalUser.name = getFullName(user);
550
+ last[parts[parts.length - 1]].avatar = user.avatar;
551
+ }
552
+ });
553
+ });
554
+ }
555
+ }
556
+ });
557
+ resolve();
558
+ });
559
+ });
560
+ }
561
+
513
562
  public makeRequest(
514
563
  url: string,
515
564
  options?: { force?: boolean; prepareData?: (data: any) => any }
@@ -524,8 +573,9 @@ export class Store extends RapidElement {
524
573
  return;
525
574
  }
526
575
 
527
- const cached = this.cache.get(url);
576
+ let cached = this.cache.get(url);
528
577
  if (cached && !options.force) {
578
+ cached = options.prepareData ? options.prepareData(cached) : cached;
529
579
  this.fireCustomEvent(CustomEventType.StoreUpdated, { url, data: cached });
530
580
  } else {
531
581
  options = options || {};
@@ -535,10 +585,9 @@ export class Store extends RapidElement {
535
585
  delete this.fetching[url];
536
586
  return;
537
587
  }
538
-
539
- data = options.prepareData ? options.prepareData(data) : data;
540
588
  this.cache.set(url, data);
541
589
  delete this.fetching[url];
590
+ data = options.prepareData ? options.prepareData(data) : data;
542
591
  this.fireCustomEvent(CustomEventType.StoreUpdated, { url, data });
543
592
  });
544
593
  }
@@ -15,12 +15,14 @@ export class TabPane extends RapidElement {
15
15
  flex-grow: 1;
16
16
  }
17
17
 
18
- .tabs {
18
+ .options {
19
19
  display: flex;
20
20
  align-items: stretch;
21
+ padding: var(--temba-tabs-options-padding, 0);
22
+ border-bottom: none;
21
23
  }
22
24
 
23
- .tab {
25
+ .option {
24
26
  user-select: none;
25
27
  padding: 0.5em 0.7em;
26
28
  margin: 0em 0em;
@@ -40,22 +42,22 @@ export class TabPane extends RapidElement {
40
42
  transition: all 100ms linear;
41
43
  }
42
44
 
43
- .focusedname .tab .name {
45
+ .focusedname .option .name {
44
46
  transition: all 0s linear !important;
45
47
  }
46
48
 
47
- .focusedname .tab.selected .name {
49
+ .focusedname .option.selected .name {
48
50
  transition: all 200ms linear !important;
49
51
  }
50
52
 
51
- .tab.hidden {
53
+ .option.hidden {
52
54
  display: none;
53
55
  }
54
56
 
55
- .tab temba-icon {
57
+ .option temba-icon {
56
58
  }
57
59
 
58
- .tab .name {
60
+ .option .name {
59
61
  margin-left: 0.4em;
60
62
  max-width: 200px;
61
63
  overflow: hidden;
@@ -64,48 +66,48 @@ export class TabPane extends RapidElement {
64
66
  text-overflow: ellipsis;
65
67
  }
66
68
 
67
- .tab .badge {
69
+ .option .badge {
68
70
  margin-left: 0.4em;
69
71
  }
70
72
 
71
73
  @media (max-width: 900px) {
72
- .collapses .tab .name {
74
+ .collapses .option .name {
73
75
  max-width: 0px;
74
76
  margin: 0;
75
77
  }
76
78
  }
77
79
 
78
80
  @media (max-width: 600px) {
79
- .collapses .tab .badge {
81
+ .collapses .option .badge {
80
82
  display: none;
81
83
  }
82
84
  }
83
85
 
84
- .focusedname .tab.selected {
86
+ .focusedname .option.selected {
85
87
  }
86
88
 
87
- .focusedname .tab .name {
89
+ .focusedname .option .name {
88
90
  max-width: 0px;
89
91
  margin: 0;
90
92
  transition: max-width 200ms linear, margin 200ms linear;
91
93
  }
92
94
 
93
- .focusedname .tab.selected .name {
95
+ .focusedname .option.selected .name {
94
96
  margin-left: 0.4em;
95
97
  max-width: 200px;
96
98
  }
97
99
 
98
- .tab {
100
+ .option {
99
101
  transform: scale(0.9) translateY(0em);
100
102
  --icon-color: rgba(0, 0, 0, 0.5);
101
103
  color: rgba(0, 0, 0, 0.5);
102
104
  }
103
105
 
104
- .tab.selected {
106
+ .option.selected {
105
107
  }
106
108
 
107
- .tab.selected,
108
- .tab.selected:hover {
109
+ .option.selected,
110
+ .option.selected:hover {
109
111
  cursor: default;
110
112
  box-shadow: 0px -3px 3px 1px rgba(0, 0, 0, 0.02);
111
113
 
@@ -117,31 +119,25 @@ export class TabPane extends RapidElement {
117
119
  border-bottom: 0px;
118
120
  }
119
121
 
120
- .embedded .tab.selected {
121
- }
122
-
123
- .tab.selected .dot {
122
+ .option.selected .dot {
124
123
  display: none;
125
124
  }
126
125
 
127
- .bottom .tab.selected {
128
- }
129
-
130
- .unselect .tab.selected {
126
+ .unselect .option.selected {
131
127
  cursor: pointer;
132
128
  }
133
129
 
134
- .unselect .tab.selected:hover {
130
+ .unselect .option.selected:hover {
135
131
  background: var(--unselect-tab-color, #eee);
136
132
  }
137
133
 
138
- .tab:hover {
134
+ .option:hover {
139
135
  --icon-color: #666;
140
136
  color: #666;
141
137
  background: rgba(0, 0, 0, 0.02);
142
138
  }
143
139
 
144
- .tab.dirty {
140
+ .option.dirty {
145
141
  font-weight: 500;
146
142
  }
147
143
 
@@ -167,9 +163,6 @@ export class TabPane extends RapidElement {
167
163
  overflow: hidden;
168
164
  }
169
165
 
170
- .badge {
171
- }
172
-
173
166
  .count {
174
167
  border-radius: 99px;
175
168
  background: rgba(0, 0, 0, 0.1);
@@ -198,22 +191,6 @@ export class TabPane extends RapidElement {
198
191
  --icon-color: var(--color-alert);
199
192
  }
200
193
 
201
- .bottom.tabs .tab {
202
- border-radius: 0em;
203
- }
204
-
205
- .bottom.pane {
206
- border-radius: 0em;
207
- }
208
-
209
- .bottom.pane.first {
210
- border-bottom-left-radius: 0px;
211
- }
212
-
213
- .bottom .tab.first {
214
- border-bottom-left-radius: var(--curvature);
215
- }
216
-
217
194
  .embedded.pane {
218
195
  box-shadow: none;
219
196
  margin: 0;
@@ -222,30 +199,22 @@ export class TabPane extends RapidElement {
222
199
  border-bottom: none !important;
223
200
  }
224
201
 
225
- .embedded.tabs {
226
- padding-top: 0em;
227
- }
228
-
229
- .embedded .tab {
202
+ .embedded .option {
230
203
  border-bottom: none !important;
231
204
  border-radius: 0em;
232
205
  border-top: none !important;
233
206
  }
234
207
 
235
- .embedded .tab.first {
208
+ .embedded .option.first {
236
209
  margin-left: 0em;
237
210
  border-top: none !important;
238
211
  border-left: none;
239
212
  }
240
213
 
241
- .embedded.tabs .tab.selected {
214
+ .embedded.options .option.selected {
242
215
  box-shadow: none !important;
243
216
  }
244
217
 
245
- .embedded.pane {
246
- // padding: 0.3em;
247
- }
248
-
249
218
  .check {
250
219
  margin-left: 0.4em;
251
220
  }
@@ -262,10 +231,6 @@ export class TabPane extends RapidElement {
262
231
  @property({ type: Boolean })
263
232
  collapses = false;
264
233
 
265
- // are the tabs on the bottom of the pane?
266
- @property({ type: Boolean })
267
- bottom = false;
268
-
269
234
  // do we allow unselecting the current tab
270
235
  @property({ type: Boolean })
271
236
  unselect = false;
@@ -281,7 +246,7 @@ export class TabPane extends RapidElement {
281
246
  refresh = '';
282
247
 
283
248
  @property({ type: Array, attribute: false })
284
- tabs: Tab[] = [];
249
+ options: Tab[] = [];
285
250
 
286
251
  private handleTabClick(event: MouseEvent): void {
287
252
  const newIndex = parseInt(
@@ -307,7 +272,7 @@ export class TabPane extends RapidElement {
307
272
  tabs.push(tab);
308
273
  }
309
274
  }
310
- this.tabs = tabs;
275
+ this.options = tabs;
311
276
  this.index = 0;
312
277
  }
313
278
 
@@ -324,16 +289,16 @@ export class TabPane extends RapidElement {
324
289
  public updated(changedProperties: Map<string, any>) {
325
290
  super.updated(changedProperties);
326
291
 
327
- if (changedProperties.has('tabs')) {
328
- this.tabs.forEach((tab, index) => {
292
+ if (changedProperties.has('options')) {
293
+ this.options.forEach((tab, index) => {
329
294
  tab.selected = index == this.index;
330
295
  });
331
296
  }
332
297
 
333
298
  if (changedProperties.has('index')) {
334
- if (this.tabs.length >= 0) {
299
+ if (this.options.length >= 0) {
335
300
  if (this.index !== changedProperties.get('index')) {
336
- this.tabs.forEach((tab, index) => {
301
+ this.options.forEach((tab, index) => {
337
302
  tab.selected = index == this.index;
338
303
  });
339
304
  this.fireEvent(CustomEventType.ContextChanged);
@@ -342,11 +307,11 @@ export class TabPane extends RapidElement {
342
307
  }
343
308
 
344
309
  // if our current tab is hidden, select the first visible one
345
- if (this.index > this.tabs.length) {
346
- const tab = this.tabs[this.index];
310
+ if (this.index > this.options.length) {
311
+ const tab = this.options[this.index];
347
312
  if (tab && tab.hidden) {
348
- for (let i = 0; i < this.tabs.length; i++) {
349
- const other = this.tabs[i];
313
+ for (let i = 0; i < this.options.length; i++) {
314
+ const other = this.options[i];
350
315
  if (other && !other.hidden) {
351
316
  this.index = i;
352
317
  return;
@@ -357,7 +322,7 @@ export class TabPane extends RapidElement {
357
322
  }
358
323
 
359
324
  public focusTab(name: string): Tab {
360
- const index = this.tabs.findIndex((tab) => tab.name === name);
325
+ const index = this.options.findIndex((tab) => tab.name === name);
361
326
  if (index >= 0) {
362
327
  this.index = index;
363
328
  return this.getTab(index);
@@ -368,8 +333,8 @@ export class TabPane extends RapidElement {
368
333
  index: number,
369
334
  details: { count: number; hidden: boolean }
370
335
  ) {
371
- if (index < this.tabs.length) {
372
- const tab = this.tabs[index];
336
+ if (index < this.options.length) {
337
+ const tab = this.options[index];
373
338
  tab.count = details.count;
374
339
  tab.hidden = details.hidden;
375
340
  this.requestUpdate();
@@ -382,11 +347,11 @@ export class TabPane extends RapidElement {
382
347
  }
383
348
 
384
349
  public getCurrentTab(): Tab {
385
- return this.tabs[this.index];
350
+ return this.options[this.index];
386
351
  }
387
352
 
388
353
  public getTab(index: number): Tab {
389
- return this.tabs[index];
354
+ return this.options[index];
390
355
  }
391
356
 
392
357
  public handleTabContentChanged() {
@@ -398,37 +363,24 @@ export class TabPane extends RapidElement {
398
363
  }
399
364
 
400
365
  public render(): TemplateResult {
401
- const activeTab = this.tabs[this.index];
366
+ const activeTab = this.options[this.index];
402
367
  return html`
403
- ${this.bottom
404
- ? html`<div
405
- class="pane ${getClasses({
406
- first: this.index == 0,
407
- embedded: this.embedded,
408
- bottom: this.bottom
409
- })}"
410
- >
411
- <slot></slot>
412
- </div>`
413
- : null}
414
-
415
368
  <div
416
- class="tabs ${getClasses({
417
- tabs: true,
418
- bottom: this.bottom,
369
+ class="${getClasses({
370
+ options: true,
419
371
  collapses: this.collapses,
420
372
  embedded: this.embedded,
421
373
  focusedname: this.focusedName,
422
374
  unselect: this.unselect
423
375
  })}"
424
376
  >
425
- ${this.tabs.map(
377
+ ${this.options.map(
426
378
  (tab, index) => html`
427
379
  <div
428
380
  @click=${this.handleTabClick}
429
381
  data-index=${index}
430
382
  class="${getClasses({
431
- tab: true,
383
+ option: true,
432
384
  first: index == 0,
433
385
  selected: index == this.index,
434
386
  hidden: tab.hidden,
@@ -473,24 +425,38 @@ export class TabPane extends RapidElement {
473
425
  <slot name="tab-right"></slot>
474
426
  </div>
475
427
  </div>
476
- ${!this.bottom
477
- ? html`<div
478
- @temba-details-changed=${this.handleTabDetailsChanged}
479
- style="${activeTab?.borderColor
480
- ? `border: 1px solid ${activeTab?.borderColor};`
481
- : ''} ${activeTab?.selectionBackground
482
- ? `background:${activeTab?.selectionBackground};`
483
- : ``}"
484
- class="pane ${getClasses({
485
- first: this.index == 0,
486
- embedded: this.embedded,
487
- bottom: this.bottom
488
- })}"
489
- >
490
- <slot></slot>
491
- <slot name="pane-bottom"></slot>
492
- </div>`
493
- : null}
428
+ <div
429
+ @temba-details-changed=${this.handleTabDetailsChanged}
430
+ style="${activeTab?.borderColor
431
+ ? `
432
+ border-top: var(--temba-tabs-border-top, 1px solid ${
433
+ activeTab?.borderColor || 'var(--color-widget-border)'
434
+ });
435
+
436
+ border-left: var(--temba-tabs-border-left, 1px solid ${
437
+ activeTab?.borderColor || 'var(--color-widget-border)'
438
+ });
439
+
440
+ border-bottom: var(--temba-tabs-border-bottom, 1px solid ${
441
+ activeTab?.borderColor || 'var(--color-widget-border)'
442
+ });
443
+
444
+ border-right: var(--temba-tabs-border-right, 1px solid ${
445
+ activeTab?.borderColor || 'var(--color-widget-border)'
446
+ });
447
+
448
+ `
449
+ : ''} ${activeTab?.selectionBackground
450
+ ? `background:${activeTab?.selectionBackground};`
451
+ : ``}"
452
+ class="pane ${getClasses({
453
+ first: this.index == 0,
454
+ embedded: this.embedded
455
+ })}"
456
+ >
457
+ <slot></slot>
458
+ <slot name="pane-bottom"></slot>
459
+ </div>
494
460
  `;
495
461
  }
496
462
  }
@@ -371,6 +371,7 @@ export class TextInput extends FormElement {
371
371
 
372
372
  let input = html`
373
373
  <input
374
+ autofocus
374
375
  class="textinput"
375
376
  autocomplete="off"
376
377
  name=${this.name}
@@ -1,14 +1,25 @@
1
1
  import { PropertyValueMap, TemplateResult, css, html } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
- import { User } from '../interfaces';
3
+
4
4
  import { colorHash, extractInitials } from '../utils';
5
- import { EndpointMonitorElement } from '../store/EndpointMonitorElement';
5
+
6
6
  import { DEFAULT_AVATAR } from '../webchat/assets';
7
+ import { RapidElement } from '../RapidElement';
7
8
 
8
- export class TembaUser extends EndpointMonitorElement {
9
+ export const getFullName = (user: {
10
+ name?: string;
11
+ first_name?: string;
12
+ last_name?: string;
13
+ }) => {
14
+ return user.name || [user.first_name, user.last_name].join(' ');
15
+ };
16
+
17
+ export class TembaUser extends RapidElement {
9
18
  public static styles = css`
10
19
  :host {
11
20
  display: flex;
21
+ transform: scale(var(--temba-scale, 1));
22
+ box-sizing: border-box;
12
23
  }
13
24
 
14
25
  .wrapper {
@@ -27,14 +38,11 @@ export class TembaUser extends EndpointMonitorElement {
27
38
  }
28
39
  `;
29
40
 
30
- @property({ type: String })
31
- email: string;
32
-
33
41
  @property({ type: Number })
34
42
  scale: number;
35
43
 
36
44
  @property({ type: Boolean })
37
- name: boolean;
45
+ showName: boolean;
38
46
 
39
47
  @property({ type: Boolean })
40
48
  system: boolean;
@@ -46,49 +54,30 @@ export class TembaUser extends EndpointMonitorElement {
46
54
  initials: string = '';
47
55
 
48
56
  @property({ type: String })
49
- fullname: string;
50
-
51
- @property({ type: Object, attribute: false })
52
- data: User;
57
+ name: string;
53
58
 
54
- prepareData(data: any) {
55
- if (data.length > 0) {
56
- return data[0];
57
- }
59
+ @property({ type: String })
60
+ email: string;
58
61
 
59
- this.fullname = this.email;
60
- return null;
61
- }
62
+ @property({ type: String })
63
+ avatar: string;
62
64
 
63
65
  public updated(
64
66
  changed: PropertyValueMap<any> | Map<PropertyKey, unknown>
65
67
  ): void {
66
68
  super.updated(changed);
67
69
 
68
- if (changed.has('email') && this.email) {
69
- this.url = `/api/v2/users.json?email=${this.email}`;
70
- }
71
-
72
70
  if (changed.has('system') && this.system) {
73
71
  this.background = `url('${DEFAULT_AVATAR}') center / contain no-repeat`;
74
72
  }
75
73
 
76
- if (changed.has('data') && this.data) {
77
- if (this.data.first_name && this.data.last_name) {
78
- this.fullname = [this.data.first_name, this.data.last_name].join(' ');
79
- this.background = colorHash.hex(this.fullname);
80
- this.initials = extractInitials(this.fullname);
81
- }
82
-
83
- if (this.data.avatar) {
84
- this.background = `url('${this.data.avatar}') center / contain no-repeat`;
85
- this.initials = '';
86
- }
74
+ if (changed.has('name') && this.name) {
75
+ this.background = colorHash.hex(this.name);
76
+ this.initials = extractInitials(this.name);
87
77
  }
88
78
 
89
- if (changed.has('fullname') && this.fullname && !this.data) {
90
- this.background = colorHash.hex(this.fullname);
91
- this.initials = extractInitials(this.fullname);
79
+ if (changed.has('avatar') && this.avatar) {
80
+ this.background = `url('${this.avatar}') center / contain no-repeat`;
92
81
  }
93
82
  }
94
83
 
@@ -99,8 +88,8 @@ export class TembaUser extends EndpointMonitorElement {
99
88
  style="
100
89
  transform:scale(${this.scale || 1});
101
90
  display: flex;
102
- height: 30px;
103
- width: 30px;
91
+ min-height: 26px;
92
+ min-width: 26px;
104
93
  flex-direction: row;
105
94
  align-items: center;
106
95
  color: #fff;
@@ -111,7 +100,7 @@ export class TembaUser extends EndpointMonitorElement {
111
100
  box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.1);
112
101
  background:${this.background}"
113
102
  >
114
- ${this.initials
103
+ ${this.initials && !this.avatar
115
104
  ? html` <div
116
105
  style="border: 0px solid red; display:flex; flex-direction: column; align-items:center;flex-grow:1"
117
106
  >
@@ -119,13 +108,13 @@ export class TembaUser extends EndpointMonitorElement {
119
108
  </div>`
120
109
  : null}
121
110
  </div>
122
- ${this.name
111
+ ${this.showName
123
112
  ? html`<div
124
113
  class="name"
125
114
  style="margin: 0px ${this.scale - 0.5}em;font-size:${this.scale +
126
115
  0.2}em"
127
116
  >
128
- ${this.fullname}
117
+ ${this.name}
129
118
  </div>`
130
119
  : null}
131
120
  </div>`;