Skip to content

Commit

Permalink
Add CSS class support for box component (#995)
Browse files Browse the repository at this point in the history
  • Loading branch information
richard-to authored Sep 30, 2024
1 parent 429fa93 commit f60e66e
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 9 deletions.
78 changes: 78 additions & 0 deletions demo/bootstrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import mesop as me


@me.page(
security_policy=me.SecurityPolicy(
allowed_iframe_parents=["https://google.github.io"]
),
stylesheets=[
"https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css",
],
path="/bootstrap",
)
def page():
with me.box(classes="container"):
with me.box(
classes="d-flex flex-wrap justify-content-between 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("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="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("FAQs")
with me.box(classes="nav-item"):
with me.box(classes="nav-link px-2 text-body-secondary"):
me.text("About")
7 changes: 7 additions & 0 deletions demo/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import autocomplete as autocomplete
import badge as badge
import basic_animation as basic_animation
import bootstrap as bootstrap
import box as box
import button as button
import chat as chat
Expand Down Expand Up @@ -125,6 +126,12 @@ class Section:
Example(name="feedback"),
],
),
Section(
name="Integrations",
examples=[
Example(name="bootstrap"),
],
),
]

COMPONENTS_SECTIONS = [
Expand Down
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 @@ -15,6 +15,7 @@ def box(
*,
style: Style | None = None,
on_click: Callable[[ClickEvent], Any] | None = None,
classes: list[str] | str = "",
key: str | None = None,
) -> Any:
"""Creates a box component.
Expand All @@ -23,6 +24,7 @@ def box(
style: Style to apply to component. Follows [HTML Element inline style API](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style).
on_click: The callback function that is called when the box is clicked.
It receives a ClickEvent as its only argument.
classes: CSS classes.
key: The component [key](../components/index.md#component-key).
Returns:
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,
)
18 changes: 17 additions & 1 deletion mesop/editor/component_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
import inspect
from dataclasses import is_dataclass
from types import NoneType
from typing import Any, Callable, ItemsView, Literal, Sequence
from typing import (
Any,
Callable,
ItemsView,
Literal,
Sequence,
get_args,
get_origin,
)

import mesop.protos.ui_pb2 as pb
from mesop.exceptions import MesopInternalException
Expand Down Expand Up @@ -80,6 +88,14 @@ def get_fields(
and args[1] is str
and args[2] is NoneType
)
or (
# special case, for list[str]|str (used for box classes), use str
args
and len(args) == 2
and get_origin(args[0]) is list
and get_args(args[0]) == (str,)
and args[1] is str
)
):
field_type = pb.FieldType(string_type=pb.StringType())
elif getattr(param_type, "__origin__", None) is Literal:
Expand Down
1 change: 1 addition & 0 deletions mesop/examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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
131 changes: 131 additions & 0 deletions mesop/examples/tailwind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""
Example Tailwind command:
```
npx tailwindcss -i ./tailwind_input.css -o ./tailwind.css
```
Example Tailwind config:
```
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["main.py"],
theme: {
extend: {},
},
plugins: [],
safelist: [],
};
```
Original HTML mark up:
```
<html>
<body class="min-h-screen flex flex-col">
<!-- Header -->
<header class="bg-gray-800 text-white py-4">
<div class="container mx-auto">
<h1 class="text-2xl font-bold">Header</h1>
</div>
</header>
<!-- Main Content with Sidebar -->
<div class="flex flex-1">
<!-- Sidebar -->
<aside class="w-64 bg-gray-200 p-4">
<h2 class="text-lg font-semibold mb-4">Sidebar</h2>
<ul>
<li><a href="#" class="text-gray-700 block py-2">Link 1</a></li>
<li><a href="#" class="text-gray-700 block py-2">Link 2</a></li>
<li><a href="#" class="text-gray-700 block py-2">Link 3</a></li>
</ul>
</aside>
<!-- Main Content -->
<main class="flex-1 p-6 bg-gray-100">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-white p-6 rounded shadow">
<h2 class="text-xl font-bold mb-2">Box 1</h2>
<p>This is the content for box 1.</p>
</div>
<div class="bg-white p-6 rounded shadow">
<h2 class="text-xl font-bold mb-2">Box 2</h2>
<p>This is the content for box 2.</p>
</div>
<div class="bg-white p-6 rounded shadow">
<h2 class="text-xl font-bold mb-2">Box 3</h2>
<p>This is the content for box 3.</p>
</div>
</div>
</main>
</div>
<!-- Footer -->
<footer class="bg-gray-800 text-white py-4">
<div class="container mx-auto">
<p>&copy; 2024 Your Company</p>
</div>
</footer>
</body>
</html>
```
"""

import mesop as me


@me.page(
security_policy=me.SecurityPolicy(
allowed_iframe_parents=["https://google.github.io"]
),
stylesheets=[
# Specify your Tailwind CSS URL here.
#
# For local testing, you can just launch a basic Python HTTP server:
# python -m http.server 8000
"http://localhost:8000/assets/tailwind.css",
],
path="/tailwind",
)
def app():
with me.box(classes="min-h-screen flex flex-col"):
with me.box(classes="bg-gray-800 text-white py-4"):
with me.box(classes="container mx-auto"):
with me.box(classes="text-2xl font-bold"):
me.text("Header")

with me.box(classes="flex flex-1"):
with me.box(classes="w-64 bg-gray-200 p-4"):
with me.box(classes="text-lg font-semibold mb-4"):
me.text("Sidebar")
with me.box(classes="text-gray-700 block py-2"):
me.text("Link 1")
with me.box(classes="text-gray-700 block py-2"):
me.text("Link 2")
with me.box(classes="text-gray-700 block py-2"):
me.text("Link 3")

with me.box(classes="flex-1 p-6 bg-gray-100"):
with me.box(classes="grid grid-cols-1 md:grid-cols-3 gap-4"):
with me.box(classes="bg-white p-6 rounded shadow"):
with me.box(classes="text-xl font-bold mb-2"):
me.text("Box 1")
me.text("This is the content for box 1.")

with me.box(classes="bg-white p-6 rounded shadow"):
with me.box(classes="text-xl font-bold mb-2"):
me.text("Box 2")
me.text("This is the content for box 2")

with me.box(classes="bg-white p-6 rounded shadow"):
with me.box(classes="text-xl font-bold mb-2"):
me.text("Box 3")
me.text("This is the content for box 3")

with me.box(classes="bg-gray-800 text-white py-4"):
with me.box(classes="container mx-auto"):
me.text("2024 Mesop")
17 changes: 17 additions & 0 deletions mesop/web/src/app/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,23 @@ body {
}
}

// The box component has div-like semantics, so make it `display: block` by default.
// We use a high-level selector first since this will allow custom CSS selectors
// to override this style more easily, specifically for flex and grid displays.
component-renderer {
display: block;
}

// Custom elements like Angular component tags are treated as inline by default.
//
// Since component-renderer encompasses many different types of a components, we
// need to add a more specific selector to make these inline by default.
component-renderer:not([mesop-box]),
// The first component-renderer object is a box, but it needs to be inline.
mat-sidenav-content > component-renderer:first-child {
display: inline;
}

mesop-markdown {
h1,
h2,
Expand Down
17 changes: 14 additions & 3 deletions mesop/web/src/component_renderer/component_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ export class ComponentRenderer {
this._boxType = BoxType.deserializeBinary(
this.component.getType()!.getValue() as unknown as Uint8Array,
);
// Used for determinine which component-renderer elements are not boxes.
this.elementRef.nativeElement.setAttribute('mesop-box', 'true');
}

this.computeStyles();
Expand Down Expand Up @@ -288,6 +290,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 @@ -395,9 +401,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 All @@ -407,6 +411,13 @@ Make sure the web component name is spelled the same between Python and JavaScri
);
}

getClasses(): string {
if (this._boxType) {
return this._boxType.getClassesList().join(' ');
}
return '';
}

@HostListener('click', ['$event'])
onClick(event: Event) {
if (!this._boxType) {
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 {
.me-container {
height: 100%;
}

Expand Down
4 changes: 2 additions & 2 deletions mesop/web/src/editor/editor.ng.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<mat-sidenav-container class="container">
<mat-sidenav-content #sidenavContent class="content">
<mat-sidenav-container class="me-container">
<mat-sidenav-content #sidenavContent class="me-content">
<mesop-shell></mesop-shell>
@defer (when showEditorToolbar()) {
<mesop-editor-toolbar></mesop-editor-toolbar>
Expand Down
4 changes: 2 additions & 2 deletions mesop/web/src/editor/editor.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
.container {
.me-container {
height: 100%;
}

.content {
.me-content {
overflow: hidden;
}

Expand Down

0 comments on commit f60e66e

Please sign in to comment.