Skip to content

Instantly share code, notes, and snippets.

@mireabot
Created February 9, 2025 19:21
Show Gist options
  • Save mireabot/baec7a38ba332781b33ffb7e2fb3e92f to your computer and use it in GitHub Desktop.
Save mireabot/baec7a38ba332781b33ffb7e2fb3e92f to your computer and use it in GitHub Desktop.
Banking Dashboard UI
import SwiftUI
struct BankindDashboard: View {
@State private var balance: Double = 3700.75
var body: some View {
VStack {
// Account Card
VStack {
// Account holder header
HStack {
HStack(spacing: 8) {
Image(systemName: "person.fill")
.font(.system(.callout))
.padding(8)
.background(
RoundedRectangle(cornerRadius: 5)
.strokeBorder(Color(hex: "#1218181B"), lineWidth: 1)
.background(.white)
)
.clipShape(RoundedRectangle(cornerRadius: 5))
Text("Adrian's Account")
.font(.system(.headline, weight: .medium))
}
Spacer()
Button(action: {}, label: {
Image(systemName: "ellipsis")
.font(.system(.callout))
.foregroundColor(.secondary)
})
}
.padding(16)
Divider()
// Balance info and action buttons
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 2) {
Text("Available balance")
.font(.system(.subheadline))
.foregroundColor(.secondary)
HStack(spacing: 0) {
Text("$").foregroundColor(.gray.opacity(0.5))
RollingCounter(
value: balance,
formattingStyle: .decimal
)
}
.font(.system(.largeTitle, weight: .semibold))
}
// Action buttons
HStack(spacing: 2) {
Button(action: {
withAnimation(.smooth) {
balance += 1450
}
}, label: {
HStack(spacing: 4) {
Image(systemName: "plus").foregroundColor(.secondary)
Text("Top up")
}
.frame(maxWidth: .infinity)
}).buttonStyle(CapsuledButtonStyle())
Button(action: {}, label: {
HStack(spacing: 4) {
Image(systemName: "paperplane").foregroundColor(.secondary)
Text("Transfer")
}
.frame(maxWidth: .infinity)
}).buttonStyle(CapsuledButtonStyle())
Button(action: {}, label: {
HStack(spacing: 4) {
Image(systemName: "arrow.left.arrow.right").foregroundColor(.secondary)
Text("Exchange")
}
.frame(maxWidth: .infinity)
}).buttonStyle(CapsuledButtonStyle())
}
}
.padding(16)
}
.background(Color.cardBackground)
.cornerRadius(16)
.padding(.horizontal, 16)
// Widgets
VStack(spacing: 16) {
HStack {
Text("Widgets")
.font(.system(.headline, weight: .medium))
.foregroundColor(.primary)
Spacer()
Button(action: {}, label: {
Image(systemName: "plus.circle").foregroundColor(.gray)
})
}
Grid {
GridRow(alignment: .top) {
ExpensesWidget()
MyCardsWidget()
}.frame(height: 180)
GridRow(alignment: .top) {
ReferalWidget()
DepositsWidget()
}.frame(height: 180)
}
}
.padding(.top, 16)
.padding(.horizontal, 16)
}
}
}
#Preview {
BankindDashboard()
}
// MARK: - Extensions
extension Color {
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (255, 0, 0, 0)
}
self.init(
.sRGB,
red: Double(r) / 255,
green: Double(g) / 255,
blue: Double(b) / 255,
opacity: Double(a) / 255
)
}
}
extension Color {
static let cardBackground = Color(hex: "F5F5F5")
static let border = Color(hex: "#1218181B")
}
// MARK: - Button Styles
struct CapsuledButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(.footnote, weight: .medium))
.padding(.horizontal, 14)
.padding(.vertical, 12)
.background(
Capsule()
.strokeBorder(Color.border, lineWidth: 1)
.background(.white)
.clipped()
)
.clipShape(Capsule())
}
}
// MARK: - Widgets
struct ExpensesWidget: View {
var body: some View {
VStack(alignment: .leading) {
Text("Expenses in Jul")
.font(.system(.subheadline, weight: .semibold))
.foregroundColor(.primary)
Spacer()
VStack(alignment: .leading, spacing: 8) {
Text("\(Text("$").foregroundColor(.gray.opacity(0.5)))\(1211.00, specifier: "%.2f")")
.font(.system(.title, weight: .semibold))
HStack(spacing: 4) {
Text("-$145 (12%)")
.foregroundColor(.green)
.font(.system(.footnote, weight: .semibold))
Image(systemName: "arrowtriangle.down.fill")
.foregroundColor(.green)
.font(.system(.caption, weight: .medium))
}
SegmentedBar(segments: [
(.orange, 0.5), // 50%
(.green, 0.3), // 30%
(.purple, 0.15), // 15%
(.pink, 0.05) // 5%
])
.frame(height: 8)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(16)
.background(
RoundedRectangle(cornerRadius: 16)
.strokeBorder(Color.border, lineWidth: 1)
.background(.white)
)
}
}
struct MyCardsWidget: View {
var body: some View {
VStack(alignment: .leading, spacing: 8) {
// Background gradient
ZStack {
LinearGradient(
gradient: Gradient(stops: [
.init(color: Color(red: 0.09, green: 0.047, blue: 0.11), location: 0),
.init(color: .white, location: 1)
]),
startPoint: .trailing,
endPoint: .leading
)
// Blurred vector shape
LinearGradient(
stops: [
Gradient.Stop(color: Color(red: 0.58, green: 0.17, blue: 0.9), location: 0.00),
Gradient.Stop(color: Color(red: 0.78, green: 0.27, blue: 0.27), location: 0.34),
Gradient.Stop(color: Color(red: 0.96, green: 0.35, blue: 0.18), location: 0.63),
Gradient.Stop(color: Color(red: 0.93, green: 0.75, blue: 0.57), location: 1.00),
],
startPoint: .bottomLeading,
endPoint: .topTrailing
)
.blur(radius: 25)
.opacity(0.8)
VStack(alignment: .leading, spacing: 8) {
Text("My cards")
.font(.system(.subheadline, weight: .semibold))
.foregroundColor(.white)
.padding(.top, 16)
Spacer()
// Card stack
ZStack {
CardView(isBackground: true)
.offset(x: 10, y: -10)
.opacity(0.6)
CardView(isBackground: false)
}
.padding(.bottom, 16)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16)
}
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(red: 0.96, green: 0.96, blue: 0.96))
.cornerRadius(16)
.clipped()
}
}
}
struct ReferalWidget: View {
var body: some View {
VStack(alignment: .leading) {
Text("Invite and earn!")
.font(.system(.subheadline, weight: .semibold))
.multilineTextAlignment(.leading)
.foregroundColor(.primary)
Spacer()
Text("Refer SmartBank to your friends and earn rewards")
.font(.system(.subheadline, weight: .regular))
.multilineTextAlignment(.leading)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(16)
.background(Color.cardBackground)
.cornerRadius(16)
}
}
struct DepositsWidget: View {
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 10, content: {
Text("You have \(Text("$\(0.00, specifier: "%.2f")").foregroundStyle(.tertiary)) in scheduled deposits every month")
.font(.system(.subheadline, weight: .semibold))
.multilineTextAlignment(.leading)
.foregroundColor(.primary)
Text("\(0) paychecks")
.font(.system(.subheadline, weight: .regular))
.multilineTextAlignment(.leading)
.foregroundColor(.secondary)
})
Spacer()
Button(action: {}, label: {
Text("Manage")
})
.buttonStyle(CapsuledButtonStyle())
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(16)
.background(
RoundedRectangle(cornerRadius: 16)
.strokeBorder(Color.border, lineWidth: 1)
.background(.white)
)
}
}
// MARK: - Helper Views
struct CardView: View {
let isBackground: Bool
var body: some View {
ZStack {
// Card background with gradient
RoundedRectangle(cornerRadius: 8)
.fill(
LinearGradient(
gradient: Gradient(colors: [
Color.white.opacity(0.4),
Color.white.opacity(0)
]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.background(.ultraThinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 8))
VStack {
Spacer()
HStack {
Text("SmartBank")
.font(.system(size: 10, weight: .bold))
.foregroundColor(.white)
Spacer()
// Card circles
HStack(spacing: -6) {
Circle()
.fill(.white.opacity(0.4))
.frame(width: 12, height: 12)
Circle()
.fill(.white.opacity(0.4))
.frame(width: 12, height: 12)
}
}
.padding(8)
}
}
.frame(width: isBackground ? 129 : 112, height: isBackground ? 80 : 70)
}
}
struct SegmentedBar: View {
let segments: [(color: Color, percentage: Double)]
var body: some View {
GeometryReader { geometry in
HStack(spacing: 2) {
ForEach(0..<segments.count, id: \.self) { index in
Rectangle()
.fill(segments[index].color)
.frame(width: geometry.size.width * segments[index].percentage)
}
}
.frame(height: 8)
.clipShape(RoundedRectangle(cornerRadius: 4))
}
}
}
struct RollingCounter: View {
let value: Double
let duration: Double
let formattingStyle: NumberFormatter.Style
@State private var animatedValue: Double
private let formatter: NumberFormatter
init(
value: Double,
duration: Double = 0.3,
formattingStyle: NumberFormatter.Style = .decimal
) {
self.value = value
self.duration = duration
self.formattingStyle = formattingStyle
self._animatedValue = State(initialValue: Double(value))
let formatter = NumberFormatter()
formatter.numberStyle = formattingStyle
self.formatter = formatter
}
var body: some View {
Text(formatter.string(from: NSNumber(value: animatedValue)) ?? "0")
.contentTransition(.numericText())
.onChange(of: value, { oldValue, newValue in
withAnimation(.spring(duration: duration)) {
animatedValue = Double(newValue)
}
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment