diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4a7669..99e0163 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ fail_fast: true repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.2 + rev: v0.12.3 hooks: - id: ruff-check files: ^reflex_ui/ diff --git a/pyproject.toml b/pyproject.toml index 1cc2560..ce532f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.0.1" description = "A set of reusable components built on top of Base UI and Tailwind, designed for use across any Reflex project" readme = "README.md" requires-python = ">=3.13" -dependencies = ["reflex>=0.8.1"] +dependencies = ["reflex>=0.8.2"] [build-system] requires = ["hatchling", "uv-dynamic-versioning"] diff --git a/reflex_ui/__init__.py b/reflex_ui/__init__.py index 446e414..68a6459 100644 --- a/reflex_ui/__init__.py +++ b/reflex_ui/__init__.py @@ -13,6 +13,7 @@ "components.base.input": ["input"], "components.base.link": ["link"], "components.base.menu": ["menu"], + "components.base.navigation_menu": ["navigation_menu"], "components.base.popover": ["popover"], "components.base.scroll_area": ["scroll_area"], "components.base.select": ["select"], diff --git a/reflex_ui/__init__.pyi b/reflex_ui/__init__.pyi index b8705d3..5ffd8ca 100644 --- a/reflex_ui/__init__.pyi +++ b/reflex_ui/__init__.pyi @@ -15,6 +15,7 @@ from .components.base.gradient_profile import gradient_profile from .components.base.input import input from .components.base.link import link from .components.base.menu import menu +from .components.base.navigation_menu import navigation_menu from .components.base.popover import popover from .components.base.scroll_area import scroll_area from .components.base.select import select @@ -41,6 +42,7 @@ _REFLEX_UI_MAPPING = { "components.base.input": ["input"], "components.base.link": ["link"], "components.base.menu": ["menu"], + "components.base.navigation_menu": ["navigation_menu"], "components.base.popover": ["popover"], "components.base.scroll_area": ["scroll_area"], "components.base.select": ["select"], @@ -78,6 +80,7 @@ __all__ = [ "input", "link", "menu", + "navigation_menu", "popover", "scroll_area", "select", diff --git a/reflex_ui/components/base/__init__.pyi b/reflex_ui/components/base/__init__.pyi index 81b4b4d..2436a31 100644 --- a/reflex_ui/components/base/__init__.pyi +++ b/reflex_ui/components/base/__init__.pyi @@ -15,6 +15,7 @@ from .gradient_profile import gradient_profile from .input import input from .link import link from .menu import menu +from .navigation_menu import navigation_menu from .popover import popover from .scroll_area import scroll_area from .select import select @@ -43,6 +44,7 @@ __all__ = [ "input", "link", "menu", + "navigation_menu", "popover", "scroll_area", "select", diff --git a/reflex_ui/components/base/navigation_menu.py b/reflex_ui/components/base/navigation_menu.py new file mode 100644 index 0000000..deaf9ea --- /dev/null +++ b/reflex_ui/components/base/navigation_menu.py @@ -0,0 +1,336 @@ +"""Custom navigation menu component.""" + +from typing import Literal + +from reflex.components.component import Component, ComponentNamespace +from reflex.event import EventHandler, passthrough_event_spec +from reflex.utils.imports import ImportVar +from reflex.vars.base import Var + +from reflex_ui.components.base_ui import PACKAGE_NAME, BaseUIComponent + +LiteralNavigationMenuOrientation = Literal["horizontal", "vertical"] +LiteralSide = Literal["top", "right", "bottom", "left"] +LiteralAlign = Literal["start", "center", "end"] +LiteralPositionMethod = Literal["absolute", "fixed"] +LiteralCollisionAvoidance = Literal["flip", "shift", "auto"] + + +class ClassNames: + """Class names for navigation menu components.""" + + ROOT = "min-w-max rounded-lg bg-secondary-1 p-1 text-secondary-12" + LIST = "relative flex" + ITEM = "relative" + TRIGGER = "box-border flex items-center justify-center gap-1.5 h-10 px-2 xs:px-3.5 m-0 rounded-md bg-secondary-1 text-secondary-12 font-medium text-[0.925rem] xs:text-base leading-6 select-none no-underline hover:bg-secondary-3 active:bg-secondary-3 data-[popup-open]:bg-secondary-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary-4" + CONTENT = "w-max max-w-[calc(100vw-40px)] sm:max-w-[600px] p-6 transition-[opacity,transform,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 data-[starting-style]:data-[activation-direction=left]:translate-x-[-50%] data-[starting-style]:data-[activation-direction=right]:translate-x-[50%] data-[ending-style]:data-[activation-direction=left]:translate-x-[50%] data-[ending-style]:data-[activation-direction=right]:translate-x-[-50%]" + LINK = "block rounded-md p-2 xs:p-3 no-underline text-inherit hover:bg-secondary-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary-4" + ICON = "transition-transform duration-200 ease-in-out data-[popup-open]:rotate-180 text-secondary-10" + PORTAL = "" + POSITIONER = "box-border h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)] transition-[top,left,right,bottom] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] before:absolute before:content-[''] data-[instant]:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0 data-[side=bottom]:before:h-2.5 data-[side=left]:before:top-0 data-[side=left]:before:right-[-10px] data-[side=left]:before:bottom-0 data-[side=left]:before:w-2.5 data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:left-[-10px] data-[side=right]:before:w-2.5 data-[side=top]:before:right-0 data-[side=top]:before:bottom-[-10px] data-[side=top]:before:left-0 data-[side=top]:before:h-2.5" + POPUP = "relative h-[var(--popup-height)] w-max origin-[var(--transform-origin)] rounded-lg bg-secondary-1 text-secondary-12 shadow-large border border-secondary-a4 transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 min-[500px]:w-[var(--popup-width)] xs:w-[var(--popup-width)]" + VIEWPORT = "relative h-full w-full overflow-hidden" + ARROW = "flex transition-[left] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180" + BACKDROP = "fixed inset-0 z-40" + + +class NavigationMenuBaseComponent(BaseUIComponent): + """Base component for navigation menu components.""" + + library = f"{PACKAGE_NAME}/navigation-menu" + + @property + def import_var(self): + """Return the import variable for the navigation menu component.""" + return ImportVar(tag="NavigationMenu", package_path="", install=False) + + +class NavigationMenuRoot(NavigationMenuBaseComponent): + """Groups all parts of the navigation menu. Renders a