progress-circle.tsx 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  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, [
  9. "percentage",
  10. "size",
  11. "strokeWidth",
  12. "class",
  13. "classList",
  14. ])
  15. const size = () => split.size || 16
  16. const strokeWidth = () => split.strokeWidth || 3
  17. const viewBoxSize = 16
  18. const center = viewBoxSize / 2
  19. const radius = () => center - strokeWidth() / 2
  20. const circumference = createMemo(() => 2 * Math.PI * radius())
  21. const offset = createMemo(() => {
  22. const clampedPercentage = Math.max(0, Math.min(100, split.percentage || 0))
  23. const progress = clampedPercentage / 100
  24. return circumference() * (1 - progress)
  25. })
  26. return (
  27. <svg
  28. {...rest}
  29. width={size()}
  30. height={size()}
  31. viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
  32. fill="none"
  33. data-component="progress-circle"
  34. classList={{
  35. ...(split.classList ?? {}),
  36. [split.class ?? ""]: !!split.class,
  37. }}
  38. >
  39. <circle
  40. cx={center}
  41. cy={center}
  42. r={radius()}
  43. data-slot="background"
  44. stroke-width={strokeWidth()}
  45. />
  46. <circle
  47. cx={center}
  48. cy={center}
  49. r={radius()}
  50. data-slot="progress"
  51. stroke-width={strokeWidth()}
  52. stroke-dasharray={circumference().toString()}
  53. stroke-dashoffset={offset()}
  54. />
  55. </svg>
  56. )
  57. }