progress-circle.tsx 1.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. import { type ComponentProps, createMemo, splitProps } from "solid-js"
  2. export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class" | "classList"> {
  3. percentage: number
  4. size?: number
  5. strokeWidth?: number
  6. }
  7. export function ProgressCircle(props: ProgressCircleProps) {
  8. const [split, rest] = splitProps(props, ["percentage", "size", "strokeWidth", "class", "classList"])
  9. const size = () => split.size || 16
  10. const strokeWidth = () => split.strokeWidth || 3
  11. const viewBoxSize = 16
  12. const center = viewBoxSize / 2
  13. const radius = () => center - strokeWidth() / 2
  14. const circumference = createMemo(() => 2 * Math.PI * radius())
  15. const offset = createMemo(() => {
  16. const clampedPercentage = Math.max(0, Math.min(100, split.percentage || 0))
  17. const progress = clampedPercentage / 100
  18. return circumference() * (1 - progress)
  19. })
  20. return (
  21. <svg
  22. {...rest}
  23. width={size()}
  24. height={size()}
  25. viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
  26. fill="none"
  27. data-component="progress-circle"
  28. classList={{
  29. ...(split.classList ?? {}),
  30. [split.class ?? ""]: !!split.class,
  31. }}
  32. >
  33. <circle cx={center} cy={center} r={radius()} data-slot="background" stroke-width={strokeWidth()} />
  34. <circle
  35. cx={center}
  36. cy={center}
  37. r={radius()}
  38. data-slot="progress"
  39. stroke-width={strokeWidth()}
  40. stroke-dasharray={circumference().toString()}
  41. stroke-dashoffset={offset()}
  42. />
  43. </svg>
  44. )
  45. }