feat(design): glass surfaces by role, the static Badge, LinkButton, user pickers on the Combobox (E28 S3d1)
This commit is contained in:
@@ -11,9 +11,10 @@ import { Button } from "./ui/Button";
|
||||
import { StatusPill } from "./downloadRow";
|
||||
import { jobTitle } from "./downloadTitle";
|
||||
import { IconButton } from "./ui/IconButton";
|
||||
import { Pressable } from "./ui/Pressable";
|
||||
import { Input } from "./ui/Input";
|
||||
import { CheckRow } from "./ui/NativeControls";
|
||||
import { SURFACE } from "./ui/surfaces";
|
||||
import UserPicker from "./UserPicker";
|
||||
|
||||
// The admin "System" tab — storage overview, per-user footprint, quota editor, and the global job
|
||||
// list. Split out of DownloadCenter (self-contained, admin-only surface).
|
||||
@@ -137,52 +138,17 @@ function QuotaModal({
|
||||
// footprint list below only shows users who already have files).
|
||||
function QuotaUserPicker({ onPick }: { onPick: (u: { id: number; email: string }) => void }) {
|
||||
const { t } = useTranslation();
|
||||
const [q, setQ] = useState("");
|
||||
const [open, setOpen] = useState(false);
|
||||
const users = useQuery({ queryKey: qk.adminUsers(), queryFn: api.adminUsers });
|
||||
const list = (users.data ?? []).filter((u) => !u.is_demo);
|
||||
const filtered = useMemo(() => {
|
||||
const s = q.trim().toLowerCase();
|
||||
const base = s
|
||||
? list.filter(
|
||||
(u) =>
|
||||
u.email.toLowerCase().includes(s) || (u.display_name ?? "").toLowerCase().includes(s),
|
||||
)
|
||||
: list;
|
||||
return base.slice(0, 8);
|
||||
}, [q, list]);
|
||||
const list = useMemo(() => (users.data ?? []).filter((u) => !u.is_demo), [users.data]);
|
||||
return (
|
||||
<div className="relative w-full sm:w-72">
|
||||
<Input
|
||||
className="w-full"
|
||||
value={q}
|
||||
onChange={(e) => {
|
||||
setQ(e.target.value);
|
||||
setOpen(true);
|
||||
}}
|
||||
onFocus={() => setOpen(true)}
|
||||
onBlur={() => setTimeout(() => setOpen(false), 150)}
|
||||
<div className="w-full sm:w-72">
|
||||
<UserPicker
|
||||
users={list}
|
||||
loading={users.isLoading}
|
||||
onPick={(u) => onPick({ id: u.id, email: u.email })}
|
||||
label={t("downloads.admin.quotaPickPlaceholder")}
|
||||
placeholder={t("downloads.admin.quotaPickPlaceholder")}
|
||||
/>
|
||||
{open && filtered.length > 0 && (
|
||||
<div className="absolute z-base mt-1 w-full rounded-lg glass-menu overflow-hidden">
|
||||
{filtered.map((u) => (
|
||||
<Pressable
|
||||
key={u.id}
|
||||
layer
|
||||
onClick={() => {
|
||||
onPick({ id: u.id, email: u.email });
|
||||
setQ("");
|
||||
setOpen(false);
|
||||
}}
|
||||
className="w-full px-3 py-2 text-sm flex flex-col"
|
||||
>
|
||||
<span className="truncate">{u.display_name || u.email}</span>
|
||||
{u.display_name && <span className="text-xs text-muted truncate">{u.email}</span>}
|
||||
</Pressable>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -205,7 +171,7 @@ export function AdminSystem() {
|
||||
s.total_cap_bytes ? formatBytes(s.total_cap_bytes) : t("downloads.admin.noCap"),
|
||||
],
|
||||
].map(([label, val]) => (
|
||||
<div key={label} className="rounded-xl glass-card p-3">
|
||||
<div key={label} className={`${SURFACE.card} p-3`}>
|
||||
<div className="text-xs text-muted">{label}</div>
|
||||
<div className="text-lg font-semibold mt-0.5">{val}</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user