Sheet
Stable Stimulus:senren--sheet
side panels
Edit profile
Update your account details. Changes save when you close the sheet.
Usage example #
Copy this ERB into a Rails view after installing the component. The snippet below is the same code used by the live preview above.
app/views/.../sheet_example.html.erb
<div class="flex w-full justify-center">
<%= render Senren::SheetComponent.new(variant: :right) do |s| %>
<% s.with_trigger do %>
<%= render(Senren::ButtonComponent.new(variant: :secondary)) { "Open sheet" } %>
<% end %>
<% s.with_title { "Edit profile" } %>
<% s.with_description { "Update your account details. Changes save when you close the sheet." } %>
<% s.with_body do %>
<div class="space-y-3">
<%= render(Senren::LabelComponent.new(for_field: "sheet_name")) { "Display name" } %>
<%= render Senren::InputComponent.new(name: "name", id: "sheet_name", value: "Senren") %>
</div>
<% end %>
<% s.with_footer do %>
<button type="button" data-action="click->senren--sheet#close" class="cursor-pointer inline-flex h-10 items-center rounded-md px-4 text-sm">Cancel</button>
<%= render(Senren::ButtonComponent.new(variant: :primary)) { "Save" } %>
<% end %>
<% end %>
</div>
Install this component #
Copy the official component into your app
This component requires Stimulus. Keep --client so the controller is copied with the ViewComponent.
Terminal
bin/rails senren:add sheet --client
Create a custom component with the same conventions
Use this when you need an app-specific component that follows Senren's ViewComponent and Stimulus structure. --client is required for this behavior.
Terminal
bin/rails generate senren:component sheet --client
Dependencies are resolved by senren:add:
button.
At a glance #
Category
Overlays
Class name
Senren::SheetComponent
Stimulus
senren--sheet
Variants
right, left, top, bottom
Depends on
button
Pairs with
form
Source #
app/components/senren/sheet_component.html.erb
<div data-controller="senren--sheet" data-senren-component="sheet" id="<%= dom_id %>">
<% if trigger? %>
<span data-action="click->senren--sheet#open" data-senren--sheet-target="trigger">
<%= trigger %>
</span>
<% else %>
<button type="button" data-action="click->senren--sheet#open" data-senren--sheet-target="trigger" class="hidden">Open</button>
<% end %>
<div data-senren--sheet-target="overlay" hidden
class="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm"></div>
<div data-senren--sheet-target="panel" hidden role="dialog" aria-modal="true"
data-open="false"
class="fixed z-50 bg-[hsl(var(--senren-background))] text-[hsl(var(--senren-foreground))] shadow-lg transition-transform duration-200 <%= self.class::VARIANTS[variant] %>">
<div class="flex h-full flex-col p-6">
<% if title? %>
<h2 class="text-lg font-semibold"><%= title %></h2>
<% end %>
<% if description? %>
<p class="text-sm text-[hsl(var(--senren-muted-foreground))]"><%= description %></p>
<% end %>
<div class="mt-4 flex-1 overflow-auto">
<%= body || content %>
</div>
<% if footer? %>
<div class="mt-4 border-t border-[hsl(var(--senren-border))] pt-4">
<%= footer %>
</div>
<% end %>
</div>
<button type="button" data-action="click->senren--sheet#close" aria-label="Close"
class="absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100">
<span aria-hidden="true">×</span>
</button>
</div>
</div>
app/components/senren/sheet_component.rb
# frozen_string_literal: true
module Senren
class SheetComponent < BaseComponent
renders_one :trigger
renders_one :title
renders_one :description
renders_one :body
renders_one :footer
VARIANTS = {
right: 'right-0 top-0 h-full w-full max-w-md translate-x-full data-[open=true]:translate-x-0',
left: 'left-0 top-0 h-full w-full max-w-md -translate-x-full data-[open=true]:translate-x-0',
top: 'left-0 top-0 w-full h-1/2 -translate-y-full data-[open=true]:translate-y-0',
bottom: 'left-0 bottom-0 w-full h-1/2 translate-y-full data-[open=true]:translate-y-0'
}.freeze
SIZES = { md: '' }.freeze
def initialize(side: :right, id: nil, class_name: nil, **html)
super(variant: side, size: :md, class_name: class_name, **html)
@dom_id = id || "senren-sheet-#{SecureRandom.hex(3)}"
end
attr_reader :dom_id
end
end
AI agent rules #
Use for
- +side panels
- +mobile filters
- +edit panels
Avoid
- -destructive confirmation - use alert_dialog
Accessibility #
- Trap focus when open.
- Escape to close.
- aria-modal=true while open.