progress-circle.tsx 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. import { type ComponentProps, createMemo, splitProps } from "solid-js"
  2. export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class" | "classList" | "style"> {
  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-background-overlay"
  45. stroke-width={strokeWidth()}
  46. />
  47. <circle
  48. cx={center}
  49. cy={center}
  50. r={radius()}
  51. data-slot="progress-circle-progress"
  52. stroke-width={strokeWidth()}
  53. stroke-dasharray={circumference().toString()}
  54. stroke-dashoffset={offset()}
  55. />
  56. </svg>
  57. )
  58. }