Skip to content

feat: add primitives and layout utilities#3

Merged
romchornyi merged 2 commits into
masterfrom
import/primitives
Jul 10, 2026
Merged

feat: add primitives and layout utilities#3
romchornyi merged 2 commits into
masterfrom
import/primitives

Conversation

@romchornyi

@romchornyi romchornyi commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Second migration PR — low-level building blocks that sit on top of Foundation.

What's included

  • Button/DashButton
  • ViewModifiers/MenuViewModifier
  • Components/Geometry/ — FrameReader, LocationReader, ScaleToFitWidth, ScrollViewWithOnScrollChanged
  • Components/Icons/XmarkIcon
  • Components/Illustrations/ — Error, Loading, Success

Notes

Summary by CodeRabbit

  • New Features
    • Added customizable buttons with size, style, icons, loading, disabled, and full-width states.
    • Added success and error illustrations, plus configurable loading spinners.
    • Added a reusable close icon.
    • Added menu styling with rounded backgrounds, padding, and shadows.
    • Added tools for responsive text scaling and tracking view position, size, and scroll changes.
    • Added a scroll view that reports offset changes for interactive interfaces.

Add the low-level building blocks on top of Foundation:
- Button/DashButton
- ViewModifiers/MenuViewModifier
- Components/Geometry (FrameReader, LocationReader, ScaleToFitWidth, ScrollViewWithOnScrollChanged)
- Components/Icons/XmarkIcon
- Components/Illustrations (Error, Loading, Success)
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@romchornyi, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 39b678a4-5dc3-4cb6-bf26-d8ce4c31bfd5

📥 Commits

Reviewing files that changed from the base of the PR and between 1ca9515 and 83d23e0.

📒 Files selected for processing (2)
  • Sources/DashUIKit/Button/DashButton.swift
  • Sources/DashUIKit/Components/Illustrations/LoadingIllustration.swift
📝 Walkthrough

Walkthrough

Adds reusable SwiftUI buttons, geometry readers, scroll offset reporting, width scaling, icons, illustrations, loading indicators, and menu styling to DashUIKit.

Changes

SwiftUI controls and layout utilities

Layer / File(s) Summary
DashButton component
Sources/DashUIKit/Button/DashButton.swift
Adds configurable button sizes, visual styles, enabled/loading states, optional icons, width filling, and previews.
Geometry measurement readers
Sources/DashUIKit/Components/Geometry/FrameReader.swift, Sources/DashUIKit/Components/Geometry/LocationReader.swift
Adds frame and center-point measurement readers with transparent view modifiers and previews.
Scroll offset reporting
Sources/DashUIKit/Components/Geometry/ScrollViewWithOnScrollChanged.swift
Adds a scroll view that reports probe position changes through a named coordinate space.
Width-constrained scaling
Sources/DashUIKit/Components/Geometry/ScaleToFitWidth.swift
Measures natural content size and scales single-line content to fit available width with a minimum scale.
Illustrations, icon, and menu styling
Sources/DashUIKit/Components/Icons/XmarkIcon.swift, Sources/DashUIKit/Components/Illustrations/*, Sources/DashUIKit/ViewModifiers/MenuViewModifier.swift
Adds a configurable Xmark icon, success/error illustrations, animated loading views, and rounded menu styling.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ScrollViewWithOnScrollChanged
  participant LocationReader
  participant Callback
  ScrollViewWithOnScrollChanged->>LocationReader: place offset probe in named coordinate space
  LocationReader->>Callback: report probe center
  Callback->>ScrollViewWithOnScrollChanged: update offset state
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the PR’s main addition of UI primitives and layout utilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch import/primitives

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
Sources/DashUIKit/Components/Geometry/ScaleToFitWidth.swift (1)

37-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer let over var for minScale.

minScale is set at initialization and never mutated. Using let better expresses intent and prevents accidental mutation.

Proposed fix
 `@available`(iOS 14, macOS 11, *)
 public struct ScaleToFitWidth: ViewModifier {
-    public var minScale: CGFloat = 0.35
+    public let minScale: CGFloat
 
     `@State` private var naturalSize: CGSize = .zero
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/Geometry/ScaleToFitWidth.swift` around lines 37
- 39, Change the minScale property in ScaleToFitWidth from var to let,
preserving its existing default value and initialization behavior.
Sources/DashUIKit/Components/Geometry/LocationReader.swift (1)

53-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider making readingLocation public for consistency with readingFrame.

readingFrame in FrameReader.swift is declared in a public extension View, but readingLocation here is in a non-public extension View (internal). The doc comment on LocationReader says "Reach for it through readingLocation", suggesting it's intended for general use. If external consumers should have access, add public.

Proposed access level fix
 `@available`(iOS 14, macOS 11, *)
-public extension View {
+extension View {
 
     /// Reports this view's center point — in `coordinateSpace` — on appear and on every change.
     /// Installed as a zero-sized, transparent background so it never affects layout.
+    public func readingLocation(
-    func readingLocation(
         coordinateSpace: CoordinateSpace = .global,
         onChange: `@escaping` (_ location: CGPoint) -> Void
     ) -> some View {
         background(LocationReader(coordinateSpace: coordinateSpace, onChange: onChange))
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/Geometry/LocationReader.swift` around lines 53 -
64, Make readingLocation publicly accessible to match readingFrame and its
documented intended use. Update the View extension containing readingLocation to
a public extension, or explicitly mark readingLocation as public while
preserving its existing API and availability.
Sources/DashUIKit/ViewModifiers/MenuViewModifier.swift (1)

21-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding a convenience View extension for ergonomic usage.

Applying the modifier currently requires .modifier(MenuViewModifier(...)). A short extension method would improve call-site readability.

✨ Optional convenience extension
+extension View {
+    public func menuStyle(shadowRadius: CGFloat = 10, innerPadding: CGFloat = 6) -> some View {
+        modifier(MenuViewModifier(shadowRadius: shadowRadius, innerPadding: innerPadding))
+    }
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/ViewModifiers/MenuViewModifier.swift` around lines 21 - 39,
Add a public View extension near MenuViewModifier with a convenience method that
applies MenuViewModifier, exposing shadowRadius and innerPadding with the same
defaults and returning the modified view. This should allow callers to use the
modifier directly through the extension instead of invoking
modifier(MenuViewModifier(...)).
Sources/DashUIKit/Components/Illustrations/ErrorIllustration.swift (1)

21-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting a shared illustration base to reduce duplication.

ErrorIllustration and SuccessIllustration are structurally identical except for the background color and asset name. A parameterized base view would eliminate the copy-paste and make adding future illustrations trivial.

♻️ Proposed shared base view
+@available(iOS 14, macOS 11, *)
+struct CircleIllustration: View {
+    let background: Color
+    let iconName: String
+    let iconHeight: CGFloat
+
+    var body: some View {
+        ZStack {
+            background
+            Image(dash: .custom(iconName, bundle: .dashUIKit))
+                .resizable()
+                .scaledToFit()
+                .frame(maxHeight: iconHeight)
+        }
+        .frame(width: 90, height: 90)
+        .clipShape(Circle())
+    }
+}

 `@available`(iOS 14, macOS 11, *)
 public struct ErrorIllustration: View {
     public init() {}
     public var body: some View {
-        ZStack {
-            Color.dash.red
-            Image(dash: .custom("illustration-xmark", bundle: .dashUIKit))
-                .resizable()
-                .scaledToFit()
-                .frame(maxHeight: 29)
-        }
-        .frame(width: 90, height: 90)
-        .clipShape(Circle())
+        CircleIllustration(background: .dash.red, iconName: "illustration-xmark", iconHeight: 29)
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/DashUIKit/Components/Illustrations/ErrorIllustration.swift` around
lines 21 - 37, Extract the duplicated layout from ErrorIllustration and
SuccessIllustration into a shared parameterized base View, accepting the
background color and illustration asset name while preserving the existing
sizing, clipping, and image configuration. Refactor both illustrations to
delegate to this base, keeping their public initializers and existing visual
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/DashUIKit/Button/DashButton.swift`:
- Around line 170-181: Add .background(style.backgroundColor(isEnabled:
isEnabled)) to the strokeGray branch of styledContent, positioned after the
frame modifier and before the overlay (or clipShape as appropriate), so the
disabled background is rendered consistently with the else branch.
- Line 175: Replace both .clipShape(.rect(cornerRadius: size.radius)) calls in
DashButton with RoundedRectangle(cornerRadius: size.radius, style: .continuous)
to preserve iOS 14 and macOS 11 compatibility.

In `@Sources/DashUIKit/Components/Illustrations/LoadingIllustration.swift`:
- Around line 76-84: Validate and clamp spokeCount in LoadingIllustration.init
to at least 1 before assigning it, so the normalized value is used by both the
ForEach range and onReceive phase modulo; update the timer interval to use the
stored clamped count rather than independently guarding only the divisor.
- Line 74: Refactor LoadingIllustration’s timer lifecycle so
Timer.publish/autoconnect is not created or started during initialization.
Update the timer properties and body’s onReceive/onAppear/onDisappear handling
to explicitly start the timer only when the view appears and stop or cancel it
when the view disappears, ensuring recreated view structs do not leave active
timers.

---

Nitpick comments:
In `@Sources/DashUIKit/Components/Geometry/LocationReader.swift`:
- Around line 53-64: Make readingLocation publicly accessible to match
readingFrame and its documented intended use. Update the View extension
containing readingLocation to a public extension, or explicitly mark
readingLocation as public while preserving its existing API and availability.

In `@Sources/DashUIKit/Components/Geometry/ScaleToFitWidth.swift`:
- Around line 37-39: Change the minScale property in ScaleToFitWidth from var to
let, preserving its existing default value and initialization behavior.

In `@Sources/DashUIKit/Components/Illustrations/ErrorIllustration.swift`:
- Around line 21-37: Extract the duplicated layout from ErrorIllustration and
SuccessIllustration into a shared parameterized base View, accepting the
background color and illustration asset name while preserving the existing
sizing, clipping, and image configuration. Refactor both illustrations to
delegate to this base, keeping their public initializers and existing visual
behavior unchanged.

In `@Sources/DashUIKit/ViewModifiers/MenuViewModifier.swift`:
- Around line 21-39: Add a public View extension near MenuViewModifier with a
convenience method that applies MenuViewModifier, exposing shadowRadius and
innerPadding with the same defaults and returning the modified view. This should
allow callers to use the modifier directly through the extension instead of
invoking modifier(MenuViewModifier(...)).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6409b66f-d215-4f3d-9d97-00ca4a09ef96

📥 Commits

Reviewing files that changed from the base of the PR and between 3273904 and 1ca9515.

📒 Files selected for processing (10)
  • Sources/DashUIKit/Button/DashButton.swift
  • Sources/DashUIKit/Components/Geometry/FrameReader.swift
  • Sources/DashUIKit/Components/Geometry/LocationReader.swift
  • Sources/DashUIKit/Components/Geometry/ScaleToFitWidth.swift
  • Sources/DashUIKit/Components/Geometry/ScrollViewWithOnScrollChanged.swift
  • Sources/DashUIKit/Components/Icons/XmarkIcon.swift
  • Sources/DashUIKit/Components/Illustrations/ErrorIllustration.swift
  • Sources/DashUIKit/Components/Illustrations/LoadingIllustration.swift
  • Sources/DashUIKit/Components/Illustrations/SuccessIllustration.swift
  • Sources/DashUIKit/ViewModifiers/MenuViewModifier.swift

Comment thread Sources/DashUIKit/Button/DashButton.swift
Comment thread Sources/DashUIKit/Button/DashButton.swift Outdated
Comment thread Sources/DashUIKit/Components/Illustrations/LoadingIllustration.swift Outdated
- DashButton: apply background in the strokeGray branch so the disabled
  background color is rendered (was omitted)
- DashButton: use RoundedRectangle(cornerRadius:style:.continuous) instead of
  .rect(cornerRadius:) (iOS 17+) to keep the iOS 14 availability surface
- LoadingSpinner: clamp spokeCount to >= 1 to avoid division-by-zero / negative
  range crashes
- LoadingSpinner: connect the timer on appear and cancel it on disappear
  instead of autoconnecting in init

@HashEngineering HashEngineering left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@romchornyi romchornyi merged commit 19d5413 into master Jul 10, 2026
1 check passed
@romchornyi romchornyi deleted the import/primitives branch July 15, 2026 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants