@kubex/zinc 1.0.18 → 1.0.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubex/zinc",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "A collection of web components for building web applications based off of @shoelace-style/Shoelace",
5
5
  "keywords": [
6
6
  "web components",
@@ -0,0 +1,128 @@
1
+ import {type CSSResultGroup, html, unsafeCSS} from 'lit';
2
+ import {property, state} from "lit/decorators.js";
3
+ import ZincElement from '../../internal/zinc-element';
4
+ import type {ZnChangeEvent} from "../../events/zn-change";
5
+
6
+ import styles from './audio-select.scss';
7
+
8
+ interface AudioFile {
9
+ name: string;
10
+ url: string;
11
+ }
12
+
13
+ /**
14
+ * @summary Short summary of the component's intended use.
15
+ * @documentation https://zinc.style/components/audio-select
16
+ * @status experimental
17
+ * @since 1.0
18
+ *
19
+ * @slot - The default slot.
20
+ * @slot actions - The actions slot.
21
+ * @slot footer - The footer slot.
22
+ *
23
+ * @csspart base - The component's base wrapper.
24
+ *
25
+ * @cssproperty --example - An example CSS custom property.
26
+ */
27
+ export default class ZnAudioSelect extends ZincElement {
28
+ static styles: CSSResultGroup = unsafeCSS(styles);
29
+
30
+ private _selectedUrl: string = '';
31
+ @state() private _isPlaying: boolean = false;
32
+ private readonly _audio: HTMLAudioElement;
33
+
34
+ @property() label = '';
35
+ @property() placeholder = '';
36
+ @property({type: Object}) files: AudioFile[];
37
+
38
+ constructor() {
39
+ super();
40
+ this._audio = new Audio();
41
+
42
+ // Handle audio ending naturally
43
+ this._audio.addEventListener('ended', () => {
44
+ this._isPlaying = false;
45
+ // Reset to start so pressing play again restarts the audio
46
+ this._audio.currentTime = 0;
47
+ });
48
+ }
49
+
50
+ disconnectedCallback() {
51
+ super.disconnectedCallback();
52
+ this._stopAudio();
53
+ }
54
+
55
+ _stopAudio() {
56
+ if (this._audio) {
57
+ this._audio.pause();
58
+ this._audio.currentTime = 0;
59
+ }
60
+ this._isPlaying = false;
61
+ }
62
+
63
+ handleSelectChange(e: ZnChangeEvent) {
64
+ if (e.target !== e.currentTarget) {
65
+ return;
66
+ }
67
+
68
+ const url = (e.target as HTMLSelectElement).value;
69
+ this._selectedUrl = url;
70
+
71
+ this._stopAudio();
72
+
73
+ if (url) {
74
+ this._audio.src = url;
75
+ }
76
+ }
77
+
78
+ togglePreview(e: CustomEvent) {
79
+ e.stopPropagation();
80
+
81
+ if (!this._selectedUrl) {
82
+ e.preventDefault();
83
+ this._isPlaying = false;
84
+ this.requestUpdate();
85
+ return;
86
+ }
87
+
88
+ if (this._isPlaying) {
89
+ this._audio.pause();
90
+ this._isPlaying = false;
91
+ } else {
92
+ this._audio.play()
93
+ .then(() => {
94
+ this._isPlaying = true;
95
+ })
96
+ .catch(err => {
97
+ console.error("Audio playback failed:", err);
98
+ this._isPlaying = false;
99
+ this.requestUpdate();
100
+ });
101
+ }
102
+ }
103
+
104
+ render() {
105
+ return html`
106
+ <div class="container">
107
+ <div class="select-wrapper">
108
+ <zn-select label="${this.label}" @zn-change="${this.handleSelectChange}">
109
+ <zn-checkbox
110
+ slot="prefix"
111
+ checked-icon="pause"
112
+ unchecked-icon="play_arrow"
113
+ checked-color="primary"
114
+ unchecked-color="secondary"
115
+ ?checked="${this._isPlaying}"
116
+ @zn-change="${this.togglePreview}">
117
+ </zn-checkbox>
118
+
119
+ <zn-option value="" disabled selected>${this.placeholder || 'Select audio'}</zn-option>
120
+ ${this.files.map(file => html`
121
+ <zn-option value="${file.url}">${file.name}</zn-option>
122
+ `)}
123
+ </zn-select>
124
+ </div>
125
+ </div>
126
+ `;
127
+ }
128
+ }
@@ -0,0 +1,4 @@
1
+ :host {
2
+ display: block;
3
+ width: 100%;
4
+ }
@@ -0,0 +1,10 @@
1
+ import '../../../dist/zn.min.js';
2
+ import { expect, fixture, html } from '@open-wc/testing';
3
+
4
+ describe('<zn-audio-select>', () => {
5
+ it('should render a component', async () => {
6
+ const el = await fixture(html` <zn-audio-select></zn-audio-select> `);
7
+
8
+ expect(el).to.exist;
9
+ });
10
+ });
@@ -0,0 +1,12 @@
1
+ import ZnAudioSelect from './audio-select.component';
2
+
3
+ export * from './audio-select.component';
4
+ export default ZnAudioSelect;
5
+
6
+ ZnAudioSelect.define('zn-audio-select');
7
+
8
+ declare global {
9
+ interface HTMLElementTagNameMap {
10
+ 'zn-audio-select': ZnAudioSelect;
11
+ }
12
+ }
@@ -13,6 +13,8 @@ import type {ZincFormControl} from '../../internal/zinc-element';
13
13
 
14
14
  import styles from './checkbox.scss';
15
15
 
16
+ type ColorOption = 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' | 'transparent';
17
+
16
18
  /**
17
19
  * @summary Short summary of the component's intended use.
18
20
  * @documentation https://zinc.style/components/checkbox
@@ -23,7 +25,7 @@ import styles from './checkbox.scss';
23
25
  *
24
26
  * @slot - The checkbox's label.
25
27
  * @slot description - A description of the checkbox's label. Serves as help text for a checkbox item. Alternatively, you can use the `description` attribute.
26
- * @slot selected-content - Use to nest rich content (like an input) inside a selected checkbox item. Use only with the contained style.
28
+ * @slot selected-content - Use to nest rich content (like an input) inside a selected checkbox item. Use only with the contained style.
27
29
  *
28
30
  * @event zn-blur - Emitted when the checkbox loses focus.
29
31
  * @event zn-change - Emitted when the checked state changes.
@@ -36,6 +38,7 @@ import styles from './checkbox.scss';
36
38
  * @csspart control--checked - Matches the control part when the checkbox is checked.
37
39
  * @csspart control--indeterminate - Matches the control part when the checkbox is indeterminate.
38
40
  * @csspart checked-icon - The checked icon, an `<zn-icon>` element.
41
+ * @csspart unchecked-icon - The unchecked icon, an `<zn-icon>` element.
39
42
  * @csspart indeterminate-icon - The indeterminate icon, an `<zn-icon>` element.
40
43
  * @csspart label - The container that wraps the checkbox's label.
41
44
  * @csspart description - The container that wraps the checkbox's description.
@@ -105,6 +108,21 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
105
108
 
106
109
  @property({attribute: 'label-tooltip'}) labelTooltip: string;
107
110
 
111
+ /** The icon to show when the checkbox is checked. */
112
+ @property({attribute: 'checked-icon'}) checkedIcon = '';
113
+
114
+ /** The icon to show when the checkbox is unchecked. */
115
+ @property({attribute: 'unchecked-icon'}) uncheckedIcon = '';
116
+
117
+ /** The color of the checkbox. */
118
+ @property({reflect: true}) color: ColorOption = 'default';
119
+
120
+ /** The color of the checkbox when checked. Overrides `color`. */
121
+ @property({attribute: 'checked-color'}) checkedColor: ColorOption;
122
+
123
+ /** The color of the checkbox when unchecked. Overrides `color`. */
124
+ @property({attribute: 'unchecked-color'}) uncheckedColor: ColorOption;
125
+
108
126
  /** Gets the validity state object */
109
127
  get validity() {
110
128
  return this.input?.validity;
@@ -216,6 +234,11 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
216
234
  const hasLabelTooltip = this.hasSlotController.test('label-tooltip');
217
235
  const hasLabel = this.label || hasLabelSlot;
218
236
 
237
+ // Determine the effective color
238
+ const effectiveColor = (this.checked || this.indeterminate)
239
+ ? (this.checkedColor || this.color)
240
+ : (this.uncheckedColor || this.color);
241
+
219
242
  //
220
243
  // NOTE: we use a <div> around the label slot because of this Chrome bug.
221
244
  //
@@ -262,6 +285,14 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
262
285
  'checkbox--small': this.size === 'small',
263
286
  'checkbox--medium': this.size === 'medium',
264
287
  'checkbox--large': this.size === 'large',
288
+ 'checkbox--default': effectiveColor === 'default',
289
+ 'checkbox--primary': effectiveColor === 'primary',
290
+ 'checkbox--secondary': effectiveColor === 'secondary',
291
+ 'checkbox--error': effectiveColor === 'error',
292
+ 'checkbox--info': effectiveColor === 'info',
293
+ 'checkbox--success': effectiveColor === 'success',
294
+ 'checkbox--warning': effectiveColor === 'warning',
295
+ 'checkbox--transparent': effectiveColor === 'transparent',
265
296
  'checkbox--has-description': hasDescription,
266
297
  'checkbox--has-selected-content': this.hasSlotController.test('selected-content')
267
298
  })}>
@@ -289,15 +320,28 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
289
320
  ? ' control--indeterminate'
290
321
  : ''}"
291
322
  class="checkbox__control">
292
- <svg viewBox="0 0 24 24"
293
- fill="none"
294
- stroke="currentColor"
295
- stroke-width="4"
296
- stroke-linecap="round"
297
- stroke-linejoin="round"
298
- class="svg-icon">
299
- <polyline class="checkbox__checked-icon" points="20 6 9 17 4 12"></polyline>
300
- </svg>
323
+
324
+ ${this.checked && !this.indeterminate
325
+ ? this.checkedIcon
326
+ ? html`
327
+ <zn-icon part="checked-icon" class="checkbox__icon" src="${this.checkedIcon}"></zn-icon>`
328
+ : html`
329
+ <svg viewBox="0 0 24 24"
330
+ fill="none"
331
+ stroke="currentColor"
332
+ stroke-width="4"
333
+ stroke-linecap="round"
334
+ stroke-linejoin="round"
335
+ class="svg-icon">
336
+ <polyline class="checkbox__checked-icon" points="20 6 9 17 4 12"></polyline>
337
+ </svg>`
338
+ : ''}
339
+
340
+ ${!this.checked && !this.indeterminate && this.uncheckedIcon
341
+ ? html`
342
+ <zn-icon part="unchecked-icon" class="checkbox__icon" src="${this.uncheckedIcon}"></zn-icon>`
343
+ : ''}
344
+
301
345
  ${!this.checked && this.indeterminate
302
346
  ? html`
303
347
  <zn-icon
@@ -25,6 +25,60 @@
25
25
  vertical-align: middle;
26
26
  cursor: pointer;
27
27
  height: 100%;
28
+
29
+ --checkbox-color: var(--zn-color-primary-600);
30
+ --checkbox-color-hover: var(--zn-color-primary-500);
31
+ --checkbox-icon-color: var(--zn-color-neutral-0);
32
+ --checkbox-border-color: var(--zn-input-border-color);
33
+ --checkbox-bg-color: var(--zn-input-background-color);
34
+
35
+ &.checkbox--default {
36
+ --checkbox-color: var(--zn-color-primary-600);
37
+ --checkbox-color-hover: var(--zn-color-primary-500);
38
+ --checkbox-border-color: var(--zn-input-border-color);
39
+ }
40
+
41
+ &.checkbox--primary {
42
+ --checkbox-color: var(--zn-color-primary-600);
43
+ --checkbox-color-hover: var(--zn-color-primary-500);
44
+ --checkbox-border-color: var(--zn-color-primary-600);
45
+ }
46
+
47
+ &.checkbox--secondary {
48
+ --checkbox-color: var(--zn-color-neutral-600);
49
+ --checkbox-color-hover: var(--zn-color-neutral-500);
50
+ --checkbox-border-color: var(--zn-color-neutral-300);
51
+ --checkbox-bg-color: var(--zn-color-neutral-50);
52
+ }
53
+
54
+ &.checkbox--info {
55
+ --checkbox-color: var(--zn-color-info-600);
56
+ --checkbox-color-hover: var(--zn-color-info-500);
57
+ --checkbox-border-color: var(--zn-color-info-600);
58
+ }
59
+
60
+ &.checkbox--success {
61
+ --checkbox-color: var(--zn-color-success-600);
62
+ --checkbox-color-hover: var(--zn-color-success-500);
63
+ --checkbox-border-color: var(--zn-color-success-600);
64
+ }
65
+
66
+ &.checkbox--warning {
67
+ --checkbox-color: var(--zn-color-warning-600);
68
+ --checkbox-color-hover: var(--zn-color-warning-500);
69
+ --checkbox-border-color: var(--zn-color-warning-600);
70
+ }
71
+
72
+ &.checkbox--error {
73
+ --checkbox-color: var(--zn-color-danger-600);
74
+ --checkbox-color-hover: var(--zn-color-danger-500);
75
+ --checkbox-border-color: var(--zn-color-danger-600);
76
+ }
77
+
78
+ &.checkbox--transparent {
79
+ --checkbox-bg-color: transparent;
80
+ --checkbox-border-color: transparent;
81
+ }
28
82
  }
29
83
 
30
84
  .checkbox--small {
@@ -60,10 +114,10 @@
60
114
  width: var(--toggle-size);
61
115
  height: var(--toggle-size);
62
116
  box-shadow: var(--zn-shadow-x-small);
63
- border: solid var(--zn-input-border-width) var(--zn-input-border-color);
117
+ border: solid var(--zn-input-border-width) var(--checkbox-border-color);
118
+ background-color: var(--checkbox-bg-color);
64
119
  border-radius: 2px;
65
- background-color: var(--zn-input-background-color);
66
- color: var(--zn-color-neutral-0);
120
+ color: var(--checkbox-icon-color);
67
121
  transition: var(--zn-transition-fast) border-color,
68
122
  var(--zn-transition-fast) background-color,
69
123
  var(--zn-transition-fast) color,
@@ -80,7 +134,7 @@
80
134
 
81
135
  .checkbox__indeterminate-icon {
82
136
  --icon-size: var(--toggle-size);
83
- --icon-color: var(--zn-color-neutral-0);
137
+ --icon-color: var(--checkbox-icon-color);
84
138
 
85
139
  display: inline-flex;
86
140
  width: var(--toggle-size);
@@ -88,9 +142,9 @@
88
142
  }
89
143
 
90
144
  .svg-icon {
91
- width: 70%;
92
- height: 70%;
93
- --icon-color: var(--zn-color-neutral-0);
145
+ width: max(16px, 70%);
146
+ height: max(16px, 70%);
147
+ --icon-color: var(--checkbox-icon-color);
94
148
 
95
149
  .checkbox__checked-icon {
96
150
  stroke-dasharray: 24;
@@ -105,7 +159,7 @@
105
159
 
106
160
  .checkbox--checked {
107
161
  .svg-icon {
108
- color: var(--zn-color-neutral-0);
162
+ color: var(--checkbox-icon-color);
109
163
  }
110
164
 
111
165
  .checkbox__checked-icon {
@@ -115,13 +169,13 @@
115
169
 
116
170
  /* Hover */
117
171
  .checkbox:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__control:hover {
118
- border-color: var(--zn-input-border-color-hover);
172
+ border-color: var(--checkbox-color-hover, var(--zn-input-border-color-hover));
119
173
  background-color: var(--zn-input-background-color-hover);
120
174
  }
121
175
 
122
176
  /* Focus */
123
177
  .checkbox:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__input:focus-visible ~ .checkbox__control {
124
- border-color: var(--zn-color-primary-500);
178
+ border-color: var(--checkbox-color);
125
179
  outline: var(--zn-focus-ring);
126
180
  outline-offset: var(--zn-focus-ring-offset);
127
181
  }
@@ -129,15 +183,16 @@
129
183
  /* Checked/indeterminate */
130
184
  .checkbox--checked .checkbox__control,
131
185
  .checkbox--indeterminate .checkbox__control {
132
- border-color: var(--zn-color-primary-600);
133
- background-color: var(--zn-color-primary-600);
186
+ border-color: var(--checkbox-color);
187
+ background-color: var(--checkbox-color);
188
+ color: var(--checkbox-icon-color);
134
189
  }
135
190
 
136
191
  /* Checked/indeterminate + hover */
137
192
  .checkbox.checkbox--checked:not(.checkbox--disabled) .checkbox__control:hover,
138
193
  .checkbox.checkbox--indeterminate:not(.checkbox--disabled) .checkbox__control:hover {
139
- border-color: var(--zn-color-primary-500);
140
- background-color: var(--zn-color-primary-500);
194
+ border-color: var(--checkbox-color-hover);
195
+ background-color: var(--checkbox-color-hover);
141
196
  }
142
197
 
143
198
  /* Checked/indeterminate + focus */
@@ -243,7 +298,39 @@
243
298
  background-color: var(--zn-color-purple-50);
244
299
  }
245
300
 
301
+ .checkbox--primary .checkbox__control,
302
+ .checkbox--success .checkbox__control,
303
+ .checkbox--warning .checkbox__control,
304
+ .checkbox--error .checkbox__control,
305
+ .checkbox--info .checkbox__control {
306
+ background-color: var(--checkbox-color);
307
+ color: var(--zn-color-neutral-0);
308
+ border-color: var(--checkbox-color);
309
+ }
310
+
311
+ .checkbox--primary:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__control:hover,
312
+ .checkbox--success:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__control:hover,
313
+ .checkbox--warning:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__control:hover,
314
+ .checkbox--error:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__control:hover,
315
+ .checkbox--info:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__control:hover {
316
+ background-color: var(--checkbox-color-hover);
317
+ border-color: var(--checkbox-color-hover);
318
+ color: var(--zn-color-neutral-0);
319
+ }
320
+
321
+ .checkbox--secondary .checkbox__control {
322
+ background-color: var(--zn-color-neutral-200);
323
+ color: var(--zn-color-neutral-700);
324
+ }
325
+
326
+ .checkbox--secondary:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__control:hover {
327
+ background-color: var(--zn-color-neutral-300);
328
+ border-color: var(--zn-color-neutral-300);
329
+ color: var(--zn-color-neutral-800);
330
+ }
331
+
246
332
  .checkbox--checked .checkbox__control {
247
- background-color: var(--zn-color-primary-600);
333
+ background-color: var(--checkbox-color);
334
+ color: var(--zn-color-neutral-0);
248
335
  }
249
336
  }
@@ -98,7 +98,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
98
98
  @state() currentOption: ZnOption;
99
99
  @state() selectedOptions: ZnOption[] = [];
100
100
  @state() private valueHasChanged: boolean = false;
101
- @state() private hasCheckboxPrefix: boolean = false;
101
+ @state() private inputPrefix: boolean = false;
102
102
 
103
103
  /** The name of the select, submitted as a name/value pair with form data. */
104
104
  @property() name = '';
@@ -271,13 +271,9 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
271
271
  }
272
272
  }
273
273
 
274
- private updateHasCheckboxPrefix() {
275
- try {
276
- const assigned = this.prefixSlot?.assignedElements({flatten: true}) || [];
277
- this.hasCheckboxPrefix = assigned.some(el => el.tagName === 'ZN-CHECKBOX');
278
- } catch {
279
- this.hasCheckboxPrefix = false;
280
- }
274
+ private updateHasInputPrefix() {
275
+ const assigned = this.prefixSlot?.assignedElements({flatten: true}) || [];
276
+ this.inputPrefix = assigned.length > 0;
281
277
  }
282
278
 
283
279
  private addOpenListeners() {
@@ -772,7 +768,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
772
768
  });
773
769
  }
774
770
 
775
- this.updateHasCheckboxPrefix();
771
+ this.updateHasInputPrefix();
776
772
  }
777
773
 
778
774
  @watch('disabled', {waitUntilFirstUpdate: true})
@@ -971,7 +967,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
971
967
  'select--small': this.size === 'small',
972
968
  'select--medium': this.size === 'medium',
973
969
  'select--large': this.size === 'large',
974
- 'select--has-checkbox-prefix': this.hasCheckboxPrefix
970
+ 'select--has-input-prefix': this.inputPrefix
975
971
  })}
976
972
  placement=${this.placement}
977
973
  strategy=${this.hoist ? 'fixed' : 'absolute'}
@@ -989,7 +985,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
989
985
  <slot part="prefix"
990
986
  name="prefix"
991
987
  class="select__prefix"
992
- @slotchange=${() => this.updateHasCheckboxPrefix()}></slot>
988
+ @slotchange=${() => this.updateHasInputPrefix()}></slot>
993
989
 
994
990
  <input
995
991
  part="display-input"
@@ -183,7 +183,7 @@
183
183
  padding-inline: var(--zn-input-spacing-medium);
184
184
  }
185
185
 
186
- .select--has-checkbox-prefix.select--medium .select__combobox {
186
+ .select--has-input-prefix.select--medium .select__combobox {
187
187
  padding-inline-start: 0;
188
188
  }
189
189
 
@@ -212,7 +212,7 @@
212
212
  padding-inline: var(--zn-input-spacing-large);
213
213
  }
214
214
 
215
- .select--has-checkbox-prefix.select--large .select__combobox {
215
+ .select--has-input-prefix.select--large .select__combobox {
216
216
  padding-inline-start: 0;
217
217
  }
218
218
 
@@ -258,7 +258,7 @@
258
258
  }
259
259
 
260
260
  /* When a checkbox is used in the prefix, remove left padding so the prefix joins flush */
261
- .select--has-checkbox-prefix.select--standard .select__combobox {
261
+ .select--has-input-prefix.select--standard .select__combobox {
262
262
  padding-inline-start: 0;
263
263
  }
264
264
 
package/src/zinc.ts CHANGED
@@ -82,6 +82,7 @@ export {default as FilterWrapper} from './components/filter-wrapper';
82
82
  export {default as SettingsContainer} from './components/settings-container';
83
83
  export { default as FilterContainer } from './components/filter-container';
84
84
  export { default as Reveal } from './components/reveal';
85
+ export { default as AudioSelect } from './components/audio-select';
85
86
  /* plop:component */
86
87
 
87
88
  // Base Component