Skip to content

feat: opt-in image prop on top-bar actions - #537

Open
prosenthal828 wants to merge 1 commit into
NativePHP:mainfrom
prosenthal828:feat/top-bar-action-image
Open

prosenthal828 wants to merge 1 commit into
NativePHP:mainfrom
prosenthal828:feat/top-bar-action-image

Conversation

@prosenthal828

Copy link
Copy Markdown
Contributor

Top-bar actions can only draw an icon. Tab apps very often put the signed-in user's avatar in the top-right, on the profile action, and today there is no native way to do that. Apps either settle for a generic person icon or patch the renderers.

This adds an opt-in image prop that takes a URL:

NavAction::make('profile')
    ->icon('person')                       // still required: shown while loading and on failure
    ->image($user->avatarUrl ?? '')        // null/blank = no image, icon only
    ->url('/profile');
<native:top-bar-action id="profile" icon="person" :image="$avatarUrl" url="/profile" />

Behavior

  • The image is drawn as a 28pt circle in place of the icon, cropped to fill.
  • The icon is drawn first and the image is laid over it. The icon shows while the image loads, and stays if the URL fails, so a bad URL falls back to today's look rather than an empty button.
  • disabled fades the image too: 0.38 alpha on Android, matching M3's disabled tone, and the existing 0.4 opacity on iOS.
  • It works for both plain actions and menu triggers (->items([...])).
  • When image is unset, everything renders exactly as it does today.

Implementation

  • PHP: TopBarAction accepts image. NavAction::image(?string $url) treats null or blank as unset, so ->image($maybeNull ?? '') needs no if.
  • Android: a new TopBarActionGlyph composable used by TopBarActionView, so it covers both the stack and the tabs top bar. It uses Coil 3's AsyncImage, which is already a core dependency, so nothing new is added.
  • iOS: a new TopBarActionGlyph view used by both NativeRootStackRenderer and TabsActionView. It uses SwiftUI AsyncImage, like NativeAsyncImage. The caller's .font, .foregroundColor and .opacity still reach the SF Symbol fallback.

Tests

  • tests/Unit/Edge/TopBarActionImageTest.php covers: serialization, omitted when unset, the fluent builder keeping its icon fallback, null/blank treated as unset, and a later image(null) clearing an earlier image.
  • The full suite passes locally: 1565 passed, 4 skipped, 4 risky; Pint is clean.
  • The Kotlin renderer compiles (:app:compileReleaseKotlin).
  • Both Swift renderers compile in an iOS simulator build.
  • I haven't yet run it on a device or simulator to see the image render.

Happy to add docs for the new prop wherever you'd like them.

🤖 Generated with Claude Code

Top-bar actions could only draw an icon, so apps had no native way to put
the signed-in user's avatar on the profile action, the most common
top-right control in tab apps. `image` takes a URL and draws it as a 28pt
circle in place of the icon: Android uses Coil's AsyncImage (already a core
dependency), iOS uses AsyncImage, matching NativeAsyncImage. The icon is
drawn first and stays visible while the image loads and when it fails, so a
bad URL degrades to today's behavior. Disabled actions fade the image too.
Menu triggers honor it. NavAction gains a fluent ->image(), which treats
null/blank as no image so `->image($user->avatarUrl ?? '')` just works.
Unset keeps today's behavior everywhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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