@kubex/zinc 1.1.13 → 1.1.14
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 +198 -7
- package/dist/vscode.html-custom-data.json +39 -2
- package/dist/web-types.json +77 -4
- package/dist/zn.d.ts +61 -4
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +318 -306
- package/docs/pages/components/channel-tile.md +3 -3
- package/docs/pages/components/chat-message-attachment.md +54 -0
- package/docs/pages/components/chat-message.md +18 -1
- package/docs/pages/components/data-table.md +1 -3
- package/package.json +1 -1
- package/scss/shared/_table.scss +9 -23
- package/scss/themes/_dark.scss +3 -0
- package/scss/themes/_light.scss +4 -0
- package/src/components/channel-tile/channel-tile.component.ts +6 -6
- package/src/components/chat-message/chat-message.component.ts +14 -5
- package/src/components/chat-message/chat-message.scss +37 -12
- package/src/components/chat-message-attachment/chat-message-attachment.component.ts +78 -0
- package/src/components/chat-message-attachment/chat-message-attachment.scss +32 -0
- package/src/components/chat-message-attachment/chat-message-attachment.test.ts +36 -0
- package/src/components/chat-message-attachment/index.ts +12 -0
- package/src/components/data-table/data-table.component.ts +65 -46
- package/src/components/data-table/data-table.scss +32 -58
- package/src/components/icon/icon.component.ts +1 -1
- package/src/components/icon/icon.scss +2 -1
- package/src/components/page/page.component.ts +32 -1
- package/src/components/page/page.scss +5 -4
- package/src/components/tooltip/tooltip.scss +1 -1
- package/src/events/zn-accept.ts +1 -1
- package/src/events/zn-reject.ts +1 -1
- package/src/zinc.ts +1 -0
|
@@ -65,7 +65,7 @@ Provide `accept-uri` and the built-in accept button becomes a link to it. When a
|
|
|
65
65
|
<div style="display: flex;">
|
|
66
66
|
<zn-channel-tile
|
|
67
67
|
available
|
|
68
|
-
|
|
68
|
+
item-id="chat-4821"
|
|
69
69
|
accept-uri="/queue/accept/chat-4821"
|
|
70
70
|
subtitle="Click to accept">
|
|
71
71
|
</zn-channel-tile>
|
|
@@ -82,7 +82,7 @@ An `available` tile with `incoming` reserves an arriving item. Provide `reserved
|
|
|
82
82
|
available
|
|
83
83
|
incoming
|
|
84
84
|
rejectable
|
|
85
|
-
|
|
85
|
+
item-id="chat-5500"
|
|
86
86
|
title="Incoming chat"
|
|
87
87
|
subtitle="Auto-accepting…"
|
|
88
88
|
accept-uri="/queue/accept/chat-5500"
|
|
@@ -107,7 +107,7 @@ Add `rejectable` to an incoming tile to offer a reject control. Pressing it emit
|
|
|
107
107
|
available
|
|
108
108
|
incoming
|
|
109
109
|
rejectable
|
|
110
|
-
|
|
110
|
+
item-id="call-7700"
|
|
111
111
|
title="Incoming call"
|
|
112
112
|
subtitle="Reject to pass"
|
|
113
113
|
reject-icon="call_end"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
meta:
|
|
3
|
+
title: Chat Message Attachment
|
|
4
|
+
description: A file or link attachment for a chat message, rendered as an icon and label.
|
|
5
|
+
layout: component
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`zn-chat-message-attachment` represents a single file or link beneath a message. It is intended to be
|
|
9
|
+
used only inside a [`zn-chat-message`](/components/chat-message) — it automatically assigns itself to
|
|
10
|
+
that component's `attachments` slot, so you can drop it straight into the message.
|
|
11
|
+
|
|
12
|
+
```html:preview
|
|
13
|
+
<zn-chat-message sender="Agent Name" time="1718193420">
|
|
14
|
+
Here are the documents you requested.
|
|
15
|
+
<zn-chat-message-attachment href="#" name="invoice.pdf"></zn-chat-message-attachment>
|
|
16
|
+
<zn-chat-message-attachment href="#" name="statement.pdf"></zn-chat-message-attachment>
|
|
17
|
+
</zn-chat-message>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### Custom Icon
|
|
23
|
+
|
|
24
|
+
Use the `icon` attribute to change the leading icon (any `zn-icon` name).
|
|
25
|
+
|
|
26
|
+
```html:preview
|
|
27
|
+
<zn-chat-message sender="Agent Name" time="1718193420">
|
|
28
|
+
A couple of links for you.
|
|
29
|
+
<zn-chat-message-attachment href="#" name="View dashboard" icon="link"></zn-chat-message-attachment>
|
|
30
|
+
<zn-chat-message-attachment href="#" name="screenshot.png" icon="image"></zn-chat-message-attachment>
|
|
31
|
+
</zn-chat-message>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Download
|
|
35
|
+
|
|
36
|
+
Set `download` to prompt a download rather than navigating to the link.
|
|
37
|
+
|
|
38
|
+
```html:preview
|
|
39
|
+
<zn-chat-message sender="Agent Name" time="1718193420">
|
|
40
|
+
Your export is ready.
|
|
41
|
+
<zn-chat-message-attachment href="#" name="export.csv" download></zn-chat-message-attachment>
|
|
42
|
+
</zn-chat-message>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Custom Label
|
|
46
|
+
|
|
47
|
+
The default slot overrides the `name` attribute when you need richer label content.
|
|
48
|
+
|
|
49
|
+
```html:preview
|
|
50
|
+
<zn-chat-message sender="Agent Name" time="1718193420">
|
|
51
|
+
See attached.
|
|
52
|
+
<zn-chat-message-attachment href="#">contract_<strong>final</strong>.pdf</zn-chat-message-attachment>
|
|
53
|
+
</zn-chat-message>
|
|
54
|
+
```
|
|
@@ -50,7 +50,24 @@ Use the `edit-dialog-trigger` slot to add an action to the end of the bubble, su
|
|
|
50
50
|
</zn-chat-message>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
###
|
|
53
|
+
### Attachments
|
|
54
|
+
|
|
55
|
+
Use [`zn-chat-message-attachment`](/components/chat-message-attachment) to display files or links
|
|
56
|
+
beneath the message content. They are automatically placed in the `attachments` slot and wrap onto
|
|
57
|
+
multiple lines when they overflow.
|
|
58
|
+
|
|
59
|
+
```html:preview
|
|
60
|
+
<zn-chat-message sender="Agent Name" time="1718193420">
|
|
61
|
+
The customer disconnected before I could answer his questions.
|
|
62
|
+
<zn-button slot="edit-dialog-trigger" icon="close" icon-button plain no-hover
|
|
63
|
+
color="error" tooltip="Remove Note"></zn-button>
|
|
64
|
+
<zn-chat-message-attachment href="#" name="this_is_an_attachment.pdf"></zn-chat-message-attachment>
|
|
65
|
+
<zn-chat-message-attachment href="#" name="quarterly_report.xlsx"></zn-chat-message-attachment>
|
|
66
|
+
<zn-chat-message-attachment href="#" name="screenshot.png"></zn-chat-message-attachment>
|
|
67
|
+
</zn-chat-message>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Custom Bubble Color
|
|
54
71
|
|
|
55
72
|
The bubble background can be themed with the `--message-background` custom property.
|
|
56
73
|
|
|
@@ -20,8 +20,6 @@ fullWidth: true
|
|
|
20
20
|
{"key":"date","label":"Date Joined", "sortable":true}
|
|
21
21
|
]'>
|
|
22
22
|
|
|
23
|
-
<zn-data-table-search slot="search" placeholder="Search records..."></zn-data-table-search>
|
|
24
|
-
|
|
25
23
|
</zn-data-table>
|
|
26
24
|
```
|
|
27
25
|
|
|
@@ -792,4 +790,4 @@ For POST requests, the table sends:
|
|
|
792
790
|
}
|
|
793
791
|
```
|
|
794
792
|
|
|
795
|
-
Additional parameters from the `inputs` slot are merged into the request.
|
|
793
|
+
Additional parameters from the `inputs` slot are merged into the request.
|
package/package.json
CHANGED
package/scss/shared/_table.scss
CHANGED
|
@@ -8,14 +8,16 @@ table:not(.remarkd-table) {
|
|
|
8
8
|
max-width: 100%;
|
|
9
9
|
table-layout: auto;
|
|
10
10
|
text-align: left;
|
|
11
|
-
|
|
12
|
-
line-height: var(--zn-text-table-content-line-height);
|
|
13
|
-
font-weight: var(--zn-text-table-content-font-weight);
|
|
11
|
+
@include m.text-style(table-content);
|
|
14
12
|
|
|
15
13
|
thead {
|
|
16
14
|
font-weight: var(--zn-font-weight-medium);
|
|
17
15
|
}
|
|
18
16
|
|
|
17
|
+
thead tr th {
|
|
18
|
+
background-color: rgb(var(--zn-table-header-background-color));
|
|
19
|
+
}
|
|
20
|
+
|
|
19
21
|
tbody {
|
|
20
22
|
//background-color: rgba(var(--zn-panel), var(--zn-panel-opacity));
|
|
21
23
|
|
|
@@ -33,28 +35,10 @@ table:not(.remarkd-table) {
|
|
|
33
35
|
border-top: 0 !important;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
tbody tr:nth-of-type(even) {
|
|
37
|
-
background-color: rgba(var(--zn-body));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
thead tr th:first-of-type,
|
|
41
|
-
tbody tr td:first-of-type {
|
|
42
|
-
padding-left: var(--zn-base-gap, 14px);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
thead tr th:last-of-type,
|
|
46
|
-
tbody tr td:last-of-type {
|
|
47
|
-
padding-right: var(--zn-base-gap, 14px);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
38
|
th, td {
|
|
51
|
-
padding:
|
|
39
|
+
padding: var(--zn-spacing-small) var(--zn-spacing-medium);
|
|
52
40
|
min-width: 90px;
|
|
53
41
|
white-space: nowrap;
|
|
54
|
-
|
|
55
|
-
&:first-child {
|
|
56
|
-
padding-left: 0;
|
|
57
|
-
}
|
|
58
42
|
}
|
|
59
43
|
|
|
60
44
|
td > span {
|
|
@@ -62,7 +46,9 @@ table:not(.remarkd-table) {
|
|
|
62
46
|
}
|
|
63
47
|
|
|
64
48
|
th {
|
|
65
|
-
|
|
49
|
+
@include m.text-style(button-label);
|
|
50
|
+
text-transform: capitalize;
|
|
51
|
+
border-top: 1px solid rgb(var(--zn-border-color));
|
|
66
52
|
border-bottom: 1px solid rgb(var(--zn-border-color));
|
|
67
53
|
}
|
|
68
54
|
}
|
package/scss/themes/_dark.scss
CHANGED
package/scss/themes/_light.scss
CHANGED
|
@@ -66,7 +66,7 @@ export default class ZnChannelTile extends ZincElement {
|
|
|
66
66
|
@property({attribute: 'progress-color'}) progressColor: string = '';
|
|
67
67
|
|
|
68
68
|
/** Identifier carried in `zn-accept` / `zn-reject` event details. */
|
|
69
|
-
@property()
|
|
69
|
+
@property({attribute: 'item-id'}) itemId: string = '';
|
|
70
70
|
|
|
71
71
|
/** (Available only) when set, accepting fetches this URI unless `zn-accept` is canceled. */
|
|
72
72
|
@property({attribute: 'accept-uri'}) acceptUri: string = '';
|
|
@@ -107,7 +107,7 @@ export default class ZnChannelTile extends ZincElement {
|
|
|
107
107
|
|
|
108
108
|
protected updated(changed: PropertyValues) {
|
|
109
109
|
super.updated(changed);
|
|
110
|
-
if (changed.has('
|
|
110
|
+
if (changed.has('itemId') && this.itemId !== this._autoAcceptFired) {
|
|
111
111
|
this._autoAcceptFired = '';
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -138,19 +138,19 @@ export default class ZnChannelTile extends ZincElement {
|
|
|
138
138
|
if (!this._isCountingDown()) return;
|
|
139
139
|
if (!this.acceptUri) return;
|
|
140
140
|
if (!this.autoAcceptDelay || !this.reservedUntil) return;
|
|
141
|
-
if (this._autoAcceptFired === this.
|
|
141
|
+
if (this._autoAcceptFired === this.itemId) return;
|
|
142
142
|
|
|
143
143
|
const until = this._reservedUntilMs();
|
|
144
144
|
if (!until || Date.now() < until) return;
|
|
145
145
|
|
|
146
|
-
this._autoAcceptFired = this.
|
|
146
|
+
this._autoAcceptFired = this.itemId;
|
|
147
147
|
this._accept();
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
private _accept(): void {
|
|
151
151
|
const event = this.emit('zn-accept', {
|
|
152
152
|
cancelable: true,
|
|
153
|
-
detail: {
|
|
153
|
+
detail: {itemId: this.itemId, acceptUri: this.acceptUri},
|
|
154
154
|
});
|
|
155
155
|
if (event.defaultPrevented || !this.acceptUri) return;
|
|
156
156
|
fetch(this.acceptUri, {credentials: 'same-origin'}).catch((err) => {
|
|
@@ -177,7 +177,7 @@ export default class ZnChannelTile extends ZincElement {
|
|
|
177
177
|
private _handleReject = (e: MouseEvent) => {
|
|
178
178
|
e.preventDefault();
|
|
179
179
|
e.stopPropagation();
|
|
180
|
-
this.emit('zn-reject', {detail: {
|
|
180
|
+
this.emit('zn-reject', {detail: {itemId: this.itemId, variant: this.variant}});
|
|
181
181
|
};
|
|
182
182
|
|
|
183
183
|
private _handleClick = (e: MouseEvent) => {
|
|
@@ -23,6 +23,8 @@ export type ChatMessageActionType = '' | 'connected.agent' | 'attachment.added'
|
|
|
23
23
|
*
|
|
24
24
|
* @slot - The message content. Ignored when the `message` attribute is set.
|
|
25
25
|
* @slot badge - Rendered in the header after the sender and time (e.g. an INTERNAL NOTE chip).
|
|
26
|
+
* @slot attachments - Attachments displayed beneath the message content. Use `zn-chat-message-attachment`,
|
|
27
|
+
* which auto-assigns itself to this slot.
|
|
26
28
|
* @slot edit-dialog-trigger - Action rendered at the end of the bubble (e.g. a remove icon button).
|
|
27
29
|
* @slot edit-dialog - Pass-through for an associated dialog element.
|
|
28
30
|
*
|
|
@@ -32,9 +34,10 @@ export type ChatMessageActionType = '' | 'connected.agent' | 'attachment.added'
|
|
|
32
34
|
* @csspart header - The sender/time/badge row.
|
|
33
35
|
* @csspart bubble - The message bubble.
|
|
34
36
|
* @csspart content - The message content within the bubble.
|
|
37
|
+
* @csspart attachments - The attachments row beneath the message content.
|
|
35
38
|
* @csspart system-card - The card rendered for system action types.
|
|
36
39
|
*
|
|
37
|
-
* @cssproperty --message-background - The bubble's background
|
|
40
|
+
* @cssproperty --message-background - The bubble's background color.
|
|
38
41
|
*/
|
|
39
42
|
export default class ZnChatMessage extends ZincElement {
|
|
40
43
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
@@ -117,8 +120,12 @@ export default class ZnChatMessage extends ZincElement {
|
|
|
117
120
|
<div part="body" class="message__body">
|
|
118
121
|
${sending ? null : this.renderHeader()}
|
|
119
122
|
<div part="bubble" class="message__bubble ${sending ? 'message__bubble--sending' : ''}">
|
|
120
|
-
<div
|
|
121
|
-
|
|
123
|
+
<div class="message__main">
|
|
124
|
+
<div part="content" class="message__content">${this.renderContent()}</div>
|
|
125
|
+
<slot name="attachments" part="attachments" class="message__attachments"></slot>
|
|
126
|
+
</div>
|
|
127
|
+
${sending ? html`
|
|
128
|
+
<div class="message__meta">Sending...</div>` : html`
|
|
122
129
|
<div class="message__actions">
|
|
123
130
|
<slot name="edit-dialog-trigger"></slot>
|
|
124
131
|
</div>`}
|
|
@@ -172,14 +179,16 @@ export default class ZnChatMessage extends ZincElement {
|
|
|
172
179
|
<div part="header" class="message__header">
|
|
173
180
|
<span class="message__sender">${this.sender || (this.customerInitiated ? 'Customer' : 'You')}</span>
|
|
174
181
|
${this.time ? html`<span class="message__time">${this.getSentTime()}</span>` : nothing}
|
|
175
|
-
${this.actionType === 'internal' ? html
|
|
182
|
+
${this.actionType === 'internal' ? html`
|
|
183
|
+
<zn-chip type="info">Internal</zn-chip>` : nothing}
|
|
176
184
|
<slot name="badge"></slot>
|
|
177
185
|
</div>`;
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
private renderContent() {
|
|
181
189
|
if (!this.message) {
|
|
182
|
-
return html
|
|
190
|
+
return html`
|
|
191
|
+
<slot></slot>`;
|
|
183
192
|
}
|
|
184
193
|
|
|
185
194
|
let content = cleanHTML(this.message);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
:host {
|
|
4
4
|
display: block;
|
|
5
|
+
padding-inline: calc(var(--zn-gutter, 0) + var(--zn-base-gap, 0));
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
.message {
|
|
@@ -38,18 +39,6 @@
|
|
|
38
39
|
@include wc.text-style(subheading-small);
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
&__badge {
|
|
42
|
-
font-size: 10px;
|
|
43
|
-
font-weight: 600;
|
|
44
|
-
letter-spacing: 0.5px;
|
|
45
|
-
padding: 2px 6px;
|
|
46
|
-
border-radius: var(--zn-border-radius);
|
|
47
|
-
border: 1px solid rgba(var(--zn-color-info), 0.4);
|
|
48
|
-
color: rgb(var(--zn-color-info));
|
|
49
|
-
background: rgba(var(--zn-color-info), 0.08);
|
|
50
|
-
text-transform: uppercase;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
42
|
&__bubble {
|
|
54
43
|
box-sizing: border-box;
|
|
55
44
|
display: flex;
|
|
@@ -67,6 +56,29 @@
|
|
|
67
56
|
}
|
|
68
57
|
}
|
|
69
58
|
|
|
59
|
+
&__main {
|
|
60
|
+
flex: 1 1 auto;
|
|
61
|
+
min-width: 0;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: var(--zn-spacing-small);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&__attachments {
|
|
68
|
+
// Always rendered so projection is reliable regardless of when children are
|
|
69
|
+
// parsed/added, but kept out of flow (no phantom gap below the bubble) until
|
|
70
|
+
// an attachment actually exists — toggled on via :host(:has(...)) below.
|
|
71
|
+
display: none;
|
|
72
|
+
flex-direction: row;
|
|
73
|
+
flex-wrap: wrap;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: var(--zn-spacing-small);
|
|
76
|
+
|
|
77
|
+
::slotted(zn-chat-message-attachment) {
|
|
78
|
+
min-width: 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
&__content {
|
|
71
83
|
@include wc.text-style(chat);
|
|
72
84
|
|
|
@@ -74,6 +86,12 @@
|
|
|
74
86
|
min-width: 0;
|
|
75
87
|
overflow-wrap: anywhere;
|
|
76
88
|
|
|
89
|
+
// Bodies passed through the default slot (e.g. alongside attachments) must
|
|
90
|
+
// match the chat type scale, not inherit the host's default size.
|
|
91
|
+
::slotted(*) {
|
|
92
|
+
@include wc.text-style(chat);
|
|
93
|
+
}
|
|
94
|
+
|
|
77
95
|
p {
|
|
78
96
|
margin: 0;
|
|
79
97
|
padding: 0;
|
|
@@ -150,6 +168,13 @@
|
|
|
150
168
|
}
|
|
151
169
|
}
|
|
152
170
|
|
|
171
|
+
// Reveal the attachments row only when an attachment is actually present. Using
|
|
172
|
+
// :has on the host keeps this live and parse-order independent — no reliance on
|
|
173
|
+
// conditional rendering that could miss a child added during parsing.
|
|
174
|
+
:host(:has([slot="attachments"])) .message__attachments {
|
|
175
|
+
display: flex;
|
|
176
|
+
}
|
|
177
|
+
|
|
153
178
|
// Bubble tints by message origin / type.
|
|
154
179
|
:host([agent-initiated]) .message__bubble {
|
|
155
180
|
background: var(--message-background, rgba(var(--zn-color-info), 0.1));
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {type CSSResultGroup, html, nothing, unsafeCSS} from 'lit';
|
|
2
|
+
import {property} from "lit/decorators.js";
|
|
3
|
+
import ZincElement from '../../internal/zinc-element';
|
|
4
|
+
import ZnIcon from "../icon";
|
|
5
|
+
|
|
6
|
+
import styles from './chat-message-attachment.scss';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @summary A single file or link attachment for a `zn-chat-message`. Renders an
|
|
10
|
+
* icon and a label as a link, styled to match the message's attachments row. It is
|
|
11
|
+
* intended to be used only inside a `zn-chat-message` and is automatically placed in
|
|
12
|
+
* that component's `attachments` slot.
|
|
13
|
+
* @documentation https://zinc.style/components/chat-message-attachment
|
|
14
|
+
* @status experimental
|
|
15
|
+
* @since 1.0
|
|
16
|
+
*
|
|
17
|
+
* @dependency zn-icon
|
|
18
|
+
*
|
|
19
|
+
* @slot - The attachment label. Falls back to the `name` attribute when empty.
|
|
20
|
+
*
|
|
21
|
+
* @csspart base - The attachment link.
|
|
22
|
+
* @csspart icon - The leading icon.
|
|
23
|
+
* @csspart label - The attachment label.
|
|
24
|
+
*/
|
|
25
|
+
export default class ZnChatMessageAttachment extends ZincElement {
|
|
26
|
+
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
27
|
+
static dependencies = {
|
|
28
|
+
'zn-icon': ZnIcon
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** The URL the attachment links to. */
|
|
32
|
+
@property() href: string = '';
|
|
33
|
+
|
|
34
|
+
/** The attachment label (e.g. the file name). Used when the default slot is empty. */
|
|
35
|
+
@property() name: string = '';
|
|
36
|
+
|
|
37
|
+
/** The leading icon name. */
|
|
38
|
+
@property() icon: string = 'attach_file';
|
|
39
|
+
|
|
40
|
+
/** Where to open the link. Defaults to a new tab. */
|
|
41
|
+
@property() target: string = '_blank';
|
|
42
|
+
|
|
43
|
+
/** Prompt a download rather than navigating to the link. */
|
|
44
|
+
@property({type: Boolean}) download = false;
|
|
45
|
+
|
|
46
|
+
connectedCallback() {
|
|
47
|
+
super.connectedCallback();
|
|
48
|
+
|
|
49
|
+
// This component is only meaningful inside the attachments row of a chat message —
|
|
50
|
+
// slot it there automatically so consumers don't have to.
|
|
51
|
+
if (!this.slot) {
|
|
52
|
+
this.slot = 'attachments';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!this.closest('zn-chat-message')) {
|
|
56
|
+
console.warn('<zn-chat-message-attachment> can only be used inside a <zn-chat-message>.', this);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected render() {
|
|
61
|
+
const newTab = this.target === '_blank';
|
|
62
|
+
|
|
63
|
+
return html`
|
|
64
|
+
<a
|
|
65
|
+
part="base"
|
|
66
|
+
class="attachment"
|
|
67
|
+
href=${this.href || '#'}
|
|
68
|
+
target=${this.target || nothing}
|
|
69
|
+
rel=${newTab ? 'noreferrer noopener' : nothing}
|
|
70
|
+
?download=${this.download}
|
|
71
|
+
>
|
|
72
|
+
${this.icon ? html`
|
|
73
|
+
<zn-icon part="icon" class="paperclip@lu" src=${this.icon} size="20"></zn-icon>` : nothing}
|
|
74
|
+
<span part="label" class="attachment__label"><slot>${this.name}</slot></span>
|
|
75
|
+
</a>
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@use "../../wc";
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
min-width: 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.attachment {
|
|
9
|
+
@include wc.text-style(paragraph);
|
|
10
|
+
|
|
11
|
+
display: inline-flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: var(--zn-spacing-2x-small);
|
|
14
|
+
color: rgb(var(--zn-text));
|
|
15
|
+
text-decoration: none;
|
|
16
|
+
min-width: 0;
|
|
17
|
+
|
|
18
|
+
&__icon {
|
|
19
|
+
flex: 0 0 auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&__label {
|
|
23
|
+
min-width: 0;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
text-overflow: ellipsis;
|
|
26
|
+
white-space: nowrap;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&:hover .attachment__label {
|
|
30
|
+
text-decoration: underline;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import '../../../dist/zn.min.js';
|
|
2
|
+
import { expect, fixture, html } from '@open-wc/testing';
|
|
3
|
+
|
|
4
|
+
describe('<zn-chat-message-attachment>', () => {
|
|
5
|
+
it('should render a component', async () => {
|
|
6
|
+
const el = await fixture(html`
|
|
7
|
+
<zn-chat-message>
|
|
8
|
+
<zn-chat-message-attachment href="#" name="file.pdf"></zn-chat-message-attachment>
|
|
9
|
+
</zn-chat-message>`);
|
|
10
|
+
|
|
11
|
+
const attachment = el.querySelector('zn-chat-message-attachment');
|
|
12
|
+
expect(attachment).to.exist;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should auto-assign itself to the attachments slot', async () => {
|
|
16
|
+
const el = await fixture(html`
|
|
17
|
+
<zn-chat-message>
|
|
18
|
+
<zn-chat-message-attachment href="#" name="file.pdf"></zn-chat-message-attachment>
|
|
19
|
+
</zn-chat-message>`);
|
|
20
|
+
|
|
21
|
+
const attachment = el.querySelector('zn-chat-message-attachment');
|
|
22
|
+
expect(attachment?.getAttribute('slot')).to.equal('attachments');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should render the name in the link', async () => {
|
|
26
|
+
const el = await fixture<HTMLElement>(html`
|
|
27
|
+
<zn-chat-message>
|
|
28
|
+
<zn-chat-message-attachment href="https://example.com/file.pdf" name="file.pdf"></zn-chat-message-attachment>
|
|
29
|
+
</zn-chat-message>`);
|
|
30
|
+
|
|
31
|
+
const attachment = el.querySelector('zn-chat-message-attachment')!;
|
|
32
|
+
const link = attachment.shadowRoot!.querySelector('a')!;
|
|
33
|
+
expect(link.getAttribute('href')).to.equal('https://example.com/file.pdf');
|
|
34
|
+
expect(link.textContent).to.contain('file.pdf');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ZnChatMessageAttachment from './chat-message-attachment.component';
|
|
2
|
+
|
|
3
|
+
export * from './chat-message-attachment.component';
|
|
4
|
+
export default ZnChatMessageAttachment;
|
|
5
|
+
|
|
6
|
+
ZnChatMessageAttachment.define('zn-chat-message-attachment');
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLElementTagNameMap {
|
|
10
|
+
'zn-chat-message-attachment': ZnChatMessageAttachment;
|
|
11
|
+
}
|
|
12
|
+
}
|