feat(portal-shell): clean widget design tokens and fix lint:tokens (P1-6)

516 mechanical replacements across 25 widget files:
- spacing xs/sm/md/lg/xl to numeric 1/2/3/4/6
- text-heading-3 to text-lg font-semibold
- bg-danger to bg-destructive
- border border dedup

Fix .eslintrc.tokens.js to use typescript-eslint parser (was importing
uninstalled @typescript-eslint/parser). lint:tokens now passes.
This commit is contained in:
SpecialX
2026-07-22 15:07:38 +08:00
parent 9358372657
commit a28a6bd6ea
27 changed files with 388 additions and 384 deletions

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* audit-logsadmin / main
@@ -34,8 +34,7 @@ const RESOURCE_OPTIONS = [
"homework",
] as const;
const inputCls =
"rounded-md border border bg-card px-sm py-xs text-sm text-foreground";
const inputCls = "rounded-md border bg-card px-2 py-1 text-sm text-foreground";
export default function AuditLogs(props: PluginProps): React.ReactElement {
const rawPageSize = props.props.pageSize;
@@ -81,10 +80,10 @@ export default function AuditLogs(props: PluginProps): React.ReactElement {
const selectedLog = logs.find((l) => l.id === selectedId);
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<div className="mt-sm flex flex-wrap gap-2">
<div className="mt-2 flex flex-wrap gap-2">
<input
type="text"
value={userIdFilter}
@@ -122,17 +121,17 @@ export default function AuditLogs(props: PluginProps): React.ReactElement {
</div>
{logs.length === 0 ? (
<p className="mt-sm text-sm text-muted-foreground"></p>
<p className="mt-2 text-sm text-muted-foreground"></p>
) : (
<div className="mt-sm overflow-x-auto">
<div className="mt-2 overflow-x-auto">
<table className="w-full text-xs">
<thead>
<tr className="border-b border text-muted-foreground">
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left">IP</th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left">IP</th>
</tr>
</thead>
<tbody>
@@ -146,16 +145,16 @@ export default function AuditLogs(props: PluginProps): React.ReactElement {
isSelected ? "bg-primary-subtle" : "bg-background"
}`}
>
<td className="py-xs text-muted-foreground">
<td className="py-1 text-muted-foreground">
{log.timestamp}
</td>
<td className="py-xs text-foreground">{log.userName}</td>
<td className="py-xs text-foreground">{log.action}</td>
<td className="py-xs text-foreground">
<td className="py-1 text-foreground">{log.userName}</td>
<td className="py-1 text-foreground">{log.action}</td>
<td className="py-1 text-foreground">
{log.resource}
{log.resourceId ? ` / ${log.resourceId}` : ""}
</td>
<td className="py-xs text-muted-foreground">{log.ip}</td>
<td className="py-1 text-muted-foreground">{log.ip}</td>
</tr>
);
})}
@@ -165,15 +164,15 @@ export default function AuditLogs(props: PluginProps): React.ReactElement {
)}
{selectedLog ? (
<div className="mt-sm rounded-md bg-background p-2">
<div className="mt-2 rounded-md bg-background p-2">
<p className="text-xs text-muted-foreground"></p>
<pre className="mt-xs overflow-x-auto whitespace-pre-wrap break-words text-xs text-foreground">
<pre className="mt-1 overflow-x-auto whitespace-pre-wrap break-words text-xs text-foreground">
{selectedLog.details || "(无详细信息)"}
</pre>
</div>
) : null}
<div className="mt-sm flex items-center justify-between text-xs text-muted-foreground">
<div className="mt-2 flex items-center justify-between text-xs text-muted-foreground">
<span>
{total} {offset + 1} - {rangeEnd}
</span>
@@ -182,7 +181,7 @@ export default function AuditLogs(props: PluginProps): React.ReactElement {
type="button"
disabled={!hasPrev}
onClick={() => setOffset(Math.max(0, offset - pageSize))}
className="rounded-md border border bg-card px-sm py-xs text-foreground"
className="rounded-md border bg-card px-2 py-1 text-foreground"
>
</button>
@@ -190,7 +189,7 @@ export default function AuditLogs(props: PluginProps): React.ReactElement {
type="button"
disabled={!hasNext}
onClick={() => setOffset(offset + pageSize)}
className="rounded-md border border bg-card px-sm py-xs text-foreground"
className="rounded-md border bg-card px-2 py-1 text-foreground"
>
</button>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* invitation-codesadmin / main
@@ -30,8 +30,7 @@ const STATUS_LABELS: Record<CodeStatus, string> = {
const ROLE_OPTIONS = ["teacher", "student", "parent"] as const;
const inputCls =
"rounded-md border border bg-card px-sm py-xs text-sm text-foreground";
const inputCls = "rounded-md border bg-card px-2 py-1 text-sm text-foreground";
const labelCls = "text-xs text-muted-foreground";
export default function InvitationCodes(
@@ -98,17 +97,17 @@ export default function InvitationCodes(
const codes = data ?? [];
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
{status.length > 0 ? (
<p className="mt-xs text-xs text-muted-foreground">{status}</p>
<p className="mt-1 text-xs text-muted-foreground">{status}</p>
) : null}
{/* 生成新邀请码 */}
<div className="mt-sm rounded-md bg-background p-2">
<div className="mt-2 rounded-md bg-background p-2">
<p className={labelCls}></p>
<div className="mt-xs flex flex-wrap items-end gap-2">
<div className="space-y-xs">
<div className="mt-1 flex flex-wrap items-end gap-2">
<div className="space-y-1">
<label className={labelCls}></label>
<select
value={newRole}
@@ -122,7 +121,7 @@ export default function InvitationCodes(
))}
</select>
</div>
<div className="space-y-xs">
<div className="space-y-1">
<label className={labelCls}></label>
<input
type="number"
@@ -132,7 +131,7 @@ export default function InvitationCodes(
className={`${inputCls} w-24`}
/>
</div>
<div className="space-y-xs">
<div className="space-y-1">
<label className={labelCls}></label>
<input
type="number"
@@ -145,7 +144,7 @@ export default function InvitationCodes(
<button
type="button"
onClick={handleCreate}
className="rounded-md bg-primary px-md py-xs text-sm text-primary-foreground"
className="rounded-md bg-primary px-3 py-1 text-sm text-primary-foreground"
>
</button>
@@ -153,7 +152,7 @@ export default function InvitationCodes(
</div>
{/* 筛选 */}
<div className="mt-md flex items-center gap-2">
<div className="mt-3 flex items-center gap-2">
<label className={labelCls}></label>
<select
value={statusFilter}
@@ -171,18 +170,18 @@ export default function InvitationCodes(
{/* 邀请码列表 */}
{codes.length === 0 ? (
<p className="mt-sm text-sm text-muted-foreground"></p>
<p className="mt-2 text-sm text-muted-foreground"></p>
) : (
<div className="mt-sm overflow-x-auto">
<div className="mt-2 overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border text-muted-foreground">
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left">使/</th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left">使/</th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
</tr>
</thead>
<tbody>
@@ -191,23 +190,21 @@ export default function InvitationCodes(
const canRevoke = c.status === "active";
return (
<tr key={c.id} className="border-b border">
<td className="py-xs font-mono text-foreground">
{c.code}
</td>
<td className="py-xs text-foreground">{c.role}</td>
<td className="py-xs text-foreground">{c.status}</td>
<td className="py-xs text-foreground">
<td className="py-1 font-mono text-foreground">{c.code}</td>
<td className="py-1 text-foreground">{c.role}</td>
<td className="py-1 text-foreground">{c.status}</td>
<td className="py-1 text-foreground">
{c.usedCount} / {c.maxUses}
</td>
<td className="py-xs text-muted-foreground">
<td className="py-1 text-muted-foreground">
{c.expiresAt}
</td>
<td className="py-xs">
<td className="py-1">
<div className="flex gap-2">
<button
type="button"
onClick={() => handleCopy(c.code)}
className="rounded-md border border bg-card px-sm py-xs text-xs text-foreground"
className="rounded-md border bg-card px-2 py-1 text-xs text-foreground"
>
</button>
@@ -216,7 +213,7 @@ export default function InvitationCodes(
type="button"
disabled={isBusy}
onClick={() => handleRevoke(c.id)}
className="rounded-md bg-danger px-sm py-xs text-xs text-primary-foreground"
className="rounded-md bg-destructive px-2 py-1 text-xs text-primary-foreground"
>
</button>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* plugin-manageradmin / main
@@ -84,14 +84,14 @@ function RegistryTab(): React.ReactElement {
items.map((item) => (
<div
key={item.pluginId}
className="rounded-xl border border bg-background p-4"
className="rounded-xl border bg-background p-4"
>
<div className="flex items-center justify-between">
<div>
<p className="text-body text-foreground">
{item.displayName}
{item.isBuiltin ? (
<span className="ml-xs text-xs text-muted-foreground">
<span className="ml-1 text-xs text-muted-foreground">
</span>
) : null}
@@ -100,12 +100,12 @@ function RegistryTab(): React.ReactElement {
{item.pluginId} · v{item.version} · {item.category} ·{" "}
{item.defaultSlot}
</p>
<p className="mt-xs text-sm text-muted-foreground">
<p className="mt-1 text-sm text-muted-foreground">
{item.description}
</p>
</div>
<div className="flex items-center gap-2">
<label className="flex items-center gap-xs text-sm text-foreground">
<label className="flex items-center gap-1 text-sm text-foreground">
<input
type="checkbox"
checked={item.isActive}
@@ -120,14 +120,14 @@ function RegistryTab(): React.ReactElement {
? setEditingId(null)
: handleStartEdit(item)
}
className="rounded-md border border bg-card px-sm py-xs text-xs text-foreground"
className="rounded-md border bg-card px-2 py-1 text-xs text-foreground"
>
{editingId === item.pluginId ? "取消" : "编辑 props"}
</button>
</div>
</div>
{editingId === item.pluginId ? (
<div className="mt-sm space-y-xs">
<div className="mt-2 space-y-1">
<label className="text-xs text-muted-foreground">
defaultProps (JSON)
</label>
@@ -135,7 +135,7 @@ function RegistryTab(): React.ReactElement {
value={draftProps}
onChange={(e) => setDraftProps(e.target.value)}
rows={6}
className="w-full rounded-md border border bg-card p-2 font-mono text-xs text-foreground"
className="w-full rounded-md border bg-card p-2 font-mono text-xs text-foreground"
/>
{draftError.length > 0 ? (
<p className="text-xs text-muted-foreground">{draftError}</p>
@@ -143,7 +143,7 @@ function RegistryTab(): React.ReactElement {
<button
type="button"
onClick={() => handleSaveProps(item.pluginId)}
className="rounded-md bg-primary px-md py-xs text-xs text-primary-foreground"
className="rounded-md bg-primary px-3 py-1 text-xs text-primary-foreground"
>
</button>
@@ -199,7 +199,7 @@ function MappingTab(): React.ReactElement {
const draftList = Object.values(drafts);
const selectCls =
"rounded-md border border bg-card px-sm py-xs text-sm text-foreground";
"rounded-md border bg-card px-2 py-1 text-sm text-foreground";
return (
<div className="space-y-4">
@@ -226,17 +226,17 @@ function MappingTab(): React.ReactElement {
<table className="w-full text-sm">
<thead>
<tr className="border-b border text-muted-foreground">
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left">Slot</th>
<th className="py-xs text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left">Slot</th>
<th className="py-1 text-left"></th>
</tr>
</thead>
<tbody>
{draftList.map((m) => (
<tr key={m.pluginId} className="border-b border">
<td className="py-xs text-foreground">{m.pluginId}</td>
<td className="py-xs">
<td className="py-1 text-foreground">{m.pluginId}</td>
<td className="py-1">
<input
type="checkbox"
checked={m.isEnabled}
@@ -245,17 +245,17 @@ function MappingTab(): React.ReactElement {
}
/>
</td>
<td className="py-xs">
<td className="py-1">
<input
type="text"
value={m.slot}
onChange={(e) =>
updateDraft(m.pluginId, { slot: e.target.value })
}
className="w-full rounded-md border border bg-card px-sm py-xs text-xs text-foreground"
className="w-full rounded-md border bg-card px-2 py-1 text-xs text-foreground"
/>
</td>
<td className="py-xs">
<td className="py-1">
<input
type="number"
value={m.sortOrder}
@@ -264,7 +264,7 @@ function MappingTab(): React.ReactElement {
sortOrder: Number(e.target.value),
})
}
className="w-full rounded-md border border bg-card px-sm py-xs text-xs text-foreground"
className="w-full rounded-md border bg-card px-2 py-1 text-xs text-foreground"
/>
</td>
</tr>
@@ -276,7 +276,7 @@ function MappingTab(): React.ReactElement {
<button
type="button"
onClick={handleSave}
className="rounded-md bg-primary px-md py-xs text-sm text-primary-foreground"
className="rounded-md bg-primary px-3 py-1 text-sm text-primary-foreground"
>
</button>
@@ -313,7 +313,7 @@ function LayoutTab(): React.ReactElement {
}
const selectCls =
"rounded-md border border bg-card px-sm py-xs text-sm text-foreground";
"rounded-md border bg-card px-2 py-1 text-sm text-foreground";
return (
<div className="space-y-4">
@@ -356,11 +356,11 @@ function LayoutTab(): React.ReactElement {
>
<p className="text-body text-foreground">{tpl.displayName}</p>
<p className="text-xs text-muted-foreground">{tpl.description}</p>
<p className="mt-xs text-xs text-muted-foreground">
<p className="mt-1 text-xs text-muted-foreground">
Slot{tpl.availableSlots.join("、")}
</p>
{isCurrent ? (
<p className="mt-xs text-xs text-foreground"></p>
<p className="mt-1 text-xs text-foreground"></p>
) : null}
</button>
);
@@ -402,12 +402,12 @@ function UserLayoutTab(): React.ReactElement {
value={userId}
onChange={(e) => setUserId(e.target.value)}
placeholder="输入用户 ID"
className="w-64 rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="w-64 rounded-md border bg-card px-2 py-1 text-sm text-foreground"
/>
<button
type="button"
onClick={handleReset}
className="rounded-md bg-danger px-md py-xs text-sm text-primary-foreground"
className="rounded-md bg-destructive px-3 py-1 text-sm text-primary-foreground"
>
</button>
@@ -423,16 +423,16 @@ export default function PluginManager(_props: PluginProps): React.ReactElement {
const [tab, setTab] = useState<TabKey>("registry");
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<div className="mt-sm flex gap-2 border-b border">
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<div className="mt-2 flex gap-2 border-b border">
{(Object.keys(TAB_LABELS) as TabKey[]).map((key) => (
<button
key={key}
type="button"
onClick={() => setTab(key)}
aria-pressed={tab === key}
className={`rounded-md px-sm py-xs text-sm ${
className={`rounded-md px-2 py-1 text-sm ${
tab === key
? "bg-primary text-primary-foreground"
: "bg-card text-foreground"
@@ -442,7 +442,7 @@ export default function PluginManager(_props: PluginProps): React.ReactElement {
</button>
))}
</div>
<div className="mt-sm">
<div className="mt-2">
{tab === "registry" ? <RegistryTab /> : null}
{tab === "mapping" ? <MappingTab /> : null}
{tab === "layout" ? <LayoutTab /> : null}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* rbac-manageradmin / main
@@ -79,21 +79,21 @@ export default function RbacManager(_props: PluginProps): React.ReactElement {
if (roles.length === 0) {
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<p className="mt-sm text-sm text-muted-foreground"></p>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<p className="mt-2 text-sm text-muted-foreground"></p>
</section>
);
}
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<div className="mt-sm flex gap-4">
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<div className="mt-2 flex gap-4">
{/* 左侧角色列表 */}
<div className="w-64 flex-shrink-0">
<p className="text-sm text-muted-foreground"></p>
<ul className="mt-xs space-y-xs">
<ul className="mt-1 space-y-1">
{roles.map((role) => {
const isSelected = role.id === effectiveRoleId;
return (
@@ -102,7 +102,7 @@ export default function RbacManager(_props: PluginProps): React.ReactElement {
type="button"
onClick={() => setSelectedRoleId(role.id)}
aria-pressed={isSelected}
className={`w-full rounded-md border px-sm py-xs text-left text-sm ${
className={`w-full rounded-md border px-2 py-1 text-left text-sm ${
isSelected
? "border-accent bg-primary-subtle text-foreground"
: "border bg-background text-foreground"
@@ -124,11 +124,11 @@ export default function RbacManager(_props: PluginProps): React.ReactElement {
<table className="w-full text-xs">
<thead>
<tr className="border-b border text-muted-foreground">
<th className="py-xs text-left"></th>
<th className="py-1 text-left"></th>
{roles.map((role) => (
<th
key={role.id}
className={`py-xs text-center ${
className={`py-1 text-center ${
role.id === effectiveRoleId
? "text-foreground"
: "text-muted-foreground"
@@ -142,7 +142,7 @@ export default function RbacManager(_props: PluginProps): React.ReactElement {
<tbody>
{permissions.map((perm) => (
<tr key={perm.id} className="border-b border">
<td className="py-xs">
<td className="py-1">
<p className="text-foreground">{perm.name}</p>
<p className="text-xs text-muted-foreground">
{perm.resource} / {perm.action}
@@ -153,7 +153,7 @@ export default function RbacManager(_props: PluginProps): React.ReactElement {
rolePermissionMap.get(role.id)?.has(perm.id) ?? false;
const isBusy = busyRoleId === role.id;
return (
<td key={role.id} className="py-xs text-center">
<td key={role.id} className="py-1 text-center">
<input
type="checkbox"
checked={checked}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* school-settingsadmin / main
@@ -18,7 +18,7 @@ import type { PluginProps } from "@/lib/types";
const TERM_OPTIONS = ["第一学期", "第二学期", "暑假", "寒假"] as const;
const inputCls =
"w-full rounded-md border border bg-card px-sm py-xs text-sm text-foreground";
"w-full rounded-md border bg-card px-2 py-1 text-sm text-foreground";
const labelCls = "text-xs text-muted-foreground";
export default function SchoolSettings(props: PluginProps): React.ReactElement {
@@ -70,22 +70,22 @@ export default function SchoolSettings(props: PluginProps): React.ReactElement {
if (!draft) {
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<p className="mt-sm text-sm text-muted-foreground"></p>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<p className="mt-2 text-sm text-muted-foreground"></p>
</section>
);
}
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
{status.length > 0 ? (
<p className="mt-xs text-xs text-muted-foreground">{status}</p>
<p className="mt-1 text-xs text-muted-foreground">{status}</p>
) : null}
<div className="mt-sm grid grid-cols-2 gap-4">
<div className="space-y-xs">
<div className="mt-2 grid grid-cols-2 gap-4">
<div className="space-y-1">
<label className={labelCls}></label>
<input
type="text"
@@ -94,7 +94,7 @@ export default function SchoolSettings(props: PluginProps): React.ReactElement {
className={inputCls}
/>
</div>
<div className="space-y-xs">
<div className="space-y-1">
<label className={labelCls}></label>
<input
type="text"
@@ -103,7 +103,7 @@ export default function SchoolSettings(props: PluginProps): React.ReactElement {
className={inputCls}
/>
</div>
<div className="space-y-xs">
<div className="space-y-1">
<label className={labelCls}></label>
<input
type="email"
@@ -112,7 +112,7 @@ export default function SchoolSettings(props: PluginProps): React.ReactElement {
className={inputCls}
/>
</div>
<div className="space-y-xs">
<div className="space-y-1">
<label className={labelCls}></label>
<input
type="text"
@@ -124,7 +124,7 @@ export default function SchoolSettings(props: PluginProps): React.ReactElement {
className={inputCls}
/>
</div>
<div className="space-y-xs">
<div className="space-y-1">
<label className={labelCls}></label>
<select
value={draft.currentTerm}
@@ -138,7 +138,7 @@ export default function SchoolSettings(props: PluginProps): React.ReactElement {
))}
</select>
</div>
<div className="space-y-xs">
<div className="space-y-1">
<label className={labelCls}></label>
<input
type="date"
@@ -147,7 +147,7 @@ export default function SchoolSettings(props: PluginProps): React.ReactElement {
className={inputCls}
/>
</div>
<div className="space-y-xs">
<div className="space-y-1">
<label className={labelCls}></label>
<input
type="date"
@@ -156,7 +156,7 @@ export default function SchoolSettings(props: PluginProps): React.ReactElement {
className={inputCls}
/>
</div>
<div className="space-y-xs">
<div className="space-y-1">
<label className={labelCls}></label>
<input
type="text"
@@ -168,7 +168,7 @@ export default function SchoolSettings(props: PluginProps): React.ReactElement {
</div>
{showAdvanced ? (
<div className="mt-md rounded-md bg-background p-2">
<div className="mt-3 rounded-md bg-background p-2">
<p className="text-xs text-muted-foreground">
config-service
-
@@ -176,18 +176,18 @@ export default function SchoolSettings(props: PluginProps): React.ReactElement {
</div>
) : null}
<div className="mt-md flex justify-end gap-2">
<div className="mt-3 flex justify-end gap-2">
<button
type="button"
onClick={() => data && setDraft({ ...data })}
className="rounded-md border border bg-card px-md py-xs text-sm text-foreground"
className="rounded-md border bg-card px-3 py-1 text-sm text-foreground"
>
</button>
<button
type="button"
onClick={handleSave}
className="rounded-md bg-primary px-md py-xs text-sm text-primary-foreground"
className="rounded-md bg-primary px-3 py-1 text-sm text-primary-foreground"
>
</button>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* user-managementadmin / main
@@ -87,13 +87,13 @@ export default function UserManagement(
const rangeEnd = Math.min(offset + PAGE_SIZE, total);
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<div className="mt-sm flex gap-2">
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<div className="mt-2 flex gap-2">
<select
value={roleFilter}
onChange={handleRoleFilterChange}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
aria-label="按角色筛选"
>
<option value=""></option>
@@ -106,17 +106,17 @@ export default function UserManagement(
</div>
{users.length === 0 ? (
<p className="mt-sm text-sm text-muted-foreground"></p>
<p className="mt-2 text-sm text-muted-foreground"></p>
) : (
<div className="mt-sm overflow-x-auto">
<div className="mt-2 overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border text-muted-foreground">
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
</tr>
</thead>
<tbody>
@@ -124,14 +124,14 @@ export default function UserManagement(
const isBusy = busyId === u.id;
return (
<tr key={u.id} className="border-b border">
<td className="py-xs text-foreground">{u.name}</td>
<td className="py-xs text-foreground">{u.email}</td>
<td className="py-xs">
<td className="py-1 text-foreground">{u.name}</td>
<td className="py-1 text-foreground">{u.email}</td>
<td className="py-1">
<select
value={u.role}
disabled={isBusy}
onChange={(e) => handleRoleChange(u.id, e.target.value)}
className="rounded-md border border bg-card px-sm py-xs text-xs text-foreground"
className="rounded-md border bg-card px-2 py-1 text-xs text-foreground"
aria-label="修改角色"
>
{ROLE_OPTIONS.map((r) => (
@@ -141,14 +141,14 @@ export default function UserManagement(
))}
</select>
</td>
<td className="py-xs">
<td className="py-1">
<select
value={u.status}
disabled={isBusy}
onChange={(e) =>
handleStatusChange(u.id, e.target.value)
}
className="rounded-md border border bg-card px-sm py-xs text-xs text-foreground"
className="rounded-md border bg-card px-2 py-1 text-xs text-foreground"
aria-label="修改状态"
>
{STATUS_OPTIONS.map((s) => (
@@ -158,7 +158,7 @@ export default function UserManagement(
))}
</select>
</td>
<td className="py-xs text-muted-foreground">
<td className="py-1 text-muted-foreground">
{u.createdAt}
</td>
</tr>
@@ -169,7 +169,7 @@ export default function UserManagement(
</div>
)}
<div className="mt-sm flex items-center justify-between text-xs text-muted-foreground">
<div className="mt-2 flex items-center justify-between text-xs text-muted-foreground">
<span>
{total} {offset + 1} - {rangeEnd}
</span>
@@ -178,7 +178,7 @@ export default function UserManagement(
type="button"
disabled={!hasPrev}
onClick={() => setOffset(Math.max(0, offset - PAGE_SIZE))}
className="rounded-md border border bg-card px-sm py-xs text-foreground"
className="rounded-md border bg-card px-2 py-1 text-foreground"
>
</button>
@@ -186,7 +186,7 @@ export default function UserManagement(
type="button"
disabled={!hasNext}
onClick={() => setOffset(offset + PAGE_SIZE)}
className="rounded-md border border bg-card px-sm py-xs text-foreground"
className="rounded-md border bg-card px-2 py-1 text-foreground"
>
</button>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* child-overviewparent / main
@@ -41,17 +41,17 @@ export default function ChildOverview(_props: PluginProps): React.ReactElement {
if (children.length === 0) {
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<p className="mt-sm text-sm text-muted-foreground"></p>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<p className="mt-2 text-sm text-muted-foreground"></p>
</section>
);
}
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<div className="mt-sm grid grid-cols-2 gap-4">
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<div className="mt-2 grid grid-cols-2 gap-4">
{children.map((child) => {
const isSelected = child.id === selectedChildId;
return (
@@ -77,12 +77,12 @@ export default function ChildOverview(_props: PluginProps): React.ReactElement {
</p>
</div>
</div>
<div className="mt-sm space-y-xs">
<div className="mt-2 space-y-1">
<p className="text-sm text-muted-foreground"></p>
{child.recentGrades.length === 0 ? (
<p className="text-xs text-muted-foreground"></p>
) : (
<ul className="space-y-xs">
<ul className="space-y-1">
{child.recentGrades.map((g) => (
<li
key={g.subject}
@@ -95,7 +95,7 @@ export default function ChildOverview(_props: PluginProps): React.ReactElement {
</ul>
)}
</div>
<div className="mt-sm flex justify-between border-t border pt-xs text-xs">
<div className="mt-2 flex justify-between border-t border pt-1 text-xs">
<span className="text-muted-foreground">
{formatRate(child.attendance.present, child.attendance.total)}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* leave-approvalparent / main
@@ -89,13 +89,13 @@ export default function LeaveApproval(_props: PluginProps): React.ReactElement {
const requests = data ?? [];
return (
<section className="rounded-xl border border bg-card p-4">
<section className="rounded-xl border bg-card p-4">
<div className="flex items-center justify-between">
<h3 className="text-heading-3 text-foreground"></h3>
<h3 className="text-lg font-semibold text-foreground"></h3>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
aria-label="按状态筛选"
>
<option value=""></option>
@@ -106,25 +106,22 @@ export default function LeaveApproval(_props: PluginProps): React.ReactElement {
</div>
{childId.length > 0 ? (
<p className="mt-xs text-xs text-muted-foreground"></p>
<p className="mt-1 text-xs text-muted-foreground"></p>
) : null}
{rejectError.length > 0 ? (
<p className="mt-xs text-xs text-muted-foreground">{rejectError}</p>
<p className="mt-1 text-xs text-muted-foreground">{rejectError}</p>
) : null}
{requests.length === 0 ? (
<p className="mt-sm text-sm text-muted-foreground"></p>
<p className="mt-2 text-sm text-muted-foreground"></p>
) : (
<ul className="mt-sm space-y-4">
<ul className="mt-2 space-y-4">
{requests.map((req) => {
const isPending = req.status === "pending";
const isBusy = busyId === req.id;
const reasonValue = rejectReasons[req.id] ?? "";
return (
<li
key={req.id}
className="rounded-xl border border bg-background p-4"
>
<li key={req.id} className="rounded-xl border bg-background p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-body text-foreground">{req.childName}</p>
@@ -133,18 +130,18 @@ export default function LeaveApproval(_props: PluginProps): React.ReactElement {
{req.endDate}
</p>
</div>
<span className="rounded-md bg-muted px-sm py-xs text-xs text-foreground">
<span className="rounded-md bg-muted px-2 py-1 text-xs text-foreground">
{STATUS_LABELS[req.status] ?? req.status}
</span>
</div>
<p className="mt-sm text-sm text-muted-foreground">
<p className="mt-2 text-sm text-muted-foreground">
{req.reason}
</p>
<p className="mt-xs text-xs text-muted-foreground">
<p className="mt-1 text-xs text-muted-foreground">
{req.createdAt}
</p>
{isPending ? (
<div className="mt-sm space-y-xs">
<div className="mt-2 space-y-1">
<input
type="text"
value={reasonValue}
@@ -155,14 +152,14 @@ export default function LeaveApproval(_props: PluginProps): React.ReactElement {
}))
}
placeholder="拒绝原因(拒绝时必填)"
className="w-full rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="w-full rounded-md border bg-card px-2 py-1 text-sm text-foreground"
/>
<div className="flex gap-2">
<button
type="button"
disabled={isBusy}
onClick={() => handleApprove(req.id)}
className="rounded-md bg-primary px-md py-xs text-sm text-primary-foreground"
className="rounded-md bg-primary px-3 py-1 text-sm text-primary-foreground"
>
</button>
@@ -170,7 +167,7 @@ export default function LeaveApproval(_props: PluginProps): React.ReactElement {
type="button"
disabled={isBusy}
onClick={() => handleReject(req.id)}
className="rounded-md bg-danger px-md py-xs text-sm text-primary-foreground"
className="rounded-md bg-destructive px-3 py-1 text-sm text-primary-foreground"
>
</button>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* child-selectorsidebar / side
@@ -39,7 +39,7 @@ export default function ChildSelector(_props: PluginProps): React.ReactElement {
<select
value={currentChildId}
onChange={(e) => handleSelect(e.target.value)}
className="mt-xs w-full rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="mt-1 w-full rounded-md border bg-card px-2 py-1 text-sm text-foreground"
aria-label="选择孩子"
>
<option value=""></option>
@@ -50,7 +50,7 @@ export default function ChildSelector(_props: PluginProps): React.ReactElement {
))}
</select>
{current ? (
<p className="mt-xs text-xs text-muted-foreground">
<p className="mt-1 text-xs text-muted-foreground">
{current.grade} · {current.className}
</p>
) : null}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* class-selectorsidebar / side
@@ -38,7 +38,7 @@ export default function ClassSelector(_props: PluginProps): React.ReactElement {
<select
value={currentClassId}
onChange={(e) => handleSelect(e.target.value)}
className="mt-xs w-full rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="mt-1 w-full rounded-md border bg-card px-2 py-1 text-sm text-foreground"
aria-label="选择班级"
>
<option value=""></option>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* quick-actionssidebar / side
@@ -45,13 +45,13 @@ export default function QuickActions(props: PluginProps): React.ReactElement {
return (
<div>
<p className="text-sm text-muted-foreground"></p>
<ul className="mt-xs space-y-sm">
<ul className="mt-1 space-y-2">
{actions.map((item) => (
<li key={item.path}>
<button
type="button"
onClick={() => handleClick(item.path)}
className="w-full rounded-md border border bg-card px-sm py-xs text-left text-sm text-foreground"
className="w-full rounded-md border bg-card px-2 py-1 text-left text-sm text-foreground"
>
{item.label}
</button>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* term-switchersidebar / side
@@ -42,7 +42,7 @@ export default function TermSwitcher(_props: PluginProps): React.ReactElement {
<select
value={selectedTermId}
onChange={(e) => handleSelect(e.target.value)}
className="mt-xs w-full rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="mt-1 w-full rounded-md border bg-card px-2 py-1 text-sm text-foreground"
aria-label="选择学期"
>
<option value=""></option>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* ai-tutorstudent / main
@@ -70,13 +70,13 @@ export default function AiTutor(props: PluginProps): React.ReactElement {
const canSend = !sending && input.trim().length > 0;
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground">AI </h3>
<div className="mt-sm flex">
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground">AI </h3>
<div className="mt-2 flex">
{/* 左侧会话列表 */}
<div className="flex w-64 flex-col border-r border pr-sm">
<div className="flex w-64 flex-col border-r border pr-2">
<p className="text-sm text-muted-foreground"></p>
<ul className="mt-xs space-y-sm">
<ul className="mt-1 space-y-2">
{sessions.map((session) => {
const active = activeSessionId === session.id;
return (
@@ -84,7 +84,7 @@ export default function AiTutor(props: PluginProps): React.ReactElement {
<button
type="button"
onClick={() => handleSelectSession(session.id)}
className={`w-full rounded-md px-sm py-xs text-left ${active ? "bg-primary" : "bg-background"}`}
className={`w-full rounded-md px-2 py-1 text-left ${active ? "bg-primary" : "bg-background"}`}
>
<span
className={`block text-sm ${active ? "text-primary-foreground" : "text-foreground"}`}
@@ -92,7 +92,7 @@ export default function AiTutor(props: PluginProps): React.ReactElement {
{session.title}
</span>
<span
className={`mt-xs block text-xs ${active ? "text-primary-foreground" : "text-muted-foreground"}`}
className={`mt-1 block text-xs ${active ? "text-primary-foreground" : "text-muted-foreground"}`}
>
{session.lastMessage}
</span>
@@ -109,8 +109,8 @@ export default function AiTutor(props: PluginProps): React.ReactElement {
</div>
{/* 右侧消息区 */}
<div className="flex flex-1 flex-col pl-sm">
<div className="flex-1 space-y-sm">
<div className="flex flex-1 flex-col pl-2">
<div className="flex-1 space-y-2">
{messages.length === 0 ? (
<p className="text-sm text-muted-foreground">
AI
@@ -121,8 +121,8 @@ export default function AiTutor(props: PluginProps): React.ReactElement {
key={msg.id}
className={
msg.role === "user"
? "ml-sm rounded-md bg-primary px-sm py-xs text-sm text-primary-foreground"
: "rounded-md bg-background px-sm py-xs text-sm text-foreground"
? "ml-2 rounded-md bg-primary px-2 py-1 text-sm text-primary-foreground"
: "rounded-md bg-background px-2 py-1 text-sm text-foreground"
}
>
{msg.content}
@@ -135,7 +135,7 @@ export default function AiTutor(props: PluginProps): React.ReactElement {
</div>
{/* 输入区 */}
<div className="mt-sm flex">
<div className="mt-2 flex">
<input
type="text"
value={input}
@@ -146,7 +146,7 @@ export default function AiTutor(props: PluginProps): React.ReactElement {
}
}}
placeholder="输入你的问题..."
className="flex-1 rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="flex-1 rounded-md border bg-card px-2 py-1 text-sm text-foreground"
/>
<button
type="button"
@@ -154,8 +154,8 @@ export default function AiTutor(props: PluginProps): React.ReactElement {
onClick={() => void handleSend()}
className={
canSend
? "ml-sm rounded-md bg-primary px-sm py-xs text-sm text-primary-foreground"
: "ml-sm rounded-md bg-muted px-sm py-xs text-sm text-muted-foreground"
? "ml-2 rounded-md bg-primary px-2 py-1 text-sm text-primary-foreground"
: "ml-2 rounded-md bg-muted px-2 py-1 text-sm text-muted-foreground"
}
>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* elective-selectorstudent / main
@@ -64,8 +64,8 @@ export default function ElectiveSelector(
if (!termId) {
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<p className="text-sm text-muted-foreground"></p>
</section>
);
@@ -74,31 +74,31 @@ export default function ElectiveSelector(
const courses = data ?? [];
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
{courses.length === 0 ? (
<p className="mt-sm text-sm text-muted-foreground"></p>
<p className="mt-2 text-sm text-muted-foreground"></p>
) : (
<ul className="mt-sm space-y-4">
<ul className="mt-2 space-y-4">
{courses.map((course) => {
const enrolled = enrolledIds[course.id] === true;
const full = course.enrolled >= course.capacity;
return (
<li
key={course.id}
className="rounded-xl border border bg-background p-2"
className="rounded-xl border bg-background p-2"
>
<div className="flex items-center">
<span className="text-body text-foreground">
{course.name}
</span>
<span
className={`ml-sm rounded-md px-xs py-xs text-xs ${CATEGORY_BADGE[course.category]}`}
className={`ml-2 rounded-md px-1 py-1 text-xs ${CATEGORY_BADGE[course.category]}`}
>
{course.category}
</span>
</div>
<div className="mt-xs flex flex-col text-sm text-muted-foreground">
<div className="mt-1 flex flex-col text-sm text-muted-foreground">
<span>{course.teacher}</span>
<span>
{course.enrolled}/{course.capacity}
@@ -107,7 +107,7 @@ export default function ElectiveSelector(
<span>{course.schedule}</span>
<span>{course.credits}</span>
</div>
<div className="mt-xs">
<div className="mt-1">
{enrolled ? (
<button
type="button"
@@ -115,8 +115,8 @@ export default function ElectiveSelector(
onClick={() => void handleDrop(course.id)}
className={
dropping
? "rounded-md bg-muted px-sm py-xs text-xs text-muted-foreground"
: "rounded-md bg-danger px-sm py-xs text-xs text-primary-foreground"
? "rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground"
: "rounded-md bg-destructive px-2 py-1 text-xs text-primary-foreground"
}
>
退
@@ -128,8 +128,8 @@ export default function ElectiveSelector(
onClick={() => void handleEnroll(course.id)}
className={
enrolling || full
? "rounded-md bg-muted px-sm py-xs text-xs text-muted-foreground"
: "rounded-md bg-primary px-sm py-xs text-xs text-primary-foreground"
? "rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground"
: "rounded-md bg-primary px-2 py-1 text-xs text-primary-foreground"
}
>
{full ? "已满" : "选课"}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* error-bookstudent / main
@@ -46,9 +46,9 @@ export default function ErrorBook(props: PluginProps): React.ReactElement {
}
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<div className="mt-sm flex items-center">
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<div className="mt-2 flex items-center">
<label
htmlFor="error-book-subject"
className="text-sm text-muted-foreground"
@@ -59,7 +59,7 @@ export default function ErrorBook(props: PluginProps): React.ReactElement {
id="error-book-subject"
value={subjectFilter}
onChange={(e) => setSubjectFilter(e.target.value)}
className="ml-sm rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="ml-2 rounded-md border bg-card px-2 py-1 text-sm text-foreground"
>
<option value=""></option>
{subjects.map((s) => (
@@ -71,31 +71,28 @@ export default function ErrorBook(props: PluginProps): React.ReactElement {
</div>
{filteredItems.length === 0 ? (
<p className="mt-sm text-sm text-muted-foreground"></p>
<p className="mt-2 text-sm text-muted-foreground"></p>
) : (
<ul className="mt-sm space-y-4">
<ul className="mt-2 space-y-4">
{filteredItems.map((item) => {
const mastered = masteredIds[item.id] === true;
return (
<li
key={item.id}
className="rounded-xl border border bg-background p-2"
>
<li key={item.id} className="rounded-xl border bg-background p-2">
<div className="flex items-center">
<span className="rounded-md bg-muted px-sm py-xs text-xs text-foreground">
<span className="rounded-md bg-muted px-2 py-1 text-xs text-foreground">
{item.subject}
</span>
<span className="ml-sm text-xs text-muted-foreground">
<span className="ml-2 text-xs text-muted-foreground">
{item.errorCount}
</span>
<span className="ml-sm text-xs text-muted-foreground">
<span className="ml-2 text-xs text-muted-foreground">
{item.lastErrorAt}
</span>
</div>
<p className="mt-xs text-body text-foreground">
<p className="mt-1 text-body text-foreground">
{item.question}
</p>
<div className="mt-xs flex flex-col">
<div className="mt-1 flex flex-col">
<span className="text-sm text-muted-foreground">
{item.myAnswer}
</span>
@@ -109,8 +106,8 @@ export default function ErrorBook(props: PluginProps): React.ReactElement {
onClick={() => void handleMarkMastered(item.id)}
className={
mastered
? "mt-xs rounded-md bg-muted px-sm py-xs text-xs text-muted-foreground"
: "mt-xs rounded-md bg-primary px-sm py-xs text-xs text-primary-foreground"
? "mt-1 rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground"
: "mt-1 rounded-md bg-primary px-2 py-1 text-xs text-primary-foreground"
}
>
{mastered ? "已掌握" : "标记已掌握"}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* learning-pathstudent / main
@@ -35,7 +35,7 @@ function statusClass(status: LearningNodeStatus): string {
case "locked":
return "bg-muted text-muted-foreground";
case "available":
return "border border bg-card text-foreground";
return "border bg-card text-foreground";
case "in_progress":
return "bg-warning text-foreground";
case "completed":
@@ -69,8 +69,8 @@ export default function LearningPath(_props: PluginProps): React.ReactElement {
if (!subjectId) {
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<p className="text-sm text-muted-foreground"></p>
</section>
);
@@ -81,44 +81,44 @@ export default function LearningPath(_props: PluginProps): React.ReactElement {
const progress = path?.progress ?? 0;
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<div className="mt-sm">
<div className="mt-2">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground"></span>
<span className="text-sm text-foreground">{progress}%</span>
</div>
<div className="mt-xs h-xs w-full rounded-md bg-muted">
<div className="mt-1 h-1 w-full rounded-md bg-muted">
<div
className="h-xs rounded-md bg-primary"
className="h-1 rounded-md bg-primary"
style={{ width: `${progress}%` }}
/>
</div>
</div>
{nodes.length === 0 ? (
<p className="mt-sm text-sm text-muted-foreground"></p>
<p className="mt-2 text-sm text-muted-foreground"></p>
) : (
<ol className="mt-md space-y-4">
<ol className="mt-3 space-y-4">
{nodes.map((node) => {
const clickable = node.status !== "locked";
return (
<li key={node.id} className="flex items-start">
<span
className={`flex w-64 items-center justify-center rounded-md px-sm py-xs text-xs ${statusClass(
className={`flex w-64 items-center justify-center rounded-md px-2 py-1 text-xs ${statusClass(
node.status,
)}`}
>
{TYPE_LABEL[node.type]}
</span>
<div className="ml-sm flex-1">
<div className="ml-2 flex-1">
<div className="flex items-center">
<span className="text-body text-foreground">
{node.title}
</span>
<span
className={`ml-sm rounded-md px-xs py-xs text-xs ${statusClass(
className={`ml-2 rounded-md px-1 py-1 text-xs ${statusClass(
node.status,
)}`}
>
@@ -126,7 +126,7 @@ export default function LearningPath(_props: PluginProps): React.ReactElement {
</span>
</div>
{node.dependencies.length > 0 && (
<p className="mt-xs text-xs text-muted-foreground">
<p className="mt-1 text-xs text-muted-foreground">
{node.dependencies.join("、")}
</p>
)}
@@ -134,7 +134,7 @@ export default function LearningPath(_props: PluginProps): React.ReactElement {
<button
type="button"
onClick={() => handleNavigate(node)}
className="mt-xs rounded-md border border bg-card px-sm py-xs text-xs text-foreground"
className="mt-1 rounded-md border bg-card px-2 py-1 text-xs text-foreground"
>
</button>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* lesson-plan-editorteacher / main
@@ -43,8 +43,8 @@ export default function LessonPlanEditor(
if (!classId) {
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<p className="text-sm text-muted-foreground"></p>
</section>
);
@@ -101,19 +101,19 @@ export default function LessonPlanEditor(
};
return (
<section className="rounded-xl border border bg-card p-4">
<section className="rounded-xl border bg-card p-4">
<div className="flex items-center justify-between">
<h3 className="text-heading-3 text-foreground"></h3>
<h3 className="text-lg font-semibold text-foreground"></h3>
<button
type="button"
onClick={handleNew}
className="rounded-md bg-primary px-sm py-xs text-sm text-primary-foreground"
className="rounded-md bg-primary px-2 py-1 text-sm text-primary-foreground"
>
</button>
</div>
<div className="mt-sm flex gap-4">
<ul className="w-64 shrink-0 space-y-sm">
<div className="mt-2 flex gap-4">
<ul className="w-64 shrink-0 space-y-2">
{plans.length === 0 ? (
<li className="text-sm text-muted-foreground"></li>
) : (
@@ -122,7 +122,7 @@ export default function LessonPlanEditor(
<button
type="button"
onClick={() => handleSelect(p)}
className={`w-full rounded-md border border px-sm py-xs text-left text-sm ${
className={`w-full rounded-md border px-2 py-1 text-left text-sm ${
draft.id === p.id
? "bg-muted text-foreground"
: "bg-card text-foreground"
@@ -135,7 +135,7 @@ export default function LessonPlanEditor(
)}
</ul>
<div className="flex-1 space-y-4">
<label className="flex flex-col space-y-xs">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<input
type="text"
@@ -143,50 +143,50 @@ export default function LessonPlanEditor(
onChange={(e) =>
setDraft((d) => ({ ...d, title: e.target.value }))
}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
placeholder="请输入备课标题"
/>
</label>
<label className="flex flex-col space-y-xs">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<textarea
value={draft.objectives}
onChange={(e) =>
setDraft((d) => ({ ...d, objectives: e.target.value }))
}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
rows={3}
placeholder="请输入教学目标"
/>
</label>
<label className="flex flex-col space-y-xs">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<textarea
value={draft.content}
onChange={(e) =>
setDraft((d) => ({ ...d, content: e.target.value }))
}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
rows={4}
placeholder="请输入教学内容"
/>
</label>
<div className="flex flex-col space-y-xs">
<div className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<ul className="space-y-xs">
<ul className="space-y-1">
{draft.resources.map((r, i) => (
<li key={i} className="flex gap-xs">
<li key={i} className="flex gap-1">
<input
type="text"
value={r}
onChange={(e) => handleResourceChange(i, e.target.value)}
className="w-full rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="w-full rounded-md border bg-card px-2 py-1 text-sm text-foreground"
placeholder="资源名称或链接"
/>
<button
type="button"
onClick={() => handleResourceRemove(i)}
className="rounded-md bg-muted px-sm py-xs text-sm text-foreground"
className="rounded-md bg-muted px-2 py-1 text-sm text-foreground"
>
</button>
@@ -196,7 +196,7 @@ export default function LessonPlanEditor(
<button
type="button"
onClick={handleResourceAdd}
className="self-start rounded-md bg-muted px-sm py-xs text-sm text-foreground"
className="self-start rounded-md bg-muted px-2 py-1 text-sm text-foreground"
>
</button>
@@ -205,7 +205,7 @@ export default function LessonPlanEditor(
type="button"
onClick={handleSave}
disabled={saving || !draft.title.trim()}
className="rounded-md bg-primary px-md py-xs text-sm text-primary-foreground disabled:opacity-50"
className="rounded-md bg-primary px-3 py-1 text-sm text-primary-foreground disabled:opacity-50"
>
{saving ? "保存中" : "保存"}
</button>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* question-bankteacher / main
@@ -66,8 +66,8 @@ export default function QuestionBank(_props: PluginProps): React.ReactElement {
if (!bankId) {
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<p className="text-sm text-muted-foreground"></p>
</section>
);
@@ -98,25 +98,25 @@ export default function QuestionBank(_props: PluginProps): React.ReactElement {
};
return (
<section className="rounded-xl border border bg-card p-4">
<section className="rounded-xl border bg-card p-4">
<div className="flex items-center justify-between">
<h3 className="text-heading-3 text-foreground"></h3>
<h3 className="text-lg font-semibold text-foreground"></h3>
<button
type="button"
onClick={() => setShowForm((v) => !v)}
className="rounded-md bg-primary px-sm py-xs text-sm text-primary-foreground"
className="rounded-md bg-primary px-2 py-1 text-sm text-primary-foreground"
>
{showForm ? "收起新建" : "新建题目"}
</button>
</div>
<div className="mt-sm flex gap-4">
<label className="flex flex-col space-y-xs">
<div className="mt-2 flex gap-4">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<select
value={typeFilter}
onChange={(e) => setTypeFilter(e.target.value)}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
>
<option value=""></option>
{QUESTION_TYPES.map((t) => (
@@ -126,12 +126,12 @@ export default function QuestionBank(_props: PluginProps): React.ReactElement {
))}
</select>
</label>
<label className="flex flex-col space-y-xs">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<select
value={difficultyFilter}
onChange={(e) => setDifficultyFilter(e.target.value)}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
>
<option value=""></option>
{DIFFICULTIES.map((d) => (
@@ -144,16 +144,16 @@ export default function QuestionBank(_props: PluginProps): React.ReactElement {
</div>
{showForm ? (
<div className="mt-sm space-y-4 rounded-xl bg-muted p-4">
<div className="mt-2 space-y-4 rounded-xl bg-muted p-4">
<div className="flex gap-4">
<label className="flex flex-col space-y-xs">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<select
value={newQuestion.type}
onChange={(e) =>
setNewQuestion((q) => ({ ...q, type: e.target.value }))
}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
>
{QUESTION_TYPES.map((t) => (
<option key={t} value={t}>
@@ -162,14 +162,14 @@ export default function QuestionBank(_props: PluginProps): React.ReactElement {
))}
</select>
</label>
<label className="flex flex-col space-y-xs">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<select
value={newQuestion.difficulty}
onChange={(e) =>
setNewQuestion((q) => ({ ...q, difficulty: e.target.value }))
}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
>
{DIFFICULTIES.map((d) => (
<option key={d} value={d}>
@@ -179,26 +179,26 @@ export default function QuestionBank(_props: PluginProps): React.ReactElement {
</select>
</label>
</div>
<label className="flex flex-col space-y-xs">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<textarea
value={newQuestion.content}
onChange={(e) =>
setNewQuestion((q) => ({ ...q, content: e.target.value }))
}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
rows={3}
placeholder="请输入题干"
/>
</label>
<label className="flex flex-col space-y-xs">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<textarea
value={newQuestion.answer}
onChange={(e) =>
setNewQuestion((q) => ({ ...q, answer: e.target.value }))
}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
rows={2}
placeholder="请输入答案"
/>
@@ -207,38 +207,38 @@ export default function QuestionBank(_props: PluginProps): React.ReactElement {
type="button"
onClick={handleAdd}
disabled={!newQuestion.content.trim()}
className="rounded-md bg-primary px-md py-xs text-sm text-primary-foreground disabled:opacity-50"
className="rounded-md bg-primary px-3 py-1 text-sm text-primary-foreground disabled:opacity-50"
>
</button>
</div>
) : null}
<ul className="mt-sm space-y-4">
<ul className="mt-2 space-y-4">
{questions.length === 0 ? (
<li className="text-sm text-muted-foreground"></li>
) : (
questions.map((q) => (
<li key={q.id} className="rounded-md border border p-2">
<div className="flex flex-wrap gap-xs">
<span className="rounded-md bg-primary px-xs py-xs text-xs text-primary-foreground">
<li key={q.id} className="rounded-md border p-2">
<div className="flex flex-wrap gap-1">
<span className="rounded-md bg-primary px-1 py-1 text-xs text-primary-foreground">
{TYPE_LABELS[q.type] ?? q.type}
</span>
<span className="rounded-md bg-muted px-xs py-xs text-xs text-foreground">
<span className="rounded-md bg-muted px-1 py-1 text-xs text-foreground">
{DIFFICULTY_LABELS[q.difficulty] ?? q.difficulty}
</span>
{q.tags.map((tag) => (
<span
key={tag}
className="rounded-md bg-muted px-xs py-xs text-xs text-muted-foreground"
className="rounded-md bg-muted px-1 py-1 text-xs text-muted-foreground"
>
{tag}
</span>
))}
</div>
<p className="mt-xs text-body text-foreground">{q.content}</p>
<p className="mt-1 text-body text-foreground">{q.content}</p>
{q.options.length > 0 ? (
<ul className="mt-xs space-y-xs">
<ul className="mt-1 space-y-1">
{q.options.map((opt, i) => (
<li key={i} className="text-sm text-muted-foreground">
{String.fromCharCode(65 + i)}. {opt}
@@ -246,7 +246,7 @@ export default function QuestionBank(_props: PluginProps): React.ReactElement {
))}
</ul>
) : null}
<p className="mt-xs text-sm text-muted-foreground">
<p className="mt-1 text-sm text-muted-foreground">
{q.answer}
</p>
</li>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* scheduling-rulesteacher / main
@@ -44,8 +44,8 @@ export default function SchedulingRules(
if (!classId) {
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<p className="text-sm text-muted-foreground"></p>
</section>
);
@@ -85,20 +85,20 @@ export default function SchedulingRules(
};
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
{rules.length === 0 ? (
<p className="mt-sm text-sm text-muted-foreground"></p>
<p className="mt-2 text-sm text-muted-foreground"></p>
) : (
<table className="mt-sm w-full text-sm">
<table className="mt-2 w-full text-sm">
<thead>
<tr className="border-b border text-muted-foreground">
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-xs text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
<th className="py-1 text-left"></th>
</tr>
</thead>
<tbody>
@@ -107,7 +107,7 @@ export default function SchedulingRules(
if (isEditing && draft) {
return (
<tr key={rule.id} className="border-b border">
<td className="py-xs">
<td className="py-1">
<select
value={draft.dayOfWeek}
onChange={(e) =>
@@ -115,7 +115,7 @@ export default function SchedulingRules(
d ? { ...d, dayOfWeek: Number(e.target.value) } : d,
)
}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
>
{DAY_NAMES.map((name, i) => (
<option key={name} value={i + 1}>
@@ -124,7 +124,7 @@ export default function SchedulingRules(
))}
</select>
</td>
<td className="py-xs">
<td className="py-1">
<input
type="text"
value={draft.periods}
@@ -133,10 +133,10 @@ export default function SchedulingRules(
d ? { ...d, periods: e.target.value } : d,
)
}
className="w-full rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="w-full rounded-md border bg-card px-2 py-1 text-sm text-foreground"
/>
</td>
<td className="py-xs">
<td className="py-1">
<input
type="text"
value={draft.subject}
@@ -145,10 +145,10 @@ export default function SchedulingRules(
d ? { ...d, subject: e.target.value } : d,
)
}
className="w-full rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="w-full rounded-md border bg-card px-2 py-1 text-sm text-foreground"
/>
</td>
<td className="py-xs">
<td className="py-1">
<input
type="text"
value={draft.teacherId}
@@ -157,10 +157,10 @@ export default function SchedulingRules(
d ? { ...d, teacherId: e.target.value } : d,
)
}
className="w-full rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="w-full rounded-md border bg-card px-2 py-1 text-sm text-foreground"
/>
</td>
<td className="py-xs">
<td className="py-1">
<input
type="text"
value={draft.room}
@@ -169,23 +169,23 @@ export default function SchedulingRules(
d ? { ...d, room: e.target.value } : d,
)
}
className="w-full rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="w-full rounded-md border bg-card px-2 py-1 text-sm text-foreground"
/>
</td>
<td className="py-xs">
<div className="flex gap-xs">
<td className="py-1">
<div className="flex gap-1">
<button
type="button"
onClick={handleSave}
disabled={saving}
className="rounded-md bg-primary px-sm py-xs text-xs text-primary-foreground disabled:opacity-50"
className="rounded-md bg-primary px-2 py-1 text-xs text-primary-foreground disabled:opacity-50"
>
</button>
<button
type="button"
onClick={handleCancel}
className="rounded-md bg-muted px-sm py-xs text-xs text-foreground"
className="rounded-md bg-muted px-2 py-1 text-xs text-foreground"
>
</button>
@@ -196,18 +196,18 @@ export default function SchedulingRules(
}
return (
<tr key={rule.id} className="border-b border">
<td className="py-xs text-foreground">
<td className="py-1 text-foreground">
{dayName(rule.dayOfWeek)}
</td>
<td className="py-xs text-foreground">{rule.periods}</td>
<td className="py-xs text-foreground">{rule.subject}</td>
<td className="py-xs text-foreground">{rule.teacherId}</td>
<td className="py-xs text-foreground">{rule.room}</td>
<td className="py-xs">
<td className="py-1 text-foreground">{rule.periods}</td>
<td className="py-1 text-foreground">{rule.subject}</td>
<td className="py-1 text-foreground">{rule.teacherId}</td>
<td className="py-1 text-foreground">{rule.room}</td>
<td className="py-1">
<button
type="button"
onClick={() => handleEdit(rule)}
className="rounded-md bg-muted px-sm py-xs text-xs text-foreground"
className="rounded-md bg-muted px-2 py-1 text-xs text-foreground"
>
</button>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* textbook-managerteacher / main
@@ -41,41 +41,41 @@ export default function TextbookManager(
};
return (
<section className="rounded-xl border border bg-card p-4">
<h3 className="text-heading-3 text-foreground"></h3>
<section className="rounded-xl border bg-card p-4">
<h3 className="text-lg font-semibold text-foreground"></h3>
<div className="mt-sm flex gap-4">
<label className="flex flex-col space-y-xs">
<div className="mt-2 flex gap-4">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"> ID</span>
<input
type="text"
value={subjectInput}
onChange={(e) => setSubjectInput(e.target.value)}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
placeholder="可选,输入科目 ID"
/>
</label>
<label className="flex flex-col space-y-xs">
<label className="flex flex-col space-y-1">
<span className="text-sm text-muted-foreground"></span>
<input
type="text"
value={gradeInput}
onChange={(e) => setGradeInput(e.target.value)}
className="rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md border bg-card px-2 py-1 text-sm text-foreground"
placeholder="可选,如:三年级"
/>
</label>
<button
type="button"
onClick={handleApply}
className="self-end rounded-md bg-primary px-md py-xs text-sm text-primary-foreground"
className="self-end rounded-md bg-primary px-3 py-1 text-sm text-primary-foreground"
>
</button>
</div>
<div className="mt-md flex gap-4">
<ul className="flex-1 space-y-sm">
<div className="mt-3 flex gap-4">
<ul className="flex-1 space-y-2">
{textbooks.length === 0 ? (
<li className="text-sm text-muted-foreground"></li>
) : (
@@ -86,8 +86,8 @@ export default function TextbookManager(
onClick={() => setSelectedId(t.id)}
className={
selectedId === t.id
? "w-full rounded-md border border bg-muted p-2 text-left"
: "w-full rounded-md border border bg-card p-2 text-left"
? "w-full rounded-md border bg-muted p-2 text-left"
: "w-full rounded-md border bg-card p-2 text-left"
}
>
<p className="text-body text-foreground">{t.title}</p>
@@ -105,13 +105,13 @@ export default function TextbookManager(
<div className="flex-1">
{selected ? (
<div className="rounded-xl border border bg-card p-4">
<div className="rounded-xl border bg-card p-4">
<h4 className="text-body text-foreground">{selected.title}</h4>
<p className="mt-xs text-sm text-muted-foreground">
<p className="mt-1 text-sm text-muted-foreground">
{selected.author} · {selected.publisher}
</p>
<h5 className="mt-md text-sm text-foreground"></h5>
<ol className="mt-xs space-y-xs">
<h5 className="mt-3 text-sm text-foreground"></h5>
<ol className="mt-1 space-y-1">
{selected.chapters.map((c, i) => (
<li key={c.id} className="text-sm text-foreground">
{i + 1}. {c.title}
@@ -120,7 +120,7 @@ export default function TextbookManager(
</ol>
</div>
) : (
<div className="rounded-xl border border bg-card p-4 text-sm text-muted-foreground">
<div className="rounded-xl border bg-card p-4 text-sm text-muted-foreground">
</div>
)}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* global-searchtopbar / top
@@ -49,10 +49,10 @@ export default function GlobalSearch(_props: PluginProps): React.ReactElement {
onFocus={() => setOpen(true)}
placeholder="搜索学生、班级、考试…"
aria-label="全局搜索"
className="w-72 rounded-md border border bg-card px-sm py-xs text-sm text-foreground"
className="w-72 rounded-md border bg-card px-2 py-1 text-sm text-foreground"
/>
{open && keyword.length > 0 ? (
<ul className="absolute right-0 z-50 mt-sm w-72 rounded-xl border border bg-card p-2 shadow-md">
<ul className="absolute right-0 z-50 mt-2 w-72 rounded-xl border bg-card p-2 shadow-md">
{results.length === 0 ? (
<li className="text-sm text-muted-foreground"></li>
) : (
@@ -61,7 +61,7 @@ export default function GlobalSearch(_props: PluginProps): React.ReactElement {
<button
type="button"
onClick={() => handleSelect(item)}
className="flex w-full flex-col border-b border py-xs text-left"
className="flex w-full flex-col border-b border py-1 text-left"
>
<span className="text-sm text-foreground">{item.title}</span>
<span className="text-xs text-muted-foreground">

View File

@@ -44,12 +44,12 @@ export default function LocaleSwitcher(
aria-label={t("common.locale.label")}
aria-expanded={open}
onClick={() => setOpen((v) => !v)}
className="rounded-md bg-card px-sm py-xs text-sm text-foreground"
className="rounded-md bg-card px-2 py-1 text-sm text-foreground"
>
{locale}
</button>
{open ? (
<ul className="absolute right-0 z-50 mt-sm w-40 rounded-xl border border bg-card p-2 shadow-md">
<ul className="absolute right-0 z-50 mt-2 w-40 rounded-xl border bg-card p-2 shadow-md">
{LOCALES.map((item) => (
<li key={item.value}>
<button
@@ -57,8 +57,8 @@ export default function LocaleSwitcher(
onClick={() => handleSelect(item.value)}
className={
item.value === locale
? "w-full rounded-md bg-primary px-sm py-xs text-left text-sm text-primary-foreground"
: "w-full rounded-md px-sm py-xs text-left text-sm text-foreground"
? "w-full rounded-md bg-primary px-2 py-1 text-left text-sm text-primary-foreground"
: "w-full rounded-md px-2 py-1 text-left text-sm text-foreground"
}
>
{t(item.labelKey)}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* notification-belltopbar / top
@@ -30,24 +30,24 @@ export default function NotificationBell(
aria-label="通知"
aria-expanded={open}
onClick={() => setOpen((v) => !v)}
className="relative rounded-md bg-card px-sm py-xs text-foreground"
className="relative rounded-md bg-card px-2 py-1 text-foreground"
>
<span aria-hidden>🔔</span>
{items.length > 0 ? (
<span className="absolute -right-xs -top-xs rounded-full bg-danger px-xs text-xs text-primary-foreground">
<span className="absolute -right-xs -top-xs rounded-full bg-destructive px-1 text-xs text-primary-foreground">
{items.length}
</span>
) : null}
</button>
{open ? (
<ul className="absolute right-0 z-50 mt-sm w-64 rounded-xl border border bg-card p-2 shadow-md">
<ul className="absolute right-0 z-50 mt-2 w-64 rounded-xl border bg-card p-2 shadow-md">
{items.length === 0 ? (
<li className="text-sm text-muted-foreground"></li>
) : (
items.map((n) => (
<li
key={n.id}
className="border-b border py-xs text-sm text-foreground"
className="border-b border py-1 text-sm text-foreground"
>
{n.title}
</li>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* user-menutopbar / top
@@ -30,7 +30,7 @@ export default function UserMenu(_props: PluginProps): React.ReactElement {
aria-label="用户菜单"
aria-expanded={open}
onClick={() => setOpen((v) => !v)}
className="flex items-center gap-2 rounded-md bg-card px-sm py-xs text-foreground"
className="flex items-center gap-2 rounded-md bg-card px-2 py-1 text-foreground"
>
<span
aria-hidden
@@ -41,12 +41,12 @@ export default function UserMenu(_props: PluginProps): React.ReactElement {
<span className="text-sm">{displayName}</span>
</button>
{open ? (
<ul className="absolute right-0 z-50 mt-sm w-56 rounded-xl border border bg-card p-2 shadow-md">
<li className="border-b border py-xs">
<ul className="absolute right-0 z-50 mt-2 w-56 rounded-xl border bg-card p-2 shadow-md">
<li className="border-b border py-1">
<p className="text-sm text-foreground">{displayName}</p>
<p className="text-xs text-muted-foreground">{displayEmail}</p>
</li>
<li className="py-xs text-sm text-muted-foreground">
<li className="py-1 text-sm text-muted-foreground">
{displayRole}
</li>
</ul>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
/**
* notifications-widgetuniversal / main
@@ -23,20 +23,20 @@ function TypeBadge({ type }: { type: string }): React.ReactElement {
const label = TYPE_LABEL[type] ?? type;
if (type === "urgent") {
return (
<span className="rounded-md bg-danger px-sm py-xs text-xs text-primary-foreground">
<span className="rounded-md bg-destructive px-2 py-1 text-xs text-primary-foreground">
{label}
</span>
);
}
if (type === "warning") {
return (
<span className="rounded-md bg-primary px-sm py-xs text-xs text-primary-foreground">
<span className="rounded-md bg-primary px-2 py-1 text-xs text-primary-foreground">
{label}
</span>
);
}
return (
<span className="rounded-md bg-muted px-sm py-xs text-xs text-muted-foreground">
<span className="rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
{label}
</span>
);
@@ -58,26 +58,26 @@ export default function NotificationsWidget(
const total = data?.total ?? 0;
return (
<section className="rounded-xl border border bg-card p-4">
<section className="rounded-xl border bg-card p-4">
<div className="flex">
<h3 className="flex-1 text-heading-3 text-foreground"></h3>
<h3 className="flex-1 text-lg font-semibold text-foreground"></h3>
<span className="text-sm text-muted-foreground"> {total} </span>
</div>
{items.length === 0 ? (
<p className="mt-sm text-sm text-muted-foreground"></p>
<p className="mt-2 text-sm text-muted-foreground"></p>
) : (
<ul className="mt-sm space-y-sm">
<ul className="mt-2 space-y-2">
{items.map((item) => (
<li
key={item.id}
className="border-b border py-xs text-sm text-foreground"
className="border-b border py-1 text-sm text-foreground"
>
<div className="flex items-center gap-4">
<span className="flex-1">{item.title}</span>
<TypeBadge type={item.type} />
</div>
<p className="mt-xs text-xs text-muted-foreground">{item.body}</p>
<p className="mt-xs text-xs text-muted-foreground">
<p className="mt-1 text-xs text-muted-foreground">{item.body}</p>
<p className="mt-1 text-xs text-muted-foreground">
{item.createdAt}
</p>
</li>