Skip to content

Commit 4697e97

Browse files
committed
Leverage consolidate_attrs() with class_ and **kwargs
1 parent 991d8da commit 4697e97

8 files changed

Lines changed: 126 additions & 92 deletions

File tree

shiny/experimental/ui/_card.py

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,6 @@
1212
from ._fill import bind_fill_role
1313
from ._utils import consolidate_attrs
1414

15-
# class Page:
16-
# x: Tag
17-
# def __init__(
18-
# self,
19-
# x: Tag,
20-
# ):
21-
# self.x = x
22-
23-
24-
# class Fragment:
25-
# x: Tag
26-
# page: Page
27-
# def __init__(
28-
# self,
29-
# x: Tag,
30-
# page: Page,
31-
# ):
32-
# self.x = x
33-
# self.page = page
34-
35-
36-
# def as_fragment(x: Tag, page: Page) -> Fragment:
37-
# return Fragment(x=x, page=page)
38-
3915

4016
# A Bootstrap card component
4117
#
@@ -97,14 +73,14 @@ def card(
9773
height: Optional[CssUnit] = None,
9874
max_height: Optional[CssUnit] = None,
9975
fill: bool = True,
100-
class_: Optional[str] = None, # Applies after `bind_fill_role()`
76+
class_: Optional[str] = None,
10177
wrapper: WrapperCallable | None | MISSING_TYPE = MISSING,
10278
**kwargs: TagAttrValue,
10379
) -> Tag:
10480
if isinstance(wrapper, MISSING_TYPE):
10581
wrapper = card_body
10682

107-
attrs, children = consolidate_attrs(*args)
83+
attrs, children = consolidate_attrs(*args, class_=class_, **kwargs)
10884
children = wrap_children_in_card(*children, wrapper=wrapper)
10985

11086
tag = div(
@@ -119,14 +95,9 @@ def card(
11995
*attrs,
12096
full_screen_toggle() if full_screen else None,
12197
card_js_init(),
122-
**kwargs,
12398
)
12499

125-
tag = bind_fill_role(tag, container=True, item=fill)
126-
# Give the user an opportunity to override the classes added by bind_fill_role()
127-
if class_ is not None:
128-
tag.add_class(class_)
129-
return tag
100+
return bind_fill_role(tag, container=True, item=fill)
130101

131102

132103
def card_js_init() -> Tag:

shiny/experimental/ui/_card_item.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def card_body(
7777
height: Optional[CssUnit] = None,
7878
gap: Optional[CssUnit] = None,
7979
fill: bool = True,
80-
class_: Optional[str] = None, # Applies after `bind_fill_role()`
80+
class_: Optional[str] = None,
8181
**kwargs: TagAttrValue,
8282
) -> CardItem:
8383
if isinstance(max_height_full_screen, MISSING_TYPE):
@@ -102,16 +102,13 @@ def card_body(
102102
"class": "card-body",
103103
"style": css(**div_style_args),
104104
},
105+
class_=class_,
105106
**kwargs,
106107
)
107108

108-
tag = bind_fill_role(tag, item=fill, container=fillable)
109-
110-
# Give the user an opportunity to override the classes added by bind_fill_role()
111-
if class_ is not None:
112-
tag.add_class(class_)
113-
114-
return CardItem(tag)
109+
return CardItem(
110+
bind_fill_role(tag, item=fill, container=fillable),
111+
)
115112

116113

117114
# https://mypy.readthedocs.io/en/stable/protocols.html#callback-protocols
@@ -248,7 +245,7 @@ def card_image(
248245
href: Optional[str] = None,
249246
border_radius: Literal["top", "bottom", "all", "none"] = "top",
250247
mime_type: Optional[str] = None,
251-
class_: Optional[str] = None, # Applies after `bind_fill_role()`
248+
class_: Optional[str] = None,
252249
height: Optional[CssUnit] = None,
253250
fill: bool = True,
254251
width: Optional[CssUnit] = None,
@@ -288,13 +285,11 @@ def card_image(
288285
},
289286
{"class": card_class_map.get(border_radius, None)},
290287
*args,
288+
class_=class_,
291289
**kwargs,
292290
)
293291

294292
image = bind_fill_role(image, item=fill)
295-
# Give the user an opportunity to override the classes added by bind_fill_role()
296-
if class_ is not None:
297-
image.add_class(class_)
298293

299294
if href is not None:
300295
image = bind_fill_role(tags.a(image, href=href), container=True, item=True)

shiny/experimental/ui/_fill.py

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from htmltools import Tag
66

77
from ._htmldeps import fill_dependency
8+
from ._tag import tag_prepend_class, tag_remove_class
89

910

1011
# TODO-future:
@@ -38,34 +39,3 @@ def bind_fill_role(
3839
tag_remove_class(tag, "html-fill-container")
3940

4041
return tag
41-
42-
43-
# Tag.add_class(x: str) -> Self[Tag]:
44-
# cls = self.attrs.get("class")
45-
# if cls:
46-
# x = cls + " " + x
47-
# self.attrs["class"] = x
48-
# return self
49-
50-
51-
def tag_prepend_class(tag: Tag, x: str) -> Tag:
52-
cls = tag.attrs.get("class")
53-
if cls:
54-
# Prepend the class!
55-
x = x + " " + cls
56-
tag.attrs["class"] = x
57-
return tag
58-
59-
60-
def tag_remove_class(tag: Tag, x: str) -> Tag:
61-
cls = tag.attrs.get("class")
62-
if not cls:
63-
return tag
64-
if x == cls:
65-
tag.attrs.pop("class")
66-
return tag
67-
68-
tag.attrs["class"] = " ".join(
69-
[cls_val for cls_val in cls.split(" ") if cls_val != x]
70-
)
71-
return tag

shiny/experimental/ui/_layout.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ def layout_column_wrap(
6363
height: Optional[CssUnit] = None,
6464
height_mobile: Optional[CssUnit] = None,
6565
gap: Optional[CssUnit] = None,
66-
class_: Optional[str] = None, # Applies after `bind_fill_role()`
66+
class_: Optional[str] = None,
6767
**kwargs: TagAttrValue,
6868
):
69-
attrs, children = consolidate_attrs(*args, **kwargs)
69+
attrs, children = consolidate_attrs(*args, class_=class_, **kwargs)
7070

7171
colspec: str | None = None
7272
if width is not None:
@@ -117,9 +117,4 @@ def layout_column_wrap(
117117
**attrs,
118118
)
119119

120-
tag = bind_fill_role(tag, item=fill)
121-
# Give the user an opportunity to override the classes added by bind_fill_role()
122-
if class_ is not None:
123-
tag.add_class(class_)
124-
125-
return tag
120+
return bind_fill_role(tag, item=fill)

shiny/experimental/ui/_page.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
from typing import Optional, overload
44

5-
from htmltools import TagAttrs, TagChild, css, tags
5+
from htmltools import TagAttrs, TagAttrValue, TagChild, css, tags
66

77
from shiny import ui
88

99
from ._css import CssUnit, validate_css_unit
1010
from ._fill import bind_fill_role
11+
from ._utils import consolidate_attrs
1112

1213

1314
def page_fillable(
@@ -17,9 +18,11 @@ def page_fillable(
1718
fill_mobile: bool = False,
1819
title: Optional[str] = None,
1920
lang: Optional[str] = None,
21+
**kwargs: TagAttrValue,
2022
):
23+
attrs, children = consolidate_attrs(*args, **kwargs)
24+
2125
style = css(
22-
# TODO: validate_css_padding(padding)
2326
padding=validate_css_padding(padding),
2427
gap=validate_css_unit(gap),
2528
__bslib_page_fill_mobile_height="100%" if fill_mobile else "auto",
@@ -28,7 +31,11 @@ def page_fillable(
2831
return ui.page_bootstrap(
2932
tags.head(tags.style("html { height: 100%; }")),
3033
bind_fill_role(
31-
tags.body(class_="bslib-page-fill", style=style, *args),
34+
tags.body(
35+
{"class_": "bslib-page-fill", "style": style},
36+
*children,
37+
**attrs,
38+
),
3239
container=True,
3340
),
3441
title=title,

shiny/experimental/ui/_tag.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from __future__ import annotations
2+
3+
from htmltools import Tag, css
4+
5+
# Tag.add_class(x: str) -> Self[Tag]:
6+
# cls = self.attrs.get("class")
7+
# if cls:
8+
# x = cls + " " + x
9+
# self.attrs["class"] = x
10+
# return self
11+
12+
13+
# Do not export
14+
# "Prepend" version of `tag.add_class(class_)`
15+
def tag_prepend_class(tag: Tag, class_: str) -> Tag:
16+
cls = tag.attrs.get("class")
17+
if cls:
18+
# Prepend the class!
19+
class_ = class_ + " " + cls
20+
tag.attrs["class"] = class_
21+
return tag
22+
23+
24+
def tag_remove_class(tag: Tag, x: str) -> Tag:
25+
"""
26+
Remove a class value from the HTML class attribute.
27+
28+
Parameters
29+
----------
30+
class_
31+
The class name to remove.
32+
33+
Returns
34+
-------
35+
:
36+
The modified tag.
37+
"""
38+
cls = tag.attrs.get("class")
39+
if not cls:
40+
return tag
41+
if x == cls:
42+
tag.attrs.pop("class")
43+
return tag
44+
45+
tag.attrs["class"] = " ".join(
46+
[cls_val for cls_val in cls.split(" ") if cls_val != x]
47+
)
48+
return tag
49+
50+
51+
def tag_add_style(
52+
tag: Tag, *style: str, collapse_: str = "", **kwargs: str | float | None
53+
) -> Tag:
54+
raise RuntimeError("Method currently not used. Remove this error message to use.")
55+
"""
56+
Add a style value(s) to the HTML style attribute.
57+
58+
Parameters
59+
----------
60+
*style
61+
CSS properties and values already properly formatted. Each should already contain trailing semicolons.
62+
collapse_
63+
Character to use to collapse properties into a single string; likely "" (the
64+
default) for style attributes, and either "\n" or None for style blocks.
65+
**kwargs
66+
Named style properties, where the name is the property name and the argument is
67+
the property value. These values are sent to `~htmltools.css` to be formatted.
68+
69+
See Also
70+
--------
71+
~htmltools.css
72+
73+
Returns
74+
-------
75+
:
76+
The modified tag.
77+
"""
78+
css_str = css(collapse_=collapse_, **kwargs)
79+
if css_str is not None:
80+
style += (css_str,)
81+
style_str = " ".join(style)
82+
83+
# Append to existing style value if it exists
84+
style_attr = tag.attrs.get("style")
85+
if style_attr:
86+
style_str = style_attr + " " + style_str
87+
tag.attrs["style"] = style_str
88+
return tag

shiny/experimental/ui/_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
from __future__ import annotations
22

3-
from shiny._typing_extensions import TypeGuard
43
import typing
54

65
from htmltools import TagAttrs, TagAttrValue, TagChild, div
76

7+
from shiny._typing_extensions import TypeGuard
8+
89
T = typing.TypeVar("T", bound=TagChild)
910

1011
# Export this type from htmltools
1112
TagAttrDict = typing.Dict[str, str]
1213

1314

14-
# TODO-barret; leverage `consolidate_attrs()` with `class_` and `**kwargs`
1515
def consolidate_attrs(
1616
*args: T | TagAttrs,
1717
**kwargs: TagAttrValue,

shiny/experimental/ui/_valuebox.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import numbers
4+
import typing
45
from typing import Callable, Optional
56

67
from htmltools import Tag, TagAttrs, TagAttrValue, TagChild, css, div
@@ -10,7 +11,7 @@
1011
from ._css import CssUnit, validate_css_unit
1112
from ._fill import bind_fill_role
1213
from ._layout import layout_column_wrap
13-
from ._utils import is_01_scalar
14+
from ._utils import consolidate_attrs, is_01_scalar
1415

1516

1617
# It seems to be to use % over fr here since there is no gap on the grid
@@ -39,9 +40,11 @@ def value_box(
3940
height: Optional[CssUnit] = None,
4041
max_height: Optional[CssUnit] = None,
4142
fill: bool = True,
42-
class_: Optional[str] = None, # Applies after `bind_fill_role()` inside `card()`
43+
class_: Optional[str] = None,
4344
**kwargs: TagAttrValue,
4445
) -> Tag:
46+
attrs, children = consolidate_attrs(*args, **kwargs)
47+
4548
if showcase_layout is None:
4649
showcase_layout = showcase_left_center()
4750
if isinstance(title, str) or isinstance(title, numbers.Number):
@@ -52,7 +55,7 @@ def value_box(
5255
contents = div(
5356
title,
5457
value,
55-
*args,
58+
*children,
5659
class_="value-box-area",
5760
)
5861
contents = bind_fill_role(contents, container=True, item=True)
@@ -66,12 +69,17 @@ def value_box(
6669

6770
return card(
6871
contents,
72+
# Must be before `attrs` so that `class_` is applied before any `attrs` values
73+
{"class": f"bslib-value-box border-0{theme_class_str}"},
74+
# TODO-barret: Why is `TagAttrDict` not accepted here as a `TagAttrs` object?
75+
# TagAttrDict: Dict[str, str]
76+
# TagAttrs: Dict[str, str | float | bool | None]
77+
typing.cast(TagAttrs, attrs),
6978
class_=f"bslib-value-box border-0{theme_class_str}{class_str}",
7079
full_screen=full_screen,
7180
height=height,
7281
max_height=max_height,
7382
fill=fill,
74-
**kwargs,
7583
)
7684

7785

0 commit comments

Comments
 (0)