Skip to content

Commit

Permalink
Rating component features completed (#2893)
Browse files Browse the repository at this point in the history
* Rating component features completed

* Change asset

* Cancel icon templating completed

* Templating changes Docs completed

* Accessibility issues solved

* Rating clickoutside issue solved

* Icon src name changed

* About cancel focus problem

* Keyboard event bug fixes

* Template refactor

Co-authored-by: Onur Senture <woof.uyelik@gmail.com>
  • Loading branch information
bahadirsofuoglu and onursenture authored Sep 8, 2022
1 parent 789ce5d commit f0ae013
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 13 deletions.
18 changes: 18 additions & 0 deletions api-generator/components/rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ const RatingProps = [
type: 'boolean',
default: 'true',
description: 'When specified a cancel icon is displayed to allow clearing the value.'
},
{
name: 'onIcon',
type: 'string',
default: 'pi pi-star',
description: 'Can be used to add different icon.'
},
{
name: 'offIcon',
type: 'string',
default: 'pi pi-star-fill',
description: 'Can be used to add different icon.'
},
{
name: 'cancelIcon',
type: 'string',
default: 'pi pi-ban',
description: 'Can be used to add different cancel icon.'
}
];

Expand Down
Binary file added public/demo/images/rating/cancel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/demo/images/rating/custom-asset-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/demo/images/rating/custom-asset.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions src/components/rating/Rating.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ export interface RatingProps {
* Default value is true.
*/
cancel?: boolean | undefined;
/**
* Can be used to add different icon.
* Default value is pi pi-star.
*/
onIcon?: string | undefined;
/**
* Can be used to add different icon.
* Default value is pi pi-star-fill.
*/
offIcon?: string | undefined;
/**
* Can be used to add different cancel icon.
* Default value is pi pi-ban.
*/
cancelIcon?: string | undefined;
}

export interface RatingSlots {
Expand Down
114 changes: 102 additions & 12 deletions src/components/rating/Rating.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
<template>
<div :class="containerClass">
<span v-if="cancel" :class="['p-rating-icon p-rating-cancel pi pi-ban', { 'p-focus': focusIndex === 0 }]" @click="onCancelClick" @keydown="onKeyDown">
<span v-if="cancel" class="p-hidden-accessible">
<input type="radio" value="0" :name="name" :checked="modelValue === 0" :disabled="disabled" :readonly="readonly" :aria-label="$primevue.config.locale.clear" @focus="onFocus($event, 0)" @blur="onBlur" @keydown="onKeyDown($event, 0)" />
<div ref="rating" :class="containerClass">
<template v-if="cancel">
<span :class="cancelIconClasses()" @click="onCancelClick" @keydown="onKeyDown">
<component v-if="$slots.cancel" :is="$slots.cancel" />
<span class="p-hidden-accessible">
<input
type="radio"
value="0"
:name="name"
:checked="modelValue === 0"
:disabled="disabled"
:readonly="readonly"
:aria-label="$primevue.config.locale.clear"
@focus="onFocus($event, 0)"
@blur="onBlur"
@keydown="onKeyDown($event, 0)"
/>
</span>
</span>
</span>
</template>
<template v-for="i in stars" :key="i">
<span :class="['p-rating-icon', { 'pi pi-star': i > modelValue, 'pi pi-star-fill': i <= modelValue, 'p-focus': i === focusIndex }]" @click="onStarClick($event, i)">
<span :class="iconClasses(i)" @click="onStarClick($event, i)">
<component v-if="hasIconSlot && i <= modelValue" :is="$slots.onIcon" :index="i" />
<component v-if="hasIconSlot && i > modelValue" :is="$slots.offIcon" :index="i" />
<span class="p-hidden-accessible">
<input type="radio" :value="i" :name="name" :checked="modelValue === i" :disabled="disabled" :readonly="readonly" :aria-label="ariaLabelTemplate(i)" @focus="onFocus($event, i)" @blur="onBlur" @keydown="onKeyDown($event, i)" />
</span>
Expand All @@ -18,7 +34,7 @@
<script>
export default {
name: 'Rating',
emits: ['update:modelValue', 'change', 'focus', 'blur'],
emits: ['update:modelValue', 'change', 'focus', 'blur', 'cancel'],
props: {
modelValue: {
type: Number,
Expand All @@ -43,21 +59,47 @@ export default {
cancel: {
type: Boolean,
default: true
},
onIcon: {
type: String,
default: 'pi pi-star'
},
offIcon: {
type: String,
default: 'pi pi-star-fill'
},
cancelIcon: {
type: String,
default: 'pi pi-ban'
}
},
data() {
return {
focusIndex: null
focusIndex: null,
outsideClickListener: null,
keyboardEvent: false
};
},
mounted() {
this.bindOutsideClickListener();
},
beforeUnmount() {
this.unbindOutsideClickListener();
},
methods: {
onStarClick(event, value) {
if (!this.readonly && !this.disabled) {
if (!this.readonly && !this.disabled && !this.keyboardEvent) {
this.updateModel(event, value);
this.focusIndex = value;
window.setTimeout(() => {
this.focusIndex = value;
}, 1);
}
this.keyboardEvent = false;
},
onKeyDown(event, value) {
this.keyboardEvent = true;
if (event.code === 'Space') {
this.updateModel(event, value);
}
Expand All @@ -68,8 +110,15 @@ export default {
},
onFocus(event, index) {
if (!this.readonly) {
if (this.modelValue === null && this.focusIndex === null) {
this.cancel ? (this.focusIndex = 0) : (this.focusIndex = 1);
if (this.modelValue === null && this.focusIndex === null && index === this.stars) {
this.focusIndex = this.cancel ? 0 : 1;
this.updateModel(event, this.focusIndex);
} else if (this.modelValue === 0 && this.focusIndex === 0 && index === this.stars - 1) {
this.focusIndex = index + 1;
this.updateModel(event, this.focusIndex);
} else if (this.modelValue === null && this.focusIndex === null) {
this.focusIndex = this.cancel ? 0 : 1;
this.updateModel(event, this.focusIndex);
} else if (this.modelValue !== null && this.focusIndex === null) {
this.focusIndex = this.modelValue;
this.updateModel(event, this.modelValue);
Expand All @@ -86,7 +135,11 @@ export default {
},
onCancelClick(event) {
if (!this.readonly && !this.disabled) {
window.setTimeout(() => {
this.focusIndex = 0;
}, 1);
this.updateModel(event, null);
this.$emit('cancel');
}
},
updateModel(event, value) {
Expand All @@ -98,6 +151,42 @@ export default {
},
ariaLabelTemplate(index) {
return index === 1 ? this.$primevue.config.locale.aria.star : this.$primevue.config.locale.aria.stars.replace(/{star}/g, index);
},
iconClasses(i) {
const iconOn = i > this.modelValue && !this.hasIconSlot() ? this.onIcon : null;
const iconOff = i <= this.modelValue && !this.hasIconSlot() ? this.offIcon : null;
return ['p-rating-icon', iconOn, iconOff, { 'p-focus': i === this.focusIndex }];
},
cancelIconClasses() {
const focusOnCancel = this.focusIndex === 0 && (this.modelValue === 0 || this.modelValue === null);
if (this.$slots.cancel) {
return ['p-rating-icon', { 'p-focus': focusOnCancel }];
}
return ['p-rating-icon p-rating-cancel', this.cancelIcon, { 'p-focus': focusOnCancel }];
},
hasIconSlot() {
return this.$slots.onIcon && this.$slots.offIcon;
},
bindOutsideClickListener() {
this.outsideClickListener = (event) => {
if (this.focusIndex !== null && this.isOutsideRatingClicked(event)) {
this.focusIndex = null;
}
};
document.addEventListener('click', this.outsideClickListener);
},
unbindOutsideClickListener() {
document.removeEventListener('click', this.outsideClickListener);
this.outsideClickListener = null;
},
isOutsideRatingClicked(event) {
const ratingRef = this.$refs.rating;
return !(ratingRef.isSameNode(event.target) || ratingRef.contains(event.target));
}
},
computed: {
Expand All @@ -117,6 +206,7 @@ export default {
<style>
.p-rating-icon {
cursor: pointer;
display: inline-block;
}
.p-rating.p-rating-readonly .p-rating-icon {
Expand Down
20 changes: 19 additions & 1 deletion src/views/rating/RatingDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@

<h5>Disabled</h5>
<Rating :modelValue="8" :disabled="true" :stars="10" name="disabled" />

<h5>Custom Icons</h5>
<Rating v-model="val3" on-icon="pi pi-heart" off-icon="pi pi-heart-fill" :stars="5" name="primeIcons" cancel-icon="pi pi-times" />

<h5>Templating</h5>
<Rating v-model="val4" name="templating">
<template #cancel>
<img src="demo/images/rating/cancel.png" class="cursor-pointer" height="24" width="24" />
</template>
<template #onIcon>
<img src="demo/images/rating/custom-asset-2.png" height="24" width="24" class="cursor-pointer" />
</template>
<template #offIcon>
<img src="demo/images/rating/custom-asset.png" height="24" width="24" class="cursor-pointer" />
</template>
</Rating>
</div>
</div>

Expand All @@ -35,7 +51,9 @@ export default {
data() {
return {
val1: null,
val2: 3
val2: 3,
val3: 2,
val4: 2
};
},
components: {
Expand Down
Loading

0 comments on commit f0ae013

Please sign in to comment.