diff --git a/reflex_ui/__init__.py b/reflex_ui/__init__.py index f830f4f..cdd5a12 100644 --- a/reflex_ui/__init__.py +++ b/reflex_ui/__init__.py @@ -3,6 +3,7 @@ from reflex.utils import lazy_loader _REFLEX_UI_MAPPING = { + "components.base.accordion": ["accordion"], "components.base.avatar": ["avatar"], "components.base.badge": ["badge"], "components.base.button": ["button"], diff --git a/reflex_ui/components/base/accordion.py b/reflex_ui/components/base/accordion.py new file mode 100644 index 0000000..f138a06 --- /dev/null +++ b/reflex_ui/components/base/accordion.py @@ -0,0 +1,296 @@ +"""Custom Accordion component.""" + +from typing import Literal + +from reflex.components.component import Component, ComponentNamespace +from reflex.components.core.foreach import foreach +from reflex.components.el import Div +from reflex.event import EventHandler, passthrough_event_spec +from reflex.utils.imports import ImportVar +from reflex.vars.base import Var +from reflex.vars.object import ObjectVar + +from reflex_ui.components.base_ui import PACKAGE_NAME, BaseUIComponent +from reflex_ui.components.icons.hugeicon import icon + +LiteralOrientation = Literal["horizontal", "vertical"] + +ITEMS_TYPE = list[dict[str, str | Component]] + + +class ClassNames: + """Class names for accordion components.""" + + ROOT = "flex flex-col justify-center shadow-small border border-secondary-a4 divide-y divide-secondary-a4 overflow-hidden rounded-xl" + ITEM = "" + HEADER = "" + TRIGGER = "group relative flex w-full items-center justify-between gap-4 bg-secondary-1 hover:bg-secondary-3 px-6 py-4 text-md font-semibold text-secondary-12 transition-colors disabled:cursor-not-allowed disabled:bg-secondary-3 disabled:text-secondary-8 disabled:[&_svg]:text-secondary-8 [&_svg]:text-secondary-11" + PANEL = "h-[var(--accordion-panel-height)] overflow-hidden text-base text-secondary-11 font-medium transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0 border-t border-secondary-a4" + PANEL_DIV = "py-4 px-6" + TRIGGER_ICON = "size-4 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45" + + +class AccordionBaseComponent(BaseUIComponent): + """Base component for accordion components.""" + + library = f"{PACKAGE_NAME}/accordion" + + @property + def import_var(self): + """Return the import variable for the accordion component.""" + return ImportVar(tag="Accordion", package_path="", install=False) + + +class AccordionRoot(AccordionBaseComponent): + """Groups all parts of the accordion.""" + + tag = "Accordion.Root" + + # The uncontrolled value of the item(s) that should be initially expanded. To render a controlled accordion, use the `value` prop instead. + default_value: Var[list[str]] + + # The controlled value of the item(s) that should be expanded. To render an uncontrolled accordion, use the `default_value` prop instead. + value: Var[list[str]] + + # Event handler called when an accordion item is expanded or collapsed. Provides the new value as an argument. + on_value_change: EventHandler[passthrough_event_spec(list[str])] + + # Allows the browser's built-in page search to find and expand the panel contents. Overrides the `keep_mounted` prop and uses `hidden="until-found"` to hide the element without removing it from the DOM. Defaults to False. + hidden_until_found: Var[bool] + + # Whether multiple items can be open at the same time. Defaults to True. + open_multiple: Var[bool] + + # Whether the component should ignore user interaction. Defaults to False. + disabled: Var[bool] + + # Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. Defaults to True. + loop: Var[bool] + + # The visual orientation of the accordion. Controls whether roving focus uses left/right or up/down arrow keys. Defaults to 'vertical'. + orientation: Var[LiteralOrientation] + + # Whether to keep the element in the DOM while the panel is closed. This prop is ignored when hidden_until_found is used. Defaults to False. + keep_mounted: Var[bool] + + # The render prop. + render_: Var[Component] + + @classmethod + def create(cls, *children, **props) -> BaseUIComponent: + """Create the accordion root component.""" + props["data-slot"] = "accordion" + cls.set_class_name(ClassNames.ROOT, props) + return super().create(*children, **props) + + +class AccordionItem(AccordionBaseComponent): + """Groups an accordion header with the corresponding panel.""" + + tag = "Accordion.Item" + + # The value that identifies this item. + value: Var[str] + + # Event handler called when the panel is opened or closed. + on_open_change: EventHandler[passthrough_event_spec(bool)] + + # Whether the component should ignore user interaction. Defaults to False. + disabled: Var[bool] + + # The render prop. + render_: Var[Component] + + @classmethod + def create(cls, *children, **props) -> BaseUIComponent: + """Create the accordion item component.""" + props["data-slot"] = "accordion-item" + cls.set_class_name(ClassNames.ITEM, props) + return super().create(*children, **props) + + +class AccordionHeader(AccordionBaseComponent): + """A heading that labels the corresponding panel.""" + + tag = "Accordion.Header" + + # The render prop. + render_: Var[Component] + + @classmethod + def create(cls, *children, **props) -> BaseUIComponent: + """Create the accordion header component.""" + props["data-slot"] = "accordion-header" + cls.set_class_name(ClassNames.HEADER, props) + return super().create(*children, **props) + + +class AccordionTrigger(AccordionBaseComponent): + """A button that opens and closes the corresponding panel.""" + + tag = "Accordion.Trigger" + + # Whether the component renders a native `