fix(ui): flex quota rows and thin global scrollbars
Replace the fixed-table quota layout with flex rows that shrink cleanly, keep the hidden-quota chip row from overflowing, and use thin mac-like scrollbars app-wide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8b0fcf4b16
commit
15dfd86416
3 changed files with 124 additions and 96 deletions
|
|
@ -148,103 +148,100 @@ export default function QuotaTable({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="overflow-x-auto">
|
<div className="space-y-px">
|
||||||
<table className="w-full table-fixed text-left">
|
{currentPageRows.map((quota) => {
|
||||||
<tbody>
|
const colors = getColorClasses(quota.remaining);
|
||||||
{currentPageRows.map((quota) => {
|
const countdown = formatResetTime(quota.resetAt);
|
||||||
const colors = getColorClasses(quota.remaining);
|
const resetDisplay = formatResetTimeDisplay(quota.resetAt);
|
||||||
const countdown = formatResetTime(quota.resetAt);
|
// recurring defaults true: a missing flag means the quota
|
||||||
const resetDisplay = formatResetTimeDisplay(quota.resetAt);
|
// refreshes at resetAt. Bonus/one-shot packs set recurring:false
|
||||||
// recurring defaults true: a missing flag means the quota
|
// and their resetAt is a hard expiry, so word it as "expires".
|
||||||
// refreshes at resetAt. Bonus/one-shot packs set recurring:false
|
const recurring = quota.recurring !== false;
|
||||||
// and their resetAt is a hard expiry, so word it as "expires".
|
const countdownLabel = recurring ? `in ${countdown}` : `expires in ${countdown}`;
|
||||||
const recurring = quota.recurring !== false;
|
|
||||||
const countdownLabel = recurring ? `in ${countdown}` : `expires in ${countdown}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<div
|
||||||
key={`${quota.name}-${quota.index}`}
|
key={`${quota.name}-${quota.index}`}
|
||||||
className="border-b border-black/5 dark:border-white/5 hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors"
|
className={`flex items-center gap-2 border-b border-black/5 dark:border-white/5 hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors ${cellPad}`}
|
||||||
|
>
|
||||||
|
{/* Name */}
|
||||||
|
<div className="flex w-36 min-w-0 items-center gap-1.5">
|
||||||
|
<span className="text-[10px] shrink-0">{colors.emoji}</span>
|
||||||
|
<span className={`${nameText} font-medium text-text-primary truncate`}>
|
||||||
|
{quota.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress + used/total */}
|
||||||
|
<div className={`min-w-0 flex-1 ${compact ? "space-y-1" : "space-y-1.5"}`}>
|
||||||
|
<div className={`${compact ? "h-1" : "h-1.5"} rounded-full overflow-hidden border ${colors.bgLight} ${
|
||||||
|
quota.remaining === 0 ? "border-black/10 dark:border-white/10" : "border-transparent"
|
||||||
|
}`}>
|
||||||
|
<div
|
||||||
|
className={`h-full transition-all duration-300 ${colors.bg}`}
|
||||||
|
style={{ width: `${Math.min(quota.remaining, 100)}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`flex items-center justify-between gap-1 min-w-0 ${compact ? "text-[10px]" : "text-xs"}`}>
|
||||||
|
<span
|
||||||
|
className="text-text-muted truncate"
|
||||||
|
title={`${quota.used.toLocaleString()} / ${quota.total > 0 ? quota.total.toLocaleString() : "∞"}`}
|
||||||
|
>
|
||||||
|
{quota.used.toLocaleString()} / {quota.total > 0 ? quota.total.toLocaleString() : "∞"}
|
||||||
|
</span>
|
||||||
|
<span className={`font-medium ${colors.text} shrink-0`}>
|
||||||
|
{quota.remaining}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Reset time */}
|
||||||
|
<div className="min-w-0 shrink">
|
||||||
|
{countdown !== "-" || resetDisplay ? (
|
||||||
|
compact ? (
|
||||||
|
<div
|
||||||
|
className={`${resetPrimary} text-text-primary font-medium truncate`}
|
||||||
|
title={resetDisplay || ""}
|
||||||
|
>
|
||||||
|
{countdown !== "-" ? countdownLabel : resetDisplay}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="min-w-0 space-y-0.5">
|
||||||
|
{countdown !== "-" && (
|
||||||
|
<div className={`${resetPrimary} text-text-primary font-medium truncate`}>
|
||||||
|
{countdownLabel}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{resetDisplay && (
|
||||||
|
<div className={`${resetSecondary} text-text-muted truncate`}>
|
||||||
|
{resetDisplay}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className={`${resetPrimary} text-text-muted italic`}>N/A</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Hide action */}
|
||||||
|
{hasHideAction && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onHideQuota(quota)}
|
||||||
|
className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-text-muted transition-colors hover:bg-black/5 hover:text-text-primary dark:hover:bg-white/5"
|
||||||
|
title="Hide this quota row"
|
||||||
|
aria-label={`Hide quota ${quota.name}`}
|
||||||
>
|
>
|
||||||
<td className={`${cellPad} w-[30%]`}>
|
<span className="material-symbols-outlined text-[15px]">
|
||||||
<div className="flex items-center gap-1.5 min-w-0">
|
visibility_off
|
||||||
<span className="text-[10px] shrink-0">{colors.emoji}</span>
|
</span>
|
||||||
<span className={`${nameText} font-medium text-text-primary truncate`}>
|
</button>
|
||||||
{quota.name}
|
)}
|
||||||
</span>
|
</div>
|
||||||
</div>
|
);
|
||||||
</td>
|
})}
|
||||||
|
|
||||||
<td className={`${cellPad} w-[45%]`}>
|
|
||||||
<div className={compact ? "space-y-1" : "space-y-1.5"}>
|
|
||||||
<div className={`${compact ? "h-1" : "h-1.5"} rounded-full overflow-hidden border ${colors.bgLight} ${
|
|
||||||
quota.remaining === 0 ? "border-black/10 dark:border-white/10" : "border-transparent"
|
|
||||||
}`}>
|
|
||||||
<div
|
|
||||||
className={`h-full transition-all duration-300 ${colors.bg}`}
|
|
||||||
style={{ width: `${Math.min(quota.remaining, 100)}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={`flex items-center justify-between ${compact ? "text-[10px]" : "text-xs"}`}>
|
|
||||||
<span className="text-text-muted">
|
|
||||||
{quota.used.toLocaleString()} / {quota.total > 0 ? quota.total.toLocaleString() : "∞"}
|
|
||||||
</span>
|
|
||||||
<span className={`font-medium ${colors.text}`}>
|
|
||||||
{quota.remaining}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td className={`${cellPad} ${hasHideAction ? "w-[20%]" : "w-[25%]"}`}>
|
|
||||||
{countdown !== "-" || resetDisplay ? (
|
|
||||||
compact ? (
|
|
||||||
<div
|
|
||||||
className={`${resetPrimary} text-text-primary font-medium truncate`}
|
|
||||||
title={resetDisplay || ""}
|
|
||||||
>
|
|
||||||
{countdown !== "-" ? countdownLabel : resetDisplay}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
{countdown !== "-" && (
|
|
||||||
<div className={`${resetPrimary} text-text-primary font-medium`}>
|
|
||||||
{countdownLabel}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{resetDisplay && (
|
|
||||||
<div className={`${resetSecondary} text-text-muted`}>
|
|
||||||
{resetDisplay}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<div className={`${resetPrimary} text-text-muted italic`}>N/A</div>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{hasHideAction && (
|
|
||||||
<td className={`${cellPad} w-[5%] text-right`}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => onHideQuota(quota)}
|
|
||||||
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-text-muted transition-colors hover:bg-black/5 hover:text-text-primary dark:hover:bg-white/5"
|
|
||||||
title="Hide this quota row"
|
|
||||||
aria-label={`Hide quota ${quota.name}`}
|
|
||||||
>
|
|
||||||
<span className="material-symbols-outlined text-[15px]">
|
|
||||||
visibility_off
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
)}
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{totalPages > 1 && (
|
{totalPages > 1 && (
|
||||||
|
|
|
||||||
|
|
@ -1270,7 +1270,7 @@ export default function ProviderLimits() {
|
||||||
visibility_off
|
visibility_off
|
||||||
</span>
|
</span>
|
||||||
<span className="shrink-0">Hidden:</span>
|
<span className="shrink-0">Hidden:</span>
|
||||||
<div className="flex w-full items-center gap-1">
|
<div className="flex min-w-0 flex-1 items-center gap-1 overflow-x-auto whitespace-nowrap pb-2">
|
||||||
{hiddenQuotaRows.map((quotaRow) => (
|
{hiddenQuotaRows.map((quotaRow) => (
|
||||||
<button
|
<button
|
||||||
key={getQuotaVisibilityKey(quotaRow)}
|
key={getQuotaVisibilityKey(quotaRow)}
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,37 @@ input, textarea {
|
||||||
background: rgba(229, 106, 74, 0.55);
|
background: rgba(229, 106, 74, 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Global thin mac-like scrollbars (overrides clunky Windows/Linux default) */
|
||||||
|
* {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(120, 120, 120, 0.35) transparent;
|
||||||
|
}
|
||||||
|
.dark * {
|
||||||
|
scrollbar-color: rgba(180, 180, 180, 0.3) transparent;
|
||||||
|
}
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(120, 120, 120, 0.35);
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
*::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(120, 120, 120, 0.55);
|
||||||
|
}
|
||||||
|
.dark *::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(180, 180, 180, 0.3);
|
||||||
|
}
|
||||||
|
.dark *::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(180, 180, 180, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
/* Reusable elevated card */
|
/* Reusable elevated card */
|
||||||
.card-soft {
|
.card-soft {
|
||||||
background-color: var(--color-surface);
|
background-color: var(--color-surface);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue