astro_avtourist/frontend/src/components/reviews/RatingStars.astro

207 lines
5.8 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
export interface Props {
rating?: number;
interactive?: boolean;
size?: 'sm' | 'md' | 'lg';
onRate?: (rating: number) => void;
}
const { rating = 0, interactive = false, size = 'md' } = Astro.props;
const sizeClasses = {
sm: 'w-4 h-4',
md: 'w-5 h-5',
lg: 'w-7 h-7'
};
const starSize = sizeClasses[size];
const uniqueId = `rating-${Math.random().toString(36).substring(2, 9)}`;
---
<div class={`rating-stars ${interactive ? 'interactive' : ''}`} data-rating={rating} data-unique={uniqueId}>
{[1, 2, 3, 4, 5].map((star) => (
<button
type="button"
class={`star ${star <= rating ? 'filled' : ''} ${starSize}`}
data-value={star}
aria-label={`Оценка ${star} из 5`}
disabled={!interactive}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill={star <= rating ? 'currentColor' : 'none'} stroke="currentColor">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
</button>
))}
</div>
<style>
.rating-stars {
display: inline-flex;
gap: 0.25rem;
align-items: center;
}
.star {
background: none;
border: none;
padding: 0;
cursor: default;
color: #d1d5db;
display: inline-flex;
align-items: center;
justify-content: center;
transition: color 0.15s ease, transform 0.15s ease;
}
.star.filled {
color: #fbbf24;
}
.star.interactive {
cursor: pointer;
}
.star.interactive:hover {
transform: scale(1.15);
}
.star.interactive:active {
transform: scale(0.95);
}
/* Hover эффект для интерактивных звёзд */
.rating-stars.interactive:hover .star.interactive {
color: #fbbf24;
}
.rating-stars.interactive .star.interactive:hover ~ .star {
color: #d1d5db;
}
</style>
<script>
// Клиентский скрипт для интерактивного голосования
document.addEventListener('DOMContentLoaded', () => {
const containers = document.querySelectorAll('.rating-stars.interactive');
containers.forEach(container => {
const stars = container.querySelectorAll('.star.interactive');
let currentRating = parseInt(container.getAttribute('data-rating') || '0');
stars.forEach(star => {
star.addEventListener('click', () => {
const value = parseInt(star.getAttribute('data-value') || '0');
currentRating = value;
container.setAttribute('data-rating', value.toString());
// Обновляем классы звёзд
stars.forEach((s, index) => {
if (index < value) {
s.classList.add('filled');
} else {
s.classList.remove('filled');
}
});
// Диспатчим кастомное событие
window.dispatchEvent(
new CustomEvent('review-rated', {
detail: {
rating: value,
uniqueId: container.getAttribute('data-unique')
}
})
);
});
// Hover эффект
star.addEventListener('mouseenter', () => {
const value = parseInt(star.getAttribute('data-value') || '0');
stars.forEach((s, index) => {
const el = s as HTMLElement;
if (index < value) {
el.style.color = '#fbbf24';
} else {
el.style.color = '#d1d5db';
}
});
});
});
// Сброс при уходе мыши
container.addEventListener('mouseleave', () => {
stars.forEach((s, index) => {
const el = s as HTMLElement;
if (index < currentRating) {
el.style.color = '#fbbf24';
} else {
el.style.color = '#d1d5db';
}
});
});
});
});
// Для Astro View Transitions
document.addEventListener('astro:page-load', () => {
const containers = document.querySelectorAll('.rating-stars.interactive');
containers.forEach(container => {
const stars = container.querySelectorAll('.star.interactive');
let currentRating = parseInt(container.getAttribute('data-rating') || '0');
stars.forEach(star => {
star.replaceWith(star.cloneNode(true));
});
const newStars = container.querySelectorAll('.star.interactive');
newStars.forEach(star => {
star.addEventListener('click', () => {
const value = parseInt(star.getAttribute('data-value') || '0');
currentRating = value;
container.setAttribute('data-rating', value.toString());
newStars.forEach((s, index) => {
if (index < value) {
s.classList.add('filled');
} else {
s.classList.remove('filled');
}
});
window.dispatchEvent(
new CustomEvent('review-rated', {
detail: {
rating: value,
uniqueId: container.getAttribute('data-unique')
}
})
);
});
star.addEventListener('mouseenter', () => {
const value = parseInt(star.getAttribute('data-value') || '0');
newStars.forEach((s, index) => {
const el = s as HTMLElement;
if (index < value) {
el.style.color = '#fbbf24';
} else {
el.style.color = '#d1d5db';
}
});
});
});
container.addEventListener('mouseleave', () => {
newStars.forEach((s, index) => {
const el = s as HTMLElement;
if (index < currentRating) {
el.style.color = '#fbbf24';
} else {
el.style.color = '#d1d5db';
}
});
});
});
});
</script>