@kubex/zinc 1.1.12 → 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.
Files changed (36) hide show
  1. package/dist/custom-elements.json +1814 -1084
  2. package/dist/vscode.html-custom-data.json +228 -88
  3. package/dist/web-types.json +521 -213
  4. package/dist/zn.d.ts +180 -1
  5. package/dist/zn.min.css +1 -1
  6. package/dist/zn.min.js +522 -459
  7. package/docs/pages/components/channel-tile.md +168 -0
  8. package/docs/pages/components/chat-message-attachment.md +54 -0
  9. package/docs/pages/components/chat-message.md +18 -1
  10. package/docs/pages/components/data-table.md +1 -3
  11. package/package.json +1 -1
  12. package/scss/shared/_table.scss +9 -23
  13. package/scss/themes/_dark.scss +3 -0
  14. package/scss/themes/_light.scss +4 -0
  15. package/scss/themes/_typography.scss +1 -0
  16. package/src/components/button/button.scss +4 -0
  17. package/src/components/channel-tile/channel-tile.component.ts +280 -0
  18. package/src/components/channel-tile/channel-tile.scss +197 -0
  19. package/src/components/channel-tile/index.ts +12 -0
  20. package/src/components/chat-message/chat-message.component.ts +14 -5
  21. package/src/components/chat-message/chat-message.scss +37 -12
  22. package/src/components/chat-message-attachment/chat-message-attachment.component.ts +78 -0
  23. package/src/components/chat-message-attachment/chat-message-attachment.scss +32 -0
  24. package/src/components/chat-message-attachment/chat-message-attachment.test.ts +36 -0
  25. package/src/components/chat-message-attachment/index.ts +12 -0
  26. package/src/components/data-table/data-table.component.ts +65 -46
  27. package/src/components/data-table/data-table.scss +32 -58
  28. package/src/components/icon/icon.component.ts +1 -1
  29. package/src/components/icon/icon.scss +2 -1
  30. package/src/components/page/page.component.ts +32 -1
  31. package/src/components/page/page.scss +6 -5
  32. package/src/components/tooltip/tooltip.scss +1 -1
  33. package/src/events/events.ts +2 -0
  34. package/src/events/zn-accept.ts +7 -0
  35. package/src/events/zn-reject.ts +7 -0
  36. package/src/zinc.ts +2 -0
@@ -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
+ }
@@ -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 colour.
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 part="content" class="message__content">${this.renderContent()}</div>
121
- ${sending ? html`<div class="message__meta">Sending...</div>` : html`
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`<span class="message__badge">INTERNAL</span>` : nothing}
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`<slot></slot>`;
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
+ }