Skip to content

feat: add core components#4

Open
romchornyi wants to merge 1 commit into
masterfrom
import/core-components
Open

feat: add core components#4
romchornyi wants to merge 1 commit into
masterfrom
import/core-components

Conversation

@romchornyi

@romchornyi romchornyi commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Fourth migration PR — core presentational components on top of Foundation + primitives.

Components (11)

SearchBar, Toast, BottomSheet, NavigationBar, MenuItem, RadioButtonRow, DashSwitch, SystemMessageView, AddressFieldView, DashAmount, Table List/List1View

Assets added

menu-receive.disabled, menu-send.disabled (MenuItem), info-rect (SystemMessage), toast-no-wifi (Toast)

iOS 14 compat fixes applied during migration ⚠️

The source versions had latent issues that either broke the build or the declared availability:

  • SystemMessageView: added missing @available(iOS 14, macOS 11, *) (was absent → build error).
  • SystemMessageView / AddressFieldView / SearchBar / Toast: replaced iOS 16+ .rect(cornerRadius:) / .contentShape(.rect) with RoundedRectangle(cornerRadius:style:.continuous) / Rectangle() to honor the declared iOS 14/15 support.

Notes

  • swift build passes.
  • Not yet migrated: CoinSelector, EnterAmount + NumericKeyboard, Converter, TopIntro.

Summary by CodeRabbit

  • New Features
    • Added reusable address input with QR scanning, paste, clearing, multiline entry, focus styling, and validation messages.
    • Added flexible bottom sheets with filled or self-sizing layouts, navigation controls, and configurable presentation.
    • Added formatted Dash amount displays with sign options and unavailable-value handling.
    • Added switches, menu items, navigation bars, radio/checkbox rows, search bars, system messages, and toasts.
    • Added a simple label-and-value list row component.
  • Style
    • Added supporting icons for disabled menu actions, system messages, and offline notifications.

Add the core presentational components on top of Foundation + primitives:
SearchBar, Toast, BottomSheet, NavigationBar, MenuItem, RadioButtonRow,
DashSwitch, SystemMessageView, AddressFieldView, DashAmount, List1View, plus
the assets they use (menu-*.disabled, info-rect, toast-no-wifi).

iOS 14 compatibility fixes applied during migration:
- SystemMessageView: add missing @available(iOS 14, macOS 11, *) (was absent,
  broke the build)
- SystemMessageView / AddressFieldView / SearchBar / Toast: replace iOS 16+
  .rect(cornerRadius:) and .contentShape(.rect) with RoundedRectangle(...,
  style: .continuous) / Rectangle() so the declared iOS 14/15 surface holds.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 13 public SwiftUI components for address/search input, navigation, sheets, menus, amounts, switches, messages, toasts, selection rows, and labeled values, plus four image-set asset definitions and debug previews.

Changes

DashUIKit component suite

Layer / File(s) Summary
Display and menu primitives
Sources/DashUIKit/Components/DashAmount.swift, Sources/DashUIKit/Components/MenuItem.swift, Sources/DashUIKit/Components/DashSwitch.swift, Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/*
Adds localized Dash amount formatting, configurable menu accessories, platform-specific switch styling, and disabled menu icon assets.
Navigation and sheet presentation
Sources/DashUIKit/Components/NavigationBar.swift, Sources/DashUIKit/Components/BottomSheet.swift
Adds composable navigation bars, navigation elements, greedy and self-sizing bottom sheets, measured detents, and presentation styling.
Address and search inputs
Sources/DashUIKit/Components/AddressFieldView.swift, Sources/DashUIKit/Components/SearchBar.swift
Adds focus-aware address entry with scan, clear, paste, error, disabled, and multiline states, plus version-specific search and cancel behavior.
System messages and toasts
Sources/DashUIKit/Components/SystemMessageView.swift, Sources/DashUIKit/Components/Toast.swift, Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/System message/info-rect.imageset/Contents.json, Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Toast/toast-no-wifi.imageset/Contents.json
Adds configurable system messages and styled toast variants with optional actions, dismissal, loading presentation, blur, and supporting assets.
Selection and list rows
Sources/DashUIKit/Components/RadioButtonRow.swift, Sources/DashUIKit/Table List/List1View.swift
Adds radio or checkbox rows with optional metadata and icons, and a labeled value row for iOS and macOS.

Estimated code review effort: 5 (Critical) | ~90 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding core components.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch import/core-components

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@romchornyi

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
Sources/DashUIKit/Table List/List1View.swift (1)

20-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use let instead of var for view properties.

SwiftUI view properties should be immutable. Declaring label and value as public var allows external mutation, which is non-idiomatic for a value-type View. Additionally, the default placeholder values "Label" and "Value" could accidentally surface in production if a caller omits them.

♻️ Proposed refactor
-    public var label: String
-    public var value: String
+    public let label: String
+    public let value: String
 
-    public init(label: String = "Label", value: String = "Value") {
+    public init(label: String, value: String) {
         self.label = label
         self.value = value
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Table` List/List1View.swift around lines 20 - 29, In
List1View, make the label and value stored properties immutable by changing both
public var declarations to public let, and remove the placeholder default
arguments from its initializer so callers must provide explicit values.
Sources/DashUIKit/Components/SearchBar.swift (1)

48-214: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

magnifyingGlass and clearButton are duplicated between SearchBarFocused and SearchBarLegacy.

Both private structs define identical magnifyingGlass and clearButton computed properties. Extracting these into a shared private helper or a shared protocol would reduce maintenance burden if the icons or styling ever change.

♻️ Suggested approach: extract shared views
+// Shared between both variants
+private struct SearchBarMagnifyingGlass: View {
+    var body: some View {
+        Image(dash: .custom("searchbar-magnifyingglass-icon", bundle: .dashUIKit))
+            .resizable()
+            .scaledToFit()
+            .frame(maxHeight: 15)
+    }
+}
+
+private struct SearchBarClearButton: View {
+    `@Binding` var text: String
+    var body: some View {
+        if !text.isEmpty {
+            Button(action: { text = "" }) {
+                Image(dash: .custom("searchbar-xmark-icon", bundle: .dashUIKit))
+                    .resizable()
+                    .scaledToFit()
+                    .frame(maxHeight: 15)
+                    .frame(width: 44, height: 44)
+                    .contentShape(Rectangle())
+            }
+            .accessibilityLabel(Text(NSLocalizedString("Clear", bundle: .module, comment: "DashUIKit")))
+        }
+    }
+}

Then replace the inline computed properties in both SearchBarFocused and SearchBarLegacy with SearchBarMagnifyingGlass() and SearchBarClearButton(text: $text).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/SearchBar.swift` around lines 48 - 214, Extract
the duplicated magnifyingGlass and clearButton implementations from
SearchBarFocused and SearchBarLegacy into shared private views, such as
SearchBarMagnifyingGlass and SearchBarClearButton(text:), preserving the
existing icons, sizing, accessibility label, and binding behavior; replace both
structs’ computed properties with those shared components.
Sources/DashUIKit/Components/DashAmount.swift (1)

37-45: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Static NumberFormatter captures .current locale at first access.

The formatter is initialized once as a static property, so f.locale = .current is locked to whatever locale was active when DashAmountFormat was first referenced. If the user changes their locale/language while the app is running, amounts will continue to display in the stale locale until the process restarts.

Consider using Formatter.withLocale(.current) per call (iOS 15+) or recreating the formatter when Locale.current changes:

♻️ Suggested approach: recompute locale per call
 private enum DashAmountFormat {
     static let duffsPerDash: Decimal = 100_000_000

-    static let numberFormatter: NumberFormatter = {
-        let f = NumberFormatter()
-        f.numberStyle = .decimal
-        f.locale = .current
-        f.minimumFractionDigits = 0
-        f.maximumFractionDigits = 5
-        f.usesGroupingSeparator = true
-        return f
-    }()
+    static func makeFormatter() -> NumberFormatter {
+        let f = NumberFormatter()
+        f.numberStyle = .decimal
+        f.locale = .current
+        f.minimumFractionDigits = 0
+        f.maximumFractionDigits = 5
+        f.usesGroupingSeparator = true
+        return f
+    }

     static func string(forDuffs duffs: Int64) -> String {
         let value = Decimal(duffs) / duffsPerDash
-        return numberFormatter.string(from: value as NSNumber) ?? "\(value)"
+        return makeFormatter().string(from: value as NSNumber) ?? "\(value)"
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/DashAmount.swift` around lines 37 - 45,
DashAmountFormat’s static numberFormatter captures Locale.current only on first
access, causing stale formatting after runtime locale changes. Update the
formatting logic to obtain or configure a NumberFormatter with Locale.current
for each formatting call, using Formatter.withLocale(.current) where supported,
and remove reliance on the permanently cached locale in numberFormatter.
Sources/DashUIKit/Components/Toast.swift (1)

24-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

BackgroundBlurView has no macOS fallback, yet Toast declares macOS 11 availability.

The entire file is guarded by #if canImport(UIKit), so on macOS neither BackgroundBlurView nor Toast will compile. The @available(iOS 14, macOS 11, *) annotations on ToastStyle and Toast are misleading — API consumers may expect macOS support that doesn't exist. Either remove the macOS availability target or provide a SwiftUI-native blur fallback (e.g., .material(.ultraThin via .background(.ultraThinMaterial)).

♻️ Proposed refactor — use SwiftUI material instead of UIKit blur (iOS 15+, or keep UIKit for iOS 14)

If iOS 15+ is acceptable for blur, replace BackgroundBlurView with the built-in SwiftUI modifier:

-        .background(
-            ZStack {
-                BackgroundBlurView()
-                Color.dash.toastBackground
-            }
-        )
+        .background(
-            ZStack {
-                BackgroundBlurView()
-                Color.dash.toastBackground
-            }
+        )

Alternatively, if iOS 14 support is required, keep BackgroundBlurView but remove macOS 11 from the @available annotations to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/Toast.swift` around lines 24 - 31, Resolve the
platform mismatch between BackgroundBlurView and Toast’s declared availability:
either add a SwiftUI-native macOS blur implementation and ensure Toast compiles
outside the UIKit guard, or remove macOS 11 from the `@available` annotations on
ToastStyle and Toast. Keep the existing UIKit blur for supported iOS versions if
iOS 14 compatibility is required.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/DashUIKit/Components/MenuItem.swift`:
- Around line 112-114: Add the disabled state to the Toggle accessory in the
menu item’s accessory rendering logic: update the `.toggle(let isOn)` case to
apply `.disabled(!isEnabled)`, ensuring the switch cannot be changed when the
surrounding menu item is disabled.

In `@Sources/DashUIKit/Components/NavigationBar.swift`:
- Around line 96-117: Add localized accessibility labels to the icon-only button
created by NavigationBarElement.button(action:), providing distinct labels for
the back, close, plus, and info cases. Reuse the project’s established
localization/accessibility-label pattern and attach the label to the Button so
VoiceOver identifies each action.
- Around line 119-127: NavigationBarButtonStyle.makeBody uses the iOS 15-only
animation(_:value:) API despite the iOS 14 deployment target. Replace it with
the backward-compatible animation(_:) modifier, or conditionally apply the
value-based modifier with an iOS 15 availability check while preserving the
pressed-state animation.

In `@Sources/DashUIKit/Components/RadioButtonRow.swift`:
- Around line 54-101: Add the selection accessibility trait to the row’s Button
in RadioButtonRow.body by applying accessibilityAddTraits with .isSelected when
isSelected is true and no additional traits otherwise, so VoiceOver announces
the current selection state for both radio and checkbox styles.

In `@Sources/DashUIKit/Components/SystemMessageView.swift`:
- Around line 75-95: Update the button container condition in SystemMessageView
to render the HStack only when at least one complete button pair exists: button
name with its corresponding action, or secondary button name with its
corresponding action. Keep the existing inner DashButton conditions and padding
unchanged.

---

Nitpick comments:
In `@Sources/DashUIKit/Components/DashAmount.swift`:
- Around line 37-45: DashAmountFormat’s static numberFormatter captures
Locale.current only on first access, causing stale formatting after runtime
locale changes. Update the formatting logic to obtain or configure a
NumberFormatter with Locale.current for each formatting call, using
Formatter.withLocale(.current) where supported, and remove reliance on the
permanently cached locale in numberFormatter.

In `@Sources/DashUIKit/Components/SearchBar.swift`:
- Around line 48-214: Extract the duplicated magnifyingGlass and clearButton
implementations from SearchBarFocused and SearchBarLegacy into shared private
views, such as SearchBarMagnifyingGlass and SearchBarClearButton(text:),
preserving the existing icons, sizing, accessibility label, and binding
behavior; replace both structs’ computed properties with those shared
components.

In `@Sources/DashUIKit/Components/Toast.swift`:
- Around line 24-31: Resolve the platform mismatch between BackgroundBlurView
and Toast’s declared availability: either add a SwiftUI-native macOS blur
implementation and ensure Toast compiles outside the UIKit guard, or remove
macOS 11 from the `@available` annotations on ToastStyle and Toast. Keep the
existing UIKit blur for supported iOS versions if iOS 14 compatibility is
required.

In `@Sources/DashUIKit/Table` List/List1View.swift:
- Around line 20-29: In List1View, make the label and value stored properties
immutable by changing both public var declarations to public let, and remove the
placeholder default arguments from its initializer so callers must provide
explicit values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 152be8f6-976d-4edf-800e-c2e33c91afb0

📥 Commits

Reviewing files that changed from the base of the PR and between 19d5413 and 700b9ce.

⛔ Files ignored due to path filters (12)
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-receive.disabled.imageset/receive-disabled.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-receive.disabled.imageset/receive-disabled@2x.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-receive.disabled.imageset/receive-disabled@3x.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-send.disabled.imageset/send-disabled.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-send.disabled.imageset/send-disabled@2x.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-send.disabled.imageset/send-disabled@3x.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/System message/info-rect.imageset/info-rect.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/System message/info-rect.imageset/info-rect@2x.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/System message/info-rect.imageset/info-rect@3x.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Toast/toast-no-wifi.imageset/toast-no-wifi.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Toast/toast-no-wifi.imageset/toast-no-wifi@2x.png is excluded by !**/*.png
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Toast/toast-no-wifi.imageset/toast-no-wifi@3x.png is excluded by !**/*.png
📒 Files selected for processing (15)
  • Sources/DashUIKit/Components/AddressFieldView.swift
  • Sources/DashUIKit/Components/BottomSheet.swift
  • Sources/DashUIKit/Components/DashAmount.swift
  • Sources/DashUIKit/Components/DashSwitch.swift
  • Sources/DashUIKit/Components/MenuItem.swift
  • Sources/DashUIKit/Components/NavigationBar.swift
  • Sources/DashUIKit/Components/RadioButtonRow.swift
  • Sources/DashUIKit/Components/SearchBar.swift
  • Sources/DashUIKit/Components/SystemMessageView.swift
  • Sources/DashUIKit/Components/Toast.swift
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-receive.disabled.imageset/Contents.json
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-send.disabled.imageset/Contents.json
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/System message/info-rect.imageset/Contents.json
  • Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Toast/toast-no-wifi.imageset/Contents.json
  • Sources/DashUIKit/Table List/List1View.swift

Comment on lines +112 to +114
case .toggle(let isOn):
Toggle("", isOn: isOn)
.labelsHidden()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Toggle accessory remains interactive when isEnabled is false.

When isEnabled is false, the leading icon and text colors switch to disabled variants, but the .toggle accessory renders a fully interactive Toggle with no .disabled() modifier. Users can still toggle the switch on a disabled menu item.

🔧 Proposed fix
         case .toggle(let isOn):
             Toggle("", isOn: isOn)
                 .labelsHidden()
+                .disabled(!isEnabled)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case .toggle(let isOn):
Toggle("", isOn: isOn)
.labelsHidden()
case .toggle(let isOn):
Toggle("", isOn: isOn)
.labelsHidden()
.disabled(!isEnabled)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/MenuItem.swift` around lines 112 - 114, Add the
disabled state to the Toggle accessory in the menu item’s accessory rendering
logic: update the `.toggle(let isOn)` case to apply `.disabled(!isEnabled)`,
ensuring the switch cannot be changed when the surrounding menu item is
disabled.

Comment on lines +96 to +117
public enum NavigationBarElement: String {
case back = "navigationbar-back"
case close = "navigationbar-close"
case plus = "navigationbar-plus"
case info = "navigationbar-info"

public var icon: some View {
Image(dash: .custom(rawValue, bundle: .module))
.resizable()
.scaledToFit()
}

public func button(action: @escaping () -> Void) -> some View {
Button(action: action) {
icon
.fixedSize()
.frame(width: 44, height: 44, alignment: .center)
.contentShape(Rectangle())
}
.buttonStyle(NavigationBarButtonStyle())
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add accessibility labels to NavigationBarElement.button.

The button(action:) helper renders an icon-only Button without any accessibilityLabel. Other components in this codebase (AddressFieldView, SearchBar, SystemMessageView) consistently provide localized accessibility labels for icon-only buttons. Without labels, VoiceOver users cannot identify the back, close, plus, or info actions.

♿ Proposed fix: add accessibility labels per element case
 public enum NavigationBarElement: String {
     case back = "navigationbar-back"
     case close = "navigationbar-close"
     case plus = "navigationbar-plus"
     case info = "navigationbar-info"

     public var icon: some View {
         Image(dash: .custom(rawValue, bundle: .module))
             .resizable()
             .scaledToFit()
     }

+    private var accessibilityLabelKey: String {
+        switch self {
+        case .back:  return "Back"
+        case .close: return "Close"
+        case .plus:  return "Add"
+        case .info:  return "Info"
+        }
+    }
+
     public func button(action: `@escaping` () -> Void) -> some View {
         Button(action: action) {
             icon
                 .fixedSize()
                 .frame(width: 44, height: 44, alignment: .center)
                 .contentShape(Rectangle())
         }
+        .accessibilityLabel(NSLocalizedString(accessibilityLabelKey, bundle: .module, comment: "DashUIKit"))
         .buttonStyle(NavigationBarButtonStyle())
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public enum NavigationBarElement: String {
case back = "navigationbar-back"
case close = "navigationbar-close"
case plus = "navigationbar-plus"
case info = "navigationbar-info"
public var icon: some View {
Image(dash: .custom(rawValue, bundle: .module))
.resizable()
.scaledToFit()
}
public func button(action: @escaping () -> Void) -> some View {
Button(action: action) {
icon
.fixedSize()
.frame(width: 44, height: 44, alignment: .center)
.contentShape(Rectangle())
}
.buttonStyle(NavigationBarButtonStyle())
}
}
public enum NavigationBarElement: String {
case back = "navigationbar-back"
case close = "navigationbar-close"
case plus = "navigationbar-plus"
case info = "navigationbar-info"
public var icon: some View {
Image(dash: .custom(rawValue, bundle: .module))
.resizable()
.scaledToFit()
}
private var accessibilityLabelKey: String {
switch self {
case .back: return "Back"
case .close: return "Close"
case .plus: return "Add"
case .info: return "Info"
}
}
public func button(action: `@escaping` () -> Void) -> some View {
Button(action: action) {
icon
.fixedSize()
.frame(width: 44, height: 44, alignment: .center)
.contentShape(Rectangle())
}
.accessibilityLabel(NSLocalizedString(accessibilityLabelKey, bundle: .module, comment: "DashUIKit"))
.buttonStyle(NavigationBarButtonStyle())
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/NavigationBar.swift` around lines 96 - 117, Add
localized accessibility labels to the icon-only button created by
NavigationBarElement.button(action:), providing distinct labels for the back,
close, plus, and info cases. Reuse the project’s established
localization/accessibility-label pattern and attach the label to the Button so
VoiceOver identifies each action.

Comment on lines +119 to +127
@available(iOS 14, macOS 11, *)
private struct NavigationBarButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.88 : 1)
.opacity(configuration.isPressed ? 0.7 : 1)
.animation(.easeInOut(duration: 0.12), value: configuration.isPressed)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the declared platform minimum versions in Package.swift
rg -n -A 15 'platforms' Package.swift

Repository: dashpay/DashUIKit

Length of output: 499


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Package.swift ==\n'
cat -n Package.swift | sed -n '1,80p'

printf '\n== NavigationBar.swift (around the reported lines) ==\n'
cat -n Sources/DashUIKit/Components/NavigationBar.swift | sed -n '100,140p'

printf '\n== Search for animation(value:) uses in repo ==\n'
rg -n '\.animation\([^)]*,\s*value:\s*' Sources || true

Repository: dashpay/DashUIKit

Length of output: 2364


🌐 Web query:

SwiftUI View.animation(_:value:) availability iOS 15 Apple documentation

💡 Result:

The SwiftUI modifier animation(_:value:) was introduced in iOS 15 [1]. It serves as the recommended replacement for the deprecated parameterless animation modifier [1]. By requiring a value to monitor for changes, this modifier ensures that animations are only triggered when the specified state value changes, providing more predictable and controlled animation behavior in SwiftUI views [2][3]. It is available on iOS 15.0 and later, iPadOS 15.0 and later, macOS 12.0 and later, tvOS 15.0 and later, and watchOS 8.0 and later [4][2].

Citations:


animation(_:value:) is iOS 15+, but this package targets iOS 14. Use the older animation(_:) form here, or gate this modifier behind if #available(iOS 15, *).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/NavigationBar.swift` around lines 119 - 127,
NavigationBarButtonStyle.makeBody uses the iOS 15-only animation(_:value:) API
despite the iOS 14 deployment target. Replace it with the backward-compatible
animation(_:) modifier, or conditionally apply the value-based modifier with an
iOS 15 availability check while preserving the pressed-state animation.

Comment on lines +54 to +101
public var body: some View {
Button(action: action) {
HStack(spacing: 16) {
if let icon = icon {
Image(dash: icon)
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
}

VStack(alignment: .leading, spacing: 0) {
Text(title)
.dashFont(.subheadMedium)
.foregroundColor(Color.dash.primaryText)

if let subtitle = subtitle {
Text(subtitle)
.dashFont(.caption1)
.foregroundColor(Color.dash.secondaryText)
}
}

Spacer()

if let trailingText = trailingText {
Text(trailingText)
.dashFont(.subheadMedium)
.foregroundColor(Color.dash.primaryText)
}

switch style {
case .radio:
Circle()
.stroke(isSelected ? Color.dash.blue : Color.dash.gray300.opacity(0.5), lineWidth: isSelected ? 6 : 2)
.frame(width: isSelected ? 21 : 24, height: isSelected ? 21 : 24)
.padding(.trailing, isSelected ? 2 : 0)
case .checkbox:
Image(dash: .custom(isSelected ? "icon_checkbox_square_checked" : "icon_checkbox_square", bundle: .dashUIKit))
.resizable()
.frame(width: 24, height: 24)
}
}
.padding(.horizontal, 16)
.contentShape(Rectangle())
.frame(minHeight: subtitle != nil ? 60 : 54)
}
.buttonStyle(PlainButtonStyle())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add accessibility traits for selection state.

The Button announces as a button to VoiceOver but does not communicate whether the row is selected or not. For a radio/checkbox component, this is essential information for accessibility users. Since the component targets iOS 14+, .accessibilityAddTraits(isSelected ? .isSelected : []) is available and should be applied.

♿ Proposed accessibility fix
         }
         .buttonStyle(PlainButtonStyle())
+        .accessibilityAddTraits(isSelected ? .isSelected : [])
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public var body: some View {
Button(action: action) {
HStack(spacing: 16) {
if let icon = icon {
Image(dash: icon)
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
}
VStack(alignment: .leading, spacing: 0) {
Text(title)
.dashFont(.subheadMedium)
.foregroundColor(Color.dash.primaryText)
if let subtitle = subtitle {
Text(subtitle)
.dashFont(.caption1)
.foregroundColor(Color.dash.secondaryText)
}
}
Spacer()
if let trailingText = trailingText {
Text(trailingText)
.dashFont(.subheadMedium)
.foregroundColor(Color.dash.primaryText)
}
switch style {
case .radio:
Circle()
.stroke(isSelected ? Color.dash.blue : Color.dash.gray300.opacity(0.5), lineWidth: isSelected ? 6 : 2)
.frame(width: isSelected ? 21 : 24, height: isSelected ? 21 : 24)
.padding(.trailing, isSelected ? 2 : 0)
case .checkbox:
Image(dash: .custom(isSelected ? "icon_checkbox_square_checked" : "icon_checkbox_square", bundle: .dashUIKit))
.resizable()
.frame(width: 24, height: 24)
}
}
.padding(.horizontal, 16)
.contentShape(Rectangle())
.frame(minHeight: subtitle != nil ? 60 : 54)
}
.buttonStyle(PlainButtonStyle())
}
public var body: some View {
Button(action: action) {
HStack(spacing: 16) {
if let icon = icon {
Image(dash: icon)
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
}
VStack(alignment: .leading, spacing: 0) {
Text(title)
.dashFont(.subheadMedium)
.foregroundColor(Color.dash.primaryText)
if let subtitle = subtitle {
Text(subtitle)
.dashFont(.caption1)
.foregroundColor(Color.dash.secondaryText)
}
}
Spacer()
if let trailingText = trailingText {
Text(trailingText)
.dashFont(.subheadMedium)
.foregroundColor(Color.dash.primaryText)
}
switch style {
case .radio:
Circle()
.stroke(isSelected ? Color.dash.blue : Color.dash.gray300.opacity(0.5), lineWidth: isSelected ? 6 : 2)
.frame(width: isSelected ? 21 : 24, height: isSelected ? 21 : 24)
.padding(.trailing, isSelected ? 2 : 0)
case .checkbox:
Image(dash: .custom(isSelected ? "icon_checkbox_square_checked" : "icon_checkbox_square", bundle: .dashUIKit))
.resizable()
.frame(width: 24, height: 24)
}
}
.padding(.horizontal, 16)
.contentShape(Rectangle())
.frame(minHeight: subtitle != nil ? 60 : 54)
}
.buttonStyle(PlainButtonStyle())
.accessibilityAddTraits(isSelected ? .isSelected : [])
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/RadioButtonRow.swift` around lines 54 - 101, Add
the selection accessibility trait to the row’s Button in RadioButtonRow.body by
applying accessibilityAddTraits with .isSelected when isSelected is true and no
additional traits otherwise, so VoiceOver announces the current selection state
for both radio and checkbox styles.

Comment on lines +75 to +95
if buttonName != nil || secondaryButtonName != nil {
HStack(spacing: 10) {
if let buttonName, let onAction {
DashUIKit.DashButton(
text: buttonName,
size: .small,
style: .filledBlue,
action: onAction
)
}
if let secondaryButtonName, let onSecondaryAction {
DashUIKit.DashButton(
text: secondaryButtonName,
size: .small,
style: .tintedBlue,
action: onSecondaryAction
)
}
}
.padding(.bottom, 14)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Button container renders with padding even when no buttons are shown.

The outer condition buttonName != nil || secondaryButtonName != nil renders the HStack with .padding(.bottom, 14), but the inner conditions require both name and action to be non-nil (if let buttonName, let onAction). If a caller passes buttonName: "Renew" without onAction, the container renders empty with 14pt bottom padding.

🐛 Proposed fix: align container condition with actual rendering
-                if buttonName != nil || secondaryButtonName != nil {
+                if (buttonName != nil && onAction != nil)
+                    || (secondaryButtonName != nil && onSecondaryAction != nil) {
                     HStack(spacing: 10) {
                         if let buttonName, let onAction {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if buttonName != nil || secondaryButtonName != nil {
HStack(spacing: 10) {
if let buttonName, let onAction {
DashUIKit.DashButton(
text: buttonName,
size: .small,
style: .filledBlue,
action: onAction
)
}
if let secondaryButtonName, let onSecondaryAction {
DashUIKit.DashButton(
text: secondaryButtonName,
size: .small,
style: .tintedBlue,
action: onSecondaryAction
)
}
}
.padding(.bottom, 14)
}
if (buttonName != nil && onAction != nil)
|| (secondaryButtonName != nil && onSecondaryAction != nil) {
HStack(spacing: 10) {
if let buttonName, let onAction {
DashUIKit.DashButton(
text: buttonName,
size: .small,
style: .filledBlue,
action: onAction
)
}
if let secondaryButtonName, let onSecondaryAction {
DashUIKit.DashButton(
text: secondaryButtonName,
size: .small,
style: .tintedBlue,
action: onSecondaryAction
)
}
}
.padding(.bottom, 14)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/SystemMessageView.swift` around lines 75 - 95,
Update the button container condition in SystemMessageView to render the HStack
only when at least one complete button pair exists: button name with its
corresponding action, or secondary button name with its corresponding action.
Keep the existing inner DashButton conditions and padding unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant