Skip to content

feat(link-pill): add optional icon tint - #921

Open
hryhoriiK97 wants to merge 5 commits into
feat/link-pillsfrom
feat/link-pill-icon-tint
Open

hryhoriiK97 wants to merge 5 commits into
feat/link-pillsfrom
feat/link-pill-icon-tint

Conversation

@hryhoriiK97

@hryhoriiK97 hryhoriiK97 commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

What/Why?

Adds an optional icon tint to link pills: a color that replaces the colors of a pill's icon and keeps its alpha. Omitting it keeps the image's own colors. It works for local and remote icons.

This replaces #863. The option and its Jest tests are @juliusmarminge's; they are ported here to the reworked pill implementation, where pill is a nested object on the native link variant. Stacked on feat/link-pills (#920), so the diff shows only the tint.

One rule was added on top of #863: the variant's tint applies only to the variant's own icon. In #863 it tinted whatever icon the pill showed, so an icon supplied per link through linkPillContent (an avatar, say) became a silhouette in the tint color.

API

<EnrichedMarkdownText
  markdownStyle={{
    linkVariants: {
      '^user:': {
        pill: { iconUri: 'user_icon', iconTintColor: '#065F46' },
      },
    },
  }}
  linkPillContent={{
    // Its own icon: not tinted by the variant.
    'user:gregory': { iconUri: avatarUri },
    // No icon of its own: the variant's icon, in this color for this link.
    'user:ernest': { iconTintColor: '#2563EB' },
  }}
/>
  • pill.iconTintColor tints the variant's icon.
  • linkPillContent[url].iconTintColor tints that link's icon. With iconUri it tints the entry's icon; without it, it recolors the variant's icon for that one link.
  • A valid color, including transparent, is applied as the tint. transparent hides the icon's pixels and keeps its space.
  • An invalid color is ignored with a warning, and the icon keeps its own colors.

Implementation

  • JS: iconTintColor is added to LinkPillStyle and to LinkPillContent, normalized as a color and passed in the nested pill struct and in the linkPillContent entries of both native component specs. It is optional on the native side, so "no tint" and "transparent" stay distinct.
  • Android: LinkPillStyle builds one source-in color filter per variant; a span builds its own only for a per-link tint. Pills share a single icon paint, so the filter is set for every icon, tinted or not; otherwise one tinted pill would tint the next.
  • iOS: the link renderer resolves the tint and passes it to the attachment, which draws the icon through imageWithTintColor:. The tint is part of the drawn-pill cache key.
  • The tint does not affect layout, so the measurement caches are unchanged. The cached icon image is never modified.

Testing

CI does not run on this PR, because the workflow only triggers for PRs that target main; it runs on #920 once this is merged into the branch. Run locally on this head:

  • Jest: 67 pass, including three for the tint (default, explicit and transparent tint; invalid color; per-link tint and its change detection).
  • Android: builds, Kotlin lint clean. iOS: builds.
  • The native unit tests for the tint (Android and iOS) were removed together with the rest of the native pill tests in feat: add native link pills #920; the Jest tests remain.
  • On an Android emulator and an iOS simulator (iPhone 17, iOS 26.5), with a local demo screen that is not part of this PR: untinted icons keep their colors, a tinted variant leaves a per-link avatar alone, and per-link tints apply. Local icons were only checked on Android; remote icons on both.

The Storybook "Pills" story now shows an icon with a tint control.

PR Checklist

  • Code compiles and runs on iOS
  • Code compiles and runs on Android
  • Updated documentation/README if applicable
  • Ran example app to verify changes
  • E2E tests are passing
  • Required E2E tests have been added (if applicable)

🤖 Generated with Claude Code

`pill.iconTintColor` replaces the colors of a pill's icon and keeps its alpha;
omitting it keeps the image's own colors. It applies to local and remote icons
and leaves the cached image untouched.

The option and its Android and Jest tests come from #863, ported to the nested
`pill` configuration. Android sets the tint on the shared icon paint for every
icon, tinted or not, so one pill cannot tint the next.

Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The variant's `pill.iconTintColor` tinted whatever icon a pill showed, so an
icon supplied through `linkPillContent` (an avatar, say) became a silhouette
in the tint color.

The variant tint now applies only to the variant's own icon. A
`linkPillContent` entry can carry its own `iconTintColor`: it tints the entry's
icon, or recolors the variant's icon for that one link when the entry has no
icon.

Also covers transparent and half-transparent tints in the iOS tests, documents
the rule, and adds an icon with a tint control to the Storybook pill story.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts:
#	packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkPillSpan.kt
# Conflicts:
#	packages/react-native-enriched-markdown/__tests__/ios/ENRMLinkPillAttachmentTests.mm
#	packages/react-native-enriched-markdown/android/src/test/java/com/swmansion/enriched/markdown/spans/LinkPillSpanTest.kt
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