diff --git a/pynecone/components/datadisplay/list.py b/pynecone/components/datadisplay/list.py index 0fabd4da2..85e33fdc2 100644 --- a/pynecone/components/datadisplay/list.py +++ b/pynecone/components/datadisplay/list.py @@ -19,6 +19,24 @@ class List(ChakraComponent): # Shorthand prop for listStyleType style_type: Var[str] + @classmethod + def create(cls, *children, items=None, **props) -> Component: + """Create a list component. + + Args: + children: The children of the component. + items: A list of items to add to the list. + props: The properties of the component. + + Returns: + The list component. + """ + if len(children) == 0: + children = [] + for item in items or []: + children.append(ListItem.create(*item)) + return super().create(*children, **props) + class ListItem(ChakraComponent): """A single list item.""" @@ -26,13 +44,13 @@ class ListItem(ChakraComponent): tag = "ListItem" -class OrderedList(ChakraComponent): +class OrderedList(List): """An ordered list component with numbers.""" tag = "OrderedList" -class UnorderedList(ChakraComponent): +class UnorderedList(List): """An unordered list component with bullets.""" tag = "UnorderedList" diff --git a/pynecone/components/datadisplay/stat.py b/pynecone/components/datadisplay/stat.py index eaedaebac..5e121ad33 100644 --- a/pynecone/components/datadisplay/stat.py +++ b/pynecone/components/datadisplay/stat.py @@ -1,5 +1,6 @@ """Statistics components.""" +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -9,6 +10,42 @@ class Stat(ChakraComponent): tag = "Stat" + @classmethod + def create( + cls, *children, label=None, number=0, help_text=None, arrow_type=None, **props + ) -> Component: + """Create a stat component. + + Args: + children: The children of the component. + label: A label for the stat component. + number: The value of the stat component. + help_text: A text added to the stat component. + arrow_type: The type of the arrow ("increase", "decrease", None) + props: The properties of the component. + + Returns: + The stat component. + """ + if len(children) == 0: + children = [] + if label: + children.append(StatLabel.create(label)) + + children.append(StatNumber.create(number)) + + if help_text: + if arrow_type: + children.append( + StatHelpText.create( + help_text, StatArrow.create(type_=arrow_type) + ) + ) + else: + children.append(StatHelpText.create(help_text)) + + return super().create(*children, **props) + class StatLabel(ChakraComponent): """A stat label component.""" diff --git a/pynecone/components/datadisplay/table.py b/pynecone/components/datadisplay/table.py index 98e7dbc88..5153f52e9 100644 --- a/pynecone/components/datadisplay/table.py +++ b/pynecone/components/datadisplay/table.py @@ -1,6 +1,7 @@ """Table components.""" from typing import List +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -23,30 +24,130 @@ class Table(ChakraComponent): # The placement of the table caption. placement: Var[str] + @classmethod + def create( + cls, *children, caption=None, headers=None, rows=None, footers=None, **props + ) -> Component: + """Create a table component. -class Thead(Table): + Args: + children: The children of the component. + caption: The caption of the table component. + headers: The headers of the table component. + rows: The rows of the table component. + footers: The footers of the table component. + props: The properties of the component. + + Returns: + The table component. + """ + if len(children) == 0: + children = [] + + if caption: + children.append(TableCaption.create(caption)) + + if headers: + children.append(Thead.create(headers=headers)) + + if rows: + children.append(Tbody.create(rows=rows)) + + if footers: + children.append(Tfoot.create(footers=footers)) + return super().create(*children, **props) + + +class Thead(ChakraComponent): """A table header component.""" tag = "Thead" + @classmethod + def create(cls, *children, headers=None, **props) -> Component: + """Create a table header component. -class Tbody(Table): + Args: + children: The children of the component. + props: The properties of the component. + headers (list, optional): List of headers. Defaults to None. + + Returns: + The table header component. + """ + if len(children) == 0: + children = [Tr.create(cell_type="header", cells=headers)] + return super().create(*children, **props) + + +class Tbody(ChakraComponent): """A table body component.""" tag = "Tbody" + @classmethod + def create(cls, *children, rows=None, **props) -> Component: + """Create a table body component. -class Tfoot(Table): + Args: + children: The children of the component. + props: The properties of the component. + rows (list[list], optional): The rows of the table body. Defaults to None. + + Returns: + Component: _description_ + """ + if len(children) == 0: + children = [Tr.create(cell_type="data", cells=row) for row in rows or []] + return super().create(*children, **props) + + +class Tfoot(ChakraComponent): """A table footer component.""" tag = "Tfoot" + @classmethod + def create(cls, *children, footers=None, **props) -> Component: + """Create a table footer component. -class Tr(Table): + Args: + children: The children of the component. + props: The properties of the component. + footers (list, optional): List of footers. Defaults to None. + + Returns: + The table footer component. + """ + if len(children) == 0: + children = [Tr.create(cell_type="header", cells=footers)] + return super().create(*children, **props) + + +class Tr(ChakraComponent): """A table row component.""" tag = "Tr" + @classmethod + def create(cls, *children, cell_type: str = "", cells=None, **props) -> Component: + """Create a table row component. + + Args: + children: The children of the component. + props: The properties of the component. + cell_type (str): the type of cells in this table row. "header" or "data". Defaults to None. + cells (list, optional): The cells value to add in the table row. Defaults to None. + + Returns: + The table row component + """ + types = {"header": Th, "data": Td} + cell_cls = types.get(cell_type) + if len(children) == 0 and cell_cls: + children = [cell_cls.create(cell) for cell in cells or []] + return super().create(*children, **props) + class Th(ChakraComponent): """A table header cell component.""" diff --git a/pynecone/components/disclosure/accordion.py b/pynecone/components/disclosure/accordion.py index 4f4d89579..58f13b66d 100644 --- a/pynecone/components/disclosure/accordion.py +++ b/pynecone/components/disclosure/accordion.py @@ -1,6 +1,7 @@ """Container to stack elements with spacing.""" from typing import List, Optional, Union +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -26,6 +27,39 @@ class Accordion(ChakraComponent): # If true, height animation and transitions will be disabled. reduce_motion: Var[bool] + @classmethod + def create(cls, *children, items=None, icon_pos="right", **props) -> Component: + """Create an accordion component. + + Args: + children: The children of the component. + items: The items of the accordion component: list of tuples (label,panel) + icon_pos: The position of the arrow icon of the accordion. "right", "left" or None + props: The properties of the component. + + Returns: + The accordion component + """ + if len(children) == 0: + children = [] + if not items: + items = [] + for label, panel in items: + if icon_pos == "right": + button = AccordionButton.create(label, AccordionIcon.create()) + elif icon_pos == "left": + button = AccordionButton.create(AccordionIcon.create(), label) + else: + button = AccordionButton.create(label) + + children.append( + AccordionItem.create( + button, + AccordionPanel.create(panel), + ) + ) + return super().create(*children, **props) + class AccordionItem(ChakraComponent): """A single accordion item.""" diff --git a/pynecone/components/disclosure/tabs.py b/pynecone/components/disclosure/tabs.py index 9d602bd6e..17ce2334d 100644 --- a/pynecone/components/disclosure/tabs.py +++ b/pynecone/components/disclosure/tabs.py @@ -1,5 +1,6 @@ """Tab components.""" +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -33,6 +34,29 @@ class Tabs(ChakraComponent): # "line" | "enclosed" | "enclosed-colored" | "soft-rounded" | "solid-rounded" | "unstyled" variant: Var[str] + @classmethod + def create(cls, *children, items=None, **props) -> Component: + """Create a tab component. + + Args: + children: The children of the component. + items: The items for the tabs component, a list of tuple (label, panel) + props: The properties of the component. + + Returns: + The tab component + """ + if len(children) == 0: + tabs = [] + panels = [] + if not items: + items = [] + for label, panel in items: + tabs.append(Tab.create(label)) + panels.append(TabPanel.create(panel)) + children = [TabList.create(*tabs), TabPanels.create(*panels)] + return super().create(*children, **props) + class Tab(ChakraComponent): """An element that serves as a label for one of the tab panels and can be activated to display that panel..""" diff --git a/pynecone/components/feedback/alert.py b/pynecone/components/feedback/alert.py index a0d1eea6e..d5935eeb5 100644 --- a/pynecone/components/feedback/alert.py +++ b/pynecone/components/feedback/alert.py @@ -1,5 +1,6 @@ """Alert components.""" +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -15,6 +16,35 @@ class Alert(ChakraComponent): # "subtle" | "left-accent" | "top-accent" | "solid" variant: Var[str] + @classmethod + def create( + cls, *children, icon=True, title="Alert title", desc=None, **props + ) -> Component: + """Create an alert component. + + Args: + children: The children of the component. + icon (bool): The icon of the alert. + title (str): The title of the alert. + desc (str): The description of the alert + props: The properties of the component. + + Returns: + The alert component. + """ + if len(children) == 0: + contents = [] + + if icon: + contents.append(AlertIcon.create()) + + contents.append(AlertTitle.create(title)) + + if desc: + contents.append(AlertDescription.create(desc)) + + return super().create(*children, **props) + class AlertIcon(ChakraComponent): """An icon displayed in the alert.""" diff --git a/pynecone/components/forms/formcontrol.py b/pynecone/components/forms/formcontrol.py index 2e000f943..46e493d8b 100644 --- a/pynecone/components/forms/formcontrol.py +++ b/pynecone/components/forms/formcontrol.py @@ -1,5 +1,6 @@ """Form components.""" +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -24,6 +25,50 @@ class FormControl(ChakraComponent): # The label text used to inform users as to what information is requested for a text field. label: Var[str] + @classmethod + def create( + cls, + *children, + label=None, + input=None, + help_text=None, + error_message=None, + **props + ) -> Component: + """Create a form control component. + + Args: + children: The children of the form control. + label: The label of the form control. + input: The input of the form control. + help_text: The help text of the form control. + error_message: The error message of the form control. + props: The properties of the form control. + + Raises: + AttributeError: raise an error if missing required kwargs. + + Returns: + The form control component. + """ + if len(children) == 0: + children = [] + + if label: + children.append(FormLabel.create(*label)) + + if not input: + raise AttributeError("input keyword argument is required") + children.append(input) + + if help_text: + children.append(FormHelperText.create(*help_text)) + + if error_message: + children.append(FormErrorMessage.create(*error_message)) + + return super().create(*children, **props) + class FormHelperText(ChakraComponent): """A form helper text component.""" diff --git a/pynecone/components/forms/rangeslider.py b/pynecone/components/forms/rangeslider.py index 82be15d02..f486beade 100644 --- a/pynecone/components/forms/rangeslider.py +++ b/pynecone/components/forms/rangeslider.py @@ -68,7 +68,7 @@ class RangeSlider(ChakraComponent): Returns: The RangeSlider component. """ - if not children: + if len(children) == 0: children = [ RangeSliderTrack.create( RangeSliderFilledTrack.create(), diff --git a/pynecone/components/forms/slider.py b/pynecone/components/forms/slider.py index 3d0175d49..ff86315c5 100644 --- a/pynecone/components/forms/slider.py +++ b/pynecone/components/forms/slider.py @@ -68,7 +68,7 @@ class Slider(ChakraComponent): Returns: The slider component. """ - if not children: + if len(children) == 0: children = [ SliderTrack.create( SliderFilledTrack.create(), diff --git a/pynecone/components/layout/wrap.py b/pynecone/components/layout/wrap.py index f6c2d8a06..2ff07f763 100644 --- a/pynecone/components/layout/wrap.py +++ b/pynecone/components/layout/wrap.py @@ -1,5 +1,6 @@ """Container to stack elements with spacing.""" +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -30,6 +31,25 @@ class Wrap(ChakraComponent): # The vertical spacing between the items. spacing_y: Var[str] + @classmethod + def create(cls, *children, items=None, **props) -> Component: + """Return a wrap component. + + Args: + children: The children of the component. + items (list): The items of the wrap component. + props: The properties of the component. + + Returns: + The wrap component. + """ + if len(children) == 0: + children = [] + for item in items or []: + children.append(WrapItem.create(*item)) + + return super().create(*children, **props) + class WrapItem(ChakraComponent): """Item of the Wrap component.""" diff --git a/pynecone/components/navigation/breadcrumb.py b/pynecone/components/navigation/breadcrumb.py index f0df3ed84..091bc4b6a 100644 --- a/pynecone/components/navigation/breadcrumb.py +++ b/pynecone/components/navigation/breadcrumb.py @@ -1,5 +1,6 @@ """Breadcrumb components.""" +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -15,6 +16,28 @@ class Breadcrumb(ChakraComponent): # The left and right margin applied to the separator separator_margin: Var[str] + @classmethod + def create(cls, *children, items=None, **props) -> Component: + """Create a breadcrumb component. + + If the kw-args `items` is provided and is a list, they will be added as children. + + Args: + children: The children of the component. + items (list): The items of the breadcrumb: (label, link) + props: The properties of the component. + + Returns: + The breadcrumb component. + """ + if len(children) == 0: + children = [] + for label, link in items or []: + children.append( + BreadcrumbItem.create(BreadcrumbLink.create(label, href=link)) + ) + return super().create(*children, **props) + class BreadcrumbItem(ChakraComponent): """Individual breadcrumb element containing a link and a divider.""" diff --git a/pynecone/components/overlay/alertdialog.py b/pynecone/components/overlay/alertdialog.py index 9514cc67b..100e5be91 100644 --- a/pynecone/components/overlay/alertdialog.py +++ b/pynecone/components/overlay/alertdialog.py @@ -2,6 +2,8 @@ from typing import Set +from pynecone.components.component import Component +from pynecone.components.media.icon import Icon from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -64,6 +66,57 @@ class AlertDialog(ChakraComponent): "on_overlay_click", } + @classmethod + def create( + cls, *children, header=None, body=None, footer=None, close_button=None, **props + ) -> Component: + """Create an alert dialog component. + + Args: + children: The children of the alert dialog component. + header: The header of the alert dialog. + body: The body of the alert dialog. + footer: The footer of the alert dialog. + close_button: The close button of the alert dialog. + props: The properties of the alert dialog component. + + Raises: + AttributeError: if there is a conflict between the props used. + + Returns: + The alert dialog component. + """ + if len(children) == 0: + contents = [] + + if header: + contents.append(AlertDialogHeader.create(header)) + + if body: + contents.append(AlertDialogBody.create(body)) + + if footer: + contents.append(AlertDialogFooter.create(footer)) + + # add AlertDialogCloseButton if either a prop for one was passed, or if on_close callback is present + if props.get("on_close"): + # get user defined close button or use default one + if not close_button: + close_button = Icon.create(tag="CloseIcon") + contents.append(AlertDialogCloseButton.create(close_button)) + elif close_button: + raise AttributeError( + "Close button can not be used if on_close event handler is not defined" + ) + + children = [ + AlertDialogOverlay.create( + AlertDialogContent.create(*contents), + ) + ] + + return super().create(*children, **props) + class AlertDialogBody(ChakraComponent): """Should contain the description announced by screen readers.""" diff --git a/pynecone/components/overlay/drawer.py b/pynecone/components/overlay/drawer.py index ad75b28a6..9598ebdd8 100644 --- a/pynecone/components/overlay/drawer.py +++ b/pynecone/components/overlay/drawer.py @@ -1,8 +1,10 @@ """Container to stack elements with spacing.""" from typing import Set +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent +from pynecone.components.media.icon import Icon from pynecone.var import Var @@ -70,6 +72,56 @@ class Drawer(ChakraComponent): "on_overlay_click", } + @classmethod + def create( + cls, *children, header=None, body=None, footer=None, close_button=None, **props + ) -> Component: + """Create a drawer component. + + Args: + children: The children of the drawer component. + header: The header of the drawer. + body: The body of the drawer. + footer: The footer of the drawer. + close_button: The close button of the drawer. + props: The properties of the drawer component. + + Raises: + AttributeError: error that occurs if conflicting props are passed + + Returns: + The drawer component. + """ + if len(children) == 0: + contents = [] + + if header: + contents.append(DrawerHeader.create(header)) + + if body: + contents.append(DrawerBody.create(body)) + + if footer: + contents.append(DrawerFooter.create(footer)) + + if props.get("on_close"): + # use default close button if undefined + if not close_button: + close_button = Icon.create(tag="CloseIcon") + contents.append(DrawerCloseButton.create(close_button)) + elif close_button: + raise AttributeError( + "Close button can not be used if on_close event handler is not defined" + ) + + children = [ + DrawerOverlay.create( + DrawerContent.create(*contents), + ) + ] + + return super().create(*children, **props) + class DrawerBody(ChakraComponent): """Drawer body.""" @@ -89,19 +141,19 @@ class DrawerFooter(ChakraComponent): tag = "DrawerFooter" -class DrawerOverlay(Drawer): +class DrawerOverlay(ChakraComponent): """Drawer overlay.""" tag = "DrawerOverlay" -class DrawerContent(Drawer): +class DrawerContent(ChakraComponent): """Drawer content.""" tag = "DrawerContent" -class DrawerCloseButton(Drawer): +class DrawerCloseButton(ChakraComponent): """Drawer close button.""" tag = "DrawerCloseButton" diff --git a/pynecone/components/overlay/menu.py b/pynecone/components/overlay/menu.py index 848d5e7d4..6d0cfb570 100644 --- a/pynecone/components/overlay/menu.py +++ b/pynecone/components/overlay/menu.py @@ -1,6 +1,7 @@ """Menu components.""" from typing import Set +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -68,6 +69,25 @@ class Menu(ChakraComponent): """ return super().get_triggers() | {"on_close", "on_open"} + @classmethod + def create(cls, *children, items=None, **props) -> Component: + """Create a menu component. + + Args: + children: The children of the component. + items (list): The item of the menu. + props: The properties of the component. + + Returns: + The menu component. + """ + if len(children) == 0: + button = MenuButton.create(*props.pop("button")) + if not items: + items = [] + children = [button, MenuList.create(*items)] + return super().create(*children, **props) + class MenuButton(ChakraComponent): """The trigger for the menu list. Must be a direct child of Menu.""" @@ -87,7 +107,7 @@ class MenuList(ChakraComponent): tag = "MenuList" -class MenuItem(Menu): +class MenuItem(ChakraComponent): """The trigger that handles menu selection. Must be a direct child of a MenuList.""" tag = "MenuItem" @@ -108,7 +128,7 @@ class MenuItem(Menu): is_focusable: Var[bool] -class MenuItemOption(Menu): +class MenuItemOption(ChakraComponent): """The checkable menu item, to be used with MenuOptionGroup.""" tag = "MenuItemOption" @@ -138,13 +158,13 @@ class MenuItemOption(Menu): value: Var[str] -class MenuGroup(Menu): +class MenuGroup(ChakraComponent): """A wrapper to group related menu items.""" tag = "MenuGroup" -class MenuOptionGroup(Menu): +class MenuOptionGroup(ChakraComponent): """A wrapper for checkable menu items (radio and checkbox).""" tag = "MenuOptionGroup" @@ -156,7 +176,7 @@ class MenuOptionGroup(Menu): value: Var[str] -class MenuDivider(Menu): +class MenuDivider(ChakraComponent): """A visual separator for menu items and groups.""" tag = "MenuDivider" diff --git a/pynecone/components/overlay/modal.py b/pynecone/components/overlay/modal.py index 2e206b0c3..c0843d7f0 100644 --- a/pynecone/components/overlay/modal.py +++ b/pynecone/components/overlay/modal.py @@ -1,8 +1,10 @@ """Modal components.""" from typing import Set +from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent +from pynecone.components.media import Icon from pynecone.var import Var @@ -64,8 +66,62 @@ class Modal(ChakraComponent): "on_overlay_click", } + @classmethod + def create( + cls, *children, header=None, body=None, footer=None, close_button=None, **props + ) -> Component: + """Create a modal component. -class ModalOverlay(Modal): + Args: + children: The children of the component. + header: The header of the modal. + body: The body of the modal. + footer: The footer of the modal. + close_button: The close button of the modal. + props: The properties of the component. + + Raises: + AttributeError: error that occurs if conflicting props are passed + + Returns: + The modal component. + """ + if len(children) == 0: + contents = [] + + # add header if present in props + if header: + contents.append(ModalHeader.create(header)) + + # add ModalBody if present in props + if body: + contents.append(ModalBody.create(body)) + + # add ModalFooter if present in props + if footer: + contents.append(ModalFooter.create(footer)) + + # add ModalCloseButton if either a prop for one was passed, or if + if props.get("on_close"): + # get user defined close button or use default one + if not close_button: + close_button = Icon.create(tag="CloseIcon") + contents.append(ModalCloseButton.create(close_button)) + elif close_button: + raise AttributeError( + "Close button can not be used if on_close event handler is not defined" + ) + + children = [ + ModalOverlay.create( + ModalContent.create(*contents), + ) + ] + + return super().create(*children, **props) + + +class ModalOverlay(ChakraComponent): """The dimmed overlay behind the modal dialog.""" tag = "ModalOverlay" @@ -83,7 +139,7 @@ class ModalFooter(ChakraComponent): tag = "ModalFooter" -class ModalContent(Modal): +class ModalContent(ChakraComponent): """The container for the modal dialog's content.""" tag = "ModalContent" @@ -95,7 +151,7 @@ class ModalBody(ChakraComponent): tag = "ModalBody" -class ModalCloseButton(Modal): +class ModalCloseButton(ChakraComponent): """The button that closes the modal.""" tag = "ModalCloseButton" diff --git a/pynecone/components/overlay/popover.py b/pynecone/components/overlay/popover.py index f6b4f5554..abc84641e 100644 --- a/pynecone/components/overlay/popover.py +++ b/pynecone/components/overlay/popover.py @@ -1,6 +1,8 @@ """Popover components.""" from typing import Set +from pynecone.components.component import Component +from pynecone.components.forms.button import Button from pynecone.components.libs.chakra import ChakraComponent from pynecone.var import Var @@ -83,8 +85,55 @@ class Popover(ChakraComponent): """ return super().get_triggers() | {"on_close", "on_open"} + @classmethod + def create( + cls, + *children, + trigger=None, + header=None, + body=None, + footer=None, + use_close_button=False, + **props + ) -> Component: + """Create a popover component. -class PopoverContent(Popover): + Args: + children: The children of the component. + trigger: The trigger that opens the popover. + header: The header of the popover. + body: The body of the popover. + footer: The footer of the popover. + use_close_button: Whether to add a close button on the popover. + props: The properties of the component. + + Returns: + The popover component. + """ + if len(children) == 0: + contents = [] + + trigger = PopoverTrigger.create(trigger) + + # add header if present in props + if header: + contents.append(PopoverHeader.create(header)) + + if body: + contents.append(PopoverBody.create(body)) + + if footer: + contents.append(PopoverFooter.create(footer)) + + if use_close_button: + contents.append(PopoverCloseButton.create()) + + children = [trigger, PopoverContent.create(*contents)] + + return super().create(*children, **props) + + +class PopoverContent(ChakraComponent): """The popover itself.""" tag = "PopoverContent" @@ -108,25 +157,25 @@ class PopoverBody(ChakraComponent): tag = "PopoverBody" -class PopoverArrow(Popover): +class PopoverArrow(ChakraComponent): """A visual arrow that points to the reference (or trigger).""" tag = "PopoverArrow" -class PopoverCloseButton(Popover): +class PopoverCloseButton(ChakraComponent): """A button to close the popover.""" tag = "PopoverCloseButton" -class PopoverAnchor(Popover): +class PopoverAnchor(ChakraComponent): """Used to wrap the position-reference element.""" tag = "PopoverAnchor" -class PopoverTrigger(Popover): +class PopoverTrigger(ChakraComponent): """Used to wrap the reference (or trigger) element.""" tag = "PopoverTrigger"