fix(apple/tvOS): focus-native home grid, separated actions, Form-free dialogs
ci / rust (push) Has been cancelled

Three more tvOS-isms, all the same lesson — let the focus engine own the chrome:

- Host cards drew their own material platter + accent ring INSIDE the .card button
  style, muting the native grow/tilt focus motion. On tvOS the card style now owns the
  platter outright (material/ring stay on the pointer platforms), and the grid gets
  48 pt spacing so the focused card swells without overlapping siblings.
- Add Host and Settings no longer sit in the hosts row: they're a compact button row
  below the grid (and the empty state gains a Settings button, since tvOS has no
  toolbar).
- The Add Host and pairing dialogs drop Form entirely on tvOS — list rows added a
  full-width focus fill plus a row platter behind every field's own pill (the
  "second outer pill"). As standalone fields in a centered dialog over the dimmed
  home, each input is exactly one pill with vertically centered text.

Verified by screenshot in the Apple TV simulator (home grid + Add Host dialog).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 13:47:27 +02:00
parent 9e57a5a1ff
commit f292b3fe3a
3 changed files with 107 additions and 40 deletions
@@ -12,6 +12,28 @@ struct AddHostSheet: View {
let onAdd: (StoredHost) -> Void
var body: some View {
#if os(tvOS)
// No Form here: tvOS list rows add a full-width focus fill + row platter
// behind the field's own pill. Standalone fields have exactly one pill.
VStack(spacing: 28) {
Text("Add Host")
.font(.title3.weight(.semibold))
TextField("Name", text: $name, prompt: Text("Optional — e.g. Living Room"))
.labelsHidden()
TextField("Address", text: $address, prompt: Text("IP or hostname"))
.labelsHidden()
TextField("Port", value: $port, format: .number.grouping(.never))
.labelsHidden()
HStack(spacing: 32) {
Button("Cancel", role: .cancel) { dismiss() }
Button("Add Host") { add() }
.disabled(address.trimmingCharacters(in: .whitespaces).isEmpty)
}
.padding(.top, 12)
}
.frame(maxWidth: 1000)
.padding(60)
#else
VStack(spacing: 0) {
Form {
TextField("Name", text: $name, prompt: Text("Optional — e.g. Living Room"))
@@ -33,13 +55,7 @@ struct AddHostSheet: View {
.keyboardShortcut(.cancelAction)
#endif
Spacer()
Button("Add Host") {
onAdd(StoredHost(
name: name.trimmingCharacters(in: .whitespaces),
address: address.trimmingCharacters(in: .whitespaces),
port: UInt16(clamping: port)))
dismiss()
}
Button("Add Host") { add() }
.buttonStyle(.borderedProminent)
#if !os(tvOS)
.keyboardShortcut(.defaultAction)
@@ -55,5 +71,14 @@ struct AddHostSheet: View {
.frame(width: 380)
.fixedSize(horizontal: false, vertical: true)
#endif
#endif
}
private func add() {
onAdd(StoredHost(
name: name.trimmingCharacters(in: .whitespaces),
address: address.trimmingCharacters(in: .whitespaces),
port: UInt16(clamping: port)))
dismiss()
}
}