feat: replace wallet balance toggle with Lightning / On-chain tabs (#2306)

* feat: prototype balance switcher variants on wallet screen

Adds two switcher styles (icon-only segmented control and tab-style
control) alongside the original "Spending Balance ⇅" toggle, with a
floating dev-only variant toggle for side-by-side comparison.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: replace balance toggle with tab switcher

Drops the icon-only and original variants plus the dev preview toggle,
keeping just the Lightning / On-chain tab switcher and a wider gap to
the balance underneath.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: use shadcn Tabs primitive for balance switcher

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: type balance switcher icons with LucideIcon

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: only render balance switcher when on-chain wallet exists

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: note lucide-react *Icon suffix convention in AGENTS.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
This commit is contained in:
René Aaron 2026-05-07 08:01:17 +02:00 committed by GitHub
parent 15be274f24
commit bb9249c767
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 42 additions and 37 deletions

View file

@ -180,6 +180,7 @@ Code under `frontend/platform_specific/http/` and `frontend/platform_specific/wa
- **Keep layouts flat** — avoid nesting cards inside cards or wrapping elements in unnecessary bordered containers. Prefer clear, flat visual hierarchy.
- **Match existing spacing patterns** — before adding new components, check sibling components for consistent padding, margins, and gaps. Ensure sibling elements have equal dimensions where appropriate.
- **Write copy from the user's perspective** — Alby Hub IS the wallet; don't explain what a lightning wallet is or tell the user to "connect to a wallet" when they're already inside one. Keep UI copy concise and use the product's own vocabulary (sats, connections, apps).
- **Use the `*Icon` suffix when importing lucide-react icons** (e.g., `ZapIcon`, `BitcoinIcon`, `ArrowDownIcon`) — both forms are valid lucide exports, but this codebase consistently uses the suffixed alias. Don't mix styles.
- Strict TypeScript — no `any` types.
- Functional components with hooks only.
- SWR for server state; Zustand for client state (stores in `frontend/src/state/`).

View file

@ -0,0 +1,32 @@
import { BitcoinIcon, type LucideIcon, ZapIcon } from "lucide-react";
import { Link } from "react-router";
import { Tabs, TabsList, TabsTrigger } from "src/components/ui/tabs";
type Mode = "lightning" | "onchain";
const items: { mode: Mode; to: string; label: string; Icon: LucideIcon }[] = [
{ mode: "lightning", to: "/wallet", label: "Lightning", Icon: ZapIcon },
{
mode: "onchain",
to: "/wallet/onchain",
label: "On-chain",
Icon: BitcoinIcon,
},
];
export function BalanceSwitcher({ active }: { active: Mode }) {
return (
<Tabs value={active}>
<TabsList>
{items.map(({ mode, to, label, Icon }) => (
<TabsTrigger key={mode} value={mode} asChild>
<Link to={to}>
<Icon />
{label}
</Link>
</TabsTrigger>
))}
</TabsList>
</Tabs>
);
}

View file

@ -1,10 +1,5 @@
import {
AlertTriangleIcon,
ArrowDownIcon,
ArrowDownUpIcon,
ArrowUpIcon,
} from "lucide-react";
import { Link, useNavigate } from "react-router";
import { AlertTriangleIcon, ArrowDownIcon, ArrowUpIcon } from "lucide-react";
import { Link } from "react-router";
import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
import FormattedFiatAmount from "src/components/FormattedFiatAmount";
import LowReceivingCapacityAlert from "src/components/LowReceivingCapacityAlert";
@ -15,6 +10,7 @@ import {
AlertTitle,
} from "src/components/ui/alert.tsx";
import { LinkButton } from "src/components/ui/custom/link-button";
import { BalanceSwitcher } from "src/components/wallet/BalanceSwitcher";
import { useBalances } from "src/hooks/useBalances";
import { useChannels } from "src/hooks/useChannels";
import { useInfo } from "src/hooks/useInfo";
@ -23,7 +19,6 @@ export default function Lightning() {
const { hasChannelManagement } = useInfo();
const { data: balances } = useBalances(true);
const { data: channels } = useChannels();
const navigate = useNavigate();
if (!balances) {
return null;
@ -76,22 +71,8 @@ export default function Lightning() {
</Alert>
)}
<div className="flex w-full flex-col items-center gap-8 pt-12 pb-16 text-center">
<div className="flex flex-col items-center gap-4">
{hasChannelManagement ? (
<button
type="button"
onClick={() => navigate("/wallet/onchain")}
aria-label="Toggle balance mode, currently Lightning Balance"
className="inline-flex items-center justify-center gap-1 text-xs font-medium leading-none uppercase text-muted-foreground transition-colors hover:text-foreground"
>
Lightning Balance
<ArrowDownUpIcon aria-hidden className="size-3 shrink-0" />
</button>
) : (
<span className="text-xs font-medium leading-none uppercase text-muted-foreground">
Lightning Balance
</span>
)}
<div className="flex flex-col items-center gap-8">
{hasChannelManagement && <BalanceSwitcher active="lightning" />}
<div className="flex flex-col items-center gap-3">
<div className="text-5xl md:text-6xl font-medium balance sensitive slashed-zero leading-none">
<FormattedBitcoinAmount

View file

@ -1,14 +1,13 @@
import { ArrowDownIcon, ArrowDownUpIcon, ArrowUpIcon } from "lucide-react";
import { useNavigate } from "react-router";
import { ArrowDownIcon, ArrowUpIcon } from "lucide-react";
import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
import FormattedFiatAmount from "src/components/FormattedFiatAmount";
import { OnchainTransactionsList } from "src/components/OnchainTransactionsList";
import { LinkButton } from "src/components/ui/custom/link-button";
import { BalanceSwitcher } from "src/components/wallet/BalanceSwitcher";
import { useBalances } from "src/hooks/useBalances";
export default function Onchain() {
const { data: balances } = useBalances(true);
const navigate = useNavigate();
if (!balances) {
return null;
@ -17,16 +16,8 @@ export default function Onchain() {
return (
<>
<div className="flex w-full flex-col items-center gap-8 pt-12 pb-16 text-center">
<div className="flex flex-col items-center gap-4">
<button
type="button"
onClick={() => navigate("/wallet")}
aria-label="Toggle balance mode, currently On-chain Balance"
className="inline-flex items-center justify-center gap-1 text-xs font-medium leading-none uppercase text-muted-foreground transition-colors hover:text-foreground"
>
On-chain Balance
<ArrowDownUpIcon aria-hidden className="size-3 shrink-0" />
</button>
<div className="flex flex-col items-center gap-8">
<BalanceSwitcher active="onchain" />
<div className="flex flex-col items-center gap-3">
<div className="text-5xl md:text-6xl font-medium balance sensitive slashed-zero leading-none">
<FormattedBitcoinAmount