diff --git a/8Knot/assets/color.css b/8Knot/assets/color.css index 089a39d0..44cd205e 100644 --- a/8Knot/assets/color.css +++ b/8Knot/assets/color.css @@ -62,6 +62,8 @@ /* MultiSelect pill colors */ --pill-default-bg: #555555; --pill-default-color: #ffffff; + --pill-pending-bg: #B03A2E; + --pill-pending-color: #ffffff; /* Table colors */ --table-bg-dark: #222; @@ -84,18 +86,18 @@ The .searching class is toggled by a callback in index_callbacks.py -/* Style for filter pills (grey by default when selected) */ +/* Style for filter pills (red by default when selected but search not executed) */ .mantine-MultiSelect-pill, .mantine-MultiSelect-value { - background-color: var(--gray-medium); - color: var(--color-white); + background-color: var(--pill-pending-bg); + color: var(--pill-pending-color); } .searchbar-dropdown .mantine-MultiSelect-pill, .searchbar-dropdown .mantine-MultiSelect-value { - background-color: var(--gray-medium); - color: var(--color-white); + background-color: var(--pill-pending-bg); + color: var(--pill-pending-color); } diff --git a/8Knot/assets/main_layout.css b/8Knot/assets/main_layout.css index 49a96103..5db3b717 100644 --- a/8Knot/assets/main_layout.css +++ b/8Knot/assets/main_layout.css @@ -473,6 +473,11 @@ button.icon-button.btn:active, padding: 0 6px; } +.search-submit-button { + margin-top: var(--spacing-sm); + width: 100%; +} + .search-controls-stack { width: 100%; justify-content: center; diff --git a/8Knot/assets/search_keyboard.js b/8Knot/assets/search_keyboard.js new file mode 100644 index 00000000..edb88b7c --- /dev/null +++ b/8Knot/assets/search_keyboard.js @@ -0,0 +1,44 @@ +/** + * Cascading Enter key handler for the search bar. + * + * When the Mantine MultiSelect dropdown has visible options, Enter + * selects the highlighted item (Mantine's default behavior — we do nothing). + * + * When there are no visible options (dropdown closed or empty), Enter + * triggers the search by clicking the search button. + */ +document.addEventListener("DOMContentLoaded", function () { + // Use MutationObserver to attach the handler once the MultiSelect input exists + var observer = new MutationObserver(function () { + var input = document.querySelector("#projects input"); + if (!input) return; + + // Stop observing once we've found the input + observer.disconnect(); + + input.addEventListener("keydown", function (e) { + if (e.key !== "Enter") return; + + // Check if the dropdown has visible selectable options. + // Mantine keeps the dropdown element in the DOM while focused, + // so we check for actual option elements instead. + var options = document.querySelectorAll( + ".mantine-MultiSelect-option" + ); + if (options.length > 0) { + // Dropdown has options — let Mantine handle Enter + // to select the highlighted item + return; + } + + // Dropdown is closed — trigger search + e.preventDefault(); + var searchButton = document.getElementById("search-button"); + if (searchButton) { + searchButton.click(); + } + }); + }); + + observer.observe(document.body, { childList: true, subtree: true }); +}); diff --git a/8Knot/pages/index/index_callbacks.py b/8Knot/pages/index/index_callbacks.py index 86734e3c..f8046201 100644 --- a/8Knot/pages/index/index_callbacks.py +++ b/8Knot/pages/index/index_callbacks.py @@ -437,10 +437,11 @@ def dynamic_multiselect_options(user_in: str, selections, cached_options): [Output("results-output-container", "children"), Output("repo-choices", "data")], [ Input("search", "n_clicks"), + Input("search-button", "n_clicks"), State("projects", "value"), ], ) -def multiselect_values_to_repo_ids(n_clicks, user_vals): +def multiselect_values_to_repo_ids(n_clicks, search_button_clicks, user_vals): if not user_vals: logging.warning("NOTHING SELECTED IN SEARCH BAR") raise dash.exceptions.PreventUpdate @@ -867,7 +868,7 @@ def hide_loading_on_landing(pathname): # Callback to change pill color when search is clicked # # This callback implements dynamic pill coloring: -# - When user selects repos/orgs: pills are grey (pending) +# - When user selects repos/orgs: pills are red (pending) # - When user clicks search icon: pills turn blue (active search) # - Default selection (chaoss) starts blue since search is auto-triggered # @@ -875,27 +876,27 @@ def hide_loading_on_landing(pathname): # ============================================================================ @callback( Output("projects", "className"), - [Input("search", "n_clicks"), Input("projects", "value")], + [Input("search", "n_clicks"), Input("search-button", "n_clicks"), Input("projects", "value")], prevent_initial_call=True, ) -def update_pill_color_on_search(_, selected_repos_orgs): +def update_pill_color_on_search(_, search_button_clicks, selected_repos_orgs): """Update pill color based on search action. When search icon is clicked, add 'searching' class to turn pills blue. - When values change (user is selecting), remove 'searching' class to keep pills grey. + When values change (user is selecting), remove 'searching' class to keep pills red. """ if not dash.ctx.triggered: return dash.no_update triggered_id = dash.ctx.triggered_id - if triggered_id == "search": + if triggered_id in ("search", "search-button"): # Search button clicked - add 'searching' class to turn pills blue logging.info(f"PILL COLOR: Search clicked - turning pills BLUE") return "searchbar-dropdown searching" if triggered_id == "projects": - # Values changed (user selecting) - remove 'searching' class to keep pills grey - logging.info(f"PILL COLOR: Values changed - turning pills GREY. Selected: {selected_repos_orgs}") + # Values changed (user selecting) - remove 'searching' class to keep pills red + logging.info(f"PILL COLOR: Values changed - turning pills RED. Selected: {selected_repos_orgs}") return "searchbar-dropdown" return dash.no_update diff --git a/8Knot/pages/index/search_utils.py b/8Knot/pages/index/search_utils.py index 8a34a371..826058ab 100644 --- a/8Knot/pages/index/search_utils.py +++ b/8Knot/pages/index/search_utils.py @@ -443,14 +443,14 @@ def create_multiselect_styles(): "margin": "var(--multiselect-item-margin)", "color": "var(--color-white)", }, - # Inline styles for grey pill default color (turns blue with "searching" class) + # Inline styles for pending pill color (red by default, turns blue with "searching" class) "value": { - "backgroundColor": "var(--pill-default-bg)", - "color": "var(--pill-default-color)", + "backgroundColor": "var(--pill-pending-bg)", + "color": "var(--pill-pending-color)", }, "pill": { - "backgroundColor": "var(--pill-default-bg)", - "color": "var(--pill-default-color)", + "backgroundColor": "var(--pill-pending-bg)", + "color": "var(--pill-pending-color)", }, } @@ -517,13 +517,20 @@ def create_search_input_section(initial_option): ], className="search-input-wrapper", ), + dbc.Button( + "Search", + id="search-button", + color="primary", + size="sm", + className="search-submit-button", + ), create_alert( alert_id="help-alert", children='Please ensure that your spelling is correct. \ If your selection definitely isn\'t present, please request that \ it be loaded using the help button "REPO/ORG Request" \ in the bottom right corner of the screen. \ - The search is only confirmed when you click the search icon and the pill turns blue.', + The search is only confirmed when you click the Search button (or press Enter) and the pill turns blue.', color="info", ), create_alert(