@nyaruka/temba-components 0.157.1 → 0.158.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 +16 -0
- package/dist/temba-components.js +1305 -530
- 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/Dropdown.ts +17 -5
- package/src/events/eventRenderers.ts +4 -9
- package/src/flow/CanvasNode.ts +14 -6
- package/src/flow/DragManager.ts +4 -2
- package/src/flow/utils.ts +1 -0
- package/src/form/DatePicker.ts +2 -1
- package/src/interfaces.ts +4 -1
- package/src/layout/Tab.ts +0 -15
- package/src/layout/TabPane.ts +76 -164
- package/src/list/ContactList.ts +225 -0
- package/src/list/ContentList.ts +1298 -0
- package/src/list/FlowList.ts +251 -0
- package/src/list/MsgList.ts +144 -0
- package/src/live/ContactChat.ts +6 -2
- package/src/live/ContactDetails.ts +40 -35
- package/src/live/ContactFieldEditor.ts +35 -55
- package/src/live/ContactFields.ts +1 -2
- package/src/live/ContactNotepad.ts +9 -1
- package/src/live/ContactPending.ts +1 -0
- package/src/styles/designTokens.ts +2 -0
- package/static/api/flow-labels.json +31 -0
- package/static/css/temba-components.css +2 -0
- package/temba-modules.ts +8 -0
- package/web-dev-server.config.mjs +156 -0
package/orca/setup.sh
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Orca setup hook for a temba-components worktree.
|
|
4
|
+
# Symlinks shared utility files from nyaruka/utils, ensures the devcontainer
|
|
5
|
+
# is built/running (recreating it if the bind mounts are stale), and installs
|
|
6
|
+
# pnpm dependencies inside the worktree.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ./orca/setup.sh # run directly
|
|
10
|
+
# (orca invokes this via orca.yaml's scripts.setup)
|
|
11
|
+
|
|
12
|
+
set -e
|
|
13
|
+
|
|
14
|
+
CONTAINER_NAME="temba-components"
|
|
15
|
+
|
|
16
|
+
# Orca exports ORCA_WORKTREE_PATH when running this as a hook; otherwise
|
|
17
|
+
# derive the worktree path from the script location.
|
|
18
|
+
if [ -n "${ORCA_WORKTREE_PATH:-}" ]; then
|
|
19
|
+
SCRIPT_DIR="$ORCA_WORKTREE_PATH"
|
|
20
|
+
else
|
|
21
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
22
|
+
fi
|
|
23
|
+
WORKSPACE_NAME="$(basename "$SCRIPT_DIR")"
|
|
24
|
+
|
|
25
|
+
# Pin WORKTREES to the orca workspaces dir so .devcontainer/devcontainer.json
|
|
26
|
+
# (which reads ${localEnv:WORKTREES}) sees the orca tree, not a stale value
|
|
27
|
+
# inherited from the user's shell.
|
|
28
|
+
WORKTREES="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
|
29
|
+
export WORKTREES
|
|
30
|
+
|
|
31
|
+
# Symlink shared files from nyaruka/utils into the worktree (gitignored).
|
|
32
|
+
UTILS_DIR="${UTILS_PATH:-$HOME/code/nyaruka/utils}/projects/temba-components"
|
|
33
|
+
if [ ! -d "$UTILS_DIR" ]; then
|
|
34
|
+
echo "Error: utils dir not found at $UTILS_DIR (set UTILS_PATH to override)"
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
for file in run.sh AGENTS.md; do
|
|
38
|
+
if [ -f "$UTILS_DIR/$file" ] && [ ! -e "$SCRIPT_DIR/$file" ]; then
|
|
39
|
+
ln -s "$UTILS_DIR/$file" "$SCRIPT_DIR/$file"
|
|
40
|
+
fi
|
|
41
|
+
done
|
|
42
|
+
if [ ! -e "$SCRIPT_DIR/CLAUDE.md" ]; then
|
|
43
|
+
ln -s "$UTILS_DIR/AGENTS.md" "$SCRIPT_DIR/CLAUDE.md"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Ensure the devcontainer is running. Bind mounts can't be edited after the
|
|
47
|
+
# container is created, so if an existing container's /workspaces/worktrees
|
|
48
|
+
# mount doesn't match $WORKTREES (e.g. a stale conductor-era container),
|
|
49
|
+
# recreate it to pick up the new mount.
|
|
50
|
+
state="$(docker inspect -f '{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null || true)"
|
|
51
|
+
|
|
52
|
+
if [ -n "$state" ]; then
|
|
53
|
+
current_worktrees="$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/workspaces/worktrees"}}{{.Source}}{{end}}{{end}}' "$CONTAINER_NAME" 2>/dev/null || true)"
|
|
54
|
+
if [ -n "$current_worktrees" ] && [ "$current_worktrees" != "$WORKTREES" ]; then
|
|
55
|
+
echo "Container '$CONTAINER_NAME' has a stale /workspaces/worktrees mount:"
|
|
56
|
+
echo " $current_worktrees (expected $WORKTREES)"
|
|
57
|
+
echo "Removing and recreating the container..."
|
|
58
|
+
docker rm -f "$CONTAINER_NAME" >/dev/null
|
|
59
|
+
state=""
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
case "$state" in
|
|
64
|
+
running) ;;
|
|
65
|
+
"")
|
|
66
|
+
echo "Container '$CONTAINER_NAME' does not exist — building devcontainer..."
|
|
67
|
+
devcontainer up --workspace-folder "$SCRIPT_DIR"
|
|
68
|
+
;;
|
|
69
|
+
*)
|
|
70
|
+
echo "Container '$CONTAINER_NAME' is $state — starting..."
|
|
71
|
+
docker start "$CONTAINER_NAME"
|
|
72
|
+
;;
|
|
73
|
+
esac
|
|
74
|
+
|
|
75
|
+
# Install dependencies inside the worktree.
|
|
76
|
+
docker exec "$CONTAINER_NAME" bash -c '
|
|
77
|
+
cd "/workspaces/worktrees/temba-components/'"$WORKSPACE_NAME"'"
|
|
78
|
+
pnpm install
|
|
79
|
+
'
|
|
80
|
+
|
|
81
|
+
echo "Worktree '$WORKSPACE_NAME' ready for development"
|
package/orca.yaml
ADDED
package/package.json
CHANGED
package/src/display/Dropdown.ts
CHANGED
|
@@ -205,13 +205,22 @@ export class Dropdown extends RapidElement {
|
|
|
205
205
|
return;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// Anchor the dropdown to the toggle's viewport coordinates.
|
|
209
|
+
// The dropdown is `position: fixed`, so viewport coords are
|
|
210
|
+
// the right reference frame. Without an explicit anchor the
|
|
211
|
+
// browser resolves `top: auto; left: auto` from the element's
|
|
212
|
+
// in-flow position, which mis-places the dropdown when the
|
|
213
|
+
// page is scrolled. Always setting top/left from
|
|
214
|
+
// getBoundingClientRect makes positioning scroll-invariant.
|
|
208
215
|
const dropdownStyle = {
|
|
209
216
|
border: '1px solid rgba(0,0,0,0.1)',
|
|
210
|
-
marginTop: '0.5em'
|
|
217
|
+
marginTop: '0.5em',
|
|
218
|
+
top: toggleBounds.bottom + 'px',
|
|
219
|
+
left: toggleBounds.left + 'px'
|
|
211
220
|
};
|
|
212
221
|
|
|
213
222
|
// if off the the right, bump it left
|
|
214
|
-
if (dropdownBounds.
|
|
223
|
+
if (toggleBounds.left + dropdownBounds.width > window.innerWidth) {
|
|
215
224
|
dropdownStyle['left'] =
|
|
216
225
|
toggleBounds.right - dropdownBounds.width + 'px';
|
|
217
226
|
delete dropdownStyle['right'];
|
|
@@ -219,7 +228,7 @@ export class Dropdown extends RapidElement {
|
|
|
219
228
|
}
|
|
220
229
|
|
|
221
230
|
// if off to the bottom, bump it up
|
|
222
|
-
if (
|
|
231
|
+
if (toggleBounds.bottom + dropdownBounds.height > window.innerHeight) {
|
|
223
232
|
dropdownStyle['top'] = toggleBounds.top - dropdownBounds.height + 'px';
|
|
224
233
|
dropdownStyle['marginTop'] = '-0.5em';
|
|
225
234
|
bumpedUp = true;
|
|
@@ -237,9 +246,12 @@ export class Dropdown extends RapidElement {
|
|
|
237
246
|
// anchored to far-left toggles (e.g. rail items) don't rub against
|
|
238
247
|
// the window edge. Shift the dropdown right and slide the arrow
|
|
239
248
|
// back the same amount so it still points at the toggle.
|
|
249
|
+
// Check against the intended `left` (toggleBounds.left) rather
|
|
250
|
+
// than the dropdown's currently-rendered bounds, since the new
|
|
251
|
+
// left is what we're about to set.
|
|
240
252
|
const MIN_LEFT = 8;
|
|
241
|
-
if (
|
|
242
|
-
const shift = MIN_LEFT -
|
|
253
|
+
if (toggleBounds.left < MIN_LEFT && !bumpedLeft) {
|
|
254
|
+
const shift = MIN_LEFT - toggleBounds.left;
|
|
243
255
|
dropdownStyle['left'] = MIN_LEFT + 'px';
|
|
244
256
|
arrowLeft -= shift;
|
|
245
257
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
UpdateFieldEvent,
|
|
14
14
|
URNsChangedEvent
|
|
15
15
|
} from '../events';
|
|
16
|
+
import { getLanguageName } from '../languages';
|
|
16
17
|
import { oxfordFn } from '../utils';
|
|
17
18
|
|
|
18
19
|
export enum Events {
|
|
@@ -291,9 +292,7 @@ export const renderTicketAssigneeChanged = (
|
|
|
291
292
|
// consistently interactive across open / close / reopen / assigned
|
|
292
293
|
// rows (the contact-history page can show events from any of the
|
|
293
294
|
// contact's tickets, so the jump-to-ticket affordance is useful).
|
|
294
|
-
const ticketLink = html`<a href="/ticket/all/open/${ticketUUID}/"
|
|
295
|
-
>ticket</a
|
|
296
|
-
>`;
|
|
295
|
+
const ticketLink = html`<a href="/ticket/all/open/${ticketUUID}/">ticket</a>`;
|
|
297
296
|
const ticketLinkCapitalized = html`<a href="/ticket/all/open/${ticketUUID}/"
|
|
298
297
|
>This ticket</a
|
|
299
298
|
>`;
|
|
@@ -356,11 +355,7 @@ export const renderContactGroupsEvent = (
|
|
|
356
355
|
if (event.groups_added) {
|
|
357
356
|
return renderInfoList('Added to', 'Added to', event.groups_added);
|
|
358
357
|
} else if (event.groups_removed) {
|
|
359
|
-
return renderInfoList(
|
|
360
|
-
'Removed from',
|
|
361
|
-
'Removed from',
|
|
362
|
-
event.groups_removed
|
|
363
|
-
);
|
|
358
|
+
return renderInfoList('Removed from', 'Removed from', event.groups_removed);
|
|
364
359
|
}
|
|
365
360
|
};
|
|
366
361
|
|
|
@@ -382,7 +377,7 @@ export const renderContactLanguageChangedEvent = (
|
|
|
382
377
|
return html`<div style=${eventLineStyle}>Cleared language</div>`;
|
|
383
378
|
}
|
|
384
379
|
return html`<div style=${eventLineStyle}>
|
|
385
|
-
Language updated to ${valueText(event.language)}
|
|
380
|
+
Language updated to ${valueText(getLanguageName(event.language))}
|
|
386
381
|
</div>`;
|
|
387
382
|
};
|
|
388
383
|
|
package/src/flow/CanvasNode.ts
CHANGED
|
@@ -219,13 +219,18 @@ export class CanvasNode extends RapidElement {
|
|
|
219
219
|
background: repeating-linear-gradient(120deg, tomato, tomato 6px, #ff7056 0, #ff7056 18px) !important;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
/* Disable links on actions/nodes with issues
|
|
222
|
+
/* Disable links on actions/nodes with issues so clicks fall through
|
|
223
|
+
to open the editor instead of navigating. */
|
|
223
224
|
.action-content.has-issues .linked-name div,
|
|
224
225
|
.node.has-issues > .router .linked-name div {
|
|
225
226
|
text-decoration: none !important;
|
|
226
227
|
cursor: default !important;
|
|
227
228
|
pointer-events: none;
|
|
228
229
|
}
|
|
230
|
+
.action-content.has-issues .linked-pill,
|
|
231
|
+
.node.has-issues > .router .linked-pill {
|
|
232
|
+
pointer-events: none;
|
|
233
|
+
}
|
|
229
234
|
|
|
230
235
|
.action.sortable {
|
|
231
236
|
display: flex;
|
|
@@ -1094,13 +1099,16 @@ export class CanvasNode extends RapidElement {
|
|
|
1094
1099
|
}
|
|
1095
1100
|
|
|
1096
1101
|
/**
|
|
1097
|
-
* Returns true if the click target is inside a `.linked-name`
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1102
|
+
* Returns true if the click target is inside a `.linked-name` or
|
|
1103
|
+
* `.linked-pill` whose containing action/node has no issues. Active
|
|
1104
|
+
* links handle their own navigation, so click-vs-drag and node-edit
|
|
1105
|
+
* handlers bail out on them. When the action/node has issues, links are
|
|
1106
|
+
* visually disabled (see CSS) and clicks fall through to open the
|
|
1107
|
+
* editor instead.
|
|
1101
1108
|
*/
|
|
1102
1109
|
private isActiveLink(target: HTMLElement, action?: Action): boolean {
|
|
1103
|
-
if (!target.closest('.linked-name')
|
|
1110
|
+
if (!target.closest('.linked-name') && !target.closest('.linked-pill'))
|
|
1111
|
+
return false;
|
|
1104
1112
|
if (action) return !this.issuesByAction?.has(action.uuid);
|
|
1105
1113
|
return !(
|
|
1106
1114
|
this.issuesByNode?.has(this.node.uuid) ||
|
package/src/flow/DragManager.ts
CHANGED
|
@@ -127,7 +127,8 @@ export class DragManager {
|
|
|
127
127
|
if (
|
|
128
128
|
target.classList.contains('exit') ||
|
|
129
129
|
target.closest('.exit') ||
|
|
130
|
-
target.closest('.linked-name')
|
|
130
|
+
target.closest('.linked-name') ||
|
|
131
|
+
target.closest('.linked-pill')
|
|
131
132
|
) {
|
|
132
133
|
return;
|
|
133
134
|
}
|
|
@@ -186,7 +187,8 @@ export class DragManager {
|
|
|
186
187
|
if (
|
|
187
188
|
target.classList.contains('exit') ||
|
|
188
189
|
target.closest('.exit') ||
|
|
189
|
-
target.closest('.linked-name')
|
|
190
|
+
target.closest('.linked-name') ||
|
|
191
|
+
target.closest('.linked-pill')
|
|
190
192
|
) {
|
|
191
193
|
return;
|
|
192
194
|
}
|
package/src/flow/utils.ts
CHANGED
package/src/form/DatePicker.ts
CHANGED
|
@@ -19,6 +19,7 @@ export class DatePicker extends FieldElement {
|
|
|
19
19
|
.container {
|
|
20
20
|
border-radius: var(--curvature);
|
|
21
21
|
border: 1px solid var(--color-widget-border);
|
|
22
|
+
background: var(--color-widget-bg);
|
|
22
23
|
display: flex;
|
|
23
24
|
cursor: pointer;
|
|
24
25
|
box-shadow: var(--widget-box-shadow);
|
|
@@ -59,7 +60,7 @@ export class DatePicker extends FieldElement {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
.tz-wrapper {
|
|
62
|
-
background:
|
|
63
|
+
background: var(--sunken);
|
|
63
64
|
display: flex;
|
|
64
65
|
flex-direction: row;
|
|
65
66
|
align-items: center;
|
package/src/interfaces.ts
CHANGED
|
@@ -323,5 +323,8 @@ export enum CustomEventType {
|
|
|
323
323
|
RevisionViewed = 'temba-revision-viewed',
|
|
324
324
|
RevisionCancelled = 'temba-revision-cancelled',
|
|
325
325
|
RevisionReverted = 'temba-revision-reverted',
|
|
326
|
-
RevisionsClosed = 'temba-revisions-closed'
|
|
326
|
+
RevisionsClosed = 'temba-revisions-closed',
|
|
327
|
+
RowClick = 'temba-row-click',
|
|
328
|
+
SelectionChange = 'temba-selection-change',
|
|
329
|
+
BulkAction = 'temba-bulk-action'
|
|
327
330
|
}
|
package/src/layout/Tab.ts
CHANGED
|
@@ -27,24 +27,9 @@ export class Tab extends RapidElement {
|
|
|
27
27
|
@property({ type: String })
|
|
28
28
|
icon: string;
|
|
29
29
|
|
|
30
|
-
@property({ type: String })
|
|
31
|
-
selectionColor: string;
|
|
32
|
-
|
|
33
|
-
@property({ type: String })
|
|
34
|
-
selectionBackground: string;
|
|
35
|
-
|
|
36
|
-
@property({ type: String })
|
|
37
|
-
borderColor: string = 'var(--color-widget-border)';
|
|
38
|
-
|
|
39
|
-
@property({ type: String })
|
|
40
|
-
activityColor: string = `var(--color-link-primary)`;
|
|
41
|
-
|
|
42
30
|
@property({ type: Boolean })
|
|
43
31
|
selected = false;
|
|
44
32
|
|
|
45
|
-
@property({ type: Boolean })
|
|
46
|
-
notify = false;
|
|
47
|
-
|
|
48
33
|
@property({ type: Boolean })
|
|
49
34
|
alert = false;
|
|
50
35
|
|
package/src/layout/TabPane.ts
CHANGED
|
@@ -18,43 +18,48 @@ export class TabPane extends RapidElement {
|
|
|
18
18
|
.options {
|
|
19
19
|
display: flex;
|
|
20
20
|
align-items: stretch;
|
|
21
|
-
|
|
22
|
-
border-bottom:
|
|
21
|
+
gap: 4px;
|
|
22
|
+
border-bottom: 1px solid var(--border);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
.option {
|
|
26
26
|
user-select: none;
|
|
27
|
-
padding: 0.5em 0.7em;
|
|
28
|
-
margin: 0em 0em;
|
|
29
|
-
cursor: pointer;
|
|
30
27
|
display: flex;
|
|
31
|
-
font-size: 1.1em;
|
|
32
28
|
align-items: center;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
padding: 8px 14px 10px;
|
|
31
|
+
margin-bottom: -1px;
|
|
32
|
+
background: transparent;
|
|
33
|
+
color: var(--text-2);
|
|
34
|
+
--icon-color: var(--text-2);
|
|
35
|
+
font-size: 13px;
|
|
36
|
+
font-weight: var(--w-medium);
|
|
37
|
+
border-bottom: 2px solid transparent;
|
|
41
38
|
white-space: nowrap;
|
|
42
|
-
transition:
|
|
39
|
+
transition:
|
|
40
|
+
color 100ms linear,
|
|
41
|
+
border-color 100ms linear;
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
.
|
|
46
|
-
|
|
44
|
+
.option:hover {
|
|
45
|
+
color: var(--text-1);
|
|
46
|
+
--icon-color: var(--text-1);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
.
|
|
50
|
-
|
|
49
|
+
.option.selected,
|
|
50
|
+
.option.selected:hover {
|
|
51
|
+
cursor: default;
|
|
52
|
+
color: var(--accent-700);
|
|
53
|
+
--icon-color: var(--accent-700);
|
|
54
|
+
border-bottom-color: var(--accent-600);
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
.option.
|
|
54
|
-
|
|
57
|
+
.unselect .option.selected {
|
|
58
|
+
cursor: pointer;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
.option
|
|
61
|
+
.option.hidden {
|
|
62
|
+
display: none;
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
.option .name {
|
|
@@ -70,6 +75,9 @@ export class TabPane extends RapidElement {
|
|
|
70
75
|
|
|
71
76
|
.option .badge {
|
|
72
77
|
margin-left: 0.4em;
|
|
78
|
+
margin-right: -6px;
|
|
79
|
+
display: inline-flex;
|
|
80
|
+
align-items: center;
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
@media (max-width: 900px) {
|
|
@@ -85,9 +93,6 @@ export class TabPane extends RapidElement {
|
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
|
|
88
|
-
.focusedname .option.selected {
|
|
89
|
-
}
|
|
90
|
-
|
|
91
96
|
.focusedname .option .name {
|
|
92
97
|
max-width: 0px;
|
|
93
98
|
margin: 0;
|
|
@@ -101,137 +106,81 @@ export class TabPane extends RapidElement {
|
|
|
101
106
|
max-width: 200px;
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
.option {
|
|
105
|
-
|
|
106
|
-
--icon-color: rgba(0, 0, 0, 0.5);
|
|
107
|
-
color: rgba(0, 0, 0, 0.5);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.option.selected {
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.option.selected,
|
|
114
|
-
.option.selected:hover {
|
|
115
|
-
cursor: default;
|
|
116
|
-
box-shadow: 0px -3px 3px 1px rgba(0, 0, 0, 0.02);
|
|
117
|
-
|
|
118
|
-
background: var(--focused-tab-color, #fff);
|
|
119
|
-
transform: scale(1) translateY(1px);
|
|
120
|
-
--icon-color: #666;
|
|
121
|
-
color: #666;
|
|
122
|
-
border: 1px inset rgba(0, 0, 0, 0.15);
|
|
123
|
-
border-bottom: 0px;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.option.selected .dot {
|
|
127
|
-
display: none;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.unselect .option.selected {
|
|
131
|
-
cursor: pointer;
|
|
109
|
+
.focusedname .option .name {
|
|
110
|
+
transition: all 0s linear !important;
|
|
132
111
|
}
|
|
133
112
|
|
|
134
|
-
.
|
|
135
|
-
|
|
113
|
+
.focusedname .option.selected .name {
|
|
114
|
+
transition: all 200ms linear !important;
|
|
136
115
|
}
|
|
137
116
|
|
|
138
|
-
.option
|
|
139
|
-
|
|
140
|
-
color: #666;
|
|
141
|
-
background: rgba(0, 0, 0, 0.02);
|
|
117
|
+
.option.dirty {
|
|
118
|
+
font-weight: var(--w-semibold);
|
|
142
119
|
}
|
|
143
120
|
|
|
144
|
-
.option.
|
|
145
|
-
|
|
121
|
+
.option.alert {
|
|
122
|
+
color: var(--danger);
|
|
123
|
+
--icon-color: var(--danger);
|
|
146
124
|
}
|
|
147
125
|
|
|
148
126
|
.pane {
|
|
149
127
|
display: flex;
|
|
150
128
|
flex-direction: column;
|
|
151
129
|
flex-grow: 1;
|
|
152
|
-
background: var(--focused-tab-color, #fff);
|
|
153
|
-
border-bottom-left-radius: var(--curvature);
|
|
154
|
-
border-bottom-right-radius: var(--curvature);
|
|
155
|
-
overflow: hidden;
|
|
156
|
-
|
|
157
|
-
box-shadow: var(
|
|
158
|
-
--tabs-shadow,
|
|
159
|
-
rgba(0, 0, 0, 0.1) 0px 1px 3px 0px,
|
|
160
|
-
rgba(0, 0, 0, 0.03) 0px 1px 2px 0px
|
|
161
|
-
);
|
|
162
130
|
min-height: 0;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.pane.first {
|
|
166
|
-
border-top-left-radius: 0px;
|
|
167
131
|
overflow: hidden;
|
|
168
132
|
}
|
|
169
133
|
|
|
170
134
|
.count {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
height: 0.5em;
|
|
182
|
-
width: 0.5em;
|
|
183
|
-
margin-left: 0.2em;
|
|
184
|
-
background: var(--color-primary-dark);
|
|
185
|
-
border-radius: 99px;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
.notify .count {
|
|
189
|
-
background: var(--color-alert);
|
|
190
|
-
color: #fff;
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
height: 16px;
|
|
139
|
+
padding: 0 2px;
|
|
140
|
+
color: inherit;
|
|
141
|
+
opacity: 0.6;
|
|
142
|
+
font-size: 11px;
|
|
143
|
+
font-weight: var(--w-medium);
|
|
144
|
+
font-variant-numeric: tabular-nums;
|
|
191
145
|
}
|
|
192
146
|
|
|
193
|
-
.
|
|
194
|
-
|
|
195
|
-
|
|
147
|
+
.option.selected .count,
|
|
148
|
+
.option.alert .count {
|
|
149
|
+
min-width: 16px;
|
|
150
|
+
padding: 0 4px;
|
|
151
|
+
border-radius: 999px;
|
|
152
|
+
opacity: 1;
|
|
196
153
|
}
|
|
197
154
|
|
|
198
|
-
.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
border-right: none !important;
|
|
203
|
-
border-bottom: none !important;
|
|
155
|
+
.option.selected .count {
|
|
156
|
+
background: var(--accent-100);
|
|
157
|
+
color: var(--accent-700);
|
|
158
|
+
font-weight: var(--w-semibold);
|
|
204
159
|
}
|
|
205
160
|
|
|
206
|
-
.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
border-top: none !important;
|
|
161
|
+
.option.alert .count {
|
|
162
|
+
background: var(--danger-bg);
|
|
163
|
+
color: var(--danger);
|
|
210
164
|
}
|
|
211
165
|
|
|
212
|
-
.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
166
|
+
.dot {
|
|
167
|
+
height: 0.5em;
|
|
168
|
+
width: 0.5em;
|
|
169
|
+
margin-left: 0.2em;
|
|
170
|
+
background: var(--accent-600);
|
|
171
|
+
border-radius: 99px;
|
|
216
172
|
}
|
|
217
173
|
|
|
218
|
-
.
|
|
219
|
-
|
|
174
|
+
.option.selected .dot {
|
|
175
|
+
display: none;
|
|
220
176
|
}
|
|
221
177
|
|
|
222
178
|
.check {
|
|
223
179
|
margin-left: 0.4em;
|
|
224
180
|
}
|
|
225
|
-
|
|
226
|
-
.pane {
|
|
227
|
-
display: flex;
|
|
228
|
-
}
|
|
229
181
|
`;
|
|
230
182
|
}
|
|
231
183
|
|
|
232
|
-
@property({ type: Boolean })
|
|
233
|
-
embedded = false;
|
|
234
|
-
|
|
235
184
|
@property({ type: Boolean })
|
|
236
185
|
collapses = false;
|
|
237
186
|
|
|
@@ -277,7 +226,9 @@ export class TabPane extends RapidElement {
|
|
|
277
226
|
}
|
|
278
227
|
}
|
|
279
228
|
this.options = tabs;
|
|
280
|
-
this.index
|
|
229
|
+
if (this.index < 0 || this.index >= tabs.length) {
|
|
230
|
+
this.index = 0;
|
|
231
|
+
}
|
|
281
232
|
}
|
|
282
233
|
|
|
283
234
|
public firstUpdated(
|
|
@@ -367,13 +318,11 @@ export class TabPane extends RapidElement {
|
|
|
367
318
|
}
|
|
368
319
|
|
|
369
320
|
public render(): TemplateResult {
|
|
370
|
-
const activeTab = this.options[this.index];
|
|
371
321
|
return html`
|
|
372
322
|
<div
|
|
373
323
|
class="${getClasses({
|
|
374
324
|
options: true,
|
|
375
325
|
collapses: this.collapses,
|
|
376
|
-
embedded: this.embedded,
|
|
377
326
|
focusedname: this.focusedName,
|
|
378
327
|
unselect: this.unselect
|
|
379
328
|
})}"
|
|
@@ -388,15 +337,9 @@ export class TabPane extends RapidElement {
|
|
|
388
337
|
first: index == 0,
|
|
389
338
|
selected: index == this.index,
|
|
390
339
|
hidden: tab.hidden,
|
|
391
|
-
notify: tab.notify,
|
|
392
340
|
alert: tab.alert,
|
|
393
341
|
dirty: tab.dirty
|
|
394
342
|
})}"
|
|
395
|
-
style="${tab.selectionColor && index == this.index
|
|
396
|
-
? `color:${tab.selectionColor};--icon-color:${tab.selectionColor};`
|
|
397
|
-
: ''} ${tab.selectionBackground && index == this.index
|
|
398
|
-
? `background-color:${tab.selectionBackground};`
|
|
399
|
-
: ''}"
|
|
400
343
|
>
|
|
401
344
|
${tab.icon ? html`<temba-icon name=${tab.icon} />` : null}
|
|
402
345
|
<div class="name">${tab.name} ${tab.dirty ? ` *` : ``}</div>
|
|
@@ -405,14 +348,11 @@ export class TabPane extends RapidElement {
|
|
|
405
348
|
<div class="badge">
|
|
406
349
|
${tab.count > 0 && !tab.activity
|
|
407
350
|
? html`<div class="count">
|
|
408
|
-
${tab.
|
|
351
|
+
${tab.count.toLocaleString()}
|
|
409
352
|
</div>`
|
|
410
353
|
: null}
|
|
411
354
|
${tab.activity && tab.count > 0 && !tab.dirty
|
|
412
|
-
? html`<div
|
|
413
|
-
class="dot"
|
|
414
|
-
style="background:${tab.activityColor}"
|
|
415
|
-
></div>`
|
|
355
|
+
? html`<div class="dot"></div>`
|
|
416
356
|
: null}
|
|
417
357
|
</div>
|
|
418
358
|
`
|
|
@@ -429,35 +369,7 @@ export class TabPane extends RapidElement {
|
|
|
429
369
|
<slot name="tab-right"></slot>
|
|
430
370
|
</div>
|
|
431
371
|
</div>
|
|
432
|
-
<div
|
|
433
|
-
@temba-details-changed=${this.handleTabDetailsChanged}
|
|
434
|
-
style="${activeTab?.borderColor
|
|
435
|
-
? `
|
|
436
|
-
border-top: var(--temba-tabs-border-top, 1px solid ${
|
|
437
|
-
activeTab?.borderColor || 'var(--color-widget-border)'
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
border-left: var(--temba-tabs-border-left, 1px solid ${
|
|
441
|
-
activeTab?.borderColor || 'var(--color-widget-border)'
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
border-bottom: var(--temba-tabs-border-bottom, 1px solid ${
|
|
445
|
-
activeTab?.borderColor || 'var(--color-widget-border)'
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
border-right: var(--temba-tabs-border-right, 1px solid ${
|
|
449
|
-
activeTab?.borderColor || 'var(--color-widget-border)'
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
`
|
|
453
|
-
: ''} ${activeTab?.selectionBackground
|
|
454
|
-
? `background:${activeTab?.selectionBackground};`
|
|
455
|
-
: ``}"
|
|
456
|
-
class="pane ${getClasses({
|
|
457
|
-
first: this.index == 0,
|
|
458
|
-
embedded: this.embedded
|
|
459
|
-
})}"
|
|
460
|
-
>
|
|
372
|
+
<div @temba-details-changed=${this.handleTabDetailsChanged} class="pane">
|
|
461
373
|
<slot></slot>
|
|
462
374
|
<slot name="pane-bottom"></slot>
|
|
463
375
|
</div>
|