Skip to content

Commit 761e031

Browse files
committed
ENG-6163: Tabs component
1 parent 2317bf8 commit 761e031

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed

reflex_ui/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"components.base.skeleton": ["skeleton"],
1818
"components.base.slider": ["slider"],
1919
"components.base.switch": ["switch"],
20+
"components.base.tabs": ["tabs"],
2021
"components.base.theme_switcher": ["theme_switcher"],
2122
"components.base.toggle": ["toggle"],
2223
"components.base.tooltip": ["tooltip"],

reflex_ui/components/base/tabs.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
"""Custom tabs component."""
2+
3+
from typing import Literal
4+
5+
from reflex.components.component import Component, ComponentNamespace
6+
from reflex.event import EventHandler, passthrough_event_spec
7+
from reflex.utils.imports import ImportVar
8+
from reflex.vars import Var
9+
10+
from reflex_ui.components.base_ui import PACKAGE_NAME, BaseUIComponent
11+
12+
LiteralOrientation = Literal["horizontal", "vertical"]
13+
14+
15+
class ClassNames:
16+
"""Class names for tabs components."""
17+
18+
ROOT = "flex flex-col gap-2"
19+
LIST = "bg-secondary-3 inline-flex gap-1 p-1 items-center justify-start rounded-md relative z-0"
20+
TAB = "h-7 px-1.5 rounded-sm justify-center items-center gap-1.5 inline-flex text-sm font-medium text-secondary-11 cursor-pointer z-[1] hover:text-secondary-12 transition-color text-nowrap data-[selected]:text-secondary-12 data-[disabled]:cursor-not-allowed data-[disabled]:text-secondary-8"
21+
INDICATOR = "absolute top-1/2 left-0 -z-1 h-7 w-(--active-tab-width) -translate-y-1/2 translate-x-(--active-tab-left) rounded-sm bg-secondary-1 shadow-small transition-all duration-200 ease-in-out"
22+
PANEL = "flex flex-col gap-2 p-2"
23+
24+
25+
class TabsBaseComponent(BaseUIComponent):
26+
"""Base component for tabs components."""
27+
28+
library = f"{PACKAGE_NAME}/tabs"
29+
30+
@property
31+
def import_var(self):
32+
"""Return the import variable for the tabs component."""
33+
return ImportVar(tag="Tabs", package_path="", install=False)
34+
35+
36+
class TabsRoot(TabsBaseComponent):
37+
"""Groups the tabs and the corresponding panels. Renders a <div> element."""
38+
39+
tag = "Tabs.Root"
40+
41+
# The default value. Use when the component is not controlled. When the value is null, no Tab will be selected. Defaults to 0.
42+
default_value: Var[str | int]
43+
44+
# The value of the currently selected Tab. Use when the component is controlled. When the value is null, no Tab will be selected.
45+
value: Var[str | int]
46+
47+
# Callback invoked when new value is being set.
48+
on_value_change: EventHandler[passthrough_event_spec(str | dict)]
49+
50+
# The component orientation (layout flow direction). Defaults to "horizontal".
51+
orientation: Var[LiteralOrientation]
52+
53+
# The render prop
54+
render_: Var[Component]
55+
56+
@classmethod
57+
def create(cls, *children, **props) -> Component:
58+
"""Create the tabs root component."""
59+
props["data-slot"] = "tabs"
60+
cls.set_class_name(ClassNames.ROOT, props)
61+
return super().create(*children, **props)
62+
63+
64+
class TabsList(TabsBaseComponent):
65+
"""Groups the individual tab buttons. Renders a <div> element."""
66+
67+
tag = "Tabs.List"
68+
69+
# Whether to automatically change the active tab on arrow key focus. Otherwise, tabs will be activated using Enter or Spacebar key press. Defaults to True.
70+
activate_on_focus: Var[bool]
71+
72+
# 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.
73+
loop: Var[bool]
74+
75+
@classmethod
76+
def create(cls, *children, **props) -> Component:
77+
"""Create the tabs list component."""
78+
props["data-slot"] = "tabs-list"
79+
cls.set_class_name(ClassNames.LIST, props)
80+
return super().create(*children, **props)
81+
82+
83+
class TabsTab(TabsBaseComponent):
84+
"""An individual interactive tab button that toggles the corresponding panel. Renders a <button> element."""
85+
86+
tag = "Tabs.Tab"
87+
88+
# The value of the Tab. When not specified, the value is the child position index.
89+
value: Var[str | int]
90+
91+
# Whether the component renders a native <button> element when replacing it via the render prop. Set to false if the rendered element is not a button (e.g. <div>). Defaults to True.
92+
native_button: Var[bool]
93+
94+
# Whether the Tab is disabled. Defaults to false.
95+
disabled: Var[bool]
96+
97+
@classmethod
98+
def create(cls, *children, **props) -> Component:
99+
"""Create the tabs tab component."""
100+
props["data-slot"] = "tabs-tab"
101+
cls.set_class_name(ClassNames.TAB, props)
102+
return super().create(*children, **props)
103+
104+
105+
class TabsIndicator(TabsBaseComponent):
106+
"""A visual indicator that can be styled to match the position of the currently active tab. Renders a <span> element."""
107+
108+
tag = "Tabs.Indicator"
109+
110+
# Whether to render itself before React hydrates. This minimizes the time that the indicator isn't visible after server-side rendering. Defaults to False.
111+
render_before_hydration: Var[bool]
112+
113+
@classmethod
114+
def create(cls, *children, **props) -> Component:
115+
"""Create the tabs indicator component."""
116+
props["data-slot"] = "tabs-indicator"
117+
cls.set_class_name(ClassNames.INDICATOR, props)
118+
return super().create(*children, **props)
119+
120+
121+
class TabsPanel(TabsBaseComponent):
122+
"""A panel displayed when the corresponding tab is active. Renders a <div> element."""
123+
124+
tag = "Tabs.Panel"
125+
126+
# The value of the TabPanel. It will be shown when the Tab with the corresponding value is selected. If not provided, it will fall back to the index of the panel. It is recommended to explicitly provide it, as it's required for the tab panel to be rendered on the server.
127+
value: Var[str | int]
128+
129+
# Whether to keep the HTML element in the DOM while the panel is hidden. Defaults to False.
130+
keep_mounted: Var[bool]
131+
132+
@classmethod
133+
def create(cls, *children, **props) -> Component:
134+
"""Create the tabs panel component."""
135+
props["data-slot"] = "tabs-panel"
136+
cls.set_class_name(ClassNames.PANEL, props)
137+
return super().create(*children, **props)
138+
139+
140+
class Tabs(ComponentNamespace):
141+
"""Namespace for Tabs components."""
142+
143+
root = __call__ = staticmethod(TabsRoot.create)
144+
list = staticmethod(TabsList.create)
145+
tab = staticmethod(TabsTab.create)
146+
panel = staticmethod(TabsPanel.create)
147+
indicator = staticmethod(TabsIndicator.create)
148+
149+
150+
tabs = Tabs()

0 commit comments

Comments
 (0)