Showcase lets you build previews for your partials, components, view helpers, Stimulus controllers and more.
Showcase lets you build previews for your partials, components, view helpers, Stimulus controllers and more — Rails engines included!
You can see it in action on https://bullettrain.co/docs/showcase.
Light Mode | Dark Mode |
---|---|
Each sample shows the render time in milliseconds and the allocation count so it’s easier to spot if there’s something different happening between your samples.
Add a partial in app/views/showcase/previews
and it’ll show up in Showcase’s sidebar menu.
So app/views/showcase/previews/_button.html.erb
will add top-level Button page. Directories are respected so a app/views/showcase/previews/deeply/nested/_partial.html.erb
file will create Deeply > Nested > Partial in the sidebar.
Within each partial preview file, you get access to a showcase
local variable.
Here’s some examples of using it, and what you can showcase in your app or engine.
Here’s how to showcase a standard button component written with standard Rails partials:
<%# app/views/showcase/previews/components/_button.html.erb %>
<% showcase.title "Button" %> <%# `title` is optional and inferred from the filename, by default. %>
<% showcase.badge :partial, :component %> <%# Optional badges you can add to further clarify the type of the showcase. %>
<% showcase.description "This button component handles what we click on" %>
<% showcase.sample "Basic" do %>
<%= render "components/button", content: "Button content", mode: :small %>
<% end %>
<% showcase.sample "Large", description: "This is our larger button" do %>
<%= render "components/button", content: "Button content", mode: :large %>
<% end %>
<% showcase.options do |o| %>
<% o.required :content, "The content to output as the button text" %>
<% o.optional :mode, "We support three modes", default: :small, options: %i[ small medium large ] %>
<% end %>
If we take the MessageComponent
as seen on https://viewcomponent.org:
# app/components/message_component.rb
class MessageComponent < ViewComponent::Base
def initialize(name:)
@name = name
end
end
<%# app/components/message_component.html.erb %>
<h1>Hello, <%= @name %>!</h1>
We can showcase it just by rendering it:
<%# app/views/showcase/previews/components/_message_component.html.erb %>
<% showcase.sample "Basic" do %>
<%= render MessageComponent.new(name: "World") %>
<% end %>
<% showcase.options do |o| %>
<% o.required :name, "The name to say hello to" %>
<% end %>
Given this phlex-rails component:
# app/views/components/article.rb
class Components::Article < Phlex::HTML
def initialize(article) = @article = article
def template
h1 { @article.title }
end
end
We can use Rails’ render
method to showcase it:
<%# app/views/showcase/previews/components/_article.html.erb %>
<% showcase.sample "Basic" do %>
<%= render Components::Article.new(Article.first) %>
<% end %>
Any application helpers defined in app/helpers
are automatically available in Showcase’s engine, so given a helper like this:
# app/helpers/upcase_helper.rb
module UpcaseHelper
def upcase_string(string)
string.upcase
end
end
You can showcase it like this:
<%# app/views/showcase/previews/helpers/_upcase_helper.html.erb %>
<% showcase.sample "Basic" do %>
<%= upcase_string "hello" %>
<% end %>
Assuming we have a Stimulus controller like this:
// app/assets/javascripts/controllers/welcome_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "greeter" ]
static values = { yell: Boolean }
connect() {
let greeting = this.hasGreeterTarget ? `Welcome, ${this.greeterTarget.textContent}!` : "Welcome!"
if (this.yellValue) greeting = greeting.toUpperCase()
console.log(greeting)
this.dispatch("greeting", { detail: { greeting } })
}
})
We can then render it to showcase it:
<% showcase.description "The welcome controller says hello when it enters the screen" %>
<% showcase.sample "Basic", events: "welcome:greeting" do %>
<div data-controller="welcome">I've just said welcome!</div>
<% end %>
<% showcase.sample "With greeter", events: "welcome:greeting" do %>
<div data-controller="welcome">
<div data-welcome-target="greeter">Somebody</div>
</div>
<% end %>
<% showcase.sample "Yelling!!!", events: "welcome:greeting" do %>
<div data-controller="welcome" data-welcome-yell-value="true">
<% end %>
<%# We're using the built-in Stimulus context here to output `data-` attributes correctly, and save some typing. %>
<% showcase.options.context :stimulus, controller: :welcome do |o| %>
<% o.optional.targets :greeter, "If the id of the target element must be printed" %>
<% o.required.values :yell, "Whether the hello is to be YELLED", default: false %>
<%# We support the other Stimulus declarations too: %>
<% o.required.classes :success, "The success class to append after greeting" %>
<% o.required.outlet :list, "An outlet to append each yelled greeter to" %>
<% o.optional.action :greet, "An action to repeat the greeting, if need be" %>
<% end %>
Note that by adding events: "welcome:greeting"
we’re listening for any time that event is dispatched. Events are logged with console.log
, but also output alongside the sample in the browser.
Add these lines to your application’s Gemfile. See next section for why Showcase is in the test group.
group :development, :test do
gem "showcase-rails"
gem "rouge", require: false # Syntax highlighting, `require: false` lets Showcase handle loading and saves boot time.
end
And then execute:
$ bundle
Or install it yourself as:
$ gem install showcase-rails
Then add the following in your config/routes.rb
within the block passed to Rails.application.routes.draw
:
mount Showcase::Engine, at: "/docs/showcase" if defined?(Showcase::Engine)
To have Showcase generate tests to exercise all your previews on CI, run bin/rails showcase:install:previews_test
to add test/views/showcase_test.rb
.
There you can add setup
and teardown
hooks, plus override the provided assert_showcase_preview
to add custom assertions for any preview.
If you need custom assertions for specific previews, you can use the test
helper:
# test/views/showcase_test.rb
require "test_helper"
class ShowcaseTest < Showcase::PreviewsTest
test showcase: "combobox" do
# This test block runs within the #combobox container element.
assert_text "This is a combobox, for sure."
end
test showcase: "button" do
assert_selector id: "basic" do
assert_button class: ["text-xs"]
end
end
test "some non-Showcase test" do
# You can still use the regular Rails `test` method too.
end
end
Add gem "rouge", require: false
to your Gemfile and Showcase will set syntax highlighting up for you. Any denoted syntaxes in your samples are then highlighted, e.g.:
# app/views/showcase/previews/_plain_ruby.ruby
<% showcase.sample "Basic", syntax: :ruby do %>
concat "hello".upcase
<% end %>
By default, syntax: :erb
is used, so you don’t need to mark the majority of your samples.
To use a different syntax highlighter, assign your own Proc to sample_renderer
like this:
# config/initializers/showcase.rb
return unless defined?(Showcase)
Showcase.sample_renderer = ->(source, syntax) do
# Return a String of lexed and formatted code.
end
By default, Showcase’s syntax highlighting runs on Rouge’s "github"
theme.
To use a different theme, override showcase/engine/_stylesheets.html.erb with the following, replacing :magritte
with a valid theme:
<%= stylesheet_link_tag "showcase" %> <%# We've removed the default showcase.highlights file here. %>
<%= tag.style Rouge::Theme.find(:magritte).render(scope: ".sc-highlight") %>
Clone the repository, run bundle install
, then run bin/rails server
, and visit localhost:3000 in your browser. You’ll see the examples from test/dummy/app/views/showcase/previews.
Showcase’s sidebar mirrors your app/views/showcase/previews
directory with their paths, and then trees at each directory level.
So a showcase/previews
directory with _top_level.html.erb
, components/_button.html.erb
, deeply/nested/_partial.html.erb
, will generate a sidebar like this:
Internally, Showcase renders an open details
element for each tree. You can control that with this:
# config/initializers/showcase.rb
return unless defined?(Showcase)
Showcase.tree_opens = true # All trees are open (the default).
Showcase.tree_opens = false # All trees are closed.
Showcase.tree_opens = ->(tree) { tree.root? } # Only open the root level trees (Previews, Components, Deeply but not Nested).
Showcase.tree_opens = ->(tree) { tree.id.start_with? ".", "components" } # Just open the top-level tree and the components tree.
Call showcase.link_to
with the URL path to the other Showcase:
<%= showcase.link_to "stimulus_controllers/welcome" %>
<%= showcase.link_to "components/button", id: "extra-large" %> <%# Pass an id to link to a specific sample %>
<%# You can also pass just an id: to target a sample on the current showcase %>
<%= showcase.link_to id: "extra-large" %>
Showcase also supports custom options contexts. They’re useful for cases where the options have a very specific format and it would be nice to keep them standardized.
By default, Showcase ships Nice Partials and Stimulus contexts out of the box. See lib/showcase.rb for how they’re defined.
To add a new context, you can do this:
# config/initializers/showcase.rb
return unless defined?(Showcase)
Showcase.options.define :some_context do
def targets(name, ...)
option("data-#{@prefix}-#{name}", ...)
end
end
And now we can use it, here passing in prefix:
which becomes an instance variable available in the define
block.
<% showcase.options.context :some_context, prefix: "super-" do |o| %>
<% o.required.targets :title %>
<% end %>
Any Rails engines in your app that ships previews in their app/views/showcase/previews
directory will automatically be surfaced in your app. Here’s an example from the bullet_train-themes-light Rails engine.
Showcase respects the Rails views rendering order, allowing you to override a specific preview. So if an engine ships an app/views/showcase/previews/partials/_alert.html.erb
preview, you can copy that to the same path in your app and tailor it to suit your app’s documentation needs. Showcase will then show your override instead of the engine’s original.
📖 How does this work? 📖 Internally, Showcase leverages Rails controllers’ ordered set of view_paths
— which each engine automatically prepends their app/views directory to by calling something like ActionController::Base.prepend_view_path
when initializing.
Showcase’s rendering happens through two controllers:
All paths shown here are assumed to be in app/views
.
The actions all use a layout "showcase"
, which renders like this:
So for Showcase::EngineController#index
we render:
And for Showcase::PreviewsController#show
we render:
If you want to override any specific rendering, e.g. how a Showcase::Preview
is rendered,
copy the file from our repo app/views
directory into your app/views
directory.
Showcase bundles its own showcase.js
, showcase.css
and showcase.highlights.css
asset files through
Action View’s javascript_include_tag and stylesheet_link_tag.
If your assets require more sophisticated loading techniques, declare your own
version of the showcase/engine/_head.html.erb partial.
If you need to tweak showcase’s assets, declare your own versions of
the showcase/engine/_javascripts.html.erb and
showcase/engine/_stylesheets.html.erb partials. When customizing those
partials, make sure to include "showcase"
in your list of assets.
Please open issues and/or pull requests with any feedback, fixes, or potential features you’d like us to look at. Thank you!
The gem is available as open source under the terms of the MIT License.