spina 2.18.0 → 2.19.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.
Potentially problematic release.
This version of spina might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/javascripts/spina/controllers/data_binding_controller.js +1 -0
- data/app/assets/javascripts/spina/controllers/page_select_controller.js +2 -37
- data/app/assets/javascripts/spina/controllers/reveal_controller.js +1 -1
- data/app/assets/javascripts/spina/controllers/select_controller.js +44 -0
- data/app/assets/javascripts/spina/libraries/stimulus-data-bindings@1.3.2.js +234 -0
- data/app/assets/javascripts/spina/libraries/stimulus-reveal@1.4.2.js +424 -0
- data/app/controllers/spina/admin/resource_select_options_controller.rb +20 -0
- data/app/models/spina/navigation_item.rb +5 -1
- data/app/models/spina/parts/image_variant.rb +3 -3
- data/app/models/spina/parts/page_link.rb +3 -2
- data/app/models/spina/parts/resource_link.rb +13 -0
- data/app/views/spina/admin/page_select_options/index.html.erb +3 -4
- data/app/views/spina/admin/page_select_options/show.html.erb +1 -1
- data/app/views/spina/admin/parts/page_links/_form.html.erb +17 -14
- data/app/views/spina/admin/parts/resource_links/_form.html.erb +32 -0
- data/app/views/spina/admin/resource_select_options/index.html.erb +13 -0
- data/app/views/spina/admin/resource_select_options/show.html.erb +3 -0
- data/config/locales/da.yml +418 -0
- data/config/locales/en.yml +8 -5
- data/config/routes.rb +4 -1
- data/lib/spina/engine.rb +2 -1
- data/lib/spina/theme.rb +8 -6
- data/lib/spina/version.rb +1 -1
- data/lib/tasks/tailwind.rake +12 -4
- metadata +25 -10
- data/app/assets/javascripts/spina/libraries/stimulus-reveal@1.2.4.js +0 -388
@@ -0,0 +1,424 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Stimulus controller to toggle element visibility
|
5
|
+
* @extends Controller
|
6
|
+
*/
|
7
|
+
export default class RevealController extends Controller {
|
8
|
+
static values = {
|
9
|
+
open: Boolean,
|
10
|
+
transitioning: Boolean,
|
11
|
+
targetSelector: String,
|
12
|
+
toggleKeys: String,
|
13
|
+
showKeys: String,
|
14
|
+
hideKeys: String,
|
15
|
+
away: Boolean,
|
16
|
+
debug: Boolean,
|
17
|
+
};
|
18
|
+
|
19
|
+
connect() {
|
20
|
+
this._initCloseKeypressListener();
|
21
|
+
this._initToggleKeypressListener();
|
22
|
+
this._initShowKeypressListener();
|
23
|
+
this._awayHandler = this._awayHandler.bind(this);
|
24
|
+
}
|
25
|
+
|
26
|
+
disconnect() {
|
27
|
+
this._teardown();
|
28
|
+
}
|
29
|
+
|
30
|
+
/**
|
31
|
+
* Shows elements connected to the controller.
|
32
|
+
* @param {Event} event - an event with a currentTarget DOMElement
|
33
|
+
*/
|
34
|
+
show(event) {
|
35
|
+
if (this.openValue || this.transitioningValue) return;
|
36
|
+
|
37
|
+
this._init(event, true);
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Hides elements connected to the controller.
|
42
|
+
* @param {Event} event - an event with a currentTarget DOMElement
|
43
|
+
*/
|
44
|
+
hide(event) {
|
45
|
+
if (!this.openValue || this.transitioningValue) return;
|
46
|
+
|
47
|
+
this._init(event, false);
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Toggles elements connected to the controller.
|
52
|
+
* @param {Event} event - an event with a currentTarget DOMElement
|
53
|
+
*/
|
54
|
+
toggle(event) {
|
55
|
+
if (this.transitioningValue) return;
|
56
|
+
|
57
|
+
this._init(event, !this.openValue);
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Stops event propagation of elements connected to the controller.
|
62
|
+
* @param {Event} event - an event with a currentTarget DOMElement
|
63
|
+
*/
|
64
|
+
stop(event) {
|
65
|
+
event.stopPropagation();
|
66
|
+
}
|
67
|
+
|
68
|
+
// Private methods
|
69
|
+
|
70
|
+
/**
|
71
|
+
* @private
|
72
|
+
* @param {Event} event
|
73
|
+
* @param {Event} shouldOpen
|
74
|
+
*/
|
75
|
+
async _init(event, shouldOpen) {
|
76
|
+
if (event && event.currentTarget && event.currentTarget.dataset) {
|
77
|
+
if ("revealPreventDefault" in event.currentTarget.dataset) {
|
78
|
+
event.preventDefault();
|
79
|
+
}
|
80
|
+
if ("revealStopPropagation" in event.currentTarget.dataset) {
|
81
|
+
event.stopPropagation();
|
82
|
+
}
|
83
|
+
}
|
84
|
+
// start stuff
|
85
|
+
const startSelector = `${this.selector}[data-${shouldOpen ? "enter" : "leave"
|
86
|
+
}-start]`;
|
87
|
+
const startPromises = this._didInitWithPromise(startSelector, shouldOpen);
|
88
|
+
await Promise.all(startPromises);
|
89
|
+
|
90
|
+
const defaultSelector = `${this.selector}:not([data-${shouldOpen ? "enter" : "leave"
|
91
|
+
}-start]):not([data-${shouldOpen ? "enter" : "leave"}-end])`;
|
92
|
+
const defaultPromises = this._didInitWithPromise(
|
93
|
+
defaultSelector,
|
94
|
+
shouldOpen
|
95
|
+
);
|
96
|
+
await Promise.all(defaultPromises);
|
97
|
+
|
98
|
+
// end stuff
|
99
|
+
const endSelector = `${this.selector}[data-${shouldOpen ? "enter" : "leave"
|
100
|
+
}-end]`;
|
101
|
+
const endPromises = this._didInitWithPromise(endSelector, shouldOpen);
|
102
|
+
await Promise.all(endPromises);
|
103
|
+
}
|
104
|
+
|
105
|
+
/**
|
106
|
+
* @private
|
107
|
+
*/
|
108
|
+
_teardown() {
|
109
|
+
if (this.hasAwayValue) {
|
110
|
+
document.removeEventListener("click", this._awayHandler);
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
_didInitWithPromise(selector, shouldOpen) {
|
115
|
+
this._debug("selecting", selector, this._fetchElements(selector));
|
116
|
+
return this._fetchElements(selector).map((element) => {
|
117
|
+
return this._doInitTransition(element, shouldOpen);
|
118
|
+
});
|
119
|
+
}
|
120
|
+
|
121
|
+
/**
|
122
|
+
* @private
|
123
|
+
*/
|
124
|
+
_initCloseKeypressListener() {
|
125
|
+
if (this.hasHideKeysValue) {
|
126
|
+
document.addEventListener("keydown", (event) => {
|
127
|
+
if (!this.openValue) return;
|
128
|
+
if (!this.hideKeysValue.split(",").includes(event.key.toLowerCase())) {
|
129
|
+
return;
|
130
|
+
}
|
131
|
+
|
132
|
+
event.stopPropagation();
|
133
|
+
this.toggle(event);
|
134
|
+
});
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
/**
|
139
|
+
* @private
|
140
|
+
*/
|
141
|
+
_initToggleKeypressListener() {
|
142
|
+
if (this.hasToggleKeysValue) {
|
143
|
+
document.addEventListener("keydown", (event) => {
|
144
|
+
if (
|
145
|
+
!this.toggleKeysValue.split(",").includes(event.key.toLowerCase())
|
146
|
+
) {
|
147
|
+
return;
|
148
|
+
}
|
149
|
+
|
150
|
+
event.stopPropagation();
|
151
|
+
|
152
|
+
this.toggle(event);
|
153
|
+
});
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* @private
|
159
|
+
*/
|
160
|
+
_initShowKeypressListener() {
|
161
|
+
if (this.hasShowKeysValue) {
|
162
|
+
document.addEventListener("keydown", (event) => {
|
163
|
+
if (this.openValue) return;
|
164
|
+
if (!this.showKeysValue.split(",").includes(event.key.toLowerCase())) {
|
165
|
+
return;
|
166
|
+
}
|
167
|
+
|
168
|
+
event.stopPropagation();
|
169
|
+
|
170
|
+
this.toggle(event);
|
171
|
+
});
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
/**
|
176
|
+
* @private
|
177
|
+
*/
|
178
|
+
_awayHandler(event) {
|
179
|
+
if (!this.element.contains(event.target)) {
|
180
|
+
document.removeEventListener("click", this._awayHandler);
|
181
|
+
this.hide(event);
|
182
|
+
}
|
183
|
+
return true;
|
184
|
+
}
|
185
|
+
|
186
|
+
/**
|
187
|
+
* @private
|
188
|
+
* @param {DOMElement} target
|
189
|
+
* @param {boolean} openState
|
190
|
+
*/
|
191
|
+
_doInitTransition(target, openState) {
|
192
|
+
this._debug("init transition", `${openState ? "open" : "closed"}`, target);
|
193
|
+
this._debug(
|
194
|
+
"dispatching event",
|
195
|
+
`reveal:${openState ? "show" : "hide"}`,
|
196
|
+
target
|
197
|
+
);
|
198
|
+
target.dispatchEvent(
|
199
|
+
new Event(`reveal:${openState ? "show" : "hide"}`, {
|
200
|
+
bubbles: true,
|
201
|
+
cancelable: false,
|
202
|
+
})
|
203
|
+
);
|
204
|
+
|
205
|
+
return new Promise((resolve, reject) => {
|
206
|
+
if (
|
207
|
+
"transition" in target.dataset &&
|
208
|
+
this.element.offsetParent !== null
|
209
|
+
) {
|
210
|
+
requestAnimationFrame(() => {
|
211
|
+
this._transitionSetup(target, openState);
|
212
|
+
const _didEndTransition = this._didEndTransition.bind(this);
|
213
|
+
|
214
|
+
target.addEventListener(
|
215
|
+
"transitionend",
|
216
|
+
function _didEndTransitionHandler() {
|
217
|
+
_didEndTransition(target, openState);
|
218
|
+
target.removeEventListener(
|
219
|
+
"transitionend",
|
220
|
+
_didEndTransitionHandler
|
221
|
+
);
|
222
|
+
resolve();
|
223
|
+
}
|
224
|
+
);
|
225
|
+
|
226
|
+
requestAnimationFrame(() => {
|
227
|
+
this._doStartTransition(target, openState);
|
228
|
+
});
|
229
|
+
});
|
230
|
+
} else {
|
231
|
+
if (openState) {
|
232
|
+
this._debug(
|
233
|
+
"force hidden - init",
|
234
|
+
`${openState ? "open" : "closed"}`,
|
235
|
+
target
|
236
|
+
);
|
237
|
+
target.hidden = !target.hidden;
|
238
|
+
}
|
239
|
+
this._doCompleteTransition(target, openState);
|
240
|
+
resolve();
|
241
|
+
}
|
242
|
+
});
|
243
|
+
}
|
244
|
+
|
245
|
+
/**
|
246
|
+
* @private
|
247
|
+
* @param {DOMElement} target
|
248
|
+
*/
|
249
|
+
_doStartTransition(target, openState) {
|
250
|
+
this._debug("start transition", `${openState ? "open" : "closed"}`, target);
|
251
|
+
this.transitioningValue = true;
|
252
|
+
if (target.dataset.useTransitionClasses === "true") {
|
253
|
+
const transitionClasses = this._transitionClasses(
|
254
|
+
target,
|
255
|
+
this.transitionType
|
256
|
+
);
|
257
|
+
target.classList.add(...transitionClasses.end.split(" "));
|
258
|
+
target.classList.remove(...transitionClasses.start.split(" "));
|
259
|
+
} else {
|
260
|
+
const transitions = this._transitionDefaults(openState);
|
261
|
+
target.style.transformOrigin = transitions.origin;
|
262
|
+
target.style.transitionProperty = "opacity transform";
|
263
|
+
target.style.transitionDuration = `${transitions.duration / 1000}s`;
|
264
|
+
target.style.transitionTimingFunction = "cubic-bezier(0.4, 0.0, 0.2, 1)";
|
265
|
+
|
266
|
+
target.style.opacity = transitions.to.opacity;
|
267
|
+
target.style.transform = `scale(${transitions.to.scale / 100})`;
|
268
|
+
}
|
269
|
+
}
|
270
|
+
|
271
|
+
/**
|
272
|
+
* @private
|
273
|
+
* @param {DOMElement} target
|
274
|
+
* @param {boolean} openState
|
275
|
+
*/
|
276
|
+
_didEndTransition(target, openState) {
|
277
|
+
this._debug("end transition", `${openState ? "open" : "closed"}`, target);
|
278
|
+
if (target.dataset.useTransitionClasses === "true") {
|
279
|
+
const transitionClasses = this._transitionClasses(
|
280
|
+
target,
|
281
|
+
this.transitionType
|
282
|
+
);
|
283
|
+
target.classList.remove(...transitionClasses.before.split(" "));
|
284
|
+
} else {
|
285
|
+
target.style.opacity = target.dataset.opacityCache;
|
286
|
+
target.style.transform = target.dataset.transformCache;
|
287
|
+
target.style.transformOrigin = target.dataset.transformOriginCache;
|
288
|
+
}
|
289
|
+
this._doCompleteTransition(target, openState);
|
290
|
+
}
|
291
|
+
|
292
|
+
/**
|
293
|
+
* @private
|
294
|
+
* @param {DOMElement} target
|
295
|
+
* @param {boolean} openState
|
296
|
+
*/
|
297
|
+
_doCompleteTransition(target, openState) {
|
298
|
+
this._debug(
|
299
|
+
"complete transition",
|
300
|
+
`${openState ? "open" : "closed"}`,
|
301
|
+
target
|
302
|
+
);
|
303
|
+
this.transitioningValue = false;
|
304
|
+
|
305
|
+
if (!openState) {
|
306
|
+
this._debug(
|
307
|
+
"force hidden - complete",
|
308
|
+
`${openState ? "open" : "closed"}`,
|
309
|
+
target
|
310
|
+
);
|
311
|
+
target.hidden = !target.hidden;
|
312
|
+
}
|
313
|
+
this.openValue = openState;
|
314
|
+
|
315
|
+
this._debug(
|
316
|
+
"dispatching event",
|
317
|
+
`reveal:${openState ? "shown" : "hidden"}`,
|
318
|
+
target
|
319
|
+
);
|
320
|
+
target.dispatchEvent(
|
321
|
+
new Event(`reveal:${openState ? "shown" : "hidden"}`, {
|
322
|
+
bubbles: true,
|
323
|
+
cancelable: false,
|
324
|
+
})
|
325
|
+
);
|
326
|
+
|
327
|
+
if (this.hasAwayValue && openState) {
|
328
|
+
document.addEventListener("click", this._awayHandler);
|
329
|
+
}
|
330
|
+
|
331
|
+
this._debug("dispatching event", "reveal:complete", target);
|
332
|
+
target.dispatchEvent(
|
333
|
+
new Event("reveal:complete", { bubbles: true, cancelable: false })
|
334
|
+
);
|
335
|
+
}
|
336
|
+
|
337
|
+
/**
|
338
|
+
* @private
|
339
|
+
* @param {DOMElement} target
|
340
|
+
* @param {boolean} openState
|
341
|
+
*/
|
342
|
+
_transitionSetup(target, openState) {
|
343
|
+
this.transitionType = openState ? "transitionEnter" : "transitionLeave";
|
344
|
+
|
345
|
+
if (this.transitionType in target.dataset) {
|
346
|
+
target.dataset.useTransitionClasses = true;
|
347
|
+
const transitionClasses = this._transitionClasses(
|
348
|
+
target,
|
349
|
+
this.transitionType
|
350
|
+
);
|
351
|
+
target.classList.add(...transitionClasses.before.split(" "));
|
352
|
+
target.classList.add(...transitionClasses.start.split(" "));
|
353
|
+
} else {
|
354
|
+
target.dataset.useTransitionClasses = false;
|
355
|
+
const transitions = this._transitionDefaults(openState);
|
356
|
+
target.dataset.opacityCache = target.style.opacity;
|
357
|
+
target.dataset.transformCache = target.style.transform;
|
358
|
+
target.dataset.transformOriginCache = target.style.transformOrigin;
|
359
|
+
|
360
|
+
target.style.opacity = transitions.from.opacity;
|
361
|
+
target.style.transform = `scale(${transitions.from.scale / 100})`;
|
362
|
+
}
|
363
|
+
if (openState) {
|
364
|
+
this._debug("opening with transition", target);
|
365
|
+
target.hidden = !target.hidden;
|
366
|
+
}
|
367
|
+
}
|
368
|
+
|
369
|
+
/**
|
370
|
+
* @private
|
371
|
+
* @param {boolean} openState
|
372
|
+
*/
|
373
|
+
_transitionDefaults(openState) {
|
374
|
+
return {
|
375
|
+
duration: openState ? 200 : 150,
|
376
|
+
origin: "center",
|
377
|
+
from: {
|
378
|
+
opacity: openState ? 0 : 1,
|
379
|
+
scale: openState ? 95 : 100,
|
380
|
+
},
|
381
|
+
to: {
|
382
|
+
opacity: openState ? 1 : 0,
|
383
|
+
scale: openState ? 100 : 95,
|
384
|
+
},
|
385
|
+
};
|
386
|
+
}
|
387
|
+
|
388
|
+
/**
|
389
|
+
* @private
|
390
|
+
* @param {DOMElement} target
|
391
|
+
* @param {string} transitionType
|
392
|
+
*/
|
393
|
+
_transitionClasses(target, transitionType) {
|
394
|
+
return {
|
395
|
+
before: target.dataset[transitionType],
|
396
|
+
start: target.dataset[`${transitionType}Start`],
|
397
|
+
end: target.dataset[`${transitionType}End`],
|
398
|
+
};
|
399
|
+
}
|
400
|
+
|
401
|
+
/**
|
402
|
+
* @private
|
403
|
+
* @param {String} selector
|
404
|
+
*/
|
405
|
+
_fetchElements(selector) {
|
406
|
+
return [this.element, ...this.element.querySelectorAll(selector)].filter(
|
407
|
+
(el) => el.matches(selector)
|
408
|
+
);
|
409
|
+
}
|
410
|
+
|
411
|
+
/**
|
412
|
+
* @private
|
413
|
+
* @param {Array} args
|
414
|
+
*/
|
415
|
+
_debug(...args) {
|
416
|
+
if (this.debugValue) console.log(...args);
|
417
|
+
}
|
418
|
+
|
419
|
+
get selector() {
|
420
|
+
return this.hasTargetSelectorValue
|
421
|
+
? this.targetSelectorValue
|
422
|
+
: "[data-reveal]";
|
423
|
+
}
|
424
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Spina
|
2
|
+
module Admin
|
3
|
+
class ResourceSelectOptionsController < AdminController
|
4
|
+
|
5
|
+
def show
|
6
|
+
@resource = Resource.find(params[:id])
|
7
|
+
end
|
8
|
+
|
9
|
+
def index
|
10
|
+
end
|
11
|
+
|
12
|
+
def search
|
13
|
+
@resources ||= Resource.all
|
14
|
+
@resources = @resources.where("name ILIKE :query OR label ILIKE :query", query: "%#{params[:search]}%").order(created_at: :desc).distinct.page(params[:page]).per(20)
|
15
|
+
render :index
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -6,7 +6,11 @@ module Spina
|
|
6
6
|
# NavigationItems can be of two different kinds:
|
7
7
|
# - A link to a page
|
8
8
|
# - A link to a URL
|
9
|
-
|
9
|
+
if Rails.version >= '7.2'
|
10
|
+
enum :kind, {page: "page", url: "url"}, suffix: true
|
11
|
+
else
|
12
|
+
enum kind: {page: "page", url: "url"}, _suffix: true
|
13
|
+
end
|
10
14
|
|
11
15
|
has_ancestry
|
12
16
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Spina
|
2
2
|
module Parts
|
3
3
|
class ImageVariant
|
4
|
+
ImageVariation = Struct.new(:key)
|
5
|
+
|
4
6
|
attr_reader :blob
|
5
7
|
|
6
8
|
def initialize(image, options)
|
@@ -9,9 +11,7 @@ module Spina
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def variation
|
12
|
-
|
13
|
-
key: ActiveStorage::Variation.encode(@options)
|
14
|
-
})
|
14
|
+
ImageVariation.new(ActiveStorage::Variation.encode(@options))
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -2,11 +2,12 @@ module Spina
|
|
2
2
|
module Parts
|
3
3
|
class PageLink < Base
|
4
4
|
attr_json :page_id, :integer, default: nil
|
5
|
-
|
5
|
+
attr_json :text, :string, default: nil
|
6
|
+
|
6
7
|
attr_accessor :options
|
7
8
|
|
8
9
|
def content
|
9
|
-
Page.live.find_by(id: page_id)
|
10
|
+
::Spina::Page.live.find_by(id: page_id)
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
<%= turbo_frame_tag "page_select_options_#{params[:object_id]}" do %>
|
2
2
|
<% if params[:search].blank? %>
|
3
|
-
<button type="button" data-action="
|
3
|
+
<button type="button" data-action="select#clear reveal#hide" class="block hover:bg-gray-100 w-full text-left px-5 py-2 font-medium leading-5 text-gray-700 hover:text-gray-900 text-sm border-t border-gray-150">
|
4
4
|
–
|
5
5
|
</button>
|
6
6
|
<% end %>
|
7
|
-
|
8
7
|
<% @pages.each do |page| %>
|
9
|
-
<button type="button" data-action="
|
8
|
+
<button type="button" data-action="select#select reveal#hide" data-title="<%= page.title %>" data-id="<%= page.id %>" class="block hover:bg-gray-100 w-full text-left px-5 py-2 font-medium leading-5 text-gray-700 hover:text-gray-900 text-sm border-t border-gray-150">
|
10
9
|
<%= page.title %>
|
11
10
|
<% if page.draft? %>
|
12
11
|
<span class="font-normal text-gray-400">
|
@@ -18,4 +17,4 @@
|
|
18
17
|
</div>
|
19
18
|
</button>
|
20
19
|
<% end %>
|
21
|
-
<% end %>
|
20
|
+
<% end %>
|
@@ -1,19 +1,23 @@
|
|
1
1
|
<div class="mt-6">
|
2
2
|
<label class="block text-sm leading-5 font-medium text-gray-700"><%= f.object.title %></label>
|
3
3
|
<div class="text-gray-400 text-sm"><%= f.object.hint %></div>
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
4
|
+
<div data-controller="select reveal data-binding" data-placeholder="<%=t "spina.pages.select_page" %>" class="relative mt-1" data-reveal-away-value>
|
5
|
+
<%= f.hidden_field :page_id, data: { select_target: "input", action: 'change->data-binding#update', binding_target: 'label', binding_condition: "$source.value == ''", binding_attribute: 'hidden' } %>
|
6
|
+
<div class="flex">
|
7
|
+
<div class="rounded-md border text-gray-700 bg-white shadow-sm border-gray-300">
|
8
|
+
<button type="button" class="w-full py-1.5 px-3 inline-flex items-center text-sm font-medium" data-action="reveal#toggle select#autofocus">
|
9
|
+
<%= heroicon("link", style: :mini, class: "w-4 h-4 mr-1 text-gray-600") %>
|
10
|
+
<div data-select-target="label">
|
11
|
+
<% if f.object.page_id.present? %>
|
12
|
+
<%= turbo_frame_tag :page_title, src: spina.admin_page_select_option_path(f.object.page_id) %>
|
13
|
+
<% end %>
|
14
|
+
</div>
|
15
|
+
</button>
|
16
|
+
<fieldset class="border-t border-gray-300 border-dashed" data-binding-ref="label">
|
17
|
+
<%= f.text_field :text, placeholder: t("spina.page_links.text_placeholder"), class: "w-full min-w-64 py-1.5 border-0 rounded-b-md text-sm text-gray-700 focus:border-spina-light focus:ring focus:ring-spina-light" %>
|
18
|
+
</fieldset>
|
14
19
|
</div>
|
15
|
-
</
|
16
|
-
|
20
|
+
</div>
|
17
21
|
<div class="relative mt-1">
|
18
22
|
<div data-reveal data-transition hidden class="absolute shadow-lg border border-gray-200 origin-top-right rounded-md z-10 top-0">
|
19
23
|
<div class="rounded-md bg-white shadow-xs">
|
@@ -21,10 +25,9 @@
|
|
21
25
|
<%= ff.hidden_field :object_id, value: f.object.object_id %>
|
22
26
|
<%= ff.hidden_field :resource, value: f.object.options&.dig(:resource) %>
|
23
27
|
<div class="p-2">
|
24
|
-
<%= ff.search_field :search, placeholder: t("spina.search"), class: "form-input sticky top-0 text-sm w-80", data: {action: "input->form#submit focus->form#submit",
|
28
|
+
<%= ff.search_field :search, placeholder: t("spina.search"), class: "form-input sticky top-0 text-sm w-80", data: {action: "input->form#submit focus->form#submit", select_target: "search"} %>
|
25
29
|
</div>
|
26
30
|
<% end %>
|
27
|
-
|
28
31
|
<div class="overflow-scroll max-h-80">
|
29
32
|
<%= turbo_frame_tag "page_select_options_#{f.object.object_id}" do %>
|
30
33
|
<% end %>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<div class="mt-6">
|
2
|
+
<label class="block text-sm leading-5 font-medium text-gray-700"><%= f.object.title %></label>
|
3
|
+
<div class="text-gray-400 text-sm"><%= f.object.hint %></div>
|
4
|
+
<div data-controller="select reveal" data-placeholder="<%=t "spina.resources.select_resource" %>" class="relative mt-1" data-reveal-away-value>
|
5
|
+
<%= f.hidden_field :resource_id, data: {select_target: "input"} %>
|
6
|
+
<button type="button" class="btn btn-default px-3 inline-flex items-center text-sm font-medium" data-action="reveal#toggle select#autofocus">
|
7
|
+
<%= heroicon("link", style: :mini, class: "w-4 h-4 mr-1 text-gray-600") %>
|
8
|
+
<div data-select-target="label">
|
9
|
+
<% if f.object.resource_id.present? %>
|
10
|
+
<%= turbo_frame_tag :resource_title, src: spina.admin_resource_select_option_path(f.object.resource_id) %>
|
11
|
+
<% end %>
|
12
|
+
</div>
|
13
|
+
</button>
|
14
|
+
<div class="relative mt-1">
|
15
|
+
<div data-reveal data-transition hidden class="absolute shadow-lg border border-gray-200 origin-top-right rounded-md z-10 top-0">
|
16
|
+
<div class="rounded-md bg-white shadow-xs">
|
17
|
+
<%= form_with url: spina.search_admin_resource_select_options_path, data: {turbo_frame: "resource_select_options_#{f.object.object_id}", controller: "form", debounce_time: 100} do |ff| %>
|
18
|
+
<%= ff.hidden_field :object_id, value: f.object.object_id %>
|
19
|
+
<%= ff.hidden_field :resource, value: f.object.options&.dig(:resource) %>
|
20
|
+
<div class="p-2">
|
21
|
+
<%= ff.search_field :search, placeholder: t("spina.search"), class: "form-input sticky top-0 text-sm w-80", data: {action: "input->form#submit focus->form#submit", select_target: "search"} %>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
24
|
+
<div class="overflow-scroll max-h-80">
|
25
|
+
<%= turbo_frame_tag "resource_select_options_#{f.object.object_id}" do %>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<%= turbo_frame_tag "resource_select_options_#{params[:object_id]}" do %>
|
2
|
+
<% if params[:search].blank? %>
|
3
|
+
<button type="button" data-action="select#clear reveal#hide" class="block hover:bg-gray-100 w-full text-left px-5 py-2 font-medium leading-5 text-gray-700 hover:text-gray-900 text-sm border-t border-gray-150">
|
4
|
+
–
|
5
|
+
</button>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<% @resources.each do |resource| %>
|
9
|
+
<button type="button" data-action="select#select reveal#hide" data-title="<%= resource.label %>" data-id="<%= resource.id %>" class="block hover:bg-gray-100 w-full text-left px-5 py-2 font-medium leading-5 text-gray-700 hover:text-gray-900 text-sm border-t border-gray-150">
|
10
|
+
<%= resource.label %>
|
11
|
+
</button>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|