Senren UI
Components / Activity Feed

Activity Feed

Stable

audit logs

  1. MN

    Mai shipped the docs preview.

    New SaaS blocks are ready for review.

  2. AT

    An reviewed component states.

    Hover, focus, and empty states passed.

  3. LP

    Linh updated the release checklist.

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/.../activity_feed_example.html.erb
<div class="w-full max-w-md">
  <%= render Senren::ActivityFeedComponent.new(
    items: [
      { initials: "MN", title: "Mai shipped the docs preview.", description: "New SaaS blocks are ready for review.", time: "2m ago" },
      { initials: "AT", title: "An reviewed component states.", description: "Hover, focus, and empty states passed.", time: "18m ago" },
      { initials: "LP", title: "Linh updated the release checklist.", time: "1h ago" }
    ]
  ) %>
</div>

Install this component #

Copy the official component into your app

Use this when you want the Senren-maintained implementation copied into app/components/senren.

Terminal
bin/rails senren:add activity_feed

Create a custom component with the same conventions

Use this when you need an app-specific static component that follows Senren's ViewComponent structure.

Terminal
bin/rails generate senren:component activity_feed --no-client

Dependencies are resolved by senren:add: avatar, badge, typography.

At a glance #

Category Saas
Class name Senren::ActivityFeedComponent
Stimulus
Variants default
Depends on avatar, badge, typography
Pairs with card

Source #

app/components/senren/activity_feed_component.html.erb
<%= tag.div(**root_attrs("rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] p-4 shadow-sm")) do %>
  <% if content? && items.empty? %>
    <%= content %>
  <% else %>
    <ol class="space-y-4">
      <% items.each do |item| %>
        <li class="relative flex gap-3">
          <span class="mt-1 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--senren-muted))] text-xs font-semibold text-[hsl(var(--senren-muted-foreground))]"><%= item_value(item, :initials) || "!" %></span>
          <div class="min-w-0 flex-1">
            <p class="text-sm text-[hsl(var(--senren-foreground))]"><%= item_value(item, :title) %></p>
            <% if item_value(item, :description).present? %>
              <p class="mt-1 text-sm leading-6 text-[hsl(var(--senren-muted-foreground))]"><%= item_value(item, :description) %></p>
            <% end %>
            <% if item_value(item, :time).present? %>
              <time class="mt-1 block text-xs text-[hsl(var(--senren-muted-foreground))]"><%= item_value(item, :time) %></time>
            <% end %>
          </div>
        </li>
      <% end %>
    </ol>
  <% end %>
<% end %>
app/components/senren/activity_feed_component.rb
# frozen_string_literal: true

module Senren
  class ActivityFeedComponent < BaseComponent
    VARIANTS = { default: '' }.freeze
    SIZES = { md: '' }.freeze

    def initialize(items: [], class_name: nil, **html)
      super(variant: :default, size: :md, class_name: class_name, **html)
      @items = Array(items)
    end

    attr_reader :items

    def item_value(item, key)
      item.is_a?(Hash) ? (item[key] || item[key.to_s]) : nil
    end
  end
end

AI agent rules #

Use for

  • +audit logs
  • +comment threads
  • +change history

Avoid

  • -transient notifications - use alert or toast

Accessibility #

  • Use a list; each entry has a relative time element.