Skip to content

Commit

Permalink
feat(catalog): Add ChoiceField
Browse files Browse the repository at this point in the history
  • Loading branch information
paveldedik committed Aug 12, 2024
1 parent 9d7f329 commit 87c27a7
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 2 deletions.
67 changes: 66 additions & 1 deletion ludic/catalog/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections.abc import Callable, Mapping
from dataclasses import dataclass
from typing import Any, Literal, get_type_hints, override
from typing import Any, Literal, NotRequired, get_type_hints, override

from ludic.attrs import (
Attrs,
Expand All @@ -13,6 +13,7 @@
TextAreaAttrs,
)
from ludic.base import BaseElement
from ludic.catalog.typography import Paragraph
from ludic.components import Component
from ludic.html import div, form, input, label, option, select, style, textarea
from ludic.types import (
Expand Down Expand Up @@ -214,6 +215,70 @@ def render(self) -> div:
return div(*elements)


class ChoiceFieldAttrs(FieldAttrs, InputAttrs):
"""Attributes of the component ``InputField``.
The attributes are subclassed from :class:`FieldAttrs` and :class:`InputAttrs`.
"""

choices: list[tuple[str, str]]
selected: NotRequired[str]


class ChoiceField(FormField[NoChildren, ChoiceFieldAttrs]):
"""Represents the HTML ``input`` element with an optional ``label`` element."""

styles = style.use(
lambda theme: {
".form-field p.form-label": {
"margin-block-end": theme.sizes.xxs,
"font-weight": "bold",
},
".form-field * + *": {
"margin-block-start": theme.sizes.xxxxs,
},
".form-field input[type=radio]": {
"inline-size": "auto",
"vertical-align": "middle",
"height": theme.sizes.m,
"margin-inline": theme.sizes.m,
},
".form-field input[type=radio] + label": {
"display": "inline-block",
"height": theme.sizes.m,
"font-weight": "normal",
"margin-block": "0",
},
}
)

@override
def render(self) -> div:
attrs = self.attrs_for(input)
attrs.setdefault("type", "radio")

elements: list[ComplexChildren] = []

if text := self.attrs.get("label"):
elements.append(Paragraph(text, classes=["form-label"]))

for value, text in self.attrs["choices"]:
elements.append(
div(
input(
id=value,
value=value,
checked=bool(value and value == self.attrs.get("selected")),
**attrs,
),
label(text, for_=value),
classes=["choice-field"],
)
)

return div(*elements)


class TextAreaField(FormField[PrimitiveChildren, TextAreaFieldAttrs]):
"""Represents the HTML ``textarea`` element with an optional ``label`` element."""

Expand Down
25 changes: 24 additions & 1 deletion tests/test_catalog.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ludic.catalog.forms import Form, InputField, TextAreaField
from ludic.catalog.forms import ChoiceField, Form, InputField, TextAreaField
from ludic.catalog.headers import H1, H2, H3, H4, Anchor
from ludic.catalog.items import Key, Pairs, Value
from ludic.catalog.messages import (
Expand Down Expand Up @@ -217,3 +217,26 @@ def test_messages() -> None:
assert MessageDanger("test").to_html() == (
'<div class="message danger"><div class="content">test</div></div>'
)


def test_choice_field() -> None:
assert ChoiceField(
choices=[("yes", "Yes"), ("no", "No")],
selected="yes",
name="yes_no",
label="Test",
).to_html() == (
'<div class="form-field">'
'<p class="form-label">Test</p>'
'<div class="choice-field">'
'<input '
'id="yes" value="yes" checked="checked" name="yes_no" type="radio"'
'>'
'<label for="yes">Yes</label>'
'</div>'
'<div class="choice-field">'
'<input id="no" value="no" name="yes_no" type="radio">'
'<label for="no">No</label>'
'</div>'
'</div>'
) # fmt: skip

0 comments on commit 87c27a7

Please sign in to comment.