reflex-schedule-x 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reflex_schedule_x-0.1.0/LICENSE +21 -0
- reflex_schedule_x-0.1.0/PKG-INFO +289 -0
- reflex_schedule_x-0.1.0/README.md +257 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x/__init__.py +73 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x/helpers.py +332 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x/py.typed +0 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x/schedule_x.jsx +1028 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x/schedule_x.py +494 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x/schedule_x.pyi +824 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x.egg-info/PKG-INFO +289 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x.egg-info/SOURCES.txt +16 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x.egg-info/dependency_links.txt +1 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x.egg-info/requires.txt +8 -0
- reflex_schedule_x-0.1.0/custom_components/reflex_schedule_x.egg-info/top_level.txt +1 -0
- reflex_schedule_x-0.1.0/pyproject.toml +73 -0
- reflex_schedule_x-0.1.0/setup.cfg +4 -0
- reflex_schedule_x-0.1.0/tests/test_components.py +117 -0
- reflex_schedule_x-0.1.0/tests/test_helpers.py +153 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ernesto Crespo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reflex-schedule-x
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Schedule-X event calendar for Reflex: day, week, month and list views, plugins, custom components, date and time pickers.
|
|
5
|
+
Author-email: Ernesto Crespo <ecrespo@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ecrespo/reflex-schedule-x
|
|
8
|
+
Project-URL: Source, https://github.com/ecrespo/reflex-schedule-x
|
|
9
|
+
Project-URL: Issues, https://github.com/ecrespo/reflex-schedule-x/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/ecrespo/reflex-schedule-x/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Schedule-X, https://schedule-x.dev
|
|
12
|
+
Keywords: reflex,reflex-custom-components,schedule-x,calendar,event-calendar,scheduler,date-picker,time-picker
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: reflex>=0.9.11
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build; extra == "dev"
|
|
27
|
+
Requires-Dist: twine; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=5; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# reflex-schedule-x
|
|
34
|
+
|
|
35
|
+
[](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/quality.yml)
|
|
36
|
+
[](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/security.yml)
|
|
37
|
+
[](https://pypi.org/project/reflex-schedule-x/)
|
|
38
|
+
[](https://pypi.org/project/reflex-schedule-x/)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
|
|
41
|
+
[Schedule-X](https://schedule-x.dev) event calendar for [Reflex](https://reflex.dev) — a modern alternative to
|
|
42
|
+
FullCalendar and react-big-calendar, driven entirely from Python.
|
|
43
|
+
|
|
44
|
+
- **6 views**: day, week, month grid, month agenda, week agenda and list.
|
|
45
|
+
- **Events in Reflex state**: pass a `list[dict]` with plain date strings; edits in Python update the running calendar.
|
|
46
|
+
- **Every callback** (`on_event_click`, `on_range_update`, `on_click_date_time`, `on_view_change`, ...) as typed Reflex event triggers.
|
|
47
|
+
- **Plugins**: events service, calendar controls, current time, event modal, scroll controller, event recurrence (RRULE / EXDATE), iCalendar import, timezone select.
|
|
48
|
+
- **Custom components**: fill any Schedule-X slot (`timeGridEvent`, `monthGridEvent`, `eventModal`, header slots, ...) with Reflex components.
|
|
49
|
+
- **i18n & timezones**: 37 locales, translation overrides, IANA timezones, dark mode, `default` and `shadcn` themes.
|
|
50
|
+
- **Standalone** date picker and time picker components.
|
|
51
|
+
- **Imperative API** (`ScheduleXAPI`) for `rx.call_script` driven control.
|
|
52
|
+
|
|
53
|
+
Bundles `@schedule-x/*` **4.8.0** (open-source packages). Premium plugins (drag & drop, resize, drag-to-create,
|
|
54
|
+
resource scheduler, ...) are not included.
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install reflex-schedule-x
|
|
60
|
+
# or
|
|
61
|
+
uv add reflex-schedule-x
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Requires `reflex>=0.9.11`. The npm packages are installed automatically by Reflex.
|
|
65
|
+
|
|
66
|
+
## Quick start
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import reflex as rx
|
|
70
|
+
from reflex_schedule_x import calendar_type, schedule_x
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class State(rx.State):
|
|
74
|
+
events: list[dict] = [
|
|
75
|
+
{
|
|
76
|
+
"id": "1",
|
|
77
|
+
"title": "Coffee with John",
|
|
78
|
+
"start": "2026-09-17 10:05",
|
|
79
|
+
"end": "2026-09-17 10:35",
|
|
80
|
+
"calendarId": "work",
|
|
81
|
+
},
|
|
82
|
+
{"id": "2", "title": "Ski trip", "start": "2026-09-19", "end": "2026-09-21"},
|
|
83
|
+
]
|
|
84
|
+
selected: dict = {}
|
|
85
|
+
|
|
86
|
+
@rx.event
|
|
87
|
+
def on_event_click(self, event: dict):
|
|
88
|
+
self.selected = event
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def index():
|
|
92
|
+
return schedule_x(
|
|
93
|
+
events=State.events,
|
|
94
|
+
calendars={"work": calendar_type("work", "#f91c45", "#ffd2dc", "#59000d", label="Work")},
|
|
95
|
+
views=["day", "week", "month-grid", "month-agenda"],
|
|
96
|
+
default_view="week",
|
|
97
|
+
timezone="America/Caracas",
|
|
98
|
+
locale="es-ES",
|
|
99
|
+
is_dark=rx.color_mode_cond(light=False, dark=True),
|
|
100
|
+
event_modal=True,
|
|
101
|
+
current_time_indicator=True,
|
|
102
|
+
on_event_click=State.on_event_click,
|
|
103
|
+
height="800px",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
app = rx.App()
|
|
108
|
+
app.add_page(index)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Dates and times
|
|
112
|
+
|
|
113
|
+
Schedule-X uses the Temporal API. The component converts strings for you:
|
|
114
|
+
|
|
115
|
+
| Value you pass | Meaning |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| `"2026-09-17"` | All-day date (`Temporal.PlainDate`) |
|
|
118
|
+
| `"2026-09-17 10:00"` or `"2026-09-17T10:00"` | Wall-clock time in the calendar `timezone` |
|
|
119
|
+
| `"2026-09-17T10:00:00-04:00"` / `"...Z"` | Absolute instant, shown in the calendar `timezone` |
|
|
120
|
+
| `"2026-09-17T10:00:00+02:00[Europe/Berlin]"` | RFC 9557 zoned date-time |
|
|
121
|
+
|
|
122
|
+
Payloads sent back to Python use `"YYYY-MM-DD"` for dates and `"YYYY-MM-DD HH:mm"` (calendar timezone) for
|
|
123
|
+
date-times, so they round-trip with your state. Set `datetime_format="iso"` to receive RFC 9557 strings instead.
|
|
124
|
+
|
|
125
|
+
Helpers: `to_sx_datetime(date | datetime)`, `to_sx_date(...)`, `parse_sx_datetime(str)`,
|
|
126
|
+
`calendar_event(...)`, `background_event(...)`, `calendar_type(...)`.
|
|
127
|
+
|
|
128
|
+
## `schedule_x` props
|
|
129
|
+
|
|
130
|
+
Props marked **live** update the running calendar; changing any other prop re-creates it.
|
|
131
|
+
|
|
132
|
+
| Prop | Type | Notes |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| `events` | `list[dict]` | **live**. `id`, `start`, `end`, `title`, `description`, `location`, `people`, `calendarId`, `rrule`, `exdate`, `_options`, `_customContent`, any custom key |
|
|
135
|
+
| `background_events` | `list[dict]` | **live**. `start`, `end`, `style`, `title`, `rrule`, `exdate` |
|
|
136
|
+
| `calendars` | `dict[str, dict]` | **live**. `colorName`, `label`, `lightColors`, `darkColors` |
|
|
137
|
+
| `views` | `list[str]` | `day`, `week`, `month-grid`, `month-agenda`, `week-agenda`, `list` |
|
|
138
|
+
| `default_view` | `str` | Initial view |
|
|
139
|
+
| `view` | `str` | **live**, controlled view (pair with `on_view_change`) |
|
|
140
|
+
| `selected_date` | `str` | **live**, `YYYY-MM-DD` |
|
|
141
|
+
| `min_date` / `max_date` | `str` | **live** |
|
|
142
|
+
| `locale` | `str` | **live**, see `reflex_schedule_x.LOCALES` |
|
|
143
|
+
| `timezone` | `str` | **live**, IANA name, default `UTC` |
|
|
144
|
+
| `first_day_of_week` | `int` | **live**, 1 = Monday … 7 = Sunday |
|
|
145
|
+
| `translations` | `dict` | Overrides merged over the built-in texts: `{"en-US": {"Week": "4 days"}}` |
|
|
146
|
+
| `day_boundaries` | `dict` | **live**, `{"start": "06:00", "end": "18:00"}` |
|
|
147
|
+
| `week_options` | `dict` | **live**, `gridHeight`, `nDays`, `eventWidth`, `gridStep`, `eventOverlap`, `timeAxisFormatOptions` |
|
|
148
|
+
| `month_grid_options` | `dict` | **live**, `{"nEventsPerDay": 4}` |
|
|
149
|
+
| `month_agenda_options` | `dict` | `{"nEventIndicatorsPerDay": 3}` |
|
|
150
|
+
| `show_week_numbers`, `is_responsive`, `skip_animations`, `skip_validation` | `bool` | |
|
|
151
|
+
| `small_breakpoint` | `int` | Width (px) under which small-screen views are used |
|
|
152
|
+
| `date_picker` | `dict` | Header date picker options |
|
|
153
|
+
| `theme` | `"default" \| "shadcn"` | Literal value; use one theme per app |
|
|
154
|
+
| `is_dark` | `bool` | **live** |
|
|
155
|
+
| `current_time_indicator`, `current_time_full_week_width` | `bool` | current-time plugin |
|
|
156
|
+
| `event_modal` | `bool` | event-modal plugin |
|
|
157
|
+
| `initial_scroll` / `scroll_to` | `str` | scroll-controller plugin; `scroll_to` is **live** |
|
|
158
|
+
| `recurrence` | `bool` | event-recurrence plugin |
|
|
159
|
+
| `ical_data` | `str` | iCalendar plugin (times are treated as UTC) |
|
|
160
|
+
| `timezone_select` | `bool` | timezone-select plugin |
|
|
161
|
+
| `datetime_format` | `"naive" \| "iso"` | Format of date-times in payloads |
|
|
162
|
+
| `id` | `str` | Enables `ScheduleXAPI(id)` |
|
|
163
|
+
|
|
164
|
+
The calendar defaults to `width="100%"`, `height="800px"`, `max_height="90vh"`; override with normal style props.
|
|
165
|
+
|
|
166
|
+
## Event triggers
|
|
167
|
+
|
|
168
|
+
| Trigger | Payload |
|
|
169
|
+
| --- | --- |
|
|
170
|
+
| `on_event_click`, `on_double_click_event`, `on_event_update` | event `dict` (plus `is_all_day`) |
|
|
171
|
+
| `on_range_update` | `{"start": str, "end": str}` |
|
|
172
|
+
| `on_calendar_render` | `{"view": str, "date": str, "range": {...}}` |
|
|
173
|
+
| `on_view_change` | view name |
|
|
174
|
+
| `on_selected_date_update`, `on_click_date`, `on_double_click_date`, `on_click_agenda_date`, `on_double_click_agenda_date`, `on_click_plus_events`, `on_scroll_day_into_view` | `"YYYY-MM-DD"` |
|
|
175
|
+
| `on_click_date_time`, `on_double_click_date_time` | `"YYYY-MM-DD HH:mm"` |
|
|
176
|
+
| `on_slot_action` | `{"action", "slot", "event", "date"}` |
|
|
177
|
+
| `on_error` | error message |
|
|
178
|
+
|
|
179
|
+
Lazy loading: load events in `on_range_update` (and `on_scroll_day_into_view` for the list view) and assign them to
|
|
180
|
+
the `events` state var.
|
|
181
|
+
|
|
182
|
+
## Custom components (slots)
|
|
183
|
+
|
|
184
|
+
Put `schedule_x_slot(name, *children)` components inside `schedule_x`. Slot content is normal Reflex UI, so it can use
|
|
185
|
+
state. Inside a slot:
|
|
186
|
+
|
|
187
|
+
- `schedule_x_field(name, format=None)` renders slot data. Event fields: `title`, `description`, `location`,
|
|
188
|
+
`people`, `start`, `end`, `calendarId`, custom keys; virtual fields `time_range`, `date_range`, `calendar_label`.
|
|
189
|
+
Other slots expose `date`, `day`, `hour`, `events`. Formats: `time`, `date`, `datetime`, `weekday`,
|
|
190
|
+
`weekday_short`, `day`, `month`, `hour`, `count`, `upper`, `json`.
|
|
191
|
+
- `schedule_x_show(name, *children, negate=False)` renders children when the field is truthy (`is_all_day` too).
|
|
192
|
+
- `schedule_x_event_card(*children, variant="container" | "main")` paints the event's calendar colors.
|
|
193
|
+
- `schedule_x_action(*children, action="delete", close_modal=True)` sends clicks to `on_slot_action`.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
schedule_x(
|
|
197
|
+
schedule_x_slot(
|
|
198
|
+
"timeGridEvent",
|
|
199
|
+
schedule_x_event_card(
|
|
200
|
+
schedule_x_field("title", tag_name="strong"),
|
|
201
|
+
schedule_x_field("time_range"),
|
|
202
|
+
schedule_x_show("location", schedule_x_field("location")),
|
|
203
|
+
),
|
|
204
|
+
),
|
|
205
|
+
schedule_x_slot(
|
|
206
|
+
"eventModal",
|
|
207
|
+
rx.card(
|
|
208
|
+
schedule_x_field("title", tag_name="h3"),
|
|
209
|
+
schedule_x_action(rx.button("Delete"), action="delete", close_modal=True),
|
|
210
|
+
),
|
|
211
|
+
),
|
|
212
|
+
schedule_x_slot("headerContentRightPrepend", rx.button("New", on_click=State.new_event)),
|
|
213
|
+
events=State.events,
|
|
214
|
+
event_modal=True,
|
|
215
|
+
on_slot_action=State.on_slot_action,
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Supported slots: `timeGridEvent`, `dateGridEvent`, `monthGridEvent`, `monthAgendaEvent`, `weekAgendaEvent`,
|
|
220
|
+
`monthAgendaDateDots`, `eventModal`, `headerContent`, `headerContentLeftPrepend`, `headerContentLeftAppend`,
|
|
221
|
+
`headerContentRightPrepend`, `headerContentRightAppend`, `weekGridDate`, `weekGridHour`, `monthGridDayName`,
|
|
222
|
+
`monthGridDate`.
|
|
223
|
+
|
|
224
|
+
## Imperative API
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from reflex_schedule_x import ScheduleXAPI
|
|
228
|
+
|
|
229
|
+
api = ScheduleXAPI("my-calendar") # schedule_x(id="my-calendar")
|
|
230
|
+
|
|
231
|
+
rx.button("Week", on_click=api.set_view("week"))
|
|
232
|
+
rx.button("Today", on_click=api.set_date(datetime.date.today()))
|
|
233
|
+
rx.button("Dump", on_click=api.get_events(callback=State.receive_events))
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Methods: `set_view`, `set_date`, `set_theme`, `add_event`, `update_event`, `remove_event`, `set_events`,
|
|
237
|
+
`close_event_modal`, `scroll_to`, `get_view`, `get_date`, `get_range`, `get_events`, `get_event`.
|
|
238
|
+
|
|
239
|
+
## Date and time pickers
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
schedule_x_date_picker(value=State.date, on_change=State.set_date, locale="es-ES", label="Fecha", min="2026-01-01")
|
|
243
|
+
schedule_x_time_picker(value=State.time, on_change=State.set_time, is_12_hour=True, label="Hora")
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Demo app
|
|
247
|
+
|
|
248
|
+
The `schedule_x_demo/` folder contains a multi-page demo: playground with every option, events CRUD and callback
|
|
249
|
+
log, custom components, recurrence + background events + iCal, language and timezones, imperative API with the list
|
|
250
|
+
view, and the pickers.
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
uv venv && uv pip install -e .
|
|
254
|
+
cd schedule_x_demo
|
|
255
|
+
uv pip install -r requirements.txt
|
|
256
|
+
uv run reflex run
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Development
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
uv sync --extra dev
|
|
263
|
+
uv run ruff check . && uv run ruff format --check .
|
|
264
|
+
uv run pytest --cov
|
|
265
|
+
uv run reflex component build # generates .pyi stubs and builds sdist + wheel
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Branches: work happens on `develop`; `main` only receives pull requests from `develop`. The `Quality` (ruff, pytest on
|
|
269
|
+
Python 3.10–3.13, build, stub freshness, demo bundle) and `Security` (CodeQL, Bandit, pip-audit, Gitleaks, dependency
|
|
270
|
+
review) workflows must pass before merging.
|
|
271
|
+
|
|
272
|
+
### Releasing
|
|
273
|
+
|
|
274
|
+
1. Bump `version` in `pyproject.toml`, update `CHANGELOG.md`, and merge `develop` into `main`.
|
|
275
|
+
2. Tag `main` and push the tag:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
git checkout main && git pull
|
|
279
|
+
git tag -a v0.1.0 -m "v0.1.0"
|
|
280
|
+
git push origin v0.1.0
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The `Release` workflow checks that the tag matches the version, runs the tests, builds the sdist and wheel, publishes
|
|
284
|
+
them to PyPI with Trusted Publishing (no API token) from the `pypi` environment, and creates the GitHub release with
|
|
285
|
+
the files attached.
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
MIT © Ernesto Crespo. Schedule-X is MIT licensed, © Tom Österlund.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# reflex-schedule-x
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/quality.yml)
|
|
4
|
+
[](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/security.yml)
|
|
5
|
+
[](https://pypi.org/project/reflex-schedule-x/)
|
|
6
|
+
[](https://pypi.org/project/reflex-schedule-x/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
[Schedule-X](https://schedule-x.dev) event calendar for [Reflex](https://reflex.dev) — a modern alternative to
|
|
10
|
+
FullCalendar and react-big-calendar, driven entirely from Python.
|
|
11
|
+
|
|
12
|
+
- **6 views**: day, week, month grid, month agenda, week agenda and list.
|
|
13
|
+
- **Events in Reflex state**: pass a `list[dict]` with plain date strings; edits in Python update the running calendar.
|
|
14
|
+
- **Every callback** (`on_event_click`, `on_range_update`, `on_click_date_time`, `on_view_change`, ...) as typed Reflex event triggers.
|
|
15
|
+
- **Plugins**: events service, calendar controls, current time, event modal, scroll controller, event recurrence (RRULE / EXDATE), iCalendar import, timezone select.
|
|
16
|
+
- **Custom components**: fill any Schedule-X slot (`timeGridEvent`, `monthGridEvent`, `eventModal`, header slots, ...) with Reflex components.
|
|
17
|
+
- **i18n & timezones**: 37 locales, translation overrides, IANA timezones, dark mode, `default` and `shadcn` themes.
|
|
18
|
+
- **Standalone** date picker and time picker components.
|
|
19
|
+
- **Imperative API** (`ScheduleXAPI`) for `rx.call_script` driven control.
|
|
20
|
+
|
|
21
|
+
Bundles `@schedule-x/*` **4.8.0** (open-source packages). Premium plugins (drag & drop, resize, drag-to-create,
|
|
22
|
+
resource scheduler, ...) are not included.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install reflex-schedule-x
|
|
28
|
+
# or
|
|
29
|
+
uv add reflex-schedule-x
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires `reflex>=0.9.11`. The npm packages are installed automatically by Reflex.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import reflex as rx
|
|
38
|
+
from reflex_schedule_x import calendar_type, schedule_x
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class State(rx.State):
|
|
42
|
+
events: list[dict] = [
|
|
43
|
+
{
|
|
44
|
+
"id": "1",
|
|
45
|
+
"title": "Coffee with John",
|
|
46
|
+
"start": "2026-09-17 10:05",
|
|
47
|
+
"end": "2026-09-17 10:35",
|
|
48
|
+
"calendarId": "work",
|
|
49
|
+
},
|
|
50
|
+
{"id": "2", "title": "Ski trip", "start": "2026-09-19", "end": "2026-09-21"},
|
|
51
|
+
]
|
|
52
|
+
selected: dict = {}
|
|
53
|
+
|
|
54
|
+
@rx.event
|
|
55
|
+
def on_event_click(self, event: dict):
|
|
56
|
+
self.selected = event
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def index():
|
|
60
|
+
return schedule_x(
|
|
61
|
+
events=State.events,
|
|
62
|
+
calendars={"work": calendar_type("work", "#f91c45", "#ffd2dc", "#59000d", label="Work")},
|
|
63
|
+
views=["day", "week", "month-grid", "month-agenda"],
|
|
64
|
+
default_view="week",
|
|
65
|
+
timezone="America/Caracas",
|
|
66
|
+
locale="es-ES",
|
|
67
|
+
is_dark=rx.color_mode_cond(light=False, dark=True),
|
|
68
|
+
event_modal=True,
|
|
69
|
+
current_time_indicator=True,
|
|
70
|
+
on_event_click=State.on_event_click,
|
|
71
|
+
height="800px",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
app = rx.App()
|
|
76
|
+
app.add_page(index)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Dates and times
|
|
80
|
+
|
|
81
|
+
Schedule-X uses the Temporal API. The component converts strings for you:
|
|
82
|
+
|
|
83
|
+
| Value you pass | Meaning |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `"2026-09-17"` | All-day date (`Temporal.PlainDate`) |
|
|
86
|
+
| `"2026-09-17 10:00"` or `"2026-09-17T10:00"` | Wall-clock time in the calendar `timezone` |
|
|
87
|
+
| `"2026-09-17T10:00:00-04:00"` / `"...Z"` | Absolute instant, shown in the calendar `timezone` |
|
|
88
|
+
| `"2026-09-17T10:00:00+02:00[Europe/Berlin]"` | RFC 9557 zoned date-time |
|
|
89
|
+
|
|
90
|
+
Payloads sent back to Python use `"YYYY-MM-DD"` for dates and `"YYYY-MM-DD HH:mm"` (calendar timezone) for
|
|
91
|
+
date-times, so they round-trip with your state. Set `datetime_format="iso"` to receive RFC 9557 strings instead.
|
|
92
|
+
|
|
93
|
+
Helpers: `to_sx_datetime(date | datetime)`, `to_sx_date(...)`, `parse_sx_datetime(str)`,
|
|
94
|
+
`calendar_event(...)`, `background_event(...)`, `calendar_type(...)`.
|
|
95
|
+
|
|
96
|
+
## `schedule_x` props
|
|
97
|
+
|
|
98
|
+
Props marked **live** update the running calendar; changing any other prop re-creates it.
|
|
99
|
+
|
|
100
|
+
| Prop | Type | Notes |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| `events` | `list[dict]` | **live**. `id`, `start`, `end`, `title`, `description`, `location`, `people`, `calendarId`, `rrule`, `exdate`, `_options`, `_customContent`, any custom key |
|
|
103
|
+
| `background_events` | `list[dict]` | **live**. `start`, `end`, `style`, `title`, `rrule`, `exdate` |
|
|
104
|
+
| `calendars` | `dict[str, dict]` | **live**. `colorName`, `label`, `lightColors`, `darkColors` |
|
|
105
|
+
| `views` | `list[str]` | `day`, `week`, `month-grid`, `month-agenda`, `week-agenda`, `list` |
|
|
106
|
+
| `default_view` | `str` | Initial view |
|
|
107
|
+
| `view` | `str` | **live**, controlled view (pair with `on_view_change`) |
|
|
108
|
+
| `selected_date` | `str` | **live**, `YYYY-MM-DD` |
|
|
109
|
+
| `min_date` / `max_date` | `str` | **live** |
|
|
110
|
+
| `locale` | `str` | **live**, see `reflex_schedule_x.LOCALES` |
|
|
111
|
+
| `timezone` | `str` | **live**, IANA name, default `UTC` |
|
|
112
|
+
| `first_day_of_week` | `int` | **live**, 1 = Monday … 7 = Sunday |
|
|
113
|
+
| `translations` | `dict` | Overrides merged over the built-in texts: `{"en-US": {"Week": "4 days"}}` |
|
|
114
|
+
| `day_boundaries` | `dict` | **live**, `{"start": "06:00", "end": "18:00"}` |
|
|
115
|
+
| `week_options` | `dict` | **live**, `gridHeight`, `nDays`, `eventWidth`, `gridStep`, `eventOverlap`, `timeAxisFormatOptions` |
|
|
116
|
+
| `month_grid_options` | `dict` | **live**, `{"nEventsPerDay": 4}` |
|
|
117
|
+
| `month_agenda_options` | `dict` | `{"nEventIndicatorsPerDay": 3}` |
|
|
118
|
+
| `show_week_numbers`, `is_responsive`, `skip_animations`, `skip_validation` | `bool` | |
|
|
119
|
+
| `small_breakpoint` | `int` | Width (px) under which small-screen views are used |
|
|
120
|
+
| `date_picker` | `dict` | Header date picker options |
|
|
121
|
+
| `theme` | `"default" \| "shadcn"` | Literal value; use one theme per app |
|
|
122
|
+
| `is_dark` | `bool` | **live** |
|
|
123
|
+
| `current_time_indicator`, `current_time_full_week_width` | `bool` | current-time plugin |
|
|
124
|
+
| `event_modal` | `bool` | event-modal plugin |
|
|
125
|
+
| `initial_scroll` / `scroll_to` | `str` | scroll-controller plugin; `scroll_to` is **live** |
|
|
126
|
+
| `recurrence` | `bool` | event-recurrence plugin |
|
|
127
|
+
| `ical_data` | `str` | iCalendar plugin (times are treated as UTC) |
|
|
128
|
+
| `timezone_select` | `bool` | timezone-select plugin |
|
|
129
|
+
| `datetime_format` | `"naive" \| "iso"` | Format of date-times in payloads |
|
|
130
|
+
| `id` | `str` | Enables `ScheduleXAPI(id)` |
|
|
131
|
+
|
|
132
|
+
The calendar defaults to `width="100%"`, `height="800px"`, `max_height="90vh"`; override with normal style props.
|
|
133
|
+
|
|
134
|
+
## Event triggers
|
|
135
|
+
|
|
136
|
+
| Trigger | Payload |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `on_event_click`, `on_double_click_event`, `on_event_update` | event `dict` (plus `is_all_day`) |
|
|
139
|
+
| `on_range_update` | `{"start": str, "end": str}` |
|
|
140
|
+
| `on_calendar_render` | `{"view": str, "date": str, "range": {...}}` |
|
|
141
|
+
| `on_view_change` | view name |
|
|
142
|
+
| `on_selected_date_update`, `on_click_date`, `on_double_click_date`, `on_click_agenda_date`, `on_double_click_agenda_date`, `on_click_plus_events`, `on_scroll_day_into_view` | `"YYYY-MM-DD"` |
|
|
143
|
+
| `on_click_date_time`, `on_double_click_date_time` | `"YYYY-MM-DD HH:mm"` |
|
|
144
|
+
| `on_slot_action` | `{"action", "slot", "event", "date"}` |
|
|
145
|
+
| `on_error` | error message |
|
|
146
|
+
|
|
147
|
+
Lazy loading: load events in `on_range_update` (and `on_scroll_day_into_view` for the list view) and assign them to
|
|
148
|
+
the `events` state var.
|
|
149
|
+
|
|
150
|
+
## Custom components (slots)
|
|
151
|
+
|
|
152
|
+
Put `schedule_x_slot(name, *children)` components inside `schedule_x`. Slot content is normal Reflex UI, so it can use
|
|
153
|
+
state. Inside a slot:
|
|
154
|
+
|
|
155
|
+
- `schedule_x_field(name, format=None)` renders slot data. Event fields: `title`, `description`, `location`,
|
|
156
|
+
`people`, `start`, `end`, `calendarId`, custom keys; virtual fields `time_range`, `date_range`, `calendar_label`.
|
|
157
|
+
Other slots expose `date`, `day`, `hour`, `events`. Formats: `time`, `date`, `datetime`, `weekday`,
|
|
158
|
+
`weekday_short`, `day`, `month`, `hour`, `count`, `upper`, `json`.
|
|
159
|
+
- `schedule_x_show(name, *children, negate=False)` renders children when the field is truthy (`is_all_day` too).
|
|
160
|
+
- `schedule_x_event_card(*children, variant="container" | "main")` paints the event's calendar colors.
|
|
161
|
+
- `schedule_x_action(*children, action="delete", close_modal=True)` sends clicks to `on_slot_action`.
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
schedule_x(
|
|
165
|
+
schedule_x_slot(
|
|
166
|
+
"timeGridEvent",
|
|
167
|
+
schedule_x_event_card(
|
|
168
|
+
schedule_x_field("title", tag_name="strong"),
|
|
169
|
+
schedule_x_field("time_range"),
|
|
170
|
+
schedule_x_show("location", schedule_x_field("location")),
|
|
171
|
+
),
|
|
172
|
+
),
|
|
173
|
+
schedule_x_slot(
|
|
174
|
+
"eventModal",
|
|
175
|
+
rx.card(
|
|
176
|
+
schedule_x_field("title", tag_name="h3"),
|
|
177
|
+
schedule_x_action(rx.button("Delete"), action="delete", close_modal=True),
|
|
178
|
+
),
|
|
179
|
+
),
|
|
180
|
+
schedule_x_slot("headerContentRightPrepend", rx.button("New", on_click=State.new_event)),
|
|
181
|
+
events=State.events,
|
|
182
|
+
event_modal=True,
|
|
183
|
+
on_slot_action=State.on_slot_action,
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Supported slots: `timeGridEvent`, `dateGridEvent`, `monthGridEvent`, `monthAgendaEvent`, `weekAgendaEvent`,
|
|
188
|
+
`monthAgendaDateDots`, `eventModal`, `headerContent`, `headerContentLeftPrepend`, `headerContentLeftAppend`,
|
|
189
|
+
`headerContentRightPrepend`, `headerContentRightAppend`, `weekGridDate`, `weekGridHour`, `monthGridDayName`,
|
|
190
|
+
`monthGridDate`.
|
|
191
|
+
|
|
192
|
+
## Imperative API
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from reflex_schedule_x import ScheduleXAPI
|
|
196
|
+
|
|
197
|
+
api = ScheduleXAPI("my-calendar") # schedule_x(id="my-calendar")
|
|
198
|
+
|
|
199
|
+
rx.button("Week", on_click=api.set_view("week"))
|
|
200
|
+
rx.button("Today", on_click=api.set_date(datetime.date.today()))
|
|
201
|
+
rx.button("Dump", on_click=api.get_events(callback=State.receive_events))
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Methods: `set_view`, `set_date`, `set_theme`, `add_event`, `update_event`, `remove_event`, `set_events`,
|
|
205
|
+
`close_event_modal`, `scroll_to`, `get_view`, `get_date`, `get_range`, `get_events`, `get_event`.
|
|
206
|
+
|
|
207
|
+
## Date and time pickers
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
schedule_x_date_picker(value=State.date, on_change=State.set_date, locale="es-ES", label="Fecha", min="2026-01-01")
|
|
211
|
+
schedule_x_time_picker(value=State.time, on_change=State.set_time, is_12_hour=True, label="Hora")
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Demo app
|
|
215
|
+
|
|
216
|
+
The `schedule_x_demo/` folder contains a multi-page demo: playground with every option, events CRUD and callback
|
|
217
|
+
log, custom components, recurrence + background events + iCal, language and timezones, imperative API with the list
|
|
218
|
+
view, and the pickers.
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
uv venv && uv pip install -e .
|
|
222
|
+
cd schedule_x_demo
|
|
223
|
+
uv pip install -r requirements.txt
|
|
224
|
+
uv run reflex run
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
uv sync --extra dev
|
|
231
|
+
uv run ruff check . && uv run ruff format --check .
|
|
232
|
+
uv run pytest --cov
|
|
233
|
+
uv run reflex component build # generates .pyi stubs and builds sdist + wheel
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Branches: work happens on `develop`; `main` only receives pull requests from `develop`. The `Quality` (ruff, pytest on
|
|
237
|
+
Python 3.10–3.13, build, stub freshness, demo bundle) and `Security` (CodeQL, Bandit, pip-audit, Gitleaks, dependency
|
|
238
|
+
review) workflows must pass before merging.
|
|
239
|
+
|
|
240
|
+
### Releasing
|
|
241
|
+
|
|
242
|
+
1. Bump `version` in `pyproject.toml`, update `CHANGELOG.md`, and merge `develop` into `main`.
|
|
243
|
+
2. Tag `main` and push the tag:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
git checkout main && git pull
|
|
247
|
+
git tag -a v0.1.0 -m "v0.1.0"
|
|
248
|
+
git push origin v0.1.0
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The `Release` workflow checks that the tag matches the version, runs the tests, builds the sdist and wheel, publishes
|
|
252
|
+
them to PyPI with Trusted Publishing (no API token) from the `pypi` environment, and creates the GitHub release with
|
|
253
|
+
the files attached.
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT © Ernesto Crespo. Schedule-X is MIT licensed, © Tom Österlund.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Reflex custom component for the Schedule-X event calendar (https://schedule-x.dev)."""
|
|
2
|
+
|
|
3
|
+
from .helpers import (
|
|
4
|
+
LOCALES,
|
|
5
|
+
ScheduleXAPI,
|
|
6
|
+
background_event,
|
|
7
|
+
calendar_event,
|
|
8
|
+
calendar_type,
|
|
9
|
+
parse_sx_datetime,
|
|
10
|
+
to_sx_date,
|
|
11
|
+
to_sx_datetime,
|
|
12
|
+
)
|
|
13
|
+
from .schedule_x import (
|
|
14
|
+
LIB_DEPENDENCIES,
|
|
15
|
+
SCHEDULE_X_VERSION,
|
|
16
|
+
SLOT_NAMES,
|
|
17
|
+
VIEWS,
|
|
18
|
+
CalendarEvent,
|
|
19
|
+
DateRange,
|
|
20
|
+
RenderInfo,
|
|
21
|
+
ScheduleX,
|
|
22
|
+
ScheduleXAction,
|
|
23
|
+
ScheduleXDatePicker,
|
|
24
|
+
ScheduleXEventCard,
|
|
25
|
+
ScheduleXField,
|
|
26
|
+
ScheduleXShow,
|
|
27
|
+
ScheduleXSlot,
|
|
28
|
+
ScheduleXTimePicker,
|
|
29
|
+
SlotAction,
|
|
30
|
+
schedule_x,
|
|
31
|
+
schedule_x_action,
|
|
32
|
+
schedule_x_date_picker,
|
|
33
|
+
schedule_x_event_card,
|
|
34
|
+
schedule_x_field,
|
|
35
|
+
schedule_x_show,
|
|
36
|
+
schedule_x_slot,
|
|
37
|
+
schedule_x_time_picker,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"LIB_DEPENDENCIES",
|
|
42
|
+
"LOCALES",
|
|
43
|
+
"SCHEDULE_X_VERSION",
|
|
44
|
+
"SLOT_NAMES",
|
|
45
|
+
"VIEWS",
|
|
46
|
+
"CalendarEvent",
|
|
47
|
+
"DateRange",
|
|
48
|
+
"RenderInfo",
|
|
49
|
+
"ScheduleX",
|
|
50
|
+
"ScheduleXAPI",
|
|
51
|
+
"ScheduleXAction",
|
|
52
|
+
"ScheduleXDatePicker",
|
|
53
|
+
"ScheduleXEventCard",
|
|
54
|
+
"ScheduleXField",
|
|
55
|
+
"ScheduleXShow",
|
|
56
|
+
"ScheduleXSlot",
|
|
57
|
+
"ScheduleXTimePicker",
|
|
58
|
+
"SlotAction",
|
|
59
|
+
"background_event",
|
|
60
|
+
"calendar_event",
|
|
61
|
+
"calendar_type",
|
|
62
|
+
"parse_sx_datetime",
|
|
63
|
+
"schedule_x",
|
|
64
|
+
"schedule_x_action",
|
|
65
|
+
"schedule_x_date_picker",
|
|
66
|
+
"schedule_x_event_card",
|
|
67
|
+
"schedule_x_field",
|
|
68
|
+
"schedule_x_show",
|
|
69
|
+
"schedule_x_slot",
|
|
70
|
+
"schedule_x_time_picker",
|
|
71
|
+
"to_sx_date",
|
|
72
|
+
"to_sx_datetime",
|
|
73
|
+
]
|