Initial commit for rating (unfinished)

pull/130/head
Cory LaViska 2020-07-20 06:17:20 -04:00
rodzic 95016b3178
commit b57151f282
5 zmienionych plików z 284 dodań i 0 usunięć

Wyświetl plik

@ -29,6 +29,7 @@
- [Progress Ring](/components/progress-ring.md)
- [Radio](/components/radio.md)
- [Range](/components/range.md)
- [Rating](/components/rating.md)
- [Select](/components/select.md)
- [Spinner](/components/spinner.md)
- [Switch](/components/switch.md)

Wyświetl plik

@ -0,0 +1,11 @@
# Rating
[component-header:sl-rating]
Ratings show feedback, typically in the form of stars, and let users provide their own feedback.
```html preview
<sl-rating></sl-rating>
```
[component-metadata:sl-rating]

57
src/components.d.ts vendored
Wyświetl plik

@ -567,6 +567,28 @@ export namespace Components {
*/
"value": number;
}
interface SlRating {
/**
* Disables the rating.
*/
"disabled": boolean;
/**
* The highest rating to show.
*/
"max": number;
/**
* The minimum increment value allowed by the control.
*/
"precision": number;
/**
* Makes the rating readonly.
*/
"readonly": boolean;
/**
* The current rating.
*/
"value": number;
}
interface SlSelect {
/**
* Set to true to disable the select control.
@ -972,6 +994,12 @@ declare global {
prototype: HTMLSlRangeElement;
new (): HTMLSlRangeElement;
};
interface HTMLSlRatingElement extends Components.SlRating, HTMLStencilElement {
}
var HTMLSlRatingElement: {
prototype: HTMLSlRatingElement;
new (): HTMLSlRatingElement;
};
interface HTMLSlSelectElement extends Components.SlSelect, HTMLStencilElement {
}
var HTMLSlSelectElement: {
@ -1048,6 +1076,7 @@ declare global {
"sl-progress-ring": HTMLSlProgressRingElement;
"sl-radio": HTMLSlRadioElement;
"sl-range": HTMLSlRangeElement;
"sl-rating": HTMLSlRatingElement;
"sl-select": HTMLSlSelectElement;
"sl-spinner": HTMLSlSpinnerElement;
"sl-switch": HTMLSlSwitchElement;
@ -1701,6 +1730,32 @@ declare namespace LocalJSX {
*/
"value"?: number;
}
interface SlRating {
/**
* Disables the rating.
*/
"disabled"?: boolean;
/**
* The highest rating to show.
*/
"max"?: number;
/**
* Emitted when the rating's value changes.
*/
"onSlChange"?: (event: CustomEvent<any>) => void;
/**
* The minimum increment value allowed by the control.
*/
"precision"?: number;
/**
* Makes the rating readonly.
*/
"readonly"?: boolean;
/**
* The current rating.
*/
"value"?: number;
}
interface SlSelect {
/**
* Set to true to disable the select control.
@ -2020,6 +2075,7 @@ declare namespace LocalJSX {
"sl-progress-ring": SlProgressRing;
"sl-radio": SlRadio;
"sl-range": SlRange;
"sl-rating": SlRating;
"sl-select": SlSelect;
"sl-spinner": SlSpinner;
"sl-switch": SlSwitch;
@ -2056,6 +2112,7 @@ declare module "@stencil/core" {
"sl-progress-ring": LocalJSX.SlProgressRing & JSXBase.HTMLAttributes<HTMLSlProgressRingElement>;
"sl-radio": LocalJSX.SlRadio & JSXBase.HTMLAttributes<HTMLSlRadioElement>;
"sl-range": LocalJSX.SlRange & JSXBase.HTMLAttributes<HTMLSlRangeElement>;
"sl-rating": LocalJSX.SlRating & JSXBase.HTMLAttributes<HTMLSlRatingElement>;
"sl-select": LocalJSX.SlSelect & JSXBase.HTMLAttributes<HTMLSlSelectElement>;
"sl-spinner": LocalJSX.SlSpinner & JSXBase.HTMLAttributes<HTMLSlSpinnerElement>;
"sl-switch": LocalJSX.SlSwitch & JSXBase.HTMLAttributes<HTMLSlSwitchElement>;

Wyświetl plik

@ -0,0 +1,49 @@
@import 'component';
:host {
display: inline-flex;
--inactive-color: var(--sl-color-gray-90);
--active-color: #f8e71c;
}
.rating {
position: relative;
display: inline-flex;
border-radius: var(--sl-border-radius-medium);
&:focus {
outline: none;
}
&.focus-visible:focus {
box-shadow: var(--sl-focus-ring-box-shadow);
}
}
.rating__symbols {
display: inline-block;
position: relative;
font-size: 1.4rem;
line-height: 0;
color: var(--inactive-color);
cursor: pointer;
white-space: nowrap;
> :not(:last-of-type) {
margin-right: var(--sl-spacing-xx-small);
}
}
.rating__indicator {
position: absolute;
top: 0;
left: 0;
color: var(--active-color);
overflow: hidden;
}
.rating__symbol {
display: inline-block;
transition: var(--sl-transition-medium) transform;
}

Wyświetl plik

@ -0,0 +1,166 @@
import { Component, Event, EventEmitter, Prop, State, Watch, h } from '@stencil/core';
import { focusVisible } from '../../utilities/focus-visible';
import { clamp } from '../../utilities/math';
/**
* @since 2.0
* @status stable
*
* @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',
shadow: true
})
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.handleMouseMove = this.handleMouseMove.bind(this);
}
rating: HTMLElement;
@State() hoverValue = 0;
@State() isHovering = false;
/** The current rating. */
@Prop({ mutable: true, reflect: true }) value = 2.5;
/** The highest rating to show. */
@Prop() max = 5;
/** The minimum increment value allowed by the control. */
@Prop() precision = 0.5;
/** Makes the rating readonly. */
@Prop() readonly = false;
/** Disables the rating. */
@Prop() disabled = false;
@Watch('value')
handleValueChange() {
this.slChange.emit();
}
/** Emitted when the rating's value changes. */
@Event() slChange: EventEmitter;
componentDidLoad() {
focusVisible.observe(this.rating);
}
componentDidUnload() {
focusVisible.unobserve(this.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);
}
handleClick(event: MouseEvent) {
this.value = this.getValueFromMousePosition(event);
}
handleKeyDown(event: KeyboardEvent) {
if (event.key === 'ArrowLeft') {
const decrement = event.shiftKey ? 1 : this.precision;
this.value = Math.max(0, this.value - decrement);
event.preventDefault();
}
if (event.key === 'ArrowRight') {
const increment = event.shiftKey ? 1 : this.precision;
this.value = Math.min(this.max, this.value + increment);
event.preventDefault();
}
if (event.key === 'Home') {
this.value = 0;
event.preventDefault();
}
if (event.key === 'End') {
this.value = this.max;
event.preventDefault();
}
}
handleMouseOver() {
this.isHovering = true;
}
handleMouseOut() {
this.isHovering = false;
}
handleMouseMove(event: MouseEvent) {
this.hoverValue = this.getValueFromMousePosition(event);
}
roundToPrecision(numberToRound: number, precision = 0.5) {
const multiplier = 1 / precision;
return Math.ceil(numberToRound * multiplier) / multiplier;
}
render() {
const counter = Array.from(Array(this.max));
const displayValue = this.isHovering ? this.hoverValue : this.value;
return (
<div
ref={el => (this.rating = el)}
part="base"
class="rating"
aria-value={this.value}
aria-valuemin={0}
aria-valuemax={this.max}
tabIndex={0}
onClick={this.handleClick}
onKeyDown={this.handleKeyDown}
onMouseEnter={this.handleMouseOver}
onMouseLeave={this.handleMouseOut}
onMouseMove={this.handleMouseMove}
>
<span class="rating__symbols">
{counter.map(() => (
<span class="rating__symbol">
<sl-icon name="star-fill" role="presentation" />
</span>
))}
</span>
<span
class="rating__symbols rating__indicator"
style={{
width: `${(displayValue / this.max) * 100}%`
}}
>
{counter.map(() => (
<span class="rating__symbol">
<sl-icon name="star-fill" role="presentation" />
</span>
))}
</span>
</div>
);
}
}