b72ac5bb9e
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>
183 lines
12 KiB
Markdown
183 lines
12 KiB
Markdown
# 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.
|