Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add class property to box component #990

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mesop/components/box/box.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ package mesop.components.box;

message BoxType {
optional string on_click_handler_id = 2;
repeated string classes = 3;
}
3 changes: 3 additions & 0 deletions mesop/components/box/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
def box(
*,
style: Style | None = None,
classes: str = "",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like below you're supporting both "foo bar" and ["foo", "bar"]? I would type this as list[str] | str

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's what I wanted to support initially. But ran into an issue with that annotation. I think just needed to update the code in the component maker code to handle that annotation. But since I just wanted a to get a quick draft up, just left it as is.

But I guess, I wonder if we need to allow a list. We could have a me.convert_class_list type helper if people are dynamically generating class names. I think I'd lean toward allowing people to pass in lists for the dynamic class name case rather than introducing a helper function.

on_click: Callable[[ClickEvent], Any] | None = None,
key: str | None = None,
) -> Any:
"""Creates a box component.

Args:
style: Style to apply to component. Follows [HTML Element inline style API](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style).
classes: CSS classes
on_click: The callback function that is called when the box is clicked.
It receives a ClickEvent as its only argument.
key: The component [key](../components/index.md#component-key).
Expand All @@ -35,6 +37,7 @@ def box(
on_click_handler_id=register_event_handler(on_click, event=ClickEvent)
if on_click
else "",
classes=classes if isinstance(classes, list) else classes.split(" "),
),
style=style,
)
2 changes: 2 additions & 0 deletions mesop/examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from mesop.examples import (
boilerplate_free_event_handlers as boilerplate_free_event_handlers,
)
from mesop.examples import bootstrap as bootstrap
from mesop.examples import box as box
from mesop.examples import buttons as buttons
from mesop.examples import checkbox_and_radio as checkbox_and_radio
Expand Down Expand Up @@ -45,6 +46,7 @@
)
from mesop.examples import starter_kit as starter_kit
from mesop.examples import sxs as sxs
from mesop.examples import tailwind as tailwind
from mesop.examples import testing as testing
from mesop.examples import viewport_size as viewport_size
from mesop.examples import web_component as web_component
Expand Down
89 changes: 89 additions & 0 deletions mesop/examples/bootstrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import mesop as me


@me.page(
security_policy=me.SecurityPolicy(
allowed_iframe_parents=["https://google.github.io"]
),
stylesheets=[
"/assets/bootstrap.css",
],
path="/bootstrap",
)
def page():
with me.box(classes="container"):
with me.box(
classes="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom",
):
with me.box(
classes="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-body-emphasis text-decoration-none fs-4",
):
me.text("Mesop")

with me.box(classes="nav nav-pills"):
with me.box(classes="nav-item"):
with me.box(classes="nav-link active"):
me.text("Pricing")
with me.box(classes="nav-item"):
with me.box(classes="nav-link"):
me.text("Features")
with me.box(classes="nav-item"):
with me.box(classes="nav-link"):
me.text("About")

with me.box(classes="container px-4 py-5"):
with me.box(classes="pb-2 border-bottom"):
me.text("Columns", type="headline-5")

with me.box(classes="row g-4 py-5 row-cols-1 row-cols-lg-3"):
with me.box(classes="feature col"):
with me.box(classes="fs-2 text-body-emphasis"):
me.text("Featured title")
me.text(
"Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words."
)
me.link(text="Call to action", url="/#")

with me.box(classes="feature col"):
with me.box(classes="fs-2 text-body-emphasis"):
me.text("Featured title")
me.text(
"Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words."
)
me.link(text="Call to action", url="/#")

with me.box(classes="feature col"):
with me.box(classes="fs-2 text-body-emphasis"):
me.text("Featured title")
me.text(
"Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words."
)
me.link(text="Call to action", url="/#")

with me.box(
classes="d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top"
):
with me.box(classes="col-md-4 mb-0 text-body-secondary"):
me.text("Copyright 2024 Mesop")

with me.box(
classes="col-md-4 d-flex align-items-center justify-content-center mb-3 mb-md-0 me-md-auto link-body-emphasis text-decoration-none"
):
me.icon("home")

with me.box(classes="nav col-md-4 justify-content-end"):
with me.box(classes="nav-item"):
with me.box(classes="nav-link px-2 text-body-secondary"):
me.text("Home")
with me.box(classes="nav-item"):
with me.box(classes="nav-link px-2 text-body-secondary"):
me.text("Features")
with me.box(classes="nav-item"):
with me.box(classes="nav-link px-2 text-body-secondary"):
me.text("Pricing")
with me.box(classes="nav-item"):
with me.box(classes="nav-link px-2 text-body-secondary"):
me.text("FAQs")
with me.box(classes="nav-item"):
with me.box(classes="nav-link px-2 text-body-secondary"):
me.text("About")
100 changes: 100 additions & 0 deletions mesop/examples/tailwind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import mesop as me


@me.page(
security_policy=me.SecurityPolicy(
allowed_iframe_parents=["https://google.github.io"]
),
stylesheets=[
"/assets/tailwind.css",
],
path="/tailwind",
)
def app():
with me.box(classes="grid grid-cols-10 gap-2"):
with me.box(classes="bg-sky-50 aspect-square"):
pass
with me.box(classes="bg-sky-100 aspect-square"):
pass
with me.box(classes="bg-sky-200 aspect-square"):
pass
with me.box(classes="bg-sky-300 aspect-square"):
pass
with me.box(classes="bg-sky-400 aspect-square"):
pass
with me.box(classes="bg-sky-500 aspect-square"):
pass
with me.box(classes="bg-sky-600 aspect-square"):
pass
with me.box(classes="bg-sky-700 aspect-square"):
pass
with me.box(classes="bg-sky-800 aspect-square"):
pass
with me.box(classes="bg-sky-900 aspect-square"):
pass

with me.box(classes="grid grid-cols-10 gap-2"):
with me.box(classes="bg-blue-50 aspect-square"):
pass
with me.box(classes="bg-blue-100 aspect-square"):
pass
with me.box(classes="bg-blue-200 aspect-square"):
pass
with me.box(classes="bg-blue-300 aspect-square"):
pass
with me.box(classes="bg-blue-400 aspect-square"):
pass
with me.box(classes="bg-blue-500 aspect-square"):
pass
with me.box(classes="bg-blue-600 aspect-square"):
pass
with me.box(classes="bg-blue-700 aspect-square"):
pass
with me.box(classes="bg-blue-800 aspect-square"):
pass
with me.box(classes="bg-blue-900 aspect-square"):
pass

with me.box(classes="grid grid-cols-10 gap-2"):
with me.box(classes="bg-indigo-50 aspect-square"):
pass
with me.box(classes="bg-indigo-100 aspect-square"):
pass
with me.box(classes="bg-indigo-200 aspect-square"):
pass
with me.box(classes="bg-indigo-300 aspect-square"):
pass
with me.box(classes="bg-indigo-400 aspect-square"):
pass
with me.box(classes="bg-indigo-500 aspect-square"):
pass
with me.box(classes="bg-indigo-600 aspect-square"):
pass
with me.box(classes="bg-indigo-700 aspect-square"):
pass
with me.box(classes="bg-indigo-800 aspect-square"):
pass
with me.box(classes="bg-indigo-900 aspect-square"):
pass

with me.box(classes="grid grid-cols-10 gap-2"):
with me.box(classes="bg-violet-50 aspect-square"):
pass
with me.box(classes="bg-violet-100 aspect-square"):
pass
with me.box(classes="bg-violet-200 aspect-square"):
pass
with me.box(classes="bg-violet-300 aspect-square"):
pass
with me.box(classes="bg-violet-400 aspect-square"):
pass
with me.box(classes="bg-violet-500 aspect-square"):
pass
with me.box(classes="bg-violet-600 aspect-square"):
pass
with me.box(classes="bg-violet-700 aspect-square"):
pass
with me.box(classes="bg-violet-800 aspect-square"):
pass
with me.box(classes="bg-violet-900 aspect-square"):
pass
27 changes: 26 additions & 1 deletion mesop/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import os
import secrets
import sys
import time
import urllib.parse as urlparse
from typing import Any, Generator, Sequence
Expand Down Expand Up @@ -40,6 +41,7 @@
if EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED:
print("Experiment enabled: EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED")


LOCALHOSTS = (
# For IPv4 localhost
"127.0.0.1",
Expand All @@ -49,6 +51,8 @@

STREAM_END = "data: <stream_end>\n\n"

DEFAULT_ASSETS_URL_PATH = "/assets"


def is_processing_request():
return _requests_in_flight > 0
Expand All @@ -60,7 +64,15 @@ def is_processing_request():
def configure_flask_app(
*, prod_mode: bool = True, exceptions_to_propagate: Sequence[type] = ()
) -> Flask:
flask_app = Flask(__name__)
static_assets_folder = get_static_assets_folder()
if static_assets_folder:
flask_app = Flask(
__name__,
static_folder=static_assets_folder,
static_url_path=DEFAULT_ASSETS_URL_PATH,
)
else:
flask_app = Flask(__name__)

def render_loop(
path: str,
Expand Down Expand Up @@ -513,3 +525,16 @@ def sse_request(
if decoded_line.startswith(SSE_DATA_PREFIX):
event_data = json.loads(decoded_line[len(SSE_DATA_PREFIX) :])
yield event_data


def get_static_assets_folder() -> str | None:
folder = os.environ.get("MESOP_STATIC_ASSETS_FOLDER", "")
if not folder:
return None

if not os.path.isabs(folder):
folder = os.path.join(
os.path.dirname(os.path.abspath(sys.argv[0])),
folder,
)
return folder
4 changes: 4 additions & 0 deletions mesop/web/src/app/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ body {
}
}

component-renderer {
display: block;
}

mesop-markdown {
h1,
h2,
Expand Down
15 changes: 11 additions & 4 deletions mesop/web/src/component_renderer/component_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ export class ComponentRenderer {

computeStyles() {
this.elementRef.nativeElement.style = this.getStyle();
const classes = this.getClasses();
if (classes) {
this.elementRef.nativeElement.classList = classes;
}
}

createComponentRef() {
Expand Down Expand Up @@ -371,6 +375,12 @@ Make sure the web component name is spelled the same between Python and JavaScri
//////////////
// Box-specific implementation:
//////////////
getClasses(): string {
if (this._boxType) {
return this._boxType.getClassesList().join(' ');
}
return '';
}

getStyle(): string {
if (!this._boxType) {
Expand All @@ -395,10 +405,7 @@ Make sure the web component name is spelled the same between Python and JavaScri
return '';
}

// `display: block` because box should have "div"-like semantics.
// Custom elements like Angular component tags are treated as inline by default.
let style = 'display: block;';

let style = '';
if (this.component.getStyle()) {
style += formatStyle(this.component.getStyle()!);
}
Expand Down
2 changes: 1 addition & 1 deletion mesop/web/src/dev_tools/editor_panel/editor_panel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
--default-bottom-panel-height: 400px;
}

.container {
.sidenav-container {
height: 100%;
}

Expand Down
2 changes: 1 addition & 1 deletion mesop/web/src/editor/editor.ng.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<mat-sidenav-container class="container">
<mat-sidenav-container class="sidenav-container">
<mat-sidenav-content #sidenavContent class="content">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious, was this causing a naming conflict with bootstrap?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they have a class named "container". So when I first tried loading the bootstrap.css, the layout was messed up and eventually tracked it down to this usage.

<mesop-shell></mesop-shell>
@defer (when showEditorToolbar()) {
Expand Down
2 changes: 1 addition & 1 deletion mesop/web/src/editor/editor.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.container {
.sidenav-container {
height: 100%;
}

Expand Down
Loading