Skip to content

Commit 959702f

Browse files
committed
Make button a simpler, high-level API which uses a lower-level API content_button
1 parent a6c0a37 commit 959702f

File tree

22 files changed

+130
-57
lines changed

22 files changed

+130
-57
lines changed

docs/components/button.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Overview
22

3-
Button is based on the [Angular Material button component](https://material.angular.io/components/button/overview). Button is a [composite component](../guides/components.md#composite-components) and frequently used as a parent of the [Text](./text.md) component.
3+
Button is based on the [Angular Material button component](https://material.angular.io/components/button/overview).
44

55
## Examples
66

@@ -11,4 +11,5 @@ Button is based on the [Angular Material button component](https://material.angu
1111
## API
1212

1313
::: mesop.components.button.button.button
14+
::: mesop.components.button.button.content_button
1415
::: mesop.events.ClickEvent

mesop/__init__.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@
3131

3232
# REF(//scripts/scaffold_component.py):insert_component_import_export
3333
from mesop.components.box.box import box as box
34-
from mesop.components.button.button import button as button
34+
from mesop.components.button.button import (
35+
button as button,
36+
)
37+
from mesop.components.button.button import (
38+
content_button as content_button,
39+
)
3540
from mesop.components.checkbox.checkbox import (
3641
CheckboxChangeEvent as CheckboxChangeEvent,
3742
)

mesop/component_helpers/helper.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,14 @@ def wrapper(*args: Any, **kw_args: Any):
100100
source_code_location = get_caller_source_code_location(levels=2)
101101
component.MergeFrom(
102102
create_component(
103-
component_name=pb.ComponentName(
104-
fn_name=fn.__name__, module_path=fn.__module__
105-
),
103+
component_name=get_component_name(fn),
106104
proto=pb.UserDefinedType(
107105
args=[
108106
pb.UserDefinedType.Arg(
109107
arg_name=kw_arg, code_value=map_code_value(value)
110108
)
111109
for kw_arg, value in kw_args.items()
110+
if map_code_value(value) is not None
112111
]
113112
),
114113
source_code_location=source_code_location,
@@ -123,7 +122,13 @@ def wrapper(*args: Any, **kw_args: Any):
123122
return cast(C, wrapper)
124123

125124

126-
def map_code_value(value: Any) -> pb.CodeValue:
125+
def get_component_name(fn: Callable[..., Any]) -> pb.ComponentName:
126+
if "mesop.components" in fn.__module__:
127+
return pb.ComponentName(core_module=True, fn_name=fn.__name__)
128+
return pb.ComponentName(fn_name=fn.__name__, module_path=fn.__module__)
129+
130+
131+
def map_code_value(value: Any) -> pb.CodeValue | None:
127132
if isinstance(value, str):
128133
return pb.CodeValue(string_value=value)
129134
if isinstance(value, int):
@@ -132,7 +137,7 @@ def map_code_value(value: Any) -> pb.CodeValue:
132137
return pb.CodeValue(double_value=value)
133138
if isinstance(value, bool):
134139
return pb.CodeValue(bool_value=value)
135-
raise Exception("Unhandled value", value)
140+
return None
136141

137142

138143
def create_component(

mesop/components/button/button.py

+40-4
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,50 @@
22

33
import mesop.components.button.button_pb2 as button_pb
44
from mesop.component_helpers import (
5+
component,
56
insert_composite_component,
67
register_event_handler,
78
register_native_component,
89
)
10+
from mesop.components.text.text import text
911
from mesop.events import ClickEvent
1012

1113

12-
@register_native_component
14+
@component
1315
def button(
16+
label: str | None = None,
17+
*,
18+
on_click: Callable[[ClickEvent], Any] | None = None,
19+
type: Literal["raised", "flat", "stroked"] | None = None,
20+
color: Literal["primary", "accent", "warn"] | None = None,
21+
disable_ripple: bool = False,
22+
disabled: bool = False,
23+
key: str | None = None,
24+
):
25+
"""Creates a simple text Button component.
26+
27+
Args:
28+
label: Text label for button
29+
on_click: [click](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click_event) is a native browser event.
30+
type: Type of button style to use
31+
color: Theme color palette of the button
32+
disable_ripple: Whether the ripple effect is disabled or not.
33+
disabled: Whether the button is disabled.
34+
key: Unique identifier for this component instance.
35+
"""
36+
with content_button(
37+
on_click=on_click,
38+
type=type,
39+
color=color,
40+
disable_ripple=disable_ripple,
41+
disabled=disabled,
42+
key=key,
43+
):
44+
text(label)
45+
46+
47+
@register_native_component
48+
def content_button(
1449
*,
1550
on_click: Callable[[ClickEvent], Any] | None = None,
1651
type: Literal["raised", "flat", "stroked", "icon"] | None = None,
@@ -19,8 +54,9 @@ def button(
1954
disabled: bool = False,
2055
key: str | None = None,
2156
):
22-
"""Creates a Button component.
23-
Button is a composite component.
57+
"""Creates a button component, which is a composite component. Typically, you would use a text or icon component as a child.
58+
59+
Intended for advanced use cases.
2460
2561
Args:
2662
on_click: [click](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click_event) is a native browser event.
@@ -32,7 +68,7 @@ def button(
3268
"""
3369
return insert_composite_component(
3470
key=key,
35-
type_name="button",
71+
type_name="content_button",
3672
proto=button_pb.ButtonType(
3773
color=color,
3874
disable_ripple=disable_ripple,

mesop/editor/editor_codemod.py

+2
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ def leave_Call( # type: ignore (erroneously forbids return type with `None`)
146146
"icon"
147147
]:
148148
first_positional_arg = "icon"
149+
if component_name.fn_name in ["button"]:
150+
first_positional_arg = "label"
149151
return self._update_call(
150152
updated_node,
151153
self.input.arg_path.segments,

mesop/editor/editor_codemod_test.py

+15
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,21 @@ def test_simple_callsite(self) -> None:
160160
),
161161
)
162162

163+
def test_button_callsite(self) -> None:
164+
self.assertEditorUpdate(
165+
"button_callsite",
166+
pb.EditorUpdateCallsite(
167+
component_name=me_name("button"),
168+
arg_path=pb.ArgPath(
169+
segments=[pb.ArgPathSegment(keyword_argument="type")]
170+
),
171+
replacement=pb.CodeReplacement(
172+
new_code=pb.CodeValue(string_value="flat"),
173+
),
174+
source_code_location=pb.SourceCodeLocation(line=5),
175+
),
176+
)
177+
163178
def test_multi_callsite(self) -> None:
164179
self.assertEditorUpdate(
165180
"multi_callsite",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import mesop as me
2+
3+
4+
def app():
5+
me.button(type="flat")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import mesop as me
2+
3+
4+
def app():
5+
me.button(type="stroked")

mesop/examples/buttons.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,19 @@ def button_click(event: me.ClickEvent):
1414
@me.page(path="/buttons")
1515
def main():
1616
state = me.state(State)
17-
with me.button(
18-
on_click=button_click, type="flat", color="primary", disabled=False
19-
):
20-
me.text(text="primary color button")
2117

22-
with me.button(on_click=button_click, type="flat"):
23-
me.text(text="flat button")
18+
me.button(
19+
"primary color button",
20+
on_click=button_click,
21+
type="flat",
22+
color="primary",
23+
disabled=False,
24+
)
2425

25-
with me.button(on_click=button_click, type="raised"):
26-
me.text(text="raised button")
26+
me.button("flat button", on_click=button_click, type="flat")
2727

28-
with me.button(on_click=button_click, type="stroked"):
29-
me.text(text="stroked button")
28+
me.button("raised button", on_click=button_click, type="raised")
29+
30+
me.button("stroked button", on_click=button_click, type="stroked")
3031

3132
me.text(text=f"{state.count_clicks} clicks")

mesop/examples/docs/counter.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,4 @@ def button_click(event: me.ClickEvent):
1515
def main():
1616
state = me.state(State)
1717
me.text(f"Clicks: {state.clicks}")
18-
with me.button(on_click=button_click):
19-
me.text("Increment")
18+
me.button("Increment", on_click=button_click)

mesop/examples/docs/loading.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,4 @@ def main():
3030
if state.is_loading:
3131
me.progress_spinner()
3232
me.text(state.data)
33-
with me.button(on_click=button_click):
34-
me.text("Call API")
33+
me.button("Call API", on_click=button_click)

mesop/examples/docs/streaming.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,5 @@ def button_click(action: me.ClickEvent):
2424
@me.page(path="/streaming")
2525
def main():
2626
state = me.state(State)
27-
with me.button(on_click=button_click):
28-
me.text(text="click")
27+
me.button("click", on_click=button_click)
2928
me.text(text=f"{state.string}")

mesop/examples/generator.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,5 @@ def button_click(action: me.ClickEvent):
2424
@me.page(path="/generator")
2525
def main():
2626
state = me.state(State)
27-
with me.button(on_click=button_click):
28-
me.text(text="click")
27+
me.button("click", on_click=button_click)
2928
me.text(text=f"{state.string}")

mesop/examples/nested.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ def app():
1919
me.text(text="hi1")
2020
with me.box(style=me.Style(background="blue")):
2121
me.text(text="hi2")
22-
with me.button(on_click=click, key="incredibly_long_key"):
23-
me.text(text="a button")
22+
me.button("a button", on_click=click, key="incredibly_long_key")
2423
with me.box(style=me.Style(background="orange")):
2524
me.text(text=f"{state.val} clicks")

mesop/examples/playground.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ def left_panel():
8686
on_input=on_input,
8787
)
8888

89-
with me.button(type="stroked", on_click=on_submit):
90-
me.text("Submit")
89+
me.button("Submit", type="stroked", on_click=on_submit)
9190

9291

9392
def right_panel():

mesop/examples/playground_critic.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ def left_panel():
9696
on_input=on_input,
9797
)
9898

99-
with me.button(type="stroked", on_click=on_submit):
100-
me.text("Submit")
99+
me.button("Submit", type="stroked", on_click=on_submit)
101100

102101
with me.box(style=me.Style(margin=me.Margin(top=16, bottom=16))):
103102
me.divider()

mesop/examples/readme_app.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ def app():
2323
me.text("Hello, world!")
2424
me.textarea(rows=10, label="Prompt", on_input=on_prompt_input)
2525

26-
with me.button(on_click=on_submit):
27-
me.text("Submit")
26+
me.button("submit", on_click=on_submit)
2827

2928
state = me.state(State)
3029
me.text(f"Output: {state.output}")

mesop/examples/shared/navmenu.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def on_click(event: me.ClickEvent):
4545
)
4646
):
4747
with me.box(style=me.Style(margin=me.Margin(bottom=8))):
48-
with me.button(type="icon", on_click=on_click, key=url):
48+
with me.content_button(type="icon", on_click=on_click, key=url):
4949
with me.box(
5050
style=me.Style(
5151
display="flex", flex_direction="column", align_items="center"

mesop/examples/simple.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ def button_click(action: me.ClickEvent):
3232
@me.page(path="/many_checkboxes")
3333
def main():
3434
state = me.state(State)
35-
with me.button(on_click=button_click):
36-
me.text(text="click me")
35+
me.button("click me", on_click=button_click)
3736
me.text(text=f"{state.count} clicks")
3837
me.text(text=f"Selected keys: {state.keys}")
3938
for i in range(1000):

mesop/web/src/component_renderer/component_renderer.ts

+5-15
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import {
1212
import {CommonModule} from '@angular/common';
1313
import {
1414
Component as ComponentProto,
15-
Key,
16-
Type,
1715
UserEvent,
1816
} from 'mesop/mesop/protos/ui_jspb_proto_pb/mesop/protos/ui_pb';
1917
import {ComponentLoader} from './component_loader';
2018
import {BoxType} from 'mesop/mesop/components/box/box_jspb_proto_pb/mesop/components/box/box_pb';
21-
import {BaseComponent, typeToComponent} from './type_to_component';
19+
import {
20+
BaseComponent,
21+
UserDefinedComponent,
22+
typeToComponent,
23+
} from './type_to_component';
2224
import {Channel} from '../services/channel';
2325
import {EditorService, SelectionMode} from '../services/editor_service';
2426
import {
@@ -361,15 +363,3 @@ function isRegularComponent(component: ComponentProto) {
361363
!(typeName.getCoreModule() && typeName.getFnName() === 'box')
362364
);
363365
}
364-
365-
@Component({
366-
template: '<ng-content></ng-content>',
367-
standalone: true,
368-
})
369-
class UserDefinedComponent implements BaseComponent {
370-
@Input() key!: Key;
371-
@Input() type!: Type;
372-
ngOnChanges() {
373-
// Placeholder function since the
374-
}
375-
}

mesop/web/src/component_renderer/type_to_component.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {CheckboxComponent} from '../../../components/checkbox/checkbox';
2222
import {ButtonComponent} from '../../../components/button/button';
2323
import {TextComponent} from '../../../components/text/text';
2424
import {MarkdownComponent} from '../../../components/markdown/markdown';
25+
import {Component, Input} from '@angular/core';
2526

2627
export interface BaseComponent {
2728
key: Key;
@@ -35,6 +36,18 @@ export interface TypeToComponent {
3536
[typeName: string]: new (...rest: any[]) => BaseComponent;
3637
}
3738

39+
@Component({
40+
template: '<ng-content></ng-content>',
41+
standalone: true,
42+
})
43+
export class UserDefinedComponent implements BaseComponent {
44+
@Input() key!: Key;
45+
@Input() type!: Type;
46+
ngOnChanges() {
47+
// Placeholder function since the
48+
}
49+
}
50+
3851
export const typeToComponent = {
3952
'video': VideoComponent,
4053
'audio': AudioComponent,
@@ -53,7 +66,8 @@ export const typeToComponent = {
5366
// Textarea is a special case where it's exposed as a separate
5467
// component / API, but the implementation is almost identical as Input.
5568
'textarea': InputComponent,
56-
'button': ButtonComponent,
69+
'button': UserDefinedComponent,
70+
'content_button': ButtonComponent,
5771
'checkbox': CheckboxComponent,
5872
'text': TextComponent,
5973
'markdown': MarkdownComponent,

mesop/web/src/dev_tools/services/logger.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ export function mapComponentToObject(
8888
};
8989
}
9090

91-
if (component.getType()?.getName()?.getCoreModule() === false) {
91+
if (
92+
component.getType()?.getName()?.getCoreModule() === false ||
93+
component.getType()?.getName()?.getFnName() === 'button'
94+
) {
9295
const value: Record<string, any> = {};
9396
// Deserialize type
9497
const userDefinedType = UserDefinedType.deserializeBinary(

0 commit comments

Comments
 (0)