@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/live/ContactChat.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
TemplateResult
|
|
8
8
|
} from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
+
import { msg } from '@lit/localize';
|
|
10
11
|
import {
|
|
11
12
|
Contact,
|
|
12
13
|
CustomEventType,
|
|
@@ -313,45 +314,51 @@ export class ContactChat extends ContactStoreElement {
|
|
|
313
314
|
border-color: #ccc;
|
|
314
315
|
}
|
|
315
316
|
|
|
316
|
-
.
|
|
317
|
-
|
|
318
|
-
|
|
317
|
+
/* "Currently in [flow]" treatment.
|
|
318
|
+
Lives in the chat footer to advertise the active run with an
|
|
319
|
+
optional Interrupt action (the chip's X). Sized to its
|
|
320
|
+
contents only (inline-flex) so the chat scrollbar to the
|
|
321
|
+
right remains clickable, and pointer-events:none on the
|
|
322
|
+
wrapping footer means the rest of the row doesn't intercept
|
|
323
|
+
scrollbar drags either. Translucent white bg + backdrop
|
|
324
|
+
blur keeps the chat history legible through the chip. */
|
|
319
325
|
.in-flow {
|
|
320
|
-
border-radius: 0.8em;
|
|
321
|
-
align-items: center;
|
|
322
|
-
background: #666;
|
|
323
|
-
padding: 0.5em 1em;
|
|
324
|
-
margin: 1em;
|
|
325
|
-
margin-right: 2em;
|
|
326
326
|
display: inline-flex;
|
|
327
|
-
|
|
327
|
+
align-items: center;
|
|
328
|
+
gap: 8px;
|
|
329
|
+
padding: 0.4em 0.75em;
|
|
330
|
+
margin: 0.5em;
|
|
331
|
+
border-radius: 999px;
|
|
332
|
+
background: rgba(255, 255, 255, 0.75);
|
|
333
|
+
backdrop-filter: blur(8px);
|
|
334
|
+
-webkit-backdrop-filter: blur(8px);
|
|
335
|
+
box-shadow: var(--shadow-1);
|
|
328
336
|
}
|
|
329
337
|
|
|
330
338
|
.flow-footer {
|
|
331
339
|
text-align: center;
|
|
332
340
|
pointer-events: none;
|
|
341
|
+
/* The chat history has a scrollbar on the right edge; the
|
|
342
|
+
footer overlay spans the full container width, so centering
|
|
343
|
+
inside it lands the chip slightly right-of-center relative
|
|
344
|
+
to the visible message area. Reserve the scrollbar width on
|
|
345
|
+
the right so the chip is centered to what the user sees. */
|
|
346
|
+
padding-right: 15px;
|
|
333
347
|
}
|
|
334
348
|
|
|
335
349
|
.flow-footer .in-flow {
|
|
336
350
|
pointer-events: auto;
|
|
337
351
|
}
|
|
338
352
|
|
|
339
|
-
.in-flow:hover {
|
|
340
|
-
opacity: 1;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
353
|
.in-flow .flow-name {
|
|
344
|
-
display: flex;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
.in-flow .interrupt-button {
|
|
354
|
-
margin-left: 1em;
|
|
354
|
+
display: inline-flex;
|
|
355
|
+
align-items: center;
|
|
356
|
+
gap: 6px;
|
|
357
|
+
/* Match the chat history event text — same hue + size — so
|
|
358
|
+
the "Currently in" line reads as one of the events rather
|
|
359
|
+
than its own UI chrome. */
|
|
360
|
+
font-size: 13.5px;
|
|
361
|
+
color: #8e8e93;
|
|
355
362
|
}
|
|
356
363
|
|
|
357
364
|
.in-flow .interrupt {
|
|
@@ -389,6 +396,18 @@ export class ContactChat extends ContactStoreElement {
|
|
|
389
396
|
background: rgba(0, 0, 0, 0.03);
|
|
390
397
|
}
|
|
391
398
|
|
|
399
|
+
/* Keep the assignment + topic controls the same height as the
|
|
400
|
+
Close button so the row reads as one strip. Shrink the user
|
|
401
|
+
avatars (--temba-scale) so they fit in the smaller box. */
|
|
402
|
+
.in-ticket temba-user-select,
|
|
403
|
+
.in-ticket temba-select {
|
|
404
|
+
--temba-select-min-height: 28px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.in-ticket temba-user-select {
|
|
408
|
+
--temba-scale: 0.75;
|
|
409
|
+
}
|
|
410
|
+
|
|
392
411
|
temba-user {
|
|
393
412
|
border: 1px solid #ddd;
|
|
394
413
|
padding: 0.2em 0.5em;
|
|
@@ -1003,8 +1022,11 @@ export class ContactChat extends ContactStoreElement {
|
|
|
1003
1022
|
if (
|
|
1004
1023
|
event.type === 'msg_created' ||
|
|
1005
1024
|
event.type === 'msg_received' ||
|
|
1006
|
-
event.type === 'ivr_created'
|
|
1025
|
+
event.type === 'ivr_created' ||
|
|
1026
|
+
event.type === 'ticket_note_added'
|
|
1007
1027
|
) {
|
|
1028
|
+
// Notes render as chat-style bubbles (see Chat.ts), so push them
|
|
1029
|
+
// through directly rather than prerendering into an inline event.
|
|
1008
1030
|
messages.push(event);
|
|
1009
1031
|
} else {
|
|
1010
1032
|
this.prerender(event);
|
|
@@ -1420,26 +1442,21 @@ export class ContactChat extends ContactStoreElement {
|
|
|
1420
1442
|
<div slot="footer" class="flow-footer">
|
|
1421
1443
|
<div class="in-flow">
|
|
1422
1444
|
<div class="flow-name">
|
|
1423
|
-
<
|
|
1424
|
-
<
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1445
|
+
<span>Currently in</span>
|
|
1446
|
+
<a
|
|
1447
|
+
href="/flow/editor/${this.currentContact.flow
|
|
1448
|
+
.uuid}/"
|
|
1449
|
+
onclick="goto(event, this)"
|
|
1450
|
+
><temba-label
|
|
1451
|
+
type="flow"
|
|
1452
|
+
clickable
|
|
1453
|
+
?removable=${this.showInterrupt}
|
|
1454
|
+
removeLabel=${msg('Interrupt flow')}
|
|
1455
|
+
@temba-remove=${this.handleInterrupt}
|
|
1456
|
+
>${this.currentContact.flow.name}</temba-label
|
|
1457
|
+
></a
|
|
1458
|
+
>
|
|
1432
1459
|
</div>
|
|
1433
|
-
${this.showInterrupt
|
|
1434
|
-
? html`<temba-button
|
|
1435
|
-
class="interrupt-button"
|
|
1436
|
-
destructive
|
|
1437
|
-
small
|
|
1438
|
-
@click=${this.handleInterrupt}
|
|
1439
|
-
name="Interrupt"
|
|
1440
|
-
>
|
|
1441
|
-
</temba-button>`
|
|
1442
|
-
: null}
|
|
1443
1460
|
</div>
|
|
1444
1461
|
</div>
|
|
1445
1462
|
`
|
|
@@ -2,6 +2,7 @@ import { css, html, TemplateResult } from 'lit';
|
|
|
2
2
|
import { ContactStoreElement } from './ContactStoreElement';
|
|
3
3
|
import { Icon } from '../Icons';
|
|
4
4
|
import { capitalize } from '../utils';
|
|
5
|
+
import { getLanguageName } from '../languages';
|
|
5
6
|
|
|
6
7
|
const STATUS = {
|
|
7
8
|
active: 'Active',
|
|
@@ -62,7 +63,7 @@ export class ContactDetails extends ContactStoreElement {
|
|
|
62
63
|
return;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
const lang =
|
|
66
|
+
const lang = getLanguageName(this.data.language);
|
|
66
67
|
|
|
67
68
|
return html`
|
|
68
69
|
<div class="wrapper">
|
|
@@ -75,6 +76,7 @@ export class ContactDetails extends ContactStoreElement {
|
|
|
75
76
|
onclick="goto(event)"
|
|
76
77
|
href="/contact/group/${group.uuid}/"
|
|
77
78
|
icon=${group.is_dynamic ? Icon.group_smart : Icon.group}
|
|
79
|
+
type="group"
|
|
78
80
|
clickable
|
|
79
81
|
>
|
|
80
82
|
${group.name}
|
|
@@ -101,16 +101,20 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
101
101
|
overflow: hidden;
|
|
102
102
|
text-overflow: ellipsis;
|
|
103
103
|
display: flex;
|
|
104
|
+
/* Pin to the top-left of the host (temba-select :host is
|
|
105
|
+
position: relative). Using top rather than margin-top keeps
|
|
106
|
+
the absolute element out of the flex flow of .left-side so
|
|
107
|
+
it doesn't push the selected value down. */
|
|
104
108
|
position: absolute;
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
top: -0.6em;
|
|
110
|
+
left: 0.5em;
|
|
107
111
|
pointer-events: none;
|
|
108
112
|
background: #fff;
|
|
109
113
|
border-radius: var(--curvature);
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
temba-select .prefix {
|
|
113
|
-
|
|
117
|
+
top: -0.7em;
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
.wrapper {
|
|
@@ -191,19 +195,22 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
191
195
|
display: none;
|
|
192
196
|
}
|
|
193
197
|
|
|
198
|
+
/* Keep popper icons within the widget's 34px content area so
|
|
199
|
+
the field height doesn't change when the search/copy buttons
|
|
200
|
+
appear (i.e. when a value is set). Horizontal padding only —
|
|
201
|
+
vertical sizing comes from align-items: stretch on the
|
|
202
|
+
flex .input-container. Inner elements (icons, save button)
|
|
203
|
+
own their own horizontal spacing via margin, so the popper
|
|
204
|
+
itself doesn't add asymmetric padding (which would offset
|
|
205
|
+
its contents and prevent centering). */
|
|
194
206
|
.popper temba-icon {
|
|
195
|
-
padding: 0.
|
|
196
|
-
padding-right: 1em;
|
|
207
|
+
padding: 0 0.6em 0 0;
|
|
197
208
|
}
|
|
198
209
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
padding-left:
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.popper:last-child {
|
|
206
|
-
padding-right: 0em;
|
|
210
|
+
/* First icon gets extra left padding so it doesn't hug the
|
|
211
|
+
popper's left edge — visually balances the inter-icon gap. */
|
|
212
|
+
.popper temba-icon:first-of-type {
|
|
213
|
+
padding-left: 0.6em;
|
|
207
214
|
}
|
|
208
215
|
|
|
209
216
|
.copy.clicked temba-icon {
|
|
@@ -223,8 +230,19 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
223
230
|
align-items: center;
|
|
224
231
|
}
|
|
225
232
|
|
|
226
|
-
.save-button
|
|
227
|
-
|
|
233
|
+
/* .save-button is the class on the <temba-button> element
|
|
234
|
+
itself. Use tag+class selector and !important to outrank
|
|
235
|
+
:host { align-self: stretch } from inside Button.ts. min/max
|
|
236
|
+
height pin the button size so it can't grow with the parent
|
|
237
|
+
line height (relevant in DatePicker, where the container
|
|
238
|
+
wraps and the line is ~50px tall). */
|
|
239
|
+
temba-button.save-button {
|
|
240
|
+
align-self: center !important;
|
|
241
|
+
height: 22px !important;
|
|
242
|
+
min-height: 22px !important;
|
|
243
|
+
max-height: 22px !important;
|
|
244
|
+
margin: 5px 6px;
|
|
245
|
+
font-size: 12px;
|
|
228
246
|
}
|
|
229
247
|
|
|
230
248
|
.dirty .copy,
|
|
@@ -272,22 +290,6 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
272
290
|
padding: 0;
|
|
273
291
|
}
|
|
274
292
|
|
|
275
|
-
.dirty temba-datepicker .popper:first-child {
|
|
276
|
-
padding-left: 1em;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
.success temba-datepicker .popper:first-child {
|
|
280
|
-
padding-left: 1em;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
.failure temba-datepicker .popper:first-child {
|
|
284
|
-
padding-left: 1em;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.saving temba-datepicker .popper:first-child {
|
|
288
|
-
padding-left: 1em;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
293
|
temba-datepicker .postfix {
|
|
292
294
|
margin-left: 0;
|
|
293
295
|
}
|
|
@@ -305,6 +307,9 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
305
307
|
|
|
306
308
|
temba-select {
|
|
307
309
|
--color-widget-bg: white;
|
|
310
|
+
/* Let the slotted prefix label escape the widget's top edge
|
|
311
|
+
— same notched-border look as the textinput / datepicker. */
|
|
312
|
+
--temba-select-container-overflow: visible;
|
|
308
313
|
}
|
|
309
314
|
|
|
310
315
|
temba-option {
|
package/src/live/FieldManager.ts
CHANGED
|
@@ -247,14 +247,14 @@ export class FieldManager extends EndpointMonitorElement {
|
|
|
247
247
|
<div
|
|
248
248
|
style="display: flex; min-width: 200px; width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 2em"
|
|
249
249
|
>
|
|
250
|
-
<
|
|
250
|
+
<temba-label
|
|
251
|
+
type="field"
|
|
252
|
+
clickable
|
|
251
253
|
@click=${this.handleFieldAction}
|
|
252
254
|
data-key=${field.key}
|
|
253
255
|
data-action="update"
|
|
254
|
-
|
|
256
|
+
>${field.label}</temba-label
|
|
255
257
|
>
|
|
256
|
-
${field.label}
|
|
257
|
-
</span>
|
|
258
258
|
${this.hasUsages(field)
|
|
259
259
|
? html`
|
|
260
260
|
<temba-icon
|
package/src/store/AppState.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createStore, StoreApi } from 'zustand/vanilla';
|
|
2
|
-
import {
|
|
2
|
+
import { generateUUID } from '../utils';
|
|
3
|
+
import { getLanguageName } from '../languages';
|
|
3
4
|
import {
|
|
4
5
|
Action,
|
|
5
6
|
Exit,
|
|
@@ -193,7 +194,6 @@ export interface AppState {
|
|
|
193
194
|
issuesByAction: Map<string, FlowIssue[]>;
|
|
194
195
|
|
|
195
196
|
languageCode: string;
|
|
196
|
-
languageNames: { [code: string]: string };
|
|
197
197
|
workspace: Workspace;
|
|
198
198
|
isTranslating: boolean;
|
|
199
199
|
viewingRevision: boolean;
|
|
@@ -209,7 +209,6 @@ export interface AppState {
|
|
|
209
209
|
getCurrentActivity: () => Activity | null;
|
|
210
210
|
fetchRevision: (endpoint: string, id?: string) => Promise<void>;
|
|
211
211
|
fetchWorkspace: (endpoint: string) => Promise<void>;
|
|
212
|
-
fetchAllLanguages: (endpoint: string) => Promise<void>;
|
|
213
212
|
fetchActivity: (endpoint: string) => Promise<void>;
|
|
214
213
|
setActivityEndpoint: (endpoint: string) => void;
|
|
215
214
|
updateActivity: (activity: Activity) => void;
|
|
@@ -267,7 +266,6 @@ export const zustand = createStore<AppState>()(
|
|
|
267
266
|
immer((set, get) => ({
|
|
268
267
|
features: [] as string[],
|
|
269
268
|
brand: '',
|
|
270
|
-
languageNames: {},
|
|
271
269
|
canvasSize: { width: 0, height: 0 },
|
|
272
270
|
languageCode: '',
|
|
273
271
|
workspace: null,
|
|
@@ -331,21 +329,6 @@ export const zustand = createStore<AppState>()(
|
|
|
331
329
|
set({ workspace: data });
|
|
332
330
|
},
|
|
333
331
|
|
|
334
|
-
fetchAllLanguages: async (endpoint) => {
|
|
335
|
-
const results = await fetchResults(endpoint);
|
|
336
|
-
|
|
337
|
-
// convert array to map for easier lookup
|
|
338
|
-
const allLanguages = results.reduce(function (
|
|
339
|
-
languages: any,
|
|
340
|
-
result: any
|
|
341
|
-
) {
|
|
342
|
-
languages[result.value] = result.name;
|
|
343
|
-
return languages;
|
|
344
|
-
}, {});
|
|
345
|
-
|
|
346
|
-
set({ languageNames: allLanguages });
|
|
347
|
-
},
|
|
348
|
-
|
|
349
332
|
setActivityEndpoint: (endpoint: string) => {
|
|
350
333
|
set({ activityEndpoint: endpoint });
|
|
351
334
|
},
|
|
@@ -394,8 +377,7 @@ export const zustand = createStore<AppState>()(
|
|
|
394
377
|
getLanguage: () => {
|
|
395
378
|
const state = get();
|
|
396
379
|
const languageCode = state.languageCode;
|
|
397
|
-
|
|
398
|
-
return { name: languageNames[languageCode], code: languageCode };
|
|
380
|
+
return { name: getLanguageName(languageCode), code: languageCode };
|
|
399
381
|
},
|
|
400
382
|
|
|
401
383
|
setFeatures: (features: string[]) => {
|
package/src/store/Store.ts
CHANGED
|
@@ -30,7 +30,6 @@ import { sourceLocale, targetLocales } from '../locales/locale-codes';
|
|
|
30
30
|
import { getFullName } from '../display/TembaUser';
|
|
31
31
|
import { AppState, zustand } from './AppState';
|
|
32
32
|
import { StoreApi } from 'zustand/vanilla';
|
|
33
|
-
import { getLanguageDisplayName } from '../flow/utils';
|
|
34
33
|
|
|
35
34
|
const { setLocale } = configureLocalization({
|
|
36
35
|
sourceLocale,
|
|
@@ -84,9 +83,6 @@ export class Store extends RapidElement {
|
|
|
84
83
|
@property({ type: String, attribute: 'globals' })
|
|
85
84
|
globalsEndpoint: string;
|
|
86
85
|
|
|
87
|
-
@property({ type: String, attribute: 'languages' })
|
|
88
|
-
languagesEndpoint: string;
|
|
89
|
-
|
|
90
86
|
@property({ type: String, attribute: 'workspace' })
|
|
91
87
|
workspaceEndpoint: string;
|
|
92
88
|
|
|
@@ -110,7 +106,6 @@ export class Store extends RapidElement {
|
|
|
110
106
|
private fields: { [key: string]: ContactField } = {};
|
|
111
107
|
private groups: { [uuid: string]: ContactGroup } = {};
|
|
112
108
|
private shortcuts: Shortcut[] = [];
|
|
113
|
-
private languages: any = {};
|
|
114
109
|
private workspace: Workspace;
|
|
115
110
|
private featuredFields: ContactField[] = [];
|
|
116
111
|
|
|
@@ -196,22 +191,6 @@ export class Store extends RapidElement {
|
|
|
196
191
|
);
|
|
197
192
|
}
|
|
198
193
|
|
|
199
|
-
if (this.languagesEndpoint) {
|
|
200
|
-
appState.fetchAllLanguages(this.languagesEndpoint);
|
|
201
|
-
fetches.push(
|
|
202
|
-
getAssets(this.languagesEndpoint).then((results: any[]) => {
|
|
203
|
-
// convert array of objects to lookup
|
|
204
|
-
this.languages = results.reduce(function (
|
|
205
|
-
languages: any,
|
|
206
|
-
result: any
|
|
207
|
-
) {
|
|
208
|
-
languages[result.value] = result.name;
|
|
209
|
-
return languages;
|
|
210
|
-
}, {});
|
|
211
|
-
})
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
194
|
if (this.groupsEndpoint) {
|
|
216
195
|
fetches.push(
|
|
217
196
|
getAssets(this.groupsEndpoint).then((groups: any[]) => {
|
|
@@ -383,14 +362,6 @@ export class Store extends RapidElement {
|
|
|
383
362
|
return this.featuredFields;
|
|
384
363
|
}
|
|
385
364
|
|
|
386
|
-
public getLanguageName(iso: string) {
|
|
387
|
-
const name = this.languages[iso];
|
|
388
|
-
if (!name || name === 'und' || iso === 'und') {
|
|
389
|
-
return getLanguageDisplayName(iso);
|
|
390
|
-
}
|
|
391
|
-
return name;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
365
|
public isDynamicGroup(uuid: string): boolean {
|
|
395
366
|
const group = this.groups[uuid];
|
|
396
367
|
// we treat missing groups as dynamic since the
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TextIt Design System tokens — single source of truth.
|
|
5
|
+
*
|
|
6
|
+
* Embedded in component shadow DOMs (via FieldElement :host) so the
|
|
7
|
+
* tokens apply regardless of host-page stylesheet. Host pages may still
|
|
8
|
+
* override these by re-declaring the variable on the component element
|
|
9
|
+
* itself (e.g. `temba-select { --accent: ... }`), which beats `:host`.
|
|
10
|
+
*
|
|
11
|
+
* Mirrored in static/css/temba-components.css for hosts that want the
|
|
12
|
+
* tokens at :root scope; keep both in sync if the design system evolves.
|
|
13
|
+
*/
|
|
14
|
+
export const designTokens = css`
|
|
15
|
+
:host {
|
|
16
|
+
/* accent ramp — the primary color sits at 400 and the ramp is
|
|
17
|
+
derived from it in both directions via sRGB mixing.
|
|
18
|
+
The anchor reads from --primary-rgb so host pages can re-theme
|
|
19
|
+
the entire ramp by setting e.g. --primary-rgb: 112, 0, 132. */
|
|
20
|
+
--accent: rgb(var(--primary-rgb, 98, 147, 201));
|
|
21
|
+
--accent-50: color-mix(in srgb, var(--accent) 6%, white);
|
|
22
|
+
--accent-100: color-mix(in srgb, var(--accent) 16%, white);
|
|
23
|
+
--accent-200: color-mix(in srgb, var(--accent) 32%, white);
|
|
24
|
+
--accent-300: color-mix(in srgb, var(--accent) 60%, white);
|
|
25
|
+
--accent-400: var(--accent);
|
|
26
|
+
--accent-500: color-mix(in srgb, var(--accent) 90%, black);
|
|
27
|
+
--accent-600: color-mix(in srgb, var(--accent) 80%, black);
|
|
28
|
+
--accent-700: color-mix(in srgb, var(--accent) 65%, black);
|
|
29
|
+
--accent-800: color-mix(in srgb, var(--accent) 50%, black);
|
|
30
|
+
--accent-900: color-mix(in srgb, var(--accent) 35%, black);
|
|
31
|
+
|
|
32
|
+
/* neutrals */
|
|
33
|
+
--bg: #f6f7f9;
|
|
34
|
+
--surface: #ffffff;
|
|
35
|
+
--sunken: #f1f3f5;
|
|
36
|
+
--border: #e6e8ec;
|
|
37
|
+
--border-strong: #d2d6dc;
|
|
38
|
+
--text-1: #1a1f26;
|
|
39
|
+
--text-2: #4d5664;
|
|
40
|
+
--text-3: #7b8593;
|
|
41
|
+
--text-4: #a2abb8;
|
|
42
|
+
|
|
43
|
+
/* status — full set */
|
|
44
|
+
--success: #16a34a;
|
|
45
|
+
--success-bg: #e8f6ee;
|
|
46
|
+
--success-border: #bfe5cd;
|
|
47
|
+
--info: #2563eb;
|
|
48
|
+
--info-bg: #e8f0fe;
|
|
49
|
+
--info-border: #c7d7f8;
|
|
50
|
+
--warning: #b45309;
|
|
51
|
+
--warning-bg: #fdf3e2;
|
|
52
|
+
--warning-border: #f2d9a9;
|
|
53
|
+
--danger: #d03f3f;
|
|
54
|
+
--danger-bg: #fcebeb;
|
|
55
|
+
--danger-border: #f4c8c8;
|
|
56
|
+
--neutral: #6b7280;
|
|
57
|
+
--neutral-bg: #eef0f3;
|
|
58
|
+
--neutral-border: #d8dce2;
|
|
59
|
+
|
|
60
|
+
/* Pill anchor hues — pillVariants derives bg/fg/border via
|
|
61
|
+
color-mix(in srgb, ...) so host pages can re-theme by
|
|
62
|
+
overriding just the anchor. These are intentionally fixed and
|
|
63
|
+
do NOT track --primary-rgb so that pill identity (group/flow/
|
|
64
|
+
field/channel) stays stable across brand themes. */
|
|
65
|
+
--recipient: #2a6fb5;
|
|
66
|
+
--flow: #16a34a;
|
|
67
|
+
--channel: #6b21a8;
|
|
68
|
+
--topic: #d97706;
|
|
69
|
+
/* Field stays slightly darker than the bright yellow-500 anchor
|
|
70
|
+
used for flow/channel — yellow-500 has too little contrast
|
|
71
|
+
against white to read as a foreground / icon hue on its own.
|
|
72
|
+
Yellow-700 doubles as the pill's icon color via .pill-field. */
|
|
73
|
+
--field: #a16207;
|
|
74
|
+
|
|
75
|
+
/* type */
|
|
76
|
+
--font: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
77
|
+
--font-mono:
|
|
78
|
+
'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
79
|
+
--w-regular: 400;
|
|
80
|
+
--w-medium: 500;
|
|
81
|
+
--w-semibold: 600;
|
|
82
|
+
--w-bold: 600;
|
|
83
|
+
|
|
84
|
+
/* shape */
|
|
85
|
+
--r: 8px;
|
|
86
|
+
--r-xs: 2px;
|
|
87
|
+
--r-sm: 4px;
|
|
88
|
+
--r-lg: 12px;
|
|
89
|
+
|
|
90
|
+
/* density */
|
|
91
|
+
--row-h: 36px;
|
|
92
|
+
--input-h: 34px;
|
|
93
|
+
--pad: 10px;
|
|
94
|
+
--gap: 14px;
|
|
95
|
+
|
|
96
|
+
/* shadows */
|
|
97
|
+
--shadow-1:
|
|
98
|
+
0 1px 1px rgba(15, 22, 36, 0.04), 0 1px 2px rgba(15, 22, 36, 0.04);
|
|
99
|
+
--shadow-2:
|
|
100
|
+
0 1px 1px rgba(15, 22, 36, 0.04), 0 4px 12px rgba(15, 22, 36, 0.06);
|
|
101
|
+
--shadow-3:
|
|
102
|
+
0 6px 20px rgba(15, 22, 36, 0.1), 0 2px 6px rgba(15, 22, 36, 0.06);
|
|
103
|
+
|
|
104
|
+
/* legacy aliases — point at the DS tokens above so existing
|
|
105
|
+
components pick up the new design language without code changes */
|
|
106
|
+
--font-family: var(--font);
|
|
107
|
+
--curvature: var(--r-sm);
|
|
108
|
+
--curvature-widget: var(--r-sm);
|
|
109
|
+
/* Focus styling.
|
|
110
|
+
--focus is the single hue, kept separate from --accent so
|
|
111
|
+
changing the focus color doesn't shift chip / recipient hues.
|
|
112
|
+
--focus-muted and --focus-halo are derived once here, so
|
|
113
|
+
everywhere that needs to draw a focus outline or ring can
|
|
114
|
+
consume the same values without re-doing the formula.
|
|
115
|
+
--color-focus / --widget-box-shadow-focused alias the muted
|
|
116
|
+
versions and are what most widgets reference — these get
|
|
117
|
+
overridden to error-red by FieldElement's .has-error rule.
|
|
118
|
+
Surfaces that should stay blue even during a parent field's
|
|
119
|
+
error state (e.g. the dropdown popup) reference --focus-muted
|
|
120
|
+
/ --focus-halo directly to skip that override. */
|
|
121
|
+
--focus: rgb(var(--focus-rgb, 91, 156, 229));
|
|
122
|
+
--focus-50: color-mix(in srgb, var(--focus) 12%, white);
|
|
123
|
+
--focus-100: color-mix(in srgb, var(--focus) 24%, white);
|
|
124
|
+
--focus-200: color-mix(in srgb, var(--focus) 40%, white);
|
|
125
|
+
--focus-300: color-mix(in srgb, var(--focus) 60%, white);
|
|
126
|
+
--focus-600: color-mix(in srgb, var(--focus) 60%, black);
|
|
127
|
+
--focus-700: color-mix(in srgb, var(--focus) 45%, black);
|
|
128
|
+
--focus-muted: color-mix(in srgb, var(--focus) 60%, white);
|
|
129
|
+
--focus-halo: 0 0 0 3px color-mix(in srgb, var(--focus) 30%, transparent);
|
|
130
|
+
--color-focus: var(--focus-muted);
|
|
131
|
+
--widget-box-shadow-focused: var(--focus-halo);
|
|
132
|
+
|
|
133
|
+
--color-widget-bg: var(--surface);
|
|
134
|
+
--color-widget-bg-focused: var(--surface);
|
|
135
|
+
--color-widget-border: var(--border-strong);
|
|
136
|
+
--color-options-bg: var(--surface);
|
|
137
|
+
--color-selection: var(--accent-50);
|
|
138
|
+
--color-success: var(--success);
|
|
139
|
+
--widget-box-shadow: none;
|
|
140
|
+
--shadow: var(--shadow-1);
|
|
141
|
+
--shadow-widget: var(--shadow-1);
|
|
142
|
+
--color-text: var(--text-1);
|
|
143
|
+
--color-widget-text: var(--text-1);
|
|
144
|
+
--color-borders: var(--border);
|
|
145
|
+
--color-placeholder: var(--text-3);
|
|
146
|
+
--color-primary-light: var(--sunken);
|
|
147
|
+
--color-label: var(--text-1);
|
|
148
|
+
--color-text-help: var(--text-3);
|
|
149
|
+
|
|
150
|
+
--temba-textinput-padding: 7px var(--pad);
|
|
151
|
+
--temba-textinput-font-size: 13.5px;
|
|
152
|
+
--temba-textinput-min-height: var(--input-h);
|
|
153
|
+
--temba-select-selected-padding: 0 var(--pad);
|
|
154
|
+
--temba-select-selected-line-height: 1.4;
|
|
155
|
+
--temba-select-selected-font-size: 13.5px;
|
|
156
|
+
--temba-select-min-height: var(--input-h);
|
|
157
|
+
}
|
|
158
|
+
`;
|