PageSourceSearch

https://railsblocks.com/assets/controllers/sidebar_controller-2228…0cdd21077b4d025a78a82b1bf83013e38e506c3b.js

js railsblocks.com collected 2026-09-25 20:24:12 UTC 5,184 bytes, 154 lines download raw bytes

1import { Controller } from "@hotwired/stimulus";
2
3// Connects to data-controller="sidebar"
4export default class extends Controller {
5  static targets = [
6    "desktopSidebar",
7    "mobileSidebar",
8    "contentTemplate",
9    "sharedContent",
10    "desktopContent",
11    "mobileBackdrop",
12    "mobilePanel",
13  ];
14  static values = {
15    storageKey: { type: String, default: "sidebarOpen" },
16  };
17
18  connect() {
19    // Bind the handler once so we can properly remove it later
20    this.boundHandleToggle = this.handleToggle.bind(this);
21
22    // Clone the sidebar content for both mobile and desktop
23    if (this.hasContentTemplateTarget) {
24      // Clone content for desktop (only if not already cloned)
25      // Check if nav element exists (the actual cloned content)
26      if (this.hasDesktopContentTarget && !this.desktopContentTarget.querySelector("nav")) {
27        const desktopClone = this.contentTemplateTarget.content.cloneNode(true);
28        this.desktopContentTarget.appendChild(desktopClone);
29      }
30
31      // Clone content for mobile (only if not already cloned)
32      if (this.hasSharedContentTarget && !this.sharedContentTarget.querySelector("nav")) {
33        const mobileClone = this.contentTemplateTarget.content.cloneNode(true);
34        this.sharedContentTarget.appendChild(mobileClone);
35
36        // Update the close button in mobile view to show X icon and have mobile close action
37        const mobileNav = this.sharedContentTarget.querySelector("nav");
38        if (mobileNav) {
39          const closeButton = mobileNav.querySelector('[data-action*="sidebar#close"]');
40          if (closeButton) {
41            closeButton.setAttribute("data-action", "click->sidebar#closeMobile");
42            closeButton.classList.remove("cursor-w-resize");
43            closeButton.setAttribute("aria-label", "Close sidebar");
44
45            // Replace the collapse icon with an X icon
46            const svg = closeButton.querySelector("svg");
47            if (svg) {
48              svg.innerHTML =
49                '<g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="13.25" y1="4.75" x2="4.75" y2="13.25"></line><line x1="13.25" y1="13.25" x2="4.75" y2="4.75"></line></g>';
50            }
51          }
52        }
53      }
54    }
55
56    if (this.hasDesktopSidebarTarget) {
57      // Temporarily disable transitions to prevent animation on page load
58      this.desktopSidebarTarget.style.transition = "none";
59
60      // Restore the saved state from localStorage, default to open if no saved state
61      const savedState = localStorage.getItem(this.storageKeyValue);
62
63      if (savedState !== null) {
64        this.desktopSidebarTarget.open = savedState === "true";
65      } else {
66        // Default to open for first-time visitors
67        this.desktopSidebarTarget.open = true;
68      }
69
70      // Re-enable transitions after a brief delay
71      requestAnimationFrame(() => {
72        requestAnimationFrame(() => {
73          this.desktopSidebarTarget.style.transition = "";
74        });
75      });
76
77      // Listen for toggle events to save the state
78      this.desktopSidebarTarget.addEventListener("toggle", this.boundHandleToggle);
79    }
80  }
81
82  disconnect() {
83    if (this.hasDesktopSidebarTarget && this.boundHandleToggle) {
84      this.desktopSidebarTarget.removeEventListener("toggle", this.boundHandleToggle);
85    }
86  }
87
88  handleToggle(event) {
89    // Save the current state to localStorage
90    localStorage.setItem(this.storageKeyValue, this.desktopSidebarTarget.open.toString());
91  }
92
93  open() {
94    if (this.hasDesktopSidebarTarget) {
95      this.desktopSidebarTarget.open = true;
96    }
97  }
98
99  close() {
100    if (this.hasDesktopSidebarTarget) {
101      this.desktopSidebarTarget.open = false;
102    }
103  }
104
105  toggle() {
106    if (this.hasDesktopSidebarTarget) {
107      this.desktopSidebarTarget.open = !this.desktopSidebarTarget.open;
108    }
109  }
110
111  openMobile() {
112    if (this.hasMobileSidebarTarget) {
113      // Set initial hidden states
114      if (this.hasMobileBackdropTarget) {
115        this.mobileBackdropTarget.style.opacity = "0";
116      }
117      if (this.hasMobilePanelTarget) {
118        this.mobilePanelTarget.style.transform = "translateX(-100%)";
119      }
120
121      // Remove hidden class
122      this.mobileSidebarTarget.classList.remove("hidden");
123
124      // Trigger transition on next frame
125      requestAnimationFrame(() => {
126        if (this.hasMobileBackdropTarget) {
127          this.mobileBackdropTarget.style.opacity = "1";
128        }
129        if (this.hasMobilePanelTarget) {
130          this.mobilePanelTarget.style.transform = "translateX(0)";
131        }
132      });
133    }
134  }
135
136  closeMobile() {
137    if (this.hasMobileSidebarTarget) {
138      // Trigger closing transition
139      if (this.hasMobileBackdropTarget) {
140        this.mobileBackdropTarget.style.opacity = "0";
141      }
142      if (this.hasMobilePanelTarget) {
143        this.mobilePanelTarget.style.transform = "translateX(-100%)";
144      }
145
146      // Wait for transition to complete before hiding
147      setTimeout(() => {
148        if (this.hasMobileSidebarTarget) {
149          this.mobileSidebarTarget.classList.add("hidden");
150        }
151      }, 300); // Match the transition duration
152    }
153  }
154};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.