frontend-patterns

📁 rolemodel/rolemodel-skills 📅 9 days ago
9
总安装量
9
周安装量
#32413
全站排名
安装命令
npx skills add https://github.com/rolemodel/rolemodel-skills --skill frontend-patterns

Agent 安装分布

github-copilot 9
opencode 8
gemini-cli 8
codex 8
kimi-cli 8
amp 8

Skill 文档

Frontend Patterns

Tech Stack

  • Slim – HTML templating
  • Stimulus – JavaScript interactions
  • CSS – Styling
  • Optics – CSS styling framework
  • Simple Form – Form builder

Slim Templates

Conventions

  • Use Ruby 3+ syntax (e.g., keyword arguments with :)
  • Keep logic minimal in views
  • Extract complex rendering to helpers or partials
  • Use locals for partial data passing
  • Always prioritize DRY principles – extract repeated markup into partials
  • Extract partials when logic or markup is repeated more than once
  • Never use inline styles

When to Use Helpers vs Partials

Use Helper Methods when:

  • Simple conditional logic that returns HTML with different text/classes
  • Formatting data (dates, currency, durations)
  • Generating single HTML elements with varying attributes
  • Logic is stateless and doesn’t need multiple elements
  • Example: status_badge(status), format_duration(seconds)

Use Partials when:

  • Complex markup structure (multiple nested elements)
  • Reusable UI components with layout
  • Need to render collections
  • Significant HTML that would clutter a helper
  • Example: _time_entry_row.html.slim, _timer_form.html.slim

Rule of thumb: If it’s primarily conditional text/classes in a single element, use a helper. If it’s a structure/layout, use a partial.

Partial Extraction Guidelines

  • Extract forms on the new and edit pages into _form partials
  • Extract repeated structures into component partials
  • Use descriptive partial names: _time_entry_row, _project_selector, _status_badge
  • Place partials in same directory as parent view or in shared/ for cross-feature use
  • Always use keyword arguments for partial locals: render 'row', time_entry:, show_actions: true

Partial Organization

app/views/
  time_entries/
    edit.html.slim            # Edit view
    index.html.slim           # Main view
    new.html.slim             # New view
    show.html.slim            # Show view
    _time_entries.html.slim   # Table collection of rows
    _time_entry.html.slim     # Individual row
    _form.html.slim           # Time Entries form
  shared/
    _status_badge.html.slim   # Reusable badge
    _empty_state.html.slim    # Empty state pattern

Conditional class names

Use the rails class_names helper to manage conditional class names in Slim templates.

button.btn class=class_names('btn--active': active) Click Me

Example

-# locals: (user:, active: false)
.user-card class=('active' if active)
  h3 = user.name
  p = user.email

Simple Form

Overview

Always use Simple Form for forms. Never use form_with, form_for, or Rails form helpers directly.

Basic Model Form

= simple_form_for @user do |f|
  = f.input :first_name
  = f.input :last_name
  = f.input :email, required: true

  .form__actions
    = link_to 'Cancel', :back, class: 'btn btn--outline'
    = f.submit 'Save', class: 'btn btn--primary'

Non-Model Form (with URL)

For forms without a model (like bulk actions or search forms):

= simple_form_for :search, url: search_path, method: :get do |f|
  = f.input :query, label: 'Search'
  = f.submit 'Search', class: 'btn btn--primary'

Important: When using simple_form_for :symbol, params are nested under the symbol:

# View: simple_form_for :time_entry
# Params received: { time_entry: { task_id: 1, description: "text" } }
# Access with: params.dig(:time_entry, :task_id)

Form with HTML Options

= simple_form_for @record, html: { id: 'custom-form', class: 'special-form' } do |f|
  = f.input :name

Form with Data Attributes (Turbo)

= simple_form_for @record, data: { turbo_frame: '_top' } do |f|
  = f.input :name

Common Input Types

/ Text input
= f.input :name

/ Text area
= f.input :description, as: :text, input_html: { rows: 4 }

/ Select dropdown
= f.input :project_id, collection: @projects, prompt: 'Select a project...'

/ Boolean checkbox
= f.input :active, as: :boolean

/ Date picker
= f.input :start_date, as: :date

/ With placeholder
= f.input :email, placeholder: 'user@example.com'

/ With custom input attributes
= f.input :description, input_html: { rows: 3, required: true, data: { controller: 'auto-save' } }

Collections and Associations

/ Simple collection
= f.input :category_id, collection: @categories

/ With custom text/value methods
= f.input :project_id, collection: @projects, label_method: :name, value_method: :id

/ Grouped collection
= f.input :task_id, as: :grouped_select, collection: @projects, group_method: :tasks

/ With prompt
= f.input :status, collection: ['pending', 'approved', 'rejected'], prompt: 'Select status...'

Custom Labels and Hints

= f.input :email, label: 'Email Address', hint: 'We will never share your email'
= f.input :password, label: 'Password', placeholder: 'At least 8 characters'

Disabled Inputs

= f.input :task_id, disabled: true, input_html: { data: { target: 'form.taskSelect' } }

Hidden Fields

= f.hidden_field :organization_id, value: current_user.organization_id

Submit Buttons

/ Standard submit
= f.submit 'Save', class: 'btn btn--primary'

/ With data attributes
= f.submit 'Save', class: 'btn btn--primary', data: { disable_with: 'Saving...' }

/ Associated with external form (for modal footers)
= f.submit 'Save', form: 'my-form-id', class: 'btn btn--primary'

Form Actions Pattern

Standard pattern for form button groups:

.form__actions
  = link_to 'Cancel', :back, class: 'btn btn--outline'
  = f.submit 'Save', class: 'btn btn--primary'

Bulk Action Forms

For forms that collect checkboxes without inputs:

= simple_form_for :bulk_action, url: bulk_approve_path, method: :post, html: { id: 'bulk-form' } do |f|
  / Form will collect checked checkboxes via form attribute

/ Checkboxes reference the form
= check_box_tag 'entry_ids[]', entry.id, false, form: 'bulk-form'

Modal Forms

Forms that submit within modals and redirect to parent page:

= simple_form_for @record, html: { id: 'modal-form' }, data: { turbo_frame: '_top' } do |f|
  = f.input :reason, as: :text, input_html: { rows: 3, required: true }

-# In modal footer (outside form)
- content_for :modal_actions do
  = button_tag 'Submit', type: 'submit', form: 'modal-form', class: 'btn btn--primary'

Best Practices

  • Always use simple_form_for, never form_with or form_for
  • Use :symbol for non-model forms with url parameter
  • Use @model for model-based forms
  • Leverage Simple Form’s automatic label generation
  • Use input_html for custom HTML attributes on the input element
  • Use html option for attributes on the form element itself
  • Keep forms accessible with proper labels
  • Use .form__actions for button groups

Stimulus Controllers

Structure

  • One controller per behavior
  • Use data attributes for configuration
  • Keep controllers focused and composable
  • Follow naming conventions (kebab-case in HTML)

Example

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["output"]
  static values = { url: String }

  connect() {
    // Initialization
  }

  perform() {
    // Action logic
  }
}

CSS & Optics

Guidelines

  • Use Optics utility classes where applicable
  • Keep custom CSS minimal and scoped
  • Follow BEM or similar naming for custom components
  • Avoid inline styles

Future Topics

  • Turbo Frames and Streams patterns
  • Form styling conventions
  • Icon helper usage
  • Responsive design patterns
  • Animation and transition guidelines