Skip to content

Commit 64f1b97

Browse files
martin-brennanjjaffeuxjordanvidrine
authored
FEATURE: Add welcome banner to core (#31516)
This is a stripped-back version of the Search Banner component https://meta.discourse.org/t/search-banner/122939, which will be renamed to Advanced Search Banner, see discourse/discourse-search-banner#84. This welcome banner interacts with the header search. When `search_experience` is set to `search_field`, we only show the header search after the welcome banner scrolls out of view, and vice-versa. Only new sites will get this feature turned on by default, existing sites have a migration to disable it. --------- Co-authored-by: Joffrey JAFFEUX <[email protected]> Co-authored-by: Jordan Vidrine <[email protected]>
1 parent c5e95b4 commit 64f1b97

File tree

20 files changed

+421
-13
lines changed

20 files changed

+421
-13
lines changed

app/assets/javascripts/discourse/app/components/header/header-search.gjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default class HeaderSearch extends Component {
2929

3030
<template>
3131
{{#if this.shouldDisplay}}
32-
{{bodyClass "header-search--visible"}}
32+
{{bodyClass "header-search--enabled"}}
3333
<div
3434
class="floating-search-input-wrapper"
3535
{{this.handleKeyboardShortcut}}
@@ -46,7 +46,7 @@ export default class HeaderSearch extends Component {
4646
@href={{this.advancedSearchButtonHref}}
4747
/>
4848

49-
<SearchMenu />
49+
<SearchMenu @location="header" />
5050
</div>
5151
</div>
5252
</div>

app/assets/javascripts/discourse/app/components/search-menu-panel.gjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default class SearchMenuPanel extends Component {
2121
@onClose={{@closeSearchMenu}}
2222
@inlineResults={{true}}
2323
@autofocusInput={{true}}
24+
@location="header"
2425
/>
2526
</MenuPanel>
2627
</template>

app/assets/javascripts/discourse/app/components/search-menu.gjs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Component from "@glimmer/component";
22
import { tracked } from "@glimmer/tracking";
3-
import { hash } from "@ember/helper";
3+
import { concat, hash } from "@ember/helper";
44
import { on } from "@ember/modifier";
55
import { action } from "@ember/object";
66
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
@@ -14,6 +14,7 @@ import AdvancedButton from "discourse/components/search-menu/advanced-button";
1414
import ClearButton from "discourse/components/search-menu/clear-button";
1515
import Results from "discourse/components/search-menu/results";
1616
import SearchTerm from "discourse/components/search-menu/search-term";
17+
import concatClass from "discourse/helpers/concat-class";
1718
import loadingSpinner from "discourse/helpers/loading-spinner";
1819
import { popupAjaxError } from "discourse/lib/ajax-error";
1920
import { CANCELLED_STATUS } from "discourse/lib/autocomplete";
@@ -392,7 +393,9 @@ export default class SearchMenu extends Component {
392393
{{! template-lint-disable no-invalid-interactive }}
393394
{{on "keydown" this.onKeydown}}
394395
>
395-
<div class="search-input">
396+
<div
397+
class={{concatClass "search-input" (concat "search-input--" @location)}}
398+
>
396399
{{#if this.search.inTopicContext}}
397400
<DButton
398401
@icon="xmark"
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import Component from "@glimmer/component";
2+
import { tracked } from "@glimmer/tracking";
3+
import { service } from "@ember/service";
4+
import { htmlSafe } from "@ember/template";
5+
import { modifier } from "ember-modifier";
6+
import DButton from "discourse/components/d-button";
7+
import PluginOutlet from "discourse/components/plugin-outlet";
8+
import SearchMenu from "discourse/components/search-menu";
9+
import bodyClass from "discourse/helpers/body-class";
10+
import { prioritizeNameFallback } from "discourse/lib/settings";
11+
import { i18n } from "discourse-i18n";
12+
13+
export default class WelcomeBanner extends Component {
14+
@service router;
15+
@service siteSettings;
16+
@service currentUser;
17+
18+
@tracked inViewport = true;
19+
20+
checkViewport = modifier((element) => {
21+
const observer = new IntersectionObserver(
22+
([entry]) => {
23+
this.inViewport = entry.isIntersecting;
24+
},
25+
{ threshold: 1.0 }
26+
);
27+
28+
observer.observe(element);
29+
30+
return () => observer.disconnect();
31+
});
32+
33+
get displayForRoute() {
34+
return this.siteSettings.top_menu
35+
.split("|")
36+
.any(
37+
(menuItem) => `discovery.${menuItem}` === this.router.currentRouteName
38+
);
39+
}
40+
41+
get headerText() {
42+
if (!this.currentUser) {
43+
return i18n("welcome_banner.header.anonymous_members", {
44+
site_name: this.siteSettings.title,
45+
});
46+
}
47+
48+
return i18n("welcome_banner.header.logged_in_members", {
49+
preferred_display_name: prioritizeNameFallback(
50+
this.currentUser.name,
51+
this.currentUser.username
52+
),
53+
});
54+
}
55+
56+
get shouldDisplay() {
57+
if (!this.siteSettings.enable_welcome_banner) {
58+
return false;
59+
}
60+
61+
return this.displayForRoute;
62+
}
63+
64+
<template>
65+
{{#if this.shouldDisplay}}
66+
{{#if this.inViewport}}
67+
{{bodyClass "welcome-banner--visible"}}
68+
{{/if}}
69+
70+
<div class="welcome-banner" {{this.checkViewport}}>
71+
<div class="custom-search-banner welcome-banner__inner-wrapper">
72+
<div class="custom-search-banner-wrap welcome-banner__wrap">
73+
<h1 class="welcome-banner__title">{{htmlSafe this.headerText}}</h1>
74+
<PluginOutlet @name="welcome-banner-below-headline" />
75+
<div class="search-menu welcome-banner__search-menu">
76+
<DButton
77+
@icon="magnifying-glass"
78+
@title="search.open_advanced"
79+
@href="/search?expanded=true"
80+
class="search-icon"
81+
/>
82+
<SearchMenu @location="welcome-banner" />
83+
</div>
84+
<PluginOutlet @name="welcome-banner-below-input" />
85+
</div>
86+
</div>
87+
</div>
88+
{{/if}}
89+
</template>
90+
}

app/assets/javascripts/discourse/app/templates/application.gjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import RenderGlimmerContainer from "discourse/components/render-glimmer-containe
2525
import Sidebar from "discourse/components/sidebar";
2626
import SoftwareUpdatePrompt from "discourse/components/software-update-prompt";
2727
import TopicEntrance from "discourse/components/topic-entrance";
28+
import WelcomeBanner from "discourse/components/welcome-banner";
2829
import routeAction from "discourse/helpers/route-action";
2930
import { i18n } from "discourse-i18n";
3031
import DMenus from "float-kit/components/d-menus";
@@ -90,6 +91,9 @@ export default RouteTemplate(
9091

9192
<div id="main-outlet">
9293
<PluginOutlet @name="above-main-container" @connectorTagName="div" />
94+
95+
<WelcomeBanner />
96+
9397
<div class="container" id="main-container">
9498
{{#if @controller.showTop}}
9599
<CustomHtml @name="top" />

app/assets/javascripts/discourse/tests/acceptance/search-test.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,16 @@ acceptance("Search - Anonymous", function (needs) {
9898
await visit("/");
9999

100100
await click("#search-button");
101-
assert.dom(".search-menu").exists();
101+
assert.dom(".search-menu-panel").exists();
102102

103103
await clickOutside();
104-
assert.dom(".search-menu").doesNotExist();
104+
assert.dom(".search-menu-panel").doesNotExist();
105105

106106
await click("#search-button");
107-
assert.dom(".search-menu").exists();
107+
assert.dom(".search-menu-panel").exists();
108108

109109
await click("#search-button"); // toggle same button
110-
assert.dom(".search-menu").doesNotExist();
110+
assert.dom(".search-menu-panel").doesNotExist();
111111
});
112112

113113
test("initial options", async function (assert) {
@@ -572,7 +572,9 @@ acceptance("Search - Authenticated", function (needs) {
572572
assert
573573
.dom("#search-button")
574574
.isFocused("Escaping search returns focus to search button");
575-
assert.dom(".search-menu").doesNotExist("Esc removes search dropdown");
575+
assert
576+
.dom(".search-menu-panel")
577+
.doesNotExist("Esc removes search dropdown");
576578

577579
await click("#search-button");
578580
await triggerKeyEvent(document.activeElement, "keyup", "ArrowDown");

app/assets/javascripts/discourse/tests/integration/components/search-menu-test.gjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module("Integration | Component | search-menu", function (hooks) {
2929
return response(searchFixtures["search/query"]);
3030
});
3131

32-
await render(<template><SearchMenu /></template>);
32+
await render(<template><SearchMenu @location="test" /></template>);
3333

3434
assert
3535
.dom(".show-advanced-search")
@@ -73,7 +73,7 @@ module("Integration | Component | search-menu", function (hooks) {
7373
test("clicking outside results hides and blurs input", async function (assert) {
7474
await render(
7575
<template>
76-
<div id="click-me"><SearchMenu /></div>
76+
<div id="click-me"><SearchMenu @location="test" /></div>
7777
</template>
7878
);
7979
await click("#search-term");

app/assets/stylesheets/common/components/_index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,4 @@
6262
@import "emoji-picker";
6363
@import "filter-input";
6464
@import "dropdown-menu";
65+
@import "welcome-banner";
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
@import "common/foundation/mixins";
2+
3+
.display-welcome-banner {
4+
#main-outlet {
5+
padding-top: 0;
6+
}
7+
}
8+
9+
.welcome-banner--visible.header-search--enabled {
10+
.floating-search-input-wrapper {
11+
display: none;
12+
}
13+
}
14+
15+
.welcome-banner {
16+
margin-bottom: 1em;
17+
border-radius: var(--d-border-radius-large);
18+
19+
&__search-menu {
20+
.menu-panel-results .menu-panel.search-menu-panel {
21+
position: unset;
22+
padding: 0;
23+
}
24+
}
25+
26+
&__wrap {
27+
box-sizing: border-box;
28+
position: relative;
29+
padding: 1.5em 0 3em;
30+
31+
@include breakpoint(tablet) {
32+
padding: 1em 8px 1.25em;
33+
margin-top: 0.5em;
34+
}
35+
36+
> div {
37+
margin: 0 auto;
38+
max-width: 600px;
39+
}
40+
41+
.search-menu {
42+
display: flex;
43+
position: relative;
44+
45+
.search-menu-container {
46+
width: 100%;
47+
min-width: 0;
48+
}
49+
}
50+
51+
.d-icon-search {
52+
margin: 0;
53+
}
54+
55+
.browser-search-tip {
56+
display: none;
57+
}
58+
59+
.search-input {
60+
#search-term {
61+
min-width: 0;
62+
flex: 1 1;
63+
}
64+
}
65+
66+
h1 {
67+
font-size: var(--font-up-6);
68+
line-height: $line-height-medium;
69+
70+
@include breakpoint(tablet) {
71+
font-size: var(--font-up-4);
72+
}
73+
}
74+
75+
h1,
76+
p {
77+
text-align: center;
78+
}
79+
80+
.btn.search-icon {
81+
z-index: 2;
82+
background: transparent;
83+
line-height: 1;
84+
color: var(--primary-medium);
85+
height: 100%;
86+
position: absolute;
87+
left: 0;
88+
89+
.rtl & {
90+
right: 0;
91+
left: unset;
92+
}
93+
94+
.discourse-no-touch & {
95+
&:hover {
96+
background: transparent;
97+
color: var(--primary);
98+
99+
.d-icon {
100+
color: currentcolor;
101+
}
102+
}
103+
}
104+
105+
+ .search-menu-container .search-input {
106+
padding-left: 1.75em;
107+
108+
.rtl & {
109+
padding-left: unset;
110+
padding-right: 1.75em;
111+
}
112+
}
113+
114+
+ .search-menu-container .search-input .search-context {
115+
margin-left: 4px;
116+
}
117+
}
118+
119+
.results {
120+
box-sizing: border-box;
121+
background: var(--secondary);
122+
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.15);
123+
position: absolute;
124+
z-index: 9;
125+
margin-left: auto;
126+
margin-right: auto;
127+
left: 0;
128+
top: 2.75em;
129+
right: 0;
130+
padding: 0.5em;
131+
132+
@include breakpoint(mobile-extra-large) {
133+
width: 100%;
134+
}
135+
136+
ul,
137+
ol {
138+
list-style-type: none;
139+
margin: 0;
140+
}
141+
142+
.d-icon-search {
143+
display: none;
144+
}
145+
}
146+
147+
.search-link .d-icon {
148+
color: var(--primary-medium);
149+
}
150+
151+
span.keyword {
152+
color: var(--primary);
153+
}
154+
}
155+
}
156+
157+
// hide search icon from default search menu
158+
.search-menu.glimmer-search-menu .search-icon {
159+
display: none;
160+
}

app/assets/stylesheets/desktop/components/header-search.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.header-search--visible,
1+
.header-search--enabled,
22
.search-header--visible {
33
.panel .header-dropdown-toggle.search-dropdown,
44
.panel .search-menu {

0 commit comments

Comments
 (0)