feat(tray): show remaining balances in green in the tray panel

Color only the balance amount green, like the provider card's quota
line, while the label stays muted and a used-up balance keeps its
warning color.
This commit is contained in:
Jason committed 2026-10-20 21:50:16 +08:00
1 parent 7e97b44f4d
commit c42e3067e9
3 files changed
+61 -1

No files matched your search

+20 -1
View File
@@ -135,6 +135,25 @@ export function formatResetIn(
);
}
/** 只给数值上绿色(「余额 82.10 CNY」里的金额),和主界面 `TONE_TEXT.normal` 同色。 */
function EmphasizedText({
text,
emphasis,
}: {
text: string;
emphasis: string | null;
}) {
const at = emphasis ? text.lastIndexOf(emphasis) : -1;
if (!emphasis || at < 0) return <>{text}</>;
return (
<>
{text.slice(0, at)}
<span className="text-green-600 dark:text-green-400">{emphasis}</span>
{text.slice(at + emphasis.length)}
</>
);
}
function QuotaBlock({
quota,
texts,
@@ -175,7 +194,7 @@ function QuotaBlock({
line.warn ? "text-amber-500" : "text-muted-foreground",
)}
>
{line.text}
<EmphasizedText text={line.text} emphasis={line.emphasis} />
</span>
{line.left == null ? (
<span />
+2
View File
@@ -38,6 +38,8 @@ export interface PanelQuotaLine {
warn: boolean;
/** 这一档什么时候重置(ISO 8601) */
resetsAt: string | null;
/** `text` 里要上绿色的那段数值(还有余额时的金额) */
emphasis: string | null;
}
export interface PanelQuota {
+39
View File
@@ -163,6 +163,45 @@ describe("TrayPanel window layout", () => {
delete document.documentElement.dataset.layout;
});
it("colors only the balance amount green, like the provider card", async () => {
vi.mocked(trayPanelApi.state).mockResolvedValue({
...state,
apps: [
{
...state.apps[0],
quota: {
lines: [
{
text: "Balance 82.10 CNY",
left: null,
warn: false,
resetsAt: null,
emphasis: "82.10 CNY",
},
{
text: "Balance used up",
left: 0,
warn: true,
resetsAt: null,
emphasis: null,
},
],
note: null,
resetCredits: null,
},
},
],
});
vi.mocked(trayPanelApi.placement).mockResolvedValue(windows);
renderPanel();
const amount = await screen.findByText("82.10 CNY");
expect(amount).toHaveClass("text-green-600");
expect(amount.parentElement).toHaveTextContent("Balance 82.10 CNY");
expect(amount.parentElement).not.toHaveClass("text-green-600");
expect(screen.getByText("Balance used up")).toHaveClass("text-amber-500");
});
it("clips the first Windows frame without waiting for animation frames in a hidden window", async () => {
vi.mocked(trayPanelApi.placement).mockResolvedValue(windows);
renderPanel();