Add tab support for user dropdown

This commit is contained in:
Jan Faracik 2025-02-28 17:59:50 +00:00
parent 43063425f5
commit 7ca2e8f63b
6 changed files with 108 additions and 63 deletions

View File

@ -1,31 +1,25 @@
<?jelly escape-by-default='true'?> <?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout"> <j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:dd="/lib/layout/dropdowns">
<div class="jenkins-dropdown"> <dd:custom>
<a class="jenkins-dropdown__item" href="${rootURL}/${it.user.url}"> <a class="jenkins-dropdown__item" href="${rootURL}/${it.user.url}">
<div class="jenkins-dropdown__item__icon"> <div class="jenkins-dropdown__item__icon">
<l:icon src="${it.iconFileName}" class="jenkins-avatar" /> <l:icon src="${it.iconFileName}" class="jenkins-avatar" />
</div> </div>
${it.user.fullName} ${it.user.fullName}
</a> </a>
</dd:custom>
<div class="jenkins-dropdown__separator" /> <dd:separator />
<j:forEach var="action" items="${it.actions}"> <j:forEach var="action" items="${it.actions}">
<a class="jenkins-dropdown__item" href="${rootURL}/${it.user.url}/${action.urlName}"> <dd:item icon="${action.iconFileName}"
<div class="jenkins-dropdown__item__icon"> text="${action.displayName}"
<l:icon src="${action.iconFileName}" /> href="${rootURL}/${it.user.url}/${action.urlName}" />
</div> </j:forEach>
${action.displayName}
</a>
</j:forEach>
<div class="jenkins-dropdown__separator" /> <dd:separator />
<a class="jenkins-dropdown__item" href="${rootURL}/logout"> <dd:item icon="symbol-log-out"
<div class="jenkins-dropdown__item__icon"> text="${%Sign out}"
<l:icon src="symbol-log-out" /> href="${rootURL}/logout" />
</div>
${%Sign out}
</a>
</div>
</j:jelly> </j:jelly>

View File

@ -36,48 +36,65 @@
<st:include page="suffix" optional="true" /> <st:include page="suffix" optional="true" />
<j:forEach var="action" items="${it.actions}"> <j:forEach var="action" items="${it.actions}">
<j:set var="isCurrent" value="${h.hyperlinkMatchesCurrentPage(action.urlName)}" /> <j:set var="isCurrent" value="${h.hyperlinkMatchesCurrentPage(action.urlName)}" />
<j:set var="jumplist">
<st:include it="${action}" page="jumplist.jelly" optional="true" />
</j:set>
<j:if test="${jumplist.length() == 0}">
<j:set var="tooltip"> <j:set var="tooltip">
<j:set var="interactive" value="true" /> <st:include it="${action}" page="tooltip.jelly" optional="true" />
<st:include it="${action}" page="jumplist.jelly" optional="true" />
</j:set> </j:set>
</j:if>
<j:if test="${tooltip.length() == 0}"> <j:set var="badge" value="${action.badge}" />
<j:set var="interactive" value="false" /> <j:if test="${jumplist.length() == 0 and tooltip.length() == 0}">
<j:set var="tooltip"> <j:set var="tooltip">
<st:include it="${action}" page="tooltip.jelly" optional="true" /> <div style="text-align: center;">${action.displayName}</div>
</j:set>
</j:if>
<j:set var="badge" value="${action.badge}" />
<j:if test="${tooltip.length() == 0}">
<j:set var="tooltip">
<div style="text-align: center;">${action.displayName}</div>
<j:if test="${badge != null}">
<div style="text-align: center; color: var(--text-color-secondary)">${badge.tooltip}</div>
</j:if>
</j:set>
</j:if>
<x:element name="${action.urlName == null ? 'button' : 'a'}">
<x:attribute name="id">root-action-${action.class.simpleName}</x:attribute>
<x:attribute name="href">${h.getActionUrl(app.url, action)}</x:attribute>
<x:attribute name="data-html-tooltip" escapeText="false"><j:out value="${tooltip}" /></x:attribute>
<x:attribute name="data-tooltip-interactive">${interactive}</x:attribute>
<x:attribute name="data-tippy-theme">${interactive ? 'dropdown' : 'tooltip'}</x:attribute>
<x:attribute name="data-tippy-trigger">${interactive ? 'mouseenter focus' : 'mouseenter'}</x:attribute>
<x:attribute name="data-tippy-touch">false</x:attribute>
<x:attribute name="data-type">header-action</x:attribute>
<x:attribute name="draggable">false</x:attribute>
<x:attribute name="class">jenkins-button ${isCurrent ? '' : 'jenkins-button--tertiary'}</x:attribute>
<l:icon src="${action.iconFileName}" />
<span class="jenkins-visually-hidden">${action.displayName}</span>
<j:if test="${badge != null}"> <j:if test="${badge != null}">
<span class="jenkins-badge jenkins-!-${badge.severity}-color" /> <div style="text-align: center; color: var(--text-color-secondary)">${badge.tooltip}</div>
</j:if> </j:if>
</x:element> </j:set>
</j:forEach> </j:if>
<j:set var="interactive" value="${jumplist.length() gt 0}" />
<x:element name="${action.urlName == null ? 'button' : 'a'}">
<x:attribute name="data-dropdown">${interactive}</x:attribute>
<x:attribute name="id">root-action-${action.class.simpleName}</x:attribute>
<x:attribute name="href">${h.getActionUrl(app.url, action)}</x:attribute>
<j:if test="${interactive}">
<x:attribute name="data-tippy-offset">[0, 10]</x:attribute>
</j:if>
<j:if test="${!interactive}">
<x:attribute name="data-html-tooltip" escapeText="false"><j:out value="${tooltip}" /></x:attribute>
</j:if>
<x:attribute name="data-tooltip-interactive">${interactive}</x:attribute>
<x:attribute name="data-tippy-animation">tooltip</x:attribute>
<x:attribute name="data-tippy-theme">${interactive ? 'dropdown' : 'tooltip'}</x:attribute>
<x:attribute name="data-tippy-trigger">mouseenter focus</x:attribute>
<x:attribute name="data-tippy-touch">${interactive}</x:attribute>
<x:attribute name="data-type">header-action</x:attribute>
<x:attribute name="draggable">false</x:attribute>
<x:attribute name="class">jenkins-button ${isCurrent ? '' : 'jenkins-button--tertiary'}</x:attribute>
<l:icon src="${action.iconFileName}" />
<span class="jenkins-visually-hidden">${action.displayName}</span>
<j:if test="${badge != null}">
<span class="jenkins-badge jenkins-!-${badge.severity}-color" />
</j:if>
</x:element>
<j:if test="${interactive}">
<template>
<div class="jenkins-dropdown">
<j:out value="${jumplist}" />
</div>
</template>
</j:if>
<j:set var="jumplist" />
<j:set var="tooltip" />
</j:forEach>
<h:login/> <h:login/>
</div> </div>

View File

@ -10,13 +10,21 @@ function init() {
"-dropdown-", "-dropdown-",
1000, 1000,
(element) => { (element) => {
Utils.generateDropdown(element, (instance) => { Utils.generateDropdown(
const elements = element,
element.nextElementSibling.content.children[0].children; (instance) => {
const mappedItems = Utils.convertHtmlToItems(elements); const elements =
element.nextElementSibling.content.children[0].children;
const mappedItems = Utils.convertHtmlToItems(elements);
instance.setContent(Utils.generateDropdownItems(mappedItems)); instance.setContent(Utils.generateDropdownItems(mappedItems));
}); instance.loaded = true;
},
false,
{
appendTo: "parent",
},
);
}, },
); );
} }

View File

@ -1,6 +1,26 @@
import { createElementFromHtml } from "@/util/dom"; import { createElementFromHtml } from "@/util/dom";
import { xmlEscape } from "@/util/security"; import { xmlEscape } from "@/util/security";
const hideOnPopperBlur = {
name: "hideOnPopperBlur",
defaultValue: true,
fn(instance) {
return {
onCreate() {
instance.popper.addEventListener("focusout", (event) => {
if (
instance.props.hideOnPopperBlur &&
event.relatedTarget &&
!instance.popper.contains(event.relatedTarget)
) {
instance.hide();
}
});
},
};
},
};
function dropdown() { function dropdown() {
return { return {
content: "<p class='jenkins-spinner'></p>", content: "<p class='jenkins-spinner'></p>",
@ -11,6 +31,7 @@ function dropdown() {
arrow: false, arrow: false,
theme: "dropdown", theme: "dropdown",
appendTo: document.body, appendTo: document.body,
plugins: [hideOnPopperBlur],
offset: [0, 0], offset: [0, 0],
animation: "dropdown", animation: "dropdown",
onShow: (instance) => { onShow: (instance) => {

View File

@ -38,7 +38,9 @@ function generateDropdown(element, callback, immediate, options = {}) {
if (immediate) { if (immediate) {
onload(); onload();
} else { } else {
instance.reference.addEventListener("mouseenter", onload); ["mouseenter", "focus"].forEach((event) => {
instance.reference.addEventListener(event, onload);
});
} }
}, },
}, },

View File

@ -110,7 +110,10 @@ function generateOverflowButton() {
const actionsContainer = document.querySelector(".jenkins-header__actions"); const actionsContainer = document.querySelector(".jenkins-header__actions");
// Insert the new element before the last child // Insert the new element before the last child
actionsContainer.insertBefore(element, actionsContainer.lastChild); actionsContainer.insertBefore(
element,
actionsContainer.children[actionsContainer.children.length - 2],
);
return element; return element;
} }