fix(ui): a longer label fits its control — pill value truncates, headers get their room (E30 S5)

Found in the Hungarian UAT: a toolbar SelectTrigger's text value wrapped out of the pill in a narrow
place (the playlists rail) — it now truncates in its own span; the Channels "Prioritás" and "Utolsó
feltöltés" headers were clipped by their fixed columns — sized for the label, funnel and sort arrow;
a sortable header is at least 24px wide (Hungarian "Ki" was a 12px target). PRE-EXISTING, any
language: the messages unlock button and "Forgot passphrase" sat flush — one gapped row.
This commit is contained in:
2026-09-30 01:08:29 +02:00
parent 12077fbebc
commit 95b94a1d72
4 changed files with 28 additions and 15 deletions
+11 -8
View File
@@ -168,15 +168,18 @@ export default function KeyGate({ meId, compact = false }: { meId: number; compa
{err}
</div>
)}
<Button variant="primary" size="md" onClick={submit} disabled={busy || !pass}>
<Lock className="w-4 h-4" />
{configured ? t("messages.unlock") : t("messages.setUp")}
</Button>
{configured && (
<Button variant="link-muted" onClick={toReset} className="text-xs">
{t("messages.forgotPass")}
{/* One row with a gap: as two bare inline buttons the link sat flush against the primary. */}
<div className="flex flex-wrap items-center gap-3">
<Button variant="primary" size="md" onClick={submit} disabled={busy || !pass}>
<Lock className="w-4 h-4" />
{configured ? t("messages.unlock") : t("messages.setUp")}
</Button>
)}
{configured && (
<Button variant="link-muted" onClick={toReset} className="text-xs">
{t("messages.forgotPass")}
</Button>
)}
</div>
{!configured && !compact && <p className="text-xs text-muted">{t("messages.setupWarn")}</p>}
</div>
);
+4 -2
View File
@@ -93,7 +93,9 @@ export function buildChannelColumns(deps: ChannelColumnDeps): Column<ManagedChan
key: "priority",
header: t("channels.cols.priority"),
align: "center",
width: "5.5rem",
// Sized to the longer language's label + the funnel + a sort arrow ("Prioritás ▼ ⧩"): fixed layout
// clips a header that outgrows its column instead of widening it.
width: "7.5rem",
// Off on a tablet-wide table, with About and Length: the row's "…" menu sets the priority too.
tabletHidden: true,
sortable: true,
@@ -155,7 +157,7 @@ export function buildChannelColumns(deps: ChannelColumnDeps): Column<ManagedChan
key: "lastUpload",
header: t("channels.cols.lastUpload"),
nowrap: true,
width: "6rem",
width: "8rem", // "Utolsó feltöltés" + a sort arrow, as the priority column above
sortable: true,
sortValue: (c) => (c.last_video_at ? new Date(c.last_video_at).getTime() : 0),
render: (c) => <RelativeTime at={c.last_video_at} empty="—" className="text-muted" />,
+10 -4
View File
@@ -33,7 +33,7 @@ export const SelectTrigger = forwardRef<
ref={ref}
type="button"
className={clsx(
"glass-hover inline-flex shrink-0 items-center gap-1.5 border text-left text-sm transition touch-target",
"glass-hover inline-flex max-w-full shrink-0 items-center gap-1.5 border text-left text-sm whitespace-nowrap transition touch-target",
context === "toolbar"
? "glass-card h-7 rounded-full px-3"
: "h-8 w-full rounded-lg bg-transparent px-2.5",
@@ -43,17 +43,23 @@ export const SelectTrigger = forwardRef<
{...rest}
>
{icon}
{label != null && <span className="text-muted">{label}:</span>}
{label != null && <span className="shrink-0 text-muted">{label}:</span>}
{/* The toolbar value is a row, whatever the caller puts in it: an <svg> is display:block under the
preflight, and in a plain span it broke the value onto its own line (the saved-view chip,
E28). The form value is text that truncates, which a flex box would not do. */}
E28). The form value is text that truncates, which a flex box would not do — nor would a bare
text node in the toolbar row, so a text value gets its own truncating span: in a narrow place
(the playlists rail) a long value ("Rendezés: Egyéni sorrend") used to wrap out of the pill. */}
<span
className={clsx(
"text-fg",
context === "form" ? "flex-1 truncate" : "flex min-w-0 items-center gap-1.5",
)}
>
{children}
{context === "toolbar" && typeof children === "string" ? (
<span className="truncate">{children}</span>
) : (
children
)}
</span>
{trailing}
<ChevronDown
+3 -1
View File
@@ -229,10 +229,12 @@ export function ColumnHead<T>({
{/* Only a sortable header is a control. A non-sortable one used to render a
focusable no-op button, which put unlabeled dead stops in the tab order —
two of them per playlist table, whose grip and actions headers are empty. */}
{/* min-w-6 as well as min-h-6: a short header ("Ki", Hungarian "Who") was a 12px
target beside its funnel, under the 24px floor. */}
{col.sortable ? (
<Pressable
onClick={() => onToggleSort(col.key)}
className="inline-flex min-h-6 items-center gap-1 hover:text-fg cursor-pointer touch-target"
className="inline-flex min-h-6 min-w-6 items-center gap-1 hover:text-fg cursor-pointer touch-target"
>
{col.header}
{isSorted &&