| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- <script setup lang="ts">
- import StarSvg from '~/assets/icons/star.svg'
- import StarActiveSvg from '~/assets/icons/star-active.svg'
- interface Props {
- // Current rating value (1-5)
- modelValue?: number
- // Display mode: 'display' for readonly, 'rate' for interactive
- mode?: 'display' | 'rate'
- // Size of the star icon
- size?: number
- // Total number of stars
- maxStars?: number
- }
- interface Emits {
- (e: 'update:modelValue' | 'change', value: number): void
- }
- const props = withDefaults(defineProps<Props>(), {
- modelValue: 0,
- mode: 'display',
- size: 40,
- maxStars: 5,
- })
- const emit = defineEmits<Emits>()
- const hoveredStar = ref<number | null>(null)
- const handleStarClick = (star: number) => {
- if (props.mode === 'rate') {
- emit('update:modelValue', star)
- emit('change', star)
- }
- }
- const handleStarHover = (star: number) => {
- if (props.mode === 'rate') {
- hoveredStar.value = star
- }
- }
- const handleStarLeave = () => {
- if (props.mode === 'rate') {
- hoveredStar.value = null
- }
- }
- const isStarActive = (star: number) => {
- if (props.mode === 'rate' && hoveredStar.value !== null) {
- return star <= hoveredStar.value
- }
- return star <= props.modelValue
- }
- </script>
- <template>
- <div
- class="star-rating flex items-center gap-1"
- :class="{ 'star-rating--interactive': mode === 'rate' }"
- >
- <button
- v-for="star in maxStars"
- :key="`star-${star}`"
- type="button"
- class="star-button"
- :class="{
- 'star-button--interactive': mode === 'rate',
- 'star-button--active': isStarActive(star),
- }"
- :disabled="mode === 'display'"
- @click="handleStarClick(star)"
- @mouseenter="handleStarHover(star)"
- @mouseleave="handleStarLeave"
- >
- <component
- :is="isStarActive(star) ? StarActiveSvg : StarSvg"
- :style="{ width: `${size}px`, height: `${size}px` }"
- />
- </button>
- </div>
- </template>
- <style lang="scss" scoped>
- .star-rating {
- display: flex;
- align-items: center;
- .star-button {
- padding: 0;
- border: none;
- background: none;
- cursor: default;
- transition: transform 0.15s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- &--interactive {
- cursor: pointer;
- &:hover {
- transform: scale(1.1);
- }
- &:active {
- transform: scale(0.95);
- }
- }
- &:disabled {
- cursor: default;
- }
- }
- }
- </style>
|