yummy-guide-generic-administrate 0.6.1 → 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 +4 -4
- data/README.md +9 -2
- data/app/assets/javascripts/yummy_guide_administrate/resizable_navigation.js +242 -0
- data/app/assets/stylesheets/yummy_guide_administrate/_resizable_navigation.scss +156 -0
- data/app/assets/stylesheets/yummy_guide_administrate/components.scss +2 -0
- data/lib/yummy_guide/administrate/engine.rb +1 -0
- data/lib/yummy_guide/administrate/filters.rb +33 -0
- data/lib/yummy_guide/administrate/version.rb +1 -1
- data/spec/yummy_guide/administrate/filter_controls_helper_spec.rb +32 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cef007572ee8fc90b0579c0e6261263bb568d192702eb92ee418976a06404ffc
|
|
4
|
+
data.tar.gz: be42920844cf830f365d2297e21605ad5191257d5a3f37ba2dd9f364ae16c768
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f9203647f13a0974af5bd4b0de54ff23c2a8991ce0eb3e065a3c0d561b1f88898c9bc470642c294bf786f5250eab2f9d5780c91b35d3f6b3cc1da9f36e2a305
|
|
7
|
+
data.tar.gz: b5d15a4f6ca3e708542fab9b1ce320ee76e45f3774bf775394cbff9548f4aca75f945259ef5e1a4c703a5ac5a3baa604a6933a6171868e83633f203698119866
|
data/README.md
CHANGED
|
@@ -355,13 +355,14 @@ dashboard の `FILTER_PATH` / `FILTER_CLEAR_PATH` より優先されます。
|
|
|
355
355
|
) %>
|
|
356
356
|
```
|
|
357
357
|
|
|
358
|
-
標準 Field は `Text`, `Select`, `Checkbox`, `RadioGroup`, `
|
|
359
|
-
`DateRange`, `DatetimeRange`, `DatetimeLocalRange`, `Custom` です。
|
|
358
|
+
標準 Field は `Text`, `Select`, `Checkbox`, `RadioGroup`, `BooleanRadioGroup`,
|
|
359
|
+
`CheckboxGroup`, `DateRange`, `DatetimeRange`, `DatetimeLocalRange`, `Custom` です。
|
|
360
360
|
主な option は次のとおりです。
|
|
361
361
|
|
|
362
362
|
- 共通: `label`, `default`, `if`, `class`, `id`, `placeholder`, `inputmode`, `pattern`
|
|
363
363
|
- 選択系: `collection` または `options`, `select_options`
|
|
364
364
|
- checkbox: `checked_value`, `unchecked_value`
|
|
365
|
+
- boolean radio group: `unspecified_label`, `true_label`, `false_label`
|
|
365
366
|
- checkbox group: `group`
|
|
366
367
|
- range: `from`, `to`, `from_default`, `to_default`, `css_class`
|
|
367
368
|
|
|
@@ -379,6 +380,12 @@ FILTER_ATTRIBUTES = {
|
|
|
379
380
|
label: "Status",
|
|
380
381
|
collection: ->(_view, locals) { locals[:status_collection] },
|
|
381
382
|
select_options: { include_blank: true }
|
|
383
|
+
),
|
|
384
|
+
visible: YummyGuide::Administrate::Filters::BooleanRadioGroup.with_options(
|
|
385
|
+
label: "Visible",
|
|
386
|
+
unspecified_label: "-",
|
|
387
|
+
true_label: "Visible",
|
|
388
|
+
false_label: "Hidden"
|
|
382
389
|
)
|
|
383
390
|
}.freeze
|
|
384
391
|
```
|
|
@@ -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
|
+
}
|
|
@@ -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
|
|
@@ -152,6 +152,39 @@ module YummyGuide
|
|
|
152
152
|
end
|
|
153
153
|
end
|
|
154
154
|
|
|
155
|
+
class BooleanRadioGroup < Base
|
|
156
|
+
protected
|
|
157
|
+
|
|
158
|
+
def input_cell(view_context, _form, scope, current_values, locals)
|
|
159
|
+
selected = current_value(current_values).to_s
|
|
160
|
+
controls = boolean_options(view_context, locals).map do |label, value|
|
|
161
|
+
value_string = value.to_s
|
|
162
|
+
id = "#{scope}_#{name}_#{value_string.presence || "unspecified"}".parameterize(separator: "_")
|
|
163
|
+
|
|
164
|
+
view_context.content_tag(:label, style: "display: inline-flex; align-items: center; gap: 6px;") do
|
|
165
|
+
view_context.safe_join([
|
|
166
|
+
view_context.radio_button_tag("#{scope}[#{name}]", value, selected == value_string, id: id),
|
|
167
|
+
view_context.content_tag(:span, label)
|
|
168
|
+
])
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
view_context.content_tag(:td) do
|
|
173
|
+
view_context.content_tag(:div, view_context.safe_join(controls), style: "display: flex; align-items: center; gap: 12px; flex-wrap: wrap;")
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
def boolean_options(view_context, locals)
|
|
180
|
+
[
|
|
181
|
+
[evaluate_option(options.fetch(:unspecified_label, "Unspecified"), view_context, locals), ""],
|
|
182
|
+
[evaluate_option(options.fetch(:true_label, "true"), view_context, locals), "true"],
|
|
183
|
+
[evaluate_option(options.fetch(:false_label, "false"), view_context, locals), "false"]
|
|
184
|
+
]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
155
188
|
class CheckboxGroup < Base
|
|
156
189
|
protected
|
|
157
190
|
|
|
@@ -49,6 +49,38 @@ RSpec.describe YummyGuide::Administrate::FilterControlsHelper do
|
|
|
49
49
|
expect(document.at_css('select[name="search_options[status]"] option[selected]')["value"]).to eq("closed")
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# boolean radio group が未指定・true・falseを横並びで描画し、ラベルと選択状態を反映することを確認する
|
|
53
|
+
it "renders boolean radio group controls with custom labels" do
|
|
54
|
+
dashboard = Class.new
|
|
55
|
+
dashboard.const_set(
|
|
56
|
+
:FILTER_ATTRIBUTES,
|
|
57
|
+
{
|
|
58
|
+
visible: YummyGuide::Administrate::Filters::BooleanRadioGroup.with_options(
|
|
59
|
+
label: "Visible",
|
|
60
|
+
unspecified_label: "-",
|
|
61
|
+
true_label: "Visible",
|
|
62
|
+
false_label: "Hidden"
|
|
63
|
+
)
|
|
64
|
+
}.freeze
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
html = helper_host.admin_filter_controls(
|
|
68
|
+
dashboard: dashboard,
|
|
69
|
+
path: "/admin/resources",
|
|
70
|
+
search_options: { visible: "false" }
|
|
71
|
+
)
|
|
72
|
+
document = fragment(html)
|
|
73
|
+
|
|
74
|
+
wrapper = document.at_css('input[name="search_options[visible]"]').ancestors("div").first
|
|
75
|
+
|
|
76
|
+
expect(document.at_css('input[type="radio"][name="search_options[visible]"][value=""]')).to be_present
|
|
77
|
+
expect(document.at_css('input[type="radio"][name="search_options[visible]"][value="true"]')).to be_present
|
|
78
|
+
expect(document.at_css('input[type="radio"][name="search_options[visible]"][value="false"]')).to be_present
|
|
79
|
+
expect(document.at_css('input[type="radio"][name="search_options[visible]"][value="false"]')["checked"]).to eq("checked")
|
|
80
|
+
expect(wrapper["style"]).to include("display: flex")
|
|
81
|
+
expect(document.text).to include("-", "Visible", "Hidden")
|
|
82
|
+
end
|
|
83
|
+
|
|
52
84
|
# dashboard に定義した FILTER_PATH をフィルター送信先として利用できることを確認する
|
|
53
85
|
it "uses dashboard filter path when no explicit path is passed" do
|
|
54
86
|
dashboard = Class.new
|
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.
|
|
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-
|
|
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
|