@nyaruka/temba-components 0.26.9 → 0.26.10

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 (58) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/{d0cc86be.js → cbffa348.js} +213 -109
  3. package/dist/index.js +213 -109
  4. package/dist/static/icons/symbol-defs.svg +35 -4
  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/ContactBadges.js +97 -0
  10. package/out-tsc/src/contacts/ContactBadges.js.map +1 -0
  11. package/out-tsc/src/contacts/ContactFieldEditor.js +53 -41
  12. package/out-tsc/src/contacts/ContactFieldEditor.js.map +1 -1
  13. package/out-tsc/src/contacts/ContactFields.js +86 -29
  14. package/out-tsc/src/contacts/ContactFields.js.map +1 -1
  15. package/out-tsc/src/interfaces.js.map +1 -1
  16. package/out-tsc/src/label/Label.js +25 -0
  17. package/out-tsc/src/label/Label.js.map +1 -1
  18. package/out-tsc/src/list/TembaMenu.js +8 -6
  19. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  20. package/out-tsc/src/store/Store.js +16 -0
  21. package/out-tsc/src/store/Store.js.map +1 -1
  22. package/out-tsc/src/tabpane/Tab.js +14 -1
  23. package/out-tsc/src/tabpane/Tab.js.map +1 -1
  24. package/out-tsc/src/tabpane/TabPane.js +35 -0
  25. package/out-tsc/src/tabpane/TabPane.js.map +1 -1
  26. package/out-tsc/src/textinput/TextInput.js +2 -2
  27. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  28. package/out-tsc/src/vectoricon/VectorIcon.js +13 -2
  29. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  30. package/out-tsc/temba-modules.js +6 -6
  31. package/out-tsc/temba-modules.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/contacts/ContactBadges.ts +104 -0
  34. package/src/contacts/ContactFieldEditor.ts +55 -41
  35. package/src/contacts/ContactFields.ts +99 -33
  36. package/src/interfaces.ts +3 -1
  37. package/src/label/Label.ts +25 -0
  38. package/src/list/TembaMenu.ts +7 -5
  39. package/src/store/Store.ts +24 -1
  40. package/src/tabpane/Tab.ts +14 -1
  41. package/src/tabpane/TabPane.ts +36 -0
  42. package/src/textinput/TextInput.ts +2 -2
  43. package/src/vectoricon/VectorIcon.ts +15 -2
  44. package/static/css/temba-components.css +3 -0
  45. package/static/icons/Read Me.txt +1 -1
  46. package/static/icons/SVG/bookmark-filled.svg +5 -0
  47. package/static/icons/SVG/bookmark.svg +1 -1
  48. package/static/icons/SVG/external-link1.svg +5 -0
  49. package/static/icons/SVG/globe.svg +5 -0
  50. package/static/icons/SVG/language.svg +5 -0
  51. package/static/icons/SVG/search.svg +5 -0
  52. package/static/icons/demo-external-svg.html +218 -165
  53. package/static/icons/demo-files/demo.css +6 -3
  54. package/static/icons/demo.html +253 -169
  55. package/static/icons/selection.json +318 -184
  56. package/static/icons/style.css +4 -0
  57. package/static/icons/symbol-defs.svg +35 -4
  58. package/temba-modules.ts +7 -6
@@ -0,0 +1,104 @@
1
+ import { css, html, TemplateResult } from 'lit';
2
+ import { Group } from '../interfaces';
3
+ import { ContactStoreElement } from './ContactStoreElement';
4
+
5
+ const STATUS = {
6
+ S: { name: 'Stopped', icon: 'x-octagon' },
7
+ B: { name: 'Blocked', icon: 'slash' },
8
+ V: { name: 'Archived', icon: 'archive' },
9
+ };
10
+
11
+ export class ContactBadges extends ContactStoreElement {
12
+ static get styles() {
13
+ return css`
14
+ temba-label {
15
+ margin: 0.3em;
16
+ }
17
+
18
+ .badges {
19
+ display: flex;
20
+ flex-wrap: wrap;
21
+ }
22
+
23
+ .flow {
24
+ }
25
+ `;
26
+ }
27
+
28
+ public render(): TemplateResult {
29
+ if (this.data) {
30
+ const status = STATUS[this.data.status];
31
+
32
+ return html`
33
+ <div class="badges">
34
+ ${status && this.data.status !== 'A'
35
+ ? html`
36
+ <temba-label
37
+ class="status"
38
+ icon="${status.icon}"
39
+ onclick="goto(event)"
40
+ href="/contact/${status.name.toLowerCase()}"
41
+ clickable
42
+ secondary
43
+ shadow
44
+ >
45
+ ${status.name}
46
+ </temba-label>
47
+ `
48
+ : null}
49
+ ${this.data.flow
50
+ ? html`
51
+ <temba-label
52
+ class="flow"
53
+ icon="flow"
54
+ onclick="goto(event)"
55
+ href="/contact/?search=flow+%3D+${encodeURIComponent(
56
+ '"' + this.data.flow.name + '"'
57
+ )}"
58
+ clickable
59
+ tertiary
60
+ shadow
61
+ >
62
+ ${this.data.flow.name}
63
+ </temba-label>
64
+ `
65
+ : null}
66
+ ${this.data.language
67
+ ? html`
68
+ <temba-label
69
+ class="language"
70
+ icon="globe"
71
+ onclick="goto(event)"
72
+ href="/contact/?search=language+%3D+${encodeURIComponent(
73
+ '"' + this.data.language + '"'
74
+ )}"
75
+ clickable
76
+ primary
77
+ shadow
78
+ >
79
+ ${this.store.getLanguageName(this.data.language)}
80
+ </temba-label>
81
+ `
82
+ : null}
83
+ ${this.data.groups.map((group: Group) => {
84
+ return html`
85
+ <temba-label
86
+ class="group"
87
+ onclick="goto(event)"
88
+ href="/contact/filter/${group.uuid}/"
89
+ icon=${group.is_dynamic ? 'atom' : 'users'}
90
+ clickable
91
+ light
92
+ shadow
93
+ >
94
+ ${group.name}
95
+ </temba-label>
96
+ `;
97
+ })}
98
+ </div>
99
+ `;
100
+ } else {
101
+ return null;
102
+ }
103
+ }
104
+ }
@@ -1,5 +1,6 @@
1
1
  import { css, html, TemplateResult } from 'lit';
2
2
  import { property } from 'lit/decorators';
3
+ import { CustomEventType } from '../interfaces';
3
4
  import { RapidElement } from '../RapidElement';
4
5
  import { TextInput } from '../textinput/TextInput';
5
6
 
@@ -26,11 +27,11 @@ export class ContactFieldEditor extends RapidElement {
26
27
  return css`
27
28
  .prefix {
28
29
  background: rgba(0, 0, 0, 0.05);
29
- border-top-left-radius: 4px;
30
- border-bottom-left-radius: 4px;
30
+ border-top-left-radius: var(--curvature-widget);
31
+ border-bottom-left-radius: var(--curvature-widget);
31
32
  color: #888;
32
33
  cursor: pointer;
33
- width: 100px;
34
+ width: 200px;
34
35
  white-space: nowrap;
35
36
  overflow: hidden;
36
37
  text-overflow: ellipsis;
@@ -38,10 +39,14 @@ export class ContactFieldEditor extends RapidElement {
38
39
  padding: 0em 0.5em;
39
40
  }
40
41
 
42
+ .wrapper {
43
+ margin-bottom: -1px;
44
+ }
45
+
41
46
  .prefix .name {
42
47
  padding: 0.5em 0em;
43
48
  color: #888;
44
- width: 80px;
49
+ width: 200px;
45
50
  white-space: nowrap;
46
51
  overflow: hidden;
47
52
  text-overflow: ellipsis;
@@ -55,8 +60,8 @@ export class ContactFieldEditor extends RapidElement {
55
60
  .popper {
56
61
  padding: 0.5em 0.75em;
57
62
  background: rgba(240, 240, 240, 1);
58
- border-top-right-radius: 4px;
59
- border-bottom-right-radius: 4px;
63
+ border-top-right-radius: var(--curvature-widget);
64
+ border-bottom-right-radius: var(--curvature-widget);
60
65
  --icon-color: #888;
61
66
  opacity: 0;
62
67
  cursor: default;
@@ -67,47 +72,34 @@ export class ContactFieldEditor extends RapidElement {
67
72
  z-index: 1000;
68
73
  }
69
74
 
70
- .postfix temba-icon[name='calendar'] {
71
- --icon-color: #e3e3e3;
72
- }
73
-
74
- .popper.check {
75
- background: rgba(90, 145, 86, 0.15);
76
- }
77
-
78
- .popper.none {
79
- opacity: 0;
75
+ temba-icon[name='calendar'] {
76
+ --icon-color: rgba(0, 0, 0, 0.2);
80
77
  }
81
78
 
82
- .popper.copy temba-icon:hover {
83
- --icon-color: #555;
84
- }
85
-
86
- .popper.corner-down-left {
87
- // background: var(--color-primary-dark);
88
- // --icon-color: var(--color-text-light);
89
- opacity: 1;
90
- transform: scale(1);
79
+ temba-icon:hover {
80
+ --icon-color: rgba(0, 0, 0, 0.5);
91
81
  }
92
82
 
93
83
  temba-icon {
94
84
  cursor: pointer;
85
+ --icon-color: rgba(0, 0, 0, 0.3);
95
86
  }
96
87
 
97
- temba-icon[name='check'] {
98
- --icon-color: rgb(90, 145, 86);
99
- }
100
-
101
- temba-textinput:hover .popper.copy {
88
+ temba-textinput:hover .popper {
102
89
  opacity: 1;
103
90
  transform: scale(1);
104
91
  }
105
92
 
106
- temba-textinput:focus .popper.copy {
93
+ temba-textinput:focus .popper {
107
94
  opacity: 1;
108
95
  transform: scale(1);
109
96
  }
110
97
 
98
+ .unset temba-textinput:focus .popper,
99
+ .unset temba-textinput:hover .popper {
100
+ opacity: 0;
101
+ }
102
+
111
103
  .copy.clicked temba-icon {
112
104
  transform: scale(1.2);
113
105
  }
@@ -115,6 +107,10 @@ export class ContactFieldEditor extends RapidElement {
115
107
  temba-icon {
116
108
  transition: all 200ms ease-in-out;
117
109
  }
110
+
111
+ temba-icon[name='search'] {
112
+ margin-right: 1em;
113
+ }
118
114
  `;
119
115
  }
120
116
 
@@ -139,6 +135,14 @@ export class ContactFieldEditor extends RapidElement {
139
135
  });
140
136
  }
141
137
  }
138
+
139
+ if (icon === 'search') {
140
+ this.fireCustomEvent(CustomEventType.ButtonClicked, {
141
+ key: this.key,
142
+ value: this.value,
143
+ });
144
+ }
145
+
142
146
  evt.preventDefault();
143
147
  evt.stopPropagation();
144
148
  }
@@ -166,7 +170,7 @@ export class ContactFieldEditor extends RapidElement {
166
170
 
167
171
  public render(): TemplateResult {
168
172
  return html`
169
- <div>
173
+ <div class="wrapper ${this.value ? 'set' : 'unset'}">
170
174
  <temba-textinput
171
175
  value="${this.value ? this.value : ''}"
172
176
  ?datetimepicker=${this.type === 'datetime'}
@@ -176,22 +180,32 @@ export class ContactFieldEditor extends RapidElement {
176
180
  >
177
181
  <div class="prefix" slot="prefix">
178
182
  <div class="name">${this.name}</div>
179
- </div>
180
-
181
- <div class="postfix">
182
183
  ${this.type === 'datetime'
183
- ? html`<div
184
- style="position: absolute; padding-top: .75em; padding-left: .75em;"
185
- >
186
- <temba-icon name="calendar" />
184
+ ? html`<div style="position: relative; padding-top: .75em;">
185
+ <temba-icon name="calendar" animateclick="pulse" />
187
186
  </div>`
188
187
  : null}
188
+ </div>
189
189
 
190
+ <div class="postfix">
190
191
  <div
191
- class="popper ${this.iconClass} ${this.icon ? this.icon : 'none'}"
192
+ class="popper ${this.iconClass}"
192
193
  @click=${this.handleIconClick}
193
194
  >
194
- <temba-icon name="${this.icon}" animatechange="spin"></temba-icon>
195
+ ${this.value
196
+ ? html`
197
+ <temba-icon
198
+ name="search"
199
+ animateclick="pulse"
200
+ ></temba-icon>
201
+ </div>
202
+ `
203
+ : null}
204
+ <temba-icon
205
+ name="${this.icon}"
206
+ animatechange="spin"
207
+ animateclick="pulse"
208
+ ></temba-icon>
195
209
  </div>
196
210
  </div>
197
211
  </temba-textinput>
@@ -1,40 +1,35 @@
1
- import { css, html, TemplateResult } from 'lit';
1
+ import { css, html, PropertyValueMap, TemplateResult } from 'lit';
2
2
  import { property } from 'lit/decorators';
3
- import { ContactField } from '../interfaces';
4
3
  import { postJSON } from '../utils';
5
4
  import { ContactFieldEditor } from './ContactFieldEditor';
6
5
  import { ContactStoreElement } from './ContactStoreElement';
6
+ import { Checkbox } from '../checkbox/Checkbox';
7
+
8
+ const MIN_FOR_FILTER = 10;
7
9
 
8
10
  export class ContactFields extends ContactStoreElement {
9
11
  static get styles() {
10
12
  return css`
11
13
  :host {
12
- display: flex;
13
- flex-wrap: wrap;
14
- flex-shrink: 1;
14
+ --curvature-widget: 0px;
15
+ border-radius: 6px;
15
16
  }
16
17
 
17
18
  .field {
18
19
  display: flex;
19
20
  margin: 0.3em 0.3em;
20
21
  box-shadow: 0 0 0.2em rgba(0, 0, 0, 0.15);
21
- border-radius: var(--curvature);
22
+ border-radius: 0px;
22
23
  align-items: center;
23
24
  overflow: hidden;
24
25
  }
25
26
 
26
- .field.set {
27
- background: #fff;
28
- }
29
-
30
- .field.unset {
31
- opacity: 0.4;
32
- }
33
-
34
- .field.unset .label {
27
+ .show-all .unset {
28
+ display: block;
35
29
  }
36
30
 
37
- .field:hover {
31
+ .unset {
32
+ display: none;
38
33
  }
39
34
 
40
35
  .field:hover {
@@ -45,8 +40,8 @@ export class ContactFields extends ContactStoreElement {
45
40
 
46
41
  .label {
47
42
  padding: 0.25em 1em;
48
- border-top-left-radius: var(--curvature);
49
- border-bottom-left-radius: var(--curvature);
43
+ border-top-left-radius: 0px;
44
+ border-bottom-left-radius: 0px;
50
45
  color: #777;
51
46
  font-size: 0.9em;
52
47
  font-weight: 400;
@@ -60,27 +55,52 @@ export class ContactFields extends ContactStoreElement {
60
55
  overflow: hidden;
61
56
  text-overflow: ellipsis;
62
57
  padding: 0.25em 1em;
63
- border-top-right-radius: var(--curvature);
64
- border-bottom-right-radius: var(--curvature);
58
+ border-top-right-radius: 0px;
59
+ border-bottom-right-radius: 0px;
65
60
  font-size: 0.9em;
66
61
  }
67
62
 
68
63
  temba-contact-field {
69
- margin: 0.3em;
70
- min-width: 320px;
71
- flex-grow: 1;
64
+ }
65
+
66
+ .footer {
67
+ margin-bottom: 0;
68
+ display: flex;
69
+ background: #fff;
70
+ align-items: center;
71
+ margin-top: 0.5em;
72
72
  }
73
73
  `;
74
74
  }
75
75
 
76
+ @property({ type: Boolean })
77
+ pinned: boolean;
78
+
79
+ @property({ type: Boolean })
80
+ system: boolean;
81
+
76
82
  @property({ type: Boolean })
77
83
  dirty: boolean;
78
84
 
85
+ @property({ type: Boolean })
86
+ showAll: boolean;
87
+
79
88
  connectedCallback(): void {
80
89
  super.connectedCallback();
81
90
  this.handleFieldChanged = this.handleFieldChanged.bind(this);
82
91
  }
83
92
 
93
+ protected updated(
94
+ changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
95
+ ): void {
96
+ super.updated(changes);
97
+ if (changes.has('data')) {
98
+ if (Object.keys(this.data.fields).length <= MIN_FOR_FILTER) {
99
+ this.showAll = true;
100
+ }
101
+ }
102
+ }
103
+
84
104
  public handleFieldChanged(evt: InputEvent) {
85
105
  const field = evt.currentTarget as ContactFieldEditor;
86
106
  this.data.fields[field.key] = field.value;
@@ -91,22 +111,68 @@ export class ContactFields extends ContactStoreElement {
91
111
  });
92
112
  }
93
113
 
94
- public render(): TemplateResult {
95
- const pinned = this.store.getPinnedFields();
114
+ public handleToggle(evt: Event) {
115
+ const checkbox = evt.currentTarget as Checkbox;
116
+ console.log(checkbox.checked);
117
+ this.showAll = checkbox.checked;
118
+ }
96
119
 
97
- const fields = pinned.map((field: ContactField) => {
98
- if (this.data) {
99
- const value = this.data.fields[field.key];
120
+ public render(): TemplateResult {
121
+ if (this.data) {
122
+ const fieldsToShow = Object.entries(this.data.fields)
123
+ .filter((entry: [string, string]) => {
124
+ return (
125
+ (this.pinned && this.store.getContactField(entry[0]).pinned) ||
126
+ (!this.pinned && !this.store.getContactField(entry[0]).pinned)
127
+ );
128
+ })
129
+ .sort((a: [string, string], b: [string, string]) => {
130
+ const [ak] = a;
131
+ const [bk] = b;
132
+ /* if (av && !bv) {
133
+ return -1;
134
+ }
135
+
136
+ if (bv && !av) {
137
+ return 1;
138
+ }*/
139
+
140
+ return ak.localeCompare(bk);
141
+ });
142
+
143
+ const fields = fieldsToShow.map((entry: [string, string]) => {
144
+ const [k, v] = entry;
145
+ const field = this.store.getContactField(k);
100
146
  return html`<temba-contact-field
147
+ class=${v ? 'set' : 'unset'}
101
148
  key=${field.key}
102
149
  name=${field.label}
103
- value=${value}
150
+ value=${v}
104
151
  type=${field.value_type}
105
152
  @change=${this.handleFieldChanged}
106
153
  ></temba-contact-field>`;
107
- }
108
- });
109
-
110
- return html`${this.data ? html` ${fields} ` : null}`;
154
+ });
155
+
156
+ return html`
157
+ <div class="${this.showAll || this.pinned ? 'show-all' : ''}">
158
+ ${fields}
159
+ </div>
160
+
161
+ ${!this.pinned && Object.keys(this.data.fields).length >= MIN_FOR_FILTER
162
+ ? html` <div class="footer">
163
+ <div style="flex-grow: 1"></div>
164
+ <div>
165
+ <temba-checkbox
166
+ ?checked=${this.showAll}
167
+ @change=${this.handleToggle}
168
+ label="Show All"
169
+ />
170
+ </div>
171
+ </div>`
172
+ : null}
173
+ `;
174
+ }
175
+
176
+ return null;
111
177
  }
112
178
  }
package/src/interfaces.ts CHANGED
@@ -84,13 +84,15 @@ export interface Contact {
84
84
  stopped: boolean;
85
85
  blocked: boolean;
86
86
  urns: string[];
87
- lang: string;
87
+ language?: string;
88
88
  fields: { [key: string]: string };
89
89
  groups: Group[];
90
90
  modified_on: string;
91
91
  created_on: string;
92
92
  last_seen_on: string;
93
+ status: string;
93
94
 
95
+ flow?: ObjectReference;
94
96
  last_msg?: Msg;
95
97
  direction?: string;
96
98
  ticket: {
@@ -50,6 +50,27 @@ export default class Label extends LitElement {
50
50
  text-shadow: none;
51
51
  }
52
52
 
53
+ .tertiary {
54
+ background: var(--color-label-tertiary);
55
+ color: var(--color-label-tertiary-text);
56
+ --icon-color: var(--color-label-tertiary-text);
57
+ text-shadow: none;
58
+ }
59
+
60
+ .tertiary {
61
+ background: var(--color-label-tertiary);
62
+ color: var(--color-label-tertiary-text);
63
+ --icon-color: var(--color-label-tertiary-text);
64
+ text-shadow: none;
65
+ }
66
+
67
+ .tertiary {
68
+ background: var(--color-label-tertiary);
69
+ color: var(--color-label-tertiary-text);
70
+ --icon-color: var(--color-label-tertiary-text);
71
+ text-shadow: none;
72
+ }
73
+
53
74
  .light {
54
75
  background: var(--color-overlay-light);
55
76
  color: var(--color-overlay-light-text);
@@ -83,6 +104,9 @@ export default class Label extends LitElement {
83
104
  @property({ type: Boolean })
84
105
  secondary: boolean;
85
106
 
107
+ @property({ type: Boolean })
108
+ tertiary: boolean;
109
+
86
110
  @property({ type: Boolean })
87
111
  light: boolean;
88
112
 
@@ -116,6 +140,7 @@ export default class Label extends LitElement {
116
140
  clickable: this.clickable,
117
141
  primary: this.primary,
118
142
  secondary: this.secondary,
143
+ tertiary: this.tertiary,
119
144
  light: this.light,
120
145
  dark: this.dark,
121
146
  shadow: this.shadow,
@@ -503,11 +503,13 @@ export class TembaMenu extends RapidElement {
503
503
  // go up the tree until we find an endpoint
504
504
  const item = this.getMenuItemForSelection(path);
505
505
 
506
- if (item.endpoint) {
507
- this.loadItems(item, false);
508
- } else {
509
- path.pop();
510
- this.refresh(path);
506
+ if (item) {
507
+ if (item.endpoint) {
508
+ this.loadItems(item, false);
509
+ } else {
510
+ path.pop();
511
+ this.refresh(path);
512
+ }
511
513
  }
512
514
  }
513
515
 
@@ -30,6 +30,9 @@ export class Store extends RapidElement {
30
30
  @property({ type: String, attribute: 'globals' })
31
31
  globalsEndpoint: string;
32
32
 
33
+ @property({ type: String, attribute: 'languages' })
34
+ languagesEndpoint: string;
35
+
33
36
  @property({ type: Object, attribute: false })
34
37
  private schema: CompletionSchema;
35
38
 
@@ -41,7 +44,7 @@ export class Store extends RapidElement {
41
44
 
42
45
  private fields: { [key: string]: ContactField } = {};
43
46
  private groups: { [uuid: string]: ContactGroup } = {};
44
-
47
+ private languages: any = {};
45
48
  private pinnedFields: ContactField[] = [];
46
49
 
47
50
  // http promise to monitor for completeness
@@ -91,6 +94,22 @@ export class Store extends RapidElement {
91
94
  );
92
95
  }
93
96
 
97
+ if (this.languagesEndpoint) {
98
+ fetches.push(
99
+ getAssets(this.languagesEndpoint).then((results: any[]) => {
100
+ // convert array of objects to lookup
101
+ this.languages = results.reduce(function (
102
+ languages: any,
103
+ result: any
104
+ ) {
105
+ languages[result.value] = result.name;
106
+ return languages;
107
+ },
108
+ {});
109
+ })
110
+ );
111
+ }
112
+
94
113
  if (this.groupsEndpoint) {
95
114
  fetches.push(
96
115
  getAssets(this.groupsEndpoint).then((groups: any[]) => {
@@ -132,6 +151,10 @@ export class Store extends RapidElement {
132
151
  return this.pinnedFields;
133
152
  }
134
153
 
154
+ public getLanguageName(iso: string) {
155
+ return this.languages[iso];
156
+ }
157
+
135
158
  public isDynamicGroup(uuid: string): boolean {
136
159
  const group = this.groups[uuid];
137
160
  if (group && group.query) {
@@ -31,12 +31,25 @@ export class Tab extends RapidElement {
31
31
  @property({ type: String })
32
32
  icon: string;
33
33
 
34
+ @property({ type: String })
35
+ selectionColor: string;
36
+
37
+ @property({ type: String })
38
+ selectionBackground: string;
39
+
34
40
  @property({ type: Boolean })
35
41
  selected = false;
36
42
 
43
+ @property({ type: Number })
44
+ count = 0;
45
+
46
+ public hasBadge() {
47
+ return this.count > 0;
48
+ }
49
+
37
50
  public render(): TemplateResult {
38
51
  return html`<slot
39
52
  class="${getClasses({ selected: this.selected })}"
40
- ></slot>`;
53
+ ></slot> `;
41
54
  }
42
55
  }