Skip to content

Commit

Permalink
feat(catalog): Add code style for theme and SelectField
Browse files Browse the repository at this point in the history
  • Loading branch information
paveldedik committed May 24, 2024
1 parent 8be3665 commit f8c798b
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 30 deletions.
116 changes: 93 additions & 23 deletions ludic/catalog/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@
from dataclasses import dataclass
from typing import Any, Literal, get_type_hints, override

from ludic.attrs import Attrs, FormAttrs, InputAttrs, TextAreaAttrs
from ludic.html import div, form, input, label, style, textarea
from ludic.attrs import (
Attrs,
FormAttrs,
InputAttrs,
OptionAttrs,
SelectAttrs,
TextAreaAttrs,
)
from ludic.html import div, form, input, label, option, select, style, textarea
from ludic.types import (
BaseElement,
ComplexChildren,
Expand All @@ -24,6 +31,10 @@
}


class SelectMetaAttrs(SelectAttrs):
options: list[OptionAttrs]


@dataclass
class FieldMeta:
"""Class to be used in attributes annotations to create form fields.
Expand All @@ -44,25 +55,33 @@ class CustomerAttrs(Attrs):
"""

label: str | Literal["auto"] | None = "auto"
kind: Literal["input", "textarea", "checkbox"] = "input"
kind: Literal["input", "textarea", "checkbox", "select"] = "input"
type: Literal["text", "email", "password", "hidden"] = "text"
attrs: InputAttrs | TextAreaAttrs | None = None
attrs: InputAttrs | TextAreaAttrs | SelectMetaAttrs | None = None
parser: Callable[[Any], PrimitiveChildren] | None = None

def format(self, key: str, value: Any) -> BaseElement:
attrs = {} if self.attrs is None else dict(self.attrs)
attrs["name"] = key

if self.label:
attrs["label"] = self.label
elif self.label == "auto":
if self.label == "auto":
attrs["label"] = attr_to_camel(key)
elif self.label:
attrs["label"] = self.label

match self.kind:
case "input":
return InputField(value=value, type=self.type, **attrs)
case "checkbox":
return InputField(checked=value, type="checkbox", **attrs)
case "select":
options: list[Option] = []

for option_dict in attrs.pop("options", []): # type: ignore
text = option_dict.pop("label", attr_to_camel(option_dict["value"]))
options.append(Option(text, **option_dict))

return SelectField(*options, **attrs)
case "textarea":
return TextAreaField(value=value, **attrs)

Expand All @@ -89,6 +108,13 @@ class InputFieldAttrs(FieldAttrs, InputAttrs):
"""


class SelectFieldAttrs(FieldAttrs, SelectAttrs):
"""Attributes of the component ``SelectField``.
The attributes are subclassed from :class:`FieldAttrs` and :class:`SelectAttrs`.
"""


class TextAreaFieldAttrs(FieldAttrs, TextAreaAttrs):
"""Attributes of the component ``TextAreaField``.
Expand All @@ -102,21 +128,40 @@ class FormField(Component[TChildren, TAttrs]):
classes = ["form-field"]
styles = style.use(
lambda theme: {
".form-field": {
"label": {
"display": "block",
"font-weight": "bold",
"margin-block-end": theme.sizes.xxs,
},
"input": {
"inline-size": "100%",
"padding": f"{theme.sizes.xxxs} {theme.sizes.xs}",
"border": f"1px solid {theme.colors.light.darken(2)}",
"border-radius": theme.rounding.normal,
"box-sizing": "border-box",
"font-size": theme.fonts.size * 0.9,
},
}
".form-field label": {
"display": "block",
"font-weight": "bold",
"margin-block-end": theme.sizes.xxs,
},
(".form-field input", ".form-field textarea", ".form-field select"): {
"inline-size": "100%",
"padding": f"{theme.sizes.xxxs} {theme.sizes.xs}",
"border": (
f"{theme.borders.thin} solid {theme.colors.light.darken(2)}"
),
"border-radius": theme.rounding.normal,
"box-sizing": "border-box",
"font-size": theme.fonts.size * 0.9,
"transition": "all 0.3s ease-in-out",
"resize": "vertical",
"background-color": theme.colors.white,
},
(".form-field input", ".form-field select"): {
"height": theme.sizes.xxxxl,
},
".form-field input[type=checkbox]": {
"height": theme.sizes.m,
"width": theme.sizes.m,
},
(
".form-field input:focus",
".form-field textarea:focus",
".form-field select:focus",
): {
"outline": "none",
"border-color": theme.colors.light.darken(7),
"border-width": theme.borders.thin,
},
}
)

Expand Down Expand Up @@ -144,6 +189,31 @@ def render(self) -> div:
return div(*elements)


class Option(Component[PrimitiveChildren, OptionAttrs]):
"""Represents the HTML ``option`` element."""

@override
def render(self) -> option:
return option(*self.children, **self.attrs)


class SelectField(FormField[Option, SelectFieldAttrs]):
"""Represents the HTML ``input`` element with an optional ``label`` element."""

@override
def render(self) -> div:
attrs = self.attrs_for(select)
if "name" in self.attrs:
attrs["id"] = self.attrs["name"]

elements: list[ComplexChildren] = []
if text := self.attrs.get("label"):
elements.append(self.create_label(text=text, for_=attrs.get("id", "")))
elements.append(select(*self.children, **attrs))

return div(*elements)


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

Expand All @@ -156,7 +226,7 @@ def render(self) -> div:
elements: list[ComplexChildren] = []
if text := self.attrs.get("label"):
elements.append(self.create_label(text=text, for_=attrs.get("id", "")))
elements.append(textarea(self.children[0], **attrs))
elements.append(textarea(*self.children, **attrs))

return div(*elements)

Expand Down
5 changes: 3 additions & 2 deletions ludic/catalog/loaders.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import NotRequired, override

from ludic.attrs import GlobalAttrs, NoAttrs
from ludic.attrs import GlobalAttrs
from ludic.html import div, style
from ludic.types import AnyChildren, Component


class Loading(Component[AnyChildren, NoAttrs]):
class Loading(Component[AnyChildren, GlobalAttrs]):
classes = ["loader"]
styles = style.use(
lambda theme: {
Expand Down Expand Up @@ -74,6 +74,7 @@ class Loading(Component[AnyChildren, NoAttrs]):
def render(self) -> div:
return div(
div(div(""), div(""), div(""), div(""), classes=["lds-ellipsis"]),
**self.attrs_for(div),
)


Expand Down
2 changes: 1 addition & 1 deletion ludic/catalog/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def render(self) -> table:


def create_rows(
attrs_list: list[TAttrs], spec: type[TAttrs], include_id_column: bool = False
attrs_list: list[TAttrs], spec: type[TAttrs], include_id_column: bool = True
) -> tuple[TableHead, *tuple[TableRow, ...]]:
"""Create table rows from the given attributes.
Expand Down
9 changes: 7 additions & 2 deletions ludic/catalog/typography.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class CodeBlock(Component[str, CodeBlockAttrs]):
styles = style.use(
lambda theme: {
".code-block": {
"background-color": theme.colors.light,
"background-color": theme.code.background_color,
"padding-block": theme.sizes.l,
"padding-inline": theme.sizes.xxl,
"font-size": theme.fonts.size * 0.9,
Expand All @@ -137,7 +137,12 @@ def render(self) -> pre:

if pygments_loaded and (language := self.attrs.get("language")):
lexer = get_lexer_by_name(language)
formatter = HtmlFormatter(noclasses=True, nobackground=True, nowrap=True)
formatter = HtmlFormatter(
noclasses=True,
nobackground=True,
nowrap=True,
style=self.theme.code.style,
)
block = Safe(highlight(content, lexer, formatter))
return pre(block, **self.attrs_for(pre))
else:
Expand Down
5 changes: 4 additions & 1 deletion ludic/catalog/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ def remove_whitespaces(text: str) -> str:
Returns:
str: The text without leading whitespaces.
"""
by_line = text.split("\n")
if not text:
return text

by_line = text.splitlines()
min_whitespaces = min(len(line) - len(line.lstrip()) for line in by_line if line)
return "\n".join(line[min_whitespaces:].rstrip() for line in by_line).strip()
1 change: 1 addition & 0 deletions ludic/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class select(Element[AnyChildren, SelectAttrs]):


class textarea(Element[PrimitiveChildren, TextAreaAttrs]):
always_pair = True
html_name = "textarea"


Expand Down
11 changes: 10 additions & 1 deletion ludic/styles/themes.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,19 @@ class Layouts:
frame: Frame = field(default_factory=Frame)


@dataclass
class CodeBlock:
"""Code block config for a theme."""

background_color: Color = Color("#f8f8f8")
style: str | type = "default"


@dataclass
class Theme(metaclass=ABCMeta):
"""An abstract base class for theme configuration."""

measure: Size = Size(110, "ch")
measure: Size = Size(70, "ch")
line_height: float = 1.5

fonts: Fonts = field(default_factory=Fonts)
Expand All @@ -180,6 +188,7 @@ class Theme(metaclass=ABCMeta):
headers: Headers = field(default_factory=Headers)

layouts: Layouts = field(default_factory=Layouts)
code: CodeBlock = field(default_factory=CodeBlock)

def __eq__(self, other: object) -> bool:
return isinstance(other, Theme) and self.name == other.name
Expand Down

0 comments on commit f8c798b

Please sign in to comment.