mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-06 08:13:49 +00:00
feat(bar): make the profile carousel mouse-navigable
The horizontal paging carousel only responded to trackpad swipe; a mouse could not move it. Add clickable prev/next arrows and make the page dots tappable -- each sets the scrollPosition binding (animated), so mouse users can page through a provider's profiles. Trackpad swipe is unchanged.
This commit is contained in:
1 parent
a0a2dd00fa
commit
f57eed8c34
1 file changed
+51
-10
@@ -291,16 +291,11 @@ struct BarMenuView: View {
|
||||
get: { selectedProfileByProvider[prov] ?? rows.first?.id },
|
||||
set: { selectedProfileByProvider[prov] = $0 ?? rows.first?.id }))
|
||||
.frame(height: carouselHeight(rows))
|
||||
// Page dots — one per profile in this provider's carousel.
|
||||
HStack(spacing: 6) {
|
||||
ForEach(rows) { row in
|
||||
let isCurrent = (selectedProfileByProvider[prov] ?? rows.first?.id) == row.id
|
||||
Circle()
|
||||
.fill(isCurrent ? theme.subscription : Color.secondary.opacity(0.3))
|
||||
.frame(width: 6, height: 6)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
// Page controls — clickable prev/next arrows + dots so the carousel
|
||||
// is navigable by MOUSE (click), not just by trackpad swipe.
|
||||
// Programmatically setting the scrollPosition binding pages the
|
||||
// ScrollView; withAnimation gives the same feel as a swipe.
|
||||
carouselControls(prov: prov, rows: rows)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,6 +359,52 @@ struct BarMenuView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Prev/next arrows + clickable dots for a provider's profile carousel, so it
|
||||
/// is navigable by MOUSE (click), not only by trackpad swipe. Selecting a page
|
||||
/// sets the scrollPosition binding, which scrolls the ScrollView to that card.
|
||||
@ViewBuilder private func carouselControls(prov: String, rows: [BarSummaryRow]) -> some View {
|
||||
let currentId = selectedProfileByProvider[prov] ?? rows.first?.id
|
||||
let curIdx = rows.firstIndex(where: { $0.id == currentId }) ?? 0
|
||||
HStack(spacing: 7) {
|
||||
pageArrow(systemName: "chevron.left", enabled: curIdx > 0) {
|
||||
if curIdx > 0 { selectPage(prov, rows[curIdx - 1].id) }
|
||||
}
|
||||
ForEach(rows) { row in
|
||||
Circle()
|
||||
.fill(currentId == row.id ? theme.subscription : Color.secondary.opacity(0.3))
|
||||
.frame(width: 6, height: 6)
|
||||
.padding(4) // larger mouse hit target than the 6pt dot
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { selectPage(prov, row.id) }
|
||||
}
|
||||
pageArrow(systemName: "chevron.right", enabled: curIdx < rows.count - 1) {
|
||||
if curIdx < rows.count - 1 { selectPage(prov, rows[curIdx + 1].id) }
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
|
||||
/// Animate the carousel to the given profile card (mouse click or dot tap).
|
||||
private func selectPage(_ prov: String, _ id: String) {
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
selectedProfileByProvider[prov] = id
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func pageArrow(
|
||||
systemName: String, enabled: Bool, action: @escaping () -> Void
|
||||
) -> some View {
|
||||
Button(action: action) {
|
||||
Image(systemName: systemName)
|
||||
.font(.system(size: 9, weight: .bold))
|
||||
.frame(width: 16, height: 16)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(!enabled)
|
||||
.opacity(enabled ? 0.7 : 0.25)
|
||||
}
|
||||
|
||||
/// Height of the tallest single card in a carousel — only one card is visible
|
||||
/// at a time, so the paged frame is sized to fit it WITHOUT reserving blank
|
||||
/// space beneath. Card = vertical padding (16) + title row (~22) + one bar per
|
||||
|
||||
Reference in new issue
Block a user