progress-circle.tsx 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  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
  34. cx={center}
  35. cy={center}
  36. r={radius()}
  37. data-slot="progress-circle-background"
  38. stroke-width={strokeWidth()}
  39. />
  40. <circle
  41. cx={center}
  42. cy={center}
  43. r={radius()}
  44. data-slot="progress-circle-progress"
  45. stroke-width={strokeWidth()}
  46. stroke-dasharray={circumference().toString()}
  47. stroke-dashoffset={offset()}
  48. />
  49. </svg>
  50. )
  51. }