@nyaruka/temba-components 0.26.1 → 0.26.2

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 (38) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/demo/index.html +32 -13
  3. package/dist/4f80c187.js +4277 -0
  4. package/dist/index.js +279 -202
  5. package/dist/sw.js +1 -1
  6. package/dist/sw.js.map +1 -1
  7. package/dist/templates/components-body.html +1 -1
  8. package/dist/templates/components-head.html +1 -1
  9. package/out-tsc/src/contacts/ContactChat.js +99 -85
  10. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  11. package/out-tsc/src/contacts/ContactHistory.js +30 -36
  12. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  13. package/out-tsc/src/contacts/events.js +61 -74
  14. package/out-tsc/src/contacts/events.js.map +1 -1
  15. package/out-tsc/src/list/TembaMenu.js +18 -13
  16. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  17. package/out-tsc/src/tabpane/Tab.js +46 -0
  18. package/out-tsc/src/tabpane/Tab.js.map +1 -0
  19. package/out-tsc/src/tabpane/TabPane.js +109 -0
  20. package/out-tsc/src/tabpane/TabPane.js.map +1 -0
  21. package/out-tsc/temba-modules.js +4 -0
  22. package/out-tsc/temba-modules.js.map +1 -1
  23. package/out-tsc/test/temba-contact-history.test.js +9 -7
  24. package/out-tsc/test/temba-contact-history.test.js.map +1 -1
  25. package/package.json +1 -1
  26. package/screenshots/truth/contacts/history-expanded.png +0 -0
  27. package/screenshots/truth/contacts/history.png +0 -0
  28. package/screenshots/truth/list/menu-submenu.png +0 -0
  29. package/src/contacts/ContactChat.ts +113 -94
  30. package/src/contacts/ContactHistory.ts +57 -59
  31. package/src/contacts/events.ts +61 -75
  32. package/src/list/TembaMenu.ts +19 -14
  33. package/src/tabpane/Tab.ts +42 -0
  34. package/src/tabpane/TabPane.ts +113 -0
  35. package/temba-modules.ts +4 -0
  36. package/test/temba-contact-history.test.ts +9 -7
  37. package/test-assets/style.css +6 -0
  38. package/dist/e477aebd.js +0 -4200
@@ -110,22 +110,24 @@ export class ContactHistory extends RapidElement {
110
110
  return css`
111
111
  ${getEventStyles()}
112
112
 
113
- :host {
114
- flex-grow: 1;
115
- flex-direction: column;
113
+ .wrapper {
114
+ border: 0px solid green;
116
115
  display: flex;
117
116
  flex-direction: column;
118
117
  align-items: items-stretch;
118
+ flex-grow: 1;
119
+ min-height: 0;
119
120
  }
120
121
 
121
122
  .events {
122
- height: 200px;
123
123
  overflow-y: scroll;
124
124
  overflow-x: hidden;
125
- flex-grow: 1;
126
- border-top-left-radius: var(--curvature);
127
- padding-top: 1em;
128
125
  background: #fff;
126
+ display: flex;
127
+ flex-direction: column;
128
+ flex-grow: 1;
129
+ min-height: 0;
130
+ padding-top: 3em;
129
131
  }
130
132
 
131
133
  temba-loading {
@@ -180,12 +182,9 @@ export class ContactHistory extends RapidElement {
180
182
  }
181
183
 
182
184
  .sticky-bin {
183
- display: flex;
184
- flex-direction: column;
185
- z-index: 2;
186
185
  border-top-left-radius: var(--curvature);
187
- overflow: hidden;
188
- box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.15);
186
+ z-index: 2;
187
+ box-shadow: rgb(0 0 0 / 15%) 0px 3px 3px 0px;
189
188
  background: rgb(240, 240, 240);
190
189
  }
191
190
 
@@ -382,7 +381,6 @@ export class ContactHistory extends RapidElement {
382
381
  if (forceOpen) {
383
382
  grouped[grouped.length - 1].open = forceOpen;
384
383
  }
385
-
386
384
  this.eventGroups = [...previousGroups, ...grouped];
387
385
  }
388
386
  this.refreshing = false;
@@ -452,6 +450,7 @@ export class ContactHistory extends RapidElement {
452
450
  if (changedProperties.has('refreshing') && !this.refreshing) {
453
451
  if (this.lastRefreshAdded > 0) {
454
452
  const events = this.getEventsPane();
453
+
455
454
  // if we are near the bottom, push us to the bottom to show new stuff
456
455
  if (this.lastHeight > 0) {
457
456
  const addedHeight = events.scrollHeight - this.lastHeight;
@@ -573,7 +572,6 @@ export class ContactHistory extends RapidElement {
573
572
  }
574
573
  eventGroup = {
575
574
  open: false,
576
- closing: false,
577
575
  events: [event],
578
576
  type: currentEventGroupType,
579
577
  };
@@ -649,21 +647,17 @@ export class ContactHistory extends RapidElement {
649
647
  }
650
648
 
651
649
  private handleEventGroupHide(event: MouseEvent) {
650
+ event.preventDefault();
651
+ event.stopPropagation();
652
+
652
653
  const grouping = event.currentTarget as HTMLDivElement;
653
654
  const groupIndex = parseInt(grouping.getAttribute('data-group-index'));
654
655
  const eventGroup =
655
656
  this.eventGroups[this.eventGroups.length - groupIndex - 1];
656
657
 
657
- // mark us as closing
658
- eventGroup.closing = true;
659
- this.requestUpdate('eventGroups');
658
+ eventGroup.open = false;
660
659
 
661
- // after our animation, close it up for real
662
- setTimeout(() => {
663
- eventGroup.closing = false;
664
- eventGroup.open = false;
665
- this.requestUpdate('eventGroups');
666
- }, 300);
660
+ this.requestUpdate('eventGroups');
667
661
  }
668
662
 
669
663
  private handleScroll() {
@@ -913,12 +907,16 @@ export class ContactHistory extends RapidElement {
913
907
  : null;
914
908
 
915
909
  return html`
916
- ${this.ticket
917
- ? html`<div class="sticky-bin">${unfetchedTickets}</div>`
918
- : null}
919
- ${this.fetching
920
- ? html`<temba-loading units="5" size="10"></temba-loading>`
921
- : html`<div style="height:0em"></div>`}
910
+ ${
911
+ this.ticket
912
+ ? html`<div class="sticky-bin">${unfetchedTickets}</div>`
913
+ : null
914
+ }
915
+ ${
916
+ this.fetching
917
+ ? html`<temba-loading units="5" size="10"></temba-loading>`
918
+ : html`<div style="height:0em"></div>`
919
+ }
922
920
  <div class="events" @scroll=${this.handleScroll}>
923
921
  ${this.eventGroups.map((eventGroup: EventGroup, index: number) => {
924
922
  const grouping = getEventGroupType(eventGroup.events[0], this.ticket);
@@ -928,9 +926,7 @@ export class ContactHistory extends RapidElement {
928
926
  grouping: true,
929
927
  [grouping]: true,
930
928
  expanded: eventGroup.open,
931
- closing: eventGroup.closing,
932
929
  });
933
-
934
930
  return html`<div class="${classes}">
935
931
  ${grouping === 'verbose'
936
932
  ? html`<div
@@ -938,36 +934,37 @@ export class ContactHistory extends RapidElement {
938
934
  @click=${this.handleEventGroupShow}
939
935
  data-group-index="${groupIndex}"
940
936
  >
941
- ${eventGroup.events.length}
942
- ${eventGroup.events.length === 1 ? html`event` : html`events`}
937
+ ${eventGroup.open
938
+ ? html`<temba-icon
939
+ @click=${this.handleEventGroupHide}
940
+ data-group-index="${groupIndex}"
941
+ name="x"
942
+ clickable
943
+ ></temba-icon>`
944
+ : html`${eventGroup.events.length}
945
+ ${eventGroup.events.length === 1
946
+ ? html`event`
947
+ : html`events`} `}
943
948
  </div>`
944
949
  : null}
945
- ${grouping === 'verbose'
946
- ? html`
947
- <temba-icon
948
- @click=${this.handleEventGroupHide}
949
- data-group-index="${groupIndex}"
950
- class="grouping-close-button"
951
- name="x"
952
- clickable
953
- ></temba-icon>
954
- `
955
- : null}
956
- ${eventGroup.events.map((event: ContactEvent) => {
957
- if (
958
- event.type === Events.TICKET_ASSIGNED &&
959
- (event as TicketEvent).note
960
- ) {
961
- const noteEvent = { ...event };
962
- noteEvent.type = Events.TICKET_NOTE_ADDED;
963
-
964
- return html`${this.renderEventContainer(
965
- noteEvent
966
- )}${this.renderEventContainer(event)}`;
967
- } else {
968
- return this.renderEventContainer(event);
969
- }
970
- })}
950
+
951
+ <div class="items">
952
+ ${eventGroup.events.map((event: ContactEvent) => {
953
+ if (
954
+ event.type === Events.TICKET_ASSIGNED &&
955
+ (event as TicketEvent).note
956
+ ) {
957
+ const noteEvent = { ...event };
958
+ noteEvent.type = Events.TICKET_NOTE_ADDED;
959
+
960
+ return html`${this.renderEventContainer(
961
+ noteEvent
962
+ )}${this.renderEventContainer(event)}`;
963
+ } else {
964
+ return this.renderEventContainer(event);
965
+ }
966
+ })}
967
+ </div>
971
968
  </div>`;
972
969
  })}
973
970
  </div>
@@ -984,6 +981,7 @@ export class ContactHistory extends RapidElement {
984
981
  New Messages
985
982
  </div>
986
983
  </div>
984
+ </div>
987
985
  `;
988
986
  }
989
987
  }
@@ -6,21 +6,54 @@ import { getDisplayName } from './helpers';
6
6
  export const getEventStyles = () => {
7
7
  return css`
8
8
  .grouping {
9
- padding: 0 2em;
10
- margin: 0 -1em;
9
+ margin-top: 1em;
11
10
  }
12
11
 
13
12
  .grouping.verbose {
14
13
  background: #f9f9f9;
15
- max-height: 1px;
16
- border-top: 1px solid #f9f9f9;
17
- padding-top: 0;
18
- padding-bottom: 0;
19
- margin-top: 0;
20
- margin-bottom: 1.5em;
21
- color: #efefef;
14
+ color: var(--color-dark);
22
15
  --color-link-primary: rgba(38, 166, 230, 1);
23
16
  pointer-events: none;
17
+ background: #fefefe;
18
+ box-shadow: -8px 0px 8px 1px rgba(0, 0, 0, 0.05) inset;
19
+ margin-right: -16px;
20
+ padding-right: 16px;
21
+ margin-bottom: 1.3em;
22
+ }
23
+
24
+ .grouping .items {
25
+ display: block;
26
+ user-select: none;
27
+ }
28
+
29
+ .grouping.verbose .items {
30
+ opacity: 0;
31
+ max-height: 0;
32
+ display: flex;
33
+ flex-direction: column;
34
+ }
35
+
36
+ .grouping.flows .items {
37
+ padding: 0;
38
+ }
39
+
40
+ .grouping.messages .items {
41
+ display: flex;
42
+ flex-direction: column;
43
+ margin: 0em 0.75em;
44
+ }
45
+
46
+ .grouping.verbose.expanded .items {
47
+ transition: max-height var(--transition-speed) ease-in-out,
48
+ opacity var(--transition-speed) ease-in-out;
49
+ opacity: 1;
50
+ max-height: 1000px;
51
+ padding: 1em 1em;
52
+ }
53
+
54
+ .grouping.verbose.expanded {
55
+ border-top: 1px solid #f3f3f3;
56
+ border-bottom: 1px solid #f3f3f3;
24
57
  }
25
58
 
26
59
  .grouping.verbose.expanded,
@@ -43,50 +76,37 @@ export const getEventStyles = () => {
43
76
  }
44
77
 
45
78
  .grouping.verbose .attn {
46
- color: #fff;
79
+ color: #666;
47
80
  }
48
81
 
49
82
  .event-count {
50
83
  position: relative;
51
- top: -1.2em;
52
84
  font-size: 0.8em;
53
85
  text-align: center;
54
- border: 2px solid #f9f9f9;
55
- background: #fff;
56
86
  margin: 0 auto;
57
87
  display: table;
58
88
  padding: 3px 10px;
59
89
  font-weight: 400;
60
- color: #777;
61
- border-radius: var(--curvature);
90
+ color: #999;
62
91
  cursor: pointer;
63
- min-width: 0%;
92
+ width: 100%;
64
93
  opacity: 1;
65
- transition: all var(--transition-speed) ease-in, opacity 0.1ms,
66
- margin-top 0ms;
67
- }
68
-
69
- .closing .grouping-close-button {
70
- opacity: 0 !important;
71
- transition: none !important;
94
+ z-index: 1;
72
95
  }
73
96
 
74
- .event-count {
75
- z-index: 1;
76
- margin-bottom: 1em;
97
+ .event-count temba-icon {
98
+ display: inline-block;
99
+ position: absolute;
100
+ right: 5px;
101
+ top: 5px;
77
102
  }
78
103
 
79
104
  .event-count:hover {
80
- padding: 3px 10px;
81
- min-width: 50%;
82
- background: #f9f9f9;
83
- color: #333;
105
+ color: var(--color-link-primary-hover);
84
106
  }
85
107
 
86
108
  .expanded .event-count {
87
- opacity: 0;
88
- margin-top: -42px;
89
- z-index: 0;
109
+ padding: 0;
90
110
  pointer-events: none;
91
111
  }
92
112
 
@@ -114,54 +134,18 @@ export const getEventStyles = () => {
114
134
  word-wrap: break-word;
115
135
  }
116
136
 
117
- .grouping.verbose.closing {
118
- opacity: 0 !important;
119
- padding: 0 !important;
120
- background: #f9f9f9 !important;
121
- max-height: 1px !important;
122
- border-top: 1px solid #f9f9f9 !important;
123
- padding-top: 0 !important;
124
- padding-bottom: 0 !important;
125
- margin-top: 0 !important;
126
- margin-bottom: 0 !important;
127
- }
128
-
129
- .grouping.verbose.closing .event,
130
- .grouping.verbose.closing pre {
131
- max-height: 0px;
132
- }
133
-
134
- .grouping.verbose.expanded {
135
- transition: all var(--transition-speed)
136
- cubic-bezier(0.68, -0.55, 0.265, 1.05),
137
- color 0.1ms;
138
- background: #444;
139
- color: #efefef;
140
- max-height: 1000px;
141
- border-top: 1px solid #f1f1f1;
142
- padding: 2em;
143
- margin-left: 1em;
144
- margin-right: 1em;
145
- border-radius: var(--curvature);
146
- padding-bottom: 1em;
147
- box-shadow: inset 0px 11px 4px -15px #000, inset 0px -11px 4px -15px #000;
148
- }
149
-
150
137
  .grouping.verbose.expanded .event,
151
138
  .grouping.verbose.expanded pre {
152
139
  max-height: 500px;
153
- margin-bottom: 0.5em;
154
140
  opacity: 1;
155
- transition: all var(--transition-speed) ease-in-out;
156
141
  }
157
142
 
158
143
  .grouping-close-button {
144
+ position: relative;
145
+ display: inline-block;
159
146
  opacity: 0;
160
147
  float: right;
161
- margin-top: -1em !important;
162
- margin-right: -1em !important;
163
- fill: #f2f2f2;
164
- transition: opacity var(--transition-speed) ease-in;
148
+ --icon-color: #666;
165
149
  }
166
150
 
167
151
  .grouping.verbose.expanded:hover .grouping-close-button {
@@ -175,7 +159,7 @@ export const getEventStyles = () => {
175
159
  }
176
160
 
177
161
  .event {
178
- margin-bottom: 1em;
162
+ margin: 0.25em 0.5em;
179
163
  border-radius: var(--curvature);
180
164
  flex-grow: 1;
181
165
  }
@@ -332,6 +316,7 @@ export const getEventStyles = () => {
332
316
  font-size: 80%;
333
317
  color: rgba(0, 0, 0, 0.6);
334
318
  padding: 6px 3px;
319
+ margin-bottom: 0.5em;
335
320
  }
336
321
 
337
322
  .msg-summary temba-icon[name='log'] {
@@ -460,7 +445,6 @@ export interface EventGroup {
460
445
  type: string;
461
446
  events: ContactEvent[];
462
447
  open: boolean;
463
- closing: boolean;
464
448
  }
465
449
 
466
450
  export enum Events {
@@ -1081,7 +1065,9 @@ export const renderContactLanguageChangedEvent = (
1081
1065
  event: ContactLanguageChangedEvent
1082
1066
  ): TemplateResult => {
1083
1067
  return html`<temba-icon name="contact"></temba-icon>
1084
- <div class="description">Language updated to ${event.language}</div>`;
1068
+ <div class="description">
1069
+ Language updated to <span class="attn">${event.language}</span>
1070
+ </div>`;
1085
1071
  };
1086
1072
 
1087
1073
  export const renderChannelEvent = (event: ChannelEvent): TemplateResult => {
@@ -20,7 +20,6 @@ export interface MenuItem {
20
20
  items?: MenuItem[];
21
21
  inline?: boolean;
22
22
  type?: string;
23
- parent?: MenuItem;
24
23
  on_submit?: string;
25
24
  }
26
25
 
@@ -57,7 +56,7 @@ export class TembaMenu extends RapidElement {
57
56
  }
58
57
 
59
58
  .section {
60
- font-size: 1.875rem;
59
+ font-size: 1.5em;
61
60
  margin-bottom: 0.2em;
62
61
  color: var(--color-text-dark);
63
62
  }
@@ -80,7 +79,7 @@ export class TembaMenu extends RapidElement {
80
79
  user-select: none;
81
80
  -webkit-user-select: none;
82
81
  display: flex;
83
- font-size: 1.15em;
82
+ font-size: 1em;
84
83
  --icon-color: var(--color-text-dark);
85
84
  }
86
85
 
@@ -368,7 +367,7 @@ export class TembaMenu extends RapidElement {
368
367
  }
369
368
 
370
369
  .sub-section {
371
- font-size: 1.1rem;
370
+ font-size: 1rem;
372
371
  color: #888;
373
372
  margin-top: 1rem;
374
373
  margin-left: 0.3rem;
@@ -395,8 +394,8 @@ export class TembaMenu extends RapidElement {
395
394
  }
396
395
 
397
396
  temba-button {
398
- margin-top: 0.5em;
399
- margin-bottom: 0.5em;
397
+ margin-top: 0.2em;
398
+ margin-bottom: 0.2em;
400
399
  margin-left: 0.75em;
401
400
  margin-right: 0.75em;
402
401
  }
@@ -519,7 +518,7 @@ export class TembaMenu extends RapidElement {
519
518
  if (subItem.items) {
520
519
  subItem.items.forEach(inlineItem => {
521
520
  inlineItem.level = item.level + 2;
522
- inlineItem.parent = subItem;
521
+ // inlineItem.parent = subItem;
523
522
  });
524
523
  }
525
524
  });
@@ -563,10 +562,10 @@ export class TembaMenu extends RapidElement {
563
562
  private handleItemClicked(
564
563
  event: MouseEvent,
565
564
  menuItem: MenuItem,
566
- alias = false
565
+ parent: MenuItem = null
567
566
  ) {
568
- if (menuItem.parent) {
569
- this.handleItemClicked(null, menuItem.parent, alias);
567
+ if (parent && parent.inline) {
568
+ this.handleItemClicked(null, parent);
570
569
  }
571
570
 
572
571
  if (this.collapsed) {
@@ -613,7 +612,6 @@ export class TembaMenu extends RapidElement {
613
612
  this.loadItems(menuItem, !menuItem.href);
614
613
  this.dispatchEvent(new Event('change'));
615
614
  } else {
616
- this.dispatchEvent(new Event('change'));
617
615
  if (this.pending && this.pending.length > 0) {
618
616
  // auto select the next pending click
619
617
  const nextId = this.pending.shift();
@@ -622,10 +620,14 @@ export class TembaMenu extends RapidElement {
622
620
  const nextItem = findItem(item.items, nextId).item;
623
621
  if (nextItem) {
624
622
  this.handleItemClicked(null, nextItem);
623
+ } else {
624
+ this.fireNoPath(nextId);
625
625
  }
626
626
  } else {
627
627
  this.fireNoPath(nextId);
628
628
  }
629
+ } else {
630
+ this.dispatchEvent(new Event('change'));
629
631
  }
630
632
  this.requestUpdate('root');
631
633
  }
@@ -756,7 +758,10 @@ export class TembaMenu extends RapidElement {
756
758
  return expanded;
757
759
  }
758
760
 
759
- private renderMenuItem = (menuItem: MenuItem): TemplateResult => {
761
+ private renderMenuItem = (
762
+ menuItem: MenuItem,
763
+ parent: MenuItem = null
764
+ ): TemplateResult => {
760
765
  if (menuItem.type === 'divider') {
761
766
  return html`<div class="divider"></div>`;
762
767
  }
@@ -812,7 +817,7 @@ export class TembaMenu extends RapidElement {
812
817
  id="menu-${menuItem.id}"
813
818
  class="${itemClasses}"
814
819
  @click=${event => {
815
- this.handleItemClicked(event, menuItem);
820
+ this.handleItemClicked(event, menuItem, parent);
816
821
  }}
817
822
  >
818
823
  ${menuItem.level === 0
@@ -928,7 +933,7 @@ export class TembaMenu extends RapidElement {
928
933
  return html`${this.renderMenuItem(item)}
929
934
  <div class="inline-children">
930
935
  ${item.items.map((child: MenuItem) => {
931
- return this.renderMenuItem(child);
936
+ return this.renderMenuItem(child, item);
932
937
  })}
933
938
  </div>`;
934
939
  }
@@ -0,0 +1,42 @@
1
+ import { css, html, TemplateResult } from 'lit';
2
+ import { property } from 'lit/decorators';
3
+ import { RapidElement } from '../RapidElement';
4
+ import { getClasses } from '../utils';
5
+
6
+ export class Tab extends RapidElement {
7
+ static get styles() {
8
+ return css`
9
+ :host {
10
+ display: flex;
11
+ flex-direction: column;
12
+ flex-grow: 1;
13
+ min-height: 0;
14
+ }
15
+
16
+ slot {
17
+ // display: none;
18
+
19
+
20
+ slot.selected {
21
+ // display: flex;
22
+ // flex-direction: column;
23
+ // flex-grow: 1;
24
+ }
25
+ `;
26
+ }
27
+
28
+ @property({ type: String })
29
+ name: string;
30
+
31
+ @property({ type: String })
32
+ icon: string;
33
+
34
+ @property({ type: Boolean })
35
+ selected = false;
36
+
37
+ public render(): TemplateResult {
38
+ return html`<slot
39
+ class="${getClasses({ selected: this.selected })}"
40
+ ></slot>`;
41
+ }
42
+ }
@@ -0,0 +1,113 @@
1
+ import { css, html, TemplateResult } from 'lit';
2
+ import { property } from 'lit/decorators';
3
+ import { RapidElement } from '../RapidElement';
4
+ import { Tab } from './Tab';
5
+
6
+ export class TabPane extends RapidElement {
7
+ static get styles() {
8
+ return css`
9
+ :host {
10
+ display: flex;
11
+ flex-direction: column;
12
+ min-height: 0;
13
+ }
14
+
15
+ .tabs {
16
+ display: flex;
17
+ }
18
+
19
+ .tab {
20
+ padding: 0.5em 1em;
21
+ margin: 0em 0em;
22
+ cursor: pointer;
23
+ display: flex;
24
+ border-radius: var(--curvature);
25
+ border-bottom-right-radius: 0px;
26
+ border-bottom-left-radius: 0px;
27
+ border: 0px solid rgba(0, 0, 0, 0.45);
28
+ color: var(--color-text-dark);
29
+ --icon-color: var(--color-text-dark);
30
+ }
31
+
32
+ .tab temba-icon {
33
+ margin-right: 0.4em;
34
+ }
35
+
36
+ .tab.selected {
37
+ cursor: default;
38
+ box-shadow: 2px 1px 3px 2px rgba(0, 0, 0, 0.07);
39
+ background: #fff;
40
+ }
41
+
42
+ .pane {
43
+ display: flex;
44
+ flex-direction: column;
45
+ flex-grow: 1;
46
+ background: #fff;
47
+ border-radius: var(--curvature);
48
+ box-shadow: 2px 5px 12px 2px rgba(0, 0, 0, 0.09),
49
+ 3px 3px 2px 1px rgba(0, 0, 0, 0.05);
50
+ min-height: 0;
51
+ }
52
+
53
+ .pane.first {
54
+ border-top-left-radius: 0px;
55
+ }
56
+ `;
57
+ }
58
+
59
+ @property({ type: Number })
60
+ index = 0;
61
+
62
+ private handleTabClick(event: MouseEvent): void {
63
+ this.index = parseInt(
64
+ (event.currentTarget as HTMLDivElement).dataset.index
65
+ );
66
+ this.requestUpdate('index');
67
+ }
68
+
69
+ public updated(changedProperties: Map<string, any>) {
70
+ super.updated(changedProperties);
71
+ if (changedProperties.has('index')) {
72
+ if (this.children.length > this.index) {
73
+ for (let i = 0; i < this.children.length; i++) {
74
+ const tab = this.children[i] as Tab;
75
+ tab.selected = i == this.index;
76
+
77
+ if (tab.selected) {
78
+ tab.style.display = 'flex';
79
+ } else {
80
+ tab.style.display = 'none';
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ public render(): TemplateResult {
88
+ const tabs: Tab[] = [];
89
+ for (const tab of this.children) {
90
+ tabs.push(tab as Tab);
91
+ }
92
+
93
+ return html`
94
+ <div class="tabs">
95
+ ${tabs.map(
96
+ (tab, index) => html`
97
+ <div
98
+ @click=${this.handleTabClick}
99
+ data-index=${index}
100
+ class="tab ${index == this.index ? 'selected' : ''}"
101
+ >
102
+ ${tab.icon ? html`<temba-icon name=${tab.icon} />` : null}
103
+ ${tab.name}
104
+ </div>
105
+ `
106
+ )}
107
+ </div>
108
+ <div class="pane ${this.index === 0 ? 'first' : null}">
109
+ <slot></slot>
110
+ </div>
111
+ `;
112
+ }
113
+ }