diff --git a/CLAUDE.md b/CLAUDE.md index e7f2e39..68322d1 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 @@ -56,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:)`. @@ -68,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/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"]), 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/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 44aecc2..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 @@ -162,36 +159,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 { @@ -253,7 +239,6 @@ extension AddressFieldView { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("Empty") { AddressFieldView( text: .constant(""), @@ -265,7 +250,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Empty with Paste button") { AddressFieldView( text: .constant(""), @@ -278,7 +262,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("With Text") { AddressFieldView( text: .constant("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"), @@ -290,7 +273,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Multiline") { AddressFieldView( text: .constant("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh\nbc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"), @@ -302,7 +284,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Error") { AddressFieldView( text: .constant("invalid-address"), @@ -314,7 +295,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Filled") { AddressFieldView( text: .constant("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"), @@ -326,7 +306,6 @@ extension AddressFieldView { .padding() } -@available(iOS 17, macOS 14, *) #Preview("Blurred + filled (no action button)") { AddressFieldView( text: .constant("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"), @@ -338,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 3a6a3e0..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,99 +231,21 @@ enum BottomSheetDismissalAction { } } -@available(iOS 14, macOS 11, *) 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 + // 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 { - 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 - - func body(content: Content) -> some View { - content.background( - ModalInPresentationSetter(isModal: isDismissDisabled) - .frame(width: 0, height: 0) - ) + content.interactiveDismissDisabled(!isEnabled) } } -@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 - } - } -} - -#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 +262,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,46 +271,30 @@ 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 } } -@available(iOS 16.0, macOS 13.0, *) private struct SelfSizingSheetModifier: ViewModifier { let fallback: CGFloat let maxHeightFraction: CGFloat @@ -406,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) @@ -441,7 +343,6 @@ private struct SelfSizingSheetModifier: ViewModifier { #if DEBUG -@available(iOS 17, macOS 14, *) #Preview("BottomSheet Filled Height") { BottomSheet( title: "Bottom Sheet", @@ -460,7 +361,6 @@ private struct SelfSizingSheetModifier: ViewModifier { } } -@available(iOS 17, macOS 14, *) #Preview("BottomSheet Natural Height") { BottomSheet( title: "Bottom Sheet", @@ -480,7 +380,6 @@ private struct SelfSizingSheetModifier: ViewModifier { } } -@available(iOS 17, macOS 14, *) #Preview("BottomSheet Custom Background") { BottomSheet( title: "Bottom Sheet", @@ -502,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 f65fd03..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 @@ -30,21 +29,14 @@ 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?) } } #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 ec60f0a..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 @@ -405,7 +403,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 @@ -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 9c3c3c2..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 @@ -43,7 +42,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) } @@ -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 bd74f24..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 @@ -42,7 +41,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) } @@ -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 0b4ef96..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,30 +122,21 @@ 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) } } -@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 { } }, @@ -165,7 +148,6 @@ private struct NavigationBarButtonStyle: ButtonStyle { ) } -@available(iOS 17, macOS 14, *) #Preview("NavigationBar Back Title Info") { NavigationBar( leading: { NavigationBarElement.back.button { } }, @@ -178,7 +160,6 @@ private struct NavigationBarButtonStyle: ButtonStyle { ) } -@available(iOS 17, macOS 14, *) #Preview("NavigationBar Variants") { VStack { NavigationBar( @@ -203,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 1950fa4..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 { @@ -145,21 +144,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 { @@ -243,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 7614b08..4917cd1 100644 --- a/Sources/DashUIKit/Components/SearchBar.swift +++ b/Sources/DashUIKit/Components/SearchBar.swift @@ -18,9 +18,8 @@ #if canImport(UIKit) import SwiftUI -// MARK: - Public shell (iOS 14+) +// MARK: - Public shell -@available(iOS 14, *) public struct SearchBar: View { @Binding private var text: String private let placeholder: String @@ -35,17 +34,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 @@ -85,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 } @@ -136,82 +130,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?) } } @@ -219,7 +150,6 @@ private struct SearchBarLegacy: 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 579d079..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 { @@ -43,21 +42,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 @@ -70,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)) @@ -81,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 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`,