Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Apps/Examples/Examples.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
A65036942CC6365C0093086A /* LoadingIndicatorExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = A65036932CC6365C0093086A /* LoadingIndicatorExample.swift */; };
A65FB3BF2DE464A000930093 /* CurrencyInputExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = A65FB3BE2DE464A000930093 /* CurrencyInputExample.swift */; };
A6DFDE582D9BE0B300666A51 /* AuthenticationScreenSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6DFDE572D9BE0B300666A51 /* AuthenticationScreenSample.swift */; };
A6F6E9F82EA8B40E009F85BD /* SemanticMessageExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F6E9F72EA8B40E009F85BD /* SemanticMessageExample.swift */; };
AB988B102631270300483D87 /* DataTableExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB988B0F2631270300483D87 /* DataTableExample.swift */; };
B100639329C0624D00AF0CA2 /* StepProgressIndicatorExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B100639229C0624D00AF0CA2 /* StepProgressIndicatorExample.swift */; };
B13408922B01FA0700600331 /* NavigationBarExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B13408912B01FA0700600331 /* NavigationBarExample.swift */; };
Expand Down Expand Up @@ -429,6 +430,7 @@
A65036932CC6365C0093086A /* LoadingIndicatorExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicatorExample.swift; sourceTree = "<group>"; };
A65FB3BE2DE464A000930093 /* CurrencyInputExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyInputExample.swift; sourceTree = "<group>"; };
A6DFDE572D9BE0B300666A51 /* AuthenticationScreenSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationScreenSample.swift; sourceTree = "<group>"; };
A6F6E9F72EA8B40E009F85BD /* SemanticMessageExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemanticMessageExample.swift; sourceTree = "<group>"; };
AB988B0F2631270300483D87 /* DataTableExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTableExample.swift; sourceTree = "<group>"; };
B100639229C0624D00AF0CA2 /* StepProgressIndicatorExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepProgressIndicatorExample.swift; sourceTree = "<group>"; };
B13408912B01FA0700600331 /* NavigationBarExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarExample.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -973,6 +975,7 @@
9D0086672BA8F6810004BE15 /* TitleFormViewExample.swift */,
9D057DAC2C34826700F5331C /* RatingControlFormViewExample.swift */,
6D6E256C2D378025009A62CA /* FilterFormViewExamples.swift */,
A6F6E9F72EA8B40E009F85BD /* SemanticMessageExample.swift */,
);
path = FormViews;
sourceTree = "<group>";
Expand Down Expand Up @@ -1373,6 +1376,7 @@
99942D59261698FC001912C5 /* InfoViewSample.swift in Sources */,
8732C2C72C3524B6002110E9 /* TimelineItemsExample.swift in Sources */,
993B55BE29DF7EC70002B065 /* IconLibraryExample.swift in Sources */,
A6F6E9F82EA8B40E009F85BD /* SemanticMessageExample.swift in Sources */,
B80DA9BE260C1CC200C0B2E9 /* ListDataProtocol.swift in Sources */,
B1DD86532B0758F000D7EDFD /* NavigationBarPopover.swift in Sources */,
3B62AB7E2C0EE257003262EB /* EditableSideBarExample.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ struct FormViewExamples: View {
} label: {
Text("CurrencyInput Example")
}
NavigationLink {
SemanticMessageExample()
} label: {
Text("SemanticMessage Example")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import FioriSwiftUICore
import FioriThemeManager
import SwiftUI

struct SemanticMessageExample: View {
let errorMessage = AttributedString("Error Message")
let hintText = AttributedString("Hint Text")
@State var showsHintText = false
@State var showsErrorMessage = false
@State var showsCharCount = false
@State var allowsBeyondLimit = false
@State var showAINotice: Bool = false
@State var isLoading: Bool = false
@State var valueText: String = ""
@State var longHelperText: Bool = false
@State var longAINoticeText: Bool = false
@State var semanticIndex: Int = 0
let styleNames = ["Error", "Success", "Informational", "Warning"]
var body: some View {
VStack {
List {
Toggle("Shows Hint Text", isOn: self.$showsHintText)

Toggle("Shows Error Message", isOn: self.$showsErrorMessage)

Picker("Semantic Style", selection: self.$semanticIndex) {
ForEach(0 ..< self.styleNames.count, id: \.self) { index in
Text(self.styleNames[index])
}
}
Toggle("Shows Char Count", isOn: self.$showsCharCount)

Toggle("Allows Beyond Limit", isOn: self.$allowsBeyondLimit)

Toggle("AI Notice", isOn: self.$showAINotice)

Toggle("longAINoticeText", isOn: self.$longAINoticeText)

Toggle("longHelperText", isOn: self.$longHelperText)

Button("Dismiss Keyboard") {
hideKeyboard()
}

Text("TextFieldFormView")
TextFieldFormView(title: "Label", text: self.$valueText, isSecureEnabled: false, placeholder: "Placeholder", errorMessage: self.getErrorMessage(), maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit, isAINoticeEnabled: self.showAINotice, isRequired: true)
.ifApply(self.showsErrorMessage) { v in
v.ifApply(self.semanticIndex == 0) { vs in
vs.textInputInfoViewStyle(.error)
}.ifApply(self.semanticIndex == 1) { vs in
vs.textInputInfoViewStyle(.success)
}.ifApply(self.semanticIndex == 2) { vs in
vs.textInputInfoViewStyle(.informational)
}.ifApply(self.semanticIndex == 3) { vs in
vs.textInputInfoViewStyle(.warning)
}
}
.aiNotice(self.$showAINotice, description: self.getNoticeText())

Text("KeyValueFormView")
KeyValueFormView(title: "Label", text: self.$valueText, placeholder: "Placeholder", errorMessage: self.getErrorMessage(), maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit, charCountBeyondLimitMsg: "reduce the number of characters", isAINoticeEnabled: self.showAINotice, isRequired: true)
.ifApply(self.showsErrorMessage) { v in
v.ifApply(self.semanticIndex == 0) { vs in
vs.textInputInfoViewStyle(.error)
}.ifApply(self.semanticIndex == 1) { vs in
vs.textInputInfoViewStyle(.success)
}.ifApply(self.semanticIndex == 2) { vs in
vs.textInputInfoViewStyle(.informational)
}.ifApply(self.semanticIndex == 3) { vs in
vs.textInputInfoViewStyle(.warning)
}
}
.aiNotice(self.$showAINotice, description: self.getNoticeText())

Text("NoteFormView")
NoteFormView(text: self.$valueText, placeholder: "Placeholder", errorMessage: self.getErrorMessage(), maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit, isAINoticeEnabled: self.showAINotice)
.ifApply(self.showsErrorMessage) { v in
v.ifApply(self.semanticIndex == 0) { vs in
vs.textInputInfoViewStyle(.error)
}.ifApply(self.semanticIndex == 1) { vs in
vs.textInputInfoViewStyle(.success)
}.ifApply(self.semanticIndex == 2) { vs in
vs.textInputInfoViewStyle(.informational)
}.ifApply(self.semanticIndex == 3) { vs in
vs.textInputInfoViewStyle(.warning)
}
}
.aiNotice(self.$showAINotice, description: self.getNoticeText())
}
}
}

func getNoticeText() -> AttributedString? {
(self.longAINoticeText && self.showAINotice) ? "Created with AI long description lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod." : "Created With AI"
}

func getHintText() -> AttributedString? {
self.showsHintText ? self.longHelperText ? "Helper text long description lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod." : self.hintText : nil
}

func getErrorMessage() -> AttributedString? {
self.showsErrorMessage ? self.errorMessage : nil
}

func getMaxTextLength() -> Int? {
self.showsCharCount ? 20 : nil
}

func getCharCountEnabled() -> Bool? {
self.showsCharCount ? true : nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ protocol _IllustratedMessageComponent: _DetailImageComponent, _TitleComponent, _
// sourcery: CompositeComponent
protocol _InformationViewComponent: _IconComponent, _DescriptionComponent {}

// sourcery: CompositeComponent, InternalComponent
// sourcery: CompositeComponent
protocol _TextInputInfoViewComponent: _InformationViewComponent, _CounterComponent {}

// sourcery: CompositeComponent
Expand Down Expand Up @@ -438,6 +438,9 @@ protocol _NoteFormViewComponent: _PlaceholderTextEditorComponent, _FormViewCompo
var charCountReachLimitMessage: String? { get }
/// The custom error message when the character count exceeds the limitation. If this property is `nil`, the default localized message will be used.
var charCountBeyondLimitMsg: String? { get }
// sourcery: defaultValue = false
/// Determine whether AINoticeView is displayed. The default is `false`.
var isAINoticeEnabled: Bool { get }
}

// sourcery: CompositeComponent
Expand Down Expand Up @@ -465,6 +468,9 @@ protocol _TitleFormViewComponent: _PlaceholderTextFieldComponent, _FormViewCompo
var charCountReachLimitMessage: String? { get }
/// The custom error message when the character count exceeds the limitation. If this property is `nil`, the default localized message will be used.
var charCountBeyondLimitMsg: String? { get }
// sourcery: defaultValue = false
/// Determine whether AINoticeView is displayed. The default is `false`.
var isAINoticeEnabled: Bool { get }
}

// sourcery: CompositeComponent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public struct AINoticeBaseStyle: AINoticeStyle {
}
Spacer()
}
.padding(.top, 4)
}

func getMessage(_ configuration: AINoticeConfiguration) -> some View {
Expand Down Expand Up @@ -95,3 +94,74 @@ public extension View {
self.modifier(AINoticeModifier(icon: icon, description: description ?? AttributedString(NSLocalizedString("Suggested by AI. Verify before use. ", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "Suggested by AI. Verify before use. ")), actionLabel: actionLabel, viewMoreAction: viewMoreAction, isPresented: isPresented))
}
}

struct AINoticeKey: EnvironmentKey {
static let defaultValue = AINoticeItemConfiguration(
isPresented: .constant(false),
icon: nil,
description: nil,
actionLabel: nil,
viewMoreAction: nil
)
}

extension EnvironmentValues {
var aiNotice: AINoticeItemConfiguration {
get { self[AINoticeKey.self] }
set { self[AINoticeKey.self] = newValue }
}
}

struct AINoticeItemConfiguration {
let isPresented: Binding<Bool>
let icon: Image?
let description: AttributedString?
let actionLabel: AttributedString?
let viewMoreAction: (() -> Void)?

init(isPresented: Binding<Bool>,
icon: Image? = nil,
description: AttributedString? = nil,
actionLabel: AttributedString? = nil,
viewMoreAction: (() -> Void)? = nil)
{
self.isPresented = isPresented
self.icon = icon
self.description = description
self.actionLabel = actionLabel
self.viewMoreAction = viewMoreAction
}
}

public extension View {
/// Sets the AI notice configuration for components
/// - Parameters:
/// - isPresented: Whether to show the AI notice
/// - icon: Icon for the AI notice, defaults to system AI icon
/// - description: Description text for the AI notice
/// - actionLabel: Action label text
/// - viewMoreAction: Action to perform when action label is tapped
/// - Returns: A view with the applied AI notice configuration
///
/// Example usage:
/// ```swift
/// SomeView(...)
/// .aiNotice(true) // Show AI notice with default style
/// .aiNotice(true, description: AttributedString("Custom message")) // Show with custom message
/// .aiNotice(false) // Hide AI notice
/// ```
func aiNotice(_ isPresented: Binding<Bool>,
icon: Image? = nil,
description: AttributedString? = nil,
actionLabel: AttributedString? = nil,
viewMoreAction: (() -> Void)? = nil) -> some View
{
self.environment(\.aiNotice, AINoticeItemConfiguration(
isPresented: isPresented,
icon: icon,
description: description,
actionLabel: actionLabel,
viewMoreAction: viewMoreAction,
))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ extension FioriSliderFioriStyle {
.frame(maxWidth: .infinity, alignment: .leading)
.font(.fiori(forTextStyle: .subheadline))
.fontWeight(.semibold)
.foregroundStyle(self.isEnabled ? (self.stateObject.isFocused ? self.trailingTextFieldStyle.focusedBorderColor : Color.preferredColor(.primaryLabel)) : Color.preferredColor(.quaternaryLabel))
.foregroundStyle(self.isEnabled ? Color.preferredColor(.primaryLabel) : Color.preferredColor(.quaternaryLabel))
.accessibilitySortPriority(self.fioriSliderConfiguration.isRangeSlider ? 7 : 0) // Need to set sort priority when the control was Range Slider
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ extension StepperViewFioriStyle {
StepperView(configuration)
.titleStyle(content: { titleConfiguration in
Title(titleConfiguration)
.foregroundColor(.preferredColor(self.isEnabled ? (self.isFocused ? .tintColor : .primaryLabel) : .separator))
.foregroundColor(.preferredColor(self.isEnabled ? .primaryLabel : .separator))
.font(.fiori(forTextStyle: .subheadline, weight: .semibold))
})
.stepperFieldStyle { config in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public struct TextFieldFormViewBaseStyle: TextFieldFormViewStyle {

func getTextInput(_ configuration: TextFieldFormViewConfiguration) -> some View {
if self.isLoading, !self.isAILoading {
return TitleFormView(.init(text: .constant("TextFieldFormView text input value in multiple lines"), isSecureEnabled: false, placeholder: .init(configuration.placeholder), controlState: .normal, errorMessage: nil, maxTextLength: 20, hintText: nil, hidesReadOnlyHint: false, isCharCountEnabled: false, allowsBeyondLimit: false, charCountReachLimitMessage: "", charCountBeyondLimitMsg: ""))
return TitleFormView(.init(text: .constant("TextFieldFormView text input value in multiple lines"), isSecureEnabled: false, placeholder: .init(configuration.placeholder), controlState: .normal, errorMessage: nil, maxTextLength: 20, hintText: nil, hidesReadOnlyHint: false, isCharCountEnabled: false, allowsBeyondLimit: false, charCountReachLimitMessage: "", charCountBeyondLimitMsg: "", isAINoticeEnabled: false))
.typeErased
} else {
return configuration._titleFormView.typeErased
Expand Down
Loading
Loading