Skip to content

Commit 27930dd

Browse files
committed
feat: add input_class and control_class props to form components
FormField, Select, Textarea now accept input_class (appended to the main input element's class string). CheckboxGroup accepts control_class (appended to the wrapping <div class="control">). Both Jinja2 and Cotton template sets updated. Existing tests updated to pass empty defaults; new tests verify prop applied to correct element and default renders without extra class. Closes #1
1 parent dd3de85 commit 27930dd

10 files changed

Lines changed: 221 additions & 16 deletions

File tree

src/cf_ui/templates/cotton/bulma/cf/checkbox-group.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
<c-vars name label error="" choices="[]" selected="[]" class="" />
1+
<c-vars name label error="" choices="[]" selected="[]" control_class="" class="" />
22
<div class="field {{ class }}">
33
<label class="label">{{ label }}</label>
4-
<div class="control">
4+
<div class="control{% if control_class %} {{ control_class }}{% endif %}">
55
{% for choice in choices %}
66
<label class="checkbox mr-3">
77
<input type="checkbox"

src/cf_ui/templates/cotton/bulma/cf/form-field.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
<c-vars name label value="" error="" type="text" required="false" class="" />
1+
<c-vars name label value="" error="" type="text" required="false" input_class="" class="" />
22
<div class="field {{ class }}">
33
<label class="label" for="{{ name }}">{{ label }}</label>
44
<div class="control">
5-
<input class="input{% if error %} is-danger{% endif %}"
5+
<input class="input{% if error %} is-danger{% endif %}{% if input_class %} {{ input_class }}{% endif %}"
66
id="{{ name }}"
77
type="{{ type }}"
88
name="{{ name }}"

src/cf_ui/templates/cotton/bulma/cf/select.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
<c-vars name label value="" error="" options="[]" class="" />
1+
<c-vars name label value="" error="" options="[]" input_class="" class="" />
22
<div class="field {{ class }}">
33
<label class="label" for="{{ name }}">{{ label }}</label>
44
<div class="control">
55
<div class="select{% if error %} is-danger{% endif %} is-fullwidth">
6-
<select id="{{ name }}" name="{{ name }}">
6+
<select id="{{ name }}" name="{{ name }}"{% if input_class %} class="{{ input_class }}"{% endif %}>
77
{% for opt in options %}
88
<option value="{{ opt.value }}"{% if opt.value|stringformat:"s" == value|stringformat:"s" %} selected{% endif %}>
99
{{ opt.label }}

src/cf_ui/templates/cotton/bulma/cf/textarea.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
<c-vars name label value="" error="" rows="4" class="" />
1+
<c-vars name label value="" error="" rows="4" input_class="" class="" />
22
<div class="field {{ class }}">
33
<label class="label" for="{{ name }}">{{ label }}</label>
44
<div class="control">
5-
<textarea class="textarea{% if error %} is-danger{% endif %}"
5+
<textarea class="textarea{% if error %} is-danger{% endif %}{% if input_class %} {{ input_class }}{% endif %}"
66
id="{{ name }}"
77
name="{{ name }}"
88
rows="{{ rows }}">{{ value }}</textarea>

src/cf_ui/templates/jinja/bulma/CheckboxGroup.jinja

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
{#def name, label, choices=[], selected=[], error="", extra_class="" #}
1+
{#def name, label, choices=[], selected=[], error="", extra_class="", control_class="" #}
22
<div class="field {{ extra_class }}">
33
<label class="label">{{ label }}</label>
4-
<div class="control">
4+
<div class="control{% if control_class %} {{ control_class }}{% endif %}">
55
{% for choice in choices %}
66
<label class="checkbox mr-3">
77
<input type="checkbox"

src/cf_ui/templates/jinja/bulma/FormField.jinja

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
{#def name, label, value="", error="", type="text", required=false, extra_class="" #}
1+
{#def name, label, value="", error="", type="text", required=false, extra_class="", input_class="" #}
22
<div class="field {{ extra_class }}">
33
<label class="label" for="{{ name }}">{{ label }}</label>
44
<div class="control">
5-
<input class="input{% if error %} is-danger{% endif %}"
5+
<input class="input{% if error %} is-danger{% endif %}{% if input_class %} {{ input_class }}{% endif %}"
66
id="{{ name }}"
77
type="{{ type }}"
88
name="{{ name }}"

src/cf_ui/templates/jinja/bulma/Select.jinja

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
{#def name, label, value="", error="", options=[], extra_class="" #}
1+
{#def name, label, value="", error="", options=[], extra_class="", input_class="" #}
22
<div class="field {{ extra_class }}">
33
<label class="label" for="{{ name }}">{{ label }}</label>
44
<div class="control">
55
<div class="select{% if error %} is-danger{% endif %} is-fullwidth">
6-
<select id="{{ name }}" name="{{ name }}">
6+
<select id="{{ name }}" name="{{ name }}"{% if input_class %} class="{{ input_class }}"{% endif %}>
77
{% for opt in options %}
88
<option value="{{ opt.value }}"{% if opt.value|string == value|string %} selected{% endif %}>
99
{{ opt.label }}

src/cf_ui/templates/jinja/bulma/Textarea.jinja

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
{#def name, label, value="", error="", rows=4, extra_class="" #}
1+
{#def name, label, value="", error="", rows=4, extra_class="", input_class="" #}
22
<div class="field {{ extra_class }}">
33
<label class="label" for="{{ name }}">{{ label }}</label>
44
<div class="control">
5-
<textarea class="textarea{% if error %} is-danger{% endif %}"
5+
<textarea class="textarea{% if error %} is-danger{% endif %}{% if input_class %} {{ input_class }}{% endif %}"
66
id="{{ name }}"
77
name="{{ name }}"
88
rows="{{ rows }}">{{ value }}</textarea>

tests/unit/cotton/test_forms.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,73 @@ def test_checkbox_group_cotton_renders_choices(cotton_render):
7272
)
7373
assert "Apple" in html
7474
assert "checked" in html
75+
76+
77+
# ── Granular class prop tests ────────────────────────────────────────────────
78+
79+
80+
def test_form_field_cotton_input_class_applied(cotton_render):
81+
html = cotton_render(
82+
"cf/form-field.html",
83+
name="email",
84+
label="Email",
85+
value="",
86+
error="",
87+
type="text",
88+
required="false",
89+
input_class="is-rounded",
90+
)
91+
assert "is-rounded" in html
92+
93+
94+
def test_form_field_cotton_input_class_default_omitted(cotton_render):
95+
html = cotton_render(
96+
"cf/form-field.html",
97+
name="email",
98+
label="Email",
99+
value="",
100+
error="",
101+
type="text",
102+
required="false",
103+
input_class="",
104+
)
105+
assert "is-rounded" not in html
106+
107+
108+
def test_select_cotton_input_class_applied(cotton_render):
109+
html = cotton_render(
110+
"cf/select.html",
111+
name="choice",
112+
label="Choose",
113+
value="",
114+
error="",
115+
options=[],
116+
input_class="my-select",
117+
)
118+
assert "my-select" in html
119+
120+
121+
def test_textarea_cotton_input_class_applied(cotton_render):
122+
html = cotton_render(
123+
"cf/textarea.html",
124+
name="bio",
125+
label="Bio",
126+
value="",
127+
error="",
128+
rows="4",
129+
input_class="has-fixed-size",
130+
)
131+
assert "has-fixed-size" in html
132+
133+
134+
def test_checkbox_group_cotton_control_class_applied(cotton_render):
135+
html = cotton_render(
136+
"cf/checkbox-group.html",
137+
name="fruits",
138+
label="Fruits",
139+
choices=[{"value": "a", "label": "Apple"}],
140+
selected=[],
141+
error="",
142+
control_class="is-flex",
143+
)
144+
assert "is-flex" in html

tests/unit/jinja/test_forms.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ def test_form_field_renders_label(render):
88
type="text",
99
required=False,
1010
extra_class="",
11+
input_class="",
1112
)
1213
assert "Email Address" in html
1314
assert '<label class="label"' in html
@@ -23,6 +24,7 @@ def test_form_field_renders_input(render):
2324
type="email",
2425
required=False,
2526
extra_class="",
27+
input_class="",
2628
)
2729
assert 'name="email"' in html
2830
assert 'type="email"' in html
@@ -39,6 +41,7 @@ def test_form_field_shows_error(render):
3941
type="text",
4042
required=False,
4143
extra_class="",
44+
input_class="",
4245
)
4346
assert "This field is required." in html
4447
assert "is-danger" in html
@@ -55,6 +58,7 @@ def test_form_field_no_error_omits_danger(render):
5558
type="text",
5659
required=False,
5760
extra_class="",
61+
input_class="",
5862
)
5963
assert "is-danger" not in html
6064

@@ -69,6 +73,7 @@ def test_form_field_required_attribute(render):
6973
type="text",
7074
required=True,
7175
extra_class="",
76+
input_class="",
7277
)
7378
assert "required" in html
7479

@@ -83,6 +88,7 @@ def test_select_renders_options(render):
8388
error="",
8489
options=options,
8590
extra_class="",
91+
input_class="",
8692
)
8793
assert "Option A" in html
8894
assert "Option B" in html
@@ -99,6 +105,7 @@ def test_select_shows_error(render):
99105
error="Required",
100106
options=[],
101107
extra_class="",
108+
input_class="",
102109
)
103110
assert "Required" in html
104111
assert "is-danger" in html
@@ -113,6 +120,7 @@ def test_textarea_renders_value(render):
113120
error="",
114121
rows=4,
115122
extra_class="",
123+
input_class="",
116124
)
117125
assert "Hello world" in html
118126
assert 'name="bio"' in html
@@ -128,6 +136,7 @@ def test_textarea_shows_error(render):
128136
error="Too short",
129137
rows=4,
130138
extra_class="",
139+
input_class="",
131140
)
132141
assert "Too short" in html
133142
assert "is-danger" in html
@@ -143,6 +152,7 @@ def test_checkbox_group_renders_choices(render):
143152
selected=["a"],
144153
error="",
145154
extra_class="",
155+
control_class="",
146156
)
147157
assert "Apple" in html
148158
assert "Banana" in html
@@ -160,6 +170,7 @@ def test_checkbox_group_unchecked_item(render):
160170
selected=["a"],
161171
error="",
162172
extra_class="",
173+
control_class="",
163174
)
164175
assert html.count("checked") == 1
165176

@@ -173,6 +184,130 @@ def test_checkbox_group_shows_error(render):
173184
selected=[],
174185
error="Select at least one",
175186
extra_class="",
187+
control_class="",
176188
)
177189
assert "Select at least one" in html
178190
assert "is-danger" in html
191+
192+
193+
# ── Granular class prop tests ────────────────────────────────────────────────
194+
195+
196+
def test_form_field_input_class_applied(render):
197+
html = render(
198+
"FormField.jinja",
199+
name="email",
200+
label="Email",
201+
value="",
202+
error="",
203+
type="text",
204+
required=False,
205+
extra_class="",
206+
input_class="is-rounded",
207+
)
208+
assert "is-rounded" in html
209+
assert 'class="input is-rounded"' in html
210+
211+
212+
def test_form_field_input_class_default_omitted(render):
213+
html = render(
214+
"FormField.jinja",
215+
name="email",
216+
label="Email",
217+
value="",
218+
error="",
219+
type="text",
220+
required=False,
221+
extra_class="",
222+
input_class="",
223+
)
224+
assert 'class="input"' in html
225+
226+
227+
def test_select_input_class_applied(render):
228+
html = render(
229+
"Select.jinja",
230+
name="choice",
231+
label="Choose",
232+
value="",
233+
error="",
234+
options=[],
235+
extra_class="",
236+
input_class="my-select",
237+
)
238+
assert 'class="my-select"' in html
239+
240+
241+
def test_select_input_class_default_omitted(render):
242+
html = render(
243+
"Select.jinja",
244+
name="choice",
245+
label="Choose",
246+
value="",
247+
error="",
248+
options=[],
249+
extra_class="",
250+
input_class="",
251+
)
252+
# No class attribute on the bare <select> when input_class is empty
253+
assert "<select " not in html or 'class=""' not in html
254+
255+
256+
def test_textarea_input_class_applied(render):
257+
html = render(
258+
"Textarea.jinja",
259+
name="bio",
260+
label="Bio",
261+
value="",
262+
error="",
263+
rows=4,
264+
extra_class="",
265+
input_class="has-fixed-size",
266+
)
267+
assert "has-fixed-size" in html
268+
assert 'class="textarea has-fixed-size"' in html
269+
270+
271+
def test_textarea_input_class_default_omitted(render):
272+
html = render(
273+
"Textarea.jinja",
274+
name="bio",
275+
label="Bio",
276+
value="",
277+
error="",
278+
rows=4,
279+
extra_class="",
280+
input_class="",
281+
)
282+
assert 'class="textarea"' in html
283+
284+
285+
def test_checkbox_group_control_class_applied(render):
286+
choices = [{"value": "a", "label": "Apple"}]
287+
html = render(
288+
"CheckboxGroup.jinja",
289+
name="fruits",
290+
label="Fruits",
291+
choices=choices,
292+
selected=[],
293+
error="",
294+
extra_class="",
295+
control_class="is-flex",
296+
)
297+
assert "is-flex" in html
298+
assert 'class="control is-flex"' in html
299+
300+
301+
def test_checkbox_group_control_class_default_omitted(render):
302+
choices = [{"value": "a", "label": "Apple"}]
303+
html = render(
304+
"CheckboxGroup.jinja",
305+
name="fruits",
306+
label="Fruits",
307+
choices=choices,
308+
selected=[],
309+
error="",
310+
extra_class="",
311+
control_class="",
312+
)
313+
assert 'class="control"' in html

0 commit comments

Comments
 (0)