@nyaruka/temba-components 0.157.0 → 0.158.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 (48) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/temba-components.js +1617 -1590
  3. package/dist/temba-components.js.map +1 -1
  4. package/orca/setup.sh +81 -0
  5. package/orca.yaml +3 -0
  6. package/package.json +1 -1
  7. package/src/display/Button.ts +102 -121
  8. package/src/display/Chat.ts +60 -9
  9. package/src/display/Dropdown.ts +11 -0
  10. package/src/display/Label.ts +1 -3
  11. package/src/display/LeafletMap.ts +4 -3
  12. package/src/display/TembaUser.ts +9 -3
  13. package/src/events/eventRenderers.ts +151 -71
  14. package/src/flow/AutoTranslate.ts +2 -2
  15. package/src/flow/CanvasNode.ts +14 -6
  16. package/src/flow/DragManager.ts +4 -2
  17. package/src/flow/Editor.ts +4 -4
  18. package/src/flow/NodeEditor.ts +2 -2
  19. package/src/flow/NodeTypeSelector.ts +0 -5
  20. package/src/flow/actions/set_contact_language.ts +5 -4
  21. package/src/flow/nodes/split_by_llm_categorize.ts +1 -6
  22. package/src/flow/utils.ts +2 -20
  23. package/src/form/ColorPicker.ts +5 -3
  24. package/src/form/DatePicker.ts +2 -1
  25. package/src/form/select/Omnibox.ts +1 -3
  26. package/src/form/select/Select.ts +5 -4
  27. package/src/interfaces.ts +1 -0
  28. package/src/languages.ts +56 -0
  29. package/src/layout/Dialog.ts +1 -3
  30. package/src/layout/Tab.ts +0 -15
  31. package/src/layout/TabPane.ts +73 -163
  32. package/src/list/ContentMenu.ts +1 -2
  33. package/src/list/SortableList.ts +1 -4
  34. package/src/list/TembaMenu.ts +159 -113
  35. package/src/live/ContactBadges.ts +2 -1
  36. package/src/live/ContactChat.ts +22 -3
  37. package/src/live/ContactDetails.ts +42 -36
  38. package/src/live/ContactFieldEditor.ts +35 -57
  39. package/src/live/ContactFields.ts +1 -2
  40. package/src/live/ContactNotepad.ts +9 -1
  41. package/src/live/ContactPending.ts +1 -0
  42. package/src/store/AppState.ts +3 -21
  43. package/src/store/Store.ts +0 -29
  44. package/src/styles/designTokens.ts +33 -18
  45. package/src/styles/pillVariants.ts +24 -13
  46. package/static/css/temba-components.css +84 -55
  47. package/web-dev-server.config.mjs +0 -1
  48. package/web-test-runner.config.mjs +0 -1
@@ -74,7 +74,8 @@ const ENDPOINT_PILL_TYPES: { pattern: RegExp; type: string }[] = [
74
74
  { pattern: /\/contacts(\.json|\/|\?|$)/, type: 'contact' },
75
75
  { pattern: /\/labels(\.json|\/|\?|$)/, type: 'label' },
76
76
  { pattern: /\/flows(\.json|\/|\?|$)/, type: 'flow' },
77
- { pattern: /\/fields(\.json|\/|\?|$)/, type: 'field' }
77
+ { pattern: /\/fields(\.json|\/|\?|$)/, type: 'field' },
78
+ { pattern: /\/topics(\.json|\/|\?|$)/, type: 'topic' }
78
79
  ];
79
80
 
80
81
  export class Select<T extends SelectOption> extends FieldElement {
@@ -2204,9 +2205,9 @@ export class Select<T extends SelectOption> extends FieldElement {
2204
2205
  class="option-name"
2205
2206
  style="display:flex; align-items:center; gap:6px;"
2206
2207
  >
2207
- ${icon
2208
- ? html`<temba-icon name="${icon}"></temba-icon>`
2209
- : null}<span>${this.renderHighlightedName(option)}</span>
2208
+ ${icon ? html`<temba-icon name="${icon}"></temba-icon>` : null}<span
2209
+ >${this.renderHighlightedName(option)}</span
2210
+ >
2210
2211
  </div>
2211
2212
  `;
2212
2213
  }
package/src/interfaces.ts CHANGED
@@ -59,6 +59,7 @@ export interface NamedUser extends User {
59
59
 
60
60
  export interface User {
61
61
  id?: number;
62
+ uuid?: string;
62
63
  first_name?: string;
63
64
  last_name?: string;
64
65
  name?: string;
@@ -0,0 +1,56 @@
1
+ const intlLanguageNames = new Intl.DisplayNames(['en'], {
2
+ type: 'language',
3
+ fallback: 'none'
4
+ });
5
+
6
+ const ADDITIONAL_LANGUAGE_NAMES: { [code: string]: string } = {
7
+ aab: 'Alumu-Tesu',
8
+ aac: 'Ari',
9
+ aas: 'Aasáx',
10
+ abp: 'Abellen Ayta',
11
+ acf: 'Saint Lucian Creole French',
12
+ aec: 'Saidi Arabic',
13
+ afb: 'Gulf Arabic',
14
+ apd: 'Sudanese Arabic',
15
+ ayl: 'Libyan Arabic',
16
+ blk: "Pa'o Karen",
17
+ bog: 'Bamako Sign Language',
18
+ bzs: 'Brazilian Sign Language',
19
+ csn: 'Colombian Sign Language',
20
+ dag: 'Dagbani',
21
+ ecs: 'Ecuadorian Sign Language',
22
+ frk: 'Frankish',
23
+ fsl: 'French Sign Language',
24
+ fuv: 'Nigerian Fulfulde',
25
+ gcr: 'Guianese Creole French',
26
+ gpe: 'Ghanaian Pidgin English',
27
+ gux: 'Gourmanchéma',
28
+ ise: 'Italian Sign Language',
29
+ ksw: "S'gaw Karen",
30
+ kun: 'Kunama',
31
+ kyu: 'Western Kayah',
32
+ laj: 'Lango',
33
+ nyj: 'Nyanga',
34
+ prd: 'Parsi-Dari',
35
+ prl: 'Peruvian Sign Language',
36
+ pst: 'Central Pashto',
37
+ rop: 'Kriol',
38
+ tdt: 'Tetun Dili',
39
+ toi: 'Tonga',
40
+ tuv: 'Turkana',
41
+ vsl: 'Venezuelan Sign Language'
42
+ };
43
+
44
+ export function getLanguageName(code: string): string {
45
+ if (!code) return '';
46
+ if (code === 'und') return 'Unknown';
47
+
48
+ try {
49
+ const name = intlLanguageNames.of(code);
50
+ if (name) return name;
51
+ } catch {
52
+ // fall through to additional lookup
53
+ }
54
+
55
+ return ADDITIONAL_LANGUAGE_NAMES[code] || code;
56
+ }
@@ -181,8 +181,6 @@ export class Dialog extends ResizeElement {
181
181
 
182
182
  temba-button {
183
183
  margin-left: 10px;
184
- --button-y: 0.4em;
185
- --button-x: 1em;
186
184
  }
187
185
 
188
186
  .dialog-body temba-loading {
@@ -555,7 +553,7 @@ export class Dialog extends ResizeElement {
555
553
  ?destructive=${button.type == 'primary' && this.destructive}
556
554
  ?primary=${button.type == 'primary' && !this.destructive}
557
555
  ?secondary=${button.type == 'secondary'}
558
- ?submitting=${this.submitting}
556
+ ?submitting=${this.submitting && button.type == 'primary'}
559
557
  ?disabled=${this.disabled && !button.closes}
560
558
  index=${index}
561
559
  @click=${this.handleClick}
package/src/layout/Tab.ts CHANGED
@@ -27,24 +27,9 @@ export class Tab extends RapidElement {
27
27
  @property({ type: String })
28
28
  icon: string;
29
29
 
30
- @property({ type: String })
31
- selectionColor: string;
32
-
33
- @property({ type: String })
34
- selectionBackground: string;
35
-
36
- @property({ type: String })
37
- borderColor: string = 'var(--color-widget-border)';
38
-
39
- @property({ type: String })
40
- activityColor: string = `var(--color-link-primary)`;
41
-
42
30
  @property({ type: Boolean })
43
31
  selected = false;
44
32
 
45
- @property({ type: Boolean })
46
- notify = false;
47
-
48
33
  @property({ type: Boolean })
49
34
  alert = false;
50
35
 
@@ -18,43 +18,48 @@ export class TabPane extends RapidElement {
18
18
  .options {
19
19
  display: flex;
20
20
  align-items: stretch;
21
- padding: var(--temba-tabs-options-padding, 0);
22
- border-bottom: none;
21
+ gap: 4px;
22
+ border-bottom: 1px solid var(--border);
23
23
  }
24
24
 
25
25
  .option {
26
26
  user-select: none;
27
- padding: 0.5em 0.7em;
28
- margin: 0em 0em;
29
- cursor: pointer;
30
27
  display: flex;
31
- font-size: 1.1em;
32
28
  align-items: center;
33
- border: 1px inset transparent;
34
- border-bottom: 0px;
35
- border-radius: var(--curvature);
36
- border-bottom-right-radius: 0px;
37
- border-bottom-left-radius: 0px;
38
-
39
- color: var(--color-text-dark);
40
- --icon-color: var(--color-text-dark);
29
+ cursor: pointer;
30
+ padding: 8px 14px 10px;
31
+ margin-bottom: -1px;
32
+ background: transparent;
33
+ color: var(--text-2);
34
+ --icon-color: var(--text-2);
35
+ font-size: 13px;
36
+ font-weight: var(--w-medium);
37
+ border-bottom: 2px solid transparent;
41
38
  white-space: nowrap;
42
- transition: all 100ms linear;
39
+ transition:
40
+ color 100ms linear,
41
+ border-color 100ms linear;
43
42
  }
44
43
 
45
- .focusedname .option .name {
46
- transition: all 0s linear !important;
44
+ .option:hover {
45
+ color: var(--text-1);
46
+ --icon-color: var(--text-1);
47
47
  }
48
48
 
49
- .focusedname .option.selected .name {
50
- transition: all 200ms linear !important;
49
+ .option.selected,
50
+ .option.selected:hover {
51
+ cursor: default;
52
+ color: var(--accent-700);
53
+ --icon-color: var(--accent-700);
54
+ border-bottom-color: var(--accent-600);
51
55
  }
52
56
 
53
- .option.hidden {
54
- display: none;
57
+ .unselect .option.selected {
58
+ cursor: pointer;
55
59
  }
56
60
 
57
- .option temba-icon {
61
+ .option.hidden {
62
+ display: none;
58
63
  }
59
64
 
60
65
  .option .name {
@@ -70,6 +75,9 @@ export class TabPane extends RapidElement {
70
75
 
71
76
  .option .badge {
72
77
  margin-left: 0.4em;
78
+ margin-right: -6px;
79
+ display: inline-flex;
80
+ align-items: center;
73
81
  }
74
82
 
75
83
  @media (max-width: 900px) {
@@ -85,9 +93,6 @@ export class TabPane extends RapidElement {
85
93
  }
86
94
  }
87
95
 
88
- .focusedname .option.selected {
89
- }
90
-
91
96
  .focusedname .option .name {
92
97
  max-width: 0px;
93
98
  margin: 0;
@@ -101,137 +106,81 @@ export class TabPane extends RapidElement {
101
106
  max-width: 200px;
102
107
  }
103
108
 
104
- .option {
105
- transform: scale(0.9) translateY(0em);
106
- --icon-color: rgba(0, 0, 0, 0.5);
107
- color: rgba(0, 0, 0, 0.5);
108
- }
109
-
110
- .option.selected {
111
- }
112
-
113
- .option.selected,
114
- .option.selected:hover {
115
- cursor: default;
116
- box-shadow: 0px -3px 3px 1px rgba(0, 0, 0, 0.02);
117
-
118
- background: var(--focused-tab-color, #fff);
119
- transform: scale(1) translateY(1px);
120
- --icon-color: #666;
121
- color: #666;
122
- border: 1px inset rgba(0, 0, 0, 0.15);
123
- border-bottom: 0px;
124
- }
125
-
126
- .option.selected .dot {
127
- display: none;
128
- }
129
-
130
- .unselect .option.selected {
131
- cursor: pointer;
109
+ .focusedname .option .name {
110
+ transition: all 0s linear !important;
132
111
  }
133
112
 
134
- .unselect .option.selected:hover {
135
- background: var(--unselect-tab-color, #eee);
113
+ .focusedname .option.selected .name {
114
+ transition: all 200ms linear !important;
136
115
  }
137
116
 
138
- .option:hover {
139
- --icon-color: #666;
140
- color: #666;
141
- background: rgba(0, 0, 0, 0.02);
117
+ .option.dirty {
118
+ font-weight: var(--w-semibold);
142
119
  }
143
120
 
144
- .option.dirty {
145
- font-weight: 500;
121
+ .option.alert {
122
+ color: var(--danger);
123
+ --icon-color: var(--danger);
146
124
  }
147
125
 
148
126
  .pane {
149
127
  display: flex;
150
128
  flex-direction: column;
151
129
  flex-grow: 1;
152
- background: var(--focused-tab-color, #fff);
153
- border-bottom-left-radius: var(--curvature);
154
- border-bottom-right-radius: var(--curvature);
155
- overflow: hidden;
156
-
157
- box-shadow: var(
158
- --tabs-shadow,
159
- rgba(0, 0, 0, 0.1) 0px 1px 3px 0px,
160
- rgba(0, 0, 0, 0.03) 0px 1px 2px 0px
161
- );
162
130
  min-height: 0;
163
- }
164
-
165
- .pane.first {
166
- border-top-left-radius: 0px;
167
131
  overflow: hidden;
168
132
  }
169
133
 
170
134
  .count {
171
- border-radius: 99px;
172
- background: rgba(0, 0, 0, 0.1);
173
- color: rgba(0, 0, 0, 0.5);
174
- font-size: 0.7em;
175
- font-weight: 500;
176
- min-width: 1.5em;
177
- text-align: center;
178
- }
179
-
180
- .dot {
181
- height: 0.5em;
182
- width: 0.5em;
183
- margin-left: 0.2em;
184
- background: var(--color-primary-dark);
185
- border-radius: 99px;
186
- }
187
-
188
- .notify .count {
189
- background: var(--color-alert);
190
- color: #fff;
135
+ display: inline-flex;
136
+ align-items: center;
137
+ justify-content: center;
138
+ height: 16px;
139
+ padding: 0 2px;
140
+ color: inherit;
141
+ opacity: 0.6;
142
+ font-size: 11px;
143
+ font-weight: var(--w-medium);
144
+ font-variant-numeric: tabular-nums;
191
145
  }
192
146
 
193
- .alert {
194
- color: var(--color-alert);
195
- --icon-color: var(--color-alert);
147
+ .option.selected .count,
148
+ .option.alert .count {
149
+ min-width: 16px;
150
+ padding: 0 4px;
151
+ border-radius: 999px;
152
+ opacity: 1;
196
153
  }
197
154
 
198
- .embedded.pane {
199
- box-shadow: none;
200
- margin: 0;
201
- border-left: none !important;
202
- border-right: none !important;
203
- border-bottom: none !important;
155
+ .option.selected .count {
156
+ background: var(--accent-100);
157
+ color: var(--accent-700);
158
+ font-weight: var(--w-semibold);
204
159
  }
205
160
 
206
- .embedded .option {
207
- border-bottom: none !important;
208
- border-radius: 0em;
209
- border-top: none !important;
161
+ .option.alert .count {
162
+ background: var(--danger-bg);
163
+ color: var(--danger);
210
164
  }
211
165
 
212
- .embedded .option.first {
213
- margin-left: 0em;
214
- border-top: none !important;
215
- border-left: none;
166
+ .dot {
167
+ height: 0.5em;
168
+ width: 0.5em;
169
+ margin-left: 0.2em;
170
+ background: var(--accent-600);
171
+ border-radius: 99px;
216
172
  }
217
173
 
218
- .embedded.options .option.selected {
219
- box-shadow: none !important;
174
+ .option.selected .dot {
175
+ display: none;
220
176
  }
221
177
 
222
178
  .check {
223
179
  margin-left: 0.4em;
224
180
  }
225
-
226
- .pane {
227
- display: flex;
228
- }
229
181
  `;
230
182
  }
231
183
 
232
- @property({ type: Boolean })
233
- embedded = false;
234
-
235
184
  @property({ type: Boolean })
236
185
  collapses = false;
237
186
 
@@ -367,13 +316,11 @@ export class TabPane extends RapidElement {
367
316
  }
368
317
 
369
318
  public render(): TemplateResult {
370
- const activeTab = this.options[this.index];
371
319
  return html`
372
320
  <div
373
321
  class="${getClasses({
374
322
  options: true,
375
323
  collapses: this.collapses,
376
- embedded: this.embedded,
377
324
  focusedname: this.focusedName,
378
325
  unselect: this.unselect
379
326
  })}"
@@ -388,15 +335,9 @@ export class TabPane extends RapidElement {
388
335
  first: index == 0,
389
336
  selected: index == this.index,
390
337
  hidden: tab.hidden,
391
- notify: tab.notify,
392
338
  alert: tab.alert,
393
339
  dirty: tab.dirty
394
340
  })}"
395
- style="${tab.selectionColor && index == this.index
396
- ? `color:${tab.selectionColor};--icon-color:${tab.selectionColor};`
397
- : ''} ${tab.selectionBackground && index == this.index
398
- ? `background-color:${tab.selectionBackground};`
399
- : ''}"
400
341
  >
401
342
  ${tab.icon ? html`<temba-icon name=${tab.icon} />` : null}
402
343
  <div class="name">${tab.name} ${tab.dirty ? ` *` : ``}</div>
@@ -405,14 +346,11 @@ export class TabPane extends RapidElement {
405
346
  <div class="badge">
406
347
  ${tab.count > 0 && !tab.activity
407
348
  ? html`<div class="count">
408
- ${tab.activity ? '' : tab.count.toLocaleString()}
349
+ ${tab.count.toLocaleString()}
409
350
  </div>`
410
351
  : null}
411
352
  ${tab.activity && tab.count > 0 && !tab.dirty
412
- ? html`<div
413
- class="dot"
414
- style="background:${tab.activityColor}"
415
- ></div>`
353
+ ? html`<div class="dot"></div>`
416
354
  : null}
417
355
  </div>
418
356
  `
@@ -429,35 +367,7 @@ export class TabPane extends RapidElement {
429
367
  <slot name="tab-right"></slot>
430
368
  </div>
431
369
  </div>
432
- <div
433
- @temba-details-changed=${this.handleTabDetailsChanged}
434
- style="${activeTab?.borderColor
435
- ? `
436
- border-top: var(--temba-tabs-border-top, 1px solid ${
437
- activeTab?.borderColor || 'var(--color-widget-border)'
438
- });
439
-
440
- border-left: var(--temba-tabs-border-left, 1px solid ${
441
- activeTab?.borderColor || 'var(--color-widget-border)'
442
- });
443
-
444
- border-bottom: var(--temba-tabs-border-bottom, 1px solid ${
445
- activeTab?.borderColor || 'var(--color-widget-border)'
446
- });
447
-
448
- border-right: var(--temba-tabs-border-right, 1px solid ${
449
- activeTab?.borderColor || 'var(--color-widget-border)'
450
- });
451
-
452
- `
453
- : ''} ${activeTab?.selectionBackground
454
- ? `background:${activeTab?.selectionBackground};`
455
- : ``}"
456
- class="pane ${getClasses({
457
- first: this.index == 0,
458
- embedded: this.embedded
459
- })}"
460
- >
370
+ <div @temba-details-changed=${this.handleTabDetailsChanged} class="pane">
461
371
  <slot></slot>
462
372
  <slot name="pane-bottom"></slot>
463
373
  </div>
@@ -39,8 +39,6 @@ export class ContentMenu extends RapidElement {
39
39
  z-index: 5000;
40
40
  }
41
41
  .container {
42
- --button-y: 0.4em;
43
- --button-x: 1em;
44
42
  display: flex;
45
43
  align-items: center;
46
44
  }
@@ -52,6 +50,7 @@ export class ContentMenu extends RapidElement {
52
50
 
53
51
  temba-button {
54
52
  margin-right: 0.5rem;
53
+ align-self: center;
55
54
  }
56
55
  .toggle {
57
56
  --icon-color: rgb(102, 102, 102);
@@ -372,10 +372,7 @@ export class SortableList extends RapidElement {
372
372
  ];
373
373
 
374
374
  private inlineComputedStyles(original: Element, clone: Element): void {
375
- if (
376
- !(original instanceof HTMLElement) ||
377
- !(clone instanceof HTMLElement)
378
- ) {
375
+ if (!(original instanceof HTMLElement) || !(clone instanceof HTMLElement)) {
379
376
  return;
380
377
  }
381
378