Skip to content

Commit

Permalink
Add date picker and date range picker components. (#982)
Browse files Browse the repository at this point in the history
  • Loading branch information
richard-to authored Sep 28, 2024
1 parent 3631e14 commit 429fa93
Show file tree
Hide file tree
Showing 36 changed files with 1,042 additions and 7 deletions.
52 changes: 52 additions & 0 deletions demo/date_picker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from dataclasses import field
from datetime import date

import mesop as me


@me.stateclass
class State:
picked_date: date | None = field(default_factory=lambda: date(2024, 10, 1))


def on_load(e: me.LoadEvent):
me.set_theme_mode("system")


@me.page(path="/date_picker", on_load=on_load)
def app():
state = me.state(State)
with me.box(
style=me.Style(
display="flex",
flex_direction="column",
gap=15,
padding=me.Padding.all(15),
)
):
me.date_picker(
label="Date",
disabled=False,
placeholder="9/1/2024",
required=True,
value=state.picked_date,
readonly=False,
hide_required_marker=False,
color="accent",
float_label="always",
appearance="outline",
on_change=on_date_change,
)

me.text("Selected date: " + _render_date(state.picked_date))


def on_date_change(e: me.DatePickerChangeEvent):
state = me.state(State)
state.picked_date = e.date


def _render_date(maybe_date: date | None) -> str:
if maybe_date:
return maybe_date.strftime("%Y-%m-%d")
return "None"
61 changes: 61 additions & 0 deletions demo/date_range_picker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from dataclasses import field
from datetime import date

import mesop as me


@me.stateclass
class State:
picked_start_date: date | None = field(
default_factory=lambda: date(2024, 10, 1)
)
picked_end_date: date | None = field(
default_factory=lambda: date(2024, 11, 1)
)


def on_load(e: me.LoadEvent):
me.set_theme_mode("system")


@me.page(path="/date_range_picker", on_load=on_load)
def app():
state = me.state(State)
with me.box(
style=me.Style(
display="flex",
flex_direction="column",
gap=15,
padding=me.Padding.all(15),
)
):
me.date_range_picker(
label="Date Range",
disabled=False,
placeholder_start_date="9/1/2024",
placeholder_end_date="10/1/2024",
required=True,
start_date=state.picked_start_date,
end_date=state.picked_end_date,
readonly=False,
hide_required_marker=False,
color="accent",
float_label="always",
appearance="outline",
on_change=on_date_range_change,
)

me.text("Start date: " + _render_date(state.picked_start_date))
me.text("End date: " + _render_date(state.picked_end_date))


def on_date_range_change(e: me.DateRangePickerChangeEvent):
state = me.state(State)
state.picked_start_date = e.start_date
state.picked_end_date = e.end_date


def _render_date(maybe_date: date | None) -> str:
if maybe_date:
return maybe_date.strftime("%Y-%m-%d")
return "None"
4 changes: 4 additions & 0 deletions demo/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
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 date_picker as date_picker
import date_range_picker as date_range_picker
import density as density
import dialog as dialog
import divider as divider
Expand Down Expand Up @@ -155,6 +157,8 @@ class Section:
Example(name="autocomplete"),
Example(name="button"),
Example(name="checkbox"),
Example(name="date_picker"),
Example(name="date_range_picker"),
Example(name="input"),
Example(name="textarea"),
Example(name="radio"),
Expand Down
17 changes: 17 additions & 0 deletions docs/components/date_picker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Overview

Date picker allows the user to enter free text or select a date from a calendar widget.
and is based on the [Angular Material datapicker component](https://material.angular.io/components/datepicker/overview).

## Examples

<iframe class="component-demo" src="https://google.github.io/mesop/demo/?demo=date_picker" style="height: 200px"></iframe>

```python
--8<-- "demo/date_picker.py"
```

## API

::: mesop.components.datepicker.datepicker.date_picker
::: mesop.components.datepicker.datepicker.DatePickerChangeEvent
17 changes: 17 additions & 0 deletions docs/components/date_range_picker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Overview

Date range picker allows the user to enter free text or select a dates from a calendar widget.
and is based on the [Angular Material datapicker component](https://material.angular.io/components/datepicker/overview).

## Examples

<iframe class="component-demo" src="https://google.github.io/mesop/demo/?demo=date_range_picker" style="height: 200px"></iframe>

```python
--8<-- "demo/date_range_picker.py"
```

## API

::: mesop.components.date_range_picker.date_range_picker.date_range_picker
::: mesop.components.date_range_picker.date_range_picker.DateRangePickerChangeEvent
2 changes: 2 additions & 0 deletions mesop/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ py_library(
deps = [
":version",
# REF(//scripts/scaffold_component.py):insert_component_import
"//mesop/components/date_range_picker:py",
"//mesop/components/datepicker:py",
"//mesop/components/autocomplete:py",
"//mesop/components/link:py",
"//mesop/components/html:py",
Expand Down
10 changes: 10 additions & 0 deletions mesop/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@
content_checkbox as content_checkbox,
)
from mesop.components.code.code import code as code
from mesop.components.date_range_picker.date_range_picker import (
DateRangePickerChangeEvent as DateRangePickerChangeEvent,
)
from mesop.components.date_range_picker.date_range_picker import (
date_range_picker as date_range_picker,
)
from mesop.components.datepicker.datepicker import (
DatePickerChangeEvent as DatePickerChangeEvent,
)
from mesop.components.datepicker.datepicker import date_picker as date_picker
from mesop.components.divider.divider import divider as divider
from mesop.components.embed.embed import embed as embed
from mesop.components.html.html import html as html
Expand Down
9 changes: 9 additions & 0 deletions mesop/components/date_range_picker/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
load("//mesop/components:defs.bzl", "mesop_component")

package(
default_visibility = ["//build_defs:mesop_internal"],
)

mesop_component(
name = "date_range_picker",
)
Empty file.
36 changes: 36 additions & 0 deletions mesop/components/date_range_picker/date_range_picker.ng.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<mat-form-field
[hideRequiredMarker]="config().getHideRequiredMarker()!"
[color]="getColor()"
[floatLabel]="getFloatLabel()"
[appearance]="getAppearance()"
[subscriptSizing]="getSubscriptSizing()"
[style]="getStyle()"
>
<mat-label *ngIf="config().getLabel()">{{config().getLabel()}}</mat-label>
<mat-date-range-input
[formGroup]="dateRange"
[rangePicker]="dp"
[required]="config().getRequired()!"
[disabled]="config().getDisabled()!"
>
<input
matStartDate
formControlName="start"
[readonly]="config().getReadonly()!"
[placeholder]="config().getPlaceholderStartDate()!"
(dateChange)="onChange($event)"
/>
<input
matEndDate
formControlName="end"
[readonly]="config().getReadonly()!"
[placeholder]="config().getPlaceholderEndDate()!"
(dateChange)="onChange($event)"
/>
</mat-date-range-input>
<mat-hint *ngIf="config().getHintLabel()">
{{ config().getHintLabel() }}
</mat-hint>
<mat-datepicker-toggle matIconSuffix [for]="dp"></mat-datepicker-toggle>
<mat-date-range-picker #dp></mat-date-range-picker>
</mat-form-field>
22 changes: 22 additions & 0 deletions mesop/components/date_range_picker/date_range_picker.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
syntax = "proto2";

package mesop.components.date_range_picker;

// Next ID: 16
message DateRangePickerType {
optional string start_date = 1;
optional string end_date = 2;
optional string placeholder_start_date = 3;
optional string placeholder_end_date = 4;
optional bool disabled = 5;
optional bool required = 6;
optional bool readonly = 7;
optional bool hide_required_marker = 8;
optional string color = 9;
optional string float_label = 10;
optional string appearance = 11;
optional string subscript_sizing = 12;
optional string hint_label = 13;
optional string label = 14;
optional string on_change_handler_id = 15;
}
122 changes: 122 additions & 0 deletions mesop/components/date_range_picker/date_range_picker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from dataclasses import dataclass
from datetime import date, datetime
from typing import Any, Callable, Literal

import mesop.components.date_range_picker.date_range_picker_pb2 as date_range_picker_pb
from mesop.component_helpers import (
Style,
insert_component,
register_event_handler,
register_event_mapper,
register_native_component,
)
from mesop.events import MesopEvent

_DATE_FORMAT = "%Y-%m-%d"


@dataclass(kw_only=True)
class DateRangePickerChangeEvent(MesopEvent):
"""Represents a date range picker change event.
This event will only fire if start and end dates are valid dates.
Attributes:
start_date: Start date
end_date: End date
key (str): key of the component that emitted this event.
"""

start_date: date
end_date: date


register_event_mapper(
DateRangePickerChangeEvent,
lambda userEvent, key: DateRangePickerChangeEvent(
start_date=_try_make_date(userEvent.date_range_picker_event.start_date),
end_date=_try_make_date(userEvent.date_range_picker_event.end_date),
key=key.key,
),
)


@register_native_component
def date_range_picker(
*,
label: str = "",
on_change: Callable[[DateRangePickerChangeEvent], Any] | None = None,
appearance: Literal["fill", "outline"] = "fill",
style: Style | None = None,
disabled: bool = False,
placeholder_start_date: str = "",
placeholder_end_date: str = "",
required: bool = False,
start_date: date | None = None,
end_date: date | None = None,
readonly: bool = False,
hide_required_marker: bool = False,
color: Literal["primary", "accent", "warn"] = "primary",
float_label: Literal["always", "auto"] = "auto",
subscript_sizing: Literal["fixed", "dynamic"] = "fixed",
hint_label: str = "",
key: str | None = None,
):
"""Creates a date range picker component.
Args:
label: Label for date range picker input.
on_change: Fires when valid date values for both start/end have been specified through Calendar date selection or user input blur.
appearance: The form field appearance style.
style: Style for date range picker input.
disabled: Whether it's disabled.
placeholder_start_date: Start date placeholder value.
placeholder_end_date: End date placeholder value.
required: Whether it's required.
start_date: Start date initial value.
end_date: End date initial value.
readonly: Whether the element is readonly.
hide_required_marker: Whether the required marker should be hidden.
color: The color palette for the form field.
float_label: Whether the label should always float or float as the user types.
subscript_sizing: Whether the form field should reserve space for one line of hint/error text (default) or to have the spacing grow from 0px as needed based on the size of the hint/error content. Note that when using dynamic sizing, layout shifts will occur when hint/error text changes.
hint_label: Text for the form field hint.
key: The component [key](../components/index.md#component-key).
"""

insert_component(
key=key,
type_name="date_range_picker",
proto=date_range_picker_pb.DateRangePickerType(
start_date=_try_make_date_str(start_date),
end_date=_try_make_date_str(end_date),
placeholder_start_date=placeholder_start_date,
placeholder_end_date=placeholder_end_date,
disabled=disabled,
required=required,
readonly=readonly,
hide_required_marker=hide_required_marker,
color=color,
float_label=float_label,
appearance=appearance,
subscript_sizing=subscript_sizing,
hint_label=hint_label,
label=label,
on_change_handler_id=register_event_handler(
on_change, event=DateRangePickerChangeEvent
)
if on_change
else "",
),
style=style,
)


def _try_make_date_str(date_input: date | None) -> str:
if date_input:
return date_input.isoformat()
return ""


def _try_make_date(date_string: str) -> date:
return datetime.strptime(date_string, _DATE_FORMAT).date()
Loading

0 comments on commit 429fa93

Please sign in to comment.