quality / commitlint (push) Skipped
quality / changes (push) Successful in 9s
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 39s
quality / web (push) Successful in 1m29s
quality / go (push) Successful in 1m3s
quality / bird2 (push) Successful in 15s
CD / quality (push) Successful in 3m43s
CD / publish (push) Failing after 8m29s
Единый kitDataGridTableLayout и ResourcePage/FrameDataGrid на всех списках. Календарь задач переведён на EventCalendar, lookup — на cascader, у операций появился вид доски. Удалены settings-7 и самописные DataGridSection/toolbar. Co-authored-by: Cursor <[email protected]>
164 lines
3.7 KiB
TypeScript
164 lines
3.7 KiB
TypeScript
import { useState } from "react"
|
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
|
|
import { cn } from "@evobgp/ui/lib/utils"
|
|
import { StarIcon } from "lucide-react"
|
|
|
|
const ratingVariants = cva("flex items-center", {
|
|
variants: {
|
|
size: {
|
|
sm: "gap-2",
|
|
default: "gap-2.5",
|
|
lg: "gap-3",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
size: "default",
|
|
},
|
|
})
|
|
|
|
const starVariants = cva("", {
|
|
variants: {
|
|
size: {
|
|
sm: "w-4 h-4",
|
|
default: "w-5 h-5",
|
|
lg: "w-6 h-6",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
size: "default",
|
|
},
|
|
})
|
|
|
|
const valueVariants = cva("text-muted-foreground w-5", {
|
|
variants: {
|
|
size: {
|
|
sm: "text-xs",
|
|
default: "text-sm",
|
|
lg: "text-base",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
size: "default",
|
|
},
|
|
})
|
|
|
|
function Rating({
|
|
rating,
|
|
maxRating = 5,
|
|
size,
|
|
className,
|
|
starClassName,
|
|
showValue = false,
|
|
editable = false,
|
|
onRatingChange,
|
|
...props
|
|
}: React.ComponentProps<"div"> &
|
|
VariantProps<typeof ratingVariants> & {
|
|
/**
|
|
* Current rating value (supports decimal values for partial stars)
|
|
*/
|
|
rating: number
|
|
/**
|
|
* Maximum rating value (number of stars to show)
|
|
*/
|
|
maxRating?: number
|
|
/**
|
|
* Whether to show the numeric rating value
|
|
*/
|
|
showValue?: boolean
|
|
/**
|
|
* Class name for the value span
|
|
*/
|
|
starClassName?: string
|
|
/**
|
|
* Whether the rating is editable (clickable)
|
|
*/
|
|
editable?: boolean
|
|
/**
|
|
* Callback function called when rating changes
|
|
*/
|
|
onRatingChange?: (rating: number) => void
|
|
}) {
|
|
const [hoveredRating, setHoveredRating] = useState<number | null>(null)
|
|
const displayRating =
|
|
editable && hoveredRating !== null ? hoveredRating : rating
|
|
|
|
const handleStarClick = (starRating: number) => {
|
|
if (editable && onRatingChange) {
|
|
onRatingChange(starRating)
|
|
}
|
|
}
|
|
|
|
const handleStarMouseEnter = (starRating: number) => {
|
|
if (editable) {
|
|
setHoveredRating(starRating)
|
|
}
|
|
}
|
|
|
|
const handleStarMouseLeave = () => {
|
|
if (editable) {
|
|
setHoveredRating(null)
|
|
}
|
|
}
|
|
|
|
const renderStars = () => {
|
|
const stars = []
|
|
|
|
for (let i = 1; i <= maxRating; i++) {
|
|
const filled = displayRating >= i
|
|
const partiallyFilled = displayRating > i - 1 && displayRating < i
|
|
const fillPercentage = partiallyFilled
|
|
? (displayRating - (i - 1)) * 100
|
|
: 0
|
|
|
|
stars.push(
|
|
<div
|
|
key={i}
|
|
className={cn("relative", editable && "cursor-pointer")}
|
|
onClick={() => handleStarClick(i)}
|
|
onMouseEnter={() => handleStarMouseEnter(i)}
|
|
onMouseLeave={handleStarMouseLeave}
|
|
>
|
|
{/* Background star (empty) */}
|
|
<StarIcon data-slot="rating-star-empty" className={cn(starVariants({ size }), "text-muted-foreground/30")} />
|
|
|
|
{/* Filled star */}
|
|
<div
|
|
className="absolute inset-0 overflow-hidden"
|
|
style={{
|
|
width: filled ? "100%" : `${fillPercentage}%`,
|
|
}}
|
|
>
|
|
<StarIcon data-slot="rating-star-filled" className={cn(
|
|
starVariants({ size }),
|
|
"fill-yellow-400 text-yellow-400"
|
|
)} />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return stars
|
|
}
|
|
|
|
return (
|
|
<div
|
|
data-slot="rating"
|
|
className={cn(ratingVariants({ size }), className)}
|
|
{...props}
|
|
>
|
|
<div className="flex items-center">{renderStars()}</div>
|
|
{showValue && (
|
|
<span
|
|
data-slot="rating-value"
|
|
className={cn(valueVariants({ size }), starClassName)}
|
|
>
|
|
{displayRating.toFixed(1)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export { Rating } |