@@ -21,18 +21,22 @@ import androidx.compose.foundation.gestures.detectTapGestures
21
21
import androidx.compose.foundation.interaction.MutableInteractionSource
22
22
import androidx.compose.foundation.layout.Column
23
23
import androidx.compose.foundation.layout.ColumnScope
24
+ import androidx.compose.foundation.layout.defaultMinSize
24
25
import androidx.compose.foundation.layout.fillMaxWidth
25
26
import androidx.compose.foundation.layout.padding
27
+ import androidx.compose.foundation.text.BasicTextField
26
28
import androidx.compose.foundation.text.KeyboardActions
27
29
import androidx.compose.foundation.text.KeyboardOptions
28
30
import androidx.compose.material.icons.Icons
29
31
import androidx.compose.material.icons.rounded.CheckCircle
30
32
import androidx.compose.material.icons.rounded.Clear
31
33
import androidx.compose.material.icons.rounded.TextFields
34
+ import androidx.compose.material3.ExperimentalMaterial3Api
32
35
import androidx.compose.material3.Icon
33
36
import androidx.compose.material3.IconButton
37
+ import androidx.compose.material3.LocalTextStyle
34
38
import androidx.compose.material3.MaterialTheme
35
- import androidx.compose.material3.OutlinedTextField
39
+ import androidx.compose.material3.OutlinedTextFieldDefaults
36
40
import androidx.compose.material3.Text
37
41
import androidx.compose.runtime.Composable
38
42
import androidx.compose.runtime.getValue
@@ -41,10 +45,12 @@ import androidx.compose.runtime.remember
41
45
import androidx.compose.runtime.setValue
42
46
import androidx.compose.ui.Modifier
43
47
import androidx.compose.ui.focus.onFocusChanged
48
+ import androidx.compose.ui.graphics.takeOrElse
44
49
import androidx.compose.ui.graphics.vector.ImageVector
45
50
import androidx.compose.ui.input.pointer.pointerInput
46
51
import androidx.compose.ui.semantics.contentDescription
47
52
import androidx.compose.ui.semantics.semantics
53
+ import androidx.compose.ui.text.TextStyle
48
54
import androidx.compose.ui.text.input.ImeAction
49
55
import androidx.compose.ui.text.input.KeyboardType
50
56
import androidx.compose.ui.text.input.VisualTransformation
@@ -91,15 +97,15 @@ private fun trailingIcon(
91
97
// multiline editable field
92
98
{
93
99
// show a done button only when focused
94
- IconButton (onClick = { onDone() }, modifier = Modifier .semantics {
100
+ IconButton (onClick = { onDone() }, modifier = Modifier .semantics {
95
101
contentDescription = " Save local edit button"
96
102
}) {
97
103
Icon (
98
104
imageVector = Icons .Rounded .CheckCircle , contentDescription = " Done"
99
105
)
100
106
}
101
107
}
102
-
108
+
103
109
} else if (! singleLine && isEditable && text.isNotEmpty()) {
104
110
{
105
111
// show a clear icon instead if the multiline field is not empty
@@ -117,8 +123,8 @@ private fun trailingIcon(
117
123
}
118
124
119
125
/* *
120
- * A base text field component built on top of an [OutlinedTextField] that provides a standard for
121
- * visual and behavioral properties. This can be used to build more customized composite components.
126
+ * A base text field component built on top of a [BasicTextField] with an [OutlinedTextFieldDefaults.DecorationBox].
127
+ * This provides a standard for visual and behavioral properties and can be used to build more customized composite components.
122
128
*
123
129
* The BaseTextField also takes care of clearing focus when the keyboard is dismissed or tapped
124
130
* outside the input area.
@@ -144,6 +150,7 @@ private fun trailingIcon(
144
150
* for this text field.
145
151
* @param trailingContent a widget to be displayed at the end of the text field container.
146
152
*/
153
+ @OptIn(ExperimentalMaterial3Api ::class )
147
154
@Composable
148
155
internal fun BaseTextField (
149
156
text : String ,
@@ -155,17 +162,26 @@ internal fun BaseTextField(
155
162
modifier : Modifier = Modifier ,
156
163
readOnly : Boolean = !isEditable,
157
164
keyboardType : KeyboardType = KeyboardType .Ascii ,
165
+ textStyle : TextStyle = LocalTextStyle .current,
158
166
trailingIcon : ImageVector ? = null,
159
167
supportingText : @Composable (ColumnScope .() -> Unit )? = null,
160
168
onFocusChange : ((Boolean ) -> Unit )? = null,
161
169
interactionSource : MutableInteractionSource = remember { MutableInteractionSource () },
162
170
trailingContent : (@Composable () -> Unit )? = null
163
- ) {
171
+ ) {
164
172
var clearFocus by remember { mutableStateOf(false ) }
165
173
var isFocused by remember { mutableStateOf(false ) }
166
-
167
- // if the keyboard is gone clear focus from the field as a side-effect
168
- ClearFocus (clearFocus) { clearFocus = false }
174
+ val visualTransformation = if (text.isEmpty())
175
+ PlaceholderTransformation (placeholder.ifEmpty { " " })
176
+ else VisualTransformation .None
177
+ val colors = baseTextFieldColors(
178
+ isEditable = isEditable
179
+ )
180
+ // If color is not provided via the text style, use content color as a default
181
+ val textColor = textStyle.color.takeOrElse {
182
+ defaultTextColor(isEditable, text.isEmpty(), placeholder.isEmpty())
183
+ }
184
+ val mergedTextStyle = textStyle.merge(TextStyle (color = textColor))
169
185
170
186
Column (modifier = modifier
171
187
.onFocusChanged {
@@ -178,65 +194,92 @@ internal fun BaseTextField(
178
194
}
179
195
.padding(start = 15 .dp, end = 15 .dp, top = 10 .dp, bottom = 10 .dp)
180
196
) {
181
- OutlinedTextField (
197
+ BasicTextField (
182
198
value = text,
183
199
onValueChange = onValueChange,
184
200
modifier = Modifier
201
+ // Merge semantics at the beginning of the modifier chain to ensure padding is
202
+ // considered part of the text field.
203
+ .semantics(mergeDescendants = true ) {}
204
+ .padding(top = 8 .dp)
205
+ .defaultMinSize(
206
+ minWidth = OutlinedTextFieldDefaults .MinWidth ,
207
+ minHeight = OutlinedTextFieldDefaults .MinHeight
208
+ )
185
209
.fillMaxWidth()
186
210
.semantics { contentDescription = " outlined text field" },
211
+ enabled = true ,
187
212
readOnly = readOnly,
188
- label = {
189
- Text (
190
- text = label,
191
- modifier = Modifier .semantics { contentDescription = " label" },
192
- overflow = TextOverflow .Ellipsis ,
193
- maxLines = 1
194
- )
195
- },
196
- trailingIcon = trailingContent
197
- ? : trailingIcon(
198
- text,
199
- isEditable,
200
- singleLine,
201
- isFocused,
202
- trailingIcon,
203
- onValueChange = onValueChange,
204
- onDone = { clearFocus = true }
205
- ),
206
- supportingText = {
207
- Column (
208
- modifier = Modifier .clickable {
209
- clearFocus = true
210
- }.semantics { contentDescription = " supporting text" }
211
- ) {
212
- supportingText?.invoke(this )
213
- }
214
- },
215
- visualTransformation = if (text.isEmpty())
216
- PlaceholderTransformation (placeholder.ifEmpty { " " })
217
- else VisualTransformation .None ,
213
+ textStyle = mergedTextStyle,
214
+ visualTransformation = visualTransformation,
218
215
keyboardActions = KeyboardActions (
219
216
onDone = { clearFocus = true }
220
217
),
221
218
keyboardOptions = KeyboardOptions .Default .copy(
222
219
imeAction = if (singleLine) ImeAction .Done else ImeAction .None ,
223
220
keyboardType = keyboardType
224
221
),
225
- singleLine = singleLine,
226
222
interactionSource = interactionSource,
227
- colors = baseTextFieldColors(
228
- isEditable = isEditable,
229
- isEmpty = text.isEmpty(),
230
- isPlaceholderEmpty = placeholder.isEmpty()
231
- )
223
+ singleLine = singleLine,
224
+ decorationBox = @Composable { innerTextField ->
225
+ OutlinedTextFieldDefaults .DecorationBox (
226
+ value = text,
227
+ visualTransformation = visualTransformation,
228
+ innerTextField = innerTextField,
229
+ label = {
230
+ Text (
231
+ text = label,
232
+ modifier = Modifier .semantics { contentDescription = " label" },
233
+ overflow = TextOverflow .Ellipsis ,
234
+ maxLines = 1
235
+ )
236
+ },
237
+ trailingIcon = trailingContent
238
+ ? : trailingIcon(
239
+ text,
240
+ isEditable,
241
+ singleLine,
242
+ isFocused,
243
+ trailingIcon,
244
+ onValueChange = onValueChange,
245
+ onDone = { clearFocus = true }
246
+ ),
247
+ supportingText = {
248
+ Column (
249
+ modifier = Modifier
250
+ .clickable {
251
+ clearFocus = true
252
+ }
253
+ ) {
254
+ supportingText?.invoke(this )
255
+ }
256
+ },
257
+ singleLine = singleLine,
258
+ enabled = true ,
259
+ isError = false ,
260
+ interactionSource = interactionSource,
261
+ colors = colors,
262
+ container = {
263
+ OutlinedTextFieldDefaults .ContainerBox (
264
+ enabled = true ,
265
+ isError = false ,
266
+ interactionSource,
267
+ colors,
268
+ OutlinedTextFieldDefaults .shape,
269
+ focusedBorderThickness = if (isEditable) 2 .dp else 1 .dp
270
+ )
271
+ }
272
+ )
273
+ }
232
274
)
233
275
}
276
+ // if the keyboard is gone clear focus from the field as a side-effect
277
+ ClearFocus (clearFocus) { clearFocus = false }
234
278
}
235
279
236
-
237
280
@Preview(showBackground = true , backgroundColor = 0xFFFFFFFF )
238
281
@Composable
239
- private fun BaseTextFieldPreview () {
282
+ private fun BaseTextFieldV2Preview () {
240
283
MaterialTheme {
241
284
BaseTextField (
242
285
text = " " ,
0 commit comments