blob: 6d46dcc4b23a834c483b234c1f873f8482a891e7 [file] [log] [blame]
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(ExperimentalMaterial3Api::class)
package androidx.compose.material3.samples
import androidx.annotation.Sampled
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.semantics.error
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Preview
@Sampled
@Composable
fun SimpleTextFieldSample() {
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") },
singleLine = true
)
}
@Preview
@Sampled
@Composable
fun SimpleOutlinedTextFieldSample() {
var text by rememberSaveable { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}
@Preview
@Sampled
@Composable
fun TextFieldWithIcons() {
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") },
leadingIcon = { Icon(Icons.Filled.Favorite, contentDescription = "Localized description") },
trailingIcon = { Icon(Icons.Filled.Info, contentDescription = "Localized description") }
)
}
@Preview
@Sampled
@Composable
fun TextFieldWithPlaceholder() {
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Email") },
placeholder = { Text("[email protected]") }
)
}
@Preview
@Sampled
@Composable
fun TextFieldWithPrefixAndSuffix() {
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
singleLine = true,
label = { Text("Label") },
prefix = { Text("www.") },
suffix = { Text(".com") },
placeholder = { Text("google") },
)
}
@Preview
@Sampled
@Composable
fun TextFieldWithErrorState() {
val errorMessage = "Text input too long"
var text by rememberSaveable { mutableStateOf("") }
var isError by rememberSaveable { mutableStateOf(false) }
val charLimit = 10
fun validate(text: String) {
isError = text.length > charLimit
}
TextField(
value = text,
onValueChange = {
text = it
validate(text)
},
singleLine = true,
label = { Text(if (isError) "Username*" else "Username") },
supportingText = {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Limit: ${text.length}/$charLimit",
textAlign = TextAlign.End,
)
},
isError = isError,
keyboardActions = KeyboardActions { validate(text) },
modifier = Modifier.semantics {
// Provide localized description of the error
if (isError) error(errorMessage)
}
)
}
@Preview
@Sampled
@Composable
fun TextFieldWithSupportingText() {
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") },
supportingText = {
Text("Supporting text that is long and perhaps goes onto another line.")
},
)
}
@Preview
@Sampled
@Composable
fun PasswordTextField() {
var password by rememberSaveable { mutableStateOf("") }
var passwordHidden by rememberSaveable { mutableStateOf(true) }
TextField(
value = password,
onValueChange = { password = it },
singleLine = true,
label = { Text("Enter password") },
visualTransformation =
if (passwordHidden) PasswordVisualTransformation() else VisualTransformation.None,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
IconButton(onClick = { passwordHidden = !passwordHidden }) {
val visibilityIcon =
if (passwordHidden) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
// Please provide localized description for accessibility services
val description = if (passwordHidden) "Show password" else "Hide password"
Icon(imageVector = visibilityIcon, contentDescription = description)
}
}
)
}
/** We copy the implementation of Visibility and VisibilityOff icons to showcase them in the
* password text field sample but to avoid adding material-icons-extended library as a dependency
* to the samples not to increase the build time
*/
private val Icons.Filled.Visibility: ImageVector
get() {
if (_visibility != null) {
return _visibility!!
}
_visibility = materialIcon(name = "Filled.Visibility") {
materialPath {
moveTo(12.0f, 4.5f)
curveTo(7.0f, 4.5f, 2.73f, 7.61f, 1.0f, 12.0f)
curveToRelative(1.73f, 4.39f, 6.0f, 7.5f, 11.0f, 7.5f)
reflectiveCurveToRelative(9.27f, -3.11f, 11.0f, -7.5f)
curveToRelative(-1.73f, -4.39f, -6.0f, -7.5f, -11.0f, -7.5f)
close()
moveTo(12.0f, 17.0f)
curveToRelative(-2.76f, 0.0f, -5.0f, -2.24f, -5.0f, -5.0f)
reflectiveCurveToRelative(2.24f, -5.0f, 5.0f, -5.0f)
reflectiveCurveToRelative(5.0f, 2.24f, 5.0f, 5.0f)
reflectiveCurveToRelative(-2.24f, 5.0f, -5.0f, 5.0f)
close()
moveTo(12.0f, 9.0f)
curveToRelative(-1.66f, 0.0f, -3.0f, 1.34f, -3.0f, 3.0f)
reflectiveCurveToRelative(1.34f, 3.0f, 3.0f, 3.0f)
reflectiveCurveToRelative(3.0f, -1.34f, 3.0f, -3.0f)
reflectiveCurveToRelative(-1.34f, -3.0f, -3.0f, -3.0f)
close()
}
}
return _visibility!!
}
private var _visibility: ImageVector? = null
private val Icons.Filled.VisibilityOff: ImageVector
get() {
if (_visibilityOff != null) {
return _visibilityOff!!
}
_visibilityOff = materialIcon(name = "Filled.VisibilityOff") {
materialPath {
moveTo(12.0f, 7.0f)
curveToRelative(2.76f, 0.0f, 5.0f, 2.24f, 5.0f, 5.0f)
curveToRelative(0.0f, 0.65f, -0.13f, 1.26f, -0.36f, 1.83f)
lineToRelative(2.92f, 2.92f)
curveToRelative(1.51f, -1.26f, 2.7f, -2.89f, 3.43f, -4.75f)
curveToRelative(-1.73f, -4.39f, -6.0f, -7.5f, -11.0f, -7.5f)
curveToRelative(-1.4f, 0.0f, -2.74f, 0.25f, -3.98f, 0.7f)
lineToRelative(2.16f, 2.16f)
curveTo(10.74f, 7.13f, 11.35f, 7.0f, 12.0f, 7.0f)
close()
moveTo(2.0f, 4.27f)
lineToRelative(2.28f, 2.28f)
lineToRelative(0.46f, 0.46f)
curveTo(3.08f, 8.3f, 1.78f, 10.02f, 1.0f, 12.0f)
curveToRelative(1.73f, 4.39f, 6.0f, 7.5f, 11.0f, 7.5f)
curveToRelative(1.55f, 0.0f, 3.03f, -0.3f, 4.38f, -0.84f)
lineToRelative(0.42f, 0.42f)
lineTo(19.73f, 22.0f)
lineTo(21.0f, 20.73f)
lineTo(3.27f, 3.0f)
lineTo(2.0f, 4.27f)
close()
moveTo(7.53f, 9.8f)
lineToRelative(1.55f, 1.55f)
curveToRelative(-0.05f, 0.21f, -0.08f, 0.43f, -0.08f, 0.65f)
curveToRelative(0.0f, 1.66f, 1.34f, 3.0f, 3.0f, 3.0f)
curveToRelative(0.22f, 0.0f, 0.44f, -0.03f, 0.65f, -0.08f)
lineToRelative(1.55f, 1.55f)
curveToRelative(-0.67f, 0.33f, -1.41f, 0.53f, -2.2f, 0.53f)
curveToRelative(-2.76f, 0.0f, -5.0f, -2.24f, -5.0f, -5.0f)
curveToRelative(0.0f, -0.79f, 0.2f, -1.53f, 0.53f, -2.2f)
close()
moveTo(11.84f, 9.02f)
lineToRelative(3.15f, 3.15f)
lineToRelative(0.02f, -0.16f)
curveToRelative(0.0f, -1.66f, -1.34f, -3.0f, -3.0f, -3.0f)
lineToRelative(-0.17f, 0.01f)
close()
}
}
return _visibilityOff!!
}
private var _visibilityOff: ImageVector? = null
@Preview
@Sampled
@Composable
fun TextFieldSample() {
var text by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue("example", TextRange(0, 7)))
}
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}
@Preview
@Sampled
@Composable
fun OutlinedTextFieldSample() {
var text by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue("example", TextRange(0, 7)))
}
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}
@OptIn(ExperimentalComposeUiApi::class)
@Preview
@Sampled
@Composable
fun TextFieldWithHideKeyboardOnImeAction() {
val keyboardController = LocalSoftwareKeyboardController.current
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {
keyboardController?.hide()
// do something here
}
)
)
}
@Composable
fun TextArea() {
var text by rememberSaveable {
mutableStateOf(
"This is a very long input that extends beyond " +
"the height of the text area."
)
}
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.height(100.dp),
label = { Text("Label") }
)
}
@Preview
@Sampled
@Composable
fun CustomTextFieldBasedOnDecorationBox() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
val interactionSource = remember { MutableInteractionSource() }
// parameters below will be passed to BasicTextField for correct behavior of the text field,
// and to the decoration box for proper styling and sizing
val enabled = true
val singleLine = true
val passwordTransformation = PasswordVisualTransformation()
BasicTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
visualTransformation = passwordTransformation,
// internal implementation of the BasicTextField will dispatch focus events
interactionSource = interactionSource,
enabled = enabled,
singleLine = singleLine
) {
TextFieldDefaults.DecorationBox(
value = value,
visualTransformation = passwordTransformation,
innerTextField = it,
singleLine = singleLine,
enabled = enabled,
// same interaction source as the one passed to BasicTextField to read focus state
// for text field styling
interactionSource = interactionSource,
supportingText = { Text("Supporting text") },
// keep horizontal paddings but change the vertical
contentPadding = TextFieldDefaults.contentPaddingWithoutLabel(
top = 8.dp, bottom = 8.dp
),
)
}
}
}
@Preview
@Sampled
@Composable
fun CustomOutlinedTextFieldBasedOnDecorationBox() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
val interactionSource = remember { MutableInteractionSource() }
// parameters below will be passed to BasicTextField for correct behavior of the text field,
// and to the decoration box for proper styling and sizing
val enabled = true
val singleLine = true
val colors = OutlinedTextFieldDefaults.colors(
unfocusedBorderColor = Color.LightGray,
focusedBorderColor = Color.DarkGray
)
BasicTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
// internal implementation of the BasicTextField will dispatch focus events
interactionSource = interactionSource,
enabled = enabled,
singleLine = singleLine
) {
OutlinedTextFieldDefaults.DecorationBox(
value = value,
visualTransformation = VisualTransformation.None,
innerTextField = it,
singleLine = singleLine,
enabled = enabled,
// same interaction source as the one passed to BasicTextField to read focus state
// for text field styling
interactionSource = interactionSource,
supportingText = { Text("Supporting text") },
// keep horizontal paddings but change the vertical
contentPadding = TextFieldDefaults.contentPaddingWithoutLabel(
top = 8.dp, bottom = 8.dp
),
// update border colors
colors = colors,
// update border thickness and shape
container = {
OutlinedTextFieldDefaults.ContainerBox(
enabled = enabled,
isError = false,
colors = colors,
interactionSource = interactionSource,
shape = RectangleShape,
unfocusedBorderThickness = 2.dp,
focusedBorderThickness = 4.dp
)
},
)
}
}
}