ldc-legaulois/resources/ts/fo/components/RatingStarsComponent.vue
2025-06-24 15:53:48 +02:00

136 lines
3 KiB
Vue

<template>
<div
ref="wrapperRef"
:class="`rating-stars-wrapper position-relative bg-tertiary ${wrapperClass} px-2`"
@mouseleave="hovered = 0"
@focusout="onFocusOut"
>
<!-- Yellow background -->
<div class="position-relative h-100">
<div
class="position-absolute bg-warning h-100"
:style="`width: ${(computedRating / 5) * 100}%;`"
/>
</div>
<!-- SVG mask with cut-out stars -->
<svg
class="position-absolute top-0 start-0"
viewBox="0 0 142 32"
>
<defs>
<mask
id="star-mask"
maskUnits="userSpaceOnUse"
>
<rect
class="w-100 h-100"
fill="white"
/>
<g>
<template
v-for="i in 5"
:key="i"
>
<path
:transform="`translate(${10 + (i - 1) * 25},5)`"
d="M11.0905 0.45459L14.654 6.1861L21.2062 7.80414L16.8565 12.9644L17.3424 19.696L11.0905 17.1537L4.83857 19.696L5.32447 12.9644L0.974682 7.80414L7.52688 6.1861L11.0905 0.45459Z"
fill="black"
/>
</template>
</g>
</mask>
</defs>
<rect
class="w-100 h-100"
fill="white"
mask="url(#star-mask)"
/>
</svg>
<!-- Selected div when read only is false -->
<div
v-if="!readOnly"
class="buttons-wrapper position-absolute top-0 start-0 d-flex justify-content-center px-2"
>
<template
v-for="i in 5"
:key="i"
>
<button
@click="selectRating(i)"
@mouseover="hovered = i"
@focus="hovered = i"
class="btn btn-warning bg-transparent border-0 p-0"
/>
</template>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, defineProps, defineEmits, watch } from "vue";
const emit = defineEmits(["update:rating"]);
const props = defineProps({
rating: {
type: Number,
default: null,
},
wrapperClass: {
type: String,
default: "",
},
readOnly: {
type: Boolean,
default: true,
},
});
const wrapperRef = ref<HTMLElement | null>(null);
const hovered = ref(0);
const selected = ref<number | null>(props.rating);
const computedRating = computed(() => {
if (props.readOnly) return props.rating ?? 0;
return hovered.value || selected.value || 0;
});
watch(() => props.rating, (val) => {
selected.value = val;
});
const selectRating = (value: number) => {
if (!props.readOnly) {
selected.value = value;
emit("update:rating", value);
}
};
const onFocusOut = (e: FocusEvent) => {
const newFocused = e.relatedTarget as HTMLElement | null;
if (!wrapperRef.value?.contains(newFocused)) {
hovered.value = 0;
}
};
</script>
<style lang="scss" scoped>
.rating-stars-wrapper {
&, svg {
width: 142px;
height: 32px;
}
.buttons-wrapper {
width: 142px;
height: 32px;
z-index: 10;
button {
flex: 1;
&:not(:focus) {
box-shadow: none;
}
}
}
}
</style>