Skip to content

Commit 7b11e28

Browse files
Add tooltip to search
You can add collection filters to a dashboard and then use them in the search bar on the index page. eg. `email:jared@example.com` However, these collection filters are not discoverable. You have to know about them already to know how to use them. This change introduces a tooltip which lists the available collection filters, allowing all users to discover what is available. The tooltip icon is `question-mark-circle` from [heroicons] (MIT licensed) We use [anchor positioning] with a [polyfill] to position the [popover] by the tooltip. Some basic JavaScript is introduced to show/hide the popover on hover to ensure the tooltip behaviour works as expected for a tooltip. I've had a first pass at the design, borrowing from existing styles. But I don't feel strongly about it if we want to go in a different direction. Do we want an option to disable the tooltip even if there are collection filters? [heroicons]: https://heroicons.com/solid [anchor positioning]: https://developer.mozilla.org/en-US/docs/Web/CSS/position-anchor [polyfill]: https://github.com/oddbird/css-anchor-positioning [popover]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/popover Closes #2750
1 parent 841b091 commit 7b11e28

File tree

13 files changed

+202
-12
lines changed

13 files changed

+202
-12
lines changed

app/assets/builds/administrate/application.css

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/assets/builds/administrate/application.css.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/assets/builds/administrate/application.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22172,6 +22172,14 @@
2217222172
// app/assets/javascripts/administrate/controllers/index.js
2217322173
application.register("select", select_controller_default);
2217422174
application.register("table", table_controller_default);
22175+
var searchPopover = document.querySelector("[popover][id='search-tooltip']");
22176+
var searchTooltip = document.querySelector("button[popovertarget='search-tooltip']");
22177+
searchTooltip.addEventListener("mouseenter", () => {
22178+
searchPopover.showPopover();
22179+
});
22180+
searchTooltip.addEventListener("mouseleave", () => {
22181+
searchPopover.hidePopover();
22182+
});
2217522183
})();
2217622184
/*! Bundled license information:
2217722185

app/assets/builds/administrate/application.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/assets/javascripts/administrate/controllers/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,13 @@ import TableController from "./table_controller";
55

66
application.register("select", SelectController);
77
application.register("table", TableController);
8+
9+
// Bug: Visit another page and come back to this page, the mouesover popover will not work
10+
const searchPopover = document.querySelector("[popover][id='search-tooltip']");
11+
const searchTooltip = document.querySelector("button[popovertarget='search-tooltip']");
12+
searchTooltip.addEventListener("mouseenter", () => {
13+
searchPopover.showPopover();
14+
});
15+
searchTooltip.addEventListener("mouseleave", () => {
16+
searchPopover.hidePopover();
17+
});

app/assets/stylesheets/administrate/components/_buttons.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,15 @@ button,
6161
.button--nav {
6262
margin-bottom: $base-spacing;
6363
}
64+
65+
.button--tooltip {
66+
background: none;
67+
color: inherit;
68+
border: none;
69+
cursor: pointer;
70+
padding: 0;
71+
72+
&:hover {
73+
background-color: unset !important;
74+
}
75+
}

app/assets/stylesheets/administrate/components/_search.scss

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,38 @@ $search-icon-size: 1rem;
4444
fill: $action-color;
4545
}
4646
}
47+
48+
.search__tooltip {
49+
margin-right: 2rem;
50+
anchor-name: --tooltip-anchor;
51+
52+
svg {
53+
height: 24px;
54+
width: 24px;
55+
fill: $grey-5;
56+
57+
&:hover {
58+
fill: $action-color;
59+
}
60+
}
61+
}
62+
63+
.search__tooltip-popover {
64+
position-anchor: --tooltip-anchor;
65+
position: fixed;
66+
top: anchor(bottom);
67+
left: anchor(center);
68+
transform: translateX(-50%);
69+
margin: 1rem;
70+
71+
padding: 2rem;
72+
border-color: $blue;
73+
background-color: $blue;
74+
color: $white;
75+
border-radius: $base-border-radius;
76+
width: max-content;
77+
}
78+
79+
.search__tooltip-popover-value {
80+
opacity: 0.5;
81+
}

app/controllers/administrate/application_controller.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ def index
1010
resources = order.apply(resources)
1111
resources = paginate_resources(resources)
1212
page = Administrate::Page::Collection.new(dashboard, order: order)
13+
filters = Administrate::Search.new(scoped_resource, dashboard, search_term).valid_filters
1314

1415
render locals: {
1516
resources: resources,
1617
search_term: search_term,
1718
page: page,
18-
show_search_bar: show_search_bar?
19+
show_search_bar: show_search_bar?,
20+
filters: filters
1921
}
2022
end
2123

app/views/administrate/application/_icons.html.erb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@
1010
<symbol id="icon-up-caret" viewBox="0 0 48 48">
1111
<path d="M2.988 33.02c-1.66 0-1.943-.81-.618-1.824l20-15.28c.878-.672 2.31-.67 3.188 0l20.075 15.288c1.316 1.003 1.048 1.816-.62 1.816H2.987z" />
1212
</symbol>
13+
14+
<symbol id="icon-question-mark" viewBox="0 0 24 24">
15+
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm11.378-3.917c-.89-.777-2.366-.777-3.255 0a.75.75 0 0 1-.988-1.129c1.454-1.272 3.776-1.272 5.23 0 1.513 1.324 1.513 3.518 0 4.842a3.75 3.75 0 0 1-.837.552c-.676.328-1.028.774-1.028 1.152v.75a.75.75 0 0 1-1.5 0v-.75c0-1.279 1.06-2.107 1.875-2.502.182-.088.351-.199.503-.331.83-.727.83-1.857 0-2.584ZM12 18a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
16+
</symbol>
1317
</svg>

app/views/administrate/application/_index_header.html.erb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
<%= display_resource_name(page.resource_name) %>
33
<% end %>
44

5+
<%#
6+
TODO: Move where this polyfill lives. Where?
7+
Also, do we want to import the polyfill js directly instead of linking to unpkg.com?
8+
%>
9+
<script type="module">
10+
if (!("anchorName" in document.documentElement.style)) {
11+
import("https://unpkg.com/@oddbird/css-anchor-positioning");
12+
}
13+
</script>
14+
515
<header class="main-content__header">
616
<h1 class="main-content__page-title" id="page-title">
717
<%= content_for(:title) %>
@@ -15,6 +25,23 @@
1525
search_term: search_term,
1626
resource_name: display_resource_name(page.resource_name)
1727
) %>
28+
29+
<% if filters.any? %>
30+
<button popovertarget="search-tooltip" class="button--tooltip search__tooltip">
31+
<svg role="img">
32+
<use xlink:href="#icon-question-mark" />
33+
</svg>
34+
</button>
35+
36+
<div popover id="search-tooltip" role="tooltip" class="search__tooltip-popover">
37+
<p><strong>Use filters to refine your search</strong></p>
38+
<ul>
39+
<% filters.keys.each do |filter_key| %>
40+
<li><%= filter_key %>:<span class="search__tooltip-popover-value">&lt;value&gt;</span></li>
41+
<% end %>
42+
</ul>
43+
</div>
44+
<% end %>
1845
<% end %>
1946

2047
<div>

app/views/administrate/application/index.html.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ It renders the `_table` partial to display details about the resources.
2929
search_term: search_term,
3030
page: page,
3131
show_search_bar: show_search_bar,
32+
filters: filters,
3233
)
3334
%>
3435

lib/administrate/search.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ def run
6363
end
6464
end
6565

66+
def valid_filters
67+
if @dashboard.class.const_defined?(:COLLECTION_FILTERS)
68+
@dashboard.class.const_get(:COLLECTION_FILTERS).stringify_keys
69+
else
70+
{}
71+
end
72+
end
73+
6674
private
6775

6876
def apply_filter(filter, filter_param, resources)
@@ -116,14 +124,6 @@ def search_results(resources)
116124
.where(query_template, *query_values)
117125
end
118126

119-
def valid_filters
120-
if @dashboard.class.const_defined?(:COLLECTION_FILTERS)
121-
@dashboard.class.const_get(:COLLECTION_FILTERS).stringify_keys
122-
else
123-
{}
124-
end
125-
end
126-
127127
def attribute_types
128128
@dashboard.class.const_get(:ATTRIBUTE_TYPES)
129129
end

spec/features/search_spec.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,54 @@ def have_a_search_bar
2525
end
2626
end
2727

28+
describe "search bar tooltip" do
29+
def have_a_search_tooltip
30+
have_css("button[popovertarget=search-tooltip]")
31+
end
32+
33+
def search_tooltip_icon
34+
find("button[popovertarget=search-tooltip]")
35+
end
36+
37+
it "is visible when the current dashboard has collection filters" do
38+
visit admin_customers_path
39+
40+
expect(page).to have_a_search_tooltip
41+
end
42+
43+
context "when clicked" do
44+
it "shows a popover with the available filters" do
45+
visit admin_customers_path
46+
47+
search_tooltip_icon.click
48+
49+
expect(page).to have_content("Use filters to refine your search")
50+
expect(page).to have_content("vip:<value>")
51+
expect(page).to have_content("kind:<value>")
52+
end
53+
end
54+
55+
it "is hidden when the current dashboard has no collection filters" do
56+
stub_const("CustomerDashboard::COLLECTION_FILTERS", {})
57+
58+
visit admin_customers_path
59+
60+
expect(page).not_to have_a_search_tooltip
61+
end
62+
63+
it "is hidden when nothing is searchable in the current dashboard" do
64+
CustomerDashboard::ATTRIBUTE_TYPES.each do |_name, field_class|
65+
allow(field_class).to(
66+
receive(:searchable?).and_return(false)
67+
)
68+
end
69+
70+
visit admin_customers_path
71+
72+
expect(page).not_to have_a_search_tooltip
73+
end
74+
end
75+
2876
scenario "admin searches for customer by email", :js do
2977
query = "bar@baz.com"
3078
perfect_match = create(:customer, email: "bar@baz.com")

0 commit comments

Comments
 (0)