Skip to content

Commit

Permalink
Account button tweaks (#843)
Browse files Browse the repository at this point in the history
- Fixed width (prevents jumps).
- Add a new “connecting” status, which only shows after a 500ms delay
  (making it invisible in most cases).
- UI kit: add a new ShowAfter component.
- Cut the text when too wide (ENS name resolution).

Also:

- Remove unnecessary hook dependencies in services/Ethereum.tsx.
  • Loading branch information
bpierre authored Feb 18, 2025
1 parent b69b6a3 commit b0a4038
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 74 deletions.
129 changes: 69 additions & 60 deletions frontend/app/src/comps/TopBar/AccountButton.tsx
Original file line number Diff line number Diff line change
@@ -1,98 +1,107 @@
import type { ReactNode } from "react";

import content from "@/src/content";
import { useDemoMode } from "@/src/demo-mode";
import { useAccount } from "@/src/services/Ethereum";
import { css } from "@/styled-system/css";
import { Button, IconAccount, shortenAddress } from "@liquity2/uikit";
import { Button, IconAccount, shortenAddress, ShowAfter } from "@liquity2/uikit";
import { ConnectButton } from "@rainbow-me/rainbowkit";
import { match, P } from "ts-pattern";
import { MenuItem } from "./MenuItem";

type ButtonData = {
label: string;
onClick: () => void;
title?: string;
variant?: "normal" | "connected";
};

export function AccountButton() {
const account = useAccount();
const demoMode = useDemoMode();

return demoMode.enabled ? <ButtonDemoMode /> : (
if (demoMode.enabled) {
return <ButtonDemoMode />;
}

return (
<ConnectButton.Custom>
{({ chain, openChainModal, openConnectModal }) => {
const button = match({ account, chain })
.returnType<ButtonData>()
return match({ account, chain })
// wrong network
.with(
// wrong network
{ chain: { unsupported: true } },
() => ({
label: content.accountButton.wrongNetwork,
onClick: openChainModal,
}),
() => (
<Button
mode="primary"
label={content.accountButton.wrongNetwork}
onClick={openChainModal}
/>
),
)
// connected
.with(
// connected
{ account: { address: P.nonNullable } },
({ account }) => ({
label: account.ensName ?? shortenAddress(account.address, 3),
onClick: account.disconnect,
title: account.address,
variant: "connected",
}),
({ account }) => (
<ButtonConnected
label={account.ensName ?? shortenAddress(account.address, 3)}
onClick={account.disconnect}
title={account.address}
/>
),
)
.otherwise(
// disconnected / not ready
() => ({
label: content.accountButton.connectAccount,
onClick: openConnectModal,
}),
);

return button.variant === "connected"
? <ButtonConnected button={button} />
: <Button mode="primary" {...button} />;
// connecting
.with(
{ account: { status: "connecting" } },
() => (
<div>
<ShowAfter delay={500}>
<ButtonConnected
label="connecting…"
onClick={() => {
account.disconnect();
}}
/>
</ShowAfter>
</div>
),
)
// disconnected
.otherwise(() => (
<Button
mode="primary"
label={content.accountButton.connectAccount}
onClick={openConnectModal}
/>
));
}}
</ConnectButton.Custom>
);
}

function ButtonDemoMode() {
const { account, updateAccountConnected } = useDemoMode();
return (
account.isConnected
? (
<ButtonConnected
button={{
label: "demo.eth",
onClick: () => {
updateAccountConnected(false);
},
}}
/>
)
: (
<Button
mode="primary"
label="Connect"
onClick={() => {
updateAccountConnected(true);
}}
/>
)
);
const onClick = () => {
updateAccountConnected(!account.isConnected);
};
return account.isConnected
? <ButtonConnected label="demo.eth" onClick={onClick} />
: <Button mode="primary" label="Connect" onClick={onClick} />;
}

function ButtonConnected({ button }: { button: ButtonData }) {
function ButtonConnected({
label,
onClick,
title,
}: {
label: ReactNode;
onClick: () => void;
title?: string;
}) {
return (
<button
onClick={button.onClick}
title={button.title}
onClick={onClick}
title={title}
className={css({
display: "flex",
height: "100%",
maxWidth: 140,
padding: 0,
whiteSpace: "nowrap",
textAlign: "center",
_active: {
translate: "0 1px",
},
Expand All @@ -104,7 +113,7 @@ function ButtonConnected({ button }: { button: ButtonData }) {
>
<MenuItem
icon={<IconAccount />}
label={button.label}
label={label}
/>
</button>
);
Expand Down
26 changes: 24 additions & 2 deletions frontend/app/src/comps/TopBar/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function MenuItem({
selected,
}: {
icon: ReactNode;
label: string;
label: ReactNode;
selected?: boolean;
}) {
return (
Expand All @@ -19,10 +19,13 @@ export function MenuItem({
display: "flex",
alignItems: "center",
gap: 12,
width: "100%",
height: "100%",
color: "content",
cursor: "pointer",
userSelect: "none",
overflow: "hidden",
textOverflow: "ellipsis",
})}
style={{
color: token(`colors.${selected ? "selected" : "interactive"}`),
Expand All @@ -38,7 +41,26 @@ export function MenuItem({
>
{icon}
</div>
{label}
<div
className={css({
flexShrink: 1,
flexGrow: 1,
overflow: "hidden",
display: "flex",
alignItems: "center",
height: "100%",
})}
>
<div
className={css({
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
})}
>
{label}
</div>
</div>
</div>
);
}
11 changes: 10 additions & 1 deletion frontend/app/src/comps/TopBar/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,16 @@ export function TopBar() {
</div>
</Link>
<Menu menuItems={menuItems} />
<AccountButton />
<div
className={css({
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
width: 140,
})}
>
<AccountButton />
</div>
</div>
</div>
);
Expand Down
12 changes: 1 addition & 11 deletions frontend/app/src/services/Ethereum.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,17 +196,7 @@ export function useWagmiConfig() {
},
ssr: true,
});
}, [
CHAIN_BLOCK_EXPLORER,
CHAIN_CONTRACT_ENS_REGISTRY,
CHAIN_CONTRACT_ENS_RESOLVER,
CHAIN_CONTRACT_MULTICALL,
CHAIN_CURRENCY,
CHAIN_ID,
CHAIN_NAME,
CHAIN_RPC_URL,
WALLET_CONNECT_PROJECT_ID,
]);
}, []);
}

function createChain({
Expand Down
24 changes: 24 additions & 0 deletions frontend/uikit/src/ShowAfter/ShowAfter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ReactNode } from "react";

import { useEffect, useState } from "react";

export function ShowAfter({
children,
delay,
}: {
children: ReactNode;
delay: number;
}) {
const [show, setShow] = useState(false);

useEffect(() => {
const timeout = setTimeout(() => {
setShow(true);
}, delay);
return () => {
clearTimeout(timeout);
};
}, [delay]);

return show ? children : null;
}
1 change: 1 addition & 0 deletions frontend/uikit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export { PillButton } from "./PillButton/PillButton";
export { Radio } from "./Radio/Radio";
export { RadioGroup, useRadioGroup } from "./Radio/RadioGroup";
export { Root, RootEntryPoint } from "./Root/Root";
export { ShowAfter } from "./ShowAfter/ShowAfter";
export { Slider } from "./Slider/Slider";
export { StatusDot } from "./StatusDot/StatusDot";
export { Tabs } from "./Tabs/Tabs";
Expand Down

0 comments on commit b0a4038

Please sign in to comment.