@nyaruka/temba-components 0.156.18 → 0.157.1
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.
- package/CHANGELOG.md +17 -0
- package/dist/temba-components.js +2119 -1617
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/display/Button.ts +102 -121
- package/src/display/Chat.ts +74 -9
- package/src/display/Dropdown.ts +11 -0
- package/src/display/Label.ts +154 -2
- package/src/display/LeafletMap.ts +4 -3
- package/src/display/Options.ts +71 -16
- package/src/display/TembaUser.ts +32 -8
- package/src/events/eventRenderers.ts +243 -95
- package/src/excellent/caret-utils.ts +0 -1
- package/src/flow/AutoTranslate.ts +2 -2
- package/src/flow/Editor.ts +4 -4
- package/src/flow/NodeEditor.ts +2 -2
- package/src/flow/NodeTypeSelector.ts +0 -5
- package/src/flow/RevisionsWindow.ts +1 -3
- package/src/flow/actions/set_contact_language.ts +5 -4
- package/src/flow/nodes/shared.ts +14 -0
- package/src/flow/nodes/split_by_llm_categorize.ts +28 -8
- package/src/flow/utils.ts +39 -60
- package/src/form/ArrayEditor.ts +9 -11
- package/src/form/Checkbox.ts +2 -2
- package/src/form/ColorPicker.ts +5 -3
- package/src/form/Compose.ts +1 -1
- package/src/form/FieldElement.ts +8 -8
- package/src/form/KeyValueEditor.ts +4 -4
- package/src/form/MessageEditor.ts +2 -3
- package/src/form/RangePicker.ts +17 -17
- package/src/form/TembaSlider.ts +10 -10
- package/src/form/TemplateEditor.ts +4 -4
- package/src/form/TextInput.ts +19 -1
- package/src/form/select/Omnibox.ts +21 -20
- package/src/form/select/Select.ts +382 -173
- package/src/form/select/WorkspaceSelect.ts +7 -1
- package/src/interfaces.ts +1 -0
- package/src/languages.ts +56 -0
- package/src/layout/Accordion.ts +2 -2
- package/src/layout/Dialog.ts +1 -3
- package/src/layout/Modax.ts +1 -1
- package/src/list/ContentMenu.ts +1 -2
- package/src/list/SortableList.ts +156 -0
- package/src/list/TembaMenu.ts +159 -113
- package/src/live/ContactBadges.ts +2 -1
- package/src/live/ContactChat.ts +62 -45
- package/src/live/ContactDetails.ts +3 -1
- package/src/live/ContactFieldEditor.ts +36 -31
- package/src/live/FieldManager.ts +4 -4
- package/src/store/AppState.ts +3 -21
- package/src/store/Store.ts +0 -29
- package/src/styles/designTokens.ts +158 -0
- package/src/styles/pillVariants.ts +147 -0
- package/static/css/temba-components.css +141 -36
- package/web-dev-server.config.mjs +0 -1
- package/web-test-runner.config.mjs +98 -1
package/src/display/Label.ts
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { LitElement, TemplateResult, html, css } from 'lit';
|
|
2
2
|
import { property } from 'lit/decorators.js';
|
|
3
|
+
import { msg } from '@lit/localize';
|
|
3
4
|
import { getClasses } from '../utils';
|
|
4
5
|
import { styleMap } from 'lit-html/directives/style-map.js';
|
|
6
|
+
import { designTokens } from '../styles/designTokens';
|
|
7
|
+
import {
|
|
8
|
+
pillVariants,
|
|
9
|
+
PILL_TYPES,
|
|
10
|
+
PILL_TYPE_ICONS
|
|
11
|
+
} from '../styles/pillVariants';
|
|
5
12
|
|
|
6
13
|
export default class Label extends LitElement {
|
|
7
14
|
static get styles() {
|
|
8
15
|
return css`
|
|
16
|
+
${designTokens}
|
|
17
|
+
|
|
9
18
|
:host {
|
|
10
19
|
display: inline-block;
|
|
20
|
+
/* Cap at parent width so a pill sitting in a constrained
|
|
21
|
+
container (e.g. a flow canvas node body) shrinks to fit
|
|
22
|
+
rather than overflowing. The slot/mask below have the
|
|
23
|
+
min-width:0 needed to let the ellipsis engage. */
|
|
24
|
+
max-width: 100%;
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
slot {
|
|
@@ -15,12 +29,19 @@ export default class Label extends LitElement {
|
|
|
15
29
|
overflow-x: hidden;
|
|
16
30
|
text-overflow: ellipsis;
|
|
17
31
|
display: block;
|
|
32
|
+
/* Without min-width:0 the slot — as a flex item inside .mask —
|
|
33
|
+
refuses to shrink below its content size, defeating the
|
|
34
|
+
overflow/ellipsis. */
|
|
35
|
+
min-width: 0;
|
|
18
36
|
}
|
|
19
37
|
|
|
20
38
|
.mask {
|
|
21
39
|
padding: 3px 8px;
|
|
22
40
|
border-radius: 12px;
|
|
23
41
|
display: flex;
|
|
42
|
+
/* Same reason as slot — let the mask shrink below its content
|
|
43
|
+
size so the inner slot can ellipsize. */
|
|
44
|
+
min-width: 0;
|
|
24
45
|
}
|
|
25
46
|
|
|
26
47
|
temba-icon {
|
|
@@ -43,6 +64,72 @@ export default class Label extends LitElement {
|
|
|
43
64
|
text-shadow: none;
|
|
44
65
|
}
|
|
45
66
|
|
|
67
|
+
/* DS pill mode — engaged when the consumer sets [type]. Overrides
|
|
68
|
+
the legacy chip chrome (shadow) and shape (12px radius) with
|
|
69
|
+
the design-system pill (flat, type-colored via .pill-{type},
|
|
70
|
+
1px border, 999px radius). Background/foreground/icon-color
|
|
71
|
+
are owned by pillVariants — we only set non-color chrome here
|
|
72
|
+
so we don't outrank the variant. */
|
|
73
|
+
.label[class*='pill-'] {
|
|
74
|
+
font-size: 11.5px;
|
|
75
|
+
font-weight: var(--w-regular);
|
|
76
|
+
/* border color is owned by .pill-{type} in pillVariants — only
|
|
77
|
+
set width/style here so we don't outrank the variant. */
|
|
78
|
+
border-width: 1px;
|
|
79
|
+
border-style: solid;
|
|
80
|
+
border-radius: 999px;
|
|
81
|
+
box-shadow: none;
|
|
82
|
+
}
|
|
83
|
+
.label[class*='pill-'] .mask {
|
|
84
|
+
padding: 0 7px;
|
|
85
|
+
height: 20px;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 4px;
|
|
88
|
+
border-radius: 999px;
|
|
89
|
+
}
|
|
90
|
+
/* Hover tint pulled from the pill's own foreground (which is the
|
|
91
|
+
dark variant shade), so flow stays bluish, group stays purplish,
|
|
92
|
+
etc. — no grey wash. */
|
|
93
|
+
.label[class*='pill-'].clickable .mask:hover {
|
|
94
|
+
background: color-mix(in oklab, currentColor 10%, transparent);
|
|
95
|
+
}
|
|
96
|
+
.label[class*='pill-'] temba-icon {
|
|
97
|
+
margin-right: 0;
|
|
98
|
+
padding-bottom: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Chip-style X button — matches the multi-select chip's
|
|
102
|
+
.remove-item. currentColor-tinted bg so it picks up the
|
|
103
|
+
pill variant's hue. Sits on the left, ahead of the icon. */
|
|
104
|
+
.label[class*='pill-'] .remove {
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
display: inline-flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: center;
|
|
109
|
+
width: 16px;
|
|
110
|
+
height: 16px;
|
|
111
|
+
padding: 0;
|
|
112
|
+
margin: 0;
|
|
113
|
+
border: 0;
|
|
114
|
+
border-radius: 999px;
|
|
115
|
+
background: color-mix(in oklab, currentColor 25%, transparent);
|
|
116
|
+
color: inherit;
|
|
117
|
+
opacity: 0.8;
|
|
118
|
+
--icon-color: currentColor;
|
|
119
|
+
}
|
|
120
|
+
.label[class*='pill-'] .remove:hover {
|
|
121
|
+
opacity: 1;
|
|
122
|
+
background: color-mix(in oklab, currentColor 45%, transparent);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* When a removable X is present, tighten the mask's left padding
|
|
126
|
+
so the X sits snug against the pill edge (matches the
|
|
127
|
+
multi-select chip's 4px left padding). Right padding stays so
|
|
128
|
+
the trailing icon/name keep their breathing room. */
|
|
129
|
+
.label[class*='pill-']:has(.remove) .mask {
|
|
130
|
+
padding-left: 4px;
|
|
131
|
+
}
|
|
132
|
+
|
|
46
133
|
.danger {
|
|
47
134
|
background: tomato;
|
|
48
135
|
color: #fff;
|
|
@@ -79,6 +166,11 @@ export default class Label extends LitElement {
|
|
|
79
166
|
.shadow {
|
|
80
167
|
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
|
|
81
168
|
}
|
|
169
|
+
|
|
170
|
+
/* DS pill variants come last so .pill-{type} wins on source
|
|
171
|
+
order against equal-specificity legacy rules above (.label,
|
|
172
|
+
.danger, .primary, etc.). */
|
|
173
|
+
${pillVariants}
|
|
82
174
|
`;
|
|
83
175
|
}
|
|
84
176
|
|
|
@@ -106,12 +198,47 @@ export default class Label extends LitElement {
|
|
|
106
198
|
@property({ type: String })
|
|
107
199
|
icon: string;
|
|
108
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Design-system pill variant — `flow`, `group`, `contact`, `field`,
|
|
203
|
+
* `keyword`, `label`, or `neutral`. When set, switches the chrome to
|
|
204
|
+
* a flat DS pill (rounded 999px, type-colored). Stays the legacy
|
|
205
|
+
* shadowed label when unset, so existing consumers are unaffected.
|
|
206
|
+
*/
|
|
207
|
+
@property({ type: String })
|
|
208
|
+
type: string;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Render a chip-style X button on the left of the pill. Clicking it
|
|
212
|
+
* fires a `temba-remove` event; the rest of the pill stays clickable
|
|
213
|
+
* for navigation as usual.
|
|
214
|
+
*/
|
|
215
|
+
@property({ type: Boolean })
|
|
216
|
+
removable: boolean;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Accessible label for the remove button. Defaults to a localized
|
|
220
|
+
* "Remove", but consumers whose action verb differs (e.g.
|
|
221
|
+
* "Interrupt flow") should pass their own — the X button is the
|
|
222
|
+
* affordance for whatever action `temba-remove` triggers, so the
|
|
223
|
+
* accessible name should match.
|
|
224
|
+
*/
|
|
225
|
+
@property({ type: String })
|
|
226
|
+
removeLabel: string;
|
|
227
|
+
|
|
109
228
|
@property()
|
|
110
229
|
backgroundColor: string;
|
|
111
230
|
|
|
112
231
|
@property()
|
|
113
232
|
textColor: string;
|
|
114
233
|
|
|
234
|
+
private handleRemove(e: MouseEvent) {
|
|
235
|
+
e.stopPropagation();
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
this.dispatchEvent(
|
|
238
|
+
new CustomEvent('temba-remove', { bubbles: true, composed: true })
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
115
242
|
public render(): TemplateResult {
|
|
116
243
|
const labelStyle = {};
|
|
117
244
|
|
|
@@ -124,6 +251,22 @@ export default class Label extends LitElement {
|
|
|
124
251
|
labelStyle['--icon-color'] = this.textColor;
|
|
125
252
|
}
|
|
126
253
|
|
|
254
|
+
// Only emit `pill-${this.type}` if it's a recognized variant.
|
|
255
|
+
// An unknown value (or one containing whitespace, e.g.
|
|
256
|
+
// `"flow danger"` from a malformed template) would otherwise split
|
|
257
|
+
// into multiple classes and collide with internal modifiers
|
|
258
|
+
// (`.danger`, `.shadow`, `.clickable`, etc.) defined on `.label`.
|
|
259
|
+
const validType = this.type && PILL_TYPES.has(this.type);
|
|
260
|
+
const variantClass = validType ? `pill-${this.type}` : '';
|
|
261
|
+
// When the consumer sets a recognized `type` (group / flow / etc.)
|
|
262
|
+
// but doesn't supply an explicit `icon`, fall back to the type's
|
|
263
|
+
// default icon from PILL_TYPE_ICONS. Call sites then only need
|
|
264
|
+
// `type="group"` instead of `type="group" icon="group"`.
|
|
265
|
+
const resolvedIcon =
|
|
266
|
+
this.icon || (validType ? PILL_TYPE_ICONS[this.type] : undefined);
|
|
267
|
+
|
|
268
|
+
const removeAriaLabel = this.removeLabel || msg('Remove');
|
|
269
|
+
|
|
127
270
|
return html`
|
|
128
271
|
<div
|
|
129
272
|
class="label ${getClasses({
|
|
@@ -134,11 +277,20 @@ export default class Label extends LitElement {
|
|
|
134
277
|
shadow: this.shadow,
|
|
135
278
|
danger: this.danger,
|
|
136
279
|
dark: this.dark
|
|
137
|
-
})}"
|
|
280
|
+
})} ${variantClass}"
|
|
138
281
|
style=${styleMap(labelStyle)}
|
|
139
282
|
>
|
|
140
283
|
<div class="mask">
|
|
141
|
-
${this.
|
|
284
|
+
${this.removable
|
|
285
|
+
? html`<button
|
|
286
|
+
class="remove"
|
|
287
|
+
@click=${this.handleRemove}
|
|
288
|
+
aria-label=${removeAriaLabel}
|
|
289
|
+
>
|
|
290
|
+
<temba-icon name="x" size="0.85"></temba-icon>
|
|
291
|
+
</button>`
|
|
292
|
+
: null}
|
|
293
|
+
${resolvedIcon ? html`<temba-icon name=${resolvedIcon} />` : null}
|
|
142
294
|
<slot></slot>
|
|
143
295
|
</div>
|
|
144
296
|
</div>
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Feature, Geometry } from 'geojson';
|
|
2
|
-
import {
|
|
2
|
+
import type {
|
|
3
3
|
GeoJSON,
|
|
4
|
-
geoJSON,
|
|
5
4
|
LeafletEvent,
|
|
6
5
|
LeafletMouseEvent,
|
|
7
6
|
Map as RenderedMap,
|
|
8
|
-
map as createMap,
|
|
9
7
|
Path
|
|
10
8
|
} from 'leaflet';
|
|
9
|
+
import * as L from 'leaflet';
|
|
10
|
+
|
|
11
|
+
const { geoJSON, map: createMap } = L;
|
|
11
12
|
import { css, html, LitElement } from 'lit';
|
|
12
13
|
import { property } from 'lit/decorators.js';
|
|
13
14
|
|
package/src/display/Options.ts
CHANGED
|
@@ -5,10 +5,13 @@ import { RapidElement, EventHandler } from '../RapidElement';
|
|
|
5
5
|
import { styleMap } from 'lit-html/directives/style-map.js';
|
|
6
6
|
import { getClasses, getScrollParent, throttle } from '../utils';
|
|
7
7
|
import { msg } from '@lit/localize';
|
|
8
|
+
import { designTokens } from '../styles/designTokens';
|
|
8
9
|
|
|
9
10
|
export class Options extends RapidElement {
|
|
10
11
|
static get styles() {
|
|
11
12
|
return css`
|
|
13
|
+
${designTokens}
|
|
14
|
+
|
|
12
15
|
:host {
|
|
13
16
|
--transition-speed: 0;
|
|
14
17
|
}
|
|
@@ -41,6 +44,10 @@ export class Options extends RapidElement {
|
|
|
41
44
|
flex-grow: 1;
|
|
42
45
|
height: 100%;
|
|
43
46
|
border: none;
|
|
47
|
+
/* Block-mode is inline (ticket sidebar, etc.) — drop the
|
|
48
|
+
z-index so floating popups (selects, dropdowns) opened over
|
|
49
|
+
the block list always stack above its options. */
|
|
50
|
+
z-index: auto;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
:host([block]) .options-scroll {
|
|
@@ -96,25 +103,75 @@ export class Options extends RapidElement {
|
|
|
96
103
|
border: none;
|
|
97
104
|
}
|
|
98
105
|
|
|
106
|
+
/* When shown, the popup is opaque and clickable. Keep the
|
|
107
|
+
high z-index from .options-container so the popup stacks
|
|
108
|
+
above neighboring widgets — e.g. embedded prefix labels of
|
|
109
|
+
later form fields, which sit absolutely positioned at the
|
|
110
|
+
top edge of their host element. */
|
|
99
111
|
.show {
|
|
100
|
-
border: 1px solid var(--color-widget-border);
|
|
101
112
|
opacity: 1;
|
|
102
|
-
z-index: 1;
|
|
103
113
|
pointer-events: auto;
|
|
104
114
|
margin-top: var(--options-margin-top);
|
|
105
115
|
}
|
|
106
116
|
|
|
117
|
+
/* Floating popup border uses --focus-muted (not --color-focus)
|
|
118
|
+
so a parent field's error state — which overrides
|
|
119
|
+
--color-focus to red via .has-error — doesn't turn the
|
|
120
|
+
popup red. The popup stays blue regardless. Single source
|
|
121
|
+
of truth: --focus in designTokens. No halo here — the
|
|
122
|
+
dropdown is an attached panel, not its own focus indicator
|
|
123
|
+
(the parent select keeps its own halo). Block-mode renders
|
|
124
|
+
inline and skips this rule entirely. */
|
|
125
|
+
:host(:not([block])) .show {
|
|
126
|
+
border: 1px solid var(--temba-options-focus-border, var(--focus-muted));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Each option is a DS-style list row: flat, fixed height,
|
|
130
|
+
tight padding, no inter-item margin. Full-bleed background on
|
|
131
|
+
hover/focus → no border-radius (rounded corners would leave
|
|
132
|
+
visible gaps at the dropdown edges). */
|
|
107
133
|
.option {
|
|
108
|
-
font-size: var(--temba-options-font-size);
|
|
109
|
-
padding: var(--temba-options-option-padding,
|
|
110
|
-
border-radius: var(--temba-options-option-radius,
|
|
111
|
-
margin: var(--temba-options-option-margin, 0
|
|
134
|
+
font-size: var(--temba-options-font-size, 13.5px);
|
|
135
|
+
padding: var(--temba-options-option-padding, 0 var(--pad));
|
|
136
|
+
border-radius: var(--temba-options-option-radius, 0);
|
|
137
|
+
margin: var(--temba-options-option-margin, 0);
|
|
138
|
+
min-height: var(--temba-options-option-min-height, 32px);
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
112
141
|
cursor: pointer;
|
|
113
|
-
color: var(--
|
|
142
|
+
color: var(--text-1);
|
|
114
143
|
scroll-margin: 5px 0px;
|
|
115
144
|
text-align: left;
|
|
116
145
|
}
|
|
117
146
|
|
|
147
|
+
/* A single wrapping renderOption child stretches to fill the row
|
|
148
|
+
so custom templates (e.g. ones using justify-content:
|
|
149
|
+
space-between to right-align trailing badges) get the full
|
|
150
|
+
width to lay out in. Scoped to :only-child so a renderOption
|
|
151
|
+
that emits multiple top-level siblings keeps its natural
|
|
152
|
+
layout instead of getting an equal-width flex partition. */
|
|
153
|
+
.option > :only-child {
|
|
154
|
+
flex: 1;
|
|
155
|
+
min-width: 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Block-mode (inline, always-visible lists like the ticket
|
|
159
|
+
sidebar): inset every option uniformly from the container
|
|
160
|
+
and from each other (padding + flex gap), and re-add the
|
|
161
|
+
radius so the focused/active wash reads as a rounded pill
|
|
162
|
+
instead of a stripe. Rich custom renderOption content
|
|
163
|
+
(multi-line ticket cards, etc.) also wants more vertical
|
|
164
|
+
padding than the dropdown's compact rows. */
|
|
165
|
+
:host([block]) .options-scroll {
|
|
166
|
+
padding: 4px;
|
|
167
|
+
gap: 4px;
|
|
168
|
+
}
|
|
169
|
+
:host([block]) .option {
|
|
170
|
+
margin: 0;
|
|
171
|
+
padding: 8px var(--pad);
|
|
172
|
+
border-radius: var(--r-sm);
|
|
173
|
+
}
|
|
174
|
+
|
|
118
175
|
.option * {
|
|
119
176
|
user-select: none;
|
|
120
177
|
-webkit-user-select: none;
|
|
@@ -169,20 +226,18 @@ export class Options extends RapidElement {
|
|
|
169
226
|
max-height: 1.1em;
|
|
170
227
|
}
|
|
171
228
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
color: var(--temba-options-option-hover-text, var(--option-hover-text));
|
|
178
|
-
}
|
|
179
|
-
|
|
229
|
+
/* The component syncs cursorIndex to pointer position on
|
|
230
|
+
mousemove, so .focused already follows the mouse. A
|
|
231
|
+
separate :hover rule would just create a second highlight
|
|
232
|
+
that flickers between rows on transition — keep only the
|
|
233
|
+
single focused/active state. */
|
|
180
234
|
.option.focused {
|
|
181
235
|
background: var(
|
|
182
236
|
--temba-options-option-focus-bg,
|
|
183
237
|
var(--color-selection)
|
|
184
238
|
);
|
|
185
|
-
color: var(--temba-options-option-focus-text, var(--
|
|
239
|
+
color: var(--temba-options-option-focus-text, var(--accent-700));
|
|
240
|
+
--icon-color: var(--accent-700);
|
|
186
241
|
}
|
|
187
242
|
|
|
188
243
|
.option.no-options {
|
package/src/display/TembaUser.ts
CHANGED
|
@@ -11,14 +11,16 @@ export const getFullName = (user: {
|
|
|
11
11
|
first_name?: string;
|
|
12
12
|
last_name?: string;
|
|
13
13
|
}) => {
|
|
14
|
-
|
|
14
|
+
if (user.first_name || user.last_name) {
|
|
15
|
+
return [user.first_name, user.last_name].filter(Boolean).join(' ');
|
|
16
|
+
}
|
|
17
|
+
return user.name || '';
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
export class TembaUser extends RapidElement {
|
|
18
21
|
public static styles = css`
|
|
19
22
|
:host {
|
|
20
23
|
display: flex;
|
|
21
|
-
transform: scale(var(--temba-scale, 1));
|
|
22
24
|
box-sizing: border-box;
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -29,6 +31,10 @@ export class TembaUser extends RapidElement {
|
|
|
29
31
|
flex-grow: 1;
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
.avatar-circle {
|
|
35
|
+
transform-origin: left center;
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
.name {
|
|
33
39
|
flex-grow: 1;
|
|
34
40
|
display: -webkit-box;
|
|
@@ -59,6 +65,12 @@ export class TembaUser extends RapidElement {
|
|
|
59
65
|
@property({ type: String })
|
|
60
66
|
name: string;
|
|
61
67
|
|
|
68
|
+
@property({ type: String })
|
|
69
|
+
first_name: string;
|
|
70
|
+
|
|
71
|
+
@property({ type: String })
|
|
72
|
+
last_name: string;
|
|
73
|
+
|
|
62
74
|
@property({ type: String })
|
|
63
75
|
email: string;
|
|
64
76
|
|
|
@@ -75,10 +87,19 @@ export class TembaUser extends RapidElement {
|
|
|
75
87
|
this.bgimage = `url('${DEFAULT_AVATAR}') center / contain no-repeat`;
|
|
76
88
|
}
|
|
77
89
|
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
if (
|
|
91
|
+
changed.has('name') ||
|
|
92
|
+
changed.has('first_name') ||
|
|
93
|
+
changed.has('last_name')
|
|
94
|
+
) {
|
|
95
|
+
const fullName = getFullName({
|
|
96
|
+
name: this.name,
|
|
97
|
+
first_name: this.first_name,
|
|
98
|
+
last_name: this.last_name
|
|
99
|
+
});
|
|
100
|
+
if (fullName) {
|
|
101
|
+
this.bgcolor = colorHash.hex(fullName);
|
|
102
|
+
this.initials = extractInitials(fullName);
|
|
82
103
|
} else {
|
|
83
104
|
this.bgcolor = '#e6e6e6';
|
|
84
105
|
this.initials = '';
|
|
@@ -97,7 +118,7 @@ export class TembaUser extends RapidElement {
|
|
|
97
118
|
<div
|
|
98
119
|
class="avatar-circle"
|
|
99
120
|
style="
|
|
100
|
-
transform:scale(${this.scale || 1});
|
|
121
|
+
transform:scale(calc(var(--temba-scale, 1) * ${this.scale || 1}));
|
|
101
122
|
display: flex;
|
|
102
123
|
min-height: 26px;
|
|
103
124
|
min-width: 26px;
|
|
@@ -108,7 +129,10 @@ export class TembaUser extends RapidElement {
|
|
|
108
129
|
font-weight: 400;
|
|
109
130
|
overflow: hidden;
|
|
110
131
|
font-size: 0.8em;
|
|
111
|
-
margin-right:
|
|
132
|
+
margin-right: max(
|
|
133
|
+
0px,
|
|
134
|
+
calc(0.75em - (1 - var(--temba-scale, 1)) * 26px)
|
|
135
|
+
);
|
|
112
136
|
box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.1);
|
|
113
137
|
background:${this.bgimage || this.bgcolor};"
|
|
114
138
|
>
|