Bottom Tab Bar Visibility Adjustment

Nucleic-Session: 747527BF-AEE9-4939-BE70-A14F1F3BBBA8
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-05 18:04:57 -07:00
co-authored by Nucleic
parent 046364f44c
commit e5117af1ea
@@ -129,41 +129,15 @@ struct StartChatComposer: View {
} }
} }
/// The sheet the floating "+" opens: the glass `StartChatComposer` in a compact panel. `project` /// The floating "+" start-chat affordance: a Liquid Glass circle in the lower-right that reveals
/// scopes it to one project (a project page's "+"); nil offers the picker (Home, Sessions). /// the glass `StartChatComposer` as a floating bar over the page — the same entry box the open
struct NewChatSheet: View { /// session carries, not a full modal pane. Shared by Home, Sessions, and a project page (which
var project: WireProject? = nil /// scopes it to `project`), so all three offer the same control.
var dismiss: () -> Void
var body: some View {
NavigationStack {
ScrollView {
StartChatComposer(onStart: dismiss, lockedProject: project)
.padding()
}
.navigationTitle("New chat")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Cancel") { dismiss() }
}
}
}
// The composer needs only a fraction of the screen — a compact height by default, still
// expandable if the advanced options are opened.
.presentationDetents([.height(360), .large])
.presentationDragIndicator(.visible)
}
}
/// The floating "+" start-chat affordance: a Liquid Glass circle in the lower-right that opens the
/// `NewChatSheet`. Shared by Home, Sessions, and a project page (which scopes it to `project`), so
/// all three offer the same control instead of an inline composer card.
struct NewChatAffordance: ViewModifier { struct NewChatAffordance: ViewModifier {
@EnvironmentObject var store: RemoteStore @EnvironmentObject var store: RemoteStore
/// Scopes the composer to one project (project page); nil offers the picker (Home, Sessions). /// Scopes the composer to one project (project page); nil offers the picker (Home, Sessions).
var project: WireProject? = nil var project: WireProject? = nil
/// Owned by the caller so it can also open the sheet programmatically (iPad ⌘N on Home). /// Owned by the caller so it can also open the composer programmatically (iPad ⌘N on Home).
@Binding var isPresented: Bool @Binding var isPresented: Bool
/// The button only makes sense when this device can control a Mac and there's a project to /// The button only makes sense when this device can control a Mac and there's a project to
@@ -174,8 +148,9 @@ struct NewChatAffordance: ViewModifier {
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
// The "+" disc — hidden while the composer is up so it doesn't sit over the bar.
.overlay(alignment: .bottomTrailing) { .overlay(alignment: .bottomTrailing) {
if canStart { if canStart && !isPresented {
Button { isPresented = true } label: { Button { isPresented = true } label: {
Image(systemName: "plus") Image(systemName: "plus")
.font(.system(size: 22, weight: .semibold)) .font(.system(size: 22, weight: .semibold))
@@ -190,14 +165,30 @@ struct NewChatAffordance: ViewModifier {
.buttonStyle(.plain) .buttonStyle(.plain)
.padding(.trailing, 20) .padding(.trailing, 20)
.padding(.bottom, 6) .padding(.bottom, 6)
.transition(.scale.combined(with: .opacity))
.accessibilityLabel("New chat") .accessibilityLabel("New chat")
} }
} }
.sheet(isPresented: $isPresented) { // The composer itself, floating up from the bottom over a light scrim — no modal pane,
NewChatSheet(project: project) { isPresented = false } // no nav chrome, just the entry box. Tapping the scrim dismisses it.
.overlay {
if isPresented {
ZStack(alignment: .bottom) {
Rectangle()
.fill(.black.opacity(0.2))
.ignoresSafeArea()
.transition(.opacity)
.onTapGesture { isPresented = false }
StartChatComposer(onStart: { isPresented = false }, lockedProject: project)
.padding(.horizontal, 12)
.padding(.bottom, 8)
.transition(.move(edge: .bottom).combined(with: .opacity))
} }
} }
} }
.animation(.spring(response: 0.35, dampingFraction: 0.86), value: isPresented)
}
}
extension View { extension View {
/// Add the floating "+" start-chat affordance (glass circle → glass composer sheet). See /// Add the floating "+" start-chat affordance (glass circle → glass composer sheet). See