CircleCI codecov Gem Version

SdrViewComponents

A rails gem to provide reusable view components used throughout the SDR applications and implement component library assets.

Installation

Add to gemfile:

gem 'sdr_view_components'

Requirements

This set of components relies on the component library stylesheets, add:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/sul-dlss/component-library@v2025-09-11/styles/sul.css">
<%= stylesheet_link_tag "sdr_view_components", "data-turbo-track": "reload" %>

with the most recent date tagged release to your application.html.erb layout file.

JavaScript

Some components require JavaScript. The gem ships Stimulus controllers under app/javascript/sdr_view_components/ and registers that path with the asset pipeline automatically.

Here is an example of how to add a Stimulus controller:

The disappearing toast uses sdr_view_components/toast_controller to remove itself from the DOM after its fade-out animation completes.

Add to config/importmap.rb:

pin "sdr_view_components/toast_controller", to: "sdr_view_components/toast_controller.js"

Register the controller in app/javascript/controllers/index.js:

import { application } from "controllers/application"
import ToastController from "sdr_view_components/toast_controller"

application.register("sdr-toast", ToastController)

SdrViewComponents::TabForm::TabListComponent (see Tab form components below) similarly ships three controllers: sdr_view_components/tab_error_controller, sdr_view_components/tab_link_controller, and sdr_view_components/tab_nav_controller. Pin and register each the same way, using the sdr-tab-error, sdr-tab-link, and sdr-tab-nav identifiers respectively:

pin "sdr_view_components/tab_error_controller", to: "sdr_view_components/tab_error_controller.js"
pin "sdr_view_components/tab_link_controller", to: "sdr_view_components/tab_link_controller.js"
pin "sdr_view_components/tab_nav_controller", to: "sdr_view_components/tab_nav_controller.js"
import { application } from "controllers/application"
import TabErrorController from "sdr_view_components/tab_error_controller"
import TabLinkController from "sdr_view_components/tab_link_controller"
import TabNavController from "sdr_view_components/tab_nav_controller"

application.register("sdr-tab-error", TabErrorController)
application.register("sdr-tab-link", TabLinkController)
application.register("sdr-tab-nav", TabNavController)

Usage

Form components

SdrViewComponents provides a wrapper for several ActionView::Helper::Tags.

Currently supported tags:

  • Checkbox
  • File
  • RadioButton
  • TextArea
  • TextField

Composed form components

SdrViewComponents provides fully composed (label, help text, and validation) instances of the above components as:

  • SdrViewComponents::Forms::CheckboxComponent
  • SdrViewComponents::Forms::FileComponent
  • SdrViewComponents::Forms::RadioButtonComponent
  • SdrViewComponents::Forms::TextAreaComponent
  • SdrViewComponents::Forms::TextFieldComponent

At a minimum, each of these components must be provided wih the form: and field_name: parameters. Additionally, you can provide prefixed parameters that will be passed the the individual parts of the components:

  • container_: is used to pass arguments to the surrounding div for the component.
  • input_: is used to pass arguments to the actual input component.
  • label_: is used to pass arguments to the label for the component.
  • help_: is used to pass arguments to the help text available for the component.
  • error_: is used to pass arguments to the validation output of the component.

TODO:

  • Outline the supported params for each prefix
  • Provide an example instantiation and output

Basic form components

Each of the supported components above uses the provided "basic" components:

  • SdrViewComponents::Forms::BasicCheckboxComponent
  • SdrViewComponents::Forms::BasicFileComponent
  • SdrViewComponents::Forms::BasicRadioButtonComponent
  • SdrViewComponents::Forms::BasicTextAreaComponent
  • SdrViewComponents::Forms::BasicTextFieldComponent

At a minimum, each of these components must be provided wih the form: and field_name: parameters. Additionally, you can provide additional parameters that will be passed to the standard ActionView::Helpers::Tag.

Tab form components

SdrViewComponents provides components for rendering a tabbed form, where a form's fields are split across tabs rather than shown all at once:

  • SdrViewComponents::TabForm::TabListComponent -- renders the tab navigation and tab panes. It doesn't render a <form> tag itself.
  • SdrViewComponents::TabForm::TabComponent -- a single tab, rendered via TabListComponent#with_tab.
  • SdrViewComponents::TabForm::HiddenFieldsFormComponent -- renders the one real <form> tag backing the tabbed form. It only contains hidden fields; the visible fields (rendered inside TabListComponent's panes) associate with this form via the HTML form attribute rather than DOM nesting, since panes aren't necessarily inside the <form>.
  • SdrViewComponents::TabForm::TabbedFormBuilder -- a FormBuilder that automatically sets the form attribute (pointing at the HiddenFieldsFormComponent's form id) on every field it builds.
  • SdrViewComponents::TabForm::PaneComponent -- generic tab-pane chrome (optional header/help/footer, Bootstrap tab-pane classes, and the target wiring the sdr-tab-error controller needs). Wrap it in an app-specific component if you need pane-level buttons or layout beyond what it provides.

Panes passed to TabListComponent#with_pane can be any renderable object (a ViewComponent, a block, etc.) -- the caller supplies its own pane component, whether that's PaneComponent directly or a wrapper around it. If the pane responds to active_tab_name=, TabListComponent sets it centrally, so panes don't need active_tab_name passed to them individually.

TabListComponent requires JavaScript to mark tabs containing invalid fields -- see the sdr-tab-error controller under JavaScript above.

A minimal example:

<%= render SdrViewComponents::TabForm::HiddenFieldsFormComponent.new(model: @work_form, id: 'tabbed_form', hidden_fields: %i[lock version]) %>

<%= render SdrViewComponents::TabForm::TabListComponent.new(id: 'tabbed_form', active_tab_name: :details) do |tab_list| %>
  <% tab_list.with_tab(label: 'Details', tab_name: :details, mark_required: true) %>
  <% tab_list.with_tab(label: 'Notes', tab_name: :notes) %>

  <% form_with(model: @work_form, html: { id: 'tabbed_form' }, builder: SdrViewComponents::TabForm::TabbedFormBuilder) do |form| %>
    <% tab_list.with_pane(SdrViewComponents::TabForm::PaneComponent.new(tab_name: :details, label: 'Details')) do %>
      <%= render SdrViewComponents::Forms::TextFieldComponent.new(form:, field_name: :title) %>
    <% end %>

    <% tab_list.with_pane(SdrViewComponents::TabForm::PaneComponent.new(tab_name: :notes, label: 'Notes')) do %>
      <%= render SdrViewComponents::Forms::TextAreaComponent.new(form:, field_name: :notes) %>
    <% end %>
  <% end %>
<% end %>

See the Lookbook preview for SdrViewComponents::TabForm::TabListComponent for a runnable example.

General usage:

<% render SdrViewComponent::....>

Component library version

The component-library version is set in lib/sdr_view_components/configuration.rb.

def initialize
  # Default URL for the component library assets
  @component_library_url = 'https://cdn.jsdelivr.net/gh/sul-dlss/component-library@v2026-01-27'
end

Lookbook

Lookbook provides a component browser for the components.

Creating previews

For a component to appear in Lookbook, it must have a preview. See spec/components/previews/sdr_view_components

Previews can easily be created with the viewComponentPreview prompt. For example: /viewComponentPreview SpinnerComponent.

Running locally

bin/rails s

Lookbook will then be available at: http://localhost:3000/lookbook

Adding to another app

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).

To run Lookbook in that app:

  1. Add Lookbook to Gemfile.rb:
group :development do
  gem 'lookbook'
end
  1. Add routes to routes.rb:
if Rails.env.development?
  mount SdrViewComponents::Engine => '/sdr_view_components'
  mount Lookbook::Engine, at: '/lookbook'
end
  1. Add config/initializers/sdr_view_components.rb:
SdrViewComponents.configure do |config|
  config.component_library_url = Settings.component_library.url
end

It is recommended to change the component library URL to a configuration in the app instead of hardcoding in layouts.

When your app is running locally, Lookbook will be available at: http://localhost:3000/lookbook