Skip to content

Commit a28f201

Browse files
authored
feat(ui5-rating-indicator): initial implementation (#1729)
1 parent 681de1f commit a28f201

13 files changed

+565
-1
lines changed

packages/base/src/UI5Element.js

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import getConstructableStyle from "./theming/getConstructableStyle.js";
1111
import createComponentStyleTag from "./theming/createComponentStyleTag.js";
1212
import getEffectiveStyle from "./theming/getEffectiveStyle.js";
1313
import Integer from "./types/Integer.js";
14+
import Float from "./types/Float.js";
1415
import { kebabToCamelCase, camelToKebabCase } from "./util/StringHelper.js";
1516
import isValidPropertyName from "./util/isValidPropertyName.js";
1617
import isSlot from "./util/isSlot.js";
@@ -306,6 +307,9 @@ class UI5Element extends HTMLElement {
306307
if (propertyTypeClass === Integer) {
307308
newValue = parseInt(newValue);
308309
}
310+
if (propertyTypeClass === Float) {
311+
newValue = parseFloat(newValue);
312+
}
309313
this[nameInCamelCase] = newValue;
310314
}
311315
}

packages/base/src/types/Float.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import DataType from "./DataType.js";
2+
3+
class Float extends DataType {
4+
static isValid(value) {
5+
// Assuming that integers are floats as well!
6+
return Number(value) === value;
7+
}
8+
}
9+
10+
export default Float;

packages/main/bundle.esm.js

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import Select from "./dist/Select.js";
5757
import Switch from "./dist/Switch.js";
5858
import MessageStrip from "./dist/MessageStrip.js";
5959
import MultiComboBox from "./dist/MultiComboBox.js";
60+
import RatingIndicator from "./dist/RatingIndicator.js";
6061
import TabContainer from "./dist/TabContainer.js";
6162
import Tab from "./dist/Tab.js";
6263
import TabSeparator from "./dist/TabSeparator.js";

packages/main/src/RatingIndicator.hbs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<div class="ui5-rating-indicator-root"
2+
role="slider"
3+
aria-roledescription="{{_ariaRoleDescription}}"
4+
aria-valuemin="0"
5+
aria-valuenow="{{this.value}}"
6+
aria-valuemax="{{this.maxValue}}"
7+
aria-orientation="horizontal"
8+
?aria-disabled="{{this.disabled}}"
9+
?aria-readonly="{{this.readOnly}}"
10+
tabindex="{{tabIndex}}"
11+
@focusin="{{_onfocusin}}"
12+
@focusout="{{_onfocusout}}"
13+
@click="{{_onclick}}"
14+
@keydown="{{_onkeydown}}"
15+
>
16+
<div
17+
class="ui5-rating-indicator-stars-wrapper"
18+
>
19+
{{#each _stars}}
20+
{{#if this.selected}}
21+
<div class="ui5-rating-indicator-icon ui5-rating-indicator-active-icon" data-value="{{this.index}}">&#9733;</div>
22+
{{else if this.halfStar}}
23+
<div class="ui5-rating-indicator-icon ui5-rating-indicator-half-icon" data-value="{{this.index}}">&#9734;</div>
24+
{{else}}
25+
<div class="ui5-rating-indicator-icon" data-value="{{this.index}}">&#9734;</div>
26+
{{/if}}
27+
{{/each}}
28+
</div>
29+
</div>

packages/main/src/RatingIndicator.js

+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
2+
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
3+
import {
4+
isDown,
5+
isUp,
6+
isLeft,
7+
isRight,
8+
isSpace,
9+
isEnter,
10+
} from "@ui5/webcomponents-base/dist/Keys.js";
11+
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
12+
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
13+
import Float from "@ui5/webcomponents-base/dist/types/Float.js";
14+
import {
15+
RATING_INDICATOR_TEXT,
16+
} from "./generated/i18n/i18n-defaults.js";
17+
import RatingIndicatorTemplate from "./generated/templates/RatingIndicatorTemplate.lit.js";
18+
19+
// Styles
20+
import RatingIndicatorCss from "./generated/themes/RatingIndicator.css.js";
21+
22+
/**
23+
* @public
24+
*/
25+
const metadata = {
26+
tag: "ui5-rating-indicator",
27+
properties: /** @lends sap.ui.webcomponents.main.RatingIndicator.prototype */ {
28+
29+
/**
30+
* The indicated value of the rating
31+
* <br><br>
32+
* <b>Note:</b> If you set a number which is not round, it would be shown as follows:
33+
* <ul>
34+
* <li>1.0 - 1.2 -> 1</li>
35+
* <li>1.3 - 1.7 -> 1.5</li>
36+
* <li>1.8 - 1.9 -> 2</li>
37+
* <ul>
38+
* @type {Float}
39+
* @defaultvalue 0
40+
* @public
41+
*/
42+
value: {
43+
type: Float,
44+
defaultValue: 0,
45+
},
46+
47+
/**
48+
* The number of displayed rating symbols
49+
* @type {Integer}
50+
* @defaultvalue 5
51+
* @public
52+
*/
53+
maxValue: {
54+
type: Integer,
55+
defaultValue: 5,
56+
},
57+
58+
/**
59+
* Defines whether the <code>ui5-rating-indicator</code> is disabled.
60+
* @type {boolean}
61+
* @defaultvalue false
62+
* @public
63+
*/
64+
disabled: {
65+
type: Boolean,
66+
},
67+
68+
/**
69+
* @type {boolean}
70+
* @defaultvalue false
71+
* @public
72+
*/
73+
readonly: {
74+
type: Boolean,
75+
},
76+
77+
/**
78+
* @private
79+
*/
80+
_stars: {
81+
type: Object,
82+
multiple: true,
83+
},
84+
85+
/**
86+
* @private
87+
*/
88+
_focused: {
89+
type: Boolean,
90+
},
91+
},
92+
slots: /** @lends sap.ui.webcomponents.main.RatingIndicator.prototype */ {
93+
//
94+
},
95+
events: /** @lends sap.ui.webcomponents.main.RatingIndicator.prototype */ {
96+
97+
/**
98+
* The event is fired when the value changes.
99+
*
100+
* @event
101+
* @public
102+
*/
103+
change: {},
104+
},
105+
};
106+
107+
/**
108+
* @class
109+
*
110+
* <h3 class="comment-api-title">Overview</h3>
111+
* The rating indicator is used to display a specific number of icons that are used to rate an item.
112+
* Additionally, it is also used to display the average and overall ratings.
113+
*
114+
* <h3>Usage</h3>
115+
* The preferred number of icons is between 5 and 7.
116+
*
117+
* <h3>Responsive Behavior</h3>
118+
* You can change the size of the Rating Indicator by changing its <code>font-size</code> CSS property.
119+
* <br>
120+
* Example: <code><ui5-rating-indicator style="font-size: 3rem;"></ui5-rating-indicator></code>
121+
*
122+
* <h3>Usage</h3>
123+
*
124+
* For the <code>ui5-rating-indicator</code>
125+
* <h3>ES6 Module Import</h3>
126+
*
127+
* <code>import @ui5/webcomponents/dist/RatingIndicator.js";</code>
128+
*
129+
* @constructor
130+
* @author SAP SE
131+
* @alias sap.ui.webcomponents.main.RatingIndicator
132+
* @extends UI5Element
133+
* @tagname ui5-rating-indicator
134+
* @public
135+
*/
136+
class RatingIndicator extends UI5Element {
137+
static get metadata() {
138+
return metadata;
139+
}
140+
141+
static get render() {
142+
return litRender;
143+
}
144+
145+
static get styles() {
146+
return RatingIndicatorCss;
147+
}
148+
149+
static get template() {
150+
return RatingIndicatorTemplate;
151+
}
152+
153+
static async onDefine() {
154+
await Promise.all([
155+
fetchI18nBundle("@ui5/webcomponents"),
156+
]);
157+
}
158+
159+
constructor() {
160+
super();
161+
162+
this._liveValue = null; // stores the value to determine when to fire change
163+
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
164+
}
165+
166+
onBeforeRendering() {
167+
this.calcState();
168+
}
169+
170+
calcState() {
171+
this._stars = [];
172+
173+
for (let i = 1; i < this.maxValue + 1; i++) {
174+
const remainder = Math.round((this.value - Math.floor(this.value)) * 10);
175+
let halfStar = false,
176+
tempValue = this.value;
177+
178+
if (Math.floor(this.value) + 1 === i && remainder > 2 && remainder < 8) {
179+
halfStar = true;
180+
} else if (remainder <= 2) {
181+
tempValue = Math.floor(this.value);
182+
} else if (remainder >= 8) {
183+
tempValue = Math.ceil(this.value);
184+
}
185+
186+
this._stars.push({
187+
selected: i <= tempValue,
188+
index: i,
189+
halfStar,
190+
});
191+
}
192+
}
193+
194+
_onclick(event) {
195+
if (this.disabled || this.readonly) {
196+
return;
197+
}
198+
199+
this.value = parseInt(event.target.getAttribute("data-value"));
200+
201+
if (this.value === 1 && this._liveValue === 1) {
202+
this.value = 0;
203+
}
204+
205+
if (this._liveValue !== this.value) {
206+
this.fireEvent("change");
207+
this._liveValue = this.value;
208+
}
209+
}
210+
211+
_onkeydown(event) {
212+
if (this.disabled || this.readonly) {
213+
return;
214+
}
215+
216+
const down = isDown(event) || isLeft(event);
217+
const up = isRight(event) || isUp(event) || isSpace(event) || isEnter(event);
218+
219+
if (down || up) {
220+
event.preventDefault();
221+
222+
if (down && this.value > 0) {
223+
this.value = Math.round(this.value - 1);
224+
this.fireEvent("change");
225+
} else if (up && this.value < this.maxValue) {
226+
this.value = Math.round(this.value + 1);
227+
this.fireEvent("change");
228+
}
229+
}
230+
}
231+
232+
_onfocusin() {
233+
if (this.disabled) {
234+
return;
235+
}
236+
237+
this._focused = true;
238+
this._liveValue = this.value;
239+
}
240+
241+
_onfocusout() {
242+
this._focused = false;
243+
}
244+
245+
get tabIndex() {
246+
return this.disabled ? "-1" : "0";
247+
}
248+
249+
get _ariaRoleDescription() {
250+
return this.i18nBundle.getText(RATING_INDICATOR_TEXT);
251+
}
252+
}
253+
254+
RatingIndicator.define();
255+
256+
export default RatingIndicator;

packages/main/src/i18n/messagebundle.properties

+3
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ MULTIINPUT_SHOW_MORE_TOKENS={0} More
265265
#XTOL: Tooltip for panel expand title
266266
PANEL_ICON=Expand/Collapse
267267

268+
#XBUT: Rating indicator aria-roledescription text
269+
RATING_INDICATOR_TEXT=Rating Indicator
270+
268271
#XACT: ARIA description for the segmented button
269272
SEGMENTEDBUTTON_ARIA_DESCRIPTION=Segmented button
270273

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
:host(:not([hidden])) {
2+
display: inline-block;
3+
font-size: 1.5rem;
4+
cursor: pointer;
5+
}
6+
7+
:host([disabled]) {
8+
opacity: .6;
9+
cursor: initial;
10+
outline: none;
11+
}
12+
13+
:host([readonly]) {
14+
cursor: initial;
15+
}
16+
17+
:host([_focused]) {
18+
outline: 1px dotted var(--sapContent_FocusColor);
19+
}
20+
21+
.ui5-rating-indicator-root {
22+
outline: none;
23+
}
24+
25+
.ui5-rating-indicator-icon {
26+
position: relative;
27+
color: var(--sapContent_UnratedColor);
28+
user-select: none;
29+
}
30+
31+
.ui5-rating-indicator-icon.ui5-rating-indicator-active-icon {
32+
color: var(--sapContent_RatedColor);
33+
}
34+
35+
.ui5-rating-indicator-icon.ui5-rating-indicator-half-icon:before {
36+
content: "\2605";
37+
position: absolute;
38+
top: 0;
39+
left: 0;
40+
width: 50%;
41+
height: 100%;
42+
color: var(--sapContent_RatedColor);
43+
overflow: hidden;
44+
}
45+
46+
.ui5-rating-indicator-stars-wrapper {
47+
display: flex;
48+
}

0 commit comments

Comments
 (0)