sdr_view_components 0.12.1 → 0.13.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 +20 -0
- data/app/components/sdr_view_components/elements/tabs/tab_list_component.html.erb +15 -7
- data/app/components/sdr_view_components/elements/tabs/tab_list_component.rb +15 -1
- data/app/javascript/sdr_view_components/lookbook.js +20 -0
- data/app/javascript/sdr_view_components/tab_overflow_controller.js +76 -0
- data/app/javascript/sdr_view_components/tab_select_controller.js +8 -5
- data/app/views/layouts/lookbook.html.erb +18 -1
- data/lib/sdr_view_components/version.rb +1 -1
- data/spec/components/previews/sdr_view_components/elements/tabs/tab_list_component_preview/collapsible.html.erb +0 -1
- data/spec/components/previews/sdr_view_components/elements/tabs/tab_list_component_preview/overflow_menu.html.erb +10 -0
- data/spec/components/previews/sdr_view_components/elements/tabs/tab_list_component_preview.rb +2 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a345126e4b1ae936afb830c142944b842691edbf460fd2092136905f4035a1b2
|
|
4
|
+
data.tar.gz: 4dcce16061ff42a6d46fc74fa91b432b60298fb96aa22860ed752d43c09aefa1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d1e085e557f9a045c2915b8f944db1ebac286fd6245e6e22b80202bf1ee87cbe1efc2e74f0c2f9650959c9ea40a35c63049d6085a67e9a9485fc623b1bad90e3
|
|
7
|
+
data.tar.gz: 1ebaa46555633944bbe4a301193fde11929668392dad3457559991ca0d279774f0a0e56b0ce3dda08f34690db49d6cd13054e8a057fc8e3796a1fbb40725382b
|
data/README.md
CHANGED
|
@@ -29,6 +29,11 @@ with the most recent date tagged release to your `application.html.erb` layout f
|
|
|
29
29
|
|
|
30
30
|
Some components require JavaScript. The gem ships Stimulus controllers under `app/javascript/sdr_view_components/` and registers that path with the asset pipeline automatically.
|
|
31
31
|
|
|
32
|
+
The controllers that use Bootstrap's JavaScript import it as an ES module (`import * as bootstrap from "bootstrap"`), so `bootstrap` must be pinned to an ES module build of Bootstrap. The UMD `bootstrap.bundle.min.js` has no ES module exports and won't work. For example, in `config/importmap.rb`:
|
|
33
|
+
```ruby
|
|
34
|
+
pin "bootstrap", to: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/+esm"
|
|
35
|
+
```
|
|
36
|
+
|
|
32
37
|
Here is an example of how to add a Stimulus controller:
|
|
33
38
|
|
|
34
39
|
The disappearing toast uses `sdr_view_components/toast_controller` to remove itself from the DOM after its fade-out animation completes.
|
|
@@ -78,6 +83,19 @@ import TabSelectController from "sdr_view_components/tab_select_controller"
|
|
|
78
83
|
application.register("sdr-tab-select", TabSelectController)
|
|
79
84
|
```
|
|
80
85
|
|
|
86
|
+
Its `overflow_menu` option similarly ships `sdr_view_components/tab_overflow_controller`, registered as `sdr-tab-overflow`:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
pin "sdr_view_components/tab_overflow_controller", to: "sdr_view_components/tab_overflow_controller.js"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
import { application } from "controllers/application"
|
|
94
|
+
import TabOverflowController from "sdr_view_components/tab_overflow_controller"
|
|
95
|
+
|
|
96
|
+
application.register("sdr-tab-overflow", TabOverflowController)
|
|
97
|
+
```
|
|
98
|
+
|
|
81
99
|
## Usage
|
|
82
100
|
|
|
83
101
|
### Form components
|
|
@@ -185,6 +203,8 @@ See the Lookbook preview for `SdrViewComponents::TabForm::TabListComponent` for
|
|
|
185
203
|
<% end %>
|
|
186
204
|
```
|
|
187
205
|
|
|
206
|
+
`TabListComponent` also accepts `overflow_menu: true`. When given, tabs that don't fit on a single row are moved into a trailing "More" dropdown tab instead of wrapping, and moved back out as space allows. It can be combined with `collapse_below:`. This requires the `sdr-tab-overflow` controller -- see [JavaScript](#javascript) above.
|
|
207
|
+
|
|
188
208
|
```
|
|
189
209
|
<% render SdrViewComponent::....>
|
|
190
210
|
```
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
+
<% tab_list = capture do %>
|
|
2
|
+
<%= tag.ul class: classes, role: 'tablist', data: tab_list_data do %>
|
|
3
|
+
<% tabs.each do |tab| %><%= tab %><% end %>
|
|
4
|
+
<% if overflow_menu? %>
|
|
5
|
+
<li class="nav-item dropdown d-none" role="presentation" data-sdr-tab-overflow-target="more">
|
|
6
|
+
<button class="nav-link dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">More</button>
|
|
7
|
+
<ul class="dropdown-menu dropdown-menu-end" data-sdr-tab-overflow-target="menu"></ul>
|
|
8
|
+
</li>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
1
13
|
<% if collapse_below? %>
|
|
2
14
|
<div data-controller="sdr-tab-select" data-action="shown.bs.tab->sdr-tab-select#sync">
|
|
3
|
-
<%= tag.select class: select_classes, 'aria-label': 'Select a tab', data: { sdr_tab_select_target: 'select', action: 'sdr-tab-select#change' } do %>
|
|
15
|
+
<%= tag.select class: select_classes, 'aria-label': 'Select a tab', data: { sdr_tab_select_target: 'select', action: 'sdr-tab-select#change turbo:before-morph-element->sdr-tab-select#preventMorph' } do %>
|
|
4
16
|
<% tabs.each do |tab| %><%= tag.option(tab.label, value: tab.id, selected: tab.active?) %><% end %>
|
|
5
17
|
<% end %>
|
|
6
18
|
|
|
7
|
-
<%=
|
|
8
|
-
<% tabs.each do |tab| %><%= tab %><% end %>
|
|
9
|
-
<% end %>
|
|
19
|
+
<%= tab_list %>
|
|
10
20
|
</div>
|
|
11
21
|
<% else %>
|
|
12
|
-
<%=
|
|
13
|
-
<% tabs.each do |tab| %><%= tab %><% end %>
|
|
14
|
-
<% end %>
|
|
22
|
+
<%= tab_list %>
|
|
15
23
|
<% end %>
|
|
16
24
|
|
|
17
25
|
<%= header %>
|
|
@@ -19,11 +19,14 @@ module SdrViewComponents
|
|
|
19
19
|
# fully functional (the `<select>` drives the same Bootstrap tab JavaScript) -- this
|
|
20
20
|
# only changes how they're presented at narrow viewports. Leave nil (the default) to
|
|
21
21
|
# always render the tabs.
|
|
22
|
-
|
|
22
|
+
# @param overflow_menu [Boolean] if true, tabs that don't fit on a single row are moved into a
|
|
23
|
+
# trailing "More" dropdown tab (by the `sdr-tab-overflow` controller).
|
|
24
|
+
def initialize(classes: [], content_classes: [], variant: :default, collapse_below: nil, overflow_menu: false)
|
|
23
25
|
@classes = classes
|
|
24
26
|
@content_classes = content_classes
|
|
25
27
|
@variant = variant
|
|
26
28
|
@collapse_below = collapse_below
|
|
29
|
+
@overflow_menu = overflow_menu
|
|
27
30
|
|
|
28
31
|
raise ArgumentError, "Invalid variant: #{variant}" unless %i[underline default].include?(variant)
|
|
29
32
|
if collapse_below && BREAKPOINTS.exclude?(collapse_below)
|
|
@@ -37,6 +40,17 @@ module SdrViewComponents
|
|
|
37
40
|
@collapse_below.present?
|
|
38
41
|
end
|
|
39
42
|
|
|
43
|
+
def overflow_menu?
|
|
44
|
+
@overflow_menu
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def tab_list_data
|
|
48
|
+
return {} unless overflow_menu?
|
|
49
|
+
|
|
50
|
+
{ controller: 'sdr-tab-overflow', action: 'shown.bs.tab->sdr-tab-overflow#syncMoreActive ' \
|
|
51
|
+
'turbo:before-morph-element->sdr-tab-overflow#preventMorph' }
|
|
52
|
+
end
|
|
53
|
+
|
|
40
54
|
def classes
|
|
41
55
|
merge_classes('nav', @classes, variant_classes, collapse_below? ? ['d-none', "d-#{@collapse_below}-flex"] : nil)
|
|
42
56
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Registers the gem's Stimulus controllers for Lookbook previews (see layouts/lookbook.html.erb).
|
|
2
|
+
import * as bootstrap from 'bootstrap'
|
|
3
|
+
import { Application } from '@hotwired/stimulus'
|
|
4
|
+
import TabErrorController from RAILS_ASSET_URL('./tab_error_controller.js')
|
|
5
|
+
import TabLinkController from RAILS_ASSET_URL('./tab_link_controller.js')
|
|
6
|
+
import TabNavController from RAILS_ASSET_URL('./tab_nav_controller.js')
|
|
7
|
+
import TabOverflowController from RAILS_ASSET_URL('./tab_overflow_controller.js')
|
|
8
|
+
import TabSelectController from RAILS_ASSET_URL('./tab_select_controller.js')
|
|
9
|
+
import ToastController from RAILS_ASSET_URL('./toast_controller.js')
|
|
10
|
+
|
|
11
|
+
// Previews' inline scripts use the global (e.g., to initialize tooltips).
|
|
12
|
+
window.bootstrap = bootstrap
|
|
13
|
+
|
|
14
|
+
const application = Application.start()
|
|
15
|
+
application.register('sdr-tab-error', TabErrorController)
|
|
16
|
+
application.register('sdr-tab-link', TabLinkController)
|
|
17
|
+
application.register('sdr-tab-nav', TabNavController)
|
|
18
|
+
application.register('sdr-tab-overflow', TabOverflowController)
|
|
19
|
+
application.register('sdr-tab-select', TabSelectController)
|
|
20
|
+
application.register('sdr-toast', ToastController)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
|
|
3
|
+
// Moves tabs that don't fit on a single row into a trailing "More" dropdown tab (see
|
|
4
|
+
// TabListComponent's `overflow_menu` option). The tab list is laid out again whenever
|
|
5
|
+
// its width changes.
|
|
6
|
+
//
|
|
7
|
+
// The tab list is excluded from Turbo morphs (e.g., page refreshes): morphing would move the
|
|
8
|
+
// tabs back to their server-rendered positions, close an open "More" menu, and replace the
|
|
9
|
+
// menu element out from under Bootstrap's Dropdown instance.
|
|
10
|
+
export default class extends Controller {
|
|
11
|
+
static targets = ['more', 'menu']
|
|
12
|
+
|
|
13
|
+
connect () {
|
|
14
|
+
this.resizeObserver = new window.ResizeObserver(() => {
|
|
15
|
+
if (this.element.offsetWidth === this.lastWidth) return
|
|
16
|
+
|
|
17
|
+
this.lastWidth = this.element.offsetWidth
|
|
18
|
+
this.layout()
|
|
19
|
+
})
|
|
20
|
+
this.resizeObserver.observe(this.element)
|
|
21
|
+
document.fonts?.ready.then(this.layout)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
disconnect () {
|
|
25
|
+
this.resizeObserver.disconnect()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
layout = () => {
|
|
29
|
+
this.restore()
|
|
30
|
+
const items = this.tabItems()
|
|
31
|
+
// Hidden (e.g., collapsed into a select) or everything fits on one row.
|
|
32
|
+
if (this.element.offsetWidth === 0 || items.length === 0) return
|
|
33
|
+
if (!this.isWrapped(items.at(-1), items[0])) return
|
|
34
|
+
|
|
35
|
+
this.moreTarget.classList.remove('d-none')
|
|
36
|
+
while (items.length > 1 && this.isWrapped(this.moreTarget, items[0])) {
|
|
37
|
+
this.overflow(items.pop())
|
|
38
|
+
}
|
|
39
|
+
this.syncMoreActive()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
syncMoreActive = () => {
|
|
43
|
+
const toggle = this.moreTarget.querySelector('.dropdown-toggle')
|
|
44
|
+
toggle.classList.toggle('active', this.menuTarget.querySelector('.active') !== null)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
preventMorph (event) {
|
|
48
|
+
if (event.target === this.element) event.preventDefault()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Moves all tabs back into the tab list (in their original order) and hides "More".
|
|
52
|
+
restore () {
|
|
53
|
+
this.tabItems().forEach((item) => {
|
|
54
|
+
if (item.parentElement === this.element) return
|
|
55
|
+
|
|
56
|
+
this.element.insertBefore(item, this.moreTarget)
|
|
57
|
+
item.querySelector('[role="tab"]').classList.replace('dropdown-item', 'nav-link')
|
|
58
|
+
})
|
|
59
|
+
this.moreTarget.classList.add('d-none')
|
|
60
|
+
this.syncMoreActive()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
overflow (item) {
|
|
64
|
+
this.menuTarget.prepend(item)
|
|
65
|
+
item.querySelector('[role="tab"]').classList.replace('nav-link', 'dropdown-item')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The tab <li>s, in document order (tab list first, then the "More" menu).
|
|
69
|
+
tabItems () {
|
|
70
|
+
return Array.from(this.element.querySelectorAll('[role="tab"]'), (tab) => tab.closest('li'))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
isWrapped (item, firstItem) {
|
|
74
|
+
return item.offsetTop > firstItem.offsetTop
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
import * as bootstrap from 'bootstrap'
|
|
2
3
|
|
|
3
4
|
// Drives a <select> that stands in for a tab list on narrow viewports (see
|
|
4
5
|
// TabListComponent's `collapse_below` option). Selecting an option shows the
|
|
5
6
|
// corresponding tab; activating a tab some other way (e.g. clicking the tab
|
|
6
7
|
// list above the collapse breakpoint) keeps the select's value in sync.
|
|
7
8
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// of it resolves to an empty namespace object even though the script itself
|
|
11
|
-
// still assigns the real API to window.bootstrap as a side effect.
|
|
9
|
+
// The select is excluded from Turbo morphs (e.g., page refreshes), which would otherwise reset
|
|
10
|
+
// its value to the server-rendered selected option regardless of which tab is active.
|
|
12
11
|
export default class extends Controller {
|
|
13
12
|
static targets = ['select']
|
|
14
13
|
|
|
15
14
|
change (event) {
|
|
16
|
-
|
|
15
|
+
bootstrap.Tab.getOrCreateInstance(document.getElementById(event.target.value)).show()
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
sync (event) {
|
|
20
19
|
this.selectTarget.value = event.target.id
|
|
21
20
|
}
|
|
21
|
+
|
|
22
|
+
preventMorph (event) {
|
|
23
|
+
if (event.target === this.selectTarget) event.preventDefault()
|
|
24
|
+
}
|
|
22
25
|
}
|
|
@@ -34,12 +34,29 @@
|
|
|
34
34
|
|
|
35
35
|
<%= stylesheet_link_tag 'sdr_view_components', 'data-turbo-track': 'reload' %>
|
|
36
36
|
<%= tag.link rel: 'stylesheet', href: "#{SdrViewComponents.configuration.component_library_url}/styles/sul.css" %>
|
|
37
|
+
<%# Bootstrap is loaded as a module (rather than the UMD bundle) so the gem's Stimulus controllers can import it
|
|
38
|
+
without a second copy registering Bootstrap's data-API handlers twice. The import map must precede any module
|
|
39
|
+
script or preload, so it's in the head and the module isn't preloaded via a Link header. %>
|
|
40
|
+
<%= tag.script type: 'importmap', nonce: content_security_policy_nonce do %>
|
|
41
|
+
{
|
|
42
|
+
"imports": {
|
|
43
|
+
"@hotwired/stimulus": "https://cdn.jsdelivr.net/npm/@hotwired/stimulus@3.2.2/dist/stimulus.js",
|
|
44
|
+
"bootstrap": "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.esm.min.js",
|
|
45
|
+
"@popperjs/core": "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/index.js"
|
|
46
|
+
},
|
|
47
|
+
"integrity": {
|
|
48
|
+
"https://cdn.jsdelivr.net/npm/@hotwired/stimulus@3.2.2/dist/stimulus.js": "sha384-Z9ISq680E84zQ7uFT7Pw8g/TlnoyCUECFycQz5aZs9IlrNFR56FhXoVi5U7bVi+l",
|
|
49
|
+
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.esm.min.js": "sha384-xBDFeCIxnhwUVfUb/XJFZZE66HuOkWrQ1ob02jFh5jhEJR9Kxx1KBEv3WvMColVg",
|
|
50
|
+
"https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/index.js": "sha384-oIbyZ0FNSTCT8v7EkK3fFXaiJMESE5k4eUKo9fAynJEhjl0JzMYoJa5pTPQUP4+g"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
<% end %>
|
|
54
|
+
<%= javascript_include_tag 'sdr_view_components/lookbook', type: 'module', nonce: true, preload_links_header: false %>
|
|
37
55
|
</head>
|
|
38
56
|
|
|
39
57
|
<body>
|
|
40
58
|
<div class="container-fluid py-4">
|
|
41
59
|
<%= yield %>
|
|
42
60
|
</div>
|
|
43
|
-
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
|
44
61
|
</body>
|
|
45
62
|
</html>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<%# Resize the preview frame below the sm breakpoint to see the tabs collapse into a select. %>
|
|
2
|
-
<%# The sdr-tab-select controller isn't registered in this dummy app, so the select won't switch panes here -- see the consuming app for a working example. %>
|
|
3
2
|
<%= render SdrViewComponents::Elements::Tabs::TabListComponent.new(variant: :underline, collapse_below: :sm) do |tab_list| %>
|
|
4
3
|
<% tab_list.with_tab(label: 'Details', id: 'details-tab', pane_id: 'details-pane', active: true) %>
|
|
5
4
|
<% tab_list.with_tab(label: 'History', id: 'history-tab', pane_id: 'history-pane') %>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%# Narrow the preview frame to see tabs that don't fit move into a "More" dropdown. %>
|
|
2
|
+
<% labels = %w[Overview Description Files History Events Metadata Workflows Versions Releases Tags] %>
|
|
3
|
+
<%= render SdrViewComponents::Elements::Tabs::TabListComponent.new(variant: :underline, overflow_menu: true) do |tab_list| %>
|
|
4
|
+
<% labels.each_with_index do |label, index| %>
|
|
5
|
+
<% tab_list.with_tab(label:, id: "#{label.downcase}-tab", pane_id: "#{label.downcase}-pane", active: index.zero?) %>
|
|
6
|
+
<% tab_list.with_pane(id: "#{label.downcase}-pane", tab_id: "#{label.downcase}-tab", active: index.zero?) do %>
|
|
7
|
+
<p><%= label %> pane content.</p>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% end %>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sdr_view_components
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aaron Collier
|
|
@@ -175,9 +175,11 @@ files:
|
|
|
175
175
|
- app/components/sdr_view_components/tables/row_component.html.erb
|
|
176
176
|
- app/components/sdr_view_components/tables/row_component.rb
|
|
177
177
|
- app/components/sdr_view_components/tables/table_component.rb
|
|
178
|
+
- app/javascript/sdr_view_components/lookbook.js
|
|
178
179
|
- app/javascript/sdr_view_components/tab_error_controller.js
|
|
179
180
|
- app/javascript/sdr_view_components/tab_link_controller.js
|
|
180
181
|
- app/javascript/sdr_view_components/tab_nav_controller.js
|
|
182
|
+
- app/javascript/sdr_view_components/tab_overflow_controller.js
|
|
181
183
|
- app/javascript/sdr_view_components/tab_select_controller.js
|
|
182
184
|
- app/javascript/sdr_view_components/toast_controller.js
|
|
183
185
|
- app/views/layouts/lookbook.html.erb
|
|
@@ -214,6 +216,7 @@ files:
|
|
|
214
216
|
- spec/components/previews/sdr_view_components/elements/tabs/tab_list_component_preview.rb
|
|
215
217
|
- spec/components/previews/sdr_view_components/elements/tabs/tab_list_component_preview/collapsible.html.erb
|
|
216
218
|
- spec/components/previews/sdr_view_components/elements/tabs/tab_list_component_preview/default_variant.html.erb
|
|
219
|
+
- spec/components/previews/sdr_view_components/elements/tabs/tab_list_component_preview/overflow_menu.html.erb
|
|
217
220
|
- spec/components/previews/sdr_view_components/elements/tabs/tab_list_component_preview/underline_variant.html.erb
|
|
218
221
|
- spec/components/previews/sdr_view_components/elements/tabs/tab_list_component_preview/with_header.html.erb
|
|
219
222
|
- spec/components/previews/sdr_view_components/elements/toast_component_preview.rb
|