Skip to content

Instantly share code, notes, and snippets.

@AbodiDawoud
Created June 13, 2026 04:37
Show Gist options
  • Select an option

  • Save AbodiDawoud/2b1abcca5f7932331705d2feaee949db to your computer and use it in GitHub Desktop.

Select an option

Save AbodiDawoud/2b1abcca5f7932331705d2feaee949db to your computer and use it in GitHub Desktop.
import SwiftUI
struct CommandBloomPicker: View {
private let commands: [BloomCommand] = [
BloomCommand(title: "Draft"),
BloomCommand(title: "Polish"),
BloomCommand(title: "Send")
]
@State private var selectedIndex = 0
@State private var isExpanded = false
@State private var isHovered = false
@State private var pulse = false
private var selected: BloomCommand {
commands[selectedIndex]
}
var body: some View {
ZStack {
ForEach(commands.indices, id: \.self) { index in
BloomChip(
command: commands[index],
isSelected: selectedIndex == index,
isExpanded: isExpanded
) {
choose(index)
}
.offset(chipOffset(for: index))
.scaleEffect(isExpanded ? 1 : 0.62)
.blur(radius: isExpanded ? 0 : 8)
.opacity(isExpanded ? 1 : 0)
.animation(
.spring(response: 0.42, dampingFraction: 0.72)
.delay(Double(index) * 0.035),
value: isExpanded
)
}
Button {
toggleBloom()
} label: {
ZStack {
Capsule()
.fill(Color(white: 0.955))
.scaleEffect(x: pulse ? 1.018 : 1, y: pulse ? 0.975 : 1)
HStack(spacing: 8) {
Text(isExpanded ? "Choose" : selected.title)
.font(.system(size: 15, weight: .semibold, design: .rounded))
.foregroundStyle(Color.primary.opacity(0.82))
.frame(width: 64)
.id(isExpanded ? "choose" : selected.title)
.transition(.blurReplace)
Image(systemName: "chevron.up")
.font(.system(size: 11, weight: .bold))
.foregroundStyle(Color.primary.opacity(0.34))
.rotationEffect(.degrees(isExpanded ? 180 : 0))
.frame(width: 22, height: 22)
}
}
.frame(width: 142, height: 48)
}
.buttonStyle(BloomPressStyle(isHovered: isHovered))
.offset(y: isExpanded ? 42 : 0)
.onHover { hovered in
withAnimation(.spring(response: 0.26, dampingFraction: 0.78)) {
isHovered = hovered
}
}
}
.frame(width: 340, height: 184)
.animation(.spring(response: 0.44, dampingFraction: 0.70), value: selectedIndex)
.animation(.spring(response: 0.42, dampingFraction: 0.72), value: isExpanded)
.animation(.spring(response: 0.30, dampingFraction: 0.62), value: pulse)
}
private func chipOffset(for index: Int) -> CGSize {
guard isExpanded else {
return CGSize(width: 0, height: 16)
}
switch index {
case 0:
return CGSize(width: -92, height: -48)
case 1:
return CGSize(width: 0, height: -76)
default:
return CGSize(width: 92, height: -48)
}
}
private func toggleBloom() {
withAnimation(.spring(response: 0.38, dampingFraction: 0.68)) {
isExpanded.toggle()
pulse = true
}
runMomentaryPulse()
}
private func choose(_ index: Int) {
withAnimation(.spring(response: 0.40, dampingFraction: 0.68)) {
selectedIndex = index
isExpanded = false
pulse = true
}
runMomentaryPulse()
}
private func runMomentaryPulse() {
Task { @MainActor in
try? await Task.sleep(nanoseconds: 230_000_000)
withAnimation(.spring(response: 0.32, dampingFraction: 0.72)) {
pulse = false
}
}
}
}
private struct BloomCommand: Identifiable {
let id = UUID()
let title: String
}
private struct BloomChip: View {
let command: BloomCommand
let isSelected: Bool
let isExpanded: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
Text(command.title)
.font(.system(size: 13, weight: .semibold, design: .rounded))
.foregroundStyle(Color.primary.opacity(0.78))
.padding(.horizontal, 14)
.frame(height: 36)
.background(Color(white: isSelected ? 0.942 : 0.965).opacity(isSelected ? 1 : 0.78), in: Capsule())
}
.buttonStyle(.plain)
.disabled(!isExpanded)
}
}
private struct BloomPressStyle: ButtonStyle {
let isHovered: Bool
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.972 : isHovered ? 1.012 : 1)
.animation(.spring(response: 0.24, dampingFraction: 0.66), value: configuration.isPressed)
.animation(.spring(response: 0.28, dampingFraction: 0.78), value: isHovered)
}
}
#Preview {
CommandBloomPicker()
.padding(80)
.background(Color.white.opacity(0.95))
.preferredColorScheme(.light)
}
@AbodiDawoud

Copy link
Copy Markdown
Author
ScreenRecording.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment