@@ -3,6 +3,7 @@ import "./index.css"
33
44interface Item {
55 color ?: string
6+ className ?: string
67 value : number
78 name : string
89}
@@ -11,9 +12,17 @@ interface Props {
1112 items : Item [ ]
1213 total : number
1314 darkMode ?: boolean
14- removeLabels ?: boolean
15+ showLabels ?: boolean
1516 showPercentage ?: boolean
1617 compactLayout ?: boolean
18+ showFallbackColors ?: boolean
19+ errorMessage ?: string
20+
21+ usageBarContainerClassName ?: string
22+ usageBarClassName ?: string
23+ tooltipClassName ?: string
24+ dotElementClassName ?: string
25+ errorMessageClassName ?: string
1726}
1827
1928const lightColors : string [ ] = [
@@ -44,16 +53,39 @@ const darkColors: string[] = [
4453const getPercentageValue = ( value : number , total : number ) : string =>
4554 `${ ( ( value / total ) * 100 ) . toFixed ( 0 ) } %`
4655
56+ const shuffleArray = ( a : string [ ] ) => {
57+ let j , x , i
58+ for ( i = a . length - 1 ; i > 0 ; i -- ) {
59+ j = Math . floor ( Math . random ( ) * ( i + 1 ) )
60+ x = a [ i ]
61+ a [ i ] = a [ j ]
62+ a [ j ] = x
63+ }
64+ return a
65+ }
66+
67+ const appendCustomClass = ( customClass ?: string ) => {
68+ if ( customClass ) {
69+ return customClass [ 0 ] === " " ? customClass : ` ${ customClass } `
70+ }
71+ return ""
72+ }
73+
4774const UsageBar : React . FC < Props > = ( {
4875 darkMode = false ,
49- removeLabels = false ,
76+ showLabels = true ,
5077 showPercentage = false ,
5178 compactLayout = false ,
79+ showFallbackColors = false ,
5280 total,
5381 items,
82+ errorMessage = "ERROR: Total elements values exceed 100%." ,
83+ usageBarContainerClassName,
84+ usageBarClassName,
85+ tooltipClassName,
86+ dotElementClassName,
87+ errorMessageClassName,
5488} ) => {
55- const [ formattedItems , setFormattedItems ] = React . useState < Item [ ] > ( [ ] )
56-
5789 /**
5890 * Checks if the total value is equal or greater than the sum of all the elements values.
5991 */
@@ -65,82 +97,84 @@ const UsageBar: React.FC<Props> = ({
6597 )
6698
6799 /**
68- * Formats the items prop array providing a color to
69- * elements without a defined one.
100+ * Returns an array of colors based on the value of `darkMode`.
101+ * The colors are either from the `darkColors` array or the `lightColors` array.
102+ * The chosen array is shuffled before being returned.
70103 */
71- const formatItemsArray = React . useCallback ( ( ) => {
72- const selectedColors : string [ ] = [ ]
104+ const fallbackColors = React . useMemo ( ( ) => {
73105 const colorsToPickFrom = darkMode ? [ ...darkColors ] : [ ...lightColors ]
106+ shuffleArray ( colorsToPickFrom )
107+ return colorsToPickFrom
108+ } , [ ] )
74109
75- // For each element a random index is generated and then used to pick a value
76- // from the colorsToPickFrom array; the selected value is removed by its original array
77- // and it's pushed into the selectedColors one.
78- for ( let i = 0 ; i < items . length ; i ++ ) {
79- const randIndex = Math . floor ( Math . random ( ) * colorsToPickFrom . length )
80- const color = colorsToPickFrom [ randIndex ]
81- selectedColors . push ( color )
82- colorsToPickFrom . splice ( randIndex , 1 )
83- }
84-
85- // Each element from the items array is formatted correctly
86- // with a defined and valid color property.
87- setFormattedItems (
88- items . map ( ( item : Item , index : number ) => {
89- return item . color ? item : { ...item , color : selectedColors [ index ] }
90- } )
91- )
92- } , [ items , darkMode ] )
93-
94- React . useEffect ( ( ) => {
95- if ( itemsValuesAreCorrect ) {
96- formatItemsArray ( )
97- }
98- } , [ itemsValuesAreCorrect , formatItemsArray ] )
110+ /**
111+ * Returns the color of an element based on certain conditions.
112+ *
113+ * @param element - The item for which the color needs to be determined.
114+ * @param index - The index of the item in the list.
115+ * @returns The color of the element, either from the `color` property or a fallback
116+ * color from the `fallbackColors` array. If the `element` does not have a `color`
117+ * property and `showFallbackColors` is false, null is returned.
118+ */
119+ const getElementColor = React . useCallback (
120+ ( element : Item , index : number ) => {
121+ if ( element . color ) return element . color
122+ return showFallbackColors
123+ ? fallbackColors [ index % fallbackColors . length ]
124+ : null
125+ } ,
126+ [ showFallbackColors , fallbackColors ]
127+ )
99128
100129 if ( ! itemsValuesAreCorrect )
101130 return (
102- < span className = "u-UsageBar__error" >
103- ERROR: Elements values exceed the total.
131+ < span
132+ className = { `u-UsageBar__error${ appendCustomClass (
133+ errorMessageClassName
134+ ) } `}
135+ >
136+ { errorMessage }
104137 </ span >
105138 )
106139
107- if ( formattedItems . length === 0 ) return null
140+ if ( items . length === 0 ) return null
108141
109142 if ( compactLayout ) {
110143 return (
111144 < div
112145 className = { `c-UsageBar c-UsageBar__compact ${
113146 darkMode ? "u-UsageBar-dark" : "u-UsageBar-light"
114- } `}
147+ } ${ appendCustomClass ( usageBarContainerClassName ) } `}
115148 >
116- < div className = "o-UsageBar__bar o-UsageBar__compact__bar" >
117- { formattedItems . map ( ( element : Item , index : number ) => {
118- return (
119- < div
120- key = { index }
121- className = "o-UsageBar__bar__element"
122- style = { {
123- width : getPercentageValue ( element . value , total ) ,
124- backgroundColor : element . color ,
125- } }
126- />
127- )
128- } ) }
149+ < div
150+ className = { `o-UsageBar__bar o-UsageBar__compact__bar ${ appendCustomClass (
151+ usageBarClassName
152+ ) } ` }
153+ >
154+ { items . map ( ( element : Item , index : number ) => (
155+ < UsageBarElement
156+ element = { element }
157+ color = { getElementColor ( element , index ) }
158+ total = { total }
159+ key = { index }
160+ />
161+ ) ) }
129162 </ div >
130- { ! removeLabels && (
163+ { showLabels && (
131164 < div className = "o-UsageBar__bar__elements__labels__container" >
132- { formattedItems . map ( ( element : Item , index : number ) => {
165+ { items . map ( ( element : Item , index : number ) => {
166+ const color = getElementColor ( element , index )
133167 return (
134168 < div key = { index } className = "o-UsageBar__bar__elements__label" >
135169 < div
136- className = "o-UsageBar__bar__elements__label--dot"
137- style = { { backgroundColor : element . color } }
170+ className = { `o-UsageBar__bar__elements__label--dot ${ appendCustomClass (
171+ dotElementClassName
172+ ) } `}
173+ style = { color ? { backgroundColor : color } : { } }
138174 />
139175 < span > { element . name } </ span >
140176 { showPercentage && (
141- < span className = "o-UsageBar__bar__tooltip__percentage" >
142- { getPercentageValue ( element . value , total ) }
143- </ span >
177+ < UsageBarPercentageLabel element = { element } total = { total } />
144178 ) }
145179 </ div >
146180 )
@@ -155,35 +189,65 @@ const UsageBar: React.FC<Props> = ({
155189 < div
156190 className = { `c-UsageBar ${
157191 darkMode ? "u-UsageBar-dark" : "u-UsageBar-light"
158- } `}
192+ } ${ appendCustomClass ( usageBarContainerClassName ) } `}
159193 >
160- < div className = "o-UsageBar__bar" >
161- { formattedItems . map ( ( element : Item , index : number ) => {
162- return (
163- < div
164- key = { index }
165- className = "o-UsageBar__bar__element"
166- style = { {
167- width : getPercentageValue ( element . value , total ) ,
168- backgroundColor : element . color ,
169- } }
170- >
171- { ! removeLabels && (
172- < div className = "o-UsageBar__bar__tooltip" >
173- < span > { element . name } </ span >
174- { showPercentage && (
175- < span className = "o-UsageBar__bar__tooltip__percentage" >
176- { getPercentageValue ( element . value , total ) }
177- </ span >
178- ) }
179- </ div >
180- ) }
181- </ div >
182- )
183- } ) }
194+ < div className = { `o-UsageBar__bar${ appendCustomClass ( usageBarClassName ) } ` } >
195+ { items . map ( ( element : Item , index : number ) => (
196+ < UsageBarElement
197+ element = { element }
198+ color = { getElementColor ( element , index ) }
199+ total = { total }
200+ key = { index }
201+ >
202+ { showLabels && (
203+ < div
204+ className = { `o-UsageBar__bar__tooltip${ appendCustomClass (
205+ tooltipClassName
206+ ) } `}
207+ >
208+ < span > { element . name } </ span >
209+ { showPercentage && (
210+ < UsageBarPercentageLabel element = { element } total = { total } />
211+ ) }
212+ </ div >
213+ ) }
214+ </ UsageBarElement >
215+ ) ) }
184216 </ div >
185217 </ div >
186218 )
187219}
188220
221+ const UsageBarElement : React . FC < {
222+ element : Item
223+ color : string | null
224+ total : number
225+ children ?: React . ReactNode
226+ } > = ( { element, color, total, children } ) => {
227+ return (
228+ < div
229+ className = { `o-UsageBar__bar__element${ appendCustomClass (
230+ element . className
231+ ) } `}
232+ style = { {
233+ width : getPercentageValue ( element . value , total ) ,
234+ ...( color ? { backgroundColor : color } : null ) ,
235+ } }
236+ >
237+ { children }
238+ </ div >
239+ )
240+ }
241+
242+ const UsageBarPercentageLabel : React . FC < { element : Item ; total : number } > = ( {
243+ element,
244+ total,
245+ } ) => {
246+ return (
247+ < span className = "o-UsageBar__bar__tooltip__percentage" >
248+ { getPercentageValue ( element . value , total ) }
249+ </ span >
250+ )
251+ }
252+
189253export default UsageBar
0 commit comments