Skip to content

feat(ui): add the 6 reusable UI components consumed by the primary screens#21

Merged
netqo merged 1 commit into
devfrom
feat/ui-components
May 27, 2026
Merged

feat(ui): add the 6 reusable UI components consumed by the primary screens#21
netqo merged 1 commit into
devfrom
feat/ui-components

Conversation

@netqo
Copy link
Copy Markdown
Owner

@netqo netqo commented May 27, 2026

Summary

Foundation for the next round of feature work (Lobby, Wallet, History, Profile shells). All 6 components are mockup-faithful, spec extracted directly from mockup/js/components.js, mockup/js/screens/*.js and mockup/styles.css.

Components

  • StackCard - generic bordered container. Optional leftAccent: Color? (3dp win/loss/etc. strip), elevated swaps surface tone, onClick toggles ripple. Default 16dp padding.
  • BalancePill - balance hero. Uppercased label + 36sp bold amount with tnum; isHidden swaps amount for six dots; onToggleVisibility wires the eye icon (or hides it for read-only rows like wallet's locked balance).
  • FilterChipRow<T> - generic single-select chip row. Active = violet fill + white text; inactive = transparent + txt-mid + line border. scrollable=true swaps the wrapped Row for a horizontal-scroll lane.
  • EmptyState - centered icon slot + title + message + optional action. Mockup's 56dp icon container, max-width 280dp, py-12 padding.
  • ErrorState - danger-tinted container with icon, title, message, required primary action plus optional secondary action and footer. Covers profile/news single-CTA, lobby dual-CTA, and news with "Last successful fetch ..." footer.
  • Skeleton - loading placeholder. Ports Tailwind animate-pulse: opacity 1->0.5->1 over 1000ms with cubic-bezier(0.4, 0, 0.6, 1). Caller decides shape via the Modifier.

Convention

  • const val tokens use SCREAMING_SNAKE_CASE; val tokens use PascalCase (matches ktlint).
  • All numeric literals live at the bottom of each file as named tokens; no inline magic numbers in the production composables.
  • detekt.yml: extend the @Preview exemption to MagicNumber too - preview composables intentionally use throwaway fractional widths.

Tests

Three instrumented Compose UI tests (run via emulator lane once card 15 lands):

  • FilterChipRowTest: labels uppercased; selection fires with typed key on click.
  • ErrorStateTest: title/message/primary render; primary click invokes callback; secondary action gates on both label+callback; footer uppercased.
  • BalancePillTest: label uppercased; hidden swaps to six dots; toggle flips state; eye absent when callback null.

Test plan

  • ./gradlew :app:assembleDebug green
  • ./gradlew :app:testDebugUnitTest green (34/34, no regressions)
  • ./gradlew ktlintCheck detekt green
  • Android Studio preview pane renders each component variant
  • CI mirrors the local result

…reens

The next round of feature work (Lobby, Wallet, History, Profile shells)
all share the same building blocks. Building them once here keeps each
screen PR focused on layout rather than re-implementing the same card
or chip styling five times. Every component below is mockup-faithful;
spec was extracted directly from mockup/js/components.js,
mockup/js/screens/*.js and mockup/styles.css.

* StackCard - generic bordered container (border-line bg-surface).
  Slots a single content lambda; optional leftAccent draws the 3dp
  win/loss/deposit/withdrawal strip; elevated swaps surface tone;
  onClick toggles ripple feedback. Defaults to 16dp content padding
  (p-4 in the mockup), overridable via contentPadding.

* BalancePill - balance hero (lobby + wallet headers). Uppercased
  label + 36sp bold amount with tabular-nums; isHidden swaps the
  amount for six dots; onToggleVisibility wires the eye icon when
  provided, hides the control otherwise (wallet's locked-balance row
  is read-only).

* FilterChipRow<T> - single-select chip row. Generic key type keeps
  callers type-safe (enums, sealed types, ids). Active chips use
  violet fill + white text + violet border; inactive ones are
  transparent with txt-mid label and line border. scrollable=true
  swaps the wrapped Row for a horizontal-scroll lane (news source
  filters use this; wallet tabs do not).

* EmptyState - centered icon + uppercased title + message + optional
  CTA. Icon slot lets the caller choose imageVector / drawable per
  context (history, KYC, filtered news). Reuses the mockup recipe:
  56dp icon container, max-width 280dp, vertical py-12 padding.

* ErrorState - danger-tinted container with icon, title, message,
  required primary action plus optional secondary action and footer
  text. Covers the three observed flavors: profile/news single-CTA,
  lobby dual-CTA (Retry + Use cache), news with footer ("Last
  successful fetch 2h ago"). Action buttons share an internal
  ErrorActionButton helper so the styling stays one source of truth.

* Skeleton - loading-placeholder primitive. Ports Tailwind's
  animate-pulse exactly: opacity 1 -> 0.5 -> 1 over 1000ms using
  cubic-bezier(0.4, 0, 0.6, 1). The caller decides the shape via the
  Modifier (Modifier.size(...), Modifier.fillMaxWidth().height(...),
  etc.), which matches the mockup's inline placeholders where every
  screen sizes the block to its target slot.

Each file ships @Preview composables for every state variant so the
Android Studio preview pane covers the design surface.

Convention adhered to per CR feedback:
* const val tokens use SCREAMING_SNAKE_CASE; plain val tokens use
  PascalCase (matches ktlint property-naming).
* Every numeric literal lives at the bottom of its file as a named
  token; no inline magic numbers in the production composables.
* detekt.yml: extend the @Preview exemption to MagicNumber too -
  preview composables intentionally use throwaway fractional widths
  to demonstrate variants.

Tests (instrumented, run via emulator lane once card 15 lands):
* FilterChipRowTest: every label renders uppercased; selection
  callback fires with the typed key on click.
* ErrorStateTest: title/message/primary render; primary click invokes
  callback; secondary action gates on both label+callback; footer is
  uppercased when supplied.
* BalancePillTest: label uppercased; hidden state swaps to six dots
  with the show/hide content description; toggle click flips state;
  eye control absent when callback is null.
@netqo netqo merged commit a79f42d into dev May 27, 2026
3 checks passed
@netqo netqo deleted the feat/ui-components branch May 27, 2026 10:18
netqo added a commit that referenced this pull request May 27, 2026
Wires the Lobby destination to a real screen rendering the success,
loading and error variants from the mockup. The success state uses the
StackCard/BalancePill/Skeleton/ErrorState components shipped in #21.

- Header: avatar, greeting and notifications button with unread badge.
- Balance hero: AVAILABLE label, PnL chip on its own slot (fades to
  alpha 0 on hide so the row never shifts), session stats on a second
  line, amount stacked with the masked dots so toggling visibility does
  not shift any surrounding layout, currency dropdown switching USDC
  and USDT, and the locked column on the right.
- Games grid: Roulette, Blackjack, Crash (LAST PLAYED, badge inset 8dp
  with the icon dropped 16dp and a shorter icon-to-title gap so the
  card height matches its row siblings), Mines and a full-width
  Coinflip card.
- Quick Actions: Deposit (down arrow, ok-green) and Withdraw (up
  arrow, warn-amber) routing to the wallet.
- Recent activity: three rows backed by StackCard with the win/loss
  left accent stripe.
- Nep FAB overlay aligned to the bottom-right of the scroll area.

LobbyPreviewData seeds the @Preview composables and the live nav-host
until the Firestore-backed ViewModel ships in a later card.

Promotes Modifier.gridBackground out of LoginScreenEffects into
ui/components so the lobby and the upcoming primary screens share it.
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