fix: refine settings dialog layout

This commit is contained in:
2026-07-03 01:03:34 -07:00
parent b5e2aa6df1
commit d2404616bc
6 changed files with 76 additions and 11 deletions

View File

@@ -155,8 +155,13 @@ The project Dialog adapter must:
- Override Kumo minimum width so mobile dialogs stay inside `100vw`.
- Keep `transform: none` and `translate: 0 0` on `.teatea-dialog`; use
opacity/scale-only motion if animation is needed.
- Kumo may inject `-translate-x-1/2 -translate-y-1/2` before project classes in the
runtime class list. Do not rely only on Tailwind class order or a plain CSS rule;
the Dialog adapter should enforce `style={{ transform: "none", translate: "0 0" }}`
or an equally strong adapter-owned override.
- Browser validation should assert the panel center is within 2px of the viewport center
on desktop and mobile.
on desktop and mobile and should inspect computed `translate` when a dialog is visibly
offset.
### Static Module Data Contract

View File

@@ -17,10 +17,11 @@ export default async function GlobalSettingsPage(): Promise<React.ReactElement>
}
const settings = await getSystemSettings();
const organizationSettingsHref = getWorkspaceHref(context.organization?.slug, "/settings/organizations");
return (
<div className="flex flex-col gap-5">
<GlobalSettingsClient settings={settings} />
<GlobalSettingsClient organizationSettingsHref={organizationSettingsHref} settings={settings} />
</div>
);
}

View File

@@ -40,12 +40,13 @@ export function Dialog({
>
<KumoDialog
className={cn(
"teatea-dialog fixed inset-0 z-50 m-auto flex h-fit max-h-[min(calc(100svh-1.5rem),52rem)] w-[min(calc(100vw-1.5rem),48rem)] min-w-0 flex-col overflow-hidden rounded-lg border border-kumo-line bg-kumo-base p-0 shadow-xl outline-none ring-1 ring-kumo-line",
"teatea-dialog fixed inset-0 z-50 m-auto flex h-fit max-h-[min(calc(100svh-1.5rem),52rem)] w-[min(calc(100vw-1.5rem),42rem)] min-w-0 flex-col overflow-hidden rounded-lg border border-kumo-line bg-kumo-base p-0 shadow-xl outline-none ring-1 ring-kumo-line",
"data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0",
className,
)}
data-kumo-backdrop-classes={kumoBackdropClassNames}
size="xl"
style={{ transform: "none", translate: "0 0" }}
>
<header className="flex shrink-0 items-start justify-between gap-4 border-b border-kumo-line bg-kumo-base p-5">
<div className="min-w-0">

View File

@@ -1,11 +1,11 @@
"use client";
import { FormEvent, useState } from "react";
import { Globe2, Save, ShieldCheck } from "lucide-react";
import { Building2, Globe2, KeyRound, Save, ShieldCheck, UserPlus } from "lucide-react";
import { useRouter } from "next/navigation";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Button, LinkButton } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
@@ -13,17 +13,22 @@ import type { ApiResult } from "@/modules/core/server/api";
import type { SystemSettings } from "@/modules/core/types";
type GlobalSettingsClientProps = {
organizationSettingsHref: string;
settings: SystemSettings;
};
type SettingsTab = "registration" | "oidc";
type SettingsTab = "registration" | "invitation" | "oidc";
const tabs: Array<{ label: string; value: SettingsTab }> = [
{ label: "注册入口", value: "registration" },
{ label: "邀请注册", value: "invitation" },
{ label: "OIDC 默认配置", value: "oidc" },
];
export function GlobalSettingsClient({ settings }: GlobalSettingsClientProps): React.ReactElement {
export function GlobalSettingsClient({
organizationSettingsHref,
settings,
}: GlobalSettingsClientProps): React.ReactElement {
const router = useRouter();
const [activeTab, setActiveTab] = useState<SettingsTab>("registration");
const [registrationEnabled, setRegistrationEnabled] = useState(settings.registrationEnabled);
@@ -135,6 +140,58 @@ export function GlobalSettingsClient({ settings }: GlobalSettingsClientProps): R
</div>
</CardContent>
</div>
<div hidden={activeTab !== "invitation"} role="tabpanel">
<CardHeader>
<div className="flex items-center gap-2">
<KeyRound className="size-5 text-primary" aria-hidden="true" />
<CardTitle></CardTitle>
</div>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-3 lg:grid-cols-3">
<section className="rounded-md border p-3">
<div className="flex items-center gap-2">
<Globe2 className="size-4 text-primary" aria-hidden="true" />
<h3 className="text-sm font-semibold"></h3>
</div>
<p className="mt-2 text-sm text-muted-foreground">
{registrationEnabled
? "访客可以从注册页创建账号,并选择申请加入开放注册的机构。"
: "普通注册入口关闭;用户需要通过邀请链接或管理员创建账号。"}
</p>
</section>
<section className="rounded-md border p-3">
<div className="flex items-center gap-2">
<UserPlus className="size-4 text-primary" aria-hidden="true" />
<h3 className="text-sm font-semibold"></h3>
</div>
<p className="mt-2 text-sm text-muted-foreground">
`/register?invite=...`
</p>
</section>
<section className="rounded-md border p-3">
<div className="flex items-center gap-2">
<Building2 className="size-4 text-primary" aria-hidden="true" />
<h3 className="text-sm font-semibold"></h3>
</div>
<p className="mt-2 text-sm text-muted-foreground">
</p>
</section>
</div>
<div className="flex flex-col gap-3 rounded-md border bg-secondary/25 p-3 sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0 text-sm">
<p className="font-medium"></p>
<p className="mt-1 text-muted-foreground"></p>
</div>
<LinkButton href={organizationSettingsHref} variant="outline">
<UserPlus className="size-4" aria-hidden="true" />
</LinkButton>
</div>
</CardContent>
</div>
<div hidden={activeTab !== "oidc"} role="tabpanel">
<CardHeader>
<div className="flex items-center gap-2">

View File

@@ -166,7 +166,7 @@ export function OrganizationRowActions({
}
return (
<div className="flex items-center justify-end gap-2">
<div className="flex flex-wrap items-center justify-end gap-2">
<Button onClick={() => setIsEditOpen(true)} size="sm" type="button" variant="outline">
<Pencil className="size-4" aria-hidden="true" />
@@ -195,6 +195,7 @@ export function OrganizationRowActions({
</Button>
)}
<Dialog
className="max-w-lg"
description="编辑机构名称、唯一标识和启停状态。"
onClose={() => setIsEditOpen(false)}
open={isEditOpen}
@@ -223,7 +224,7 @@ export function OrganizationRowActions({
{message}
</p>
) : null}
<div className="flex justify-end gap-2">
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
<Button onClick={() => setIsEditOpen(false)} type="button" variant="outline">
</Button>

View File

@@ -204,7 +204,7 @@ export function RoleManagementClient({ permissions }: RoleManagementClientProps)
</Button>
<Dialog
className="max-w-5xl"
className="w-[min(calc(100vw-1.5rem),64rem)]"
description="角色由系统注册权限点组合而成。"
onClose={closeCreateDialog}
open={isCreateOpen}
@@ -345,7 +345,7 @@ export function RoleRowActions({ permissions, role }: RoleRowActionsProps): Reac
</Button>
)}
<Dialog
className="max-w-5xl"
className="w-[min(calc(100vw-1.5rem),64rem)]"
description="编辑自定义角色的名称、说明、状态和权限点。"
onClose={closeEditDialog}
open={isEditOpen}