Package-level declarations

Types

Link copied to clipboard
data class AdaptiveTokens(val screenPadding: Dp, val itemSpacing: Dp, val sectionSpacing: Dp, val toolbarHeight: Dp, val gridColumns: Int, val overscanPadding: Dp, val focusScale: Float, val title: TextUnit, val sectionTitle: TextUnit, val body: TextUnit, val caption: TextUnit)

The subset of DesignTokens that should breathe with the surface. Everything else — shapes, elevation, motion and the a11y-constant Size.minTouch — deliberately stays static in DesignTokens: a 48dp touch target is 48dp on every device, and corner radius is brand identity, not a function of viewport width.

Link copied to clipboard

Design tokens — the single source of spacing / shape / size values. Consume these instead of hard-coded dp so the whole fleet shares one visual rhythm (4dp scale).

Link copied to clipboard

Viewing posture and input model. Not derivable from width — it comes from the platform, via rememberFormFactor.

Link copied to clipboard

Default seam: holds the choice for the process lifetime only.

Link copied to clipboard

Which navigation affordance a surface should show.

Link copied to clipboard

How a single OTP cell is drawn.

Link copied to clipboard

Resolved visual state of one cell, in precedence order.

Link copied to clipboard
Link copied to clipboard
data class OtpFieldStyle(val cellSize: Dp, val spacing: Dp, val textStyle: TextStyle, val shape: Shape, val borderWidth: Dp, val cellShape: OtpCellShape, val activeColor: Color, val filledColor: Color, val emptyColor: Color, val errorColor: Color)
Link copied to clipboard

The lifecycle state of a single timeline node.

Link copied to clipboard
class ThemeController(store: ThemeStore = InMemoryThemeStore, defaultDark: Boolean = true)

App-wide theme state holder (Mileway ThemeController idiom). Dark-first: defaults to true when the user hasn't chosen. Reads the persisted choice from store at construction and writes every change back, so the Settings toggle survives process death once a persistent ThemeStore is bound. Wasm-safe. Bound as a Koin singleton so the shell and the Settings screen share one instance.

Link copied to clipboard
interface ThemeStore

Persistence seam for the theme choice. The default is in-memory; core:data binds a DataStore-backed implementation in Koin (same interface → no shell/settings changes), which is how the dark-mode choice survives process death.

Link copied to clipboard
data class TimelineStep(val title: String, val subtitle: String?, val state: StepState, val payload: ImmutableList<Pair<String, String>>)

One node in a StepTimeline: a titled step with an optional subtitle, its state, and an ordered payload of key/value rows revealed beneath the step.

Link copied to clipboard

How much room there is, within a given FormFactor. The thresholds differ per form factor (see windowTypeFor) — Expanded means "a big TV" on FormFactor.Tv and "a tablet in landscape" on FormFactor.Handheld.

Properties

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

ponytail: defaults to Compact handheld rather than error("no AdaptiveTheme"). A hard error is louder, but it also breaks every @Preview and Compose UI test that renders a leaf composable without the app shell around it. Compact is the safe wrong answer — phone metrics on a television look cramped, not broken. Switch to error() if a real screen ever ships un-wrapped by accident.

Link copied to clipboard
Link copied to clipboard

Screen-level enter transition: a fade with a barely-there scale-up. Deliberately plain EnterTransition values rather than the AnimatedContentTransitionScope<NavBackStackEntry>.() -> lambdas the idiom usually ships as — that signature drags in androidx.navigation, which this module does not depend on and which has no Compose Multiplatform equivalent on every target.

Link copied to clipboard

Screen-level exit transition. Mirror of screenEnter.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

Functions

Link copied to clipboard
fun AdaptiveTheme(formFactor: FormFactor = rememberFormFactor(), content: @Composable () -> Unit)

Provides AdaptiveTokens for the current surface. Wrap the app shell once, then read LocalAdaptiveTokens.current.screenPadding anywhere below.

fun AdaptiveTheme(formFactor: FormFactor, windowType: WindowType, content: @Composable () -> Unit)

Escape hatch for consumers that already resolve a window size class upstream (Android currentWindowAdaptiveInfo(), a desktop window listener) and don't want a second BoxWithConstraints subcomposition at the root.

Link copied to clipboard
fun AnimatedCounter(target: Int, modifier: Modifier = Modifier, suffix: String = "", style: TextStyle = MaterialTheme.typography.headlineSmall, reducedMotion: Boolean = false)

Counts up from zero to target on first composition — e.g. "62 gateways integrated" or a "94%" success rate. suffix is appended verbatim after the number (e.g. "%").

Link copied to clipboard
fun <T> byFormFactor(default: T, watch: T = default, handheld: T = default, desktop: T = default, tv: T = default): T

byFormFactor against the ambient LocalFormFactor.

fun <T> byFormFactor(formFactor: FormFactor, default: T, watch: T = default, handheld: T = default, desktop: T = default, tv: T = default): T

Picks a per-surface value, overriding only the surfaces that actually differ.

Link copied to clipboard
fun <T> byWindow(compact: T, medium: T, expanded: T): T

byWindow against the ambient LocalWindowType.

fun <T> byWindow(windowType: WindowType, compact: T, medium: T, expanded: T): T

Picks a per-bucket value. This is the whole object FooDefaults { fun width(w: WindowType) } idiom compressed to one call — the source this came from wrote that when block out longhand thirty-one times across ten files (MovieCardDefaults, BannerDefaults, ButtonDefaults, CastCardDefaults, …), which is why none of those files were worth copying individually and this function is.

Link copied to clipboard
fun ComingSoonDialog(onDismiss: () -> Unit, title: String = "Coming soon", message: String = "This is coming in a future release.")

The single deferred-feature affordance. Every web feature not in v1 opens this instead of a real screen (see the "95% later" policy). One component so the message/styling stays consistent.

Link copied to clipboard
fun Modifier.contentWidth(max: Dp = DesignTokens.Spacing.contentMaxWidth): Modifier

Caps a layout column at max and centres it. Wider than readableWidth on purpose: a grid of cards or a row of stat tiles stays legible at widths where a paragraph would not.

Link copied to clipboard
fun EmptyState(title: String, modifier: Modifier = Modifier, body: String? = null, icon: ImageVector? = null, action: @Composable () -> Unit? = null)

The succeeded-but-nothing-here sibling of LoadingState. Distinct from ErrorState on purpose: "no search results" is a normal outcome and must not be painted in the error colour.

Link copied to clipboard
fun ErrorState(message: String, modifier: Modifier = Modifier, onRetry: () -> Unit? = null, retryLabel: String = "Retry")

The failed sibling of LoadingState. Retry is optional — a permanently failed screen (404, revoked access) should not offer a button that will fail again.

Link copied to clipboard

Grows this element while it holds focus, by AdaptiveTokens.focusScale — the "which card am I on" affordance that a D-pad surface lives or dies by, since a television is read from three metres and a subtle border change is invisible at that distance.

Link copied to clipboard
fun LoadingState(modifier: Modifier = Modifier, label: String? = null)
Link copied to clipboard
fun MarkdownText(markdown: String, modifier: Modifier = Modifier)

Lightweight, hand-rolled Markdown renderer — headers (#/##/###), bold (x), inline code (x), links (u rendered as underlined brand text), bullet/numbered lists, blockquotes, --- rules, and GitHub-style pipe tables. Deliberately minimal (no new dependency) — enough to render the A–G evaluation reports faithfully. Not a full CommonMark parser; unknown syntax falls back to plain text.

Link copied to clipboard
fun navigationLayoutFor(formFactor: FormFactor, width: Dp, tabletop: Boolean = false): NavigationLayout

Picks the navigation affordance for a surface.

Link copied to clipboard
fun otpCellState(index: Int, value: String, fieldFocused: Boolean, isError: Boolean): OtpCellState

Which cell the caret is on, and how each cell should be painted.

Link copied to clipboard
fun OtpField(value: String, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, length: Int = 6, enabled: Boolean = true, isError: Boolean = false, onComplete: (String) -> Unit = {}, style: OtpFieldStyle = OtpFieldDefaults.style())

A one-time-code field: length cells backed by a single real text field.

Link copied to clipboard
fun PageHeader(title: String, modifier: Modifier = Modifier, eyebrow: String? = null, subtitle: String? = null, actions: @Composable () -> Unit? = null)

Page header with eyebrow, title and optional subtitle + trailing actions.

Link copied to clipboard
fun PageIndicator(currentPage: Int, pageCount: Int, modifier: Modifier = Modifier, activeColor: Color = MaterialTheme.colorScheme.primary, inactiveColor: Color = MaterialTheme.colorScheme.outlineVariant)

Pill-style page indicator for a carousel, banner or onboarding pager: the active page's dot stretches into a pill, the rest stay round.

Link copied to clipboard
fun PayloadCard(title: String, entries: ImmutableList<Pair<String, String>>, modifier: Modifier = Modifier)

A card that renders a labelled key/value table. Keys are muted labels; values are rendered in a monospace face so amounts, ids and hashes line up and read as machine data.

Link copied to clipboard

Caps a prose column at a comfortable measure and centres it.

Link copied to clipboard
fun RedactionReveal(value: String, modifier: Modifier = Modifier, reducedMotion: Boolean = false)

Animates onto an already-masked value (e.g. "9f••••3a") by scrambling the visible characters through a few random glyphs before settling — makes the redaction happen visibly instead of the masked string just appearing, which is the whole point of showing it at all.

Link copied to clipboard

Android is the one target where the surface genuinely varies at runtime — the same codebase runs on a phone, a Wear OS watch and an Android TV. uiMode's type bits are the platform's own answer, and reading them through LocalConfiguration means a fold, a display switch or a move to an external monitor recomposes with the right tokens.

The platform's own answer for which surface this is. Android is the only target that genuinely varies at runtime (the same APK runs on a phone, a watch and a television), so it reads the system UI mode; the other targets are known at compile time.

iOS and iPadOS are both arm's-length touch surfaces, so the width axis alone separates iPhone from iPad — no runtime probe needed.

The browser is treated as Desktop. That is the posture, not the screen size — a phone browser is still a ~360dp viewport and resolves to WindowType.Compact, which is what actually drives the layout. Desktop and Handheld share a token ladder precisely so this call doesn't have to be right about the device, only about the viewing distance.

Link copied to clipboard
fun sanitizeOtp(input: String, length: Int): String

Digits only, capped at length. Applied to every edit, so pasting "OTP: 123 456" from a notification yields "123456" rather than being rejected — the single most common way a user actually enters a code.

Link copied to clipboard
fun Modifier.screenPadding(horizontal: Boolean = true, vertical: Boolean = true): Modifier

Insets the content by the surface's unsafe regions and then by its adaptive screenPadding, in one call. Use this instead of padding(tokens.screenPadding).

Link copied to clipboard
fun SectionCard(modifier: Modifier = Modifier, title: String? = null, subtitle: String? = null, leadingIcon: ImageVector? = null, trailingAction: @Composable () -> Unit? = null, contentPadding: Dp = DesignTokens.Spacing.l, content: @Composable ColumnScope.() -> Unit)

Bordered surface card — the fundamental container: optional leading icon in a tinted rounded container, title + subtitle, and a trailing action slot, with the body rendered below in a Column.

Link copied to clipboard
fun SectionLabel(text: String, modifier: Modifier = Modifier, icon: ImageVector? = null)

Section label — the "// SECTION" mono caption idiom above grouped content.

Link copied to clipboard
fun StepTimeline(steps: ImmutableList<TimelineStep>, modifier: Modifier = Modifier)

A vertical, connected timeline. Each step renders a coloured node joined by a rail to the next; ACTIVE pulses in the primary colour, DONE shows a check, ERROR shows a cross, PENDING is muted. Any step's payload key/value rows render inline beneath its title.

Link copied to clipboard
fun TagChip(text: String, modifier: Modifier = Modifier, selected: Boolean = false, onClick: () -> Unit? = null)

Small pill / tag chip.

Link copied to clipboard
fun tokensFor(formFactor: FormFactor, windowType: WindowType): AdaptiveTokens

Resolves the token set for a surface. Total coverage: every FormFactor x WindowType pair.

Link copied to clipboard
fun windowTypeFor(width: Dp, formFactor: FormFactor = FormFactor.Handheld): WindowType

Resolves the width bucket, using breakpoints appropriate to formFactor.