@kubex/zinc 1.1.11 → 1.1.13
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/dist/custom-elements.json +540 -1
- package/dist/vscode.html-custom-data.json +103 -0
- package/dist/web-types.json +236 -1
- package/dist/zn.d.ts +122 -0
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +404 -353
- package/docs/pages/components/channel-tile.md +168 -0
- package/package.json +1 -1
- package/scss/themes/_typography.scss +1 -0
- package/src/components/button/button.scss +4 -0
- package/src/components/channel-tile/channel-tile.component.ts +280 -0
- package/src/components/channel-tile/channel-tile.scss +197 -0
- package/src/components/channel-tile/index.ts +12 -0
- package/src/components/page/page.scss +1 -1
- package/src/components/tooltip/tooltip.scss +2 -2
- package/src/events/events.ts +2 -0
- package/src/events/zn-accept.ts +7 -0
- package/src/events/zn-reject.ts +7 -0
- package/src/zinc.ts +1 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
meta:
|
|
3
|
+
title: Channel Tile
|
|
4
|
+
description: A channel/queue slot tile with an active (occupied) face showing an in-progress item, and an available (empty) face advertising capacity with optional auto-accept.
|
|
5
|
+
layout: component
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The channel tile represents a single slot in a queue or channel rail. It has two faces:
|
|
9
|
+
|
|
10
|
+
- **Active** — the slot is occupied by an in-progress item (header, leading icon, title/subtitle, and a progress/SLA bar).
|
|
11
|
+
- **Available** — the slot is empty and advertising capacity. When `incoming` is set it reserves an arriving item with a countdown that can auto-accept.
|
|
12
|
+
|
|
13
|
+
Tiles are designed to sit side by side in a horizontal rail; each is a fixed-width block with a right divider.
|
|
14
|
+
|
|
15
|
+
```html:preview
|
|
16
|
+
<div style="display: flex;">
|
|
17
|
+
<zn-channel-tile
|
|
18
|
+
header="Acme Support"
|
|
19
|
+
icon="chat"
|
|
20
|
+
color="info"
|
|
21
|
+
title="Jane Cooper"
|
|
22
|
+
subtitle="Billing question"
|
|
23
|
+
progress="60"
|
|
24
|
+
progress-color="rgb(var(--zn-color-info))">
|
|
25
|
+
</zn-channel-tile>
|
|
26
|
+
<zn-channel-tile available header="Acme Support" subtitle="Waiting for the next chat"></zn-channel-tile>
|
|
27
|
+
</div>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Examples
|
|
31
|
+
|
|
32
|
+
### Active Tile
|
|
33
|
+
|
|
34
|
+
The default (occupied) face. Set `header` for the top bar, `icon` + `color` for the leading badge, and `title`/`subtitle` for the item. The `progress` value (0–100) fills the SLA bar along the bottom edge.
|
|
35
|
+
|
|
36
|
+
```html:preview
|
|
37
|
+
<div style="display: flex;">
|
|
38
|
+
<zn-channel-tile
|
|
39
|
+
header="Acme Support"
|
|
40
|
+
icon="chat"
|
|
41
|
+
color="info"
|
|
42
|
+
title="Jane Cooper"
|
|
43
|
+
subtitle="Billing question"
|
|
44
|
+
progress="45"
|
|
45
|
+
progress-color="rgb(var(--zn-color-info))">
|
|
46
|
+
</zn-channel-tile>
|
|
47
|
+
</div>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Available Tile
|
|
51
|
+
|
|
52
|
+
Set `available` to render the empty face. When `title` is omitted it defaults to "Available". The leading slot shows a built-in accept button.
|
|
53
|
+
|
|
54
|
+
```html:preview
|
|
55
|
+
<div style="display: flex;">
|
|
56
|
+
<zn-channel-tile available subtitle="Ready for the next interaction"></zn-channel-tile>
|
|
57
|
+
</div>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Accepting an Item
|
|
61
|
+
|
|
62
|
+
Provide `accept-uri` and the built-in accept button becomes a link to it. When a tile is accepted, a cancelable `zn-accept` event fires before the fetch — call `preventDefault()` on it to take over the accept yourself.
|
|
63
|
+
|
|
64
|
+
```html:preview
|
|
65
|
+
<div style="display: flex;">
|
|
66
|
+
<zn-channel-tile
|
|
67
|
+
available
|
|
68
|
+
fid="chat-4821"
|
|
69
|
+
accept-uri="/queue/accept/chat-4821"
|
|
70
|
+
subtitle="Click to accept">
|
|
71
|
+
</zn-channel-tile>
|
|
72
|
+
</div>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Incoming with Auto-Accept
|
|
76
|
+
|
|
77
|
+
An `available` tile with `incoming` reserves an arriving item. Provide `reserved-until` (an epoch in seconds or milliseconds marking the end of the window) and `auto-accept-delay` (the window length in milliseconds) to drive the countdown overlay. When the window elapses the tile auto-accepts via `accept-uri`.
|
|
78
|
+
|
|
79
|
+
```html:preview
|
|
80
|
+
<div style="display: flex;">
|
|
81
|
+
<zn-channel-tile
|
|
82
|
+
available
|
|
83
|
+
incoming
|
|
84
|
+
rejectable
|
|
85
|
+
fid="chat-5500"
|
|
86
|
+
title="Incoming chat"
|
|
87
|
+
subtitle="Auto-accepting…"
|
|
88
|
+
accept-uri="/queue/accept/chat-5500"
|
|
89
|
+
auto-accept-delay="15000">
|
|
90
|
+
</zn-channel-tile>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<script>
|
|
94
|
+
// Set the reservation window relative to now so the countdown animates.
|
|
95
|
+
const tile = document.currentScript.previousElementSibling.querySelector('zn-channel-tile');
|
|
96
|
+
tile.reservedUntil = Date.now() + 15000;
|
|
97
|
+
</script>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Rejectable
|
|
101
|
+
|
|
102
|
+
Add `rejectable` to an incoming tile to offer a reject control. Pressing it emits `zn-reject` (and suppresses accept).
|
|
103
|
+
|
|
104
|
+
```html:preview
|
|
105
|
+
<div style="display: flex;">
|
|
106
|
+
<zn-channel-tile
|
|
107
|
+
available
|
|
108
|
+
incoming
|
|
109
|
+
rejectable
|
|
110
|
+
fid="call-7700"
|
|
111
|
+
title="Incoming call"
|
|
112
|
+
subtitle="Reject to pass"
|
|
113
|
+
reject-icon="call_end"
|
|
114
|
+
reject-label="Reject call">
|
|
115
|
+
</zn-channel-tile>
|
|
116
|
+
</div>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Colors
|
|
120
|
+
|
|
121
|
+
The `color` attribute sets the accent (the leading icon badge and the `--channel-tile-color` custom property). Available colors are: `default`, `primary`, `info`, `success`, `warning`, `error`, and `disabled`.
|
|
122
|
+
|
|
123
|
+
```html:preview
|
|
124
|
+
<div style="display: flex; flex-wrap: wrap;">
|
|
125
|
+
<zn-channel-tile icon="chat" color="primary" title="Primary" subtitle="Accent example"></zn-channel-tile>
|
|
126
|
+
<zn-channel-tile icon="chat" color="info" title="Info" subtitle="Accent example"></zn-channel-tile>
|
|
127
|
+
<zn-channel-tile icon="chat" color="success" title="Success" subtitle="Accent example"></zn-channel-tile>
|
|
128
|
+
<zn-channel-tile icon="chat" color="warning" title="Warning" subtitle="Accent example"></zn-channel-tile>
|
|
129
|
+
<zn-channel-tile icon="chat" color="error" title="Error" subtitle="Accent example"></zn-channel-tile>
|
|
130
|
+
<zn-channel-tile icon="chat" color="disabled" title="Disabled" subtitle="Accent example"></zn-channel-tile>
|
|
131
|
+
</div>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Custom Leading Content
|
|
135
|
+
|
|
136
|
+
Use the `leading` slot to replace the leading icon in the active state.
|
|
137
|
+
|
|
138
|
+
```html:preview
|
|
139
|
+
<div style="display: flex;">
|
|
140
|
+
<zn-channel-tile header="Acme Support" title="Jane Cooper" subtitle="VIP customer">
|
|
141
|
+
<zn-icon slot="leading" library="avatar" src="Jane Cooper" size="36" round></zn-icon>
|
|
142
|
+
</zn-channel-tile>
|
|
143
|
+
</div>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Custom Action
|
|
147
|
+
|
|
148
|
+
Use the `action` slot to replace the default accept button on an available tile — for example with a form.
|
|
149
|
+
|
|
150
|
+
```html:preview
|
|
151
|
+
<div style="display: flex;">
|
|
152
|
+
<zn-channel-tile available subtitle="Pick a queue to join">
|
|
153
|
+
<zn-button slot="action" icon="login" icon-size="20" icon-button="round" color="success"></zn-button>
|
|
154
|
+
</zn-channel-tile>
|
|
155
|
+
</div>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Footer
|
|
159
|
+
|
|
160
|
+
Use the `footer` slot for trailing content such as status badges in the active state.
|
|
161
|
+
|
|
162
|
+
```html:preview
|
|
163
|
+
<div style="display: flex;">
|
|
164
|
+
<zn-channel-tile header="Acme Support" icon="chat" color="info" title="Jane Cooper" subtitle="Billing question">
|
|
165
|
+
<zn-chip slot="footer" type="warning">SLA</zn-chip>
|
|
166
|
+
</zn-channel-tile>
|
|
167
|
+
</div>
|
|
168
|
+
```
|
package/package.json
CHANGED
|
@@ -24,6 +24,7 @@ $text-styles: (
|
|
|
24
24
|
"h1": ("size": 16px, "line": 16px, "spacing": 18px, "weight": semibold, "color": default, "transform": none),
|
|
25
25
|
"h2": ("size": 14px, "line": 16px, "spacing": 15px, "weight": semibold, "color": default, "transform": none),
|
|
26
26
|
"h3": ("size": 11px, "line": 16px, "spacing": 15px, "weight": semibold, "color": default, "transform": none),
|
|
27
|
+
"heading-section": ("size": 14px, "line": 16px, "spacing": 15px, "weight": semibold, "color": default, "transform": none),
|
|
27
28
|
"subheading": ("size": 14px, "line": 16px, "spacing": 15px, "weight": normal, "color": muted, "transform": none),
|
|
28
29
|
"subheading-small": ("size": 13px, "line": 16px, "spacing": 15px, "weight": normal, "color": muted, "transform": none),
|
|
29
30
|
"input-placeholder": ("size": 14px, "line": 24px, "spacing": 18px, "weight": normal, "color": muted, "transform": none),
|
|
@@ -319,6 +319,10 @@
|
|
|
319
319
|
background: rgb(var(--zn-panel));
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
&.button--icon-button-round:hover:not([disabled]), &.button--icon-button-round:active:not([disabled]) {
|
|
323
|
+
background: rgb(var(--zn-color-tab));
|
|
324
|
+
}
|
|
325
|
+
|
|
322
326
|
&.button--plain:hover:not([disabled]), &.button--plain:active:not([disabled]) {
|
|
323
327
|
background: transparent;
|
|
324
328
|
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import {classMap} from 'lit/directives/class-map.js';
|
|
2
|
+
import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
|
|
3
|
+
import {property} from 'lit/decorators.js';
|
|
4
|
+
import ZincElement from '../../internal/zinc-element';
|
|
5
|
+
|
|
6
|
+
import styles from './channel-tile.scss';
|
|
7
|
+
|
|
8
|
+
export type ChannelTileColor =
|
|
9
|
+
'default' | 'primary' | 'info' | 'success' | 'warning' | 'error' | 'disabled';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @summary A channel/queue slot tile with two faces: an active (occupied) state
|
|
13
|
+
* showing an in-progress item, and an available (empty) state advertising
|
|
14
|
+
* capacity — optionally reserving an incoming item with an auto-accept countdown.
|
|
15
|
+
* @documentation https://zinc.style/components/channel-tile
|
|
16
|
+
* @status experimental
|
|
17
|
+
* @since 1.0
|
|
18
|
+
*
|
|
19
|
+
* @dependency zn-button
|
|
20
|
+
* @dependency zn-icon
|
|
21
|
+
*
|
|
22
|
+
* @event zn-accept - Emitted when an available tile is accepted (click or auto-accept).
|
|
23
|
+
* Cancelable — call `preventDefault()` to suppress the built-in `accept-uri` fetch.
|
|
24
|
+
* @event zn-reject - Emitted when the reject control is pressed.
|
|
25
|
+
*
|
|
26
|
+
* @slot leading - Replaces the leading icon in the active state.
|
|
27
|
+
* @slot action - Action content for the available state (e.g. a form). Falls back to a default accept button.
|
|
28
|
+
* @slot footer - Trailing content (e.g. status badges) in the active state.
|
|
29
|
+
*
|
|
30
|
+
* @csspart base - The component's base wrapper.
|
|
31
|
+
* @csspart progress - The progress indicator (incoming countdown overlay or active progress bar).
|
|
32
|
+
*
|
|
33
|
+
* @cssproperty --channel-tile-color - Resolved accent color (set from the `color` property).
|
|
34
|
+
*/
|
|
35
|
+
export default class ZnChannelTile extends ZincElement {
|
|
36
|
+
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
37
|
+
|
|
38
|
+
/** Renders the empty/available face instead of the active face. */
|
|
39
|
+
@property({type: Boolean, reflect: true}) available: boolean = false;
|
|
40
|
+
|
|
41
|
+
/** (Available only) the tile is reserving an incoming item awaiting acceptance. */
|
|
42
|
+
@property({type: Boolean, reflect: true}) incoming: boolean = false;
|
|
43
|
+
|
|
44
|
+
/** Free-form grouping/theming key (reflected so consumers can query/style by it). */
|
|
45
|
+
@property({reflect: true}) variant: string = '';
|
|
46
|
+
|
|
47
|
+
/** Top-bar text (e.g. brand). */
|
|
48
|
+
@property() header: string = '';
|
|
49
|
+
|
|
50
|
+
/** Leading icon (active state). */
|
|
51
|
+
@property() icon: string = '';
|
|
52
|
+
|
|
53
|
+
/** Accent color driving the leading icon and `--channel-tile-color`. */
|
|
54
|
+
@property({reflect: true}) color: ChannelTileColor = 'default';
|
|
55
|
+
|
|
56
|
+
/** Primary line. Defaults to "Available" in the available state when unset. */
|
|
57
|
+
@property() title: string = '';
|
|
58
|
+
|
|
59
|
+
/** Secondary line. */
|
|
60
|
+
@property() subtitle: string = '';
|
|
61
|
+
|
|
62
|
+
/** (Active only) progress bar fill, 0–100. */
|
|
63
|
+
@property({type: Number}) progress: number = 0;
|
|
64
|
+
|
|
65
|
+
/** (Active only) CSS color for the progress bar fill. */
|
|
66
|
+
@property({attribute: 'progress-color'}) progressColor: string = '';
|
|
67
|
+
|
|
68
|
+
/** Identifier carried in `zn-accept` / `zn-reject` event details. */
|
|
69
|
+
@property() fid: string = '';
|
|
70
|
+
|
|
71
|
+
/** (Available only) when set, accepting fetches this URI unless `zn-accept` is canceled. */
|
|
72
|
+
@property({attribute: 'accept-uri'}) acceptUri: string = '';
|
|
73
|
+
|
|
74
|
+
/** (Available/incoming) epoch (seconds or millis) at which the reservation window ends. */
|
|
75
|
+
@property({type: Number, attribute: 'reserved-until'}) reservedUntil: number = 0;
|
|
76
|
+
|
|
77
|
+
/** (Available/incoming) auto-accept window length in milliseconds. */
|
|
78
|
+
@property({type: Number, attribute: 'auto-accept-delay'}) autoAcceptDelay: number = 0;
|
|
79
|
+
|
|
80
|
+
/** (Available/incoming) whether a reject control is offered. */
|
|
81
|
+
@property({type: Boolean, reflect: true}) rejectable: boolean = false;
|
|
82
|
+
|
|
83
|
+
/** Icon for the built-in accept button. */
|
|
84
|
+
@property({attribute: 'accept-icon'}) acceptIcon: string = 'plus@lu';
|
|
85
|
+
|
|
86
|
+
/** Optional analytics id forwarded to the built-in accept button. */
|
|
87
|
+
@property({attribute: 'accept-gaid'}) acceptGaid: string = '';
|
|
88
|
+
|
|
89
|
+
/** Icon for the reject control. */
|
|
90
|
+
@property({attribute: 'reject-icon'}) rejectIcon: string = 'call_end';
|
|
91
|
+
|
|
92
|
+
/** Accessible label for the reject control. */
|
|
93
|
+
@property({attribute: 'reject-label'}) rejectLabel: string = 'Reject';
|
|
94
|
+
|
|
95
|
+
private _tickInterval: number | undefined;
|
|
96
|
+
private _autoAcceptFired: string = '';
|
|
97
|
+
|
|
98
|
+
protected firstUpdated(changed: PropertyValues) {
|
|
99
|
+
super.firstUpdated(changed);
|
|
100
|
+
this._startTicker();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
disconnectedCallback() {
|
|
104
|
+
this._stopTicker();
|
|
105
|
+
super.disconnectedCallback();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected updated(changed: PropertyValues) {
|
|
109
|
+
super.updated(changed);
|
|
110
|
+
if (changed.has('fid') && this.fid !== this._autoAcceptFired) {
|
|
111
|
+
this._autoAcceptFired = '';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private _isCountingDown(): boolean {
|
|
116
|
+
return this.available && this.incoming;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private _startTicker() {
|
|
120
|
+
if (this._tickInterval !== undefined) return;
|
|
121
|
+
this._tickInterval = window.setInterval(() => {
|
|
122
|
+
// Only the incoming countdown needs a self-driven re-render; active
|
|
123
|
+
// tiles are refreshed by the consumer setting `progress`/`color`.
|
|
124
|
+
if (!this._isCountingDown()) return;
|
|
125
|
+
this.requestUpdate();
|
|
126
|
+
this._maybeAutoAccept();
|
|
127
|
+
}, 200);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private _stopTicker() {
|
|
131
|
+
if (this._tickInterval !== undefined) {
|
|
132
|
+
clearInterval(this._tickInterval);
|
|
133
|
+
this._tickInterval = undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private _maybeAutoAccept() {
|
|
138
|
+
if (!this._isCountingDown()) return;
|
|
139
|
+
if (!this.acceptUri) return;
|
|
140
|
+
if (!this.autoAcceptDelay || !this.reservedUntil) return;
|
|
141
|
+
if (this._autoAcceptFired === this.fid) return;
|
|
142
|
+
|
|
143
|
+
const until = this._reservedUntilMs();
|
|
144
|
+
if (!until || Date.now() < until) return;
|
|
145
|
+
|
|
146
|
+
this._autoAcceptFired = this.fid;
|
|
147
|
+
this._accept();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private _accept(): void {
|
|
151
|
+
const event = this.emit('zn-accept', {
|
|
152
|
+
cancelable: true,
|
|
153
|
+
detail: {fid: this.fid, acceptUri: this.acceptUri},
|
|
154
|
+
});
|
|
155
|
+
if (event.defaultPrevented || !this.acceptUri) return;
|
|
156
|
+
fetch(this.acceptUri, {credentials: 'same-origin'}).catch((err) => {
|
|
157
|
+
console.error('Error accepting interaction', err);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private _reservedUntilMs(): number {
|
|
162
|
+
if (!this.reservedUntil) return 0;
|
|
163
|
+
return this.reservedUntil.toString().length === 10
|
|
164
|
+
? this.reservedUntil * 1000
|
|
165
|
+
: this.reservedUntil;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private _countdownPercent(): number {
|
|
169
|
+
const until = this._reservedUntilMs();
|
|
170
|
+
if (!until || !this.autoAcceptDelay) return 0;
|
|
171
|
+
const total = this.autoAcceptDelay;
|
|
172
|
+
const remaining = Math.max(0, until - Date.now());
|
|
173
|
+
const elapsed = Math.max(0, total - remaining);
|
|
174
|
+
return Math.min(100, Math.max(0, (elapsed / total) * 100));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private _handleReject = (e: MouseEvent) => {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
e.stopPropagation();
|
|
180
|
+
this.emit('zn-reject', {detail: {fid: this.fid, variant: this.variant}});
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
private _handleClick = (e: MouseEvent) => {
|
|
184
|
+
if (!this._isCountingDown() || !this.acceptUri) return;
|
|
185
|
+
if (e.defaultPrevented) return;
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
e.stopPropagation();
|
|
188
|
+
this._accept();
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
protected render(): unknown {
|
|
192
|
+
const title = this.title || (this.available ? 'Available' : '');
|
|
193
|
+
const countdown = this._isCountingDown() ? this._countdownPercent() : null;
|
|
194
|
+
|
|
195
|
+
return html`
|
|
196
|
+
<div
|
|
197
|
+
part="base"
|
|
198
|
+
class=${classMap({
|
|
199
|
+
'channel-tile': true,
|
|
200
|
+
'channel-tile--available': this.available,
|
|
201
|
+
'channel-tile--active': !this.available,
|
|
202
|
+
'channel-tile--incoming': this.available && this.incoming,
|
|
203
|
+
[`channel-tile--${this.color}`]: true,
|
|
204
|
+
})}
|
|
205
|
+
role="button"
|
|
206
|
+
tabindex="0"
|
|
207
|
+
@click=${this._handleClick}>
|
|
208
|
+
<div class="channel-tile__header">${this.header ? html`<h3>${this.header}</h3>` : ''}</div>
|
|
209
|
+
${countdown !== null
|
|
210
|
+
? html`
|
|
211
|
+
<div part="progress" class="channel-tile__overlay" style="width: ${countdown}%"></div>`
|
|
212
|
+
: ''}
|
|
213
|
+
<div class="channel-tile__body">
|
|
214
|
+
${this._renderLeading()}
|
|
215
|
+
<div class="channel-tile__content">
|
|
216
|
+
<h3 class="channel-tile__title">${title}</h3>
|
|
217
|
+
<p class="channel-tile__subtitle">${this.subtitle}</p>
|
|
218
|
+
</div>
|
|
219
|
+
<slot name="footer" class="channel-tile__footer"></slot>
|
|
220
|
+
</div>
|
|
221
|
+
${!this.available
|
|
222
|
+
? html`
|
|
223
|
+
<div part="progress" class="channel-tile__bar">
|
|
224
|
+
<div class="channel-tile__bar-fill"
|
|
225
|
+
style="width: ${this.progress}%; background-color: ${this.progressColor || 'transparent'};"></div>
|
|
226
|
+
</div>`
|
|
227
|
+
: ''}
|
|
228
|
+
</div>`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private _renderLeading() {
|
|
232
|
+
if (this.available) {
|
|
233
|
+
return this._renderAvailableAction();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return html`
|
|
237
|
+
<slot name="leading">
|
|
238
|
+
${this.icon
|
|
239
|
+
? html`
|
|
240
|
+
<zn-button icon=${this.icon}
|
|
241
|
+
icon-size="20"
|
|
242
|
+
icon-button="round"
|
|
243
|
+
color=${this.color}
|
|
244
|
+
no-hover></zn-button>`
|
|
245
|
+
: ''}
|
|
246
|
+
</slot>`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private _renderAvailableAction() {
|
|
250
|
+
if (this.incoming && this.rejectable) {
|
|
251
|
+
return html`
|
|
252
|
+
<button class="channel-tile__reject" type="button" aria-label=${this.rejectLabel}
|
|
253
|
+
@click=${this._handleReject}>
|
|
254
|
+
<zn-icon src=${this.rejectIcon} size="22"></zn-icon>
|
|
255
|
+
</button>`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (this.acceptUri) {
|
|
259
|
+
return html`
|
|
260
|
+
<zn-button href=${this.acceptUri}
|
|
261
|
+
icon=${this.acceptIcon}
|
|
262
|
+
icon-size="20"
|
|
263
|
+
icon-button="round"
|
|
264
|
+
gaid=${this.acceptGaid}
|
|
265
|
+
loading-text="Accepting"
|
|
266
|
+
no-hover>
|
|
267
|
+
</zn-button>`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return html`
|
|
271
|
+
<slot name="action">
|
|
272
|
+
<zn-button icon=${this.acceptIcon}
|
|
273
|
+
icon-size="20"
|
|
274
|
+
icon-button="round"
|
|
275
|
+
gaid=${this.acceptGaid}
|
|
276
|
+
no-hover>
|
|
277
|
+
</zn-button>
|
|
278
|
+
</slot>`;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
--channel-tile-color: var(--zn-color-border);
|
|
3
|
+
|
|
4
|
+
flex: 1 1 280px;
|
|
5
|
+
width: 280px;
|
|
6
|
+
min-width: 180px;
|
|
7
|
+
max-width: 320px;
|
|
8
|
+
display: block;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.channel-tile {
|
|
12
|
+
position: relative;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
height: 100%;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
border-right: 1px solid rgb(var(--zn-border-color));
|
|
19
|
+
background-color: rgb(var(--zn-body));
|
|
20
|
+
transition: background-color 0.15s ease;
|
|
21
|
+
user-select: none;
|
|
22
|
+
|
|
23
|
+
&:focus-visible {
|
|
24
|
+
outline: 2px solid rgb(var(--zn-primary));
|
|
25
|
+
outline-offset: -2px;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
:host(.active) .channel-tile {
|
|
30
|
+
background-color: rgb(var(--zn-panel));
|
|
31
|
+
cursor: default;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:host(:not(.active)) .channel-tile--active:hover {
|
|
35
|
+
background-color: rgb(var(--zn-panel));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.channel-tile--incoming {
|
|
39
|
+
background-color: rgba(var(--zn-color-success), 0.15);
|
|
40
|
+
|
|
41
|
+
&:hover {
|
|
42
|
+
background-color: rgba(var(--zn-color-success), 0.22);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.channel-tile--disabled .channel-tile__body {
|
|
47
|
+
opacity: 0.6;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.channel-tile__header {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
flex-shrink: 0;
|
|
54
|
+
height: 24px;
|
|
55
|
+
padding: 0 var(--zn-spacing-small);
|
|
56
|
+
background-color: rgb(var(--zn-color-tab));
|
|
57
|
+
|
|
58
|
+
h3 {
|
|
59
|
+
margin: 0;
|
|
60
|
+
font-size: var(--zn-text-h3-font-size);
|
|
61
|
+
line-height: var(--zn-text-h3-line-height);
|
|
62
|
+
font-weight: var(--zn-text-h3-font-weight);
|
|
63
|
+
color: rgb(var(--zn-text-heading));
|
|
64
|
+
text-overflow: ellipsis;
|
|
65
|
+
white-space: nowrap;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.channel-tile__overlay {
|
|
71
|
+
position: absolute;
|
|
72
|
+
top: 0;
|
|
73
|
+
left: 0;
|
|
74
|
+
bottom: 0;
|
|
75
|
+
width: 0;
|
|
76
|
+
background-color: rgba(var(--zn-color-success), 0.55);
|
|
77
|
+
transition: width 0.2s linear;
|
|
78
|
+
pointer-events: none;
|
|
79
|
+
z-index: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.channel-tile__body {
|
|
83
|
+
position: relative;
|
|
84
|
+
flex: 1;
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: var(--zn-spacing-small);
|
|
88
|
+
padding: var(--zn-spacing-x-small) var(--zn-spacing-small);
|
|
89
|
+
width: 100%;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
z-index: 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.channel-tile__content {
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
flex-grow: 1;
|
|
98
|
+
gap: 4px;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
|
|
101
|
+
@container (max-width: 200px) {
|
|
102
|
+
display: none;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.channel-tile__title {
|
|
107
|
+
margin: 0;
|
|
108
|
+
font-size: var(--zn-text-heading-section-font-size);
|
|
109
|
+
line-height: var(--zn-text-heading-section-line-height);
|
|
110
|
+
font-weight: var(--zn-text-heading-section-font-weight);
|
|
111
|
+
color: var(--zn-text-heading-section-color);
|
|
112
|
+
text-transform: var(--zn-text-heading-section-transform);
|
|
113
|
+
text-overflow: ellipsis;
|
|
114
|
+
white-space: nowrap;
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.channel-tile__subtitle {
|
|
119
|
+
margin: 0;
|
|
120
|
+
font-size: var(--zn-text-subheading-small-font-size);
|
|
121
|
+
line-height: var(--zn-text-subheading-small-line-height);
|
|
122
|
+
font-weight: var(--zn-text-subheading-small-font-weight);
|
|
123
|
+
color: var(--zn-text-subheading-small-color);
|
|
124
|
+
text-transform: var(--zn-text-subheading-small-transform);
|
|
125
|
+
text-overflow: ellipsis;
|
|
126
|
+
white-space: nowrap;
|
|
127
|
+
overflow: hidden;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.channel-tile__footer {
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
align-self: flex-start;
|
|
134
|
+
gap: var(--zn-spacing-small);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.channel-tile__reject {
|
|
138
|
+
flex-shrink: 0;
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
justify-content: center;
|
|
142
|
+
width: 36px;
|
|
143
|
+
height: 36px;
|
|
144
|
+
border-radius: 50%;
|
|
145
|
+
border: none;
|
|
146
|
+
padding: 0;
|
|
147
|
+
margin: 0;
|
|
148
|
+
background-color: rgb(var(--zn-panel));
|
|
149
|
+
color: rgb(var(--zn-color-error));
|
|
150
|
+
cursor: pointer;
|
|
151
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
|
152
|
+
|
|
153
|
+
&:hover {
|
|
154
|
+
background-color: rgba(var(--zn-color-error), 0.1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.channel-tile__bar {
|
|
159
|
+
position: absolute;
|
|
160
|
+
bottom: 0;
|
|
161
|
+
left: 0;
|
|
162
|
+
right: 0;
|
|
163
|
+
height: 3px;
|
|
164
|
+
background-color: rgba(var(--zn-shadow), 0.2);
|
|
165
|
+
overflow: hidden;
|
|
166
|
+
z-index: 1;
|
|
167
|
+
|
|
168
|
+
&-fill {
|
|
169
|
+
height: 100%;
|
|
170
|
+
width: 0;
|
|
171
|
+
transition: width 0.5s linear, background-color 0.5s ease;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.channel-tile--primary {
|
|
176
|
+
--channel-tile-color: var(--zn-color-primary);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.channel-tile--info {
|
|
180
|
+
--channel-tile-color: var(--zn-color-info);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.channel-tile--success {
|
|
184
|
+
--channel-tile-color: var(--zn-color-success);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.channel-tile--warning {
|
|
188
|
+
--channel-tile-color: var(--zn-color-warning);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.channel-tile--error {
|
|
192
|
+
--channel-tile-color: var(--zn-color-error);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.channel-tile--disabled {
|
|
196
|
+
--channel-tile-color: var(--zn-color-disabled, 204, 204, 204);
|
|
197
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ZnChannelTile from './channel-tile.component';
|
|
2
|
+
|
|
3
|
+
export * from './channel-tile.component';
|
|
4
|
+
export default ZnChannelTile;
|
|
5
|
+
|
|
6
|
+
ZnChannelTile.define('zn-channel-tile');
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLElementTagNameMap {
|
|
10
|
+
'zn-channel-tile': ZnChannelTile;
|
|
11
|
+
}
|
|
12
|
+
}
|