buridan-create 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- buridan_create-0.1.0/.gitignore +15 -0
- buridan_create-0.1.0/.python-version +1 -0
- buridan_create-0.1.0/PKG-INFO +6 -0
- buridan_create-0.1.0/app/__init__.py +0 -0
- buridan_create-0.1.0/app/app.py +6 -0
- buridan_create-0.1.0/app/base_ui/__init__.py +0 -0
- buridan_create-0.1.0/app/base_ui/components/__init__.py +0 -0
- buridan_create-0.1.0/app/base_ui/components/base/__init__.py +0 -0
- buridan_create-0.1.0/app/base_ui/components/base/accordion.py +331 -0
- buridan_create-0.1.0/app/base_ui/components/base/avatar.py +126 -0
- buridan_create-0.1.0/app/base_ui/components/base/badge.py +88 -0
- buridan_create-0.1.0/app/base_ui/components/base/breadcrumb.py +95 -0
- buridan_create-0.1.0/app/base_ui/components/base/button.py +124 -0
- buridan_create-0.1.0/app/base_ui/components/base/checkbox.py +138 -0
- buridan_create-0.1.0/app/base_ui/components/base/collapsible.py +141 -0
- buridan_create-0.1.0/app/base_ui/components/base/context_menu.py +678 -0
- buridan_create-0.1.0/app/base_ui/components/base/dialog.py +277 -0
- buridan_create-0.1.0/app/base_ui/components/base/input.py +47 -0
- buridan_create-0.1.0/app/base_ui/components/base/input_group.py +93 -0
- buridan_create-0.1.0/app/base_ui/components/base/kbd.py +48 -0
- buridan_create-0.1.0/app/base_ui/components/base/link.py +105 -0
- buridan_create-0.1.0/app/base_ui/components/base/menu.py +690 -0
- buridan_create-0.1.0/app/base_ui/components/base/popover.py +375 -0
- buridan_create-0.1.0/app/base_ui/components/base/scroll_area.py +206 -0
- buridan_create-0.1.0/app/base_ui/components/base/select.py +593 -0
- buridan_create-0.1.0/app/base_ui/components/base/skeleton.py +23 -0
- buridan_create-0.1.0/app/base_ui/components/base/slider.py +232 -0
- buridan_create-0.1.0/app/base_ui/components/base/switch.py +118 -0
- buridan_create-0.1.0/app/base_ui/components/base/tabs.py +167 -0
- buridan_create-0.1.0/app/base_ui/components/base/textarea.py +46 -0
- buridan_create-0.1.0/app/base_ui/components/base/theme_switcher.py +37 -0
- buridan_create-0.1.0/app/base_ui/components/base/toggle.py +62 -0
- buridan_create-0.1.0/app/base_ui/components/base/toggle_group.py +69 -0
- buridan_create-0.1.0/app/base_ui/components/base/tooltip.py +316 -0
- buridan_create-0.1.0/app/base_ui/components/base/typography.py +146 -0
- buridan_create-0.1.0/app/base_ui/components/base_ui.py +12 -0
- buridan_create-0.1.0/app/base_ui/components/component.py +43 -0
- buridan_create-0.1.0/app/base_ui/icons/hugeicon.py +64 -0
- buridan_create-0.1.0/app/base_ui/icons/others.py +89 -0
- buridan_create-0.1.0/app/base_ui/utils/__init__.py +0 -0
- buridan_create-0.1.0/app/base_ui/utils/twmerge.py +28 -0
- buridan_create-0.1.0/app/constants.py +0 -0
- buridan_create-0.1.0/app/engine/actions.py +831 -0
- buridan_create-0.1.0/app/engine/seed.py +235 -0
- buridan_create-0.1.0/app/engine/url_sync.py +80 -0
- buridan_create-0.1.0/app/examples/components.py +1162 -0
- buridan_create-0.1.0/app/examples/utils.py +54 -0
- buridan_create-0.1.0/app/export.py +0 -0
- buridan_create-0.1.0/app/hooks.py +35 -0
- buridan_create-0.1.0/app/registry/colors.py +264 -0
- buridan_create-0.1.0/app/registry/fonts.py +28 -0
- buridan_create-0.1.0/app/registry/radii.py +6 -0
- buridan_create-0.1.0/app/registry/styles.py +56 -0
- buridan_create-0.1.0/app/registry/themes.py +433 -0
- buridan_create-0.1.0/app/templates/compiler.py +420 -0
- buridan_create-0.1.0/app/templates/config.py +60 -0
- buridan_create-0.1.0/app/templates/mainpage.py +25 -0
- buridan_create-0.1.0/app/templates/navbar.py +68 -0
- buridan_create-0.1.0/app/templates/options.py +596 -0
- buridan_create-0.1.0/app/templates/preview.py +64 -0
- buridan_create-0.1.0/app/templates/reset.py +51 -0
- buridan_create-0.1.0/app/templates/sidebar.py +99 -0
- buridan_create-0.1.0/assets/favicon.ico +0 -0
- buridan_create-0.1.0/assets/globals.css +72 -0
- buridan_create-0.1.0/assets/svg/claude/claude_dark.svg +1 -0
- buridan_create-0.1.0/assets/svg/claude/claude_light.svg +1 -0
- buridan_create-0.1.0/assets/svg/markdown/md_dark.svg +1 -0
- buridan_create-0.1.0/assets/svg/markdown/md_light.svg +1 -0
- buridan_create-0.1.0/assets/svg/openai/ai_dark.svg +1 -0
- buridan_create-0.1.0/assets/svg/openai/ai_light.svg +1 -0
- buridan_create-0.1.0/assets/svg/reflex/reflex_dark.svg +5 -0
- buridan_create-0.1.0/assets/svg/reflex/reflex_light.svg +5 -0
- buridan_create-0.1.0/assets/svg/theme/theme_dark.svg +31 -0
- buridan_create-0.1.0/assets/svg/theme/theme_light.svg +24 -0
- buridan_create-0.1.0/cli/main.py +273 -0
- buridan_create-0.1.0/pyproject.toml +19 -0
- buridan_create-0.1.0/reflex.lock/bun.lock +649 -0
- buridan_create-0.1.0/reflex.lock/package.json +42 -0
- buridan_create-0.1.0/rxconfig.py +63 -0
- buridan_create-0.1.0/scratch.py +609 -0
- buridan_create-0.1.0/uv.lock +922 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Custom Accordion component."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, 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.base import Var
|
|
9
|
+
from reflex.vars.object import ObjectVar
|
|
10
|
+
from reflex_components_core.core.foreach import foreach
|
|
11
|
+
from reflex_components_core.el import Div
|
|
12
|
+
|
|
13
|
+
from ...icons.hugeicon import hi, icon
|
|
14
|
+
from ..base.button import button
|
|
15
|
+
from ..base_ui import PACKAGE_NAME, BaseUIComponent
|
|
16
|
+
|
|
17
|
+
LiteralOrientation = Literal["horizontal", "vertical"]
|
|
18
|
+
|
|
19
|
+
ITEMS_TYPE = list[dict[str, str | Component]]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ClassNames:
|
|
23
|
+
"""Class names for accordion components."""
|
|
24
|
+
|
|
25
|
+
ROOT = (
|
|
26
|
+
"flex flex-col justify-center divide-y divide-input overflow-hidden rounded-xl"
|
|
27
|
+
)
|
|
28
|
+
ITEM = "not-last:border-b"
|
|
29
|
+
HEADER = ""
|
|
30
|
+
TRIGGER = "group relative flex w-full items-center justify-between gap-4 bg-secondary py-2 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"
|
|
31
|
+
PANEL = "h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0"
|
|
32
|
+
PANEL_DIV = ""
|
|
33
|
+
TRIGGER_ICON = "size-4 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AccordionBaseComponent(BaseUIComponent):
|
|
37
|
+
"""Base component for accordion components."""
|
|
38
|
+
|
|
39
|
+
library = f"{PACKAGE_NAME}/accordion"
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def import_var(self):
|
|
43
|
+
"""Return the import variable for the accordion component."""
|
|
44
|
+
return ImportVar(tag="Accordion", package_path="", install=False)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AccordionRoot(AccordionBaseComponent):
|
|
48
|
+
"""Groups all parts of the accordion."""
|
|
49
|
+
|
|
50
|
+
tag = "Accordion.Root"
|
|
51
|
+
|
|
52
|
+
# The uncontrolled value of the item(s) that should be initially expanded. To render a controlled accordion, use the `value` prop instead.
|
|
53
|
+
default_value: Var[list[Any]]
|
|
54
|
+
|
|
55
|
+
# The controlled value of the item(s) that should be expanded. To render an uncontrolled accordion, use the `default_value` prop instead.
|
|
56
|
+
value: Var[list[Any]]
|
|
57
|
+
|
|
58
|
+
# Event handler called when an accordion item is expanded or collapsed. Provides the new value as an argument.
|
|
59
|
+
on_value_change: EventHandler[passthrough_event_spec(list[str])]
|
|
60
|
+
|
|
61
|
+
# 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.
|
|
62
|
+
hidden_until_found: Var[bool]
|
|
63
|
+
|
|
64
|
+
# Whether multiple items can be open at the same time. Defaults to True.
|
|
65
|
+
multiple: Var[bool]
|
|
66
|
+
|
|
67
|
+
# Whether the component should ignore user interaction. Defaults to False.
|
|
68
|
+
disabled: Var[bool]
|
|
69
|
+
|
|
70
|
+
# 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.
|
|
71
|
+
loop_focus: Var[bool]
|
|
72
|
+
|
|
73
|
+
# The visual orientation of the accordion. Controls whether roving focus uses left/right or up/down arrow keys. Defaults to 'vertical'.
|
|
74
|
+
orientation: Var[LiteralOrientation]
|
|
75
|
+
|
|
76
|
+
# 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.
|
|
77
|
+
keep_mounted: Var[bool]
|
|
78
|
+
|
|
79
|
+
# The render prop.
|
|
80
|
+
render_: Var[Component]
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def create(cls, *children, **props) -> BaseUIComponent:
|
|
84
|
+
"""Create the accordion root component."""
|
|
85
|
+
props["data-slot"] = "accordion"
|
|
86
|
+
cls.set_class_name(ClassNames.ROOT, props)
|
|
87
|
+
return super().create(*children, **props)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AccordionItem(AccordionBaseComponent):
|
|
91
|
+
"""Groups an accordion header with the corresponding panel."""
|
|
92
|
+
|
|
93
|
+
tag = "Accordion.Item"
|
|
94
|
+
|
|
95
|
+
# The value that identifies this item.
|
|
96
|
+
value: Var[str]
|
|
97
|
+
|
|
98
|
+
# Event handler called when the panel is opened or closed.
|
|
99
|
+
on_open_change: EventHandler[passthrough_event_spec(bool)]
|
|
100
|
+
|
|
101
|
+
# Whether the component should ignore user interaction. Defaults to False.
|
|
102
|
+
disabled: Var[bool]
|
|
103
|
+
|
|
104
|
+
# The render prop.
|
|
105
|
+
render_: Var[Component]
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def create(cls, *children, **props) -> BaseUIComponent:
|
|
109
|
+
"""Create the accordion item component."""
|
|
110
|
+
props["data-slot"] = "accordion-item"
|
|
111
|
+
cls.set_class_name(ClassNames.ITEM, props)
|
|
112
|
+
return super().create(*children, **props)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class AccordionHeader(AccordionBaseComponent):
|
|
116
|
+
"""A heading that labels the corresponding panel."""
|
|
117
|
+
|
|
118
|
+
tag = "Accordion.Header"
|
|
119
|
+
|
|
120
|
+
# The render prop.
|
|
121
|
+
render_: Var[Component]
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def create(cls, *children, **props) -> BaseUIComponent:
|
|
125
|
+
"""Create the accordion header component."""
|
|
126
|
+
props["data-slot"] = "accordion-header"
|
|
127
|
+
cls.set_class_name(ClassNames.HEADER, props)
|
|
128
|
+
return super().create(*children, **props)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class AccordionTrigger(AccordionBaseComponent):
|
|
132
|
+
"""A button that opens and closes the corresponding panel."""
|
|
133
|
+
|
|
134
|
+
tag = "Accordion.Trigger"
|
|
135
|
+
|
|
136
|
+
title: Var[str]
|
|
137
|
+
native_button: Var[bool]
|
|
138
|
+
|
|
139
|
+
# Default is None so we can detect overrides
|
|
140
|
+
render_: Var[Component] = None
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def create(cls, *children, **props) -> BaseUIComponent:
|
|
144
|
+
props["data-slot"] = "accordion-trigger"
|
|
145
|
+
cls.set_class_name(ClassNames.TRIGGER, props)
|
|
146
|
+
|
|
147
|
+
# 1️⃣ If render_ is NOT provided, we must resolve title
|
|
148
|
+
if "render_" not in props or props["render_"] is None:
|
|
149
|
+
if "title" not in props:
|
|
150
|
+
if len(children) == 1 and isinstance(children[0], str):
|
|
151
|
+
props["title"] = children[0]
|
|
152
|
+
elif children:
|
|
153
|
+
raise TypeError(
|
|
154
|
+
"AccordionTrigger expects a single string child "
|
|
155
|
+
"when used without a `title` prop."
|
|
156
|
+
)
|
|
157
|
+
else:
|
|
158
|
+
raise ValueError("AccordionTrigger requires a `title`.")
|
|
159
|
+
|
|
160
|
+
# 2️⃣ Generate default render_
|
|
161
|
+
props["render_"] = button(
|
|
162
|
+
props["title"],
|
|
163
|
+
hi(
|
|
164
|
+
"Add01Icon",
|
|
165
|
+
class_name=(
|
|
166
|
+
"size-3 transition-transform duration-50 ease-in-out "
|
|
167
|
+
"group-aria-[expanded=true]:rotate-45"
|
|
168
|
+
),
|
|
169
|
+
),
|
|
170
|
+
variant="ghost",
|
|
171
|
+
class_name=(
|
|
172
|
+
"w-full flex items-center justify-between group py-2 "
|
|
173
|
+
"font-medium !text-sm hover:bg-transparent !px-0"
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# 3️⃣ render_ was supplied → leave it untouched
|
|
178
|
+
return super().create(*children, **props)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class AccordionPanel(AccordionBaseComponent):
|
|
182
|
+
"""A collapsible panel with the accordion item contents."""
|
|
183
|
+
|
|
184
|
+
tag = "Accordion.Panel"
|
|
185
|
+
|
|
186
|
+
# 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.
|
|
187
|
+
hidden_until_found: Var[bool]
|
|
188
|
+
|
|
189
|
+
# 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.
|
|
190
|
+
keep_mounted: Var[bool]
|
|
191
|
+
|
|
192
|
+
# The render prop.
|
|
193
|
+
render_: Var[Component]
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def create(cls, *children, **props) -> BaseUIComponent:
|
|
197
|
+
"""Create the accordion panel component."""
|
|
198
|
+
props["data-slot"] = "accordion-panel"
|
|
199
|
+
cls.set_class_name(ClassNames.PANEL, props)
|
|
200
|
+
return super().create(*children, **props)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class HighLevelAccordion(AccordionRoot):
|
|
204
|
+
"""High level wrapper for the Accordion component."""
|
|
205
|
+
|
|
206
|
+
items: Var[ITEMS_TYPE] | ITEMS_TYPE
|
|
207
|
+
|
|
208
|
+
_item_props = {"on_open_change", "disabled"}
|
|
209
|
+
_trigger_props = {"native_button"}
|
|
210
|
+
_panel_props = {"hidden_until_found", "keep_mounted"}
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def create(
|
|
214
|
+
cls,
|
|
215
|
+
items: Var[ITEMS_TYPE] | ITEMS_TYPE,
|
|
216
|
+
**props,
|
|
217
|
+
) -> BaseUIComponent:
|
|
218
|
+
"""Create a high level accordion component.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
items: List of dictionaries with 'trigger', 'content', and optional 'value' and 'disabled' keys.
|
|
222
|
+
**props: Additional properties to apply to the accordion component.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
The accordion component with all necessary subcomponents.
|
|
226
|
+
"""
|
|
227
|
+
# Extract props for different parts
|
|
228
|
+
item_props = {k: props.pop(k) for k in cls._item_props & props.keys()}
|
|
229
|
+
trigger_props = {k: props.pop(k) for k in cls._trigger_props & props.keys()}
|
|
230
|
+
panel_props = {k: props.pop(k) for k in cls._panel_props & props.keys()}
|
|
231
|
+
|
|
232
|
+
if isinstance(items, Var):
|
|
233
|
+
accordion_items = foreach(
|
|
234
|
+
items,
|
|
235
|
+
lambda item: cls._create_accordion_item_dynamic(
|
|
236
|
+
item, item_props, trigger_props, panel_props
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
return AccordionRoot.create(accordion_items, **props)
|
|
240
|
+
accordion_items = [
|
|
241
|
+
cls._create_accordion_item(
|
|
242
|
+
item, index, item_props, trigger_props, panel_props
|
|
243
|
+
)
|
|
244
|
+
for index, item in enumerate(items)
|
|
245
|
+
]
|
|
246
|
+
return AccordionRoot.create(*accordion_items, **props)
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def _create_trigger_icon(cls) -> Component:
|
|
250
|
+
"""Create the accordion trigger icon."""
|
|
251
|
+
return icon(
|
|
252
|
+
"PlusSignIcon",
|
|
253
|
+
class_name=ClassNames.TRIGGER_ICON,
|
|
254
|
+
data_slot="accordion-trigger-icon",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def _create_accordion_item(
|
|
259
|
+
cls,
|
|
260
|
+
item: dict[str, str | Component],
|
|
261
|
+
index: int,
|
|
262
|
+
item_props: dict,
|
|
263
|
+
trigger_props: dict,
|
|
264
|
+
panel_props: dict,
|
|
265
|
+
) -> BaseUIComponent:
|
|
266
|
+
"""Create a single accordion item from a dictionary (for normal lists)."""
|
|
267
|
+
return AccordionItem.create(
|
|
268
|
+
AccordionHeader.create(
|
|
269
|
+
AccordionTrigger.create(
|
|
270
|
+
item.get("trigger"),
|
|
271
|
+
cls._create_trigger_icon(),
|
|
272
|
+
**trigger_props,
|
|
273
|
+
),
|
|
274
|
+
),
|
|
275
|
+
AccordionPanel.create(
|
|
276
|
+
Div.create(
|
|
277
|
+
item.get("content"),
|
|
278
|
+
class_name=ClassNames.PANEL_DIV,
|
|
279
|
+
data_slot="accordion-panel-div",
|
|
280
|
+
),
|
|
281
|
+
**panel_props,
|
|
282
|
+
),
|
|
283
|
+
value=item.get("value", f"item-{index + 1}"),
|
|
284
|
+
disabled=item.get("disabled", False),
|
|
285
|
+
**item_props,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
@classmethod
|
|
289
|
+
def _create_accordion_item_dynamic(
|
|
290
|
+
cls,
|
|
291
|
+
item: ObjectVar[dict[str, str | Component]],
|
|
292
|
+
item_props: dict,
|
|
293
|
+
trigger_props: dict,
|
|
294
|
+
panel_props: dict,
|
|
295
|
+
) -> BaseUIComponent:
|
|
296
|
+
"""Create a single accordion item from a dictionary (for Var items)."""
|
|
297
|
+
return AccordionItem.create(
|
|
298
|
+
AccordionHeader.create(
|
|
299
|
+
AccordionTrigger.create(
|
|
300
|
+
item["trigger"],
|
|
301
|
+
cls._create_trigger_icon(),
|
|
302
|
+
**trigger_props,
|
|
303
|
+
),
|
|
304
|
+
),
|
|
305
|
+
AccordionPanel.create(
|
|
306
|
+
Div.create(
|
|
307
|
+
item["content"],
|
|
308
|
+
class_name=ClassNames.PANEL_DIV,
|
|
309
|
+
data_slot="accordion-panel-div",
|
|
310
|
+
),
|
|
311
|
+
**panel_props,
|
|
312
|
+
),
|
|
313
|
+
value=item.get("value", ""),
|
|
314
|
+
disabled=item.get("disabled", False).bool(),
|
|
315
|
+
**item_props,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class Accordion(ComponentNamespace):
|
|
320
|
+
"""Namespace for Accordion components."""
|
|
321
|
+
|
|
322
|
+
root = staticmethod(AccordionRoot.create)
|
|
323
|
+
item = staticmethod(AccordionItem.create)
|
|
324
|
+
header = staticmethod(AccordionHeader.create)
|
|
325
|
+
trigger = staticmethod(AccordionTrigger.create)
|
|
326
|
+
panel = staticmethod(AccordionPanel.create)
|
|
327
|
+
class_names = ClassNames
|
|
328
|
+
__call__ = staticmethod(HighLevelAccordion.create)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
accordion = Accordion()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Custom avatar component."""
|
|
2
|
+
|
|
3
|
+
from reflex.components.component import Component, ComponentNamespace
|
|
4
|
+
from reflex.event import EventHandler, passthrough_event_spec
|
|
5
|
+
from reflex.utils.imports import ImportVar
|
|
6
|
+
from reflex.vars.base import Var
|
|
7
|
+
|
|
8
|
+
from ..base_ui import PACKAGE_NAME, BaseUIComponent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ClassNames:
|
|
12
|
+
"""Class names for avatar components."""
|
|
13
|
+
|
|
14
|
+
ROOT = "shrink-0 inline-flex size-8 items-center justify-center overflow-hidden rounded-full align-middle text-base font-medium select-none"
|
|
15
|
+
IMAGE = "size-full object-cover shrink-0"
|
|
16
|
+
FALLBACK = "flex size-full items-center justify-center text-sm bg-muted"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AvatarBaseComponent(BaseUIComponent):
|
|
20
|
+
"""Base component for avatar components."""
|
|
21
|
+
|
|
22
|
+
library = f"{PACKAGE_NAME}/avatar"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def import_var(self):
|
|
26
|
+
"""Return the import variable for the avatar component."""
|
|
27
|
+
return ImportVar(tag="Avatar", package_path="", install=False)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AvatarRoot(AvatarBaseComponent):
|
|
31
|
+
"""Displays a user's profile picture, initials, or fallback icon."""
|
|
32
|
+
|
|
33
|
+
tag = "Avatar.Root"
|
|
34
|
+
|
|
35
|
+
# The component to render
|
|
36
|
+
render_: Var[Component]
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def create(cls, *children, **props) -> BaseUIComponent:
|
|
40
|
+
"""Create the avatar root component."""
|
|
41
|
+
props["data-slot"] = "avatar"
|
|
42
|
+
cls.set_class_name(ClassNames.ROOT, props)
|
|
43
|
+
return super().create(*children, **props)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AvatarImage(AvatarBaseComponent):
|
|
47
|
+
"""The image to be displayed in the avatar."""
|
|
48
|
+
|
|
49
|
+
tag = "Avatar.Image"
|
|
50
|
+
|
|
51
|
+
# The image source URL
|
|
52
|
+
src: Var[str]
|
|
53
|
+
|
|
54
|
+
# Callback when loading status changes
|
|
55
|
+
on_loading_status_change: EventHandler[passthrough_event_spec(str)]
|
|
56
|
+
|
|
57
|
+
# The component to render
|
|
58
|
+
render_: Var[Component]
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def create(cls, *children, **props) -> BaseUIComponent:
|
|
62
|
+
"""Create the avatar image component."""
|
|
63
|
+
props["data-slot"] = "avatar-image"
|
|
64
|
+
cls.set_class_name(ClassNames.IMAGE, props)
|
|
65
|
+
return super().create(*children, **props)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AvatarFallback(AvatarBaseComponent):
|
|
69
|
+
"""Rendered when the image fails to load or when no image is provided."""
|
|
70
|
+
|
|
71
|
+
tag = "Avatar.Fallback"
|
|
72
|
+
|
|
73
|
+
# How long to wait before showing the fallback. Specified in milliseconds
|
|
74
|
+
delay: Var[int]
|
|
75
|
+
|
|
76
|
+
# The component to render
|
|
77
|
+
render_: Var[Component]
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def create(cls, *children, **props) -> BaseUIComponent:
|
|
81
|
+
"""Create the avatar fallback component."""
|
|
82
|
+
props["data-slot"] = "avatar-fallback"
|
|
83
|
+
cls.set_class_name(ClassNames.FALLBACK, props)
|
|
84
|
+
return super().create(*children, **props)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class HighLevelAvatar(AvatarRoot):
|
|
88
|
+
"""High level wrapper for the Avatar component."""
|
|
89
|
+
|
|
90
|
+
# The image source URL
|
|
91
|
+
src: Var[str]
|
|
92
|
+
|
|
93
|
+
# Image props
|
|
94
|
+
_image_props = {"src", "on_loading_status_change", "render_"}
|
|
95
|
+
|
|
96
|
+
# Fallback props
|
|
97
|
+
_fallback_props = {"delay"}
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def create(cls, *children, **props) -> BaseUIComponent:
|
|
101
|
+
"""Create the avatar component."""
|
|
102
|
+
# Extract props for each subcomponent
|
|
103
|
+
image_props = {k: props.pop(k) for k in cls._image_props & props.keys()}
|
|
104
|
+
fallback_props = {k: props.pop(k) for k in cls._fallback_props & props.keys()}
|
|
105
|
+
|
|
106
|
+
fallback_content = props.pop("fallback", "")
|
|
107
|
+
|
|
108
|
+
return AvatarRoot.create(
|
|
109
|
+
AvatarImage.create(**image_props),
|
|
110
|
+
AvatarFallback.create(fallback_content, **fallback_props),
|
|
111
|
+
*children,
|
|
112
|
+
**props,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Avatar(ComponentNamespace):
|
|
117
|
+
"""Namespace for Avatar components."""
|
|
118
|
+
|
|
119
|
+
root = staticmethod(AvatarRoot.create)
|
|
120
|
+
image = staticmethod(AvatarImage.create)
|
|
121
|
+
fallback = staticmethod(AvatarFallback.create)
|
|
122
|
+
class_names = ClassNames
|
|
123
|
+
__call__ = staticmethod(HighLevelAvatar.create)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
avatar = Avatar()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from reflex.vars.base import Var
|
|
4
|
+
from reflex_components_core.el import Span
|
|
5
|
+
|
|
6
|
+
from ..component import CoreComponent
|
|
7
|
+
|
|
8
|
+
LiteralBadgeVariant = Literal["default", "secondary", "destructive", "outline"]
|
|
9
|
+
|
|
10
|
+
DEFAULT_BASE_CLASSES = (
|
|
11
|
+
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium "
|
|
12
|
+
"w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none "
|
|
13
|
+
"focus-visible:border-[var(--ring)] focus-visible:ring-[var(--ring)]/50 focus-visible:ring-[3px] "
|
|
14
|
+
"aria-invalid:ring-[var(--destructive)]/20 dark:aria-invalid:ring-[var(--destructive)]/40 "
|
|
15
|
+
"aria-invalid:border-[var(--destructive)] transition-[color,box-shadow] overflow-hidden"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
BADGE_VARIANTS = {
|
|
19
|
+
"default": (
|
|
20
|
+
"border-transparent bg-[var(--primary)] text-[var(--primary-foreground)] "
|
|
21
|
+
"[a&]:hover:bg-[var(--primary)]/90"
|
|
22
|
+
),
|
|
23
|
+
"secondary": (
|
|
24
|
+
"border-transparent bg-[var(--secondary)] text-[var(--secondary-foreground)] "
|
|
25
|
+
"[a&]:hover:bg-[var(--secondary)]/90"
|
|
26
|
+
),
|
|
27
|
+
"destructive": (
|
|
28
|
+
"border-transparent bg-[var(--destructive)] text-white "
|
|
29
|
+
"[a&]:hover:bg-[var(--destructive)]/90 "
|
|
30
|
+
"focus-visible:ring-[var(--destructive)]/20 dark:focus-visible:ring-[var(--destructive)]/40 "
|
|
31
|
+
"dark:bg-[var(--destructive)]/60"
|
|
32
|
+
),
|
|
33
|
+
"outline": (
|
|
34
|
+
"text-[var(--foreground)] border-[var(--input)] "
|
|
35
|
+
"[a&]:hover:bg-[var(--accent)] [a&]:hover:text-[var(--accent-foreground)]"
|
|
36
|
+
),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_badge_classes(variant: LiteralBadgeVariant) -> str:
|
|
41
|
+
"""Get the complete badge class string.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
variant: The badge variant to apply
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The complete class string for the badge
|
|
48
|
+
"""
|
|
49
|
+
variant_classes = BADGE_VARIANTS[variant]
|
|
50
|
+
return f"{DEFAULT_BASE_CLASSES} {variant_classes}"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Badge(Span, CoreComponent):
|
|
54
|
+
"""A badge component that displays a label."""
|
|
55
|
+
|
|
56
|
+
# Badge variant
|
|
57
|
+
variant: Var[LiteralBadgeVariant]
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def create(cls, *children, **props) -> Span:
|
|
61
|
+
"""Create the badge component.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
*children: The badge content
|
|
65
|
+
**props: Component properties including variant
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
A configured Span component
|
|
69
|
+
"""
|
|
70
|
+
variant = props.pop("variant", "default")
|
|
71
|
+
|
|
72
|
+
cls.set_class_name(get_badge_classes(variant), props)
|
|
73
|
+
|
|
74
|
+
# Add data-slot attribute
|
|
75
|
+
props.setdefault("data_slot", "badge")
|
|
76
|
+
|
|
77
|
+
return super().create(*children, **props)
|
|
78
|
+
|
|
79
|
+
def _exclude_props(self) -> list[str]:
|
|
80
|
+
"""Exclude component-specific props from being passed to the DOM.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
List of prop names to exclude
|
|
84
|
+
"""
|
|
85
|
+
return [*super()._exclude_props(), "variant"]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
badge = Badge.create
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import reflex as rx
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def breadcrumb(*children, **props):
|
|
5
|
+
"""Breadcrumb navigation container"""
|
|
6
|
+
return rx.el.nav(
|
|
7
|
+
*children, aria_label="breadcrumb", data_slot="breadcrumb", **props
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def breadcrumb_list(*children, class_name: str = "", **props):
|
|
12
|
+
"""Ordered list container for breadcrumb items"""
|
|
13
|
+
base_classes = (
|
|
14
|
+
"text-[var(--muted-foreground)] flex flex-wrap items-center gap-1 text-sm "
|
|
15
|
+
"break-words sm:gap-2.5"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
return rx.el.ol(
|
|
19
|
+
*children,
|
|
20
|
+
data_slot="breadcrumb-list",
|
|
21
|
+
class_name=f"{base_classes} {class_name}".strip(),
|
|
22
|
+
**props,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def breadcrumb_item(*children, class_name: str = "", **props):
|
|
27
|
+
"""Individual breadcrumb item"""
|
|
28
|
+
base_classes = "inline-flex items-center gap-1.5"
|
|
29
|
+
|
|
30
|
+
return rx.el.li(
|
|
31
|
+
*children,
|
|
32
|
+
data_slot="breadcrumb-item",
|
|
33
|
+
class_name=f"{base_classes} {class_name}".strip(),
|
|
34
|
+
**props,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def breadcrumb_link(*children, href: str = "#", class_name: str = "", **props):
|
|
39
|
+
"""Breadcrumb link (clickable)"""
|
|
40
|
+
base_classes = "hover:text-[var(--foreground)] transition-colors no-underline"
|
|
41
|
+
|
|
42
|
+
return rx.el.a(
|
|
43
|
+
*children,
|
|
44
|
+
href=href,
|
|
45
|
+
data_slot="breadcrumb-link",
|
|
46
|
+
class_name=f"{base_classes} {class_name}".strip(),
|
|
47
|
+
**props,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def breadcrumb_page(*children, class_name: str = "", **props):
|
|
52
|
+
"""Current page breadcrumb (non-clickable)"""
|
|
53
|
+
base_classes = "text-[var(--foreground)] font-normal"
|
|
54
|
+
|
|
55
|
+
return rx.el.span(
|
|
56
|
+
*children,
|
|
57
|
+
role="link",
|
|
58
|
+
aria_disabled="true",
|
|
59
|
+
aria_current="page",
|
|
60
|
+
data_slot="breadcrumb-page",
|
|
61
|
+
class_name=f"{base_classes} {class_name}".strip(),
|
|
62
|
+
**props,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def breadcrumb_separator(*children, class_name: str = "", **props):
|
|
67
|
+
"""Separator between breadcrumb items"""
|
|
68
|
+
base_classes = "[&>svg]:size-3.5"
|
|
69
|
+
|
|
70
|
+
if not children:
|
|
71
|
+
children = (rx.icon(tag="chevron-right", size=14),)
|
|
72
|
+
|
|
73
|
+
return rx.el.li(
|
|
74
|
+
*children,
|
|
75
|
+
role="presentation",
|
|
76
|
+
aria_hidden="true",
|
|
77
|
+
data_slot="breadcrumb-separator",
|
|
78
|
+
class_name=f"{base_classes} {class_name}".strip(),
|
|
79
|
+
**props,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def breadcrumb_ellipsis(class_name: str = "", **props):
|
|
84
|
+
"""Ellipsis for collapsed breadcrumb items"""
|
|
85
|
+
base_classes = "flex size-9 items-center justify-center"
|
|
86
|
+
|
|
87
|
+
return rx.el.span(
|
|
88
|
+
rx.icon(tag="ellipsis", size=16),
|
|
89
|
+
rx.el.span("More", class_name="sr-only"),
|
|
90
|
+
role="presentation",
|
|
91
|
+
aria_hidden="true",
|
|
92
|
+
data_slot="breadcrumb-ellipsis",
|
|
93
|
+
class_name=f"{base_classes} {class_name}".strip(),
|
|
94
|
+
**props,
|
|
95
|
+
)
|