Merge pull request #6948 from thundernest/add_number_input
Add TextFieldOutlinedNumber atom and NumberInput molecule to the design system
This commit is contained in:
commit
f99f6309ae
11 changed files with 325 additions and 6 deletions
|
@ -16,6 +16,7 @@ import app.k9mail.core.ui.compose.designsystem.atom.Icon
|
|||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextSubtitle1
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlined
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlinedEmailAddress
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlinedNumber
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlinedPassword
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlinedSelect
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.CheckboxInput
|
||||
|
@ -36,6 +37,8 @@ fun LazyGridScope.textFieldItems() {
|
|||
passwordTextFieldOutlinedItems()
|
||||
sectionSubtitleItem(text = "Email address")
|
||||
emailTextFieldOutlinedItems()
|
||||
sectionSubtitleItem(text = "Number")
|
||||
numberTextFieldOutlinedItems()
|
||||
sectionSubtitleItem(text = "Selection")
|
||||
selectionTextFieldOutlinedItems()
|
||||
}
|
||||
|
@ -215,6 +218,28 @@ private fun LazyGridScope.emailTextFieldOutlinedItems() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun LazyGridScope.numberTextFieldOutlinedItems() {
|
||||
item {
|
||||
TextFieldDemo(
|
||||
initialState = TextFieldState<Long?>(
|
||||
input = 123L,
|
||||
label = "Number",
|
||||
),
|
||||
) { state ->
|
||||
TextFieldOutlinedNumber(
|
||||
value = state.value.input,
|
||||
label = if (state.value.showLabel) state.value.label else null,
|
||||
onValueChange = { state.value = state.value.copy(input = it) },
|
||||
isEnabled = !state.value.isDisabled,
|
||||
isReadOnly = state.value.isReadOnly,
|
||||
isRequired = state.value.isRequired,
|
||||
hasError = state.value.hasError,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class TextFieldSelectState(
|
||||
val options: ImmutableList<String> = persistentListOf("Option 1", "Option 2", "Option 3"),
|
||||
val selectedOption: String = options.first(),
|
||||
|
|
|
@ -3,6 +3,7 @@ package app.k9mail.ui.catalog.ui.molecule.items
|
|||
import androidx.compose.foundation.lazy.grid.LazyGridScope
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.CheckboxInput
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.EmailAddressInput
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.NumberInput
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.PasswordInput
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.SelectInput
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.SwitchInput
|
||||
|
@ -65,6 +66,31 @@ fun LazyGridScope.inputItems() {
|
|||
}
|
||||
}
|
||||
|
||||
sectionHeaderItem(text = "NumberInput")
|
||||
sectionSubtitleItem(text = "Default")
|
||||
item {
|
||||
ItemOutlined {
|
||||
WithRememberedState<Long?>(input = null) { state ->
|
||||
NumberInput(
|
||||
value = state.value,
|
||||
onValueChange = { state.value = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
sectionSubtitleItem(text = "With error")
|
||||
item {
|
||||
ItemOutlined {
|
||||
WithRememberedState<Long?>(input = 123L) { state ->
|
||||
NumberInput(
|
||||
value = state.value,
|
||||
onValueChange = { state.value = it },
|
||||
errorMessage = "Invalid number",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sectionHeaderItem(text = "PasswordInput")
|
||||
sectionSubtitleItem(text = "Default")
|
||||
item {
|
||||
|
|
|
@ -39,7 +39,7 @@ fun TextFieldOutlinedEmailAddress(
|
|||
@Composable
|
||||
internal fun TextFieldOutlinedEmailAddressPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlined(
|
||||
TextFieldOutlinedEmailAddress(
|
||||
value = "Input text",
|
||||
onValueChange = {},
|
||||
)
|
||||
|
@ -74,7 +74,7 @@ internal fun TextFieldOutlinedEmailDisabledPreview() {
|
|||
@Composable
|
||||
internal fun TextFieldOutlinedEmailErrorPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlined(
|
||||
TextFieldOutlinedEmailAddress(
|
||||
value = "Input text",
|
||||
onValueChange = {},
|
||||
hasError = true,
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.OutlinedTextField as MaterialOutlinedTextField
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun TextFieldOutlinedNumber(
|
||||
value: Long?,
|
||||
onValueChange: (Long?) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
label: String? = null,
|
||||
isEnabled: Boolean = true,
|
||||
isReadOnly: Boolean = false,
|
||||
isRequired: Boolean = false,
|
||||
hasError: Boolean = false,
|
||||
) {
|
||||
MaterialOutlinedTextField(
|
||||
value = value?.toString() ?: "",
|
||||
onValueChange = {
|
||||
onValueChange(it.toLongOrNull())
|
||||
},
|
||||
modifier = modifier,
|
||||
enabled = isEnabled,
|
||||
label = selectLabel(label, isRequired),
|
||||
readOnly = isReadOnly,
|
||||
isError = hasError,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.NumberPassword,
|
||||
),
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextFieldOutlinedNumberPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlinedNumber(
|
||||
value = 123L,
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextFieldOutlinedNumberWithLabelPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlinedNumber(
|
||||
value = 123L,
|
||||
label = "Label",
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextFieldOutlinedNumberDisabledPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlinedNumber(
|
||||
value = 123L,
|
||||
onValueChange = {},
|
||||
isEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextFieldOutlinedNumberErrorPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlinedNumber(
|
||||
value = 123L,
|
||||
onValueChange = {},
|
||||
hasError = true,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ fun EmailAddressInput(
|
|||
errorMessage: String? = null,
|
||||
contentPadding: PaddingValues = inputContentPadding(),
|
||||
) {
|
||||
TextInputLayout(
|
||||
InputLayout(
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
errorMessage = errorMessage,
|
||||
|
|
|
@ -11,7 +11,7 @@ import app.k9mail.core.ui.compose.designsystem.atom.text.TextCaption
|
|||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
|
||||
@Composable
|
||||
internal fun TextInputLayout(
|
||||
internal fun InputLayout(
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = inputContentPadding(),
|
||||
errorMessage: String? = null,
|
|
@ -0,0 +1,73 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.molecule.input
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlinedNumber
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun NumberInput(
|
||||
onValueChange: (Long?) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
value: Long? = null,
|
||||
label: String? = null,
|
||||
isRequired: Boolean = false,
|
||||
errorMessage: String? = null,
|
||||
contentPadding: PaddingValues = inputContentPadding(),
|
||||
) {
|
||||
val inputError = remember { mutableStateOf<String?>(null) }
|
||||
|
||||
InputLayout(
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
errorMessage = errorMessage ?: inputError.value,
|
||||
) {
|
||||
TextFieldOutlinedNumber(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
label = label,
|
||||
isRequired = isRequired,
|
||||
hasError = errorMessage != null,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun IntegerInputPreview() {
|
||||
PreviewWithThemes {
|
||||
NumberInput(
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun IntegerInputIsRequiredPreview() {
|
||||
PreviewWithThemes {
|
||||
NumberInput(
|
||||
onValueChange = {},
|
||||
label = "Text input is required",
|
||||
isRequired = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun IntegerInputWithErrorPreview() {
|
||||
PreviewWithThemes {
|
||||
NumberInput(
|
||||
onValueChange = {},
|
||||
errorMessage = "Text input error",
|
||||
)
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ fun PasswordInput(
|
|||
errorMessage: String? = null,
|
||||
contentPadding: PaddingValues = inputContentPadding(),
|
||||
) {
|
||||
TextInputLayout(
|
||||
InputLayout(
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
errorMessage = errorMessage,
|
||||
|
|
|
@ -20,7 +20,7 @@ fun TextInput(
|
|||
contentPadding: PaddingValues = inputContentPadding(),
|
||||
isSingleLine: Boolean = true,
|
||||
) {
|
||||
TextInputLayout(
|
||||
InputLayout(
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
errorMessage = errorMessage,
|
||||
|
|
|
@ -227,6 +227,31 @@ class CommonTextFieldTest(
|
|||
}
|
||||
},
|
||||
),
|
||||
CommonTextFieldTestData(
|
||||
name = "TextFieldOutlinedNumber",
|
||||
content = { modifier, config ->
|
||||
if (config.isEnabled != null) {
|
||||
TextFieldOutlinedNumber(
|
||||
value = 123L,
|
||||
onValueChange = {},
|
||||
modifier = modifier,
|
||||
label = config.label,
|
||||
isEnabled = config.isEnabled,
|
||||
isReadOnly = config.isReadOnly,
|
||||
isRequired = config.isRequired,
|
||||
)
|
||||
} else {
|
||||
TextFieldOutlinedNumber(
|
||||
value = 123L,
|
||||
onValueChange = {},
|
||||
modifier = modifier,
|
||||
label = config.label,
|
||||
isRequired = config.isRequired,
|
||||
isReadOnly = config.isReadOnly,
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
CommonTextFieldTestData(
|
||||
name = "TextFieldOutlinedSelect",
|
||||
content = { modifier, config ->
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTextClearance
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import org.junit.Test
|
||||
|
||||
private const val TEST_TAG = "TextFieldOutlinedNumber"
|
||||
|
||||
class TextFieldOutlinedNumberKtTest : ComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `should call onValueChange with null when input is empty`() = runComposeTest {
|
||||
var value: Long? = 1L
|
||||
setContent {
|
||||
TextFieldOutlinedNumber(
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
modifier = Modifier.testTag(TEST_TAG),
|
||||
)
|
||||
}
|
||||
|
||||
onNodeWithTag(TEST_TAG).performClick()
|
||||
onNodeWithTag(TEST_TAG).performTextClearance()
|
||||
|
||||
assertThat(value).isEqualTo(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should call onValueChange when value changes`() = runComposeTest {
|
||||
var value: Long? = 123L
|
||||
setContent {
|
||||
TextFieldOutlinedNumber(
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
modifier = Modifier.testTag(TEST_TAG),
|
||||
)
|
||||
}
|
||||
|
||||
onNodeWithTag(TEST_TAG).performClick()
|
||||
onNodeWithTag(TEST_TAG).performTextInput("456")
|
||||
|
||||
assertThat(value).isEqualTo(123456L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return null when no number`() = runComposeTest {
|
||||
var value: Long? = 123L
|
||||
setContent {
|
||||
TextFieldOutlinedNumber(
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
modifier = Modifier.testTag(TEST_TAG),
|
||||
)
|
||||
}
|
||||
|
||||
onNodeWithTag(TEST_TAG).performClick()
|
||||
onNodeWithTag(TEST_TAG).performTextInput(",")
|
||||
|
||||
assertThat(value).isEqualTo(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return null when input exceeds max long`() = runComposeTest {
|
||||
var value: Long? = null
|
||||
setContent {
|
||||
TextFieldOutlinedNumber(
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
modifier = Modifier.testTag(TEST_TAG),
|
||||
)
|
||||
}
|
||||
|
||||
onNodeWithTag(TEST_TAG).performClick()
|
||||
onNodeWithTag(TEST_TAG).performTextInput("9223372036854775808")
|
||||
|
||||
assertThat(value).isEqualTo(null)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue