Skip to content

Commit c62e5d7

Browse files
committed
Create sidenav component
1 parent 31800c3 commit c62e5d7

21 files changed

+181
-3
lines changed

docs/components/sidenav.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## Overview
2+
3+
Sidenav is a sidebar typically used for navigation and is based on the [Angular Material sidenav component](https://material.angular.io/components/sidenav/overview).
4+
5+
## Examples
6+
7+
```python
8+
--8<-- "mesop/components/sidenav/e2e/sidenav_app.py"
9+
```
10+
11+
## API
12+
13+
::: mesop.components.sidenav.sidenav.sidenav

mesop/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ py_library(
1818
visibility = ["//build_defs:mesop_users"],
1919
deps = [
2020
# REF(//scripts/scaffold_component.py):insert_component_import
21+
"//mesop/components/sidenav:py",
2122
"//mesop/components/video:py",
2223
"//mesop/components/audio:py",
2324
"//mesop/components/image:py",

mesop/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
from mesop.components.select.select import (
8383
select as select,
8484
)
85+
from mesop.components.sidenav.sidenav import sidenav as sidenav
8586
from mesop.components.slide_toggle.slide_toggle import (
8687
SlideToggleChangeEvent as SlideToggleChangeEvent,
8788
)

mesop/components/sidenav/BUILD

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
load("//mesop/components:defs.bzl", "mesop_component")
2+
3+
package(
4+
default_visibility = ["//build_defs:mesop_internal"],
5+
)
6+
7+
mesop_component(
8+
name = "sidenav",
9+
)

mesop/components/sidenav/__init__.py

Whitespace-only changes.

mesop/components/sidenav/e2e/BUILD

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
load("//build_defs:defaults.bzl", "py_library")
2+
3+
package(
4+
default_visibility = ["//build_defs:mesop_examples"],
5+
)
6+
7+
py_library(
8+
name = "e2e",
9+
srcs = glob(["*.py"]),
10+
deps = [
11+
"//mesop",
12+
],
13+
)
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import sidenav_app as sidenav_app
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import mesop as me
2+
3+
4+
@me.stateclass
5+
class State:
6+
sidenav_open: bool
7+
8+
9+
def on_click(e: me.ClickEvent):
10+
s = me.state(State)
11+
s.sidenav_open = not s.sidenav_open
12+
13+
14+
SIDENAV_WIDTH = 200
15+
16+
17+
@me.page(path="/components/sidenav/e2e/sidenav_app")
18+
def app():
19+
state = me.state(State)
20+
with me.sidenav(
21+
opened=state.sidenav_open, style=me.Style(width=SIDENAV_WIDTH)
22+
):
23+
me.text("Inside sidenav")
24+
25+
with me.box(
26+
style=me.Style(
27+
margin=me.Margin(left=SIDENAV_WIDTH if state.sidenav_open else 0),
28+
),
29+
):
30+
with me.content_button(on_click=on_click):
31+
me.icon("menu")
32+
me.markdown("Main content")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {test, expect} from '@playwright/test';
2+
3+
test('test', async ({page}) => {
4+
await page.goto('/components/sidenav/e2e/sidenav_app');
5+
expect(await page.getByText('Hello, world!').textContent()).toContain(
6+
'Hello, world!',
7+
);
8+
});
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<mat-sidenav
2+
[style]="getStyle()"
3+
[opened]="config().getOpened()"
4+
[fixedInViewport]="true"
5+
>
6+
<ng-content></ng-content>
7+
</mat-sidenav>
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
syntax = "proto2";
2+
3+
package mesop.components.sidenav;
4+
5+
message SidenavType {
6+
optional bool opened = 1;
7+
}

mesop/components/sidenav/sidenav.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import mesop.components.sidenav.sidenav_pb2 as sidenav_pb
2+
from mesop.component_helpers import (
3+
Style,
4+
insert_composite_component,
5+
register_native_component,
6+
)
7+
8+
9+
@register_native_component
10+
def sidenav(
11+
*,
12+
opened: bool = True,
13+
style: Style | None = None,
14+
key: str | None = None,
15+
):
16+
"""
17+
This function creates a sidenav.
18+
19+
Args:
20+
opened: A flag to determine if the sidenav is open or closed. Defaults to True.
21+
style: An optional Style object to apply custom styles. Defaults to None.
22+
key: An optional key for the component. Defaults to None.
23+
"""
24+
return insert_composite_component(
25+
key=key,
26+
type_name="sidenav",
27+
style=style,
28+
proto=sidenav_pb.SidenavType(
29+
opened=opened,
30+
),
31+
)

mesop/components/sidenav/sidenav.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {Component, Input} from '@angular/core';
2+
import {
3+
Key,
4+
Style,
5+
Type,
6+
} from 'mesop/mesop/protos/ui_jspb_proto_pb/mesop/protos/ui_pb';
7+
import {SidenavType} from 'mesop/mesop/components/sidenav/sidenav_jspb_proto_pb/mesop/components/sidenav/sidenav_pb';
8+
import {Channel} from '../../web/src/services/channel';
9+
import {MatSidenavModule} from '@angular/material/sidenav';
10+
import {formatStyle} from '../../web/src/utils/styles';
11+
12+
@Component({
13+
selector: 'mesop-sidenav',
14+
templateUrl: 'sidenav.ng.html',
15+
standalone: true,
16+
imports: [MatSidenavModule],
17+
})
18+
export class SidenavComponent {
19+
@Input({required: true}) type!: Type;
20+
@Input() key!: Key;
21+
@Input() style!: Style;
22+
private _config!: SidenavType;
23+
isChecked = false;
24+
25+
constructor(private readonly channel: Channel) {}
26+
27+
ngOnChanges() {
28+
this._config = SidenavType.deserializeBinary(
29+
this.type.getValue() as unknown as Uint8Array,
30+
);
31+
}
32+
33+
config(): SidenavType {
34+
return this._config;
35+
}
36+
37+
getStyle(): string {
38+
return formatStyle(this.style);
39+
}
40+
}

mesop/example_index.py

+1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@
2424
import mesop.components.image.e2e as image_e2e
2525
import mesop.components.audio.e2e as audio_e2e
2626
import mesop.components.video.e2e as video_e2e
27+
import mesop.components.sidenav.e2e as sidenav_e2e
2728
# REF(//scripts/scaffold_component.py):insert_component_e2e_import_export

mesop/examples/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ py_library(
1414
srcs = glob(["*.py"]),
1515
deps = [
1616
# REF(//scripts/scaffold_component.py):insert_component_e2e_import
17+
"//mesop/components/sidenav/e2e",
1718
"//mesop/components/video/e2e",
1819
"//mesop/components/audio/e2e",
1920
"//mesop/components/image/e2e",

mesop/web/src/component_renderer/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ng_module(
1414
]) + ["component_renderer.css"],
1515
deps = [
1616
# REF(//scripts/scaffold_component.py):insert_component_import
17+
"//mesop/components/sidenav:ng",
1718
"//mesop/components/video:ng",
1819
"//mesop/components/audio:ng",
1920
"//mesop/components/image:ng",

mesop/web/src/component_renderer/type_to_component.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {SidenavComponent} from '../../../components/sidenav/sidenav';
12
import {VideoComponent} from '../../../components/video/video';
23
import {AudioComponent} from '../../../components/audio/audio';
34
import {ImageComponent} from '../../../components/image/image';
@@ -49,6 +50,7 @@ export class UserDefinedComponent implements BaseComponent {
4950
}
5051

5152
export const typeToComponent = {
53+
'sidenav': SidenavComponent,
5254
'video': VideoComponent,
5355
'audio': AudioComponent,
5456
'image': ImageComponent,

mesop/web/src/shell/shell.ng.html

+5-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
mode="query"
66
></mat-progress-bar>
77
</div>
8-
<ng-container *ngIf="rootComponent != null">
9-
<component-renderer [component]="rootComponent"></component-renderer>
10-
</ng-container>
8+
<mat-sidenav-container style="height: 100%">
9+
<ng-container *ngIf="rootComponent != null">
10+
<component-renderer [component]="rootComponent"></component-renderer>
11+
</ng-container>
12+
</mat-sidenav-container>

mesop/web/src/shell/shell.scss

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
:host {
2+
display: block;
3+
height: 100%;
4+
}
5+
16
.status {
27
position: fixed;
38
width: 100%;

mesop/web/src/shell/shell.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {provideAnimations} from '@angular/platform-browser/animations';
1515
import {bootstrapApplication} from '@angular/platform-browser';
1616
import {MatIconModule, MatIconRegistry} from '@angular/material/icon';
1717
import {EditorService} from '../services/editor_service';
18+
import {MatSidenavModule} from '@angular/material/sidenav';
1819

1920
@Component({
2021
selector: 'mesop-shell',
@@ -25,6 +26,7 @@ import {EditorService} from '../services/editor_service';
2526
ComponentRenderer,
2627
MatProgressBarModule,
2728
MatIconModule,
29+
MatSidenavModule,
2830
],
2931
styleUrl: 'shell.css',
3032
})

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ nav:
1717
- Text I/O: components/text_io.md
1818
- Layout:
1919
- Box: components/box.md
20+
- Sidenav: components/sidenav.md
2021
- Text:
2122
- Text: components/text.md
2223
- Markdown: components/markdown.md

0 commit comments

Comments
 (0)