Skip to content

feat(list): add List8View, a label ↔ avatar + value row - #23

Merged
romchornyi merged 13 commits into
masterfrom
feat/list-8
Oct 19, 2026
Merged

romchornyi merged 13 commits into
masterfrom
feat/list-8

Conversation

@romchornyi

@romchornyi romchornyi commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

Adds the design system's list-8 row (Design system – iOS, Figma node 2881:69): a label on the left and, right-aligned, a round 24pt avatar followed by a value. dashwallet-ios needs it for the "To" row of the DashPay contact payment confirmation sheet (dashpay/dashwallet-ios#1214), where the recipient is shown with their avatar.

What was done?

  • List8View (Table List/List8View.swift, iOS 14 / macOS 11): List8View(label:value:avatar:). The label uses .subheadMedium + tertiaryText, the value .subhead + primaryText (the same tokens as List1View), with 14/12pt padding, a 20pt minimum gap and 8pt between avatar and value, all taken from the Figma node.
  • Avatar (optional): List8View(label:value:) gives a row without one. List8View(label:value:) { … } takes it as a @ViewBuilder, gives it a fixed 24×24 frame and clips it round. A builder that produces no view ({ if let avatar { … } } with no avatar) takes no space either. The library does not load images itself.
  • Long texts: both texts stay on one line. A long label next to a short value keeps its full width. When both are long they share the row, so the recipient never shrinks to a bare "…". The value truncates in the middle, which keeps the end of a username visible.
  • Alignment: label and value are centred on the avatar, so the row is 48pt tall. Without an avatar it is the text's height plus 12pt above and below.
  • Accessibility: VoiceOver reads the row as one element ("To, username") and skips the avatar.
  • Docs: a List8View section in docs/lists-and-rows.md, plus entries in README.md, docs/README.md and CLAUDE.md.

Screenshots

Figma list-8 Light Dark
Figma list-8 List8View light List8View dark

Rows, top to bottom: with an avatar, without an avatar, a long value, a long label beside a short value, and both long.

How Has This Been Tested?

  • swift build -c release and swift build (debug, previews included).
  • Rendered the views with ImageRenderer at 393, 375 and 320pt and compared the output with the Figma screenshot of 2881:69. Four states were checked: short/short, short label + long value, long label + short value, and long/long.
  • #Preview("Every state") covers the same four states.

Breaking Changes

None. This is a new component only.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

🤖 Generated with Claude Code

romchornyi and others added 6 commits October 19, 2026 14:57
The design system's list-8 (Design system – iOS, node 2881:69): a tertiary
Subhead-medium label on the left and, right-aligned, a round 24pt avatar
and a Subhead value. The avatar is the caller's view; the row sizes and
clips it, and VoiceOver reads the row as one element.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Label and value centre on the 24pt avatar instead of the label riding
2pt high; the label no longer uses fixedSize, so a long one cannot push
the row past its container; drop the no-op minHeight and truncation
mode; guard the preview with #if DEBUG and add a long-label state; list
List8View in docs/README.md and CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A long label no longer takes the width ahead of the value: the two share
it, so the recipient never shrinks to a bare ellipsis. The value
truncates in the middle, keeping the end of a username. Docs now say the
avatar gets a 24pt frame (the caller passes a view that fills it) and
that the row is 48pt, and drop the Dynamic Type claim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trailing group took every spare point (maxWidth .infinity), capping
the label at half the row even beside a three-letter value. A
Spacer(minLength: 20) between them gives each text its own width and
splits only when both are long. Preview gains the long-label/short-value
state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: dashpay/DashUIKit/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4055bb3b-a698-4ed2-9b32-e2d173662ce3
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

romchornyi and others added 5 commits October 19, 2026 15:21
List8View(label:value:) builds the row without an avatar: the value
stands alone at the trailing edge and the row is 44pt. The avatar
variant is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
init(label:value:avatar: Avatar?) drops the avatar slot for nil, where
an if inside the builder would still reserve 24pt and leave a gap, and
keeps rows with and without an avatar one type. Docs say when to use it
and how a no-avatar List8View differs from List1View; the summary line
calls the avatar optional; a long no-avatar preview state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A builder that draws nothing already takes no space (measured: an empty
if, EmptyView and the no-avatar init all lay out alike), so the Avatar?
init and optional storage were not needed. The no-avatar init delegates
to the builder one; docs show the if-let builder for recipients that
only sometimes have an avatar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a builder that produces no view (an if without an else) collapses;
an invisible view such as Color.clear keeps the slot. State the
no-avatar height plainly, drop a stray trailing comma, and preview the
if-false avatar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
romchornyi pushed a commit to dashpay/dashwallet-ios that referenced this pull request Oct 19, 2026
List8View is on dashpay/DashUIKit#23. Point the package at that branch
so this PR builds; switch back to master once it is merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@llbartekll llbartekll 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.

Approving. The component does what the description says, and I checked the claims that dashpay/dashwallet-ios#1214 relies on.

What I verified (at 44385a9)

  • swift build on macOS and xcodebuild for the iOS Simulator both succeed, with no warnings from List8View.swift.
  • Rendered every preview state at 320pt and 375pt, plus two more: a long label beside a long value, and a long display name. I used a copy of body with plain colours, because the asset colours don't load under swift test. A long label next to a short value keeps its full width. When both are long they split the row, and the value never shrinks to a bare …. With an avatar the row is 48pt.
  • An empty builder takes no space. The ideal width is 133pt for List8View(label:value:), for { if false { … } } and for { EmptyView() }, and 165pt with an avatar. That matches the 32pt shift in the docs.
  • VoiceOver: the avatar is hidden inside .accessibilityElement(children: .combine), so the row is one element.
  • dashFont uses fixed .system(size:) with no Dynamic Type, so the documented 48pt height holds.

Non-blocking

  • The value always truncates in the middle, and callers can't change that. See the inline comment. It matters for #1214, which usually passes a display name rather than a username.
  • No test covers the empty-builder behaviour the docs promise. A size comparison like the one above (fittingSize / sizeThatFits on .fixedSize() rows) would take about ten lines. No CI job here runs swift test, though, so this is optional.

Comment thread Sources/DashUIKit/Table List/List8View.swift Outdated
Comment thread Sources/DashUIKit/Table List/List8View.swift Outdated
romchornyi and others added 2 commits October 19, 2026 16:05
truncationMode defaults to .middle, which keeps a username's end; a
display name reads better cut at the tail. The modifier sits on the
inner Text, so it has to be a parameter rather than an outer modifier.
The preview's if-false avatar row says what it exercises.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tail-truncation preview uses a name long enough to cut; the empty
avatar row uses a plain if false (it does not warn); the List1View
comparison calls middle truncation the default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@romchornyi
romchornyi merged commit 426ab40 into master Oct 19, 2026
2 checks passed
@romchornyi
romchornyi deleted the feat/list-8 branch October 19, 2026 13:14
romchornyi pushed a commit that referenced this pull request Oct 23, 2026
Eight PRs landed on master since this branch was cut (#16–#23). Their new
files came with the old floor's annotations and two fallback branches, which
this merge brings onto the iOS 18 floor the same way the earlier commits did
for everything else:

- `@available(iOS 14, macOS 11)` / `(iOS 17, macOS 14)` dropped from
  `Criteria`, `SimpleSelect`, `List8View` and the `NavigationBarCircle*` /
  `NavigationBarButton` files, previews included.
- `NavigationBarButtonStyle` moved to `NavigationBarButton.swift` on master;
  the branch's copy in `NavigationBar.swift` is gone and the moved one uses
  `animation(_:value:)` unconditionally.
- `NavigationBarCircleMenu` always takes the bar's button style — the pre-16
  branch that dimmed the menu by hand is gone, and with it the unused
  `isEnabled`.
- `AddressFieldView`'s two new focus mirrors use the two-parameter
  `onChange`.
- The three new doc pages lose their `@available` line, and the circle-menu
  paragraph its "On iOS 16+".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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