@@ -6,12 +6,24 @@ import {Flex, HelpMark, Text} from '@gravity-ui/uikit';
66import { cn } from '../../utils/cn' ;
77import type { ProgressStatus } from '../../utils/progress' ;
88
9+ import { SvgCircle } from './SvgCircle' ;
10+ import {
11+ ROTATION_OFFSET ,
12+ SIZE_CONFIG ,
13+ calculateCircumference ,
14+ calculateOverlapDasharray ,
15+ calculateStrokeDasharray ,
16+ } from './utils' ;
17+
918import './DoughnutMetrics.scss' ;
1019
1120const b = cn ( 'ydb-doughnut-metrics' ) ;
1221
13- const SizeContext = React . createContext < 'small' | 'medium' | 'large' > ( 'medium' ) ;
22+ type Size = keyof typeof SIZE_CONFIG ;
23+
24+ const SizeContext = React . createContext < Size > ( 'medium' ) ;
1425
26+ // Legend component
1527interface LegendProps {
1628 children ?: React . ReactNode ;
1729 variant ?: TextProps [ 'variant' ] ;
@@ -25,7 +37,7 @@ function Legend({
2537 variant = 'subheader-3' ,
2638 color = 'primary' ,
2739 note,
28- noteIconSize,
40+ noteIconSize = 'm' ,
2941} : LegendProps ) {
3042 return (
3143 < Flex gap = { 1 } alignItems = "center" >
@@ -34,7 +46,7 @@ function Legend({
3446 </ Text >
3547 { note && (
3648 < HelpMark
37- iconSize = { noteIconSize || 'm' }
49+ iconSize = { noteIconSize }
3850 className = { b ( 'legend-note' ) }
3951 popoverProps = { { placement : 'right' } }
4052 >
@@ -44,16 +56,11 @@ function Legend({
4456 </ Flex >
4557 ) ;
4658}
59+
60+ // Value component
4761function Value ( { children, variant} : LegendProps ) {
4862 const size = React . useContext ( SizeContext ) ;
49-
50- const sizeVariantMap = {
51- small : 'subheader-1' ,
52- medium : 'subheader-2' ,
53- large : 'subheader-3' ,
54- } as const ;
55-
56- const finalVariant = variant || sizeVariantMap [ size ] ;
63+ const finalVariant = variant || SIZE_CONFIG [ size ] . textVariant ;
5764
5865 return (
5966 < Text variant = { finalVariant } className = { b ( 'value' ) } >
@@ -62,12 +69,13 @@ function Value({children, variant}: LegendProps) {
6269 ) ;
6370}
6471
72+ // Main component
6573interface DoughnutProps {
6674 status : ProgressStatus ;
6775 fillWidth : number ;
6876 children ?: React . ReactNode ;
6977 className ?: string ;
70- size ?: 'small' | 'medium' | 'large' ;
78+ size ?: Size ;
7179}
7280
7381export function DoughnutMetrics ( {
@@ -77,24 +85,57 @@ export function DoughnutMetrics({
7785 className,
7886 size = 'medium' ,
7987} : DoughnutProps ) {
80- let filledDegrees = fillWidth * 3.6 ;
81- let doughnutFillVar = 'var(--doughnut-color)' ;
82- let doughnutBackdropVar = 'var(--doughnut-backdrop-color)' ;
88+ const config = SIZE_CONFIG [ size ] ;
89+ const radius = ( config . width - config . strokeWidth ) / 2 ;
90+ const circumference = calculateCircumference ( radius ) ;
91+ const strokeDashoffset = circumference * ROTATION_OFFSET ;
8392
84- if ( filledDegrees > 360 ) {
85- filledDegrees -= 360 ;
86- doughnutBackdropVar = 'var(--doughnut-color)' ;
87- doughnutFillVar = 'var(--doughnut-overlap-color)' ;
88- }
93+ const centerX = config . width / 2 ;
94+ const centerY = config . width / 2 ;
8995
90- const doughnutStyle : React . CSSProperties = {
91- background : `conic-gradient( ${ doughnutFillVar } 0deg ${ filledDegrees } deg, ${ doughnutBackdropVar } ${ filledDegrees } deg 360deg)` ,
92- } ;
96+ const strokeDasharray = calculateStrokeDasharray ( fillWidth , circumference ) ;
97+ const overlapDasharray = calculateOverlapDasharray ( fillWidth , circumference ) ;
98+ const needsOverlapCircle = fillWidth > 100 ;
9399
94100 return (
95101 < SizeContext . Provider value = { size } >
96- < div className = { b ( { status} , className ) } style = { { position : 'relative' } } >
97- < div style = { doughnutStyle } className = { b ( 'doughnut' , { size} ) } > </ div >
102+ < div className = { b ( { status} , className ) } >
103+ < svg width = { config . width } height = { config . width } className = { b ( 'doughnut' ) } >
104+ { /* Background circle */ }
105+ < SvgCircle
106+ cx = { centerX }
107+ cy = { centerY }
108+ r = { radius }
109+ stroke = "var(--doughnut-backdrop-color)"
110+ strokeWidth = { config . strokeWidth }
111+ />
112+
113+ { /* Progress circle */ }
114+ < SvgCircle
115+ cx = { centerX }
116+ cy = { centerY }
117+ r = { radius }
118+ stroke = "var(--doughnut-color)"
119+ strokeWidth = { config . strokeWidth }
120+ strokeDasharray = { strokeDasharray }
121+ strokeDashoffset = { strokeDashoffset }
122+ className = { b ( 'progress-circle' ) }
123+ />
124+
125+ { /* Overlap circle for values > 100% */ }
126+ { needsOverlapCircle && (
127+ < SvgCircle
128+ cx = { centerX }
129+ cy = { centerY }
130+ r = { radius }
131+ stroke = "var(--doughnut-overlap-color)"
132+ strokeWidth = { config . strokeWidth }
133+ strokeDasharray = { overlapDasharray }
134+ strokeDashoffset = { strokeDashoffset }
135+ className = { b ( 'overlap-circle' ) }
136+ />
137+ ) }
138+ </ svg >
98139 < div className = { b ( 'text-wrapper' ) } > { children } </ div >
99140 </ div >
100141 </ SizeContext . Provider >
0 commit comments