fix(panel): LuckPerms 页在手机上玩家栏按内容收高、列表在卡内滚动,选中玩家的面板落在首屏

This commit is contained in:
Lemon-miaow committed 2026-09-27 16:08:21 +08:00
1 parent 7b28434068
commit de5fbb7283
3 files changed
+43 -4

No files matched your search

+21
View File
@@ -20,6 +20,7 @@ for (const [account, path] of [
["linked", "/account"], ["linked", "/account"],
["owner", "/servers"], ["owner", "/servers"],
["owner", "/admin/users"], ["owner", "/admin/users"],
["owner", "/servers/survival/luckperms"],
] as const) { ] as const) {
test(`${path} fits a 375px screen for ${account}`, async ({ page, signIn }) => { test(`${path} fits a 375px screen for ${account}`, async ({ page, signIn }) => {
await signIn(account); await signIn(account);
@@ -29,3 +30,23 @@ for (const [account, path] of [
await expectFitsScreen(page); await expectFitsScreen(page);
}); });
} }
test("the LuckPerms player list leaves the rest of the page on the first screen", async ({ page, signIn }) => {
await signIn("owner");
await page.goto("/servers/survival/luckperms");
const list = page.getByRole("button", { name: "mock_player", exact: true }).locator("..");
await expect(list).toBeVisible();
// Twelve players online: the list scrolls inside its card, so the prompt
// under it shows without scrolling the page.
await expect(page.getByText(t("servers:luckperms_select_player_prompt"))).toBeInViewport({ ratio: 1 });
const { scrollHeight, clientHeight } = await list.evaluate((el) => ({
scrollHeight: el.scrollHeight,
clientHeight: el.clientHeight,
}));
expect(scrollHeight).toBeGreaterThan(clientHeight);
// The last one is still a tap away.
await page.getByRole("button", { name: "Herobrine", exact: true }).click();
await expect(page.getByRole("textbox", { name: t("servers:luckperms_player_lookup") })).toHaveValue("Herobrine");
});
+16
View File
@@ -73,3 +73,19 @@ test("a player's pages load on demand and never pull in the admin pages", async
await expect.poll(() => fetched.some((p) => ACCOUNT_PAGE.test(p))).toBe(true); await expect.poll(() => fetched.some((p) => ACCOUNT_PAGE.test(p))).toBe(true);
expect(fetched.filter((p) => ADMIN_PAGE.test(p))).toEqual([]); expect(fetched.filter((p) => ADMIN_PAGE.test(p))).toEqual([]);
}); });
test("on a wide screen the LuckPerms player list runs down its column", async ({ page, signIn }) => {
await signIn("owner");
await page.goto("/servers/survival/luckperms");
const list = page.getByRole("button", { name: "mock_player", exact: true }).locator("..");
await expect(list).toBeVisible();
const listBox = (await list.boundingBox())!;
const cardBox = (await list.locator("..").boundingBox())!;
expect(cardBox.height).toBeGreaterThanOrEqual(500);
expect(Math.abs(listBox.y + listBox.height - (cardBox.y + cardBox.height))).toBeLessThanOrEqual(2);
// A taller screen grows the card to its cap, whatever the roster holds.
await page.setViewportSize({ width: 1280, height: 1000 });
await expect.poll(async () => (await list.locator("..").boundingBox())!.height).toBe(700);
});
+6 -4
View File
@@ -397,8 +397,10 @@ export function ServerLuckPerms() {
/> />
) : ( ) : (
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6 items-start"> <div className="grid grid-cols-1 lg:grid-cols-4 gap-6 items-start">
{/* Left Directory Sidebar: Single card for Search + Online Players */} {/* Left Directory Sidebar: Single card for Search + Online Players. On a
<Card className="lg:col-span-1 flex flex-col h-[calc(100vh-14rem)] lg:min-h-[500px] max-h-[700px] border-border bg-card/60 backdrop-blur-sm overflow-hidden"> wide screen it fills the column; on a phone it fits its content and the
list scrolls inside it, so the player's panel below is on the first screen. */}
<Card className="lg:col-span-1 flex flex-col lg:h-[calc(100vh-14rem)] lg:min-h-[500px] lg:max-h-[700px] border-border bg-card/60 backdrop-blur-sm overflow-hidden">
<CardHeader className="p-4 pb-2 border-b border-border/50 shrink-0"> <CardHeader className="p-4 pb-2 border-b border-border/50 shrink-0">
<CardTitle className="text-xs font-bold uppercase tracking-wider text-muted-foreground flex items-center gap-2"> <CardTitle className="text-xs font-bold uppercase tracking-wider text-muted-foreground flex items-center gap-2">
<Users className="h-4 w-4 text-primary" /> <Users className="h-4 w-4 text-primary" />
@@ -424,7 +426,7 @@ export function ServerLuckPerms() {
{t("access_online_title")} {t("access_online_title")}
</span> </span>
</div> </div>
<CardContent className="p-2 overflow-y-auto flex-1 space-y-0.5 min-h-0"> <CardContent className="p-2 overflow-y-auto max-h-52 lg:max-h-none flex-1 space-y-0.5 min-h-0">
{playersLoading && onlinePlayers.length === 0 ? ( {playersLoading && onlinePlayers.length === 0 ? (
<div className="flex justify-center py-6"> <div className="flex justify-center py-6">
<Loader2 className="h-5 w-5 animate-spin text-primary" /> <Loader2 className="h-5 w-5 animate-spin text-primary" />
@@ -463,7 +465,7 @@ export function ServerLuckPerms() {
{/* Right Main Panel */} {/* Right Main Panel */}
<div className="lg:col-span-3 space-y-6"> <div className="lg:col-span-3 space-y-6">
{!selectedPlayer ? ( {!selectedPlayer ? (
<Card className="flex flex-col items-center justify-center py-24 text-center border-dashed border-border bg-card/20 min-h-[500px]"> <Card className="flex flex-col items-center justify-center py-8 lg:py-24 text-center border-dashed border-border bg-card/20 lg:min-h-[500px]">
<Shield className="h-14 w-14 text-muted-foreground/15 mb-4 animate-pulse" /> <Shield className="h-14 w-14 text-muted-foreground/15 mb-4 animate-pulse" />
<p className="text-sm text-muted-foreground font-medium max-w-xs leading-normal"> <p className="text-sm text-muted-foreground font-medium max-w-xs leading-normal">
{t("luckperms_select_player_prompt")} {t("luckperms_select_player_prompt")}