Skip to content

feat: add createIcon factory to decouple Icon from a single icon set#786

Open
adrienzheng-cb wants to merge 2 commits into
masterfrom
adrien/create-icon-seam-example
Open

feat: add createIcon factory to decouple Icon from a single icon set#786
adrienzheng-cb wants to merge 2 commits into
masterfrom
adrien/create-icon-seam-example

Conversation

@adrienzheng-cb

Copy link
Copy Markdown
Contributor

Example PR demonstrating the createIcon seam ("Option B") from the CDS Icon Style Update TDD. Backward-compatible infrastructure only — no new icon assets.

What changed? Why?

The web and mobile Icon components hardcode a single icon source (@coinbase/cds-icons/glyphMap), a single font family ('CoinbaseIcons'), and a single name type (IconName). That makes it impossible to render an alternate icon set (e.g. a private, retail-specific style) without forking the whole component.

This PR extracts the rendering logic into a createIcon<Name> factory (mirroring the existing createIllustration<Variant> convention) that binds:

  • a glyph map (GlyphMap<Name>, keys `${name}-${size}-${active}`),
  • an injectable font family (defaults to 'CoinbaseIcons'), and
  • a typed name union (Name).

The default Icon becomes a one-liner:

export const Icon = createIcon<IconName>({ glyphMap, fontFamily: DEFAULT_ICON_FONT_FAMILY });

So the public API and behavior are unchanged, while a consumer with their own icon package can do createIcon<RetailIconName>({ glyphMap, fontFamily }) and get a fully-typed icon component that reuses all CDS accessibility, theming, sizing, and componentConfig behavior — no duplicated component logic.

On web, font-family moved off the static Linaria css block (which can't interpolate a runtime value) to an inline style on the glyph span; mobile already applied it via an inline style object.

Root cause (required for bugfixes)

N/A — additive infrastructure.

UI changes

None. The default icon set, font, glyph keys, and sizing are identical, so rendering is unchanged.

Testing

How has it been tested?

  • Unit tests
  • Interaction tests
  • Pseudo State tests
  • Manual - Web
  • Manual - Android (Emulator / Device)
  • Manual - iOS (Emulator / Device)

Testing instructions

  • yarn nx run web:typecheck and yarn nx run mobile:typecheck — pass
  • yarn nx run web:test --testPathPattern="icons/" — pass (5 suites)
  • yarn nx run mobile:test --testPathPattern="icons/" — pass

Illustrations/Icons Checklist

N/A — no files under packages/icons/** or packages/illustrations/** changed; no icon assets or names added.

Change management

type=routine
risk=low
impact=sev5

automerge=false

Made with Cursor

Extract the web and mobile Icon rendering into a `createIcon<Name>` factory
that binds a glyph map, font family, and typed name union. The default
`Icon` becomes `createIcon<IconName>({ glyphMap, fontFamily })`, so behavior
and the public API are unchanged, while consumers with their own icon
package can create a typed icon component that reuses all CDS rendering,
accessibility, and theming logic.

Font family is now injectable (moved off the static Linaria block on web to
an inline style) rather than hardcoded, enabling alternate icon sets without
forking the component.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cb-heimdall

Copy link
Copy Markdown
Collaborator

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 1
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1
CODEOWNERS 🟡 See below

🟡 CODEOWNERS

Code Owner Status Calculation
ui-systems-eng-team 🟡 0/1
Denominator calculation
Additional CODEOWNERS Requirement
Show calculation
Sum 0
0
From CODEOWNERS 1
Sum 1

@github-actions

Copy link
Copy Markdown
Contributor

Keep the static Linaria block and express font-family as
`var(--cds-icon-font-family, 'CoinbaseIcons')`. The factory only sets the
variable when a non-default font is bound, so consumers can override the
icon font via className, inline style, or theme scope without a specificity
battle. Default rendering is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants