Skip to content

Commit

Permalink
✨ Introduce LuminareToggleCompose & LuminareButtonCompose
Browse files Browse the repository at this point in the history
  • Loading branch information
KrLite committed Dec 17, 2024
1 parent d16df4f commit 3f58c59
Show file tree
Hide file tree
Showing 18 changed files with 282 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import AppKit
import SwiftUI

/// The direction of an ``InfiniteScrollView``.
public enum InfiniteScrollViewDirection: String, Equatable, Hashable, Identifiable, CaseIterable, Codable {
public enum InfiniteScrollViewDirection: String, Equatable, Hashable, Identifiable, CaseIterable, Codable, Sendable {
/// The view can, and can only be scrolled horizontally.
case horizontal
/// The view can, and can only be scrolled vertically.
case vertical

public var id: String { rawValue }
public var id: Self { self }

/// Initializes an ``InfiniteScrollViewDirection`` from an `Axis`.
public init(axis: Axis) {
Expand Down
112 changes: 112 additions & 0 deletions Sources/Luminare/Components/Compose/LuminareButtonCompose.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// LuminareButtonCompose.swift
// Luminare
//
// Created by KrLite on 2024/12/17.
//

import SwiftUI

// MARK: - Button Compose

struct LuminareButtonCompose<Label, Content>: View where Label: View, Content: View {
// MARK: Fields

private let role: ButtonRole?
@ViewBuilder private var label: () -> Label
@ViewBuilder private var content: () -> Content
private let action: () -> ()

// MARK: Initializers

public init(
role: ButtonRole? = nil,
@ViewBuilder label: @escaping () -> Label,
@ViewBuilder content: @escaping () -> Content,
action: @escaping () -> ()
) {
self.role = role
self.label = label
self.content = content
self.action = action
}

public init(
_ titleKey: LocalizedStringKey,
role: ButtonRole? = nil,
@ViewBuilder content: @escaping () -> Content,
action: @escaping () -> ()
) where Label == Text {
self.init(
role: role
) {
Text(titleKey)
} content: {
content()
} action: {
action()
}
}

public init(
_ contentKey: LocalizedStringKey,
role: ButtonRole? = nil,
@ViewBuilder label: @escaping () -> Label,
action: @escaping () -> ()
) where Content == Text {
self.init(
role: role,
label: label
) {
Text(contentKey)
} action: {
action()
}
}

public init(
_ titleKey: LocalizedStringKey,
_ contentKey: LocalizedStringKey,
role: ButtonRole? = nil,
action: @escaping () -> ()
) where Label == Text, Content == Text {
self.init(
role: role
) {
Text(titleKey)
} content: {
Text(contentKey)
} action: {
action()
}
}

// MARK: Body

var body: some View {
LuminareCompose(contentMaxWidth: nil) {
Button(role: role) {
action()
} label: {
label()
}
.buttonStyle(.luminareCompact)
} label: {
label()
}
}
}

// MARK: - Preview

@available(macOS 15.0, *)
#Preview(
"LuminareButtonCompose",
traits: .sizeThatFitsLayout
) {
LuminareSection {
LuminareButtonCompose("Button", "Click Me!") {
print(1)
}
}
}
76 changes: 63 additions & 13 deletions Sources/Luminare/Components/Compose/LuminareCompose.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,72 @@

import SwiftUI

/// The control size for views based on ``LuminareCompose``.
public enum LuminareComposeControlSize: String, Equatable, Hashable, Identifiable, CaseIterable, Codable, Sendable {
case automatic
@available(macOS 14.0, *)
case extraLarge
case large
case regular
case small
case mini

public static var allCases: [LuminareComposeControlSize] {
if #available(macOS 14.0, *) {
[.automatic, .extraLarge, .large, .regular, .small, .mini]
} else {
[.automatic, .large, .regular, .small, .mini]
}
}

public var id: Self { self }

public var proposal: ControlSize? {
if #available(macOS 14.0, *) {
switch self {
case .extraLarge: .extraLarge
case .large: .large
case .regular: .regular
case .small: .small
case .mini: .mini
default: nil
}
} else {
switch self {
case .large: .large
case .regular: .regular
case .small: .small
case .mini: .mini
default: nil
}
}
}
}

/// The layout for views based on ``LuminareCompose``.
///
/// Typically, this is eligible for views that have additional controls beside static contents.
public enum LuminareComposeControlSize: String, Equatable, Hashable, Identifiable, CaseIterable, Codable {
/// The regular size where the content is separated into two lines.
public enum LuminareComposeLayout: String, Equatable, Hashable, Identifiable, CaseIterable, Codable, Sendable {
/// The regular layout where the content is separated into two lines.
case regular
/// The compact size where the content is in one single line.
/// The compact layout where the content is in one single line.
case compact

public var id: String { rawValue }
public var id: Self { self }

var height: CGFloat {
public var height: CGFloat {
switch self {
case .regular: 70
case .compact: 34
}
}
}

public enum LuminareComposeStyle: String, Equatable, Hashable, Identifiable, CaseIterable, Codable {
public enum LuminareComposeStyle: String, Equatable, Hashable, Identifiable, CaseIterable, Codable, Sendable {
case automatic
case regular
case inline

public var id: String { rawValue }
public var id: Self { self }
}

// MARK: - Compose
Expand All @@ -44,6 +85,7 @@ public struct LuminareCompose<Label, Content>: View
@Environment(\.isEnabled) private var isEnabled
@Environment(\.luminareMinHeight) private var minHeight
@Environment(\.luminareHorizontalPadding) private var horizontalPadding
@Environment(\.luminareComposeControlSize) private var controlSize
@Environment(\.luminareComposeStyle) private var style

// MARK: Fields
Expand Down Expand Up @@ -91,10 +133,9 @@ public struct LuminareCompose<Label, Content>: View
) where Label == Text {
self.init(
contentMaxWidth: contentMaxWidth,
spacing: spacing
spacing: spacing,
content: content
) {
content()
} label: {
Text(key)
}
}
Expand All @@ -116,11 +157,11 @@ public struct LuminareCompose<Label, Content>: View
HStack(spacing: 0) {
Spacer()

content()
wrappedContent()
}
.frame(maxWidth: contentMaxWidth)
} else {
content()
wrappedContent()
}
}
.frame(maxWidth: .infinity, minHeight: minHeight)
Expand Down Expand Up @@ -153,6 +194,15 @@ public struct LuminareCompose<Label, Content>: View
default: .init(top: 0, leading: horizontalPadding, bottom: 0, trailing: horizontalPadding)
}
}

@ViewBuilder private func wrappedContent() -> some View {
if let controlSize = controlSize.proposal {
content()
.controlSize(controlSize)
} else {
content()
}
}
}

// MARK: - Preview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public struct LuminareSliderPickerCompose<Label, Content, V>: View where Label:

@Environment(\.luminareAnimation) private var animation
@Environment(\.luminareHorizontalPadding) private var horizontalPadding
@Environment(\.luminareComposeControlSize) private var controlSize
@Environment(\.luminareComposeLayout) private var layout

// MARK: Fields

Expand Down Expand Up @@ -114,7 +114,7 @@ public struct LuminareSliderPickerCompose<Label, Content, V>: View where Label:

public var body: some View {
VStack {
switch controlSize {
switch layout {
case .regular:
LuminareCompose {
text()
Expand All @@ -139,7 +139,7 @@ public struct LuminareSliderPickerCompose<Label, Content, V>: View where Label:
.luminareComposeStyle(.inline)
}
}
.frame(height: controlSize.height)
.frame(height: layout.height)
.animation(animation, value: selection)
}

Expand Down Expand Up @@ -217,6 +217,6 @@ public struct LuminareSliderPickerCompose<Label, Content, V>: View where Label:
.foregroundStyle(.secondary)
}
}
.luminareComposeControlSize(.compact)
.luminareComposeLayout(.compact)
}
}
67 changes: 67 additions & 0 deletions Sources/Luminare/Components/Compose/LuminareToggleCompose.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// LuminareToggleCompose.swift
// Luminare
//
// Created by KrLite on 2024/12/17.
//

import SwiftUI

// MARK: - Toggle Compose

struct LuminareToggleCompose<Label>: View where Label: View {
// MARK: Environments

@Environment(\.luminareComposeControlSize) private var controlSize

// MARK: Fields

@Binding private var value: Bool
@ViewBuilder private var label: () -> Label

// MARK: Initializers

public init(
isOn value: Binding<Bool>,
@ViewBuilder label: @escaping () -> Label
) {
self._value = value
self.label = label
}

public init(
_ key: LocalizedStringKey,
isOn value: Binding<Bool>
) where Label == Text {
self.init(isOn: value) {
Text(key)
}
}

// MARK: Body

var body: some View {
LuminareCompose(contentMaxWidth: nil) {
Toggle("", isOn: $value)
.labelsHidden()
.toggleStyle(.switch)
.controlSize(controlSize.proposal ?? .small)
} label: {
label()
}
}
}

// MARK: - Preview

@available(macOS 15.0, *)
#Preview(
"LuminareToggleCompose",
traits: .sizeThatFitsLayout
) {
@Previewable @State var value = false

LuminareSection {
LuminareToggleCompose("Toggle", isOn: $value)
}
}
Loading

0 comments on commit 3f58c59

Please sign in to comment.