Migrate
This commit is contained in:
233
microservices/.agent/skills/swift-ui-components/SKILL.md
Normal file
233
microservices/.agent/skills/swift-ui-components/SKILL.md
Normal file
@@ -0,0 +1,233 @@
|
||||
---
|
||||
name: swift-ui-components
|
||||
description: Reusable SwiftUI components, Extensions, Validation patterns. Use for String/View extensions, custom UI components, hoặc khi cần reusable code.
|
||||
compatibility: "Swift 5.9+, iOS 17+, SwiftUI"
|
||||
metadata:
|
||||
author: Velik Ho
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# Swift UI Components & Extensions
|
||||
|
||||
Reusable components và Extensions cho SwiftUI Enterprise.
|
||||
|
||||
## When to Use This Skill / Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when:
|
||||
- Creating string validation / Tạo validation chuỗi
|
||||
- Building reusable views / Xây dựng views tái sử dụng
|
||||
- View modifiers / Thêm view modifiers
|
||||
- Localization patterns / Patterns đa ngôn ngữ
|
||||
|
||||
## String Extensions
|
||||
|
||||
```swift
|
||||
// Core/Extensions/String+Extensions.swift
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
||||
// MARK: - Localization
|
||||
|
||||
/// Localized string
|
||||
var localized: String {
|
||||
NSLocalizedString(self, comment: "")
|
||||
}
|
||||
|
||||
/// Localized with arguments
|
||||
func localized(with arguments: CVarArg...) -> String {
|
||||
String(format: localized, arguments: arguments)
|
||||
}
|
||||
|
||||
// MARK: - Validation
|
||||
|
||||
/// Valid email check
|
||||
var isValidEmail: Bool {
|
||||
let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
|
||||
return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: self)
|
||||
}
|
||||
|
||||
/// Valid Vietnamese phone
|
||||
var isValidVietnamesePhone: Bool {
|
||||
let regex = "^(0|\\+84)(3|5|7|8|9)[0-9]{8}$"
|
||||
return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: self)
|
||||
}
|
||||
|
||||
/// Valid password (8+ chars, upper, lower, digit)
|
||||
var isValidPassword: Bool {
|
||||
let regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d@$!%*?&]{8,}$"
|
||||
return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: self)
|
||||
}
|
||||
|
||||
/// Trimmed string
|
||||
var trimmed: String {
|
||||
trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
/// Is blank (empty or whitespace)
|
||||
var isBlank: Bool {
|
||||
trimmed.isEmpty
|
||||
}
|
||||
|
||||
// MARK: - Formatting
|
||||
|
||||
/// Masked email (j***@example.com)
|
||||
var maskedEmail: String {
|
||||
guard isValidEmail else { return self }
|
||||
let parts = split(separator: "@")
|
||||
guard parts.count == 2 else { return self }
|
||||
let local = String(parts[0])
|
||||
let domain = String(parts[1])
|
||||
if local.count <= 2 {
|
||||
return "\(local.prefix(1))***@\(domain)"
|
||||
}
|
||||
return "\(local.prefix(1))***\(local.suffix(1))@\(domain)"
|
||||
}
|
||||
|
||||
/// Format VND currency
|
||||
static func formatVND(_ amount: Double) -> String {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .currency
|
||||
formatter.currencyCode = "VND"
|
||||
formatter.currencySymbol = "₫"
|
||||
formatter.maximumFractionDigits = 0
|
||||
return formatter.string(from: NSNumber(value: amount)) ?? "\(Int(amount))₫"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## View Extensions
|
||||
|
||||
```swift
|
||||
// Core/Extensions/View+Extensions.swift
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
|
||||
/// Conditional modifier
|
||||
@ViewBuilder
|
||||
func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
|
||||
if condition {
|
||||
transform(self)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Hide keyboard
|
||||
func hideKeyboard() {
|
||||
UIApplication.shared.sendAction(
|
||||
#selector(UIResponder.resignFirstResponder),
|
||||
to: nil, from: nil, for: nil
|
||||
)
|
||||
}
|
||||
|
||||
/// Corner radius with specific corners
|
||||
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
|
||||
clipShape(RoundedCorner(radius: radius, corners: corners))
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom rounded corner shape
|
||||
struct RoundedCorner: Shape {
|
||||
var radius: CGFloat = .infinity
|
||||
var corners: UIRectCorner = .allCorners
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
let path = UIBezierPath(
|
||||
roundedRect: rect,
|
||||
byRoundingCorners: corners,
|
||||
cornerRadii: CGSize(width: radius, height: radius)
|
||||
)
|
||||
return Path(path.cgPath)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Components
|
||||
|
||||
### Primary Button
|
||||
|
||||
```swift
|
||||
/// Primary action button
|
||||
struct PrimaryButton: View {
|
||||
let title: String
|
||||
let action: () -> Void
|
||||
var isLoading: Bool = false
|
||||
var isDisabled: Bool = false
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: DesignSystem.spacingSM) {
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.tint(.white)
|
||||
}
|
||||
Text(title)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, DesignSystem.spacingMD)
|
||||
.background(isDisabled ? Color.gray : Color.accentColor)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(DesignSystem.cornerRadiusMD)
|
||||
}
|
||||
.disabled(isDisabled || isLoading)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Text Field with Validation
|
||||
|
||||
```swift
|
||||
/// Validated text field
|
||||
struct ValidatedTextField: View {
|
||||
let placeholder: String
|
||||
@Binding var text: String
|
||||
var isSecure: Bool = false
|
||||
var isValid: Bool = true
|
||||
var errorMessage: String?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: DesignSystem.spacingXS) {
|
||||
Group {
|
||||
if isSecure {
|
||||
SecureField(placeholder, text: $text)
|
||||
} else {
|
||||
TextField(placeholder, text: $text)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGray6))
|
||||
.cornerRadius(DesignSystem.cornerRadiusSM)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: DesignSystem.cornerRadiusSM)
|
||||
.stroke(isValid ? Color.clear : Color.red, lineWidth: 1)
|
||||
)
|
||||
|
||||
if let error = errorMessage, !isValid {
|
||||
Text(error)
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Reference / Tham Chiếu Nhanh
|
||||
|
||||
| Pattern | Usage |
|
||||
|---------|-------|
|
||||
| `string.localized` | NSLocalizedString wrapper |
|
||||
| `string.isValidEmail` | Email regex validation |
|
||||
| `string.trimmed` | Remove whitespace |
|
||||
| `.if(condition) { }` | Conditional modifier |
|
||||
| `PrimaryButton` | Loading + disabled support |
|
||||
|
||||
## Resources / Tài Nguyên
|
||||
|
||||
- [Swift Enterprise Architect](../swift-enterprise-architect/SKILL.md)
|
||||
- [Swift Security](../swift-security/SKILL.md) - Validation patterns
|
||||
Reference in New Issue
Block a user