diff --git a/Sources/Ignite/Elements/NavigationBar.swift b/Sources/Ignite/Elements/NavigationBar.swift index 4a7e8c664..3c48e4fff 100644 --- a/Sources/Ignite/Elements/NavigationBar.swift +++ b/Sources/Ignite/Elements/NavigationBar.swift @@ -61,9 +61,12 @@ public struct NavigationBar: HTML { /// clickable to let users navigate to your homepage. let logo: any InlineElement - /// An array of items to show in this navigation bar. + /// An array of collapsible items to show in this navigation bar. let items: [any NavigationItem] + /// An array of permanent elements to show in this navigation bar. + let controls: [any NavigationItem] + /// The style to use when rendering this bar. var style = NavigationBarStyle.default @@ -78,36 +81,60 @@ public struct NavigationBar: HTML { ) { self.logo = logo ?? EmptyHTML() self.items = [] + self.controls = [] } - /// Creates a new `NavigationBar` instance from the `logo` and - /// `items` provided. + /// Creates a new `NavigationBar` instance from the `logo`, + /// `items`, and `actions` provided. /// - Parameters: /// - logo: The logo to use in the top-left edge of your bar. - /// - items: An element builder that returns an array of - /// `NavigationItem` objects. + /// - items: Basic navigation items like `Link` and `Span` that will be + /// collapsed into a hamburger menu at small screen sizes. + /// - actions: Elements positioned at the end of the navigation bar, like + /// call-to-action buttons and search fields, and visible across all screen sizes. public init( logo: (any InlineElement)? = nil, - @ElementBuilder items: () -> [any NavigationItem] + @ElementBuilder items: () -> [any NavigationItem], + @ElementBuilder actions: () -> [any NavigationItem] = { [] } ) { self.logo = logo ?? EmptyHTML() self.items = items() + self.controls = actions() } - /// Creates a new `NavigationBar` instance from the `logo` and - /// `items` provided. + /// Creates a new `NavigationBar` instance from the `logo`, + /// `items`, and `actions` provided. /// - Parameters: - /// - items: An element builder that returns an array of - /// `NavigationItem` objects. + /// - items: Basic navigation items like `Link` and `Span` that will be + /// collapsed into a hamburger menu at small screen sizes. + /// - actions: Elements positioned at the end of the navigation bar, like + /// call-to-action buttons and search fields, and visible across all screen sizes. /// - logo: The logo to use in the top-left edge of your bar. public init( @ElementBuilder items: () -> [any NavigationItem], + @ElementBuilder actions: () -> [any NavigationItem] = { [] }, @InlineElementBuilder logo: () -> any InlineElement = { EmptyHTML() } ) { self.items = items() + self.controls = actions() self.logo = logo() } + /// Creates a new `NavigationBar` instance from the `items` and `actions` provided. + /// - Parameters: + /// - items: Basic navigation items like `Link` and `Span` that will be + /// collapsed into a hamburger menu at small screen sizes. + /// - actions: Elements positioned at the end of the navigation bar, like + /// call-to-action buttons and search fields, and visible across all screen sizes. + public init( + @ElementBuilder items: () -> [any NavigationItem], + @ElementBuilder actions: () -> [any NavigationItem] = { [] } + ) { + self.items = items() + self.controls = actions() + self.logo = EmptyHTML() + } + /// Adjusts the style of this navigation bar. /// - Parameter style: The new style. /// - Returns: A new `NavigationBar` instance with the updated style. @@ -158,7 +185,17 @@ public struct NavigationBar: HTML { Section { if logo.isEmpty == false { renderLogo(logo) + .class("me-auto me-md-0") } + + if controls.isEmpty == false { + Section(HTMLCollection(controls)) + .class("gap-2") + .class("ms-md-2") + .class("d-inline-flex", "align-items-center") + .class("order-md-last", "ms-auto me-2") + } + if items.isEmpty == false { renderToggleButton() renderNavItems() @@ -243,7 +280,7 @@ public struct NavigationBar: HTML { return logo .trimmingMargin() - .class("d-inline-flex align-items-center") + .class("d-inline-flex", "align-items-center") .class("navbar-brand") } } diff --git a/Sources/Ignite/Framework/CoreAttributes.swift b/Sources/Ignite/Framework/CoreAttributes.swift index cb14ba1d8..4565df156 100644 --- a/Sources/Ignite/Framework/CoreAttributes.swift +++ b/Sources/Ignite/Framework/CoreAttributes.swift @@ -183,6 +183,7 @@ public struct CoreAttributes: Equatable, Sendable, CustomStringConvertible { /// Appends multiple extra inline CSS styles. /// - Parameter classes: The inline CSS styles to append. mutating func append(styles: InlineStyle...) { + let styles = styles.filter { !$0.value.isEmpty } self.styles.formUnion(styles) } @@ -190,7 +191,8 @@ public struct CoreAttributes: Equatable, Sendable, CustomStringConvertible { /// - Parameter style: The style name, e.g. background-color /// - Parameter value: The style value, e.g. steelblue mutating func append(style: Property, value: String) { - styles.append(InlineStyle(style, value: value)) + guard !value.isEmpty else { return } + styles.append(.init(style, value: value)) } /// Appends a data attribute. @@ -211,6 +213,7 @@ public struct CoreAttributes: Equatable, Sendable, CustomStringConvertible { /// CSS style properties and their values to be appended. mutating func append(styles newStyles: some Collection) { var styles = self.styles + let newStyles = newStyles.filter { !$0.value.isEmpty } styles.formUnion(newStyles) self.styles = styles } @@ -230,11 +233,11 @@ public struct CoreAttributes: Equatable, Sendable, CustomStringConvertible { /// Removes specified CSS properties from the element's inline styles. /// - Parameter properties: Variable number of CSS properties to remove. mutating func remove(styles properties: Property...) { - var styles = Array(self.styles) + var styles = self.styles for property in properties { styles.removeAll(where: { $0.property == property.rawValue }) } - self.styles = OrderedSet(styles) + self.styles = styles } /// Retrieves the inline styles for specified CSS properties. diff --git a/Sources/Ignite/Framework/ElementTypes/HTML.swift b/Sources/Ignite/Framework/ElementTypes/HTML.swift index eb0df3396..ae7abd023 100644 --- a/Sources/Ignite/Framework/ElementTypes/HTML.swift +++ b/Sources/Ignite/Framework/ElementTypes/HTML.swift @@ -102,17 +102,17 @@ extension HTML { /// A Boolean value indicating whether this represents `Text`. var isText: Bool { - body is Text || (body as? AnyHTML)?.wrapped is Text + self.is(Text.self) } /// A Boolean value indicating whether this represents `Image`. var isImage: Bool { - self is Image || (self as? AnyHTML)?.wrapped is Image + self.is(Image.self) } /// A Boolean value indicating whether this represents `Section`. var isSection: Bool { - self is Section || (self as? AnyHTML)?.wrapped is Section + self.is(Section.self) } /// A Boolean value indicating whether this represents a textual element.