@@ -19,12 +19,12 @@ const numericVariants = cva('font-mono tabular-nums', {
1919 } ,
2020} )
2121
22- type NumericRole = VariantProps < typeof numericVariants > [ 'role' ]
22+ type LegacyNumericRole = VariantProps < typeof numericVariants > [ 'role' ]
2323
2424interface NumericProps {
2525 value : number | null | undefined
2626 format ?: 'usd' | 'token' | 'pct' | 'number'
27- role ?: NumericRole
27+ role ?: LegacyNumericRole
2828 decimals ?: number
2929 compact ?: boolean
3030 fallback ?: string
@@ -63,6 +63,69 @@ function formatNumber(value: number): string {
6363 return value . toLocaleString ( 'en-US' )
6464}
6565
66+ /**
67+ * Semantic tone for a numeric value already formatted by the caller.
68+ *
69+ * Unlike `Numeric` below, this primitive does not format or round its
70+ * children — it only supplies the tabular-figure font treatment and the
71+ * approved positive/negative/warning/accent/muted tones (DS-012).
72+ */
73+ const numericTextVariants = cva ( "font-mono tabular-nums slashed-zero" , {
74+ variants : {
75+ role : {
76+ neutral : "text-foreground" ,
77+ positive : "text-success" ,
78+ negative : "text-destructive" ,
79+ warning : "text-warning" ,
80+ accent : "text-primary" ,
81+ muted : "text-muted-foreground" ,
82+ } ,
83+ size : {
84+ "2xs" : "text-[0.625rem] leading-4" ,
85+ xs : "text-[0.6875rem] leading-4" ,
86+ sm : "text-xs leading-5" ,
87+ md : "text-[0.8125rem] leading-5" ,
88+ base : "text-sm leading-5" ,
89+ lg : "text-base leading-6" ,
90+ } ,
91+ weight : {
92+ normal : "font-normal" ,
93+ medium : "font-medium" ,
94+ semibold : "font-semibold" ,
95+ bold : "font-bold" ,
96+ } ,
97+ } ,
98+ defaultVariants : {
99+ role : "neutral" ,
100+ size : "base" ,
101+ weight : "normal" ,
102+ } ,
103+ } )
104+
105+ type NumericRole = VariantProps < typeof numericTextVariants > [ 'role' ]
106+
107+ type NumericTextProps = React . ComponentProps < 'span' > &
108+ VariantProps < typeof numericTextVariants >
109+
110+ function NumericText ( { role, size, weight, className, ...props } : NumericTextProps ) {
111+ return (
112+ < span
113+ data-slot = "numeric-text"
114+ className = { cn ( numericTextVariants ( { role, size, weight } ) , className ) }
115+ { ...props }
116+ />
117+ )
118+ }
119+
120+ /** Derives a positive/negative/neutral role from a signed value's sign. */
121+ function numericRoleForValue (
122+ value : number
123+ ) : Extract < NumericRole , 'positive' | 'negative' | 'neutral' > {
124+ if ( value > 0 ) return 'positive'
125+ if ( value < 0 ) return 'negative'
126+ return 'neutral'
127+ }
128+
66129function Numeric ( {
67130 value,
68131 format = 'number' ,
@@ -98,5 +161,5 @@ function Numeric({
98161 )
99162}
100163
101- export { Numeric , numericVariants }
102- export type { NumericRole , NumericProps }
164+ export { Numeric , numericVariants , NumericText , numericTextVariants , numericRoleForValue }
165+ export type { NumericRole , NumericProps , LegacyNumericRole , NumericTextProps }
0 commit comments