From b6aea4607e2c7aa6090c35462a37d7278b6c30f8 Mon Sep 17 00:00:00 2001 From: shengxu7 Date: Thu, 13 Jun 2024 14:05:22 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[jira:=202572]=20new=20c?= =?UTF-8?q?ard=20footer=20for=20Joule=20Object=20Card=20(#706)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [jira: 2572] new card footer for Joule Object Card * fix: 🐛 [jira: 2572] change the backgroud of popup in footer --- .../Card/MobileCardExample.swift | 45 +- .../EqualWidthWithMaxWidthHStackLayout.swift | 108 ---- .../BaseComponentProtocols.swift | 14 + .../CompositeComponentProtocols.swift | 2 +- .../_FioriStyles/CardFooterStyle.fiori.swift | 538 +++++++++++++++++- .../_FioriStyles/CardStyle.fiori.swift | 191 ++++++- .../OverflowActionStyle.fiori.swift | 33 ++ .../SecondaryActionStyle.fiori.swift | 3 - .../TertiaryActionStyle.fiori.swift | 32 ++ .../Card/Card.generated.swift | 20 +- .../Card/CardStyle.generated.swift | 6 + .../CardFooter/CardFooter.generated.swift | 20 +- .../CardFooterStyle.generated.swift | 6 + .../OverflowAction.generated.swift | 63 ++ .../OverflowActionStyle.generated.swift | 28 + .../TertiaryAction.generated.swift | 63 ++ .../TertiaryActionStyle.generated.swift | 28 + ...entStyleProtocol+Extension.generated.swift | 112 ++++ .../EnvironmentVariables.generated.swift | 42 ++ .../ModifiedStyle.generated.swift | 56 ++ .../ResolvedStyle.generated.swift | 32 ++ ...yleConfiguration+Extension.generated.swift | 2 +- .../View+Extension_.generated.swift | 34 ++ ...iewEmptyChecking+Extension.generated.swift | 20 +- 24 files changed, 1332 insertions(+), 166 deletions(-) delete mode 100644 Sources/FioriSwiftUICore/Utils/EqualWidthWithMaxWidthHStackLayout.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/OverflowActionStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/TertiaryActionStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowAction.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowActionStyle.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryAction.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryActionStyle.generated.swift diff --git a/Apps/Examples/Examples/FioriSwiftUICore/Card/MobileCardExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/Card/MobileCardExample.swift index 849f0ed1d..a664cdcce 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/Card/MobileCardExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/Card/MobileCardExample.swift @@ -20,6 +20,19 @@ struct MobileCardExample: View { } label: { Text("Cards") } + + NavigationLink { + List { + ForEach(0 ..< CardTests.cardFooterSamples.count, id: \.self) { i in + CardTests.cardFooterSamples[i] + } + } + .cardStyle(.card) + .listStyle(.plain) + .navigationBarTitle("Footers", displayMode: .inline) + } label: { + Text("Footers") + } NavigationLink { MasonryTestView() @@ -90,24 +103,26 @@ struct CarouselTestView: View { } var body: some View { - Carousel(numberOfColumns: Int(self.numberOfColumns), spacing: self.spacing, alignment: self.alignment == 0 ? .top : (self.alignment == 1 ? .center : .bottom), isSnapping: self.isSnapping) { - if self.contentType == 0 { - ForEach(0 ..< CardTests.cardSamples.count, id: \.self) { i in - CardTests.cardSamples[i] - } - } else { - ForEach(0 ..< 20, id: \.self) { i in - Text("Text \(i)") - .font(.title) - .padding() - .frame(height: 100) - .background(Color.gray) + ScrollView(.vertical) { + Carousel(numberOfColumns: Int(self.numberOfColumns), spacing: self.spacing, alignment: self.alignment == 0 ? .top : (self.alignment == 1 ? .center : .bottom), isSnapping: self.isSnapping) { + if self.contentType == 0 { + ForEach(0 ..< CardTests.cardSamples.count, id: \.self) { i in + CardTests.cardSamples[i] + } + } else { + ForEach(0 ..< 20, id: \.self) { i in + Text("Text \(i)") + .font(.title) + .padding() + .frame(height: 100) + .background(Color.gray) + } } } + .cardStyle(.card) + .padding(self.padding) + .border(Color.gray) } - .cardStyle(.card) - .padding(self.padding) - .border(Color.gray) .sheet(isPresented: self.$isPresented, content: { VStack { HStack { diff --git a/Sources/FioriSwiftUICore/Utils/EqualWidthWithMaxWidthHStackLayout.swift b/Sources/FioriSwiftUICore/Utils/EqualWidthWithMaxWidthHStackLayout.swift deleted file mode 100644 index bc3292d28..000000000 --- a/Sources/FioriSwiftUICore/Utils/EqualWidthWithMaxWidthHStackLayout.swift +++ /dev/null @@ -1,108 +0,0 @@ -import SwiftUI - -struct EqualWidthWithMaxWidthHStackLayout: Layout { - /// The distance between adjacent subviews. - var spacing: CGFloat? = 4 - - /// Same height for all subviews - var isSameHeight: Bool = true - - /// The vertical alignment of subviews. - var alignment: VerticalAlignment = .center - - /// Maximum width for each element in the container - var maxWidth: CGFloat? = nil - - var horizontalSizeClass: UserInterfaceSizeClass? = .compact - - func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { - guard !subviews.isEmpty else { return .zero } - - let subViewSizes = subviews.map { - $0.sizeThatFits(.unspecified) - } - let width: CGFloat = subViewSizes.reduce(0) { curr, subviewSize in - curr + subviewSize.width - } - let idealWidth = width + CGFloat(subViewSizes.count - 1) * (self.spacing ?? 0) - let maxHeight: CGFloat = subViewSizes.reduce(0) { curr, subviewSize in - max(curr, subviewSize.height) - } - - return CGSize(width: proposal.width ?? idealWidth, height: maxHeight) - } - - func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { - guard !subviews.isEmpty else { return } - - let subViewSizes = subviews.map { - $0.sizeThatFits(.unspecified) - } - let maxHeight: CGFloat = subViewSizes.reduce(0) { curr, subviewSize in - max(curr, subviewSize.height) - } - let theSpacing: CGFloat = self.spacing ?? 0 - let totalSpacing = theSpacing * CGFloat(subviews.count - 1) - let width: CGFloat = subViewSizes.reduce(0) { curr, subviewSize in - curr + subviewSize.width - } - let idealWidth = width + totalSpacing - let containerWidth = proposal.width ?? idealWidth - - let theMaxWidth = containerWidth <= 430 ? CGFloat.greatestFiniteMagnitude : self.maxWidth ?? CGFloat.greatestFiniteMagnitude - let subviewWidth: CGFloat = min(theMaxWidth, (containerWidth - CGFloat(subviews.count - 1) * theSpacing) / CGFloat(subviews.count)) - - // align trailing - var x: CGFloat = bounds.minX + subviewWidth / 2 + bounds.size.width - subviewWidth * CGFloat(subviews.count) - totalSpacing - var y = bounds.midY - var anchor = UnitPoint.center - switch self.alignment { - case .top: - y = bounds.minY - anchor = .top - case .bottom: - y = bounds.maxY - anchor = .bottom - default: - y = bounds.midY - anchor = .center - } - for i in subviews.indices { - subviews[i].place(at: CGPointMake(x, y), - anchor: anchor, - proposal: ProposedViewSize(width: subviewWidth, height: self.isSameHeight ? maxHeight : subViewSizes[i].height)) - x += subviewWidth + theSpacing - } - } -} - -#Preview("layout top") { - EqualWidthWithMaxWidthHStackLayout(spacing: 8, alignment: .top) { - FioriButton(title: "Save") - .border(Color.green) - Text("Decline") - .border(Color.green) - }.border(Color.gray) -} - -#Preview("layout center") { - EqualWidthWithMaxWidthHStackLayout(spacing: 8, alignment: .center) { - FioriButton(title: "Save") - .frame(maxWidth: .infinity) - .border(Color.green) - Text("Decline") - .border(Color.green) - }.border(Color.gray) -} - -#Preview("layout bottom") { - EqualWidthWithMaxWidthHStackLayout(spacing: 8, alignment: .bottom, maxWidth: 120) { - FioriButton(title: "Save") - .frame(maxWidth: .infinity) - .border(Color.green) - - Text("Decline") - .frame(maxWidth: .infinity) - .border(Color.green) - }.border(Color.gray) -} diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift index 12d87f15f..979cbc986 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift @@ -138,6 +138,20 @@ protocol _SecondaryActionComponent { var secondaryAction: FioriButton? { get } } +// sourcery: BaseComponent +protocol _TertiaryActionComponent { + // sourcery: @ViewBuilder + var tertiaryAction: FioriButton? { get } +} + +// sourcery: BaseComponent +protocol _OverflowActionComponent { + // sourcery: @ViewBuilder + // sourcery: defaultValue = "FioriButton { _ in Image(systemName: "ellipsis") }" + // sourcery: resultBuilder.defaultValue = "{ FioriButton { _ in Image(systemName: "ellipsis") } }" + var overflowAction: FioriButton? { get } +} + // sourcery: BaseComponent protocol _Row1Component { // var numberOfLines: Int { get set } diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift index db8c5b42a..edafa11a8 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift @@ -24,7 +24,7 @@ protocol _CardMainHeaderComponent: _TitleComponent, _SubtitleComponent, _IconsCo protocol _CardExtHeaderComponent: _Row1Component, _Row2Component, _Row3Component, _KpiComponent, _KpiCaptionComponent {} // sourcery: CompositeComponent -protocol _CardFooterComponent: _ActionComponent, _SecondaryActionComponent {} +protocol _CardFooterComponent: _ActionComponent, _SecondaryActionComponent, _TertiaryActionComponent, _OverflowActionComponent {} // sourcery: CompositeComponent protocol _CardHeaderComponent: _CardMediaComponent, _CardMainHeaderComponent, _CardExtHeaderComponent {} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/CardFooterStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/CardFooterStyle.fiori.swift index c8da20aad..59dfcc5f7 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/CardFooterStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/CardFooterStyle.fiori.swift @@ -2,6 +2,279 @@ import FioriThemeManager import Foundation import SwiftUI +private struct CardFooterLayout: Layout { + enum ButtonWidthMode { + /// same and fill its width to use available container width + case sameAndFill + + /// intrinsic size's width + case intrinsic + } + + struct LayoutMode { + let mode: ButtonWidthMode + let num: Int + } + + struct CacheData { + var fitSize: CGSize + var frames: [CGRect] + + mutating func clear() { + self.fitSize = .zero + self.frames = [] + } + } + + @Binding var numButtonsDisplayInOverflow: Int + + /// The distance between adjacent subviews. + var spacing: CGFloat? = 8 + + /// Maximum width for each element in the container + var maxButtonWidth: CGFloat + + var horizontalSizeClass: UserInterfaceSizeClass? = .compact + + init(numButtonsDisplayInOverflow: Binding, spacing: CGFloat? = nil, maxButtonWidth: CGFloat? = nil, horizontalSizeClass: UserInterfaceSizeClass? = nil) { + self._numButtonsDisplayInOverflow = numButtonsDisplayInOverflow + self.spacing = spacing + self.maxButtonWidth = max(100, maxButtonWidth ?? CGFloat.greatestFiniteMagnitude) + self.horizontalSizeClass = horizontalSizeClass + } + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) -> CGSize { + guard !subviews.isEmpty else { return .zero } + + self.calculateLayout(proposal: proposal, subviews: subviews, cache: &cache) + return cache.fitSize + } + + /// Creates and initializes a cache for a layout instance. + func makeCache(subviews: Subviews) -> CacheData { + CacheData(fitSize: .zero, frames: []) + } + + func calculateLayout(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) { + let subViewSizes = subviews.reversed().map { + $0.sizeThatFits(.unspecified) + } + + cache.clear() + + let isRegular = self.horizontalSizeClass == .regular && (proposal.width ?? 500 > 667) + let layoutMode = LayoutMode(mode: isRegular ? .intrinsic : .sameAndFill, + num: isRegular ? 3 : 2) + + let hideRect = CGRect(x: -2000, y: 0, width: 0, height: 0) + self.calculateLayout(proposalWidth: proposal.width, subViewSizes: subViewSizes, hideRect: hideRect, layoutMode: layoutMode, cache: &cache) + } + + /// .compact, .sameAndFill, same size, up to 2 buttons + /// .reguar, .intrinsic, up to 3 buttons + func calculateLayout(proposalWidth: CGFloat?, subViewSizes: [CGSize], hideRect: CGRect, layoutMode: LayoutMode, cache: inout CacheData) { + let subViewNoOflSizes = subViewSizes.dropLast() + let numButtons = subViewNoOflSizes.count + let overflowSize = subViewSizes[numButtons] + let theSpacing: CGFloat = self.spacing ?? 0 + var maxHeight: CGFloat = 0 + var requiredFinalWidth: CGFloat = 0 + var finalWidth = proposalWidth ?? 0 + var buttonWidth: CGFloat? = nil + let idealNumToShow = min(layoutMode.num, numButtons) + var numToShow = idealNumToShow + var numToHide = 0 + + /// calculate numToShow, buttonWidth, requiredFinalWidth + if finalWidth == 0 { + if layoutMode.mode == .sameAndFill { + let tmpButtonWidth: CGFloat = subViewNoOflSizes.suffix(numToShow).reduce(0) { partialResult, size in + min(self.maxButtonWidth, max(partialResult, size.width)) + } + requiredFinalWidth = tmpButtonWidth * CGFloat(numToShow) + theSpacing * CGFloat(numToShow - 1) + buttonWidth = tmpButtonWidth + } else { + requiredFinalWidth = subViewNoOflSizes.suffix(numToShow).reduce(0) { partialResult, size in + partialResult + min(self.maxButtonWidth, size.width) + } + requiredFinalWidth += theSpacing * CGFloat(numToShow - 1) + } + + maxHeight = subViewNoOflSizes.suffix(numToShow).reduce(0) { partialResult, size in + max(partialResult, size.height) + } + + // check if overflow should be shown + if numToShow < numButtons { + requiredFinalWidth += min(self.maxButtonWidth, overflowSize.width) + theSpacing + } + finalWidth = requiredFinalWidth + } else { // there is a proposalWidth + var tmpButtonWidth: CGFloat = 0 + for i in 0 ..< idealNumToShow { + if layoutMode.mode == .sameAndFill { + tmpButtonWidth = min(self.maxButtonWidth, max(tmpButtonWidth, subViewNoOflSizes[i].width)) + requiredFinalWidth = tmpButtonWidth * CGFloat(i + 1) + theSpacing * CGFloat(i) + } else { + requiredFinalWidth += min(self.maxButtonWidth, subViewNoOflSizes[i].width) + (i > 0 ? theSpacing : 0) + } + maxHeight = max(maxHeight, subViewNoOflSizes[i].height) + numToShow = i + 1 + + /// Failed to fit numToShow buttons + if requiredFinalWidth + (numToShow < numButtons ? min(self.maxButtonWidth, overflowSize.width) + theSpacing : 0) > finalWidth { + if numToShow > 1 { + numToShow -= 1 + } + if layoutMode.mode == .sameAndFill { + let availableWidth = finalWidth - theSpacing * CGFloat(numToShow) - overflowSize.width + buttonWidth = min(self.maxButtonWidth, availableWidth / CGFloat(numToShow)) + } + break + } + } + + if buttonWidth == nil, layoutMode.mode == .sameAndFill { + var availableWidth = finalWidth - theSpacing * CGFloat(numToShow - 1) + if numToShow < numButtons { + availableWidth -= min(self.maxButtonWidth, overflowSize.width) + theSpacing + } + buttonWidth = min(self.maxButtonWidth, availableWidth / CGFloat(numToShow)) + } + } + + numToHide = numButtons - numToShow + if numToHide > 0 { + maxHeight = max(maxHeight, overflowSize.height) + } + /// calculate numToShow, buttonWidth, requiredFinalWidth + /// set up frames for each subview + + let y = maxHeight / 2 + + var frames = [CGRect]() + + var x: CGFloat = 0 + for i in 0 ... numButtons { + if i < numToShow { + let btWidth = buttonWidth ?? min(self.maxButtonWidth, subViewNoOflSizes[i].width) + x += btWidth + (i > 0 ? theSpacing : 0) + frames.append(CGRect(origin: CGPoint(x: finalWidth - x + btWidth / 2, y: y), size: CGSize(width: btWidth, height: maxHeight))) + } else if i < numButtons { // rest button to hide + frames.append(hideRect) + } else { // overflow + if numToHide > 0 { + frames.append(CGRect(x: overflowSize.width / 2, y: y, width: min(self.maxButtonWidth, overflowSize.width), height: overflowSize.height)) + } else { + frames.append(hideRect) + } + } + } + + DispatchQueue.main.async { + self.numButtonsDisplayInOverflow = numToHide + } + cache.frames = frames.reversed() + cache.fitSize = CGSize(width: finalWidth, height: maxHeight) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) { + guard !subviews.isEmpty else { return } + + if cache.frames.isEmpty || cache.fitSize.width != proposal.width { + self.calculateLayout(proposal: proposal, subviews: subviews, cache: &cache) + } + + for (i, subview) in subviews.enumerated() { + let x = cache.frames[i].origin.x + bounds.minX + let y = cache.frames[i].origin.y + bounds.minY + subview.place(at: CGPointMake(x, y), + anchor: .center, + proposal: ProposedViewSize(width: cache.frames[i].size.width, height: cache.frames[i].size.height)) + } + } +} + +private struct FooterPopupLayout: Layout { + public struct CacheData { + var width: CGFloat? + var maxWidth: CGFloat + var rows: [CGRect] + + mutating func clear() { + self.width = nil + self.maxWidth = 0 + self.rows.removeAll() + } + } + + let lineSpacing: CGFloat + + public init(lineSpacing: CGFloat = 8) { + self.lineSpacing = lineSpacing + } + + public func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) -> CGSize { + self.calculateLayout(for: subviews, containerWidth: proposal.width, cache: &cache) + let finalWidth = min(proposal.width ?? 0, cache.maxWidth) + let height: CGFloat = cache.rows.last?.maxY ?? 0 + + return CGSize(width: finalWidth, height: height) + } + + public func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) { + self.calculateLayout(for: subviews, containerWidth: proposal.width, cache: &cache) + + for (i, subview) in subviews.enumerated() { + let item = cache.rows[i] + let pt = CGPoint(x: item.origin.x + bounds.origin.x, y: item.origin.y + bounds.origin.y) + subview.place(at: pt, proposal: ProposedViewSize(width: cache.maxWidth, height: item.height)) + } + } + + public func makeCache(subviews: Subviews) -> CacheData { + CacheData(width: nil, maxWidth: 0, rows: []) + } + + func calculateLayout(for subviews: Subviews, containerWidth: CGFloat?, cache: inout CacheData) { + if subviews.isEmpty || (cache.width == containerWidth && !cache.rows.isEmpty) { + return + } + cache.clear() + cache.width = containerWidth + + let idealSizes = subviews.map { + $0.sizeThatFits(.unspecified) + } + let idealMaxWidth: CGFloat = idealSizes.reduce(0) { partialResult, size in + max(partialResult, size.width) + } + var finalSizes = idealSizes + + if let tmpWidth = containerWidth, idealMaxWidth > tmpWidth + 1 { + let maxContainerWidth = tmpWidth - 32 + let proposal = ProposedViewSize(width: maxContainerWidth, height: nil) + let sizes = subviews.map { + $0.sizeThatFits(proposal) + } + let maxWidth: CGFloat = sizes.reduce(0) { partialResult, size in + max(partialResult, size.width) + } + if maxWidth < idealMaxWidth { + finalSizes = sizes + } + } + + var pt = CGPoint.zero + + for size in finalSizes { + cache.rows.append(CGRect(origin: pt, size: size)) + pt.y += size.height + self.lineSpacing + cache.maxWidth = max(cache.maxWidth, size.width) + } + } +} + /** This file provides default fiori style for the component. @@ -14,14 +287,72 @@ import SwiftUI // Base Layout style public struct CardFooterBaseStyle: CardFooterStyle { @Environment(\.horizontalSizeClass) var horizontalSizeClass + @State var numButtonsDisplayInOverflow: Int = 0 + @State var showPopover = false + var tap: some Gesture { + TapGesture(count: 1) + .onEnded { _ in + self.showPopover = false + } + } @ViewBuilder public func makeBody(_ configuration: CardFooterConfiguration) -> some View { // Add default layout here - EqualWidthWithMaxWidthHStackLayout(spacing: 8, alignment: .center, maxWidth: 170, horizontalSizeClass: self.horizontalSizeClass) { - configuration.secondaryAction + CardFooterLayout(numButtonsDisplayInOverflow: self.$numButtonsDisplayInOverflow, spacing: 8, maxButtonWidth: nil, horizontalSizeClass: self.horizontalSizeClass) { + configuration.overflowAction + .onSimultaneousTapGesture { + self.showPopover.toggle() + } + .popover(self.$showPopover) { + FooterPopupLayout(lineSpacing: 3) { + if self.numButtonsDisplayInOverflow == 1 { + if !configuration.tertiaryAction.isEmpty { + configuration.tertiaryAction + .onSimultaneousTapGesture { + self.showPopover = false + } + } else { + configuration.secondaryAction + .onSimultaneousTapGesture { + self.showPopover = false + } + } + } else if self.numButtonsDisplayInOverflow == 2 { + configuration.secondaryAction + .onSimultaneousTapGesture { + self.showPopover = false + } + + configuration.tertiaryAction + .onSimultaneousTapGesture { + self.showPopover = false + } + } + } + .fioriButtonStyle(FioriTertiaryButtonStyle(maxWidth: .infinity)) + .multilineTextAlignment(.center) + .padding(EdgeInsets(top: 3, leading: 0, bottom: 3, trailing: 0)) + .cornerRadius(14) + .compositingGroup() + .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 14)) + .background(Color.preferredColor(.chrome)) + .shadow(color: .black.opacity(0.04), radius: 2, x: 0, y: 0) + .shadow(color: .black.opacity(0.04), radius: 16, x: 0, y: 8) + .shadow(color: .black.opacity(0.2), radius: 100, x: 0, y: 10) + } + + if !configuration.tertiaryAction.isEmpty { + configuration.tertiaryAction + } - configuration.action + if !configuration.secondaryAction.isEmpty { + configuration.secondaryAction + } + + if !configuration.action.isEmpty { + configuration.action + } } } } @@ -29,8 +360,6 @@ public struct CardFooterBaseStyle: CardFooterStyle { // Default fiori styles extension CardFooterFioriStyle { struct ContentFioriStyle: CardFooterStyle { - @Environment(\.horizontalSizeClass) var horizontalSizeClass - func makeBody(_ configuration: CardFooterConfiguration) -> some View { CardFooter(configuration) // Add default style for its content @@ -43,7 +372,6 @@ extension CardFooterFioriStyle { func makeBody(_ configuration: ActionConfiguration) -> some View { Action(configuration) - .frame(maxWidth: .infinity) .fioriButtonStyle(FioriPrimaryButtonStyle(.infinity)) } } @@ -53,28 +381,195 @@ extension CardFooterFioriStyle { func makeBody(_ configuration: SecondaryActionConfiguration) -> some View { SecondaryAction(configuration) - .frame(maxWidth: .infinity) .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .normal, maxWidth: .infinity)) } } + + struct TertiaryActionFioriStyle: TertiaryActionStyle { + let cardFooterConfiguration: CardFooterConfiguration + + func makeBody(_ configuration: TertiaryActionConfiguration) -> some View { + TertiaryAction(configuration) + .fioriButtonStyle(FioriTertiaryButtonStyle(maxWidth: .infinity)) + } + } + + struct OverflowActionFioriStyle: OverflowActionStyle { + let cardFooterConfiguration: CardFooterConfiguration + + func makeBody(_ configuration: OverflowActionConfiguration) -> some View { + OverflowAction(configuration) + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .normal)) + } + } +} + +#Preview("P") { + CardFooter(action: FioriButton(title: "Primary"), overflowAction: FioriButton(title: "Overflow")) +} + +#Preview("S") { + CardFooter(secondaryAction: FioriButton(title: "Save")) } -#Preview("Model") { +#Preview("T") { + CardFooter(tertiaryAction: FioriButton(title: "Tertiary")) +} + +#Preview("PS") { CardFooter(action: FioriButton(title: "Primary"), secondaryAction: FioriButton(title: "Save")) } +#Preview("ST") { + CardFooter(secondaryAction: FioriButton(title: "Save"), tertiaryAction: FioriButton(title: "Tertiary")) +} + +#Preview("3") { + CardFooter(action: FioriButton(title: "Primary"), secondaryAction: FioriButton(title: "Save"), tertiaryAction: FioriButton(title: "Tertiary"), overflowAction: FioriButton(title: "...")) +} + +#Preview("LongP") { + CardFooter(action: FioriButton(title: "Primary long long long"), secondaryAction: FioriButton(title: "Save"), tertiaryAction: FioriButton(title: "Tertiary"), overflowAction: FioriButton(title: "Overflow")) +} + +#Preview("LongS") { + CardFooter(action: FioriButton(title: "Primary"), secondaryAction: FioriButton(title: "Save long long long long"), tertiaryAction: FioriButton(title: "Tertiary"), overflowAction: FioriButton(title: "Overflow")) +} + +#Preview("LongLong") { + CardFooter(action: FioriButton(title: "Primary long long long long long long long long"), secondaryAction: FioriButton(title: "Save long long long long long long long long"), tertiaryAction: FioriButton(title: "Tertiary"), overflowAction: FioriButton(title: "Overflow")) +} + +#Preview("Shadow1") { + VStack { + VStack { + Text("hello foia weoi foiawefioa aiow efjoi aewiofifaj oei") + Text("hewoeo fiowaoeifj jioaf wejioeawjfo oijfawe oijfaoiwef oaifj aeiwof oai fiowaoifejaiow fjioawefoaief joiafj ioawej foiawejfo iewfjoiaew fjioaewfjowaie") + + }.overlay { + VStack { + Text("hello world") + } + .frame(width: 200, height: 200) + .background(.thinMaterial) + .shadow(color: .black.opacity(0.04), radius: 2, x: 0, y: 0) + .shadow(color: .black.opacity(0.04), radius: 16, x: 0, y: 8) + .shadow(color: .black.opacity(0.2), radius: 100, x: 0, y: 10) + } + } + .padding(.top, 60) + .frame(maxWidth: .infinity) + .background(Color.preferredColor(.primaryGroupedBackground)) +} + +#Preview("Same width") { + VStack { + Button("Log in") {} + .foregroundStyle(.white) + .padding() + .frame(maxWidth: .infinity) + .background(.red) + + Button("Reset Password") {} + .foregroundStyle(.white) + .padding() + .frame(maxWidth: .infinity) + .background(.red) + } + .fixedSize(horizontal: true, vertical: false) +} + +#Preview("Model-4") { + ScrollView(.horizontal) { + CardFooter(action: FioriButton(title: "Primary long long long long long long long long"), secondaryAction: FioriButton(title: "Save"), tertiaryAction: FioriButton(title: "Tertiary"), overflowAction: FioriButton(title: "Overflow")) + } +} + +#Preview("Model-4") { + ScrollView(.horizontal) { + CardFooter(action: FioriButton(title: "Primary"), secondaryAction: FioriButton(title: "Save long long long"), tertiaryAction: FioriButton(title: "Tertiary"), overflowAction: FioriButton(title: "...")) + } +} + #Preview("VB FioriButton") { CardFooter { - FioriButton(title: "Save") + FioriButton { state in + print("primaryAction \(state)") + } label: { _ in + HStack { + Image(systemName: "tray") + Text("Save") + } + } } secondaryAction: { - FioriButton(title: "Decline") + FioriButton { state in + print("secondaryAction \(state)") + } label: { _ in + HStack { + Image(systemName: "doc") + Text("Decline") + } + } + } tertiaryAction: { + FioriButton { state in + print("tertiaryAction \(state)") + } label: { _ in + HStack { + Image(systemName: "paperplane") + Text("Third") + } + } } } #Preview("VB FioriButton") { - CardFooter(action: { - FioriButton(title: "Save") - }) + CardFooter { + FioriButton { state in + print("primaryAction \(state)") + } label: { _ in + HStack { + Image(systemName: "tray") + Text("Save") + } + } + } secondaryAction: { + Image(systemName: "doc") + .onTapGesture { + print("secondaryAction tapped") + } + } tertiaryAction: { + RoundedRectangle(cornerRadius: 10) + .foregroundColor(Color.purple) + .frame(width: 200, height: 80) + .onTapGesture { + print("tertiaryAction tapped") + } + } +} + +#Preview("VB FioriButton") { + CardFooter { + FioriButton { state in + print("primaryAction \(state)") + } label: { _ in + HStack { + Image(systemName: "tray") + Text("Save") + } + } + } secondaryAction: { + RoundedRectangle(cornerRadius: 10) + .foregroundColor(Color.purple) + .frame(width: 150, height: 80) + .onTapGesture { + print("tertiaryAction tapped") + } + } tertiaryAction: { + Image(systemName: "doc") + .onTapGesture { + print("secondaryAction tapped") + } + } } #Preview("VB Button") { @@ -97,3 +592,20 @@ extension CardFooterFioriStyle { .buttonStyle(.bordered) } } + +#Preview("Menu") { + HStack { + Menu("Working Day") { + FioriButton(title: "Monday") + FioriButton(title: "Tuesday") + FioriButton(title: "Wednesday") + }.menuStyle(.borderlessButton) + + FioriIcon.actions.overflow + Image(fioriName: "fiori.overflow") + Image("fiori.overflow", bundle: Bundle.accessor) + Image(systemName: "ellipsis") + FioriButton(title: "Save") + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .normal)) + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/CardStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/CardStyle.fiori.swift index 6db656f8f..555a2378b 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/CardStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/CardStyle.fiori.swift @@ -19,13 +19,15 @@ public struct CardBaseStyle: CardStyle { CardLayout(lineSpacing: 0) { configuration._cardHeader .padding(EdgeInsets(top: 0, leading: 0, bottom: 6, trailing: 0)) - + if !configuration.cardBody.isEmpty { configuration.cardBody .padding(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16)) } - if !(configuration._cardFooter.action.isEmpty && configuration._cardFooter.secondaryAction.isEmpty) { + if !(configuration._cardFooter.action.isEmpty && configuration._cardFooter.secondaryAction.isEmpty && + configuration._cardFooter.tertiaryAction.isEmpty) + { configuration._cardFooter .padding(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16)) } @@ -47,7 +49,7 @@ struct CardLayout: Layout { self.rows.removeAll() } } - + let lineSpacing: CGFloat public init(lineSpacing: CGFloat = 8) { @@ -61,28 +63,28 @@ struct CardLayout: Layout { return CGSize(width: finalWidth, height: height) } - + public func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) { self.calculateLayout(for: subviews, containerWidth: proposal.width, cache: &cache) - + for (i, subview) in subviews.enumerated() { let item = cache.rows[i] let pt = CGPoint(x: item.origin.x + bounds.origin.x, y: item.origin.y + bounds.origin.y) subview.place(at: pt, proposal: ProposedViewSize(width: item.size.width, height: nil)) } } - + public func makeCache(subviews: Subviews) -> CacheData { CacheData(width: nil, maxWidth: 0, rows: []) } - + func calculateLayout(for subviews: Subviews, containerWidth: CGFloat?, cache: inout CacheData) { - guard !subviews.isEmpty else { + if subviews.isEmpty || (cache.width == containerWidth && !cache.rows.isEmpty) { return } cache.clear() cache.width = containerWidth - + let maxContainerWidth = containerWidth ?? CGFloat.greatestFiniteMagnitude let proposal = containerWidth == nil ? ProposedViewSize.unspecified : ProposedViewSize(width: containerWidth, height: nil) let sizes = subviews.map { @@ -291,6 +293,28 @@ extension CardFioriStyle { } } + struct TertiaryActionFioriStyle: TertiaryActionStyle { + let cardConfiguration: CardConfiguration + + func makeBody(_ configuration: TertiaryActionConfiguration) -> some View { + TertiaryAction(configuration) + // Add default style for TertiaryAction + // .foregroundStyle(Color.preferredColor(<#fiori color#>)) + // .font(.fiori(forTextStyle: <#fiori font#>)) + } + } + + struct OverflowActionFioriStyle: OverflowActionStyle { + let cardConfiguration: CardConfiguration + + func makeBody(_ configuration: OverflowActionConfiguration) -> some View { + OverflowAction(configuration) + // Add default style for OverflowAction + // .foregroundStyle(Color.preferredColor(<#fiori color#>)) + // .font(.fiori(forTextStyle: <#fiori font#>)) + } + } + struct CardHeaderFioriStyle: CardHeaderStyle { let cardConfiguration: CardConfiguration @@ -337,7 +361,7 @@ public extension CardStyle where Self == CardCardStyle { struct ColorTagStyle: TagStyle { /// text color var textColor: Color = .preferredColor(.secondaryLabel) - + /// Color inside the tag var fillColor: Color = .clear @@ -423,7 +447,7 @@ public enum CardTests { static let row1 = TableRowItem(data: [DataTextItem("Need Attention", Font.subheadline.weight(.medium), Color.preferredColor(.criticalLabel)), DataTextItem("Yesterday", Font.caption, Color.preferredColor(.tertiaryLabel))]) static let row2 = TableRowItem(data: [DataTextItem("Stable", Font.subheadline), DataTextItem("Jul 5, 2021", Font.caption, Color.preferredColor(.tertiaryLabel))]) static let row3 = TableRowItem(data: [DataTextItem("Need Attention", Font.subheadline), DataTextItem("Jul 4, 2021", Font.caption, Color.preferredColor(.tertiaryLabel))]) - + static let tableCard = TableModel(headerData: nil, rowData: [row1, row2, row3], isHeaderSticky: false, @@ -904,9 +928,150 @@ public enum CardTests { static let titleOnly = Card(title: "Title") - /// Sample cards for testing - public static let cardSamples = [sampleCard1, sampleCard2, sampleCard3, sampleCard4, sampleCard5, sampleCard6, sampleCard7, sampleCard9, sampleCard10, vbCard, sampleCard11, sampleCard8, fullCard] + static let sampleCard12 = Card(title: "Title", + subtitle: "Subtitle that goes to multiple lines before truncating just like that", + icons: [TextOrIcon.icon(Image(systemName: "circle.fill")), TextOrIcon.icon(Image(systemName: "paperclip")), TextOrIcon.text("1")], + headerAction: FioriButton(title: "..."), + counter: "1 of 3", + action: FioriButton(title: "Primary"), + secondaryAction: FioriButton(title: "Secondary")) + + static let sampleCard13 = Card(title: "Title", + subtitle: "Subtitle that goes to multiple lines before truncating just like that", + icons: [TextOrIcon.icon(Image(systemName: "circle.fill")), TextOrIcon.icon(Image(systemName: "paperclip")), TextOrIcon.text("1")], + headerAction: FioriButton(title: "..."), + counter: "1 of 3", + action: FioriButton(title: "Primary"), + secondaryAction: FioriButton(title: "Secondary"), tertiaryAction: FioriButton(title: "Tertiary")) + + static let sampleCard14 = Card(title: "Title", + subtitle: "Subtitle that goes to multiple lines before truncating just like that", + icons: [TextOrIcon.icon(Image(systemName: "circle.fill")), TextOrIcon.icon(Image(systemName: "paperclip")), TextOrIcon.text("1")], + headerAction: FioriButton(title: "..."), + counter: "1 of 3", + action: FioriButton(title: "Primary long long long long long"), + secondaryAction: FioriButton(title: "Secondary"), tertiaryAction: FioriButton(title: "Tertiary")) + + static let sampleCard15 = Card(title: "Title", + subtitle: "Subtitle that goes to multiple lines before truncating just like that", + icons: [TextOrIcon.icon(Image(systemName: "circle.fill")), TextOrIcon.icon(Image(systemName: "paperclip")), TextOrIcon.text("1")], + headerAction: FioriButton(title: "..."), + counter: "1 of 3", + action: FioriButton(title: "Primary"), + secondaryAction: FioriButton(title: "Secondary long long long long long a b c long long long long"), tertiaryAction: FioriButton(title: "Tertiary"), + overflowAction: FioriButton(title: "Overflow")) + + static let sampleCard20 = Card(title: "Title", + subtitle: "Subtitle that goes to multiple lines before truncating just like that", + icons: [TextOrIcon.icon(Image(systemName: "circle.fill")), TextOrIcon.icon(Image(systemName: "paperclip")), TextOrIcon.text("1")], + headerAction: FioriButton(title: "..."), + counter: "1 of 3", + action: FioriButton(title: "Primary long long long long long long long long"), + secondaryAction: FioriButton(title: "Secondary long long long long long long long long long long long"), tertiaryAction: FioriButton(title: "Tertiary"), + overflowAction: FioriButton(title: "Overflow")) + + static let sampleCard16 = Card(title: "Title", + subtitle: "Subtitle that goes to multiple lines before truncating just like that", + icons: [TextOrIcon.icon(Image(systemName: "circle.fill")), TextOrIcon.icon(Image(systemName: "paperclip")), TextOrIcon.text("1")], + headerAction: FioriButton(title: "..."), + counter: "1 of 3", + secondaryAction: FioriButton(title: "Secondary")) + + static let sampleCard17 = Card(title: "Title", + subtitle: "Subtitle that goes to multiple lines before truncating just like that", + icons: [TextOrIcon.icon(Image(systemName: "circle.fill")), TextOrIcon.icon(Image(systemName: "paperclip")), TextOrIcon.text("1")], + headerAction: FioriButton(title: "..."), + counter: "1 of 3", + tertiaryAction: FioriButton(title: "Tertiary"), + overflowAction: FioriButton(title: "Overflow")) + + static let sampleCard18 = Card { + Text("Standard Room, 2 Single Beds") + } subtitle: { + Text("Gbt") + } row1: { + HStack { + RattingViewExample() + LabelItem(title: "Free Breakfast") + } + } row2: { + Tag("Business Rate") + .tagStyle(ColorTagStyle(textColor: .preferredColor(.grey9), fillColor: .preferredColor(.grey2))) + } kpi: { + KPIItem(data: .components([.unit("$"), .metric("90")]), subtitle: "avg. per night") + .frame(height: 20) + } action: { + FioriButton { state in + print("primaryAction \(state)") + } label: { _ in + HStack { + Image(systemName: "tray") + Text("Reserve") + } + } + } secondaryAction: { + RoundedRectangle(cornerRadius: 10) + .foregroundColor(Color.purple) + .frame(width: 120, height: 80) + .onTapGesture { + print("secondaryAction tapped") + } + } tertiaryAction: { + Image(systemName: "doc") + .onTapGesture { + print("tertiaryAction tapped") + } + } overflowAction: { + RoundedRectangle(cornerRadius: 10) + .foregroundColor(Color.green) + .frame(width: 40, height: 40) + .onTapGesture { + print("overflowAction tapped") + } + } + static let sampleCard19 = Card { + Text("Standard Room, 2 Single Beds") + } subtitle: { + Text("Gbt") + } row1: { + HStack { + RattingViewExample() + LabelItem(title: "Free Breakfast") + } + } row2: { + Tag("Business Rate") + .tagStyle(ColorTagStyle(textColor: .preferredColor(.grey9), fillColor: .preferredColor(.grey2))) + } kpi: { + KPIItem(data: .components([.unit("$"), .metric("90")]), subtitle: "avg. per night") + .frame(height: 20) + } action: { + Button { + print("Tapped") + } label: { + Text("Save") + .frame(maxWidth: .infinity) + } + .buttonStyle(.bordered) + } secondaryAction: { + Image(systemName: "doc") + .onTapGesture { + print("secondaryAction tapped") + } + } tertiaryAction: { + RoundedRectangle(cornerRadius: 10) + .foregroundColor(Color.purple) + .frame(width: 200, height: 80) + .onTapGesture { + print("tertiaryAction tapped") + } + } overflowAction: { + FioriButton(title: "Overflow") + } + + /// Sample cards for testing + public static let cardSamples = [sampleCard1, sampleCard13, sampleCard2, sampleCard3, sampleCard4, sampleCard5, sampleCard6, sampleCard7, sampleCard9, sampleCard10, vbCard, sampleCard11, sampleCard8, fullCard] + public static let cardFooterSamples = [sampleCard6, sampleCard16, sampleCard17, sampleCard12, sampleCard13, sampleCard14, sampleCard15, sampleCard20, sampleCard18, sampleCard19] static let previewCardSamples = [sampleCard1, sampleCard2, sampleCard3, sampleCard4, sampleCard5, sampleCard6, sampleCard7, sampleCard8, sampleCard9, sampleCard10, sampleCard11, vbCard, fullCard, headerOnly, titleOnly] } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/OverflowActionStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/OverflowActionStyle.fiori.swift new file mode 100644 index 000000000..18cad456a --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/OverflowActionStyle.fiori.swift @@ -0,0 +1,33 @@ +import FioriThemeManager + +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +/** + This file provides default fiori style for the component. + + 1. Uncomment the following code. + 2. Implement layout and style in corresponding places. + 3. Delete `.generated` from file name. + 4. Move this file to `_FioriStyles` folder under `FioriSwiftUICore`. + */ + +// Base Layout style +public struct OverflowActionBaseStyle: OverflowActionStyle { + @ViewBuilder + public func makeBody(_ configuration: OverflowActionConfiguration) -> some View { + // Add default layout here + configuration.overflowAction + } +} + +// Default fiori styles +public struct OverflowActionFioriStyle: OverflowActionStyle { + @ViewBuilder + public func makeBody(_ configuration: OverflowActionConfiguration) -> some View { + OverflowAction(configuration) + .fioriButtonStyle(FioriTertiaryButtonStyle()) + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/SecondaryActionStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/SecondaryActionStyle.fiori.swift index b006b7e09..fcbb100f2 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/SecondaryActionStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/SecondaryActionStyle.fiori.swift @@ -25,8 +25,5 @@ public struct SecondaryActionFioriStyle: SecondaryActionStyle { @ViewBuilder public func makeBody(_ configuration: SecondaryActionConfiguration) -> some View { SecondaryAction(configuration) - // Add default style here - // .foregroundStyle(Color.preferredColor(<#fiori color#>)) - // .font(.fiori(forTextStyle: <#fiori font#>)) } } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/TertiaryActionStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/TertiaryActionStyle.fiori.swift new file mode 100644 index 000000000..83de27e91 --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/TertiaryActionStyle.fiori.swift @@ -0,0 +1,32 @@ +import FioriThemeManager + +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +/** + This file provides default fiori style for the component. + + 1. Uncomment the following code. + 2. Implement layout and style in corresponding places. + 3. Delete `.generated` from file name. + 4. Move this file to `_FioriStyles` folder under `FioriSwiftUICore`. + */ + +// Base Layout style +public struct TertiaryActionBaseStyle: TertiaryActionStyle { + @ViewBuilder + public func makeBody(_ configuration: TertiaryActionConfiguration) -> some View { + // Add default layout here + configuration.tertiaryAction + } +} + +// Default fiori styles +public struct TertiaryActionFioriStyle: TertiaryActionStyle { + @ViewBuilder + public func makeBody(_ configuration: TertiaryActionConfiguration) -> some View { + TertiaryAction(configuration) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/Card.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/Card.generated.swift index e7ceb143b..ae1c4c638 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/Card.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/Card.generated.swift @@ -20,6 +20,8 @@ public struct Card { let cardBody: any View let action: any View let secondaryAction: any View + let tertiaryAction: any View + let overflowAction: any View @Environment(\.cardStyle) var style @@ -40,7 +42,9 @@ public struct Card { @ViewBuilder kpiCaption: () -> any View = { EmptyView() }, @ViewBuilder cardBody: () -> any View = { EmptyView() }, @ViewBuilder action: () -> any View = { EmptyView() }, - @ViewBuilder secondaryAction: () -> any View = { EmptyView() }) + @ViewBuilder secondaryAction: () -> any View = { EmptyView() }, + @ViewBuilder tertiaryAction: () -> any View = { EmptyView() }, + @ViewBuilder overflowAction: () -> any View = { FioriButton { _ in Image(systemName: "ellipsis") } }) { self.mediaImage = MediaImage { mediaImage() } self.description = Description { description() } @@ -58,6 +62,8 @@ public struct Card { self.cardBody = CardBody { cardBody() } self.action = Action { action() } self.secondaryAction = SecondaryAction { secondaryAction() } + self.tertiaryAction = TertiaryAction { tertiaryAction() } + self.overflowAction = OverflowAction { overflowAction() } } } @@ -77,9 +83,11 @@ public extension Card { kpiCaption: AttributedString? = nil, @ViewBuilder cardBody: () -> any View = { EmptyView() }, action: FioriButton? = nil, - secondaryAction: FioriButton? = nil) + secondaryAction: FioriButton? = nil, + tertiaryAction: FioriButton? = nil, + overflowAction: FioriButton? = FioriButton { _ in Image(systemName: "ellipsis") }) { - self.init(mediaImage: { OptionalImage(mediaImage) }, description: { OptionalText(description) }, title: { Text(title) }, subtitle: { OptionalText(subtitle) }, icons: { IconStack(icons) }, detailImage: { detailImage }, headerAction: { headerAction }, counter: { OptionalText(counter) }, row1: row1, row2: row2, row3: row3, kpi: { OptionalKPIItem(kpi) }, kpiCaption: { OptionalText(kpiCaption) }, cardBody: cardBody, action: { action }, secondaryAction: { secondaryAction }) + self.init(mediaImage: { OptionalImage(mediaImage) }, description: { OptionalText(description) }, title: { Text(title) }, subtitle: { OptionalText(subtitle) }, icons: { IconStack(icons) }, detailImage: { detailImage }, headerAction: { headerAction }, counter: { OptionalText(counter) }, row1: row1, row2: row2, row3: row3, kpi: { OptionalKPIItem(kpi) }, kpiCaption: { OptionalText(kpiCaption) }, cardBody: cardBody, action: { action }, secondaryAction: { secondaryAction }, tertiaryAction: { tertiaryAction }, overflowAction: { overflowAction }) } } @@ -105,6 +113,8 @@ public extension Card { self.cardBody = configuration.cardBody self.action = configuration.action self.secondaryAction = configuration.secondaryAction + self.tertiaryAction = configuration.tertiaryAction + self.overflowAction = configuration.overflowAction self._shouldApplyDefaultStyle = shouldApplyDefaultStyle } } @@ -114,7 +124,7 @@ extension Card: View { if self._shouldApplyDefaultStyle { self.defaultStyle() } else { - self.style.resolve(configuration: .init(mediaImage: .init(self.mediaImage), description: .init(self.description), title: .init(self.title), subtitle: .init(self.subtitle), icons: .init(self.icons), detailImage: .init(self.detailImage), headerAction: .init(self.headerAction), counter: .init(self.counter), row1: .init(self.row1), row2: .init(self.row2), row3: .init(self.row3), kpi: .init(self.kpi), kpiCaption: .init(self.kpiCaption), cardBody: .init(self.cardBody), action: .init(self.action), secondaryAction: .init(self.secondaryAction))).typeErased + self.style.resolve(configuration: .init(mediaImage: .init(self.mediaImage), description: .init(self.description), title: .init(self.title), subtitle: .init(self.subtitle), icons: .init(self.icons), detailImage: .init(self.detailImage), headerAction: .init(self.headerAction), counter: .init(self.counter), row1: .init(self.row1), row2: .init(self.row2), row3: .init(self.row3), kpi: .init(self.kpi), kpiCaption: .init(self.kpiCaption), cardBody: .init(self.cardBody), action: .init(self.action), secondaryAction: .init(self.secondaryAction), tertiaryAction: .init(self.tertiaryAction), overflowAction: .init(self.overflowAction))).typeErased .transformEnvironment(\.cardStyleStack) { stack in if !stack.isEmpty { stack.removeLast() @@ -132,7 +142,7 @@ private extension Card { } func defaultStyle() -> some View { - Card(.init(mediaImage: .init(self.mediaImage), description: .init(self.description), title: .init(self.title), subtitle: .init(self.subtitle), icons: .init(self.icons), detailImage: .init(self.detailImage), headerAction: .init(self.headerAction), counter: .init(self.counter), row1: .init(self.row1), row2: .init(self.row2), row3: .init(self.row3), kpi: .init(self.kpi), kpiCaption: .init(self.kpiCaption), cardBody: .init(self.cardBody), action: .init(self.action), secondaryAction: .init(self.secondaryAction))) + Card(.init(mediaImage: .init(self.mediaImage), description: .init(self.description), title: .init(self.title), subtitle: .init(self.subtitle), icons: .init(self.icons), detailImage: .init(self.detailImage), headerAction: .init(self.headerAction), counter: .init(self.counter), row1: .init(self.row1), row2: .init(self.row2), row3: .init(self.row3), kpi: .init(self.kpi), kpiCaption: .init(self.kpiCaption), cardBody: .init(self.cardBody), action: .init(self.action), secondaryAction: .init(self.secondaryAction), tertiaryAction: .init(self.tertiaryAction), overflowAction: .init(self.overflowAction))) .shouldApplyDefaultStyle(false) .cardStyle(CardFioriStyle.ContentFioriStyle()) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/CardStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/CardStyle.generated.swift index eb11e1a47..8cf307acb 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/CardStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/CardStyle.generated.swift @@ -38,6 +38,8 @@ public struct CardConfiguration { public let cardBody: CardBody public let action: Action public let secondaryAction: SecondaryAction + public let tertiaryAction: TertiaryAction + public let overflowAction: OverflowAction public typealias MediaImage = ConfigurationViewWrapper public typealias Description = ConfigurationViewWrapper @@ -55,6 +57,8 @@ public struct CardConfiguration { public typealias CardBody = ConfigurationViewWrapper public typealias Action = ConfigurationViewWrapper public typealias SecondaryAction = ConfigurationViewWrapper + public typealias TertiaryAction = ConfigurationViewWrapper + public typealias OverflowAction = ConfigurationViewWrapper } public struct CardFioriStyle: CardStyle { @@ -76,6 +80,8 @@ public struct CardFioriStyle: CardStyle { .cardBodyStyle(CardBodyFioriStyle(cardConfiguration: configuration)) .actionStyle(ActionFioriStyle(cardConfiguration: configuration)) .secondaryActionStyle(SecondaryActionFioriStyle(cardConfiguration: configuration)) + .tertiaryActionStyle(TertiaryActionFioriStyle(cardConfiguration: configuration)) + .overflowActionStyle(OverflowActionFioriStyle(cardConfiguration: configuration)) .cardHeaderStyle(CardHeaderFioriStyle(cardConfiguration: configuration)) .cardFooterStyle(CardFooterFioriStyle(cardConfiguration: configuration)) } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooter.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooter.generated.swift index 643a461a2..01d1672ee 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooter.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooter.generated.swift @@ -6,24 +6,32 @@ import SwiftUI public struct CardFooter { let action: any View let secondaryAction: any View + let tertiaryAction: any View + let overflowAction: any View @Environment(\.cardFooterStyle) var style fileprivate var _shouldApplyDefaultStyle = true public init(@ViewBuilder action: () -> any View = { EmptyView() }, - @ViewBuilder secondaryAction: () -> any View = { EmptyView() }) + @ViewBuilder secondaryAction: () -> any View = { EmptyView() }, + @ViewBuilder tertiaryAction: () -> any View = { EmptyView() }, + @ViewBuilder overflowAction: () -> any View = { FioriButton { _ in Image(systemName: "ellipsis") } }) { self.action = Action { action() } self.secondaryAction = SecondaryAction { secondaryAction() } + self.tertiaryAction = TertiaryAction { tertiaryAction() } + self.overflowAction = OverflowAction { overflowAction() } } } public extension CardFooter { init(action: FioriButton? = nil, - secondaryAction: FioriButton? = nil) + secondaryAction: FioriButton? = nil, + tertiaryAction: FioriButton? = nil, + overflowAction: FioriButton? = FioriButton { _ in Image(systemName: "ellipsis") }) { - self.init(action: { action }, secondaryAction: { secondaryAction }) + self.init(action: { action }, secondaryAction: { secondaryAction }, tertiaryAction: { tertiaryAction }, overflowAction: { overflowAction }) } } @@ -35,6 +43,8 @@ public extension CardFooter { internal init(_ configuration: CardFooterConfiguration, shouldApplyDefaultStyle: Bool) { self.action = configuration.action self.secondaryAction = configuration.secondaryAction + self.tertiaryAction = configuration.tertiaryAction + self.overflowAction = configuration.overflowAction self._shouldApplyDefaultStyle = shouldApplyDefaultStyle } } @@ -44,7 +54,7 @@ extension CardFooter: View { if self._shouldApplyDefaultStyle { self.defaultStyle() } else { - self.style.resolve(configuration: .init(action: .init(self.action), secondaryAction: .init(self.secondaryAction))).typeErased + self.style.resolve(configuration: .init(action: .init(self.action), secondaryAction: .init(self.secondaryAction), tertiaryAction: .init(self.tertiaryAction), overflowAction: .init(self.overflowAction))).typeErased .transformEnvironment(\.cardFooterStyleStack) { stack in if !stack.isEmpty { stack.removeLast() @@ -62,7 +72,7 @@ private extension CardFooter { } func defaultStyle() -> some View { - CardFooter(.init(action: .init(self.action), secondaryAction: .init(self.secondaryAction))) + CardFooter(.init(action: .init(self.action), secondaryAction: .init(self.secondaryAction), tertiaryAction: .init(self.tertiaryAction), overflowAction: .init(self.overflowAction))) .shouldApplyDefaultStyle(false) .cardFooterStyle(CardFooterFioriStyle.ContentFioriStyle()) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooterStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooterStyle.generated.swift index bf2baf9a1..3b2224201 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooterStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooterStyle.generated.swift @@ -24,9 +24,13 @@ struct AnyCardFooterStyle: CardFooterStyle { public struct CardFooterConfiguration { public let action: Action public let secondaryAction: SecondaryAction + public let tertiaryAction: TertiaryAction + public let overflowAction: OverflowAction public typealias Action = ConfigurationViewWrapper public typealias SecondaryAction = ConfigurationViewWrapper + public typealias TertiaryAction = ConfigurationViewWrapper + public typealias OverflowAction = ConfigurationViewWrapper } public struct CardFooterFioriStyle: CardFooterStyle { @@ -34,5 +38,7 @@ public struct CardFooterFioriStyle: CardFooterStyle { CardFooter(configuration) .actionStyle(ActionFioriStyle(cardFooterConfiguration: configuration)) .secondaryActionStyle(SecondaryActionFioriStyle(cardFooterConfiguration: configuration)) + .tertiaryActionStyle(TertiaryActionFioriStyle(cardFooterConfiguration: configuration)) + .overflowActionStyle(OverflowActionFioriStyle(cardFooterConfiguration: configuration)) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowAction.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowAction.generated.swift new file mode 100644 index 000000000..1e606dc3e --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowAction.generated.swift @@ -0,0 +1,63 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public struct OverflowAction { + let overflowAction: any View + + @Environment(\.overflowActionStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder overflowAction: () -> any View = { FioriButton { _ in Image(systemName: "ellipsis") } }) { + self.overflowAction = overflowAction() + } +} + +public extension OverflowAction { + init(overflowAction: FioriButton? = FioriButton { _ in Image(systemName: "ellipsis") }) { + self.init(overflowAction: { overflowAction }) + } +} + +public extension OverflowAction { + init(_ configuration: OverflowActionConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: OverflowActionConfiguration, shouldApplyDefaultStyle: Bool) { + self.overflowAction = configuration.overflowAction + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension OverflowAction: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(overflowAction: .init(self.overflowAction))).typeErased + .transformEnvironment(\.overflowActionStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension OverflowAction { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + OverflowAction(overflowAction: { self.overflowAction }) + .shouldApplyDefaultStyle(false) + .overflowActionStyle(.fiori) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowActionStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowActionStyle.generated.swift new file mode 100644 index 000000000..da75737b1 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowActionStyle.generated.swift @@ -0,0 +1,28 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol OverflowActionStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: OverflowActionConfiguration) -> Body +} + +struct AnyOverflowActionStyle: OverflowActionStyle { + let content: (OverflowActionConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (OverflowActionConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: OverflowActionConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct OverflowActionConfiguration { + public let overflowAction: OverflowAction + + public typealias OverflowAction = ConfigurationViewWrapper +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryAction.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryAction.generated.swift new file mode 100644 index 000000000..a5b734426 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryAction.generated.swift @@ -0,0 +1,63 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public struct TertiaryAction { + let tertiaryAction: any View + + @Environment(\.tertiaryActionStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder tertiaryAction: () -> any View = { EmptyView() }) { + self.tertiaryAction = tertiaryAction() + } +} + +public extension TertiaryAction { + init(tertiaryAction: FioriButton? = nil) { + self.init(tertiaryAction: { tertiaryAction }) + } +} + +public extension TertiaryAction { + init(_ configuration: TertiaryActionConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: TertiaryActionConfiguration, shouldApplyDefaultStyle: Bool) { + self.tertiaryAction = configuration.tertiaryAction + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension TertiaryAction: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(tertiaryAction: .init(self.tertiaryAction))).typeErased + .transformEnvironment(\.tertiaryActionStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension TertiaryAction { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + TertiaryAction(tertiaryAction: { self.tertiaryAction }) + .shouldApplyDefaultStyle(false) + .tertiaryActionStyle(.fiori) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryActionStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryActionStyle.generated.swift new file mode 100644 index 000000000..3e4a23c9f --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryActionStyle.generated.swift @@ -0,0 +1,28 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol TertiaryActionStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: TertiaryActionConfiguration) -> Body +} + +struct AnyTertiaryActionStyle: TertiaryActionStyle { + let content: (TertiaryActionConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (TertiaryActionConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: TertiaryActionConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct TertiaryActionConfiguration { + public let tertiaryAction: TertiaryAction + + public typealias TertiaryAction = ConfigurationViewWrapper +} diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift index 9878aa946..3d5a6edf0 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift @@ -486,6 +486,48 @@ public extension CardStyle where Self == CardSecondaryActionStyle { } } +public struct CardTertiaryActionStyle: CardStyle { + let style: any TertiaryActionStyle + + public func makeBody(_ configuration: CardConfiguration) -> some View { + Card(configuration) + .tertiaryActionStyle(self.style) + .typeErased + } +} + +public extension CardStyle where Self == CardTertiaryActionStyle { + static func tertiaryActionStyle(_ style: some TertiaryActionStyle) -> CardTertiaryActionStyle { + CardTertiaryActionStyle(style: style) + } + + static func tertiaryActionStyle(@ViewBuilder content: @escaping (TertiaryActionConfiguration) -> some View) -> CardTertiaryActionStyle { + let style = AnyTertiaryActionStyle(content) + return CardTertiaryActionStyle(style: style) + } +} + +public struct CardOverflowActionStyle: CardStyle { + let style: any OverflowActionStyle + + public func makeBody(_ configuration: CardConfiguration) -> some View { + Card(configuration) + .overflowActionStyle(self.style) + .typeErased + } +} + +public extension CardStyle where Self == CardOverflowActionStyle { + static func overflowActionStyle(_ style: some OverflowActionStyle) -> CardOverflowActionStyle { + CardOverflowActionStyle(style: style) + } + + static func overflowActionStyle(@ViewBuilder content: @escaping (OverflowActionConfiguration) -> some View) -> CardOverflowActionStyle { + let style = AnyOverflowActionStyle(content) + return CardOverflowActionStyle(style: style) + } +} + public struct CardCardHeaderStyle: CardStyle { let style: any CardHeaderStyle @@ -703,6 +745,48 @@ public extension CardFooterStyle where Self == CardFooterSecondaryActionStyle { } } +public struct CardFooterTertiaryActionStyle: CardFooterStyle { + let style: any TertiaryActionStyle + + public func makeBody(_ configuration: CardFooterConfiguration) -> some View { + CardFooter(configuration) + .tertiaryActionStyle(self.style) + .typeErased + } +} + +public extension CardFooterStyle where Self == CardFooterTertiaryActionStyle { + static func tertiaryActionStyle(_ style: some TertiaryActionStyle) -> CardFooterTertiaryActionStyle { + CardFooterTertiaryActionStyle(style: style) + } + + static func tertiaryActionStyle(@ViewBuilder content: @escaping (TertiaryActionConfiguration) -> some View) -> CardFooterTertiaryActionStyle { + let style = AnyTertiaryActionStyle(content) + return CardFooterTertiaryActionStyle(style: style) + } +} + +public struct CardFooterOverflowActionStyle: CardFooterStyle { + let style: any OverflowActionStyle + + public func makeBody(_ configuration: CardFooterConfiguration) -> some View { + CardFooter(configuration) + .overflowActionStyle(self.style) + .typeErased + } +} + +public extension CardFooterStyle where Self == CardFooterOverflowActionStyle { + static func overflowActionStyle(_ style: some OverflowActionStyle) -> CardFooterOverflowActionStyle { + CardFooterOverflowActionStyle(style: style) + } + + static func overflowActionStyle(@ViewBuilder content: @escaping (OverflowActionConfiguration) -> some View) -> CardFooterOverflowActionStyle { + let style = AnyOverflowActionStyle(content) + return CardFooterOverflowActionStyle(style: style) + } +} + // MARK: CardHeaderStyle public extension CardHeaderStyle where Self == CardHeaderBaseStyle { @@ -2656,6 +2740,20 @@ public extension ObjectItemStyle where Self == ObjectItemActionStyle { } } +// MARK: OverflowActionStyle + +public extension OverflowActionStyle where Self == OverflowActionBaseStyle { + static var base: OverflowActionBaseStyle { + OverflowActionBaseStyle() + } +} + +public extension OverflowActionStyle where Self == OverflowActionFioriStyle { + static var fiori: OverflowActionFioriStyle { + OverflowActionFioriStyle() + } +} + // MARK: PlaceholderStyle public extension PlaceholderStyle where Self == PlaceholderBaseStyle { @@ -3321,6 +3419,20 @@ public extension TagsStyle where Self == TagsFioriStyle { } } +// MARK: TertiaryActionStyle + +public extension TertiaryActionStyle where Self == TertiaryActionBaseStyle { + static var base: TertiaryActionBaseStyle { + TertiaryActionBaseStyle() + } +} + +public extension TertiaryActionStyle where Self == TertiaryActionFioriStyle { + static var fiori: TertiaryActionFioriStyle { + TertiaryActionFioriStyle() + } +} + // MARK: TextFieldFormViewStyle public extension TextFieldFormViewStyle where Self == TextFieldFormViewBaseStyle { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift index 64b9e9559..bb6d2e44f 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift @@ -927,6 +927,27 @@ extension EnvironmentValues { } } +// MARK: OverflowActionStyle + +struct OverflowActionStyleStackKey: EnvironmentKey { + static let defaultValue: [any OverflowActionStyle] = [] +} + +extension EnvironmentValues { + var overflowActionStyle: any OverflowActionStyle { + self.overflowActionStyleStack.last ?? .base + } + + var overflowActionStyleStack: [any OverflowActionStyle] { + get { + self[OverflowActionStyleStackKey.self] + } + set { + self[OverflowActionStyleStackKey.self] = newValue + } + } +} + // MARK: PlaceholderStyle struct PlaceholderStyleStackKey: EnvironmentKey { @@ -1263,6 +1284,27 @@ extension EnvironmentValues { } } +// MARK: TertiaryActionStyle + +struct TertiaryActionStyleStackKey: EnvironmentKey { + static let defaultValue: [any TertiaryActionStyle] = [] +} + +extension EnvironmentValues { + var tertiaryActionStyle: any TertiaryActionStyle { + self.tertiaryActionStyleStack.last ?? .base + } + + var tertiaryActionStyleStack: [any TertiaryActionStyle] { + get { + self[TertiaryActionStyleStackKey.self] + } + set { + self[TertiaryActionStyleStackKey.self] = newValue + } + } +} + // MARK: TextFieldFormViewStyle struct TextFieldFormViewStyleStackKey: EnvironmentKey { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift index 90cf161fd..4c9ff4c8f 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift @@ -1240,6 +1240,34 @@ public extension ObjectItemStyle { } } +// MARK: OverflowActionStyle + +extension ModifiedStyle: OverflowActionStyle where Style: OverflowActionStyle { + public func makeBody(_ configuration: OverflowActionConfiguration) -> some View { + OverflowAction(configuration) + .overflowActionStyle(self.style) + .modifier(self.modifier) + } +} + +public struct OverflowActionStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.overflowActionStyle(self.style) + } +} + +public extension OverflowActionStyle { + func modifier(_ modifier: some ViewModifier) -> some OverflowActionStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some OverflowActionStyle) -> some OverflowActionStyle { + style.modifier(OverflowActionStyleModifier(style: self)) + } +} + // MARK: PlaceholderStyle extension ModifiedStyle: PlaceholderStyle where Style: PlaceholderStyle { @@ -1688,6 +1716,34 @@ public extension TagsStyle { } } +// MARK: TertiaryActionStyle + +extension ModifiedStyle: TertiaryActionStyle where Style: TertiaryActionStyle { + public func makeBody(_ configuration: TertiaryActionConfiguration) -> some View { + TertiaryAction(configuration) + .tertiaryActionStyle(self.style) + .modifier(self.modifier) + } +} + +public struct TertiaryActionStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.tertiaryActionStyle(self.style) + } +} + +public extension TertiaryActionStyle { + func modifier(_ modifier: some ViewModifier) -> some TertiaryActionStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some TertiaryActionStyle) -> some TertiaryActionStyle { + style.modifier(TertiaryActionStyleModifier(style: self)) + } +} + // MARK: TextFieldFormViewStyle extension ModifiedStyle: TextFieldFormViewStyle where Style: TextFieldFormViewStyle { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift index a71c0f1cd..0881f3b48 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift @@ -707,6 +707,22 @@ extension ObjectItemStyle { } } +// MARK: OverflowActionStyle + +struct ResolvedOverflowActionStyle: View { + let style: Style + let configuration: OverflowActionConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension OverflowActionStyle { + func resolve(configuration: OverflowActionConfiguration) -> some View { + ResolvedOverflowActionStyle(style: self, configuration: configuration) + } +} + // MARK: PlaceholderStyle struct ResolvedPlaceholderStyle: View { @@ -963,6 +979,22 @@ extension TagsStyle { } } +// MARK: TertiaryActionStyle + +struct ResolvedTertiaryActionStyle: View { + let style: Style + let configuration: TertiaryActionConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension TertiaryActionStyle { + func resolve(configuration: TertiaryActionConfiguration) -> some View { + ResolvedTertiaryActionStyle(style: self, configuration: configuration) + } +} + // MARK: TextFieldFormViewStyle struct ResolvedTextFieldFormViewStyle: View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift index 9c3469832..2cc239235 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift @@ -13,7 +13,7 @@ extension CardConfiguration { } var _cardFooter: CardFooter { - CardFooter(.init(action: .init(self.action), secondaryAction: .init(self.secondaryAction)), shouldApplyDefaultStyle: true) + CardFooter(.init(action: .init(self.action), secondaryAction: .init(self.secondaryAction), tertiaryAction: .init(self.tertiaryAction), overflowAction: .init(self.overflowAction)), shouldApplyDefaultStyle: true) } } diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift index 9275fb741..dc7c14742 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift @@ -751,6 +751,23 @@ public extension View { } } +// MARK: OverflowActionStyle + +public extension View { + func overflowActionStyle(_ style: some OverflowActionStyle) -> some View { + self.transformEnvironment(\.overflowActionStyleStack) { stack in + stack.append(style) + } + } + + func overflowActionStyle(@ViewBuilder content: @escaping (OverflowActionConfiguration) -> some View) -> some View { + self.transformEnvironment(\.overflowActionStyleStack) { stack in + let style = AnyOverflowActionStyle(content) + stack.append(style) + } + } +} + // MARK: PlaceholderStyle public extension View { @@ -1023,6 +1040,23 @@ public extension View { } } +// MARK: TertiaryActionStyle + +public extension View { + func tertiaryActionStyle(_ style: some TertiaryActionStyle) -> some View { + self.transformEnvironment(\.tertiaryActionStyleStack) { stack in + stack.append(style) + } + } + + func tertiaryActionStyle(@ViewBuilder content: @escaping (TertiaryActionConfiguration) -> some View) -> some View { + self.transformEnvironment(\.tertiaryActionStyleStack) { stack in + let style = AnyTertiaryActionStyle(content) + stack.append(style) + } + } +} + // MARK: TextFieldFormViewStyle public extension View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift index fc2a4701e..ca31088a1 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift @@ -52,7 +52,9 @@ extension Card: _ViewEmptyChecking { kpiCaption.isEmpty && cardBody.isEmpty && action.isEmpty && - secondaryAction.isEmpty + secondaryAction.isEmpty && + tertiaryAction.isEmpty && + overflowAction.isEmpty } } @@ -69,7 +71,9 @@ extension CardExtHeader: _ViewEmptyChecking { extension CardFooter: _ViewEmptyChecking { public var isEmpty: Bool { action.isEmpty && - secondaryAction.isEmpty + secondaryAction.isEmpty && + tertiaryAction.isEmpty && + overflowAction.isEmpty } } @@ -335,6 +339,12 @@ extension ObjectItem: _ViewEmptyChecking { } } +extension OverflowAction: _ViewEmptyChecking { + public var isEmpty: Bool { + overflowAction.isEmpty + } +} + extension Placeholder: _ViewEmptyChecking { public var isEmpty: Bool { placeholder.isEmpty @@ -441,6 +451,12 @@ extension Tags: _ViewEmptyChecking { } } +extension TertiaryAction: _ViewEmptyChecking { + public var isEmpty: Bool { + tertiaryAction.isEmpty + } +} + extension TextFieldFormView: _ViewEmptyChecking { public var isEmpty: Bool { title.isEmpty &&