Skip to content

Commit

Permalink
Apply modal pattern to search box pop-up (#1932)
Browse files Browse the repository at this point in the history
Closes external issue
Quansight-Labs/czi-scientific-python-mgmt#83.

### Summary

This PR implements the pop-up search field as an HTML-native `<dialog>`.
This somewhat simplifies our implementation and brings accessibility
affordances with it.

I also introduced a couple visual changes, which fix #1714.
  • Loading branch information
gabalafou authored Jul 25, 2024
1 parent 9962752 commit 72a756d
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 67 deletions.
61 changes: 44 additions & 17 deletions src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ var findSearchInput = () => {
} else {
// must be at least one persistent form, use the first persistent one
form = document.querySelector(
"div:not(.search-button__search-container) > form.bd-search",
":not(#pst-search-dialog) > form.bd-search",
);
}
return form.querySelector("input");
Expand All @@ -208,22 +208,30 @@ var findSearchInput = () => {
*/
var toggleSearchField = () => {
// Find the search input to highlight
let input = findSearchInput();
const input = findSearchInput();

// if the input field is the hidden one (the one associated with the
// search button) then toggle the button state (to show/hide the field)
let searchPopupWrapper = document.querySelector(".search-button__wrapper");
let hiddenInput = searchPopupWrapper.querySelector("input");
const searchDialog = document.getElementById("pst-search-dialog");
const hiddenInput = searchDialog.querySelector("input");
if (input === hiddenInput) {
searchPopupWrapper.classList.toggle("show");
}
// when toggling off the search field, remove its focus
if (document.activeElement === input) {
input.blur();
if (searchDialog.open) {
searchDialog.close();
} else {
// Note: browsers should focus the input field inside the modal dialog
// automatically when it is opened.
searchDialog.showModal();
}
} else {
input.focus();
input.select();
input.scrollIntoView({ block: "center" });
// if the input field is not the hidden one, then toggle its focus state

if (document.activeElement === input) {
input.blur();
} else {
input.focus();
input.select();
input.scrollIntoView({ block: "center" });
}
}
};

Expand Down Expand Up @@ -295,11 +303,30 @@ var setupSearchButtons = () => {
btn.onclick = toggleSearchField;
});

// Add the search button overlay event callback
let overlay = document.querySelector(".search-button__overlay");
if (overlay) {
overlay.onclick = toggleSearchField;
}
// If user clicks outside the search modal dialog, then close it.
const searchDialog = document.getElementById("pst-search-dialog");
// Dialog click handler includes clicks on dialog ::backdrop.
searchDialog.addEventListener("click", (event) => {
if (!searchDialog.open) {
return;
}

// Dialog.getBoundingClientRect() does not include ::backdrop. (This is the
// trick that allows us to determine if click was inside or outside of the
// dialog: click handler includes backdrop, getBoundingClientRect does not.)
const { left, right, top, bottom } = searchDialog.getBoundingClientRect();

// 0, 0 means top left
const clickWasOutsideDialog =
event.clientX < left ||
right < event.clientX ||
event.clientY < top ||
bottom < event.clientY;

if (clickWasOutsideDialog) {
searchDialog.close();
}
});
};

/*******************************************************************************
Expand Down
87 changes: 42 additions & 45 deletions src/pydata_sphinx_theme/assets/styles/components/_search.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
color: var(--pst-color-text-muted);
}

// Hoist the focus ring from the input field to its parent
&:focus-within {
box-shadow: $focus-ring-box-shadow;

input:focus {
box-shadow: none;
}
}

.icon {
position: absolute;
color: var(--pst-color-border);
Expand All @@ -28,7 +37,11 @@
color: var(--pst-color-text-muted);
}

input {
input.form-control {
background-color: var(--pst-color-background);
color: var(--pst-color-text-base);
border: none;

// Inner-text of the search bar
&::placeholder {
color: var(--pst-color-text-muted);
Expand All @@ -39,46 +52,36 @@
&::-webkit-search-decoration {
appearance: none;
}

&:focus,
&:focus-visible {
color: var(--pst-color-text-muted);
}
}

// Shows off the keyboard shortcuts for the button
.search-button__kbd-shortcut {
display: flex;
position: absolute;
right: 0.5rem;
margin-inline-end: 0.5rem;
color: var(--pst-color-border);
}
}

.form-control {
background-color: var(--pst-color-background);
color: var(--pst-color-text-base);

&:focus,
&:focus-visible {
border: none;
background-color: var(--pst-color-background);
color: var(--pst-color-text-muted);
}
}

/**
* Search button - located in the navbar
*/

// Search link icon should be a bit bigger since it is separate from icon links
.search-button i {
// Search link icon should be a bit bigger since it is separate from icon links
font-size: 1.3rem;
}

// __search-container will only show up when we use the search pop-up bar
.search-button__search-container,
.search-button__overlay {
/**
* The search modal <dialog>
*/
#pst-search-dialog {
display: none;
}

.search-button__wrapper.show {
.search-button__search-container {
&[open] {
display: flex;

// Center in middle of screen just underneath header
Expand All @@ -91,30 +94,24 @@
margin-top: 0.5rem;
width: 90%;
max-width: 800px;
}
background-color: transparent;
padding: $focus-ring-width;
border: none;

.search-button__overlay {
display: flex;
position: fixed;
z-index: $zindex-modal-backdrop;
background-color: black;
opacity: 0.5;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
&::backdrop {
background-color: black;
opacity: 0.5;
}

form.bd-search {
flex-grow: 1;
padding-top: 0;
padding-bottom: 0;
}
form.bd-search {
flex-grow: 1;

// Font and input text a bit bigger
svg,
input {
font-size: var(--pst-font-size-icon);
// Font and input text a bit bigger
svg,
input {
font-size: var(--pst-font-size-icon);
}
}
}
}

Expand All @@ -141,7 +138,7 @@
border-radius: $search-button-border-radius;
}

// The keyboard shotcut text
// The keyboard shortcut text
.search-button__default-text {
font-size: var(--bs-nav-link-font-size);
font-weight: var(--bs-nav-link-font-weight);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<input type="search"
class="form-control"
name="q"
id="search-input"
placeholder="{{ theme_search_bar_text }}"
aria-label="{{ theme_search_bar_text }}"
autocomplete="off"
Expand Down
7 changes: 3 additions & 4 deletions src/pydata_sphinx_theme/theme/pydata_sphinx_theme/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,9 @@
id="pst-secondary-sidebar-checkbox"/>
<label class="overlay overlay-secondary" for="pst-secondary-sidebar-checkbox"></label>
{# A search field pop-up that will only show when the search button is clicked #}
<div class="search-button__wrapper">
<div class="search-button__overlay"></div>
<div class="search-button__search-container">{% include "../components/search-field.html" %}</div>
</div>
<dialog id="pst-search-dialog">
{% include "../components/search-field.html" %}
</dialog>

{% include "sections/announcement.html" %}

Expand Down

0 comments on commit 72a756d

Please sign in to comment.