feat(portal-shell): ghost-style Sign-in button with log-in icon #189
Reference in New Issue
Block a user
Delete Branch "feat/portal-shell-sign-in-icon-ghost"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The anonymous "Sign in" CTA in
portal-shell's header was a filled brand-primary block sitting next to two round icon-only buttons (Notifications, Help). The contrast was off — the filled rectangle visually dominated the row even though it's the third action in the strip. This PR turns it into a ghost-style button (no fill, no border) with alog-inicon ahead of the label, matching the quiet posture of its neighbours while still reading as the primary CTA for unauthenticated users.What lands
apps/portal-shell/src/app/components/header/header.html— the anonymous-state button:bg-brand-primary-500(filled)bg-transparent(ghost)text-whitetext-brand-primary-500(-300in dark)bg-brand-primary-50tint +text-brand-primary-600<lib-icon name="log-in" [size]="16" aria-hidden="true" />ahead of the labelpx-4 gap-2px-3 gap-1.5(slightly tighter, makes room for the icon without growing the chrome)h-11h-11(unchanged — 44 × 44 touch target per ADR-0016 holds)libs/shared/ui/src/lib/icon/icon.ts— adds the missing pair of the existinglog-outicon:LogInfromlucide-angular.'log-in': LogIn,in the alphabetical registry between'layout-dashboard'and'log-out'.Notes for the reviewer
h-11is kept on purpose. The CI a11y gate from ADR-0016 (touch-target check (44×44 min)) is non-negotiable for interactive controls; visually shrinking horizontal padding + reducing visual weight is the right way to "compact" a button without breaking the target rule.aria-hidden="true"on the icon. The adjacent<span>carries the localised label, and the screen-reader contract is "the button announces 'Sign in', not 'log-in icon Sign in'". The icon is decorative reinforcement.<span i18n="@@header.signIn">rather than directly on the button. Required because the button now contains both an icon child and the text — Angular'si18non the button itself would extract the icon's rendered SVG into the translation unit, which is not what translators want to see. Wrapping the text isolates the translation unit cleanly.log-inbelongs inshared-ui, not portal-shell. Even though portal-shell is the only consumer today, the icon registry is by contract the single point of truth for both apps —portal-admin's eventual sign-in surface will use the same icon, so registering it once in the shared lib is the right boundary.Test plan
pnpm nx test portal-shell— green. The existing spec assertsbtn.textContent.trim() === 'Sign in'; the<lib-icon>renders to an SVG (no text content) and the label is now inside a<span>, so the text-trim check still holds.pnpm nx test shared-ui— green. Icon registry's exhaustive-key spec picks up the new entry automatically (it iteratesObject.keys(ICON_REGISTRY)).pnpm nx build portal-shell— clean, no bundle-size deltas worth flagging (log-inis tree-shaken alongside the rest of lucide-angular).pnpm nx lint portal-shell shared-ui— clean.pnpm nx serve portal-shell, signed-out, header visible:<lib-user-menu>(unchanged).errorstate: amber "Can't reach the server" badge (unchanged).brand-primary-300, hover surfaces agray-800fill; still readable.outline-brand-primary-500 outline-offset-2).What's next
portal-adminwill get the samelog-inicon for its own (still skeleton) sign-in surface once that wiring lands — single shared registry means no further change here.