mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-10-26 19:15:09 +00:00
Refine the 'New Item' screen (#26031)
* Refine the 'New Item' screen * Simplify SCSS * Fix submission, fix some tests, improve tabbing + focus states * Fix more tests * Simplify JS + hopefully fix test * Update pom.xml * Update Jenkinsfile * Fix issue noticed in #26069 * Restore Jenkinsfile/test pom * Simplify JS * Restore ability to have headings * Fix focus state in Firefox/Chrome * Add separator between items and 'Duplicate an item'
This commit is contained in:
1 parent
4daf33db48
commit
f972b9d819
13 files changed
+381
-328
No files matched your search
@@ -35,35 +35,48 @@ THE SOFTWARE.
|
||||
<l:breadcrumb title="${%NewJob(it.newPronoun)}" />
|
||||
|
||||
<l:main-panel>
|
||||
<template id="i18n" data-empty-name="${%ItemName.validation.required}" />
|
||||
|
||||
<div id="add-item-panel" style="display: none;">
|
||||
<h1>${%NewJob(it.newPronoun)}</h1>
|
||||
<l:app-bar title="${%NewJob(it.newPronoun)}" />
|
||||
|
||||
<form method="post" action="createItem" name="createItem" id="createItem">
|
||||
<div class="header">
|
||||
<div class="add-item-name">
|
||||
<div class="jenkins-form-item add-item-name">
|
||||
<label for="name" class="jenkins-form-label">${%ItemName.label}</label>
|
||||
<input name="name" class="jenkins-input" id="name" data-valid="false" type="text" tabindex="0" />
|
||||
<div id="itemname-required" class="input-validation-message input-message-disabled">» ${%ItemName.validation.required}</div>
|
||||
<div id="itemname-invalid" class="input-validation-message input-message-disabled"></div>
|
||||
<div id="itemtype-required" class="input-validation-message input-message-disabled">» ${%ItemType.validation.required}</div>
|
||||
<div class="validation-error-area" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="jenkins-form-label">${%ItemType.label}</div>
|
||||
<div id="items" class="categories flat" role="radiogroup" aria-labelledby="Items" data-valid="false" />
|
||||
</div>
|
||||
<fieldset class="jenkins-!-display-contents">
|
||||
<div class="jenkins-form-item">
|
||||
<legend class="jenkins-form-label">${%ItemType.label}</legend>
|
||||
<div id="items" class="jenkins-choice-list categories" data-valid="false" />
|
||||
|
||||
<j:if test="${!empty(app.items)}">
|
||||
<div class="item-copy">
|
||||
<p class="jenkins-form-label">${%CopyOption.description}</p>
|
||||
<div class="add-item-copy">
|
||||
<input type="radio" name="mode" value="copy" />
|
||||
<label>${%CopyOption.label}</label>
|
||||
<j:set var="descriptor" value="${it.descriptor}" />
|
||||
<s:textbox id="from" data-valid="false" name="from" placeholder="${%CopyOption.placeholder}" field="copyNewItemFrom"/>
|
||||
</div>
|
||||
<j:if test="${!empty(app.items)}">
|
||||
<div class="jenkins-choice-list jenkins-!-margin-top-2">
|
||||
<div class="jenkins-choice-list__item">
|
||||
<label id="duplicate-job">
|
||||
<div class="jenkins-choice-list__item__icon">
|
||||
<l:icon src="symbol-duplicate" />
|
||||
</div>
|
||||
<input type="radio" name="mode" value="copy" />
|
||||
<div class="jenkins-choice-list__item__label">
|
||||
${%CopyOption.label}
|
||||
</div>
|
||||
<div class="jenkins-choice-list__item__description">${%CopyOption.description}</div>
|
||||
</label>
|
||||
|
||||
<div class="jenkins-choice-list__item__conditional">
|
||||
<j:set var="descriptor" value="${it.descriptor}" />
|
||||
<s:textbox id="from" data-valid="false" name="from" placeholder="${%CopyOption.placeholder}" field="copyNewItemFrom"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</j:if>
|
||||
</div>
|
||||
</j:if>
|
||||
</fieldset>
|
||||
|
||||
<s:bottomButtonBar>
|
||||
<st:include page="newJobButtonBar.jelly"/>
|
||||
|
||||
@@ -6,7 +6,7 @@ ItemType.label=Select an item type
|
||||
ItemName.validation.required=This field cannot be empty, please enter a valid name
|
||||
ItemType.validation.required=Please select an item type
|
||||
CopyOption.placeholder=Type to autocomplete
|
||||
CopyOption.description=If you want to create a new item from other existing, you can use this option:
|
||||
CopyOption.label=Copy from
|
||||
CopyOption.description=Create a new item by copying the configuration of an existing one.
|
||||
CopyOption.label=Duplicate an existing item
|
||||
CopyExisting=Copy existing {0}
|
||||
|
||||
@@ -77,6 +77,7 @@ module.exports = [
|
||||
Utilities: "readonly",
|
||||
UTILITIES_VERSION: "writeable",
|
||||
YAHOO: "readonly",
|
||||
updateValidationArea: "readonly",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+71
-122
@@ -1,5 +1,7 @@
|
||||
import { getI18n } from "@/util/i18n";
|
||||
import { createElementFromHtml } from "@/util/dom";
|
||||
|
||||
const enableHeadings = false;
|
||||
const nameInput = document.querySelector(`#createItem input[name="name"]`);
|
||||
const copyFromInput = document.querySelector(`#createItem input[name="from"]`);
|
||||
const copyRadio = document.querySelector(`#createItem input[value="copy"]`);
|
||||
@@ -60,22 +62,25 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
}
|
||||
}
|
||||
|
||||
function activateValidationMessage(messageId, context, message) {
|
||||
if (message !== undefined && message !== "") {
|
||||
document.querySelector(context + " " + messageId).textContent =
|
||||
"» " + message;
|
||||
/**
|
||||
* Shows or clears the validation message for the name input.
|
||||
*
|
||||
* Only updates the UI after the user has interacted with the input, which is
|
||||
* indicated by `nameInput.dataset.dirty` being set.
|
||||
*/
|
||||
function activateValidationMessage(message) {
|
||||
if (!nameInput.dataset.dirty) {
|
||||
return;
|
||||
}
|
||||
cleanValidationMessages(context);
|
||||
document
|
||||
.querySelector(messageId)
|
||||
.classList.remove("input-message-disabled");
|
||||
refreshSubmitButtonState();
|
||||
}
|
||||
|
||||
function cleanValidationMessages(context) {
|
||||
document
|
||||
.querySelectorAll(context + " .input-validation-message")
|
||||
.forEach((element) => element.classList.add("input-message-disabled"));
|
||||
updateValidationArea(
|
||||
document.querySelector(".validation-error-area"),
|
||||
message !== undefined && message !== ""
|
||||
? `<div class="error">${message}</div>`
|
||||
: `<div/>`,
|
||||
);
|
||||
|
||||
refreshSubmitButtonState();
|
||||
}
|
||||
|
||||
function refreshSubmitButtonState() {
|
||||
@@ -96,15 +101,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
}
|
||||
|
||||
function cleanItemSelection() {
|
||||
document
|
||||
.querySelector('.categories li[role="radio"]')
|
||||
.setAttribute("aria-checked", "false");
|
||||
document
|
||||
.querySelector('#createItem input[type="radio"][name="mode"]')
|
||||
.removeAttribute("checked");
|
||||
document.querySelectorAll(".categories .active").forEach((item) => {
|
||||
item.classList.remove("active");
|
||||
});
|
||||
setFieldValidationStatus("items", false);
|
||||
}
|
||||
|
||||
@@ -120,42 +119,37 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// Draw functions
|
||||
|
||||
function drawCategory(category) {
|
||||
var $category = createElementFromHtml("<div class='category' />");
|
||||
$category.setAttribute(
|
||||
"id",
|
||||
"j-add-item-type-" + cleanClassName(category.id),
|
||||
const heading = createElementFromHtml(
|
||||
"<div class='jenkins-choice-list__heading'></div>",
|
||||
);
|
||||
var $items = createElementFromHtml(`<ul class="j-item-options" />`);
|
||||
var $catHeader = createElementFromHtml(`<div class="header" />`);
|
||||
var title = "<h2>" + category.name + "</h2>";
|
||||
var description = "<p>" + category.description + "</p>";
|
||||
const title = createElementFromHtml("<h2>" + category.name + "</h2>");
|
||||
const description = createElementFromHtml(
|
||||
"<p>" + category.description + "</p>",
|
||||
);
|
||||
heading.appendChild(title);
|
||||
heading.appendChild(description);
|
||||
const response = [];
|
||||
|
||||
if (enableHeadings) {
|
||||
response.push(heading);
|
||||
}
|
||||
|
||||
// Add items
|
||||
category.items.forEach((elem) => {
|
||||
$items.append(drawItem(elem));
|
||||
response.push(drawItem(elem));
|
||||
});
|
||||
|
||||
$catHeader.append(createElementFromHtml(title));
|
||||
$catHeader.append(createElementFromHtml(description));
|
||||
$category.append($catHeader);
|
||||
$category.append($items);
|
||||
|
||||
return $category;
|
||||
return response;
|
||||
}
|
||||
|
||||
function drawItem(elem) {
|
||||
var item = document.createElement("li");
|
||||
item.tabIndex = 0;
|
||||
item.className = cleanClassName(elem.class);
|
||||
item.setAttribute("role", "radio");
|
||||
item.setAttribute("aria-checked", "false");
|
||||
var item = document.createElement("div");
|
||||
item.className =
|
||||
cleanClassName(elem.class) + " jenkins-choice-list__item";
|
||||
|
||||
var label = item.appendChild(document.createElement("label"));
|
||||
|
||||
var iconDiv = drawIcon(elem);
|
||||
item.appendChild(iconDiv);
|
||||
var labelContainer = document.createElement("div");
|
||||
item.appendChild(labelContainer);
|
||||
|
||||
var label = labelContainer.appendChild(document.createElement("label"));
|
||||
label.appendChild(iconDiv);
|
||||
|
||||
var radio = label.appendChild(document.createElement("input"));
|
||||
radio.type = "radio";
|
||||
@@ -163,36 +157,24 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
radio.value = elem.class;
|
||||
|
||||
var displayName = label.appendChild(document.createElement("span"));
|
||||
displayName.className = "label";
|
||||
|
||||
displayName.className = "jenkins-choice-list__item__label";
|
||||
displayName.appendChild(document.createTextNode(elem.displayName));
|
||||
|
||||
var desc = labelContainer.appendChild(document.createElement("div"));
|
||||
desc.className = "desc";
|
||||
var desc = label.appendChild(document.createElement("div"));
|
||||
desc.className = "jenkins-choice-list__item__description";
|
||||
desc.innerHTML = checkForLink(elem.description);
|
||||
|
||||
function select(e) {
|
||||
e.preventDefault();
|
||||
function select() {
|
||||
cleanCopyFromOption();
|
||||
cleanItemSelection();
|
||||
|
||||
item.setAttribute("aria-checked", "true");
|
||||
radio.checked = true;
|
||||
item.classList.add("active");
|
||||
|
||||
setFieldValidationStatus("items", true);
|
||||
|
||||
if (getFieldValidationStatus("name")) {
|
||||
refreshSubmitButtonState();
|
||||
}
|
||||
}
|
||||
|
||||
item.addEventListener("click", select);
|
||||
item.addEventListener("keydown", function (evt) {
|
||||
if (evt.code === "Space" || evt.code === "Enter") {
|
||||
this.click();
|
||||
evt.stopPropagation();
|
||||
}
|
||||
});
|
||||
radio.addEventListener("change", select);
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -200,10 +182,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
function drawIcon(elem) {
|
||||
var iconDiv = document.createElement("div");
|
||||
if (elem.iconXml) {
|
||||
iconDiv.className = "icon";
|
||||
iconDiv.className = "jenkins-choice-list__item__icon";
|
||||
iconDiv.innerHTML = elem.iconXml;
|
||||
} else if (elem.iconClassName && elem.iconQualifiedUrl) {
|
||||
iconDiv.className = "icon";
|
||||
iconDiv.className = "jenkins-choice-list__item__icon";
|
||||
|
||||
var img1 = document.createElement("img");
|
||||
img1.src = elem.iconQualifiedUrl;
|
||||
@@ -212,7 +194,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// Example for Freestyle project
|
||||
// <div class="icon"><img class="icon-freestyle-project icon-xlg" src="/jenkins/static/108b2346/images/48x48/freestyleproject.png"></div>
|
||||
} else if (elem.iconFilePathPattern) {
|
||||
iconDiv.className = "icon";
|
||||
iconDiv.className = "jenkins-choice-list__item__icon";
|
||||
|
||||
var iconFilePath =
|
||||
jRoot + "/" + elem.iconFilePathPattern.replace(":size", "48x48");
|
||||
@@ -238,7 +220,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
spanFakeImgB.className = "b";
|
||||
spanFakeImgB.innerText = b;
|
||||
iconDiv.appendChild(spanFakeImgB);
|
||||
iconDiv.className = "default-icon";
|
||||
iconDiv.className = "jenkins-choice-list__item__icon";
|
||||
|
||||
// Example for MockFolder
|
||||
// <div class="default-icon c-49728B"><span class="a">M</span><span class="b">o</span></div>
|
||||
@@ -250,14 +232,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelector("#add-item-panel").removeAttribute("style");
|
||||
|
||||
// Render all categories
|
||||
var $categories = document.querySelector("div.categories");
|
||||
var $categories = document.querySelector(".categories");
|
||||
data.categories.forEach((elem) => {
|
||||
$categories.append(drawCategory(elem));
|
||||
drawCategory(elem).forEach((e) => $categories.append(e));
|
||||
});
|
||||
|
||||
// Focus
|
||||
document.querySelector("#add-item-panel #name").focus();
|
||||
|
||||
// Init NameField
|
||||
function nameFieldEvent() {
|
||||
if (!isItemNameEmpty()) {
|
||||
@@ -268,13 +247,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
response.text().then((data) => {
|
||||
var message = parseResponseFromCheckJobName(data);
|
||||
if (message !== "") {
|
||||
activateValidationMessage(
|
||||
"#itemname-invalid",
|
||||
".add-item-name",
|
||||
message,
|
||||
);
|
||||
activateValidationMessage(message);
|
||||
setFieldValidationStatus("name", false);
|
||||
refreshSubmitButtonState();
|
||||
} else {
|
||||
cleanValidationMessages(".add-item-name");
|
||||
activateValidationMessage("");
|
||||
setFieldValidationStatus("name", true);
|
||||
refreshSubmitButtonState();
|
||||
}
|
||||
@@ -283,14 +260,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
);
|
||||
} else {
|
||||
setFieldValidationStatus("name", false);
|
||||
cleanValidationMessages(".add-item-name");
|
||||
activateValidationMessage("#itemname-required", ".add-item-name");
|
||||
activateValidationMessage(getI18n("empty-name"));
|
||||
refreshSubmitButtonState();
|
||||
}
|
||||
}
|
||||
|
||||
nameInput.addEventListener("blur", nameFieldEvent);
|
||||
nameInput.addEventListener("input", nameFieldEvent);
|
||||
nameInput.addEventListener("input", () => {
|
||||
nameInput.dataset.dirty = "true";
|
||||
nameFieldEvent();
|
||||
});
|
||||
|
||||
// Init CopyFromField
|
||||
function copyFromFieldEvent() {
|
||||
@@ -300,53 +279,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
cleanItemSelection();
|
||||
copyRadio.setAttribute("checked", true);
|
||||
setFieldValidationStatus("from", true);
|
||||
if (!getFieldValidationStatus("name")) {
|
||||
activateValidationMessage("#itemname-required", ".add-item-name");
|
||||
setTimeout(function () {
|
||||
var parentName = copyFromInput.value;
|
||||
|
||||
fetch("job/" + parentName + "/api/json?tree=name").then(
|
||||
(response) => {
|
||||
response.json().then((data) => {
|
||||
if (data.name === parentName) {
|
||||
//if "name" is invalid, but "from" is a valid job, then switch focus to "name"
|
||||
nameInput.focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}, 400);
|
||||
} else {
|
||||
refreshSubmitButtonState();
|
||||
}
|
||||
refreshSubmitButtonState();
|
||||
}
|
||||
}
|
||||
|
||||
copyFromInput?.addEventListener("blur", copyFromFieldEvent);
|
||||
copyFromInput?.addEventListener("input", copyFromFieldEvent);
|
||||
|
||||
// Client-side validation
|
||||
document
|
||||
.querySelector("#createItem")
|
||||
.addEventListener("submit", function (event) {
|
||||
if (!getFormValidationStatus()) {
|
||||
event.preventDefault();
|
||||
if (!getFieldValidationStatus("name")) {
|
||||
activateValidationMessage("#itemname-required", ".add-item-name");
|
||||
nameInput.focus();
|
||||
} else {
|
||||
if (
|
||||
!getFieldValidationStatus("items") &&
|
||||
!getFieldValidationStatus("from")
|
||||
) {
|
||||
activateValidationMessage("#itemtype-required", ".add-item-name");
|
||||
nameInput.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Focus the Name input on load
|
||||
document.querySelector("#add-item-panel #name").focus();
|
||||
|
||||
// Disable the submit button
|
||||
// Disable the Submit button on load
|
||||
refreshSubmitButtonState();
|
||||
});
|
||||
|
||||
if (copyRadio !== null) {
|
||||
copyRadio.addEventListener("change", () => {
|
||||
copyFromInput.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,19 @@
|
||||
/*
|
||||
* Widget styles
|
||||
*/
|
||||
@use "widgets/add/addform";
|
||||
#add-item-panel {
|
||||
max-width: 750px;
|
||||
margin: auto;
|
||||
|
||||
.jenkins-choice-list__item {
|
||||
// Default icons' text placeholder don't appear unless z-index is set
|
||||
.jenkins-choice-list__item__icon {
|
||||
span {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Make 'Duplicate an existing item' input not full width
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
@use "../../../scss/abstracts/mixins";
|
||||
#add-item-panel {
|
||||
max-width: 850px;
|
||||
margin: auto;
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--section-padding);
|
||||
|
||||
#bottom-sticker {
|
||||
margin-top: calc(var(--section-padding) * -1);
|
||||
}
|
||||
}
|
||||
|
||||
.item-copy {
|
||||
position: relative;
|
||||
|
||||
.add-item-copy {
|
||||
position: relative;
|
||||
|
||||
input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
line-height: 48px;
|
||||
padding: 0 10px 0 0;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-item-name,
|
||||
.add-item-copy {
|
||||
.input-validation-message {
|
||||
color: var(--error-color);
|
||||
padding-top: 2px;
|
||||
font-weight: var(--font-bold-weight);
|
||||
}
|
||||
|
||||
.input-message-disabled {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.default-icon {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border: 1px solid var(--text-color);
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
font-weight: bold;
|
||||
font-size: 125%;
|
||||
color: var(--text-color);
|
||||
opacity: 0.75;
|
||||
|
||||
.b {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
width: 50%;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.categories {
|
||||
.category {
|
||||
.header {
|
||||
padding: 0 20px;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
padding: 15px 0 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.categories.flat {
|
||||
.category {
|
||||
border-bottom: none;
|
||||
|
||||
.header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.j-item-options li,
|
||||
.j-item-options {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.j-item-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
|
||||
li.active {
|
||||
&::before {
|
||||
background: var(--item-background--hover);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
@include mixins.item($border: false);
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
user-select: none;
|
||||
padding: 0.75rem 1rem;
|
||||
min-height: 68px;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.25rem 1rem;
|
||||
|
||||
.icon {
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-bold-weight);
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
left: 25px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
grid-column: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
.jenkins-choice-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: var(--form-input-border-radius);
|
||||
border: var(--card-border-width) solid var(--card-border-color);
|
||||
background: var(--card-background);
|
||||
|
||||
&__heading {
|
||||
font-size: 1rem;
|
||||
padding: 0.875rem;
|
||||
border-bottom: var(--card-border-width) solid var(--card-border-color);
|
||||
|
||||
& > * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: var(--font-bold-weight);
|
||||
font-size: var(--font-size-sm);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
-webkit-touch-callout: none;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
white-space: unset;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
border-bottom: var(--card-border-width) solid var(--card-border-color);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
transition: var(--standard-transition);
|
||||
background: transparent;
|
||||
box-shadow:
|
||||
0 0 0 0.09375rem transparent,
|
||||
0 0 0 0.5rem transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
background: color-mix(
|
||||
in sRGB,
|
||||
var(--text-color-secondary) 7.5%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:has(input[type="radio"]:focus-visible) {
|
||||
z-index: 10;
|
||||
border-radius: calc(var(--form-input-border-radius) / 2);
|
||||
|
||||
&::before {
|
||||
background: color-mix(
|
||||
in sRGB,
|
||||
var(--text-color-secondary) 12.5%,
|
||||
transparent
|
||||
) !important;
|
||||
box-shadow:
|
||||
0 0 0 0.09375rem
|
||||
color-mix(
|
||||
in sRGB,
|
||||
var(--text-color-secondary) 20%,
|
||||
var(--card-background)
|
||||
),
|
||||
0 0 0 0.34375rem
|
||||
color-mix(
|
||||
in sRGB,
|
||||
color-mix(
|
||||
in sRGB,
|
||||
var(--text-color-secondary) 15%,
|
||||
var(--card-background)
|
||||
)
|
||||
65%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:has(input[type="radio"]:focus-visible) {
|
||||
.jenkins-choice-list__item__icon::before {
|
||||
background: rgb(from var(--text-color-secondary) r g b / 0.1);
|
||||
box-shadow:
|
||||
inset var(--jenkins-border--subtle-shadow),
|
||||
inset 0 0 0 var(--jenkins-border-width)
|
||||
rgb(from var(--text-color-secondary) r g b / 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:has(input[type="radio"]:focus-visible) {
|
||||
&::before {
|
||||
box-shadow: 0 0 0 0.2rem var(--text-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(input:checked) {
|
||||
&::before {
|
||||
background: color-mix(
|
||||
in sRGB,
|
||||
var(--text-color-secondary) 5%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.jenkins-choice-list__item__icon {
|
||||
color: var(--background);
|
||||
|
||||
&::before {
|
||||
box-shadow:
|
||||
inset var(--jenkins-border--subtle-shadow),
|
||||
inset 0 0 18px 18px var(--accent-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.jenkins-choice-list__item__conditional {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
border-top-left-radius: calc(
|
||||
var(--form-input-border-radius) - var(--jenkins-border-width)
|
||||
) !important;
|
||||
border-top-right-radius: calc(
|
||||
var(--form-input-border-radius) - var(--jenkins-border-width)
|
||||
) !important;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom-left-radius: calc(
|
||||
var(--form-input-border-radius) - var(--jenkins-border-width)
|
||||
) !important;
|
||||
border-bottom-right-radius: calc(
|
||||
var(--form-input-border-radius) - var(--jenkins-border-width)
|
||||
) !important;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
gap: 0.25rem 0.875rem;
|
||||
padding: 0.875rem;
|
||||
grid-template-columns: auto 1fr;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label > input[type="radio"] {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2.25rem;
|
||||
width: 2.25rem;
|
||||
grid-row: span 2;
|
||||
z-index: 10;
|
||||
transition: var(--standard-transition);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: var(--form-input-border-radius);
|
||||
transition: var(--standard-transition);
|
||||
z-index: 0;
|
||||
background: rgb(from var(--text-color-secondary) r g b / 0.05);
|
||||
box-shadow:
|
||||
inset 0 0 0 var(--jenkins-border-width) var(--jenkins-border-color),
|
||||
inset 0 0 0 var(--jenkins-border-width)
|
||||
rgb(from var(--text-color-secondary) r g b / 0.05);
|
||||
}
|
||||
|
||||
svg,
|
||||
img {
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-bold-weight);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: var(--text-color-secondary);
|
||||
grid-column: 2;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
&__conditional {
|
||||
display: none;
|
||||
margin-left: 4rem;
|
||||
margin-top: -0.25rem;
|
||||
margin-bottom: 0.875rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
@use "breadcrumbs";
|
||||
@use "buttons";
|
||||
@use "cards";
|
||||
@use "choice-list";
|
||||
@use "command-palette";
|
||||
@use "content-blocks";
|
||||
@use "description";
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
width: 100%;
|
||||
min-height: 2.375rem;
|
||||
box-shadow: var(--form-input-glow);
|
||||
caret-color: var(--focus-input-border);
|
||||
|
||||
// Set height transition to 0s as vertical resizing has a delay/lag otherwise
|
||||
transition:
|
||||
|
||||
@@ -50,6 +50,22 @@
|
||||
&--medium {
|
||||
max-width: var(--form-item-max-width--medium);
|
||||
}
|
||||
|
||||
&:has(> .validation-error-area .warning) {
|
||||
--input-border: var(--warning-color);
|
||||
--input-border-hover: var(--warning-color);
|
||||
--focus-input-border: var(--warning-color);
|
||||
--form-input-glow--focus: 0 0 0 0.25rem
|
||||
color-mix(in sRGB, var(--warning-color) 20%, transparent);
|
||||
}
|
||||
|
||||
&:has(> .validation-error-area .error) {
|
||||
--input-border: var(--error-color);
|
||||
--input-border-hover: var(--error-color);
|
||||
--focus-input-border: var(--error-color);
|
||||
--form-input-glow--focus: 0 0 0 0.25rem
|
||||
color-mix(in sRGB, var(--error-color) 20%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.jenkins-form-label {
|
||||
|
||||
@@ -88,7 +88,7 @@ class MyViewTest {
|
||||
form.getInputByName("name").setValue("job");
|
||||
form.getInputByName("name").blur();
|
||||
// Select the item clicking on the first item type shown
|
||||
HtmlElement itemType = newItemPage.getFirstByXPath("//div[@class='category']/ul/li");
|
||||
HtmlElement itemType = newItemPage.querySelector(".jenkins-choice-list div label");
|
||||
itemType.click();
|
||||
rule.submit(form);
|
||||
Item item = rule.jenkins.getItem("job");
|
||||
|
||||
@@ -844,7 +844,8 @@ class ViewTest {
|
||||
|
||||
HtmlPage page = wc.goTo("view/all/newJob");
|
||||
|
||||
Object result = page.executeJavaScript("Array.from(document.querySelectorAll('.label')).filter(el => el.innerText.indexOf('" + customizableTLID.customDisplayName + "') !== -1)[0].parentElement.parentElement").getJavaScriptResult();
|
||||
Object result = page.executeJavaScript("Array.from(document.querySelectorAll('.jenkins-choice-list__item__label'))" +
|
||||
".filter(el => el.innerText.indexOf('" + customizableTLID.customDisplayName + "') !== -1)[0].parentElement.parentElement").getJavaScriptResult();
|
||||
assertThat(result, instanceOf(HTMLElement.class));
|
||||
HTMLElement resultElement = (HTMLElement) result;
|
||||
assertThat(resultElement.getAttribute("onclick"), nullValue());
|
||||
@@ -861,7 +862,7 @@ class ViewTest {
|
||||
|
||||
HtmlPage page = wc.goTo("view/all/newJob");
|
||||
|
||||
Object result = page.executeJavaScript("document.querySelector('.xss-dn .label').innerHTML").getJavaScriptResult();
|
||||
Object result = page.executeJavaScript("document.querySelector('.xss-dn .jenkins-choice-list__item__label').innerHTML").getJavaScriptResult();
|
||||
assertThat(result, instanceOf(String.class));
|
||||
String resultString = (String) result;
|
||||
assertThat(resultString, not(containsString("<")));
|
||||
@@ -877,7 +878,7 @@ class ViewTest {
|
||||
|
||||
HtmlPage page = wc.goTo("view/all/newJob");
|
||||
|
||||
Object result = page.executeJavaScript("document.querySelector('.html-desc .desc strong')").getJavaScriptResult();
|
||||
Object result = page.executeJavaScript("document.querySelector('.html-desc .jenkins-choice-list__item__description strong')").getJavaScriptResult();
|
||||
assertThat(result, instanceOf(HTMLElement.class));
|
||||
assertThat(((HTMLElement) result).getTagName(), is("STRONG"));
|
||||
}
|
||||
@@ -894,12 +895,12 @@ class ViewTest {
|
||||
|
||||
HtmlPage page = wc.goTo("view/all/newJob");
|
||||
|
||||
Object resultIconChildrenCount = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .icon').children.length").getJavaScriptResult();
|
||||
Object resultIconChildrenCount = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .jenkins-choice-list__item__icon').children.length").getJavaScriptResult();
|
||||
assertThat(resultIconChildrenCount, instanceOf(Integer.class));
|
||||
int resultIconChildrenCountInt = (int) resultIconChildrenCount;
|
||||
assertEquals(1, resultIconChildrenCountInt);
|
||||
|
||||
Object resultImgAttributesCount = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .icon img').attributes.length").getJavaScriptResult();
|
||||
Object resultImgAttributesCount = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .jenkins-choice-list__item__icon img').attributes.length").getJavaScriptResult();
|
||||
assertThat(resultImgAttributesCount, instanceOf(Integer.class));
|
||||
int resultImgAttributesCountInt = (int) resultImgAttributesCount;
|
||||
assertEquals(1, resultImgAttributesCountInt);
|
||||
@@ -929,7 +930,7 @@ class ViewTest {
|
||||
|
||||
HtmlPage page = wc.goTo("view/all/newJob");
|
||||
|
||||
Object resultSrc = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .icon img').src").getJavaScriptResult();
|
||||
Object resultSrc = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .jenkins-choice-list__item__icon img').src").getJavaScriptResult();
|
||||
|
||||
assertThat(resultSrc, instanceOf(String.class));
|
||||
String resultSrcString = (String) resultSrc;
|
||||
@@ -944,7 +945,7 @@ class ViewTest {
|
||||
|
||||
HtmlPage page = wc.goTo("view/all/newJob");
|
||||
|
||||
Object result = page.executeJavaScript("document.querySelector('.hudson_model_FreeStyleProject .icon svg')").getJavaScriptResult();
|
||||
Object result = page.executeJavaScript("document.querySelector('.hudson_model_FreeStyleProject .jenkins-choice-list__item__icon svg')").getJavaScriptResult();
|
||||
assertThat(result, instanceOf(SVGElement.class));
|
||||
SVGElement svg = (SVGElement) result;
|
||||
assertThat(svg.getClassName_js(), is("icon-xlg"));
|
||||
@@ -962,7 +963,7 @@ class ViewTest {
|
||||
|
||||
HtmlPage page = wc.goTo("view/all/newJob");
|
||||
|
||||
Object result = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .default-icon')").getJavaScriptResult();
|
||||
Object result = page.executeJavaScript("document.querySelector('." + customizableTLID.customId + " .jenkins-choice-list__item__icon')").getJavaScriptResult();
|
||||
assertThat(result, instanceOf(HTMLElement.class));
|
||||
HTMLElement resultHtml = (HTMLElement) result;
|
||||
HTMLElement spanA = (HTMLElement) resultHtml.getFirstElementChild();
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><rect x="128" y="128" width="336" height="336" rx="57" ry="57" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path d="M383.5 128l.5-24a56.16 56.16 0 00-56-56H112a64.19 64.19 0 00-64 64v216a56.16 56.16 0 0056 56h24M296 216v160M376 296H216" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
||||
|
After Width: | Height: | Size: 454 B |
Reference in new issue
Block a user