;
/**
* The current rating.
*/
@@ -1735,6 +1747,10 @@ declare namespace LocalJSX {
* Disables the rating.
*/
"disabled"?: boolean;
+ /**
+ * A function that returns the symbols to display. Accepts an option `value` parameter you can use to map a specific symbol to a value.
+ */
+ "getSymbol"?: (value?: number) => string;
/**
* The highest rating to show.
*/
diff --git a/src/components/rating/rating.scss b/src/components/rating/rating.scss
index 7cc5f6c5..0c839d93 100644
--- a/src/components/rating/rating.scss
+++ b/src/components/rating/rating.scss
@@ -1,16 +1,25 @@
@import 'component';
+/**
+ * @prop --symbol-color: The inactive color for symbols.
+ * @prop --symbol-color-active: The active color for symbols.
+ * @prop --symbol-size: The size of symbols.
+ * @prop --symbol-spacing: The spacing to use around symbols.
+ */
:host {
- display: inline-flex;
+ display: inline-block;
- --inactive-color: var(--sl-color-gray-90);
- --active-color: #f8e71c;
+ --symbol-color: var(--sl-color-gray-85);
+ --symbol-color-active: #ffbe00;
+ --symbol-size: 1.2rem;
+ --symbol-spacing: var(--sl-spacing-xxx-small);
}
.rating {
position: relative;
display: inline-flex;
border-radius: var(--sl-border-radius-medium);
+ vertical-align: middle;
&:focus {
outline: none;
@@ -22,28 +31,50 @@
}
.rating__symbols {
- display: inline-block;
+ display: inline-flex;
position: relative;
- font-size: 1.4rem;
+ font-size: var(--symbol-size);
line-height: 0;
- color: var(--inactive-color);
- cursor: pointer;
+ color: var(--symbol-color);
white-space: nowrap;
+ cursor: pointer;
- > :not(:last-of-type) {
- margin-right: var(--sl-spacing-xx-small);
+ > * {
+ padding: var(--symbol-spacing);
}
}
-.rating__indicator {
+.rating__symbols--indicator {
position: absolute;
top: 0;
left: 0;
- color: var(--active-color);
- overflow: hidden;
+ color: var(--symbol-color-active);
+ pointer-events: none;
}
.rating__symbol {
- display: inline-block;
- transition: var(--sl-transition-medium) transform;
+ transition: var(--sl-transition-fast) transform;
+}
+
+.rating__symbol--hover {
+ transform: scale(1.2);
+}
+
+.rating--disabled,
+.rating--readonly {
+ .rating__symbols {
+ cursor: default;
+ }
+
+ .rating__symbol--hover {
+ transform: none;
+ }
+}
+
+.rating--disabled {
+ opacity: 0.5;
+
+ .rating__symbols {
+ cursor: not-allowed;
+ }
}
diff --git a/src/components/rating/rating.tsx b/src/components/rating/rating.tsx
index f3362e10..98bf925a 100644
--- a/src/components/rating/rating.tsx
+++ b/src/components/rating/rating.tsx
@@ -1,4 +1,4 @@
-import { Component, Event, EventEmitter, Prop, State, Watch, h } from '@stencil/core';
+import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch, h } from '@stencil/core';
import { focusVisible } from '../../utilities/focus-visible';
import { clamp } from '../../utilities/math';
@@ -9,17 +9,6 @@ import { clamp } from '../../utilities/math';
* @part base - The component's base wrapper.
*/
-//
-// TODO:
-//
-// - sizing
-// - labels
-// - disabled
-// - readonly
-// - custom icons
-// - icon should grow on hover
-//
-
@Component({
tag: 'sl-rating',
styleUrl: 'rating.scss',
@@ -29,24 +18,26 @@ export class Rating {
constructor() {
this.handleClick = this.handleClick.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
- this.handleMouseOver = this.handleMouseOver.bind(this);
- this.handleMouseOut = this.handleMouseOut.bind(this);
+ this.handleMouseEnter = this.handleMouseEnter.bind(this);
+ this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
}
rating: HTMLElement;
+ @Element() host: HTMLSlRatingElement;
+
@State() hoverValue = 0;
@State() isHovering = false;
/** The current rating. */
- @Prop({ mutable: true, reflect: true }) value = 2.5;
+ @Prop({ mutable: true, reflect: true }) value = 0;
/** The highest rating to show. */
@Prop() max = 5;
/** The minimum increment value allowed by the control. */
- @Prop() precision = 0.5;
+ @Prop() precision = 1;
/** Makes the rating readonly. */
@Prop() readonly = false;
@@ -54,6 +45,11 @@ export class Rating {
/** Disables the rating. */
@Prop() disabled = false;
+ /** A function that returns the symbols to display. Accepts an option `value` parameter you can use to map a specific
+ * symbol to a value. */
+ // @ts-ignore
+ @Prop() getSymbol = (value?: number) => '';
+
@Watch('value')
handleValueChange() {
this.slChange.emit();
@@ -62,6 +58,18 @@ export class Rating {
/** Emitted when the rating's value changes. */
@Event() slChange: EventEmitter;
+ /** Sets focus on the rating. */
+ @Method()
+ async setFocus() {
+ this.rating.focus();
+ }
+
+ /** Removes focus from the rating. */
+ @Method()
+ async removeFocus() {
+ this.rating.blur();
+ }
+
componentDidLoad() {
focusVisible.observe(this.rating);
}
@@ -73,14 +81,33 @@ export class Rating {
getValueFromMousePosition(event: MouseEvent) {
const containerLeft = this.rating.getBoundingClientRect().left;
const containerWidth = this.rating.getBoundingClientRect().width;
- return clamp(this.roundToPrecision(((event.clientX - containerLeft) / containerWidth) * this.max), 0, this.max);
+ return clamp(
+ this.roundToPrecision(((event.clientX - containerLeft) / containerWidth) * this.max, this.precision),
+ 0,
+ this.max
+ );
}
handleClick(event: MouseEvent) {
- this.value = this.getValueFromMousePosition(event);
+ if (this.disabled || this.readonly) {
+ return;
+ }
+
+ const newValue = this.getValueFromMousePosition(event);
+
+ if (newValue === this.value) {
+ this.value = 0;
+ this.isHovering = false;
+ } else {
+ this.value = newValue;
+ }
}
handleKeyDown(event: KeyboardEvent) {
+ if (this.disabled || this.readonly) {
+ return;
+ }
+
if (event.key === 'ArrowLeft') {
const decrement = event.shiftKey ? 1 : this.precision;
this.value = Math.max(0, this.value - decrement);
@@ -104,11 +131,11 @@ export class Rating {
}
}
- handleMouseOver() {
+ handleMouseEnter() {
this.isHovering = true;
}
- handleMouseOut() {
+ handleMouseLeave() {
this.isHovering = false;
}
@@ -122,42 +149,66 @@ export class Rating {
}
render() {
- const counter = Array.from(Array(this.max));
- const displayValue = this.isHovering ? this.hoverValue : this.value;
+ const counter = Array.from(Array(this.max).keys());
+ let displayValue = 0;
+
+ if (this.disabled || this.readonly) {
+ displayValue = this.value;
+ } else {
+ displayValue = this.isHovering ? this.hoverValue : this.value;
+ }
return (
(this.rating = el)}
part="base"
- class="rating"
+ class={{
+ rating: true,
+ 'rating--readonly': this.readonly,
+ 'rating--disabled': this.disabled
+ }}
+ aria-disabled={this.disabled}
+ aria-readonly={this.readonly}
aria-value={this.value}
aria-valuemin={0}
aria-valuemax={this.max}
- tabIndex={0}
+ tabIndex={this.disabled ? -1 : 0}
onClick={this.handleClick}
onKeyDown={this.handleKeyDown}
- onMouseEnter={this.handleMouseOver}
- onMouseLeave={this.handleMouseOut}
+ onMouseEnter={this.handleMouseEnter}
+ onMouseLeave={this.handleMouseLeave}
onMouseMove={this.handleMouseMove}
>
-
- {counter.map(() => (
-
-
-
+
+ {counter.map(index => (
+
))}
-
- {counter.map(() => (
-
-
-
+
+ {counter.map(index => (
+ index + 1 ? null : `inset(0 ${100 - ((displayValue - index) / 1) * 100}% 0 0)`
+ }}
+ role="presentation"
+ innerHTML={this.getSymbol(index + 1)}
+ />
))}