Use adhoc Component() less
This commit is contained in:
@@ -4,8 +4,6 @@ Split into core / primitives / domain / filters submodules; this package
|
||||
re-exports the public API so ``from common.components import X`` keeps working.
|
||||
"""
|
||||
|
||||
from common.utils import truncate
|
||||
|
||||
from common.components.core import (
|
||||
Component,
|
||||
HTMLAttribute,
|
||||
@@ -13,41 +11,6 @@ from common.components.core import (
|
||||
_render_element,
|
||||
randomid,
|
||||
)
|
||||
from common.components.primitives import (
|
||||
A,
|
||||
AddForm,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
CsrfInput,
|
||||
Div,
|
||||
ExternalScript,
|
||||
H1,
|
||||
Icon,
|
||||
Input,
|
||||
Modal,
|
||||
ModuleScript,
|
||||
Pill,
|
||||
Popover,
|
||||
PopoverTruncated,
|
||||
SearchField,
|
||||
SimpleTable,
|
||||
Span,
|
||||
Label,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableTd,
|
||||
Template,
|
||||
YearPicker,
|
||||
paginated_table_content,
|
||||
)
|
||||
from common.components.search_select import (
|
||||
DEFAULT_PREFETCH,
|
||||
FilterSelect,
|
||||
LabeledOption,
|
||||
SearchSelect,
|
||||
SearchSelectOption,
|
||||
searchselect_selected,
|
||||
)
|
||||
from common.components.domain import (
|
||||
GameLink,
|
||||
GameStatus,
|
||||
@@ -60,13 +23,53 @@ from common.components.domain import (
|
||||
_resolve_name_with_icon,
|
||||
)
|
||||
from common.components.filters import (
|
||||
FilterBar,
|
||||
PurchaseFilterBar,
|
||||
SessionFilterBar,
|
||||
DeviceFilterBar,
|
||||
FilterBar,
|
||||
PlatformFilterBar,
|
||||
PlayEventFilterBar,
|
||||
PurchaseFilterBar,
|
||||
SessionFilterBar,
|
||||
)
|
||||
from common.components.primitives import (
|
||||
H1,
|
||||
A,
|
||||
AddForm,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
CsrfInput,
|
||||
Div,
|
||||
ExternalScript,
|
||||
Icon,
|
||||
Input,
|
||||
Label,
|
||||
Li,
|
||||
Modal,
|
||||
ModuleScript,
|
||||
Pill,
|
||||
Popover,
|
||||
PopoverTruncated,
|
||||
SearchField,
|
||||
SimpleTable,
|
||||
Span,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableTd,
|
||||
Td,
|
||||
Template,
|
||||
Tr,
|
||||
Ul,
|
||||
YearPicker,
|
||||
paginated_table_content,
|
||||
)
|
||||
from common.components.search_select import (
|
||||
DEFAULT_PREFETCH,
|
||||
FilterSelect,
|
||||
LabeledOption,
|
||||
SearchSelect,
|
||||
SearchSelectOption,
|
||||
searchselect_selected,
|
||||
)
|
||||
from common.utils import truncate
|
||||
|
||||
__all__ = [
|
||||
"truncate",
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.template.defaultfilters import floatformat
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import SafeText, mark_safe
|
||||
|
||||
from common.components.core import Component, HTMLTag
|
||||
from common.components.core import HTMLTag
|
||||
from common.components.primitives import (
|
||||
A,
|
||||
Div,
|
||||
@@ -33,10 +33,9 @@ def GameLink(
|
||||
return Span(
|
||||
attributes=[("class", "truncate-container")],
|
||||
children=[
|
||||
Component(
|
||||
tag_name="a",
|
||||
A(
|
||||
href=link,
|
||||
attributes=[
|
||||
("href", link),
|
||||
("class", "underline decoration-slate-500 sm:decoration-2"),
|
||||
],
|
||||
children=display if isinstance(display, list) else [display],
|
||||
|
||||
@@ -6,8 +6,12 @@ from django.db import models
|
||||
from django.utils.safestring import SafeText, mark_safe
|
||||
|
||||
from common.components.core import Component
|
||||
from common.components.primitives import Label, Span
|
||||
from common.components.search_select import DEFAULT_PREFETCH, FilterSelect, LabeledOption
|
||||
from common.components.primitives import Div, Input, Label, Span
|
||||
from common.components.search_select import (
|
||||
DEFAULT_PREFETCH,
|
||||
FilterSelect,
|
||||
LabeledOption,
|
||||
)
|
||||
|
||||
|
||||
class FilterChoice(NamedTuple):
|
||||
@@ -206,8 +210,7 @@ def _filter_mins_to_hrs(val) -> str:
|
||||
|
||||
def _filter_field(label: str, widget) -> SafeText:
|
||||
"""A labelled filter field: <div><label>…</label>{widget}</div>."""
|
||||
return Component(
|
||||
tag_name="div",
|
||||
return Div(
|
||||
attributes=[("class", "flex flex-col gap-1")],
|
||||
children=[
|
||||
Label(
|
||||
@@ -223,8 +226,7 @@ def _filter_checkbox(name: str, label: str, checked: bool) -> SafeText:
|
||||
return Label(
|
||||
attributes=[("class", "flex items-center gap-2 text-sm text-heading")],
|
||||
children=[
|
||||
Component(
|
||||
tag_name="input",
|
||||
Input(
|
||||
attributes=[
|
||||
("type", "checkbox"),
|
||||
("name", name),
|
||||
@@ -283,13 +285,11 @@ def RangeSlider(
|
||||
point_mode = bool(min_value and max_value and min_value == max_value)
|
||||
initial_mode = "point" if point_mode else "range"
|
||||
|
||||
return Component(
|
||||
tag_name="div",
|
||||
return Div(
|
||||
attributes=[("class", "range-slider-block mb-4")],
|
||||
children=[
|
||||
# ── Label row ──
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", "flex items-center gap-2 mb-1")],
|
||||
children=[
|
||||
Label(
|
||||
@@ -299,8 +299,7 @@ def RangeSlider(
|
||||
],
|
||||
children=[label],
|
||||
),
|
||||
Component(
|
||||
tag_name="input",
|
||||
Input(
|
||||
attributes=[
|
||||
("type", "number"),
|
||||
("name", min_input_id),
|
||||
@@ -324,8 +323,7 @@ def RangeSlider(
|
||||
],
|
||||
children=["–"],
|
||||
),
|
||||
Component(
|
||||
tag_name="input",
|
||||
Input(
|
||||
attributes=[
|
||||
("type", "number"),
|
||||
("name", max_input_id),
|
||||
@@ -379,8 +377,7 @@ def RangeSlider(
|
||||
],
|
||||
),
|
||||
# ── Slider row ──
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[
|
||||
("class", "range-slider relative h-10 select-none mt-1"),
|
||||
("data-mode", initial_mode),
|
||||
@@ -389,8 +386,7 @@ def RangeSlider(
|
||||
("data-step", str(step)),
|
||||
],
|
||||
children=[
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[
|
||||
(
|
||||
"class",
|
||||
@@ -399,8 +395,7 @@ def RangeSlider(
|
||||
),
|
||||
],
|
||||
),
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[
|
||||
(
|
||||
"class",
|
||||
@@ -411,8 +406,7 @@ def RangeSlider(
|
||||
],
|
||||
),
|
||||
# Min handle (hidden in point mode via JS)
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[
|
||||
(
|
||||
"class",
|
||||
@@ -429,8 +423,7 @@ def RangeSlider(
|
||||
],
|
||||
),
|
||||
# Max handle
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[
|
||||
(
|
||||
"class",
|
||||
@@ -480,8 +473,7 @@ def _filter_collapse_button() -> SafeText:
|
||||
|
||||
|
||||
def _filter_action_row(preset_list_url: str, preset_save_url: str) -> SafeText:
|
||||
return Component(
|
||||
tag_name="div",
|
||||
return Div(
|
||||
attributes=[("class", "flex gap-3 items-center")],
|
||||
children=[
|
||||
Component(
|
||||
@@ -521,8 +513,7 @@ def _filter_action_row(preset_list_url: str, preset_save_url: str) -> SafeText:
|
||||
("id", "save-preset-area"),
|
||||
],
|
||||
children=[
|
||||
Component(
|
||||
tag_name="input",
|
||||
Input(
|
||||
attributes=[
|
||||
("type", "text"),
|
||||
("id", "preset-name-input"),
|
||||
@@ -572,8 +563,7 @@ def _filter_action_row(preset_list_url: str, preset_save_url: str) -> SafeText:
|
||||
),
|
||||
],
|
||||
),
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[
|
||||
("id", "preset-dropdown"),
|
||||
("class", "relative"),
|
||||
@@ -594,13 +584,11 @@ def _filter_bar(fields, filter_json, preset_list_url, preset_save_url) -> SafeTe
|
||||
"""Shared collapsible filter-bar chrome. `fields` is the per-entity body
|
||||
(grids, sliders, checkboxes); the shell adds the collapse toggle, the form,
|
||||
the hidden filter-json input and the Apply/Clear/preset action row."""
|
||||
return Component(
|
||||
tag_name="div",
|
||||
return Div(
|
||||
attributes=[("id", "filter-bar"), ("class", "mb-6")],
|
||||
children=[
|
||||
_filter_collapse_button(),
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[
|
||||
("id", "filter-bar-body"),
|
||||
(
|
||||
@@ -617,8 +605,7 @@ def _filter_bar(fields, filter_json, preset_list_url, preset_save_url) -> SafeTe
|
||||
("onsubmit", "return applyFilterBar(event)"),
|
||||
],
|
||||
children=[
|
||||
Component(
|
||||
tag_name="input",
|
||||
Input(
|
||||
attributes=[
|
||||
("type", "hidden"),
|
||||
("id", _FILTER_INPUT_ID),
|
||||
@@ -725,8 +712,7 @@ def FilterBar(
|
||||
price_range_max = max(int(price_aggregate.get("price_max") or 100), 1)
|
||||
|
||||
fields = [
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||
children=[
|
||||
_filter_field(
|
||||
@@ -817,8 +803,7 @@ def FilterBar(
|
||||
min_placeholder="e.g. 1985",
|
||||
max_placeholder="e.g. 2010",
|
||||
),
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", "flex items-end gap-4 mb-4 flex-wrap")],
|
||||
children=[
|
||||
_filter_checkbox("filter-mastered", "Mastered", mastered_value),
|
||||
@@ -970,8 +955,7 @@ def SessionFilterBar(
|
||||
duration_range_max = 200
|
||||
|
||||
fields = [
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||
children=[
|
||||
_filter_field(
|
||||
@@ -1027,8 +1011,7 @@ def SessionFilterBar(
|
||||
min_placeholder="e.g. 30",
|
||||
max_placeholder="e.g. 180",
|
||||
),
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", "flex gap-4 mb-4")],
|
||||
children=[
|
||||
_filter_checkbox("filter-emulated", "Emulated", emulated_value),
|
||||
@@ -1079,8 +1062,7 @@ def PurchaseFilterBar(
|
||||
num_range_min, num_range_max = 0, 10
|
||||
|
||||
fields = [
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||
children=[
|
||||
_filter_field(
|
||||
@@ -1127,42 +1109,48 @@ def PurchaseFilterBar(
|
||||
),
|
||||
],
|
||||
),
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", "flex items-end gap-4 mb-4")],
|
||||
children=[
|
||||
_filter_checkbox("filter-refunded", "Refunded", is_refunded_value),
|
||||
_filter_checkbox("filter-infinite", "Infinite", infinite_value),
|
||||
_filter_checkbox("filter-needs-price-update", "Needs Price Update", needs_price_update_value),
|
||||
_filter_checkbox(
|
||||
"filter-needs-price-update",
|
||||
"Needs Price Update",
|
||||
needs_price_update_value,
|
||||
),
|
||||
],
|
||||
),
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||
children=[
|
||||
_filter_field(
|
||||
"Original Currency",
|
||||
Component(
|
||||
tag_name="input",
|
||||
Input(
|
||||
attributes=[
|
||||
("type", "text"),
|
||||
("name", "filter-price_currency"),
|
||||
("value", price_currency_value),
|
||||
("placeholder", "e.g. USD, EUR"),
|
||||
("class", "w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body"),
|
||||
(
|
||||
"class",
|
||||
"w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_filter_field(
|
||||
"Converted Currency",
|
||||
Component(
|
||||
tag_name="input",
|
||||
Input(
|
||||
attributes=[
|
||||
("type", "text"),
|
||||
("name", "filter-converted_currency"),
|
||||
("value", converted_currency_value),
|
||||
("placeholder", "e.g. USD, EUR"),
|
||||
("class", "w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body"),
|
||||
(
|
||||
"class",
|
||||
"w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -1193,9 +1181,7 @@ def PurchaseFilterBar(
|
||||
return _filter_bar(fields, filter_json, preset_list_url, preset_save_url)
|
||||
|
||||
|
||||
def DeviceFilterBar(
|
||||
filter_json="", preset_list_url="", preset_save_url=""
|
||||
) -> SafeText:
|
||||
def DeviceFilterBar(filter_json="", preset_list_url="", preset_save_url="") -> SafeText:
|
||||
"""Collapsible filter bar for the Device list."""
|
||||
from games.models import Device
|
||||
|
||||
@@ -1204,8 +1190,7 @@ def DeviceFilterBar(
|
||||
type_choice = _filter_get_choice(existing, "type")
|
||||
|
||||
fields = [
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||
children=[
|
||||
_filter_field(
|
||||
@@ -1233,33 +1218,36 @@ def PlatformFilterBar(
|
||||
group_value = existing.get("group", {}).get("value", "")
|
||||
|
||||
fields = [
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||
children=[
|
||||
_filter_field(
|
||||
"Platform Name",
|
||||
Component(
|
||||
tag_name="input",
|
||||
Input(
|
||||
attributes=[
|
||||
("type", "text"),
|
||||
("name", "filter-name"),
|
||||
("value", name_value),
|
||||
("placeholder", "e.g. Nintendo Switch"),
|
||||
("class", "w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body"),
|
||||
(
|
||||
"class",
|
||||
"w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_filter_field(
|
||||
"Platform Group",
|
||||
Component(
|
||||
tag_name="input",
|
||||
Input(
|
||||
attributes=[
|
||||
("type", "text"),
|
||||
("name", "filter-group"),
|
||||
("value", group_value),
|
||||
("placeholder", "e.g. Nintendo"),
|
||||
("class", "w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body"),
|
||||
(
|
||||
"class",
|
||||
"w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -1278,8 +1266,7 @@ def PlayEventFilterBar(
|
||||
days_min, days_max = _parse_range(existing, "days_to_finish")
|
||||
|
||||
fields = [
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||
children=[
|
||||
_filter_field(
|
||||
|
||||
@@ -6,10 +6,9 @@ from django.urls import reverse
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.safestring import SafeText, mark_safe
|
||||
|
||||
from common.components.core import Component, HTMLAttribute, HTMLTag, randomid
|
||||
from common.icons import get_icon
|
||||
from common.utils import truncate
|
||||
from common.components.core import Component, HTMLAttribute, HTMLTag, randomid
|
||||
|
||||
|
||||
_COLOR_CLASSES = {
|
||||
"blue": "text-white bg-brand box-border border border-transparent hover:bg-brand-strong focus:ring-4 focus:ring-brand-medium",
|
||||
@@ -57,8 +56,7 @@ def _popover_html(
|
||||
"dark:bg-purple-800"
|
||||
)
|
||||
|
||||
div = Component(
|
||||
tag_name="div",
|
||||
div = Div(
|
||||
attributes=[
|
||||
("data-popover", ""),
|
||||
("id", id),
|
||||
@@ -66,12 +64,11 @@ def _popover_html(
|
||||
("class", popover_tooltip_class),
|
||||
],
|
||||
children=[
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", "px-3 py-2")],
|
||||
children=[popover_content],
|
||||
),
|
||||
Component(tag_name="div", attributes=[("data-popper-arrow", "")]),
|
||||
Div(attributes=[("data-popper-arrow", "")]),
|
||||
mark_safe( # nosec — intentional HTML comment for Tailwind JIT
|
||||
"<!-- for Tailwind CSS to generate decoration-dotted CSS "
|
||||
"from Python component -->"
|
||||
@@ -323,8 +320,7 @@ def ButtonGroup(buttons: list[dict] | None = None) -> SafeText:
|
||||
)
|
||||
)
|
||||
|
||||
return Component(
|
||||
tag_name="div",
|
||||
return Div(
|
||||
attributes=[("class", "inline-flex rounded-md shadow-xs"), ("role", "group")],
|
||||
children=children,
|
||||
)
|
||||
@@ -339,6 +335,42 @@ def Div(
|
||||
return Component(tag_name="div", attributes=attributes, children=children)
|
||||
|
||||
|
||||
def P(
|
||||
attributes: list[HTMLAttribute] | None = None,
|
||||
children: list[HTMLTag] | HTMLTag | None = None,
|
||||
) -> SafeText:
|
||||
attributes = attributes or []
|
||||
children = children or []
|
||||
return Component(tag_name="p", attributes=attributes, children=children)
|
||||
|
||||
|
||||
def Ul(
|
||||
attributes: list[HTMLAttribute] | None = None,
|
||||
children: list[HTMLTag] | HTMLTag | None = None,
|
||||
) -> SafeText:
|
||||
attributes = attributes or []
|
||||
children = children or []
|
||||
return Component(tag_name="ul", attributes=attributes, children=children)
|
||||
|
||||
|
||||
def Li(
|
||||
attributes: list[HTMLAttribute] | None = None,
|
||||
children: list[HTMLTag] | HTMLTag | None = None,
|
||||
) -> SafeText:
|
||||
attributes = attributes or []
|
||||
children = children or []
|
||||
return Component(tag_name="li", attributes=attributes, children=children)
|
||||
|
||||
|
||||
def Strong(
|
||||
attributes: list[HTMLAttribute] | None = None,
|
||||
children: list[HTMLTag] | HTMLTag | None = None,
|
||||
) -> SafeText:
|
||||
attributes = attributes or []
|
||||
children = children or []
|
||||
return Component(tag_name="strong", attributes=attributes, children=children)
|
||||
|
||||
|
||||
def Input(
|
||||
type: str = "text",
|
||||
attributes: list[HTMLAttribute] | None = None,
|
||||
@@ -600,8 +632,7 @@ def SearchField(
|
||||
],
|
||||
children=["Search"],
|
||||
),
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[("class", "relative")],
|
||||
children=[
|
||||
mark_safe(
|
||||
@@ -612,10 +643,9 @@ def SearchField(
|
||||
'd="m21 21-3.5-3.5M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/>'
|
||||
"</svg></div>"
|
||||
),
|
||||
Component(
|
||||
tag_name="input",
|
||||
Input(
|
||||
type="search",
|
||||
attributes=[
|
||||
("type", "search"),
|
||||
("id", id),
|
||||
("name", id),
|
||||
("value", search_string),
|
||||
@@ -687,8 +717,7 @@ def Modal(
|
||||
) -> SafeText:
|
||||
"""Modal overlay with container. Content (form, buttons) goes in children."""
|
||||
children = children or []
|
||||
outer = Component(
|
||||
tag_name="div",
|
||||
outer = Div(
|
||||
attributes=[
|
||||
("id", modal_id),
|
||||
(
|
||||
@@ -698,8 +727,7 @@ def Modal(
|
||||
),
|
||||
],
|
||||
children=[
|
||||
Component(
|
||||
tag_name="div",
|
||||
Div(
|
||||
attributes=[
|
||||
(
|
||||
"class",
|
||||
@@ -714,13 +742,39 @@ def Modal(
|
||||
return mark_safe(str(outer))
|
||||
|
||||
|
||||
def Td(
|
||||
attributes: list[HTMLAttribute] | None = None,
|
||||
children: list[HTMLTag] | HTMLTag | None = None,
|
||||
) -> SafeText:
|
||||
attributes = attributes or []
|
||||
children = children or []
|
||||
return Component(tag_name="td", attributes=attributes, children=children)
|
||||
|
||||
|
||||
def Tr(
|
||||
attributes: list[HTMLAttribute] | None = None,
|
||||
children: list[HTMLTag] | HTMLTag | None = None,
|
||||
) -> SafeText:
|
||||
attributes = attributes or []
|
||||
children = children or []
|
||||
return Component(tag_name="tr", attributes=attributes, children=children)
|
||||
|
||||
|
||||
def Th(
|
||||
attributes: list[HTMLAttribute] | None = None,
|
||||
children: list[HTMLTag] | HTMLTag | None = None,
|
||||
) -> SafeText:
|
||||
attributes = attributes or []
|
||||
children = children or []
|
||||
return Component(tag_name="th", attributes=attributes, children=children)
|
||||
|
||||
|
||||
def TableTd(
|
||||
children: list[HTMLTag] | HTMLTag | None = None,
|
||||
) -> SafeText:
|
||||
"""Styled table cell."""
|
||||
children = children or []
|
||||
return Component(
|
||||
tag_name="td",
|
||||
return Td(
|
||||
attributes=[("class", "px-6 py-4 min-w-20-char max-w-20-char")],
|
||||
children=children if isinstance(children, list) else [children],
|
||||
)
|
||||
@@ -765,8 +819,7 @@ def TableRow(data: dict | list | None = None) -> SafeText:
|
||||
for i, cell in enumerate(cells):
|
||||
if i == 0:
|
||||
cell_elements.append(
|
||||
Component(
|
||||
tag_name="th",
|
||||
Th(
|
||||
attributes=[
|
||||
("scope", "row"),
|
||||
(
|
||||
@@ -781,7 +834,7 @@ def TableRow(data: dict | list | None = None) -> SafeText:
|
||||
else:
|
||||
cell_elements.append(TableTd(children=[cell]))
|
||||
|
||||
return Component(tag_name="tr", attributes=tr_attrs, children=cell_elements)
|
||||
return Tr(attributes=tr_attrs, children=cell_elements)
|
||||
|
||||
|
||||
def Icon(
|
||||
|
||||
Reference in New Issue
Block a user