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.
@@ -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
+ [![Quality](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/quality.yml/badge.svg?branch=main)](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/quality.yml)
36
+ [![Security](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/security.yml/badge.svg?branch=main)](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/security.yml)
37
+ [![PyPI](https://img.shields.io/pypi/v/reflex-schedule-x)](https://pypi.org/project/reflex-schedule-x/)
38
+ [![Python](https://img.shields.io/pypi/pyversions/reflex-schedule-x)](https://pypi.org/project/reflex-schedule-x/)
39
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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
+ [![Quality](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/quality.yml/badge.svg?branch=main)](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/quality.yml)
4
+ [![Security](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/security.yml/badge.svg?branch=main)](https://github.com/ecrespo/reflex-schedule-x/actions/workflows/security.yml)
5
+ [![PyPI](https://img.shields.io/pypi/v/reflex-schedule-x)](https://pypi.org/project/reflex-schedule-x/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/reflex-schedule-x)](https://pypi.org/project/reflex-schedule-x/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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
+ ]