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.IconButton
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -365,47 +364,80 @@ fun SearchScreen(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
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,
|
value = textFieldValue,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
textFieldValue = it
|
textFieldValue = it
|
||||||
state.updateFromText(it.text)
|
state.updateFromText(it.text)
|
||||||
},
|
},
|
||||||
// .height(44.dp) overrides M3's 56dp min-height (intended for
|
modifier = Modifier.weight(1f).height(40.dp).focusRequester(focusRequester),
|
||||||
// mobile touch) — desktop inputs should feel closer to Slack /
|
textStyle =
|
||||||
// Raycast / VS Code. 44dp (not 40) keeps the bodyMedium
|
MaterialTheme.typography.bodyMedium
|
||||||
// placeholder from clipping vertically inside M3's built-in
|
.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||||
// content padding.
|
cursorBrush =
|
||||||
modifier = Modifier.weight(1f).height(44.dp).focusRequester(focusRequester),
|
androidx.compose.ui.graphics
|
||||||
textStyle = MaterialTheme.typography.bodyMedium,
|
.SolidColor(MaterialTheme.colorScheme.primary),
|
||||||
placeholder = {
|
singleLine = true,
|
||||||
Text(
|
interactionSource = searchInteraction,
|
||||||
"Search people, tags, notes…",
|
decorationBox = { innerTextField ->
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
androidx.compose.material3.OutlinedTextFieldDefaults.DecorationBox(
|
||||||
)
|
value = textFieldValue.text,
|
||||||
},
|
innerTextField = innerTextField,
|
||||||
leadingIcon = {
|
enabled = true,
|
||||||
Icon(
|
singleLine = true,
|
||||||
MaterialSymbols.Search,
|
visualTransformation = androidx.compose.ui.text.input.VisualTransformation.None,
|
||||||
contentDescription = "Search",
|
interactionSource = searchInteraction,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
placeholder = {
|
||||||
modifier = Modifier.size(18.dp),
|
Text(
|
||||||
)
|
"Search people, tags, notes…",
|
||||||
},
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
trailingIcon = {
|
)
|
||||||
if (displayText.isNotEmpty()) {
|
},
|
||||||
IconButton(onClick = { state.clearSearch() }, modifier = Modifier.size(28.dp)) {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
MaterialSymbols.Clear,
|
MaterialSymbols.Search,
|
||||||
contentDescription = "Clear",
|
contentDescription = "Search",
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.size(18.dp),
|
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) {
|
if (account != null && !account.isReadOnly) {
|
||||||
IconButton(onClick = { showRelayPicker = true }) {
|
IconButton(onClick = { showRelayPicker = true }) {
|
||||||
|
|||||||
+46
-15
@@ -43,7 +43,6 @@ import androidx.compose.material3.HorizontalDivider
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Surface
|
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,
|
value = messageText,
|
||||||
onValueChange = onMessageChange,
|
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 =
|
||||||
Modifier
|
Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.heightIn(min = 44.dp)
|
.heightIn(min = 40.dp)
|
||||||
.onPreviewKeyEvent { event ->
|
.onPreviewKeyEvent { event ->
|
||||||
if (event.type != KeyEventType.KeyDown) return@onPreviewKeyEvent false
|
if (event.type != KeyEventType.KeyDown) return@onPreviewKeyEvent false
|
||||||
// Cmd+Enter (Mac) or Ctrl+Enter to send
|
// Cmd+Enter (Mac) or Ctrl+Enter to send
|
||||||
@@ -740,16 +744,43 @@ private fun MessageInput(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
textStyle = MaterialTheme.typography.bodyMedium,
|
textStyle =
|
||||||
placeholder = {
|
MaterialTheme.typography.bodyMedium
|
||||||
Text(
|
.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||||
"Message... (${if (isMacOS) "\u2318" else "Ctrl"}+Enter to send)",
|
cursorBrush =
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
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(
|
IconButton(
|
||||||
|
|||||||
Reference in New Issue
Block a user