feat(glass): glassify modal inner panels, dropdowns & list rows (Phase 2)

Medium container surfaces -> the glass system so they refract the backdrop
consistently: Playlists row (bg-card->glass-card), DownloadCenter user-picker +
ShareDialog share-picker dropdowns (bg-card->glass-menu), DownloadCenter stat
cards / download rows / usage block, ShareDialog share-link block, VideoEditor
selection bar, ProfileEditor profile rows (bg-card/40|bg-surface/60->glass-card).
Tight admin rows and tiny inputs left solid on purpose.
This commit is contained in:
npeter83 2026-07-12 22:54:13 +02:00
parent 7fd5f20490
commit b09923661a
5 changed files with 10 additions and 10 deletions

View file

@ -189,7 +189,7 @@ function DownloadRow({
const { t } = useTranslation(); const { t } = useTranslation();
const running = job.status === "running"; const running = job.status === "running";
return ( return (
<div className="flex gap-3 p-2.5 rounded-xl bg-card/40 hover:bg-card transition"> <div className="flex gap-3 p-2.5 rounded-xl glass-card glass-hover transition">
<Thumb job={job} /> <Thumb job={job} />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="font-medium leading-snug line-clamp-1 flex items-center gap-1.5"> <div className="font-medium leading-snug line-clamp-1 flex items-center gap-1.5">
@ -338,7 +338,7 @@ function UsageBar() {
if (!u) return null; if (!u) return null;
const pct = u.unlimited || !u.max_bytes ? 0 : Math.min(100, (u.footprint_bytes / u.max_bytes) * 100); const pct = u.unlimited || !u.max_bytes ? 0 : Math.min(100, (u.footprint_bytes / u.max_bytes) * 100);
return ( return (
<div className="rounded-xl bg-card/40 p-3 mb-4"> <div className="rounded-xl glass-card p-3 mb-4">
<div className="flex justify-between text-sm mb-1.5"> <div className="flex justify-between text-sm mb-1.5">
<span className="font-medium">{t("downloads.usage.title")}</span> <span className="font-medium">{t("downloads.usage.title")}</span>
<span className="text-muted"> <span className="text-muted">
@ -446,7 +446,7 @@ function QuotaUserPicker({ onPick }: { onPick: (u: { id: number; email: string }
className={inputCls} className={inputCls}
/> />
{open && filtered.length > 0 && ( {open && filtered.length > 0 && (
<div className="absolute z-10 mt-1 w-full rounded-lg border border-border bg-card shadow-xl overflow-hidden"> <div className="absolute z-10 mt-1 w-full rounded-lg glass-menu shadow-xl overflow-hidden">
{filtered.map((u) => ( {filtered.map((u) => (
<button <button
key={u.id} key={u.id}
@ -482,7 +482,7 @@ function AdminSystem() {
[t("downloads.admin.totalSize"), formatBytes(s.total_bytes)], [t("downloads.admin.totalSize"), formatBytes(s.total_bytes)],
[t("downloads.admin.cap"), s.total_cap_bytes ? formatBytes(s.total_cap_bytes) : t("downloads.admin.noCap")], [t("downloads.admin.cap"), s.total_cap_bytes ? formatBytes(s.total_cap_bytes) : t("downloads.admin.noCap")],
].map(([label, val]) => ( ].map(([label, val]) => (
<div key={label} className="rounded-xl bg-card/40 p-3"> <div key={label} className="rounded-xl glass-card p-3">
<div className="text-xs text-muted">{label}</div> <div className="text-xs text-muted">{label}</div>
<div className="text-lg font-semibold mt-0.5">{val}</div> <div className="text-lg font-semibold mt-0.5">{val}</div>
</div> </div>

View file

@ -123,7 +123,7 @@ function Row({
<div <div
ref={setNodeRef} ref={setNodeRef}
style={style} style={style}
className="flex items-center gap-2.5 p-2 rounded-lg border border-border bg-card" className="flex items-center gap-2.5 p-2 rounded-lg glass-card glass-hover"
> >
{readOnly ? ( {readOnly ? (
<span className="w-4" /> <span className="w-4" />

View file

@ -97,7 +97,7 @@ export default function ProfileEditor({ onClose }: { onClose: () => void }) {
<div className="space-y-1.5 mb-4"> <div className="space-y-1.5 mb-4">
<div className="text-xs uppercase tracking-wide text-muted">{t("downloads.profiles.builtin")}</div> <div className="text-xs uppercase tracking-wide text-muted">{t("downloads.profiles.builtin")}</div>
{builtins.map((p) => ( {builtins.map((p) => (
<div key={p.id} className="flex items-center gap-2 text-sm px-3 py-1.5 rounded-lg bg-surface/60"> <div key={p.id} className="flex items-center gap-2 text-sm px-3 py-1.5 rounded-lg glass-card">
<Lock className="w-3.5 h-3.5 text-muted shrink-0" /> <Lock className="w-3.5 h-3.5 text-muted shrink-0" />
<span className="flex-1 truncate">{p.name}</span> <span className="flex-1 truncate">{p.name}</span>
</div> </div>
@ -106,7 +106,7 @@ export default function ProfileEditor({ onClose }: { onClose: () => void }) {
<div className="text-xs uppercase tracking-wide text-muted pt-2">{t("downloads.profiles.yours")}</div> <div className="text-xs uppercase tracking-wide text-muted pt-2">{t("downloads.profiles.yours")}</div>
)} )}
{mine.map((p) => ( {mine.map((p) => (
<div key={p.id} className="flex items-center gap-2 text-sm px-3 py-1.5 rounded-lg bg-surface/60"> <div key={p.id} className="flex items-center gap-2 text-sm px-3 py-1.5 rounded-lg glass-card">
<span className="flex-1 truncate">{p.name}</span> <span className="flex-1 truncate">{p.name}</span>
<button onClick={() => startEdit(p)} title={t("downloads.actions.rename")} className="p-1 rounded hover:bg-surface text-muted hover:text-fg"> <button onClick={() => startEdit(p)} title={t("downloads.actions.rename")} className="p-1 rounded hover:bg-surface text-muted hover:text-fg">
<Pencil className="w-3.5 h-3.5" /> <Pencil className="w-3.5 h-3.5" />

View file

@ -56,7 +56,7 @@ function UserShare({ job }: { job: DownloadJob }) {
className={inputCls} className={inputCls}
/> />
{open && filtered.length > 0 && ( {open && filtered.length > 0 && (
<div className="absolute z-10 mt-1 w-full rounded-lg border border-border bg-card shadow-xl overflow-hidden"> <div className="absolute z-10 mt-1 w-full rounded-lg glass-menu shadow-xl overflow-hidden">
{filtered.map((u) => ( {filtered.map((u) => (
<button <button
key={u.id} key={u.id}
@ -110,7 +110,7 @@ function LinkRow({ link, onChanged }: { link: ShareLink; onChanged: () => void }
if (link.expires_at) badges.push(t("downloads.share.expiresOn", { date: new Date(link.expires_at).toLocaleDateString() })); if (link.expires_at) badges.push(t("downloads.share.expiresOn", { date: new Date(link.expires_at).toLocaleDateString() }));
return ( return (
<div className="rounded-lg bg-card/40 p-2.5 space-y-2"> <div className="rounded-lg glass-card p-2.5 space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Link2 className="w-4 h-4 text-muted shrink-0" /> <Link2 className="w-4 h-4 text-muted shrink-0" />
<input readOnly value={fullUrl} className="flex-1 min-w-0 bg-transparent text-xs text-muted truncate outline-none" /> <input readOnly value={fullUrl} className="flex-1 min-w-0 bg-transparent text-xs text-muted truncate outline-none" />

View file

@ -443,7 +443,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
{/* selected-segment editor */} {/* selected-segment editor */}
{sel && ( {sel && (
<div className="flex flex-wrap items-center gap-2 text-sm rounded-lg bg-card/40 px-3 py-2"> <div className="flex flex-wrap items-center gap-2 text-sm rounded-lg glass-card px-3 py-2">
<span className="font-medium">{t("editor.segment", { n: selIdx + 1 })}</span> <span className="font-medium">{t("editor.segment", { n: selIdx + 1 })}</span>
<button <button
onClick={() => toggleKeep(sel.id)} onClick={() => toggleKeep(sel.id)}