Skip to content

Commit 31b8295

Browse files
committed
feat(calendar-web): add per item format and text config
1 parent 28d67e8 commit 31b8295

File tree

5 files changed

+233
-23
lines changed

5 files changed

+233
-23
lines changed

packages/pluggableWidgets/calendar-web/src/Calendar.editorConfig.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
StructurePreviewProps,
66
text
77
} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
8-
import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
8+
import { hideNestedPropertiesIn, hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
99
import { CalendarPreviewProps } from "../typings/CalendarProps";
1010
import IconSVGDark from "./assets/StructureCalendarDark.svg";
1111
import IconSVG from "./assets/StructureCalendarLight.svg";
@@ -47,6 +47,55 @@ export function getProperties(values: CalendarPreviewProps, defaultProperties: P
4747
hidePropertyIn(defaultProperties, values, "defaultViewStandard");
4848
}
4949

50+
values.toolbarItems?.forEach((item, index) => {
51+
// Hide all format properties for non-view items (navigation buttons, title)
52+
if (!["day", "month", "agenda", "week", "work_week"].includes(item.itemType)) {
53+
hideNestedPropertiesIn(defaultProperties, values, "toolbarItems", index, [
54+
"customViewHeaderDayFormat",
55+
"customViewCellDateFormat",
56+
"customViewGutterDateFormat",
57+
"customViewGutterTimeFormat",
58+
"customViewAllDayText",
59+
"customViewTextHeaderDate",
60+
"customViewTextHeaderTime",
61+
"customViewTextHeaderEvent"
62+
]);
63+
} else {
64+
switch (item.itemType) {
65+
case "day":
66+
case "week":
67+
case "work_week":
68+
// Day/Week/Custom Week: show headerDayFormat, hide all others
69+
hideNestedPropertiesIn(defaultProperties, values, "toolbarItems", index, [
70+
"customViewCellDateFormat",
71+
"customViewGutterDateFormat",
72+
"customViewAllDayText",
73+
"customViewTextHeaderDate",
74+
"customViewTextHeaderTime",
75+
"customViewTextHeaderEvent"
76+
]);
77+
break;
78+
case "month":
79+
// Month: show headerDayFormat and cellDateFormat, hide gutter/agenda-specific
80+
hideNestedPropertiesIn(defaultProperties, values, "toolbarItems", index, [
81+
"customViewGutterDateFormat",
82+
"customViewGutterTimeFormat",
83+
"customViewAllDayText",
84+
"customViewTextHeaderDate",
85+
"customViewTextHeaderTime",
86+
"customViewTextHeaderEvent"
87+
]);
88+
break;
89+
case "agenda":
90+
// Agenda: show gutter and text headers, hide headerDayFormat and cellDateFormat
91+
hideNestedPropertiesIn(defaultProperties, values, "toolbarItems", index, [
92+
"customViewCellDateFormat"
93+
]);
94+
break;
95+
}
96+
}
97+
});
98+
5099
// Show/hide title properties based on selection
51100
if (values.titleType === "attribute") {
52101
hidePropertyIn(defaultProperties, values, "titleExpression");

packages/pluggableWidgets/calendar-web/src/Calendar.xml

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -141,23 +141,23 @@
141141
<properties>
142142
<property key="itemType" type="enumeration" defaultValue="month">
143143
<caption>Item</caption>
144-
<category>Toolbar</category>
144+
<category>Appearance</category>
145145
<description>Select which item to render on the toolbar.</description>
146146
<enumerationValues>
147+
<enumerationValue key="day">Day view button</enumerationValue>
148+
<enumerationValue key="month">Month view button</enumerationValue>
149+
<enumerationValue key="agenda">Agenda view button</enumerationValue>
150+
<enumerationValue key="week">Week view button</enumerationValue>
151+
<enumerationValue key="work_week">Custom week view button</enumerationValue>
152+
<enumerationValue key="title">Title date text</enumerationValue>
147153
<enumerationValue key="previous">Previous button</enumerationValue>
148-
<enumerationValue key="today">Today button</enumerationValue>
149154
<enumerationValue key="next">Next button</enumerationValue>
150-
<enumerationValue key="title">Title date text</enumerationValue>
151-
<enumerationValue key="month">Month button</enumerationValue>
152-
<enumerationValue key="week">Week button</enumerationValue>
153-
<enumerationValue key="work_week">Work week button</enumerationValue>
154-
<enumerationValue key="day">Day button</enumerationValue>
155-
<enumerationValue key="agenda">Agenda button</enumerationValue>
155+
<enumerationValue key="today">Today button</enumerationValue>
156156
</enumerationValues>
157157
</property>
158158
<property key="position" type="enumeration" defaultValue="left">
159159
<caption>Position</caption>
160-
<category>Toolbar</category>
160+
<category>Appearance</category>
161161
<description>Align the item within the toolbar.</description>
162162
<enumerationValues>
163163
<enumerationValue key="left">Left</enumerationValue>
@@ -167,23 +167,63 @@
167167
</property>
168168
<property key="caption" type="textTemplate" required="false">
169169
<caption>Caption</caption>
170-
<category>Toolbar</category>
170+
<category>Appearance</category>
171171
<description>Optional text for the button or title. If empty, a default localized label is used.</description>
172172
</property>
173173
<property key="tooltip" type="textTemplate" required="false">
174174
<caption>Tooltip</caption>
175-
<category>Toolbar</category>
175+
<category>Appearance</category>
176176
<description>Optional hover text for the button.</description>
177177
</property>
178178
<property key="renderMode" type="enumeration" defaultValue="button">
179179
<caption>Render mode</caption>
180-
<category>Toolbar</category>
180+
<category>Appearance</category>
181181
<description>Choose how the item is rendered.</description>
182182
<enumerationValues>
183183
<enumerationValue key="button">Button</enumerationValue>
184184
<enumerationValue key="link">Link</enumerationValue>
185185
</enumerationValues>
186186
</property>
187+
<property key="customViewHeaderDayFormat" type="textTemplate" required="false">
188+
<caption>Header day format</caption>
189+
<category>Custom formats</category>
190+
<description>Format of date(s) in the header above the day, week, month, custom or Agenda view.</description>
191+
</property>
192+
<property key="customViewCellDateFormat" type="textTemplate" required="false">
193+
<caption>Cell date format</caption>
194+
<category>Custom formats</category>
195+
<description>Date shown in the month cells.</description>
196+
</property>
197+
<property key="customViewGutterTimeFormat" type="textTemplate" required="false">
198+
<caption>Time gutter format</caption>
199+
<category>Custom formats</category>
200+
<description>Time shown as the first column in the week, day and agenda view.</description>
201+
</property>
202+
<property key="customViewGutterDateFormat" type="textTemplate" required="false">
203+
<caption>Date gutter format</caption>
204+
<category>Custom formats</category>
205+
<description>Date shown as the first column in the agenda view.</description>
206+
</property>
207+
<property key="customViewAllDayText" type="textTemplate" required="false">
208+
<caption>All day text</caption>
209+
<category>Text</category>
210+
<description>Text shown in the all day column.</description>
211+
</property>
212+
<property key="customViewTextHeaderDate" type="textTemplate" required="false">
213+
<caption>Text header date</caption>
214+
<category>Text</category>
215+
<description>Text showing in the agenda view to header in the column date.</description>
216+
</property>
217+
<property key="customViewTextHeaderTime" type="textTemplate" required="false">
218+
<caption>Text header time</caption>
219+
<category>Text</category>
220+
<description>Text showing in the agenda view to header in the column time.</description>
221+
</property>
222+
<property key="customViewTextHeaderEvent" type="textTemplate" required="false">
223+
<caption>Text header event</caption>
224+
<category>Text</category>
225+
<description>Text showing in the agenda view to header in the column event.</description>
226+
</property>
187227
</properties>
188228
</property>
189229
</propertyGroup>

packages/pluggableWidgets/calendar-web/src/components/Toolbar.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ export type ResolvedToolbarItem = {
5353
caption?: string;
5454
tooltip?: string;
5555
renderMode: "button" | "link";
56+
// Custom formatting/text options for Custom view
57+
customViewHeaderDayFormat?: string;
58+
customViewCellDateFormat?: string;
59+
customViewGutterTimeFormat?: string;
60+
customViewGutterDateFormat?: string;
61+
customViewAllDayText?: string;
62+
customViewTextHeaderDate?: string;
63+
customViewTextHeaderTime?: string;
64+
customViewTextHeaderEvent?: string;
5665
};
5766

5867
export function createConfigurableToolbar(items: ResolvedToolbarItem[]): (props: ToolbarProps) => ReactElement {

packages/pluggableWidgets/calendar-web/src/helpers/CalendarPropsBuilder.ts

Lines changed: 105 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ export class CalendarPropsBuilder {
4949
toolbar
5050
},
5151
defaultView: this.defaultView,
52-
messages: {
53-
work_week: workWeekCaption
54-
},
52+
messages: this.buildMessages(workWeekCaption),
5553
events: this.events,
5654
formats,
5755
localizer,
@@ -71,6 +69,27 @@ export class CalendarPropsBuilder {
7169
};
7270
}
7371

72+
private buildMessages(workWeekCaption: string): {
73+
work_week: string;
74+
allDay?: string;
75+
date?: string;
76+
time?: string;
77+
event?: string;
78+
} {
79+
if (this.isCustomView && this.toolbarItems && this.toolbarItems.length > 0) {
80+
const byType = new Map(this.toolbarItems.map(i => [i.itemType, i]));
81+
const agenda = byType.get("agenda");
82+
return {
83+
work_week: workWeekCaption,
84+
...(agenda?.customViewAllDayText ? { allDay: agenda.customViewAllDayText } : {}),
85+
...(agenda?.customViewTextHeaderDate ? { date: agenda.customViewTextHeaderDate } : {}),
86+
...(agenda?.customViewTextHeaderTime ? { time: agenda.customViewTextHeaderTime } : {}),
87+
...(agenda?.customViewTextHeaderEvent ? { event: agenda.customViewTextHeaderEvent } : {})
88+
} as const;
89+
}
90+
return { work_week: workWeekCaption } as const;
91+
}
92+
7493
private buildEvents(items: ObjectItem[]): CalendarEvent[] {
7594
return items.map(item => {
7695
return this.buildEventItem(item);
@@ -127,11 +146,6 @@ export class CalendarPropsBuilder {
127146
) => `${formatWith(start, loc)}${formatWith(end, loc)}`;
128147
}
129148

130-
// Ensure showEventDate=false always hides event time ranges
131-
if (this.props.showEventDate?.value === false) {
132-
formats.eventTimeRangeFormat = () => "";
133-
}
134-
135149
const titlePattern = this.props.topBarDateFormat?.value?.trim();
136150
if (titlePattern) {
137151
formats.dayHeaderFormat = (date: Date, _culture: string, loc: DateLocalizer) =>
@@ -150,6 +164,80 @@ export class CalendarPropsBuilder {
150164
) => `${loc.format(start, titlePattern)}${loc.format(end, titlePattern)}`;
151165
}
152166

167+
// Apply per-view custom formats only in custom view mode
168+
if (this.isCustomView && this.toolbarItems && this.toolbarItems.length > 0) {
169+
const byType = new Map(this.toolbarItems.map(i => [i.itemType, i]));
170+
171+
type HeaderFormat = Formats["dayHeaderFormat"];
172+
const applyHeader = (pattern?: string, existing?: HeaderFormat): HeaderFormat | undefined => {
173+
if (!pattern) return existing;
174+
return (date: Date, _culture: string, loc: DateLocalizer) => loc.format(date, pattern);
175+
};
176+
177+
const dayHeaderPattern = byType.get("day")?.customViewHeaderDayFormat || this.props.topBarDateFormat?.value;
178+
const weekHeaderPattern =
179+
byType.get("week")?.customViewHeaderDayFormat ||
180+
byType.get("work_week")?.customViewHeaderDayFormat ||
181+
this.props.topBarDateFormat?.value;
182+
const monthHeaderPattern =
183+
byType.get("month")?.customViewHeaderDayFormat || this.props.topBarDateFormat?.value;
184+
const agendaHeaderPattern =
185+
byType.get("agenda")?.customViewHeaderDayFormat || this.props.topBarDateFormat?.value;
186+
187+
formats.dayHeaderFormat = applyHeader(dayHeaderPattern, formats.dayHeaderFormat);
188+
formats.dayRangeHeaderFormat = (
189+
range: { start: Date; end: Date },
190+
_culture: string,
191+
loc: DateLocalizer
192+
) => {
193+
const pattern = weekHeaderPattern;
194+
if (pattern) {
195+
return `${loc.format(range.start, pattern)}${loc.format(range.end, pattern)}`;
196+
}
197+
return `${loc.format(range.start, "P")}${loc.format(range.end, "P")}`;
198+
};
199+
formats.monthHeaderFormat = applyHeader(monthHeaderPattern, formats.monthHeaderFormat);
200+
formats.agendaHeaderFormat = (range: { start: Date; end: Date }, _culture: string, loc: DateLocalizer) => {
201+
const pattern = agendaHeaderPattern;
202+
if (pattern) {
203+
return `${loc.format(range.start, pattern)}${loc.format(range.end, pattern)}`;
204+
}
205+
return `${loc.format(range.start, "P")}${loc.format(range.end, "P")}`;
206+
};
207+
208+
// Month day numbers
209+
const monthCellDate = byType.get("month")?.customViewCellDateFormat;
210+
if (monthCellDate) {
211+
formats.dateFormat = (date: Date, _culture: string, loc: DateLocalizer) =>
212+
loc.format(date, monthCellDate);
213+
}
214+
215+
// Time gutters
216+
const weekTimeGutter = byType.get("week")?.customViewGutterTimeFormat;
217+
const dayTimeGutter = byType.get("day")?.customViewGutterTimeFormat;
218+
const workWeekTimeGutter = byType.get("work_week")?.customViewGutterTimeFormat;
219+
const chosenTimeGutter = weekTimeGutter || dayTimeGutter || workWeekTimeGutter;
220+
if (chosenTimeGutter) {
221+
formats.timeGutterFormat = (date: Date, _culture: string, loc: DateLocalizer) =>
222+
loc.format(date, chosenTimeGutter);
223+
}
224+
const agendaTime = byType.get("agenda")?.customViewGutterTimeFormat;
225+
if (agendaTime) {
226+
formats.agendaTimeFormat = (date: Date, _culture: string, loc: DateLocalizer) =>
227+
loc.format(date, agendaTime);
228+
}
229+
const agendaDate = byType.get("agenda")?.customViewGutterDateFormat;
230+
if (agendaDate) {
231+
formats.agendaDateFormat = (date: Date, _culture: string, loc: DateLocalizer) =>
232+
loc.format(date, agendaDate);
233+
}
234+
}
235+
236+
// Ensure showEventDate=false always hides event time ranges
237+
if (this.props.showEventDate?.value === false) {
238+
formats.eventTimeRangeFormat = () => "";
239+
}
240+
153241
return formats;
154242
}
155243

@@ -226,7 +314,15 @@ export class CalendarPropsBuilder {
226314
position: i.position,
227315
caption: i.caption?.value,
228316
tooltip: i.tooltip?.value,
229-
renderMode: i.renderMode
317+
renderMode: i.renderMode,
318+
customViewHeaderDayFormat: i.customViewHeaderDayFormat?.value,
319+
customViewCellDateFormat: i.customViewCellDateFormat?.value,
320+
customViewGutterTimeFormat: i.customViewGutterTimeFormat?.value,
321+
customViewGutterDateFormat: i.customViewGutterDateFormat?.value,
322+
customViewAllDayText: i.customViewAllDayText?.value,
323+
customViewTextHeaderDate: i.customViewTextHeaderDate?.value,
324+
customViewTextHeaderTime: i.customViewTextHeaderTime?.value,
325+
customViewTextHeaderEvent: i.customViewTextHeaderEvent?.value
230326
}));
231327
}
232328
}

packages/pluggableWidgets/calendar-web/typings/CalendarProps.d.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type DefaultViewStandardEnum = "day" | "week" | "month";
1414

1515
export type DefaultViewCustomEnum = "day" | "week" | "month" | "work_week" | "agenda";
1616

17-
export type ItemTypeEnum = "previous" | "today" | "next" | "title" | "month" | "week" | "work_week" | "day" | "agenda";
17+
export type ItemTypeEnum = "day" | "month" | "agenda" | "week" | "work_week" | "title" | "previous" | "next" | "today";
1818

1919
export type PositionEnum = "left" | "center" | "right";
2020

@@ -26,6 +26,14 @@ export interface ToolbarItemsType {
2626
caption?: DynamicValue<string>;
2727
tooltip?: DynamicValue<string>;
2828
renderMode: RenderModeEnum;
29+
customViewHeaderDayFormat?: DynamicValue<string>;
30+
customViewCellDateFormat?: DynamicValue<string>;
31+
customViewGutterTimeFormat?: DynamicValue<string>;
32+
customViewGutterDateFormat?: DynamicValue<string>;
33+
customViewAllDayText?: DynamicValue<string>;
34+
customViewTextHeaderDate?: DynamicValue<string>;
35+
customViewTextHeaderTime?: DynamicValue<string>;
36+
customViewTextHeaderEvent?: DynamicValue<string>;
2937
}
3038

3139
export type WidthUnitEnum = "pixels" | "percentage";
@@ -44,6 +52,14 @@ export interface ToolbarItemsPreviewType {
4452
caption: string;
4553
tooltip: string;
4654
renderMode: RenderModeEnum;
55+
customViewHeaderDayFormat: string;
56+
customViewCellDateFormat: string;
57+
customViewGutterTimeFormat: string;
58+
customViewGutterDateFormat: string;
59+
customViewAllDayText: string;
60+
customViewTextHeaderDate: string;
61+
customViewTextHeaderTime: string;
62+
customViewTextHeaderEvent: string;
4763
}
4864

4965
export interface CalendarContainerProps {

0 commit comments

Comments
 (0)