From 0e2a4acf3df971c92eb14c54875a39be86993960 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 21:37:54 +0300 Subject: [PATCH 01/13] Add Platform and minor bug fixes --- 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 +- 20 files changed, 308 insertions(+), 103 deletions(-) create mode 100644 Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift 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..6405bd7 --- /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 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 + } + #elseif os(watchOS) + return .watch + #elseif os(visionOS) + return .vision + #elseif os(macOS) || targetEnvironment(macCatalyst) + return .mac + #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" From 94505bc7283e3f884671a27803ebd88a3d10861d Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 21:53:00 +0300 Subject: [PATCH 02/13] Up CI devices --- .github/workflows/ci-pull-request.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml index ba40e0e..b38d5d9 100644 --- a/.github/workflows/ci-pull-request.yml +++ b/.github/workflows/ci-pull-request.yml @@ -22,7 +22,7 @@ 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) @@ -45,7 +45,7 @@ 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) @@ -58,7 +58,7 @@ 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 Series 10 (42mm),OS=11.0'] with: path: Example/Example scheme: Example (watchOS) From b8cf6bc7d5c7cc41a2de093225455a6bb97bb300 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 22:09:42 +0300 Subject: [PATCH 03/13] Fix platform --- .../OversizeUI/Core/EnvironmentKeys/Platform.swift | 14 +++++++------- .../OversizeUI/Shapes/RoundedRectangleCorner.swift | 11 +---------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift b/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift index 6405bd7..853b23b 100644 --- a/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift +++ b/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift @@ -14,7 +14,13 @@ public enum Platform { private struct PlatformKey: EnvironmentKey { static let defaultValue: Platform = { - #if canImport(UIKit) + #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 @@ -33,12 +39,6 @@ private struct PlatformKey: EnvironmentKey { @unknown default: return .other } - #elseif os(watchOS) - return .watch - #elseif os(visionOS) - return .vision - #elseif os(macOS) || targetEnvironment(macCatalyst) - return .mac #else return .other #endif 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 From b0aa8aa5a631edc4d11ebd02cb57b5bef7dc2818 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 22:16:12 +0300 Subject: [PATCH 04/13] Fix ci --- .github/workflows/ci-pull-request.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml index b38d5d9..a2a91bb 100644 --- a/.github/workflows/ci-pull-request.yml +++ b/.github/workflows/ci-pull-request.yml @@ -3,9 +3,6 @@ on: pull_request: branches: - main - push: - branches: - - develop workflow_dispatch: jobs: From 676d05b5316eaf7541099cc7982f427975e6f9ed Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 22:23:38 +0300 Subject: [PATCH 05/13] Update ci-pull-request.yml --- .github/workflows/ci-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml index a2a91bb..ae6f8c7 100644 --- a/.github/workflows/ci-pull-request.yml +++ b/.github/workflows/ci-pull-request.yml @@ -55,7 +55,7 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main strategy: matrix: - destination: ['platform=watchOS Simulator,name=Apple Watch Series 10 (42mm),OS=11.0'] + destination: ['platform=watchOS Simulator,name=Apple Watch SE (40mm) (2nd generation),OS=11.0'] with: path: Example/Example scheme: Example (watchOS) From 852d398f569155597a19bcf115af82abf38d4297 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 23:06:48 +0300 Subject: [PATCH 06/13] Update ci-pull-request.yml --- .github/workflows/ci-pull-request.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml index ae6f8c7..9cbd2b3 100644 --- a/.github/workflows/ci-pull-request.yml +++ b/.github/workflows/ci-pull-request.yml @@ -9,8 +9,16 @@ jobs: build-swiftpm: name: Build SwiftPM uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm.yml@main + strategy: + matrix: + 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: OversizeUI + destination: ${{ matrix.destination }} secrets: inherit build-iOS-example: From 718b80a8174041b82dd60963a555594cb9fae370 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 23:10:44 +0300 Subject: [PATCH 07/13] Update ci-pull-request.yml --- .github/workflows/ci-pull-request.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml index 9cbd2b3..f606ecc 100644 --- a/.github/workflows/ci-pull-request.yml +++ b/.github/workflows/ci-pull-request.yml @@ -27,7 +27,9 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main strategy: matrix: - destination: ['platform=iOS Simulator,name=iPhone 16 Pro,OS=18.0', 'platform=iOS Simulator,name=iPad (10th generation),OS=18.0'] + 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) @@ -50,7 +52,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=18.0', 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=18.0'] + 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) @@ -63,7 +67,8 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main strategy: matrix: - destination: ['platform=watchOS Simulator,name=Apple Watch SE (40mm) (2nd generation),OS=11.0'] + destination: + - platform=watchOS Simulator,name=Apple Watch SE (40mm) (2nd generation),OS=11.0 with: path: Example/Example scheme: Example (watchOS) From fcb58f6707b3b1274f81da5af542d870431dae93 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 23:32:30 +0300 Subject: [PATCH 08/13] Update CI # Conflicts: # .github/workflows/ci-pull-request.yml --- .github/workflows/ci-pull-request.yml | 34 +++++++++-- .github/workflows/ci-release.yml | 81 --------------------------- .github/workflows/publish-docc.yml | 37 ------------ 3 files changed, 30 insertions(+), 122 deletions(-) delete mode 100644 .github/workflows/ci-release.yml delete mode 100644 .github/workflows/publish-docc.yml diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml index f606ecc..34ca7d1 100644 --- a/.github/workflows/ci-pull-request.yml +++ b/.github/workflows/ci-pull-request.yml @@ -1,8 +1,13 @@ -name: CI - Pull Request +name: CI on: pull_request: + types: + - closed branches: - main + push: + branches: + - '**' workflow_dispatch: jobs: @@ -27,7 +32,7 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main strategy: matrix: - destination: + destination: - platform=iOS Simulator,name=iPhone 16 Pro,OS=18.0 - platform=iOS Simulator,name=iPad (10th generation),OS=18.0 with: @@ -52,7 +57,7 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main strategy: matrix: - destination: + 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: @@ -67,10 +72,31 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main strategy: matrix: - destination: + 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 + 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 + if: github.ref == 'refs/heads/main' + needs: bump + uses: ./.github/workflows/publish-docc.yml + secrets: inherit + diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml deleted file mode 100644 index 6bc476a..0000000 --- a/.github/workflows/ci-release.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: CI - Release -on: - pull_request: - types: - - closed - branches: - - main - workflow_dispatch: - -jobs: - build-swiftpm: - name: Build SwiftPM - uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm.yml@main - strategy: - matrix: - packages: [OversizeUI] - with: - package: ${{ matrix.packages }} - 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 - - bump: - name: Bump version - 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 From 718d93b800270aa7c25ed6000b5fbaf0e0528946 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 23:34:06 +0300 Subject: [PATCH 09/13] Update ci-pull-request.yml --- .github/workflows/ci-pull-request.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml index 34ca7d1..af8f61b 100644 --- a/.github/workflows/ci-pull-request.yml +++ b/.github/workflows/ci-pull-request.yml @@ -92,11 +92,4 @@ jobs: uses: oversizedev/GithubWorkflows/.github/workflows/bump.yml@main secrets: inherit - - publish-docc: - name: Publish docc - if: github.ref == 'refs/heads/main' - needs: bump - uses: ./.github/workflows/publish-docc.yml - secrets: inherit From fb8c56c017c653871ca0e3206f41a20e75bbab24 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 23:44:57 +0300 Subject: [PATCH 10/13] Update steps # Conflicts: # .github/workflows/ci-pull-request.yml --- .github/workflows/ci-pull-request.yml | 95 --------------------------- .github/workflows/ci.yml | 66 +++++++++++++++++++ 2 files changed, 66 insertions(+), 95 deletions(-) delete mode 100644 .github/workflows/ci-pull-request.yml create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml deleted file mode 100644 index af8f61b..0000000 --- a/.github/workflows/ci-pull-request.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: CI -on: - pull_request: - types: - - closed - branches: - - main - push: - branches: - - '**' - workflow_dispatch: - -jobs: - build-swiftpm: - name: Build SwiftPM - uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm.yml@main - strategy: - matrix: - 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: OversizeUI - destination: ${{ matrix.destination }} - 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 16 Pro,OS=18.0 - - platform=iOS Simulator,name=iPad (10th generation),OS=18.0 - 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=18.0 - - platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=18.0 - 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 (40mm) (2nd generation),OS=11.0 - with: - path: Example/Example - scheme: Example (watchOS) - destination: ${{ matrix.destination }} - secrets: inherit - - bump: - name: Bump version - 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 - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..733075d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI +on: + pull_request: + types: + - closed + branches: + - main + push: + branches: + - '**' + workflow_dispatch: + +jobs: + build-swiftpm: + name: Build SwiftPM + uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm.yml@main + strategy: + matrix: + 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: OversizeUI + destination: ${{ matrix.destination }} + secrets: inherit + + build-app: + name: Build examples + needs: build-swiftpm + uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main + strategy: + matrix: + platform: + - { name: "iOS Simulator", os: "iPhone 16,OS=18.0", scheme: "Example (iOS)" } + - { name: "iPad (10th generation)", os: "OS=18.0", scheme: "Example (iOS)" } + - { name: "watchOS Simulator", os: "Apple Watch SE (40mm) (2nd generation),OS=11.0", scheme: "Example (watchOS)" } + - { name: "tvOS Simulator", os: "Apple TV 4K (3rd generation) (at 1080p),OS=18.0", scheme: "Example (tvOS)" } + - { name: "macOS", os: "macOS,arch=arm64", scheme: "Example (macOS)" } + with: + path: Example/Example + scheme: ${{ matrix.platform.scheme }} + destination: platform=${{ matrix.platform.os }} + secrets: inherit + + bump: + name: Bump version + 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 + if: github.ref == 'refs/heads/main' + needs: bump + uses: ./.github/workflows/publish-docc.yml + secrets: inherit + From 51a6825647a6ad723b9ca659c3139010c486184e Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 23:47:31 +0300 Subject: [PATCH 11/13] Update ci.yml --- .github/workflows/ci.yml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 733075d..ce5bf65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,19 +48,6 @@ jobs: name: Bump version if: github.ref == 'refs/heads/main' needs: - - build-swiftpm - - build-iOS-example - - build-macOS-example - - build-tvOS-example - - build-watchOS-example - + - build-app uses: oversizedev/GithubWorkflows/.github/workflows/bump.yml@main secrets: inherit - - publish-docc: - name: Publish docc - if: github.ref == 'refs/heads/main' - needs: bump - uses: ./.github/workflows/publish-docc.yml - secrets: inherit - From d0c835469d4e861d041e7334c35f96540c4b1e50 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Thu, 26 Sep 2024 23:55:33 +0300 Subject: [PATCH 12/13] Revert changes --- .github/workflows/ci.yml | 63 +++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce5bf65..ddb0340 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,28 +26,69 @@ jobs: destination: ${{ matrix.destination }} secrets: inherit - build-app: - name: Build examples + build-iOS-example: + name: Build iOS example needs: build-swiftpm uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main strategy: matrix: - platform: - - { name: "iOS Simulator", os: "iPhone 16,OS=18.0", scheme: "Example (iOS)" } - - { name: "iPad (10th generation)", os: "OS=18.0", scheme: "Example (iOS)" } - - { name: "watchOS Simulator", os: "Apple Watch SE (40mm) (2nd generation),OS=11.0", scheme: "Example (watchOS)" } - - { name: "tvOS Simulator", os: "Apple TV 4K (3rd generation) (at 1080p),OS=18.0", scheme: "Example (tvOS)" } - - { name: "macOS", os: "macOS,arch=arm64", scheme: "Example (macOS)" } + 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) + 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: ${{ matrix.platform.scheme }} - destination: platform=${{ matrix.platform.os }} + 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=18.0 + - platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=18.0 + 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 (40mm) (2nd generation),OS=11.0 + with: + path: Example/Example + scheme: Example (watchOS) + destination: ${{ matrix.destination }} secrets: inherit bump: name: Bump version if: github.ref == 'refs/heads/main' needs: - - build-app + - build-swiftpm + - build-iOS-example + - build-macOS-example + - build-tvOS-example + - build-watchOS-example + uses: oversizedev/GithubWorkflows/.github/workflows/bump.yml@main secrets: inherit From 65f14edce7da2393fdca01d0c636ddb4009883f0 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Sun, 6 Oct 2024 11:46:38 +0400 Subject: [PATCH 13/13] Add RoundedRectangleCorner for macOS, update filed position and etc --- .../OversizeUI/Controls/Avatar/Avatar.swift | 31 ++++++- .../Controls/Avatar/AvatarModifiers.swift | 3 +- .../Controls/Button/FieldButtonStyle.swift | 23 ++--- .../Controls/IconPicker/IconPicker.swift | 5 +- .../Controls/PageControl/PageIndexView.swift | 4 + .../OversizeUI/Controls/PageView/Page.swift | 2 +- .../Controls/PriceField/PriceField.swift | 2 +- .../Controls/Select/MultiSelect.swift | 5 ++ .../OversizeUI/Controls/Select/Select.swift | 5 ++ .../OversizeUI/Controls/TextBox/TextBox.swift | 45 ++++++---- .../TextEditorPlaceholderViewModifier.swift | 74 +++++++++++++--- .../TextField/FieldHelperViewModifier.swift | 4 +- .../TextField/LabeledTextFieldStyle.swift | 88 +++++++++++-------- .../Core/Appearance/ThemeSettings.swift | 6 ++ .../FieldPositionEnvironment.swift | 10 +-- .../Core/EnvironmentKeys/Platform.swift | 14 +-- Sources/OversizeUI/Core/Typography.swift | 42 ++++++++- Sources/OversizeUI/Deprecated/PageView.swift | 16 ++++ .../Extensions/View/View+CornerRadius.swift | 14 ++- .../Shapes/RoundedRectangleCorner.swift | 84 ++++++++++++++++-- 20 files changed, 365 insertions(+), 112 deletions(-) diff --git a/Sources/OversizeUI/Controls/Avatar/Avatar.swift b/Sources/OversizeUI/Controls/Avatar/Avatar.swift index 9c1d2fb..80449be 100644 --- a/Sources/OversizeUI/Controls/Avatar/Avatar.swift +++ b/Sources/OversizeUI/Controls/Avatar/Avatar.swift @@ -41,6 +41,9 @@ public struct Avatar: View { /// Sets a stroke color for the Avatar. var strokeColor: Color = .clear + /// Sets a stroke width for the Avatar. + var strokeLineWidth: CGFloat = 2 + /// Sets a custom background color for the Avatar. var background: AvatarBackgroundType = .color(.surfaceSecondary) @@ -82,13 +85,23 @@ public struct Avatar: View { .scaledToFill() .frame(width: avatarSize, height: avatarSize) .clipShape(Circle()) - .overlay(Circle().stroke(strokeColor, lineWidth: 2)) + .overlay( + Circle().stroke( + strokeColor, + lineWidth: strokeLineWidth + ) + ) } else { ZStack { avatarSurface .frame(width: avatarSize, height: avatarSize) - .overlay(Circle().stroke(strokeColor, lineWidth: 2)) + .overlay( + Circle().stroke( + strokeColor, + lineWidth: strokeLineWidth + ) + ) avatarLabel } @@ -106,13 +119,23 @@ public struct Avatar: View { .scaledToFill() .frame(width: Space.xxxLarge.rawValue, height: Space.xxxLarge.rawValue) .clipShape(Circle()) - .overlay(Circle().stroke(strokeColor, lineWidth: 2)) + .overlay( + Circle().stroke( + strokeColor, + lineWidth: strokeLineWidth + ) + ) } else { ZStack { avatarSurface .frame(width: Space.xxxLarge.rawValue, height: Space.xxxLarge.rawValue) - .overlay(Circle().stroke(strokeColor, lineWidth: 2)) + .overlay( + Circle().stroke( + strokeColor, + lineWidth: strokeLineWidth + ) + ) } } } diff --git a/Sources/OversizeUI/Controls/Avatar/AvatarModifiers.swift b/Sources/OversizeUI/Controls/Avatar/AvatarModifiers.swift index 0e1db62..da840d0 100644 --- a/Sources/OversizeUI/Controls/Avatar/AvatarModifiers.swift +++ b/Sources/OversizeUI/Controls/Avatar/AvatarModifiers.swift @@ -27,9 +27,10 @@ public extension Avatar { /// Sets a stroke for the Avatar /// - Parameter strokeColor: Color for Avatar stroke /// - Returns: The modified view with stroke. - func avatarStroke(_ strokeColor: Color = .surfacePrimary) -> Avatar { + func avatarStroke(_ strokeColor: Color = .surfacePrimary, lineWidth: CGFloat = 2) -> Avatar { var control = self control.strokeColor = strokeColor + control.strokeLineWidth = lineWidth return control } } diff --git a/Sources/OversizeUI/Controls/Button/FieldButtonStyle.swift b/Sources/OversizeUI/Controls/Button/FieldButtonStyle.swift index 5121cbc..1a79818 100644 --- a/Sources/OversizeUI/Controls/Button/FieldButtonStyle.swift +++ b/Sources/OversizeUI/Controls/Button/FieldButtonStyle.swift @@ -7,7 +7,7 @@ import SwiftUI public struct FieldButtonStyle: ButtonStyle { @Environment(\.theme) private var theme: ThemeSettings - @Environment(\.fieldPosition) private var fieldPosition: FieldPosition + @Environment(\.fieldPosition) private var fieldPosition: VerticalAlignment? public init() {} @@ -24,16 +24,6 @@ public struct FieldButtonStyle: ButtonStyle { @ViewBuilder private func fieldBackground(isPressed: Bool) -> some View { switch fieldPosition { - case .default: - RoundedRectangle(cornerRadius: Radius.medium, style: .continuous) - .fill(isPressed ? Color.surfaceTertiary : Color.surfaceSecondary) - .overlay( - RoundedRectangle(cornerRadius: Radius.medium, - style: .continuous) - .stroke(theme.borderTextFields - ? Color.border - : Color.surfaceSecondary, lineWidth: CGFloat(theme.borderSize)) - ) case .top, .bottom, .center: #if os(iOS) RoundedRectangleCorner(radius: Radius.medium, corners: backgroundShapeCorners) @@ -55,6 +45,17 @@ public struct FieldButtonStyle: ButtonStyle { : Color.surfaceSecondary, lineWidth: CGFloat(theme.borderSize)) ) #endif + + default: + RoundedRectangle(cornerRadius: Radius.medium, style: .continuous) + .fill(isPressed ? Color.surfaceTertiary : Color.surfaceSecondary) + .overlay( + RoundedRectangle(cornerRadius: Radius.medium, + style: .continuous) + .stroke(theme.borderTextFields + ? Color.border + : Color.surfaceSecondary, lineWidth: CGFloat(theme.borderSize)) + ) } } diff --git a/Sources/OversizeUI/Controls/IconPicker/IconPicker.swift b/Sources/OversizeUI/Controls/IconPicker/IconPicker.swift index dd1ca6d..c5314e9 100644 --- a/Sources/OversizeUI/Controls/IconPicker/IconPicker.swift +++ b/Sources/OversizeUI/Controls/IconPicker/IconPicker.swift @@ -5,9 +5,7 @@ import SwiftUI -@available(macOS, unavailable) -@available(watchOS, unavailable) -@available(tvOS, unavailable) +#if os(iOS) public struct IconPicker: View { @Environment(\.theme) private var theme: ThemeSettings @Environment(\.horizontalSizeClass) var horizontalSizeClass @@ -109,3 +107,4 @@ public struct IconPicker: View { } } } +#endif diff --git a/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift b/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift index 72d48c3..5fc08ef 100644 --- a/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift +++ b/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift @@ -19,7 +19,11 @@ public struct PageIndexView: View { ForEach(0 ..< maxIndex, id: \.self) { index in Capsule() .fill(index == self.index ? Color.accent : Color.surfaceTertiary) + #if os(iOS) .frame(width: index == self.index ? 28 : 8, height: 8) + #else + .frame(width: index == self.index ? 24 : 6, height: 6) + #endif .animation(.default, value: index) } } diff --git a/Sources/OversizeUI/Controls/PageView/Page.swift b/Sources/OversizeUI/Controls/PageView/Page.swift index 9709c53..b176af8 100644 --- a/Sources/OversizeUI/Controls/PageView/Page.swift +++ b/Sources/OversizeUI/Controls/PageView/Page.swift @@ -1109,7 +1109,7 @@ struct SeartchTextFieldButtonStyle: ButtonStyle { private extension View { @ViewBuilder func prefersNavigationBarHidden() -> some View { - #if os(watchOS) + #if os(watchOS) || os(macOS) self #else if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) { diff --git a/Sources/OversizeUI/Controls/PriceField/PriceField.swift b/Sources/OversizeUI/Controls/PriceField/PriceField.swift index 8495391..4f19577 100644 --- a/Sources/OversizeUI/Controls/PriceField/PriceField.swift +++ b/Sources/OversizeUI/Controls/PriceField/PriceField.swift @@ -9,7 +9,7 @@ import SwiftUI import UIKit #endif -@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) +@available(macOS 14, iOS 16, tvOS 16, watchOS 9, *) public struct PriceField: View { private var formatter: NumberFormatter { let formatter = NumberFormatter() diff --git a/Sources/OversizeUI/Controls/Select/MultiSelect.swift b/Sources/OversizeUI/Controls/Select/MultiSelect.swift index e9ae58f..8588495 100644 --- a/Sources/OversizeUI/Controls/Select/MultiSelect.swift +++ b/Sources/OversizeUI/Controls/Select/MultiSelect.swift @@ -6,6 +6,7 @@ import SwiftUI // swiftlint:disable all +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct MultiSelect: View where Content: View, @@ -143,6 +144,7 @@ public struct MultiSelect: View where Content: View, @@ -138,6 +139,7 @@ public struct Select TextBox { diff --git a/Sources/OversizeUI/Controls/TextEditor/TextEditorPlaceholderViewModifier.swift b/Sources/OversizeUI/Controls/TextEditor/TextEditorPlaceholderViewModifier.swift index 8a6f9f0..92bbc53 100644 --- a/Sources/OversizeUI/Controls/TextEditor/TextEditorPlaceholderViewModifier.swift +++ b/Sources/OversizeUI/Controls/TextEditor/TextEditorPlaceholderViewModifier.swift @@ -31,14 +31,12 @@ public struct TextEditorPlaceholderViewModifier: ViewModifier { } content - .padding(.horizontal, .xSmall) - .padding(.top, topInputPadding) - .padding(.bottom, 10) + .padding(padding) .headline(.medium) .onSurfaceHighEmphasisForegroundColor() .background { ZStack { - RoundedRectangle(cornerRadius: .medium, style: .continuous) + RoundedRectangle(cornerRadius: fieldRadius, style: .continuous) .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) overlay } @@ -53,12 +51,66 @@ public struct TextEditorPlaceholderViewModifier: ViewModifier { } } - var topInputPadding: CGFloat { + private var fieldRadius: Radius { + #if os(macOS) + return .xSmall + #else + return .medium + #endif + } + + var padding: EdgeInsets { + switch fieldPlaceholderPosition { + case .default, .adjacent: + #if os(macOS) + return .init( + top: 10, + leading: Space.xSmall.rawValue, + bottom: 10, + trailing: Space.xSmall.rawValue + ) + #else + return .init( + top: 10, + leading: Space.xSmall.rawValue, + bottom: 10, + trailing: Space.xSmall.rawValue + ) + #endif + case .overInput: + #if os(macOS) + return .init( + top: text.isEmpty ? 13 : 22, + leading: Space.xxSmall.rawValue, + bottom: 10, + trailing: Space.xxSmall.rawValue + ) + #else + return .init( + top: text.isEmpty ? 8 : 22, + leading: Space.xSmall.rawValue, + bottom: 10, + trailing: Space.xSmall.rawValue + ) + + #endif + } + } + + var labelPadding: EdgeInsets { switch fieldPlaceholderPosition { case .default, .adjacent: - 10 + #if os(macOS) + return .init(Space.xSmall) + #else + return .init(Space.xSmall) + #endif case .overInput: - text.isEmpty ? 8 : 22 + #if os(macOS) + return .init(horizontal: Space.xSmall, vertical: Space.xSmall) + #else + return .init(Space.xxSmall) + #endif } } @@ -71,7 +123,7 @@ public struct TextEditorPlaceholderViewModifier: ViewModifier { .subheadline() .onSurfaceDisabledForegroundColor() .opacity(0.7) - .padding(.small) + .padding(labelPadding) } case .adjacent: EmptyView() @@ -81,14 +133,14 @@ public struct TextEditorPlaceholderViewModifier: ViewModifier { .fontWeight(text.isEmpty ? .medium : .semibold) .onSurfaceDisabledForegroundColor() .opacity(0.7) - .padding(.small) + .padding(labelPadding) .offset(y: text.isEmpty ? 0 : -6) } } @ViewBuilder var overlay: some View { - RoundedRectangle(cornerRadius: Radius.medium, style: .continuous) + RoundedRectangle(cornerRadius: fieldRadius, style: .continuous) .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) } @@ -123,10 +175,12 @@ struct TextEditor_preview: PreviewProvider { TextEditor(text: .constant("")) .textEditorPlaceholder("Complaint", text: .constant("")) .fieldLabelPosition(.overInput) + .fieldPosition(.top) TextEditor(text: .constant("Text")) .textEditorPlaceholder("Complaint", text: .constant("Text")) .fieldLabelPosition(.overInput) + .fieldPosition(.bottom) Spacer() } diff --git a/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift b/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift index fa1edce..ce2e97e 100644 --- a/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift +++ b/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift @@ -23,13 +23,13 @@ public struct FieldHelperViewModifier: ViewModifier { } public func body(content: Content) -> some View { - VStack(alignment: .leading, spacing: platform == .mac ? .xxxSmall : .xSmall) { + VStack(alignment: .leading, spacing: platform == .macOS ? .xxxSmall : .xSmall) { content if helperText.isEmpty == false, helperStyle != .none { Text(helperText) .subheadline(.medium) .foregroundColor(helperForegroundColor) - .offset(x: platform == .mac ? 4 : 0) + .offset(x: platform == .macOS ? 4 : 0) } } .animation(.easeIn(duration: 0.15), value: helperStyle) diff --git a/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift b/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift index a88d016..3407d6e 100644 --- a/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift +++ b/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift @@ -6,10 +6,11 @@ import SwiftUI // swiftlint:disable identifier_name +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct LabeledTextFieldStyle: TextFieldStyle { @Environment(\.theme) private var theme: ThemeSettings @Environment(\.fieldLabelPosition) private var fieldPlaceholderPosition: FieldLabelPosition - @Environment(\.fieldPosition) private var fieldPosition: FieldPosition + @Environment(\.fieldPosition) private var fieldPosition: VerticalAlignment? @Environment(\.platform) private var platform: Platform @FocusState private var isFocused: Bool @Binding private var text: String @@ -21,13 +22,13 @@ public struct LabeledTextFieldStyle: TextFieldStyle { } public func _body(configuration: TextField) -> some View { - VStack(alignment: .leading, spacing: platform == .mac ? .xxxSmall : .xSmall) { + VStack(alignment: .leading, spacing: platform == .macOS ? .xxxSmall : .xSmall) { if fieldPlaceholderPosition == .adjacent { Text(placeholder) .subheadline(.medium) - .foregroundColor(platform == .mac ? .onSurfaceMediumEmphasis : .onSurfaceHighEmphasis) + .foregroundColor(platform == .macOS ? .onSurfaceMediumEmphasis : .onSurfaceHighEmphasis) .frame(maxWidth: .infinity, alignment: .leading) - .offset(x: platform == .mac ? 4 : 0) + .offset(x: platform == .macOS ? 4 : 0) } ZStack(alignment: .leading) { labelTextView @@ -62,7 +63,7 @@ public struct LabeledTextFieldStyle: TextFieldStyle { #endif case .overInput: #if os(macOS) - return .init(horizontal: .xxSmall, vertical: .xSmall) + return .init(.xSmall) #else return .init( top: Space.xxxSmall.rawValue + Space.small.rawValue, @@ -83,27 +84,28 @@ public struct LabeledTextFieldStyle: TextFieldStyle { @ViewBuilder private var fieldBackground: some View { switch fieldPosition { - case .default: + case .top, .bottom, .center: #if canImport(UIKit) - RoundedRectangle( - cornerRadius: fieldRadius, - style: .continuous + RoundedRectangleCorner( + radius: fieldRadius, + corners: backgroundShapeCorners ) .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) #else if fieldPlaceholderPosition != .adjacent { - RoundedRectangle( - cornerRadius: fieldRadius, - style: .continuous + RoundedRectangleCorner( + radius: fieldRadius, + corners: backgroundShapeCorners ) .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) } #endif - case .top, .bottom, .center: + + default: #if canImport(UIKit) - RoundedRectangleCorner( - radius: fieldRadius, - corners: backgroundShapeCorners + RoundedRectangle( + cornerRadius: fieldRadius, + style: .continuous ) .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) #else @@ -118,7 +120,7 @@ public struct LabeledTextFieldStyle: TextFieldStyle { } } - var fieldRadius: Radius { + private var fieldRadius: Radius { #if os(macOS) return .xSmall #else @@ -141,13 +143,28 @@ public struct LabeledTextFieldStyle: TextFieldStyle { } #endif + #if canImport(AppKit) + private var backgroundShapeCorners: RectCorner { + switch fieldPosition { + case .top: + [.topLeft, .topRight] + case .bottom: + [.bottomLeft, .bottomRight] + case .center: + [] + default: + [.allCorners] + } + } + #endif + private var fieldOffset: CGFloat { switch fieldPlaceholderPosition { case .default, .adjacent: .zero case .overInput: #if os(macOS) - text.isEmpty ? 0 : 8 + text.isEmpty && !isFocused ? -2 : text.isEmpty ? 0 : isFocused ? 9 : 7 #else text.isEmpty ? 0 : 10 #endif @@ -157,23 +174,6 @@ public struct LabeledTextFieldStyle: TextFieldStyle { @ViewBuilder private var overlay: some View { switch fieldPosition { - case .default: - #if canImport(UIKit) - RoundedRectangle( - cornerRadius: fieldRadius, - style: .continuous - ) - .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) - #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) @@ -181,6 +181,22 @@ public struct LabeledTextFieldStyle: TextFieldStyle { overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize) ) + #elseif canImport(AppKit) + if fieldPlaceholderPosition != .adjacent { + RoundedRectangleCorner(radius: fieldRadius, corners: backgroundShapeCorners) + .stroke( + overlayBorderColor, + lineWidth: isFocused ? 2 : CGFloat(theme.borderSize) + ) + } + #endif + default: + #if canImport(UIKit) + RoundedRectangle( + cornerRadius: fieldRadius, + style: .continuous + ) + .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) #else if fieldPlaceholderPosition != .adjacent { RoundedRectangle( @@ -237,6 +253,7 @@ public struct LabeledTextFieldStyle: TextFieldStyle { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension TextFieldStyle where Self == LabeledTextFieldStyle { static var `default`: LabeledTextFieldStyle { LabeledTextFieldStyle(placeholder: "", text: .constant("")) @@ -247,6 +264,7 @@ public extension TextFieldStyle where Self == LabeledTextFieldStyle { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) struct LabeledTextFieldStyle_Previews: PreviewProvider { static var previews: some View { VStack(spacing: 32) { diff --git a/Sources/OversizeUI/Core/Appearance/ThemeSettings.swift b/Sources/OversizeUI/Core/Appearance/ThemeSettings.swift index 4d12550..f7031be 100644 --- a/Sources/OversizeUI/Core/Appearance/ThemeSettings.swift +++ b/Sources/OversizeUI/Core/Appearance/ThemeSettings.swift @@ -38,8 +38,14 @@ public class ThemeSettings: ObservableObject { @AppStorage(ThemeSettingsNames.borderApp) public var borderApp: Bool = false @AppStorage(ThemeSettingsNames.borderButtons) public var borderButtons: Bool = false + #if os(macOS) + @AppStorage(ThemeSettingsNames.borderTextFields) public var borderTextFields: Bool = true + @AppStorage(ThemeSettingsNames.borderSurface) public var borderSurface: Bool = true + #else @AppStorage(ThemeSettingsNames.borderSurface) public var borderSurface: Bool = false @AppStorage(ThemeSettingsNames.borderTextFields) public var borderTextFields: Bool = false + #endif + @AppStorage(ThemeSettingsNames.borderControls) public var borderControls: Bool = false @AppStorage(ThemeSettingsNames.borderSize) public var borderSize: Double = 0.5 diff --git a/Sources/OversizeUI/Core/EnvironmentKeys/FieldPositionEnvironment.swift b/Sources/OversizeUI/Core/EnvironmentKeys/FieldPositionEnvironment.swift index 5ea8649..9011d73 100644 --- a/Sources/OversizeUI/Core/EnvironmentKeys/FieldPositionEnvironment.swift +++ b/Sources/OversizeUI/Core/EnvironmentKeys/FieldPositionEnvironment.swift @@ -5,23 +5,19 @@ import SwiftUI -public enum FieldPosition { - case `default`, top, bottom, center -} - private struct FieldPositionKey: EnvironmentKey { - public static var defaultValue: FieldPosition = .default + public static var defaultValue: VerticalAlignment? = nil } public extension EnvironmentValues { - var fieldPosition: FieldPosition { + var fieldPosition: VerticalAlignment? { get { self[FieldPositionKey.self] } set { self[FieldPositionKey.self] = newValue } } } public extension View { - func fieldPosition(_ position: FieldPosition) -> some View { + func fieldPosition(_ position: VerticalAlignment?) -> some View { environment(\.fieldPosition, position) } } diff --git a/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift b/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift index 853b23b..2122915 100644 --- a/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift +++ b/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift @@ -9,15 +9,15 @@ import UIKit #endif public enum Platform { - case iPhone, iPad, mac, tv, watch, vision, carPlay, other + case iPhone, iPadOS, macOS, tvOS, watchOS, visionOS, carPlay, other } private struct PlatformKey: EnvironmentKey { static let defaultValue: Platform = { #if os(macOS) || targetEnvironment(macCatalyst) - return .mac + return .macOS #elseif os(watchOS) - return .watch + return .watchOS #elseif os(visionOS) return .vision #elseif canImport(UIKit) @@ -25,15 +25,15 @@ private struct PlatformKey: EnvironmentKey { case .phone: return .iPhone case .pad: - return .iPad + return .iPadOS case .tv: - return .tv + return .tvOS case .carPlay: return .carPlay case .mac: - return .mac + return .macOS case .vision: - return .vision + return .visionOS case .unspecified: return .other @unknown default: diff --git a/Sources/OversizeUI/Core/Typography.swift b/Sources/OversizeUI/Core/Typography.swift index 12a9631..b779820 100644 --- a/Sources/OversizeUI/Core/Typography.swift +++ b/Sources/OversizeUI/Core/Typography.swift @@ -38,35 +38,71 @@ public struct Typography: ViewModifier { public func body(content: Content) -> some View { content - .font(.system(fontStyle, design: fontDesign).weight(fontWeight).leading(.tight)) - .frame(minHeight: lineHeight) + .font( + .system(fontStyle, design: fontDesign) + .weight(fontWeight) + .leading(.tight) + ) .lineSpacing(lineHeight * 0.2) + .frame(minHeight: lineHeight) } private var lineHeight: CGFloat { switch fontStyle { case .largeTitle: + #if os(macOS) + return 40 + #else return 44 + #endif case .title: + #if os(macOS) + return 32 + #else return 36 + #endif case .title2: + #if os(macOS) + return 24 + #else return 28 + #endif case .title3: + #if os(macOS) + return 20 + #else return 24 + #endif case .headline: + #if os(macOS) + return 20 + #else return 24 + #endif case .subheadline: + #if os(macOS) + return 16 + #else return 20 + #endif case .body: + #if os(macOS) + return 20 + #else return 24 + #endif case .callout: + #if os(macOS) + return 16 + #else return 20 + #endif case .footnote: return 16 case .caption: return 16 case .caption2: - return 12 + return 16 @unknown default: return 16 } diff --git a/Sources/OversizeUI/Deprecated/PageView.swift b/Sources/OversizeUI/Deprecated/PageView.swift index 2004381..d3ccc6a 100644 --- a/Sources/OversizeUI/Deprecated/PageView.swift +++ b/Sources/OversizeUI/Deprecated/PageView.swift @@ -5,6 +5,7 @@ import SwiftUI +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct PageView: View where Content: View, LeadingBar: View, TrailingBar: View, TopToolbar: View, TitleLabel: View { @Environment(\.screenSize) var screenSize @@ -271,6 +272,7 @@ public struct PageView } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView { enum PageViewBottomType { case shadow, gradient, none @@ -281,6 +283,7 @@ public extension PageView { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where LeadingBar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -296,6 +299,7 @@ public extension PageView where LeadingBar == EmptyView, TitleLabel == EmptyView } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -311,6 +315,7 @@ public extension PageView where TrailingBar == EmptyView, TitleLabel == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -327,6 +332,7 @@ public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyView, TopToolbar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -344,6 +350,7 @@ public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where LeadingBar == EmptyView, TopToolbar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -360,6 +367,7 @@ public extension PageView where LeadingBar == EmptyView, TopToolbar == EmptyView } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, TopToolbar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -376,6 +384,7 @@ public extension PageView where TrailingBar == EmptyView, TopToolbar == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TopToolbar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -391,6 +400,7 @@ public extension PageView where TopToolbar == EmptyView, TitleLabel == EmptyView } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyView, TopToolbar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -407,6 +417,7 @@ public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, TopToolbar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -422,6 +433,7 @@ public extension PageView where TrailingBar == EmptyView, TopToolbar == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where LeadingBar == EmptyView, TopToolbar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -437,6 +449,7 @@ public extension PageView where LeadingBar == EmptyView, TopToolbar == EmptyView } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -451,6 +464,7 @@ public extension PageView where TrailingBar == EmptyView { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where LeadingBar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -465,6 +479,7 @@ public extension PageView where LeadingBar == EmptyView { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TopToolbar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -479,6 +494,7 @@ public extension PageView where TopToolbar == EmptyView { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, diff --git a/Sources/OversizeUI/Extensions/View/View+CornerRadius.swift b/Sources/OversizeUI/Extensions/View/View+CornerRadius.swift index c3a79c7..366b083 100644 --- a/Sources/OversizeUI/Extensions/View/View+CornerRadius.swift +++ b/Sources/OversizeUI/Extensions/View/View+CornerRadius.swift @@ -3,14 +3,22 @@ // View+CornerRadius.swift, created on 11.09.2021 // -#if os(iOS) import SwiftUI +#if canImport(UIKit) +public extension View { + func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { + clipShape(RoundedRectangleCorner(radius: radius, corners: corners)) + } +} +#endif + +#if canImport(AppKit) public extension View { - @available(macOS, unavailable) @available(watchOS, unavailable) @available(tvOS, unavailable) - func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { + @available(macOS 14, *) + func cornerRadius(_ radius: CGFloat, corners: RectCorner) -> some View { clipShape(RoundedRectangleCorner(radius: radius, corners: corners)) } } diff --git a/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift b/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift index 4de13c5..c41437c 100644 --- a/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift +++ b/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift @@ -1,16 +1,22 @@ -// -// Copyright © 2021 Alexander Romanov -// RoundedRectangleCorner.swift, created on 11.09.2021 -// +import SwiftUI #if canImport(UIKit) -import SwiftUI +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct RoundedRectangleCorner: Shape { private var radius: CGFloat = .infinity + #if canImport(UIKit) private var corners: UIRectCorner = .allCorners + #else + private var corners: RectCorner = .allCorners + #endif + #if canImport(UIKit) public init(radius: CGFloat, corners: UIRectCorner) { self.radius = radius self.corners = corners @@ -20,11 +26,79 @@ public struct RoundedRectangleCorner: Shape { self.radius = radius.rawValue self.corners = corners } + #else + public init(radius: CGFloat, corners: RectCorner) { + self.radius = radius + self.corners = corners + } + + public init(radius: Radius, corners: RectCorner) { + self.radius = radius.rawValue + self.corners = corners + } + #endif public func path(in rect: CGRect) -> Path { + #if canImport(UIKit) let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) return Path(path.cgPath) + #else + let path = NSBezierPath() + + let topLeftRadius = corners.contains(.topLeft) ? radius : 0 + let topRightRadius = corners.contains(.topRight) ? radius : 0 + let bottomLeftRadius = corners.contains(.bottomLeft) ? radius : 0 + let bottomRightRadius = corners.contains(.bottomRight) ? radius : 0 + + path.move(to: CGPoint(x: rect.minX + topLeftRadius, y: rect.minY)) + + path.line(to: CGPoint(x: rect.maxX - topRightRadius, y: rect.minY)) + if topRightRadius > 0 { + path.curve(to: CGPoint(x: rect.maxX, y: rect.minY + topRightRadius), + controlPoint1: CGPoint(x: rect.maxX - topRightRadius / 2, y: rect.minY), + controlPoint2: CGPoint(x: rect.maxX, y: rect.minY + topRightRadius / 2)) + } + + path.line(to: CGPoint(x: rect.maxX, y: rect.maxY - bottomRightRadius)) + if bottomRightRadius > 0 { + path.curve(to: CGPoint(x: rect.maxX - bottomRightRadius, y: rect.maxY), + controlPoint1: CGPoint(x: rect.maxX, y: rect.maxY - bottomRightRadius / 2), + controlPoint2: CGPoint(x: rect.maxX - bottomRightRadius / 2, y: rect.maxY)) + } + + path.line(to: CGPoint(x: rect.minX + bottomLeftRadius, y: rect.maxY)) + if bottomLeftRadius > 0 { + path.curve(to: CGPoint(x: rect.minX, y: rect.maxY - bottomLeftRadius), + controlPoint1: CGPoint(x: rect.minX + bottomLeftRadius / 2, y: rect.maxY), + controlPoint2: CGPoint(x: rect.minX, y: rect.maxY - bottomLeftRadius / 2)) + } + + path.line(to: CGPoint(x: rect.minX, y: rect.minY + topLeftRadius)) + if topLeftRadius > 0 { + path.curve(to: CGPoint(x: rect.minX + topLeftRadius, y: rect.minY), + controlPoint1: CGPoint(x: rect.minX, y: rect.minY + topLeftRadius / 2), + controlPoint2: CGPoint(x: rect.minX + topLeftRadius / 2, y: rect.minY)) + } + + path.close() + return Path(path.cgPath) + #endif + } +} + +#if !canImport(UIKit) +public struct RectCorner: OptionSet, Sendable { + public let rawValue: Int + + public static let topLeft = RectCorner(rawValue: 1 << 0) + public static let topRight = RectCorner(rawValue: 1 << 1) + public static let bottomLeft = RectCorner(rawValue: 1 << 2) + public static let bottomRight = RectCorner(rawValue: 1 << 3) + public static let allCorners: RectCorner = [.topLeft, .topRight, .bottomLeft, .bottomRight] + + public init(rawValue: Int) { + self.rawValue = rawValue } } #endif