Skip to content

Commit 6f79bb7

Browse files
Animate sub navigation items (#93)
* Suppress chevron transition when restoring navigation section state on load Restored open/closed state was animating the section chevron on page load. Apply a temporary --no-transition modifier while setting the initial aria-expanded state, then remove it after two animation frames so the chevron renders in position without animating. * Document intentional transition-none on collapsed chevron
1 parent cbc6736 commit 6f79bb7

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

app/assets/tailwind/layered_ui/engine.css

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,8 +747,16 @@
747747
mask-size: contain;
748748
}
749749

750+
/* transition-none is intentional: the after-change style determines the
751+
transition, so this gives "animate open, snap shut" - opening matches the
752+
base rule (animates), closing matches this rule (snaps instantly). */
750753
.l-ui-navigation__section-toggle[aria-expanded="false"] .l-ui-navigation__section-chevron {
751-
@apply -rotate-90;
754+
@apply -rotate-90
755+
transition-none;
756+
}
757+
758+
.l-ui-navigation__section-toggle--no-transition .l-ui-navigation__section-chevron {
759+
@apply transition-none;
752760
}
753761

754762
.l-ui-navigation__section-items {
@@ -771,6 +779,22 @@
771779
gap-0.5 px-3 py-3;
772780
}
773781

782+
@keyframes l-ui-navigation-slide-down {
783+
from {
784+
opacity: 0;
785+
transform: translateY(-0.375rem);
786+
}
787+
788+
to {
789+
opacity: 1;
790+
transform: translateY(0);
791+
}
792+
}
793+
794+
.l-ui-navigation__section-items--opening > li {
795+
animation: l-ui-navigation-slide-down 0.2s ease-out both;
796+
}
797+
774798
.l-ui-navigation__user {
775799
@apply shrink-0
776800
p-4;

app/javascript/layered_ui/controllers/l_ui/navigation_section_controller.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,29 @@ export default class extends Controller {
2121
if (stored === null) return
2222

2323
const isOpen = stored === "true"
24-
this.toggleTarget.setAttribute("aria-expanded", isOpen ? "true" : "false")
24+
// Apply the restored state without animating the chevron on load; it should
25+
// simply render in the correct position.
26+
const toggle = this.toggleTarget
27+
toggle.classList.add("l-ui-navigation__section-toggle--no-transition")
28+
toggle.setAttribute("aria-expanded", isOpen ? "true" : "false")
2529
if (isOpen) {
2630
this.panelTarget.removeAttribute("hidden")
2731
} else {
2832
this.panelTarget.setAttribute("hidden", "")
2933
}
34+
requestAnimationFrame(() => {
35+
requestAnimationFrame(() => {
36+
toggle.classList.remove("l-ui-navigation__section-toggle--no-transition")
37+
})
38+
})
3039
}
3140

3241
toggle() {
3342
const isOpen = this.toggleTarget.getAttribute("aria-expanded") !== "true"
3443
this.toggleTarget.setAttribute("aria-expanded", isOpen ? "true" : "false")
3544
if (isOpen) {
3645
this.panelTarget.removeAttribute("hidden")
46+
this.#animateOpen()
3747
} else {
3848
this.panelTarget.setAttribute("hidden", "")
3949
}
@@ -45,4 +55,14 @@ export default class extends Controller {
4555
// ignore
4656
}
4757
}
58+
59+
// Slide the items in only on user-initiated open, not on initial page load.
60+
#animateOpen() {
61+
const panel = this.panelTarget
62+
const CLASS = "l-ui-navigation__section-items--opening"
63+
panel.classList.add(CLASS)
64+
panel.addEventListener("animationend", () => {
65+
panel.classList.remove(CLASS)
66+
}, { once: true })
67+
}
4868
}

0 commit comments

Comments
 (0)