[REF-2098] Allow overlay Close components to have on_click handlers (#2793)

* [REF-2098] Allow overlay Close components to have on_click handlers

The child of the Radix *Close components are passed `asChild`, meaning they are
treated as the direct child of the Close itself. This causes the `on_click`
handler attached to such an element to override the default `on_click` behavior
of the Close, which is to close the overlay, thus breaking the desired behavior
of the Close component.

When creating a Close component, if the child has an `on_click` handler, then
internally wrap it in a `Flex` so that the `Flex` gets the `asChild` treatment,
and both the default on_click and the child's on_click are both fired.

Bonus fix: pass `asChild` when constructing the `DrawerClose` to avoid a
<button> in <button> warning in the browser console, because the Drawer does
NOT pass `asChild` to the Close by default.

* Move Trigger logic to RadixThemesTriggerComponent base class

Apply trigger logic to Trigger, Close, Cancel, and Accept components; including
hover_card and dropdown_menu.
This commit is contained in:
Masen Furer 2024-03-06 17:09:46 -08:00 committed by GitHub
parent c61419a761
commit ecb4dbaea9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 200 additions and 192 deletions

View File

@ -6,9 +6,10 @@ from __future__ import annotations
from typing import Any, Dict, List, Literal, Optional, Union
from reflex.components.component import ComponentNamespace
from reflex.components.component import Component, ComponentNamespace
from reflex.components.radix.primitives.base import RadixPrimitiveComponent
from reflex.components.radix.themes.base import Theme
from reflex.components.radix.themes.layout.flex import Flex
from reflex.constants import EventTriggers
from reflex.vars import Var
@ -80,6 +81,23 @@ class DrawerTrigger(DrawerComponent):
# Defaults to true, if the first child acts as the trigger.
as_child: Var[bool] = True # type: ignore
@classmethod
def create(cls, *children: Any, **props: Any) -> Component:
"""Create a new DrawerTrigger instance.
Args:
children: The children of the element.
props: The properties of the element.
Returns:
The new DrawerTrigger instance.
"""
for child in children:
if "on_click" in getattr(child, "event_triggers", {}):
children = (Flex.create(*children),)
break
return super().create(*children, **props)
class DrawerPortal(DrawerComponent):
"""Portals your drawer into the body."""
@ -182,7 +200,7 @@ class DrawerOverlay(DrawerComponent):
return {"css": base_style}
class DrawerClose(DrawerComponent):
class DrawerClose(DrawerTrigger):
"""A button that closes the drawer."""
tag = "Drawer.Close"

View File

@ -8,9 +8,10 @@ from reflex.vars import Var, BaseVar, ComputedVar
from reflex.event import EventChain, EventHandler, EventSpec
from reflex.style import Style
from typing import Any, Dict, List, Literal, Optional, Union
from reflex.components.component import ComponentNamespace
from reflex.components.component import Component, ComponentNamespace
from reflex.components.radix.primitives.base import RadixPrimitiveComponent
from reflex.components.radix.themes.base import Theme
from reflex.components.radix.themes.layout.flex import Flex
from reflex.constants import EventTriggers
from reflex.vars import Var
@ -265,24 +266,14 @@ class DrawerTrigger(DrawerComponent):
] = None,
**props
) -> "DrawerTrigger":
"""Create the component.
"""Create a new DrawerTrigger instance.
Args:
*children: The children of the component.
as_child: Change the default rendered element for the one passed as a child.
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.
children: The children of the element.
props: The properties of the element.
Returns:
The component.
Raises:
TypeError: If an invalid child is passed.
The new DrawerTrigger instance.
"""
...
@ -546,7 +537,7 @@ class DrawerOverlay(DrawerComponent):
"""
...
class DrawerClose(DrawerComponent):
class DrawerClose(DrawerTrigger):
@overload
@classmethod
def create( # type: ignore
@ -606,24 +597,14 @@ class DrawerClose(DrawerComponent):
] = None,
**props
) -> "DrawerClose":
"""Create the component.
"""Create a new DrawerTrigger instance.
Args:
*children: The children of the component.
as_child: Change the default rendered element for the one passed as a child.
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.
children: The children of the element.
props: The properties of the element.
Returns:
The component.
Raises:
TypeError: If an invalid child is passed.
The new DrawerTrigger instance.
"""
...

View File

@ -114,6 +114,35 @@ class RadixThemesComponent(Component):
}
class RadixThemesTriggerComponent(RadixThemesComponent):
"""Base class for Trigger, Close, Cancel, and Accept components.
These components trigger some action in an overlay component that depends on the
on_click event, and thus if a child is provided and has on_click specified, it
will overtake the internal action, unless it is wrapped in some inert component,
in this case, a Flex.
"""
@classmethod
def create(cls, *children: Any, **props: Any) -> Component:
"""Create a new RadixThemesTriggerComponent instance.
Args:
children: The children of the component.
props: The properties of the component.
Returns:
The new RadixThemesTriggerComponent instance.
"""
from .layout.flex import Flex
for child in children:
if "on_click" in getattr(child, "event_triggers", {}):
children = (Flex.create(*children),)
break
return super().create(*children, **props)
class Theme(RadixThemesComponent):
"""A theme provider for radix components.

View File

@ -258,6 +258,76 @@ class RadixThemesComponent(Component):
"""
...
class RadixThemesTriggerComponent(RadixThemesComponent):
@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
) -> "RadixThemesTriggerComponent":
"""Create a new RadixThemesTriggerComponent instance.
Args:
children: The children of the component.
props: The properties of the component.
Returns:
The new RadixThemesTriggerComponent instance.
"""
...
class Theme(RadixThemesComponent):
@overload
@classmethod

View File

@ -6,7 +6,7 @@ from reflex.components.component import ComponentNamespace
from reflex.constants import EventTriggers
from reflex.vars import Var
from ..base import RadixThemesComponent
from ..base import RadixThemesComponent, RadixThemesTriggerComponent
LiteralContentSize = Literal["1", "2", "3", "4"]
@ -31,7 +31,7 @@ class AlertDialogRoot(RadixThemesComponent):
}
class AlertDialogTrigger(RadixThemesComponent):
class AlertDialogTrigger(RadixThemesTriggerComponent):
"""Wraps the control that will open the dialog."""
tag = "AlertDialog.Trigger"
@ -79,7 +79,7 @@ class AlertDialogDescription(RadixThemesComponent):
tag = "AlertDialog.Description"
class AlertDialogAction(RadixThemesComponent):
class AlertDialogAction(RadixThemesTriggerComponent):
"""Wraps the control that will close the dialog. This should be distinguished
visually from the Cancel control.
"""
@ -87,7 +87,7 @@ class AlertDialogAction(RadixThemesComponent):
tag = "AlertDialog.Action"
class AlertDialogCancel(RadixThemesComponent):
class AlertDialogCancel(RadixThemesTriggerComponent):
"""Wraps the control that will close the dialog. This should be distinguished
visually from the Action control.
"""

View File

@ -12,7 +12,7 @@ from reflex import el
from reflex.components.component import ComponentNamespace
from reflex.constants import EventTriggers
from reflex.vars import Var
from ..base import RadixThemesComponent
from ..base import RadixThemesComponent, RadixThemesTriggerComponent
LiteralContentSize = Literal["1", "2", "3", "4"]
@ -101,7 +101,7 @@ class AlertDialogRoot(RadixThemesComponent):
"""
...
class AlertDialogTrigger(RadixThemesComponent):
class AlertDialogTrigger(RadixThemesTriggerComponent):
@overload
@classmethod
def create( # type: ignore
@ -160,23 +160,14 @@ class AlertDialogTrigger(RadixThemesComponent):
] = None,
**props
) -> "AlertDialogTrigger":
"""Create a new component instance.
Will prepend "RadixThemes" to the component tag to avoid conflicts with
other UI libraries for common names, like Text and Button.
"""Create a new RadixThemesTriggerComponent instance.
Args:
*children: Child components.
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: Component properties.
children: The children of the component.
props: The properties of the component.
Returns:
A new component instance.
The new RadixThemesTriggerComponent instance.
"""
...
@ -489,7 +480,7 @@ class AlertDialogDescription(RadixThemesComponent):
"""
...
class AlertDialogAction(RadixThemesComponent):
class AlertDialogAction(RadixThemesTriggerComponent):
@overload
@classmethod
def create( # type: ignore
@ -548,27 +539,18 @@ class AlertDialogAction(RadixThemesComponent):
] = None,
**props
) -> "AlertDialogAction":
"""Create a new component instance.
Will prepend "RadixThemes" to the component tag to avoid conflicts with
other UI libraries for common names, like Text and Button.
"""Create a new RadixThemesTriggerComponent instance.
Args:
*children: Child components.
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: Component properties.
children: The children of the component.
props: The properties of the component.
Returns:
A new component instance.
The new RadixThemesTriggerComponent instance.
"""
...
class AlertDialogCancel(RadixThemesComponent):
class AlertDialogCancel(RadixThemesTriggerComponent):
@overload
@classmethod
def create( # type: ignore
@ -627,23 +609,14 @@ class AlertDialogCancel(RadixThemesComponent):
] = None,
**props
) -> "AlertDialogCancel":
"""Create a new component instance.
Will prepend "RadixThemes" to the component tag to avoid conflicts with
other UI libraries for common names, like Text and Button.
"""Create a new RadixThemesTriggerComponent instance.
Args:
*children: Child components.
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: Component properties.
children: The children of the component.
props: The properties of the component.
Returns:
A new component instance.
The new RadixThemesTriggerComponent instance.
"""
...

View File

@ -9,6 +9,7 @@ from reflex.vars import Var
from ..base import (
RadixThemesComponent,
RadixThemesTriggerComponent,
)
@ -32,7 +33,7 @@ class DialogRoot(RadixThemesComponent):
}
class DialogTrigger(RadixThemesComponent):
class DialogTrigger(RadixThemesTriggerComponent):
"""Trigger an action or event, to open a Dialog modal."""
tag = "Dialog.Trigger"
@ -74,7 +75,7 @@ class DialogDescription(RadixThemesComponent):
tag = "Dialog.Description"
class DialogClose(RadixThemesComponent):
class DialogClose(RadixThemesTriggerComponent):
"""Close button component to close an open Dialog modal."""
tag = "Dialog.Close"

View File

@ -12,7 +12,7 @@ from reflex import el
from reflex.components.component import ComponentNamespace
from reflex.constants import EventTriggers
from reflex.vars import Var
from ..base import RadixThemesComponent
from ..base import RadixThemesComponent, RadixThemesTriggerComponent
class DialogRoot(RadixThemesComponent):
def get_event_triggers(self) -> Dict[str, Any]: ...
@ -99,7 +99,7 @@ class DialogRoot(RadixThemesComponent):
"""
...
class DialogTrigger(RadixThemesComponent):
class DialogTrigger(RadixThemesTriggerComponent):
@overload
@classmethod
def create( # type: ignore
@ -158,23 +158,14 @@ class DialogTrigger(RadixThemesComponent):
] = None,
**props
) -> "DialogTrigger":
"""Create a new component instance.
Will prepend "RadixThemes" to the component tag to avoid conflicts with
other UI libraries for common names, like Text and Button.
"""Create a new RadixThemesTriggerComponent instance.
Args:
*children: Child components.
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: Component properties.
children: The children of the component.
props: The properties of the component.
Returns:
A new component instance.
The new RadixThemesTriggerComponent instance.
"""
...
@ -491,7 +482,7 @@ class DialogDescription(RadixThemesComponent):
"""
...
class DialogClose(RadixThemesComponent):
class DialogClose(RadixThemesTriggerComponent):
@overload
@classmethod
def create( # type: ignore
@ -550,23 +541,14 @@ class DialogClose(RadixThemesComponent):
] = None,
**props
) -> "DialogClose":
"""Create a new component instance.
Will prepend "RadixThemes" to the component tag to avoid conflicts with
other UI libraries for common names, like Text and Button.
"""Create a new RadixThemesTriggerComponent instance.
Args:
*children: Child components.
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: Component properties.
children: The children of the component.
props: The properties of the component.
Returns:
A new component instance.
The new RadixThemesTriggerComponent instance.
"""
...

View File

@ -8,6 +8,7 @@ from reflex.vars import Var
from ..base import (
LiteralAccentColor,
RadixThemesComponent,
RadixThemesTriggerComponent,
)
LiteralDirType = Literal["ltr", "rtl"]
@ -58,7 +59,7 @@ class DropdownMenuRoot(RadixThemesComponent):
}
class DropdownMenuTrigger(RadixThemesComponent):
class DropdownMenuTrigger(RadixThemesTriggerComponent):
"""The button that toggles the dropdown menu."""
tag = "DropdownMenu.Trigger"
@ -140,7 +141,7 @@ class DropdownMenuContent(RadixThemesComponent):
}
class DropdownMenuSubTrigger(RadixThemesComponent):
class DropdownMenuSubTrigger(RadixThemesTriggerComponent):
"""An item that opens a submenu."""
tag = "DropdownMenu.SubTrigger"

View File

@ -11,7 +11,7 @@ from typing import Any, Dict, List, Literal, Union
from reflex.components.component import ComponentNamespace
from reflex.constants import EventTriggers
from reflex.vars import Var
from ..base import LiteralAccentColor, RadixThemesComponent
from ..base import LiteralAccentColor, RadixThemesComponent, RadixThemesTriggerComponent
LiteralDirType = Literal["ltr", "rtl"]
LiteralSizeType = Literal["1", "2"]
@ -111,7 +111,7 @@ class DropdownMenuRoot(RadixThemesComponent):
"""
...
class DropdownMenuTrigger(RadixThemesComponent):
class DropdownMenuTrigger(RadixThemesTriggerComponent):
@overload
@classmethod
def create( # type: ignore
@ -171,24 +171,14 @@ class DropdownMenuTrigger(RadixThemesComponent):
] = None,
**props
) -> "DropdownMenuTrigger":
"""Create a new component instance.
Will prepend "RadixThemes" to the component tag to avoid conflicts with
other UI libraries for common names, like Text and Button.
"""Create a new RadixThemesTriggerComponent instance.
Args:
*children: Child components.
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False.
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: Component properties.
children: The children of the component.
props: The properties of the component.
Returns:
A new component instance.
The new RadixThemesTriggerComponent instance.
"""
...
@ -401,7 +391,7 @@ class DropdownMenuContent(RadixThemesComponent):
"""
...
class DropdownMenuSubTrigger(RadixThemesComponent):
class DropdownMenuSubTrigger(RadixThemesTriggerComponent):
@overload
@classmethod
def create( # type: ignore
@ -463,26 +453,14 @@ class DropdownMenuSubTrigger(RadixThemesComponent):
] = None,
**props
) -> "DropdownMenuSubTrigger":
"""Create a new component instance.
Will prepend "RadixThemes" to the component tag to avoid conflicts with
other UI libraries for common names, like Text and Button.
"""Create a new RadixThemesTriggerComponent instance.
Args:
*children: Child components.
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False.
disabled: When true, prevents the user from interacting with the item.
text_value: Optional text used for typeahead purposes. By default the typeahead behavior will use the .textContent of the item. Use this when the content is complex, or you have non-textual content inside.
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: Component properties.
children: The children of the component.
props: The properties of the component.
Returns:
A new component instance.
The new RadixThemesTriggerComponent instance.
"""
...

View File

@ -8,6 +8,7 @@ from reflex.vars import Var
from ..base import (
RadixThemesComponent,
RadixThemesTriggerComponent,
)
@ -40,7 +41,7 @@ class HoverCardRoot(RadixThemesComponent):
}
class HoverCardTrigger(RadixThemesComponent):
class HoverCardTrigger(RadixThemesTriggerComponent):
"""Wraps the link that will open the hover card."""
tag = "HoverCard.Trigger"

View File

@ -12,7 +12,7 @@ from reflex import el
from reflex.components.component import ComponentNamespace
from reflex.constants import EventTriggers
from reflex.vars import Var
from ..base import RadixThemesComponent
from ..base import RadixThemesComponent, RadixThemesTriggerComponent
class HoverCardRoot(RadixThemesComponent):
def get_event_triggers(self) -> Dict[str, Any]: ...
@ -105,7 +105,7 @@ class HoverCardRoot(RadixThemesComponent):
"""
...
class HoverCardTrigger(RadixThemesComponent):
class HoverCardTrigger(RadixThemesTriggerComponent):
@overload
@classmethod
def create( # type: ignore
@ -164,23 +164,14 @@ class HoverCardTrigger(RadixThemesComponent):
] = None,
**props
) -> "HoverCardTrigger":
"""Create a new component instance.
Will prepend "RadixThemes" to the component tag to avoid conflicts with
other UI libraries for common names, like Text and Button.
"""Create a new RadixThemesTriggerComponent instance.
Args:
*children: Child components.
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: Component properties.
children: The children of the component.
props: The properties of the component.
Returns:
A new component instance.
The new RadixThemesTriggerComponent instance.
"""
...

View File

@ -8,6 +8,7 @@ from reflex.vars import Var
from ..base import (
RadixThemesComponent,
RadixThemesTriggerComponent,
)
@ -34,7 +35,7 @@ class PopoverRoot(RadixThemesComponent):
}
class PopoverTrigger(RadixThemesComponent):
class PopoverTrigger(RadixThemesTriggerComponent):
"""Wraps the control that will open the popover."""
tag = "Popover.Trigger"
@ -80,7 +81,7 @@ class PopoverContent(el.Div, RadixThemesComponent):
}
class PopoverClose(RadixThemesComponent):
class PopoverClose(RadixThemesTriggerComponent):
"""Wraps the control that will close the popover."""
tag = "Popover.Close"

View File

@ -12,7 +12,7 @@ from reflex import el
from reflex.components.component import ComponentNamespace
from reflex.constants import EventTriggers
from reflex.vars import Var
from ..base import RadixThemesComponent
from ..base import RadixThemesComponent, RadixThemesTriggerComponent
class PopoverRoot(RadixThemesComponent):
def get_event_triggers(self) -> Dict[str, Any]: ...
@ -101,7 +101,7 @@ class PopoverRoot(RadixThemesComponent):
"""
...
class PopoverTrigger(RadixThemesComponent):
class PopoverTrigger(RadixThemesTriggerComponent):
@overload
@classmethod
def create( # type: ignore
@ -160,23 +160,14 @@ class PopoverTrigger(RadixThemesComponent):
] = None,
**props
) -> "PopoverTrigger":
"""Create a new component instance.
Will prepend "RadixThemes" to the component tag to avoid conflicts with
other UI libraries for common names, like Text and Button.
"""Create a new RadixThemesTriggerComponent instance.
Args:
*children: Child components.
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: Component properties.
children: The children of the component.
props: The properties of the component.
Returns:
A new component instance.
The new RadixThemesTriggerComponent instance.
"""
...
@ -358,7 +349,7 @@ class PopoverContent(el.Div, RadixThemesComponent):
"""
...
class PopoverClose(RadixThemesComponent):
class PopoverClose(RadixThemesTriggerComponent):
@overload
@classmethod
def create( # type: ignore
@ -417,23 +408,14 @@ class PopoverClose(RadixThemesComponent):
] = None,
**props
) -> "PopoverClose":
"""Create a new component instance.
Will prepend "RadixThemes" to the component tag to avoid conflicts with
other UI libraries for common names, like Text and Button.
"""Create a new RadixThemesTriggerComponent instance.
Args:
*children: Child components.
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: Component properties.
children: The children of the component.
props: The properties of the component.
Returns:
A new component instance.
The new RadixThemesTriggerComponent instance.
"""
...