Skip to content

Commit c3672fb

Browse files
authored
Add basic form examples. (#962)
Just some examples to show ways to layout forms. Will add these to goldens as well later.
1 parent 525ccd8 commit c3672fb

File tree

3 files changed

+405
-0
lines changed

3 files changed

+405
-0
lines changed

demo/form_billing.py

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
from dataclasses import asdict
2+
from typing import Literal
3+
4+
import mesop as me
5+
6+
ROW_GAP = 15
7+
BOX_PADDING = 20
8+
9+
10+
@me.stateclass
11+
class State:
12+
first_name: str
13+
last_name: str
14+
username: str
15+
email: str
16+
address: str
17+
address_2: str
18+
country: str
19+
state: str
20+
zip: str
21+
payment_type: str
22+
name_on_card: str
23+
credit_card: str
24+
expiration: str
25+
cvv: str
26+
errors: dict[str, str]
27+
28+
29+
def is_mobile():
30+
return me.viewport_size().width < 620
31+
32+
33+
def calc_input_size(items: int):
34+
return int(
35+
(me.viewport_size().width - (ROW_GAP * items) - (BOX_PADDING * 2)) / items
36+
)
37+
38+
39+
def load(e: me.LoadEvent):
40+
me.set_theme_density(-3)
41+
me.set_theme_mode("system")
42+
43+
44+
@me.page(
45+
security_policy=me.SecurityPolicy(
46+
allowed_iframe_parents=["https://google.github.io"]
47+
),
48+
path="/form_billing",
49+
on_load=load,
50+
)
51+
def page():
52+
state = me.state(State)
53+
54+
with me.box(
55+
style=me.Style(
56+
padding=me.Padding.all(BOX_PADDING),
57+
max_width=800,
58+
margin=me.Margin.symmetric(horizontal="auto"),
59+
)
60+
):
61+
me.text(
62+
"Billing form",
63+
type="headline-4",
64+
style=me.Style(margin=me.Margin(bottom=10)),
65+
)
66+
67+
with form_group():
68+
name_width = calc_input_size(2) if is_mobile() else "100%"
69+
input_field(label="First name", width=name_width)
70+
input_field(label="Last name", width=name_width)
71+
72+
with form_group():
73+
input_field(label="Username")
74+
75+
with me.box(style=me.Style(display="flex", gap=ROW_GAP)):
76+
input_field(label="Email", input_type="email")
77+
78+
with me.box(style=me.Style(display="flex", gap=ROW_GAP)):
79+
input_field(label="Address")
80+
81+
with form_group():
82+
input_field(label="Address 2")
83+
84+
with form_group():
85+
country_state_zip_width = calc_input_size(3) if is_mobile() else "100%"
86+
input_field(label="Country", width=country_state_zip_width)
87+
input_field(label="State", width=country_state_zip_width)
88+
input_field(label="Zip", width=country_state_zip_width)
89+
90+
divider()
91+
92+
me.text(
93+
"Payment",
94+
type="headline-4",
95+
style=me.Style(margin=me.Margin(bottom=10)),
96+
)
97+
98+
with form_group(flex_direction="column"):
99+
me.radio(
100+
key="payment_type",
101+
on_change=on_change,
102+
options=[
103+
me.RadioOption(label="Credit card", value="credit_card"),
104+
me.RadioOption(label="Debit card", value="debit_card"),
105+
me.RadioOption(label="Paypal", value="paypal"),
106+
],
107+
style=me.Style(
108+
display="flex", flex_direction="column", margin=me.Margin(bottom=20)
109+
),
110+
)
111+
if "payment_type" in state.errors:
112+
me.text(
113+
state.errors["payment_type"],
114+
style=me.Style(
115+
margin=me.Margin(top=-30, left=5, bottom=15),
116+
color=me.theme_var("error"),
117+
font_size=13,
118+
),
119+
)
120+
121+
with form_group():
122+
payments_width = calc_input_size(2) if is_mobile() else "100%"
123+
input_field(label="Name on card", width=payments_width)
124+
input_field(label="Credit card", width=payments_width)
125+
126+
with form_group():
127+
input_field(label="Expiration", width=payments_width)
128+
input_field(label="CVV", width=payments_width, input_type="number")
129+
130+
divider()
131+
132+
me.button(
133+
"Continue to checkout",
134+
type="flat",
135+
style=me.Style(width="100%", padding=me.Padding.all(25), font_size=20),
136+
on_click=on_click,
137+
)
138+
139+
140+
def divider():
141+
with me.box(style=me.Style(margin=me.Margin.symmetric(vertical=20))):
142+
me.divider()
143+
144+
145+
@me.content_component
146+
def form_group(flex_direction: Literal["row", "column"] = "row"):
147+
with me.box(
148+
style=me.Style(
149+
display="flex", flex_direction=flex_direction, gap=ROW_GAP, width="100%"
150+
)
151+
):
152+
me.slot()
153+
154+
155+
def input_field(
156+
*,
157+
key: str = "",
158+
label: str,
159+
value: str = "",
160+
width: str | int = "100%",
161+
input_type: Literal[
162+
"color",
163+
"date",
164+
"datetime-local",
165+
"email",
166+
"month",
167+
"number",
168+
"password",
169+
"search",
170+
"tel",
171+
"text",
172+
"time",
173+
"url",
174+
"week",
175+
] = "text",
176+
):
177+
state = me.state(State)
178+
key = key if key else label.lower().replace(" ", "_")
179+
with me.box(style=me.Style(flex_grow=1, width=width)):
180+
me.input(
181+
key=key,
182+
label=label,
183+
value=value,
184+
appearance="outline",
185+
style=me.Style(width=width),
186+
type=input_type,
187+
on_blur=on_blur,
188+
)
189+
if key in state.errors:
190+
me.text(
191+
state.errors[key],
192+
style=me.Style(
193+
margin=me.Margin(top=-13, left=15, bottom=15),
194+
color=me.theme_var("error"),
195+
font_size=13,
196+
),
197+
)
198+
199+
200+
def on_change(e: me.RadioChangeEvent):
201+
state = me.state(State)
202+
setattr(state, e.key, e.value)
203+
204+
205+
def on_blur(e: me.InputBlurEvent):
206+
state = me.state(State)
207+
setattr(state, e.key, e.value)
208+
209+
210+
def on_click(e: me.ClickEvent):
211+
state = me.state(State)
212+
213+
# Replace with real validation logic.
214+
errors = {}
215+
for key, value in asdict(state).items(): # type: ignore
216+
if key == "error":
217+
continue
218+
if not value:
219+
errors[key] = f"{key.replace('_', ' ').capitalize()} is required"
220+
state.errors = errors
221+
222+
# Replace with form processing logic.
223+
if not state.errors:
224+
pass

0 commit comments

Comments
 (0)