nextpy-framework 1.0.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.
- nextpy/__init__.py +50 -0
- nextpy/auth.py +94 -0
- nextpy/builder.py +123 -0
- nextpy/cli.py +490 -0
- nextpy/components/__init__.py +45 -0
- nextpy/components/feedback.py +210 -0
- nextpy/components/form.py +346 -0
- nextpy/components/head.py +167 -0
- nextpy/components/hooks_provider.py +64 -0
- nextpy/components/image.py +180 -0
- nextpy/components/layout.py +206 -0
- nextpy/components/link.py +132 -0
- nextpy/components/loader.py +65 -0
- nextpy/components/toast.py +101 -0
- nextpy/components/visual.py +185 -0
- nextpy/config.py +75 -0
- nextpy/core/__init__.py +21 -0
- nextpy/core/builder.py +237 -0
- nextpy/core/data_fetching.py +221 -0
- nextpy/core/renderer.py +252 -0
- nextpy/core/router.py +233 -0
- nextpy/core/sync.py +34 -0
- nextpy/db.py +121 -0
- nextpy/dev_server.py +69 -0
- nextpy/dev_tools.py +157 -0
- nextpy/errors.py +70 -0
- nextpy/hooks.py +348 -0
- nextpy/performance.py +78 -0
- nextpy/plugins.py +61 -0
- nextpy/py.typed +0 -0
- nextpy/server/__init__.py +6 -0
- nextpy/server/app.py +325 -0
- nextpy/server/debug.py +93 -0
- nextpy/server/middleware.py +88 -0
- nextpy/utils/__init__.py +0 -0
- nextpy/utils/cache.py +89 -0
- nextpy/utils/email.py +59 -0
- nextpy/utils/file_upload.py +65 -0
- nextpy/utils/logging.py +52 -0
- nextpy/utils/search.py +59 -0
- nextpy/utils/seo.py +85 -0
- nextpy/utils/validators.py +58 -0
- nextpy/websocket.py +76 -0
- nextpy_framework-1.0.0.dist-info/METADATA +343 -0
- nextpy_framework-1.0.0.dist-info/RECORD +49 -0
- nextpy_framework-1.0.0.dist-info/WHEEL +5 -0
- nextpy_framework-1.0.0.dist-info/entry_points.txt +2 -0
- nextpy_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
- nextpy_framework-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Feedback components for NextPy
|
|
3
|
+
Alert, Toast, Progress, Spinner, Badge, etc.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def Alert(
|
|
10
|
+
message: str = "",
|
|
11
|
+
type: str = "info",
|
|
12
|
+
title: Optional[str] = None,
|
|
13
|
+
dismissible: bool = False,
|
|
14
|
+
**kwargs
|
|
15
|
+
) -> str:
|
|
16
|
+
"""Alert component"""
|
|
17
|
+
colors = {
|
|
18
|
+
"info": "bg-blue-50 border-l-4 border-blue-600 text-blue-900",
|
|
19
|
+
"success": "bg-green-50 border-l-4 border-green-600 text-green-900",
|
|
20
|
+
"warning": "bg-yellow-50 border-l-4 border-yellow-600 text-yellow-900",
|
|
21
|
+
"error": "bg-red-50 border-l-4 border-red-600 text-red-900",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
color_class = colors.get(type, colors["info"])
|
|
25
|
+
close_btn = '<button class="ml-auto">×</button>' if dismissible else ""
|
|
26
|
+
title_html = f'<h3 class="font-bold mb-1">{title}</h3>' if title else ""
|
|
27
|
+
|
|
28
|
+
return f'''<div class="p-4 rounded-lg {color_class} flex items-start gap-3">
|
|
29
|
+
{title_html}
|
|
30
|
+
<p>{message}</p>
|
|
31
|
+
{close_btn}
|
|
32
|
+
</div>'''
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def Badge(
|
|
36
|
+
text: str = "",
|
|
37
|
+
color: str = "blue",
|
|
38
|
+
size: str = "md",
|
|
39
|
+
**kwargs
|
|
40
|
+
) -> str:
|
|
41
|
+
"""Badge component"""
|
|
42
|
+
sizes = {
|
|
43
|
+
"sm": "px-2 py-1 text-xs",
|
|
44
|
+
"md": "px-3 py-1 text-sm",
|
|
45
|
+
"lg": "px-4 py-2 text-base",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
colors_class = {
|
|
49
|
+
"blue": "bg-blue-100 text-blue-800",
|
|
50
|
+
"red": "bg-red-100 text-red-800",
|
|
51
|
+
"green": "bg-green-100 text-green-800",
|
|
52
|
+
"yellow": "bg-yellow-100 text-yellow-800",
|
|
53
|
+
"purple": "bg-purple-100 text-purple-800",
|
|
54
|
+
"gray": "bg-gray-100 text-gray-800",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
size_class = sizes.get(size, sizes["md"])
|
|
58
|
+
color_class = colors_class.get(color, colors_class["blue"])
|
|
59
|
+
|
|
60
|
+
return f'<span class="inline-block rounded-full {size_class} {color_class}">{text}</span>'
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def Progress(
|
|
64
|
+
value: int = 0,
|
|
65
|
+
max: int = 100,
|
|
66
|
+
show_label: bool = True,
|
|
67
|
+
**kwargs
|
|
68
|
+
) -> str:
|
|
69
|
+
"""Progress bar component"""
|
|
70
|
+
percentage = (value / max) * 100
|
|
71
|
+
label_html = f'<span class="text-xs font-semibold">{percentage:.0f}%</span>' if show_label else ""
|
|
72
|
+
|
|
73
|
+
return f'''<div class="flex items-center gap-2">
|
|
74
|
+
<div class="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
|
|
75
|
+
<div class="h-full bg-blue-600" style="width: {percentage}%"></div>
|
|
76
|
+
</div>
|
|
77
|
+
{label_html}
|
|
78
|
+
</div>'''
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def Spinner(
|
|
82
|
+
size: str = "md",
|
|
83
|
+
color: str = "blue",
|
|
84
|
+
**kwargs
|
|
85
|
+
) -> str:
|
|
86
|
+
"""Spinner/loading component"""
|
|
87
|
+
sizes = {
|
|
88
|
+
"sm": "w-4 h-4",
|
|
89
|
+
"md": "w-8 h-8",
|
|
90
|
+
"lg": "w-12 h-12",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
colors_class = {
|
|
94
|
+
"blue": "text-blue-600",
|
|
95
|
+
"red": "text-red-600",
|
|
96
|
+
"green": "text-green-600",
|
|
97
|
+
"gray": "text-gray-600",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
size_class = sizes.get(size, sizes["md"])
|
|
101
|
+
color_class = colors_class.get(color, colors_class["blue"])
|
|
102
|
+
|
|
103
|
+
return f'''<svg class="animate-spin {size_class} {color_class}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
104
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
105
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
106
|
+
</svg>'''
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def Skeleton(
|
|
110
|
+
width: str = "w-full",
|
|
111
|
+
height: str = "h-4",
|
|
112
|
+
count: int = 1,
|
|
113
|
+
**kwargs
|
|
114
|
+
) -> str:
|
|
115
|
+
"""Skeleton loader component"""
|
|
116
|
+
skeletons = ""
|
|
117
|
+
for _ in range(count):
|
|
118
|
+
skeletons += f'<div class="bg-gray-200 rounded animate-pulse {width} {height} mb-2"></div>'
|
|
119
|
+
|
|
120
|
+
return skeletons
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def Toast(
|
|
124
|
+
message: str = "",
|
|
125
|
+
type: str = "info",
|
|
126
|
+
duration: int = 3000,
|
|
127
|
+
**kwargs
|
|
128
|
+
) -> str:
|
|
129
|
+
"""Toast notification component"""
|
|
130
|
+
colors = {
|
|
131
|
+
"info": "bg-blue-600",
|
|
132
|
+
"success": "bg-green-600",
|
|
133
|
+
"warning": "bg-yellow-600",
|
|
134
|
+
"error": "bg-red-600",
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
color_class = colors.get(type, colors["info"])
|
|
138
|
+
|
|
139
|
+
return f'''<div class="fixed bottom-4 right-4 {color_class} text-white px-6 py-3 rounded-lg shadow-lg animate-fade-in-up" role="alert">
|
|
140
|
+
{message}
|
|
141
|
+
</div>
|
|
142
|
+
<style>
|
|
143
|
+
@keyframes fadeInUp {{
|
|
144
|
+
from {{ opacity: 0; transform: translateY(20px); }}
|
|
145
|
+
to {{ opacity: 1; transform: translateY(0); }}
|
|
146
|
+
}}
|
|
147
|
+
.animate-fade-in-up {{ animation: fadeInUp 0.3s ease-in-out; }}
|
|
148
|
+
</style>'''
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def Tooltip(
|
|
152
|
+
text: str = "",
|
|
153
|
+
content: str = "",
|
|
154
|
+
position: str = "top",
|
|
155
|
+
**kwargs
|
|
156
|
+
) -> str:
|
|
157
|
+
"""Tooltip component"""
|
|
158
|
+
positions = {
|
|
159
|
+
"top": "bottom-full mb-2",
|
|
160
|
+
"bottom": "top-full mt-2",
|
|
161
|
+
"left": "right-full mr-2",
|
|
162
|
+
"right": "left-full ml-2",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pos_class = positions.get(position, positions["top"])
|
|
166
|
+
|
|
167
|
+
return f'''<div class="relative inline-block group">
|
|
168
|
+
<span class="cursor-help underline decoration-dotted">{text}</span>
|
|
169
|
+
<div class="absolute {pos_class} opacity-0 group-hover:opacity-100 transition-opacity bg-gray-900 text-white px-3 py-2 rounded text-sm whitespace-nowrap pointer-events-none">
|
|
170
|
+
{content}
|
|
171
|
+
</div>
|
|
172
|
+
</div>'''
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def Loader(
|
|
176
|
+
text: str = "Loading...",
|
|
177
|
+
**kwargs
|
|
178
|
+
) -> str:
|
|
179
|
+
"""Loader component with text"""
|
|
180
|
+
return f'''<div class="flex flex-col items-center justify-center gap-4">
|
|
181
|
+
{Spinner(size="lg")}
|
|
182
|
+
<p class="text-gray-600 font-semibold">{text}</p>
|
|
183
|
+
</div>'''
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def Empty(
|
|
187
|
+
icon: str = "📭",
|
|
188
|
+
title: str = "No data",
|
|
189
|
+
message: str = "Nothing to display",
|
|
190
|
+
**kwargs
|
|
191
|
+
) -> str:
|
|
192
|
+
"""Empty state component"""
|
|
193
|
+
return f'''<div class="flex flex-col items-center justify-center py-16 text-center">
|
|
194
|
+
<div class="text-5xl mb-4">{icon}</div>
|
|
195
|
+
<h3 class="text-xl font-bold text-gray-900 mb-2">{title}</h3>
|
|
196
|
+
<p class="text-gray-600">{message}</p>
|
|
197
|
+
</div>'''
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
__all__ = [
|
|
201
|
+
'Alert',
|
|
202
|
+
'Badge',
|
|
203
|
+
'Progress',
|
|
204
|
+
'Spinner',
|
|
205
|
+
'Skeleton',
|
|
206
|
+
'Toast',
|
|
207
|
+
'Tooltip',
|
|
208
|
+
'Loader',
|
|
209
|
+
'Empty',
|
|
210
|
+
]
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Form components for NextPy
|
|
3
|
+
Input, TextArea, Select, Checkbox, Radio, Form, etc.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional, List, Dict, Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def Input(
|
|
10
|
+
name: str = "",
|
|
11
|
+
type: str = "text",
|
|
12
|
+
placeholder: str = "",
|
|
13
|
+
value: str = "",
|
|
14
|
+
required: bool = False,
|
|
15
|
+
disabled: bool = False,
|
|
16
|
+
class_name: str = "",
|
|
17
|
+
**kwargs
|
|
18
|
+
) -> str:
|
|
19
|
+
"""Input component"""
|
|
20
|
+
req = "required" if required else ""
|
|
21
|
+
dis = "disabled" if disabled else ""
|
|
22
|
+
default_class = "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
23
|
+
|
|
24
|
+
return f'''<input
|
|
25
|
+
type="{type}"
|
|
26
|
+
name="{name}"
|
|
27
|
+
placeholder="{placeholder}"
|
|
28
|
+
value="{value}"
|
|
29
|
+
class="{class_name or default_class}"
|
|
30
|
+
{req}
|
|
31
|
+
{dis}
|
|
32
|
+
>'''
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def TextArea(
|
|
36
|
+
name: str = "",
|
|
37
|
+
placeholder: str = "",
|
|
38
|
+
value: str = "",
|
|
39
|
+
rows: int = 4,
|
|
40
|
+
required: bool = False,
|
|
41
|
+
disabled: bool = False,
|
|
42
|
+
class_name: str = "",
|
|
43
|
+
**kwargs
|
|
44
|
+
) -> str:
|
|
45
|
+
"""TextArea component"""
|
|
46
|
+
req = "required" if required else ""
|
|
47
|
+
dis = "disabled" if disabled else ""
|
|
48
|
+
default_class = "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
49
|
+
|
|
50
|
+
return f'''<textarea
|
|
51
|
+
name="{name}"
|
|
52
|
+
placeholder="{placeholder}"
|
|
53
|
+
rows="{rows}"
|
|
54
|
+
class="{class_name or default_class}"
|
|
55
|
+
{req}
|
|
56
|
+
{dis}
|
|
57
|
+
>{value}</textarea>'''
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def Select(
|
|
61
|
+
name: str = "",
|
|
62
|
+
options: Optional[List[Dict[str, str]]] = None,
|
|
63
|
+
value: str = "",
|
|
64
|
+
required: bool = False,
|
|
65
|
+
disabled: bool = False,
|
|
66
|
+
class_name: str = "",
|
|
67
|
+
**kwargs
|
|
68
|
+
) -> str:
|
|
69
|
+
"""Select dropdown component"""
|
|
70
|
+
if options is None:
|
|
71
|
+
options = []
|
|
72
|
+
|
|
73
|
+
req = "required" if required else ""
|
|
74
|
+
dis = "disabled" if disabled else ""
|
|
75
|
+
default_class = "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
76
|
+
|
|
77
|
+
option_html = ""
|
|
78
|
+
for opt in options:
|
|
79
|
+
selected = "selected" if opt.get("value") == value else ""
|
|
80
|
+
option_html += f'<option value="{opt.get("value")}" {selected}>{opt.get("label")}</option>'
|
|
81
|
+
|
|
82
|
+
return f'''<select
|
|
83
|
+
name="{name}"
|
|
84
|
+
class="{class_name or default_class}"
|
|
85
|
+
{req}
|
|
86
|
+
{dis}
|
|
87
|
+
>{option_html}</select>'''
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def Checkbox(
|
|
91
|
+
name: str = "",
|
|
92
|
+
label: str = "",
|
|
93
|
+
checked: bool = False,
|
|
94
|
+
required: bool = False,
|
|
95
|
+
disabled: bool = False,
|
|
96
|
+
**kwargs
|
|
97
|
+
) -> str:
|
|
98
|
+
"""Checkbox component"""
|
|
99
|
+
check = "checked" if checked else ""
|
|
100
|
+
dis = "disabled" if disabled else ""
|
|
101
|
+
req = "required" if required else ""
|
|
102
|
+
|
|
103
|
+
return f'''<label class="flex items-center gap-2">
|
|
104
|
+
<input
|
|
105
|
+
type="checkbox"
|
|
106
|
+
name="{name}"
|
|
107
|
+
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500"
|
|
108
|
+
{check}
|
|
109
|
+
{req}
|
|
110
|
+
{dis}
|
|
111
|
+
>
|
|
112
|
+
<span>{label}</span>
|
|
113
|
+
</label>'''
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def Radio(
|
|
117
|
+
name: str = "",
|
|
118
|
+
label: str = "",
|
|
119
|
+
value: str = "",
|
|
120
|
+
checked: bool = False,
|
|
121
|
+
required: bool = False,
|
|
122
|
+
disabled: bool = False,
|
|
123
|
+
**kwargs
|
|
124
|
+
) -> str:
|
|
125
|
+
"""Radio button component"""
|
|
126
|
+
check = "checked" if checked else ""
|
|
127
|
+
dis = "disabled" if disabled else ""
|
|
128
|
+
req = "required" if required else ""
|
|
129
|
+
|
|
130
|
+
return f'''<label class="flex items-center gap-2">
|
|
131
|
+
<input
|
|
132
|
+
type="radio"
|
|
133
|
+
name="{name}"
|
|
134
|
+
value="{value}"
|
|
135
|
+
class="w-4 h-4 text-blue-600 border-gray-300 focus:ring-2 focus:ring-blue-500"
|
|
136
|
+
{check}
|
|
137
|
+
{req}
|
|
138
|
+
{dis}
|
|
139
|
+
>
|
|
140
|
+
<span>{label}</span>
|
|
141
|
+
</label>'''
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def RadioGroup(
|
|
145
|
+
name: str = "",
|
|
146
|
+
options: Optional[List[Dict[str, str]]] = None,
|
|
147
|
+
value: str = "",
|
|
148
|
+
**kwargs
|
|
149
|
+
) -> str:
|
|
150
|
+
"""Radio group component"""
|
|
151
|
+
if options is None:
|
|
152
|
+
options = []
|
|
153
|
+
|
|
154
|
+
html = f'<fieldset class="space-y-3">'
|
|
155
|
+
|
|
156
|
+
for opt in options:
|
|
157
|
+
checked = opt.get("value") == value
|
|
158
|
+
label = opt.get("label", "")
|
|
159
|
+
opt_value = opt.get("value", "")
|
|
160
|
+
html += Radio(
|
|
161
|
+
name=name,
|
|
162
|
+
label=label,
|
|
163
|
+
value=opt_value,
|
|
164
|
+
checked=checked
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
html += '</fieldset>'
|
|
168
|
+
return html
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def Form(
|
|
172
|
+
children: str = "",
|
|
173
|
+
action: str = "",
|
|
174
|
+
method: str = "POST",
|
|
175
|
+
onsubmit: str = "",
|
|
176
|
+
class_name: str = "",
|
|
177
|
+
**kwargs
|
|
178
|
+
) -> str:
|
|
179
|
+
"""Form component"""
|
|
180
|
+
submit_handler = f'onsubmit="{onsubmit}"' if onsubmit else ""
|
|
181
|
+
default_class = "space-y-6"
|
|
182
|
+
|
|
183
|
+
return f'''<form
|
|
184
|
+
action="{action}"
|
|
185
|
+
method="{method}"
|
|
186
|
+
class="{class_name or default_class}"
|
|
187
|
+
{submit_handler}
|
|
188
|
+
>{children}</form>'''
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def FormGroup(
|
|
192
|
+
label: str = "",
|
|
193
|
+
children: str = "",
|
|
194
|
+
error: str = "",
|
|
195
|
+
**kwargs
|
|
196
|
+
) -> str:
|
|
197
|
+
"""Form group component (label + input)"""
|
|
198
|
+
error_html = f'<p class="text-red-600 text-sm mt-1">{error}</p>' if error else ""
|
|
199
|
+
|
|
200
|
+
return f'''<div class="space-y-2">
|
|
201
|
+
{f'<label class="block text-sm font-semibold text-gray-900">{label}</label>' if label else ''}
|
|
202
|
+
{children}
|
|
203
|
+
{error_html}
|
|
204
|
+
</div>'''
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def FileInput(
|
|
208
|
+
name: str = "",
|
|
209
|
+
accept: str = "",
|
|
210
|
+
multiple: bool = False,
|
|
211
|
+
required: bool = False,
|
|
212
|
+
disabled: bool = False,
|
|
213
|
+
**kwargs
|
|
214
|
+
) -> str:
|
|
215
|
+
"""File input component"""
|
|
216
|
+
req = "required" if required else ""
|
|
217
|
+
dis = "disabled" if disabled else ""
|
|
218
|
+
mult = "multiple" if multiple else ""
|
|
219
|
+
|
|
220
|
+
return f'''<input
|
|
221
|
+
type="file"
|
|
222
|
+
name="{name}"
|
|
223
|
+
accept="{accept}"
|
|
224
|
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
225
|
+
{mult}
|
|
226
|
+
{req}
|
|
227
|
+
{dis}
|
|
228
|
+
>'''
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def DateInput(
|
|
232
|
+
name: str = "",
|
|
233
|
+
label: str = "",
|
|
234
|
+
value: str = "",
|
|
235
|
+
required: bool = False,
|
|
236
|
+
**kwargs
|
|
237
|
+
) -> str:
|
|
238
|
+
"""Date input component"""
|
|
239
|
+
req = "required" if required else ""
|
|
240
|
+
label_html = f'<label class="block text-sm font-medium mb-1">{label}</label>' if label else ''
|
|
241
|
+
return f'''<div class="mb-4">
|
|
242
|
+
{label_html}
|
|
243
|
+
<input type="date" name="{name}" value="{value}" {req}
|
|
244
|
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"/>
|
|
245
|
+
</div>'''
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def TimeInput(
|
|
249
|
+
name: str = "",
|
|
250
|
+
label: str = "",
|
|
251
|
+
value: str = "",
|
|
252
|
+
required: bool = False,
|
|
253
|
+
**kwargs
|
|
254
|
+
) -> str:
|
|
255
|
+
"""Time input component"""
|
|
256
|
+
req = "required" if required else ""
|
|
257
|
+
label_html = f'<label class="block text-sm font-medium mb-1">{label}</label>' if label else ''
|
|
258
|
+
return f'''<div class="mb-4">
|
|
259
|
+
{label_html}
|
|
260
|
+
<input type="time" name="{name}" value="{value}" {req}
|
|
261
|
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"/>
|
|
262
|
+
</div>'''
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def PasswordInput(
|
|
266
|
+
name: str = "",
|
|
267
|
+
label: str = "",
|
|
268
|
+
placeholder: str = "",
|
|
269
|
+
required: bool = False,
|
|
270
|
+
**kwargs
|
|
271
|
+
) -> str:
|
|
272
|
+
"""Password input component"""
|
|
273
|
+
req = "required" if required else ""
|
|
274
|
+
label_html = f'<label class="block text-sm font-medium mb-1">{label}</label>' if label else ''
|
|
275
|
+
return f'''<div class="mb-4">
|
|
276
|
+
{label_html}
|
|
277
|
+
<input type="password" name="{name}" placeholder="{placeholder}" {req}
|
|
278
|
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"/>
|
|
279
|
+
</div>'''
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def RangeInput(
|
|
283
|
+
name: str = "",
|
|
284
|
+
label: str = "",
|
|
285
|
+
value: int = 50,
|
|
286
|
+
min: int = 0,
|
|
287
|
+
max: int = 100,
|
|
288
|
+
**kwargs
|
|
289
|
+
) -> str:
|
|
290
|
+
"""Range slider component"""
|
|
291
|
+
return f'''<div class="mb-4">
|
|
292
|
+
{f'<label class="block text-sm font-medium mb-1">{label} (<span id="{name}_value">{value}</span>)</label>' if label else ''}
|
|
293
|
+
<input type="range" name="{name}" value="{value}" min="{min}" max="{max}"
|
|
294
|
+
onchange="document.getElementById('{name}_value').textContent = this.value"
|
|
295
|
+
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"/>
|
|
296
|
+
</div>'''
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def ColorInput(
|
|
300
|
+
name: str = "",
|
|
301
|
+
label: str = "",
|
|
302
|
+
value: str = "#3b82f6",
|
|
303
|
+
**kwargs
|
|
304
|
+
) -> str:
|
|
305
|
+
"""Color picker component"""
|
|
306
|
+
return f'''<div class="mb-4">
|
|
307
|
+
{f'<label class="block text-sm font-medium mb-1">{label}</label>' if label else ''}
|
|
308
|
+
<input type="color" name="{name}" value="{value}"
|
|
309
|
+
class="w-16 h-10 border border-gray-300 rounded-lg cursor-pointer"/>
|
|
310
|
+
</div>'''
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def NumberInput(
|
|
314
|
+
name: str = "",
|
|
315
|
+
placeholder: str = "",
|
|
316
|
+
value: str = "",
|
|
317
|
+
min: str = "",
|
|
318
|
+
max: str = "",
|
|
319
|
+
step: str = "1",
|
|
320
|
+
required: bool = False,
|
|
321
|
+
**kwargs
|
|
322
|
+
) -> str:
|
|
323
|
+
"""Number input component"""
|
|
324
|
+
return Input(
|
|
325
|
+
name=name,
|
|
326
|
+
type="number",
|
|
327
|
+
placeholder=placeholder,
|
|
328
|
+
value=value,
|
|
329
|
+
required=required
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
__all__ = [
|
|
334
|
+
'Input',
|
|
335
|
+
'TextArea',
|
|
336
|
+
'Select',
|
|
337
|
+
'Checkbox',
|
|
338
|
+
'Radio',
|
|
339
|
+
'RadioGroup',
|
|
340
|
+
'Form',
|
|
341
|
+
'FormGroup',
|
|
342
|
+
'FileInput',
|
|
343
|
+
'PasswordInput',
|
|
344
|
+
'EmailInput',
|
|
345
|
+
'NumberInput',
|
|
346
|
+
]
|