Feat/nitro035 support#2
Conversation
Prepare v2.0.0: large refactor and docs overhaul. Adds CHANGELOG, SECURITY, dependabot and PR template, and Biome config; rewrites README with new API and migration notes (icons moved to subpath export, enums -> as const, iOS min bumped to 16, WCAG & Dynamic Type improvements). Simplifies Android implementation to a no-op stub that emits a single warning (AtomicBoolean) and removes verbose placeholder UI. Updates example, generated config, types, and build/project tweaks. Breaking changes and migration steps are documented in README/CHANGELOG.
Drop the reserved/no-op native APIs (reduceComplexity, updateSymbol, animateSymbol) from Android and iOS implementations and remove the generated NitroSfsymbolsConfig.json and its sync script. Inline the host component config in src/index.tsx and update getHostComponent to use it, removing the runtime JSON sync. Simplify package.json nitrogen script to remove the extra sync step. These changes remove dead/ABI-reserved code and eliminate a build-time file sync to streamline the module.
…ns; update SFSymbolView for dynamic font scaling Co-authored-by: Copilot <copilot@github.com>
Raise minimum platform versions to iOS 16.0 (podspec, example project, Podfile) and bump macOS/tvOS mins in the podspec. Update example Xcode project's IPHONEOS_DEPLOYMENT_TARGET entries to 16.0. Add .github/FUNDING.yml. Regenerate example/ios/Podfile.lock (checksums updated). These changes align the example and podspec with the new minimum supported OS versions.
There was a problem hiding this comment.
Pull request overview
This PR modernizes react-native-nitro-sfsymbols for a 2.0.0 release by updating the public TypeScript API, switching to tree-shakable constants and an opt-in icon catalog subpath, refactoring native iOS/Android implementations, and refreshing repo tooling/docs to match updated platform baselines (notably iOS 16+).
Changes:
- Introduces a new public API surface for
<SFSymbolView />(Dynamic Type scaling, accessibility defaults, fallback symbols) and converts enums to tree-shakableas constconstants. - Moves the curated SF Symbols catalog behind an
./iconssubpath export and updates TS pathing/exports accordingly. - Refactors native implementations: iOS coalesced render pipeline + caching + symbol effects; Android becomes a zero-cost stub with a single warning; updates tooling/docs (Biome, React Compiler, changelog/security policy).
Reviewed changes
Copilot reviewed 35 out of 43 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| turbo.json | Formats Turbo env list for build:ios. |
| tsconfig.test.json | Adds a dedicated ts-jest/Node-oriented TS config for tests. |
| tsconfig.json | Adds TS path mapping for react-native-nitro-sfsymbols/icons. |
| src/utils.ts | Removes the previous runtime utility helpers module. |
| src/types.ts | Reworks public types/constants to as const, adds new props and exports (e.g., minTouchTargetStyle). |
| src/index.tsx | Rebuilds the public entry with a new host config, accessibility behavior, Dynamic Type scaling, and wire-shape adapters. |
| src/icons.ts | Replaces the enum-based catalog with a tree-shakable SFIcons const object and type export. |
| src/generated/NitroSfsymbolsConfig.json | Removes generated host config JSON (now inlined). |
| src/tests/types.test.ts | Adds tests validating exported constants and catalog shape. |
| src/tests/index.test.tsx | Removes the previous broad test suite (icons/utils/etc.). |
| src/NitroSfsymbols.nitro.ts | Simplifies the Nitro view spec; adds new wire props (e.g., fallbackName) and removes imperative methods. |
| scripts/sync-nitro-config.cjs | Removes the script that copied generated host config JSON into src/. |
| package.json | Bumps to 2.0.0, adds subpath exports, switches linting to Biome, updates dev deps, and changes Jest config to ts-jest. |
| nitro.json | Updates autolinking config format (separate ios/android blocks). |
| lefthook.yml | Switches pre-commit linting to Biome and expands glob to include JSON. |
| ios/NitroSfsymbols.swift | Major refactor: coalesced renders, per-config image cache, hex parsing cache, symbol effects, fallback symbol support. |
| android/src/main/java/com/margelo/nitro/nitrosfsymbols/NitroSfsymbols.kt | Replaces verbose Android placeholder with an empty view + one-time warning. |
| SECURITY.md | Adds supported versions + responsible disclosure guidance. |
| README.md | Rewrites docs for v2 API, new platform minimums, /icons subpath, and migration guidance. |
| NitroSfsymbols.podspec | Raises Apple platform minimums (iOS 16, macOS 13, tvOS 16). |
| CHANGELOG.md | Adds a Keep a Changelog / SemVer changelog for 2.0.0. |
| .github/pull_request_template.md | Adds a PR template/checklist. |
| .github/dependabot.yml | Adds Dependabot config for npm + GitHub Actions. |
| .github/FUNDING.yml | Adds GitHub funding configuration. |
| biome.json | Adds Biome formatter/linter configuration with repo-specific overrides. |
| babel.config.js | Adds the React Compiler Babel plugin for the library build pipeline. |
| eslint.config.mjs | Removes the old ESLint flat config (replaced by Biome). |
| example/src/App.tsx | Updates example imports to use /icons subpath and applies formatting tweaks. |
| example/package.json | Bumps RN/Nitro versions and tooling deps in the example app; updates Node engine requirement. |
| example/ios/Podfile | Pins iOS deployment target to 16.0 in the example. |
| example/ios/NitroSfsymbolsExample/Images.xcassets/Contents.json | Re-formats JSON (Biome). |
| example/ios/NitroSfsymbolsExample/Images.xcassets/AppIcon.appiconset/Contents.json | Re-formats JSON (Biome). |
| example/ios/NitroSfsymbolsExample.xcodeproj/project.pbxproj | Updates iOS deployment target and tweaks Xcode build settings. |
| example/index.js | Minor import ordering change. |
| example/babel.config.js | Adds React Compiler plugin to the example app Babel config. |
| example/Gemfile | Adds nkf gem to the example Ruby deps. |
| example/Gemfile.lock | Locks updated Ruby dependencies including nkf. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Only freeze the tint when the user supplied one explicitly. Otherwise | ||
| // we leave the image as `.alwaysTemplate` so that `imageView.tintColor` | ||
| // reacts dynamically to dark/light mode and Increase Contrast. | ||
| if mode == "monochrome", hasExplicitTint, let tint = uiColorFromHex(tintHex) { | ||
| resolved = resolved.withTintColor(tint, renderingMode: .alwaysOriginal) | ||
| } | ||
|
|
||
| /** | ||
| * Apply hierarchical color rendering | ||
| * | ||
| * Creates opacity-based color layers using primary, secondary, and tertiary colors. | ||
| * | ||
| * @private | ||
| * @method applyHierarchicalMode | ||
| * @param {UIImage} image - Base symbol image | ||
| * @returns {UIImage} Image with hierarchical colors applied | ||
| */ | ||
| @available(iOS 15.0, *) | ||
| private func applyHierarchicalMode(_ image: UIImage) -> UIImage { | ||
| guard let config = hierarchicalConfig, | ||
| let primaryColorHex = config["primaryColor"] else { | ||
| return image | ||
| } | ||
| Self.imageCache.setObject(resolved, forKey: cacheKey, cost: Int(pointSize * pointSize * 4)) | ||
| return resolved | ||
| }() | ||
|
|
||
| let primaryColor = hexStringToUIColor(primaryColorHex) | ||
| let symbolConfig = UIImage.SymbolConfiguration(hierarchicalColor: primaryColor) | ||
| imageView.image = image | ||
| if mode == "monochrome", !hasExplicitTint { | ||
| imageView.tintColor = .label | ||
| } |
There was a problem hiding this comment.
If tintColor is provided but not a parseable hex string, hasExplicitTint stays true so the view won’t fall back to .label, and uiColorFromHex() returning nil means the tint is not applied. This can lead to stale tint from a previous render. Consider treating “invalid hex” the same as “no explicit tint” (and optionally logging a DEBUG warning) so the icon renders predictably.
| SF Symbols 7.0+ • Supports iOS 13+ | ||
| </Text> | ||
| <Text style={styles.footerText}>Built with React Native Nitro 🚀</Text> | ||
| <Text style={styles.footerSubtext}>SF Symbols 7.0+ • Supports iOS 16+</Text> |
There was a problem hiding this comment.
The example app UI still says “Supports iOS 13+”, but this PR raises the minimum iOS version to 16.0 (Podfile/podspec). Update this footer text to avoid misleading users testing the example app.
| <Text style={styles.footerSubtext}>SF Symbols 7.0+ • Supports iOS 16+</Text> | |
| <Text style={styles.footerSubtext}>SF Symbols 7.0+ • Supports iOS 16+</Text> |
| /** | ||
| * Supported animation types for SF Symbols (iOS 17+). | ||
| * Branded string type for the curated SF Symbols catalog. | ||
| * | ||
| * @enum {string} | ||
| * Importing the catalog (`react-native-nitro-sfsymbols/icons`) widens the | ||
| * `name` prop to the curated names while still allowing any string. | ||
| */ | ||
| export enum SFSymbolAnimationType { | ||
| /** No animation */ | ||
| NONE = 'none', | ||
| /** Bounce animation */ | ||
| BOUNCE = 'bounce', | ||
| /** Scale animation */ | ||
| SCALE = 'scale', | ||
| /** Pulse animation */ | ||
| PULSE = 'pulse', | ||
| /** Rotate animation */ | ||
| ROTATE = 'rotate', | ||
| /** Appear animation */ | ||
| APPEAR = 'appear', | ||
| /** Disappear animation */ | ||
| DISAPPEAR = 'disappear', | ||
| /** Replace animation */ | ||
| REPLACE = 'replace', | ||
| } | ||
| export type SFIconName = string; |
There was a problem hiding this comment.
SFIconName is documented as a branded type that gets widened by importing the /icons catalog, but it's currently just type SFIconName = string, so no branding or widening can occur. Either adjust the docstring to reflect the actual behavior (autocomplete via SFIcons.* constants) or implement a real branded/augmented type strategy.
| /** | ||
| * Color used to tint the symbol. When omitted and the user has *Increase | ||
| * Contrast* enabled, the system label color is used for guaranteed contrast. | ||
| */ | ||
| tintColor?: ColorValue; |
There was a problem hiding this comment.
tintColor is typed as ColorValue, but the JS layer only forwards string values and the native iOS implementation only parses hex strings. This makes non-hex ColorValues (e.g. rgb(...), hsl(...), processColor numbers) silently drop or mis-render. Consider narrowing the public type/docs to a hex string, or add proper React Native color resolution and pass a supported wire format to native.
| function resolveColor(color: ColorValue | undefined): string | undefined { | ||
| if (color == null) return undefined; | ||
| if (typeof color === 'string') return color; | ||
| // OpaqueColorValue / number — let RN handle resolution downstream. | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
resolveColor() claims to let RN resolve non-string ColorValues downstream, but it returns undefined for numeric/opaque colors, effectively dropping the tint. This should either resolve numeric colors (e.g. via processColor) or the public type/docs should be restricted to the supported string format to avoid surprising no-ops.
| /// Hex representation of the system label color so cache keys remain stable | ||
| /// when no explicit tint is provided. Honors *Increase Contrast*. | ||
| private static func systemTintHex() -> String { | ||
| if UIAccessibility.isDarkerSystemColorsEnabled { return "@label-hc" } | ||
| return "@label" | ||
| } |
There was a problem hiding this comment.
systemTintHex() is currently unused (no call sites). Either remove it to keep the implementation lean, or use it in the cache key construction so the intent of stable cache keys for system tint is actually realized.
| /// Hex representation of the system label color so cache keys remain stable | |
| /// when no explicit tint is provided. Honors *Increase Contrast*. | |
| private static func systemTintHex() -> String { | |
| if UIAccessibility.isDarkerSystemColorsEnabled { return "@label-hc" } | |
| return "@label" | |
| } |
| ## Migrating from 1.x | ||
|
|
||
| ```bash | ||
| yarn lint --fix | ||
| ``` | ||
|
|
||
| ### 🏗️ Build | ||
|
|
||
| ```bash | ||
| yarn prepare | ||
| ``` | ||
|
|
||
| > [!NOTE] | ||
| > Run `yarn nitrogen` after modifying `NitroSfsymbols.nitro.ts` | ||
|
|
||
| --- | ||
|
|
||
| ## 🤝 Contributing | ||
|
|
||
| Contributions are **welcome and encouraged**! 🎉 | ||
|
|
||
| Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for: | ||
| - 📋 Development workflow | ||
| - ✅ Code style guidelines | ||
| - 🧪 Testing requirements | ||
| - 📤 Pull request process | ||
| - 📖 [Code of Conduct](./CODE_OF_CONDUCT.md) | ||
|
|
||
| --- | ||
|
|
||
| ## 📄 License | ||
|
|
||
| MIT © [Mateus Andrade](https://github.com/mCodex) | ||
|
|
||
| <div align="center"> | ||
|
|
||
| **[↑ back to top](#-react-native-nitro-sfsymbols)** | ||
|
|
||
| </div> | ||
|
|
||
| --- | ||
|
|
||
| ## 🔗 Resources | ||
|
|
||
| | Resource | Link | | ||
| |----------|------| | ||
| | 📖 SF Symbols Guide | [developer.apple.com/design/human-interface-guidelines/sf-symbols](https://developer.apple.com/design/human-interface-guidelines/sf-symbols) | | ||
| | 🎨 SF Symbols Browser | [developer.apple.com/sf-symbols](https://developer.apple.com/sf-symbols/) | | ||
| | ⚡ Nitro Modules Docs | [nitro.margelo.com](https://nitro.margelo.com/) | | ||
| | 🚀 React Native Docs | [reactnative.dev](https://reactnative.dev/) | | ||
| | 🎨 Material Icons | [fonts.google.com/icons](https://fonts.google.com/icons) | | ||
|
|
||
| --- | ||
| 1. **Bump `react-native-nitro-modules` to `>= 0.35`.** | ||
| 2. **Imports**: `SFIcons` moved to a subpath: | ||
| ```diff | ||
| - import { SFIcons } from 'react-native-nitro-sfsymbols'; | ||
| + import { SFIcons } from 'react-native-nitro-sfsymbols/icons'; | ||
| ``` | ||
| 3. **Removed runtime helpers** (use the constants directly): | ||
| - `isValidSFIcon`, `getAllSFIcons`, `camelCaseToSFSymbol`, `searchSFIcon` | ||
| - `isValidColor`, `normalizeColor`, `clampOpacity`, `validateConfig`, | ||
| `applyDefaults`, `optimizeProps`, `createHierarchicalConfig`, | ||
| `createPaletteConfig`, `dimensionToSymbolSize`, `getPresetSize` | ||
| 4. **Constants are now plain objects** (no longer `enum`s). Member access is | ||
| identical (`SFSymbolWeight.BOLD`); the type is a string-literal union. | ||
| 5. **Color config keys**: `primaryColor` → `primary`, `secondaryColor` → `secondary`, etc. | ||
| ```diff | ||
| - hierarchical={{ primaryColor: '#FF5722' }} |
There was a problem hiding this comment.
The migration guide mentions moving SFIcons to the /icons subpath, but the actual catalog also changes/renames entries (e.g. TOGGLE_POWER is gone and POWER exists). Since this is a breaking change for consumers, the migration steps should call out that the catalog is curated and some keys were removed/renamed, with guidance on how to update.
| /** `{ primary, secondary?, tertiary? }` hex colors. */ | ||
| hierarchicalConfig?: Record<string, string>; | ||
|
|
||
| /** | ||
| * Palette color configuration | ||
| * Used when renderingMode is "palette" | ||
| */ | ||
| /** `{ primary, secondary?, tertiary? }` hex colors. */ | ||
| paletteConfig?: Record<string, string>; |
There was a problem hiding this comment.
The doc comment for hierarchicalConfig/paletteConfig says the wire shape is { primary, secondary?, tertiary? }, but the JS adapter sends { primaryColor, secondaryColor, tertiaryColor } and the iOS implementation reads primaryColor keys. Please update the comment to reflect the actual wire keys to avoid confusion when debugging native props.
| let pointSize = CGFloat(size ?? 24) | ||
| let weightVal = parseWeight(weight ?? "regular") | ||
| let scaleVal = parseScale(scale ?? "medium") | ||
| let mode = renderingMode ?? "monochrome" | ||
| let hasExplicitTint = (tintColor != nil) | ||
| let tintHex = tintColor ?? "@label" | ||
|
|
||
| let cacheKey = NSString(format: "%@|%@|%.1f|%d|%d|%@|%@|%@|%@|%d|%d", | ||
| symbolName, fallbackName ?? "_", | ||
| pointSize, weightVal.rawValue, scaleVal.rawValue, | ||
| mode, tintHex, | ||
| hashConfig(hierarchicalConfig), | ||
| hashConfig(paletteConfig), | ||
| hasExplicitTint ? 1 : 0, | ||
| (variableColor ?? false) ? 1 : 0) |
There was a problem hiding this comment.
variableColor is included in the cache key and exposed as a prop, but it isn't actually applied to the UIImage configuration anywhere in render(). As-is, toggling variableColor appears to be a no-op (aside from cache misses). Either implement the intended variable-color configuration on iOS (iOS 16+) or remove/deprecate the prop and update the docs/changelog accordingly.
- Drop variableColor boolean prop (was a no-op; use animation={{type:'variableColor'}})
- Narrow tintColor to string (hex); drop redundant resolveColor helper
- Treat invalid hex as no-tint instead of leaving stale color in cache key
- Remove unused systemTintHex Swift helper
- Fix SFIconName, hierarchicalConfig, paletteConfig doc comments
- README/CHANGELOG: document tintColor narrowing + catalog renames
Validate and resolve tintColor hex up-front to avoid rendering stale tints from previous passes, falling back to the system label color when the hex is unparseable. Update image cache key and remove the old systemTintHex helper. Remove variableColor from the native view, JS props, and type definitions, and simplify the React wrapper by removing resolveColor and related wiring. Docs/types updated: tintColor now accepts #RRGGBB or #RRGGBBAA and SFIconName/docs clarified; package.json version adjusted.
Revamp README presentation and content: add centered header, additional badges (npm downloads, PRs welcome), and a Table of Contents; update headings with emojis and improved wording for Highlights, Install, Usage, Accessibility and Performance sections. Add new sections for Contributing, Support and Acknowledgments with a quick-start contributor workflow and example app instructions. Clarify API docs (tintColor now documented as a string hex format) and remove the variableColor prop entry. Minor copy edits: platform table iconography, animation/reduce-motion note, install callout, and update license attribution link to GitHub profile.
Upgrade GitHub Actions references and platform/tool versions across CI. Switched action pins to stable tags (actions/setup-node@v4, actions/cache@v4, actions/checkout@v5, actions/setup-java@v4, maxim-lobanov/setup-xcode@v1) and updated internal setup action to use setup-node@v4. Bumped Java to 21, Node (.nvmrc) to v24, macOS runner to macos-15 and Xcode to 26.2. These changes refresh action versions and align CI with newer toolchains and runners.
This pull request introduces a number of foundational improvements and maintenance updates to the project, focusing on documentation, platform requirements, development tooling, and Android platform behavior. The key changes include a major version bump with a detailed changelog, stricter platform requirements (notably raising the minimum iOS version to 16.0), improved Android stub implementation, and the addition of configuration files for funding, security, and automated tooling.
Documentation and Versioning:
CHANGELOG.mdfollowing Keep a Changelog and Semantic Versioning, documenting all notable changes for version 2.0.0, including breaking changes, new features, removals, and bug fixes.SECURITY.mdfile outlining supported versions and responsible disclosure guidelines for security vulnerabilities..github/pull_request_template.mdto standardize PR submissions.Platform and Compatibility Updates:
NitroSfsymbols.podspec) and the example Xcode project, and updated other Apple platform minimums (macOS 13.0, tvOS 16.0). [1] [2] [3]Android Implementation:
NitroSfsymbols.kt) for<SFSymbolView />to render an empty, zero-cost view, and log a single warning on first use, replacing the previous verbose placeholder and per-prop warnings. This makes the Android behavior lighter and less noisy for developers.Development and Tooling:
.github/FUNDING.yml), automated dependency updates (.github/dependabot.yml), code formatting and linting (biome.json), and removed the old ESLint config in favor of Biome. [1] [2] [3] [4]Other Notable Changes:
nkfgem to the example app'sGemfilefor improved text processing.These updates collectively modernize the project, improve cross-platform developer experience, and lay a solid foundation for future development.