fix(desktop): BasicTextField+DecorationBox for compact Search/Chat inputs
M3 OutlinedTextField has ~32dp of vertical contentPadding baked in, so forcing .height(40-44dp) clipped the bodyMedium (14sp, 20dp line- height) placeholder vertically. Refactored both Search and Chat inputs to BasicTextField wrapped in OutlinedTextFieldDefaults.DecorationBox, which lets us override contentPadding to (12.dp horizontal, 8.dp vertical). The field now renders at a true 40dp tall with the placeholder centered and no clipping. Border, focus states, placeholder, and leading/trailing icons still come from M3 so the look stays consistent. https://claude.ai/code/session_01NufduPfZvYQVYwLkbCjCUo
This commit is contained in:
+64
-32
@@ -49,7 +49,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -365,47 +364,80 @@ fun SearchScreen(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
// Compact desktop search field. M3's OutlinedTextField has a hard
|
||||
// ~32dp vertical contentPadding baked in, so forcing .height(40dp)
|
||||
// clipped the placeholder. BasicTextField + DecorationBox lets us
|
||||
// override contentPadding to 8dp vertical so the field can sit at
|
||||
// a true 40dp tall without clipping the bodyMedium line-height.
|
||||
val searchInteraction =
|
||||
remember {
|
||||
androidx.compose.foundation.interaction
|
||||
.MutableInteractionSource()
|
||||
}
|
||||
androidx.compose.foundation.text.BasicTextField(
|
||||
value = textFieldValue,
|
||||
onValueChange = {
|
||||
textFieldValue = it
|
||||
state.updateFromText(it.text)
|
||||
},
|
||||
// .height(44.dp) overrides M3's 56dp min-height (intended for
|
||||
// mobile touch) — desktop inputs should feel closer to Slack /
|
||||
// Raycast / VS Code. 44dp (not 40) keeps the bodyMedium
|
||||
// placeholder from clipping vertically inside M3's built-in
|
||||
// content padding.
|
||||
modifier = Modifier.weight(1f).height(44.dp).focusRequester(focusRequester),
|
||||
textStyle = MaterialTheme.typography.bodyMedium,
|
||||
placeholder = {
|
||||
Text(
|
||||
"Search people, tags, notes…",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
MaterialSymbols.Search,
|
||||
contentDescription = "Search",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(18.dp),
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
if (displayText.isNotEmpty()) {
|
||||
IconButton(onClick = { state.clearSearch() }, modifier = Modifier.size(28.dp)) {
|
||||
modifier = Modifier.weight(1f).height(40.dp).focusRequester(focusRequester),
|
||||
textStyle =
|
||||
MaterialTheme.typography.bodyMedium
|
||||
.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
cursorBrush =
|
||||
androidx.compose.ui.graphics
|
||||
.SolidColor(MaterialTheme.colorScheme.primary),
|
||||
singleLine = true,
|
||||
interactionSource = searchInteraction,
|
||||
decorationBox = { innerTextField ->
|
||||
androidx.compose.material3.OutlinedTextFieldDefaults.DecorationBox(
|
||||
value = textFieldValue.text,
|
||||
innerTextField = innerTextField,
|
||||
enabled = true,
|
||||
singleLine = true,
|
||||
visualTransformation = androidx.compose.ui.text.input.VisualTransformation.None,
|
||||
interactionSource = searchInteraction,
|
||||
placeholder = {
|
||||
Text(
|
||||
"Search people, tags, notes…",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
MaterialSymbols.Clear,
|
||||
contentDescription = "Clear",
|
||||
MaterialSymbols.Search,
|
||||
contentDescription = "Search",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(18.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
if (displayText.isNotEmpty()) {
|
||||
IconButton(onClick = { state.clearSearch() }, modifier = Modifier.size(28.dp)) {
|
||||
Icon(
|
||||
MaterialSymbols.Clear,
|
||||
contentDescription = "Clear",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(18.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
contentPadding =
|
||||
androidx.compose.foundation.layout.PaddingValues(
|
||||
horizontal = 12.dp,
|
||||
vertical = 8.dp,
|
||||
),
|
||||
container = {
|
||||
androidx.compose.material3.OutlinedTextFieldDefaults.Container(
|
||||
enabled = true,
|
||||
isError = false,
|
||||
interactionSource = searchInteraction,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
)
|
||||
if (account != null && !account.isReadOnly) {
|
||||
IconButton(onClick = { showRelayPicker = true }) {
|
||||
|
||||
+46
-15
@@ -43,7 +43,6 @@ import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
@@ -718,17 +717,22 @@ private fun MessageInput(
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
// BasicTextField + DecorationBox with custom contentPadding lets the
|
||||
// field sit at a compact ~40dp minimum on desktop (M3's default
|
||||
// OutlinedTextField has ~32dp vertical contentPadding baked in, which
|
||||
// would clip the bodyMedium placeholder at any height below ~52dp).
|
||||
val messageInteraction =
|
||||
remember {
|
||||
androidx.compose.foundation.interaction
|
||||
.MutableInteractionSource()
|
||||
}
|
||||
androidx.compose.foundation.text.BasicTextField(
|
||||
value = messageText,
|
||||
onValueChange = onMessageChange,
|
||||
// heightIn(min = 44.dp) overrides M3's 56dp mobile-touch min so the
|
||||
// chat input sits at a desktop-sensible ~44dp when empty; still grows
|
||||
// to up to ~120dp with content via maxLines = 4. 44 (not 40) keeps the
|
||||
// bodyMedium placeholder from clipping inside M3's internal padding.
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.heightIn(min = 44.dp)
|
||||
.heightIn(min = 40.dp)
|
||||
.onPreviewKeyEvent { event ->
|
||||
if (event.type != KeyEventType.KeyDown) return@onPreviewKeyEvent false
|
||||
// Cmd+Enter (Mac) or Ctrl+Enter to send
|
||||
@@ -740,16 +744,43 @@ private fun MessageInput(
|
||||
false
|
||||
}
|
||||
},
|
||||
textStyle = MaterialTheme.typography.bodyMedium,
|
||||
placeholder = {
|
||||
Text(
|
||||
"Message... (${if (isMacOS) "\u2318" else "Ctrl"}+Enter to send)",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textStyle =
|
||||
MaterialTheme.typography.bodyMedium
|
||||
.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
cursorBrush =
|
||||
androidx.compose.ui.graphics
|
||||
.SolidColor(MaterialTheme.colorScheme.primary),
|
||||
maxLines = 4,
|
||||
interactionSource = messageInteraction,
|
||||
decorationBox = { innerTextField ->
|
||||
androidx.compose.material3.OutlinedTextFieldDefaults.DecorationBox(
|
||||
value = messageText,
|
||||
innerTextField = innerTextField,
|
||||
enabled = true,
|
||||
singleLine = false,
|
||||
visualTransformation = androidx.compose.ui.text.input.VisualTransformation.None,
|
||||
interactionSource = messageInteraction,
|
||||
placeholder = {
|
||||
Text(
|
||||
"Message\u2026 (${if (isMacOS) "\u2318" else "Ctrl"}+Enter to send)",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
},
|
||||
contentPadding =
|
||||
androidx.compose.foundation.layout.PaddingValues(
|
||||
horizontal = 12.dp,
|
||||
vertical = 8.dp,
|
||||
),
|
||||
container = {
|
||||
androidx.compose.material3.OutlinedTextFieldDefaults.Container(
|
||||
enabled = true,
|
||||
isError = false,
|
||||
interactionSource = messageInteraction,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
singleLine = false,
|
||||
maxLines = 4,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
)
|
||||
|
||||
IconButton(
|
||||
|
||||
Reference in New Issue
Block a user