fix(web): unify display-field spacing (shared Field) + clearer layout help
- Every option in the custom form now renders through one `Field` wrapper (label → control → help at a consistent `space-y-3`), so the label→input gap is roomier and identical across keep-alive, the button groups, and max-displays — the first field no longer spaces differently from the rest. - Reworded the multi-monitor layout help: it now says Auto is side-by-side and Manual gives a per-display X/Y editor "in the Live displays section below once two or more are streaming" — instead of pointing at an "arrangement table" that isn't visible until clients connect. web tsc + vite build + biome-lint green; deployed on .21. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -100,7 +100,7 @@
|
|||||||
"display_identity_per_client": "Pro Client",
|
"display_identity_per_client": "Pro Client",
|
||||||
"display_identity_per_client_mode": "Pro Client + Auflösung",
|
"display_identity_per_client_mode": "Pro Client + Auflösung",
|
||||||
"display_layout_mode": "Multi-Monitor-Anordnung",
|
"display_layout_mode": "Multi-Monitor-Anordnung",
|
||||||
"display_layout_help": "Wie mehrere Anzeigen auf dem Desktop angeordnet werden. Manuell nutzt die Anordnungstabelle unten (ab 2 Anzeigen).",
|
"display_layout_help": "Automatisch ordnet die Anzeigen nebeneinander an (links nach rechts). Manuell: Du platzierst jede selbst — ein X/Y-Editor pro Anzeige erscheint im Abschnitt „Aktive Displays“ unten, sobald zwei oder mehr streamen.",
|
||||||
"display_layout_auto_row": "Automatisch (nebeneinander)",
|
"display_layout_auto_row": "Automatisch (nebeneinander)",
|
||||||
"display_layout_manual": "Manuell",
|
"display_layout_manual": "Manuell",
|
||||||
"clients_title": "Gekoppelte Geräte",
|
"clients_title": "Gekoppelte Geräte",
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
"display_identity_per_client": "Per client",
|
"display_identity_per_client": "Per client",
|
||||||
"display_identity_per_client_mode": "Per client + resolution",
|
"display_identity_per_client_mode": "Per client + resolution",
|
||||||
"display_layout_mode": "Multi-monitor layout",
|
"display_layout_mode": "Multi-monitor layout",
|
||||||
"display_layout_help": "How several displays are arranged on the desktop. Manual uses the arrangement table below (with 2+ displays).",
|
"display_layout_help": "Auto lays displays out side by side, left to right. Manual: you position each one yourself — a per-display X/Y editor appears in the Live displays section below once two or more are streaming.",
|
||||||
"display_layout_auto_row": "Auto (side by side)",
|
"display_layout_auto_row": "Auto (side by side)",
|
||||||
"display_layout_manual": "Manual",
|
"display_layout_manual": "Manual",
|
||||||
"clients_title": "Paired clients",
|
"clients_title": "Paired clients",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { Button } from "@unom/ui/button";
|
import { Button } from "@unom/ui/button";
|
||||||
import { type FC, useEffect, useState } from "react";
|
import { type FC, type ReactNode, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
getGetDisplayStateQueryKey,
|
getGetDisplayStateQueryKey,
|
||||||
getGetDisplaySettingsQueryKey,
|
getGetDisplaySettingsQueryKey,
|
||||||
@@ -210,8 +210,7 @@ const DisplayForm: FC<{
|
|||||||
{/* Custom: every option by hand */}
|
{/* Custom: every option by hand */}
|
||||||
{isCustom && (
|
{isCustom && (
|
||||||
<div className="space-y-6 rounded-lg border p-5">
|
<div className="space-y-6 rounded-lg border p-5">
|
||||||
<div className="space-y-2.5">
|
<Field label={m.display_keep_alive()} help={m.display_keep_alive_help()}>
|
||||||
<Label>{m.display_keep_alive()}</Label>
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -251,8 +250,7 @@ const DisplayForm: FC<{
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">{m.display_keep_alive_help()}</p>
|
</Field>
|
||||||
</div>
|
|
||||||
|
|
||||||
<Choice
|
<Choice
|
||||||
label={m.display_topology()}
|
label={m.display_topology()}
|
||||||
@@ -296,10 +294,8 @@ const DisplayForm: FC<{
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="space-y-2.5">
|
<Field label={m.display_max()}>
|
||||||
<Label htmlFor="disp-max">{m.display_max()}</Label>
|
|
||||||
<Input
|
<Input
|
||||||
id="disp-max"
|
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
max={16}
|
max={16}
|
||||||
@@ -313,7 +309,7 @@ const DisplayForm: FC<{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Field>
|
||||||
|
|
||||||
<div className="border-t pt-4">
|
<div className="border-t pt-4">
|
||||||
<Button onClick={() => apply(draft)} disabled={busy}>
|
<Button onClick={() => apply(draft)} disabled={busy}>
|
||||||
@@ -340,7 +336,21 @@ const DisplayForm: FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** A labeled row of mutually-exclusive option buttons (topology / conflict / identity / layout). */
|
/** A labeled config field — label, then the control, then optional help. The single source of the
|
||||||
|
* label→control→help spacing so every field (keep-alive, the button groups, max-displays) lines up. */
|
||||||
|
const Field: FC<{ label: string; help?: string; children: ReactNode }> = ({
|
||||||
|
label,
|
||||||
|
help,
|
||||||
|
children,
|
||||||
|
}) => (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label className="block">{label}</Label>
|
||||||
|
{children}
|
||||||
|
{help && <p className="text-xs text-muted-foreground">{help}</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
/** A [`Field`] whose control is a row of mutually-exclusive option buttons (topology / conflict / …). */
|
||||||
const Choice: FC<{
|
const Choice: FC<{
|
||||||
label: string;
|
label: string;
|
||||||
help?: string;
|
help?: string;
|
||||||
@@ -350,8 +360,7 @@ const Choice: FC<{
|
|||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
onPick: (v: string) => void;
|
onPick: (v: string) => void;
|
||||||
}> = ({ label, help, value, options, labels, disabled, onPick }) => (
|
}> = ({ label, help, value, options, labels, disabled, onPick }) => (
|
||||||
<div className="space-y-2.5">
|
<Field label={label} help={help}>
|
||||||
<Label>{label}</Label>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{options.map((o) => (
|
{options.map((o) => (
|
||||||
<Button
|
<Button
|
||||||
@@ -365,8 +374,7 @@ const Choice: FC<{
|
|||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{help && <p className="text-xs text-muted-foreground">{help}</p>}
|
</Field>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user