Skip to content
Open
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
16 changes: 16 additions & 0 deletions packages/pluggableWidgets/calendar-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- You can now customize which controls appear in the calendar’s top bar and how they are arranged, with optional captions, tooltips, and a link-style appearance.

- The calendar title can be formatted consistently across views, including custom work week.

- Time formatting is applied consistently to the time gutter and to event/agenda time ranges, with robust fallbacks for invalid patterns.

### Changed

- When the event time range is disabled, events no longer display start/end time text.

### Breaking changes

- Custom view buttons and their captions are now set inside the Custom top bar views configuration.

## [2.0.0] - 2025-08-12

### Breaking changes
Expand Down
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/calendar-web/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@mendix/calendar-web",
"widgetName": "Calendar",
"version": "2.0.0",
"version": "2.2.0",
"description": "Calendar",
"copyright": "© Mendix Technology BV 2025. All rights reserved.",
"license": "Apache-2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,11 @@ import {
StructurePreviewProps,
text
} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
import { hideNestedPropertiesIn, hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
import { CalendarPreviewProps } from "../typings/CalendarProps";
import IconSVGDark from "./assets/StructureCalendarDark.svg";
import IconSVG from "./assets/StructureCalendarLight.svg";

const CUSTOM_VIEW_CONFIG: Array<keyof CalendarPreviewProps> = [
"customViewShowDay",
"customViewShowWeek",
"customViewShowMonth",
"customViewShowAgenda",
"customViewShowCustomWeek",
"customViewCaption",
"defaultViewCustom"
];

const CUSTOM_VIEW_DAYS_CONFIG: Array<keyof CalendarPreviewProps> = [
"customViewShowMonday",
"customViewShowTuesday",
"customViewShowWednesday",
"customViewShowThursday",
"customViewShowFriday",
"customViewShowSaturday",
"customViewShowSunday"
];

export function getProperties(values: CalendarPreviewProps, defaultProperties: Properties): Properties {
if (values.heightUnit === "percentageOfWidth") {
hidePropertyIn(defaultProperties, values, "height");
Expand All @@ -51,16 +31,73 @@ export function getProperties(values: CalendarPreviewProps, defaultProperties: P
hidePropertiesIn(defaultProperties, values, ["maxHeight", "overflowY"]);
}

// Hide custom week range properties when the view is set to 'standard'
if (values.view === "standard") {
hidePropertiesIn(defaultProperties, values, [...CUSTOM_VIEW_CONFIG, ...CUSTOM_VIEW_DAYS_CONFIG]);
hidePropertiesIn(defaultProperties, values, [
"defaultViewCustom",
"toolbarItems",
"customViewShowMonday",
"customViewShowTuesday",
"customViewShowWednesday",
"customViewShowThursday",
"customViewShowFriday",
"customViewShowSaturday",
"customViewShowSunday"
]);
} else {
hidePropertyIn(defaultProperties, values, "defaultViewStandard");
hidePropertiesIn(defaultProperties, values, ["defaultViewStandard", "topBarDateFormat"]);
}

if (values.customViewShowCustomWeek === false) {
hidePropertiesIn(defaultProperties, values, ["customViewCaption", ...CUSTOM_VIEW_DAYS_CONFIG]);
values.toolbarItems?.forEach((item, index) => {
if (item.itemType === "title") {
hideNestedPropertiesIn(defaultProperties, values, "toolbarItems", index, ["buttonTooltip", "buttonStyle"]);
}
}
// Hide all format properties for non-view items (navigation buttons, title)
if (!["day", "month", "agenda", "week", "work_week"].includes(item.itemType)) {
hideNestedPropertiesIn(defaultProperties, values, "toolbarItems", index, [
"customViewHeaderDayFormat",
"customViewCellDateFormat",
"customViewGutterDateFormat",
"customViewGutterTimeFormat",
"customViewAllDayText",
"customViewTextHeaderDate",
"customViewTextHeaderTime",
"customViewTextHeaderEvent"
]);
} else {
switch (item.itemType) {
case "day":
case "week":
case "work_week":
// Day/Week/Custom Week: show headerDayFormat, hide all others
hideNestedPropertiesIn(defaultProperties, values, "toolbarItems", index, [
"customViewCellDateFormat",
"customViewGutterDateFormat",
"customViewAllDayText",
"customViewTextHeaderDate",
"customViewTextHeaderTime",
"customViewTextHeaderEvent"
]);
break;
case "month":
// Month: show headerDayFormat and cellDateFormat, hide gutter/agenda-specific
hideNestedPropertiesIn(defaultProperties, values, "toolbarItems", index, [
"customViewGutterDateFormat",
"customViewGutterTimeFormat",
"customViewAllDayText",
"customViewTextHeaderDate",
"customViewTextHeaderTime",
"customViewTextHeaderEvent"
]);
break;
case "agenda":
// Agenda: show gutter and text headers, hide headerDayFormat and cellDateFormat
hideNestedPropertiesIn(defaultProperties, values, "toolbarItems", index, [
"customViewCellDateFormat"
]);
break;
}
}
});

// Show/hide title properties based on selection
if (values.titleType === "attribute") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import classnames from "classnames";
import { ReactElement } from "react";
import { Calendar, dateFnsLocalizer, EventPropGetter } from "react-big-calendar";
import { Calendar, dateFnsLocalizer, EventPropGetter, View } from "react-big-calendar";
import { CalendarPreviewProps } from "../typings/CalendarProps";
import { CustomToolbar } from "./components/Toolbar";
import { constructWrapperStyle, WrapperStyleProps } from "./utils/style-utils";
import { createConfigurableToolbar, CustomToolbar } from "./components/Toolbar";
import { eventPropGetter, format, getDay, parse, startOfWeek } from "./utils/calendar-utils";
import { constructWrapperStyle, WrapperStyleProps } from "./utils/style-utils";

import "react-big-calendar/lib/css/react-big-calendar.css";
import "./ui/Calendar.scss";
Expand Down Expand Up @@ -75,15 +75,35 @@ export function preview(props: CalendarPreviewProps): ReactElement {
// Cast eventPropGetter to satisfy preview Calendar generic
const previewEventPropGetter = eventPropGetter as unknown as EventPropGetter<(typeof events)[0]>;

const isCustomView = props.view === "custom";
const toolbar =
isCustomView && props.toolbarItems?.length
? createConfigurableToolbar(
props.toolbarItems.map(i => ({
itemType: i.itemType,
position: i.position,
caption: i.caption,
renderMode: i.renderMode,
customButtonTooltip: undefined,
customButtonStyle: i.buttonStyle
})) as any
)
: CustomToolbar;

const defaultView = isCustomView ? props.defaultViewCustom : props.defaultViewStandard;
const views: View[] = isCustomView
? (["day", "week", "month", "work_week"] as View[])
: (["day", "week", "month"] as View[]);

return (
<div className={classnames("widget-events-preview", "widget-calendar", className)} style={wrapperStyle}>
<Calendar
components={{ toolbar: CustomToolbar }}
defaultView={props.defaultViewStandard}
components={{ toolbar }}
defaultView={defaultView}
events={events}
localizer={localizer}
messages={{ ...localizer.messages, work_week: "Custom" }}
views={["day", "week", "month", "work_week"]}
views={views}
eventPropGetter={previewEventPropGetter}
/>
</div>
Expand Down
9 changes: 7 additions & 2 deletions packages/pluggableWidgets/calendar-web/src/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DnDCalendar } from "./utils/calendar-utils";
import { constructWrapperStyle } from "./utils/style-utils";
import "./ui/Calendar.scss";
import { useCalendarEvents } from "./helpers/useCalendarEvents";
import { useLocalizer } from "./helpers/useLocalizer";

export default function MxCalendar(props: CalendarContainerProps): ReactElement {
// useMemo with empty dependency array is used
Expand All @@ -15,10 +16,14 @@ export default function MxCalendar(props: CalendarContainerProps): ReactElement
const wrapperStyle = useMemo(() => constructWrapperStyle(props), []);
// eslint-disable-next-line react-hooks/exhaustive-deps
const calendarController = useMemo(() => new CalendarPropsBuilder(props), []);

// Get locale-aware localizer
const { localizer, culture } = useLocalizer();

const calendarProps = useMemo(() => {
calendarController.updateProps(props);
return calendarController.build();
}, [props, calendarController]);
return calendarController.build(localizer, culture);
}, [props, calendarController, localizer, culture]);

const calendarEvents = useCalendarEvents(props);
return (
Expand Down
Loading
Loading