sdr_view_components 0.1.14 → 0.2.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +55 -38
  3. data/app/assets/sdr_view_components.css +110 -0
  4. data/app/components/sdr_view_components/elements/heading_component.rb +3 -1
  5. data/app/components/sdr_view_components/elements/icon_button_component.rb +10 -0
  6. data/app/components/sdr_view_components/elements/spinner_component.html.erb +2 -2
  7. data/app/components/sdr_view_components/elements/spinner_component.rb +5 -4
  8. data/app/components/sdr_view_components/elements/tabs/pane_component.html.erb +3 -0
  9. data/app/components/sdr_view_components/elements/tabs/pane_component.rb +27 -0
  10. data/app/components/sdr_view_components/elements/tabs/tab_component.html.erb +3 -0
  11. data/app/components/sdr_view_components/elements/tabs/tab_component.rb +42 -0
  12. data/app/components/sdr_view_components/elements/tabs/tab_list_component.html.erb +9 -0
  13. data/app/components/sdr_view_components/elements/tabs/tab_list_component.rb +33 -0
  14. data/app/components/sdr_view_components/elements/toast_component.rb +1 -1
  15. data/app/components/sdr_view_components/tables/base_table_component.html.erb +32 -0
  16. data/app/components/sdr_view_components/tables/base_table_component.rb +69 -0
  17. data/app/components/sdr_view_components/tables/cell_component.html.erb +3 -0
  18. data/app/components/sdr_view_components/tables/cell_component.rb +21 -0
  19. data/app/components/sdr_view_components/tables/header_component.html.erb +4 -0
  20. data/app/components/sdr_view_components/tables/header_component.rb +22 -0
  21. data/app/components/sdr_view_components/tables/list_cell_component.html.erb +5 -0
  22. data/app/components/sdr_view_components/tables/list_cell_component.rb +22 -0
  23. data/app/components/sdr_view_components/tables/raw_table_component.rb +10 -0
  24. data/app/components/sdr_view_components/tables/row_component.html.erb +24 -0
  25. data/app/components/sdr_view_components/tables/row_component.rb +32 -0
  26. data/app/components/sdr_view_components/tables/table_component.rb +10 -0
  27. data/app/views/layouts/lookbook.html.erb +46 -0
  28. data/lib/sdr_view_components/configuration.rb +22 -0
  29. data/lib/sdr_view_components/engine.rb +17 -7
  30. data/lib/sdr_view_components/error.rb +7 -0
  31. data/lib/sdr_view_components/version.rb +1 -1
  32. data/lib/sdr_view_components.rb +2 -0
  33. metadata +25 -6
  34. data/app/assets/stylesheets/styles.scss +0 -118
  35. data/app/components/component_support/file_hierarchy.rb +0 -22
  36. data/app/components/sdr_view_components/forms/button_component.rb +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e89b11e845efdc4fa1bea907cac71a0e4165853da5233d4007e15c829709a3e
4
- data.tar.gz: 486b3bf6dbf1b657cd76d93327892feec81600502c1f75e3aa9578d108478660
3
+ metadata.gz: cbfc52ae67fecdff708f4696aaa7092396da6dff749264858c11dbbfbe8a4382
4
+ data.tar.gz: 32a60c51ecd3fdb27a1ef011ad8fbaee64053f956e66d0d3d42c51fd36801f78
5
5
  SHA512:
6
- metadata.gz: 9723c013dfb1a1ba6d6cc4af4c1170aec972539dbff1e6ae78065006b32233c812a66cb1b3b04b8d7a0909acd886783dc1f33d92af62ea3c290760e08be15f96
7
- data.tar.gz: 4130c0034e46554ccc3e1e5fff29869a0aca37c7c718e75067fc5d1ab0763cb96ed1dd8a7cf96fd756f2641ca6e4857bdf2ff96d10093a91fbdfef0a91b614db
6
+ metadata.gz: 0f7f24fc7019c867299b390b5ae6dff1a93fc7b41f66a5075f473b5ebd69061d7cd4f44f92480b6e3337f26ac2683b5b48260580bbc11aa72b9007a910884c11
7
+ data.tar.gz: bbca5c2fc24924a8df4e40a706140b568502bf4816b3e2a43840614d201c28d849d1e2374febb56d9b4656f4ae6a0d8cd28e13750fcc5c94da0685dc78305161
data/README.md CHANGED
@@ -4,8 +4,7 @@
4
4
 
5
5
  # SdrViewComponents
6
6
 
7
- A rails gem to provide reusable view components used throughout the SDR applications and implement
8
- component library assets.
7
+ A rails gem to provide reusable view components used throughout the SDR applications and implement component library assets.
9
8
 
10
9
  # Installation
11
10
 
@@ -21,48 +20,13 @@ This set of components relies on the component library stylesheets, add:
21
20
 
22
21
  ```
23
22
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/sul-dlss/component-library@v2025-09-11/styles/sul.css">
23
+ <%= stylesheet_link_tag "sdr_view_components", "data-turbo-track": "reload" %>
24
24
  ```
25
25
 
26
26
  with the most recent date tagged release to your `application.html.erb` layout file.
27
27
 
28
28
  ## Usage
29
29
 
30
- ### Add the SUL Header to your application
31
-
32
- Supported header variations are `:dark`, `:light`, and `:white` (default is `:light`)
33
-
34
- ```
35
- <%= render SdrViewComponents::Structure::HeaderComponent.new(title: 'Test Header', subtitle: 'Test Subtitle', variant: :dark) do |header| %>
36
- <% header.with_primary_navigation_link do %>
37
- <%= render SdrViewComponents::Elements::Navigation::NavItemComponent.new(text: 'Home', path: '#') %>
38
- <% end %>
39
- <% header.with_primary_navigation_link do %>
40
- <%= render SdrViewComponents::Elements::Navigation::DropdownMenuComponent.new(text: 'Logged in: amcollie-preview-dropdown') do |dropdown| %>
41
- <% dropdown.with_item do %>
42
- <%= link_to 'Logout', '/Shibboleth.sso/Logout', class: 'dropdown-item' %>
43
- <% end %>
44
- <% end %>
45
- <%# ... all primary nav links %>
46
- <% end %>
47
- <% header.with_secondary_navigation_link do %>
48
- <%= render SdrViewComponents::Elements::Navigation::NavItemComponent.new(text: 'Option', path: '/item1') %>
49
- <%# ... all secondary nav links>
50
- <% end %>
51
- <% end %>
52
- ```
53
-
54
- The `:dark` variation supports providing an rgb value via the `background_color` param in order to override the default dark background, for example:
55
-
56
- ```
57
- <%= render SdrViewComponents::Structure::HeaderComponent.new(title: 'Test Header', subtitle: 'Test Subtitle', variant: :dark, rgb_color_str: '1, 104, 149') do |header| %>
58
-
59
- ...
60
-
61
- <% end %>
62
- ```
63
-
64
- By default, the SUL Rosette is included in the header, this can be disabled by setting `rosette: false` in the parameter list when instantiating the header.
65
-
66
30
  ### Form components
67
31
 
68
32
  SdrViewComponents provides a wrapper for several [ActionView::Helper::Tags](https://api.rubyonrails.org/v8.1.1/classes/ActionView/Helpers/Tags.html).
@@ -113,3 +77,56 @@ At a minimum, each of these components must be provided wih the `form:` and `fie
113
77
  ```
114
78
  <% render SdrViewComponent::....>
115
79
  ```
80
+
81
+ ## Component library version
82
+ The [component-library](https://github.com/sul-dlss/component-library/) version is set in `lib/sdr_view_components/configuration.rb`.
83
+
84
+ ```
85
+ def initialize
86
+ # Default URL for the component library assets
87
+ @component_library_url = 'https://cdn.jsdelivr.net/gh/sul-dlss/component-library@v2026-01-27'
88
+ end
89
+ ```
90
+
91
+ ## Lookbook
92
+
93
+ [Lookbook](https://lookbook.build/) provides a component browser for the components.
94
+
95
+ ### Creating previews
96
+ For a component to appear in Lookbook, it must have a preview. See `spec/components/previews/sdr_view_components`
97
+
98
+ Previews can easily be created with the `viewComponentPreview` prompt. For example: `/viewComponentPreview SpinnerComponent`.
99
+
100
+ ### Running locally
101
+
102
+ `bin/rails s`
103
+
104
+ Lookbook will then be available at: http://localhost:3000/lookbook
105
+
106
+ ### Adding to another app
107
+
108
+ When performing development in an app that is using SdrViewComponents, it may be helpful to be running Lookbook in that app (instead of having to run a separate local instance of it).
109
+
110
+ To run Lookbook in that app:
111
+ 1. Add Lookbook to `Gemfile.rb`:
112
+ ```
113
+ group :development do
114
+ gem 'lookbook'
115
+ end
116
+ ```
117
+ 2. Add routes to `routes.rb`:
118
+ ```
119
+ if Rails.env.development?
120
+ mount SdrViewComponents::Engine => '/sdr_view_components'
121
+ mount Lookbook::Engine, at: '/lookbook'
122
+ end
123
+ ```
124
+ 3. Add `config/initializers/sdr_view_components.rb`:
125
+ ```
126
+ SdrViewComponents.configure do |config|
127
+ config.component_library_url = Settings.component_library.url
128
+ end
129
+ ```
130
+ It is recommended to change the component library URL to a configuration in the app instead of hardcoding in layouts.
131
+
132
+ When your app is running locally, Lookbook will be available at: http://localhost:3000/lookbook
@@ -0,0 +1,110 @@
1
+ /* Header */
2
+ .masthead {
3
+ color: white;
4
+ }
5
+
6
+ .masthead h1,
7
+ .masthead .h1 {
8
+ font-size: 2.25rem;
9
+ }
10
+
11
+ /* Toggle */
12
+ .btn-group-toggle label.btn {
13
+ --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgb(var(--bs-btn-focus-shadow-rgb), .5);
14
+ }
15
+
16
+ .btn-group-toggle input:checked + label {
17
+ background-color: var(--bs-primary);
18
+ border-color: var(--bs-primary);
19
+ color: #fff;
20
+ }
21
+
22
+ .btn-group-toggle input:not(:checked) + label {
23
+ background-color: transparent;
24
+ border-color: var(--bs-primary);
25
+ color: var(--bs-primary);
26
+ }
27
+
28
+ .btn-group-toggle .btn-check:checked + .btn {
29
+ background-color: var(--stanford-digital-blue);
30
+ border-color: var(--stanford-digital-blue);
31
+ color: #fff;
32
+ }
33
+
34
+ .btn-group-toggle :not(.btn-check:checked) + .btn {
35
+ color: var(--stanford-digital-blue);
36
+ border-color: var(--stanford-digital-blue);
37
+ }
38
+
39
+ .btn-group-toggle input:checked + label::before {
40
+ display: inline-block;
41
+ margin-right: 3px;
42
+ font-family: bootstrap-icons;
43
+ vertical-align: -15%;
44
+ content: "\F633";
45
+ }
46
+
47
+ /* Banner */
48
+ .banner.alert a.btn-primary {
49
+ color: white;
50
+ }
51
+
52
+ /* Forms */
53
+ .form-control,
54
+ .form-select,
55
+ .form-check-input {
56
+ border-color: var(--stanford-60-black);
57
+ }
58
+
59
+ /* Tabs */
60
+ .nav-underline {
61
+ border-bottom: 1px solid black;
62
+ margin-bottom: 10px;
63
+ }
64
+
65
+ .nav-underline .nav-link {
66
+ width: 10.5rem;
67
+ }
68
+
69
+ .nav-underline .nav-link:not(.active) {
70
+ color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
71
+ }
72
+
73
+ .nav-underline .nav-link.active {
74
+ border-bottom: 3px solid #B1040E;
75
+ color: #B1040E;
76
+ font-weight: bold;
77
+ }
78
+
79
+ /* Tables */
80
+ table.table-h3 caption {
81
+ padding: .25rem;
82
+ font-size: 1.5rem;
83
+ font-weight: 600;
84
+ line-height: 1.5;
85
+ color: var(--stanford-black);
86
+ caption-side: top;
87
+ border-bottom: 1px solid var(--stanford-black);
88
+ }
89
+
90
+ table.table-h3 th {
91
+ font-size: 1.0rem;
92
+ font-weight: 600;
93
+ color: var(--stanford-black);
94
+ }
95
+
96
+ table.table-h3 tr {
97
+ border-color: var(--stanford-30-black);
98
+ }
99
+
100
+ /* Item lists */
101
+ table.table-h3 .list-unstyled {
102
+ list-style: none;
103
+ margin-left: 0;
104
+ padding-left: 0;
105
+ }
106
+
107
+ table.table-data th {
108
+ width: fit-content;
109
+ white-space: nowrap;
110
+ }
@@ -20,7 +20,9 @@ module SdrViewComponents
20
20
 
21
21
  # Renders the component without the need for a .erb partial.
22
22
  def call
23
- content_tag(@level, class: classes) do
23
+ return unless tag.respond_to?(@level)
24
+
25
+ tag.public_send(@level, class: classes) do
24
26
  @text || content
25
27
  end
26
28
  end
@@ -12,6 +12,10 @@ module SdrViewComponents
12
12
  super()
13
13
  end
14
14
 
15
+ def before_render
16
+ raise SdrViewComponents::Error::UnknownComponentIcon, "Unknown icon type: #{@icon}" unless button_icon?
17
+ end
18
+
15
19
  attr_reader :label, :options
16
20
 
17
21
  def classes
@@ -21,6 +25,12 @@ module SdrViewComponents
21
25
  def button_icon
22
26
  helpers.public_send(:"#{@icon}_icon")
23
27
  end
28
+
29
+ private
30
+
31
+ def button_icon?
32
+ helpers.respond_to?(:"#{@icon}_icon")
33
+ end
24
34
  end
25
35
  end
26
36
  end
@@ -1,8 +1,8 @@
1
1
  <%= tag.div class: classes do %>
2
2
  <% if image_path %>
3
- <%= tag.img src: image_path, alt: 'Spinner', class: spinner_classes, role: 'status', style: spinner_style, **options %>
3
+ <%= tag.img src: image_path, alt: 'Spinner', class: spinner_classes, role: 'status', style: spinner_style, **spinner_options %>
4
4
  <% else %>
5
- <%= tag.div class: spinner_classes, role: 'status', style: spinner_style, **options %>
5
+ <%= tag.div class: spinner_classes, role: 'status', style: spinner_style, **spinner_options %>
6
6
  <% end %>
7
7
  <%= tag.div class: message_classes do %><%= message %><% end %>
8
8
  <% end %>
@@ -6,7 +6,7 @@ module SdrViewComponents
6
6
  class SpinnerComponent < BaseComponent
7
7
  def initialize(message: 'Loading...', message_classes: [], message_position: :right, hide_message: false, # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
8
8
  image_path: nil, variant: nil, classes: [],
9
- height: nil, width: nil, speed: 0.75, **options)
9
+ height: nil, width: nil, speed: 0.75, spinner_classes: ['mx-2'], **spinner_options)
10
10
  @message = message
11
11
  @variant = variant
12
12
  @hide_message = hide_message
@@ -16,15 +16,16 @@ module SdrViewComponents
16
16
  @width = width
17
17
  @message_classes = message_classes
18
18
  @speed = speed # In seconds, so a larger number is slower. The default (0.75) is the same as Bootstrap's default.
19
- @options = options
19
+ @spinner_classes = spinner_classes
20
+ @spinner_options = spinner_options
20
21
  @message_position = message_position # :bottom or :right
21
22
  super()
22
23
  end
23
24
 
24
- attr_reader :message, :image_path, :options
25
+ attr_reader :message, :image_path, :spinner_options
25
26
 
26
27
  def spinner_classes
27
- merge_classes('spinner-border', variant_class, border_class, 'mx-2')
28
+ merge_classes('spinner-border', variant_class, border_class, @spinner_classes)
28
29
  end
29
30
 
30
31
  def message_classes
@@ -0,0 +1,3 @@
1
+ <%= tag.div class: classes, id:, role: 'tabpanel', 'aria-labelledby': tab_id, tabindex: '0' do %>
2
+ <%= content %>
3
+ <% end %>
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Elements
5
+ module Tabs
6
+ # Component for rendering a tab pane in a tabbed interface.
7
+ class PaneComponent < BaseComponent
8
+ def initialize(id:, tab_id:, active: false)
9
+ @id = id
10
+ @tab_id = tab_id
11
+ @active = active
12
+ super()
13
+ end
14
+
15
+ attr_reader :id, :tab_id
16
+
17
+ def active?
18
+ @active
19
+ end
20
+
21
+ def classes
22
+ merge_classes(%w[tab-pane fade], active? ? %w[show active] : nil)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ <li class="nav-item" role="presentation">
2
+ <%= tag.button class: button_classes, id:, data: { bs_toggle: 'tab', bs_target: "##{pane_id}" }, type: 'button', role: 'tab', 'aria-controls': 'details-pane', 'aria-selected': active? do %><%= label %><% end %>
3
+ </li>
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Elements
5
+ module Tabs
6
+ # Component for rendering a tab in a tabbed interface.
7
+ class TabComponent < BaseComponent
8
+ def initialize(label:, id:, pane_id:, active: false, variant: :underline)
9
+ @label = label
10
+ @id = id
11
+ @pane_id = pane_id
12
+ @active = active
13
+ @variant = variant
14
+
15
+ raise ArgumentError, "Invalid variant: #{variant}" unless %i[underline default].include?(variant)
16
+
17
+ super()
18
+ end
19
+
20
+ attr_reader :label, :id, :pane_id, :variant
21
+
22
+ def active?
23
+ @active
24
+ end
25
+
26
+ def classes
27
+ merge_classes('nav', variant_classes)
28
+ end
29
+
30
+ def button_classes
31
+ merge_classes('nav-link', active? ? 'active' : nil)
32
+ end
33
+
34
+ private
35
+
36
+ def variant_classes
37
+ 'nav-underline' if variant == :underline
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ <%= tag.ul class: classes, role: 'tablist' do %>
2
+ <% tabs.each do |tab| %><%= tab %><% end %>
3
+ <% end %>
4
+
5
+ <%= header %>
6
+
7
+ <div class="tab-content">
8
+ <% panes.each do |pane| %><%= pane %><% end %>
9
+ </div>
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Elements
5
+ module Tabs
6
+ # Component for rendering a list of tabs in a tabbed interface.
7
+ class TabListComponent < BaseComponent
8
+ renders_one :header # optional
9
+ renders_many :tabs, Elements::Tabs::TabComponent
10
+ renders_many :panes, Elements::Tabs::PaneComponent
11
+
12
+ def initialize(classes: [], variant: :default)
13
+ @classes = classes
14
+ @variant = variant
15
+
16
+ raise ArgumentError, "Invalid variant: #{variant}" unless %i[underline default].include?(variant)
17
+
18
+ super()
19
+ end
20
+
21
+ def classes
22
+ merge_classes('nav', @classes, variant_classes)
23
+ end
24
+
25
+ private
26
+
27
+ def variant_classes
28
+ 'nav-underline' if @variant == :underline
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -21,7 +21,7 @@ module SdrViewComponents
21
21
  def background_color
22
22
  case variant
23
23
  when :red
24
- 'bg-stanford-cardinal'
24
+ 'bg-stanford-digital-red'
25
25
  when :green
26
26
  'bg-stanford-digital-green'
27
27
  when :poppy
@@ -0,0 +1,32 @@
1
+ <% @table_content = capture do %>
2
+ <%= tag.table class: classes, 'aria-label': label, **@table_options do %>
3
+ <% if show_label? %>
4
+ <caption><%= label || caption %></caption>
5
+ <% end %>
6
+ <% if headers? %>
7
+ <%= tag.thead class: head_classes do %>
8
+ <tr>
9
+ <% headers.each do |header| %>
10
+ <%= header %>
11
+ <% end %>
12
+ </tr>
13
+ <% end %>
14
+ <% end %>
15
+ <%= tag.tbody class: body_classes do %>
16
+ <% rows.each do |row| %>
17
+ <%= row %>
18
+ <% end %>
19
+ <% end %>
20
+ <% end %>
21
+ <% if empty_message && !rows? %>
22
+ <p class="fst-italic"><%= empty_message %></p>
23
+ <% end %>
24
+ <% end %>
25
+
26
+ <% if responsive? %>
27
+ <%= tag.div class: 'table-responsive' do %>
28
+ <%= @table_content %>
29
+ <% end %>
30
+ <% else %>
31
+ <%= @table_content %>
32
+ <% end %>
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Tables
5
+ # Base component for rendering a table.
6
+ class BaseTableComponent < BaseComponent
7
+ renders_many :headers, SdrViewComponents::Tables::HeaderComponent
8
+ renders_one :caption
9
+ # Subclasses should provide rows, e.g., renders_many :rows
10
+
11
+ def initialize(label: nil, classes: [], head_classes: [], body_classes: [], show_label: true, # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
12
+ empty_message: nil, responsive: false, variant: :h3, **table_options)
13
+ @classes = classes
14
+ @body_classes = body_classes
15
+ @label = label
16
+ @show_label = show_label
17
+ @empty_message = empty_message
18
+ @head_classes = head_classes
19
+ @responsive = responsive
20
+ @table_options = table_options
21
+ @variant = variant
22
+ raise ArgumentError, 'Unknown variant' unless %i[h3 data].include?(@variant)
23
+
24
+ raise ArgumentError, 'Subclasses must provide rows' unless respond_to?(:rows)
25
+
26
+ super()
27
+ end
28
+
29
+ attr_reader :label, :empty_message, :table_options, :variant
30
+
31
+ def before_render
32
+ raise ArgumentError, 'Must provide label or caption' unless label.present? || caption?
33
+ end
34
+
35
+ def classes
36
+ merge_classes(%w[table caption-top], variant_classes, @classes)
37
+ end
38
+
39
+ def head_classes
40
+ merge_classes(@head_classes)
41
+ end
42
+
43
+ def body_classes
44
+ merge_classes(@body_classes)
45
+ end
46
+
47
+ def show_label?
48
+ @show_label
49
+ end
50
+
51
+ def render?
52
+ rows? || empty_message.present?
53
+ end
54
+
55
+ def responsive?
56
+ @responsive
57
+ end
58
+
59
+ def variant_classes
60
+ case variant
61
+ when :h3
62
+ 'table-h3'
63
+ when :data
64
+ 'table-data table-striped table-hover table-bordered'
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ <%= tag.td(colspan:, class: classes, **cell_options) do %>
2
+ <%= content %>
3
+ <% end %>
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Tables
5
+ # Component for rendering a table cell.
6
+ class CellComponent < BaseComponent
7
+ def initialize(colspan: nil, classes: [], **cell_options)
8
+ @colspan = colspan
9
+ @classes = classes
10
+ @cell_options = cell_options
11
+ super()
12
+ end
13
+
14
+ attr_reader :colspan, :cell_options
15
+
16
+ def classes
17
+ merge_classes(@classes)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ <%= tag.th scope: 'col', class: classes, **header_options do %>
2
+ <%= label %>
3
+ <%= render SdrViewComponents::Elements::TooltipComponent.new(target_label: label, tooltip:) %>
4
+ <% end %>
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Tables
5
+ # Component for rendering a table header.
6
+ class HeaderComponent < BaseComponent
7
+ def initialize(label:, classes: [], tooltip: nil, **header_options)
8
+ @label = label
9
+ @classes = classes
10
+ @tooltip = tooltip
11
+ @header_options = header_options
12
+ super()
13
+ end
14
+
15
+ attr_reader :label, :tooltip, :header_options
16
+
17
+ def classes
18
+ merge_classes(@classes)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ <ul class="list-unstyled mb-0">
2
+ <% list_items.each do |item| %>
3
+ <li><%= item %></li>
4
+ <% end %>
5
+ </ul>
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Tables
5
+ # Component for rendering a list in a table cell.
6
+ class ListCellComponent < BaseComponent
7
+ renders_many :items
8
+
9
+ def initialize(item_values: [])
10
+ # Provide items or item_values
11
+ @item_values = item_values
12
+ super()
13
+ end
14
+
15
+ attr_reader :item_values
16
+
17
+ def list_items
18
+ items? ? items : item_values
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Tables
5
+ # Component for rendering a table in which rows must be provided.
6
+ class RawTableComponent < BaseTableComponent
7
+ renders_many :rows
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ <%= tag.tr **row_options do %>
2
+ <% if label.present? || label_content.present? %>
3
+ <th class="col-3" scope="row">
4
+ <%= label || label_content %>
5
+ <%= render SdrViewComponents::Elements::TooltipComponent.new(target_label: label, tooltip:) %>
6
+ </th>
7
+ <% end %>
8
+
9
+ <% if first_value.present? %>
10
+ <td class="col-3" scope="row"><%= first_value %></td>
11
+ <% end %>
12
+
13
+ <% values.each do |value| %>
14
+ <td><%= value %></td>
15
+ <% end %>
16
+
17
+ <% cells.each do |cell| %>
18
+ <%= cell %>
19
+ <% end %>
20
+
21
+ <% if empty_cell? %>
22
+ <td></td>
23
+ <% end %>
24
+ <% end %>
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Tables
5
+ # Component for rendering a table row.
6
+ class RowComponent < BaseComponent
7
+ renders_many :cells, SdrViewComponents::Tables::CellComponent
8
+ renders_one :label_content
9
+
10
+ def initialize(label: nil, first_value: nil, values: [], value: nil, tooltip: nil, **row_options) # rubocop:disable Metrics/ParameterLists
11
+ # Provide either label or first_value but not both; these are rendered in the first column
12
+ # label renders with <th> (bold), first_value is a normal <td>
13
+ @label = label
14
+ @first_value = first_value
15
+ # Provide either value, values or cells (e.g. for content files).
16
+ @values = Array(value).presence || values
17
+ @tooltip = tooltip
18
+ @row_options = row_options
19
+
20
+ raise ArgumentError if label.present? && first_value.present?
21
+
22
+ super()
23
+ end
24
+
25
+ attr_reader :label, :values, :tooltip, :first_value, :row_options
26
+
27
+ def empty_cell?
28
+ label.present? && values.empty? && !cells?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Tables
5
+ # Component for rendering a table.
6
+ class TableComponent < BaseTableComponent
7
+ renders_many :rows, SdrViewComponents::Tables::RowComponent
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,46 @@
1
+ <!DOCTYPE html>
2
+
3
+ <html>
4
+ <head>
5
+ <title><%= content_for(:title) || 'Lookbook' %></title>
6
+
7
+ <meta name="viewport" content="width=device-width,initial-scale=1">
8
+
9
+ <meta name="apple-mobile-web-app-capable" content="yes">
10
+
11
+ <meta name="application-name" content="Lookbook">
12
+
13
+ <meta name="mobile-web-app-capable" content="yes">
14
+
15
+ <%= csrf_meta_tags %>
16
+ <%= csp_meta_tag %>
17
+
18
+ <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
19
+ <%# = tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
20
+
21
+ <link rel="icon" href="/icon.png" type="image/png">
22
+
23
+ <link rel="icon" href="/icon.svg" type="image/svg+xml">
24
+
25
+ <link rel="apple-touch-icon" href="/icon.png">
26
+
27
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
28
+ <link
29
+ rel="stylesheet"
30
+ href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css"
31
+ >
32
+ <%= tag.link rel: 'icon', href: "#{SdrViewComponents.configuration.component_library_url}/styles/icon.png", type: 'image/png' %>
33
+ <%= tag.link rel: 'icon', href: "#{SdrViewComponents.configuration.component_library_url}/styles/icon.svg", type: 'image/svg+xml' %>
34
+ <%= tag.link rel: 'apple-touch-icon', href: "#{SdrViewComponents.configuration.component_library_url}/styles/icon.png" %>
35
+
36
+ <%= stylesheet_link_tag 'sdr_view_components', 'data-turbo-track': 'reload' %>
37
+ <%= tag.link rel: 'stylesheet', href: "#{SdrViewComponents.configuration.component_library_url}/styles/sul.css" %>
38
+ </head>
39
+
40
+ <body>
41
+ <div class="container-fluid py-4">
42
+ <%= yield %>
43
+ </div>
44
+ <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>
45
+ </body>
46
+ </html>
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SDR View Components module
4
+ module SdrViewComponents
5
+ # Configuration class for the SDR View Components gem
6
+ class Configuration
7
+ attr_accessor :component_library_url
8
+
9
+ def initialize
10
+ # Default URL for the component library assets
11
+ @component_library_url = 'https://cdn.jsdelivr.net/gh/sul-dlss/component-library@v2026-01-27'
12
+ end
13
+ end
14
+
15
+ def self.configuration
16
+ @configuration ||= Configuration.new
17
+ end
18
+
19
+ def self.configure
20
+ yield configuration
21
+ end
22
+ end
@@ -7,19 +7,25 @@ module SdrViewComponents
7
7
  class Engine < ::Rails::Engine
8
8
  isolate_namespace SdrViewComponents
9
9
 
10
- initializer 'sdr_view_components.view_component' do
11
- require 'view_component/engine'
10
+ initializer 'sdr_view_components.preview_autoload', before: :set_autoload_paths do |app|
11
+ preview_path = root.join('spec/components/previews').to_s
12
+ app.config.autoload_paths |= [preview_path]
13
+ end
14
+
15
+ initializer 'sdr_view_components.preview_paths' do |app|
16
+ preview_path = root.join('spec/components/previews').to_s
12
17
 
13
- # Tell VC where your previews live (inside the engine)
14
- config.view_component.previews.paths << root.join('spec/components/previews')
18
+ app.config.view_component.previews.paths |= [preview_path] if app.config.view_component.respond_to?(:previews)
15
19
 
16
- # Optional: which layout to wrap previews in (comes from the dummy app)
17
- config.view_component.default_preview_layout = 'application'
20
+ # Lookbook direct config
21
+ if app.config.respond_to?(:lookbook) && app.config.lookbook.respond_to?(:preview_paths)
22
+ app.config.lookbook.preview_paths |= [preview_path]
23
+ end
18
24
  end
19
25
 
20
26
  initializer 'sdr_view_components.assets' do |app|
27
+ app.config.assets.paths << Engine.root.join('app', 'assets').to_s
21
28
  app.config.assets.paths << Engine.root.join('app', 'assets', 'stylesheets').to_s
22
- app.config.assets.precompile += %w[sdr_view_components.css]
23
29
  end
24
30
 
25
31
  initializer 'sdr_view_components.helpers' do
@@ -27,5 +33,9 @@ module SdrViewComponents
27
33
  include SdrViewComponents::Helpers
28
34
  end
29
35
  end
36
+
37
+ initializer 'sdr_view_components.configure_lookbook' do
38
+ config.view_component.previews.default_layout = 'lookbook'
39
+ end
30
40
  end
31
41
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrViewComponents
4
+ module Error
5
+ class UnknownComponentIcon < StandardError; end
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SdrViewComponents
4
- VERSION = '0.1.14'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'sdr_view_components/version'
4
+ require 'sdr_view_components/configuration'
4
5
  require 'sdr_view_components/engine'
6
+ require 'sdr_view_components/error'
5
7
  require 'zeitwerk'
6
8
 
7
9
  loader = Zeitwerk::Loader.for_gem
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sdr_view_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Collier
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -61,11 +61,10 @@ files:
61
61
  - MIT-LICENSE
62
62
  - README.md
63
63
  - Rakefile
64
- - app/assets/stylesheets/styles.scss
64
+ - app/assets/sdr_view_components.css
65
65
  - app/components/base_component.rb
66
66
  - app/components/component_support/button_support.rb
67
67
  - app/components/component_support/css_classes.rb
68
- - app/components/component_support/file_hierarchy.rb
69
68
  - app/components/sdr_view_components/elements/alert_component.html.erb
70
69
  - app/components/sdr_view_components/elements/alert_component.rb
71
70
  - app/components/sdr_view_components/elements/banner_component.html.erb
@@ -98,6 +97,12 @@ files:
98
97
  - app/components/sdr_view_components/elements/skip_links_component.rb
99
98
  - app/components/sdr_view_components/elements/spinner_component.html.erb
100
99
  - app/components/sdr_view_components/elements/spinner_component.rb
100
+ - app/components/sdr_view_components/elements/tabs/pane_component.html.erb
101
+ - app/components/sdr_view_components/elements/tabs/pane_component.rb
102
+ - app/components/sdr_view_components/elements/tabs/tab_component.html.erb
103
+ - app/components/sdr_view_components/elements/tabs/tab_component.rb
104
+ - app/components/sdr_view_components/elements/tabs/tab_list_component.html.erb
105
+ - app/components/sdr_view_components/elements/tabs/tab_list_component.rb
101
106
  - app/components/sdr_view_components/elements/toast_component.html.erb
102
107
  - app/components/sdr_view_components/elements/toast_component.rb
103
108
  - app/components/sdr_view_components/elements/tooltip_component.html.erb
@@ -108,7 +113,6 @@ files:
108
113
  - app/components/sdr_view_components/forms/basic_radio_button_component.rb
109
114
  - app/components/sdr_view_components/forms/basic_text_area_component.rb
110
115
  - app/components/sdr_view_components/forms/basic_text_field_component.rb
111
- - app/components/sdr_view_components/forms/button_component.rb
112
116
  - app/components/sdr_view_components/forms/checkbox_component.rb
113
117
  - app/components/sdr_view_components/forms/field_component.html.erb
114
118
  - app/components/sdr_view_components/forms/field_component.rb
@@ -140,9 +144,24 @@ files:
140
144
  - app/components/sdr_view_components/structure/style_override_dark_component.rb
141
145
  - app/components/sdr_view_components/structure/style_override_light_component.html.erb
142
146
  - app/components/sdr_view_components/structure/style_override_light_component.rb
147
+ - app/components/sdr_view_components/tables/base_table_component.html.erb
148
+ - app/components/sdr_view_components/tables/base_table_component.rb
149
+ - app/components/sdr_view_components/tables/cell_component.html.erb
150
+ - app/components/sdr_view_components/tables/cell_component.rb
151
+ - app/components/sdr_view_components/tables/header_component.html.erb
152
+ - app/components/sdr_view_components/tables/header_component.rb
153
+ - app/components/sdr_view_components/tables/list_cell_component.html.erb
154
+ - app/components/sdr_view_components/tables/list_cell_component.rb
155
+ - app/components/sdr_view_components/tables/raw_table_component.rb
156
+ - app/components/sdr_view_components/tables/row_component.html.erb
157
+ - app/components/sdr_view_components/tables/row_component.rb
158
+ - app/components/sdr_view_components/tables/table_component.rb
159
+ - app/views/layouts/lookbook.html.erb
143
160
  - config/routes.rb
144
161
  - lib/sdr_view_components.rb
162
+ - lib/sdr_view_components/configuration.rb
145
163
  - lib/sdr_view_components/engine.rb
164
+ - lib/sdr_view_components/error.rb
146
165
  - lib/sdr_view_components/helpers.rb
147
166
  - lib/sdr_view_components/helpers/icon_helper.rb
148
167
  - lib/sdr_view_components/version.rb
@@ -166,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
185
  - !ruby/object:Gem::Version
167
186
  version: '0'
168
187
  requirements: []
169
- rubygems_version: 3.6.2
188
+ rubygems_version: 4.0.6
170
189
  specification_version: 4
171
190
  summary: Rails gem for providing SDR-specific ViewComponents.
172
191
  test_files: []
@@ -1,118 +0,0 @@
1
- /*
2
- * This is a manifest file that'll be compiled into application.css, which will include all the files
3
- * listed below.
4
- *
5
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
- * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
- *
8
- * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
- * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
- * files in this directory. Styles in this file should be added after the last require_* statement.
11
- * It is generally better to create a new file per style scope.
12
- *
13
- *= require_tree .
14
- *= require_self
15
- */
16
-
17
- // Header
18
- .cardinal {
19
- --bs-dark-rgb: var(--stanford-cardinal-rgb);
20
- }
21
-
22
- .container.without-progress-bar {
23
- max-width: 960px;
24
- }
25
-
26
- .masthead {
27
- color: white;
28
-
29
- h1 {
30
- font-size: 2.25rem;
31
- }
32
- }
33
-
34
- // Submission form
35
- .header-help {
36
- padding-left: 42px;
37
- }
38
-
39
- .header-help p:last-of-type {
40
- margin-bottom: 0;
41
- }
42
-
43
- .character-circle {
44
- @extend .d-inline-flex;
45
- @extend .align-items-center;
46
- @extend .justify-content-center;
47
- @extend .rounded-circle;
48
- @extend .text-white;
49
-
50
- width: 28px;
51
- height: 28px;
52
- font-size: 1rem;
53
- }
54
-
55
- .character-circle.character-circle-disabled {
56
- background-color: var(--stanford-60-black);
57
- }
58
-
59
- .character-circle.character-circle-success {
60
- @extend .bg-success;
61
- }
62
-
63
- .character-circle.character-circle-blank {
64
- @extend .border;
65
- @extend .border-1;
66
- }
67
-
68
- .character-circle.character-circle-check {
69
- font-family: bootstrap-icons !important;
70
- }
71
-
72
- .badge.badge-in-progress {
73
- background-color: var(--stanford-60-black);
74
- }
75
-
76
- .badge.badge-completed {
77
- @extend .text-bg-success;
78
- }
79
-
80
- .btn-group-toggle {
81
- // Provides visible indication when selected.
82
- label.btn {
83
- --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgb(var(--bs-btn-focus-shadow-rgb), .5);
84
- }
85
-
86
- input:checked + label {
87
- @extend .btn-primary;
88
- }
89
-
90
- input:not(:checked) + label {
91
- @extend .btn-outline-primary;
92
- }
93
-
94
- .btn-check:checked + .btn {
95
- background-color: var(--stanford-digital-blue);
96
- border-color: var(--stanford-digital-blue);
97
- }
98
-
99
- :not(.btn-check:checked) + .btn {
100
- color: var(--stanford-digital-blue);
101
- border-color: var(--stanford-digital-blue);
102
- }
103
- }
104
-
105
- .card-step {
106
- --bs-card-cap-padding-y: 1rem;
107
- }
108
-
109
- .banner.alert {
110
- a.btn-primary {
111
- color: white;
112
- }
113
- }
114
-
115
- // Increase contrast for input borders
116
- .form-control, .form-select, .form-check-input {
117
- border-color: var(--stanford-60-black);
118
- }
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ComponentSupport
4
- # Support methods for working with file hierarchies.
5
- class FileHierarchy
6
- # Determine the difference between the last path parts and the current path parts.
7
- # @param [Array<String>] last_path_parts the last path parts (e.g., ['dir1', 'dir2'])
8
- # @param [Array<String>] path_parts the current path parts
9
- # @return [Array<String>] path parts from the current path parts that differ
10
- def self.path_parts_diff(last_path_parts:, path_parts:)
11
- return [] if last_path_parts == path_parts
12
-
13
- matching_index = 0
14
- loop do
15
- break if path_parts[matching_index] != last_path_parts[matching_index]
16
-
17
- matching_index += 1
18
- end
19
- path_parts.slice(matching_index..)
20
- end
21
- end
22
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SdrViewComponents
4
- module Forms
5
- # Component for a button that is wrapped in a form
6
- class ButtonComponent < BaseComponent
7
- def initialize(link:, label: nil, variant: :primary, classes: [], method: :get, confirm: nil, # rubocop:disable Metrics/ParameterLists
8
- top: true, data: {})
9
- @link = link
10
- @label = label
11
- @variant = variant
12
- @classes = classes
13
- @method = method
14
- @confirm = confirm
15
- @top = top
16
- @data = data
17
- super()
18
- end
19
-
20
- attr_reader :link
21
-
22
- def call
23
- button_to(link, method: @method,
24
- class: ComponentSupport::ButtonSupport.classes(variant: @variant, classes:),
25
- form: { data: }) do
26
- @label || content
27
- end
28
- end
29
-
30
- def classes
31
- merge_classes(@classes)
32
- end
33
-
34
- def data
35
- @data.tap do |data|
36
- data[:turbo_frame] = '_top' if @top
37
- data[:turbo_confirm] = @confirm if @confirm
38
- end
39
- end
40
- end
41
- end
42
- end