Skip to content

feat(android): DdsSegmentedControl — the web segmented control at its touch size - #149

Merged
jlc488 merged 2 commits into
mainfrom
feat/android-segmented-control
Nov 13, 2026
Merged

jlc488 merged 2 commits into
mainfrom
feat/android-segmented-control

Conversation

@jlc488

@jlc488 jlc488 commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

What

DdsSegmentedControl + DdsSegment for DDS for Android: one choice among a few visible options (a view switch, a filter, a type such as Person / Business). It is the web SegmentedControl (packages/dds-css/src/segmented-control.css, packages/dds-react|solid|vue/src/segmented-control.*) at the size the web uses on a touch screen.

  • Track: bg.subtle, 1dp border.strong edge, radius.md, 2dp inside it and 2dp between segments.
  • Segment: radius.sm, label type, text.secondary. Chosen: bg.default face, 1dp border.strong edge, level-1 shadow (DdsElevation.level1), text.primary, weight 600.
  • Height: one size. The track is 50dp = 48dp of touch + the 1dp edge above and below; a segment's face is 44dp (48 less 2dp above and below), and a content-width segment is at least 44dp wide. Large text mode uses the 56dp touch height. The web's md 40 / sm 32 are pointer heights; under pointer: coarse the web also gives both a 50 track, so Android has no size argument.
  • Width: content width by default. Every label holds its bold width in every state (the existing WeightStableLabel), so choosing never moves a neighbour; too many segments scroll inside the track. fullWidth = true shares the width equally (the pixels an equal split leaves over go to the first segments, so the last one ends on the inner edge) and ends a long label in an ellipsis. Labels never wrap.
  • States: pressed = the face's pressed tint (no ripple, as on the web), hover tint for a mouse, focus = a 2dp border.focus line inside the segment that replaces the chosen edge, enabled = false dims the whole track to 0.45, DdsSegment(enabled = false) dims one segment, readOnly can be focused and read but not changed, error turns the track edge status.danger and shows a spoken sentence under it (never the color alone, same line as DdsTextField).
  • Screen reader: a radio group (selectableGroup); each segment is a Role.RadioButton with its selected state; the unseen bold copy is not read. contentDescription names the group when there is no visible label.
  • segments must be non-empty with unique values (else IllegalArgumentException); a selected that matches none chooses none; onSelect is not called for the already chosen segment or while readOnly.

Catalog: a "Segmented control" section on the Parts tab (full width, content width with a disabled segment, read-only, disabled, error).

Docs: guide §12 (docs/mobile-native.md + .ko.md), android/CHANGELOG.md + .ko.md, both READMEs, backlog.

One decision for the owner: guide §10 no. 5 drops card and menu shadows on Android. This face keeps the web's level-1 shadow (the segmented look the owner picked); the 1dp edge sets it apart in the dark theme, where the shadow barely shows. Recorded as §10 no. 20, not yet confirmed.

First consumer

Qooroo's card-type toggle (개인 / 비즈니스, jlc488/qooroo, branch redesign/t6-editor) ships a local Compose copy, DdsStyleSegmented, written to the same spec. This replaces it.

Verified locally

  • ./gradlew testDebugUnitTest :dds-tokens:lintRelease :dds-compose:lintRelease :dds-compose-testing:lintRelease :dds-tokens:assembleRelease :dds-compose:assembleRelease :dds-compose-testing:assembleRelease :catalog:assembleDebug :dds-compose:assembleDebugAndroidTest on the final commit — BUILD SUCCESSFUL; every unit suite 0 failures / 0 errors, new SegmentedTest 4/4.
  • node --test tests/android-tokens.test.mjs 13/13, token/icon --check and check-android-sources.mjs clean.
  • Device tests on SM-F971N (Android 17): SegmentedOnDeviceTest 20/20 (failures="0" errors="0" skipped="0") — sizes, insets, no shift on choosing, full-width shares and end edge, scroll and no wrap, pixel checks of track edge / track / chosen face / chosen edge in light, dark and error, disabled opacity, screen-reader semantics, callbacks, read-only, disabled.
  • Looked at the catalog's "Segmented control" section on the same phone (unfolded, font scale 1.3) in light and dark: every track starts on the section's text line, full-width halves are equal, the disabled segment and the disabled control are dimmed, read-only is not, the error shows the danger edge and the sentence.

Release

The owner asked for this to ship: the second commit makes it 0.8.0 (VERSION_NAME, changelog, guide, READMEs, backlog). After merge, android-v0.8.0 goes on the merge commit.

…segmented control at its touch size

The web SegmentedControl (packages/dds-css/src/segmented-control.css) in Compose:
a bg.subtle track with a 1dp border.strong edge and radius md, 2dp inside and
between segments; the chosen segment a bg.default face with a 1dp edge, a
level-1 shadow and weight 600. One height, the web's touch height: a 50dp track
(48 of touch + the edge), 44dp faces, 56 of touch in large text mode.

Content width (default) holds the bold width in every state so choosing never
moves a neighbour, and too many segments scroll inside the track; fullWidth
shares the width equally (left-over pixels spread so the last segment ends on
the inner edge) and ellipsizes a long label. Read as a radio group. label /
contentDescription, error (edge color + a spoken sentence), readOnly, enabled,
and enabled per segment.

First consumer: Qooroo's card-type toggle, whose in-app DdsStyleSegmented copy
this replaces.
VERSION_NAME 0.8.0; the changelog, guide §10/§12, both READMEs and the backlog name 0.8.0. Checked on SM-F971N before release: SegmentedOnDeviceTest 20/20, and the catalog's Segmented control section looked at in light and dark at font scale 1.3.
@jlc488
jlc488 merged commit eb4c30d into main Nov 13, 2026
3 checks passed
@jlc488
jlc488 deleted the feat/android-segmented-control branch November 13, 2026 01:43
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.

1 participant