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

Make Material density configurable #716

Merged
merged 3 commits into from
Aug 2, 2024
Merged
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
38 changes: 38 additions & 0 deletions demo/density.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import mesop as me


def select_density(e: me.SelectSelectionChangeEvent):
me.set_theme_density(int(e.value)) # type: ignore


@me.page(
security_policy=me.SecurityPolicy(
allowed_iframe_parents=["https://google.github.io"]
),
path="/density",
)
def main():
me.select(
label="Density",
options=[
me.SelectOption(label="0 (least dense)", value="0"),
me.SelectOption(label="-1", value="-1"),
me.SelectOption(label="-2", value="-2"),
me.SelectOption(label="-3", value="-3"),
me.SelectOption(label="-4 (most dense)", value="-4"),
],
on_selection_change=select_density,
)
me.text("Button types:", style=me.Style(margin=me.Margin(bottom=12)))
with me.box(style=me.Style(display="flex", flex_direction="row", gap=12)):
me.button("default")
me.button("raised", type="raised")
me.button("flat", type="flat")
me.button("stroked", type="stroked")

me.text("Button colors:", style=me.Style(margin=me.Margin(bottom=12)))
with me.box(style=me.Style(display="flex", flex_direction="row", gap=12)):
me.button("default", type="flat")
me.button("primary", color="primary", type="flat")
me.button("secondary", color="accent", type="flat")
me.button("warn", color="warn", type="flat")
7 changes: 7 additions & 0 deletions demo/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import chat_inputs as chat_inputs
import checkbox as checkbox
import code_demo as code_demo # cannot call it code due to python library naming conflict
import density as density
import dialog as dialog
import divider as divider
import embed as embed
Expand Down Expand Up @@ -103,6 +104,12 @@ class Section:
Example(name="chat_inputs"),
],
),
Section(
name="Features",
examples=[
Example(name="density"),
],
),
Section(
name="Misc",
examples=[
Expand Down
23 changes: 22 additions & 1 deletion docs/guides/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Mesop has early-stage support for theming so you can support light theme and dark theme in a Mesop application.

## Example
## Dark Theming

For an actual example of using Mesop's theming API to support light theme and dark theme, we will look at the labs [chat component](../components/chat.md) which itself is written all in Python built on top of lower-level Mesop components.

Expand Down Expand Up @@ -61,8 +61,29 @@ def on_load(e: me.LoadEvent):
me.set_theme_mode("system")
```

## Theme Density

You can set the visual density of the Material components. By default, Mesop uses the least visually dense setting, i.e.

```py
me.set_theme_density(0) # 0 is the least dense
```

You can configure the density as an integer from 0 (least dense) to -4 (most dense). For example, if you want a medium-dense UI, you can do the following:

```py
def on_load(e: me.LoadEvent):
me.set_theme_density(-2) # -2 is more dense than the default


@me.page(on_load=on_load)
def page():
...
```

## API

::: mesop.features.theme.set_theme_density
::: mesop.features.theme.set_theme_mode
::: mesop.features.theme.theme_brightness
::: mesop.features.theme.theme_var
Expand Down
1 change: 1 addition & 0 deletions mesop/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
MesopUserException as MesopUserException,
)
from mesop.features import page as page
from mesop.features.theme import set_theme_density as set_theme_density
from mesop.features.theme import set_theme_mode as set_theme_mode
from mesop.features.theme import theme_brightness as theme_brightness
from mesop.features.theme import theme_var as theme_var
Expand Down
22 changes: 22 additions & 0 deletions mesop/examples/e2e/theme_density_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {test, expect} from '@playwright/test';

test('theme density', async ({page}) => {
await page.goto('/testing/theme_density');

// Open select dropdown:
await page.locator('div').filter({hasText: 'Density'}).first().click();
// Select -1 option:
await page.getByRole('option', {name: '-1'}).click();
expect(await page.evaluate(hasThemeDensity, -1)).toEqual(true);

// Open select dropdown:
await page.locator('div').filter({hasText: 'Density-'}).first().click();
// Select -3 option:
await page.getByRole('option', {name: '-3'}).click();
expect(await page.evaluate(hasThemeDensity, -1)).toEqual(false);
expect(await page.evaluate(hasThemeDensity, -3)).toEqual(true);
});

function hasThemeDensity(density: number) {
return document.body.classList.contains('theme-density-' + density);
}
3 changes: 3 additions & 0 deletions mesop/examples/testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@
from mesop.examples.testing import (
theme as theme,
)
from mesop.examples.testing import (
theme_density as theme_density,
)
23 changes: 23 additions & 0 deletions mesop/examples/testing/theme_density.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import mesop as me


def select_density(e: me.SelectSelectionChangeEvent):
me.set_theme_density(int(e.value)) # type: ignore


@me.page(
path="/testing/theme_density",
)
def main():
me.select(
label="Density",
options=[
me.SelectOption(label="0 (least dense)", value="0"),
me.SelectOption(label="-1", value="-1"),
me.SelectOption(label="-2", value="-2"),
me.SelectOption(label="-3", value="-3"),
me.SelectOption(label="-4 (most dense)", value="-4"),
],
on_selection_change=select_density,
)
me.button("button", type="flat")
12 changes: 12 additions & 0 deletions mesop/features/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
ThemeBrightness = Literal["light", "dark"]


def set_theme_density(density: Literal[0, -1, -2, -3, -4]) -> None:
"""
Sets the theme density for the Material components in the application.
A higher density (more negative value) results in a more compact UI layout.

Args:
density: The desired theme density. It can be 0 (least dense),
-1, -2, -3, or -4 (most dense).
"""
runtime().context().set_theme_density(density)


def set_theme_mode(theme_mode: ThemeMode) -> None:
"""
Sets the theme mode for the application.
Expand Down
5 changes: 5 additions & 0 deletions mesop/protos/ui.proto
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ message Command {
NavigateCommand navigate = 1;
ScrollIntoViewCommand scroll_into_view = 2;
SetThemeMode set_theme_mode = 3;
SetThemeDensity set_theme_density = 4;
}
}

Expand All @@ -174,6 +175,10 @@ message SetThemeMode {
optional ThemeMode theme_mode = 1;
}

message SetThemeDensity {
optional int32 density =1;
}

message States {
repeated State states = 1;
}
Expand Down
5 changes: 5 additions & 0 deletions mesop/runtime/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ def scroll_into_view(self, key: str) -> None:
pb.Command(scroll_into_view=pb.ScrollIntoViewCommand(key=key))
)

def set_theme_density(self, density: int) -> None:
self._commands.append(
pb.Command(set_theme_density=pb.SetThemeDensity(density=density))
)

def set_theme_mode(self, theme_mode: pb.ThemeMode.ValueType) -> None:
self._commands.append(
pb.Command(set_theme_mode=pb.SetThemeMode(theme_mode=theme_mode))
Expand Down
37 changes: 27 additions & 10 deletions mesop/web/src/app/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,23 @@ $_tertiary: map.merge(map.get($_palettes, tertiary), $_rest);
$primary: $_primary;
$tertiary: $_tertiary;

$light-theme: mat.define-theme(
(
color: (
theme-type: light,
primary: $_primary,
tertiary: $_tertiary,
use-system-variables: true,
),
)
);
@function create-theme($density: 0) {
@return mat.define-theme(
(
color: (
theme-type: light,
primary: $_primary,
tertiary: $_tertiary,
use-system-variables: true,
),
density: (
scale: $density,
),
)
);
}

$light-theme: create-theme();
$dark-theme: mat.define-theme(
(
color: (
Expand All @@ -174,6 +181,16 @@ body.dark-theme {
@include mat.system-level-colors($dark-theme);
}

$density-scales: (-1, -2, -3, -4);
@each $scale in $density-scales {
.theme-density-#{$scale} {
$density-theme: create-theme(
$density: $scale,
);
@include mat.all-component-densities($density-theme);
}
}

html,
body {
--default-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Expand Down
10 changes: 10 additions & 0 deletions mesop/web/src/services/theme_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class ThemeService {
// of proto enum differs between upstream and downstream.
mode = DEFAULT_THEME_MODE;
onChangePrefersColorScheme!: () => void;
previousDensityClass: string | undefined;

constructor() {
this.prefersDarkColorSchemeMediaQuery.addEventListener('change', (e) => {
Expand All @@ -48,6 +49,15 @@ export class ThemeService {
this.updateTheme();
}

setDensity(density: number) {
const densityClass = `theme-density-${density}`;
if (this.previousDensityClass) {
document.body.classList.remove(this.previousDensityClass);
}
document.body.classList.add(densityClass);
this.previousDensityClass = densityClass;
}

private updateTheme(): void {
if (this.isUsingDarkTheme()) {
document.body.classList.add('dark-theme');
Expand Down
7 changes: 7 additions & 0 deletions mesop/web/src/shell/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ export class Shell {
throw new Error('Theme mode undefined in setThemeMode command');
}
this.themeService.setThemeMode(themeMode);
} else if (command.hasSetThemeDensity()) {
const setThemeDensity = command.getSetThemeDensity()!;
const density = setThemeDensity.getDensity();
if (density == null) {
throw new Error('Density undefined in setThemeDensity command');
}
this.themeService.setDensity(density);
} else {
throw new Error(
'Unhandled command: ' + command.getCommandCase().toString(),
Expand Down
Loading