fix(settings): stop settings and admin cards widening the page on a phone

Three cards put a full sentence into the SettingsCardHeader status slot
(mood reminder, low-stock warning, the app-log refresh button), which
does not wrap: the row grew to 1000px and /settings/notifications
panned sideways at every width. The sentence moves into the card body,
and the slot is capped at the row so a long status wraps instead.

Also on a phone: the provider-chain add control wraps, the report
picker's standard-template action drops under its sentence instead of
running past the card, and the dashboard layout rows put their switches
and arrows on a second line so the tile name is no longer cut to five
characters.
This commit is contained in:
Marc-André Bombeck committed 2026-10-01 09:44:43 +02:00
1 parent dc38540573
commit 537ac3c7a1
7 files changed
+24 -14

No files matched your search

@@ -125,7 +125,7 @@ export function AppLogPreviewSection() {
<Button
variant="outline"
size="sm"
className="min-h-11 sm:min-h-9"
className="min-h-11 min-w-11 sm:min-h-9 sm:min-w-9"
onClick={() => refetch()}
disabled={isFetching}
aria-label={t("admin.section.app-logs.refresh")}
@@ -133,13 +133,12 @@ export function AppLogPreviewSection() {
<RefreshCw
className={`h-4 w-4 ${isFetching ? "animate-spin" : ""} motion-reduce:animate-none`}
/>
<p className="text-sm">
{t("admin.section.app-logs.processDetail")}
</p>
</Button>
}
/>
<p className="text-sm">{t("admin.section.app-logs.processDetail")}</p>
<div className="grid gap-2 md:grid-cols-2 lg:grid-cols-4">
<Input
type="search"
+6 -1
View File
@@ -89,7 +89,12 @@ export function SettingsCardHeader({
{titleAccessory}
</div>
{status ? (
<div className="flex shrink-0 items-center gap-2">{status}</div>
// `max-w-full` caps the slot at the row once it wraps under the
// title, so a status line longer than a phone row wraps inside
// the card instead of widening the page.
<div className="flex max-w-full shrink-0 items-center gap-2">
{status}
</div>
) : null}
</div>
{/* One sentence, one text run. This slot deliberately carries no
@@ -294,7 +294,7 @@ function AddProviderControl({
const [picked, setPicked] = useState<string>(addable[0] ?? "");
return (
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
<NativeSelect
aria-label={t("settings.ai.providerChain.addProvider")}
value={picked}
@@ -869,7 +869,7 @@ function SortableWidgetRow({
style={style}
data-slot="widget-row"
data-dragging={isDragging ? "true" : undefined}
className={`border-border bg-background/30 flex min-h-12 items-center gap-2 rounded-md border px-3 py-2 ${
className={`border-border bg-background/30 flex min-h-12 flex-wrap items-center gap-2 rounded-md border px-3 py-2 sm:flex-nowrap ${
isDragging ? "ring-primary z-10 opacity-90 shadow-lg ring-2" : ""
}`}
>
@@ -898,7 +898,12 @@ function SortableWidgetRow({
<span className="flex-1 truncate text-sm" title={labels.widgetLabel}>
{labels.widgetLabel}
</span>
<div className="flex w-12 justify-center">
{/* On a phone the two switches and two arrows (~250 px) left the name
five characters ("Gewic…"). Below `sm` the controls wrap onto a
second line, right-aligned under the column heads, and the name
keeps the first line to itself. */}
<span className="basis-full sm:hidden" aria-hidden="true" />
<div className="flex w-12 justify-center max-sm:ml-auto">
<Switch
checked={tileChecked}
onCheckedChange={(v) => onToggleTile(widget.id, v)}
+1 -2
View File
@@ -234,8 +234,6 @@ export function LowStockCard({
disabled={!isAuthenticated || saving}
aria-label={t("notifications.lowStock.toggleAria")}
/>
<p className="text-sm">{t("notifications.lowStock.detail")}</p>
<span className="text-muted-foreground text-xs">
{enabled
? t("notifications.lowStock.statusOn")
@@ -244,6 +242,7 @@ export function LowStockCard({
</label>
}
/>
<p className="text-sm">{t("notifications.lowStock.detail")}</p>
{enabled && (
<div className="flex min-h-11 items-center gap-3">
<label htmlFor="low-stock-days" className="text-sm font-medium">
@@ -193,8 +193,6 @@ export function MoodReminderCard({
disabled={!isAuthenticated || saving}
aria-label={t("notifications.moodReminder.toggleAria")}
/>
<p className="text-sm">{t("notifications.moodReminder.detail")}</p>
<span className="text-muted-foreground text-xs">
{enabled
? t("notifications.moodReminder.statusOn")
@@ -203,6 +201,7 @@ export function MoodReminderCard({
</label>
}
/>
<p className="text-sm">{t("notifications.moodReminder.detail")}</p>
{enabled && (
<div className="flex min-h-11 items-center gap-3">
<label htmlFor="mood-reminder-hour" className="text-sm font-medium">
@@ -112,8 +112,11 @@ export function ReportScopePicker({
{/* The one-click way to a complete report. An inline action row
(UI-STANDARDS §11): the sentence wraps, the action stays beside it.
It is offered, never applied — and it stays offered so a person can
come back to the standard set after experimenting. */}
<div className="flex items-start justify-between gap-3">
come back to the standard set after experimenting. On a phone the
action's own label is wider than half the row, so beside it the
sentence shrank to a one-word column and the button ran past the
card edge: there the action drops under the sentence instead. */}
<div className="flex items-start justify-between gap-3 max-sm:flex-col">
<p
id={hintId}
className="text-muted-foreground min-w-0 flex-1 text-xs"