feat(collections/calendar): use own UI for header

This commit is contained in:
Elian Doran 2025-09-06 12:26:42 +03:00
parent 49c80f0e0b
commit afc17f41f6
No known key found for this signature in database
6 changed files with 116 additions and 20 deletions

View File

@ -293,6 +293,11 @@ button.close:hover {
pointer-events: none;
}
.icon-action.btn {
padding: 0 8px;
min-width: unset !important;
}
.ui-widget-content a:not(.ui-tabs-anchor) {
color: #337ab7 !important;
}

View File

@ -587,7 +587,17 @@
"september": "September",
"october": "October",
"november": "November",
"december": "December"
"december": "December",
"week": "Week",
"week_previous": "Previous week",
"week_next": "Next week",
"month": "Month",
"month_previous": "Previous month",
"month_next": "Next month",
"year": "Year",
"year_previous": "Previous year",
"year_next": "Next year",
"list": "List"
},
"close_pane_button": {
"close_this_pane": "Close this pane"

View File

@ -59,4 +59,21 @@ body.desktop:not(.zen) .calendar-container .fc-toolbar.fc-header-toolbar {
font-size: 0.85em;
opacity: 0.85;
overflow: hidden;
}
}
/* #region Header */
.calendar-header {
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.calendar-header .btn {
min-width: unset !important;
}
.calendar-header > .title {
flex-grow: 1;
}
/* #endregion */

View File

@ -16,18 +16,50 @@ import date_notes from "../../../services/date_notes";
import appContext from "../../../components/app_context";
import { DateClickArg } from "@fullcalendar/interaction";
import FNote from "../../../entities/fnote";
import Button, { ButtonGroup } from "../../react/Button";
import ActionButton from "../../react/ActionButton";
import { RefObject } from "preact";
interface CalendarViewData {
}
interface CalendarViewData {
type: string;
name: string;
previousText: string;
nextText: string;
}
const CALENDAR_VIEWS = [
"timeGridWeek",
"dayGridMonth",
"multiMonthYear",
"listMonth"
{
type: "timeGridWeek",
name: t("calendar.week"),
previousText: t("calendar.week_previous"),
nextText: t("calendar.week_next")
},
{
type: "dayGridMonth",
name: t("calendar.month"),
previousText: t("calendar.month_previous"),
nextText: t("calendar.month_next")
},
{
type: "multiMonthYear",
name: t("calendar.year"),
previousText: t("calendar.year_previous"),
nextText: t("calendar.year_next")
},
{
type: "listMonth",
name: t("calendar.list"),
previousText: t("calendar.month_previous"),
nextText: t("calendar.month_next")
}
]
const SUPPORTED_CALENDAR_VIEW_TYPE = CALENDAR_VIEWS.map(v => v.type);
// Here we hard-code the imports in order to ensure that they are embedded by webpack without having to load all the languages.
export const LOCALE_MAPPINGS: Record<LOCALE_IDS, (() => Promise<{ default: LocaleInput }>) | null> = {
de: () => import("@fullcalendar/core/locales/de"),
@ -83,20 +115,18 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
return (plugins &&
<div className="calendar-view" ref={containerRef}>
<CalendarHeader calendarRef={calendarRef} />
<Calendar
events={eventBuilder}
calendarRef={calendarRef}
plugins={plugins}
tabIndex={100}
initialView={initialView.current && CALENDAR_VIEWS.includes(initialView.current) ? initialView.current : "dayGridMonth"}
headerToolbar={{
start: "title",
end: `${CALENDAR_VIEWS.join(",")} today prev,next`
}}
initialView={initialView.current && SUPPORTED_CALENDAR_VIEW_TYPE.includes(initialView.current) ? initialView.current : "dayGridMonth"}
headerToolbar={false}
firstDay={firstDayOfWeek ?? 0}
weekends={!hideWeekends}
weekNumbers={weekNumbers}
height="100%"
height="90%"
nowIndicator
handleWindowResize={false}
locale={locale}
@ -113,6 +143,31 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
);
}
function CalendarHeader({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
const currentViewType = calendarRef.current?.view?.type;
const currentViewData = CALENDAR_VIEWS.find(v => calendarRef.current && v.type === currentViewType);
return (
<div className="calendar-header">
<span className="title">{calendarRef.current?.view.title}</span>
<ButtonGroup>
{CALENDAR_VIEWS.map(viewData => (
<Button
text={viewData.name.toLocaleLowerCase()}
className={currentViewType === viewData.type ? "active" : ""}
onClick={() => calendarRef.current?.changeView(viewData.type)}
/>
))}
</ButtonGroup>
<Button text="today" onClick={() => calendarRef.current?.today()} />
<ButtonGroup>
<ActionButton icon="bx bx-chevron-left" text={currentViewData?.previousText ?? ""} frame onClick={() => calendarRef.current?.prev()} />
<ActionButton icon="bx bx-chevron-right" text={currentViewData?.nextText ?? ""} frame onClick={() => calendarRef.current?.next()} />
</ButtonGroup>
</div>
)
}
function usePlugins(isEditable: boolean, isCalendarRoot: boolean) {
const [ plugins, setPlugins ] = useState<PluginDef[]>();

View File

@ -11,18 +11,19 @@ export interface ActionButtonProps {
onClick?: (e: MouseEvent) => void;
triggerCommand?: CommandNames;
noIconActionClass?: boolean;
frame?: boolean;
}
export default function ActionButton({ text, icon, className, onClick, triggerCommand, titlePosition, noIconActionClass }: ActionButtonProps) {
export default function ActionButton({ text, icon, className, onClick, triggerCommand, titlePosition, noIconActionClass, frame }: ActionButtonProps) {
const buttonRef = useRef<HTMLButtonElement>(null);
const [ keyboardShortcut, setKeyboardShortcut ] = useState<string[]>();
useStaticTooltip(buttonRef, {
title: keyboardShortcut?.length ? `${text} (${keyboardShortcut?.join(",")})` : text,
placement: titlePosition ?? "bottom",
fallbackPlacements: [ titlePosition ?? "bottom" ]
});
useEffect(() => {
if (triggerCommand) {
keyboard_actions.getAction(triggerCommand, true).then(action => setKeyboardShortcut(action?.effectiveShortcuts));
@ -31,8 +32,8 @@ export default function ActionButton({ text, icon, className, onClick, triggerCo
return <button
ref={buttonRef}
class={`${className ?? ""} ${!noIconActionClass ? "icon-action" : "btn"} ${icon}`}
onClick={onClick}
class={`${className ?? ""} ${!noIconActionClass ? "icon-action" : "btn"} ${icon} ${frame ? "btn btn-primary" : ""}`}
onClick={onClick}
data-trigger-command={triggerCommand}
/>;
}
}

View File

@ -1,4 +1,4 @@
import type { RefObject } from "preact";
import type { ComponentChildren, RefObject } from "preact";
import type { CSSProperties } from "preact/compat";
import { useMemo } from "preact/hooks";
import { memo } from "preact/compat";
@ -72,4 +72,12 @@ const Button = memo(({ name, buttonRef, className, text, onClick, keyboardShortc
);
});
export default Button;
export function ButtonGroup({ children }: { children: ComponentChildren }) {
return (
<div className="btn-group" role="group">
{children}
</div>
)
}
export default Button;