From e5a69b19d477d1d32d790b92689e10911154488e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 23 Oct 2026 22:48:08 +0300 Subject: [PATCH] fix(search-bar): style the typed text and drive Cancel from focus The field styled only its prompt. Typed text fell back to the system body font in `.primary`, so the line grew from 15pt to 17pt on the first keystroke and did not use the design-system colour. It now uses `.dashFont(.subhead)` and `Color.dash.primaryText`, the same modifiers as `AddressFieldView`. The Cancel button followed a `@State` copy of the focus state, synced in `onAppear` and `onChange`, and each focus change was animated three times. It now reads `isFocused` directly, with one `.animation(value:)`. Co-Authored-By: Claude Opus 5.5 --- Sources/DashUIKit/Components/SearchBar.swift | 34 +++++++++++--------- docs/buttons-and-inputs.md | 6 ++-- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Sources/DashUIKit/Components/SearchBar.swift b/Sources/DashUIKit/Components/SearchBar.swift index 97ca5f4..cded48d 100644 --- a/Sources/DashUIKit/Components/SearchBar.swift +++ b/Sources/DashUIKit/Components/SearchBar.swift @@ -34,7 +34,6 @@ public struct SearchBar: View { private let placeholder: String @FocusState private var isFocused: Bool - @State private var isEditing: Bool = false public init( text: Binding, @@ -58,20 +57,12 @@ public struct SearchBar: View { .background(Color.dash.searchBackground) .clipShape(RoundedRectangle(cornerRadius: Layout.fieldCornerRadius, style: .continuous)) - if isEditing { + if isFocused { cancelButton .transition(.move(edge: .trailing).combined(with: .opacity)) } } - .animation(.easeInOut(duration: Layout.animationDuration), value: isEditing) - .onAppear { - isEditing = isFocused - } - .onChange(of: isFocused) { _, focused in - withAnimation(.easeInOut(duration: Layout.animationDuration)) { - isEditing = focused - } - } + .animation(.easeInOut(duration: Layout.animationDuration), value: isFocused) } private var magnifyingGlass: some View { @@ -103,9 +94,6 @@ public struct SearchBar: View { Button( action: { text = "" - withAnimation(.easeInOut(duration: Layout.animationDuration)) { - isEditing = false - } isFocused = false }, label: { @@ -130,6 +118,8 @@ public struct SearchBar: View { EmptyView() } .focused($isFocused) + .dashFont(.subhead) + .foregroundStyle(Color.dash.primaryText) .tint(Color.dash.primaryText) } } @@ -138,11 +128,25 @@ public struct SearchBar: View { #if DEBUG -#Preview { +#Preview("Empty") { @Previewable @State var text = "" SearchBar(text: $text) .padding() } +#Preview("Typed") { + @Previewable @State var text = "Dash" + SearchBar(text: $text) + .padding() +} + +#Preview("Typed, dark") { + @Previewable @State var text = "Dash" + SearchBar(text: $text) + .padding() + .background(Color.dash.primaryBackground) + .preferredColorScheme(.dark) +} + #endif // DEBUG #endif // canImport(UIKit) diff --git a/docs/buttons-and-inputs.md b/docs/buttons-and-inputs.md index 0e2cb7b..a41c673 100644 --- a/docs/buttons-and-inputs.md +++ b/docs/buttons-and-inputs.md @@ -72,8 +72,10 @@ SearchBar(text: $query, placeholder: "Search coins") // placeholder optional Behavior: -- Uses `@FocusState`; an animated **Cancel** button - slides in while editing (clears text + resigns focus on tap). +- An animated **Cancel** button slides in while the field has focus (clears text + resigns + focus on tap). +- Typed text is `.subhead` in `Color.dash.primaryText`, the same size as the placeholder. + Like every `Font.dash` token it is a fixed size and does not scale with Dynamic Type. Default placeholder is the localized "Search". Background uses `Color.dash.searchBackground`.