Spaces:
Sleeping
Sleeping
| from fasthtml.common import * | |
| # MonsterUI shadows fasthtml components with the same name | |
| from monsterui.all import * | |
| # If you don't want shadowing behavior, you use import monsterui.core as ... style instead | |
| from fasthtml.components import Uk_input_tag | |
| from fasthtml.svg import * | |
| import calendar | |
| from datetime import datetime | |
| # Get frankenui and tailwind headers via CDN using Theme.blue.headers() | |
| hdrs = Theme.blue.headers() | |
| # fast_app is shadowed by MonsterUI to make it default to no Pico, and add body classes | |
| # needed for frankenui theme styling | |
| app, rt = fast_app(hdrs=hdrs, live=True) | |
| products = [ | |
| {"name": "Kalpasi (Black Stone Flower)", "price": "$509.50", "img": "https://picsum.photos/400/100?random=1"}, | |
| {"name": "Radhuni (Wild Celery Seeds)", "price": "$505.25", "img": "https://picsum.photos/400/100?random=2"}, | |
| {"name": "Kokum", "price": "$503.00", "img": "https://picsum.photos/400/100?random=3"}, | |
| {"name": "Mace (Javitri)", "price": "$504.50", "img": "https://picsum.photos/400/100?random=4"}, | |
| {"name": "Anardana (Dried Pomegranate)", "price": "$506.25", "img": "https://picsum.photos/400/100?random=5"}, | |
| {"name": "Ajmod (Indian Lovage)", "price": "$507.00", "img": "https://picsum.photos/400/100?random=6"}, | |
| {"name": "Marathi Moggu", "price": "$508.50", "img": "https://picsum.photos/400/100?random=7"}, | |
| {"name": "Kalonji (Nigella Seeds)", "price": "$504.25", "img": "https://picsum.photos/400/100?random=8"} | |
| ] | |
| CreateAccount = Card( | |
| Grid( | |
| Button(DivLAligned(UkIcon('github'), Div('Github')), cls="w-full"), | |
| Button('Google', cls="w-full") | |
| ), | |
| DividerSplit("OR CONTINUE WITH", text_cls=TextPresets.muted_sm), | |
| LabelInput('Email', id='email', placeholder='m@example.com'), | |
| LabelInput('Password', id='password',placeholder='Password', type='Password'), | |
| header=(H3('Create an Account'), Subtitle('Enter your email below to create your account')), | |
| footer=Button('Create Account', cls=(ButtonT.primary, 'w-full')) | |
| ) | |
| PaypalSVG_data = "..." # unchanged | |
| AppleSVG_data = "..." # unchanged | |
| Card1Svg = Svg(viewBox="0 0 24 24", fill="none", stroke="currentColor", stroke_linecap="round", stroke_linejoin="round", stroke_width="2", cls="h-6 w-6 mr-1")( | |
| Rect(width="20", height="14", x="2", y="5", rx="2"), | |
| Path(d="M2 10h20") | |
| ) | |
| PaypalSvg = Svg(role="img", viewBox="0 0 24 24", cls="h-6 w-6 mr-1")(Path(d=PaypalSVG_data, fill="currentColor")) | |
| AppleSvg = Svg(role="img", viewBox="0 0 24 24", cls="h-6 w-6 mr-1")(Path(d=AppleSVG_data, fill="currentColor")) | |
| PaymentMethod = Card( | |
| Grid( | |
| Button(DivCentered(Card1Svg, "Card"), cls='h-20 border-2 border-primary rounded-lg', type="button"), | |
| Button(DivCentered(PaypalSvg, "PayPal"), cls='h-20 border rounded-lg', type="button"), | |
| Button(DivCentered(AppleSvg, "Apple"), cls='h-20 border rounded-lg', type="button") | |
| ), | |
| Form( | |
| LabelInput('Name', id='name', placeholder='John Doe'), | |
| LabelInput('Card Number', id='card_number', placeholder='1234 5678 9012 3456'), | |
| Grid( | |
| LabelSelect(*Options(*calendar.month_name[1:], selected_idx=0), label='Expires', id='expire_month'), | |
| LabelSelect(*Options(*range(2024,2030), selected_idx=0), label='Year', id='expire_year'), | |
| LabelInput('CVV', id='cvv', placeholder='CVV', cls='mt-0') | |
| ) | |
| ), | |
| header=(H3('Payment Method'), Subtitle('Add a new payment method to your account.')) | |
| ) | |
| def ProductCard(p): | |
| # Card does lots of boilerplate classes so you can just pass in the content | |
| return Card( | |
| # width:100% makes the image take the full width so we are guarenteed that we won't | |
| # have the image cut off or not large enough. Because all our images are a consistent | |
| # size we do not need to worry about stretching or skewing the image, this is ideal. | |
| # If you have images of different sizes, you will need to use object-fit:cover and/or | |
| # height to either strech, shrink, or crop the image. It is much better to adjust your | |
| # images to be a consistent size upfront so you don't have to handle edge cases of | |
| # different images skeweing/stretching differently. | |
| Img(src=p["img"], alt=p["name"], cls="w-full h-40 object-cover rounded-t-lg"), | |
| # All components can take a cls argument to add additional styling - `mt-2` adds margin | |
| # to the top (see spacing tutorial for details on spacing). | |
| # | |
| # Often adding space makes a site look more put together - usually the 2 - 5 range is a | |
| # good choice | |
| H4(p["name"], cls="mt-3 text-lg font-semibold text-gray-900"), | |
| # There are helpful Enums, such as TextPresetsT, ButtonT, ContainerT, etc that allow for easy | |
| # discoverability of class options. | |
| # bold_sm is helpful for things that you want to look like regular text, but stand out | |
| # visually for emphasis. | |
| P(p["price"], cls=(TextPresets.bold_sm, "text-gray-700 mt-1")), | |
| # ButtonT.primary is useful for actions you really want the user to take (like adding | |
| # something to the cart) - these stand out visually. For dangerous actions (like | |
| # deleting something) you generally would want to use ButtonT.destructive. For UX actions | |
| # that aren't a goal of the page (like cancelling something that hasn't been submitted) | |
| # you generally want the default styling. | |
| Button("Add to cart", cls=(ButtonT.primary, "mt-3 w-full"), | |
| hx_get=product_detail.to(product_name=p['name'], product_price=p['price']), | |
| hx_push_url='true', | |
| hx_target='body'), | |
| cls="shadow-lg rounded-lg overflow-hidden bg-white hover:shadow-xl transition-shadow" | |
| ) | |
| # Define this once, at module level (top of file is best) | |
| example_product_description = """ | |
| Lorem ipsum dolor sit amet, consectetur adipiscing elit. | |
| Praesent euismod, sapien nec facilisis tincidunt, nunc | |
| nibh posuere justo, vitae luctus neque magna vel nulla. | |
| Curabitur at felis ac nulla fermentum tincidunt. | |
| Integer non risus nec nulla cursus porttitor. | |
| Suspendisse potenti. Donec vel sapien nec erat | |
| malesuada viverra sed a lorem. | |
| - Proin facilisis ligula sed sapien tincidunt, at | |
| fermentum magna volutpat. | |
| - Curabitur vitae lectus nec justo cursus | |
| sollicitudin non sed est. | |
| - Sed ut perspiciatis unde omnis iste natus error | |
| sit voluptatem accusantium doloremque laudantium. | |
| """ | |
| def index(): | |
| # Titled using a H1 title, sets the page title, and wraps contents in Main(Container(...)) using | |
| # frankenui styles. Generally you will want to use Titled for all of your pages | |
| return Titled("Store", | |
| Grid(*[ProductCard(p) for p in products], cols_lg=3, gap=6, cls="p-6") | |
| ) | |
| def product_detail(product_name: str, product_price: str): | |
| return ( | |
| Title("Product Detail"), | |
| Grid( | |
| Div( | |
| H3(product_name, cls="text-xl font-semibold mb-4"), | |
| render_md(example_product_description.format(product_name=product_name)) | |
| ), | |
| Div( | |
| H3("Order", cls="text-lg font-semibold mb-3"), | |
| Form( | |
| LabelInput("Product", id="name", value=product_name, readonly=True, | |
| cls="bg-gray-100 cursor-not-allowed"), | |
| LabelInput("Price", id="price", value=product_price, readonly=True, | |
| cls="bg-gray-100 cursor-not-allowed"), | |
| LabelInput("Quantity", id="quantity", placeholder="1"), | |
| LabelInput("Email", id="email", placeholder="accountemail@example.com"), | |
| Div(PaymentMethod, cls="space-y-4 mt-4"), | |
| Button("Add to order", cls=(ButtonT.primary, "w-full mt-4")), | |
| Button("Continue shopping", | |
| cls=(ButtonT.secondary, "w-full mt-2"), | |
| hx_get="/", | |
| hx_push_url="true", | |
| hx_target="body") | |
| ) | |
| ), | |
| cols_lg=2, | |
| cls="gap-8 p-6" | |
| ) | |
| ) | |
| serve() |