From 9d791ddfe32178821538ece2e19fcc1b99e64cb2 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Tue, 25 Aug 2026 15:27:53 +0200 Subject: [PATCH] feat: new progress circle component --- src/components/form/PasswordInput.tsx | 4 +- src/components/progress/Progress.stories.tsx | 31 ++++++-- src/components/progress/Progress.style.scss | 51 ++++++++++++- src/components/progress/ProgressCircle.tsx | 73 +++++++++++++++++++ .../{Progress.tsx => ProgressLinear.tsx} | 18 +++-- src/index.ts | 3 +- 6 files changed, 161 insertions(+), 19 deletions(-) create mode 100644 src/components/progress/ProgressCircle.tsx rename src/components/progress/{Progress.tsx => ProgressLinear.tsx} (74%) diff --git a/src/components/form/PasswordInput.tsx b/src/components/form/PasswordInput.tsx index 0c7603617..81fcec9ce 100644 --- a/src/components/form/PasswordInput.tsx +++ b/src/components/form/PasswordInput.tsx @@ -4,7 +4,7 @@ import {IconEye, IconX} from "@tabler/icons-react"; import {Button, ButtonProps} from "../button/Button"; import {clearInputElement} from "./Input.utils"; import {Flex} from "../flex/Flex"; -import {Progress} from "../progress/Progress"; +import {ProgressLinear} from "../progress/ProgressLinear"; import {InputMessage} from "./InputMessage"; import {ButtonGroup} from "../button-group/ButtonGroup"; @@ -77,7 +77,7 @@ export const PasswordInput: React.ForwardRefExoticComponent { usesPasswordValidation && !formValidation?.valid && ( - {(() => { const nextRule = ["2", "3", "4", "5", "1"].find(rule => notValidMessage.includes(rule)) diff --git a/src/components/progress/Progress.stories.tsx b/src/components/progress/Progress.stories.tsx index c7b68ccd7..90299b0f6 100644 --- a/src/components/progress/Progress.stories.tsx +++ b/src/components/progress/Progress.stories.tsx @@ -1,29 +1,46 @@ import {Meta} from "@storybook/react-vite"; -import {Progress} from "./Progress"; +import {ProgressLinear} from "./ProgressLinear"; +import {ProgressCircle} from "./ProgressCircle"; import React from "react"; import {Card} from "../card/Card"; import {Spacing} from "../spacing/Spacing"; import {Text} from "../text/Text"; +import {Flex} from "../flex/Flex"; export default { title: "Progress", - component: Progress, + component: ProgressLinear, } as Meta -export const ProgressExample = () => { +export const Linear = () => { return - + You used 50% of your available workflow executions and will used 75% until its reseted. - + You used 30% of your available workflow executions and will used 120% until its reseted. You need to upgrade your plan to be able to execute workflows seamlessly. -} \ No newline at end of file +} + +export const Circle = () => { + return + + 50%}/> + + + + + The circle shares the same interface and design as the linear variant – + value, predictionValue, color and dot behave identically. + + +} diff --git a/src/components/progress/Progress.style.scss b/src/components/progress/Progress.style.scss index d920867a1..35a2e6b42 100644 --- a/src/components/progress/Progress.style.scss +++ b/src/components/progress/Progress.style.scss @@ -3,7 +3,7 @@ @use "../../styles/variables"; -.progress { +.progress-linear { & { @include box.box(variables.$primary); @@ -65,4 +65,51 @@ background-size: calc(100% * (100 / var(--progressPrediction, 100))) 100%; } } -} \ No newline at end of file +} + +.progress-circle { + + & { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--size); + height: var(--size); + } + + &__circle { + display: block; + width: var(--size); + height: var(--size); + transform: translateZ(0); + } + + &__track { + opacity: 0.1; + stroke: var(--color); + stroke-linecap: round; + } + + &__prediction { + opacity: 0.25; + stroke: url(#circle-stripe-overlay); + stroke-linecap: round; + z-index: 1; + } + + &__range { + stroke: var(--color); + stroke-linecap: round; + z-index: 2; + } + + &__dot { + position: absolute; + inset: 0; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + } +} diff --git a/src/components/progress/ProgressCircle.tsx b/src/components/progress/ProgressCircle.tsx new file mode 100644 index 000000000..e0c0a1d02 --- /dev/null +++ b/src/components/progress/ProgressCircle.tsx @@ -0,0 +1,73 @@ +import {mergeComponentProps} from "../../utils"; +import { + ProgressCircle as ArkProgressCircle, + ProgressCircleRange, + ProgressCircleTrack, + ProgressRoot, + ProgressRootProps +} from "@ark-ui/react"; +import React, {CSSProperties} from "react"; +import "./Progress.style.scss" +import {ProgressProps} from "./ProgressLinear"; + +export type ProgressCircleProps = ProgressProps & Omit & { + size?: CSSProperties['width'] + thickness?: CSSProperties['strokeWidth'] +} + +export const ProgressCircle: React.FC = (props) => { + + const { + color = "white", + predictionValue = 0, + dot, + value, + max = 100, + size = 64, + thickness, + ...rest + } = props + + const toLength = (value: string | number | undefined): string | number | undefined => + typeof value === "number" ? `${value}px` : value + + return + + + + + + + + + + + + + + + + + + +
+ {dot} +
+
+} diff --git a/src/components/progress/Progress.tsx b/src/components/progress/ProgressLinear.tsx similarity index 74% rename from src/components/progress/Progress.tsx rename to src/components/progress/ProgressLinear.tsx index 142de2d55..f4ac74a47 100644 --- a/src/components/progress/Progress.tsx +++ b/src/components/progress/ProgressLinear.tsx @@ -3,13 +3,17 @@ import * as Radix from "@radix-ui/react-progress" import React, {CSSProperties} from "react"; import "./Progress.style.scss" -export type ProgressProps = ComponentProps & Radix.ProgressProps & { +export type ProgressProps = ComponentProps & { + value?: number | null + max?: number color?: CSSProperties['background'] dot?: React.ReactNode predictionValue?: number | null } -export const Progress: React.FC = (props) => { +export type ProgressLinearProps = ProgressProps & Omit + +export const ProgressLinear: React.FC = (props) => { const {color = "white", predictionValue, dot, ...rest} = props @@ -19,7 +23,7 @@ export const Progress: React.FC = (props) => { const progressPrediction = ((Math.min(props.predictionValue ?? 0, props.max ?? 100)) / (props.max ?? 100)) * 100; const transformPredictionValue = `translateX(-${100 - progressPrediction}%)`; - return = (props) => { } })}> -
+
{dot}
@@ -41,15 +45,15 @@ export const Progress: React.FC = (props) => { position: "relative", }}> {typeof props.predictionValue === "number" && }
-} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 51b9be0d8..6e3e013e2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,8 @@ export * from "./components/gantt/Gantt" export * from "./components/icon/Icon" export * from "./components/layout/Layout" export * from "./components/menu/Menu" -export * from "./components/progress/Progress" +export * from "./components/progress/ProgressLinear" +export * from "./components/progress/ProgressCircle" export * from "./components/quote/Quote" export * from "./components/resizable/Resizable" export * from "./components/row/Row"