Skip to content

Conversation

@landomen
Copy link
Contributor

@landomen landomen commented Nov 28, 2025

Task/Issue URL: https://app.asana.com/1/137249556945/project/1210594645151737/task/1211659112661196?focus=true

Description

Adds a Compose version of the DaxTextField.

Steps to test this PR

ADS Preview

  • Go to ADS Preview screen and click on Text Input tab
  • Check compose versions

UI changes

See ADS Preview screen


Note

Introduces Compose text fields (standard and secure) with trailing icon API and previews, adds theme text-field colors, and enforces trailing icon usage via new lint checks.

  • Design System (Compose):
    • New components: DaxTextField and DaxSecureTextField with DaxTextFieldInputMode, DaxTextFieldLineLimits, keyboard options, input transformations, error/supporting text, and DaxTextFieldTrailingIconScope API.
    • Secure field: visibility toggle integrated with optional trailing icon.
    • Previews: comprehensive previews for states and configurations.
  • ADS Preview/Examples:
    • Integrates multiple Compose samples in ComponentTextInputFragment and component_text_input_view.xml (hint, single/multi/form, read-only, disabled, error, URL/IP, focus behaviors, observable text, password variants).
  • Theme:
    • Adds DuckDuckGoTextFieldColors (colors.textField.borders) and exposes DuckDuckGoTheme.iconColors; minor color addition White78.
  • Lint Rules:
    • New detectors: DaxTextFieldTrailingIconDetector and DaxSecureTextFieldTrailingIconDetector; registered in DuckDuckGoIssueRegistry with extensive tests to enforce using DaxTextFieldTrailingIconScope in trailingIcon.

Written by Cursor Bugbot for commit 33aa9f1. This will update automatically on new commits. Configure here.

Copy link
Contributor

@malmstein malmstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice work, just a couple of nits. I’ll leave it to @mikescamell for the final approval.

@mikescamell
Copy link
Contributor

Screenshot_20251210_103909

@landomen I can still click on the disabled version and get focus.

@mikescamell
Copy link
Contributor

Screenshot_20251210_103909 @landomen I can still click on the disabled version and get focus.

I think we need yo end up in a state of

            enabled = false,
            readOnly = true,

For it not to be focusable

@landomen
Copy link
Contributor Author

Screenshot_20251210_103909 @landomen I can still click on the disabled version and get focus.

I think we need yo end up in a state of

            enabled = false,
            readOnly = true,

For it not to be focusable

Thanks for catching this! This was a bug because of the wrong combination of arguments after I cleaned up the code 😅. It's fixed now with the new enum.

@landomen landomen force-pushed the feature/domen/compose-text-field branch from 89c6858 to b3b471e Compare December 12, 2025 14:35
@landomen
Copy link
Contributor Author

@mikescamell @GerardPaligot Thanks for all your great feedback! I've refactored the composable, please take another look when you get a chance.

Copy link
Contributor

@GerardPaligot GerardPaligot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new version is really good!

}

// Non-editable text full click listener with end icon
view.setupThemedComposeView(id = com.duckduckgo.common.ui.internal.R.id.compose_text_input_30, isDarkTheme = isDarkTheme) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a difference between the XML and Compose components here.
When I'm clicking on the XML view, the view isn't focused.
The same thing on the Compose component switch the border in blue (focus mode).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there is an easy way to prevent that. Would probably have to do something with the MutableInteractionSource but that could be tricky. In my opinion, the current behavior is okay, because you interacted with the text field (it's still enabled).

The XML one sometimes also behaves like that, at least on the "Non-editable text with line truncation" example. And there is a bug on the XML one where the keyboard still shows up and you can enter new text even though its set to non-editable 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Wouldn't this be solved by setting enabled to false when in ReadOnly state? I'm wondering if there are any ramifications to this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, from my opinion, the current behavior is okay too because if the component is enabled, it should be focusable (and therefore readable by screen readers). I'm okay to keep the compose behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be solved by setting enabled to false when in ReadOnly state?

@mikescamell Setting enabled=false and readOnly=true does make it not focus like we want. However, there are three side-effects: longer text is not scrollable, you can't copy text and the text field won't be picked up by accessibility services. I think those tradeoffs are not worth it.

These are descriptions of the fields from the documentation. I think Compose behavior is the correct one and the one we should prefer over the XML one. But let me know if you feel different.

  • Enabled: "When false, this component will not respond to user input, and it will appear visually disabled and disabled to accessibility services."

  • ReadOnly: "When true, the text field cannot be modified. However, a user can focus it and copy text from it. Read-only text fields are usually used to display pre-filled forms that a user cannot edit."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM @landomen, lets go with it 👍

// Editable password that fits in one line
view.setupThemedComposeView(id = com.duckduckgo.common.ui.internal.R.id.compose_text_input_6, isDarkTheme = isDarkTheme) {
val state = rememberTextFieldState("Loremipsumolor")
DaxSecureTextField(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a difference between the XML and Compose components here.
When I'm clicking on the XML view, the content is no more masked.
When I'm clicking on the Compose view, the content stay masked.
From my opinion, we should keep the Compose behavior but because there is a difference, I want to report it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reporting it!

Yeah, the XML one first shows unmasked text when focused and masking it requires action.
The Compose one first shows masked text when focused and unmasking it requires action.

I agree the Compose behavior is better, more secure and I think more in line with how a secure text input should behave. So I'm voting for keeping it. @mikescamell Your thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree, it feels weird to have the show password icon button if clicking into the shows the password implicitly 🤔

@malmstein any historical context on why this happens?

Clicking on the View version, password shown automatically:

Screenshot_20251216_095657

What we'd expect, clicking doesn't automatically show the password until the show password button is clicked:

Screenshot_20251216_095652

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@malmstein Any idea why the xml input field reveals the password automatically when clicked?

@landomen
Copy link
Contributor Author

landomen commented Dec 16, 2025

@landomen landomen force-pushed the feature/domen/compose-text-field branch from 464f7a6 to a7aa6a2 Compare December 16, 2025 06:56
Copy link
Contributor

@GerardPaligot GerardPaligot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential few changes to do in linter rules but for the rest, everything is good for me now! Well done!


// Only DaxTextFieldTrailingIcon should be used in the trailingIcon parameter
// If the source doesn't contain it, then it's invalid
return !source.contains("DaxTextFieldTrailingIcon")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably get member list declared in the scope object class and check if the source contains one member of this list? This will allow us to avoid to edit this linter rule every time we need to edit the scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the detector so it works with any additional new composable we declare in the scope. Let me know if this is what you had in mind

@landomen landomen force-pushed the feature/domen/compose-text-field branch from f23d1ef to 33aa9f1 Compare December 18, 2025 19:32
@landomen
Copy link
Contributor Author

landomen commented Dec 18, 2025

@mikescamell @GerardPaligot I've addressed the open comments, please take another look

Copy link
Contributor

@GerardPaligot GerardPaligot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 🎉 Congrats @landomen for this contribution

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants