diff --git a/Sources/Luminare/Components/Composes/LuminareButtonRow.swift b/Sources/Luminare/Components/Composes/LuminareButtonRow.swift new file mode 100644 index 00000000..4b113dee --- /dev/null +++ b/Sources/Luminare/Components/Composes/LuminareButtonRow.swift @@ -0,0 +1,111 @@ +// +// LuminareButtonRow.swift +// Luminare +// +// Created by Adon Omeri on 23/3/2026. +// + +import SwiftUI + +@resultBuilder +public struct LuminareButtonBuilder { + public static func buildExpression(_ expression: some View) -> [AnyView] { + [AnyView(expression)] + } + + public static func buildBlock(_ components: [AnyView]...) -> [AnyView] { + components.flatMap(\.self) + } + + public static func buildOptional(_ component: [AnyView]?) -> [AnyView] { + component ?? [] + } + + public static func buildEither(first component: [AnyView]) -> [AnyView] { + component + } + + public static func buildEither(second component: [AnyView]) -> [AnyView] { + component + } + + public static func buildArray(_ components: [[AnyView]]) -> [AnyView] { + components.flatMap(\.self) + } + + public static func buildLimitedAvailability(_ component: [AnyView]) -> [AnyView] { + component + } +} + +public struct LuminareButtonRow: View { + @Environment(\.luminareButtonComposeSpacing) private var spacing + + @Environment(\.luminareTopLeadingRounded) private var topLeadingRounded + @Environment(\.luminareTopTrailingRounded) private var topTrailingRounded + @Environment(\.luminareBottomLeadingRounded) private var bottomLeadingRounded + @Environment(\.luminareBottomTrailingRounded) private var bottomTrailingRounded + + private let buttons: [AnyView] + + public init( + @LuminareButtonBuilder _ buttons: () -> [AnyView] + ) { + self.buttons = buttons() + } + + public var body: some View { + HStack(spacing: spacing) { + ForEach(buttons.indices, id: \.self) { index in + let button = buttons[index] + let isFirst = index == 0 + let isLast = index == buttons.count - 1 + + button + .luminareRoundingBehavior( + topLeading: isFirst ? topLeadingRounded : false, + topTrailing: isLast ? topTrailingRounded : false, + bottomLeading: isFirst ? bottomLeadingRounded : false, + bottomTrailing: isLast ? bottomTrailingRounded : false + ) + } + } + .buttonStyle(.luminare) + } +} + +// MARK: - Preview + +@available(macOS 15.0, *) +#Preview( + "LuminareButtonRow", + traits: .sizeThatFitsLayout +) { + LuminarePane { + LuminareSection { + LuminareButtonRow { + Button { + print(1) + } label: { + Text("Button 1") + } + + Button { + print(2) + } label: { + Text("Button 2") + } + + Button { + print(3) + } label: { + Text("Button 3") + } + } + .luminareRoundingBehavior(top: true) + + Text("Other content ...") + .foregroundStyle(.secondary) + } + } +} diff --git a/Sources/Luminare/Components/Stepper/LuminareStepper.swift b/Sources/Luminare/Components/Stepper/LuminareStepper.swift index 5d55d388..59b8f9a6 100644 --- a/Sources/Luminare/Components/Stepper/LuminareStepper.swift +++ b/Sources/Luminare/Components/Stepper/LuminareStepper.swift @@ -440,8 +440,7 @@ public struct LuminareStepper: View where V: Strideable & BinaryFloatingPoint private func magnifyFactor(at index: Int) -> CGFloat { let standardDeviation = 0.5 - let value = bellCurve(shift(at: index), standardDeviation: standardDeviation) - return value + return bellCurve(shift(at: index), standardDeviation: standardDeviation) } private func blurFactor(at index: Int) -> CGFloat { diff --git a/Sources/Luminare/Luminare.docc/Components/Compose/LuminareButtonRow.md b/Sources/Luminare/Luminare.docc/Components/Compose/LuminareButtonRow.md new file mode 100644 index 00000000..21e45421 --- /dev/null +++ b/Sources/Luminare/Luminare.docc/Components/Compose/LuminareButtonRow.md @@ -0,0 +1,37 @@ +# ``Luminare/LuminareButtonRow`` + +A horizontalrow of Luminare buttons. + + + +@Row { + @Column { + ```swift + LuminareButtonRow { + Button { + print(1) + } label: { + Text("Button 1") + } + + Button { + print(2) + } label: { + Text("Button 2") + } + + Button { + print(3) + } label: { + Text("Button 3") + } + } + ``` + } +} + +@Row { + @Column { + ![LuminareButtonRow](LuminareButtonRow) + } +} diff --git a/Sources/Luminare/Luminare.docc/Resources/Previews Screenshots/Compose/LuminareButtonRow/LuminareButtonRow@1x.png b/Sources/Luminare/Luminare.docc/Resources/Previews Screenshots/Compose/LuminareButtonRow/LuminareButtonRow@1x.png new file mode 100644 index 00000000..42d588bb Binary files /dev/null and b/Sources/Luminare/Luminare.docc/Resources/Previews Screenshots/Compose/LuminareButtonRow/LuminareButtonRow@1x.png differ diff --git a/Sources/Luminare/Luminare.docc/Resources/Previews Screenshots/Compose/LuminareButtonRow/LuminareButtonRow~dark@1x.png b/Sources/Luminare/Luminare.docc/Resources/Previews Screenshots/Compose/LuminareButtonRow/LuminareButtonRow~dark@1x.png new file mode 100644 index 00000000..387c933c Binary files /dev/null and b/Sources/Luminare/Luminare.docc/Resources/Previews Screenshots/Compose/LuminareButtonRow/LuminareButtonRow~dark@1x.png differ diff --git a/Sources/Luminare/Utilities/Extensions/Color+Extensions.swift b/Sources/Luminare/Utilities/Extensions/Color+Extensions.swift index 9f7abc7a..5261a974 100644 --- a/Sources/Luminare/Utilities/Extensions/Color+Extensions.swift +++ b/Sources/Luminare/Utilities/Extensions/Color+Extensions.swift @@ -9,7 +9,7 @@ import AppKit import SwiftUI /// A shorthand for storing colors in hue-saturation-brightness format -struct HSBColor: Equatable, Hashable, Codable, Sendable { +struct HSBColor: Equatable, Hashable, Codable { var hue: Double var saturation: Double var brightness: Double diff --git a/Sources/Luminare/Utilities/Extensions/EnvironmentValues+Extensions.swift b/Sources/Luminare/Utilities/Extensions/EnvironmentValues+Extensions.swift index bcfbc486..4f86073c 100644 --- a/Sources/Luminare/Utilities/Extensions/EnvironmentValues+Extensions.swift +++ b/Sources/Luminare/Utilities/Extensions/EnvironmentValues+Extensions.swift @@ -91,6 +91,10 @@ public extension EnvironmentValues { /// If 0, then luminareSection will be of fixed size. @Entry var luminareSectionMaxWidth: CGFloat? = .infinity + // MARK: Button Compose + + @Entry var luminareButtonComposeSpacing: CGFloat = 4 + // MARK: Compose @Entry var luminareComposeControlSize: LuminareComposeControlSize = .automatic diff --git a/Sources/Luminare/Utilities/Extensions/View+Extensions.swift b/Sources/Luminare/Utilities/Extensions/View+Extensions.swift index 9b1b6060..c4bbd91b 100644 --- a/Sources/Luminare/Utilities/Extensions/View+Extensions.swift +++ b/Sources/Luminare/Utilities/Extensions/View+Extensions.swift @@ -394,6 +394,12 @@ public extension View { ) } + // MARK: Button Compose + + func luminareButtonComposeSpacing(_ spacing: CGFloat) -> some View { + environment(\.luminareButtonComposeSpacing, spacing) + } + // MARK: Tool Tip func luminareToolTipTrigger(_ trigger: LuminareToolTipTrigger) -> some View {