@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/temba-components.js +2119 -1617
  3. package/dist/temba-components.js.map +1 -1
  4. package/package.json +1 -1
  5. package/src/display/Button.ts +102 -121
  6. package/src/display/Chat.ts +74 -9
  7. package/src/display/Dropdown.ts +11 -0
  8. package/src/display/Label.ts +154 -2
  9. package/src/display/LeafletMap.ts +4 -3
  10. package/src/display/Options.ts +71 -16
  11. package/src/display/TembaUser.ts +32 -8
  12. package/src/events/eventRenderers.ts +243 -95
  13. package/src/excellent/caret-utils.ts +0 -1
  14. package/src/flow/AutoTranslate.ts +2 -2
  15. package/src/flow/Editor.ts +4 -4
  16. package/src/flow/NodeEditor.ts +2 -2
  17. package/src/flow/NodeTypeSelector.ts +0 -5
  18. package/src/flow/RevisionsWindow.ts +1 -3
  19. package/src/flow/actions/set_contact_language.ts +5 -4
  20. package/src/flow/nodes/shared.ts +14 -0
  21. package/src/flow/nodes/split_by_llm_categorize.ts +28 -8
  22. package/src/flow/utils.ts +39 -60
  23. package/src/form/ArrayEditor.ts +9 -11
  24. package/src/form/Checkbox.ts +2 -2
  25. package/src/form/ColorPicker.ts +5 -3
  26. package/src/form/Compose.ts +1 -1
  27. package/src/form/FieldElement.ts +8 -8
  28. package/src/form/KeyValueEditor.ts +4 -4
  29. package/src/form/MessageEditor.ts +2 -3
  30. package/src/form/RangePicker.ts +17 -17
  31. package/src/form/TembaSlider.ts +10 -10
  32. package/src/form/TemplateEditor.ts +4 -4
  33. package/src/form/TextInput.ts +19 -1
  34. package/src/form/select/Omnibox.ts +21 -20
  35. package/src/form/select/Select.ts +382 -173
  36. package/src/form/select/WorkspaceSelect.ts +7 -1
  37. package/src/interfaces.ts +1 -0
  38. package/src/languages.ts +56 -0
  39. package/src/layout/Accordion.ts +2 -2
  40. package/src/layout/Dialog.ts +1 -3
  41. package/src/layout/Modax.ts +1 -1
  42. package/src/list/ContentMenu.ts +1 -2
  43. package/src/list/SortableList.ts +156 -0
  44. package/src/list/TembaMenu.ts +159 -113
  45. package/src/live/ContactBadges.ts +2 -1
  46. package/src/live/ContactChat.ts +62 -45
  47. package/src/live/ContactDetails.ts +3 -1
  48. package/src/live/ContactFieldEditor.ts +36 -31
  49. package/src/live/FieldManager.ts +4 -4
  50. package/src/store/AppState.ts +3 -21
  51. package/src/store/Store.ts +0 -29
  52. package/src/styles/designTokens.ts +158 -0
  53. package/src/styles/pillVariants.ts +147 -0
  54. package/static/css/temba-components.css +141 -36
  55. package/web-dev-server.config.mjs +0 -1
  56. package/web-test-runner.config.mjs +98 -1
@@ -1,16 +1,20 @@
1
- import { FormData, NodeConfig, ACTION_GROUPS, Features } from '../types';
1
+ import { FormData, NodeConfig, ACTION_GROUPS, FlowTypes } from '../types';
2
2
  import { CallLLM, Node } from '../../store/flow-definition';
3
3
  import { generateUUID, createMultiCategoryRouter } from '../../utils';
4
4
  import { html } from 'lit';
5
5
  import { validateWith } from '../utils';
6
6
  import { LLMModel, hasLLMRole } from '../flow-utils';
7
+ import {
8
+ resultNameField,
9
+ localizeCategoriesField,
10
+ nodeOptionsAccordionCategoriesOnly
11
+ } from './shared';
7
12
 
8
13
  export const split_by_llm_categorize: NodeConfig = {
9
14
  type: 'split_by_llm_categorize',
10
15
  name: 'Split by AI',
11
16
  group: ACTION_GROUPS.services,
12
- flowTypes: [],
13
- features: [Features.AI],
17
+ flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],
14
18
  form: {
15
19
  llm: {
16
20
  type: 'select',
@@ -49,9 +53,11 @@ export const split_by_llm_categorize: NodeConfig = {
49
53
  required: true
50
54
  }
51
55
  }
52
- }
56
+ },
57
+ result_name: resultNameField,
58
+ localizeCategories: localizeCategoriesField
53
59
  },
54
- layout: ['llm', 'input', 'categories'],
60
+ layout: ['llm', 'input', 'categories', nodeOptionsAccordionCategoriesOnly],
55
61
  validate: validateWith((formData, errors) => {
56
62
  if (!formData.categories || !Array.isArray(formData.categories)) return;
57
63
 
@@ -91,7 +97,7 @@ export const split_by_llm_categorize: NodeConfig = {
91
97
  <div class="body">Categorize with ${callLlmAction.llm.name}</div>
92
98
  `;
93
99
  },
94
- toFormData: (node: Node) => {
100
+ toFormData: (node: Node, nodeUI?: any) => {
95
101
  // Extract data from the existing node structure
96
102
  const callLlmAction = node.actions?.find(
97
103
  (action) => action.type === 'call_llm'
@@ -105,9 +111,18 @@ export const split_by_llm_categorize: NodeConfig = {
105
111
  uuid: node.uuid,
106
112
  llm: callLlmAction?.llm ? [callLlmAction.llm] : [],
107
113
  input: callLlmAction?.input || '@input',
108
- categories: categories
114
+ categories: categories,
115
+ result_name: node.router?.result_name || '',
116
+ localizeCategories: nodeUI?.config?.localizeCategories || false
109
117
  };
110
118
  },
119
+ toUIConfig: (formData: FormData) => {
120
+ const config: Record<string, any> = {};
121
+ config.localizeCategories = formData.result_name
122
+ ? !!formData.localizeCategories
123
+ : false;
124
+ return config;
125
+ },
111
126
  fromFormData: (formData: FormData, originalNode: Node): Node => {
112
127
  // Get LLM selection
113
128
  const llmSelection =
@@ -158,11 +173,16 @@ export const split_by_llm_categorize: NodeConfig = {
158
173
  existingCases
159
174
  );
160
175
 
176
+ const finalRouter: any = { ...router };
177
+ if (formData.result_name && formData.result_name.trim() !== '') {
178
+ finalRouter.result_name = formData.result_name.trim();
179
+ }
180
+
161
181
  // Return the complete node
162
182
  return {
163
183
  uuid: originalNode.uuid,
164
184
  actions: [callLlmAction],
165
- router: router,
185
+ router: finalRouter,
166
186
  exits: exits
167
187
  };
168
188
  },
package/src/flow/utils.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { html, TemplateResult } from 'lit-html';
2
+ import { iconToPillType } from '../styles/pillVariants';
2
3
  import { Action, NamedObject, FlowPosition } from '../store/flow-definition';
3
- import { FlowIssue, zustand } from '../store/AppState';
4
+ import { FlowIssue } from '../store/AppState';
4
5
  import { CustomEventType } from '../interfaces';
5
6
  import { tokenize, TokenType } from '../excellent/tokenizer';
6
7
  import { TOKEN_COLORS } from '../excellent/token-styles';
@@ -106,25 +107,6 @@ export function resolveFromLocalizationFormData(
106
107
  return undefined;
107
108
  }
108
109
 
109
- const intlLanguageNames = new Intl.DisplayNames(['en'], { type: 'language' });
110
-
111
- export function getLanguageDisplayName(code: string): string {
112
- if (code === 'und') return 'Unknown';
113
-
114
- // Prefer names from the RapidPro languages endpoint, which supplies
115
- // ISO 639-3 codes (e.g. prd, pst) that Intl.DisplayNames doesn't cover.
116
- const storeName = zustand.getState().languageNames?.[code];
117
- if (storeName) {
118
- return storeName;
119
- }
120
-
121
- try {
122
- return intlLanguageNames.of(code) || code;
123
- } catch {
124
- return code;
125
- }
126
- }
127
-
128
110
  const IS_MAC =
129
111
  typeof navigator !== 'undefined' &&
130
112
  /Mac|iPod|iPhone|iPad/.test(navigator.platform);
@@ -256,6 +238,15 @@ export const renderClamped = (
256
238
  </div>`;
257
239
  };
258
240
 
241
+ /**
242
+ * Inline margin for stacked pills — the previous implementation used
243
+ * `class="mr-1 mb-1"` (Tailwind utility classes), but this package
244
+ * doesn't ship Tailwind, so the classes resolved to no-ops in any host
245
+ * page that didn't already include it. Inline style is predictable
246
+ * across hosts.
247
+ */
248
+ const PILL_MARGIN_STYLE = 'margin: 0 4px 4px 0;';
249
+
259
250
  /**
260
251
  * Renders a single line item with optional icon.
261
252
  * Content can be plain text or a TemplateResult (e.g. highlighted text).
@@ -265,17 +256,14 @@ export const renderLineItem = (
265
256
  icon?: string,
266
257
  content?: TemplateResult
267
258
  ) => {
268
- return html`<div style="display:flex;align-items:center;">
269
- ${icon
270
- ? html`<temba-icon name=${icon} style="margin-right:0.5em"></temba-icon>`
271
- : null}
272
- <div
273
- style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 250px;"
274
- title="${name}"
275
- >
276
- ${content || name}
277
- </div>
278
- </div>`;
259
+ const pillType = iconToPillType(icon);
260
+ return html`<temba-label
261
+ icon=${icon || ''}
262
+ type=${pillType || 'neutral'}
263
+ style=${PILL_MARGIN_STYLE}
264
+ title="${name}"
265
+ >${content || name}</temba-label
266
+ >`;
279
267
  };
280
268
 
281
269
  /**
@@ -319,12 +307,9 @@ export const renderStringList = (
319
307
  if (items.length > maxDisplay && items.length !== 4) {
320
308
  const remainingCount = items.length - maxDisplay;
321
309
  itemElements.push(
322
- html`<div style="display:flex;align-items:center;margin-top:0.2em;">
323
- ${icon
324
- ? html`<div style="margin-right:0.4em; width: 1em;"></div>` // spacing placeholder
325
- : null}
326
- <div style="font-size:0.8em">+${remainingCount} more</div>
327
- </div>`
310
+ html`<temba-label type="neutral" style=${PILL_MARGIN_STYLE}
311
+ >+${remainingCount} more</temba-label
312
+ >`
328
313
  );
329
314
  }
330
315
  return itemElements;
@@ -368,17 +353,16 @@ export const renderMixedList = (items: MixedListItem[]) => {
368
353
  if (items.length > maxDisplay && items.length !== 4) {
369
354
  const remainingCount = items.length - maxDisplay;
370
355
  itemElements.push(
371
- html`<div style="display:flex;align-items:center;margin-top:0.2em;">
372
- <div style="margin-right:0.4em; width: 1em;"></div>
373
- <div style="font-size:0.8em">+${remainingCount} more</div>
374
- </div>`
356
+ html`<temba-label type="neutral" style=${PILL_MARGIN_STYLE}
357
+ >+${remainingCount} more</temba-label
358
+ >`
375
359
  );
376
360
  }
377
361
  return itemElements;
378
362
  };
379
363
 
380
364
  /**
381
- * Renders a named object as a clickable link that fires a custom event
365
+ * Renders a named object as a clickable DS pill that fires a custom event
382
366
  */
383
367
  const renderLinkedObject = (
384
368
  obj: NamedObject,
@@ -400,18 +384,16 @@ const renderLinkedObject = (
400
384
  }
401
385
  };
402
386
 
403
- return html`<div style="display:flex;align-items:center;max-width:100%;">
404
- ${icon
405
- ? html`<temba-icon name=${icon} style="margin-right:0.5em"></temba-icon>`
406
- : null}
407
- <div
408
- class="linked-name"
409
- @click=${handleClick}
410
- style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 250px; text-decoration: underline; cursor: pointer;"
411
- >
412
- ${obj.name}
413
- </div>
414
- </div>`;
387
+ const pillType = iconToPillType(icon);
388
+ return html`<temba-label
389
+ icon=${icon || ''}
390
+ type=${pillType || 'neutral'}
391
+ clickable
392
+ style=${PILL_MARGIN_STYLE}
393
+ title="${obj.name}"
394
+ @click=${handleClick}
395
+ >${obj.name}</temba-label
396
+ >`;
415
397
  };
416
398
 
417
399
  /**
@@ -436,12 +418,9 @@ const renderLinkedObjects = (
436
418
  if (objects.length > maxDisplay && objects.length !== 4) {
437
419
  const remainingCount = objects.length - maxDisplay;
438
420
  itemElements.push(
439
- html`<div style="display:flex;align-items:center;margin-top:0.2em;">
440
- ${icon
441
- ? html`<div style="margin-right:0.4em; width: 1em;"></div>`
442
- : null}
443
- <div style="font-size:0.8em">+${remainingCount} more</div>
444
- </div>`
421
+ html`<temba-label type="neutral" style=${PILL_MARGIN_STYLE}
422
+ >+${remainingCount} more</temba-label
423
+ >`
445
424
  );
446
425
  }
447
426
  return itemElements;
@@ -604,12 +604,11 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
604
604
  class="remove-btn"
605
605
  style="
606
606
  padding: 4px;
607
- border: 1px solid #ccc;
608
- border-radius: 4px;
609
- background: white;
607
+ border: 1px solid var(--color-widget-border);
608
+ border-radius: var(--curvature-widget);
609
+ background: var(--surface);
610
610
  cursor: pointer;
611
- background: #fefefe;
612
- color: #999;
611
+ color: var(--text-3);
613
612
  font-size: 14px;
614
613
  "
615
614
  ?disabled=${!canRemove}
@@ -651,21 +650,20 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
651
650
  .add-btn,
652
651
  .remove-btn {
653
652
  padding: 4px;
654
- border: 1px solid #ccc;
655
- border-radius: 4px;
656
- background: white;
653
+ border: 1px solid var(--color-widget-border);
654
+ border-radius: var(--curvature-widget);
655
+ background: var(--surface);
657
656
  cursor: pointer;
658
657
  font-size: 14px;
659
658
  }
660
659
 
661
660
  .add-btn:hover,
662
661
  .remove-btn:hover {
663
- background: #f8f8f8;
662
+ background: var(--sunken);
664
663
  }
665
664
 
666
665
  .remove-btn {
667
- background: #fefefe;
668
- color: #999;
666
+ color: var(--text-3);
669
667
  }
670
668
 
671
669
  .removable .remove-btn {
@@ -25,7 +25,7 @@ export class Checkbox extends FieldElement {
25
25
  width: 12px;
26
26
  height: 12px;
27
27
  background: var(--checkbox-background, rgba(255, 255, 255, 0.8));
28
- border-radius: 2px;
28
+ border-radius: var(--r-xs);
29
29
  }
30
30
 
31
31
  .wrapper.label {
@@ -34,7 +34,7 @@ export class Checkbox extends FieldElement {
34
34
  }
35
35
 
36
36
  .wrapper.label:hover {
37
- background: var(--checkbox-hover-bg, #f9f9f9);
37
+ background: var(--checkbox-hover-bg, var(--sunken));
38
38
  }
39
39
 
40
40
  .checkbox-container {
@@ -29,7 +29,6 @@ export class ColorPicker extends FieldElement {
29
29
  :host {
30
30
  color: var(--color-text);
31
31
  display: inline-block;
32
- --curvature: 0.55em;
33
32
  width: 100%;
34
33
 
35
34
  --temba-textinput-padding: 0.4em;
@@ -37,13 +36,16 @@ export class ColorPicker extends FieldElement {
37
36
 
38
37
  temba-textinput {
39
38
  margin-left: 0.3em;
40
- width: 5em;
39
+ width: 7em;
40
+ --temba-textinput-min-height: 0;
41
+ --temba-textinput-padding: 4px 8px;
42
+ --temba-textinput-font-size: 12.5px;
41
43
  }
42
44
 
43
45
  .wrapper {
44
46
  border: 1px solid var(--color-widget-border);
45
47
  padding: calc(var(--curvature) / 2);
46
- border-radius: calc(var(--curvature) * 1.5);
48
+ border-radius: var(--curvature);
47
49
  transition: all calc(var(--transition-speed) * 2) var(--bounce);
48
50
 
49
51
  display: flex;
@@ -104,7 +104,7 @@ export class Compose extends FieldElement {
104
104
  --curvature-widget: 0px;
105
105
  --color-options-bg: #fff;
106
106
  border: 1px solid var(--color-widget-border);
107
- border-radius: 6px;
107
+ border-radius: var(--curvature-widget);
108
108
  background: var(--color-widget-bg, #fff);
109
109
  box-shadow: var(--options-shadow);
110
110
  z-index: 1000003;
@@ -2,6 +2,7 @@ import { TemplateResult, html, css } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
3
  import { RapidElement } from '../RapidElement';
4
4
  import { renderMarkdownInline } from '../markdown';
5
+ import { designTokens } from '../styles/designTokens';
5
6
 
6
7
  /**
7
8
  * FieldElement is a base class for form components that provides built-in
@@ -62,26 +63,25 @@ export abstract class FieldElement extends RapidElement {
62
63
 
63
64
  static get styles() {
64
65
  return css`
66
+ ${designTokens}
67
+
65
68
  :host {
66
69
  font-family: var(--font-family);
67
70
  }
68
71
 
69
72
  label {
70
- margin-bottom: 5px;
71
- margin-left: 4px;
73
+ margin-bottom: 6px;
72
74
  display: block;
73
- font-weight: 400;
74
- font-size: var(--label-size);
75
- letter-spacing: 0.05em;
75
+ font-weight: var(--w-medium);
76
+ font-size: 12.5px;
76
77
  line-height: normal;
77
- color: var(--color-label, #777);
78
+ color: var(--color-label);
78
79
  }
79
80
 
80
81
  .help-text {
81
- font-size: var(--help-text-size);
82
+ font-size: 12px;
82
83
  line-height: normal;
83
84
  color: var(--color-text-help);
84
- margin-left: var(--help-text-margin-left);
85
85
  margin-top: 6px;
86
86
  opacity: 1;
87
87
  }
@@ -282,10 +282,10 @@ export class KeyValueEditor extends BaseListEditor<KeyValueItem> {
282
282
  .remove-btn {
283
283
  width: 32px;
284
284
  height: 32px;
285
- border: 1px solid #ccc;
286
- border-radius: 4px;
287
- background: #f8f8f8;
288
- color: #666;
285
+ border: 1px solid var(--color-widget-border);
286
+ border-radius: var(--curvature-widget);
287
+ background: var(--sunken);
288
+ color: var(--text-2);
289
289
  cursor: pointer;
290
290
  display: flex;
291
291
  align-items: center;
@@ -23,7 +23,7 @@ export class MessageEditor extends FieldElement {
23
23
  .message-editor-container {
24
24
  border: 1px solid var(--color-widget-border);
25
25
  border-radius: var(--curvature-widget);
26
- background: #fff;
26
+ background: var(--surface);
27
27
  position: relative;
28
28
  transition:
29
29
  border-color 0.2s ease-in-out,
@@ -75,10 +75,9 @@ export class MessageEditor extends FieldElement {
75
75
 
76
76
  .media-wrapper {
77
77
  padding: 4px 8px;
78
- background: rgba(0, 0, 0, 0.03);
78
+ background: var(--sunken);
79
79
  border-top: 1px solid var(--color-widget-border);
80
80
  border-radius: 0 0 var(--curvature-widget) var(--curvature-widget);
81
- box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.05);
82
81
  margin-top: 3px;
83
82
  display: none;
84
83
  }
@@ -16,21 +16,21 @@ export class RangePicker extends RapidElement {
16
16
  cursor: pointer;
17
17
  padding: 0.2em 0.5em;
18
18
  margin: 0.6em 0;
19
- border-radius: 4px;
19
+ border-radius: var(--curvature-widget);
20
20
  border: 1px solid transparent;
21
21
  transition: border 0.2s;
22
22
  }
23
23
 
24
24
  .date-display:hover {
25
- border: 1px solid var(--color-widget-border, #bbb);
26
- background: var(--color-widget-hover, #f5f5f5);
25
+ border: 1px solid var(--color-widget-border);
26
+ background: var(--sunken);
27
27
  }
28
28
 
29
29
  input[type='date'] {
30
30
  font-size: 1em;
31
31
  padding: 0.2em 0.5em;
32
- border-radius: 4px;
33
- border: 1px solid #bbb;
32
+ border-radius: var(--curvature-widget);
33
+ border: 1px solid var(--color-widget-border);
34
34
  }
35
35
 
36
36
  .navigation-container {
@@ -40,10 +40,10 @@ export class RangePicker extends RapidElement {
40
40
  }
41
41
 
42
42
  .nav-arrow {
43
- background: #f5f5f5;
44
- border: 1px solid #bbb;
43
+ background: var(--sunken);
44
+ border: 1px solid var(--color-widget-border);
45
45
 
46
- border-radius: var(--curvature);
46
+ border-radius: var(--curvature-widget);
47
47
  padding: 0em 0em;
48
48
  cursor: pointer;
49
49
  font-size: 0.6em;
@@ -59,14 +59,14 @@ export class RangePicker extends RapidElement {
59
59
  }
60
60
 
61
61
  .nav-arrow:hover:not(:disabled) {
62
- background: #e0eaff;
63
- border-color: #3399ff;
62
+ background: var(--accent-50);
63
+ border-color: var(--accent);
64
64
  }
65
65
 
66
66
  .nav-arrow:disabled {
67
67
  opacity: 0.5;
68
68
  cursor: not-allowed;
69
- background: #f9f9f9;
69
+ background: var(--sunken);
70
70
  }
71
71
 
72
72
  .nav-arrow.hidden {
@@ -78,8 +78,8 @@ export class RangePicker extends RapidElement {
78
78
  margin-left: 0em;
79
79
  }
80
80
  .range-btn {
81
- background: #f5f5f5;
82
- border: 1px solid #bbb;
81
+ background: var(--sunken);
82
+ border: 1px solid var(--color-widget-border);
83
83
  border-radius: 0px;
84
84
  margin-left: -1px;
85
85
  padding: 0.2em 0.8em;
@@ -91,17 +91,17 @@ export class RangePicker extends RapidElement {
91
91
  }
92
92
 
93
93
  .button-group .range-btn:first-child {
94
- border-radius: 4px 0 0 4px;
94
+ border-radius: var(--curvature-widget) 0 0 var(--curvature-widget);
95
95
  }
96
96
 
97
97
  .button-group .range-btn:last-child {
98
- border-radius: 0 4px 4px 0;
98
+ border-radius: 0 var(--curvature-widget) var(--curvature-widget) 0;
99
99
  }
100
100
 
101
101
  .range-btn.selected,
102
102
  .range-btn:active {
103
- background: #e0eaff;
104
- border-color: #3399ff;
103
+ background: var(--accent-50);
104
+ border-color: var(--accent);
105
105
  }
106
106
  `;
107
107
 
@@ -13,9 +13,9 @@ export class TembaSlider extends FieldElement {
13
13
 
14
14
  .track {
15
15
  height: 2px;
16
- border-top: 0.5em solid #fff;
17
- border-bottom: 0.5em solid #fff;
18
- background: #ddd;
16
+ border-top: 0.5em solid var(--surface);
17
+ border-bottom: 0.5em solid var(--surface);
18
+ background: var(--border-strong);
19
19
  flex-grow: 1;
20
20
  }
21
21
 
@@ -24,11 +24,11 @@ export class TembaSlider extends FieldElement {
24
24
  margin-left: -0.5em;
25
25
  width: 0.75em;
26
26
  height: 0.75em;
27
- border: 2px solid #999;
27
+ border: 2px solid var(--border-strong);
28
28
  border-radius: 999px;
29
29
  position: relative;
30
- background: #fff;
31
- box-shadow: 0 0 0 4px rgb(255, 255, 255);
30
+ background: var(--surface);
31
+ box-shadow: 0 0 0 4px var(--surface);
32
32
  transition: transform 200ms ease-in-out;
33
33
  }
34
34
 
@@ -37,13 +37,13 @@ export class TembaSlider extends FieldElement {
37
37
  }
38
38
 
39
39
  :hover .circle {
40
- border-color: #777;
40
+ border-color: var(--text-2);
41
41
  cursor: pointer;
42
42
  }
43
43
 
44
44
  .grabbed .circle {
45
- border-color: var(--color-primary-dark);
46
- background: #fff;
45
+ border-color: var(--accent);
46
+ background: var(--surface);
47
47
  }
48
48
 
49
49
  .grabbed .circle {
@@ -58,7 +58,7 @@ export class TembaSlider extends FieldElement {
58
58
  .pre,
59
59
  .post {
60
60
  font-size: 0.9em;
61
- color: #999;
61
+ color: var(--text-3);
62
62
  padding: 0em 1em;
63
63
  }
64
64
  `;
@@ -99,9 +99,9 @@ export class TemplateEditor extends FieldElement {
99
99
  }
100
100
 
101
101
  .button {
102
- background: #fff;
102
+ background: var(--surface);
103
103
  padding: 0.3em 1em;
104
- border: 1px solid #e6e6e6;
104
+ border: 1px solid var(--color-widget-border);
105
105
  border-radius: var(--curvature);
106
106
  min-height: 23px;
107
107
  display: flex;
@@ -118,7 +118,7 @@ export class TemplateEditor extends FieldElement {
118
118
 
119
119
  .button .display {
120
120
  margin-right: 0.5em;
121
- background: #f9f9f9;
121
+ background: var(--sunken);
122
122
  padding: 0.25em 1em;
123
123
  border-radius: var(--curvature);
124
124
  }
@@ -130,7 +130,7 @@ export class TemplateEditor extends FieldElement {
130
130
  }
131
131
 
132
132
  .template {
133
- background: #fff;
133
+ background: var(--surface);
134
134
  border-radius: var(--curvature);
135
135
  border: 1px solid var(--color-widget-border);
136
136
  padding: 1em;
@@ -26,13 +26,32 @@ export class TextInput extends FieldElement {
26
26
  display: flex;
27
27
  flex-direction: row;
28
28
  align-items: stretch;
29
+ min-height: var(--temba-textinput-min-height, var(--input-h));
29
30
  box-shadow: var(--widget-box-shadow);
30
31
  caret-color: var(--input-caret);
32
+ /* Establish a stable containing block for slotted absolutely-
33
+ positioned children (e.g. ContactFieldEditor's embedded
34
+ prefix label). Setting position: relative only on
35
+ :focus-within would re-anchor the prefix when focused,
36
+ visually shifting the label. */
37
+ position: relative;
38
+ }
39
+
40
+ .input-container.textarea,
41
+ .input-container:has(textarea) {
42
+ min-height: 0;
31
43
  }
32
44
 
33
45
  .xsmall {
34
46
  --temba-textinput-padding: 6px 8px;
35
47
  --temba-textinput-font-size: 13px;
48
+ /* Opt out of the 34px --input-h floor so an xsmall textinput
49
+ lines up with RichEditor xsmall (the "evaluated" argument
50
+ input in rule editor rows) and with Select xsmall, neither
51
+ of which carry the floor. Without this, a [operator select
52
+ | rich-edit argument | textinput category] row renders the
53
+ category ~4px taller than its neighbors. */
54
+ --temba-textinput-min-height: 0;
36
55
  }
37
56
 
38
57
  .small {
@@ -66,7 +85,6 @@ export class TextInput extends FieldElement {
66
85
  border-color: var(--color-focus);
67
86
  background: var(--color-widget-bg-focused);
68
87
  box-shadow: var(--widget-box-shadow-focused);
69
- position: relative;
70
88
  }
71
89
 
72
90
  .input-container:hover {