ivanscorral / ivanscorral/KoWorkout
[Feature] Implement Shared TagView Component
@ivanscorral is already working on this.
Since Oct 14, 2025.
- Dominant language
- Swift
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
# Implement Shared TagView Component
---
## Summary
Unify the tag UI used across exercise and workout views by introducing a single reusable SwiftUI component and refactoring existing usages to adopt it.
- Create a shared `TagView` SwiftUI component to render tags consistently.
- Replace ad-hoc tag UI in:
- `KoWorkout/Views/ExerciseListView.swift`
- `KoWorkout/Views/WorkoutDetail/WorkoutDetailView.swift`
- Place the component under a shared views directory for reuse across the app.
---
## Motivation
- Eliminate duplicated tag rendering logic and styling drift between views.
- Improve maintainability and velocity by centralizing styling and behavior.
- Ensure consistent accessibility, dynamic type, and dark mode support.
- Enable future tag variants (size, style, icon) in one place.
---
## Scope
**Include:**
- Add `TagView` component with configurable text, optional SF Symbol, and style/size variants.
- Support dynamic type, dark mode, and accessibility labels.
- Refactor the two target views to use `TagView`.
- Provide previews for common variants.
**Exclude:**
- Changes to data models, repositories, or navigation.
- New tag semantics (selection state, filter behavior).
- Localization expansion beyond existing app languages.
---
## Implementation Strategy
- Implement `TagView: View` with a simple public API:
- Initializers for text-only and text + systemImage.
- Style enum (e.g., `.filled`, `.tinted`, `.outlined`) with semantic colors.
- Size enum (e.g., `.small`, `.medium`) for padding, font, and corner radius.
- Use `Label` when an icon is provided to maintain spacing and accessibility.
- Apply semantic colors that adapt to color scheme and contrast.
- Add `TagViewStyle` configuration defaults to keep usage terse.
- Provide SwiftUI previews to demonstrate variants and validate appearance.
- Refactor both views to replace any Text + capsule/rounded rect stacks with `TagView`.
---
## Proposed Coverage / Functionality
### **Tag rendering**
- Displays provided text with optional SF Symbol.
- Applies consistent padding, background, border, corner radius, and font.
- Adapts colors for light/dark mode and increased contrast.
- Respects Dynamic Type and VoiceOver (accessible label mirrors content).
### **Variants**
- `.filled` — background color, contrasting text.
- `.tinted` — tint background with opacity; text uses tint color.
- `.outlined` — transparent fill, stroked border.
### **Sizes**
- `.small` and `.medium` with sensible defaults.
### **Integrations**
- `KoWorkout/Views/ExerciseListView.swift` uses `TagView` wherever a tag appears (e.g., muscle group, difficulty).
- `KoWorkout/Views/WorkoutDetail/WorkoutDetailView.swift` uses `TagView` for tag chips (e.g., duration, intensity, equipment).
### **Previews**
- Showcase each style/size with and without icons.
---
## Example Skeleton
**File:** `KoWorkout/Views/Shared/TagView.swift`
```swift
import SwiftUI
public enum TagStyle {
case filled(Color)
case tinted(Color)
case outlined(Color)
}
public enum TagSize {
case small
case medium
var font: Font {
switch self {
case .small: return .caption
case .medium: return .callout
}
}
var padding: EdgeInsets {
switch self {
case .small: return EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)
case .medium: return EdgeInsets(top: 6, leading: 10, bottom: 6, trailing: 10)
}
}
var cornerRadius: CGFloat { 10 }
var lineWidth: CGFloat { 1 }
}
public struct TagView: View {
private let text: String
private let systemImage: String?
private let style: TagStyle
private let size: TagSize
public init(_ text: String,
systemImage: String? = nil,
style: TagStyle = .tinted(.accentColor),
size: TagSize = .small) {
self.text = text
self.systemImage = systemImage
self.style = style
self.size = size
}
public var body: some View {
content
.font(size.font)
.padding(size.padding)
.background(background)
.overlay(overlay)
.clipShape(RoundedRectangle(cornerRadius: size.cornerRadius, style: .continuous))
.accessibilityLabel(Text(text))
}
@ViewBuilder
private var content: some View {
if let systemImage {
Label(text, systemImage: systemImage)
.labelStyle(.titleAndIcon)
.foregroundStyle(foregroundColor)
} else {
Text(text)
.foregroundStyle(foregroundColor)
}
}
private var foregroundColor: Color {
switch style {
case .filled(let color):
return color.isBright ? .black : .white
case .tinted(let color):
return color
case .outlined(let color):
return color
}
}
@ViewBuilder
private var background: some View {
switch style {
case .filled(let color):
color
case .tinted(let color):
color.opacity(0.15)
case .outlined:
Color.clear
}
}
@ViewBuilder
private var overlay: some View {
switch style {
case .outlined(let color):
RoundedRectangle(cornerRadius: size.cornerRadius, style: .continuous)
.stroke(color, lineWidth: size.lineWidth)
default:
EmptyView()
}
}
}
private extension Color {
var isBright: Bool {
#if canImport(UIKit)
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0
UIColor(self).getRed(&r, green: &g, blue: &b, alpha: nil)
return (0.299*r + 0.587*g + 0.114*b) > 0.6
#else
false
#endif
}
}
```
**Usage examples in views:**
- **ExerciseListView.swift**
`HStack { TagView("Strength", systemImage: "bolt.fill", style: .tinted(.orange)) }`
- **WorkoutDetailView.swift**
`HStack { TagView("45 min", systemImage: "clock", style: .filled(.accentColor), size: .medium) }`
---
## Acceptance Criteria
- [ ] Shared `TagView` compiles and previews render across variants.
- [ ] `ExerciseListView` and `WorkoutDetailView` use `TagView` for all tag UI.
- [ ] Visual styling is consistent between screens in light/dark modes.
- [ ] Dynamic Type and accessibility labels function as expected.
- [ ] Duplicate tag-related view code is removed from refactored files.
- [ ] Code compiles and runs on CI with no regressions.
---
## Files To Touch
- `KoWorkout/Views/Shared/TagView.swift` (new)
- `KoWorkout/Views/ExerciseListView.swift`
- `KoWorkout/Views/WorkoutDetail/WorkoutDetailView.swift`
- `KoWorkout.xcodeproj` project file updates if a new group is added for Shared views (if needed)
---
## Notes
- Align colors with existing design tokens or theme helpers if present; otherwise use `.accentColor` and semantic system colors.
- Prefer SF Symbols that exist for the minimum iOS target.
- If a local style system exists (e.g., custom Color/Font extensions), map `TagView` to those instead of hard-coded values.
- Consider future states (selected/disabled) when defining the style API, but do not implement them now unless already needed.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.