@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.
- package/CHANGELOG.md +16 -0
- package/dist/temba-components.js +1617 -1590
- package/dist/temba-components.js.map +1 -1
- package/orca/setup.sh +81 -0
- package/orca.yaml +3 -0
- package/package.json +1 -1
- package/src/display/Button.ts +102 -121
- package/src/display/Chat.ts +60 -9
- package/src/display/Dropdown.ts +11 -0
- package/src/display/Label.ts +1 -3
- package/src/display/LeafletMap.ts +4 -3
- package/src/display/TembaUser.ts +9 -3
- package/src/events/eventRenderers.ts +151 -71
- package/src/flow/AutoTranslate.ts +2 -2
- package/src/flow/CanvasNode.ts +14 -6
- package/src/flow/DragManager.ts +4 -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/actions/set_contact_language.ts +5 -4
- package/src/flow/nodes/split_by_llm_categorize.ts +1 -6
- package/src/flow/utils.ts +2 -20
- package/src/form/ColorPicker.ts +5 -3
- package/src/form/DatePicker.ts +2 -1
- package/src/form/select/Omnibox.ts +1 -3
- package/src/form/select/Select.ts +5 -4
- package/src/interfaces.ts +1 -0
- package/src/languages.ts +56 -0
- package/src/layout/Dialog.ts +1 -3
- package/src/layout/Tab.ts +0 -15
- package/src/layout/TabPane.ts +73 -163
- package/src/list/ContentMenu.ts +1 -2
- package/src/list/SortableList.ts +1 -4
- package/src/list/TembaMenu.ts +159 -113
- package/src/live/ContactBadges.ts +2 -1
- package/src/live/ContactChat.ts +22 -3
- package/src/live/ContactDetails.ts +42 -36
- package/src/live/ContactFieldEditor.ts +35 -57
- package/src/live/ContactFields.ts +1 -2
- package/src/live/ContactNotepad.ts +9 -1
- package/src/live/ContactPending.ts +1 -0
- package/src/store/AppState.ts +3 -21
- package/src/store/Store.ts +0 -29
- package/src/styles/designTokens.ts +33 -18
- package/src/styles/pillVariants.ts +24 -13
- package/static/css/temba-components.css +84 -55
- package/web-dev-server.config.mjs +0 -1
- package/web-test-runner.config.mjs +0 -1
|
@@ -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',
|
|
@@ -20,36 +21,39 @@ const SCHEMES = {
|
|
|
20
21
|
export class ContactDetails extends ContactStoreElement {
|
|
21
22
|
static get styles() {
|
|
22
23
|
return css`
|
|
23
|
-
.
|
|
24
|
-
|
|
25
|
-
padding: 0.4em 1em 0.8em 1em;
|
|
26
|
-
border-bottom: 1px solid #e6e6e6;
|
|
27
|
-
margin-bottom: 0.5em;
|
|
24
|
+
.wrapper {
|
|
25
|
+
padding-top: 0em;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
/* Mirrors the disabled <temba-contact-field> row so the Groups
|
|
29
|
+
entry reads as just another field — same label color/size,
|
|
30
|
+
same horizontal inset, same bottom separator. Margin matches
|
|
31
|
+
the combined .wrapper + :host margins of contact-field
|
|
32
|
+
(0.5em + 1em) so spacing between rows stays uniform. */
|
|
33
|
+
.row {
|
|
34
|
+
padding-bottom: 0.6em;
|
|
35
|
+
border-bottom: 1px solid #ececec;
|
|
36
|
+
margin-bottom: 1.5em;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
.
|
|
35
|
-
|
|
39
|
+
.row .label {
|
|
40
|
+
color: var(--text-2);
|
|
41
|
+
font-size: 12px;
|
|
42
|
+
font-weight: var(--w-medium);
|
|
43
|
+
margin-top: 0.25em;
|
|
44
|
+
margin-left: 0.25em;
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
margin-bottom: 0.7em;
|
|
47
|
+
.row .value {
|
|
48
|
+
margin-left: 0.25em;
|
|
49
|
+
margin-top: 0.1em;
|
|
50
|
+
min-height: 1.75em;
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-wrap: wrap;
|
|
53
|
+
gap: 0.4em;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
.
|
|
49
|
-
font-size: 0.8em;
|
|
50
|
-
color: rgb(136, 136, 136);
|
|
51
|
-
margin-left: 0.5em;
|
|
52
|
-
margin-bottom: 0.4em;
|
|
56
|
+
.group {
|
|
53
57
|
}
|
|
54
58
|
`;
|
|
55
59
|
}
|
|
@@ -62,25 +66,27 @@ export class ContactDetails extends ContactStoreElement {
|
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
const lang =
|
|
69
|
+
const lang = getLanguageName(this.data.language);
|
|
66
70
|
|
|
67
71
|
return html`
|
|
68
72
|
<div class="wrapper">
|
|
69
73
|
${this.data.groups.length > 0
|
|
70
|
-
? html` <div class="
|
|
74
|
+
? html` <div class="row">
|
|
71
75
|
<div class="label">Groups</div>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
<div class="value">
|
|
77
|
+
${this.data.groups.map((group) => {
|
|
78
|
+
return html`<temba-label
|
|
79
|
+
class="group"
|
|
80
|
+
onclick="goto(event)"
|
|
81
|
+
href="/contact/group/${group.uuid}/"
|
|
82
|
+
icon=${group.is_dynamic ? Icon.group_smart : Icon.group}
|
|
83
|
+
type="group"
|
|
84
|
+
clickable
|
|
85
|
+
>
|
|
86
|
+
${group.name}
|
|
87
|
+
</temba-label>`;
|
|
88
|
+
})}
|
|
89
|
+
</div>
|
|
84
90
|
</div>`
|
|
85
91
|
: null}
|
|
86
92
|
${this.data.urns.map((urn) => {
|
|
@@ -93,51 +93,34 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
93
93
|
--color-widget-border: rgb(235, 235, 235);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
.prefix {
|
|
97
|
-
border-top-left-radius: var(--curvature-widget);
|
|
98
|
-
border-bottom-left-radius: var(--curvature-widget);
|
|
99
|
-
cursor: pointer !important;
|
|
100
|
-
white-space: nowrap;
|
|
101
|
-
overflow: hidden;
|
|
102
|
-
text-overflow: ellipsis;
|
|
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. */
|
|
108
|
-
position: absolute;
|
|
109
|
-
top: -0.6em;
|
|
110
|
-
left: 0.5em;
|
|
111
|
-
pointer-events: none;
|
|
112
|
-
background: #fff;
|
|
113
|
-
border-radius: var(--curvature);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
temba-select .prefix {
|
|
117
|
-
top: -0.7em;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
96
|
.wrapper {
|
|
121
97
|
margin-bottom: 0.5em;
|
|
122
98
|
}
|
|
123
99
|
|
|
124
|
-
.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
100
|
+
.field-label {
|
|
101
|
+
display: block;
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
font-weight: var(--w-medium);
|
|
104
|
+
color: var(--text-2);
|
|
105
|
+
margin: 0 0 4px 2px;
|
|
128
106
|
white-space: nowrap;
|
|
129
107
|
overflow: hidden;
|
|
130
108
|
text-overflow: ellipsis;
|
|
131
|
-
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.label .name {
|
|
112
|
+
color: var(--text-2);
|
|
113
|
+
font-size: 12px;
|
|
114
|
+
font-weight: var(--w-medium);
|
|
132
115
|
}
|
|
133
116
|
|
|
134
117
|
.disabled .name {
|
|
135
|
-
margin-top:
|
|
136
|
-
margin-left: 0.
|
|
118
|
+
margin-top: 0.25em;
|
|
119
|
+
margin-left: 0.25em;
|
|
137
120
|
}
|
|
138
121
|
|
|
139
122
|
.disabled .value {
|
|
140
|
-
margin-left: 0.
|
|
123
|
+
margin-left: 0.25em;
|
|
141
124
|
margin-top: 0.1em;
|
|
142
125
|
min-height: 1.75em;
|
|
143
126
|
}
|
|
@@ -242,8 +225,6 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
242
225
|
min-height: 22px !important;
|
|
243
226
|
max-height: 22px !important;
|
|
244
227
|
margin: 5px 6px;
|
|
245
|
-
--button-y: 0;
|
|
246
|
-
--button-x: 10px;
|
|
247
228
|
font-size: 12px;
|
|
248
229
|
}
|
|
249
230
|
|
|
@@ -428,27 +409,30 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
428
409
|
}
|
|
429
410
|
|
|
430
411
|
private renderDateField(state: TemplateResult) {
|
|
431
|
-
return html`
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
412
|
+
return html`
|
|
413
|
+
<label id="field-label" class="field-label">${this.name}</label>
|
|
414
|
+
<temba-datepicker
|
|
415
|
+
aria-labelledby="field-label"
|
|
416
|
+
timezone=${this.timezone}
|
|
417
|
+
value="${this.value ? this.value : ''}"
|
|
418
|
+
@change=${this.handleDateChange}
|
|
419
|
+
?disabled=${this.disabled}
|
|
420
|
+
time
|
|
421
|
+
>
|
|
422
|
+
<div class="postfix" slot="postfix">
|
|
423
|
+
<div class="popper ${this.status} ${this.dirty ? 'dirty' : ''}">
|
|
424
|
+
${state}
|
|
425
|
+
</div>
|
|
444
426
|
</div>
|
|
445
|
-
</
|
|
446
|
-
|
|
427
|
+
</temba-datepicker>
|
|
428
|
+
`;
|
|
447
429
|
}
|
|
448
430
|
|
|
449
431
|
private renderTextField(state: TemplateResult) {
|
|
450
432
|
return html`
|
|
433
|
+
<label id="field-label" class="field-label">${this.name}</label>
|
|
451
434
|
<temba-textinput
|
|
435
|
+
aria-labelledby="field-label"
|
|
452
436
|
class="${this.status} ${this.dirty ? 'dirty' : ''}"
|
|
453
437
|
value="${this.value ? this.value : ''}"
|
|
454
438
|
@keyup=${this.handleInput}
|
|
@@ -456,10 +440,6 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
456
440
|
type=${this.getInputType(this.type)}
|
|
457
441
|
?disabled=${this.disabled}
|
|
458
442
|
>
|
|
459
|
-
<div class="prefix" slot="prefix">
|
|
460
|
-
<div class="name">${this.name}</div>
|
|
461
|
-
</div>
|
|
462
|
-
|
|
463
443
|
<div class="postfix">
|
|
464
444
|
<div
|
|
465
445
|
class="popper ${this.iconClass} ${this.status} ${this.dirty
|
|
@@ -508,7 +488,9 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
508
488
|
|
|
509
489
|
public renderLocationField(level: string = 'state') {
|
|
510
490
|
return html`
|
|
491
|
+
<label id="field-label" class="field-label">${this.name}</label>
|
|
511
492
|
<temba-select
|
|
493
|
+
aria-labelledby="field-label"
|
|
512
494
|
endpoint="/api/internal/locations.json?level=${level}"
|
|
513
495
|
nameKey="path"
|
|
514
496
|
valueKey="path"
|
|
@@ -518,14 +500,10 @@ export class ContactFieldEditor extends RapidElement {
|
|
|
518
500
|
queryParam="query"
|
|
519
501
|
searchable
|
|
520
502
|
clearable
|
|
521
|
-
inpsutStyle=${JSON.stringify({ 'margin-top': '1.1em !important;' })}
|
|
522
503
|
values=${this.value
|
|
523
504
|
? JSON.stringify([{ path: this.value, osm_id: this.value }])
|
|
524
505
|
: '[]'}
|
|
525
506
|
>
|
|
526
|
-
<div class="prefix" slot="prefix">
|
|
527
|
-
<div class="name">${this.name}</div>
|
|
528
|
-
</div>
|
|
529
507
|
</temba-select>
|
|
530
508
|
`;
|
|
531
509
|
}
|
|
@@ -3,6 +3,7 @@ import { property } from 'lit/decorators.js';
|
|
|
3
3
|
import { ContactStoreElement } from './ContactStoreElement';
|
|
4
4
|
import { getDisplayName } from './ContactChat';
|
|
5
5
|
import { ContactNote, CustomEventType } from '../interfaces';
|
|
6
|
+
import { designTokens } from '../styles/designTokens';
|
|
6
7
|
|
|
7
8
|
export class ContactNotepad extends ContactStoreElement {
|
|
8
9
|
@property({ type: Object, attribute: false })
|
|
@@ -14,9 +15,12 @@ export class ContactNotepad extends ContactStoreElement {
|
|
|
14
15
|
|
|
15
16
|
static get styles() {
|
|
16
17
|
return css`
|
|
18
|
+
${designTokens}
|
|
19
|
+
|
|
17
20
|
:host {
|
|
18
21
|
height: 100%;
|
|
19
22
|
display: flex;
|
|
23
|
+
margin-top: var(--gap);
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
.wrapper {
|
|
@@ -24,9 +28,13 @@ export class ContactNotepad extends ContactStoreElement {
|
|
|
24
28
|
--color-widget-bg: transparent;
|
|
25
29
|
--color-widget-bg-focused: transparent;
|
|
26
30
|
outline: none;
|
|
27
|
-
border-radius: var(--curvature);
|
|
28
31
|
display: flex;
|
|
29
32
|
flex-direction: column;
|
|
33
|
+
background: var(--surface-note);
|
|
34
|
+
border: 1px solid var(--border-note);
|
|
35
|
+
border-radius: var(--r-sm);
|
|
36
|
+
box-shadow: var(--shadow-2);
|
|
37
|
+
overflow: hidden;
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
.notepad {
|
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
|
|
@@ -13,25 +13,30 @@ import { css } from 'lit';
|
|
|
13
13
|
*/
|
|
14
14
|
export const designTokens = css`
|
|
15
15
|
:host {
|
|
16
|
-
/* accent ramp —
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
--accent
|
|
21
|
-
--accent-
|
|
22
|
-
--accent-
|
|
23
|
-
--accent-
|
|
24
|
-
--accent-
|
|
25
|
-
--accent-
|
|
26
|
-
--accent-
|
|
27
|
-
--accent-
|
|
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);
|
|
28
31
|
|
|
29
32
|
/* neutrals */
|
|
30
33
|
--bg: #f6f7f9;
|
|
31
34
|
--surface: #ffffff;
|
|
35
|
+
--surface-note: #fff9c2;
|
|
32
36
|
--sunken: #f1f3f5;
|
|
33
37
|
--border: #e6e8ec;
|
|
34
38
|
--border-strong: #d2d6dc;
|
|
39
|
+
--border-note: #ebdf6f;
|
|
35
40
|
--text-1: #1a1f26;
|
|
36
41
|
--text-2: #4d5664;
|
|
37
42
|
--text-3: #7b8593;
|
|
@@ -55,10 +60,14 @@ export const designTokens = css`
|
|
|
55
60
|
--neutral-border: #d8dce2;
|
|
56
61
|
|
|
57
62
|
/* Pill anchor hues — pillVariants derives bg/fg/border via
|
|
58
|
-
color-mix(in
|
|
59
|
-
overriding just the anchor.
|
|
63
|
+
color-mix(in srgb, ...) so host pages can re-theme by
|
|
64
|
+
overriding just the anchor. These are intentionally fixed and
|
|
65
|
+
do NOT track --primary-rgb so that pill identity (group/flow/
|
|
66
|
+
field/channel) stays stable across brand themes. */
|
|
67
|
+
--recipient: #2a6fb5;
|
|
60
68
|
--flow: #16a34a;
|
|
61
69
|
--channel: #6b21a8;
|
|
70
|
+
--topic: #d97706;
|
|
62
71
|
/* Field stays slightly darker than the bright yellow-500 anchor
|
|
63
72
|
used for flow/channel — yellow-500 has too little contrast
|
|
64
73
|
against white to read as a foreground / icon hue on its own.
|
|
@@ -72,7 +81,7 @@ export const designTokens = css`
|
|
|
72
81
|
--w-regular: 400;
|
|
73
82
|
--w-medium: 500;
|
|
74
83
|
--w-semibold: 600;
|
|
75
|
-
--w-bold:
|
|
84
|
+
--w-bold: 600;
|
|
76
85
|
|
|
77
86
|
/* shape */
|
|
78
87
|
--r: 8px;
|
|
@@ -111,9 +120,15 @@ export const designTokens = css`
|
|
|
111
120
|
Surfaces that should stay blue even during a parent field's
|
|
112
121
|
error state (e.g. the dropdown popup) reference --focus-muted
|
|
113
122
|
/ --focus-halo directly to skip that override. */
|
|
114
|
-
--focus:
|
|
115
|
-
--focus-
|
|
116
|
-
--focus-
|
|
123
|
+
--focus: rgb(var(--focus-rgb, 91, 156, 229));
|
|
124
|
+
--focus-50: color-mix(in srgb, var(--focus) 12%, white);
|
|
125
|
+
--focus-100: color-mix(in srgb, var(--focus) 24%, white);
|
|
126
|
+
--focus-200: color-mix(in srgb, var(--focus) 40%, white);
|
|
127
|
+
--focus-300: color-mix(in srgb, var(--focus) 60%, white);
|
|
128
|
+
--focus-600: color-mix(in srgb, var(--focus) 60%, black);
|
|
129
|
+
--focus-700: color-mix(in srgb, var(--focus) 45%, black);
|
|
130
|
+
--focus-muted: color-mix(in srgb, var(--focus) 60%, white);
|
|
131
|
+
--focus-halo: 0 0 0 3px color-mix(in srgb, var(--focus) 30%, transparent);
|
|
117
132
|
--color-focus: var(--focus-muted);
|
|
118
133
|
--widget-box-shadow-focused: var(--focus-halo);
|
|
119
134
|
|
|
@@ -9,8 +9,9 @@ import { css } from 'lit';
|
|
|
9
9
|
* flow/utils.ts.
|
|
10
10
|
*
|
|
11
11
|
* Variant theming:
|
|
12
|
-
* - .pill-contact / .pill-group:
|
|
13
|
-
*
|
|
12
|
+
* - .pill-contact / .pill-group: derived from --recipient (a fixed
|
|
13
|
+
* blue), NOT the accent ramp. This keeps recipient pills stable
|
|
14
|
+
* across brand re-theming via --primary-rgb.
|
|
14
15
|
* - .pill-flow, .pill-channel: bg/border derived from --flow /
|
|
15
16
|
* --channel via color-mix; text + icon use the anchor directly.
|
|
16
17
|
* - .pill-field: bg/border are fixed at the Tailwind yellow ramp
|
|
@@ -40,7 +41,8 @@ export const PILL_TYPES: ReadonlySet<string> = new Set([
|
|
|
40
41
|
'field',
|
|
41
42
|
'label',
|
|
42
43
|
'keyword',
|
|
43
|
-
'channel'
|
|
44
|
+
'channel',
|
|
45
|
+
'topic'
|
|
44
46
|
]);
|
|
45
47
|
|
|
46
48
|
/** Default icon name for each pill variant. Used when a consumer
|
|
@@ -52,7 +54,8 @@ export const PILL_TYPE_ICONS: Readonly<Record<string, string>> = {
|
|
|
52
54
|
contact: 'contact',
|
|
53
55
|
field: 'fields',
|
|
54
56
|
flow: 'flow',
|
|
55
|
-
label: 'label'
|
|
57
|
+
label: 'label',
|
|
58
|
+
topic: 'topic'
|
|
56
59
|
};
|
|
57
60
|
|
|
58
61
|
/** Inverse mapping: icon name (alias or resolved SVG id) → pill type.
|
|
@@ -88,25 +91,33 @@ export const pillVariants = css`
|
|
|
88
91
|
--icon-color: var(--text-2);
|
|
89
92
|
}
|
|
90
93
|
.pill-flow {
|
|
91
|
-
background: color-mix(in
|
|
94
|
+
background: color-mix(in srgb, var(--flow) 12%, white);
|
|
92
95
|
color: var(--flow);
|
|
93
|
-
border-color: color-mix(in
|
|
96
|
+
border-color: color-mix(in srgb, var(--flow) 25%, white);
|
|
94
97
|
--icon-color: var(--flow);
|
|
95
98
|
}
|
|
96
|
-
/* Recipient color — shared by contacts and groups.
|
|
99
|
+
/* Recipient color — shared by contacts and groups. Anchored to
|
|
100
|
+
--recipient (fixed blue), NOT the accent ramp, so pills keep their
|
|
101
|
+
identity even when the host page re-themes via --primary-rgb. */
|
|
97
102
|
.pill-contact,
|
|
98
103
|
.pill-group {
|
|
99
|
-
background: var(--
|
|
100
|
-
color: var(--
|
|
101
|
-
border-color: var(--
|
|
102
|
-
--icon-color: var(--
|
|
104
|
+
background: color-mix(in srgb, var(--recipient) 12%, white);
|
|
105
|
+
color: var(--recipient);
|
|
106
|
+
border-color: color-mix(in srgb, var(--recipient) 25%, white);
|
|
107
|
+
--icon-color: var(--recipient);
|
|
103
108
|
}
|
|
104
109
|
.pill-channel {
|
|
105
|
-
background: color-mix(in
|
|
110
|
+
background: color-mix(in srgb, var(--channel) 12%, white);
|
|
106
111
|
color: var(--channel);
|
|
107
|
-
border-color: color-mix(in
|
|
112
|
+
border-color: color-mix(in srgb, var(--channel) 25%, white);
|
|
108
113
|
--icon-color: var(--channel);
|
|
109
114
|
}
|
|
115
|
+
.pill-topic {
|
|
116
|
+
background: color-mix(in srgb, var(--topic) 12%, white);
|
|
117
|
+
color: var(--topic);
|
|
118
|
+
border-color: color-mix(in srgb, var(--topic) 25%, white);
|
|
119
|
+
--icon-color: var(--topic);
|
|
120
|
+
}
|
|
110
121
|
.pill-field {
|
|
111
122
|
/* Yellow has very low contrast against white, so the color-mix
|
|
112
123
|
approach used by other variants washes out at any readable mix
|