Skip to content

Commit f507073

Browse files
committed
ui/primitives/TooltipWrapper;
ui/util/formatAndAbbreviateAsCurrency
1 parent 6493ce9 commit f507073

6 files changed

+168
-91
lines changed

pkg/ui/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hanzo/ui",
3-
"version": "4.3.8",
3+
"version": "4.4.0",
44
"description": "Library that contains shared UI primitives, support for a common design system, and other boilerplate support.",
55
"publishConfig": {
66
"registry": "https://registry.npmjs.org/",

pkg/ui/primitives/tooltip.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,22 @@ const TooltipContent = React.forwardRef<
2727
))
2828
TooltipContent.displayName = TooltipPrimitive.Content.displayName
2929

30-
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
30+
const TooltipWrapper: React.FC<{
31+
text: string
32+
tooltipClx?: string
33+
} & React.PropsWithChildren> = ({
34+
children,
35+
text,
36+
tooltipClx=''
37+
}) => (
38+
<Tooltip>
39+
<TooltipTrigger asChild>
40+
{children}
41+
</TooltipTrigger>
42+
<TooltipContent className={tooltipClx}>
43+
{text}
44+
</TooltipContent>
45+
</Tooltip>
46+
)
47+
48+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, TooltipWrapper }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import Abbr, { type QuantityAbbrSymbol, ABBR_SYMBOLS_ARRAY } from './number-abbreviate'
2+
3+
interface FormatThreshold {
4+
from: number
5+
use: QuantityAbbrSymbol
6+
}
7+
8+
const formatAndAbbreviateAsCurrency = (
9+
n: number | null,
10+
thresholds: FormatThreshold[] = [{
11+
from: 1000000000,
12+
use: 'M'
13+
}],
14+
/**
15+
* Chars that will be added by ui if the number is rounded.
16+
* For example, if the desired output for 10.15 is "~10.1",
17+
* the tilda counts as 1 char.
18+
*/
19+
roundingAdds: number = 1,
20+
maxDecimal: number = 2
21+
): {
22+
full: string
23+
result: string
24+
change: 'rounded' | 'none' | 'abbr' | 'empty'
25+
} => {
26+
if (n === null) {
27+
return {
28+
full: '',
29+
result: '',
30+
change: 'empty'
31+
}
32+
}
33+
34+
const usdFormatter = Intl.NumberFormat('en-US', {
35+
style: 'currency',
36+
currency: 'USD',
37+
minimumFractionDigits: 0
38+
})
39+
const formatted = usdFormatter.format(n)
40+
41+
if (n < thresholds[0].from) {
42+
return {
43+
full: formatted,
44+
result: formatted,
45+
change: 'none'
46+
}
47+
}
48+
49+
// Get operative FormatThreshold pair...
50+
let threshold: FormatThreshold
51+
for (
52+
let i = 0, threshold = thresholds[0];
53+
i < thresholds.length, n >= thresholds[i].from;
54+
i++
55+
) {}
56+
57+
// Build up units array to all units
58+
// up to threshold.use
59+
const units: QuantityAbbrSymbol[] = []
60+
for (let i = 0; i < ABBR_SYMBOLS_ARRAY.length; i++) {
61+
const current = ABBR_SYMBOLS_ARRAY[i]
62+
units.push(current)
63+
if (current === threshold!.use) {
64+
break
65+
}
66+
}
67+
68+
const abbreviator = new Abbr(units)
69+
70+
// Use thresholdFrom as a guide to how many chars are available
71+
// first digit + comma = 2
72+
// Possible trailing cents: '.xx'.length = 3
73+
// 3 - 2 = 1
74+
const charsAvail = usdFormatter.format(threshold!.from).length + 1
75+
const abbr = abbreviator.abbreviate(n, charsAvail) // arbitrary, but good approx
76+
const numStr = abbr.slice(0, -1)
77+
const abbreviation = abbr.slice(-1)
78+
const numerical = parseFloat(numStr)
79+
80+
const integral = Math.floor(numerical)
81+
const integralString = usdFormatter.format(integral)
82+
const commas = integralString.split(',').length - 1
83+
84+
// minus abbr, dec point, dollar sign, and roundingAdds / tilda,
85+
// (1 + 1 + 1 + roundingAdds)
86+
// ("precision" does NOT include the decimal point itself,
87+
// so we have to explicitly factor it in.)
88+
const roundedString = numerical.toPrecision(charsAvail - commas - (3 + roundingAdds))
89+
// remove trailing zeros, if any
90+
const roundedNumerical = parseFloat(roundedString)
91+
const roundedIntegral = Math.trunc(roundedNumerical)
92+
const roundedIntegralString = usdFormatter.format(roundedIntegral)
93+
94+
let decimalPortion = roundedNumerical - roundedIntegral
95+
let result
96+
if (decimalPortion !== 0) {
97+
// remove trailing zeros if any
98+
decimalPortion = parseFloat(decimalPortion.toFixed(maxDecimal))
99+
const decimalPortionString = decimalPortion.toString()
100+
const afterDecimalString = decimalPortionString.slice(decimalPortionString.indexOf('.') + 1)
101+
result = roundedIntegralString + '.' + afterDecimalString + abbreviation
102+
}
103+
else {
104+
result = roundedIntegralString + abbreviation
105+
}
106+
// Did we lose any precision?
107+
const rounded = (roundedIntegral + decimalPortion !== n)
108+
return {
109+
full: formatted,
110+
result,
111+
change: rounded ? 'rounded' : 'abbr'
112+
}
113+
}
114+
115+
export {
116+
formatAndAbbreviateAsCurrency as default,
117+
type FormatThreshold,
118+
type QuantityAbbrSymbol
119+
}

pkg/ui/util/format-to-max-char.ts

+5-20
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,14 @@ import Abbr from './number-abbreviate'
22

33
const abbr = new Abbr(['K', 'M', 'B', 'T'])
44

5-
const isPowerOf10 = (n: number) => {
6-
if (n <= 0) {
7-
return false
8-
}
9-
10-
while (n > 1) {
11-
if (n % 10 !== 0) {
12-
return false
13-
}
14-
n /= 10
15-
}
16-
17-
return true
18-
}
19-
205
const formatToMaxChar = (
216
n: number | null,
227
maxChars: number,
23-
/**
24-
* Chars that will be added by ui if the number is rounded.
25-
* For example, if the desired output for 10.15 is "~10.1",
26-
* the tilda counts as 1 char.
27-
*/
8+
/**
9+
* Chars that will be added by ui if the number is rounded.
10+
* For example, if the desired output for 10.15 is "~10.1",
11+
* the tilda counts as 1 char.
12+
*/
2813
roundingAdds: number = 1
2914
): {
3015
result: string

pkg/ui/util/index.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ export const capitalize = (str: string): string => (
6767

6868
export { default as spreadToTransform } from './spread-to-transform'
6969
export { default as formatToMaxChar } from './format-to-max-char'
70-
// Must be imported from 'use client'
70+
export {
71+
default as formatAndAbbreviateAsCurrency,
72+
type FormatThreshold,
73+
type QuantityAbbrSymbol
74+
} from './format-and-abbreviate-as-currency'
75+
76+
// Must be imported from 'use client', so can't include this...
7177
// export * from './step-animation'
7278

pkg/ui/util/number-abbreviate.ts

+17-68
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
// cf: https://github.com/domharrington/js-number-abbreviate/blob/master/index.js
22

3-
const DEF_ABBREVIATIONS = ['K', 'M', 'B', 'T']
3+
type QuantityAbbrSymbol = 'K' | 'M' | 'B' | 'T'
4+
const ABBR_SYMBOLS_ARRAY = ['K', 'M', 'B', 'T'] satisfies QuantityAbbrSymbol[]
45

56
class NumberAbbreviator {
67

7-
private _units: string[]
8+
private _units: QuantityAbbrSymbol[]
89

9-
constructor(units?: string[]) {
10-
this._units = units ?? DEF_ABBREVIATIONS
10+
constructor(units?: QuantityAbbrSymbol[]) {
11+
this._units = units ?? ABBR_SYMBOLS_ARRAY
1112
}
1213

13-
private _abbreviate = (n: number, decPlaces: number): string => {
14+
private _abbreviate = (
15+
n: number,
16+
decPlaces: number,
17+
log: boolean = false
18+
): string => {
1419

1520
const _decPlaces = Math.pow(10, decPlaces)
1621
let _n = n
@@ -31,70 +36,14 @@ class NumberAbbreviator {
3136
return _n.toString() + (_unit ?? '')
3237
}
3338

34-
abbreviate = (n: number, decPlaces: number) => {
35-
const abbreviatedNumber = this._abbreviate(Math.abs(n), decPlaces || 0)
39+
abbreviate = (n: number, decPlaces: number, log: boolean = false) => {
40+
const abbreviatedNumber = this._abbreviate(Math.abs(n), decPlaces, log)
3641
return n < 0 ? '-' + abbreviatedNumber : abbreviatedNumber
3742
}
3843
}
3944

40-
41-
export default NumberAbbreviator
42-
43-
/*
44-
(function(root){
45-
'use strict';
46-
47-
function NumberAbbreviate() {
48-
var units
49-
if (!(this instanceof NumberAbbreviate)) {
50-
// function usage: abbrev(n, decPlaces, units)
51-
var n = arguments[0]
52-
var decPlaces = arguments[1]
53-
units = arguments[2]
54-
var ab = new NumberAbbreviate(units)
55-
return ab.abbreviate(n, decPlaces)
56-
}
57-
// class usage: new NumberAbbreviate(units)
58-
units = arguments[0]
59-
this.units = units == null ? ['k', 'm', 'b', 't'] : units
60-
}
61-
62-
NumberAbbreviate.prototype._abbreviate = function(number, decPlaces) {
63-
decPlaces = Math.pow(10, decPlaces)
64-
65-
for (var i = this.units.length - 1; i >= 0; i--) {
66-
67-
var size = Math.pow(10, (i + 1) * 3)
68-
69-
if (size <= number) {
70-
number = Math.round(number * decPlaces / size) / decPlaces
71-
72-
if ((number === 1000) && (i < this.units.length - 1)) {
73-
number = 1
74-
i++
75-
}
76-
77-
number += this.units[i]
78-
79-
break
80-
}
81-
}
82-
83-
return number
84-
}
85-
86-
NumberAbbreviate.prototype.abbreviate = function(number, decPlaces) {
87-
var isNegative = number < 0
88-
var abbreviatedNumber = this._abbreviate(Math.abs(number), decPlaces || 0)
89-
90-
return isNegative ? '-' + abbreviatedNumber : abbreviatedNumber;
91-
}
92-
93-
if (typeof module !== 'undefined' && module.exports) {
94-
module.exports = NumberAbbreviate
95-
} else {
96-
root.NumberAbbreviate = NumberAbbreviate
97-
}
98-
99-
})(this);
100-
*/
45+
export {
46+
type QuantityAbbrSymbol,
47+
ABBR_SYMBOLS_ARRAY,
48+
NumberAbbreviator as default
49+
}

0 commit comments

Comments
 (0)