Skip to content

Commit

Permalink
LMN-773: Add clear action for app search modal (#1801)
Browse files Browse the repository at this point in the history
* LMN-773: Add clear action for app search modal

* Updated snapshots for 'rtl'

* Updated snapshots for 'dm'

* Updated snapshots for 'default'

* Make clearAction required for app search modal

* Remove unused param

* Updated snapshots for 'rtl'

* Updated snapshots for 'dm'

* Updated snapshots for 'rtl'

* Updated snapshots for 'dm'

* Updated snapshots for 'default'

* Put clear action into clear status.

* Updated snapshots for 'dm'

* Updated snapshots for 'rtl'

* Updated snapshots for 'default'

* Use separate clear action instead.

* Fix animation visibility condition

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
karljiang1 and github-actions[bot] authored Dec 19, 2023
1 parent c6292f5 commit 510977a
Show file tree
Hide file tree
Showing 24 changed files with 160 additions and 20 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,21 @@ class BpkTextFieldTest : BpkSnapshotTest() {
}
}

@Test
@Variants(BpkTestVariant.Default, BpkTestVariant.Rtl)
fun withClearStatus() {
snap {
BpkTextField(
value = "Value",
onValueChange = {},
placeholder = "Placeholder",
status = BpkFieldStatus.Default,
icon = BpkIcon.Accessibility,
clearAction = BpkClearAction("Clear") {},
)
}
}

@Test
@Variants(BpkTestVariant.Default)
fun singleLine() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import net.skyscanner.backpack.compose.appsearchmodal.BpkSectionHeading
import net.skyscanner.backpack.compose.appsearchmodal.BpkShortcut
import net.skyscanner.backpack.compose.button.BpkButton
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.textfield.BpkClearAction
import net.skyscanner.backpack.compose.tokens.Airports
import net.skyscanner.backpack.compose.tokens.City
import net.skyscanner.backpack.compose.tokens.Landmark
Expand Down Expand Up @@ -115,6 +116,7 @@ internal fun DefaultAppSearchModalSample(
closeAccessibilityLabel = stringResource(id = R.string.navigation_close),
onClose = { showModal.value = false },
onInputChanged = { destination.value = it },
clearAction = BpkClearAction(stringResource(id = R.string.text_field_clear_action_description)) { destination.value = "" },
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package net.skyscanner.backpack.demo.compose

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
Expand All @@ -35,10 +36,12 @@ import net.skyscanner.backpack.compose.fieldset.BpkFieldStatus
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.text.BpkText
import net.skyscanner.backpack.compose.textarea.BpkTextArea
import net.skyscanner.backpack.compose.textfield.BpkClearAction
import net.skyscanner.backpack.compose.textfield.BpkTextField
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.tokens.Accessibility
import net.skyscanner.backpack.compose.tokens.BpkSpacing
import net.skyscanner.backpack.compose.tokens.Search
import net.skyscanner.backpack.demo.R
import net.skyscanner.backpack.demo.components.TextFieldComponent
import net.skyscanner.backpack.demo.meta.ComposeStory
Expand Down Expand Up @@ -78,6 +81,30 @@ fun TextFieldStory(
}
}

@Composable
@TextFieldComponent
@ComposeStory(name = "Clear")
fun TextClearActionStory(modifier: Modifier = Modifier) {
val initialValue = stringResource(R.string.city_shenzhen)
var value by remember { mutableStateOf(initialValue) }

Column(
modifier = modifier.padding(BpkSpacing.Base),
verticalArrangement = Arrangement.spacedBy(BpkSpacing.Base),
) {
BpkText(stringResource(R.string.with_clear_action_title))
BpkTextField(
value = value,
onValueChange = { value = it },
placeholder = stringResource(R.string.generic_placeholder),
icon = BpkIcon.Search,
clearAction = BpkClearAction(stringResource(R.string.text_field_clear_action_description)) {
value = ""
},
)
}
}

@Composable
@TextFieldComponent
@ComposeStory("Disabled", StoryKind.ScreenshotOnly)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import net.skyscanner.backpack.compose.navigationbar.IconAction
import net.skyscanner.backpack.compose.navigationbar.NavIcon
import net.skyscanner.backpack.compose.navigationbar.nestedScroll
import net.skyscanner.backpack.compose.navigationbar.rememberTopAppBarState
import net.skyscanner.backpack.compose.textfield.BpkClearAction
import net.skyscanner.backpack.compose.textfield.BpkTextField
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.tokens.BpkSpacing
Expand Down Expand Up @@ -99,6 +100,9 @@ fun ComponentListScreen(
.fillMaxWidth()
.padding(BpkSpacing.Base),
icon = BpkIcon.Search,
clearAction = BpkClearAction(stringResource(R.string.text_field_clear_action_description)) {
searchQuery = ""
},
)
val filteredTokens = repository.tokenComponents.filter {
it.name.contains(searchQuery, ignoreCase = true)
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,6 @@
<string name="inset_banner_cta_text">Sponsored</string>
<string name="inset_banner_cta_accessibility_label">Acessibility Label</string>
<string name="inset_banner_body">You can change your destination, date of travel, or both, with no change fee. Valid for all new bookings made up to 31 May for travel between now and 31 December 2020.</string>
<string name="text_field_clear_action_description">Clear</string>
<string name="with_clear_action_title">With clear action</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import net.skyscanner.backpack.compose.modal.BpkModal
import net.skyscanner.backpack.compose.modal.BpkModalState
import net.skyscanner.backpack.compose.modal.rememberBpkModalState
import net.skyscanner.backpack.compose.navigationbar.NavIcon
import net.skyscanner.backpack.compose.textfield.BpkClearAction

sealed class BpkAppSearchModalResult {
data class Content(
Expand Down Expand Up @@ -74,6 +75,7 @@ fun BpkAppSearchModal(
closeAccessibilityLabel: String,
onInputChanged: (String) -> Unit,
onClose: () -> Unit,
clearAction: BpkClearAction,
modifier: Modifier = Modifier,
state: BpkModalState = rememberBpkModalState(),
) {
Expand All @@ -96,6 +98,7 @@ fun BpkAppSearchModal(
inputHint = inputHint,
results = results,
onInputChanged = onInputChanged,
clearAction = clearAction,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import net.skyscanner.backpack.compose.appsearchmodal.BpkAppSearchModalResult
import net.skyscanner.backpack.compose.fieldset.BpkFieldStatus
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.textfield.BpkClearAction
import net.skyscanner.backpack.compose.textfield.BpkTextField
import net.skyscanner.backpack.compose.tokens.BpkSpacing
import net.skyscanner.backpack.compose.tokens.Search
Expand All @@ -35,6 +38,7 @@ internal fun BpkAppSearchModalImpl(
inputHint: String,
results: BpkAppSearchModalResult,
onInputChanged: (String) -> Unit,
clearAction: BpkClearAction,
modifier: Modifier = Modifier,
) {
when (results) {
Expand All @@ -48,10 +52,13 @@ internal fun BpkAppSearchModalImpl(
icon = BpkIcon.Search,
modifier = Modifier
.fillMaxWidth()
.padding(BpkSpacing.Base),
.padding(BpkSpacing.Base)
.testTag("searchModalTextField"),
value = inputText,
placeholder = inputHint,
onValueChange = onInputChanged,
status = BpkFieldStatus.Default,
clearAction = clearAction,
)
if (results is BpkAppSearchModalResult.Content) {
BpkSearchModalContent(results)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ fun BpkFieldSet(
when (status) {
is BpkFieldStatus.Disabled -> BpkTheme.colors.textDisabled
is BpkFieldStatus.Error -> BpkTheme.colors.textError
is BpkFieldStatus.Validated -> BpkTheme.colors.textPrimary
is BpkFieldStatus.Default -> BpkTheme.colors.textPrimary
else -> BpkTheme.colors.textPrimary
},
).value,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ import net.skyscanner.backpack.compose.fieldset.LocalFieldStatus
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.textfield.internal.BpkTextFieldImpl

data class BpkClearAction(
val contentDescription: String,
val onClick: () -> Unit,
)

@Composable
fun BpkTextField(
value: String,
Expand All @@ -45,6 +50,7 @@ fun BpkTextField(
keyboardActions: KeyboardActions = KeyboardActions(),
maxLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
clearAction: BpkClearAction? = null,
) {
BpkTextFieldImpl(
value = value,
Expand All @@ -59,6 +65,7 @@ fun BpkTextField(
keyboardActions = keyboardActions,
maxLines = maxLines,
interactionSource = interactionSource,
clearAction = clearAction,
)
}

Expand All @@ -76,6 +83,7 @@ fun BpkTextField(
keyboardActions: KeyboardActions = KeyboardActions(),
maxLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
clearAction: BpkClearAction? = null,
) {
BpkTextFieldImpl(
value = value,
Expand All @@ -90,5 +98,6 @@ fun BpkTextField(
keyboardActions = keyboardActions,
maxLines = maxLines,
interactionSource = interactionSource,
clearAction = clearAction,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow
Expand All @@ -57,11 +59,14 @@ import net.skyscanner.backpack.compose.fieldset.LocalFieldStatus
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.icon.BpkIconSize
import net.skyscanner.backpack.compose.text.BpkText
import net.skyscanner.backpack.compose.textfield.BpkClearAction
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.tokens.BpkBorderRadius
import net.skyscanner.backpack.compose.tokens.BpkSpacing
import net.skyscanner.backpack.compose.tokens.CloseCircle
import net.skyscanner.backpack.compose.tokens.ExclamationCircle
import net.skyscanner.backpack.compose.tokens.TickCircle
import net.skyscanner.backpack.compose.utils.clickable
import net.skyscanner.backpack.compose.utils.hideContentIf

@Composable
Expand All @@ -80,6 +85,7 @@ internal fun BpkTextFieldImpl(
maxLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
trailingIcon: BpkIcon? = null,
clearAction: BpkClearAction? = null,
) {

var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = value)) }
Expand All @@ -105,6 +111,7 @@ internal fun BpkTextFieldImpl(
maxLines = maxLines,
interactionSource = interactionSource,
trailingIcon = trailingIcon,
clearAction = clearAction,
)
}

Expand All @@ -124,6 +131,7 @@ internal fun BpkTextFieldImpl(
maxLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
trailingIcon: BpkIcon? = null,
clearAction: BpkClearAction? = null,
) {
BasicTextField(
value = value,
Expand Down Expand Up @@ -158,6 +166,7 @@ internal fun BpkTextFieldImpl(
interactionSource = interactionSource,
trailingIcon = trailingIcon,
textFieldContent = it,
clearAction = if (readOnly) null else clearAction, // Remove clearAction if readOnly enabled.
)
},
)
Expand All @@ -173,6 +182,7 @@ private fun TextFieldBox(
maxLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
trailingIcon: BpkIcon? = null,
clearAction: BpkClearAction? = null,
textFieldContent: @Composable () -> Unit,
) {
val isFocused by interactionSource.collectIsFocusedAsState()
Expand Down Expand Up @@ -230,7 +240,6 @@ private fun TextFieldBox(
textFieldContent()
}

var lastIcon by remember { mutableStateOf<Pair<BpkIcon, Color>?>(null) }
if (trailingIcon != null) {
BpkIcon(
icon = trailingIcon,
Expand All @@ -244,23 +253,48 @@ private fun TextFieldBox(
).value,
)
} else {
when (status) {
is BpkFieldStatus.Validated -> lastIcon = Pair(BpkIcon.TickCircle, BpkTheme.colors.statusSuccessSpot)
is BpkFieldStatus.Error -> lastIcon = Pair(BpkIcon.ExclamationCircle, BpkTheme.colors.statusDangerSpot)
else -> Unit // do nothing
var lastIcon by remember { mutableStateOf<Icon?>(null) }
when {
status is BpkFieldStatus.Validated -> lastIcon =
Icon(icon = BpkIcon.TickCircle, size = BpkIconSize.Large, color = BpkTheme.colors.statusSuccessSpot)

status is BpkFieldStatus.Error -> lastIcon = Icon(
icon = BpkIcon.ExclamationCircle,
size = BpkIconSize.Large,
color = BpkTheme.colors.statusDangerSpot,
)

status is BpkFieldStatus.Default && clearAction != null && value.text.isNotEmpty() -> lastIcon = Icon(
icon = BpkIcon.CloseCircle,
contentDescription = clearAction.contentDescription,
size = BpkIconSize.Small,
color = BpkTheme.colors.textSecondary,
modifier = Modifier
.clickable(bounded = false, role = Role.Button) {
clearAction.onClick()
}
.testTag("textFieldClearButton"),
)

else -> Unit
}

val isAnimationVisible =
status is BpkFieldStatus.Validated || status is BpkFieldStatus.Error ||
(status is BpkFieldStatus.Default && clearAction != null && value.text.isNotEmpty())
AnimatedVisibility(
visible = status is BpkFieldStatus.Validated || status is BpkFieldStatus.Error,
visible = isAnimationVisible,
enter = fadeIn() + scaleIn(),
exit = scaleOut() + fadeOut(),
) {
lastIcon?.let {
Crossfade(it) { (icon, color) ->
lastIcon?.let { it ->
Crossfade(it, label = "textFieldTrailingIcon") {
BpkIcon(
icon = icon,
contentDescription = null,
size = BpkIconSize.Large,
tint = color,
icon = it.icon,
contentDescription = it.contentDescription,
size = it.size,
tint = it.color,
modifier = it.modifier,
)
}
}
Expand All @@ -270,3 +304,11 @@ private fun TextFieldBox(
}

private val Shape = RoundedCornerShape(BpkBorderRadius.Sm)

private data class Icon(
val icon: BpkIcon,
val size: BpkIconSize,
val color: Color,
val contentDescription: String? = null,
val modifier: Modifier = Modifier,
)
Loading

0 comments on commit 510977a

Please sign in to comment.