Files
kvitt/docs/ux/components.md
T
jacobh2 b72ac5bb9e UX sprint U1: three directions, IA, screens, flows, components, tokens, strings
Three visual directions (Skärgård, Kvitto, Kalas) as self-contained HTML pages with
every SPEC §13.3 screen in light and dark, Swedish, plus PNG exports and the exporter
script. Direction-independent docs: ia.md, screens.md, flows.md, components.md,
tokens.md (values for all three directions), strings.csv (key,en,sv,notes), and the
comparison with a recommendation in directions/README.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:27:51 +02:00

183 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Kvitt: components for `:core:design`
Status: U1 proposal (2026-10-17), direction-independent anatomy, states and API shape. The look (colours, shapes, type) comes from the chosen direction's tokens (`tokens.md`). In U2 this list moves into `design-spec.md` with final sizes. Names are Compose function names; every component takes a `Modifier` and gets screenshot tests in light and dark, Swedish and English, at 100 % and 200 % font scale (M6).
Rules for all components:
* Colours and type only from `KvittTheme` (Material 3 `ColorScheme` + `KvittColors` extension + `KvittTypography`); no literal values.
* Strings only from resources (`strings.csv` keys); components that need words take them as resolved `String` parameters or take the model value and resolve inside `:core:design` (shared strings live there, §14.7).
* Touch targets ≥ 48 dp (`minimumInteractiveComponentSize`), TalkBack `contentDescription` / `semantics` as stated per component.
* Material 3 Expressive APIs (`@ExperimentalMaterial3ExpressiveApi`) are used only behind a `:core:design` wrapper so the stable fallback named below can be swapped in one place. The implementing agent confirms each API's status from the official documentation at implementation time (CLAUDE.md › Fast-moving APIs).
## 1. Money and text
### `MoneyText`
Formats and shows an amount in öre via `MoneyFormatter` (§9.8). Never shows a sign.
* Params: `ore: Long`, `style: AmountStyle` (`Hero`, `Large`, `Medium`, `Small`), `tone: AmountTone` (`Neutral`, `Owed`, `Owing`, `Muted`), `maxLines` (default 1; never ellipsizes: overflow wraps to 2 lines or auto-steps down one type size for `Hero`/`Large`).
* Tabular figures always (`fontFeatureSettings = "tnum"`).
* Semantics: spoken form from `MoneyFormatter.spoken(ore)`: Swedish "3 250,00 kronor", English "3,250.00 Swedish kronor" (TalkBack reads "kr" and "SEK" poorly).
### `AmountInput`
Large amount entry for S-7, S-9, S-19. Parses per §9.6 (string → öre, never floating point).
* Params: `value: String`, `onValueChange`, `error: String?`, `size` (`Display`, `Field`).
* States: empty (hint "0,00"), typing, error (supporting text, error colour), disabled.
* Accepts `,` and `.`; displays the locale's separator; inserts no-break-space thousands grouping while typing.
### `AmountKeypad` (only if the chosen direction uses an in-app keypad)
3 × 4 grid: 1–9, decimal separator (locale), 0, backspace (TalkBack "Radera"); keys ≥ 64 dp tall; optional "Klar". Fallback: system keyboard `KeyboardType.Decimal`.
### `EffectLine`
The one-line effect from the viewer's side (§9.2) for an `ExpenseEffect`.
* Variants: `Lent(ore)` "Du lånade ut X" (owed tone) · `Borrowed(ore)` "Du lånade X" (owing tone) · `NotInvolved` "Inte inblandad" · `PaidForYourself` "Betalade för dig själv" (neutral).
### `BalanceWording`
Wording + amount for a `BalanceView` (§9.3).
* Variants: `Global` ("Anna är skyldig dig", "Du är skyldig Anna", "Ni är kvitt") and `Account` (… "Kvitt").
* Semantics: merged into one sentence.
## 2. People
### `PersonAvatar`
* Params: `user: User`, `size` (24, 32, 40, 56, 96 dp), `shape` (direction token; fallback `CircleShape`).
* Picture via Coil keyed by `user.id` + `pictureVersion` (AC-4.6); else initials (first letter of the first two words, uppercase, AC-4.5) on `avatar-1`/`avatar-2` chosen by a stable hash of the user id (the two colours are tokens).
* States: loading (initials shown), picture, initials, uploading (progress ring overlay).
* Semantics: the display name (or decorative when the name is next to it).
### `PersonChip`
Avatar 24 dp + name ("Du" for the viewer). Variants: `Display`, `Selectable` (S-7 payer toggle, S-7 full-split bearer). Selected state per M3 FilterChip.
### `PayerToggle`
Two `PersonChip`s in a connected group; one tap switches. Label "Betalt av". Semantics: "Betalt av: du. Dubbeltryck för att byta till Anna."
## 3. Categories and accounts
### `CategoryIcon`
Material Symbols icon (token from §9.7) on a container in `cat-<color>-container` with the icon in `cat-<color>`. Sizes 24/32/40/56 dp. Shape per direction (fallback circle or rounded square). Decorative when a name is shown next to it; else the category name.
### `CategoryChip`
`CategoryIcon` (24) + name. Variants: `Display`, `Selectable`. Archived variant adds "(arkiverad)".
### `CategoryGrid`
Picker grid in S-7 and S-19: tiles of icon + name, 4 columns at 411 dp (3 at 200 %), selected with outline/check. Only active categories.
### `CategoryIconPicker`, `ColorSwatchPicker`
S-15 editor: 30 icons (6 columns), 12 colour swatches (6 columns), each 48 dp, selected with a check mark (not colour alone). Swatches have names for TalkBack ("Grön", "Orange", …).
### `AccountRow`
Home and pickers: name, `BalanceWording.Account`, amount, secondary "11 utgifter i oktober". Variants: `Home`, `Picker` (no count), `Archived` (badge). 200 %: amount moves under the name.
## 4. Ledger
### `LedgerMonthHeader`
"Oktober · 7 457,90 kr" (household spend); sticky in `LazyColumn`. Year added when not the current year.
### `ExpenseRow`
`CategoryIcon`, description (1 line), date, `MoneyText` amount, `EffectLine`, optional account name (S-12), `SyncPendingMarker`. Heights per direction (56–88 dp). States: normal, pending, pressed. Semantics: "ICA Maxi Lindhagen, 1 247,60 kronor, 16 oktober, du lånade 623,80 kronor, väntar på synk".
### `SettlementRow`
`payments` icon, "Anna betalade dig 800,00 kr", "· 2 konton", date, pending marker.
### `SyncPendingMarker`
Small icon (`cloud_upload` or direction equivalent) in `pending`; label "Väntar på synk".
## 5. Balance and sync
### `BalanceHero`
Home's global balance (S-4) and the account header (S-5, smaller variant).
* Anatomy: both `PersonAvatar`s, `BalanceWording.Global`, `MoneyText(Hero)`, optional action button "Gör upp" / "Gör upp det här kontot".
* States: `Owed` (partner owes you: owed colours, button), `Owing` (you owe: owing colours, button), `Settled` ("Ni är kvitt"/"Kvitt", no amount, no button), `Empty` (no accounts: welcome text), `Loading` (skeleton).
* 200 %: wording and amount each take a full line; avatars may shrink to 40 dp.
* Semantics: one merged sentence + the button.
### `SyncStatusLine`
From `SyncSummary` + connectivity.
* Variants: `Synced` ("Synkad för 3 min sedan"), `Syncing` ("Synkar…"), `Offline` ("Offline"), with suffix "· 2 ändringar väntar på synk" (plural); `Warning` (rejections or clock skew) with icon and warning colour.
* Tap → S-16 (whole line is the 48 dp target). Relative time updates every minute.
### `UpdateBanner`
S-17 states: `Available` (text + "Uppdatera" + "Senare"), `Downloading` (determinate `LinearProgressIndicator` + percent), `Failed` (text + "Försök igen"). Fallback for Expressive wavy progress: standard linear.
### `NeedsConnectionHint`
Inline supporting text / banner "Kräver anslutning" with `cloud_off` icon; used on every online-only control (§9.9). Variant `Banner` for whole forms (S-6, S-14, S-15, S-19).
## 6. Split and settle
### `SplitModeSelector`
Single-select connected buttons (Expressive `ButtonGroup`/connected toggle buttons; fallback `SingleChoiceSegmentedButtonRow`): "Kontots standard 60/40", "Egen fördelning", "Exakta belopp", "Allt på en person". Wraps to 2 rows (Swedish), 4 rows at 200 % (a vertical radio list fallback when the row cannot fit).
### `RatioEditor`
Slider (0–100, step 1, haptic at 50) + two percent `TextField`s summing to 100 (two decimals) + preset chips (50/50, 60/40, 40/60). Used in S-6 and S-7 `ratio`. Semantics: slider "Jacobs andel, 60 procent".
### `ExactSplitEditor`
Two money fields: the typed one editable, the other read-only "beloppet minus …"; error "Andelen är större än beloppet".
### `FullSplitPicker`
Two `PersonChip`s: "Allt på Jacob" / "Allt på Anna".
### `SplitPreview`
"Du 206,00 kr · Anna 206,00 kr" + the effect the row will show. Data from `ExpenseRepository.preview`; hidden when null. Optional bar showing the two shares as proportions (decorative).
### `SplitPicker` (composite)
The S-7 split section: `SplitModeSelector` + the mode's editor + `SplitPreview`; collapsed state is a single chip "Delning: Kontots standard 50/50" which expands it in one tap.
### `DirectionSwitcher`
S-9 "Du betalar Anna" with avatars and an arrow; swap button `swap_horiz` (TalkBack "Byt riktning").
### `AllocationEditor`
S-9 list of `AllocationRow`s + "Lägg till konto" + `RemainingIndicator`.
* `AllocationRow`: account name, debt line ("Du är skyldig 3 084,50 kr"), `AmountInput(Field)`, remove (`close`, "Ta bort Huset från fördelningen"), warning line (`warning` icon + "Det här reglerar mer än som är skyldigt i Japan 2027") when `overSettles`. Variant `Fixed` (per-account settle: no remove).
* `RemainingIndicator`: "Kvar att fördela 0,00 kr" (neutral/positive), "250,00 kr kvar" (warning), "250,00 kr för mycket" (error), always words + colour.
## 7. Timeline
### `TimelineItem`
Anatomy: actor avatar (24 dp; system icon `autorenew` for recurring, `upload_file` for import), sentence, "· 16 okt. 19:40"; vertical connector between items.
* Variants: `Create`, `Update` (one line per `FieldChange`, sentence built from field + old → new, values formatted by `MoneyFormatter`/`DateFormatter`, ids already names), `Delete`, `PendingLocal` ("Din ändring väntar på synk", pending colour, dashed connector), `System` (recurring/import).
* Field names (sv/en) in `strings.csv` (`timeline_field_*`).
### `Timeline`
Column of `TimelineItem`, oldest first, newest last (AC-12.4), with the section title "Historik".
## 8. Charts (`:core:design`, Vico or Canvas fallback per §6.2)
### `ChartCard`
Container: title, optional subtitle (range), the chart, legend, and an invisible-but-spoken summary sentence (`semantics { contentDescription = summary }`). States: loaded, empty ("Inga utgifter i den här perioden"), loading (shimmer; only when the computation takes > 300 ms).
### `StackedBarChart`
Buckets on x (month "maj" or week "v. 41"), kronor on y (whole kronor, 3–5 gridlines, compact "20 tkr" labels in sv, "20k" in en), segments coloured by group (category colour or `chart-cat-n` for accounts, stable order = legend order), 2 dp gap between segments; selected bar highlighted, others dimmed; tap → callback(bucketKey). Each bar is a focusable semantics node ("September, 41 484 kronor"). Horizontal scroll when more than ~8 buckets; newest visible.
### `ChartLegend`
Wrapping row of swatch + label; only groups with data; static (filtering by legend is not in the spec).
### `BreakdownBarList`
Rows: icon/swatch, name, proportional bar, amount, percent (one decimal, AC-14.10). Sorted descending. Tap → callback(groupId). 200 %: bar on its own line.
### `FilterBar`
S-11 filters: range chip with menu (`DropdownMenu` of `RangePreset`s), three binary connected toggles. Wraps to two rows.
## 9. Scaffolding
### `KvittScaffold`
Top app bar (small, 64 dp; back or close), content, optional FAB slot, snackbar host, edge-to-edge insets.
### `KvittNavigationBar`
Three items (ia.md §1). Expressive `ShortNavigationBar` (fallback `NavigationBar`). Selected icon filled. Labels always visible.
### `AddExpenseFab`
Extended FAB "Lägg till utgift" (icon `add`), collapses to icon-only on scroll; Expressive size variants (fallback `ExtendedFloatingActionButton`).
### `EmptyState`
Icon, title, body, optional primary action. Used by S-3, S-4, S-5, S-11, S-16, S-19.
### `ConfirmDialog`
Title, body, confirm (destructive variant in `error`), dismiss.
### `SectionHeader`, `SettingsRow`, `CollapsibleSection`
Settings rows (icon, title, optional trailing value/badge, chevron), section headers ("Konton" + trailing action), "Arkiverade (n)" collapse with rotation of the chevron.
### `InviteCodeDisplay`, `InviteCodeField`
Code in three groups of four, mono/tabular, large; selectable; TalkBack spells characters ("7 K 3 M, Q X 9 D, 2 H P A"). Field variant normalises as typed (§8.3).
### `Skeleton`
Shimmer placeholders for the first-sync loading state only.