Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,7 @@ Checkboxes render dark while unchecked and use the light primary checked state.
<%= f.select :role, [:admin, :user] %>
<%= f.date_field :birth_date %>
<%= f.datetime_field :confirmed_at %>
<%= f.rich_text_area :bio %>
<%= f.tramway_select :permissions, [['Create User', 'create_user'], ['Update user', 'update_user']] %>
<%= f.file_field :file %>
<%= f.submit 'Create User' %>
Expand Down Expand Up @@ -1243,6 +1244,7 @@ Available form helpers:
* date_field
* datetime_field
* time_field
* rich_text_area (Action Text/Trix editor with Tramway dark form classes)
* tramway_select ([Stimulus-based](https://github.com/Purple-Magic/tramway#stimulus-based-inputs))
* submit

Expand Down
107 changes: 107 additions & 0 deletions app/assets/javascripts/tramway/tramway.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,112 @@
import { Controller } from "@hotwired/stimulus"

const richTextToolbarClasses = {
toolbar: [
"mb-2",
"rounded-md",
"border",
"!border-zinc-800",
"!bg-zinc-950",
"p-1"
],
buttonGroup: [
"!border-zinc-800",
"!bg-zinc-950"
],
button: [
"!border-zinc-800",
"!bg-zinc-950",
"!text-zinc-50",
"hover:!bg-zinc-900"
],
dialog: [
"rounded-md",
"border",
"!border-zinc-800",
"!bg-zinc-950",
"!text-zinc-50",
"shadow-sm"
],
input: [
"rounded-md",
"border",
"!border-zinc-800",
"!bg-zinc-950",
"!text-zinc-50",
"placeholder:text-zinc-500",
"focus:outline-none",
"focus:ring-2",
"focus:ring-zinc-300",
"focus:ring-offset-2",
"focus:ring-offset-zinc-950"
]
}

function installRichTextToolbarStyles() {
if (document.getElementById("tramway-rich-text-toolbar-styles")) return

const style = document.createElement("style")
style.id = "tramway-rich-text-toolbar-styles"
style.textContent = `
trix-toolbar[data-tramway-rich-text-styled="true"] .trix-button--icon::before {
filter: invert(1) !important;
opacity: 0.95 !important;
}

trix-toolbar[data-tramway-rich-text-styled="true"] .trix-button--icon:disabled::before {
opacity: 0.35 !important;
}
`
document.head.appendChild(style)
}

function applyClasses(element, classes) {
if (element) element.classList.add(...classes)
}

function syncRichTextButtonState(button) {
button.classList.remove("bg-zinc-50", "text-zinc-950", "!bg-zinc-50", "!text-zinc-950")
button.classList.add("bg-zinc-950", "text-zinc-50", "!bg-zinc-950", "!text-zinc-50")
}

function styleRichTextToolbar(editor) {
const toolbar = document.getElementById(editor.getAttribute("toolbar"))
if (!toolbar || toolbar.dataset.tramwayRichTextStyled === "true") return

installRichTextToolbarStyles()
toolbar.dataset.tramwayRichTextStyled = "true"
applyClasses(toolbar, richTextToolbarClasses.toolbar)

toolbar.querySelectorAll(".trix-button-group").forEach((buttonGroup) => {
applyClasses(buttonGroup, richTextToolbarClasses.buttonGroup)
})

toolbar.querySelectorAll(".trix-button").forEach((button) => {
applyClasses(button, richTextToolbarClasses.button)
syncRichTextButtonState(button)

new window.MutationObserver(() => syncRichTextButtonState(button)).observe(button, {
attributeFilter: ["class"],
attributes: true
})
})

toolbar.querySelectorAll(".trix-dialog").forEach((dialog) => {
applyClasses(dialog, richTextToolbarClasses.dialog)
})

toolbar.querySelectorAll(".trix-input").forEach((input) => {
applyClasses(input, richTextToolbarClasses.input)
})
}

document.addEventListener("trix-initialize", (event) => {
const editor = event.target
if (editor.dataset.tramwayRichTextArea !== "true") return

styleRichTextToolbar(editor)
})

class TramwaySelect extends Controller {
static targets = ["dropdown", "showSelectedArea", "hiddenInput", "caretDown", "caretUp"]

Expand Down
42 changes: 42 additions & 0 deletions app/components/tramway/form/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@ class Builder < Tramway::Views::FormBuilder
include Tramway::Utils::Field
include Tramway::ColorsMethods

RICH_TEXT_AREA_CLASSES = [
'trix-content',
'prose',
'prose-invert',
'max-w-none',
'w-full',
'min-h-40',
'rounded-md',
'border',
'border-zinc-800',
'bg-zinc-950',
'!bg-zinc-950',
'text-zinc-50',
'!text-zinc-50',
'shadow-sm',
'transition-colors',
'focus-visible:outline-none',
'focus-visible:ring-2',
'focus-visible:ring-zinc-300',
'focus-visible:ring-offset-2',
'focus-visible:ring-offset-zinc-950',
'disabled:cursor-not-allowed',
'disabled:opacity-50'
].freeze

def initialize(object_name, object, template, options)
@horizontal = options[:horizontal] || false
@remote = options[:remote_submit] || false
Expand Down Expand Up @@ -56,6 +81,16 @@ def text_area(attribute, **, &)
common_field(:text_area, :text_area, attribute, **, &)
end

def rich_text_area(attribute, **, &)
rich_textarea(attribute, **, &)
end

def rich_textarea(attribute, **options, &)
sanitized_options = sanitize_options(options)

super(attribute, rich_text_area_options(sanitized_options), &)
end

def password_field(attribute, **options, &)
sanitized_options = sanitize_options(options)

Expand Down Expand Up @@ -217,6 +252,13 @@ def sanitize_options(options)
end
end

def rich_text_area_options(options)
custom_class = options.delete(:class) || options.delete('class')
data = (options.delete(:data) || options.delete('data') || {}).merge(tramway_rich_text_area: true)

options.merge(class: [RICH_TEXT_AREA_CLASSES, custom_class].compact.join(' '), data:)
end

# REMOVE IT. WE MUST UNDERSTAND WHY INCLUDE_BLANK DOES NOT WORK
def explicitly_add_blank_option(collection, options)
if options[:include_blank]
Expand Down
4 changes: 4 additions & 0 deletions app/controllers/tramway/entities_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ def show

def new
@record = tramway_form model_class.new, namespace: entity.namespace
@form_title = @record.object.model_name.human
end

def edit
@record = tramway_form model_class.find(params[:id]), namespace: entity.namespace
@form_title = @record.form_title
end

# rubocop:disable Metrics/AbcSize
Expand All @@ -50,6 +52,7 @@ def create
if @record.submit params[model_class.model_name.param_key]
redirect_to public_send(entity.show_helper_method, @record.id), notice: t('tramway.notices.created')
else
@form_title = @record.object.model_name.human
render :new
end
end
Expand All @@ -60,6 +63,7 @@ def update
if @record.submit params[model_class.model_name.param_key]
redirect_to public_send(entity.show_helper_method, @record.id), notice: t('tramway.notices.updated')
else
@form_title = @record.form_title
render :edit
end
end
Expand Down
2 changes: 1 addition & 1 deletion app/views/tramway/entities/edit.html.haml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
= tramway_container do
= tramway_title text: t('tramway.pages.edit.title', model_name: @record.object.model_name.human)
= tramway_title text: t('tramway.pages.edit.title', model_name: @form_title)

.mt-4
= render 'form'
2 changes: 1 addition & 1 deletion app/views/tramway/entities/new.html.haml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
= tramway_container do
= tramway_title text: t('tramway.pages.new.title', model_name: @record.object.model_name.human)
= tramway_title text: t('tramway.pages.new.title', model_name: @form_title)

.mt-4
= render 'form'
9 changes: 9 additions & 0 deletions config/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
'md:left-auto',
'md:translate-x-0',
'border-white',
'!border-zinc-800',
'h-screen',
'w-screen',
'z-50',
Expand All @@ -27,20 +28,23 @@ module.exports = {
'pt-16',
'min-h-8',
'bg-zinc-950',
'!bg-zinc-950',
'bg-zinc-950/95',
'bg-zinc-950/80',
'bg-zinc-900',
'bg-zinc-800',
'bg-zinc-900/80',
'border-zinc-800',
'text-zinc-50',
'!text-zinc-50',
'text-zinc-100',
'text-zinc-200',
'text-zinc-400',
'text-zinc-500',
'placeholder:text-zinc-500',
'hover:bg-zinc-800',
'hover:bg-zinc-900',
'hover:!bg-zinc-900',
'hover:text-zinc-50',
'focus-visible:ring-zinc-300',
'focus-visible:ring-zinc-400',
Expand Down Expand Up @@ -221,6 +225,9 @@ module.exports = {
'hidden',
'text-xl',
'font-bold',
'prose',
'prose-invert',
'max-w-none',

// === Button base shell ===
'inline-flex',
Expand Down Expand Up @@ -338,6 +345,7 @@ module.exports = {
'min-h-10',
'min-h-12',
'min-h-15',
'min-h-40',
'max-w-full',

// === Spacing utilities ===
Expand Down Expand Up @@ -411,6 +419,7 @@ module.exports = {
'h-12',
'border-transparent',
'focus:ring-2',
'focus:ring-zinc-300',
'focus:ring-zinc-400',
'focus:ring-offset-2',
'focus:ring-offset-zinc-950',
Expand Down
6 changes: 6 additions & 0 deletions lib/tramway/base_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ def assign(params)
__submit params
end

def form_title
return object.title if object.respond_to?(:title) && object.title.present?

object.model_name.human
end

def method_missing(method_name, *args)
if method_name.to_s.end_with?('=') && args.one?
object.public_send(method_name, args.first)
Expand Down
2 changes: 1 addition & 1 deletion lib/tramway/utils/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def tramway_field(field_data, attribute, **options, &)

def field_name(field_data)
case field_data.to_sym
when :text_area, :select, :tramway_select, :check_box
when :text_area, :rich_text_area, :rich_textarea, :select, :tramway_select, :check_box
field_data
when :checkbox
:check_box
Expand Down
58 changes: 58 additions & 0 deletions spec/components/tramway/form/builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@
]
}.freeze

RICH_TEXT_AREA_CLASSES = %w[
trix-content prose prose-invert w-full min-h-40 rounded-md border border-zinc-800 bg-zinc-950 !bg-zinc-950
text-zinc-50 !text-zinc-50 focus-visible:ring-zinc-300 focus-visible:ring-offset-zinc-950
].freeze

module DefaultRichTextAreaFormBuilder
def rich_textarea(attribute, options = {}, &)
template.content_tag('trix-editor', '', options.merge(input: attribute), &)
end

alias rich_text_area rich_textarea
end

Tramway::Views::FormBuilder.prepend(DefaultRichTextAreaFormBuilder)

describe Tramway::Form::Builder, type: :view do
let(:resource) { build :user }
let(:form_options) { {} }
Expand Down Expand Up @@ -275,6 +290,49 @@
end
end

describe '#rich_text_area' do
let(:output) { builder.rich_text_area :personal_info }
let(:editor) { Capybara.string(output).find('trix-editor') }

it 'calls the default rich text area helper' do
expect(output).to have_selector 'trix-editor[input="personal_info"]'
end

it 'renders rich text input classes matching Tramway form colors' do
expect(editor[:class].split).to include(*RICH_TEXT_AREA_CLASSES)
end

it 'marks Trix editors for Tramway toolbar styling' do
expect(output).to have_selector 'trix-editor[data-tramway-rich-text-area="true"]'
end

context 'with custom classes and unsupported Tramway sizing option' do
let(:output) { builder.rich_text_area :personal_info, class: 'custom-rich-text', size: :large }

it 'preserves custom classes' do
expect(output).to have_selector 'trix-editor.custom-rich-text'
end

it 'does not pass the Tramway sizing option to Action Text' do
expect(output).not_to have_selector 'trix-editor[size]'
end

it 'preserves custom data attributes' do
output = builder.rich_text_area :personal_info, data: { controller: 'mentions' }

expect(output).to have_selector 'trix-editor[data-controller="mentions"][data-tramway-rich-text-area="true"]'
end
end
end

describe '#tramway_field' do
let(:output) { builder.tramway_field(:rich_text_area, :personal_info) }

it 'renders rich text areas from field definitions' do
expect(output).to have_selector 'trix-editor[input="personal_info"]'
end
end

describe '#file_field' do
let(:output) do
builder.file_field :file
Expand Down
2 changes: 1 addition & 1 deletion spec/features/entities/update_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

click_button 'Edit'

expect(page).to have_content('Edit Post')
expect(page).to have_content('Edit Original Post')
expect(page).to have_field('post[title]', with: 'Original Post')
expect(page).to have_field('post[text]', with: 'Original text')
expect(page).to have_field('post[user_id]', type: :hidden, with: User.first.id)
Expand Down
Loading
Loading