Skip to content

feat(widget): add redesigned amount input cards#776

Open
effie-ms wants to merge 9 commits into
mainfrom
feat/amount-input-cards
Open

feat(widget): add redesigned amount input cards#776
effie-ms wants to merge 9 commits into
mainfrom
feat/amount-input-cards

Conversation

@effie-ms

@effie-ms effie-ms commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Which Linear task is linked to this PR?

EMB-452

Why was it implemented this way?

This replaces the legacy amount input (AmountInput + inline price helper) with a redesigned Send/Receive card pair (AmountInputCardPair), rendered from MainPage for the standard swap/bridge flow (every non-custom mode, plus custom deposit). The custom-contract flow path is unchanged and still renders SelectChainAndToken. The legacy AmountInput and its sub-components are removed.

  • Send card (SendAmountCard): large editable input with adaptive font sizing, token pill selector, percentage chips, fiat/token input toggle, and wallet balance display. Supports price (fiat) input mode via the input-mode store. Percentage chips (25/50/75/Max) are shown only when a wallet is connected; Max deducts the recommended gas reserve on native tokens.
  • Receive card (ReceiveAmountCard): read-only amount from the best route, with a fiat/token toggle and token pill selector. The footer shows the route's price impact (computed via getPriceImpact, identical to RouteDetails) with an info tooltip. The amount and footer values fall back to skeletons while routes are fetching.
  • Token pill (TokenPillButton): the selected state is a neutral chip-style pill matching the percentage pills; the empty "select" state is derived from the app's primary Button, so its background/hover/disabled treatment stays in sync with the theme.
  • Swap button (SwapButton): vertical swap arrow between the cards that reverses the from/to chain + token (clearing the amount) and runs the toAddress auto-populate / reset. Hidden when the mode is refuel (no receive token).
  • Avatar (AvatarBadgedDefault): gains optional avatarSize / badgeSize props so the token pill can size its chain avatar and badge.

New components

  • AmountInputCard/SendAmountCard, ReceiveAmountCard, AmountInputCardPair, PercentageChips, BalanceDisplay, FiatValueToggle, shared styles
  • SwapButton/ — vertical swap arrow with overlapping-card design
  • TokenPillButton/ — pill-shaped token selector (chip-style when selected, primary-button call to action when empty)

Removed

  • AmountInput/AmountInput, AmountInputStartAdornment, PriceFormHelperText and their styles

Visual showcase (Screenshots or Videos)

Screen.Recording.2026-06-22.at.10.36.40.mov

Checklist before requesting a review

  • I have performed a self-review and testing of my code.
  • This pull request is focused and addresses a single problem.
  • If this PR modifies the Widget API or adds new features that require documentation, I have updated the documentation in the public-docs repository.

@effie-ms effie-ms marked this pull request as draft June 11, 2026 11:39
@changeset-bot

changeset-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e066384

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@lifi/widget Minor
nft-checkout Patch
tanstack-router-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@effie-ms effie-ms force-pushed the feat/amount-input-cards branch from 9c39a0a to 6ae7bef Compare June 22, 2026 08:27
@effie-ms effie-ms changed the base branch from feat/header-tabs-store to main June 22, 2026 08:27
@effie-ms effie-ms marked this pull request as ready for review June 22, 2026 08:44
@effie-ms effie-ms added the Agent Review Request triggers QA Agent Zeus label Jun 22, 2026
@github-actions github-actions Bot added QA AI Reviewing and removed Agent Review Request triggers QA Agent Zeus labels Jun 22, 2026
@effie-ms effie-ms changed the title feat(widget): add redesigned amount input cards for jumper modes feat(widget): add redesigned amount input cards Jun 22, 2026
@lifi-qa-agent

lifi-qa-agent Bot commented Jun 22, 2026

Copy link
Copy Markdown

🔍 QA Review — EMB-452

🔗 Linear Ticket · Pull Request #776
⚠️ New commits pushed after last review — re-analysing post-review changes.

🧠 What this ticket does

Replaces the legacy AmountInput component (and all its sub-components) with a redesigned AmountInputCardPair — a stacked Send/Receive card pair that is now the default amount-entry UI for every non-custom mode plus the custom deposit mode. The custom-contract (non-deposit) flow is untouched. The redesign introduces seven new components: SendAmountCard, ReceiveAmountCard, PercentageChips, BalanceDisplay, FiatValueToggle, SwapButton, and TokenPillButton. Each card shows a large auto-scaling amount input/display, a token pill for navigation, and a fiat/token footer toggle. The swap arrow between cards reverses from/to selections, and the receive card shows a price-impact tooltip sourced from route data.

Verdict: Pass — All 8 previously flagged items are resolved. Items #2#8 are fixed in code; item #1 (unit tests) is accepted with a valid justification (no component rendering test infrastructure exists in the widget package). All e2e CI suites pass (158 playground tests, smoke passing).


📋 Ticket Summary

Add Send/Receive amount input cards
Replaces the legacy AmountInput with AmountInputCardPair across the standard swap/bridge flow for every non-custom mode plus custom deposit; the custom-contract flow remains unchanged.

Acceptance Criteria:

  1. Card pair renders for the standard flow — all non-custom modes plus custom deposit. — ✅ Met
  2. Custom-contract (non-deposit) flow renders SelectChainAndToken unchanged. — ✅ Met
  3. Token pill navigates to the picker and displays the selected token. — ✅ Met
  4. Swap button reverses from/to tokens and clears the amount; hidden in refuel mode. — ⚠️ Partial (button is visibility: hidden in refuel mode, retaining layout footprint — preserving overlap design is the likely intent)
  5. Percentage chips (25/50/75/Max) set correct amounts with gas deduction on native tokens; hidden when no wallet is connected. — ✅ Met (also now guards disabledUI.fromAmount)
  6. Fiat/token toggle works on both Send and Receive cards. — ✅ Met
  7. Price (fiat) input mode works on the Send card. — ✅ Met
  8. Receive card shows route price impact with an info tooltip; amount and footer show skeletons while routes are fetching. — ✅ Met
  9. Large numbers auto-scale font size without changing card height. — ✅ Met
  10. Dark mode renders correctly. — ✅ Met (all theming via theme.vars.*)

🔎 Resolution Status — All 8 Items

# Severity Type Issue Status
1 🟠 High Test gap No unit tests for any of the eight new components ✅ Accepted — no DOM test stack exists; interactive paths covered by e2e
2 🟠 High E2e regression fromButton / toButton / reverseTokensButton selectors broken ✅ Fixed — dual selectors + data-testid added; 158 e2e tests passing
3 🟠 High Release process No changeset for @lifi/widget ✅ Fixed — .changeset/emb-452-amount-input-cards.md (minor) added
4 🟡 Medium Accessibility SwapButton had no accessible name ✅ Fixed — aria-label={t('button.swap')} added
5 🟡 Medium Accessibility Fiat toggle ToggleButton had no accessible name ✅ Fixed — aria-label added to both FiatValueToggle.tsx and ReceiveAmountCard.tsx
6 🟡 Medium Styling Magic zIndex: 1110 with no named constant ✅ Fixed — const swapButtonZIndex = 1110 with explanatory comment
7 🟢 Low Styling consistency theme.shape used instead of theme.vars.shape ✅ Fixed — theme.vars.shape.borderRadiusSecondary in TokenPillButton.style.tsx
8 🟢 Low Dead code inputMode['to'] set but not read ✅ Fixed — ReceiveAmountCard now reads from useInputModeStore for 'to' key; toggle state survives navigation

🗒️ Minor Notes (non-blocking)

  • Fiat toggle aria-label i18n: Both FiatValueToggle.tsx and ReceiveAmountCard.tsx use the hardcoded string 'Toggle fiat value' rather than t('button.toggleFiatValue'). The accessibility gap is closed, but the label won't be translated for non-English locales. Consider adding a translation key in a follow-up.
  • PR title: Missing [EMB-452] ticket prefix per project convention. Not blocking — ticket is linked in the PR description.

🧪 Test Coverage (post-fix)

Layer Score Notes
Unit (Vitest) None No component rendering infrastructure exists (jsdom/@testing-library/react absent from package). All existing widget tests are .test.ts (pure functions/stores). Accepted as a separate future effort.
E2e (Playwright) ✅ Good WidgetView.ts updated with dual selectors (data-testid-first, legacy role fallback). Smoke: 4/4 passing. Playground: 158/158 passing.

🔗 Downstream Impact

Related to: EMB-451 — "Add support for tabs passed from Simple and Advanced Jumper modes". No blocking gaps — the card pair's mode-aware showSwapButton logic directly supports the refuel/swap/bridge mode distinction EMB-451 requires.


QA Agent — 2026-06-22 · Re-review

@lifi-qa-agent lifi-qa-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on 8 items — each requires either a code fix or an explicit acceptance comment with justification before this review is considered complete.

# Severity Type Issue / File
1 🟠 High Test gap No unit tests for any of the eight new components
2 🟠 High E2e regression fromButton / toButton / reverseTokensButton e2e selectors broken and not updated
3 🟠 High Release process No changeset for publishable @lifi/widget package (CLAUDE.md rule)
4 🟡 Medium Accessibility SwapButton renders as a button with no accessible name
5 🟡 Medium Accessibility Fiat toggle (ToggleButton) in ReceiveAmountCard has no accessible name when interactive
6 🟡 Medium Styling Magic zIndex: 1110 in SwapButton.style.tsx — no named constant
7 🟢 Low Styling consistency TokenPillButton.style.tsx uses theme.shape instead of theme.vars.shape
8 🟢 Low Dead code inputMode['to'] state is set but never read in the new components

1. [High] No unit tests for any of the eight new components
AmountInputCardPair, SendAmountCard, ReceiveAmountCard, PercentageChips, BalanceDisplay, FiatValueToggle, TokenPillButton, and SwapButton are all new exports with zero test files. Required coverage per component is detailed in the full QA report comment. Create co-located test files under packages/widget/src/components/AmountInputCard/, SwapButton/, and TokenPillButton/.

2. [High] E2e selectors broken — fromButton / toButton / reverseTokensButton
WidgetView.ts selectors that matched the legacy AmountInput UI (role-by-name From/To, data-testid="widget-reverse-tokens-button") no longer match any element in the new AmountInputCardPair layout. CI e2e suites (smoke.spec.ts, widget-smoke.spec.ts, settings.mode-variant.spec.ts) will break. Update selectors to match TokenPillButton and add data-testid="widget-swap-tokens-button" to SwapIconCard.

3. [High] Missing changeset for @lifi/widget
CLAUDE.md requires a .changeset/*.md for every publishable package change. changeset-bot has already flagged this. Run pnpm changeset, select @lifi/widget, choose minor, and add a summary.

4. [Medium] SwapButton missing aria-label
<SwapIconCard component="button"> contains only an aria-hidden icon. Add aria-label={t('button.swap')} to the button element in SwapButton.tsx.

5. [Medium] Fiat toggle ToggleButton in ReceiveAmountCard missing aria-label
When canToggle is true, the ToggleButton (ButtonBase) has no accessible name. Add aria-label={t('button.toggleFiatValue')} or equivalent in ReceiveAmountCard.tsx.

6. [Medium] Magic zIndex: 1110 in SwapButton.style.tsx
Hardcoded z-index value with no named constant or comment. Extract to a named constant or use theme.zIndex.appBar + 10 with documentation in SwapButton.style.tsx line 10.

7. [Low] theme.shape instead of theme.vars.shape in TokenPillButton.style.tsx
pillLayout uses theme.shape.borderRadiusSecondary inconsistently with the rest of the PR. Change to theme.vars.shape.borderRadiusSecondary for CSS variable consistency.

8. [Low] inputMode['to'] dead state in useInputModeStore
ReceiveAmountCard uses local useState for its fiat toggle, leaving the 'to' key in useInputModeStore orphaned. Either wire ReceiveAmountCard to the store or remove the 'to' path from the store type.

💡 Once you've addressed the items above, re-apply the "Agent Review Request" label to trigger an automated re-review.

@effie-ms

Copy link
Copy Markdown
Contributor Author

Review feedback addressed

Pushed in 23c71d9b:

  • LF: wallet support #2 E2e selectors — added data-testid="widget-{from,to}-token-button" to TokenPillButton and data-testid="widget-swap-tokens-button" to SwapButton; updated WidgetView.ts (fromButton/toButton/reverseTokensButton) to match. All affected specs run the standard flow, so the pills are always present.
  • feat: get and display routes #3 Changeset — added .changeset/emb-452-amount-input-cards.md (@lifi/widget minor).
  • LF: testing deployment #4 SwapButton a11yaria-label={t('button.swap')} (reused existing key).
  • LF-552: wallet bugfix #5 Fiat toggle a11yaria-label on the ToggleButton in both ReceiveAmountCard and FiatValueToggle when interactive.
  • put wallet management into own package #6 Magic z-index — extracted 1110 to a documented swapButtonZIndex constant.
  • feat: blockwallet support #7 theme.shape — changed to theme.vars.shape.borderRadiusSecondary in TokenPillButton.style.tsx.
  • feat: deactivate metamask provider check #8 Dead inputMode['to'] — wired ReceiveAmountCard to useInputModeStore for 'to', so the key is now consumed (toggle state also survives navigation).
  • Extra (regression)PercentageChips now also hides when disabledUI.fromAmount is set, so the chips can't set a disabled amount field.

#1 Unit tests — not added (justification)

The widget package has no component-test infrastructure: no jsdom/happy-dom, no @testing-library/react, no vitest config, and zero .tsx tests — the existing suite is node-only pure-function/store tests. Adding rendering tests for these eight provider/hook-dependent components would require standing up a whole DOM test stack and provider render harness, which is out of scope for this PR. The interactive paths are covered by the updated e2e selectors above. Component-level unit tests should be a separate, dedicated effort if the team wants to introduce that layer.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

✅ E2E Dev Smoke — passing

Check Result
Dev server start (pnpm dev) ✅ started
Smoke tests ✅ passed

4 passed · 0 failed · 0 skipped · 21s

View run

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

E2E Examples — all passed

All examples passed in the latest run.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

E2E Playground results

passed  158 passed

Details

stats  158 tests across 10 suites
duration  2 minutes, 19 seconds
commit  e066384

📥 Download full HTML report (open the run → Artifacts → playwright-report)

@lifi-qa-agent lifi-qa-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ QA Pass — all 8 previously flagged items resolved. E2e CI 158/158 passing. Approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant