appkit-ui 0.12.0__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.
@@ -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=kwargs.get("required", False),
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,31 @@
1
+ import reflex as rx
2
+
3
+
4
+ def header(
5
+ text: str,
6
+ indent: bool = False,
7
+ header_items: rx.Component | None = None,
8
+ ) -> rx.Component:
9
+ return rx.hstack(
10
+ rx.color_mode_cond(
11
+ light=rx.heading(
12
+ text,
13
+ size="5",
14
+ class_name="text-black",
15
+ ),
16
+ dark=rx.heading(
17
+ text,
18
+ size="5",
19
+ class_name="text-white",
20
+ ),
21
+ ),
22
+ header_items,
23
+ class_name=(
24
+ "fixed top-0 z-[1000] "
25
+ "border-b border-gray-300 dark:border-neutral-700 "
26
+ "bg-gray-50 dark:bg-neutral-800 "
27
+ "p-3 pl-8 transition-colors duration-300"
28
+ ),
29
+ margin_left=rx.cond(indent, "0", "-32px"),
30
+ width=rx.cond(indent, "calc(100vw - 290px)", "calc(100vw - 322px)"),
31
+ )
@@ -0,0 +1,9 @@
1
+ import reflex as rx
2
+
3
+
4
+ class LoadingState(rx.State):
5
+ is_loading: bool = False
6
+ is_loading_message: str = "Lade..."
7
+
8
+ def set_is_loading(self, is_loading: bool) -> None:
9
+ self.is_loading = is_loading
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
+ }