Files
rideaware-ui/src/components/workout/StarRating.vue
2026-01-21 18:25:46 -06:00

106 lines
2.0 KiB
Vue

<template>
<div class="star-rating" :class="[`size-${size}`, { readonly }]">
<button
v-for="star in 5"
:key="star"
type="button"
class="star-btn"
:class="{ filled: star <= displayRating, hovered: star <= hoverRating }"
@click="!readonly && selectRating(star)"
@mouseenter="!readonly && (hoverRating = star)"
@mouseleave="hoverRating = 0"
:disabled="readonly"
>
<svg viewBox="0 0 24 24" fill="none">
<path
d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"
:fill="star <= displayRating || star <= hoverRating ? 'currentColor' : 'none'"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button>
</div>
</template>
<script setup>
import { ref, computed, defineProps, defineEmits } from 'vue'
const props = defineProps({
rating: {
type: Number,
default: 0
},
readonly: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'medium',
validator: (v) => ['small', 'medium', 'large'].includes(v)
}
})
const emit = defineEmits(['update:rating', 'rate'])
const hoverRating = ref(0)
const displayRating = computed(() => Math.round(props.rating))
function selectRating(star) {
emit('update:rating', star)
emit('rate', star)
}
</script>
<style scoped>
.star-rating {
display: inline-flex;
gap: 2px;
}
.star-btn {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--color-border);
transition: all var(--transition-base);
}
.star-btn:disabled {
cursor: default;
}
.star-btn.filled,
.star-btn.hovered {
color: #f5a623;
}
.star-btn:not(:disabled):hover {
transform: scale(1.1);
}
.readonly .star-btn {
cursor: default;
}
.size-small .star-btn svg {
width: 14px;
height: 14px;
}
.size-medium .star-btn svg {
width: 20px;
height: 20px;
}
.size-large .star-btn svg {
width: 28px;
height: 28px;
}
</style>