Skip to content

feat: fraction colour for two-tone amounts - #6

Draft
IgnacioNavarro wants to merge 1 commit into
AmatoGiulio:mainfrom
IgnacioNavarro:feat/fraction-color
Draft

feat: fraction colour for two-tone amounts#6
IgnacioNavarro wants to merge 1 commit into
AmatoGiulio:mainfrom
IgnacioNavarro:feat/fraction-color

Conversation

@IgnacioNavarro

Copy link
Copy Markdown

A balance is commonly drawn with its decimals dimmed, so the figure that matters reads first. This adds fractionColor, a second colour for the fraction span — the decimal separator, the digits after it, and any trailing affix.

<NumericText
  value={balance}
  currency="USD"
  style={{ fontSize: 50, color: '#FFFFFF' }}
  fractionColor="#8A9BA8"
/>
// $1,234.56  ->  "$1,234" white, ".56" grey

Omit the prop and the number is one colour, exactly as before. No existing behaviour changes.

The span

Read from the formatted string rather than from the value, so it lands on the separator the locale actually drew — 1.234,56 € dims ,56 €. A format with no fraction digits dims only a trailing affix, if there is one. The two platforms are deliberately given the same rule.

Android

The line is rasterised white and tinted at composite time, so a colour is a property of the draw rather than of the bitmap — which means a second one costs nothing but choosing a different PorterDuffColorFilter per keyed slice. Because onDraw routes everything through drawRolling, settled and transitioning frames pick it up alike, so there is no second code path to keep in sync.

The fraction colour joins the RenderNode cache key, since the tint is recorded into the display list.

isFractionKey moved to TransitionLogic — it reads the key encoding layoutKeyedSlots assigns, so it belongs beside it, and that makes it directly testable. FractionSpanTest covers the span it selects (separator + decimals, integer left alone, trailing affix included, leading currency symbol excluded) plus the predicate itself.

iOS

Two concatenated Text runs.

Your own comments establish that .numericText() is closed — SwiftUI rasterises once per value and animates the raster, with no CALayer per digit and nothing per glyph to reach into. That rules out the obvious approaches, but not this one: Text + Text is still one Text, so the raster it produces simply carries two coloured runs and the transition is unaffected.

Two sibling views would not survive — each would rasterise and transition independently, and they would drift apart on any change that moves the decimal point. The concatenation is what keeps it a single transition.

foregroundStyle on 17+, foregroundColor below, so the colour still applies where the transition itself does not.

Verification

Honest about this: I've run neither the example app nor the native test suites here — the change came out of using the library, and I can't currently build both platforms. What I have done is follow the existing structure closely and keep the diff narrow.

The Android mechanism is in production use via a local patch of this same change, on a real balance screen, so the per-slice tint and the cache-key interaction are exercised. The iOS concatenation is the approach I'm already using in a hand-rolled SwiftUI numeric text in the same app, so the "concatenation survives numericText" claim is from observation rather than theory — but not in this codebase, which is why this is a draft.

Happy to adjust naming (fractionColor vs fractionStyle), the span definition, or anything else that doesn't fit how you'd like this to work.

A balance is commonly drawn with its decimals dimmed so the figure that
matters reads first. `fractionColor` colours the fraction span — the
decimal separator, the digits after it, and any trailing affix — leaving
the rest in the `style` colour. Omitted, nothing changes.

The span is read from the formatted string rather than from the value, so
it lands on the separator the locale actually drew, and a format with no
fraction digits dims only a trailing affix if there is one.

Both renderers keep the transition intact rather than working around it:

Android draws the line into one white raster and tints it at composite
time, so a colour is a property of the draw and not of the bitmap. The
second colour is a second PorterDuffColorFilter chosen per keyed slice.
Settled and transitioning frames share one draw path, so both pick it up,
and the fraction colour joins the RenderNode cache key because the tint is
recorded into the display list.

iOS concatenates two Text runs. `.numericText()` is closed, but `Text +
Text` is still one Text, so its raster simply carries two coloured runs
and the transition is untouched. Two sibling views would not survive:
each would rasterise and transition on its own and they would drift apart
on any change that moves the decimal point.

The key predicate moves to TransitionLogic, next to the encoding it reads,
with unit tests over the span it selects.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant