Skip to content
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
34 changes: 34 additions & 0 deletions docs/library/data-display/moment.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,40 @@ rx.vstack(
)
```

### Shared defaults

Wrap components in `rx.moment_provider` to give them a shared `locale`,
`format`, `timezone`, `local`, `parse`, `element`, `filters`, or `renderers`
default. A prop set on a component overrides the provider. Components outside
the provider keep English. Set `pool=True` to update all descendants from one
timer that ticks every `interval` milliseconds.

```python demo exec
def moment_provider_example():
return rx.moment_provider(
rx.moment("2024-03-14"),
rx.moment("2024-03-14", locale="en"),
locale="fr",
format="D MMM YYYY",
timezone="Europe/Paris",
)
```

### Additional react-moment 2.x options

`rx.moment` also supports the `utc`, `calendar`, `from_`, `to`, `ago`, `element`,
`fallback`, `filters`, and `renderers` options from `react-moment` 2.x. In Python,
`from_` maps to the JavaScript `from` prop. `filters` and `renderers` accept
JavaScript function values:

```python
rx.moment(
"2024-03-14",
format="MMMM",
filters=[rx.Var("(content) => content.toUpperCase()")],
)
```

### Timezones

You can also set dates to display in a specific timezone:
Expand Down
1 change: 1 addition & 0 deletions news/+moment-provider.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expose `rx.moment_provider` for sharing locale, timezone, formatting, and timer defaults across Moment components.
1 change: 1 addition & 0 deletions packages/reflex-components-moment/news/+7100.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expose the `utc`, `calendar`, `from_`, `to`, `ago`, `element`, `fallback`, `filters`, and `renderers` options of `react-moment` 2.x, which previously rendered as CSS, and add `moment_provider` for shared defaults and pooled timers.
Comment thread
FarhanAliRaza marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Moment.js component."""

from .moment import Moment, MomentDelta
from .moment import Moment, MomentProvider
from .moment import MomentDelta as MomentDelta

moment = Moment.create
moment_provider = MomentProvider.create
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@

import dataclasses
import datetime

from reflex_base.components.component import MemoizationLeaf, NoSSRComponent, field
from typing import Any

from reflex_base.components.component import (
Component,
MemoizationLeaf,
NoSSRComponent,
field,
)
from reflex_base.event import EventHandler, passthrough_event_spec
from reflex_base.utils.imports import ImportDict, ImportVar
from reflex_base.vars.base import LiteralVar, Var
Expand All @@ -26,6 +32,100 @@ class MomentDelta:
milliseconds: int | None = dataclasses.field(default=None)


def _locale_module(locale: Var[str] | None) -> str | None:
"""Return the moment locale module that a locale prop requires.

Args:
locale: The locale prop, if set.

Returns:
The locale module, or None when the built-in English locale suffices.
"""
if isinstance(locale, LiteralVar):
if locale._var_value == "en":
return None
return f"moment/locale/{locale._var_value}"
return None if locale is None else "moment/min/locales"


def _locale_timezone_imports(
locale: Var[str] | None, timezone: Var[str] | None
) -> ImportDict:
"""Return the locale and timezone imports required by Moment props.

Args:
locale: The locale prop, if set.
timezone: The timezone prop, if set.

Returns:
The locale and timezone module imports, plus moment itself for
_restore_global_locale when a locale module is imported.
"""
imports: ImportDict = {}
if (module := _locale_module(locale)) is not None:
imports[""] = module
imports["moment@2.31.0"] = ImportVar(tag="moment", is_default=True)
if timezone is not None:
imports["moment-timezone@0.6.5"] = ""
return imports


def _restore_global_locale(locale: Var[str] | None) -> list[str]:
"""Return code that resets moment's global locale after a locale import.

Importing a locale module also makes it moment's global locale, which
Moment components without a locale or provider use.

Args:
locale: The locale prop, if set.

Returns:
The reset code, or nothing when no locale module is imported.
"""
return [] if _locale_module(locale) is None else ['moment.locale("en");']


class MomentProvider(Component):
"""Provide shared defaults and timer pooling for descendant Moment components."""

tag: str | None = "MomentProvider"
library: str | None = "react-moment@2.0.2"
lib_dependencies: list[str] = ["moment@2.31.0"]

pool: Var[bool] = field(doc="Use one timer for all descendant Moment components.")
interval: Var[int] = field(doc="The interval in milliseconds for the pooled timer.")
locale: Var[str] = field(doc="The default locale for descendant Moment components.")
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved
local: Var[bool] = field(doc="Display descendant dates in local time by default.")
format: Var[str] = field(doc="The default date format for descendants.")
parse: Var[str | list[str]] = field(
doc="The default date parse format for descendants."
)
filters: Var[list[Var[Any]]] = field(
doc="JavaScript content filters applied to descendant Moment components."
)
renderers: Var[list[Var[Any]]] = field(
doc="JavaScript content renderers tried for descendant Moment components."
)
element: Var[str] = field(doc="The default HTML element for descendant components.")
timezone: Var[str] = field(doc="The default timezone for descendant components.")

def add_imports(self) -> ImportDict:
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
"""Import the locale and timezone modules required by the defaults.

Returns:
The locale and timezone module imports.
"""
return _locale_timezone_imports(self.locale, self.timezone)

def add_custom_code(self) -> list[str]:
"""Keep moment's global locale English after the locale import.

Returns:
The global locale reset, if a locale module is imported.
"""
return _restore_global_locale(self.locale)


class Moment(NoSSRComponent, MemoizationLeaf):
"""The Moment component."""

Expand All @@ -50,6 +150,40 @@ class Moment(NoSSRComponent, MemoizationLeaf):
doc=" Use the parse attribute to tell moment how to parse the given date when non-standard. Accepts a single format string or a list of formats to try."
)

utc: Var[bool] = field(doc="Parses and displays the date in UTC.")

calendar: Var[bool | dict[str, str]] = field(
doc="Displays the date using Moment's calendar format, optionally with custom calendar strings."
)

from_: Var[
str | datetime.datetime | datetime.date | datetime.time | datetime.timedelta
] = field(doc="Displays the interval from this date to the component's date.")

to: Var[
str | datetime.datetime | datetime.date | datetime.time | datetime.timedelta
] = field(doc="Displays the interval from the component's date to this date.")

ago: Var[bool] = field(
doc='Omits the "ago" or "in" suffix from relative-time output.'
)

element: Var[str] = field(
doc="The HTML element used to render the formatted date (defaults to time)."
)

fallback: Var[str | int] = field(
doc="Content to render when the date input is invalid."
)

filters: Var[list[Var[Any]]] = field(
doc="JavaScript functions applied in order to the rendered content."
)

renderers: Var[list[Var[Any]]] = field(
doc="JavaScript renderer functions tried before the built-in output modes."
)

add: Var[MomentDelta] = field(
doc='Add a delta to the base date (keys are "years", "quarters", "months", "weeks", "days", "hours", "minutes", "seconds")'
)
Expand Down Expand Up @@ -111,8 +245,7 @@ class Moment(NoSSRComponent, MemoizationLeaf):
tz: Var[str] = field(doc="Display the date in the given timezone.")

locale: Var[str] = field(
default=Var.create("en"),
doc="The locale for this component. Defaults to English independently of other Moment components.",
doc="The locale for this component. Defaults to the nearest rx.moment_provider locale, or English.",
)
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved

on_change: EventHandler[passthrough_event_spec(str)] = field(
Expand All @@ -125,19 +258,16 @@ def add_imports(self) -> ImportDict:
Returns:
The import dict for the component.
"""
imports = {}

if isinstance(self.locale, LiteralVar):
# English is built into Moment and has no separate locale module.
if self.locale._var_value != "en":
imports[""] = f"moment/locale/{self.locale._var_value}"
elif self.locale is not None:
# If the user is using a variable for the locale, we can't know the
# value at compile time so import all locales available.
imports[""] = "moment/min/locales"
if self.tz is not None:
imports["moment-timezone@0.6.5"] = ""
imports = _locale_timezone_imports(self.locale, self.tz)
if self.duration is not None or self.duration_from_now is not None:
imports["moment-duration-format@2.3.2"] = ImportVar(tag=None)

return imports

def add_custom_code(self) -> list[str]:
"""Keep moment's global locale English after the locale import.

Returns:
The global locale reset, if a locale module is imported.
"""
return _restore_global_locale(self.locale)
4 changes: 2 additions & 2 deletions pyi_hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "2ce1c076ecf5c2fa4945b4abdbf2f91d",
"packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "1e331a3d6420b97e5b1ce7f63ad53de8",
"packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "6759daf6e242e209d9298045c7b4456a",
"packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "13e39f8d2b46bfd76c50cb04062da8ff",
"packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "21c3396cb28b1ef6442c5a9588dc708b",
"packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "f411341e59a3065fee964747584f9fcf",
"packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "a77352f60fb6f4135b5d08a6e56efa6d",
"packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "bbd4d1a4fa73275a882c33ba485d0165",
Expand Down Expand Up @@ -118,7 +118,7 @@
"packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "99ebcfc07868061bdc3c2010d85a153f",
"packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "f2a9cf6db58d169289fbc76de6813300",
"packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f170ac685b6ba5892370166c80684db3",
"reflex/__init__.pyi": "1c841ad90d789507cd56d966b474d11c",
"reflex/__init__.pyi": "23c090b0294d3a91e59f447f36a7a37f",
"reflex/components/__init__.pyi": "9facd05a776d0641432696bbf8e34388",
"reflex/experimental/memo.pyi": "87e485038dc79f66b2ab15144845ef1e"
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ dependencies = [
"reflex-components-gridjs >= 0.10.0",
"reflex-components-lucide >= 1.1.0",
"reflex-components-markdown >= 0.10.0",
"reflex-components-moment >= 0.10.0",
"reflex-components-moment >= 0.10.1.dev0",
"reflex-components-plotly >= 0.10.0",
"reflex-components-radix >= 0.10.1.dev0",
"reflex-components-react-player >= 0.10.0",
Expand Down
2 changes: 1 addition & 1 deletion reflex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
"reflex_base.components.props": ["PropsBase"],
"reflex_components_core.datadisplay.logo": ["logo"],
"reflex_components_gridjs": ["data_table"],
"reflex_components_moment": ["MomentDelta", "moment"],
"reflex_components_moment": ["MomentDelta", "moment", "moment_provider"],
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved
}

_COMPONENT_NAME_TO_PATH: dict[str, str] = {
Expand Down
Loading
Loading