Skip to content

Commit 91d68f2

Browse files
committed
Simplify Radio to single-component API, remove RadioCollection
Radio now works like Checkbox: call radio(value) to get a single component with automatic name/value/checked binding. The user controls iteration and HTML structure. Removes RadioCollection and RadioOption which added dispatch complexity without value.
1 parent 05760be commit 91d68f2

5 files changed

Lines changed: 36 additions & 132 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -417,15 +417,15 @@ class SignupForm < Components::Form
417417
end
418418
end
419419

420-
# Radio groups: pass [value, label] options and iterate. Superform
421-
# handles the name, value, and checked state automatically.
420+
# Radio groups: iterate your options and call radio(value) on the field.
421+
# Superform handles the name, value, and checked state automatically.
422422
fieldset do
423423
legend { "Plan" }
424-
Field(:plan_id).radio(*Plan.pluck(:id, :name)).each do |plan|
424+
Plan.all.each do |plan|
425425
label do
426-
plan.radio
426+
Field(:plan_id).radio(plan.id)
427427
whitespace
428-
plain plan.label
428+
plain plan.name
429429
end
430430
end
431431
end

lib/superform/rails/components/radio.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,17 @@ module Superform
22
module Rails
33
module Components
44
class Radio < Field
5-
def initialize(field, value:, index: nil, attributes: {})
5+
def initialize(field, value:, attributes: {})
66
super(field, attributes: attributes)
77
@value = value
8-
@index = index
98
end
109

1110
def view_template(&)
1211
input(type: :radio, **attributes)
1312
end
1413

1514
def field_attributes
16-
id = @index ? "#{dom.id}_#{@index}" : dom.id
17-
{ id: id, name: dom.name, value: @value, checked: field.value == @value }
15+
{ id: dom.id, name: dom.name, value: @value, checked: field.value == @value }
1816
end
1917
end
2018
end

lib/superform/rails/field.rb

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,8 @@ def file(*, **)
143143
input(*, **, type: :file)
144144
end
145145

146-
def radio(*args, **attributes)
147-
if args.length == 1 && !args.first.is_a?(Array)
148-
# Single scalar value: Field(:gender).radio("male")
149-
Components::Radio.new(field, value: args.first, attributes: attributes)
150-
else
151-
# Collection of options: Field(:status).radio("active", "inactive", "pending")
152-
# or Field(:status).radio(["active", "Active"], ["inactive", "Inactive"])
153-
RadioCollection.new(field: field, options: args)
154-
end
146+
def radio(value, **attributes)
147+
Components::Radio.new(field, value: value, attributes: attributes)
155148
end
156149

157150
# Rails compatibility aliases

lib/superform/rails/radio_collection.rb

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 27 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,37 @@
11
RSpec.describe Superform::Rails::Components::Radio, type: :view do
2-
describe 'single radio' do
3-
let(:object) { double('object', gender: "male") }
4-
let(:field) do
5-
Superform::Rails::Field.new(:gender, parent: nil, object: object)
6-
end
7-
8-
it 'renders a checked radio when value matches' do
9-
html = render(field.radio("male"))
10-
expect(html).to include('type="radio"')
11-
expect(html).to include('name="gender"')
12-
expect(html).to include('value="male"')
13-
expect(html).to include('checked')
14-
end
15-
16-
it 'renders an unchecked radio when value does not match' do
17-
html = render(field.radio("female"))
18-
expect(html).to include('value="female"')
19-
expect(html).not_to include('checked')
20-
end
21-
22-
it 'accepts custom attributes' do
23-
html = render(field.radio("male", class: "radio-input"))
24-
expect(html).to include('class="radio-input"')
25-
end
2+
let(:object) { double('object', gender: "male") }
3+
let(:field) do
4+
Superform::Rails::Field.new(:gender, parent: nil, object: object)
265
end
276

28-
describe 'radio collection' do
29-
let(:object) { double('object', status: "active") }
30-
let(:field) do
31-
Superform::Rails::Field.new(:status, parent: nil, object: object)
32-
end
33-
34-
it 'returns an enumerable' do
35-
collection = field.radio("active", "inactive", "pending")
36-
expect(collection).to respond_to(:each)
37-
end
38-
39-
it 'yields options with value, label, and radio' do
40-
options = field.radio(["active", "Active"], ["inactive", "Inactive"]).to_a
41-
expect(options.length).to eq(2)
42-
expect(options.first.value).to eq("active")
43-
expect(options.first.label).to eq("Active")
44-
expect(options.first).to respond_to(:radio)
45-
end
46-
47-
it 'renders radios with correct checked state' do
48-
html = ""
49-
field.radio(["active", "Active"], ["inactive", "Inactive"], ["pending", "Pending"]).each do |status|
50-
html += render(status.radio)
51-
end
52-
53-
expect(html.scan(/type="radio"/).count).to eq(3)
54-
expect(html.scan(/name="status"/).count).to eq(3)
55-
expect(html.scan(/checked/).count).to eq(1)
56-
expect(html).to match(/<input[^>]*value="active"[^>]*checked/)
57-
expect(html).not_to match(/<input[^>]*value="inactive"[^>]*checked/)
58-
expect(html).not_to match(/<input[^>]*value="pending"[^>]*checked/)
59-
end
60-
61-
it 'generates unique IDs for each radio' do
62-
html = ""
63-
field.radio("active", "inactive").each do |status|
64-
html += render(status.radio)
65-
end
7+
it 'renders a checked radio when value matches' do
8+
html = render(field.radio("male"))
9+
expect(html).to include('type="radio"')
10+
expect(html).to include('name="gender"')
11+
expect(html).to include('value="male"')
12+
expect(html).to include('checked')
13+
end
6614

67-
expect(html).to include('id="status_1"')
68-
expect(html).to include('id="status_2"')
69-
end
15+
it 'renders an unchecked radio when value does not match' do
16+
html = render(field.radio("female"))
17+
expect(html).to include('value="female"')
18+
expect(html).not_to include('checked')
19+
end
7020

71-
it 'works with the label pattern from the docs' do
72-
html = ""
73-
field.radio(["active", "Active"], ["inactive", "Inactive"]).each do |status|
74-
# Simulating: label { status.radio; whitespace; plain status.value.humanize }
75-
html += render(status.radio)
76-
end
21+
it 'accepts custom attributes' do
22+
html = render(field.radio("male", class: "radio-input"))
23+
expect(html).to include('class="radio-input"')
24+
end
7725

78-
expect(html).to include('value="active"')
79-
expect(html).to include('value="inactive"')
80-
expect(html.scan(/checked/).count).to eq(1)
26+
it 'renders a group with correct checked state' do
27+
html = ""
28+
["male", "female", "other"].each do |value|
29+
html += render(field.radio(value))
8130
end
8231

83-
it 'works with single-value options (value used as label)' do
84-
options = field.radio("active", "inactive").to_a
85-
expect(options.first.value).to eq("active")
86-
expect(options.first.label).to eq("active")
87-
end
32+
expect(html.scan(/type="radio"/).count).to eq(3)
33+
expect(html.scan(/name="gender"/).count).to eq(3)
34+
expect(html.scan(/checked/).count).to eq(1)
35+
expect(html).to match(/<input[^>]*value="male"[^>]*checked/)
8836
end
8937
end

0 commit comments

Comments
 (0)