left_sidebar: Standardize topic list filter input.

This follow-up commit replaces the current left sidebar topic list
filter input implementation with the redesigned input_wrapper
component.

This commit also serves as the base for supporting inputs using the
search_pill_widget, and thus adjusts the previously defined logic at
certain places to ensure that the input pills are handled and
displayed accurately.

Fixes part of #34476.
This commit is contained in:
Sayam Samal
2025-07-10 03:34:55 +05:30
committed by Tim Abbott
parent a4dd8e515d
commit f3fc26c6ff
6 changed files with 79 additions and 60 deletions

View File

@@ -838,7 +838,7 @@ export function initialize(): void {
// LEFT SIDEBAR
$("body").on("click", "#clear_search_topic_button", topic_list.clear_topic_search);
$("body").on("click", ".filter-topics .input-button", topic_list.clear_topic_search);
$(".streams_filter_icon").on("click", (e) => {
e.stopPropagation();

View File

@@ -1,6 +1,8 @@
import $ from "jquery";
$("body").on("input", ".input-element", function (this: HTMLInputElement, _e: JQuery.Event) {
// We use the `input` tag in the selector to avoid conflicts with the pill containing
// counterpart, which uses a `contenteditable` div instead of an input element.
$("body").on("input", "input.input-element", function (this: HTMLInputElement, _e: JQuery.Event) {
if (this.value.length === 0) {
$(this).removeClass("input-element-nonempty");
} else {
@@ -8,6 +10,25 @@ $("body").on("input", ".input-element", function (this: HTMLInputElement, _e: JQ
}
});
$("body").on(
"input change",
".has-input-pills .pill-container",
function (this: HTMLInputElement, _e: JQuery.Event) {
// We define another event handler for inputs with pill, similar to the one above.
// However, due to the way inputs with pill use a contenteditable div instead of an
// input element, we need to check the textContent of the pill container instead of
// the value of an input element.
// Here we need to listen to the `change` event in conjunction with the `input` event
// to handle the addition or removal of input pills.
const value = this.textContent?.trim() ?? "";
if (value.length === 0) {
$(this).removeClass("input-element-nonempty");
} else {
$(this).addClass("input-element-nonempty");
}
},
);
$("body").on(
"click",
".filter-input .input-button",

View File

@@ -374,26 +374,17 @@ export class LeftSidebarTopicListWidget extends TopicListWidget {
export function clear_topic_search(e: JQuery.Event): void {
e.stopPropagation();
search_pill_widget?.clear(true);
const $input = $("#topic_filter_query");
if ($input.length > 0) {
$input.text("");
$input.trigger("blur");
search_pill_widget?.clear(true);
update_clear_button();
// Since this changes the contents of the search input, we
// need to rerender the topic list.
const stream_ids = [...active_widgets.keys()];
const stream_id = stream_ids[0];
assert(stream_id !== undefined);
const widget = active_widgets.get(stream_id);
assert(widget !== undefined);
const parent_widget = widget.get_parent();
rebuild_left_sidebar(parent_widget, stream_id);
}
// Since the `clear` function of the search_pill_widget
// takes care of clearing both the text content and the
// pills, we just need to trigger an input event on the
// contenteditable element to reset the topic list via
// the `input` event handler without having to manually
// manage the reset of the topic list.
$input.trigger("input");
}
export function active_stream_id(): number | undefined {
@@ -517,7 +508,7 @@ const filter_options = new Map<string, string>([
export function update_clear_button(): void {
const $filter_query = $("#topic_filter_query");
const $clear_button = $("#clear_search_topic_button");
const $clear_button = $(".filter-topics .input-button");
if (get_left_sidebar_topic_search_term() === "" && get_typeahead_search_term() === "") {
$clear_button.css("visibility", "hidden");
// When we use backspace to clear the content of the search box,

View File

@@ -60,6 +60,38 @@
var(--input-button-ending-offset)
);
}
/* Special styles for input with pills */
&.has-input-pills .pill-container {
.input {
flex-grow: 1;
/* Override default values in web/styles/input_pill.css */
padding: 0;
line-height: inherit;
&:empty::before {
color: var(--color-text-placeholder);
content: attr(data-placeholder);
}
}
.pill {
height: 1.25em; /* 20px at 16px/1em */
}
&:has(.input:hover) {
border-color: var(--color-border-input-hover);
}
&:has(.input:focus) {
box-shadow: 0 0 5px var(--color-box-shadow-input-focus);
}
&:has(.input:focus),
&.input-element-nonempty:has(.input) {
@extend .input-active-styles;
}
}
}
.input-icon {
@@ -77,11 +109,15 @@
padding: 0.25em; /* 4px at 16px/1em */
}
.filter-input .input-element {
/* We use the `input` tag in the selector to avoid conflicts
with the pill containing counterpart, which uses a `contenteditable`
div instead of an input element, and thus doesn't support the
placeholder pseudo-classes. */
.filter-input input.input-element {
&:placeholder-shown {
/* In case of filter inputs, when the input field
is empty, we hide the input button and adjust
the right padding to compensate for the same. */
is empty, we hide the input button and adjust
the right padding to compensate for the same. */
padding-right: 0.5em;
~ .input-button {

View File

@@ -1363,31 +1363,8 @@ li.top_left_scheduled_messages {
}
}
.topic-list-filter {
grid-area: filter-box;
padding-right: var(--line-height-sidebar-row-prominent);
box-shadow: none;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
gap: 0.125em; /* 2px at 16px em */
background-color: var(--color-background-active-narrow-filter);
font-weight: 400;
.input:empty::before {
color: var(--color-text-placeholder);
content: attr(data-placeholder);
}
.input {
flex-grow: 1;
min-width: 0;
}
}
#clear_search_topic_button {
visibility: hidden;
height: 2em; /* 32px at 16px/1em */
.filter-topics {
font-weight: initial;
}
.searching-for-more-topics img {

View File

@@ -1,13 +1,7 @@
<div class="left-sidebar-filter-input-container">
<div class="topic_search_section filter-topics left-sidebar-filter-row">
<div class="topic-list-filter home-page-input filter_text_input pill-container" id="left-sidebar-filter-topic-input">
<div class="input" contenteditable="true" id="topic_filter_query"
data-placeholder="{{t 'Filter topics' }}">
{{~! Squash whitespace so that placeholder is displayed when empty. ~}}
</div>
{{#> input_wrapper input_type="filter-input" custom_classes="topic_search_section filter-topics has-input-pills" icon="search" input_button_icon="close"}}
<div class="input-element home-page-input pill-container" id="left-sidebar-filter-topic-input">
<div class="input" contenteditable="true" id="topic_filter_query" data-placeholder="{{t 'Filter topics' }}"></div>
</div>
<button type="button" class="clear_search_button" id="clear_search_topic_button">
<i class="zulip-icon zulip-icon-close" aria-hidden="true"></i>
</button>
</div>
{{/input_wrapper}}
</div>