UX: add welcome banner location site setting to admin interface (#33438)

Add a Welcome banner location setting to the *Interface & layout* page
that admins can adjust to change the location of the welcome banner.

---------

Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
This commit is contained in:
Yuriy Kurant
2025-07-17 13:34:22 +08:00
committed by GitHub
co-authored by chapoi
parent deced1909b
commit f15778023c
11 changed files with 189 additions and 100 deletions
@@ -1,11 +1,13 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import { dasherize } from "@ember/string";
import { htmlSafe } from "@ember/template";
import { modifier } from "ember-modifier";
import DButton from "discourse/components/d-button";
import PluginOutlet from "discourse/components/plugin-outlet";
import SearchMenu from "discourse/components/search-menu";
import bodyClass from "discourse/helpers/body-class";
import concatClass from "discourse/helpers/concat-class";
import { prioritizeNameFallback } from "discourse/lib/settings";
import { applyValueTransformer } from "discourse/lib/transformer";
import I18n, { i18n } from "discourse-i18n";
@@ -98,12 +100,15 @@ export default class WelcomeBanner extends Component {
: "";
}
get locationClass() {
return `--${dasherize(this.siteSettings.welcome_banner_location)}`;
}
<template>
{{bodyClass this.bodyClasses}}
{{#if this.shouldDisplay}}
<div
class="welcome-banner"
class={{concatClass "welcome-banner" this.locationClass}}
{{this.checkViewport}}
{{this.handleKeyboardShortcut}}
>
@@ -1,7 +1,7 @@
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import DialogHolder from "dialog-holder/components/dialog-holder";
import RouteTemplate from "ember-route-template";
import { and } from "truth-helpers";
import { and, eq } from "truth-helpers";
import CardContainer from "discourse/components/card-container";
import ComposerContainer from "discourse/components/composer-container";
import CustomHtml from "discourse/components/custom-html";
@@ -73,6 +73,14 @@ export default RouteTemplate(
<OfflineIndicator />
{{/if}}
{{#if
(eq
@controller.siteSettings.welcome_banner_location "below_site_header"
)
}}
<WelcomeBanner />
{{/if}}
<PluginOutlet
@name="below-site-header"
@connectorTagName="div"
@@ -96,7 +104,14 @@ export default RouteTemplate(
<div id="main-outlet">
<PluginOutlet @name="above-main-container" @connectorTagName="div" />
<WelcomeBanner />
{{#if
(eq
@controller.siteSettings.welcome_banner_location
"above_topic_content"
)
}}
<WelcomeBanner />
{{/if}}
<div class="container" id="main-container">
{{#if @controller.showTop}}
@@ -16,6 +16,10 @@
.welcome-banner {
border-radius: var(--d-border-radius-large);
@include viewport.until(sm) {
display: none;
}
&__wrap {
box-sizing: border-box;
position: relative;
@@ -32,18 +36,11 @@
margin-top: 0;
}
> div {
margin: 0 auto;
max-width: 600px;
}
.search-menu {
display: flex;
position: relative;
@include viewport.until(sm) {
display: none;
}
margin-inline: auto;
max-width: 600px;
.search-menu-container {
width: 100%;
@@ -62,41 +59,6 @@
}
}
.welcome-banner__title {
font-size: var(--font-up-6);
line-height: $line-height-medium;
font-weight: bold;
margin-bottom: var(--space-2);
@include viewport.until(md) {
font-size: var(--font-up-4);
}
@include viewport.until(sm) {
font-size: var(--font-up-1);
}
}
.welcome-banner__title,
p {
text-align: center;
}
.welcome-banner__subheader {
font-size: var(--font-down-6);
font-weight: normal;
margin: 0;
padding: 0.25rem 0;
@include viewport.until(md) {
font-size: var(--font-down-4);
}
@include viewport.until(sm) {
font-size: var(--font-down-1);
}
}
.btn.search-icon {
z-index: 2;
background: transparent;
@@ -183,6 +145,31 @@
}
}
.welcome-banner__title {
font-size: var(--font-up-6);
line-height: $line-height-medium;
font-weight: bold;
margin-bottom: var(--space-2);
margin-inline: auto;
max-width: 600px;
text-align: center;
@include viewport.until(md) {
font-size: var(--font-up-4);
}
@include viewport.until(sm) {
font-size: var(--font-up-1);
}
}
.welcome-banner__subheader {
font-size: var(--font-up-1-rem);
font-weight: normal;
margin: 0;
padding: 0.25rem 0;
}
// hide search icon from default search menu
.search-menu.glimmer-search-menu .search-icon {
display: none;
+20
View File
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require "enum_site_setting"
class WelcomeBannerLocation < EnumSiteSetting
def self.valid_value?(val)
values.any? { |v| v[:value].to_s == val.to_s }
end
def self.values
@values ||= [
{ name: "welcome_banner_location.above_topic_content", value: "above_topic_content" },
{ name: "welcome_banner_location.below_site_header", value: "below_site_header" },
]
end
def self.translate_names?
true
end
end
+3
View File
@@ -2678,6 +2678,9 @@ en:
disabled: "Do not display"
sidebar_footer: "Display in sidebar footer"
header: "Display in header"
welcome_banner_location:
above_topic_content: "Above topic content"
below_site_header: "Below site header"
shortcut_modifier_key:
shift: "Shift"
+1
View File
@@ -2711,6 +2711,7 @@ en:
show_bottom_topic_map: "Shows the topic map at the bottom of the topic when it has 10 replies or more."
show_topic_map_in_topics_without_replies: "Shows the topic map even if the topic has no replies."
enable_welcome_banner: "Display a banner on your main topic list pages to welcome members and allow them to search site content"
welcome_banner_location: "Determines where on the page the welcome banner will appear."
splash_screen: "Displays a temporary loading screen while site assets load"
navigation_menu: "Specify sidebar or header dropdown as the main navigation menu for your site. Sidebar is recommended."
+6
View File
@@ -3648,6 +3648,12 @@ uncategorized:
themeable: true
area: "interface"
welcome_banner_location:
client: true
enum: "WelcomeBannerLocation"
default: "above_topic_content"
area: "interface"
user_preferences:
default_email_digest_frequency:
enum: "DigestEmailSiteSetting"
@@ -47,6 +47,14 @@ module PageObjects
text: I18n.t("js.welcome_banner.subheader.logged_in_members"),
)
end
def above_topic_content?
has_css?("#main-outlet > .--above-topic-content", visible: :visible)
end
def below_site_header?
has_css?(".discourse-root > .--below-site-header", visible: :visible)
end
end
end
end
+14
View File
@@ -145,6 +145,20 @@ describe "Welcome banner", type: :system do
expect(search_page).to have_no_search_icon
end
end
context "with interface location setting" do
it "shows above topic content" do
SiteSetting.welcome_banner_location = "above_topic_content"
visit "/"
expect(banner).to be_above_topic_content
end
it "shows below site header" do
SiteSetting.welcome_banner_location = "below_site_header"
visit "/"
expect(banner).to be_below_site_header
end
end
end
context "when disabled" do
+1 -1
View File
@@ -7,7 +7,7 @@
html:not(:has(.has-full-page-chat)) {
background-color: var(--background-color);
@include viewport.until(sm) {
@include viewport.until(lg) {
background-color: var(--d-content-background);
}
}
+81 -51
View File
@@ -1,18 +1,34 @@
@use "lib/viewport";
.welcome-banner {
&__wrap {
display: grid;
grid-template-rows: 0.33fr 0.33fr 0.33fr;
grid-template-columns: 1fr 1fr;
grid-column-gap: 1em;
grid-row-gap: 0.5em;
border-bottom: 1px solid var(--primary-300);
padding: 1.5em 0 2.5em;
margin-bottom: 0;
&.--below-site-header {
background-color: var(--background-color);
border-radius: 0;
@media screen and (width <= 768px) {
padding: 1em;
@include viewport.until(lg) {
margin-bottom: var(--space-4);
}
}
&__inner-wrapper {
container-type: inline-size;
}
&__wrap {
display: flex;
align-items: center;
gap: var(--space-8);
border-bottom: 1px solid var(--primary-300);
margin-block: 0;
padding-block: var(--space-4) var(--space-8);
@include viewport.from(xl) {
justify-content: center;
}
@include viewport.until(md) {
flex-direction: column;
gap: var(--space-4);
}
@include viewport.until(sm) {
@@ -21,29 +37,42 @@
margin-top: 0;
}
.--below-site-header & {
border-bottom: 0;
padding: var(--space-4) var(--space-4) var(--space-8);
@include viewport.from(xl) {
margin-left: max(var(--d-sidebar-width), var(--space-4));
}
@include viewport.until(lg) {
padding: var(--space-8) var(--space-4) var(--space-8);
}
}
.--above-topic-content & {
@container (width <= 800px) {
flex-direction: column;
gap: var(--space-4);
}
@include viewport.until(lg) {
padding-inline: var(--space-4);
}
}
.search-menu {
grid-column: 2/3;
grid-row: 1/-1;
width: 100%;
align-self: center;
@media screen and (width <= 1028px) {
grid-row: 2/-1;
grid-column: 1/-1;
.--above-topic-content & {
@container (width >= 800px) {
width: 50cqw;
}
}
@media screen and (width <= 768px) {
grid-column: 2/-1;
grid-row: 1/-1;
}
@media screen and (width <= 600px) {
grid-row: 2/-1;
grid-column: 1/-1;
}
@include viewport.until(sm) {
display: none;
@include viewport.from(xl) {
margin-inline: unset;
}
}
@@ -80,38 +109,39 @@
}
&__title {
grid-column: 1/2;
grid-row: 1/-1;
text-align: left;
align-self: center;
margin: 0;
font-weight: 400;
color: var(--search-color);
@media screen and (width <= 1028px) {
.--above-topic-content & {
@container (width >= 800px) {
flex-grow: 1;
}
@container (width <= 800px) {
white-space: nowrap;
text-align: center;
}
}
@include viewport.from(xl) {
margin-inline: unset;
}
@include viewport.from(md) {
font-size: clamp(var(--font-up-4), 4vw, var(--font-up-6));
}
@include viewport.until(md) {
font-size: var(--font-up-4);
grid-column: 1/-1;
grid-row: 1;
text-align: center;
margin-bottom: 0.5em;
}
@media screen and (width <= 768px) {
text-align: left;
grid-column: 1/2;
grid-row: 1/-1;
font-size: var(--font-up-3);
}
@media screen and (width <= 600px) {
grid-column: 1/-1;
grid-row: 1;
text-align: center;
margin-bottom: 0.5em;
}
@include viewport.until(sm) {
font-size: var(--font-up-2);
}
}
&__subheader {
white-space: wrap;
}
}