Tab Bar Load Animation
Nucleic-Session: EEC60FAE-8368-49CA-9202-7026C12E7F8C Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -74,10 +74,13 @@ struct CompactRootView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Our own bottom tab bar for the compact shell — a plain `.bar`-material strip of five items,
|
/// Our own bottom tab bar for the compact shell — a floating Liquid Glass capsule of five items,
|
||||||
/// standing in for the system tab bar so `CompactRootView` can animate its visibility itself
|
/// standing in for the system tab bar so `CompactRootView` can animate its visibility itself (see
|
||||||
/// (see that view for why). The material bleeds into the home-indicator area; the items stay in
|
/// that view for why) while keeping the iOS 26 floating-glass look. Real `glassEffect` on iOS 26;
|
||||||
/// the safe region. The Sessions item carries the "needs you" badge the system tab item used to.
|
/// an ultra-thin material capsule with a hairline edge + soft shadow on earlier systems, matching
|
||||||
|
/// the app's `glassSurface`/`glassCircle`. It floats inset from the edges, above the home
|
||||||
|
/// indicator — it does not bleed to the screen edge. The Sessions item carries the "needs you"
|
||||||
|
/// badge the system tab item used to.
|
||||||
struct NucleicTabBar: View {
|
struct NucleicTabBar: View {
|
||||||
@Binding var selection: Int
|
@Binding var selection: Int
|
||||||
/// Sessions blocking on a human — the red badge on the Sessions item (was the tab item badge).
|
/// Sessions blocking on a human — the red badge on the Sessions item (was the tab item badge).
|
||||||
@@ -98,15 +101,13 @@ struct NucleicTabBar: View {
|
|||||||
itemButton(item)
|
itemButton(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 8)
|
.padding(.horizontal, 6)
|
||||||
.padding(.bottom, 2)
|
.padding(.vertical, 8)
|
||||||
// A hairline top separator, like the system bar's edge over scrolling content.
|
.glassBar()
|
||||||
.overlay(alignment: .top) {
|
// Float the capsule inset from the edges and just above the home indicator, rather than
|
||||||
Rectangle().fill(Color.primary.opacity(0.10)).frame(height: 0.5)
|
// filling the bottom edge like the old opaque bar.
|
||||||
}
|
.padding(.horizontal, 16)
|
||||||
// `.bar` is the material the system uses for tab/tool bars; bleed it into the bottom
|
.padding(.bottom, 4)
|
||||||
// unsafe area so it reaches the screen edge under the home indicator.
|
|
||||||
.background(.bar, ignoresSafeAreaEdges: .bottom)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func itemButton(_ item: Item) -> some View {
|
private func itemButton(_ item: Item) -> some View {
|
||||||
@@ -116,8 +117,8 @@ struct NucleicTabBar: View {
|
|||||||
} label: {
|
} label: {
|
||||||
VStack(spacing: 3) {
|
VStack(spacing: 3) {
|
||||||
Image(systemName: item.icon)
|
Image(systemName: item.icon)
|
||||||
.font(.system(size: 22, weight: selected ? .semibold : .regular))
|
.font(.system(size: 21, weight: selected ? .semibold : .regular))
|
||||||
.frame(height: 26)
|
.frame(height: 24)
|
||||||
.overlay(alignment: .topTrailing) {
|
.overlay(alignment: .topTrailing) {
|
||||||
if item.index == 1, needsYou > 0 {
|
if item.index == 1, needsYou > 0 {
|
||||||
Text(needsYou > 99 ? "99+" : "\(needsYou)")
|
Text(needsYou > 99 ? "99+" : "\(needsYou)")
|
||||||
@@ -133,6 +134,14 @@ struct NucleicTabBar: View {
|
|||||||
}
|
}
|
||||||
.foregroundStyle(selected ? Palette.accent : Color.secondary)
|
.foregroundStyle(selected ? Palette.accent : Color.secondary)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
// The selected item rides a soft accent-tinted capsule, echoing the system floating
|
||||||
|
// bar's highlighted tab.
|
||||||
|
.background {
|
||||||
|
if selected {
|
||||||
|
Capsule().fill(Palette.accent.opacity(0.15))
|
||||||
|
}
|
||||||
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
@@ -141,6 +150,22 @@ struct NucleicTabBar: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension View {
|
||||||
|
/// The floating tab bar's Liquid Glass capsule backing — the capsule twin of `glassSurface`.
|
||||||
|
/// Real `glassEffect` on iOS 26; an ultra-thin material capsule with a hairline edge and soft
|
||||||
|
/// shadow on earlier systems so it still lifts off the content.
|
||||||
|
@ViewBuilder
|
||||||
|
func glassBar() -> some View {
|
||||||
|
if #available(iOS 26.0, *) {
|
||||||
|
glassEffect(.regular, in: .capsule)
|
||||||
|
} else {
|
||||||
|
background(.ultraThinMaterial, in: Capsule())
|
||||||
|
.overlay(Capsule().strokeBorder(Color.primary.opacity(0.08), lineWidth: 1))
|
||||||
|
.shadow(color: .black.opacity(0.18), radius: 10, y: 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Regular (iPad sidebar + detail)
|
// MARK: - Regular (iPad sidebar + detail)
|
||||||
|
|
||||||
/// What the sidebar can select. Mirrors the Mac sidebar: a few global destinations plus the
|
/// What the sidebar can select. Mirrors the Mac sidebar: a few global destinations plus the
|
||||||
|
|||||||
Reference in New Issue
Block a user