design : updates TextField api

This commit is contained in:
ganfra 2024-11-08 11:54:57 +01:00
parent 612dfa4c9a
commit a4b83fe024

View file

@ -23,13 +23,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -63,18 +67,16 @@ fun TextField2(
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default,
visualTransformation: VisualTransformation = VisualTransformation.None, visualTransformation: VisualTransformation = VisualTransformation.None,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
onTextLayout: (TextLayoutResult) -> Unit = {}, onTextLayout: (TextLayoutResult) -> Unit = {},
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState() val isFocused by interactionSource.collectIsFocusedAsState()
BasicTextField( BasicTextField(
value = value, value = value,
onValueChange = onValueChange, onValueChange = onValueChange,
modifier = modifier, modifier = modifier,
textStyle = ElementTheme.typography.fontBodyLgRegular.copy( textStyle = textFieldStyle(enabled),
color = if (readOnly) ElementTheme.colors.textSecondary else ElementTheme.colors.textPrimary
),
interactionSource = interactionSource, interactionSource = interactionSource,
enabled = enabled, enabled = enabled,
singleLine = singleLine, singleLine = singleLine,
@ -87,77 +89,202 @@ fun TextField2(
visualTransformation = visualTransformation, visualTransformation = visualTransformation,
onTextLayout = onTextLayout, onTextLayout = onTextLayout,
) { innerTextField -> ) { innerTextField ->
Column { DecorationBox(
if (label != null) { label = label,
Text( readOnly = readOnly,
text = label, enabled = enabled,
color = ElementTheme.colors.textPrimary, isFocused = isFocused,
style = ElementTheme.typography.fontBodyMdRegular, isError = isError,
) leadingIcon = leadingIcon,
} placeholder = placeholder,
isTextEmpty = value.isEmpty(),
innerTextField = innerTextField,
trailingIcon = trailingIcon,
supportingText = supportingText
)
}
}
@Composable
fun TextField2(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
label: String? = null,
supportingText: String? = null,
placeholder: String? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
isError: Boolean = false,
enabled: Boolean = true,
readOnly: Boolean = false,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
visualTransformation: VisualTransformation = VisualTransformation.None,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
onTextLayout: (TextLayoutResult) -> Unit = {},
modifier: Modifier = Modifier
) {
val isFocused by interactionSource.collectIsFocusedAsState()
BasicTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
textStyle = textFieldStyle(enabled),
interactionSource = interactionSource,
enabled = enabled,
singleLine = singleLine,
maxLines = maxLines,
minLines = minLines,
readOnly = readOnly,
cursorBrush = SolidColor(ElementTheme.colors.textPrimary),
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
visualTransformation = visualTransformation,
onTextLayout = onTextLayout,
) { innerTextField ->
DecorationBox(
label = label,
readOnly = readOnly,
enabled = enabled,
isFocused = isFocused,
isError = isError,
leadingIcon = leadingIcon,
placeholder = placeholder,
isTextEmpty = value.text.isEmpty(),
innerTextField = innerTextField,
trailingIcon = trailingIcon,
supportingText = supportingText
)
}
}
@Composable
private fun DecorationBox(
label: String?,
enabled: Boolean,
readOnly: Boolean,
isFocused: Boolean,
isError: Boolean,
placeholder: String?,
isTextEmpty: Boolean,
supportingText: String?,
leadingIcon: @Composable (() -> Unit)?,
trailingIcon: @Composable (() -> Unit)?,
innerTextField: @Composable () -> Unit,
) {
Column {
if (label != null) {
Text(
text = label,
color = ElementTheme.colors.textPrimary,
style = ElementTheme.typography.fontBodyMdRegular,
)
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Surface( }
shape = RoundedCornerShape(4.dp), TextFieldContainer(
border = if (readOnly) { enabled = enabled,
null readOnly = readOnly,
} else { isFocused = isFocused,
BorderStroke( isError = isError
width = if (isFocused) 2.dp else 1.dp, ) {
color = when { Row(modifier = Modifier.padding(16.dp)) {
isError -> ElementTheme.colors.borderCriticalPrimary if (leadingIcon != null) {
isFocused -> ElementTheme.colors.borderInteractiveHovered CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.iconSecondary) {
else -> ElementTheme.colors.borderInteractiveSecondary
}
)
},
color = if (readOnly) ElementTheme.colors.bgSubtleSecondary else ElementTheme.colors.bgCanvasDefault,
) {
Row(modifier = Modifier.padding(16.dp)) {
if (leadingIcon != null) {
leadingIcon() leadingIcon()
Spacer(modifier = Modifier.width(8.dp))
} }
Box(modifier = Modifier.weight(1f)) { Spacer(modifier = Modifier.width(8.dp))
if (placeholder != null && value.isEmpty()) { }
Text( Box(modifier = Modifier.weight(1f)) {
text = placeholder, if (placeholder != null && isTextEmpty) {
color = ElementTheme.colors.textPlaceholder, Text(
style = ElementTheme.typography.fontBodyLgRegular, text = placeholder,
) color = ElementTheme.colors.textPlaceholder,
} style = ElementTheme.typography.fontBodyLgRegular,
innerTextField() )
} }
if (trailingIcon != null) { innerTextField()
Spacer(modifier = Modifier.width(8.dp)) }
if (trailingIcon != null) {
Spacer(modifier = Modifier.width(8.dp))
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.iconSecondary) {
trailingIcon() trailingIcon()
} }
} }
} }
if (supportingText != null) { }
Spacer(modifier = Modifier.height(4.dp)) if (supportingText != null) {
Row( Spacer(modifier = Modifier.height(4.dp))
verticalAlignment = Alignment.CenterVertically, SupportingTextLayout(isError, supportingText)
horizontalArrangement = spacedBy(4.dp),
) {
if (isError) {
Icon(
imageVector = CompoundIcons.Error(),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = ElementTheme.colors.iconCriticalPrimary
)
}
Text(
text = supportingText,
color = if (isError) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textSecondary,
style = ElementTheme.typography.fontBodySmRegular,
)
}
}
} }
} }
} }
@Composable
private fun TextFieldContainer(
enabled: Boolean,
readOnly: Boolean,
isFocused: Boolean,
isError: Boolean,
content: @Composable () -> Unit
) {
Surface(
shape = RoundedCornerShape(4.dp),
border = if (readOnly) {
null
} else {
BorderStroke(
width = if (isFocused) 2.dp else 1.dp,
color = when {
!enabled -> ElementTheme.colors.borderDisabled
isError -> ElementTheme.colors.borderCriticalPrimary
isFocused -> ElementTheme.colors.borderInteractiveHovered
else -> ElementTheme.colors.borderInteractiveSecondary
}
)
},
color = when {
readOnly -> ElementTheme.colors.bgSubtleSecondary
!enabled -> ElementTheme.colors.bgCanvasDisabled
else -> ElementTheme.colors.bgCanvasDefault
},
content = content
)
}
@Composable
private fun SupportingTextLayout(isError: Boolean, supportingText: String) {
Row(horizontalArrangement = spacedBy(4.dp)) {
if (isError) {
Icon(
imageVector = CompoundIcons.Error(),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = ElementTheme.colors.iconCriticalPrimary
)
}
Text(
text = supportingText,
color = if (isError) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textSecondary,
style = ElementTheme.typography.fontBodySmRegular,
)
}
}
@Composable
private fun textFieldStyle(enabled: Boolean): TextStyle {
return ElementTheme.typography.fontBodyLgRegular.copy(
color = if (enabled) {
ElementTheme.colors.textPrimary
} else {
ElementTheme.colors.textSecondary
}
)
}
@Preview(group = PreviewGroup.TextFields) @Preview(group = PreviewGroup.TextFields)
@Composable @Composable
internal fun TextFields2LightPreview() = ElementPreviewLight { ContentToPreview() } internal fun TextFields2LightPreview() = ElementPreviewLight { ContentToPreview() }