diff options
Diffstat (limited to 'packages/ui')
| -rw-r--r-- | packages/ui/src/components/index.ts | 1 | ||||
| -rw-r--r-- | packages/ui/src/components/progress-circle.css | 12 | ||||
| -rw-r--r-- | packages/ui/src/components/progress-circle.tsx | 63 | ||||
| -rw-r--r-- | packages/ui/src/styles/index.css | 1 |
4 files changed, 77 insertions, 0 deletions
diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 4b60ddabc..63be24d58 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -11,6 +11,7 @@ export * from "./input" export * from "./fonts" export * from "./list" export * from "./message-part" +export * from "./progress-circle" export * from "./select" export * from "./select-dialog" export * from "./tabs" diff --git a/packages/ui/src/components/progress-circle.css b/packages/ui/src/components/progress-circle.css new file mode 100644 index 000000000..591825183 --- /dev/null +++ b/packages/ui/src/components/progress-circle.css @@ -0,0 +1,12 @@ +[data-component="progress-circle"] { + transform: rotate(-90deg); + + [data-slot="background"] { + stroke: var(--border-weak-base); + } + + [data-slot="progress"] { + stroke: var(--border-active); + transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1); + } +} diff --git a/packages/ui/src/components/progress-circle.tsx b/packages/ui/src/components/progress-circle.tsx new file mode 100644 index 000000000..a659c0f2e --- /dev/null +++ b/packages/ui/src/components/progress-circle.tsx @@ -0,0 +1,63 @@ +import { type ComponentProps, createMemo, splitProps } from "solid-js" + +export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class" | "classList"> { + percentage: number + size?: number + strokeWidth?: number +} + +export function ProgressCircle(props: ProgressCircleProps) { + const [split, rest] = splitProps(props, [ + "percentage", + "size", + "strokeWidth", + "class", + "classList", + ]) + + const size = () => split.size || 16 + const strokeWidth = () => split.strokeWidth || 3 + + const viewBoxSize = 16 + const center = viewBoxSize / 2 + const radius = () => center - strokeWidth() / 2 + const circumference = createMemo(() => 2 * Math.PI * radius()) + + const offset = createMemo(() => { + const clampedPercentage = Math.max(0, Math.min(100, split.percentage || 0)) + const progress = clampedPercentage / 100 + return circumference() * (1 - progress) + }) + + return ( + <svg + {...rest} + width={size()} + height={size()} + viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`} + fill="none" + data-component="progress-circle" + classList={{ + ...(split.classList ?? {}), + [split.class ?? ""]: !!split.class, + }} + > + <circle + cx={center} + cy={center} + r={radius()} + data-slot="background" + stroke-width={strokeWidth()} + /> + <circle + cx={center} + cy={center} + r={radius()} + data-slot="progress" + stroke-width={strokeWidth()} + stroke-dasharray={circumference().toString()} + stroke-dashoffset={offset()} + /> + </svg> + ) +} diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index 3ebe6e9ea..9742caa0a 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -17,6 +17,7 @@ @import "../components/input.css" layer(components); @import "../components/list.css" layer(components); @import "../components/message-part.css" layer(components); +@import "../components/progress-circle.css" layer(components); @import "../components/select.css" layer(components); @import "../components/select-dialog.css" layer(components); @import "../components/tabs.css" layer(components); |
