@kubex/zinc 1.1.77 → 1.1.78
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 +169 -75
- package/dist/vscode.html-custom-data.json +22 -12
- package/dist/web-types.json +50 -25
- package/dist/zn.d.ts +13 -0
- package/dist/zn.min.js +122 -100
- package/docs/pages/components/rating.md +59 -0
- package/package.json +1 -1
- package/src/components/rating/rating.component.ts +112 -55
- package/src/components/rating/rating.scss +24 -0
|
@@ -74,6 +74,29 @@ Use the `disabled` attribute to disable the rating component.
|
|
|
74
74
|
<zn-rating value="3" disabled label="Rating disabled with value"></zn-rating>
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
### Help Text
|
|
78
|
+
|
|
79
|
+
Add descriptive help text to a rating with the `help-text` attribute. For help text that contains HTML, use the
|
|
80
|
+
`help-text` slot instead.
|
|
81
|
+
|
|
82
|
+
```html:preview
|
|
83
|
+
<zn-rating label="Rate this product" help-text="Select a star to rate this product."></zn-rating>
|
|
84
|
+
<br />
|
|
85
|
+
<zn-rating value="3" precision="0.5" label="Overall rating">
|
|
86
|
+
<div slot="help-text">Ratings are <strong>public</strong> and can be changed at any time.</div>
|
|
87
|
+
</zn-rating>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Help text is styled to match [Input](/components/input) and follows the `size` attribute.
|
|
91
|
+
|
|
92
|
+
```html:preview
|
|
93
|
+
<zn-rating size="small" value="3" help-text="Small help text"></zn-rating>
|
|
94
|
+
<br />
|
|
95
|
+
<zn-rating size="medium" value="3" help-text="Medium help text"></zn-rating>
|
|
96
|
+
<br />
|
|
97
|
+
<zn-rating size="large" value="3" help-text="Large help text"></zn-rating>
|
|
98
|
+
```
|
|
99
|
+
|
|
77
100
|
### Sizes
|
|
78
101
|
|
|
79
102
|
Use the `size` attribute to change the rating size. Available sizes are `small`, `medium` (default), and `large`.
|
|
@@ -116,6 +139,40 @@ The rating component displays hover effects by default, scaling the hovered star
|
|
|
116
139
|
**Usage:** Hover effects provide visual feedback to help users understand which rating they will select. The hover effect is automatically disabled for `readonly` and `disabled` ratings.
|
|
117
140
|
:::
|
|
118
141
|
|
|
142
|
+
### Preview
|
|
143
|
+
|
|
144
|
+
Use the `preview` attribute to show the value next to the symbols. While the pointer moves across the symbols the
|
|
145
|
+
preview follows it, so the value being selected is visible before it is committed. Once the pointer leaves, the preview
|
|
146
|
+
falls back to the selected value.
|
|
147
|
+
|
|
148
|
+
```html:preview
|
|
149
|
+
<zn-rating preview label="Rate this product"></zn-rating>
|
|
150
|
+
<br />
|
|
151
|
+
<zn-rating preview value="2.5" precision="0.5" label="Half star precision"></zn-rating>
|
|
152
|
+
<br />
|
|
153
|
+
<zn-rating preview value="3.75" precision="0.25" max="10" size="large" label="Quarter star precision"></zn-rating>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The preview is formatted to match `precision`, so a rating with `precision="0.5"` previews `4.5` rather than `4`.
|
|
157
|
+
It also works with `readonly` and `disabled` ratings, where it simply displays the current value.
|
|
158
|
+
|
|
159
|
+
```html:preview
|
|
160
|
+
<zn-rating preview value="4.5" precision="0.5" readonly label="Average rating"></zn-rating>
|
|
161
|
+
<br />
|
|
162
|
+
<zn-rating preview value="3" disabled label="Rating disabled"></zn-rating>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Style the preview with the `--preview-color` and `--preview-size` custom properties, or the `preview` part.
|
|
166
|
+
|
|
167
|
+
```html:preview
|
|
168
|
+
<zn-rating
|
|
169
|
+
preview
|
|
170
|
+
value="4"
|
|
171
|
+
label="Custom preview styling"
|
|
172
|
+
style="--preview-color: #3B82F6; --preview-size: 1.25rem;"
|
|
173
|
+
></zn-rating>
|
|
174
|
+
```
|
|
175
|
+
|
|
119
176
|
### Form Integration
|
|
120
177
|
|
|
121
178
|
Ratings work seamlessly with forms and will be submitted with form data using the `name` attribute.
|
|
@@ -272,6 +329,8 @@ Available CSS custom properties:
|
|
|
272
329
|
- `--symbol-color-active` - Color of filled/active symbols
|
|
273
330
|
- `--symbol-size` - Size of the symbols
|
|
274
331
|
- `--symbol-spacing` - Spacing between symbols
|
|
332
|
+
- `--preview-color` - Color of the preview value (see [Preview](#preview))
|
|
333
|
+
- `--preview-size` - Font size of the preview value
|
|
275
334
|
|
|
276
335
|
### Complete Example
|
|
277
336
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {classMap} from "lit/directives/class-map.js";
|
|
2
2
|
import {type CSSResultGroup, html, unsafeCSS} from 'lit';
|
|
3
3
|
import {FormControlController, validValidityState} from "../../internal/form";
|
|
4
|
+
import {HasSlotController} from "../../internal/slot";
|
|
4
5
|
import {property, query, state} from 'lit/decorators.js';
|
|
5
6
|
import {styleMap} from 'lit/directives/style-map.js';
|
|
6
7
|
import {unsafeHTML} from "lit/directives/unsafe-html.js";
|
|
@@ -22,10 +23,16 @@ import styles from './rating.scss';
|
|
|
22
23
|
*
|
|
23
24
|
* @slot - The default slot.
|
|
24
25
|
* @slot example - An example slot.
|
|
26
|
+
* @slot help-text - Text that describes how to use the rating. Alternatively, you can use the `help-text` attribute.
|
|
25
27
|
*
|
|
28
|
+
* @csspart form-control - The form control that wraps the symbols and help text.
|
|
29
|
+
* @csspart form-control-help-text - The help text's wrapper.
|
|
26
30
|
* @csspart base - The component's base wrapper.
|
|
31
|
+
* @csspart preview - The value shown next to the symbols when `preview` is enabled.
|
|
27
32
|
*
|
|
28
33
|
* @cssproperty --example - An example CSS custom property.
|
|
34
|
+
* @cssproperty --preview-color - The color of the preview value.
|
|
35
|
+
* @cssproperty --preview-size - The font size of the preview value.
|
|
29
36
|
*/
|
|
30
37
|
export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
31
38
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
@@ -34,14 +41,21 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
34
41
|
assumeInteractionOn: ['zn-blur', 'zn-input']
|
|
35
42
|
});
|
|
36
43
|
|
|
44
|
+
private readonly hasSlotController = new HasSlotController(this, 'help-text');
|
|
45
|
+
|
|
37
46
|
@query('.rating') rating: HTMLElement;
|
|
38
47
|
|
|
48
|
+
@query('.rating__symbols') symbols: HTMLElement;
|
|
49
|
+
|
|
39
50
|
@state() private hoverValue: number = 0;
|
|
40
51
|
|
|
41
52
|
@state() private isHovering: boolean = false;
|
|
42
53
|
|
|
43
54
|
@property() label: string;
|
|
44
55
|
|
|
56
|
+
/** The rating's help text. If you need to display HTML, use the `help-text` slot instead. */
|
|
57
|
+
@property({attribute: 'help-text'}) helpText: string = '';
|
|
58
|
+
|
|
45
59
|
@property() name: string;
|
|
46
60
|
|
|
47
61
|
@property({type: Number}) value: number = 0;
|
|
@@ -54,6 +68,9 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
54
68
|
|
|
55
69
|
@property({type: Boolean}) disabled: boolean = false;
|
|
56
70
|
|
|
71
|
+
/** Displays the value alongside the symbols, updating live as the pointer moves across them. */
|
|
72
|
+
@property({type: Boolean}) preview: boolean = false;
|
|
73
|
+
|
|
57
74
|
@property({}) size: 'small' | 'medium' | 'large' = 'medium';
|
|
58
75
|
|
|
59
76
|
@property() getSymbol: (value: number) => string = () => '<zn-icon src="star" library="material"></zn-icon>';
|
|
@@ -94,12 +111,17 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
94
111
|
}
|
|
95
112
|
|
|
96
113
|
private _getValueFromXCoordinate(coordinate: number): number {
|
|
97
|
-
const {left, width} = this.
|
|
114
|
+
const {left, width} = this.symbols.getBoundingClientRect();
|
|
98
115
|
const value = this._roundToPrecision(((coordinate - left) / width) * this.max, this.precision);
|
|
99
116
|
|
|
100
117
|
return Math.min(Math.max(value, 0), this.max);
|
|
101
118
|
}
|
|
102
119
|
|
|
120
|
+
private _formatValue(value: number): string {
|
|
121
|
+
const decimals = String(this.precision).split('.')[1]?.length ?? 0;
|
|
122
|
+
return value.toFixed(decimals);
|
|
123
|
+
}
|
|
124
|
+
|
|
103
125
|
private _getValueFromMousePosition(event: MouseEvent): number {
|
|
104
126
|
return this._getValueFromXCoordinate(event.clientX);
|
|
105
127
|
}
|
|
@@ -150,13 +172,15 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
150
172
|
|
|
151
173
|
private _handleTouchEnd(event: TouchEvent) {
|
|
152
174
|
this.isHovering = false;
|
|
153
|
-
|
|
175
|
+
// `touches` is empty once the finger lifts, so the final position lives in `changedTouches`.
|
|
176
|
+
this._setValue(this._getValueFromXCoordinate(event.changedTouches[0].clientX));
|
|
154
177
|
|
|
155
178
|
event.preventDefault();
|
|
156
179
|
}
|
|
157
180
|
|
|
158
181
|
render() {
|
|
159
182
|
const counter = Array.from(Array(this.max).keys());
|
|
183
|
+
const hasHelpText = this.helpText ? true : this.hasSlotController.test('help-text');
|
|
160
184
|
let displayValue = 0;
|
|
161
185
|
|
|
162
186
|
if (this.disabled || this.readonly) {
|
|
@@ -167,69 +191,102 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
167
191
|
|
|
168
192
|
return html`
|
|
169
193
|
<div
|
|
170
|
-
part="
|
|
194
|
+
part="form-control"
|
|
171
195
|
class="${classMap({
|
|
172
|
-
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
${
|
|
196
|
-
|
|
196
|
+
'form-control': true,
|
|
197
|
+
'form-control--small': this.size === 'small',
|
|
198
|
+
'form-control--medium': this.size === 'medium',
|
|
199
|
+
'form-control--large': this.size === 'large',
|
|
200
|
+
'form-control--has-help-text': hasHelpText
|
|
201
|
+
})}">
|
|
202
|
+
<div
|
|
203
|
+
part="base"
|
|
204
|
+
class="${classMap({
|
|
205
|
+
rating: true,
|
|
206
|
+
'rating--readonly': this.readonly,
|
|
207
|
+
'rating--disabled': this.disabled,
|
|
208
|
+
'rating--small': this.size === 'small',
|
|
209
|
+
'rating--medium': this.size === 'medium',
|
|
210
|
+
'rating--large': this.size === 'large'
|
|
211
|
+
})}"
|
|
212
|
+
role="slider"
|
|
213
|
+
aria-label="${this.label}"
|
|
214
|
+
aria-describedby="help-text"
|
|
215
|
+
aria-disabled="${this.disabled ? 'true' : 'false'}"
|
|
216
|
+
aria-readonly="${this.readonly ? 'true' : 'false'}"
|
|
217
|
+
aria-valuenow="${this.value}"
|
|
218
|
+
aria-valuemin="0"
|
|
219
|
+
aria-valuemax="${this.max}"
|
|
220
|
+
>
|
|
221
|
+
<span
|
|
222
|
+
class="rating__symbols"
|
|
223
|
+
@click="${this._handleClick}"
|
|
224
|
+
@mouseenter="${this._handleMouseEnter}"
|
|
225
|
+
@mousemove="${this._handleMouseMove}"
|
|
226
|
+
@mouseleave="${this._handleMouseLeave}"
|
|
227
|
+
@touchstart="${this._handleTouchStart}"
|
|
228
|
+
@touchmove="${this._handleTouchMove}"
|
|
229
|
+
@touchend="${this._handleTouchEnd}"
|
|
230
|
+
>
|
|
231
|
+
${counter.map(index => {
|
|
232
|
+
if (displayValue > index && displayValue < index + 1) {
|
|
233
|
+
return html`
|
|
234
|
+
<span
|
|
235
|
+
class=${classMap({
|
|
236
|
+
rating__symbol: true,
|
|
237
|
+
'rating__partial-symbol-container': true,
|
|
238
|
+
'rating__symbol--hover': this.isHovering && Math.ceil(displayValue) === index + 1
|
|
239
|
+
})}
|
|
240
|
+
role="presentation">
|
|
241
|
+
<div
|
|
242
|
+
style=${styleMap({
|
|
243
|
+
clipPath: `inset(0 0 0 ${(displayValue - index) * 100}%)`
|
|
244
|
+
})}>
|
|
245
|
+
${unsafeHTML(this.getSymbol(index + 1))}
|
|
246
|
+
</div>
|
|
247
|
+
<div
|
|
248
|
+
class="rating__partial--filled"
|
|
249
|
+
style=${styleMap({
|
|
250
|
+
clipPath: `inset(0 ${100 - (displayValue - index) * 100}% 0 0)`
|
|
251
|
+
})}>
|
|
252
|
+
${unsafeHTML(this.getSymbol(index + 1))}
|
|
253
|
+
</div>
|
|
254
|
+
</span>
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
197
257
|
return html`
|
|
198
258
|
<span
|
|
199
259
|
class=${classMap({
|
|
200
260
|
rating__symbol: true,
|
|
201
|
-
'
|
|
202
|
-
'rating__symbol--
|
|
261
|
+
'rating__symbol--hover': this.isHovering && Math.ceil(displayValue) === index + 1,
|
|
262
|
+
'rating__symbol--active': displayValue >= index + 1
|
|
203
263
|
})}
|
|
204
264
|
role="presentation">
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
<div
|
|
212
|
-
class="rating__partial--filled"
|
|
213
|
-
style=${styleMap({
|
|
214
|
-
clipPath: `inset(0 ${100 - (displayValue - index) * 100}% 0 0)`
|
|
215
|
-
})}>
|
|
216
|
-
${unsafeHTML(this.getSymbol(index + 1))}
|
|
217
|
-
</div>
|
|
218
|
-
</span>
|
|
219
|
-
`;
|
|
220
|
-
}
|
|
221
|
-
return html`
|
|
265
|
+
${unsafeHTML(this.getSymbol(index + 1))}
|
|
266
|
+
</span>`;
|
|
267
|
+
})}
|
|
268
|
+
</span>
|
|
269
|
+
${this.preview
|
|
270
|
+
? html`
|
|
222
271
|
<span
|
|
272
|
+
part="preview"
|
|
223
273
|
class=${classMap({
|
|
224
|
-
|
|
225
|
-
'
|
|
226
|
-
'rating__symbol--active': displayValue >= index + 1
|
|
274
|
+
rating__preview: true,
|
|
275
|
+
'rating__preview--hover': this.isHovering
|
|
227
276
|
})}
|
|
228
|
-
|
|
229
|
-
${
|
|
230
|
-
</span
|
|
231
|
-
|
|
232
|
-
</
|
|
277
|
+
aria-hidden="true">
|
|
278
|
+
${this._formatValue(displayValue)}
|
|
279
|
+
</span>`
|
|
280
|
+
: ''}
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<div
|
|
284
|
+
part="form-control-help-text"
|
|
285
|
+
id="help-text"
|
|
286
|
+
class="form-control__help-text"
|
|
287
|
+
aria-hidden=${hasHelpText ? 'false' : 'true'}>
|
|
288
|
+
<slot name="help-text">${this.helpText}</slot>
|
|
289
|
+
</div>
|
|
233
290
|
</div>
|
|
234
291
|
`;
|
|
235
292
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
@use "../../wc";
|
|
2
|
+
@use "../../form-control";
|
|
2
3
|
|
|
3
4
|
:host {
|
|
4
5
|
--symbol-color: rgb(var(--zn-border-color));
|
|
5
6
|
--symbol-color-active: var(--zn-color-amber-400);
|
|
6
7
|
--symbol-size: 1.2rem;
|
|
7
8
|
--symbol-spacing: 4px;
|
|
9
|
+
--preview-color: rgb(var(--zn-text));
|
|
10
|
+
--preview-size: 0.875rem;
|
|
8
11
|
|
|
9
12
|
display: block;
|
|
10
13
|
}
|
|
@@ -12,6 +15,7 @@
|
|
|
12
15
|
.rating {
|
|
13
16
|
position: relative;
|
|
14
17
|
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
15
19
|
border-radius: var(--zn-border-radius);
|
|
16
20
|
vertical-align: middle;
|
|
17
21
|
|
|
@@ -62,6 +66,21 @@
|
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
&__preview {
|
|
70
|
+
margin-left: var(--symbol-spacing);
|
|
71
|
+
font-size: var(--preview-size);
|
|
72
|
+
font-weight: var(--zn-font-weight-semibold);
|
|
73
|
+
line-height: 1;
|
|
74
|
+
color: var(--preview-color);
|
|
75
|
+
font-variant-numeric: tabular-nums;
|
|
76
|
+
pointer-events: none;
|
|
77
|
+
user-select: none;
|
|
78
|
+
|
|
79
|
+
&--hover {
|
|
80
|
+
color: var(--symbol-color-active);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
&--disabled &__symbols,
|
|
66
85
|
&--readonly &__symbols {
|
|
67
86
|
cursor: default;
|
|
@@ -72,6 +91,11 @@
|
|
|
72
91
|
scale: none;
|
|
73
92
|
}
|
|
74
93
|
|
|
94
|
+
&--disabled &__preview--hover,
|
|
95
|
+
&--readonly &__preview--hover {
|
|
96
|
+
color: var(--preview-color);
|
|
97
|
+
}
|
|
98
|
+
|
|
75
99
|
&--disabled {
|
|
76
100
|
opacity: 0.5;
|
|
77
101
|
}
|