Skip to content

Commit 0e01c66

Browse files
gadenbuiecpsievert
andcommitted
chore: Refactor mobile values and is_always_open method
Co-authored-by: Carson <cpsievert1@gmail.com>
1 parent d76f61d commit 0e01c66

File tree

2 files changed

+31
-38
lines changed

2 files changed

+31
-38
lines changed

shiny/ui/_sidebar.py

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import warnings
44
from dataclasses import dataclass, field
5-
from typing import TYPE_CHECKING, Literal, Optional, Union, cast
5+
from typing import TYPE_CHECKING, Literal, Optional, cast
66

77
from htmltools import (
88
HTML,
@@ -53,14 +53,10 @@
5353
* `"always"`: the sidebar is always open and cannot be closed
5454
"""
5555

56-
SidebarOpenValueMobile = Union[SidebarOpenValue, Literal["always-above"]]
57-
58-
_always_open_mobile = ("always", "always-above")
59-
6056

6157
class SidebarOpenSpec(TypedDict):
6258
desktop: SidebarOpenValue
63-
mobile: SidebarOpenValueMobile
59+
mobile: SidebarOpenValue | Literal["always-above"]
6460

6561

6662
@dataclass
@@ -72,7 +68,7 @@ class SidebarOpen:
7268
desktop: SidebarOpenValue = "open"
7369
"""The initial state of the sidebar on desktop screen sizes."""
7470

75-
mobile: SidebarOpenValueMobile = "closed"
71+
mobile: SidebarOpenValue | Literal["always-above"] = "closed"
7672
"""The initial state of the sidebar on mobile screen sizes."""
7773

7874
_VALUES: tuple[SidebarOpenValue, ...] = field(
@@ -82,28 +78,26 @@ class SidebarOpen:
8278
compare=False,
8379
)
8480

85-
@staticmethod
86-
def _values_str(values: tuple[str, ...] | None = None) -> str:
87-
quoted = [f'"{x}"' for x in (values or SidebarOpen._VALUES)]
88-
ret = ""
89-
for i, value in enumerate(quoted):
90-
if i > 0 and len(quoted) > 2:
91-
ret += ","
92-
if i == len(quoted) - 1:
93-
ret += " or"
94-
if i > 0:
95-
ret += " "
96-
ret += value
97-
return ret
81+
_VALUES_MOBILE: tuple[SidebarOpenValue | Literal["always-above"], ...] = field(
82+
default=("open", "closed", "always", "always-above"),
83+
repr=False,
84+
hash=False,
85+
compare=False,
86+
)
9887

9988
def __post_init__(self):
10089
if self.desktop not in self._VALUES:
101-
raise ValueError(f"`desktop` must be one of {self._values_str()}")
102-
mobileValues = tuple(str(x) for x in set((*self._VALUES, *_always_open_mobile)))
103-
if self.mobile not in mobileValues:
104-
raise ValueError(
105-
f"`mobile` must be one of {self._values_str(mobileValues)}"
106-
)
90+
raise ValueError(f"`desktop` must be one of: {self._VALUES}.")
91+
if self.mobile not in self._VALUES_MOBILE:
92+
raise ValueError(f"`mobile` must be one of: {self._VALUES_MOBILE}.")
93+
94+
def _is_always_open(
95+
self, on: Literal["desktop", "mobile", "both"] = "both"
96+
) -> bool:
97+
desktop = self.desktop == "always"
98+
mobile = self.mobile in ("always", "always-above")
99+
switch = {"desktop": desktop, "mobile": mobile, "both": desktop and mobile}
100+
return switch[on]
107101

108102
@classmethod
109103
def _from_string(cls, open: str) -> SidebarOpen:
@@ -130,7 +124,7 @@ def _from_string(cls, open: str) -> SidebarOpen:
130124
A :class:`~shiny.ui.SidebarOpen` object.
131125
"""
132126
bad_value = ValueError(
133-
f"`open` must be a non-empty string of one of {SidebarOpen._values_str()}."
127+
f"`open` must be a string matching one of: {SidebarOpen._VALUES}."
134128
)
135129

136130
if not isinstance(open, str) or len(open) == 0:
@@ -155,7 +149,7 @@ def _as_open(
155149
return cls._from_string(open)
156150

157151
raise ValueError(
158-
f"""`open` must be one of {SidebarOpen._values_str()}, """
152+
f"""`open` must be one of {SidebarOpen._VALUES}, """
159153
+ "or a dictionary with keys `desktop` and `mobile` using these values."
160154
)
161155

@@ -338,10 +332,10 @@ def open(
338332
def max_height_mobile(self) -> Optional[str]:
339333
max_height_mobile = self._max_height_mobile
340334

341-
if max_height_mobile is not None and self.open().mobile in _always_open_mobile:
335+
if max_height_mobile is not None and not self.open()._is_always_open("mobile"):
342336
warnings.warn(
343337
"The `shiny.ui.sidebar(max_height_mobile=)` argument only applies to "
344-
+ f"the sidebar when `open` is {SidebarOpen._values_str(_always_open_mobile)} on mobile, but "
338+
+ "the sidebar when `open` is 'always' or 'always-above' on mobile, but "
345339
+ f"`open` is `'{self.open().mobile}'`. "
346340
+ "The `max_height_mobile` argument will be ignored.",
347341
# `stacklevel=2`: Refers to the caller of `.max_height_mobile` property method
@@ -369,11 +363,10 @@ def _as_open(
369363

370364
return SidebarOpen._as_open(open)
371365

372-
def _is_always_open(self) -> bool:
373-
return (
374-
self.open().desktop == "always"
375-
and self.open().mobile in _always_open_mobile
376-
)
366+
def _is_always_open(
367+
self, on: Literal["desktop", "mobile", "both"] = "both"
368+
) -> bool:
369+
return self.open()._is_always_open(on)
377370

378371
def _get_sidebar_id(self) -> Optional[str]:
379372
"""
@@ -689,10 +682,10 @@ def layout_sidebar(
689682
data_open_desktop=sidebar.open().desktop,
690683
data_open_mobile=sidebar.open().mobile,
691684
data_collapsible_mobile=(
692-
"false" if sidebar.open().mobile in _always_open_mobile else "true"
685+
"false" if sidebar.open()._is_always_open("mobile") else "true"
693686
),
694687
data_collapsible_desktop=(
695-
"false" if sidebar.open().desktop == "always" else "true"
688+
"false" if sidebar.open()._is_always_open("desktop") else "true"
696689
),
697690
data_bslib_sidebar_border=trinary(border),
698691
data_bslib_sidebar_border_radius=trinary(border_radius),

tests/pytest/test_sidebar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def get_sidebar_collapse_aria_expanded(
115115

116116

117117
def test_sidebar_throws_for_invalid_open():
118-
with pytest.raises(ValueError, match="`open` must be a non-empty string"):
118+
with pytest.raises(ValueError, match="`open` must be a string matching"):
119119
ui.sidebar(open="bad") # pyright: ignore[reportArgumentType]
120120

121121
with pytest.raises(ValueError, match="`open` must be one of"):

0 commit comments

Comments
 (0)