Skip to content
Merged
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
89 changes: 89 additions & 0 deletions docs/app/views/docs/form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,95 @@ def view_template
RUBY
end

Heading(level: 2) { "Rails Integration" }

Text do
plain "RubyUI Form components are plain HTML — they work with any form submission strategy. "
plain "The recommended approach for Rails apps is to use "
InlineLink(href: "https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with", target: "_blank") { "form_with" }
plain " to generate the "
code(class: "font-mono text-sm") { "action" }
plain " URL and CSRF token, then pass explicit "
code(class: "font-mono text-sm") { "name" }
plain " / "
code(class: "font-mono text-sm") { "id" }
plain " attributes to each RubyUI input so the browser serialises them correctly. "
plain "Server-side errors can be surfaced by rendering "
code(class: "font-mono text-sm") { "FormFieldError" }
plain " with content from "
code(class: "font-mono text-sm") { "model.errors.full_messages_for(:attr)" }
plain "."
end

Heading(level: 3) { "Minimal Rails form" }
Codeblock(<<~RUBY, syntax: :ruby)
# In your Phlex view, call form_with via helpers:
# form_with(url: users_path, method: :post) passes action + CSRF automatically.
#
# You can also set action and the CSRF token manually:
Form(action: helpers.users_path, method: "post", class: "w-2/3 space-y-6") do
input(type: "hidden", name: "authenticity_token", value: helpers.form_authenticity_token)

FormField do
FormFieldLabel(for: "user_email") { "Email" }
Input(
type: "email",
id: "user_email",
name: "user[email]",
placeholder: "you@example.com",
required: true
)
FormFieldError()
end

Button(type: "submit") { "Continue" }
end
RUBY

Heading(level: 3) { "Devise-style login form" }
Codeblock(<<~RUBY, syntax: :ruby)
# Full sign-in form mirroring Devise session[email] / session[password] params.
# Pass backend errors (e.g. "Invalid email or password") into FormFieldError.
Form(action: helpers.user_session_path, method: "post", class: "space-y-6") do
input(type: "hidden", name: "authenticity_token", value: helpers.form_authenticity_token)

FormField do
FormFieldLabel(for: "session_email") { "Email" }
Input(
type: "email",
id: "session_email",
name: "session[email]",
placeholder: "you@example.com",
autocomplete: "email",
required: true
)
FormFieldError { @error_message }
end

FormField do
FormFieldLabel(for: "session_password") { "Password" }
Input(
type: "password",
id: "session_password",
name: "session[password]",
autocomplete: "current-password",
required: true,
minlength: "8"
)
FormFieldError()
end

FormField do
div(class: "flex items-center gap-2") do
Checkbox(id: "session_remember_me", name: "session[remember_me]", value: "1")
FormFieldLabel(for: "session_remember_me") { "Remember me" }
end
end

Button(type: "submit", class: "w-full") { "Sign in" }
end
RUBY

render Components::ComponentSetup::Tabs.new(component_name: component)

render Docs::ComponentsTable.new(component_files(component))
Expand Down
89 changes: 89 additions & 0 deletions gem/lib/ruby_ui/form/form_docs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,95 @@ def view_template
RUBY
end

Heading(level: 2) { "Rails Integration" }

Text do
plain "RubyUI Form components are plain HTML — they work with any form submission strategy. "
plain "The recommended approach for Rails apps is to use "
InlineLink(href: "https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with", target: "_blank") { "form_with" }
plain " to generate the "
code(class: "font-mono text-sm") { "action" }
plain " URL and CSRF token, then pass explicit "
code(class: "font-mono text-sm") { "name" }
plain " / "
code(class: "font-mono text-sm") { "id" }
plain " attributes to each RubyUI input so the browser serialises them correctly. "
plain "Server-side errors can be surfaced by rendering "
code(class: "font-mono text-sm") { "FormFieldError" }
plain " with content from "
code(class: "font-mono text-sm") { "model.errors.full_messages_for(:attr)" }
plain "."
end

Heading(level: 3) { "Minimal Rails form" }
Codeblock(<<~RUBY, syntax: :ruby)
# In your Phlex view, call form_with via helpers:
# form_with(url: users_path, method: :post) passes action + CSRF automatically.
#
# You can also set action and the CSRF token manually:
Form(action: helpers.users_path, method: "post", class: "w-2/3 space-y-6") do
input(type: "hidden", name: "authenticity_token", value: helpers.form_authenticity_token)

FormField do
FormFieldLabel(for: "user_email") { "Email" }
Input(
type: "email",
id: "user_email",
name: "user[email]",
placeholder: "you@example.com",
required: true
)
FormFieldError()
end

Button(type: "submit") { "Continue" }
end
RUBY

Heading(level: 3) { "Devise-style login form" }
Codeblock(<<~RUBY, syntax: :ruby)
# Full sign-in form mirroring Devise session[email] / session[password] params.
# Pass backend errors (e.g. "Invalid email or password") into FormFieldError.
Form(action: helpers.user_session_path, method: "post", class: "space-y-6") do
input(type: "hidden", name: "authenticity_token", value: helpers.form_authenticity_token)

FormField do
FormFieldLabel(for: "session_email") { "Email" }
Input(
type: "email",
id: "session_email",
name: "session[email]",
placeholder: "you@example.com",
autocomplete: "email",
required: true
)
FormFieldError { @error_message }
end

FormField do
FormFieldLabel(for: "session_password") { "Password" }
Input(
type: "password",
id: "session_password",
name: "session[password]",
autocomplete: "current-password",
required: true,
minlength: "8"
)
FormFieldError()
end

FormField do
div(class: "flex items-center gap-2") do
Checkbox(id: "session_remember_me", name: "session[remember_me]", value: "1")
FormFieldLabel(for: "session_remember_me") { "Remember me" }
end
end

Button(type: "submit", class: "w-full") { "Sign in" }
end
RUBY

render Components::ComponentSetup::Tabs.new(component_name: component)

render Docs::ComponentsTable.new(component_files(component))
Expand Down
2 changes: 1 addition & 1 deletion mcp/data/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -1394,7 +1394,7 @@
"gems": []
},
"install_command": "rails g ruby_ui:component Form",
"docs_markdown": "# Form\n\nBuilding forms with built-in client-side validations.\n\n## Usage\n\n### Example\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n FormFieldLabel { \"Default error\" }\n Input(placeholder: \"Joel Drapper\", required: true, minlength: \"3\") { \"Joel Drapper\" }\n FormFieldHint()\n FormFieldError()\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n### Disabled\n\n```ruby\nFormField do\n FormFieldLabel { \"Disabled\" }\n Input(disabled: true, placeholder: \"Joel Drapper\", required: true, minlength: \"3\") { \"Joel Drapper\" }\nend\n```\n\n### Aria Disabled\n\n```ruby\nFormField do\n FormFieldLabel { \"Aria Disabled\" }\n Input(aria: {disabled: \"true\"}, placeholder: \"Joel Drapper\", required: true, minlength: \"3\") { \"Joel Drapper\" }\nend\n```\n\n### Custom error message\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n FormFieldLabel { \"Custom error message\" }\n Input(placeholder: \"joel@drapper.me\", required: true, data_value_missing: \"Custom error message\")\n FormFieldError()\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n### Backend error\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n FormFieldLabel { \"Backend error\" }\n Input(placeholder: \"Joel Drapper\", required: true)\n FormFieldError { \"Error from backend\" }\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n### Checkbox\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n Checkbox(required: true)\n label(\n class:\n \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n ) { \" Accept terms and conditions \" }\n FormFieldError()\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n### Select\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n FormFieldLabel { \"Select\" }\n Select do\n SelectInput(required: true)\n SelectTrigger do\n SelectValue(placeholder: \"Select a fruit\")\n end\n SelectContent() do\n SelectGroup do\n SelectLabel { \"Fruits\" }\n SelectItem(value: \"apple\") { \"Apple\" }\n SelectItem(value: \"orange\") { \"Orange\" }\n SelectItem(value: \"banana\") { \"Banana\" }\n SelectItem(value: \"watermelon\") { \"Watermelon\" }\n end\n end\n end\n FormFieldError()\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n### Combobox\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n FormFieldLabel { \"Combobox\" }\n\n Combobox do\n ComboboxTrigger placeholder: \"Pick value\"\n\n ComboboxPopover do\n ComboboxSearchInput(placeholder: \"Pick value or type anything\")\n\n ComboboxList do\n ComboboxEmptyState { \"No result\" }\n\n ComboboxListGroup label: \"Fruits\" do\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"apple\", required: true)\n span { \"Apple\" }\n end\n\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"banana\", required: true)\n span { \"Banana\" }\n end\n end\n\n ComboboxListGroup label: \"Vegetable\" do\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"brocoli\", required: true)\n span { \"Broccoli\" }\n end\n\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"carrot\", required: true)\n span { \"Carrot\" }\n end\n end\n\n ComboboxListGroup label: \"Others\" do\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"chocolate\", required: true)\n span { \"Chocolate\" }\n end\n\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"milk\", required: true)\n span { \"Milk\" }\n end\n end\n end\n end\n end\n\n FormFieldError()\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```",
"docs_markdown": "# Form\n\nBuilding forms with built-in client-side validations.\n\n## Usage\n\n### Example\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n FormFieldLabel { \"Default error\" }\n Input(placeholder: \"Joel Drapper\", required: true, minlength: \"3\") { \"Joel Drapper\" }\n FormFieldHint()\n FormFieldError()\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n### Disabled\n\n```ruby\nFormField do\n FormFieldLabel { \"Disabled\" }\n Input(disabled: true, placeholder: \"Joel Drapper\", required: true, minlength: \"3\") { \"Joel Drapper\" }\nend\n```\n\n### Aria Disabled\n\n```ruby\nFormField do\n FormFieldLabel { \"Aria Disabled\" }\n Input(aria: {disabled: \"true\"}, placeholder: \"Joel Drapper\", required: true, minlength: \"3\") { \"Joel Drapper\" }\nend\n```\n\n### Custom error message\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n FormFieldLabel { \"Custom error message\" }\n Input(placeholder: \"joel@drapper.me\", required: true, data_value_missing: \"Custom error message\")\n FormFieldError()\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n### Backend error\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n FormFieldLabel { \"Backend error\" }\n Input(placeholder: \"Joel Drapper\", required: true)\n FormFieldError { \"Error from backend\" }\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n### Checkbox\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n Checkbox(required: true)\n label(\n class:\n \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n ) { \" Accept terms and conditions \" }\n FormFieldError()\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n### Select\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n FormFieldLabel { \"Select\" }\n Select do\n SelectInput(required: true)\n SelectTrigger do\n SelectValue(placeholder: \"Select a fruit\")\n end\n SelectContent() do\n SelectGroup do\n SelectLabel { \"Fruits\" }\n SelectItem(value: \"apple\") { \"Apple\" }\n SelectItem(value: \"orange\") { \"Orange\" }\n SelectItem(value: \"banana\") { \"Banana\" }\n SelectItem(value: \"watermelon\") { \"Watermelon\" }\n end\n end\n end\n FormFieldError()\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n### Combobox\n\n```ruby\nForm(class: \"w-2/3 space-y-6\") do\n FormField do\n FormFieldLabel { \"Combobox\" }\n\n Combobox do\n ComboboxTrigger placeholder: \"Pick value\"\n\n ComboboxPopover do\n ComboboxSearchInput(placeholder: \"Pick value or type anything\")\n\n ComboboxList do\n ComboboxEmptyState { \"No result\" }\n\n ComboboxListGroup label: \"Fruits\" do\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"apple\", required: true)\n span { \"Apple\" }\n end\n\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"banana\", required: true)\n span { \"Banana\" }\n end\n end\n\n ComboboxListGroup label: \"Vegetable\" do\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"brocoli\", required: true)\n span { \"Broccoli\" }\n end\n\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"carrot\", required: true)\n span { \"Carrot\" }\n end\n end\n\n ComboboxListGroup label: \"Others\" do\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"chocolate\", required: true)\n span { \"Chocolate\" }\n end\n\n ComboboxItem do\n ComboboxRadio(name: \"food\", value: \"milk\", required: true)\n span { \"Milk\" }\n end\n end\n end\n end\n end\n\n FormFieldError()\n end\n Button(type: \"submit\") { \"Save\" }\nend\n```\n\n## Rails Integration\n\n### Minimal Rails form\n\n### Devise-style login form",
"examples": [
{
"title": "Example",
Expand Down