From 84cd1604df79d6bbf562889bbe8f6f7b5f7bbcee Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 23:59:02 +0300 Subject: [PATCH] Add Platform and minor bug fixes (#42) * Add Platform and minor bug fixes --- .github/workflows/ci-pull-request.yml | 66 -------- .github/workflows/{ci-release.yml => ci.yml} | 43 +++-- .github/workflows/publish-docc.yml | 37 ----- README.md | 2 +- .../Controls/ContentView/ContentView.swift | 2 - .../Controls/Divider/Separator.swift | 36 +++-- .../Controls/PageControl/PageIndexView.swift | 6 +- .../Controls/PriceField/PriceField.swift | 10 +- Sources/OversizeUI/Controls/Row/Row.swift | 13 +- .../ScrollView/ScrollViewWithOffset.swift | 16 +- .../OversizeUI/Controls/Surface/Surface.swift | 28 +++- .../TextField/FieldHelperViewModifier.swift | 36 +++-- .../TextField/LabeledTextFieldStyle.swift | 152 ++++++++++++++---- .../Controls/URLField/URLField.swift | 7 +- Sources/OversizeUI/Core/EdgeSpaceInsets.swift | 7 + .../Core/EnvironmentKeys/Platform.swift | 53 ++++++ Sources/OversizeUI/Core/Radius.swift | 4 + Sources/OversizeUI/Core/Space.swift | 5 + Sources/OversizeUI/Core/Typography.swift | 2 +- .../Color/Color+RawRepresentable.swift | 2 +- .../Extensions/Image/Image+Extensions.swift | 12 +- .../Extensions/Spacing/Spacing.swift | 12 ++ .../BackgroundTertiary.colorset/Contents.json | 6 +- .../Shapes/RoundedRectangleCorner.swift | 11 +- 24 files changed, 337 insertions(+), 231 deletions(-) delete mode 100644 .github/workflows/ci-pull-request.yml rename .github/workflows/{ci-release.yml => ci.yml} (61%) delete mode 100644 .github/workflows/publish-docc.yml create mode 100644 Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml deleted file mode 100644 index ba40e0e..0000000 --- a/.github/workflows/ci-pull-request.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: CI - Pull Request -on: - pull_request: - branches: - - main - push: - branches: - - develop - workflow_dispatch: - -jobs: - build-swiftpm: - name: Build SwiftPM - uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm.yml@main - with: - package: OversizeUI - secrets: inherit - - build-iOS-example: - name: Build iOS example - needs: build-swiftpm - uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main - strategy: - matrix: - destination: ['platform=iOS Simulator,name=iPhone 15 Pro,OS=17.2', 'platform=iOS Simulator,name=iPad Pro (12.9-inch) (6th generation),OS=17.2'] - with: - path: Example/Example - scheme: Example (iOS) - destination: ${{ matrix.destination }} - secrets: inherit - - build-macOS-example: - name: Build macOS examples - needs: build-swiftpm - uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main - with: - path: Example/Example - scheme: Example (macOS) - destination: platform=macOS,arch=arm64 - secrets: inherit - - build-tvOS-example: - name: Build tvOS examples - needs: build-swiftpm - uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main - strategy: - matrix: - destination: ['platform=tvOS Simulator,name=Apple TV 4K (3rd generation) (at 1080p),OS=17.2', 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation) (at 1080p),OS=16.4'] - with: - path: Example/Example - scheme: Example (tvOS) - destination: ${{ matrix.destination }} - secrets: inherit - - build-watchOS-example: - name: Build watchOS examples - needs: build-swiftpm - uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main - strategy: - matrix: - destination: ['platform=watchOS Simulator,name=Apple Watch SE (44mm) (2nd generation),OS=10.5'] - with: - path: Example/Example - scheme: Example (watchOS) - destination: ${{ matrix.destination }} - secrets: inherit diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci.yml similarity index 61% rename from .github/workflows/ci-release.yml rename to .github/workflows/ci.yml index 6bc476a..ddb0340 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,13 @@ -name: CI - Release +name: CI on: pull_request: types: - closed branches: - main + push: + branches: + - '**' workflow_dispatch: jobs: @@ -13,9 +16,14 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm.yml@main strategy: matrix: - packages: [OversizeUI] + destination: + - platform=iOS Simulator,name=iPhone 16,OS=18.0 + - platform=watchOS Simulator,name=Apple Watch SE (40mm) (2nd generation),OS=11.0 + - platform=tvOS Simulator,name=Apple TV 4K (3rd generation) (at 1080p),OS=18.0 + - platform=macOS,arch=arm64 with: - package: ${{ matrix.packages }} + package: OversizeUI + destination: ${{ matrix.destination }} secrets: inherit build-iOS-example: @@ -24,7 +32,9 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main strategy: matrix: - destination: ['platform=iOS Simulator,name=iPhone 15 Pro,OS=17.2', 'platform=iOS Simulator,name=iPad Pro (12.9-inch) (6th generation),OS=17.2'] + destination: + - platform=iOS Simulator,name=iPhone 16 Pro,OS=18.0 + - platform=iOS Simulator,name=iPad (10th generation),OS=18.0 with: path: Example/Example scheme: Example (iOS) @@ -47,7 +57,9 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main strategy: matrix: - destination: ['platform=tvOS Simulator,name=Apple TV 4K (3rd generation) (at 1080p),OS=17.2', 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation) (at 1080p),OS=16.4'] + destination: + - platform=tvOS Simulator,name=Apple TV 4K (3rd generation) (at 1080p),OS=18.0 + - platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=18.0 with: path: Example/Example scheme: Example (tvOS) @@ -60,22 +72,23 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main strategy: matrix: - destination: ['platform=watchOS Simulator,name=Apple Watch SE (44mm) (2nd generation),OS=10.5'] + destination: + - platform=watchOS Simulator,name=Apple Watch SE (40mm) (2nd generation),OS=11.0 with: path: Example/Example scheme: Example (watchOS) destination: ${{ matrix.destination }} secrets: inherit - + bump: name: Bump version - needs: [build-swiftpm, build-iOS-example, build-macOS-example, build-tvOS-example, build-watchOS-example] + if: github.ref == 'refs/heads/main' + needs: + - build-swiftpm + - build-iOS-example + - build-macOS-example + - build-tvOS-example + - build-watchOS-example + uses: oversizedev/GithubWorkflows/.github/workflows/bump.yml@main secrets: inherit - - publish-docc: - name: Publish docc - needs: bump - uses: ./.github/workflows/publish-docc.yml - secrets: inherit - diff --git a/.github/workflows/publish-docc.yml b/.github/workflows/publish-docc.yml deleted file mode 100644 index d461258..0000000 --- a/.github/workflows/publish-docc.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Deploy DocC -on: - workflow_dispatch: - workflow_call: -permissions: - contents: read - pages: write - id-token: write -concurrency: - group: "pages" - cancel-in-progress: true -jobs: - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: macos-14 - steps: - - name: Checkout 🛎️ - uses: actions/checkout@v3 - - name: Build DocC - run: | - xcodebuild docbuild -scheme -skipPackagePluginValidation OversizeUI \ - -derivedDataPath /tmp/docbuild \ - -destination 'generic/platform=iOS'; - $(xcrun --find docc) process-archive \ - transform-for-static-hosting /tmp/docbuild/Build/Products/Debug-iphoneos/OversizeUI.doccarchive \ - --hosting-base-path OversizeUI \ - --output-path docs; - echo "" > docs/index.html; - - name: Upload artifact - uses: actions/upload-pages-artifact@v1 - with: - path: 'docs' - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v1 diff --git a/README.md b/README.md index b9d7f64..054c8d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OversizeUI -[![Build Example](https://github.com/oversizedev/OversizeUI/actions/workflows/build-example.yml/badge.svg)](https://github.com/oversizedev/OversizeUI/actions/workflows/build-example.yml) [![Deploy DocC](https://github.com/oversizedev/OversizeUI/actions/workflows/publish-docc.yml/badge.svg)](https://github.com/oversizedev/OversizeUI/actions/workflows/publish-docc.yml) +[![Build Example](https://github.com/oversizedev/OversizeUI/actions/workflows/build-example.yml/badge.svg)](https://github.com/oversizedev/OversizeUI/actions/workflows/ci-release.yml) Yet another component library on SwiftUI diff --git a/Sources/OversizeUI/Controls/ContentView/ContentView.swift b/Sources/OversizeUI/Controls/ContentView/ContentView.swift index f7290bd..93b4d0f 100644 --- a/Sources/OversizeUI/Controls/ContentView/ContentView.swift +++ b/Sources/OversizeUI/Controls/ContentView/ContentView.swift @@ -88,7 +88,6 @@ extension ContentView { #if os(macOS) .controlSize(.large) #endif - case .back: Button(action: { dismiss() }) { @@ -247,7 +246,6 @@ extension ContentView { .buttonStyle(.tertiary) #endif .disabled(true) - case .none: EmptyView() } diff --git a/Sources/OversizeUI/Controls/Divider/Separator.swift b/Sources/OversizeUI/Controls/Divider/Separator.swift index cc1f04c..fb7f655 100644 --- a/Sources/OversizeUI/Controls/Divider/Separator.swift +++ b/Sources/OversizeUI/Controls/Divider/Separator.swift @@ -24,24 +24,40 @@ public struct Separator: View { return Rectangle() .fill(Color.border) - .frame(width: isHorizontal ? nil : 0.5, - height: isHorizontal ? 0.5 : nil) + .frame( + width: isHorizontal ? nil : lineWidth, + height: isHorizontal ? lineWidth : nil + ) + #if !os(macOS) .padding(insets(isHorizontal)) + #endif } private func insets(_ isHorizontal: Bool) -> EdgeInsets { if isHorizontal { - EdgeInsets(top: 0.5, - leading: padding.rawValue, - bottom: 0, - trailing: padding.rawValue) + EdgeInsets( + top: 0.5, + leading: padding.rawValue, + bottom: 0, + trailing: padding.rawValue + ) } else { - EdgeInsets(top: padding.rawValue, - leading: 0.5, - bottom: padding.rawValue, - trailing: 0) + EdgeInsets( + top: padding.rawValue, + leading: 0.5, + bottom: padding.rawValue, + trailing: 0 + ) } } + + var lineWidth: CGFloat { + #if os(macOS) + return 1 + #else + return 0.5 + #endif + } } struct Separator_Preview: PreviewProvider { diff --git a/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift b/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift index b549095..72d48c3 100644 --- a/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift +++ b/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift @@ -6,11 +6,11 @@ import SwiftUI public struct PageIndexView: View { - @Binding private var index: Int + private var index: Int private let maxIndex: Int - public init(_ index: Binding, maxIndex: Int) { - _index = index + public init(_ index: Int, maxIndex: Int) { + self.index = index self.maxIndex = maxIndex } diff --git a/Sources/OversizeUI/Controls/PriceField/PriceField.swift b/Sources/OversizeUI/Controls/PriceField/PriceField.swift index 3d1a48b..8495391 100644 --- a/Sources/OversizeUI/Controls/PriceField/PriceField.swift +++ b/Sources/OversizeUI/Controls/PriceField/PriceField.swift @@ -22,17 +22,16 @@ public struct PriceField: View { return formatter } - private var strValue2: String { value.components(separatedBy: CharacterSet(charactersIn: "0123456789").inverted).joined() } // value.filter { !$0.isWhitespace } } - private var doubleValue2: Double { .init((Double(strValue2) ?? 0) / 100.0) ?? 0 } + private var stringValue2: String { value.components(separatedBy: CharacterSet(charactersIn: "0123456789").inverted).joined() } + private var doubleValue2: Double { .init((Double(stringValue2) ?? 0) / 100.0) ?? 0 } - private var strValue3: String { value.components(separatedBy: CharacterSet(charactersIn: "0123456789").inverted).joined() } // value.filter { !$0.isWhitespace } } - private var doubleValue3: Double { .init((Double(strValue3) ?? 0) * 100) ?? 0 } + private var stringValue3: String { value.components(separatedBy: CharacterSet(charactersIn: "0123456789").inverted).joined() } + private var doubleValue3: Double { .init((Double(stringValue3) ?? 0) * 100) ?? 0 } private var strValue: String { value.filter { !$0.isWhitespace } } private var doubleValue: Double { .init(strValue) ?? 0 } private var formattedValue: String { let value = doubleValue - return formatter.string(for: value) ?? "" } @@ -80,7 +79,6 @@ public struct PriceField: View { .padding(.leading, .xSmall) Text(dispalySymbol) - .onChange(of: currency) { _ in validate() } diff --git a/Sources/OversizeUI/Controls/Row/Row.swift b/Sources/OversizeUI/Controls/Row/Row.swift index f04c98f..95f0e65 100644 --- a/Sources/OversizeUI/Controls/Row/Row.swift +++ b/Sources/OversizeUI/Controls/Row/Row.swift @@ -47,12 +47,13 @@ public struct Row: View where LeadingLabel: View, T (subtitle?.isEmpty) != nil } - public init(_ title: String, - subtitle: String? = nil, - action: (() -> Void)? = nil, - @ViewBuilder leading: () -> LeadingLabel, - @ViewBuilder trailing: () -> TrailingLabel) - { + public init( + _ title: String, + subtitle: String? = nil, + action: (() -> Void)? = nil, + @ViewBuilder leading: () -> LeadingLabel, + @ViewBuilder trailing: () -> TrailingLabel + ) { self.title = title self.subtitle = subtitle self.action = action diff --git a/Sources/OversizeUI/Controls/ScrollView/ScrollViewWithOffset.swift b/Sources/OversizeUI/Controls/ScrollView/ScrollViewWithOffset.swift index 4c87040..ecf8804 100644 --- a/Sources/OversizeUI/Controls/ScrollView/ScrollViewWithOffset.swift +++ b/Sources/OversizeUI/Controls/ScrollView/ScrollViewWithOffset.swift @@ -6,6 +6,14 @@ import SwiftUI public struct ScrollViewWithOffsetTracking: View { + public typealias ScrollAction = (_ offset: CGPoint) -> Void + + private let axes: Axis.Set + private let showsIndicators: Bool + private let cordinateSpaceName: String + private let onScroll: ScrollAction + private let content: () -> Content + public init( _ axes: Axis.Set = .vertical, showsIndicators: Bool = true, @@ -20,14 +28,6 @@ public struct ScrollViewWithOffsetTracking: View { self.content = content } - public typealias ScrollAction = (_ offset: CGPoint) -> Void - - private let axes: Axis.Set - private let showsIndicators: Bool - private let cordinateSpaceName: String - private let onScroll: ScrollAction - private let content: () -> Content - public var body: some View { ScrollView(axes, showsIndicators: showsIndicators) { ScrollViewOffsetTracker { diff --git a/Sources/OversizeUI/Controls/Surface/Surface.swift b/Sources/OversizeUI/Controls/Surface/Surface.swift index 39ede1c..cd13ba9 100644 --- a/Sources/OversizeUI/Controls/Surface/Surface.swift +++ b/Sources/OversizeUI/Controls/Surface/Surface.swift @@ -69,17 +69,29 @@ public struct Surface: View { .padding(.leading, forceContentInsets?.leading ?? contentInsets.leading) .padding(.trailing, forceContentInsets?.trailing ?? contentInsets.trailing) .background( - RoundedRectangle(cornerRadius: surfaceRadius, style: .continuous) - .fill(surfaceBackgroundColor) - .overlay( - RoundedRectangle(cornerRadius: surfaceRadius, style: .continuous) - .strokeBorder(strokeBorderColor, lineWidth: strokeBorderLineWidth) - ) - .shadowElevaton(elevation) + RoundedRectangle( + cornerRadius: surfaceRadius, + style: .continuous + ) + .fill(surfaceBackgroundColor) + .shadowElevaton(elevation) + ) + .overlay( + RoundedRectangle( + cornerRadius: surfaceRadius, + style: .continuous + ) + .strokeBorder( + strokeBorderColor, + lineWidth: strokeBorderLineWidth + ) ) .if(isSurfaceClipped) { view in view.clipShape( - RoundedRectangle(cornerRadius: surfaceRadius, style: .continuous) + RoundedRectangle( + cornerRadius: surfaceRadius, + style: .continuous + ) ) } } diff --git a/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift b/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift index 78672a2..fa1edce 100644 --- a/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift +++ b/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift @@ -14,6 +14,7 @@ public enum FieldHelperStyle { public struct FieldHelperViewModifier: ViewModifier { @Environment(\.theme) private var theme: ThemeSettings + @Environment(\.platform) private var platform: Platform @Binding public var helperText: String @Binding public var helperStyle: FieldHelperStyle public init(helperText: Binding, helperStyle: Binding) { @@ -22,24 +23,29 @@ public struct FieldHelperViewModifier: ViewModifier { } public func body(content: Content) -> some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: platform == .mac ? .xxxSmall : .xSmall) { content - if helperText != "" { - if helperStyle == .helperText { - Text(helperText) - .subheadline(.semibold) - .foregroundColor(.onSurfaceMediumEmphasis) - } else if helperStyle == .errorText { - Text(helperText) - .subheadline(.semibold) - .foregroundColor(.error) - } else if helperStyle == .sussesText { - Text(helperText) - .subheadline(.semibold) - .foregroundColor(.success) - } + if helperText.isEmpty == false, helperStyle != .none { + Text(helperText) + .subheadline(.medium) + .foregroundColor(helperForegroundColor) + .offset(x: platform == .mac ? 4 : 0) } } + .animation(.easeIn(duration: 0.15), value: helperStyle) + } + + private var helperForegroundColor: Color { + switch helperStyle { + case .helperText: + Color.onSurfaceMediumEmphasis + case .errorText: + Color.error + case .sussesText: + Color.success + case .none: + Color.clear + } } } diff --git a/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift b/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift index 3972a36..a88d016 100644 --- a/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift +++ b/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift @@ -10,7 +10,8 @@ public struct LabeledTextFieldStyle: TextFieldStyle { @Environment(\.theme) private var theme: ThemeSettings @Environment(\.fieldLabelPosition) private var fieldPlaceholderPosition: FieldLabelPosition @Environment(\.fieldPosition) private var fieldPosition: FieldPosition - @FocusState var isFocused: Bool + @Environment(\.platform) private var platform: Platform + @FocusState private var isFocused: Bool @Binding private var text: String private let placeholder: String @@ -20,58 +21,112 @@ public struct LabeledTextFieldStyle: TextFieldStyle { } public func _body(configuration: TextField) -> some View { - VStack(alignment: .leading, spacing: .xSmall) { + VStack(alignment: .leading, spacing: platform == .mac ? .xxxSmall : .xSmall) { if fieldPlaceholderPosition == .adjacent { - HStack { - Text(placeholder) - .subheadline(.medium) - .foregroundColor(.onSurfaceHighEmphasis) - Spacer() - } + Text(placeholder) + .subheadline(.medium) + .foregroundColor(platform == .mac ? .onSurfaceMediumEmphasis : .onSurfaceHighEmphasis) + .frame(maxWidth: .infinity, alignment: .leading) + .offset(x: platform == .mac ? 4 : 0) } ZStack(alignment: .leading) { labelTextView configuration .headline(.medium) .foregroundColor(.onSurfaceHighEmphasis) + .padding(padding) .offset(y: fieldOffset) .focused($isFocused) #if os(macOS) - .textFieldStyle(.plain) - .padding(.xxSmall) - #else - .padding(.vertical, fieldPlaceholderPosition == .overInput ? .xxxSmall : .zero) - .padding() - + .if(fieldPlaceholderPosition == .adjacent) { + $0.textFieldStyle(.roundedBorder).controlSize(.large) + } + .if(fieldPlaceholderPosition != .adjacent) { + $0.textFieldStyle(.plain) + } #endif } .background(fieldBackground) .overlay(overlay) } - .animation(.easeIn(duration: 0.15), value: text) + .animation(.easeIn(duration: 0.10), value: text) + } + + private var padding: EdgeInsets { + switch fieldPlaceholderPosition { + case .default: + #if os(macOS) + return .init(.xxSmall) + #else + return .init(.small) + #endif + case .overInput: + #if os(macOS) + return .init(horizontal: .xxSmall, vertical: .xSmall) + #else + return .init( + top: Space.xxxSmall.rawValue + Space.small.rawValue, + leading: Space.small.rawValue, + bottom: Space.xxxSmall.rawValue + Space.small.rawValue, + trailing: Space.small.rawValue + ) + #endif + case .adjacent: + #if os(macOS) + return .init(.zero) + #else + return .init(.small) + #endif + } } @ViewBuilder private var fieldBackground: some View { switch fieldPosition { case .default: + #if canImport(UIKit) RoundedRectangle( - cornerRadius: Radius.medium, + cornerRadius: fieldRadius, style: .continuous ) .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) + #else + if fieldPlaceholderPosition != .adjacent { + RoundedRectangle( + cornerRadius: fieldRadius, + style: .continuous + ) + .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) + } + #endif case .top, .bottom, .center: - #if os(iOS) - RoundedRectangleCorner(radius: Radius.medium, corners: backgroundShapeCorners) + #if canImport(UIKit) + RoundedRectangleCorner( + radius: fieldRadius, + corners: backgroundShapeCorners + ) + .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) + #else + if fieldPlaceholderPosition != .adjacent { + RoundedRectangle( + cornerRadius: fieldRadius, + style: .continuous + ) .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) + } #endif } } - #if os(iOS) - @available(macOS, unavailable) - @available(watchOS, unavailable) - @available(tvOS, unavailable) + var fieldRadius: Radius { + #if os(macOS) + return .xSmall + #else + return .medium + #endif + } + + #if canImport(UIKit) private var backgroundShapeCorners: UIRectCorner { switch fieldPosition { case .default: @@ -88,12 +143,14 @@ public struct LabeledTextFieldStyle: TextFieldStyle { private var fieldOffset: CGFloat { switch fieldPlaceholderPosition { - case .default: - 0 - case .adjacent: - 0 + case .default, .adjacent: + .zero case .overInput: + #if os(macOS) + text.isEmpty ? 0 : 8 + #else text.isEmpty ? 0 : 10 + #endif } } @@ -101,21 +158,37 @@ public struct LabeledTextFieldStyle: TextFieldStyle { private var overlay: some View { switch fieldPosition { case .default: + #if canImport(UIKit) RoundedRectangle( - cornerRadius: Radius.medium, + cornerRadius: fieldRadius, style: .continuous ) .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) - case .top, .bottom, .center: - #if os(iOS) - RoundedRectangleCorner(radius: Radius.medium, corners: backgroundShapeCorners) + #else + if fieldPlaceholderPosition != .adjacent { + RoundedRectangle( + cornerRadius: fieldRadius, + style: .continuous + ) .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) + } + #endif + + case .top, .bottom, .center: + #if canImport(UIKit) + RoundedRectangleCorner(radius: fieldRadius, corners: backgroundShapeCorners) + .stroke( + overlayBorderColor, + lineWidth: isFocused ? 2 : CGFloat(theme.borderSize) + ) #else - RoundedRectangle( - cornerRadius: Radius.medium, - style: .continuous - ) - .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) + if fieldPlaceholderPosition != .adjacent { + RoundedRectangle( + cornerRadius: fieldRadius, + style: .continuous + ) + .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) + } #endif } } @@ -129,7 +202,11 @@ public struct LabeledTextFieldStyle: TextFieldStyle { .subheadline() .onSurfaceDisabledForegroundColor() .opacity(0.7) + #if os(macOS) + .padding(.xSmall) + #else .padding(.small) + #endif } case .adjacent: EmptyView() @@ -138,8 +215,13 @@ public struct LabeledTextFieldStyle: TextFieldStyle { .font(text.isEmpty ? .headline : .subheadline) .fontWeight(text.isEmpty ? .medium : .semibold) .onSurfaceDisabledForegroundColor() + #if os(macOS) + .padding(.xSmall) + .offset(y: text.isEmpty ? 0 : -10) + #else .padding(.small) .offset(y: text.isEmpty ? 0 : -13) + #endif .opacity(text.isEmpty ? 0 : 1) } } diff --git a/Sources/OversizeUI/Controls/URLField/URLField.swift b/Sources/OversizeUI/Controls/URLField/URLField.swift index c3a42f3..341c6de 100644 --- a/Sources/OversizeUI/Controls/URLField/URLField.swift +++ b/Sources/OversizeUI/Controls/URLField/URLField.swift @@ -7,6 +7,7 @@ import SwiftUI #if os(iOS) || os(macOS) @available(iOS 15.0, *) +@available(macOS 14.0, *) @available(watchOS, unavailable) @available(tvOS, unavailable) public struct URLField: View { @@ -37,7 +38,7 @@ public struct URLField: View { if state { textFieldHelper = .none - } else if let url = URL(string: urlString) { + } else if let url = URL(string: urlString), NSWorkspace.shared.urlForApplication(toOpen: url) != nil { textFieldHelper = .none self.url = url } else { @@ -55,7 +56,7 @@ public struct URLField: View { textFieldHelper = .errorText } #else - if let url = URL(string: urlString) { + if let url = URL(string: urlString), NSWorkspace.shared.urlForApplication(toOpen: url) != nil { textFieldHelper = .none self.url = url } else { @@ -66,8 +67,8 @@ public struct URLField: View { #if os(iOS) .keyboardType(.URL) .textInputAutocapitalization(.never) - .textContentType(.URL) #endif + .textContentType(.URL) .autocorrectionDisabled() .fieldHelper(.constant("Invalid URL"), style: $textFieldHelper) } diff --git a/Sources/OversizeUI/Core/EdgeSpaceInsets.swift b/Sources/OversizeUI/Core/EdgeSpaceInsets.swift index 9419a6f..f3fcf0b 100644 --- a/Sources/OversizeUI/Core/EdgeSpaceInsets.swift +++ b/Sources/OversizeUI/Core/EdgeSpaceInsets.swift @@ -24,4 +24,11 @@ public struct EdgeSpaceInsets { bottom = vertical trailing = horizontal } + + public init(_ all: Space) { + top = all + leading = all + bottom = all + trailing = all + } } diff --git a/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift b/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift new file mode 100644 index 0000000..853b23b --- /dev/null +++ b/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift @@ -0,0 +1,53 @@ +// +// Copyright © 2024 Alexander Romanov +// Platform.swift, created on 07.09.2024 +// + +import SwiftUI +#if canImport(UIKit) +import UIKit +#endif + +public enum Platform { + case iPhone, iPad, mac, tv, watch, vision, carPlay, other +} + +private struct PlatformKey: EnvironmentKey { + static let defaultValue: Platform = { + #if os(macOS) || targetEnvironment(macCatalyst) + return .mac + #elseif os(watchOS) + return .watch + #elseif os(visionOS) + return .vision + #elseif canImport(UIKit) + switch UIDevice.current.userInterfaceIdiom { + case .phone: + return .iPhone + case .pad: + return .iPad + case .tv: + return .tv + case .carPlay: + return .carPlay + case .mac: + return .mac + case .vision: + return .vision + case .unspecified: + return .other + @unknown default: + return .other + } + #else + return .other + #endif + }() +} + +public extension EnvironmentValues { + var platform: Platform { + get { self[PlatformKey.self] } + set { self[PlatformKey.self] = newValue } + } +} diff --git a/Sources/OversizeUI/Core/Radius.swift b/Sources/OversizeUI/Core/Radius.swift index 947f6c4..51d37e3 100644 --- a/Sources/OversizeUI/Core/Radius.swift +++ b/Sources/OversizeUI/Core/Radius.swift @@ -12,6 +12,8 @@ public enum Radius { /// 0 case zero + /// 4 + case xSmall /// 8 case small /// 12 @@ -25,6 +27,8 @@ public enum Radius { switch self { case .zero: .zero + case .xSmall: + CGFloat(theme.radius / 2) case .small: CGFloat(theme.radius) case .medium: diff --git a/Sources/OversizeUI/Core/Space.swift b/Sources/OversizeUI/Core/Space.swift index 1b193f0..4704e29 100644 --- a/Sources/OversizeUI/Core/Space.swift +++ b/Sources/OversizeUI/Core/Space.swift @@ -21,8 +21,13 @@ public enum Space: CGFloat { /// 16 case small = 16 + #if os(macOS) + /// 20 + case medium = 20 + #else /// 24 case medium = 24 + #endif /// 32 case large = 32 diff --git a/Sources/OversizeUI/Core/Typography.swift b/Sources/OversizeUI/Core/Typography.swift index 818cc89..12a9631 100644 --- a/Sources/OversizeUI/Core/Typography.swift +++ b/Sources/OversizeUI/Core/Typography.swift @@ -40,7 +40,7 @@ public struct Typography: ViewModifier { content .font(.system(fontStyle, design: fontDesign).weight(fontWeight).leading(.tight)) .frame(minHeight: lineHeight) - // .lineSpacing(lineHeight) + .lineSpacing(lineHeight * 0.2) } private var lineHeight: CGFloat { diff --git a/Sources/OversizeUI/Extensions/Color/Color+RawRepresentable.swift b/Sources/OversizeUI/Extensions/Color/Color+RawRepresentable.swift index 8d2657e..3af7df7 100644 --- a/Sources/OversizeUI/Extensions/Color/Color+RawRepresentable.swift +++ b/Sources/OversizeUI/Extensions/Color/Color+RawRepresentable.swift @@ -8,7 +8,7 @@ import Foundation import SwiftUI import UIKit -extension Color: RawRepresentable { +extension Color: @retroactive RawRepresentable { @available(macOS, unavailable) @available(watchOS, unavailable) @available(tvOS, unavailable) diff --git a/Sources/OversizeUI/Extensions/Image/Image+Extensions.swift b/Sources/OversizeUI/Extensions/Image/Image+Extensions.swift index 053df9c..f89ef12 100644 --- a/Sources/OversizeUI/Extensions/Image/Image+Extensions.swift +++ b/Sources/OversizeUI/Extensions/Image/Image+Extensions.swift @@ -16,6 +16,13 @@ public extension Image { public extension Image { func icon(_ color: Color = Color.onSurfaceHighEmphasis) -> some View { renderingMode(.template) + #if os(macOS) + .resizable() + .frame( + width: IconSizes.medium.rawValue, + height: IconSizes.medium.rawValue + ) + #endif .foregroundColor(color) } } @@ -24,7 +31,10 @@ public extension Image { func icon(_ color: Color = Color.onSurfaceHighEmphasis, size: IconSizes) -> some View { renderingMode(.template) .resizable() - .frame(width: size.rawValue, height: size.rawValue) + .frame( + width: size.rawValue, + height: size.rawValue + ) .foregroundColor(color) } } diff --git a/Sources/OversizeUI/Extensions/Spacing/Spacing.swift b/Sources/OversizeUI/Extensions/Spacing/Spacing.swift index 588a019..ea583fd 100644 --- a/Sources/OversizeUI/Extensions/Spacing/Spacing.swift +++ b/Sources/OversizeUI/Extensions/Spacing/Spacing.swift @@ -59,3 +59,15 @@ public extension EdgeInsets { self = .init(top: top.rawValue, leading: leading.rawValue, bottom: bottom.rawValue, trailing: trailing.rawValue) } } + +public extension EdgeInsets { + init(_ all: Space) { + self = .init(top: all.rawValue, leading: all.rawValue, bottom: all.rawValue, trailing: all.rawValue) + } +} + +public extension EdgeInsets { + init(horizontal: Space, vertical: Space) { + self = .init(top: vertical.rawValue, leading: horizontal.rawValue, bottom: vertical.rawValue, trailing: horizontal.rawValue) + } +} diff --git a/Sources/OversizeUI/Resources/Colors.xcassets/Background/BackgroundTertiary.colorset/Contents.json b/Sources/OversizeUI/Resources/Colors.xcassets/Background/BackgroundTertiary.colorset/Contents.json index d2f2378..2924543 100644 --- a/Sources/OversizeUI/Resources/Colors.xcassets/Background/BackgroundTertiary.colorset/Contents.json +++ b/Sources/OversizeUI/Resources/Colors.xcassets/Background/BackgroundTertiary.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.902", - "green" : "0.902", - "red" : "0.902" + "blue" : "0xE6", + "green" : "0xE6", + "red" : "0xE6" } }, "idiom" : "universal" diff --git a/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift b/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift index bb14bbe..4de13c5 100644 --- a/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift +++ b/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift @@ -3,28 +3,19 @@ // RoundedRectangleCorner.swift, created on 11.09.2021 // -#if os(iOS) +#if canImport(UIKit) import SwiftUI -@available(watchOS, unavailable) -@available(tvOS, unavailable) -@available(macOS, unavailable) public struct RoundedRectangleCorner: Shape { private var radius: CGFloat = .infinity private var corners: UIRectCorner = .allCorners - @available(watchOS, unavailable) - @available(tvOS, unavailable) - @available(macOS, unavailable) public init(radius: CGFloat, corners: UIRectCorner) { self.radius = radius self.corners = corners } - @available(watchOS, unavailable) - @available(tvOS, unavailable) - @available(macOS, unavailable) public init(radius: Radius, corners: UIRectCorner) { self.radius = radius.rawValue self.corners = corners