diff --git a/8Knot/assets/color.css b/8Knot/assets/color.css index 089a39d0..e8834d4a 100644 --- a/8Knot/assets/color.css +++ b/8Knot/assets/color.css @@ -60,8 +60,8 @@ --button-text-light: #fff9f9; /* MultiSelect pill colors */ - --pill-default-bg: #555555; - --pill-default-color: #ffffff; + --pill-pending-bg: #B03A2E; + --pill-text-color: #ffffff; /* Table colors */ --table-bg-dark: #222; @@ -84,18 +84,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-text-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-text-color); } diff --git a/8Knot/assets/main_layout.css b/8Knot/assets/main_layout.css index 49a96103..1bcf90e9 100644 --- a/8Knot/assets/main_layout.css +++ b/8Knot/assets/main_layout.css @@ -34,7 +34,7 @@ --placeholder-top-offset: 1px; /* MultiSelect specific sizing */ --multiselect-min-height: 48px; - --multiselect-padding: 12px 16px 12px 44px; + --multiselect-padding: 12px 16px; --multiselect-item-margin: 2px 4px; } @@ -332,20 +332,6 @@ a.sidebar-section-text { white-space: nowrap; } -.search-icon { - background-color: transparent; - border: none; - font-size: 16px; - width: 16px; - height: 16px; - position: absolute; - left: 10px; - top: 50%; - transform: translateY(-100%); - font-weight: bold; - z-index: 2; -} - /* Search Controls */ @@ -473,6 +459,12 @@ button.icon-button.btn:active, padding: 0 6px; } +.search-button-wrapper { + display: flex; + justify-content: flex-end; + margin-top: var(--spacing-sm); +} + .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..36398899 --- /dev/null +++ b/8Knot/assets/search_keyboard.js @@ -0,0 +1,32 @@ +/** + * Cascading Enter key handler for the search bar. + * + * When the Mantine MultiSelect dropdown is open (aria-expanded="true"), + * Enter selects the highlighted item (Mantine's default — we do nothing). + * + * When the dropdown is closed, Enter triggers the search by incrementing + * the search-trigger dcc.Store via dash_clientside.set_props. + */ +document.addEventListener("DOMContentLoaded", function () { + var observer = new MutationObserver(function () { + var input = document.querySelector("#projects input"); + if (!input) return; + + observer.disconnect(); + + input.addEventListener("keydown", function (e) { + if (e.key !== "Enter") return; + + // If dropdown is open, let Mantine handle Enter to select the highlighted item + if (input.getAttribute("aria-expanded") === "true") return; + + // Dropdown is closed — trigger search via the dcc.Store + e.preventDefault(); + var store = document.querySelector("#search-trigger"); + var current = store ? JSON.parse(store.textContent || "0") : 0; + dash_clientside.set_props("search-trigger", { data: current + 1 }); + }); + }); + + 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..2910cc26 100644 --- a/8Knot/pages/index/index_callbacks.py +++ b/8Knot/pages/index/index_callbacks.py @@ -436,11 +436,14 @@ def dynamic_multiselect_options(user_in: str, selections, cached_options): @callback( [Output("results-output-container", "children"), Output("repo-choices", "data")], [ - Input("search", "n_clicks"), + Input("search-button", "n_clicks"), + Input("search-trigger", "data"), State("projects", "value"), ], ) -def multiselect_values_to_repo_ids(n_clicks, user_vals): +def multiselect_values_to_repo_ids(search_button_clicks, search_trigger, user_vals): + if dash.ctx.triggered_id not in ("search-button", "search-trigger"): + raise dash.exceptions.PreventUpdate if not user_vals: logging.warning("NOTHING SELECTED IN SEARCH BAR") raise dash.exceptions.PreventUpdate @@ -867,7 +870,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 +878,27 @@ def hide_loading_on_landing(pathname): # ============================================================================ @callback( Output("projects", "className"), - [Input("search", "n_clicks"), Input("projects", "value")], + [Input("search-button", "n_clicks"), Input("search-trigger", "data"), 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, search_trigger, 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": - # Search button clicked - add 'searching' class to turn pills blue + if triggered_id in ("search-button", "search-trigger"): + # Search button clicked or Enter key pressed - 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..1385bc2c 100644 --- a/8Knot/pages/index/search_utils.py +++ b/8Knot/pages/index/search_utils.py @@ -404,6 +404,7 @@ def create_search_storage_components(): create_store("cached-options", storage_type="session"), html.Div(id="cache-init-trigger", className="hidden"), create_store("search-cache-init-hidden", storage_type="session"), + create_store("search-trigger", storage_type="memory", data=0), ] @@ -443,14 +444,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-text-color)", }, "pill": { - "backgroundColor": "var(--pill-default-bg)", - "color": "var(--pill-default-color)", + "backgroundColor": "var(--pill-pending-bg)", + "color": "var(--pill-text-color)", }, } @@ -496,34 +497,26 @@ def create_search_input_section(initial_option): html.Div( [ create_search_multiselect(initial_option), - create_button( - button_id="search", - content=html.I(className="fas fa-search"), - title="Search", - custom_style={ - "backgroundColor": "transparent", - "border": "none", - "fontSize": "16px", - "width": "16px", - "height": "16px", - "position": "absolute", - "left": "10px", - "top": "50%", - "transform": "translateY(-100%)", - "fontWeight": "bold", - "zIndex": 2, - }, - ), ], className="search-input-wrapper", ), + html.Div( + dbc.Button( + "Search", + id="search-button", + color="outline-secondary", + size="sm", + className="about-graph-button", + ), + className="search-button-wrapper", + ), 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 and the pill turns blue.', color="info", ), create_alert(