@nyaruka/temba-components 0.112.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 +27 -2
  2. package/demo/index.html +1 -1
  3. package/dist/temba-components.js +794 -968
  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 +16 -20
  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 +82 -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 +17 -22
  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 +91 -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,72 +42,72 @@ 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
- max-width: 80px;
62
+ max-width: 200px;
61
63
  overflow: hidden;
62
64
  transition: max-width 500ms ease-in-out, margin 500ms ease-in-out;
63
65
  white-space: nowrap;
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,8 @@ export class TabPane extends RapidElement {
307
272
  tabs.push(tab);
308
273
  }
309
274
  }
310
- this.tabs = tabs;
275
+ this.options = tabs;
276
+ this.index = 0;
311
277
  }
312
278
 
313
279
  public firstUpdated(
@@ -322,19 +288,30 @@ export class TabPane extends RapidElement {
322
288
 
323
289
  public updated(changedProperties: Map<string, any>) {
324
290
  super.updated(changedProperties);
325
- if (changedProperties.has('index') || changedProperties.has('tabs')) {
326
- this.tabs.forEach((tab, index) => {
291
+
292
+ if (changedProperties.has('options')) {
293
+ this.options.forEach((tab, index) => {
327
294
  tab.selected = index == this.index;
328
295
  });
329
- this.fireEvent(CustomEventType.ContextChanged);
296
+ }
297
+
298
+ if (changedProperties.has('index')) {
299
+ if (this.options.length >= 0) {
300
+ if (this.index !== changedProperties.get('index')) {
301
+ this.options.forEach((tab, index) => {
302
+ tab.selected = index == this.index;
303
+ });
304
+ this.fireEvent(CustomEventType.ContextChanged);
305
+ }
306
+ }
330
307
  }
331
308
 
332
309
  // if our current tab is hidden, select the first visible one
333
- if (this.index > this.tabs.length) {
334
- const tab = this.tabs[this.index];
310
+ if (this.index > this.options.length) {
311
+ const tab = this.options[this.index];
335
312
  if (tab && tab.hidden) {
336
- for (let i = 0; i < this.tabs.length; i++) {
337
- const other = this.tabs[i];
313
+ for (let i = 0; i < this.options.length; i++) {
314
+ const other = this.options[i];
338
315
  if (other && !other.hidden) {
339
316
  this.index = i;
340
317
  return;
@@ -345,7 +322,7 @@ export class TabPane extends RapidElement {
345
322
  }
346
323
 
347
324
  public focusTab(name: string): Tab {
348
- const index = this.tabs.findIndex((tab) => tab.name === name);
325
+ const index = this.options.findIndex((tab) => tab.name === name);
349
326
  if (index >= 0) {
350
327
  this.index = index;
351
328
  return this.getTab(index);
@@ -356,8 +333,8 @@ export class TabPane extends RapidElement {
356
333
  index: number,
357
334
  details: { count: number; hidden: boolean }
358
335
  ) {
359
- if (index < this.tabs.length) {
360
- const tab = this.tabs[index];
336
+ if (index < this.options.length) {
337
+ const tab = this.options[index];
361
338
  tab.count = details.count;
362
339
  tab.hidden = details.hidden;
363
340
  this.requestUpdate();
@@ -370,11 +347,11 @@ export class TabPane extends RapidElement {
370
347
  }
371
348
 
372
349
  public getCurrentTab(): Tab {
373
- return this.tabs[this.index];
350
+ return this.options[this.index];
374
351
  }
375
352
 
376
353
  public getTab(index: number): Tab {
377
- return this.tabs[index];
354
+ return this.options[index];
378
355
  }
379
356
 
380
357
  public handleTabContentChanged() {
@@ -386,37 +363,24 @@ export class TabPane extends RapidElement {
386
363
  }
387
364
 
388
365
  public render(): TemplateResult {
389
- const activeTab = this.tabs[this.index];
366
+ const activeTab = this.options[this.index];
390
367
  return html`
391
- ${this.bottom
392
- ? html`<div
393
- class="pane ${getClasses({
394
- first: this.index == 0,
395
- embedded: this.embedded,
396
- bottom: this.bottom
397
- })}"
398
- >
399
- <slot></slot>
400
- </div>`
401
- : null}
402
-
403
368
  <div
404
- class="tabs ${getClasses({
405
- tabs: true,
406
- bottom: this.bottom,
369
+ class="${getClasses({
370
+ options: true,
407
371
  collapses: this.collapses,
408
372
  embedded: this.embedded,
409
373
  focusedname: this.focusedName,
410
374
  unselect: this.unselect
411
375
  })}"
412
376
  >
413
- ${this.tabs.map(
377
+ ${this.options.map(
414
378
  (tab, index) => html`
415
379
  <div
416
380
  @click=${this.handleTabClick}
417
381
  data-index=${index}
418
382
  class="${getClasses({
419
- tab: true,
383
+ option: true,
420
384
  first: index == 0,
421
385
  selected: index == this.index,
422
386
  hidden: tab.hidden,
@@ -461,24 +425,38 @@ export class TabPane extends RapidElement {
461
425
  <slot name="tab-right"></slot>
462
426
  </div>
463
427
  </div>
464
- ${!this.bottom
465
- ? html`<div
466
- @temba-details-changed=${this.handleTabDetailsChanged}
467
- style="${activeTab?.borderColor
468
- ? `border: 1px solid ${activeTab?.borderColor};`
469
- : ''} ${activeTab?.selectionBackground
470
- ? `background:${activeTab?.selectionBackground};`
471
- : ``}"
472
- class="pane ${getClasses({
473
- first: this.index == 0,
474
- embedded: this.embedded,
475
- bottom: this.bottom
476
- })}"
477
- >
478
- <slot></slot>
479
- <slot name="pane-bottom"></slot>
480
- </div>`
481
- : 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>
482
460
  `;
483
461
  }
484
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>`;