appkit-ui 0.7.2__py3-none-any.whl
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.
- appkit_ui/__init__.py +0 -0
- appkit_ui/components/__init__.py +17 -0
- appkit_ui/components/collabsible.py +84 -0
- appkit_ui/components/dialogs.py +123 -0
- appkit_ui/components/editor.py +274 -0
- appkit_ui/components/form_inputs.py +425 -0
- appkit_ui/components/header.py +28 -0
- appkit_ui/global_states.py +9 -0
- appkit_ui/styles.py +26 -0
- appkit_ui-0.7.2.dist-info/METADATA +8 -0
- appkit_ui-0.7.2.dist-info/RECORD +12 -0
- appkit_ui-0.7.2.dist-info/WHEEL +4 -0
appkit_ui/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from appkit_ui.components.collabsible import collabsible
|
|
4
|
+
from appkit_ui.components.editor import (
|
|
5
|
+
EditorButtonList,
|
|
6
|
+
EditorOptions,
|
|
7
|
+
EventHandler,
|
|
8
|
+
editor,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"EditorButtonList",
|
|
13
|
+
"EditorOptions",
|
|
14
|
+
"EventHandler",
|
|
15
|
+
"collabsible",
|
|
16
|
+
"editor",
|
|
17
|
+
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
import reflex as rx
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def collabsible(
|
|
7
|
+
*children: rx.Component,
|
|
8
|
+
title: str,
|
|
9
|
+
info_text: str = "",
|
|
10
|
+
show_condition: bool = True,
|
|
11
|
+
expanded: bool = False,
|
|
12
|
+
on_toggle: Callable | None = None,
|
|
13
|
+
**props,
|
|
14
|
+
) -> rx.Component:
|
|
15
|
+
"""
|
|
16
|
+
Erstellt eine Collapsible Komponente mit beliebig vielen Child-Komponenten
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
*children: Beliebige Anzahl von Reflex Komponenten als positionale Argumente
|
|
20
|
+
title: Titel der Collapsible Sektion
|
|
21
|
+
info_text: Info-Text rechts neben dem Titel
|
|
22
|
+
show_condition: Bedingung, wann die Komponente angezeigt wird
|
|
23
|
+
expanded: Zustand, ob die Komponente erweitert ist
|
|
24
|
+
on_toggle: Event handler für das Umschalten
|
|
25
|
+
**props: Zusätzliche Props für das Container-Element
|
|
26
|
+
"""
|
|
27
|
+
return rx.vstack(
|
|
28
|
+
# Collapsible header - klickbarer Bereich
|
|
29
|
+
rx.vstack(
|
|
30
|
+
rx.hstack(
|
|
31
|
+
rx.icon(
|
|
32
|
+
rx.cond(
|
|
33
|
+
expanded,
|
|
34
|
+
"chevron-down",
|
|
35
|
+
"chevron-right",
|
|
36
|
+
),
|
|
37
|
+
size=16,
|
|
38
|
+
),
|
|
39
|
+
rx.text(
|
|
40
|
+
title,
|
|
41
|
+
size="1",
|
|
42
|
+
font_weight="medium",
|
|
43
|
+
color=rx.color("gray", 11),
|
|
44
|
+
flex_grow="1",
|
|
45
|
+
),
|
|
46
|
+
rx.text(
|
|
47
|
+
info_text,
|
|
48
|
+
size="1",
|
|
49
|
+
color=rx.color("gray", 9),
|
|
50
|
+
text_align="right",
|
|
51
|
+
width="40%",
|
|
52
|
+
),
|
|
53
|
+
spacing="2",
|
|
54
|
+
align="start",
|
|
55
|
+
width="100%",
|
|
56
|
+
),
|
|
57
|
+
on_click=on_toggle,
|
|
58
|
+
padding="8px",
|
|
59
|
+
width="100%",
|
|
60
|
+
_hover={"background_color": rx.color("gray", 2)},
|
|
61
|
+
),
|
|
62
|
+
# Expandierbarer Inhalt - alle Children werden in einem vstack angeordnet
|
|
63
|
+
rx.cond(
|
|
64
|
+
expanded,
|
|
65
|
+
*children, # Alle übergebenen Komponenten werden hier eingefügt
|
|
66
|
+
),
|
|
67
|
+
# Container Styling
|
|
68
|
+
spacing="3",
|
|
69
|
+
width="calc(90% + 18px)",
|
|
70
|
+
background_color=rx.color("gray", 1),
|
|
71
|
+
border=f"1px solid {rx.color('gray', 6)}",
|
|
72
|
+
border_radius="8px",
|
|
73
|
+
# Animationen
|
|
74
|
+
opacity=rx.cond(show_condition, "1", "0"),
|
|
75
|
+
transform=rx.cond(show_condition, "translateY(0)", "translateY(-20px)"),
|
|
76
|
+
height=rx.cond(show_condition, "auto", "0"),
|
|
77
|
+
max_height=rx.cond(show_condition, "500px", "0"),
|
|
78
|
+
padding=rx.cond(show_condition, "3px", "0"),
|
|
79
|
+
margin_top=rx.cond(show_condition, "16px", "-16px"),
|
|
80
|
+
overflow="hidden",
|
|
81
|
+
pointer_events=rx.cond(show_condition, "auto", "none"),
|
|
82
|
+
# transition="all 1s ease-out",
|
|
83
|
+
**props,
|
|
84
|
+
)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import reflex as rx
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def delete_dialog(
|
|
5
|
+
title: str,
|
|
6
|
+
content: str,
|
|
7
|
+
on_click: rx.EventHandler,
|
|
8
|
+
icon_button: bool = False,
|
|
9
|
+
class_name: str = "dialog",
|
|
10
|
+
**kwargs,
|
|
11
|
+
) -> rx.Component:
|
|
12
|
+
"""Generic delete confirmation dialog.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
title: Dialog title
|
|
16
|
+
content: The name/identifier of the item to delete
|
|
17
|
+
on_click: Event handler for delete action
|
|
18
|
+
icon_button: If True, use icon_button instead of button for trigger
|
|
19
|
+
**kwargs: Additional props for the trigger button
|
|
20
|
+
"""
|
|
21
|
+
# Create the appropriate trigger based on icon_button parameter
|
|
22
|
+
if icon_button:
|
|
23
|
+
trigger = rx.icon_button(rx.icon("trash-2", size=19), **kwargs)
|
|
24
|
+
else:
|
|
25
|
+
trigger = rx.button(rx.icon("trash-2", size=16), **kwargs)
|
|
26
|
+
|
|
27
|
+
return rx.alert_dialog.root(
|
|
28
|
+
rx.alert_dialog.trigger(trigger),
|
|
29
|
+
rx.alert_dialog.content(
|
|
30
|
+
rx.alert_dialog.title(title),
|
|
31
|
+
rx.alert_dialog.description(
|
|
32
|
+
rx.text(
|
|
33
|
+
"Bist du sicher, dass du ",
|
|
34
|
+
rx.text.strong(content),
|
|
35
|
+
" löschen möchtest? ",
|
|
36
|
+
"Diese Aktion wird das ausgewählte Element und alle zugehörigen ",
|
|
37
|
+
"Daten dauerhaft löschen. Dieser Vorgang kann nicht rückgängig ",
|
|
38
|
+
"gemacht werden!",
|
|
39
|
+
),
|
|
40
|
+
class_name="mb-4",
|
|
41
|
+
),
|
|
42
|
+
rx.flex(
|
|
43
|
+
rx.alert_dialog.cancel(
|
|
44
|
+
rx.button(
|
|
45
|
+
"Abbrechen",
|
|
46
|
+
class_name=(
|
|
47
|
+
"bg-gray-100 dark:bg-neutral-700 text-gray-700 "
|
|
48
|
+
"dark:text-neutral-300 hover:bg-gray-200 "
|
|
49
|
+
"dark:hover:bg-neutral-600 px-4 py-2 rounded"
|
|
50
|
+
),
|
|
51
|
+
),
|
|
52
|
+
),
|
|
53
|
+
rx.alert_dialog.action(
|
|
54
|
+
rx.button(
|
|
55
|
+
"Löschen",
|
|
56
|
+
class_name=(
|
|
57
|
+
"bg-red-500 text-white hover:bg-red-600 px-4 py-2 rounded"
|
|
58
|
+
),
|
|
59
|
+
on_click=on_click,
|
|
60
|
+
)
|
|
61
|
+
),
|
|
62
|
+
class_name="justify-end gap-3",
|
|
63
|
+
),
|
|
64
|
+
class_name=class_name,
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def dialog_header(icon: str, title: str, description: str) -> rx.Component:
|
|
70
|
+
"""Reusable dialog header component."""
|
|
71
|
+
return rx.hstack(
|
|
72
|
+
rx.badge(
|
|
73
|
+
rx.icon(tag=icon, size=34),
|
|
74
|
+
color_scheme="grass",
|
|
75
|
+
radius="full",
|
|
76
|
+
padding="0.65rem",
|
|
77
|
+
),
|
|
78
|
+
rx.vstack(
|
|
79
|
+
rx.dialog.title(
|
|
80
|
+
title,
|
|
81
|
+
weight="bold",
|
|
82
|
+
margin="0",
|
|
83
|
+
),
|
|
84
|
+
rx.dialog.description(description),
|
|
85
|
+
spacing="1",
|
|
86
|
+
height="100%",
|
|
87
|
+
align_items="start",
|
|
88
|
+
),
|
|
89
|
+
height="100%",
|
|
90
|
+
spacing="4",
|
|
91
|
+
margin_bottom="1.5em",
|
|
92
|
+
align_items="center",
|
|
93
|
+
width="100%",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def dialog_buttons(
|
|
98
|
+
submit_text: str, spacing: str = "3", has_errors: bool = False
|
|
99
|
+
) -> rx.Component:
|
|
100
|
+
"""Reusable dialog action buttons."""
|
|
101
|
+
return rx.flex(
|
|
102
|
+
rx.dialog.close(
|
|
103
|
+
rx.button(
|
|
104
|
+
"Abbrechen",
|
|
105
|
+
class_name=(
|
|
106
|
+
"bg-gray-100 dark:bg-neutral-700 text-gray-700 "
|
|
107
|
+
"dark:text-neutral-300 hover:bg-gray-200 "
|
|
108
|
+
"dark:hover:bg-neutral-600 px-4 py-2 rounded"
|
|
109
|
+
),
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
rx.form.submit(
|
|
113
|
+
rx.dialog.close(
|
|
114
|
+
rx.button(
|
|
115
|
+
submit_text,
|
|
116
|
+
class_name="px-4 py-2 rounded",
|
|
117
|
+
disabled=has_errors,
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
as_child=True,
|
|
121
|
+
),
|
|
122
|
+
class_name=f"pt-8 gap-{spacing} mt-4 justify-end",
|
|
123
|
+
)
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""A Rich Text Editor based on SunEditor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import enum
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from reflex.components.component import Component, NoSSRComponent
|
|
10
|
+
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
|
|
11
|
+
from reflex.utils.format import to_camel_case
|
|
12
|
+
from reflex.utils.imports import ImportDict, ImportVar
|
|
13
|
+
from reflex.vars.base import Var
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EditorButtonList(list, enum.Enum):
|
|
17
|
+
"""List enum that provides three predefined button lists."""
|
|
18
|
+
|
|
19
|
+
BASIC = [
|
|
20
|
+
["font", "fontSize"],
|
|
21
|
+
["fontColor"],
|
|
22
|
+
["horizontalRule"],
|
|
23
|
+
["link", "image"],
|
|
24
|
+
]
|
|
25
|
+
FORMATTING = [
|
|
26
|
+
["undo", "redo"],
|
|
27
|
+
["bold", "underline", "italic", "strike", "subscript", "superscript"],
|
|
28
|
+
["removeFormat"],
|
|
29
|
+
["outdent", "indent"],
|
|
30
|
+
["fullScreen", "showBlocks", "codeView"],
|
|
31
|
+
["preview", "print"],
|
|
32
|
+
]
|
|
33
|
+
COMPLEX = [
|
|
34
|
+
["undo", "redo"],
|
|
35
|
+
["font", "fontSize", "formatBlock"],
|
|
36
|
+
["bold", "underline", "italic", "strike", "subscript", "superscript"],
|
|
37
|
+
["removeFormat"],
|
|
38
|
+
"/",
|
|
39
|
+
["fontColor", "hiliteColor"],
|
|
40
|
+
["outdent", "indent"],
|
|
41
|
+
["align", "horizontalRule", "list", "table"],
|
|
42
|
+
["link", "image", "video"],
|
|
43
|
+
["fullScreen", "showBlocks", "codeView"],
|
|
44
|
+
["preview", "print"],
|
|
45
|
+
["save", "template"],
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class EditorOptions(BaseModel):
|
|
50
|
+
"""Some of the additional options to configure the Editor.
|
|
51
|
+
|
|
52
|
+
Complete list of options found here:
|
|
53
|
+
https://github.com/JiHong88/SunEditor/blob/master/README.md#options.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Specifies default tag name of the editor.
|
|
57
|
+
# default: 'p' {String}
|
|
58
|
+
default_tag: str | None = None
|
|
59
|
+
|
|
60
|
+
# The mode of the editor ('classic', 'inline', 'balloon', 'balloon-always').
|
|
61
|
+
# default: 'classic' {String}
|
|
62
|
+
mode: str | None = None
|
|
63
|
+
|
|
64
|
+
# If true, the editor is set to RTL(Right To Left) mode.
|
|
65
|
+
# default: false {Boolean}
|
|
66
|
+
rtl: bool | None = None
|
|
67
|
+
|
|
68
|
+
# List of buttons to use in the toolbar.
|
|
69
|
+
button_list: list[list[str] | str] | None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def on_blur_spec(_e: Var, content: Var[str]) -> tuple[Var[str]]:
|
|
73
|
+
"""A helper function to specify the on_blur event handler.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
_e: The event.
|
|
77
|
+
content: The content of the editor.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
A tuple containing the content of the editor.
|
|
81
|
+
"""
|
|
82
|
+
return (content,)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def on_paste_spec(
|
|
86
|
+
_e: Var, clean_data: Var[str], max_char_count: Var[bool]
|
|
87
|
+
) -> tuple[Var[str], Var[bool]]:
|
|
88
|
+
"""A helper function to specify the on_paste event handler.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
_e: The event.
|
|
92
|
+
clean_data: The clean data.
|
|
93
|
+
max_char_count: The maximum character count.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
A tuple containing the clean data and the maximum character count.
|
|
97
|
+
"""
|
|
98
|
+
return (clean_data, max_char_count)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Editor(NoSSRComponent):
|
|
102
|
+
"""A Rich Text Editor component based on SunEditor.
|
|
103
|
+
|
|
104
|
+
Not every JS prop is listed here (some are not easily usable from python),
|
|
105
|
+
refer to the library docs for a complete list.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
library = "suneditor-react@3.6.1"
|
|
109
|
+
|
|
110
|
+
tag = "SunEditor"
|
|
111
|
+
|
|
112
|
+
is_default = True
|
|
113
|
+
|
|
114
|
+
lib_dependencies: list[str] = ["suneditor"]
|
|
115
|
+
|
|
116
|
+
# Language of the editor.
|
|
117
|
+
# Alternatively to a string, a dict of your language can be passed to this prop.
|
|
118
|
+
# Please refer to the library docs for this.
|
|
119
|
+
# options: "en" | "da" | "de" | "es" | "fr" | "ja" | "ko" | "pt_br" |
|
|
120
|
+
# "ru" | "zh_cn" | "ro" | "pl" | "ckb" | "lv" | "se" | "ua" | "he" | "it"
|
|
121
|
+
# default: "en".
|
|
122
|
+
lang: Var[
|
|
123
|
+
Literal[
|
|
124
|
+
"en",
|
|
125
|
+
"da",
|
|
126
|
+
"de",
|
|
127
|
+
"es",
|
|
128
|
+
"fr",
|
|
129
|
+
"ja",
|
|
130
|
+
"ko",
|
|
131
|
+
"pt_br",
|
|
132
|
+
"ru",
|
|
133
|
+
"zh_cn",
|
|
134
|
+
"ro",
|
|
135
|
+
"pl",
|
|
136
|
+
"ckb",
|
|
137
|
+
"lv",
|
|
138
|
+
"se",
|
|
139
|
+
"ua",
|
|
140
|
+
"he",
|
|
141
|
+
"it",
|
|
142
|
+
]
|
|
143
|
+
| dict
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
# This is used to set the HTML form name of the editor.
|
|
147
|
+
# This means on HTML form submission,
|
|
148
|
+
# it will be submitted together with contents of the editor by the name provided.
|
|
149
|
+
name: Var[str]
|
|
150
|
+
|
|
151
|
+
# Sets the default value of the editor.
|
|
152
|
+
# This is useful if you don't want the on_change method to be called on render.
|
|
153
|
+
# If you want the on_change method to be called on render use the set_contents prop
|
|
154
|
+
default_value: Var[str]
|
|
155
|
+
|
|
156
|
+
# Sets the width of the editor.
|
|
157
|
+
# px and percentage values are accepted, eg width="100%" or width="500px"
|
|
158
|
+
# default: 100%
|
|
159
|
+
width: Var[str]
|
|
160
|
+
|
|
161
|
+
# Sets the height of the editor.
|
|
162
|
+
# px and percentage values are accepted, eg height="100%" or height="100px"
|
|
163
|
+
height: Var[str]
|
|
164
|
+
|
|
165
|
+
# Sets the placeholder of the editor.
|
|
166
|
+
placeholder: Var[str]
|
|
167
|
+
|
|
168
|
+
# Should the editor receive focus when initialized?
|
|
169
|
+
auto_focus: Var[bool]
|
|
170
|
+
|
|
171
|
+
# Pass an EditorOptions instance to modify the behaviour of Editor even more.
|
|
172
|
+
set_options: Var[dict]
|
|
173
|
+
|
|
174
|
+
# Whether all SunEditor plugins should be loaded.
|
|
175
|
+
# default: True.
|
|
176
|
+
set_all_plugins: Var[bool]
|
|
177
|
+
|
|
178
|
+
# Set the content of the editor.
|
|
179
|
+
# Note: To set the initial contents of the editor
|
|
180
|
+
# without calling the on_change event,
|
|
181
|
+
# please use the default_value prop.
|
|
182
|
+
# set_contents is used to set the contents of the editor programmatically.
|
|
183
|
+
# You must be aware that, when the set_contents's prop changes,
|
|
184
|
+
# the on_change event is triggered.
|
|
185
|
+
set_contents: Var[str]
|
|
186
|
+
|
|
187
|
+
# Append editor content
|
|
188
|
+
append_contents: Var[str]
|
|
189
|
+
|
|
190
|
+
# Sets the default style of the editor's edit area
|
|
191
|
+
set_default_style: Var[str]
|
|
192
|
+
|
|
193
|
+
# Disable the editor
|
|
194
|
+
# default: False.
|
|
195
|
+
disable: Var[bool]
|
|
196
|
+
|
|
197
|
+
# Hide the editor
|
|
198
|
+
# default: False.
|
|
199
|
+
hide: Var[bool]
|
|
200
|
+
|
|
201
|
+
# Hide the editor toolbar
|
|
202
|
+
# default: False.
|
|
203
|
+
hide_toolbar: Var[bool]
|
|
204
|
+
|
|
205
|
+
# Disable the editor toolbar
|
|
206
|
+
# default: False.
|
|
207
|
+
disable_toolbar: Var[bool]
|
|
208
|
+
|
|
209
|
+
# Fired when the editor content changes.
|
|
210
|
+
on_change: EventHandler[passthrough_event_spec(str)]
|
|
211
|
+
|
|
212
|
+
# Fired when the something is inputted in the editor.
|
|
213
|
+
on_input: EventHandler[no_args_event_spec]
|
|
214
|
+
|
|
215
|
+
# Fired when the editor loses focus.
|
|
216
|
+
on_blur: EventHandler[on_blur_spec]
|
|
217
|
+
|
|
218
|
+
# Fired when the editor is loaded.
|
|
219
|
+
on_load: EventHandler[passthrough_event_spec(bool)]
|
|
220
|
+
|
|
221
|
+
# Fired when the editor content is copied.
|
|
222
|
+
on_copy: EventHandler[no_args_event_spec]
|
|
223
|
+
|
|
224
|
+
# Fired when the editor content is cut.
|
|
225
|
+
on_cut: EventHandler[no_args_event_spec]
|
|
226
|
+
|
|
227
|
+
# Fired when the editor content is pasted.
|
|
228
|
+
on_paste: EventHandler[on_paste_spec]
|
|
229
|
+
|
|
230
|
+
# Fired when the code view is toggled.
|
|
231
|
+
toggle_code_view: EventHandler[passthrough_event_spec(bool)]
|
|
232
|
+
|
|
233
|
+
# Fired when the full screen mode is toggled.
|
|
234
|
+
toggle_full_screen: EventHandler[passthrough_event_spec(bool)]
|
|
235
|
+
|
|
236
|
+
def add_imports(self) -> ImportDict:
|
|
237
|
+
"""Add imports for the Editor component.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
The import dict.
|
|
241
|
+
"""
|
|
242
|
+
return {
|
|
243
|
+
"": ImportVar(tag="suneditor/dist/css/suneditor.min.css", install=False)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@classmethod
|
|
247
|
+
def create(
|
|
248
|
+
cls, set_options: EditorOptions | None = None, **props: object
|
|
249
|
+
) -> Component:
|
|
250
|
+
"""Create an instance of Editor. No children allowed.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
set_options: Configuration object to further configure the instance.
|
|
254
|
+
**props: Any properties to be passed to the Editor
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
An Editor instance.
|
|
258
|
+
|
|
259
|
+
Raises:
|
|
260
|
+
ValueError: If set_options is a state Var.
|
|
261
|
+
"""
|
|
262
|
+
if set_options is not None:
|
|
263
|
+
if isinstance(set_options, Var):
|
|
264
|
+
msg = "EditorOptions cannot be a state Var"
|
|
265
|
+
raise ValueError(msg)
|
|
266
|
+
props["set_options"] = {
|
|
267
|
+
to_camel_case(k): v
|
|
268
|
+
for k, v in set_options.dict().items()
|
|
269
|
+
if v is not None
|
|
270
|
+
}
|
|
271
|
+
return super().create(*[], **props)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
editor = Editor.create
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import reflex as rx
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
import appkit_mantine as mn
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SelectItem(BaseModel):
|
|
12
|
+
label: str
|
|
13
|
+
value: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def hidden_field(
|
|
17
|
+
**kwargs,
|
|
18
|
+
) -> rx.Component:
|
|
19
|
+
"""Creates a hidden input field."""
|
|
20
|
+
return rx.el.input(
|
|
21
|
+
type="hidden",
|
|
22
|
+
**kwargs,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def inline_form_field(
|
|
27
|
+
icon: str,
|
|
28
|
+
label: str,
|
|
29
|
+
hint: str = "",
|
|
30
|
+
**kwargs,
|
|
31
|
+
) -> rx.Component:
|
|
32
|
+
if "value" in kwargs:
|
|
33
|
+
kwargs["default_value"] = None
|
|
34
|
+
elif "default_value" in kwargs:
|
|
35
|
+
kwargs["value"] = None
|
|
36
|
+
|
|
37
|
+
minlength = kwargs.get("min_length", 0)
|
|
38
|
+
maxlength = kwargs.get("max_length", 0)
|
|
39
|
+
pattern = kwargs.get("pattern", "")
|
|
40
|
+
|
|
41
|
+
logger.debug(
|
|
42
|
+
"Creating form field: %s, minlength=%s, maxlength=%s, pattern=%s",
|
|
43
|
+
label,
|
|
44
|
+
minlength,
|
|
45
|
+
maxlength,
|
|
46
|
+
pattern,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return rx.form.field(
|
|
50
|
+
rx.flex(
|
|
51
|
+
rx.hstack(
|
|
52
|
+
rx.icon(icon, size=17, stroke_width=1.5),
|
|
53
|
+
rx.form.label(label),
|
|
54
|
+
class_name="label",
|
|
55
|
+
),
|
|
56
|
+
rx.hstack(
|
|
57
|
+
rx.cond(
|
|
58
|
+
hint,
|
|
59
|
+
rx.form.message(
|
|
60
|
+
hint,
|
|
61
|
+
color="gray",
|
|
62
|
+
class_name="hint",
|
|
63
|
+
),
|
|
64
|
+
),
|
|
65
|
+
rx.form.control(
|
|
66
|
+
rx.input(
|
|
67
|
+
**kwargs,
|
|
68
|
+
),
|
|
69
|
+
as_child=True,
|
|
70
|
+
),
|
|
71
|
+
rx.cond(
|
|
72
|
+
kwargs.get("required", False),
|
|
73
|
+
rx.form.message(
|
|
74
|
+
f"{label} ist ein Pflichtfeld.",
|
|
75
|
+
name=kwargs.get("name", ""),
|
|
76
|
+
color="red",
|
|
77
|
+
class_name="error required",
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
rx.cond(
|
|
81
|
+
minlength > 0,
|
|
82
|
+
rx.form.message(
|
|
83
|
+
f"{label} muss mindestens {minlength} Zeichen enthalten.",
|
|
84
|
+
name=kwargs.get("name", ""),
|
|
85
|
+
color="red",
|
|
86
|
+
class_name="error minlength",
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
rx.cond(
|
|
90
|
+
maxlength > 0,
|
|
91
|
+
rx.form.message(
|
|
92
|
+
f"{label} darf maximal {maxlength} Zeichen enthalten.",
|
|
93
|
+
name=kwargs.get("name", ""),
|
|
94
|
+
color="red",
|
|
95
|
+
class_name="error maxlength",
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
rx.cond(
|
|
99
|
+
pattern,
|
|
100
|
+
rx.form.message(
|
|
101
|
+
f"{label} entspricht nicht dem geforderten Format: {pattern}",
|
|
102
|
+
name=kwargs.get("name", ""),
|
|
103
|
+
color="red",
|
|
104
|
+
class_name="error pattern",
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
direction="column",
|
|
108
|
+
spacing="0",
|
|
109
|
+
),
|
|
110
|
+
),
|
|
111
|
+
width="100%",
|
|
112
|
+
class_name="form-group",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def form_field(
|
|
117
|
+
icon: str,
|
|
118
|
+
label: str,
|
|
119
|
+
hint: str = "",
|
|
120
|
+
validation_error: str | None = None,
|
|
121
|
+
**kwargs,
|
|
122
|
+
) -> rx.Component:
|
|
123
|
+
if "value" in kwargs:
|
|
124
|
+
kwargs["default_value"] = None
|
|
125
|
+
elif "default_value" in kwargs:
|
|
126
|
+
kwargs["value"] = None
|
|
127
|
+
|
|
128
|
+
if "size" in kwargs:
|
|
129
|
+
# convert to mantine size "xs", "sm", "md", "lg", "xl"
|
|
130
|
+
size_map = {"1": "xs", "2": "sm", "3": "md", "4": "lg", "5": "xl"}
|
|
131
|
+
kwargs["size"] = size_map.get(kwargs["size"], "md")
|
|
132
|
+
|
|
133
|
+
return mn.form.wrapper(
|
|
134
|
+
mn.form.input(
|
|
135
|
+
**kwargs,
|
|
136
|
+
left_section=rx.cond(icon, rx.icon(icon, size=17, stroke_width=1.5), None),
|
|
137
|
+
),
|
|
138
|
+
label=label,
|
|
139
|
+
description=hint,
|
|
140
|
+
error=validation_error,
|
|
141
|
+
required=True,
|
|
142
|
+
width="100%",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def form_inline_field(
|
|
147
|
+
icon: str,
|
|
148
|
+
**kwargs,
|
|
149
|
+
) -> rx.Component:
|
|
150
|
+
if kwargs.get("width") is None:
|
|
151
|
+
kwargs["width"] = "100%"
|
|
152
|
+
if kwargs.get("size") is None:
|
|
153
|
+
kwargs["size"] = "3"
|
|
154
|
+
|
|
155
|
+
return rx.form.field(
|
|
156
|
+
rx.input(
|
|
157
|
+
rx.input.slot(rx.icon(icon)),
|
|
158
|
+
**kwargs,
|
|
159
|
+
),
|
|
160
|
+
class_name="form-group",
|
|
161
|
+
width="100%",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def form_textarea(
|
|
166
|
+
*components,
|
|
167
|
+
name: str,
|
|
168
|
+
icon: str,
|
|
169
|
+
label: str,
|
|
170
|
+
hint: str = "",
|
|
171
|
+
monospace: bool = True,
|
|
172
|
+
**kwargs,
|
|
173
|
+
) -> rx.Component:
|
|
174
|
+
if "value" in kwargs:
|
|
175
|
+
kwargs["default_value"] = None
|
|
176
|
+
elif "default_value" in kwargs:
|
|
177
|
+
kwargs["value"] = None
|
|
178
|
+
|
|
179
|
+
minlength = kwargs.get("min_length", 0)
|
|
180
|
+
maxlength = kwargs.get("max_length", 0)
|
|
181
|
+
|
|
182
|
+
logger.debug(
|
|
183
|
+
"Creating form textarea: %s, minlength=%s, maxlength=%s",
|
|
184
|
+
label,
|
|
185
|
+
minlength,
|
|
186
|
+
maxlength,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return rx.flex(
|
|
190
|
+
rx.form.field(
|
|
191
|
+
rx.flex(
|
|
192
|
+
rx.hstack(
|
|
193
|
+
rx.icon(icon, size=16, stroke_width=1.5),
|
|
194
|
+
rx.form.label(label),
|
|
195
|
+
class_name="label",
|
|
196
|
+
),
|
|
197
|
+
rx.spacer(),
|
|
198
|
+
*components,
|
|
199
|
+
direction="row",
|
|
200
|
+
),
|
|
201
|
+
rx.cond(
|
|
202
|
+
hint,
|
|
203
|
+
rx.form.message(
|
|
204
|
+
hint,
|
|
205
|
+
color="gray",
|
|
206
|
+
class_name="hint",
|
|
207
|
+
),
|
|
208
|
+
),
|
|
209
|
+
rx.form.message(
|
|
210
|
+
f"{label} ist ein Pflichtfeld.",
|
|
211
|
+
name=name,
|
|
212
|
+
color="red",
|
|
213
|
+
class_name="error required",
|
|
214
|
+
),
|
|
215
|
+
rx.cond(
|
|
216
|
+
minlength > 0,
|
|
217
|
+
rx.form.message(
|
|
218
|
+
f"{label} muss mindestens {minlength} Zeichen enthalten.",
|
|
219
|
+
name=name,
|
|
220
|
+
color="red",
|
|
221
|
+
match="tooShort",
|
|
222
|
+
class_name="error minlength",
|
|
223
|
+
),
|
|
224
|
+
),
|
|
225
|
+
rx.cond(
|
|
226
|
+
maxlength > 0,
|
|
227
|
+
rx.form.message(
|
|
228
|
+
f"{label} darf maximal {maxlength} Zeichen enthalten.",
|
|
229
|
+
name=name,
|
|
230
|
+
color="red",
|
|
231
|
+
match="tooLong",
|
|
232
|
+
class_name="error maxlength",
|
|
233
|
+
),
|
|
234
|
+
),
|
|
235
|
+
rx.text_area(
|
|
236
|
+
name=name,
|
|
237
|
+
id=name,
|
|
238
|
+
font_family=rx.cond(
|
|
239
|
+
monospace,
|
|
240
|
+
"Consolas, Monaco, 'Courier New', monospace;",
|
|
241
|
+
"inherit",
|
|
242
|
+
),
|
|
243
|
+
**kwargs,
|
|
244
|
+
),
|
|
245
|
+
class_name="form-group",
|
|
246
|
+
),
|
|
247
|
+
direction="column",
|
|
248
|
+
spacing="0",
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def form_checkbox(
|
|
253
|
+
name: str,
|
|
254
|
+
label: str,
|
|
255
|
+
hint: str | None = None,
|
|
256
|
+
**kwargs,
|
|
257
|
+
) -> rx.Component:
|
|
258
|
+
return rx.form.field(
|
|
259
|
+
rx.hstack(
|
|
260
|
+
rx.switch(
|
|
261
|
+
name=name,
|
|
262
|
+
margin_top="8px",
|
|
263
|
+
margin_right="9px",
|
|
264
|
+
**kwargs,
|
|
265
|
+
),
|
|
266
|
+
rx.flex(
|
|
267
|
+
rx.form.label(label, padding_bottom="3px"),
|
|
268
|
+
rx.cond(
|
|
269
|
+
hint,
|
|
270
|
+
rx.form.message(
|
|
271
|
+
hint, color="gray", margin_top="-10px", class_name="hint"
|
|
272
|
+
),
|
|
273
|
+
),
|
|
274
|
+
direction="column",
|
|
275
|
+
),
|
|
276
|
+
spacing="2",
|
|
277
|
+
align="start",
|
|
278
|
+
justify="start",
|
|
279
|
+
margin_top="1em",
|
|
280
|
+
),
|
|
281
|
+
name=name,
|
|
282
|
+
width="100%",
|
|
283
|
+
class_name="form-group",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def render_select_item(option: SelectItem) -> rx.Component:
|
|
288
|
+
return rx.select.item(
|
|
289
|
+
rx.hstack(
|
|
290
|
+
rx.text(option.label),
|
|
291
|
+
),
|
|
292
|
+
value=option.value,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def form_select(
|
|
297
|
+
*components,
|
|
298
|
+
options: list[SelectItem],
|
|
299
|
+
icon: str = "circle-dot",
|
|
300
|
+
label: str = "",
|
|
301
|
+
hint: str = "",
|
|
302
|
+
placeholder: str = "",
|
|
303
|
+
on_change: rx.EventHandler | None = None,
|
|
304
|
+
with_label: bool = True,
|
|
305
|
+
column_width: str = "100%",
|
|
306
|
+
**kwargs,
|
|
307
|
+
) -> rx.Component:
|
|
308
|
+
if "value" in kwargs:
|
|
309
|
+
kwargs["default_value"] = None
|
|
310
|
+
elif "default_value" in kwargs:
|
|
311
|
+
kwargs["value"] = None
|
|
312
|
+
|
|
313
|
+
if on_change is not None:
|
|
314
|
+
kwargs["on_change"] = on_change
|
|
315
|
+
|
|
316
|
+
classes = "rt-SelectTrigger"
|
|
317
|
+
if "size" in kwargs:
|
|
318
|
+
classes += f" rt-r-size-{kwargs['size']}"
|
|
319
|
+
else:
|
|
320
|
+
classes += " rt-r-size-2"
|
|
321
|
+
|
|
322
|
+
if "variant" in kwargs:
|
|
323
|
+
classes += f" rt-variant-{kwargs['variant'].lower()}"
|
|
324
|
+
else:
|
|
325
|
+
classes += " rt-variant-surface"
|
|
326
|
+
|
|
327
|
+
return rx.flex(
|
|
328
|
+
rx.form.field(
|
|
329
|
+
rx.cond(
|
|
330
|
+
with_label,
|
|
331
|
+
rx.hstack(
|
|
332
|
+
rx.icon(icon, size=16, stroke_width=1.5),
|
|
333
|
+
rx.form.label(label),
|
|
334
|
+
class_name="label",
|
|
335
|
+
),
|
|
336
|
+
),
|
|
337
|
+
rx.cond(
|
|
338
|
+
hint,
|
|
339
|
+
rx.form.message(
|
|
340
|
+
hint,
|
|
341
|
+
color="gray",
|
|
342
|
+
class_name="hint",
|
|
343
|
+
),
|
|
344
|
+
),
|
|
345
|
+
rx.hstack(
|
|
346
|
+
rx.el.select(
|
|
347
|
+
rx.cond(placeholder, rx.el.option(placeholder, value="")),
|
|
348
|
+
rx.foreach(
|
|
349
|
+
options.to(list[SelectItem]),
|
|
350
|
+
lambda option: rx.el.option(
|
|
351
|
+
label=option.label,
|
|
352
|
+
value=option.value,
|
|
353
|
+
class_name="rt-SelectItem",
|
|
354
|
+
),
|
|
355
|
+
),
|
|
356
|
+
appearance="base-select",
|
|
357
|
+
class_name=classes,
|
|
358
|
+
**kwargs,
|
|
359
|
+
),
|
|
360
|
+
*components,
|
|
361
|
+
),
|
|
362
|
+
rx.form.message(
|
|
363
|
+
f"{label} darf nicht leer sein.",
|
|
364
|
+
name=kwargs.get("name", ""),
|
|
365
|
+
color="red",
|
|
366
|
+
class_name="error required",
|
|
367
|
+
),
|
|
368
|
+
spacing="2",
|
|
369
|
+
margin_bottom="0.75em",
|
|
370
|
+
class_name="form-group",
|
|
371
|
+
),
|
|
372
|
+
width=column_width,
|
|
373
|
+
flex_grow="1",
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def form_text_editor(
|
|
378
|
+
icon: str,
|
|
379
|
+
label: str,
|
|
380
|
+
name: str = "editor",
|
|
381
|
+
hint: str = "",
|
|
382
|
+
on_blur: rx.EventHandler | None = None,
|
|
383
|
+
**kwargs,
|
|
384
|
+
) -> rx.Component:
|
|
385
|
+
if kwargs.get("width") is None:
|
|
386
|
+
kwargs["width"] = "100%"
|
|
387
|
+
|
|
388
|
+
if on_blur is not None:
|
|
389
|
+
kwargs["on_blur"] = on_blur
|
|
390
|
+
|
|
391
|
+
return rx.vstack(
|
|
392
|
+
rx.form.field(
|
|
393
|
+
rx.hstack(
|
|
394
|
+
rx.icon(icon, size=16, stroke_width=1.5),
|
|
395
|
+
rx.form.label(label),
|
|
396
|
+
class_name="label",
|
|
397
|
+
),
|
|
398
|
+
rx.cond(
|
|
399
|
+
hint,
|
|
400
|
+
rx.form.message(
|
|
401
|
+
hint,
|
|
402
|
+
color="gray",
|
|
403
|
+
class_name="hint",
|
|
404
|
+
),
|
|
405
|
+
),
|
|
406
|
+
margin_bottom="-12px",
|
|
407
|
+
),
|
|
408
|
+
mn.rich_text_editor(
|
|
409
|
+
id=name,
|
|
410
|
+
toolbar_config=mn.EditorToolbarConfig(
|
|
411
|
+
control_groups=[
|
|
412
|
+
["bold", "italic", "underline"],
|
|
413
|
+
["h1", "h2", "h3"],
|
|
414
|
+
["bulletList", "orderedList", "blockquote", "code"],
|
|
415
|
+
["alignLeft", "alignCenter", "alignRight", "alignJustify"],
|
|
416
|
+
["link", "unlink"],
|
|
417
|
+
["image"],
|
|
418
|
+
]
|
|
419
|
+
),
|
|
420
|
+
variant="subtle",
|
|
421
|
+
**kwargs,
|
|
422
|
+
),
|
|
423
|
+
width="100%",
|
|
424
|
+
class_name="form-group",
|
|
425
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import reflex as rx
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def header(
|
|
5
|
+
text: str,
|
|
6
|
+
indent: bool = False,
|
|
7
|
+
) -> rx.Component:
|
|
8
|
+
return rx.box(
|
|
9
|
+
rx.color_mode_cond(
|
|
10
|
+
light=rx.heading(
|
|
11
|
+
text,
|
|
12
|
+
size="5",
|
|
13
|
+
class_name="text-black",
|
|
14
|
+
),
|
|
15
|
+
dark=rx.heading(
|
|
16
|
+
text,
|
|
17
|
+
size="5",
|
|
18
|
+
class_name="text-white",
|
|
19
|
+
),
|
|
20
|
+
),
|
|
21
|
+
class_name=(
|
|
22
|
+
"fixed top-0 z-[1000] w-full "
|
|
23
|
+
"border-b border-gray-300 dark:border-neutral-700 "
|
|
24
|
+
"bg-gray-50 dark:bg-neutral-800 "
|
|
25
|
+
"p-3 pl-8 transition-colors duration-300"
|
|
26
|
+
),
|
|
27
|
+
margin_left=rx.cond(indent, "0", "-32px"),
|
|
28
|
+
)
|
appkit_ui/styles.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import reflex as rx
|
|
2
|
+
|
|
3
|
+
splash_container: dict[str, str] = {
|
|
4
|
+
"background": (
|
|
5
|
+
f"linear-gradient(99deg, {rx.color('blue', 4)},"
|
|
6
|
+
f" {rx.color('pink', 3)}, {rx.color('mauve', 3)})"
|
|
7
|
+
),
|
|
8
|
+
"background_size": "cover",
|
|
9
|
+
"background_position": "center",
|
|
10
|
+
"background_repeat": "no-repeat",
|
|
11
|
+
"min_height": "100vh",
|
|
12
|
+
"width": "100%",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
dialog_styles = {
|
|
16
|
+
"max_width": "540px",
|
|
17
|
+
"padding": "1.5em",
|
|
18
|
+
"border": f"2px solid {rx.color('accent', 9)}",
|
|
19
|
+
"border_radius": "25px",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
label_styles = {
|
|
23
|
+
"align": "center",
|
|
24
|
+
"spacing": "1",
|
|
25
|
+
"margin_bottom": "3px",
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
appkit_ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
appkit_ui/global_states.py,sha256=V-Tmz4nmAcn4NAtW-Q12xNZSbQdJe7Oexvf25WdEdVY,215
|
|
3
|
+
appkit_ui/styles.py,sha256=2L2ZVSRLrjNpuCWx8OthwcZZ3w1WXmioADVjY11t3BY,601
|
|
4
|
+
appkit_ui/components/__init__.py,sha256=DPKkO65DGtXfla1esbS9wkEpxbVlMTs45g6oqgJVQYo,321
|
|
5
|
+
appkit_ui/components/collabsible.py,sha256=JAAIEvwShHMhtcGfZwXkR59AC2hEgZrH4DGmuVTGWFE,2833
|
|
6
|
+
appkit_ui/components/dialogs.py,sha256=nc4-5PNUwzz-8ZB-Y3bUGkw6z6RlEfNto7icPHjycog,3916
|
|
7
|
+
appkit_ui/components/editor.py,sha256=Z9cew7hrLuBoXBYk7xW-OsbhIcdW4LFlBqi_8eYtCTY,7956
|
|
8
|
+
appkit_ui/components/form_inputs.py,sha256=lCA1VhEcZeVZDwiiq0JBsjDy1PvTIrTPZXkIf5gqLwQ,11521
|
|
9
|
+
appkit_ui/components/header.py,sha256=JLAiDEVku99pRgAsQAMUIui7i70hS6H6Bbbe8FVj0DU,715
|
|
10
|
+
appkit_ui-0.7.2.dist-info/METADATA,sha256=fthBAwDpIYDBXkh5qWPYyrYmGaMABNQ0n-NABZsMFVc,196
|
|
11
|
+
appkit_ui-0.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
12
|
+
appkit_ui-0.7.2.dist-info/RECORD,,
|