yummy-guide-generic-administrate 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19e487e3834d936ad510df92a00a47f99447a31efd61145fd31798b5a44cbaec
4
- data.tar.gz: ec73a8e829f7533ac79ebe3d4cca687b0d519c6e048940e2978b3ad25d44f0cc
3
+ metadata.gz: cef007572ee8fc90b0579c0e6261263bb568d192702eb92ee418976a06404ffc
4
+ data.tar.gz: be42920844cf830f365d2297e21605ad5191257d5a3f37ba2dd9f364ae16c768
5
5
  SHA512:
6
- metadata.gz: 9d3da1b8ec25f8a3b0e4c01ef625f35a9e2d369c8a2656cb98f0813ec6ffe6343c375cfa91d42038c4d3527eafb3b61cdff8446cb508124a68c585a80fd5f834
7
- data.tar.gz: d590ff5f0b427417f86a5951bdb514ab16ee27659ddccbfd22b84737a4f34ce7b78964ae5dc4bd10b4ba075df6363c634f87f274b2489945589fb1df44100561
6
+ metadata.gz: 1f9203647f13a0974af5bd4b0de54ff23c2a8991ce0eb3e065a3c0d561b1f88898c9bc470642c294bf786f5250eab2f9d5780c91b35d3f6b3cc1da9f36e2a305
7
+ data.tar.gz: b5d15a4f6ca3e708542fab9b1ce320ee76e45f3774bf775394cbff9548f4aca75f945259ef5e1a4c703a5ac5a3baa604a6933a6171868e83633f203698119866
@@ -0,0 +1,242 @@
1
+ (function() {
2
+ var STORAGE_KEY = "yummyGuideAdministrate.navigationWidthPx";
3
+ var LEGACY_STORAGE_KEYS = ["wowTokyo.adminNavigationWidthPx"];
4
+ var WIDTH_VARIABLE = "--admin-navigation-width";
5
+ var NAVIGATION_SELECTOR = ".app-container > .navigation, body.admin-body > aside";
6
+ var HANDLE_CLASS = "admin-navigation-resize-handle";
7
+ var SCROLL_AREA_CLASS = "admin-navigation-scroll-area";
8
+ var TOOLTIP_CLASS = "admin-navigation-tooltip";
9
+ var TOOLTIP_VISIBLE_CLASS = "admin-navigation-tooltip--visible";
10
+ var RESIZING_BODY_CLASS = "admin-navigation-is-resizing";
11
+ var RESIZING_NAVIGATION_CLASS = "admin-navigation--resizing";
12
+ var DESKTOP_MEDIA_QUERY = "(min-width: 768px)";
13
+ var MIN_WIDTH_PX = 25;
14
+ var MAX_WIDTH_PX = 250;
15
+
16
+ function ready(callback) {
17
+ if (document.readyState === "loading") {
18
+ document.addEventListener("DOMContentLoaded", callback, { once: true });
19
+ return;
20
+ }
21
+
22
+ callback();
23
+ }
24
+
25
+ function toArray(nodeList) {
26
+ return Array.prototype.slice.call(nodeList || []);
27
+ }
28
+
29
+ function isDesktop() {
30
+ return window.matchMedia(DESKTOP_MEDIA_QUERY).matches;
31
+ }
32
+
33
+ function parseStoredWidth(value) {
34
+ if (!value) return null;
35
+
36
+ var width = parseFloat(value);
37
+ return Number.isFinite(width) ? width : null;
38
+ }
39
+
40
+ function storedWidthForKey(key) {
41
+ try {
42
+ return parseStoredWidth(window.localStorage.getItem(key));
43
+ } catch (_error) {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function readStoredWidth() {
49
+ var width = storedWidthForKey(STORAGE_KEY);
50
+ if (width !== null) return width;
51
+
52
+ for (var index = 0; index < LEGACY_STORAGE_KEYS.length; index += 1) {
53
+ width = storedWidthForKey(LEGACY_STORAGE_KEYS[index]);
54
+ if (width !== null) return width;
55
+ }
56
+
57
+ return null;
58
+ }
59
+
60
+ function saveWidth(width) {
61
+ try {
62
+ window.localStorage.setItem(STORAGE_KEY, Math.round(width) + "px");
63
+ } catch (_error) {
64
+ // localStorage may be unavailable in private browsing or restricted contexts.
65
+ }
66
+ }
67
+
68
+ function clampWidth(width) {
69
+ return Math.min(Math.max(width, MIN_WIDTH_PX), MAX_WIDTH_PX);
70
+ }
71
+
72
+ function applyWidth(width, body) {
73
+ var clampedWidth = clampWidth(width);
74
+ body.style.setProperty(WIDTH_VARIABLE, Math.round(clampedWidth) + "px");
75
+
76
+ return clampedWidth;
77
+ }
78
+
79
+ function applyStoredWidth(body) {
80
+ var storedWidth = readStoredWidth();
81
+ if (storedWidth === null) return null;
82
+
83
+ return applyWidth(storedWidth, body);
84
+ }
85
+
86
+ function resizeWidthFromPointer(event, body, navigation) {
87
+ var navigationLeft = navigation.getBoundingClientRect().left;
88
+ return applyWidth(event.clientX - navigationLeft, body);
89
+ }
90
+
91
+ function childWithClass(element, className) {
92
+ return toArray(element.children).find(function(child) {
93
+ return child.classList.contains(className);
94
+ });
95
+ }
96
+
97
+ function setupNavigationScrollArea(navigation) {
98
+ var existingScrollArea = childWithClass(navigation, SCROLL_AREA_CLASS);
99
+ if (existingScrollArea) return existingScrollArea;
100
+
101
+ var scrollArea = document.createElement("div");
102
+ var handle = childWithClass(navigation, HANDLE_CLASS);
103
+
104
+ scrollArea.className = SCROLL_AREA_CLASS;
105
+ toArray(navigation.childNodes).forEach(function(node) {
106
+ if (node === handle) return;
107
+ scrollArea.appendChild(node);
108
+ });
109
+ navigation.insertBefore(scrollArea, handle || null);
110
+
111
+ return scrollArea;
112
+ }
113
+
114
+ function setupNavigationTooltips(container) {
115
+ var tooltipTargets = toArray(container.querySelectorAll("a, button"));
116
+ if (tooltipTargets.length === 0) return;
117
+
118
+ var tooltip = document.createElement("div");
119
+ var activeTarget = null;
120
+
121
+ tooltip.className = TOOLTIP_CLASS;
122
+ tooltip.setAttribute("role", "tooltip");
123
+ document.body.appendChild(tooltip);
124
+
125
+ function hideTooltip() {
126
+ tooltip.classList.remove(TOOLTIP_VISIBLE_CLASS);
127
+ activeTarget = null;
128
+ }
129
+
130
+ function positionTooltip() {
131
+ if (!activeTarget || !isDesktop()) {
132
+ hideTooltip();
133
+ return;
134
+ }
135
+
136
+ var targetRect = activeTarget.getBoundingClientRect();
137
+ var tooltipRect = tooltip.getBoundingClientRect();
138
+ var top = Math.min(
139
+ Math.max(targetRect.top + (targetRect.height / 2) - (tooltipRect.height / 2), 8),
140
+ window.innerHeight - tooltipRect.height - 8
141
+ );
142
+ var left = Math.min(targetRect.right + 8, window.innerWidth - tooltipRect.width - 8);
143
+
144
+ tooltip.style.left = Math.max(left, 8) + "px";
145
+ tooltip.style.top = top + "px";
146
+ }
147
+
148
+ function showTooltip(target) {
149
+ var label = target.textContent.trim();
150
+ if (!label || !isDesktop()) return;
151
+
152
+ activeTarget = target;
153
+ tooltip.textContent = label;
154
+ tooltip.classList.add(TOOLTIP_VISIBLE_CLASS);
155
+ positionTooltip();
156
+ }
157
+
158
+ tooltipTargets.forEach(function(target) {
159
+ target.addEventListener("mouseenter", function() {
160
+ showTooltip(target);
161
+ });
162
+ target.addEventListener("mouseleave", hideTooltip);
163
+ target.addEventListener("focus", function() {
164
+ showTooltip(target);
165
+ });
166
+ target.addEventListener("blur", hideTooltip);
167
+ });
168
+
169
+ window.addEventListener("resize", positionTooltip);
170
+ window.addEventListener("scroll", positionTooltip, true);
171
+ }
172
+
173
+ function addResizeHandle(body, navigation) {
174
+ if (navigation.querySelector("." + HANDLE_CLASS)) return;
175
+
176
+ var handle = document.createElement("div");
177
+ var latestWidth = null;
178
+
179
+ handle.className = HANDLE_CLASS;
180
+ handle.setAttribute("role", "separator");
181
+ handle.setAttribute("aria-orientation", "vertical");
182
+ handle.setAttribute("aria-label", "Resize navigation");
183
+ navigation.appendChild(handle);
184
+
185
+ handle.addEventListener("pointerdown", function(event) {
186
+ if (!isDesktop() || (event.pointerType === "mouse" && event.button !== 0)) return;
187
+
188
+ event.preventDefault();
189
+ latestWidth = navigation.getBoundingClientRect().width;
190
+ body.classList.add(RESIZING_BODY_CLASS);
191
+ navigation.classList.add(RESIZING_NAVIGATION_CLASS);
192
+ handle.setPointerCapture(event.pointerId);
193
+
194
+ function onPointerMove(moveEvent) {
195
+ latestWidth = resizeWidthFromPointer(moveEvent, body, navigation);
196
+ }
197
+
198
+ function stopResize() {
199
+ document.removeEventListener("pointermove", onPointerMove);
200
+ document.removeEventListener("pointerup", stopResize);
201
+ document.removeEventListener("pointercancel", stopResize);
202
+ body.classList.remove(RESIZING_BODY_CLASS);
203
+ navigation.classList.remove(RESIZING_NAVIGATION_CLASS);
204
+
205
+ if (latestWidth !== null) saveWidth(latestWidth);
206
+ latestWidth = null;
207
+ }
208
+
209
+ document.addEventListener("pointermove", onPointerMove);
210
+ document.addEventListener("pointerup", stopResize);
211
+ document.addEventListener("pointercancel", stopResize);
212
+ });
213
+ }
214
+
215
+ function setupResizableNavigation() {
216
+ var body = document.querySelector("body.admin-body") || document.body;
217
+ var navigation = document.querySelector(NAVIGATION_SELECTOR);
218
+ var scrollArea = null;
219
+ var resizeAnimationFrame = null;
220
+
221
+ if (!body || !navigation || navigation.dataset.resizableNavigationInitialized === "true") return;
222
+
223
+ navigation.dataset.resizableNavigationInitialized = "true";
224
+ applyStoredWidth(body);
225
+ scrollArea = setupNavigationScrollArea(navigation);
226
+ setupNavigationTooltips(scrollArea);
227
+ addResizeHandle(body, navigation);
228
+
229
+ window.addEventListener("resize", function() {
230
+ if (resizeAnimationFrame) {
231
+ window.cancelAnimationFrame(resizeAnimationFrame);
232
+ }
233
+
234
+ resizeAnimationFrame = window.requestAnimationFrame(function() {
235
+ applyStoredWidth(body);
236
+ resizeAnimationFrame = null;
237
+ });
238
+ });
239
+ }
240
+
241
+ ready(setupResizableNavigation);
242
+ })();
@@ -0,0 +1,156 @@
1
+ @media screen and (min-width: 768px) {
2
+ .app-container > .navigation,
3
+ body.admin-body > aside {
4
+ --admin-navigation-default-width: 240px;
5
+ align-self: flex-start;
6
+ box-sizing: border-box;
7
+ flex: 0 0 clamp(25px, var(--admin-navigation-width, var(--admin-navigation-default-width)), 250px);
8
+ max-height: calc(100vh - 2rem);
9
+ min-width: 25px;
10
+ max-width: 250px;
11
+ overflow: visible;
12
+ position: sticky;
13
+ top: 1rem;
14
+ width: clamp(25px, var(--admin-navigation-width, var(--admin-navigation-default-width)), 250px);
15
+ }
16
+
17
+ body.admin-body > aside:not(.navigation) {
18
+ --admin-navigation-default-width: 13rem;
19
+ }
20
+
21
+ .app-container > .navigation {
22
+ max-height: calc(100vh - 3em);
23
+ max-height: calc(100dvh - 3em);
24
+ top: 1.5em;
25
+ }
26
+
27
+ .app-container > .main-content,
28
+ body.admin-body > main.admin-main {
29
+ flex: 1 1 auto;
30
+ min-width: 0;
31
+ position: relative;
32
+ z-index: 1;
33
+ width: auto;
34
+ }
35
+
36
+ body.admin-body > main.main-content {
37
+ width: auto;
38
+ }
39
+
40
+ .admin-navigation-resize-handle {
41
+ bottom: 0;
42
+ cursor: col-resize;
43
+ position: absolute;
44
+ right: 0;
45
+ top: 0;
46
+ touch-action: none;
47
+ width: 8px;
48
+ z-index: 2;
49
+
50
+ &::after {
51
+ background-color: transparent;
52
+ bottom: 0;
53
+ content: "";
54
+ left: 3px;
55
+ position: absolute;
56
+ top: 0;
57
+ transition: background-color 0.15s ease;
58
+ width: 2px;
59
+ }
60
+
61
+ &:hover::after {
62
+ background-color: #9aa8ba;
63
+ }
64
+ }
65
+
66
+ .admin-navigation-scroll-area {
67
+ max-height: inherit;
68
+ overflow-x: hidden;
69
+ overflow-y: auto;
70
+ overscroll-behavior: contain;
71
+ }
72
+
73
+ .admin-navigation--resizing .admin-navigation-resize-handle::after {
74
+ background-color: #5d7596;
75
+ }
76
+
77
+ .admin-navigation-scroll-area .menubar,
78
+ .admin-navigation-scroll-area .menubar a,
79
+ .admin-navigation-scroll-area .menubar__group-label,
80
+ .admin-navigation-scroll-area .navigation__link,
81
+ .admin-navigation-scroll-area .navigation__link__secondary {
82
+ white-space: nowrap;
83
+ }
84
+
85
+ .admin-navigation-scroll-area .menubar {
86
+ margin-right: 0;
87
+ }
88
+
89
+ .admin-navigation-scroll-area .menubar li.menubar__item {
90
+ padding-left: 0;
91
+ }
92
+
93
+ .admin-navigation-scroll-area .menubar li a,
94
+ .admin-navigation-scroll-area .navigation__link {
95
+ box-sizing: border-box;
96
+ margin-left: 0;
97
+ margin-right: 0;
98
+ overflow: hidden;
99
+ width: 100%;
100
+ }
101
+
102
+ .admin-navigation-scroll-area .menubar li.menubar__item a {
103
+ padding-left: 36px;
104
+ }
105
+
106
+ .admin-navigation-scroll-area .button_to {
107
+ margin: 0;
108
+ }
109
+
110
+ .admin-navigation-scroll-area .btn_public,
111
+ .admin-navigation-scroll-area .btn_logout {
112
+ box-sizing: border-box;
113
+ margin-left: 0;
114
+ margin-right: 0;
115
+ min-width: 0;
116
+ overflow: hidden;
117
+ text-overflow: clip;
118
+ white-space: nowrap;
119
+ width: 100%;
120
+ }
121
+
122
+ .admin-navigation-tooltip {
123
+ background-color: #1f2933;
124
+ border-radius: 4px;
125
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
126
+ color: #fff;
127
+ font-size: 0.85rem;
128
+ line-height: 1.4;
129
+ max-width: 260px;
130
+ opacity: 0;
131
+ padding: 6px 8px;
132
+ pointer-events: none;
133
+ position: fixed;
134
+ transform: translateX(-2px);
135
+ transition: opacity 0.12s ease, transform 0.12s ease;
136
+ white-space: nowrap;
137
+ z-index: 1000;
138
+ }
139
+
140
+ .admin-navigation-tooltip--visible {
141
+ opacity: 1;
142
+ transform: translateX(0);
143
+ }
144
+
145
+ body.admin-navigation-is-resizing,
146
+ body.admin-navigation-is-resizing * {
147
+ cursor: col-resize !important;
148
+ user-select: none;
149
+ }
150
+ }
151
+
152
+ @media screen and (max-width: 767px) {
153
+ .admin-navigation-resize-handle {
154
+ display: none;
155
+ }
156
+ }
@@ -679,3 +679,5 @@ table[data-fixed-columns-count] [data-fixed-header-column-min-width] {
679
679
  grid-template-columns: 1fr;
680
680
  }
681
681
  }
682
+
683
+ @import "resizable_navigation";
@@ -14,6 +14,7 @@ module YummyGuide
14
14
  yummy_guide_administrate/filter_controls.js
15
15
  yummy_guide_administrate/filter_form.js
16
16
  yummy_guide_administrate/sticky_left_columns.js
17
+ yummy_guide_administrate/resizable_navigation.js
17
18
  yummy_guide_administrate/sticky_table_headers.js
18
19
  ]
19
20
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module YummyGuide
4
4
  module Administrate
5
- VERSION = "0.6.2"
5
+ VERSION = "0.7.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yummy-guide-generic-administrate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - akatsuki-kk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-27 00:00:00.000000000 Z
11
+ date: 2026-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: administrate
@@ -95,10 +95,12 @@ files:
95
95
  - app/assets/javascripts/yummy_guide_administrate/filter_controls.js
96
96
  - app/assets/javascripts/yummy_guide_administrate/filter_form.js
97
97
  - app/assets/javascripts/yummy_guide_administrate/fixed_submit_actions.js
98
+ - app/assets/javascripts/yummy_guide_administrate/resizable_navigation.js
98
99
  - app/assets/javascripts/yummy_guide_administrate/sticky_left_columns.js
99
100
  - app/assets/javascripts/yummy_guide_administrate/sticky_table_headers.js
100
101
  - app/assets/stylesheets/yummy_guide_administrate/_datetime_input.scss
101
102
  - app/assets/stylesheets/yummy_guide_administrate/_fixed_submit_actions.scss
103
+ - app/assets/stylesheets/yummy_guide_administrate/_resizable_navigation.scss
102
104
  - app/assets/stylesheets/yummy_guide_administrate/components.scss
103
105
  - app/controllers/concerns/yummy_guide/administrate/datetime_filter_parameters.rb
104
106
  - app/controllers/concerns/yummy_guide/administrate/default_sorting.rb