animations: add UI animations across desk for a polished, alive feel
- Dropdowns: smooth fade in/out via visibility+opacity transition - Modals/Dialogs: entrance with translateY+scale+opacity (0.5s) - Search dialog: fade in/out with slide, backdrop cleanup, input re-focus - Form sections: cascade entrance animation on load and tab switch - Form tabs: sliding active indicator via ::after scaleX - Collapse/expand: smooth max-height transition with JS-calculated height - Sidebar: hover padding shift, icon scale, active accent bar - List view: staggered row entrance from left - Workspace cards: slower entrance (0.7s), number value delayed fade-in - Widget z-index: open dropdown (200) above hovered neighbor (100) - Report summary: cascade card entrance - Link fields: awesomplete dropdown fade-in - Input focus: inset press effect - Buttons: shine sweep on hover, inset shadow on click - Chrome theme: tab indicator uses ::after instead of border-bottom - Observer fix: null-check parentNode for detached text nodes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
12f3e16b18
commit
5cbc36566b
|
|
@ -277,8 +277,353 @@ input.list-header-checkbox:checked::after {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Modal / Dialog — Smooth Entrance
|
||||
========================================================================== */
|
||||
|
||||
.modal.fade .modal-dialog {
|
||||
transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1) !important;
|
||||
transform: translateY(-24px) scale(0.97);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal.show .modal-dialog {
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Form Sections — Cascade Entrance
|
||||
========================================================================== */
|
||||
|
||||
@keyframes jey-section-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.form-layout .form-section .section-head,
|
||||
.form-layout .form-section .section-body {
|
||||
animation: jey-section-in 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.form-layout .form-section:nth-child(1) .section-head,
|
||||
.form-layout .form-section:nth-child(1) .section-body { animation-delay: 0.04s; }
|
||||
.form-layout .form-section:nth-child(2) .section-head,
|
||||
.form-layout .form-section:nth-child(2) .section-body { animation-delay: 0.10s; }
|
||||
.form-layout .form-section:nth-child(3) .section-head,
|
||||
.form-layout .form-section:nth-child(3) .section-body { animation-delay: 0.16s; }
|
||||
.form-layout .form-section:nth-child(4) .section-head,
|
||||
.form-layout .form-section:nth-child(4) .section-body { animation-delay: 0.22s; }
|
||||
.form-layout .form-section:nth-child(5) .section-head,
|
||||
.form-layout .form-section:nth-child(5) .section-body { animation-delay: 0.28s; }
|
||||
.form-layout .form-section:nth-child(6) .section-head,
|
||||
.form-layout .form-section:nth-child(6) .section-body { animation-delay: 0.34s; }
|
||||
.form-layout .form-section:nth-child(7) .section-head,
|
||||
.form-layout .form-section:nth-child(7) .section-body { animation-delay: 0.40s; }
|
||||
.form-layout .form-section:nth-child(8) .section-head,
|
||||
.form-layout .form-section:nth-child(8) .section-body { animation-delay: 0.46s; }
|
||||
.form-layout .form-section:nth-child(9) .section-head,
|
||||
.form-layout .form-section:nth-child(9) .section-body { animation-delay: 0.52s; }
|
||||
.form-layout .form-section:nth-child(10) .section-head,
|
||||
.form-layout .form-section:nth-child(10) .section-body { animation-delay: 0.58s; }
|
||||
.form-layout .form-section:nth-child(11) .section-head,
|
||||
.form-layout .form-section:nth-child(11) .section-body { animation-delay: 0.64s; }
|
||||
.form-layout .form-section:nth-child(12) .section-head,
|
||||
.form-layout .form-section:nth-child(12) .section-body { animation-delay: 0.70s; }
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Form Tabs — Sliding Active Indicator
|
||||
========================================================================== */
|
||||
|
||||
.form-tabs .nav-link {
|
||||
position: relative;
|
||||
border-bottom: none !important;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-tabs .nav-link::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--text-color);
|
||||
transform: scaleX(0);
|
||||
transform-origin: center;
|
||||
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.form-tabs .nav-link.active::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Dropdown Animations
|
||||
========================================================================== */
|
||||
|
||||
.dropdown-menu {
|
||||
display: block !important;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease, visibility 0.2s;
|
||||
}
|
||||
|
||||
.dropdown-menu.show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Sidebar — Hover Effects
|
||||
========================================================================== */
|
||||
|
||||
.body-sidebar .standard-sidebar-item .item-anchor {
|
||||
transition: padding-left 0.4s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
background 0.4s ease;
|
||||
}
|
||||
|
||||
.body-sidebar .standard-sidebar-item .item-anchor:hover {
|
||||
padding-left: calc(var(--padding-xs, 8px) + 4px);
|
||||
}
|
||||
|
||||
.body-sidebar .standard-sidebar-item .sidebar-item-icon {
|
||||
transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.body-sidebar .standard-sidebar-item .item-anchor:hover .sidebar-item-icon {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
/* Active item — subtle left accent bar */
|
||||
.body-sidebar .standard-sidebar-item.active-sidebar .item-anchor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.body-sidebar .standard-sidebar-item.active-sidebar .item-anchor::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 20%;
|
||||
bottom: 20%;
|
||||
width: 3px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
background: var(--primary, #2490ef);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
List View — Row Entrance Animation
|
||||
========================================================================== */
|
||||
|
||||
@keyframes jey-list-row-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-16px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.frappe-list .result .list-row-container:not(:first-child) {
|
||||
animation: jey-list-row-in 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.frappe-list .result .list-row-container:nth-child(2) { animation-delay: 0.03s; }
|
||||
.frappe-list .result .list-row-container:nth-child(3) { animation-delay: 0.05s; }
|
||||
.frappe-list .result .list-row-container:nth-child(4) { animation-delay: 0.07s; }
|
||||
.frappe-list .result .list-row-container:nth-child(5) { animation-delay: 0.09s; }
|
||||
.frappe-list .result .list-row-container:nth-child(6) { animation-delay: 0.11s; }
|
||||
.frappe-list .result .list-row-container:nth-child(7) { animation-delay: 0.13s; }
|
||||
.frappe-list .result .list-row-container:nth-child(8) { animation-delay: 0.15s; }
|
||||
.frappe-list .result .list-row-container:nth-child(9) { animation-delay: 0.17s; }
|
||||
.frappe-list .result .list-row-container:nth-child(10) { animation-delay: 0.19s; }
|
||||
.frappe-list .result .list-row-container:nth-child(11) { animation-delay: 0.21s; }
|
||||
.frappe-list .result .list-row-container:nth-child(12) { animation-delay: 0.23s; }
|
||||
.frappe-list .result .list-row-container:nth-child(13) { animation-delay: 0.25s; }
|
||||
.frappe-list .result .list-row-container:nth-child(14) { animation-delay: 0.27s; }
|
||||
.frappe-list .result .list-row-container:nth-child(15) { animation-delay: 0.29s; }
|
||||
.frappe-list .result .list-row-container:nth-child(16) { animation-delay: 0.31s; }
|
||||
.frappe-list .result .list-row-container:nth-child(17) { animation-delay: 0.33s; }
|
||||
.frappe-list .result .list-row-container:nth-child(18) { animation-delay: 0.35s; }
|
||||
.frappe-list .result .list-row-container:nth-child(19) { animation-delay: 0.37s; }
|
||||
.frappe-list .result .list-row-container:nth-child(20) { animation-delay: 0.39s; }
|
||||
.frappe-list .result .list-row-container:nth-child(21) { animation-delay: 0.41s; }
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Search Dialog — Fade In / Out
|
||||
========================================================================== */
|
||||
|
||||
.modal:has(.cool-awesomebar-modal-footer) {
|
||||
display: block !important;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
visibility 0.5s;
|
||||
}
|
||||
|
||||
.modal:has(.cool-awesomebar-modal-footer) .modal-dialog {
|
||||
transform: translateY(-30px);
|
||||
transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.modal.show:has(.cool-awesomebar-modal-footer) {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.modal.show:has(.cool-awesomebar-modal-footer) .modal-dialog {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Report Summary — Card Cascade
|
||||
========================================================================== */
|
||||
|
||||
@keyframes jey-summary-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.report-summary .summary-item {
|
||||
animation: jey-summary-in 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.report-summary .summary-item:nth-child(1) { animation-delay: 0.05s; }
|
||||
.report-summary .summary-item:nth-child(2) { animation-delay: 0.10s; }
|
||||
.report-summary .summary-item:nth-child(3) { animation-delay: 0.15s; }
|
||||
.report-summary .summary-item:nth-child(4) { animation-delay: 0.20s; }
|
||||
.report-summary .summary-item:nth-child(5) { animation-delay: 0.25s; }
|
||||
.report-summary .summary-item:nth-child(6) { animation-delay: 0.30s; }
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Form Section Collapse / Expand — Smooth
|
||||
========================================================================== */
|
||||
|
||||
.form-section .collapse-indicator {
|
||||
transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.form-section .section-head.collapsed .collapse-indicator {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.form-section .section-head.collapsible + .section-body {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.5s ease-in-out,
|
||||
padding 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.form-section .section-head.collapsed + .section-body {
|
||||
max-height: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
transition: max-height 0.5s ease-in-out,
|
||||
padding 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Link Field — Awesomplete Dropdown Fade
|
||||
========================================================================== */
|
||||
|
||||
.awesomplete > [role="listbox"] {
|
||||
animation: jey-awesomplete-in 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
@keyframes jey-awesomplete-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Input Focus — Inset Press
|
||||
========================================================================== */
|
||||
|
||||
.form-control,
|
||||
.frappe-control input,
|
||||
.frappe-control textarea,
|
||||
.frappe-control select {
|
||||
transition: box-shadow 0.4s ease, border-color 0.4s ease;
|
||||
}
|
||||
|
||||
.form-control:focus,
|
||||
.frappe-control input:focus,
|
||||
.frappe-control textarea:focus,
|
||||
.frappe-control select:focus {
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
border-color: var(--gray-400, #c0c6cc) !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Buttons — Shine on Hover, Press on Click
|
||||
========================================================================== */
|
||||
|
||||
.btn {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: box-shadow 0.4s ease, background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.btn::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(105deg,
|
||||
transparent 35%,
|
||||
rgba(255, 255, 255, 0.25) 45%,
|
||||
rgba(255, 255, 255, 0.25) 55%,
|
||||
transparent 65%);
|
||||
transform: translateX(-110%);
|
||||
transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn:hover::after {
|
||||
transform: translateX(110%);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
|
||||
/* Hide dropdown items now surfaced as sidebar buttons */
|
||||
.dropdown-menu-item[onclick*="toggle_full_width"],
|
||||
.dropdown-menu-item[onclick*="clear_cache"],
|
||||
.dropdown-menu-item[onclick*="ThemeSwitcher"],
|
||||
.dropdown-menu-item[data-name="website"],
|
||||
.dropdown-menu-item.jey-hidden-item {
|
||||
|
|
@ -473,13 +818,23 @@ input.list-header-checkbox:checked::after {
|
|||
.ce-block .number-widget-box {
|
||||
opacity: 0;
|
||||
transform: translateY(24px) scale(0.97);
|
||||
animation: jey-card-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
animation: jey-card-in 0.7s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
border-radius: var(--border-radius-lg, 12px);
|
||||
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 0.35s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Number card value — delayed fade-in after card entrance */
|
||||
@keyframes jey-number-in {
|
||||
from { opacity: 0; transform: translateY(6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.number-widget-box .widget-content .number {
|
||||
animation: jey-number-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) 0.3s both;
|
||||
}
|
||||
|
||||
/* Allow dropdown menus (card actions) to escape the widget box.
|
||||
Without this, .widget-control .dropdown-menu is clipped/hidden behind
|
||||
the widget because Popper renders it as a sibling inside the box. */
|
||||
|
|
@ -559,10 +914,13 @@ input.list-header-checkbox:checked::after {
|
|||
.ce-block .links-widget-box:hover,
|
||||
.ce-block .number-widget-box:hover,
|
||||
.ce-block .links-widget-box:focus-within,
|
||||
.ce-block .number-widget-box:focus-within,
|
||||
.ce-block .number-widget-box:focus-within {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.ce-block .links-widget-box:has(.dropdown-menu.show),
|
||||
.ce-block .number-widget-box:has(.dropdown-menu.show) {
|
||||
z-index: 100;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -680,10 +680,13 @@
|
|||
|
||||
[data-jey-theme="chrome"] .form-tabs .nav-link.active {
|
||||
color: #333 !important;
|
||||
border-bottom: 2px solid #777 !important;
|
||||
background: linear-gradient(180deg, rgba(255,255,255,0.2) 0%, transparent 100%) !important;
|
||||
}
|
||||
|
||||
[data-jey-theme="chrome"] .form-tabs .nav-link::after {
|
||||
background: #777;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Dropdowns — Chrome Panel
|
||||
|
|
|
|||
|
|
@ -1433,13 +1433,13 @@
|
|||
var node = m.addedNodes[j];
|
||||
if (node.nodeType === 1 && !SKIP_TAGS[node.tagName]) {
|
||||
processNode(node);
|
||||
} else if (node.nodeType === 3 && !SKIP_TAGS[node.parentNode.tagName] && needsRebrand(node.nodeValue)) {
|
||||
} else if (node.nodeType === 3 && node.parentNode && !SKIP_TAGS[node.parentNode.tagName] && needsRebrand(node.nodeValue)) {
|
||||
node.nodeValue = rebrandText(node.nodeValue);
|
||||
}
|
||||
}
|
||||
/* Handle direct text changes (e.g. frappe sets .textContent) */
|
||||
if (m.type === "characterData" && m.target.nodeType === 3) {
|
||||
if (!SKIP_TAGS[m.target.parentNode.tagName] && needsRebrand(m.target.nodeValue)) {
|
||||
if (m.target.parentNode && !SKIP_TAGS[m.target.parentNode.tagName] && needsRebrand(m.target.nodeValue)) {
|
||||
m.target.nodeValue = rebrandText(m.target.nodeValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -1460,3 +1460,105 @@
|
|||
init();
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Form Tabs — Re-trigger section cascade on tab switch
|
||||
========================================================================== */
|
||||
(function () {
|
||||
document.addEventListener("click", function (e) {
|
||||
var tab = e.target.closest(".form-tabs .nav-link");
|
||||
if (!tab) return;
|
||||
|
||||
// Small delay so Frappe shows the tab content first
|
||||
setTimeout(function () {
|
||||
var sections = document.querySelectorAll(".form-layout .form-section .section-head, .form-layout .form-section .section-body");
|
||||
sections.forEach(function (s) {
|
||||
s.style.animation = "none";
|
||||
// Force reflow then restart animation
|
||||
void s.offsetHeight;
|
||||
s.style.animation = "";
|
||||
});
|
||||
}, 20);
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Form Sections — Set exact max-height for smooth collapse animation
|
||||
========================================================================== */
|
||||
(function () {
|
||||
document.addEventListener("click", function (e) {
|
||||
var head = e.target.closest(".section-head.collapsible");
|
||||
if (!head) return;
|
||||
var body = head.nextElementSibling;
|
||||
if (!body || !body.classList.contains("section-body")) return;
|
||||
|
||||
// Set exact max-height so transition has a real start/end value
|
||||
body.style.maxHeight = body.scrollHeight + "px";
|
||||
// Force reflow — browser must register this value before Frappe toggles .collapsed
|
||||
void body.offsetHeight;
|
||||
}, true);
|
||||
})();
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Search Modal — smooth close (backdrop cleanup)
|
||||
========================================================================== */
|
||||
// Bootstrap leaves the backdrop when we override display.
|
||||
// Remove it manually when .show is removed.
|
||||
(function () {
|
||||
var searchModal = null;
|
||||
var observer = new MutationObserver(function (mutations) {
|
||||
for (var i = 0; i < mutations.length; i++) {
|
||||
var m = mutations[i];
|
||||
if (m.attributeName === "class" && searchModal) {
|
||||
if (!searchModal.classList.contains("show") && searchModal.dataset.jeyWasOpen) {
|
||||
delete searchModal.dataset.jeyWasOpen;
|
||||
// After transition ends, clean up backdrop
|
||||
setTimeout(function () {
|
||||
var bd = document.querySelector(".modal-backdrop");
|
||||
if (bd) bd.remove();
|
||||
document.body.classList.remove("modal-open");
|
||||
document.body.style.removeProperty("padding-right");
|
||||
}, 550);
|
||||
}
|
||||
if (searchModal.classList.contains("show")) {
|
||||
searchModal.dataset.jeyWasOpen = "1";
|
||||
// Re-focus input after CSS transition so awesomplete opens
|
||||
var input = searchModal.querySelector("#navbar-search");
|
||||
if (input) {
|
||||
setTimeout(function () {
|
||||
input.blur();
|
||||
input.focus();
|
||||
}, 550);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Find search modal and start observing
|
||||
var bodyObserver = new MutationObserver(function () {
|
||||
if (searchModal) return;
|
||||
var el = document.querySelector(".modal:has(.cool-awesomebar-modal-footer)");
|
||||
if (!el) return;
|
||||
searchModal = el;
|
||||
bodyObserver.disconnect();
|
||||
observer.observe(searchModal, { attributes: true, attributeFilter: ["class"] });
|
||||
// If .show is already present (race condition on first open), trigger focus now
|
||||
if (searchModal.classList.contains("show")) {
|
||||
searchModal.dataset.jeyWasOpen = "1";
|
||||
var input = searchModal.querySelector("#navbar-search");
|
||||
if (input) {
|
||||
setTimeout(function () {
|
||||
input.blur();
|
||||
input.focus();
|
||||
}, 550);
|
||||
}
|
||||
}
|
||||
});
|
||||
bodyObserver.observe(document.body, { childList: true, subtree: true });
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in New Issue