From e24d35e6c6aab2047c6e14b5a26e1be01ac2bc74 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 27 Aug 2026 17:24:30 +0300 Subject: [PATCH 1/6] chore: raise the deployment target to iOS 18 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dashwallet-ios, the only consumer, has been iOS 18 for a while: its Podfile says `platform :ios, '18.0'` and every shipping build configuration of the `dashwallet` and `dashpay` targets — the two that link this package — is 18.0. Nothing has needed the iOS 14 floor for some time, and holding it costs a fallback path in every component that wants an API newer than 2020. macOS is declared alongside it at 15. The package never declared a macOS platform at all, so a macOS build fell back to the SwiftPM default and every type had to carry `@available(macOS 11)` by hand to compile there. Saying it once in the manifest replaces all of that. The `Hard constraint` section in CLAUDE.md is replaced by the new rule: do not annotate for anything at or below the floor, and gate only what is genuinely newer — as `selfSizingSheet` still does for iOS 26 corner styling. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 27 +++++++++++++-------------- Package.swift | 3 ++- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e7f2e39..8f1651d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,24 +10,23 @@ the host passes in. - Assets live in `Sources/DashUIKit/Resources/Media.xcassets` and are reached via `Bundle.module` / `Bundle.dashUIKit`. -## ⚠️ Hard constraint: must support iOS 14 +## Deployment target: iOS 18 -**This library must remain available on iOS 14.** The package deployment target is -`iOS(.v14)` (see `Package.swift`) and this is non-negotiable — every public component -must be usable from an iOS 14 host. +The package targets **iOS 18 / macOS 15** (see `Package.swift`), matching dashwallet-ios, +the only consumer. Everything SwiftUI shipped up to iOS 17 — `@FocusState`, +`presentationDetents`, `presentationBackground`, the `#Preview` macro, `.isToggle` — is +available unconditionally, so components carry no `@available` annotation and no fallback +branch for it. When writing or changing code: -- Annotate public API with `@available(iOS 14, macOS 11, *)` (or `macOS 10.15` for - Foundation-level token types). **Do not** make a component's *minimum* availability - higher than iOS 14. -- If you need a SwiftUI API introduced after iOS 14 (e.g. `@FocusState` 15, - `presentationDetents` 16, `#Preview` macro 17), **gate it with `if #available(...)` - and provide an iOS 14 fallback path** — never raise the whole component's floor. - See `SearchBar` (15+ focus path + 14 legacy path), `BottomSheet` / `selfSizingSheet` - (16+ detents, no-op below), and `AddressFieldView` (17 axis field + older fallback). -- It is fine for `#Preview`-only code to require iOS 17 — previews are `#if DEBUG` and - never ship — but the component itself must still build and run on iOS 14. +- **Do not** add `@available(iOS …)` for anything at or below the floor; the package + declares it once. Annotate only what genuinely needs a *higher* version than the + floor, and then gate it with `if #available(...)` plus a path that works below it — + `selfSizingSheet` does this for the iOS 26 corner styling (`#unavailable(iOS 26.0)`). +- Raising the floor again is a package-level decision, not a per-component one. If a + component cannot work at the declared floor, say so in review rather than annotating + around it. ## Where things are diff --git a/Package.swift b/Package.swift index 64df805..efa46f6 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,8 @@ let package = Package( name: "DashUIKit", defaultLocalization: "en", platforms: [ - .iOS(.v14) + .iOS(.v18), + .macOS(.v15) ], products: [ .library(name: "DashUIKit", targets: ["DashUIKit"]), From afa43d205ab4fe301cac3a880c7dbdcbaca15a84 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 27 Aug 2026 17:27:43 +0300 Subject: [PATCH 2/6] refactor: drop the pre-iOS 18 fallback paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every branch here existed to keep a component running below the old floor, and each one was a second implementation that nobody could see fail: - `SearchBar` dispatched between a focus-driven bar and `SearchBarLegacy`, a whole second bar without the cancel animation. The legacy one is gone and the shell renders the focused bar directly. - `SearchBar` and `AddressFieldView` each carried a pre-iOS 17 `TextField` without a styled prompt — and, in the address field, without the vertical axis that gives it its two-line read-out. - `DashSwitch` fell back to `accentColor`, `NavigationBarButtonStyle` to the value-less `animation(_:)` — the one that animates every change in the view, not just the press. - `SwitchView` reported `.isButton` instead of `.isToggle` to VoiceOver. - `BottomSheet` had the `isModalInPresentation` shim added for iOS 14 last week, and `selfSizingSheet` returned `self` unmodified below iOS 16 — a self-sizing sheet that did not self-size. The `#unavailable(iOS 26.0)` check stays: that one is genuinely above the floor. Co-Authored-By: Claude Opus 5 --- .../Components/AddressFieldView.swift | 49 +++--- .../Components/BottomSheet/BottomSheet.swift | 143 ++++-------------- Sources/DashUIKit/Components/DashSwitch.swift | 12 +- .../DashUIKit/Components/NavigationBar.swift | 10 +- Sources/DashUIKit/Components/SearchBar.swift | 96 ++---------- .../Components/Switch/SwitchView.swift | 12 +- 6 files changed, 65 insertions(+), 257 deletions(-) diff --git a/Sources/DashUIKit/Components/AddressFieldView.swift b/Sources/DashUIKit/Components/AddressFieldView.swift index 44aecc2..c3f320a 100644 --- a/Sources/DashUIKit/Components/AddressFieldView.swift +++ b/Sources/DashUIKit/Components/AddressFieldView.swift @@ -162,36 +162,25 @@ extension AddressFieldView { ) } - @ViewBuilder private var textField: some View { - if #available(iOS 17.0, *) { - TextField( - "", - text: $text, - // A prompt must stay a `Text`; `dashFont` returns `some View`, and a - // line height cannot be applied to `Text` anyway. - prompt: Text(placeholder) - .font(Font.dash.subhead) - .foregroundStyle(Color.dash.black1000Alpha30), - axis: .vertical - ) - .lineLimit(1...2) - .dashFont(.subhead) - .textInputAutocapitalization(.never) - .disableAutocorrection(true) - .foregroundStyle(Color.dash.primaryText) - .tint(Color.dash.primaryText) - .focused($isTextFieldFocused) - .disabled(isDisabled) - } else { - TextField(placeholder, text: $text) - .dashFont(.subhead) - .textInputAutocapitalization(.never) - .disableAutocorrection(true) - .foregroundStyle(Color.dash.primaryText) - .tint(Color.dash.primaryText) - .focused($isTextFieldFocused) - .disabled(isDisabled) - } + private var textField: some View { + TextField( + "", + text: $text, + // A prompt must stay a `Text`; `dashFont` returns `some View`, and a + // line height cannot be applied to `Text` anyway. + prompt: Text(placeholder) + .font(Font.dash.subhead) + .foregroundStyle(Color.dash.black1000Alpha30), + axis: .vertical + ) + .lineLimit(1...2) + .dashFont(.subhead) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .foregroundStyle(Color.dash.primaryText) + .tint(Color.dash.primaryText) + .focused($isTextFieldFocused) + .disabled(isDisabled) } private var actionButton: some View { diff --git a/Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift b/Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift index 3a6a3e0..624a3a0 100644 --- a/Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift +++ b/Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift @@ -239,95 +239,19 @@ enum BottomSheetDismissalAction { private struct BottomSheetDismissalModifier: ViewModifier { let isEnabled: Bool - // The branch is on `#available` alone, never on `isEnabled`. A `@ViewBuilder` - // if/else produces `_ConditionalContent`, and the two branches are different - // views to SwiftUI: switching between them tears the sheet down and rebuilds - // it, taking every piece of `@State` the host keeps inside `content()` with - // it — a half-typed field, the scroll position, the keyboard. `#available` - // cannot flip while the app runs, so this branch is decided once and the - // sheet keeps one identity for as long as it is on screen. - @ViewBuilder - func body(content: Content) -> some View { - if #available(iOS 15, macOS 12, *) { - content.interactiveDismissDisabled(!isEnabled) - } else { - content.modifier(LegacyInteractiveDismissModifier(isDismissDisabled: !isEnabled)) - } - } -} - -#if canImport(UIKit) - -/// `interactiveDismissDisabled` is iOS 15, and this library ships to 14. The flag -/// it sets underneath — `UIViewController.isModalInPresentation` — is iOS 13, so -/// the older systems can be given the same protection rather than none at all. -@available(iOS 14, macOS 11, *) -private struct LegacyInteractiveDismissModifier: ViewModifier { - let isDismissDisabled: Bool - + // The value is passed to the modifier rather than deciding whether to apply + // it: a `@ViewBuilder` if/else would hand back `_ConditionalContent`, and + // flipping between its branches makes SwiftUI rebuild the sheet, taking the + // host's state inside `content()` with it. func body(content: Content) -> some View { - content.background( - ModalInPresentationSetter(isModal: isDismissDisabled) - .frame(width: 0, height: 0) - ) - } -} - -@available(iOS 14, macOS 11, *) -private struct ModalInPresentationSetter: UIViewControllerRepresentable { - let isModal: Bool - - func makeUIViewController(context: Context) -> Controller { - Controller() - } - - func updateUIViewController(_ controller: Controller, context: Context) { - controller.isModal = isModal - } - - final class Controller: UIViewController { - var isModal = false { - didSet { applyToPresentedController() } - } - - override func didMove(toParent parent: UIViewController?) { - super.didMove(toParent: parent) - applyToPresentedController() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - applyToPresentedController() - } - - /// The swipe belongs to the controller that was actually presented, not to - /// this one: the representable sits in a background deep inside the sheet's - /// hosting controller, so walk up to the top of the containment chain. - private func applyToPresentedController() { - var controller: UIViewController = self - while let parent = controller.parent { - controller = parent - } - controller.isModalInPresentation = isModal - } + content.interactiveDismissDisabled(!isEnabled) } } -#else - -@available(iOS 14, macOS 11, *) -private struct LegacyInteractiveDismissModifier: ViewModifier { - let isDismissDisabled: Bool - - func body(content: Content) -> some View { content } -} - -#endif - @available(iOS 14, macOS 11, *) public extension View { /// Sizes a `BottomSheet` (built with `fillsHeight: false`) to its content's natural height — - /// no hardcoded `.height(...)` needed. On iOS < 16 it is a no-op. + /// no hardcoded `.height(...)` needed. /// /// The content is measured directly (via a `GeometryReader` background), so it does not rely /// on any published preference — it self-sizes whatever finite-height view it wraps. The @@ -344,8 +268,8 @@ public extension View { /// colour the wrapped `BottomSheet` was built with, so a custom one has /// to be passed here too — or use `BottomSheet.selfSizing(...)`, which /// forwards a single `background` to both. - /// - cornerRadius: Optional corner radius applied via `presentationCornerRadius` on - /// iOS 16.4..<26 (iOS 26+ keeps the system corner styling). + /// - cornerRadius: Optional corner radius applied via `presentationCornerRadius` + /// below iOS 26 (iOS 26+ keeps the system corner styling). @ViewBuilder func selfSizingSheet( fallback: CGFloat = 0, @@ -353,42 +277,27 @@ public extension View { background: Color = .dash.primaryBackground, cornerRadius: CGFloat? = nil ) -> some View { - if #available(iOS 16.0, macOS 13.0, *) { - let modified = modifier(SelfSizingSheetModifier(fallback: fallback, maxHeightFraction: maxHeightFraction)) - #if os(iOS) - if #available(iOS 16.4, *) { - // The background is filled whatever the corner radius: the - // measured height excludes the home-indicator inset that - // `.presentationDetents([.height])` adds back, so that strip - // sits outside the sheet's own `VStack` and shows the system - // background unless this fills it. - if #unavailable(iOS 26.0), let cornerRadius { - modified - .presentationCornerRadius(cornerRadius) - .presentationBackground(background) - } else { - // iOS 26+ keeps the system corner styling. - modified - .presentationBackground(background) - } - } else { - modified - } - #elseif os(macOS) - // `presentationCornerRadius` is iOS-only, but the presentation - // background lands on macOS 13.3 — apply it there too so the - // parameter is not silently ignored. - if #available(macOS 13.3, *) { - modified.presentationBackground(background) - } else { - modified - } - #else + let modified = modifier(SelfSizingSheetModifier(fallback: fallback, maxHeightFraction: maxHeightFraction)) + #if os(iOS) + // The background is filled whatever the corner radius: the measured + // height excludes the home-indicator inset that + // `.presentationDetents([.height])` adds back, so that strip sits + // outside the sheet's own `VStack` and shows the system background + // unless this fills it. + if #unavailable(iOS 26.0), let cornerRadius { modified - #endif + .presentationCornerRadius(cornerRadius) + .presentationBackground(background) } else { - self + // iOS 26+ keeps the system corner styling. + modified + .presentationBackground(background) } + #else + // `presentationCornerRadius` is iOS-only; the presentation background + // is not, so the parameter is not silently ignored off iOS. + modified.presentationBackground(background) + #endif } } diff --git a/Sources/DashUIKit/Components/DashSwitch.swift b/Sources/DashUIKit/Components/DashSwitch.swift index f65fd03..80e02ad 100644 --- a/Sources/DashUIKit/Components/DashSwitch.swift +++ b/Sources/DashUIKit/Components/DashSwitch.swift @@ -30,15 +30,9 @@ public struct DashSwitch: View { } public var body: some View { - if #available(iOS 15, *) { - Toggle("", isOn: $isOn) - .labelsHidden() - .tint(Color.dash.switchTrackFillOn as Color?) - } else { - Toggle("", isOn: $isOn) - .labelsHidden() - .accentColor(Color.dash.switchTrackFillOn) - } + Toggle("", isOn: $isOn) + .labelsHidden() + .tint(Color.dash.switchTrackFillOn as Color?) } } diff --git a/Sources/DashUIKit/Components/NavigationBar.swift b/Sources/DashUIKit/Components/NavigationBar.swift index 0b4ef96..40f96e2 100644 --- a/Sources/DashUIKit/Components/NavigationBar.swift +++ b/Sources/DashUIKit/Components/NavigationBar.swift @@ -132,17 +132,11 @@ public enum NavigationBarElement: String { @available(iOS 14, macOS 11, *) private struct NavigationBarButtonStyle: ButtonStyle { - @ViewBuilder func makeBody(configuration: Configuration) -> some View { - let content = configuration.label + configuration.label .scaleEffect(configuration.isPressed ? 0.88 : 1) .opacity(configuration.isPressed ? 0.7 : 1) - - if #available(iOS 15, macOS 12, *) { - content.animation(.easeInOut(duration: 0.12), value: configuration.isPressed) - } else { - content.animation(.easeInOut(duration: 0.12)) - } + .animation(.easeInOut(duration: 0.12), value: configuration.isPressed) } } diff --git a/Sources/DashUIKit/Components/SearchBar.swift b/Sources/DashUIKit/Components/SearchBar.swift index 7614b08..3fa0e53 100644 --- a/Sources/DashUIKit/Components/SearchBar.swift +++ b/Sources/DashUIKit/Components/SearchBar.swift @@ -18,7 +18,7 @@ #if canImport(UIKit) import SwiftUI -// MARK: - Public shell (iOS 14+) +// MARK: - Public shell @available(iOS 14, *) public struct SearchBar: View { @@ -35,17 +35,12 @@ public struct SearchBar: View { } public var body: some View { - if #available(iOS 15, *) { - SearchBarFocused(text: $text, placeholder: placeholder) - } else { - SearchBarLegacy(text: $text, placeholder: placeholder) - } + SearchBarFocused(text: $text, placeholder: placeholder) } } -// MARK: - iOS 15+ (focus-driven cancel button) +// MARK: - Focus-driven cancel button -@available(iOS 15, *) private struct SearchBarFocused: View { private enum Layout { static let fieldHeight: CGFloat = 40 @@ -136,82 +131,19 @@ private struct SearchBarFocused: View { .tint(Color.dash.primaryText as Color?) } - @ViewBuilder private var searchField: some View { - if #available(iOS 17.0, *) { - TextField( - text: $text, - // A prompt must stay a `Text`; `dashFont` returns `some View`, and a - // line height cannot be applied to `Text` anyway. - prompt: Text(placeholder) - .font(Font.dash.subhead) - .foregroundStyle(Color.dash.black1000Alpha30) - ) { - EmptyView() - } - .focused($isFocused) - .tint(Color.dash.primaryText as Color?) - } else { - TextField(placeholder, text: $text) - .focused($isFocused) - .tint(Color.dash.primaryText as Color?) - .foregroundColor(Color.dash.primaryText) - } - } -} - -// MARK: - iOS 14 fallback (no focus state — static bar without cancel animation) - -@available(iOS 14, *) -private struct SearchBarLegacy: View { - private enum Layout { - static let fieldHeight: CGFloat = 40 - static let fieldCornerRadius: CGFloat = 14 - static let fieldHorizontalPadding: CGFloat = 14 - static let fieldSpacing: CGFloat = 10 - } - - @Binding var text: String - let placeholder: String - - var body: some View { - HStack(spacing: Layout.fieldSpacing) { - magnifyingGlass - TextField(placeholder, text: $text) - .accentColor(Color.dash.primaryText) - .foregroundColor(Color.dash.primaryText) - clearButton - } - .padding(.horizontal, Layout.fieldHorizontalPadding) - .frame(height: Layout.fieldHeight) - .frame(maxWidth: .infinity, alignment: .leading) - .background(Color.dash.searchBackground) - .clipShape(RoundedRectangle(cornerRadius: Layout.fieldCornerRadius, style: .continuous)) - } - - private var magnifyingGlass: some View { - DashIcon.SearchBar.magnifyingglassIcon.image - .resizable() - .scaledToFit() - .frame(maxHeight: 15) - } - - @ViewBuilder - private var clearButton: some View { - if !text.isEmpty { - Button( - action: { text = "" }, - label: { - DashIcon.SearchBar.xmarkIcon.image - .resizable() - .scaledToFit() - .frame(maxHeight: 15) - .frame(width: 44, height: 44) - .contentShape(Rectangle()) - } - ) - .accessibilityLabel(Text(NSLocalizedString("Clear", bundle: .module, comment: "DashUIKit"))) + TextField( + text: $text, + // A prompt must stay a `Text`; `dashFont` returns `some View`, and a + // line height cannot be applied to `Text` anyway. + prompt: Text(placeholder) + .font(Font.dash.subhead) + .foregroundStyle(Color.dash.black1000Alpha30) + ) { + EmptyView() } + .focused($isFocused) + .tint(Color.dash.primaryText as Color?) } } diff --git a/Sources/DashUIKit/Components/Switch/SwitchView.swift b/Sources/DashUIKit/Components/Switch/SwitchView.swift index 579d079..582e755 100644 --- a/Sources/DashUIKit/Components/Switch/SwitchView.swift +++ b/Sources/DashUIKit/Components/Switch/SwitchView.swift @@ -43,21 +43,11 @@ public struct SwitchView: View { .animation(.easeInOut(duration: Constants.animationDuration), value: isOn) .contentShape(Rectangle()) .onTapGesture { isOn.toggle() } - .accessibilityAddTraits(toggleTrait) + .accessibilityAddTraits(.isToggle) .accessibilityValue(Text(isOn ? Constants.onValue : Constants.offValue)) .accessibilityAction { isOn.toggle() } } - /// `.isToggle` is iOS 17, and this component must stay usable on iOS 14, so the - /// older path keeps the button trait. - private var toggleTrait: AccessibilityTraits { - if #available(iOS 17, macOS 14, *) { - return .isToggle - } else { - return .isButton - } - } - private var trackFill: Color { switch (isOn, isEnabled) { case (true, true): return Constants.switchTrackFillOn From f6f817953f33a95a7f4a81db9aed5b7bf7d88a76 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 27 Aug 2026 17:27:43 +0300 Subject: [PATCH 3/6] refactor: adopt the two-parameter onChange `onChange(of:perform:)` was deprecated in iOS 17 / macOS 14. It stayed because the old floor made the replacement unavailable; declaring the macOS platform surfaced it as three build warnings. The package builds warning-free again. Co-Authored-By: Claude Opus 5 --- Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift | 2 +- Sources/DashUIKit/Components/Geometry/FrameReader.swift | 2 +- Sources/DashUIKit/Components/Geometry/LocationReader.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift b/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift index ec60f0a..2181451 100644 --- a/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift +++ b/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift @@ -405,7 +405,7 @@ private struct AnimatedSwapLayout: View { } .frame(height: SwapAnimLayout.containerHeight) .frame(maxWidth: .infinity) - .onChange(of: isPrimaryLarge) { newValue in + .onChange(of: isPrimaryLarge) { _, newValue in // A rapid re-toggle must invalidate the previous change's still-pending callbacks, // otherwise a stale scale/offset update can run after the newer state (visible jump). swapGeneration &+= 1 diff --git a/Sources/DashUIKit/Components/Geometry/FrameReader.swift b/Sources/DashUIKit/Components/Geometry/FrameReader.swift index 9c3c3c2..24cce9f 100644 --- a/Sources/DashUIKit/Components/Geometry/FrameReader.swift +++ b/Sources/DashUIKit/Components/Geometry/FrameReader.swift @@ -43,7 +43,7 @@ struct FrameReader: View { Color.clear .frame(maxWidth: .infinity, maxHeight: .infinity) .onAppear { report(frame) } - .onChange(of: frame, perform: report) + .onChange(of: frame) { _, newFrame in report(newFrame) } } .frame(maxWidth: .infinity, maxHeight: .infinity) } diff --git a/Sources/DashUIKit/Components/Geometry/LocationReader.swift b/Sources/DashUIKit/Components/Geometry/LocationReader.swift index bd74f24..4d02488 100644 --- a/Sources/DashUIKit/Components/Geometry/LocationReader.swift +++ b/Sources/DashUIKit/Components/Geometry/LocationReader.swift @@ -42,7 +42,7 @@ struct LocationReader: View { Color.clear .onAppear { report(center) } - .onChange(of: center, perform: report) + .onChange(of: center) { _, newCenter in report(newCenter) } } .frame(width: 0, height: 0, alignment: .center) } From 415bbcc1495ea025f1465fadffe1815959169f09 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 27 Aug 2026 17:27:43 +0300 Subject: [PATCH 4/6] refactor(keyboard): draw the panel with UnevenRoundedRectangle The panel was a fully rounded rectangle pushed below its own frame by the corner radius so its bottom corners fell off screen, with a comment explaining that the shape saying this in one line was iOS 16 and the library shipped to 14. It no longer does, so the workaround goes and the shape says what it draws. Rendered before and after in the simulator, light and dark: identical. Co-Authored-By: Claude Opus 5 --- .../Components/NumericKeyboardView.swift | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Sources/DashUIKit/Components/NumericKeyboardView.swift b/Sources/DashUIKit/Components/NumericKeyboardView.swift index 1950fa4..81f0546 100644 --- a/Sources/DashUIKit/Components/NumericKeyboardView.swift +++ b/Sources/DashUIKit/Components/NumericKeyboardView.swift @@ -145,21 +145,17 @@ public struct NumericKeyboardView: View { .frame(maxWidth: .infinity) // Rounded at the top only, and run down into the bottom safe area. // - // Drawn as a fully rounded rectangle pushed below its own frame by the - // radius, so the bottom corners are never on screen. - // `UnevenRoundedRectangle` says that in one line but is iOS 16, and - // this library ships to 14; `background(alignment:content:)` is 15. - // Both are avoidable, the deployment target is not. - // // `.continuous` because the circular default kinks visibly where the // arc meets the top edge at this radius. - .background( - RoundedRectangle(cornerRadius: Layout.panelCornerRadius, style: .continuous) - .fill(Color.dash.secondaryBackground) - .padding(.bottom, -Layout.panelCornerRadius) - .ignoresSafeArea(edges: .bottom), - alignment: .top - ) + .background(alignment: .top) { + UnevenRoundedRectangle( + topLeadingRadius: Layout.panelCornerRadius, + topTrailingRadius: Layout.panelCornerRadius, + style: .continuous + ) + .fill(Color.dash.secondaryBackground) + .ignoresSafeArea(edges: .bottom) + } } private var keyboardRowsView: some View { From 19f3b39faea8b982cbdd122560e8d44786f1c8f3 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 27 Aug 2026 17:28:58 +0300 Subject: [PATCH 5/6] refactor: drop the now-redundant availability annotations 200 `@available` lines across 48 files, every one of them at or below the declared floor: `iOS 14 / macOS 11` on nearly every public type, `macOS 10.15` on the Foundation-level token types, `iOS 17 / macOS 14` on each `#Preview`. The manifest states the minimum once, so repeating a lower one per declaration only invited the question of which is true. Also folds in the last two deprecated `onChange(of:perform:)` call sites, in `BottomSheet` and `SearchBar`. They sit in UIKit-only paths, so the macOS build of the previous commit could not see them; the iOS simulator build did. Co-Authored-By: Claude Opus 5 --- Sources/DashUIKit/Button/DashButton.swift | 19 ------------------- .../Components/AddressFieldView.swift | 11 ----------- .../Components/BottomSheet/BottomSheet.swift | 13 +------------ .../Components/BottomSheet/SheetFeature.swift | 4 ---- .../DashUIKit/Components/CoinSelector.swift | 3 --- .../ConverterCard/ConverterArrowBadge.swift | 1 - .../ConverterCard/ConverterCard.swift | 4 ---- .../ConverterCard/ConverterCardItem.swift | 1 - .../ConverterCard/ConverterCardRow.swift | 1 - Sources/DashUIKit/Components/DashAmount.swift | 9 --------- Sources/DashUIKit/Components/DashSwitch.swift | 2 -- .../EnterAmount/CurrencyOption.swift | 1 - .../EnterAmount/DashBalanceView.swift | 2 -- .../EnterAmount/DashPickerView.swift | 3 --- .../EnterAmount/DualSwapAmountView.swift | 2 -- .../EnterAmount/EnterAmountView.swift | 7 ------- .../EnterAmount/SwapAmountView.swift | 10 ---------- .../Components/Geometry/FrameReader.swift | 3 --- .../Components/Geometry/LocationReader.swift | 4 ---- .../Components/Geometry/ScaleToFitWidth.swift | 2 -- .../ScrollViewWithOnScrollChanged.swift | 2 -- .../Components/Icons/CheckmarkIcon.swift | 3 --- .../Components/Icons/ChevronIcon.swift | 5 ----- .../Components/Icons/InfoRoundIcon.swift | 4 ---- .../Components/Icons/XmarkIcon.swift | 3 --- .../Illustrations/ErrorIllustration.swift | 2 -- .../Illustrations/LoadingIllustration.swift | 3 --- .../Illustrations/SuccessIllustration.swift | 2 -- Sources/DashUIKit/Components/MenuItem.swift | 15 --------------- .../DashUIKit/Components/NavigationBar.swift | 14 -------------- .../Components/NumericKeyboardView.swift | 2 -- .../DashUIKit/Components/RadioButtonRow.swift | 2 -- .../Components/ReceiveEstimateView.swift | 2 -- Sources/DashUIKit/Components/SearchBar.swift | 4 +--- .../Switch/Components/ThumbView.swift | 2 -- .../Components/Switch/SwitchView.swift | 3 --- .../Components/SystemMessageView.swift | 3 --- Sources/DashUIKit/Components/Toast.swift | 4 ---- .../Components/TopIntro/TopIntroView.swift | 1 - .../Transaction/TransactionView.swift | 6 ------ .../DashUIKit/Foundation/Color+DashUI.swift | 3 --- .../DashUIKit/Foundation/DashTextStyle.swift | 5 ----- .../DashUIKit/Foundation/Font+DashUI.swift | 2 -- .../DashUIKit/Foundation/Icon_DashUI.swift | 3 --- .../DashUIKit/Foundation/Image+DashUI.swift | 2 -- .../Foundation/LineHeight+DashUI.swift | 2 -- Sources/DashUIKit/Table List/List1View.swift | 2 -- .../ViewModifiers/MenuViewModifier.swift | 1 - 48 files changed, 2 insertions(+), 202 deletions(-) diff --git a/Sources/DashUIKit/Button/DashButton.swift b/Sources/DashUIKit/Button/DashButton.swift index c84b256..cc8d028 100644 --- a/Sources/DashUIKit/Button/DashButton.swift +++ b/Sources/DashUIKit/Button/DashButton.swift @@ -17,7 +17,6 @@ import SwiftUI -@available(iOS 14, macOS 10.15, *) public enum DashButtonSize: Sendable { case large, medium, small, extraSmall @@ -67,7 +66,6 @@ public enum DashButtonSize: Sendable { } } -@available(iOS 14, macOS 10.15, *) public enum DashButtonStyle { case filledBlue, filledRed, strokeGray, tintedBlue, tintedGray, plainBlue, plainBlack, plainRed, filledWhiteBlue, tintedWhite, plainWhite @@ -120,7 +118,6 @@ public enum DashButtonStyle { } } -@available(iOS 14, macOS 11, *) public struct DashButton: View { public var text: String? = "Label" @@ -216,7 +213,6 @@ public struct DashButton: View { #if DEBUG -@available(iOS 17, macOS 14, *) private let previewButtonSizes: [DashButtonSize] = [ .large, .medium, @@ -224,7 +220,6 @@ private let previewButtonSizes: [DashButtonSize] = [ .extraSmall, ] -@available(iOS 17, macOS 14, *) private extension DashButtonSize { var previewTitle: String { switch self { @@ -236,7 +231,6 @@ private extension DashButtonSize { } } -@available(iOS 17, macOS 14, *) private extension DashButtonStyle { var previewTitle: String { switch self { @@ -264,7 +258,6 @@ private extension DashButtonStyle { } } -@available(iOS 17, macOS 14, *) private struct DashButtonStylePreview: View { let style: DashButtonStyle @@ -313,7 +306,6 @@ private struct DashButtonStylePreview: View { } } -@available(iOS 17, macOS 14, *) #Preview("Example") { VStack { DashButton( @@ -335,57 +327,46 @@ private struct DashButtonStylePreview: View { .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Filled Blue") { DashButtonStylePreview(style: .filledBlue) } -@available(iOS 17, macOS 14, *) #Preview("Filled Red") { DashButtonStylePreview(style: .filledRed) } -@available(iOS 17, macOS 14, *) #Preview("Stroke Gray") { DashButtonStylePreview(style: .strokeGray) } -@available(iOS 17, macOS 14, *) #Preview("Tinted Blue") { DashButtonStylePreview(style: .tintedBlue) } -@available(iOS 17, macOS 14, *) #Preview("Tinted Gray") { DashButtonStylePreview(style: .tintedGray) } -@available(iOS 17, macOS 14, *) #Preview("Plain Blue") { DashButtonStylePreview(style: .plainBlue) } -@available(iOS 17, macOS 14, *) #Preview("Plain Black") { DashButtonStylePreview(style: .plainBlack) } -@available(iOS 17, macOS 14, *) #Preview("Plain Red") { DashButtonStylePreview(style: .plainRed) } -@available(iOS 17, macOS 14, *) #Preview("Filled White Blue") { DashButtonStylePreview(style: .filledWhiteBlue) } -@available(iOS 17, macOS 14, *) #Preview("Tinted White") { DashButtonStylePreview(style: .tintedWhite) } -@available(iOS 17, macOS 14, *) #Preview("Plain White") { DashButtonStylePreview(style: .plainWhite) } diff --git a/Sources/DashUIKit/Components/AddressFieldView.swift b/Sources/DashUIKit/Components/AddressFieldView.swift index c3f320a..2f50aaa 100644 --- a/Sources/DashUIKit/Components/AddressFieldView.swift +++ b/Sources/DashUIKit/Components/AddressFieldView.swift @@ -28,7 +28,6 @@ private enum Layout { static let actionTapArea: CGFloat = 40 } -@available(iOS 15, macOS 12, *) public struct AddressFieldView: View { @Binding private var text: String @@ -118,7 +117,6 @@ public struct AddressFieldView: View { } -@available(iOS 15, macOS 12, *) public extension AddressFieldView where Accessory == EmptyView { /// No label accessory — the original shape, unchanged for callers that /// have nothing to put there. @@ -145,7 +143,6 @@ public extension AddressFieldView where Accessory == EmptyView { } } -@available(iOS 15, macOS 12, *) extension AddressFieldView { // MARK: - Subviews @@ -242,7 +239,6 @@ extension AddressFieldView { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Empty") { AddressFieldView( text: .constant(""), @@ -254,7 +250,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Empty with Paste button") { AddressFieldView( text: .constant(""), @@ -267,7 +262,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("With Text") { AddressFieldView( text: .constant("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"), @@ -279,7 +273,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Multiline") { AddressFieldView( text: .constant("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh\nbc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"), @@ -291,7 +284,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Error") { AddressFieldView( text: .constant("invalid-address"), @@ -303,7 +295,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Filled") { AddressFieldView( text: .constant("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"), @@ -315,7 +306,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Blurred + filled (no action button)") { AddressFieldView( text: .constant("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"), @@ -327,7 +317,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Label accessory") { AddressFieldView( text: .constant("yV1D1ivvSUyKPJnbFmzSTVh1MyZ3JbeVkY"), diff --git a/Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift b/Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift index 624a3a0..f3e6bc3 100644 --- a/Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift +++ b/Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift @@ -4,7 +4,6 @@ import SwiftUI import UIKit #endif -@available(iOS 14, macOS 11, *) public struct BottomSheet: View { @Environment(\.presentationMode) private var presentationMode @@ -163,7 +162,6 @@ public struct BottomSheet: View { } } -@available(iOS 14, macOS 11, *) public struct BottomSheetHeightPreferenceKey: PreferenceKey { public static let defaultValue: CGFloat = 0 @@ -172,7 +170,6 @@ public struct BottomSheetHeightPreferenceKey: PreferenceKey { } } -@available(iOS 14, macOS 11, *) public extension BottomSheet { /// Self-sizing bottom sheet: builds with `fillsHeight: false` and applies /// `.selfSizingSheet(...)` so the two can't be mismatched. Drop the result @@ -211,7 +208,6 @@ public extension BottomSheet { } } -@available(iOS 14, macOS 11, *) enum BottomSheetDismissalAction { /// The button is live while it still has something to do. Blocking dismissal only /// takes away what the sheet itself owns — the `dismiss()` it would call — so a host @@ -235,7 +231,6 @@ enum BottomSheetDismissalAction { } } -@available(iOS 14, macOS 11, *) private struct BottomSheetDismissalModifier: ViewModifier { let isEnabled: Bool @@ -248,7 +243,6 @@ private struct BottomSheetDismissalModifier: ViewModifier { } } -@available(iOS 14, macOS 11, *) public extension View { /// Sizes a `BottomSheet` (built with `fillsHeight: false`) to its content's natural height — /// no hardcoded `.height(...)` needed. @@ -301,7 +295,6 @@ public extension View { } } -@available(iOS 16.0, macOS 13.0, *) private struct SelfSizingSheetModifier: ViewModifier { let fallback: CGFloat let maxHeightFraction: CGFloat @@ -315,7 +308,7 @@ private struct SelfSizingSheetModifier: ViewModifier { GeometryReader { proxy in Color.clear .onAppear { update(proxy.size.height) } - .onChange(of: proxy.size.height) { update($0) } + .onChange(of: proxy.size.height) { _, newHeight in update(newHeight) } } ) .presentationDetents(detents) @@ -350,7 +343,6 @@ private struct SelfSizingSheetModifier: ViewModifier { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("BottomSheet Filled Height") { BottomSheet( title: "Bottom Sheet", @@ -369,7 +361,6 @@ private struct SelfSizingSheetModifier: ViewModifier { } } -@available(iOS 17, macOS 14, *) #Preview("BottomSheet Natural Height") { BottomSheet( title: "Bottom Sheet", @@ -389,7 +380,6 @@ private struct SelfSizingSheetModifier: ViewModifier { } } -@available(iOS 17, macOS 14, *) #Preview("BottomSheet Custom Background") { BottomSheet( title: "Bottom Sheet", @@ -411,7 +401,6 @@ private struct SelfSizingSheetModifier: ViewModifier { } } -@available(iOS 17, macOS 14, *) #Preview("BottomSheet Dismissal States") { VStack(spacing: 12) { BottomSheet( diff --git a/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift b/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift index c0c1bd8..13ec9a0 100644 --- a/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift +++ b/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift @@ -27,7 +27,6 @@ import SwiftUI /// leading mark is not always an image — a tinted glyph, a badge or a coloured /// container all appear in this position. It is sized to 40×40 here so a column /// of features stays aligned whatever each row puts in it. -@available(iOS 14, macOS 11, *) public struct SheetFeature: View { public var title: String public var description: String @@ -66,7 +65,6 @@ public struct SheetFeature: View { } } -@available(iOS 14, macOS 11, *) public extension SheetFeature where Icon == AnyView { /// Convenience for an asset in the icon slot. /// @@ -105,7 +103,6 @@ public extension SheetFeature where Icon == AnyView { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Feature list") { VStack(alignment: .leading, spacing: 16) { SheetFeature( @@ -125,7 +122,6 @@ public extension SheetFeature where Icon == AnyView { .background(Color.dash.primaryBackground) } -@available(iOS 17, macOS 14, *) #Preview("Custom icon slot") { SheetFeature( title: "Anything in the slot", diff --git a/Sources/DashUIKit/Components/CoinSelector.swift b/Sources/DashUIKit/Components/CoinSelector.swift index 7ce6ec4..4e7b6e1 100644 --- a/Sources/DashUIKit/Components/CoinSelector.swift +++ b/Sources/DashUIKit/Components/CoinSelector.swift @@ -16,7 +16,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) public enum CoinSelectorTrailing { case price(String) case halted @@ -31,7 +30,6 @@ private enum CoinSelectorLayout { static let badgeCornerRadius: CGFloat = 6 } -@available(iOS 14, macOS 11, *) public struct CoinSelector: View { private let name: String @@ -121,7 +119,6 @@ public struct CoinSelector: View { #if DEBUG -@available(iOS 14, macOS 11, *) struct CoinSelector_Previews: PreviewProvider { static var iconPlaceholder: some View { diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterArrowBadge.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterArrowBadge.swift index 31168fa..a055067 100644 --- a/Sources/DashUIKit/Components/ConverterCard/ConverterArrowBadge.swift +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterArrowBadge.swift @@ -24,7 +24,6 @@ import SwiftUI /// - `onSwap == nil` → static `arrow-down` icon, non-interactive. /// - `onSwap != nil` → tappable `diagonal-up-down` button that rotates 180° on each tap and fires /// the swap action. -@available(iOS 14, macOS 11, *) struct ConverterArrowBadge: View { let onSwap: (() -> Void)? @State private var rotation: Double = 0 diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift index 50e8c48..bb9ee22 100644 --- a/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift @@ -27,7 +27,6 @@ import SwiftUI /// /// A row given an `onTap` becomes a button and grows a trailing chevron, so a /// row that opens a picker reads as one at rest. -@available(iOS 14, macOS 11, *) public struct ConverterCard: View { private enum Layout { @@ -166,7 +165,6 @@ public struct ConverterCard: View { } #if DEBUG -@available(iOS 14, macOS 11, *) struct ConverterCard_Previews: PreviewProvider { static var previews: some View { Group { @@ -248,7 +246,6 @@ struct ConverterCard_Previews: PreviewProvider { } } -@available(iOS 14, macOS 11, *) struct SwapPreview: View { @State private var swapped = false @@ -273,6 +270,5 @@ struct SwapPreview: View { } } -@available(iOS 17, macOS 14, *) #Preview("Swap animation") { SwapPreview() } #endif diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift index c12f97c..0c3ca9b 100644 --- a/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift @@ -29,7 +29,6 @@ import SwiftUI /// (the Coinbase row keeps the same id whether it occupies the top or bottom slot). The default /// `id = title` is sufficient when titles are distinct and stable. Pass an explicit `id` if the /// title might change or if two rows could share a title. -@available(iOS 14, macOS 11, *) public struct ConverterCardItem: Identifiable { public let id: AnyHashable /// Static leading icon. Ignored when `iconView` is non-nil. diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift index 4b61fe2..ac7b208 100644 --- a/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift @@ -46,7 +46,6 @@ struct ConverterRowHeightKey: PreferenceKey { /// The chrome swallows touches by default: a row is display-only, and the badge drawn over the /// seam is the card's only control. A row that carries its own action opts back in with /// `isInteractive` — without it the button inside the content would never see the tap. -@available(iOS 14, macOS 11, *) struct ConverterCardRow: View { let slot: ConverterRowSlot var isInteractive: Bool = false diff --git a/Sources/DashUIKit/Components/DashAmount.swift b/Sources/DashUIKit/Components/DashAmount.swift index 23efd6d..886ee5b 100644 --- a/Sources/DashUIKit/Components/DashAmount.swift +++ b/Sources/DashUIKit/Components/DashAmount.swift @@ -19,7 +19,6 @@ import SwiftUI // MARK: - Sign control -@available(iOS 14, macOS 11, *) public enum DashAmountSign { /// Never render a sign: "0.05 Ð". case none @@ -59,7 +58,6 @@ public enum DashAmountFormat { // MARK: - DashAmount view -@available(iOS 14, macOS 11, *) public struct DashAmount: View { public var amount: Int64 @@ -122,37 +120,31 @@ public struct DashAmount: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("sign: .none (positive)") { DashAmount(amount: 6_791_000, fontSize: 15, sign: .none) .padding() } -@available(iOS 17, macOS 14, *) #Preview("sign: .negativeOnly — positive (no prefix)") { DashAmount(amount: 6_791_000, fontSize: 15, sign: .negativeOnly) .padding() } -@available(iOS 17, macOS 14, *) #Preview("sign: .negativeOnly — negative (-)") { DashAmount(amount: -6_791_000, fontSize: 15, sign: .negativeOnly) .padding() } -@available(iOS 17, macOS 14, *) #Preview("sign: .always — positive (+)") { DashAmount(amount: 6_791_000, fontSize: 15, sign: .always) .padding() } -@available(iOS 17, macOS 14, *) #Preview("sign: .always — negative (-)") { DashAmount(amount: -6_791_000, fontSize: 15, sign: .always) .padding() } -@available(iOS 17, macOS 14, *) #Preview("Zero (no sign in any mode)") { VStack(spacing: 8) { DashAmount(amount: 0, fontSize: 15, sign: .none) @@ -162,7 +154,6 @@ public struct DashAmount: View { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Not available (Int64.max)") { DashAmount(amount: .max, fontSize: 15) .padding() diff --git a/Sources/DashUIKit/Components/DashSwitch.swift b/Sources/DashUIKit/Components/DashSwitch.swift index 80e02ad..215bdcc 100644 --- a/Sources/DashUIKit/Components/DashSwitch.swift +++ b/Sources/DashUIKit/Components/DashSwitch.swift @@ -20,7 +20,6 @@ import SwiftUI // TODO: Add a custom DashToggleStyle using switchThumbFill / switchTrackFillOff / // switchTrackFillOffDisabled tokens for full pixel-exact fidelity when needed. -@available(iOS 14, *) public struct DashSwitch: View { @Binding private var isOn: Bool @@ -38,7 +37,6 @@ public struct DashSwitch: View { #if DEBUG -@available(iOS 17, *) #Preview { @Previewable @State var on = true VStack(spacing: 20) { diff --git a/Sources/DashUIKit/Components/EnterAmount/CurrencyOption.swift b/Sources/DashUIKit/Components/EnterAmount/CurrencyOption.swift index beade79..bf8fe13 100644 --- a/Sources/DashUIKit/Components/EnterAmount/CurrencyOption.swift +++ b/Sources/DashUIKit/Components/EnterAmount/CurrencyOption.swift @@ -35,7 +35,6 @@ enum DashCurrencySymbol { // MARK: - CurrencyOption -@available(iOS 14, macOS 11, *) public enum CurrencyOption: Hashable { case fiat(String) case dash diff --git a/Sources/DashUIKit/Components/EnterAmount/DashBalanceView.swift b/Sources/DashUIKit/Components/EnterAmount/DashBalanceView.swift index 20e5264..33a9258 100644 --- a/Sources/DashUIKit/Components/EnterAmount/DashBalanceView.swift +++ b/Sources/DashUIKit/Components/EnterAmount/DashBalanceView.swift @@ -22,7 +22,6 @@ import SwiftUI /// Trailing balance for the convert source row: a symbol-free Dash amount followed by the Dash /// symbol, with the fiat value beneath. Both strings come from the view model /// (`dashBalanceFormatted` / `dashBalanceFiat`); pass `fiat: nil` to hide the fiat line. -@available(iOS 14, macOS 11, *) public struct DashBalanceView: View { /// Symbol-free formatted balance, e.g. "1.5". public let balance: String @@ -63,7 +62,6 @@ public struct DashBalanceView: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview { VStack(spacing: 16) { DashBalanceView(balance: "1.5", fiat: "$ 150.00") diff --git a/Sources/DashUIKit/Components/EnterAmount/DashPickerView.swift b/Sources/DashUIKit/Components/EnterAmount/DashPickerView.swift index 787e7da..c8dd6fe 100644 --- a/Sources/DashUIKit/Components/EnterAmount/DashPickerView.swift +++ b/Sources/DashUIKit/Components/EnterAmount/DashPickerView.swift @@ -17,14 +17,12 @@ import SwiftUI -@available(iOS 14, macOS 11, *) private enum Layout { static let hPadding: CGFloat = 6 static let vPadding: CGFloat = 3 static let cornerRadius: CGFloat = 6 } -@available(iOS 14, macOS 11, *) public struct DashPickerView: View { public let options: [Option] @@ -63,7 +61,6 @@ public struct DashPickerView: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview { DashPickerView( options: ["US$", "DASH", "BTC"], diff --git a/Sources/DashUIKit/Components/EnterAmount/DualSwapAmountView.swift b/Sources/DashUIKit/Components/EnterAmount/DualSwapAmountView.swift index 2830e6c..9e624d8 100644 --- a/Sources/DashUIKit/Components/EnterAmount/DualSwapAmountView.swift +++ b/Sources/DashUIKit/Components/EnterAmount/DualSwapAmountView.swift @@ -30,7 +30,6 @@ import SwiftUI /// or view recreation. The content in each row never changes — only the visual position does. /// /// Tapping anywhere in the container (except the chevron) fires `onSwap`. -@available(iOS 14, macOS 11, *) internal struct DualSwapAmountView: View { let primaryAmount: String let secondaryAmount: String @@ -71,7 +70,6 @@ internal struct DualSwapAmountView: View { /// Trailing vertical list of currency-code buttons; the selected entry is highlighted. /// Fixed width matches `DualSwapLayout.sideColumnWidth` so it occupies its side column exactly. -@available(iOS 14, macOS 11, *) internal struct DualInputTypeSwitcher: View { let codes: [String] let selected: String diff --git a/Sources/DashUIKit/Components/EnterAmount/EnterAmountView.swift b/Sources/DashUIKit/Components/EnterAmount/EnterAmountView.swift index d984248..ca055bc 100644 --- a/Sources/DashUIKit/Components/EnterAmount/EnterAmountView.swift +++ b/Sources/DashUIKit/Components/EnterAmount/EnterAmountView.swift @@ -20,7 +20,6 @@ import SwiftUI // MARK: - EnterAmountStyle /// Controls which visual layout `EnterAmountView` renders. -@available(iOS 14, macOS 11, *) public enum EnterAmountStyle { /// Single-amount input: Max button + `SwapAmountView` + `DashPickerView`. (default) case single @@ -30,7 +29,6 @@ public enum EnterAmountStyle { // MARK: - EnterAmountView -@available(iOS 14, macOS 11, *) public struct EnterAmountView: View { // MARK: State A (.single) — existing public API @@ -271,7 +269,6 @@ public struct EnterAmountView: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Single state") { EnterAmountView( value: .constant("12.5"), @@ -283,19 +280,16 @@ public struct EnterAmountView: View { .padding(20) } -@available(iOS 17, macOS 14, *) #Preview("Dual swap — with Max button (interactive)") { DualSwapPreviewContainer(showMax: true) .background(Color.dash.secondaryBackground) } -@available(iOS 17, macOS 14, *) #Preview("Dual swap — no Max button (interactive)") { DualSwapPreviewContainer(showMax: false) .background(Color.dash.secondaryBackground) } -@available(iOS 17, macOS 14, *) #Preview("Dual swap — edge cases") { VStack(spacing: 28) { // Very long Dash value — amount should scale to fit @@ -338,7 +332,6 @@ public struct EnterAmountView: View { .background(Color.dash.secondaryBackground) } -@available(iOS 17, macOS 14, *) private struct DualSwapPreviewContainer: View { let showMax: Bool @State private var isPrimarySelected = true diff --git a/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift b/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift index 2181451..f17b6d8 100644 --- a/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift +++ b/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift @@ -22,7 +22,6 @@ import UIKit // MARK: - SwapAmountView -@available(iOS 14, macOS 11, *) public struct SwapAmountView: View { // MARK: Primary row props @@ -266,7 +265,6 @@ public struct SwapAmountView: View { /// /// Result: correct bold weight in the large slot, correct regular weight in the small slot, /// smooth size transition via `scaleEffect` bridging the font snap. -@available(iOS 14, macOS 11, *) private struct AnimatedSwapLayout: View { // MARK: Content props @@ -481,7 +479,6 @@ private struct AnimatedSwapLayout: View { // MARK: - Paste Context Menu -@available(iOS 14, macOS 11, *) extension View { /// Long-press to paste. Intentionally uses an `onLongPressGesture` and NOT `.contextMenu`: /// `.contextMenu` adds a `_UIReparentingView` to the host's view hierarchy, which is @@ -509,7 +506,6 @@ extension View { // MARK: - Swap animation layout constants // Internal so DualSwapAmountView can read them if needed; private would hide from the module. -@available(iOS 14, macOS 11, *) enum SwapAnimLayout { // Slot heights (reference: SendAmountAmountsStack) static let primaryHeight: CGFloat = 41 @@ -537,7 +533,6 @@ enum SwapAnimLayout { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Normal amounts") { VStack(spacing: 20) { SwapAmountView( @@ -556,7 +551,6 @@ enum SwapAnimLayout { .padding(20) } -@available(iOS 17, macOS 14, *) #Preview("Edge cases — scaling") { VStack(spacing: 20) { SwapAmountView( @@ -579,7 +573,6 @@ enum SwapAnimLayout { .padding(20) } -@available(iOS 17, macOS 14, *) #Preview("Edge cases — precision") { VStack(spacing: 20) { SwapAmountView( @@ -603,7 +596,6 @@ enum SwapAnimLayout { .padding(20) } -@available(iOS 17, macOS 14, *) #Preview("Leading zero — bare decimal") { VStack(spacing: 20) { SwapAmountView( @@ -621,7 +613,6 @@ enum SwapAnimLayout { .padding(20) } -@available(iOS 17, macOS 14, *) #Preview("Animated dual-swap (interactive)") { VStack { SwapAmountView( @@ -637,7 +628,6 @@ enum SwapAnimLayout { } } -@available(iOS 17, macOS 14, *) private struct SwapAmountAnimatedPreview: View { @State private var isPrimarySelected = true diff --git a/Sources/DashUIKit/Components/Geometry/FrameReader.swift b/Sources/DashUIKit/Components/Geometry/FrameReader.swift index 24cce9f..27c6ffc 100644 --- a/Sources/DashUIKit/Components/Geometry/FrameReader.swift +++ b/Sources/DashUIKit/Components/Geometry/FrameReader.swift @@ -24,7 +24,6 @@ import SwiftUI /// It paints nothing: a transparent, greedy overlay that forwards the resolved `CGRect` /// once on first layout and again on every subsequent change. Attach it through /// ``SwiftUI/View/readingFrame(coordinateSpace:onChange:)`` rather than building it directly. -@available(iOS 14, macOS 11, *) struct FrameReader: View { private let coordinateSpace: CoordinateSpace @@ -51,7 +50,6 @@ struct FrameReader: View { // MARK: - View sugar -@available(iOS 14, macOS 11, *) public extension View { /// Reports this view's frame — in `coordinateSpace` — as it appears and whenever it moves @@ -68,7 +66,6 @@ public extension View { #if DEBUG -@available(iOS 14, macOS 11, *) struct FrameReader_Previews: PreviewProvider { private struct Demo: View { diff --git a/Sources/DashUIKit/Components/Geometry/LocationReader.swift b/Sources/DashUIKit/Components/Geometry/LocationReader.swift index 4d02488..2f9fd95 100644 --- a/Sources/DashUIKit/Components/Geometry/LocationReader.swift +++ b/Sources/DashUIKit/Components/Geometry/LocationReader.swift @@ -24,7 +24,6 @@ import SwiftUI /// The point-sized sibling of ``FrameReader``: it collapses to a zero-sized probe and only /// emits the midpoint, which is all most scroll/position effects need. Reach for it through /// ``SwiftUI/View/readingLocation(coordinateSpace:onChange:)``. -@available(iOS 14, macOS 11, *) struct LocationReader: View { private let coordinateSpace: CoordinateSpace @@ -50,7 +49,6 @@ struct LocationReader: View { // MARK: - View sugar -@available(iOS 14, macOS 11, *) extension View { /// Reports this view's center point — in `coordinateSpace` — on appear and on every change. @@ -65,7 +63,6 @@ extension View { // MARK: - Helpers -@available(iOS 14, macOS 11, *) private extension CGRect { /// Geometric center of the rect. var center: CGPoint { CGPoint(x: midX, y: midY) } @@ -75,7 +72,6 @@ private extension CGRect { #if DEBUG -@available(iOS 14, macOS 11, *) struct LocationReader_Previews: PreviewProvider { private struct Demo: View { diff --git a/Sources/DashUIKit/Components/Geometry/ScaleToFitWidth.swift b/Sources/DashUIKit/Components/Geometry/ScaleToFitWidth.swift index cfce5b8..a077725 100644 --- a/Sources/DashUIKit/Components/Geometry/ScaleToFitWidth.swift +++ b/Sources/DashUIKit/Components/Geometry/ScaleToFitWidth.swift @@ -34,7 +34,6 @@ private struct ScaleToFitContentSizeKey: PreferenceKey { /// /// The modifier reserves the content's natural single-line height (constant) so it keeps the /// surrounding vertical layout stable while only the horizontal scale changes. -@available(iOS 14, macOS 11, *) public struct ScaleToFitWidth: ViewModifier { public var minScale: CGFloat = 0.35 @@ -70,7 +69,6 @@ public struct ScaleToFitWidth: ViewModifier { } } -@available(iOS 14, macOS 11, *) public extension View { /// Scales the view uniformly to fit the available width on one line, down to `minScale`. func scaleToFitWidth(minScale: CGFloat = 0.35) -> some View { diff --git a/Sources/DashUIKit/Components/Geometry/ScrollViewWithOnScrollChanged.swift b/Sources/DashUIKit/Components/Geometry/ScrollViewWithOnScrollChanged.swift index 8cb5825..17b5269 100644 --- a/Sources/DashUIKit/Components/Geometry/ScrollViewWithOnScrollChanged.swift +++ b/Sources/DashUIKit/Components/Geometry/ScrollViewWithOnScrollChanged.swift @@ -26,7 +26,6 @@ import SwiftUI /// The trick: a zero-sized ``LocationReader`` rides at the very top of the content inside a /// private named coordinate space. As the content slides, the probe's position within that /// space is exactly the scroll offset, which is forwarded through `onScrollChanged`. -@available(iOS 14, macOS 11, *) public struct ScrollViewWithOnScrollChanged: View { private let axes: Axis.Set @@ -67,7 +66,6 @@ public struct ScrollViewWithOnScrollChanged: View { #if DEBUG -@available(iOS 14, macOS 11, *) struct ScrollViewWithOnScrollChanged_Previews: PreviewProvider { private struct Demo: View { diff --git a/Sources/DashUIKit/Components/Icons/CheckmarkIcon.swift b/Sources/DashUIKit/Components/Icons/CheckmarkIcon.swift index 375e588..063e064 100644 --- a/Sources/DashUIKit/Components/Icons/CheckmarkIcon.swift +++ b/Sources/DashUIKit/Components/Icons/CheckmarkIcon.swift @@ -27,7 +27,6 @@ import SwiftUI /// The default colour is `Color.dash.blue`, which is the `#008DE4` the SVG /// strokes with; naming the token rather than the hex keeps it following the /// palette. -@available(iOS 14, macOS 11, *) public struct CheckmarkIcon: View { public var size: CGFloat = 15 public var color: Color = Color.dash.blue @@ -57,7 +56,6 @@ public struct CheckmarkIcon: View { /// The tick polyline, normalized from the 15×12 source viewBox so it keeps its /// proportions at any size. -@available(iOS 14, macOS 11, *) private struct CheckmarkShape: Shape { private static let viewBox = CGSize(width: 15, height: 12) @@ -88,7 +86,6 @@ private struct CheckmarkShape: Shape { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview { VStack(spacing: 24) { CheckmarkIcon() diff --git a/Sources/DashUIKit/Components/Icons/ChevronIcon.swift b/Sources/DashUIKit/Components/Icons/ChevronIcon.swift index 8539ab1..b1f0cce 100644 --- a/Sources/DashUIKit/Components/Icons/ChevronIcon.swift +++ b/Sources/DashUIKit/Components/Icons/ChevronIcon.swift @@ -33,7 +33,6 @@ import SwiftUI /// /// The default colour is `Color.dash.gray300Alpha90` — the palette entry for /// the `#B0B6BC` at 90% the SVG strokes with. -@available(iOS 14, macOS 11, *) public struct ChevronIcon: View { /// Where the chevron points. @@ -103,7 +102,6 @@ public struct ChevronIcon: View { /// The chevron polyline, normalized from the 7×12 source viewBox so it keeps /// its proportions at any size. Points right; `ChevronIcon` rotates it. -@available(iOS 14, macOS 11, *) private struct ChevronShape: Shape { private static let viewBox = CGSize(width: 7, height: 12) @@ -135,7 +133,6 @@ private struct ChevronShape: Shape { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Four directions") { HStack(spacing: 32) { ChevronIcon(direction: .left) @@ -149,7 +146,6 @@ private struct ChevronShape: Shape { /// Each one drawn on its own bounds, so the frame the rotation reports is /// visible: left/right are tall, up/down are wide. -@available(iOS 17, macOS 14, *) #Preview("Measured bounds") { HStack(spacing: 24) { ForEach([ChevronIcon.Direction.left, .right, .up, .down], id: \.angle.degrees) { direction in @@ -161,7 +157,6 @@ private struct ChevronShape: Shape { .background(Color.dash.primaryBackground) } -@available(iOS 17, macOS 14, *) #Preview("Dark") { HStack(spacing: 32) { ChevronIcon(direction: .left) diff --git a/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift b/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift index e6c4fa5..863b953 100644 --- a/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift +++ b/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift @@ -24,7 +24,6 @@ import SwiftUI /// Drawn rather than shipped as an asset, for the same reason `XmarkIcon` is: /// it stays crisp at any size, and the disc takes the `Blue` token instead of /// baking `#008DE4` into a PDF that would then miss a palette change. -@available(iOS 14, macOS 11, *) public struct InfoRoundIcon: View { public var size: CGFloat = 19 public var color: Color = Color.dash.blue @@ -70,7 +69,6 @@ public struct InfoRoundIcon: View { /// The dot is a 0.01-long segment rather than a circle — with a round cap that /// renders as a dot of exactly the stem's weight, which is how the source /// draws it and what keeps the two visually matched at every size. -@available(iOS 14, macOS 11, *) private struct InfoGlyphShape: Shape { private let centerXRatio: CGFloat = 9.2998 / 19 private let stemTopRatio: CGFloat = 9.30005 / 19 @@ -92,7 +90,6 @@ private struct InfoGlyphShape: Shape { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Sizes") { HStack(alignment: .center, spacing: 12) { InfoRoundIcon(size: 14) @@ -103,7 +100,6 @@ private struct InfoGlyphShape: Shape { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Recoloured") { HStack(spacing: 12) { InfoRoundIcon(size: 32, color: Color.dash.gray300) diff --git a/Sources/DashUIKit/Components/Icons/XmarkIcon.swift b/Sources/DashUIKit/Components/Icons/XmarkIcon.swift index eea8d4c..1755f2a 100644 --- a/Sources/DashUIKit/Components/Icons/XmarkIcon.swift +++ b/Sources/DashUIKit/Components/Icons/XmarkIcon.swift @@ -21,7 +21,6 @@ import SwiftUI /// A code-drawn "✕" (close) icon. Mirrors the source SVG (9×9 viewBox, two diagonals /// inset from 0.75 to 7.75, round caps/joins). Scales cleanly to any `size`. -@available(iOS 14, macOS 11, *) public struct XmarkIcon: View { public var size: CGFloat = 9 public var color: Color = Color.dash.primaryText @@ -51,7 +50,6 @@ public struct XmarkIcon: View { /// Two diagonal strokes forming an "✕". Endpoints are normalized from the 9-unit /// source viewBox (inset 0.75 → 7.75) so the cross keeps its proportions at any size. -@available(iOS 14, macOS 11, *) private struct XmarkShape: Shape { private let insetRatio: CGFloat = 0.75 / 9 private let extentRatio: CGFloat = 7.75 / 9 @@ -73,7 +71,6 @@ private struct XmarkShape: Shape { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview { VStack(spacing: 24) { XmarkIcon() diff --git a/Sources/DashUIKit/Components/Illustrations/ErrorIllustration.swift b/Sources/DashUIKit/Components/Illustrations/ErrorIllustration.swift index 2265e0f..d6bf6ed 100644 --- a/Sources/DashUIKit/Components/Illustrations/ErrorIllustration.swift +++ b/Sources/DashUIKit/Components/Illustrations/ErrorIllustration.swift @@ -17,7 +17,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) public struct ErrorIllustration: View { public init() {} @@ -38,7 +37,6 @@ public struct ErrorIllustration: View { #if DEBUG -@available(iOS 14, macOS 11, *) struct ErrorIllustration_Previews: PreviewProvider { static var previews: some View { ErrorIllustration() diff --git a/Sources/DashUIKit/Components/Illustrations/LoadingIllustration.swift b/Sources/DashUIKit/Components/Illustrations/LoadingIllustration.swift index 6d5a3a0..d597f3b 100644 --- a/Sources/DashUIKit/Components/Illustrations/LoadingIllustration.swift +++ b/Sources/DashUIKit/Components/Illustrations/LoadingIllustration.swift @@ -22,7 +22,6 @@ import SwiftUI /// Wrapper that centers the loading spinner inside a fixed-size frame, matching the /// Maya design (Figma node 6:254 — a 90×90 frame containing a 61.73 spinner). -@available(iOS 14, macOS 11, *) public struct LoadingIllustration: View { /// Diameter of the spinner. @@ -52,7 +51,6 @@ public struct LoadingIllustration: View { /// `UIActivityIndicatorView`: the spokes are **static**, and instead of rotating the whole ring /// the bright "head" steps clockwise from spoke to spoke while the others fade — i.e. each line /// changes opacity in turn. The opacity crossfades between steps for a smooth iOS-style look. -@available(iOS 14, macOS 11, *) public struct LoadingSpinner: View { /// Default tint — DS blue. @@ -129,7 +127,6 @@ public struct LoadingSpinner: View { #if DEBUG -@available(iOS 14, macOS 11, *) struct LoadingIllustration_Previews: PreviewProvider { static var previews: some View { VStack(spacing: 40) { diff --git a/Sources/DashUIKit/Components/Illustrations/SuccessIllustration.swift b/Sources/DashUIKit/Components/Illustrations/SuccessIllustration.swift index f1dc914..a074776 100644 --- a/Sources/DashUIKit/Components/Illustrations/SuccessIllustration.swift +++ b/Sources/DashUIKit/Components/Illustrations/SuccessIllustration.swift @@ -17,7 +17,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) public struct SuccessIllustration: View { public init() {} @@ -38,7 +37,6 @@ public struct SuccessIllustration: View { #if DEBUG -@available(iOS 14, macOS 11, *) struct SuccessIllustration_Previews: PreviewProvider { static var previews: some View { SuccessIllustration() diff --git a/Sources/DashUIKit/Components/MenuItem.swift b/Sources/DashUIKit/Components/MenuItem.swift index 61ed051..8828075 100644 --- a/Sources/DashUIKit/Components/MenuItem.swift +++ b/Sources/DashUIKit/Components/MenuItem.swift @@ -20,7 +20,6 @@ import SwiftUI /// Finite set of trailing accessories for `MenuItem`. /// Add a new case here — not a per-call-site font/color override — when a /// new trailing look is needed, to keep all rows consistent. -@available(iOS 14, macOS 11, *) public enum MenuItemAccessory { case none case toggle(isOn: Binding) @@ -42,13 +41,11 @@ public enum MenuItemAccessory { /// /// `.round` is the design system's own info mark; `.icon` stays open for a row /// that needs to flag something else entirely. -@available(iOS 14, macOS 11, *) public enum MenuItemInfo { case round(color: Color) case icon(DashIconSource) } -@available(iOS 14, macOS 11, *) public struct MenuItem: View { public var leadingIcon: DashIconSource? @@ -165,13 +162,11 @@ public struct MenuItem: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Title only") { MenuItem(title: "Notifications") .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Title + helpText") { MenuItem( title: "Recovery phrase", @@ -180,7 +175,6 @@ public struct MenuItem: View { .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Title + info + helpText") { MenuItem( title: "Network fee", @@ -191,7 +185,6 @@ public struct MenuItem: View { .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Accessory: toggle") { @Previewable @State var isOn = true MenuItem( @@ -201,7 +194,6 @@ public struct MenuItem: View { .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Accessory: text") { MenuItem( title: "Balance", @@ -210,7 +202,6 @@ public struct MenuItem: View { .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Accessory: button") { MenuItem( title: "Withdraw", @@ -224,7 +215,6 @@ public struct MenuItem: View { .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Accessory: balance default (.negativeOnly)") { VStack(spacing: 0) { MenuItem( @@ -240,7 +230,6 @@ public struct MenuItem: View { .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Accessory: balance .always (transaction style)") { VStack(spacing: 0) { MenuItem( @@ -256,7 +245,6 @@ public struct MenuItem: View { .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Accessory: balance zero") { MenuItem( title: "Available", @@ -265,7 +253,6 @@ public struct MenuItem: View { .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Accessory: balance not available") { MenuItem( title: "Pending", @@ -275,7 +262,6 @@ public struct MenuItem: View { } /// A picker list: one row marked, the rest holding the tick's slot empty. -@available(iOS 17, macOS 14, *) #Preview("Accessory: selection") { VStack(spacing: 0) { MenuItem( @@ -299,7 +285,6 @@ public struct MenuItem: View { .padding(.horizontal) } -@available(iOS 17, macOS 14, *) #Preview("Enabled vs disabled") { VStack(spacing: 0) { MenuItem( diff --git a/Sources/DashUIKit/Components/NavigationBar.swift b/Sources/DashUIKit/Components/NavigationBar.swift index 40f96e2..0787db8 100644 --- a/Sources/DashUIKit/Components/NavigationBar.swift +++ b/Sources/DashUIKit/Components/NavigationBar.swift @@ -17,7 +17,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) public struct NavigationBar: View { private let leading: Leading private let central: Central @@ -50,49 +49,42 @@ public struct NavigationBar: View } } -@available(iOS 14, macOS 11, *) public extension NavigationBar where Trailing == EmptyView { init(@ViewBuilder leading: () -> Leading, @ViewBuilder central: () -> Central) { self.init(leading: leading, central: central, trailing: { EmptyView() }) } } -@available(iOS 14, macOS 11, *) public extension NavigationBar where Central == EmptyView { init(@ViewBuilder leading: () -> Leading, @ViewBuilder trailing: () -> Trailing) { self.init(leading: leading, central: { EmptyView() }, trailing: trailing) } } -@available(iOS 14, macOS 11, *) public extension NavigationBar where Leading == EmptyView { init(@ViewBuilder central: () -> Central, @ViewBuilder trailing: () -> Trailing) { self.init(leading: { EmptyView() }, central: central, trailing: trailing) } } -@available(iOS 14, macOS 11, *) public extension NavigationBar where Central == EmptyView, Trailing == EmptyView { init(@ViewBuilder leading: () -> Leading) { self.init(leading: leading, central: { EmptyView() }, trailing: { EmptyView() }) } } -@available(iOS 14, macOS 11, *) public extension NavigationBar where Leading == EmptyView, Trailing == EmptyView { init(@ViewBuilder central: () -> Central) { self.init(leading: { EmptyView() }, central: central, trailing: { EmptyView() }) } } -@available(iOS 14, macOS 11, *) public extension NavigationBar where Leading == EmptyView, Central == EmptyView { init(@ViewBuilder trailing: () -> Trailing) { self.init(leading: { EmptyView() }, central: { EmptyView() }, trailing: trailing) } } -@available(iOS 14, macOS 11, *) public enum NavigationBarElement: String { case back = "navigationbar-back" case close = "navigationbar-close" @@ -130,7 +122,6 @@ public enum NavigationBarElement: String { } } -@available(iOS 14, macOS 11, *) private struct NavigationBarButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label @@ -140,14 +131,12 @@ private struct NavigationBarButtonStyle: ButtonStyle { } } -@available(iOS 17, macOS 14, *) #Preview("NavigationBar Back") { NavigationBar( leading: { NavigationBarElement.back.button { } } ) } -@available(iOS 17, macOS 14, *) #Preview("NavigationBar Back Title") { NavigationBar( leading: { NavigationBarElement.back.button { } }, @@ -159,7 +148,6 @@ private struct NavigationBarButtonStyle: ButtonStyle { ) } -@available(iOS 17, macOS 14, *) #Preview("NavigationBar Back Title Info") { NavigationBar( leading: { NavigationBarElement.back.button { } }, @@ -172,7 +160,6 @@ private struct NavigationBarButtonStyle: ButtonStyle { ) } -@available(iOS 17, macOS 14, *) #Preview("NavigationBar Variants") { VStack { NavigationBar( @@ -197,7 +184,6 @@ private struct NavigationBarButtonStyle: ButtonStyle { } } -@available(iOS 17, macOS 14, *) #Preview("NavigationBar Title Close") { NavigationBar( central: { diff --git a/Sources/DashUIKit/Components/NumericKeyboardView.swift b/Sources/DashUIKit/Components/NumericKeyboardView.swift index 81f0546..92c40a3 100644 --- a/Sources/DashUIKit/Components/NumericKeyboardView.swift +++ b/Sources/DashUIKit/Components/NumericKeyboardView.swift @@ -80,7 +80,6 @@ enum NumericKeyboardLocaleSupport { // MARK: - NumericKeyboardView -@available(iOS 14, macOS 11, *) public struct NumericKeyboardView: View { private enum Layout { @@ -239,7 +238,6 @@ public struct NumericKeyboardView: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview { ZStack { Color.red.opacity(0.3) diff --git a/Sources/DashUIKit/Components/RadioButtonRow.swift b/Sources/DashUIKit/Components/RadioButtonRow.swift index c6bfa4e..734b77c 100644 --- a/Sources/DashUIKit/Components/RadioButtonRow.swift +++ b/Sources/DashUIKit/Components/RadioButtonRow.swift @@ -17,7 +17,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) public struct RadioButtonRow: View { public enum Style { @@ -104,7 +103,6 @@ public struct RadioButtonRow: View { #if DEBUG -@available(iOS 14, macOS 11, *) struct RadioButtonRow_Previews: PreviewProvider { static var previews: some View { VStack(spacing: 0) { diff --git a/Sources/DashUIKit/Components/ReceiveEstimateView.swift b/Sources/DashUIKit/Components/ReceiveEstimateView.swift index c0b850f..6cff1ef 100644 --- a/Sources/DashUIKit/Components/ReceiveEstimateView.swift +++ b/Sources/DashUIKit/Components/ReceiveEstimateView.swift @@ -24,7 +24,6 @@ import SwiftUI /// receive amount under a caption. Collapses to nothing when `isVisible` is `false`. /// /// The `title` label is passed in so the component stays localization-agnostic. -@available(iOS 14, macOS 11, *) public struct ReceiveEstimateView: View { private let isVisible: Bool @@ -92,7 +91,6 @@ public struct ReceiveEstimateView: View { } #if DEBUG -@available(iOS 14, macOS 11, *) struct ReceiveEstimateView_Previews: PreviewProvider { static var previews: some View { VStack(spacing: 24) { diff --git a/Sources/DashUIKit/Components/SearchBar.swift b/Sources/DashUIKit/Components/SearchBar.swift index 3fa0e53..4917cd1 100644 --- a/Sources/DashUIKit/Components/SearchBar.swift +++ b/Sources/DashUIKit/Components/SearchBar.swift @@ -20,7 +20,6 @@ import SwiftUI // MARK: - Public shell -@available(iOS 14, *) public struct SearchBar: View { @Binding private var text: String private let placeholder: String @@ -80,7 +79,7 @@ private struct SearchBarFocused: View { .onAppear { isEditing = isFocused } - .onChange(of: isFocused) { focused in + .onChange(of: isFocused) { _, focused in withAnimation(.easeInOut(duration: Layout.animationDuration)) { isEditing = focused } @@ -151,7 +150,6 @@ private struct SearchBarFocused: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview { @Previewable @State var text = "" SearchBar(text: $text) diff --git a/Sources/DashUIKit/Components/Switch/Components/ThumbView.swift b/Sources/DashUIKit/Components/Switch/Components/ThumbView.swift index fe58a4d..c5e352a 100644 --- a/Sources/DashUIKit/Components/Switch/Components/ThumbView.swift +++ b/Sources/DashUIKit/Components/Switch/Components/ThumbView.swift @@ -7,7 +7,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) struct ThumbView: View { private struct Constants { @@ -28,7 +27,6 @@ struct ThumbView: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview { ThumbView() .padding() diff --git a/Sources/DashUIKit/Components/Switch/SwitchView.swift b/Sources/DashUIKit/Components/Switch/SwitchView.swift index 582e755..3e260bc 100644 --- a/Sources/DashUIKit/Components/Switch/SwitchView.swift +++ b/Sources/DashUIKit/Components/Switch/SwitchView.swift @@ -7,7 +7,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) public struct SwitchView: View { private struct Constants { @@ -60,7 +59,6 @@ public struct SwitchView: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("States") { VStack(alignment: .leading, spacing: 20) { SwitchView(isOn: .constant(true)) @@ -71,7 +69,6 @@ public struct SwitchView: View { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Interactive") { @Previewable @State var isOn = false SwitchView(isOn: $isOn) diff --git a/Sources/DashUIKit/Components/SystemMessageView.swift b/Sources/DashUIKit/Components/SystemMessageView.swift index 67752c8..39598f5 100644 --- a/Sources/DashUIKit/Components/SystemMessageView.swift +++ b/Sources/DashUIKit/Components/SystemMessageView.swift @@ -17,7 +17,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) public struct SystemMessageView: View { public let title: String @@ -116,7 +115,6 @@ public struct SystemMessageView: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Default (back-compat)") { SystemMessageView( title: "You have a balance on CrowdNode", @@ -125,7 +123,6 @@ public struct SystemMessageView: View { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Custom icon + background + two buttons + close") { SystemMessageView( title: "Address Expired", diff --git a/Sources/DashUIKit/Components/Toast.swift b/Sources/DashUIKit/Components/Toast.swift index 84dfbf7..6b84b73 100644 --- a/Sources/DashUIKit/Components/Toast.swift +++ b/Sources/DashUIKit/Components/Toast.swift @@ -21,7 +21,6 @@ import UIKit // MARK: - BackgroundBlurView -@available(iOS 14, *) struct BackgroundBlurView: UIViewRepresentable { func makeUIView(context: Context) -> UIVisualEffectView { UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterialDark)) @@ -42,7 +41,6 @@ struct BackgroundBlurView: UIViewRepresentable { /// toast-success.imageset /// toast-copied.imageset /// toast-loading.imageset (optional — `loading` uses `LoadingSpinner`) -@available(iOS 14, macOS 11, *) public enum ToastStyle { case warning, info, error, success, copied, loading, noInternet @@ -64,7 +62,6 @@ public enum ToastStyle { // MARK: - Toast -@available(iOS 14, macOS 11, *) public struct Toast: View { private let style: ToastStyle @@ -138,7 +135,6 @@ public struct Toast: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview { VStack(spacing: 12) { Toast(style: .warning, message: "Sgome coins are not available", onDismiss: {}) diff --git a/Sources/DashUIKit/Components/TopIntro/TopIntroView.swift b/Sources/DashUIKit/Components/TopIntro/TopIntroView.swift index e77ce4d..8f5eb7e 100644 --- a/Sources/DashUIKit/Components/TopIntro/TopIntroView.swift +++ b/Sources/DashUIKit/Components/TopIntro/TopIntroView.swift @@ -17,7 +17,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) public struct TopIntroView: View { private let title: String private let mainDescription: String? diff --git a/Sources/DashUIKit/Components/Transaction/TransactionView.swift b/Sources/DashUIKit/Components/Transaction/TransactionView.swift index 4b63182..2797def 100644 --- a/Sources/DashUIKit/Components/Transaction/TransactionView.swift +++ b/Sources/DashUIKit/Components/Transaction/TransactionView.swift @@ -26,7 +26,6 @@ import SwiftUI /// The amount is a raw duff value (`Int64`, 10⁸ per Dash) rendered via `DashAmount`, mirroring /// `ConverterCardItem.dashBalance`. Icons are `DashIconSource` (asset in the DashUIKit bundle, an /// SF Symbol, or a runtime `UIImage`); pass `iconView` for a custom / remotely-loaded main icon. -@available(iOS 14, macOS 11, *) public struct TransactionView: View { private enum Layout { @@ -238,7 +237,6 @@ public struct TransactionView: View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Received") { TransactionView( icon: .system("arrow.down.circle.fill"), @@ -252,7 +250,6 @@ public struct TransactionView: View { .background(Color.dash.primaryBackground) } -@available(iOS 17, macOS 14, *) #Preview("Sent + badge") { TransactionView( icon: .system("arrow.up.circle.fill"), @@ -268,7 +265,6 @@ public struct TransactionView: View { .background(Color.dash.primaryBackground) } -@available(iOS 17, macOS 14, *) #Preview("Grouped set") { TransactionView( icon: .system("circle.grid.2x2.fill"), @@ -282,7 +278,6 @@ public struct TransactionView: View { .background(Color.dash.primaryBackground) } -@available(iOS 17, macOS 14, *) #Preview("Internal transfer — amount accessory") { TransactionView( icon: .system("arrow.forward.circle.fill"), @@ -297,7 +292,6 @@ public struct TransactionView: View { .background(Color.dash.primaryBackground) } -@available(iOS 17, macOS 14, *) #Preview("Locked reward") { TransactionView( icon: DashIcon.Transaction.mining.source, diff --git a/Sources/DashUIKit/Foundation/Color+DashUI.swift b/Sources/DashUIKit/Foundation/Color+DashUI.swift index b81bc06..3ba7fe0 100644 --- a/Sources/DashUIKit/Foundation/Color+DashUI.swift +++ b/Sources/DashUIKit/Foundation/Color+DashUI.swift @@ -20,12 +20,10 @@ import SwiftUI import UIKit #endif -@available(iOS 14, macOS 10.15, *) public extension Color { static var dash: DashColors.Type { DashColors.self } } -@available(iOS 14, macOS 10.15, *) public enum DashColors { // MARK: Text @@ -307,7 +305,6 @@ public enum DashColors { } } -@available(iOS 14, macOS 10.15, *) private extension DashColors { static func dashAsset(_ name: String) -> Color { Color(name, bundle: .module) diff --git a/Sources/DashUIKit/Foundation/DashTextStyle.swift b/Sources/DashUIKit/Foundation/DashTextStyle.swift index e057673..58a4269 100644 --- a/Sources/DashUIKit/Foundation/DashTextStyle.swift +++ b/Sources/DashUIKit/Foundation/DashTextStyle.swift @@ -22,7 +22,6 @@ import SwiftUI /// /// `Font` alone cannot carry a line height (leading is applied by view modifiers, not /// the font), so this pairs the font metrics with the spec line height. -@available(iOS 14, macOS 10.15, *) public struct DashTextStyle: Sendable { public let size: CGFloat public let weight: Font.Weight @@ -40,7 +39,6 @@ public struct DashTextStyle: Sendable { // MARK: - Tokens (mirror `DashFonts`, with the documented line heights) -@available(iOS 14, macOS 10.15, *) public extension DashTextStyle { static let largeTitle = DashTextStyle(size: 34, weight: .bold, lineHeight: 41) static let title1 = DashTextStyle(size: 28, weight: .bold, lineHeight: 34) @@ -62,7 +60,6 @@ public extension DashTextStyle { // MARK: - Convenience accessor (`Font.dash.style.footnote`) -@available(iOS 14, macOS 10.15, *) public extension Font { /// Namespace for Dash text *styles* (font + line height), e.g. `Font.dashStyle.footnote`. static var dashStyle: DashTextStyle.Type { DashTextStyle.self } @@ -70,7 +67,6 @@ public extension Font { // MARK: - View modifier -@available(iOS 14, macOS 10.15, *) public extension View { /// Applies a Dash text style: sets the font **and** its documented line height in /// one call. @@ -87,7 +83,6 @@ public extension View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("dashFont") { let sample = "Back up your wallet to keep your funds safe and recover it on a new device." diff --git a/Sources/DashUIKit/Foundation/Font+DashUI.swift b/Sources/DashUIKit/Foundation/Font+DashUI.swift index 5ed43f6..aeb3041 100644 --- a/Sources/DashUIKit/Foundation/Font+DashUI.swift +++ b/Sources/DashUIKit/Foundation/Font+DashUI.swift @@ -17,12 +17,10 @@ import SwiftUI -@available(iOS 14, macOS 10.15, *) public extension Font { static var dash: DashFonts.Type { DashFonts.self } } -@available(iOS 14, macOS 10.15, *) public enum DashFonts { /// Large Title: 34pt Bold (line height: 41pt) diff --git a/Sources/DashUIKit/Foundation/Icon_DashUI.swift b/Sources/DashUIKit/Foundation/Icon_DashUI.swift index a97588f..0998da6 100644 --- a/Sources/DashUIKit/Foundation/Icon_DashUI.swift +++ b/Sources/DashUIKit/Foundation/Icon_DashUI.swift @@ -26,13 +26,11 @@ import SwiftUI /// Image(dash: DashIcon.Menu.send.source) /// DashIcon.Toast.success.image.resizable().frame(width: 24, height: 24) /// ``` -@available(iOS 14, macOS 11, *) public protocol DashIconAsset { /// The imageset name in the asset catalog. var assetName: String { get } } -@available(iOS 14, macOS 11, *) public extension DashIconAsset where Self: RawRepresentable, Self.RawValue == String { var assetName: String { rawValue } @@ -45,7 +43,6 @@ public extension DashIconAsset where Self: RawRepresentable, Self.RawValue == St /// Namespace for every icon in the library's asset catalog, grouped the same /// way the catalog is. -@available(iOS 14, macOS 11, *) public enum DashIcon { // MARK: - Common diff --git a/Sources/DashUIKit/Foundation/Image+DashUI.swift b/Sources/DashUIKit/Foundation/Image+DashUI.swift index 13706c1..b16d150 100644 --- a/Sources/DashUIKit/Foundation/Image+DashUI.swift +++ b/Sources/DashUIKit/Foundation/Image+DashUI.swift @@ -22,14 +22,12 @@ import UIKit public typealias UIImage = Never #endif -@available(iOS 14, macOS 11, *) public enum DashIconSource { case system(_ name: String) case custom(_ name: String, bundle: Bundle? = nil) case uiImage(_ image: UIImage) } -@available(iOS 14, macOS 11, *) public extension Image { /// Resolves a Dash icon source into a plain `Image`. The caller applies all /// styling (`.resizable()`, `.foregroundStyle()`, sizing, etc.). diff --git a/Sources/DashUIKit/Foundation/LineHeight+DashUI.swift b/Sources/DashUIKit/Foundation/LineHeight+DashUI.swift index f53b457..d5e39f1 100644 --- a/Sources/DashUIKit/Foundation/LineHeight+DashUI.swift +++ b/Sources/DashUIKit/Foundation/LineHeight+DashUI.swift @@ -22,7 +22,6 @@ import UIKit import AppKit #endif -@available(iOS 14, macOS 10.15, *) public extension View { /// Applies a design line height to text rendered with a fixed-size system font. /// @@ -60,7 +59,6 @@ public extension View { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Footnote line height") { let sample = "Back up your wallet to keep your funds safe and recover it on a new device." diff --git a/Sources/DashUIKit/Table List/List1View.swift b/Sources/DashUIKit/Table List/List1View.swift index 7c36767..5e2a95b 100644 --- a/Sources/DashUIKit/Table List/List1View.swift +++ b/Sources/DashUIKit/Table List/List1View.swift @@ -17,7 +17,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) public struct List1View: View { public var label: String @@ -45,7 +44,6 @@ public struct List1View: View { } } -@available(iOS 17, macOS 14, *) #Preview { List1View() } diff --git a/Sources/DashUIKit/ViewModifiers/MenuViewModifier.swift b/Sources/DashUIKit/ViewModifiers/MenuViewModifier.swift index b00e1e5..94bf336 100644 --- a/Sources/DashUIKit/ViewModifiers/MenuViewModifier.swift +++ b/Sources/DashUIKit/ViewModifiers/MenuViewModifier.swift @@ -17,7 +17,6 @@ import SwiftUI -@available(iOS 14, macOS 11, *) public struct MenuViewModifier: ViewModifier { var shadowRadius: CGFloat var innerPadding: CGFloat From 7050613b64aa329fa76fe43008927bfd165460ed Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 27 Aug 2026 17:30:25 +0300 Subject: [PATCH 6/6] docs: state the floor once and drop the per-component versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every component page opened with the `@available` line the type used to carry, and the prose recorded which feature lit up on which release — a SearchBar paragraph for the iOS 14 bar that no longer exists, a self-sizing sheet described as a no-op below iOS 16, swipe blocking split across two APIs. None of it is true at an iOS 18 floor, and none of it was worth restating per page now that the manifest says it once. `ScrollViewWithOnScrollChanged` keeps its page but says plainly that it predates `onScrollGeometryChange` and that new code can use the system API. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 8 +++----- README.md | 4 ++-- docs/README.md | 2 +- docs/amount-and-currency.md | 12 ++++++------ docs/buttons-and-inputs.md | 17 ++++++++--------- docs/feedback.md | 16 ++++++++-------- docs/lists-and-rows.md | 12 ++++++------ docs/navigation-and-containers.md | 21 ++++++++++----------- docs/utilities.md | 14 ++++++-------- 9 files changed, 50 insertions(+), 56 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8f1651d..68322d1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,9 +55,8 @@ Sources/DashUIKit/ or `Font.dash.subhead` when you only need the font. Tokens defined in `DashTextStyle.swift`. - **Icons:** pass a `DashIconSource` (`.system` / `.custom(name, bundle:)` / `.uiImage`) and render with `Image(dash: source)`. Custom assets resolve from `.dashUIKit`/`.module`. -- **Availability:** annotate public API with `@available(iOS 14, macOS 11, *)` (or higher only - when a newer SwiftUI feature requires it, with an iOS 14 fallback path — see `SearchBar`, - `BottomSheet`). +- **Availability:** don't annotate. The floor is declared in `Package.swift`; annotate only + an API that needs something *newer* than it, and gate that with `if #available(...)`. - **UIKit-only files** are wrapped in `#if canImport(UIKit)` (e.g. `SearchBar`, `Toast`, `AddressFieldView`, `DashSwitch`). - **Localization:** user-facing strings use `NSLocalizedString(_, bundle: .module, comment:)`. @@ -67,8 +66,7 @@ Sources/DashUIKit/ ## Build / preview - This is a plain SwiftPM library — `swift build` compiles it; there is no app target. -- Develop visually with Xcode SwiftUI **#Previews** (open a file, use the canvas). Previews - require iOS 17 for the `#Preview` macro; older `PreviewProvider` previews work back further. +- Develop visually with Xcode SwiftUI **#Previews** (open a file, use the canvas). ## Component catalog diff --git a/README.md b/README.md index 7f558dd..3931b35 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Every component is **presentational and value-driven**: it owns no business logi networking, or persistence. You pass in formatted strings, flags, bindings, and callbacks; the component renders and reports user intent back to you. -- **Platforms:** iOS 14+ (some features light up on 15 / 16 / 17 with graceful fallbacks) +- **Platforms:** iOS 18+ / macOS 15+ - **Swift tools:** 6.3 - **Module:** `import DashUIKit` @@ -67,7 +67,7 @@ a component already exists before building your own. |---|---|---| | `DashButton` | Themed button — 4 sizes × 11 styles, icons, loading, full-width | [Buttons & inputs](docs/buttons-and-inputs.md) | | `DashSwitch` | DS-tinted toggle | [Buttons & inputs](docs/buttons-and-inputs.md) | -| `SearchBar` | Search field with animated cancel (iOS 15+) and iOS 14 fallback | [Buttons & inputs](docs/buttons-and-inputs.md) | +| `SearchBar` | Search field with an animated cancel button | [Buttons & inputs](docs/buttons-and-inputs.md) | | `AddressFieldView` | Crypto-address text field with QR / clear and error state | [Buttons & inputs](docs/buttons-and-inputs.md) | | `NumericKeyboardView` | Locale-aware on-screen number pad with action button | [Buttons & inputs](docs/buttons-and-inputs.md) | | `EnterAmountView` | Amount-entry screen control — single & dual-swap modes | [Amount & currency](docs/amount-and-currency.md) | diff --git a/docs/README.md b/docs/README.md index 8ff032f..4baedd7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,7 +5,7 @@ UI element, scan this index to see whether it already exists. All components are **presentational and value-driven** — you pass strings/flags/bindings and callbacks; they render and report intent. They require `import DashUIKit` and target -**iOS 14+** (newer SwiftUI features are gated with fallbacks). +**iOS 18+ / macOS 15+**, declared once in `Package.swift`. ## Pages diff --git a/docs/amount-and-currency.md b/docs/amount-and-currency.md index 3e20f2e..60e7c8f 100644 --- a/docs/amount-and-currency.md +++ b/docs/amount-and-currency.md @@ -7,7 +7,7 @@ These power Send / Convert / DashDEX-style screens. ## EnterAmountView -File `Components/EnterAmount/EnterAmountView.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/EnterAmount/EnterAmountView.swift` The top-level amount-entry control. Two layouts selected by **`EnterAmountStyle`**: @@ -67,7 +67,7 @@ the host formats**; the component never does currency math. ## SwapAmountView -File `Components/EnterAmount/SwapAmountView.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/EnterAmount/SwapAmountView.swift` The amount display used inside `EnterAmountView`. Renders a large amount (optional currency symbol + number + Dash logo) with optional top/bottom helper text and an optional @@ -111,7 +111,7 @@ The `.dashPasteContextMenu(onPaste:)` helper (in this file) wires long-press-to- ## DashAmount -File `Components/DashAmount.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/DashAmount.swift` Renders a **duffs `Int64`** (1 DASH = 100,000,000 duffs) as a localized decimal amount followed by the Dash currency glyph. Display-only. @@ -137,7 +137,7 @@ separators, current locale. ## DashBalanceView -File `Components/EnterAmount/DashBalanceView.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/EnterAmount/DashBalanceView.swift` Trailing balance block: a **symbol-free, pre-formatted** Dash amount string + Dash glyph, with an optional fiat sub-line beneath. Unlike `DashAmount`, it takes already-formatted @@ -151,7 +151,7 @@ DashBalanceView(balance: "1.5", fiat: "$ 150.00") // fiat nil → hide sub-line ## ReceiveEstimateView -File `Components/ReceiveEstimateView.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/ReceiveEstimateView.swift` A centered estimate line shown beneath amount inputs. It shows a loading spinner first, then an error message, then the title + estimated amount. When `fiat` is provided it is @@ -175,7 +175,7 @@ The view collapses to nothing when `isVisible` is `false`. ## DashPickerView -File `Components/EnterAmount/DashPickerView.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/EnterAmount/DashPickerView.swift` A compact vertical inline picker over any `Hashable` option; the selected row gets a subtle highlighted background. Generic and reusable. diff --git a/docs/buttons-and-inputs.md b/docs/buttons-and-inputs.md index 8fd8e2a..5ca4326 100644 --- a/docs/buttons-and-inputs.md +++ b/docs/buttons-and-inputs.md @@ -6,7 +6,7 @@ Interactive controls for tapping and text/number entry. ## DashButton -File `Button/DashButton.swift` · `@available(iOS 14, macOS 11, *)` +File `Button/DashButton.swift` The design-system button. Supports leading/trailing icons, a loading spinner, optional full-width fill, four sizes, and eleven styles. @@ -43,7 +43,7 @@ Notes: `isLoading` both shows a `ProgressView` and disables the button. The `… ## DashSwitch -File `Components/DashSwitch.swift` · `@available(iOS 14, *)` · **UIKit only** +File `Components/DashSwitch.swift` · **UIKit only** (`#if canImport(UIKit)`) A thin wrapper over `Toggle` tinted with the DS "on" track color @@ -61,7 +61,7 @@ DashSwitch(isOn: $isOn) ## SearchBar -File `Components/SearchBar.swift` · `@available(iOS 14, *)` · **UIKit only** +File `Components/SearchBar.swift` · **UIKit only** Rounded search field with magnifying-glass icon and inline clear (✕) button. @@ -72,9 +72,8 @@ SearchBar(text: $query, placeholder: "Search coins") // placeholder optional Behavior: -- **iOS 15+** (`SearchBarFocused`): uses `@FocusState`; an animated **Cancel** button +- Uses `@FocusState`; an animated **Cancel** button slides in while editing (clears text + resigns focus on tap). -- **iOS 14** (`SearchBarLegacy`): static bar without the focus-driven cancel animation. Default placeholder is the localized "Search". Background uses `Color.dash.searchBackground`. @@ -82,11 +81,11 @@ Default placeholder is the localized "Search". Background uses `Color.dash.searc ## AddressFieldView -File `Components/AddressFieldView.swift` · `@available(iOS 15, macOS 12, *)` · **UIKit only** +File `Components/AddressFieldView.swift` · **UIKit only** A labeled crypto-address input with a trailing action button (scan-QR when empty, clear -when filled), error styling, and a multi-line read-out state. iOS 17+ uses a vertical -axis field (1–3 lines); older iOS uses a single-line field. +when filled), error styling, and a multi-line read-out state. Uses a vertical +axis field (1–3 lines). ```swift @State private var address = "" @@ -114,7 +113,7 @@ State-driven styling: ## NumericKeyboardView -File `Components/NumericKeyboardView.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/NumericKeyboardView.swift` A custom on-screen numeric pad (1–9, 0, decimal separator, delete) plus a primary `DashButton` action and optional helper text. **Locale-aware**: the decimal separator and diff --git a/docs/feedback.md b/docs/feedback.md index 0702607..3498eb8 100644 --- a/docs/feedback.md +++ b/docs/feedback.md @@ -6,7 +6,7 @@ Status, progress, and notification visuals. ## Toast -File `Components/Toast.swift` · `@available(iOS 14, macOS 11, *)` · **UIKit only** +File `Components/Toast.swift` · **UIKit only** (`#if canImport(UIKit)`) A blurred, rounded toast: leading status icon, message, and an optional dismiss (✕) @@ -36,7 +36,7 @@ responsibility (e.g. overlay it and drive visibility yourself). ## SystemMessageView -File `Components/SystemMessageView.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/SystemMessageView.swift` An icon-led system banner with optional subtitle, up to two action buttons, and an optional close button. The default icon is the bundled `warning_triangle` asset, and the @@ -59,7 +59,7 @@ one or two of them. ## LoadingIllustration / LoadingSpinner -File `Components/Illustrations/LoadingIllustration.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/Illustrations/LoadingIllustration.swift` `LoadingSpinner` is an iOS-style activity indicator built from `spokeCount` capsules in a ring. The spokes are static; a bright "head" steps clockwise while the others fade @@ -84,7 +84,7 @@ Defaults: `size: 61.73`, `color: LoadingSpinner.defaultColor` (DS blue), `contai ## SuccessIllustration / ErrorIllustration Files `Components/Illustrations/SuccessIllustration.swift`, -`Components/Illustrations/ErrorIllustration.swift` · `@available(iOS 14, macOS 11, *)` +`Components/Illustrations/ErrorIllustration.swift` 90×90 circular status badges — a green circle with a checkmark, and a red circle with an ✕ (both from bundled assets). No parameters. @@ -98,7 +98,7 @@ ErrorIllustration() ## InfoRoundIcon -File `Components/Icons/InfoRoundIcon.swift` · `@available(iOS 14, macOS 11, *)` · public +File `Components/Icons/InfoRoundIcon.swift` · public A code-drawn round "i" — a filled disc with a white glyph — for the "there is more to say about this" affordance. Geometry is normalized from a 19×19 SVG, so stem and dot keep @@ -117,7 +117,7 @@ follow the palette instead of a baked-in hex. `MenuItem` renders it for ## XmarkIcon -File `Components/Icons/XmarkIcon.swift` · `@available(iOS 14, macOS 11, *)` · public +File `Components/Icons/XmarkIcon.swift` · public A code-drawn "✕" close icon (a `Shape` with two round-capped diagonals, mirroring a 9×9 SVG), so it scales cleanly to any size without an asset. Used by `Toast`'s dismiss button. @@ -136,7 +136,7 @@ XmarkIcon(size: 24, color: .white, lineWidth: 2) ## CheckmarkIcon -File `Components/Icons/CheckmarkIcon.swift` · `@available(iOS 14, macOS 11, *)` · public +File `Components/Icons/CheckmarkIcon.swift` · public A code-drawn "✓" selection mark (a `Shape` stroking the polyline of a 15×12 SVG, round caps and joins). Backs `MenuItemAccessory.selection`. @@ -154,7 +154,7 @@ CheckmarkIcon(size: 24, color: .white, lineWidth: 3) ## ChevronIcon -File `Components/Icons/ChevronIcon.swift` · `@available(iOS 14, macOS 11, *)` · public +File `Components/Icons/ChevronIcon.swift` · public A code-drawn chevron, in any of the four directions (a `Shape` stroking the polyline of a 7×12 SVG, round caps and joins). `ConverterCard` puts one on every row that carries an diff --git a/docs/lists-and-rows.md b/docs/lists-and-rows.md index 8db289b..fa81a4f 100644 --- a/docs/lists-and-rows.md +++ b/docs/lists-and-rows.md @@ -8,7 +8,7 @@ as needed. ## CoinSelector -File `Components/CoinSelector.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/CoinSelector.swift` A coin/asset row: leading icon (your view), name + code stacked, and a flexible trailing slot — a price/network readout or a "halted" badge. Generic over the icon view. @@ -36,7 +36,7 @@ is tappable when you wrap it in a `Button`. ## MenuItem -File `Components/MenuItem.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/MenuItem.swift` A settings/menu row: optional leading icon, title with optional inline info glyph, optional help text, and a flexible trailing **accessory**. @@ -76,7 +76,7 @@ per-call fonts/colors, to keep rows consistent): ## ConverterCard -File `Components/ConverterCard/ConverterCard.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/ConverterCard/ConverterCard.swift` A two-row source/destination card with a seam-centered arrow badge. Each row is wrapped in shared card chrome; passing `onSwap` makes the badge tappable and rotates the arrow, @@ -123,7 +123,7 @@ Internal seam badge: static `arrow-down` without swapping, or tappable ## TransactionView -File `Components/Transaction/TransactionView.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/Transaction/TransactionView.swift` A transaction row with a leading icon, optional status badge, title/time/detail text, and a trailing Dash amount plus optional fiat line. Pass `action` to make the whole row @@ -148,7 +148,7 @@ The amount is a raw duff value (`Int64`, 10⁸ per Dash) rendered via `DashAmoun ## RadioButtonRow -File `Components/RadioButtonRow.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/RadioButtonRow.swift` A tappable selectable row with title, optional subtitle, optional trailing text, optional leading icon, and a selection indicator in one of two styles. @@ -173,7 +173,7 @@ subtitle, otherwise 54; the whole row is the tap target. ## List1View -File `Table List/List1View.swift` · `@available(iOS 14, macOS 11, *)` +File `Table List/List1View.swift` The simplest row: a tertiary-colored **label** on the left and a primary-colored **value** on the right (value is right-aligned and can wrap). Useful for detail / summary lists. diff --git a/docs/navigation-and-containers.md b/docs/navigation-and-containers.md index eb2a68d..4749a7c 100644 --- a/docs/navigation-and-containers.md +++ b/docs/navigation-and-containers.md @@ -6,7 +6,7 @@ Structural chrome — nav bars, bottom sheets, and card styling. ## NavigationBar -File `Components/NavigationBar.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/NavigationBar.swift` A custom three-slot top bar: **leading**, **central**, **trailing**, each a `@ViewBuilder`. The leading/trailing pair is laid out in an `HStack` with a `Spacer` @@ -39,7 +39,7 @@ Convenience initializers let you omit any slot (e.g. only `leading`, or `central ## TopIntroView -File `Components/TopIntro/TopIntroView.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/TopIntro/TopIntroView.swift` A lightweight top-of-screen intro block: title plus up to two description lines, laid out as a leading-aligned text stack with extra trailing padding so it breathes beside @@ -59,7 +59,7 @@ Use it for screen headers that sit above the main content rather than inside a n ## BottomSheet -File `Components/BottomSheet/BottomSheet.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/BottomSheet/BottomSheet.swift` Sheet chrome to put **inside** a SwiftUI `.sheet { }`: a grabber, a `NavigationBar` header (optional back button + title + close), and your content. Two height modes. @@ -82,7 +82,7 @@ Sheet chrome to put **inside** a SwiftUI `.sheet { }`: a grabber, a `NavigationB ``` - **`fillsHeight: true`** (default) — content fills the sheet; pair with an explicit detent - on **iOS 16+** (`.large` / `.medium` / `.height`). Wraps content in a `NavigationView`. + (`.large` / `.medium` / `.height`). Wraps content in a `NavigationView`. - **`fillsHeight: false`** — natural height; pair with `.selfSizingSheet(…)` so the sheet snaps to its content. @@ -109,8 +109,7 @@ no `onClose` — and `isCloseButtonEnabled: false` takes it away outright. An in visibly dimmed and exposes the disabled accessibility trait. Use `showsCloseButton: false` when the sheet should have no close affordance at all. -Swipe blocking uses `interactiveDismissDisabled` on **iOS 15+** / **macOS 12+** and -`UIViewController.isModalInPresentation` below that, so an iOS 14 host is protected too. +Swipe blocking uses `interactiveDismissDisabled`. `onClose` covers the **close button only**: an interactive swipe dismisses the sheet without calling it. A host that has to hear about every dismissal should also pass @@ -131,7 +130,7 @@ the modifier are applied together: fallback: 240, // height before first measurement (avoids .medium flash) maxHeightFraction: 0.95, // cap at 95% of window height (clip taller → use ScrollView) background: .dash.secondaryBackground, // also fills the home-indicator strip - cornerRadius: 24 // iOS 16.4..<26; iOS 26+ keeps system corners + cornerRadius: 24 // below iOS 26; iOS 26+ keeps system corners ) { MyContent() } @@ -139,8 +138,8 @@ the modifier are applied together: ``` `.selfSizingSheet(…)` (a `View` extension) measures the wrapped content directly via a -`GeometryReader` and drives `presentationDetents([.height(measured)])`. **iOS 16+** only; -a **no-op below iOS 16**. The measured content must have a finite intrinsic height (no +`GeometryReader` and drives `presentationDetents([.height(measured)])`. The measured +content must have a finite intrinsic height (no greedy `Spacer`/`maxHeight: .infinity`), or the measurement is wrong. `BottomSheetHeightPreferenceKey` is exposed for advanced cases. @@ -151,7 +150,7 @@ background as a pale band along the bottom edge, whatever the content is styled ## SheetFeature -File `Components/BottomSheet/SheetFeature.swift` · `@available(iOS 14, macOS 11, *)` · public +File `Components/BottomSheet/SheetFeature.swift` · public One "here is what this gives you" line for a `BottomSheet`: an icon beside a name and a sentence. Stack several to describe what a feature unlocks. @@ -178,7 +177,7 @@ more than one colour keeps them, since a template flattens everything to a singl ## MenuViewModifier -File `ViewModifiers/MenuViewModifier.swift` · `@available(iOS 14, macOS 11, *)` +File `ViewModifiers/MenuViewModifier.swift` Card chrome: inner padding, a rounded (radius 20, continuous) `secondaryBackground` fill, and a soft DS shadow. Use it to wrap grouped content (e.g. a stack of `MenuItem`s) into a diff --git a/docs/utilities.md b/docs/utilities.md index 64ba0bc..f4ae859 100644 --- a/docs/utilities.md +++ b/docs/utilities.md @@ -7,8 +7,7 @@ mechanics for measuring and fitting views. ## Frame & location readers -Files `Components/Geometry/FrameReader.swift`, `LocationReader.swift` · -`@available(iOS 14, macOS 11, *)` +Files `Components/Geometry/FrameReader.swift`, `LocationReader.swift` Read a view's frame or center point in a coordinate space via a background `GeometryReader`, without disturbing layout. Callbacks fire on appear and on change. @@ -33,13 +32,12 @@ MyView() ## ScrollViewWithOnScrollChanged -File `Components/Geometry/ScrollViewWithOnScrollChanged.swift` · -`@available(iOS 14, macOS 11, *)` +File `Components/Geometry/ScrollViewWithOnScrollChanged.swift` A `ScrollView` that reports its content's scroll origin as it moves — handy for -scroll-driven effects (collapsing headers, shadows, parallax) without iOS 17's -`onScrollGeometryChange`. Works back to iOS 14 by placing a `LocationReader` at the top of -the content in a named coordinate space. +scroll-driven effects (collapsing headers, shadows, parallax). It works by placing a +`LocationReader` at the top of the content in a named coordinate space, which predates +`onScrollGeometryChange`; new code can use the system API directly. ```swift ScrollViewWithOnScrollChanged(.vertical, showsIndicators: false) { @@ -53,7 +51,7 @@ ScrollViewWithOnScrollChanged(.vertical, showsIndicators: false) { ## scaleToFitWidth -File `Components/Geometry/ScaleToFitWidth.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/Geometry/ScaleToFitWidth.swift` Scales a view **uniformly** to fit the available width on a single line, shrinking the whole group together (e.g. currency symbol + amount + logo) — unlike `minimumScaleFactor`,