IdentitySuggestDropdown.kt

5.0 KB · raw

1package com.peachpasswords.ui.vault23import androidx.compose.foundation.background4import androidx.compose.foundation.border5import androidx.compose.foundation.layout.Box6import androidx.compose.foundation.layout.Column7import androidx.compose.foundation.layout.Spacer8import androidx.compose.foundation.layout.fillMaxWidth9import androidx.compose.foundation.layout.height10import androidx.compose.foundation.layout.padding11import androidx.compose.foundation.shape.RoundedCornerShape12import androidx.compose.foundation.text.BasicTextField13import androidx.compose.material3.DropdownMenu14import androidx.compose.material3.DropdownMenuItem15import androidx.compose.material3.Text16import androidx.compose.runtime.Composable17import androidx.compose.runtime.getValue18import androidx.compose.runtime.mutableStateOf19import androidx.compose.runtime.remember20import androidx.compose.runtime.setValue21import androidx.compose.ui.Modifier22import androidx.compose.ui.draw.clip23import androidx.compose.ui.focus.onFocusChanged24import androidx.compose.ui.graphics.SolidColor25import androidx.compose.ui.text.TextStyle26import androidx.compose.ui.unit.dp27import androidx.compose.ui.unit.sp28import androidx.compose.ui.window.PopupProperties29import com.peachpasswords.ui.theme.LocalPeachAccent30import com.peachpasswords.ui.theme.peachColors31import com.peachpasswords.ui.theme.PeachType3233/**34 * Anchored suggestion dropdown for identity-style fields (username/email).35 *36 * Self-contained for clean merge into EntryEditScreen.kt. The brief uses37 * `DropdownMenu(expanded = focused && suggestions.isNotEmpty())`; we keep that38 * shape but mark the popup non-focusable so it never steals focus from the39 * BasicTextField that drives `expanded` — otherwise the field blurs the instant40 * the menu opens and the dropdown flickers shut.41 *42 * Mirrors the editor's visual language: IBM Plex Mono rows on the S2 glass43 * surface with a hairline T3 border. Suggestions are expected pre-trimmed to44 * <=4 by the ViewModel (see usernameSuggestions in INTEGRATION.md).45 */46@Composable47fun IdentitySuggestDropdown(48    expanded: Boolean,49    suggestions: List<String>,50    onPick: (String) -> Unit,51    onDismiss: () -> Unit,52) {53    DropdownMenu(54        expanded = expanded && suggestions.isNotEmpty(),55        onDismissRequest = onDismiss,56        properties = PopupProperties(focusable = false),57        modifier = Modifier58            .background(peachColors.S2)59            .border(1.dp, peachColors.T3, RoundedCornerShape(10.dp))60            .clip(RoundedCornerShape(10.dp)),61    ) {62        suggestions.forEach { suggestion ->63            DropdownMenuItem(64                text = {65                    Text(66                        suggestion,67                        style = TextStyle(68                            color = peachColors.T1,69                            fontFamily = PeachType.Mono,70                            fontSize = 11.sp,71                        ),72                    )73                },74                onClick = { onPick(suggestion) },75                modifier = Modifier.fillMaxWidth(),76            )77        }78    }79}8081/**82 * Identity field + anchored suggest dropdown, wired to focus state.83 *84 * Drop-in replacement for the bare `Field(...)` calls for username/email in85 * EntryEditScreen. It owns the focus tracking the dropdown needs; the parent86 * supplies value, suggestions, and the change callback. The dropdown is wrapped87 * with the field so the popup anchors directly beneath it.88 *89 * `Field` is `private` in EntryEditScreen.kt, so this re-implements the same90 * underline input rather than calling it. Keep this composable in the same file91 * when merging, or make `Field` accept `onFocusChanged` and reuse it (see92 * INTEGRATION.md "Option A").93 */94@Composable95fun IdentitySuggestField(96    label: String,97    value: String,98    suggestions: List<String>,99    onChange: (String) -> Unit,100) {101    val accent = LocalPeachAccent.current102    var focused by remember { mutableStateOf(false) }103104    Column(Modifier.fillMaxWidth().padding(bottom = 14.dp)) {105        Text(label.uppercase(), color = peachColors.T1, fontFamily = PeachType.Mono, fontSize = 8.sp)106        Spacer(Modifier.height(6.dp))107        Box(108            Modifier.fillMaxWidth()109                .border(1.dp, peachColors.T3, RoundedCornerShape(10.dp))110                .padding(horizontal = 12.dp, vertical = 12.dp),111        ) {112            BasicTextField(113                value = value,114                onValueChange = onChange,115                singleLine = true,116                textStyle = TextStyle(color = peachColors.T0, fontFamily = PeachType.Mono, fontSize = 13.sp),117                cursorBrush = SolidColor(accent),118                modifier = Modifier119                    .fillMaxWidth()120                    .onFocusChanged { focused = it.isFocused },121            )122        }123        IdentitySuggestDropdown(124            expanded = focused,125            suggestions = suggestions.filter { it != value },126            onPick = { picked ->127                onChange(picked)128                focused = false129            },130            onDismiss = { focused = false },131        )132    }133}