Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cc22b82
Add a configurable page header layout style variable
dtdesign Oct 4, 2026
b7b12dc
Resolve `pageHeader` to `system_pageHeader` through style-driven temp…
dtdesign Oct 4, 2026
b88ff86
Reserve the `system_` template prefix as non-customizable
dtdesign Oct 5, 2026
20e579c
Apply the template listeners of `foo` to its `system_foo` variant
dtdesign Oct 5, 2026
1d6d9f6
Compile the page header SCSS per style from `layout/pageHeader/{class…
dtdesign Oct 5, 2026
9ad48c1
Brighten the dark mode header active states and the search box placeh…
dtdesign Oct 5, 2026
811a072
Add `Component/Drawer` for modal header panels on small screens
dtdesign Oct 5, 2026
b882bfa
Add logo row positions (`logoTop`/`logoBelow`/`logoInBar`) to `system…
dtdesign Oct 5, 2026
f430322
Add `system_pageHeaderMenu` with drawer-ready main menu and `MenuItem…
dtdesign Oct 5, 2026
e7a5325
Add `system_pageHeaderUser` with reordered user panel, guest language…
dtdesign Oct 5, 2026
c90d09f
Add desktop styles for the system page header and switch the frontend…
dtdesign Oct 5, 2026
7fa3eb0
Add `Component/MainMenu` with hover intent, submenu toggles and RTL-a…
dtdesign Oct 5, 2026
6237599
Add the mobile drawer for the system page header with a delegated gue…
dtdesign Oct 5, 2026
244ff9e
Add the user drawer with tabs for the control panel and user menus to…
dtdesign Oct 5, 2026
1f73297
Add the in-bar search with a toggle to the system page header
dtdesign Oct 5, 2026
4de9373
Reduce the system page header bar to 50px to match the classic header…
dtdesign Oct 5, 2026
30927ef
Add the sticky shadow for the system page header and re-align user me…
dtdesign Oct 5, 2026
cf18f67
Signal unread content behind the mobile menu and user drawer buttons …
dtdesign Oct 5, 2026
b2c81f0
Server-render the devtools language chooser in the system page header
dtdesign Oct 5, 2026
41107f4
Unify the background color of sub menus and hover states
dtdesign Oct 5, 2026
e5cfe32
Collapse user drawer tabs to icon-only with visually hidden labels
dtdesign Oct 5, 2026
812a25c
Flatten the system page header SCSS and drop the root scope
dtdesign Oct 5, 2026
628ba1f
Render system header breadcrumbs without the bar and darken wcfConten…
dtdesign Oct 6, 2026
94fdbe1
Strip the bold formatting that causes layout shifts
dtdesign Oct 6, 2026
179bad9
Use the mobile logo for the logoInBar preset
dtdesign Oct 6, 2026
deac5c8
Model per-style options as IStyleOption enums, starting with PageHead…
dtdesign Oct 7, 2026
8da96dc
Render per-style options generically in the style editor and validate…
dtdesign Oct 7, 2026
4c27f4c
Render style options as radio cards with schematic previews and map m…
dtdesign Oct 7, 2026
7351bca
Updated the default style
dtdesign Oct 7, 2026
bedbf55
Extend the open submenu's fill to the bar edge and align user panel m…
dtdesign Oct 7, 2026
85f2286
Add Style::usesClassicPageHeader() and switch the new user panel to r…
dtdesign Oct 8, 2026
4459faf
Render the menu items are flat links
dtdesign Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified com.woltlab.wcf/defaultStyle.tar
Binary file not shown.
1 change: 1 addition & 0 deletions com.woltlab.wcf/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
because deleting the legacy condition object types cascades to `wcf1_condition`.
<instruction type="objectType" />
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_styleTemplateGroup.php</instruction>
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_styleVariables.php</instruction>
<instruction type="database" run="standalone">acp/database/update_com.woltlab.wcf_6.3_step2.php</instruction>
<instruction type="script" run="standalone">acp/update_com.woltlab.wcf_6.3_embeddedObjects.php</instruction>

Expand Down
3 changes: 2 additions & 1 deletion com.woltlab.wcf/templates/__devtoolsLanguageChooser.tpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{if ENABLE_DEBUG_MODE && ENABLE_DEVELOPER_TOOLS && !$__wcf->user->isGuest()}
{* The `system_pageHeader` renders this chooser on the server. *}
{if ENABLE_DEBUG_MODE && ENABLE_DEVELOPER_TOOLS && !$__wcf->user->isGuest() && $__wcf->getStyleHandler()->getStyle()->usesClassicPageHeader()}
<script data-relocate="true">
require(['Ajax', 'WoltLabSuite/Core/Language/Chooser'], function(Ajax, LanguageChooser) {
var item = elCreate('li');
Expand Down
1 change: 1 addition & 0 deletions com.woltlab.wcf/templates/headIncludeJavaScript.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ window.addEventListener('pageshow', function(event) {
]),
{/if}
reportEndpoint: '{link controller="Report"}{/link}',
templateVariants: [{implode from=$__wcf->getStyleHandler()->getStyle()->getTemplateVariants() item=_templateVariant}'{unsafe:$_templateVariant|encodeJS}'{/implode}],
});
});
</script>
Expand Down
71 changes: 71 additions & 0 deletions com.woltlab.wcf/templates/system_pageHeader.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{assign var='__pageHeaderLayout' value=$__wcf->getStyleHandler()->getStyle()->getVariable('pageHeaderLayout')}
<div id="pageHeaderContainer" class="pageHeaderContainer pageHeaderContainer--{$__pageHeaderLayout}">
{if $__pageHeaderLayout === 'logoTop'}
<div id="pageHeaderFacade" class="pageHeaderFacade">
<div class="layoutBoundary">
{include file='system_pageHeaderLogo'}
</div>
</div>
{/if}

<header id="pageHeader" class="pageHeader">
<div id="pageHeaderPanel" class="pageHeaderPanel">
<div class="layoutBoundary">
{if $__pageHeaderLayout === 'logoInBar'}
{include file='system_pageHeaderLogo'}
{else}
{* The logo row is hidden on small screens, the bar shows the mobile logo instead. *}
<a href="{if PAGE_LOGO_LINK_TO_APP_DEFAULT}{link application=$__wcf->getActiveApplication()->getAbbreviation()}{/link}{else}{link}{/link}{/if}" class="pageHeaderBarLogo" aria-label="{PAGE_TITLE|phrase}">
<img src="{$__wcf->getStyleHandler()->getStyle()->getPageLogoMobile()}" alt="" class="pageHeaderLogoSmall"{*
*}{if $__wcf->getStyleHandler()->getStyle()->getPageLogoSmallHeight()} height="{$__wcf->getStyleHandler()->getStyle()->getPageLogoSmallHeight()}"{/if}{*
*}{if $__wcf->getStyleHandler()->getStyle()->getPageLogoSmallWidth()} width="{$__wcf->getStyleHandler()->getStyle()->getPageLogoSmallWidth()}"{/if}{*
*} loading="eager">
</a>
{/if}

{include file='system_pageHeaderMenu' sandbox=true}

{include file='system_pageHeaderSearch'}

{include file='system_pageHeaderUser'}

<button
type="button"
class="pageHeaderMenuMobile"
aria-controls="mainMenu"
aria-expanded="false"
aria-label="{lang}wcf.menu.page{/lang}"
data-drawer-target="mainMenu"
>
{icon size=24 name='bars'}
</button>
</div>
</div>
</header>

{if $__pageHeaderLayout === 'logoBelow'}
<div id="pageHeaderFacade" class="pageHeaderFacade">
<div class="layoutBoundary">
{include file='system_pageHeaderLogo'}
</div>
</div>
{/if}

{hascontent}
<div class="boxesHero">
<div class="layoutBoundary">
<div class="boxContainer">
{content}
{if !$boxesHero|empty}
{unsafe:$boxesHero}
{/if}

{foreach from=$__wcf->getBoxHandler()->getBoxes('hero') item=box}
{unsafe:$box->render()}
{/foreach}
{/content}
</div>
</div>
</div>
{/hascontent}
</div>
19 changes: 19 additions & 0 deletions com.woltlab.wcf/templates/system_pageHeaderLogo.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div id="pageHeaderLogo" class="pageHeaderLogo">
{if MODULE_WCF_AD && $__disableAds|empty}{unsafe:$__wcf->getAdHandler()->getAds('com.woltlab.wcf.logo')}{/if}

<a href="{if PAGE_LOGO_LINK_TO_APP_DEFAULT}{link application=$__wcf->getActiveApplication()->getAbbreviation()}{/link}{else}{link}{/link}{/if}" aria-label="{PAGE_TITLE|phrase}">
{* The bar only has room for the mobile logo. *}
{if $__pageHeaderLayout !== 'logoInBar'}
<img src="{$__wcf->getStyleHandler()->getStyle()->getPageLogo()}" alt="" class="pageHeaderLogoLarge"{*
*}{if $__wcf->getStyleHandler()->getStyle()->getVariable('pageLogoHeight')} height="{$__wcf->getStyleHandler()->getStyle()->getVariable('pageLogoHeight')}"{/if}{*
*}{if $__wcf->getStyleHandler()->getStyle()->getVariable('pageLogoWidth')} width="{$__wcf->getStyleHandler()->getStyle()->getVariable('pageLogoWidth')}"{/if}{*
*} loading="eager">
{/if}
<img src="{$__wcf->getStyleHandler()->getStyle()->getPageLogoMobile()}" alt="" class="pageHeaderLogoSmall"{*
*}{if $__wcf->getStyleHandler()->getStyle()->getPageLogoSmallHeight()} height="{$__wcf->getStyleHandler()->getStyle()->getPageLogoSmallHeight()}"{/if}{*
*}{if $__wcf->getStyleHandler()->getStyle()->getPageLogoSmallWidth()} width="{$__wcf->getStyleHandler()->getStyle()->getPageLogoSmallWidth()}"{/if}{*
*} loading="eager">

{event name='headerLogo'}
</a>
</div>
116 changes: 116 additions & 0 deletions com.woltlab.wcf/templates/system_pageHeaderMenu.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{* The variables of `__menu` are required by its template listeners, see `TemplateEngine::SYSTEM_TEMPLATE_LISTENER_ALIASES`. *}
{assign var='menuIdentifier' value='com.woltlab.wcf.MainMenu'}
{assign var='__mainMenu' value=$__wcf->getBoxHandler()->getBoxByIdentifier($menuIdentifier)->getMenu()}
{assign var='menuItemNodeList' value=$__mainMenu->getMenuItemNodeList()}
{assign var='menuTitle' value=$__mainMenu->getTitle()}
<nav id="mainMenu" class="mainMenu" aria-label="{$menuTitle}">
<div class="mainMenuMobileOnly mainMenuHead">
<span class="mainMenuTitle">{lang}wcf.menu.page{/lang}</span>
<button type="button" class="mainMenuClose" data-drawer-close aria-label="{lang}wcf.global.button.close{/lang}">
{icon size=24 name='xmark'}
</button>
</div>

{if $__wcf->user->isGuest()}
<div class="mainMenuMobileOnly mainMenuGuest">
{if $__userAuthConfig->canRegister}
<a href="{link controller='Register'}{/link}" class="button buttonPrimary" rel="nofollow">{lang}wcf.user.button.register{/lang}</a>
{/if}
<a href="{link controller='Login' url=$__wcf->getRequestURI()}{/link}" class="button" rel="nofollow">{lang}wcf.user.button.login{/lang}</a>
</div>
{/if}

<ol class="boxMenu">
{event name='menuBefore'}

{foreach from=$menuItemNodeList item=menuItemNode}
<li class="{if $menuItemNode->isActiveNode()}active{/if}{if $menuItemNode->hasChildren()} boxMenuHasChildren{/if}" data-identifier="{$menuItemNode->identifier}">
<a
{anchorAttributes url=$menuItemNode->getURL() appendClassname=false}
class="boxMenuLink"
{if $menuItemNode->getOutstandingItems() > 0}
aria-label="{$menuItemNode->getTitle()} {lang}wcf.page.menu.outstandingItems{/lang}"
{/if}
{if $menuItemNode->isCurrentNode()} aria-current="page"{/if}
>
<span class="boxMenuLinkTitle">{$menuItemNode->getTitle()}</span>
{if $menuItemNode->getOutstandingItems() > 0}
<span class="boxMenuLinkOutstandingItems badge badgeUpdate" aria-hidden="true">{#$menuItemNode->getOutstandingItems()}</span>
{/if}
</a>

{if $menuItemNode->hasChildren()}
<button
type="button"
class="boxMenuToggle"
aria-expanded="false"
aria-controls="mainMenuItem{$menuItemNode->itemID}"
aria-label="{lang title=$menuItemNode->getTitle()}wcf.menu.page.button.toggle{/lang}"
>
{icon name='chevron-down' type='solid'}
</button>
<ol id="mainMenuItem{$menuItemNode->itemID}" class="boxMenuDepth{$menuItemNode->getDepth()}">
{else}
</li>
{/if}

{if !$menuItemNode->hasChildren() && $menuItemNode->isLastSibling()}
{unsafe:"</ol></li>"|str_repeat:$menuItemNode->getOpenParentNodes()}
{/if}
{/foreach}

{event name='menuAfter'}

{* Receives the items that do not fit into the bar, the overflow detection runs on the client. *}
<li class="mainMenuOverflow boxMenuHasChildren" hidden>
<button
type="button"
class="boxMenuLink boxMenuToggle mainMenuOverflowToggle"
aria-expanded="false"
aria-controls="mainMenuOverflowItems"
>
<span class="boxMenuLinkTitle">{lang}wcf.global.button.more{/lang}</span>
{icon name='chevron-down' type='solid'}
</button>
<ol id="mainMenuOverflowItems" class="boxMenuDepth1"></ol>
</li>
</ol>

{if $__wcf->user->isGuest() && $__wcf->getLanguage()->getLanguages()|count > 1}
<div class="mainMenuMobileOnly mainMenuSettings">
<button type="button" class="mainMenuSettingsToggle" aria-expanded="false" aria-controls="mainMenuLanguages">
<span class="mainMenuSettingsLabel">{lang}wcf.user.language{/lang}</span>
<span class="mainMenuSettingsValue">{$__wcf->getLanguage()}</span>
{icon name='chevron-down' type='solid'}
</button>
<ul id="mainMenuLanguages" class="mainMenuLanguageList" hidden>
{foreach from=$__wcf->getLanguage()->getLanguages() item=_language}
<li>
<button
type="button"
class="mainMenuLanguage"
data-switch-language="{$_language->languageID}"
data-language-code="{$_language->languageCode}"
lang="{$_language->languageCode}"
{if $_language->languageID === $__wcf->getLanguage()->languageID} aria-current="true"{/if}
>
<img src="{$_language->getIconPath()}" alt="" class="iconFlag">
{$_language}
</button>
</li>
{/foreach}
</ul>
</div>
{/if}

{assign var='__footerMenuBox' value=$__wcf->getBoxHandler()->getBoxByIdentifier('com.woltlab.wcf.FooterMenu')}
{if $__footerMenuBox !== null && $__footerMenuBox->hasContent()}
<ul class="mainMenuMobileOnly mainMenuFooter">
{foreach from=$__footerMenuBox->getMenu()->getMenuItemNodeList() item=menuItemNode}
{if $menuItemNode->getDepth() === 1}
<li><a {anchorAttributes url=$menuItemNode->getURL()}>{$menuItemNode->getTitle()}</a></li>
{/if}
{/foreach}
</ul>
{/if}
</nav>
70 changes: 70 additions & 0 deletions com.woltlab.wcf/templates/system_pageHeaderSearch.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{if $__searchTypeLabel|empty}
{capture assign='__searchTypeLabel'}{lang}wcf.search.type.{if !$__searchObjectTypeName|empty}{$__searchObjectTypeName}{else}everywhere{/if}{/lang}{/capture}
{/if}

{if MODULE_ARTICLE && SEARCH_ENABLE_ARTICLES && $__wcf->getActivePage() != null && ($__wcf->getActivePage()->identifier == 'com.woltlab.wcf.ArticleList' || $__wcf->getActivePage()->identifier == 'com.woltlab.wcf.CategoryArticleList' || $__wcf->getActivePage()->identifier == 'com.woltlab.wcf.Article')}
{if $category|isset}
{capture assign='__searchTypeLabel'}{$category->getTitle()}{/capture}
{else}
{capture assign='__searchTypeLabel'}{lang}wcf.search.type.com.woltlab.wcf.article{/lang}{/capture}
{/if}

{assign var='__searchObjectTypeName' value='com.woltlab.wcf.article'}

{capture assign='__searchTypesScoped'}
{if $category|isset}<li><a href="#" data-extended-link="{link controller='Search'}type=com.woltlab.wcf.article&extended=1{/link}" data-object-type="com.woltlab.wcf.article" data-parameters='{ "articleCategoryID": {$category->categoryID} }'>{$category->getTitle()}</a></li>{/if}
{/capture}
{assign var='__searchAreaInitialized' value=true}
{/if}

{event name='settings'}

<div id="pageHeaderSearch" class="pageHeaderSearch">
<form method="post" action="{link controller='Search'}{/link}" role="search">
<div id="pageHeaderSearchInputContainer" class="pageHeaderSearchInputContainer">
{icon name='magnifying-glass'}

<input type="search" name="q" id="pageHeaderSearchInput" class="pageHeaderSearchInput" placeholder="{lang}wcf.global.search.enterSearchTerm{/lang}" aria-label="{lang}wcf.global.search.enterSearchTerm{/lang}" autocomplete="off" value="{if $query|isset}{$query}{/if}">

<div class="pageHeaderSearchType dropdown">
<a href="#" class="button dropdownToggle" id="pageHeaderSearchTypeSelect" role="button">
<span class="pageHeaderSearchTypeLabel">{unsafe:$__searchTypeLabel}</span>
{icon name='chevron-down' type='solid'}
</a>
<ul class="dropdownMenu">
<li><a href="#" data-extended-link="{link controller='Search'}extended=1{/link}" data-object-type="everywhere">{lang}wcf.search.type.everywhere{/lang}</a></li>
<li class="dropdownDivider"></li>

{hascontent}
{content}
{if !$__searchTypesScoped|empty}{unsafe:$__searchTypesScoped}{/if}
{/content}

<li class="dropdownDivider"></li>
{/hascontent}

{foreach from=$__wcf->getSearchEngine()->getAvailableObjectTypes() key=_searchObjectTypeName item=_searchObjectType}
{if $_searchObjectType->isAccessible()}
<li><a href="#" data-extended-link="{link controller='Search' type=$_searchObjectTypeName extended=1}{/link}" data-object-type="{$_searchObjectTypeName}">{lang}wcf.search.type.{$_searchObjectTypeName}{/lang}</a></li>
{/if}
{/foreach}

<li class="dropdownDivider"></li>
<li><a class="pageHeaderSearchExtendedLink" href="{link controller='Search'}extended=1{/link}">{lang}wcf.search.extended{/lang}</a></li>
</ul>
</div>

<div id="pageHeaderSearchParameters"></div>

{if !$__searchStaticOptions|empty}{unsafe:$__searchStaticOptions}{/if}
</div>
</form>
</div>

{if (!OFFLINE || $__wcf->session->getPermission('admin.general.canViewPageDuringOfflineMode')) && (!FORCE_LOGIN || !$__wcf->user->isGuest())}
<script data-relocate="true">
require(['WoltLabSuite/Core/Ui/Search/Page'], function(UiSearchPage) {
UiSearchPage.init('{if !$__searchObjectTypeName|empty}{unsafe:$__searchObjectTypeName|encodeJS}{elseif !$searchPreselectObjectType|empty}{$searchPreselectObjectType}{else}everywhere{/if}');
});
</script>
{/if}
Loading
Loading