From 23c4570f8b92100bdd43ee4d2d70460ea8fd4481 Mon Sep 17 00:00:00 2001 From: Damian Van de Kauter Date: Fri, 15 May 2026 11:29:14 +0200 Subject: [PATCH 1/3] Add badge support to TabView, move modifier Move BadgeModifier from List.swift into AdditionalViewModifiers.swift and keep its combined(for:) helper. Add badge support across TabView: import Badge/BadgedBox, add a badge property to TabContent, Tab, and TabSection, propagate badges to section children, and render tab icons inside a BadgedBox when a badge is present. Implement TabContent.badge(...) modifier overloads to set badge values instead of being unavailable, and apply the combined BadgeModifier when building tabs. Remove the duplicate BadgeModifier definition from List.swift. --- Sources/SkipUI/SkipUI/Containers/List.swift | 24 -------- .../SkipUI/SkipUI/Containers/TabView.swift | 61 +++++++++++++++---- .../SkipUI/View/AdditionalViewModifiers.swift | 24 ++++++++ 3 files changed, 72 insertions(+), 37 deletions(-) diff --git a/Sources/SkipUI/SkipUI/Containers/List.swift b/Sources/SkipUI/SkipUI/Containers/List.swift index 9d37af99..eb28086d 100644 --- a/Sources/SkipUI/SkipUI/Containers/List.swift +++ b/Sources/SkipUI/SkipUI/Containers/List.swift @@ -903,30 +903,6 @@ final class ListItemModifier: RenderModifier { return ListItemModifier(background: background, separator: separator) } } - -final class BadgeModifier: RenderModifier { - let badge: Text? - let prominence: BadgeProminence? - - init(badge: Text? = nil, prominence: BadgeProminence? = nil) { - self.badge = badge - self.prominence = prominence - super.init() - } - - static func combined(for renderable: Renderable) -> BadgeModifier { - var badge: Text? = nil - var prominence: BadgeProminence? = nil - renderable.forEachModifier { - if let badgeModifier = $0 as? BadgeModifier { - badge = badge ?? badgeModifier.badge - prominence = prominence ?? badgeModifier.prominence - } - return nil - } - return BadgeModifier(badge: badge, prominence: prominence) - } -} #endif #if false diff --git a/Sources/SkipUI/SkipUI/Containers/TabView.swift b/Sources/SkipUI/SkipUI/Containers/TabView.swift index ab2f7f54..115a3fdc 100644 --- a/Sources/SkipUI/SkipUI/Containers/TabView.swift +++ b/Sources/SkipUI/SkipUI/Containers/TabView.swift @@ -39,6 +39,8 @@ import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.NavigationRail import androidx.compose.material3.NavigationRailItem import androidx.compose.material3.NavigationRailItemDefaults +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox import androidx.compose.material3.contentColorFor import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults @@ -228,10 +230,16 @@ public struct TabView : View, Renderable { let tabRenderables = EvaluateContent(context: tabContext) let tabs: kotlin.collections.List = tabRenderables.map { let renderable = $0 as Renderable // Let transpiler understand type - if let tab = renderable.strip() as? Tab { + let badgeModifier = BadgeModifier.combined(for: renderable) + if var tab = renderable.strip() as? Tab { + if let badge = badgeModifier.badge { + tab.badge = badge + } return tab } else if let tabItemModifier = renderable.forEachModifier(perform: { $0 as? TabItemModifier }) as? TabItemModifier { - return Tab(content: { renderable.asView() }, label: { tabItemModifier.label }) + var tab = Tab(content: { renderable.asView() }, label: { tabItemModifier.label }) + tab.badge = badgeModifier.badge + return tab } else { return nil } @@ -743,6 +751,7 @@ public enum AdaptableTabBarPlacement : Hashable { public protocol TabContent : View { var isHidden: Bool { get set } var isDisabled: Bool { get set } + var badge: Text? { get set } } // SKIP @bridge @@ -850,6 +859,7 @@ public struct Tab : TabContent, Renderable { public var isHidden = false public var isDisabled = false + public var badge: Text? = nil #if SKIP @Composable override func Render(context: ComposeContext) { @@ -888,7 +898,7 @@ public struct Tab : TabContent, Renderable { renderable.Render(context: context) } } - Box(modifier: outerModifier, contentAlignment: androidx.compose.ui.Alignment.Center) { + let iconContent: (@Composable () -> ()) = { Box(modifier: Modifier.graphicsLayer(scaleX: Float(1.5), scaleY: Float(1.5)), contentAlignment: androidx.compose.ui.Alignment.Center) { // Default to a lighter symbol weight so tab icons approximate SwiftUI sizing if EnvironmentValues.shared._textEnvironment.fontWeight == nil { @@ -905,6 +915,22 @@ public struct Tab : TabContent, Renderable { } } } + + Box(modifier: outerModifier, contentAlignment: androidx.compose.ui.Alignment.Center) { + if let badge { + BadgedBox( + badge: { + Badge { + badge.Render(context: context) + } + } + ) { + iconContent() + } + } else { + iconContent() + } + } } #else public var body: some View { @@ -960,6 +986,7 @@ public struct TabSection : TabContent, Renderable { public var isHidden = false public var isDisabled = false + public var badge: Text? = nil #if SKIP @Composable override func Evaluate(context: ComposeContext, options: Int) -> kotlin.collections.List { @@ -971,6 +998,9 @@ public struct TabSection : TabContent, Renderable { if isDisabled { (renderable as? TabContent)?.isDisabled = true } + if let badge { + (renderable as? TabContent)?.badge = badge + } } return renderables } @@ -1078,29 +1108,34 @@ extension TabContent { return self } - @available(*, unavailable) public func badge(_ count: Int) -> some TabContent { - return self + var tabContent = self + tabContent.badge = count > 0 ? Text(String(count)) : nil + return tabContent } - @available(*, unavailable) public func badge(_ label: Text?) -> some TabContent { - return self + var tabContent = self + tabContent.badge = label + return tabContent } - @available(*, unavailable) public func badge(_ key: LocalizedStringKey) -> some TabContent { - return self + var tabContent = self + tabContent.badge = Text(key) + return tabContent } - @available(*, unavailable) public func badge(_ resource: LocalizedStringResource) -> some TabContent { - return self + var tabContent = self + tabContent.badge = Text(resource) + return tabContent } - @available(*, unavailable) public func badge(_ label: String) -> some TabContent { - return self + var tabContent = self + tabContent.badge = Text(label) + return tabContent } @available(*, unavailable) diff --git a/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift b/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift index c4bbcb31..3e69dd90 100644 --- a/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift +++ b/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift @@ -1487,6 +1487,30 @@ final class AspectRatioModifier: RenderModifier { } } +final class BadgeModifier: RenderModifier { + let badge: Text? + let prominence: BadgeProminence? + + init(badge: Text? = nil, prominence: BadgeProminence? = nil) { + self.badge = badge + self.prominence = prominence + super.init() + } + + static func combined(for renderable: Renderable) -> BadgeModifier { + var badge: Text? = nil + var prominence: BadgeProminence? = nil + renderable.forEachModifier { + if let badgeModifier = $0 as? BadgeModifier { + badge = badge ?? badgeModifier.badge + prominence = prominence ?? badgeModifier.prominence + } + return nil + } + return BadgeModifier(badge: badge, prominence: prominence) + } +} + final class DisabledModifier: EnvironmentModifier { let disabled: Bool From 7c2960b52e057876f1f3274582a88bb804931091 Mon Sep 17 00:00:00 2001 From: Damian Van de Kauter Date: Fri, 15 May 2026 12:42:15 +0200 Subject: [PATCH 2/3] Refactor Tab icon rendering and badge offset Rename iconContent to renderIcon and reformat Box initializer for clarity. Import Modifier.offset and apply a small offset (8.dp, -4.dp) to Badge so the badge is positioned correctly over tab icons. Ensure badge content is rendered with a white foregroundStyle for proper contrast. Minor cleanup of render calls to use renderIcon. --- Sources/SkipUI/SkipUI/Containers/TabView.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/SkipUI/SkipUI/Containers/TabView.swift b/Sources/SkipUI/SkipUI/Containers/TabView.swift index 115a3fdc..65b07a56 100644 --- a/Sources/SkipUI/SkipUI/Containers/TabView.swift +++ b/Sources/SkipUI/SkipUI/Containers/TabView.swift @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.safeDrawing @@ -898,8 +899,11 @@ public struct Tab : TabContent, Renderable { renderable.Render(context: context) } } - let iconContent: (@Composable () -> ()) = { - Box(modifier: Modifier.graphicsLayer(scaleX: Float(1.5), scaleY: Float(1.5)), contentAlignment: androidx.compose.ui.Alignment.Center) { + let renderIcon: (@Composable () -> ()) = { + Box( + modifier: Modifier.graphicsLayer(scaleX: Float(1.5), scaleY: Float(1.5)), + contentAlignment: androidx.compose.ui.Alignment.Center + ) { // Default to a lighter symbol weight so tab icons approximate SwiftUI sizing if EnvironmentValues.shared._textEnvironment.fontWeight == nil { EnvironmentValues.shared.setValues { @@ -920,15 +924,15 @@ public struct Tab : TabContent, Renderable { if let badge { BadgedBox( badge: { - Badge { - badge.Render(context: context) + Badge(modifier: Modifier.offset(x: 8.dp, y: -4.dp)) { + badge.foregroundStyle(Color.white).Render(context: context) } } ) { - iconContent() + renderIcon() } } else { - iconContent() + renderIcon() } } } From e5a6e8b19fbd0f2ca17f7ad5552aab2b0b1fecf9 Mon Sep 17 00:00:00 2001 From: Damian Van de Kauter <44820440+vdkdamian@users.noreply.github.com> Date: Fri, 15 May 2026 23:11:41 +0200 Subject: [PATCH 3/3] Mark .badge as supported in README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the detailed/partial-support row for `.badge` with a simple supported indicator (✅) in README.md. Removes the expandable details noting limited support on `List` and lack of `TabView` support, simplifying the support table entry. --- README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c7f7c1dd..6eeba21a 100644 --- a/README.md +++ b/README.md @@ -1463,16 +1463,8 @@ Support levels: .backgroundStyle - 🟡 - -
- .badge -
    -
  • Supported on List items
  • -
  • Not yet supported on TabView
  • -
-
- + ✅ + .badge 🟢