diff --git a/Sources/Luminare/Components/Composes/LuminarePickerMenu.swift b/Sources/Luminare/Components/Composes/LuminarePickerMenu.swift index bc3be138..31bb688c 100644 --- a/Sources/Luminare/Components/Composes/LuminarePickerMenu.swift +++ b/Sources/Luminare/Components/Composes/LuminarePickerMenu.swift @@ -10,18 +10,18 @@ import SwiftUI // MARK: - Picker Menu (Compose) public struct LuminarePickerMenu: View -where Label: View, Option: View, Item: Hashable { + where Label: View, Option: View, Item: Hashable { @Environment(\.luminareSectionHorizontalPadding) private var horizontalPadding - + // MARK: Fields - + @ViewBuilder private var label: () -> Label @Binding private var selection: Item let items: [Item] @ViewBuilder private var itemToView: (Item) -> Option - + // MARK: Initializers - + public init( selection: Binding, items: [Item], @@ -64,31 +64,40 @@ where Label: View, Option: View, Item: Hashable { Text(titleKey) } } - + // MARK: Body - + public var body: some View { LuminareCompose { - HStack(spacing: 8) { - itemToView(selection) - - Image(systemName: "chevron.up.chevron.down") - .font(.caption2) - .padding(4) - .luminareSurface() - .luminareCornerRadius(8) - } - .overlay { - Picker("", selection: $selection) { - ForEach(items, id: \.self) { item in + Menu { + ForEach(items, id: \.self) { item in + Toggle(isOn: Binding( + get: { selection == item }, + set: { isSelected in + if isSelected { + selection = item + } + } + )) { itemToView(item) - .tag(item) } } - .opacity(0.001) - .fixedSize() + } label: { + HStack(spacing: 8) { + itemToView(selection) + + Image(systemName: "chevron.up.chevron.down") + .font(.caption2) + .padding(4) + .luminareSurface() + .luminareCornerRadius(8) + } .contentShape(.rect) } + .menuStyle(.button) + .buttonStyle(.plain) + .menuIndicator(.hidden) + .fixedSize() } label: { label() } @@ -116,4 +125,3 @@ where Label: View, Option: View, Item: Hashable { } .frame(width: 300) } - diff --git a/Sources/Luminare/Components/LuminareCompactPicker.swift b/Sources/Luminare/Components/LuminareCompactPicker.swift index aa4caff2..40480642 100644 --- a/Sources/Luminare/Components/LuminareCompactPicker.swift +++ b/Sources/Luminare/Components/LuminareCompactPicker.swift @@ -30,6 +30,7 @@ public struct LuminareCompactPicker: View where Content: View, V: Ha // MARK: Environments @Environment(\.luminareCompactPickerStyle) private var style + @Environment(\.luminareMinHeight) private var minHeight // MARK: Fields @@ -64,6 +65,9 @@ public struct LuminareCompactPicker: View where Content: View, V: Ha .pickerStyle(.menu) .buttonStyle(.borderless) .padding(.trailing, -2) + .frame(minHeight: minHeight) + .luminareSurface(isHovering: isHovering, style: .flat) + .luminareFilledStates(.all) case .segmented: UnaryVariadicView(content()) { children in SegmentedVariadic( @@ -73,9 +77,9 @@ public struct LuminareCompactPicker: View where Content: View, V: Ha ) } .padding(.horizontal, 4) + .luminareSurface(style: .flat) } } - .luminareSurface(style: .flat) .onHover { isHovering = $0 } } @@ -93,32 +97,79 @@ public struct LuminareCompactPicker: View where Content: View, V: Ha @Namespace private var namespace var body: some View { - HStack(spacing: 4) { - ForEach(Array(children.enumerated()), id: \.offset) { index, child in - if let value = child.id(as: V.self) { - SegmentedKnob( - child: child, - namespace: namespace, - isParentHovering: isHovering, - selection: $selection, - value: value, - index: index, - maxIndex: children.count - 1 - ) - .foregroundStyle(isHovering && selection == value ? .primary : .secondary) - .zIndex(1) - - if hasDividers, - child.id != children.last?.id { - Divider() - .frame(height: minHeight / 2) - .zIndex(0) + ProposedHeightLayout(minimumHeight: minHeight) { + HStack(spacing: 4) { + ForEach(Array(children.enumerated()), id: \.offset) { index, child in + if let value = child.id(as: V.self) { + let nextValue: V? = if index < children.count - 1 { + children[index + 1].id(as: V.self) + } else { + nil + } + SegmentedKnob( + child: child, + namespace: namespace, + isParentHovering: isHovering, + selection: $selection, + value: value, + index: index, + maxIndex: children.count - 1 + ) + .foregroundStyle(selection == value ? .primary : .secondary) + .zIndex(1) + + if hasDividers, + child.id != children.last?.id { + Divider() + .frame(height: minHeight / 2) + .opacity(selection == value || selection == nextValue ? 0 : 1) + .zIndex(0) + } } } } } } + struct ProposedHeightLayout: Layout { + let minimumHeight: CGFloat + + func sizeThatFits( + proposal: ProposedViewSize, + subviews: Subviews, + cache _: inout () + ) -> CGSize { + guard let subview = subviews.first else { + return .zero + } + + let proposedWidth = proposal.width.flatMap { $0.isFinite ? $0 : nil } + let proposedHeight = proposal.height.flatMap { $0.isFinite ? $0 : nil } + let size = subview.sizeThatFits(.init( + width: proposedWidth, + height: proposedHeight + )) + + return .init( + width: proposedWidth ?? size.width, + height: max(minimumHeight, proposedHeight ?? size.height) + ) + } + + func placeSubviews( + in bounds: CGRect, + proposal _: ProposedViewSize, + subviews: Subviews, + cache _: inout () + ) { + subviews.first?.place( + at: bounds.origin, + anchor: .topLeading, + proposal: .init(bounds.size) + ) + } + } + struct SegmentedKnob: View { @Environment(\.luminareAnimation) private var animation @Environment(\.luminareMinHeight) private var minHeight @@ -143,10 +194,11 @@ public struct LuminareCompactPicker: View where Content: View, V: Ha } } label: { child - .frame(maxWidth: .infinity, minHeight: minHeight - 8) - .padding(.horizontal, 8) + .frame(maxWidth: .infinity, minHeight: minHeight - 8, maxHeight: .infinity) + .padding(.horizontal, 12) } .buttonStyle(.borderless) + .frame(minHeight: minHeight - 8, maxHeight: .infinity) .onHover { isHovering = $0 } .background { Group { @@ -162,7 +214,7 @@ public struct LuminareCompactPicker: View where Content: View, V: Ha } } .padding(.vertical, 4) - .frame(minHeight: minHeight) + .frame(minHeight: minHeight, maxHeight: .infinity) } private var constrainedCornerRadii: RectangleCornerRadii { @@ -181,24 +233,8 @@ public struct LuminareCompactPicker: View where Content: View, V: Ha } private func knob() -> some View { - Group { - if isParentHovering { - Rectangle() - .foregroundStyle(.background.opacity(0.8)) - } else { - // The `.blendMode()` prevents `.quinary` style to be clipped - Rectangle() - .foregroundStyle(.quinary.blendMode(.luminosity)) - } - } - .overlay { - if isHovering { - Rectangle() - .foregroundStyle(.background.opacity(0.2)) - .blendMode(.luminosity) - } - } - .clipShape(.rect(cornerRadii: constrainedCornerRadii)) + UnevenRoundedRectangle(cornerRadii: constrainedCornerRadii) + .foregroundStyle(isHovering ? .quaternary : .quinary) } } } @@ -213,7 +249,7 @@ private struct PickerPreview: View where V: Hashable & Equatable { var body: some View { LuminareCompactPicker(selection: $selection) { ForEach(elements, id: \.self) { element in - Text("\(element)") + Text(verbatim: "\(element)") } } } @@ -225,12 +261,6 @@ private struct PickerPreview: View where V: Hashable & Equatable { traits: .sizeThatFitsLayout ) { LuminareSection { - LuminareCompose("Button") { - Button {} label: { - Text("42") - } - } - LuminareCompose("Pick from a menu") { PickerPreview(elements: Array(0 ..< 200), selection: 42) } diff --git a/Sources/Luminare/Luminare.docc/Components/LuminareCompactPicker.md b/Sources/Luminare/Luminare.docc/Components/LuminareCompactPicker.md index c8304620..b138b3ee 100644 --- a/Sources/Luminare/Luminare.docc/Components/LuminareCompactPicker.md +++ b/Sources/Luminare/Luminare.docc/Components/LuminareCompactPicker.md @@ -15,11 +15,12 @@ @Row { @Column(size: 3) { ```swift - LuminareCompactPicker(selection: $selection, style: .menu) { + LuminareCompactPicker(selection: $selection) { ForEach(data, id: \.keyPath) { element in // Content } } + .luminareCompactPickerStyle(.menu) ``` } @@ -31,7 +32,7 @@ @Row { @Column(size: 2) { ```swift - LuminareCompactPicker(selection: $selection, style: .segmented) { + LuminareCompactPicker(selection: $selection) { ForEach(data, id: \.self) { element in Group { // Content @@ -39,6 +40,7 @@ .id(element) } } + .luminareCompactPickerStyle(.segmented) ``` }