Add high-level API for accordion (#2285)
This commit is contained in:
parent
7388617b72
commit
5d21f0ca60
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
from hashlib import md5
|
||||
from typing import Any, Callable, Iterable
|
||||
from typing import Any, Callable, Iterable, Optional
|
||||
|
||||
from reflex.components.component import Component
|
||||
from reflex.components.layout.fragment import Fragment
|
||||
@ -23,6 +23,17 @@ class Foreach(Component):
|
||||
# A function from the render args to the component.
|
||||
render_fn: Callable = Fragment.create
|
||||
|
||||
# The theme if set.
|
||||
theme: Optional[Component] = None
|
||||
|
||||
def _apply_theme(self, theme: Component):
|
||||
"""Apply the theme to this component.
|
||||
|
||||
Args:
|
||||
theme: The theme to apply.
|
||||
"""
|
||||
self.theme = theme
|
||||
|
||||
@classmethod
|
||||
def create(cls, iterable: Var[Iterable], render_fn: Callable, **props) -> Foreach:
|
||||
"""Create a foreach component.
|
||||
@ -85,6 +96,11 @@ class Foreach(Component):
|
||||
"""
|
||||
tag = self._render()
|
||||
component = tag.render_component()
|
||||
|
||||
# Apply the theme to the children.
|
||||
if self.theme is not None:
|
||||
component.apply_theme(self.theme)
|
||||
|
||||
return dict(
|
||||
tag.add_props(
|
||||
**self.event_triggers,
|
||||
|
@ -41,7 +41,7 @@ class Icon(ChakraIconComponent):
|
||||
raise AttributeError("Missing 'tag' keyword-argument for Icon")
|
||||
if type(props["tag"]) != str or props["tag"].lower() not in ICON_LIST:
|
||||
raise ValueError(
|
||||
f"Invalid icon tag: {props['tag']}. Please use one of the following: {ICON_LIST}"
|
||||
f"Invalid icon tag: {props['tag']}. Please use one of the following: {sorted(ICON_LIST)}"
|
||||
)
|
||||
props["tag"] = format.to_title_case(props["tag"]) + "Icon"
|
||||
return super().create(*children, **props)
|
||||
|
@ -1,3 +1,3 @@
|
||||
"""Radix primitive components (https://www.radix-ui.com/primitives)."""
|
||||
|
||||
from .accordion import accordion
|
||||
from .accordion import accordion, accordion_item
|
||||
|
@ -3,9 +3,9 @@
|
||||
from typing import Literal
|
||||
|
||||
from reflex.components.component import Component
|
||||
from reflex.components.tags import Tag
|
||||
from reflex.components.radix.themes.components.icons import Icon
|
||||
from reflex.style import Style
|
||||
from reflex.utils import format, imports
|
||||
from reflex.utils import imports
|
||||
from reflex.vars import Var
|
||||
|
||||
LiteralAccordionType = Literal["single", "multiple"]
|
||||
@ -24,17 +24,6 @@ class AccordionComponent(Component):
|
||||
# Change the default rendered element for the one passed as a child.
|
||||
as_child: Var[bool]
|
||||
|
||||
def _render(self) -> Tag:
|
||||
return (
|
||||
super()
|
||||
._render()
|
||||
.add_props(
|
||||
**{
|
||||
"class_name": format.to_title_case(self.tag or ""),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class AccordionRoot(AccordionComponent):
|
||||
"""An accordion component."""
|
||||
@ -152,6 +141,10 @@ class AccordionTrigger(AccordionComponent):
|
||||
"&:hover": {
|
||||
"background_color": "var(--gray-2)",
|
||||
},
|
||||
"& > .AccordionChevron": {
|
||||
"color": "var(--accent-10)",
|
||||
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
|
||||
},
|
||||
"&[data-state='open'] > .AccordionChevron": {
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
@ -218,62 +211,36 @@ to {
|
||||
"""
|
||||
|
||||
|
||||
# TODO: Remove this once the radix-icons PR is merged in.
|
||||
class ChevronDownIcon(Component):
|
||||
"""A chevron down icon."""
|
||||
|
||||
library = "@radix-ui/react-icons"
|
||||
|
||||
tag = "ChevronDownIcon"
|
||||
|
||||
def _apply_theme(self, theme: Component):
|
||||
self.style = Style(
|
||||
{
|
||||
"color": "var(--accent-10)",
|
||||
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
|
||||
**self.style,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
accordion_root = AccordionRoot.create
|
||||
accordion_item = AccordionItem.create
|
||||
accordion_trigger = AccordionTrigger.create
|
||||
accordion_content = AccordionContent.create
|
||||
accordion_header = AccordionHeader.create
|
||||
chevron_down_icon = ChevronDownIcon.create
|
||||
|
||||
|
||||
def accordion(items: list[tuple[str, str]], **props) -> Component:
|
||||
"""High level API for the Radix accordion.
|
||||
|
||||
#TODO: We need to handle taking in state here. This is just for a POC.
|
||||
|
||||
def accordion_item(header: Component, content: Component, **props) -> Component:
|
||||
"""Create an accordion item.
|
||||
|
||||
Args:
|
||||
items: The items of the accordion component: list of tuples (label,panel)
|
||||
**props: The properties of the component.
|
||||
header: The header of the accordion item.
|
||||
content: The content of the accordion item.
|
||||
**props: Additional properties to apply to the accordion item.
|
||||
|
||||
Returns:
|
||||
The accordion component.
|
||||
The accordion item.
|
||||
"""
|
||||
return accordion_root(
|
||||
*[
|
||||
accordion_item(
|
||||
accordion_header(
|
||||
accordion_trigger(
|
||||
label,
|
||||
chevron_down_icon(
|
||||
class_name="AccordionChevron",
|
||||
),
|
||||
),
|
||||
# The item requires a value to toggle (use the header as the default value).
|
||||
value = props.pop("value", str(header))
|
||||
|
||||
return AccordionItem.create(
|
||||
AccordionHeader.create(
|
||||
AccordionTrigger.create(
|
||||
header,
|
||||
Icon.create(
|
||||
tag="chevron_down",
|
||||
class_name="AccordionChevron",
|
||||
),
|
||||
accordion_content(
|
||||
panel,
|
||||
),
|
||||
value=f"item-{i}",
|
||||
)
|
||||
for i, (label, panel) in enumerate(items)
|
||||
],
|
||||
),
|
||||
),
|
||||
AccordionContent.create(
|
||||
content,
|
||||
),
|
||||
value=value,
|
||||
**props,
|
||||
)
|
||||
|
||||
|
||||
accordion = AccordionRoot.create
|
||||
|
@ -9,9 +9,9 @@ from reflex.event import EventChain, EventHandler, EventSpec
|
||||
from reflex.style import Style
|
||||
from typing import Literal
|
||||
from reflex.components.component import Component
|
||||
from reflex.components.tags import Tag
|
||||
from reflex.components.radix.themes.components.icons import Icon
|
||||
from reflex.style import Style
|
||||
from reflex.utils import format, imports
|
||||
from reflex.utils import imports
|
||||
from reflex.vars import Var
|
||||
|
||||
LiteralAccordionType = Literal["single", "multiple"]
|
||||
@ -530,90 +530,6 @@ class AccordionContent(AccordionComponent):
|
||||
"""
|
||||
...
|
||||
|
||||
class ChevronDownIcon(Component):
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
cls,
|
||||
*children,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
id: Optional[Any] = None,
|
||||
class_name: Optional[Any] = None,
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_context_menu: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_double_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_focus: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_mount: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_mouse_down: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_mouse_enter: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_mouse_leave: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_mouse_move: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_mouse_out: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_mouse_over: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_mouse_up: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_scroll: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_unmount: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
**props
|
||||
) -> "ChevronDownIcon":
|
||||
"""Create the component.
|
||||
def accordion_item(header: Component, content: Component, **props) -> Component: ...
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
style: The style of the component.
|
||||
key: A unique key for the component.
|
||||
id: The id for the component.
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
|
||||
Raises:
|
||||
TypeError: If an invalid child is passed.
|
||||
"""
|
||||
...
|
||||
|
||||
accordion_root = AccordionRoot.create
|
||||
accordion_item = AccordionItem.create
|
||||
accordion_trigger = AccordionTrigger.create
|
||||
accordion_content = AccordionContent.create
|
||||
accordion_header = AccordionHeader.create
|
||||
chevron_down_icon = ChevronDownIcon.create
|
||||
|
||||
def accordion(items: list[tuple[str, str]], **props) -> Component: ...
|
||||
accordion = AccordionRoot.create
|
||||
|
@ -10,7 +10,7 @@ from reflex.utils import format
|
||||
class RadixIconComponent(Component):
|
||||
"""A component used as basis for Radix icons."""
|
||||
|
||||
library = "@radix-ui/react-icons"
|
||||
library = "@radix-ui/react-icons@^1.3.0"
|
||||
|
||||
|
||||
class Icon(RadixIconComponent):
|
||||
@ -43,7 +43,7 @@ class Icon(RadixIconComponent):
|
||||
raise AttributeError("Missing 'tag' keyword-argument for Icon")
|
||||
if type(props["tag"]) != str or props["tag"].lower() not in ICON_LIST:
|
||||
raise ValueError(
|
||||
f"Invalid icon tag: {props['tag']}. Please use one of the following: {ICON_LIST}"
|
||||
f"Invalid icon tag: {props['tag']}. Please use one of the following: {sorted(ICON_LIST)}"
|
||||
)
|
||||
props["tag"] = format.to_title_case(props["tag"]) + "Icon"
|
||||
return super().create(*children, **props)
|
||||
|
@ -108,7 +108,7 @@ def convert(style_dict):
|
||||
var_data = None # Track import/hook data from any Vars in the style dict.
|
||||
out = {}
|
||||
for key, value in style_dict.items():
|
||||
key = format.to_camel_case(key)
|
||||
key = format.to_camel_case(key, allow_hyphens=True)
|
||||
if isinstance(value, dict):
|
||||
# Recursively format nested style dictionaries.
|
||||
out[key], new_var_data = convert(value)
|
||||
|
@ -125,7 +125,7 @@ def to_snake_case(text: str) -> str:
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower().replace("-", "_")
|
||||
|
||||
|
||||
def to_camel_case(text: str) -> str:
|
||||
def to_camel_case(text: str, allow_hyphens: bool = False) -> str:
|
||||
"""Convert a string to camel case.
|
||||
|
||||
The first word in the text is converted to lowercase and
|
||||
@ -133,12 +133,14 @@ def to_camel_case(text: str) -> str:
|
||||
|
||||
Args:
|
||||
text: The string to convert.
|
||||
allow_hyphens: Whether to allow hyphens in the string.
|
||||
|
||||
Returns:
|
||||
The camel case string.
|
||||
"""
|
||||
words = re.split("[_-]", text.lstrip("-_"))
|
||||
leading_underscores_or_hyphens = "".join(re.findall(r"^[_-]+", text))
|
||||
char = "_" if allow_hyphens else "-_"
|
||||
words = re.split(f"[{char}]", text.lstrip(char))
|
||||
leading_underscores_or_hyphens = "".join(re.findall(rf"^[{char}]+", text))
|
||||
# Capitalize the first letter of each word except the first one
|
||||
converted_word = words[0] + "".join(x.capitalize() for x in words[1:])
|
||||
return leading_underscores_or_hyphens + converted_word
|
||||
|
@ -17,7 +17,7 @@ test_style = [
|
||||
({"::test_case": {"a": 1}}, {"::testCase": {"a": 1}}),
|
||||
(
|
||||
{"::-webkit-scrollbar": {"display": "none"}},
|
||||
{"::WebkitScrollbar": {"display": "none"}},
|
||||
{"::-webkit-scrollbar": {"display": "none"}},
|
||||
),
|
||||
]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user