Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: calendar core #10

Open
wants to merge 128 commits into
base: v0
Choose a base branch
from

Conversation

bart-krakowski
Copy link

@bart-krakowski bart-krakowski commented Jun 13, 2024

Calendar Core

Summary

The CalendarCore class provides a set of functionalities for managing calendar events, view modes, and period navigation. This class is designed to be used in various calendar applications where precise date management and event handling are required.

Motivation

The motivation behind this class is to provide a reusable and efficient core for calendar functionalities that can be integrated into different applications, ensuring consistent and simplified management of calendar events, view modes, and period navigation.

Parameters

  • weekStartsOn?: number
    • An optional number that specifies the day of the week that the calendar should start on. It defaults to 1 (Monday).
  • events?: TEvent[]
    • An optional array of events that the calendar should display.
  • viewMode: ViewMode
    • An object that specifies the initial view mode of the calendar.
  • locale?: Parameters<Temporal.PlainDate['toLocaleString']>['0']
    • An optional string that specifies the locale to use for formatting dates and times.

Returns

  • getDaysWithEvents(): Array<Day<TEvent>>
    • Returns an array of days in the current period with their associated events.
  • getDaysNames(): string[]
    • Returns an array of strings representing the names of the days of the week based on the locale and week start day.
  • changeViewMode(newViewMode: ViewMode): void
    • Changes the view mode of the calendar.
  • goToPreviousPeriod(): void
    • Navigates to the previous period based on the current view mode.
  • goToNextPeriod(): void
    • Navigates to the next period based on the current view mode.
  • goToCurrentPeriod(): void
    • Navigates to the current period.
  • goToSpecificPeriod(date: Temporal.PlainDate): void
    • Navigates to a specific period based on the provided date.
  • updateCurrentTime(): void
    • Updates the current time.
  • getEventProps(id: Event['id']): { style: CSSProperties } | null
    • Retrieves the style properties for a specific event based on its ID.
  • getCurrentTimeMarkerProps(): { style: CSSProperties; currentTime: string | undefined }
    • Retrieves the style properties and current time for the current time marker.
  • groupDaysBy(props: Omit<GroupDaysByProps<TEvent>, 'weekStartsOn'>): (Day<TEvent> | null)[][]
    • Groups the days in the current period by a specified unit. The fillMissingDays parameter can be used to fill in missing days with previous or next month's days.

Example Usage

import { CalendarCore, Event } from '@tanstack/time';
import { Temporal } from '@js-temporal/polyfill';

interface MyEvent extends Event {
  location: string;
}

const events: MyEvent[] = [
  {
    id: '1',
    startDate: Temporal.PlainDateTime.from('2024-06-10T09:00'),
    endDate: Temporal.PlainDateTime.from('2024-06-10T10:00'),
    title: 'Event 1',
    location: 'Room 101',
  },
  {
    id: '2',
    startDate: Temporal.PlainDateTime.from('2024-06-12T11:00'),
    endDate: Temporal.PlainDateTime.from('2024-06-12T12:00'),
    title: 'Event 2',
    location: 'Room 202',
  },
];

const calendarCore = new CalendarCore<MyEvent>({
  weekStartsOn: 1,
  viewMode: { value: 1, unit: 'month' },
  events,
  locale: 'en-US',
});

// Get days with events
const daysWithEvents = calendarCore.getDaysWithEvents();
console.log(daysWithEvents);

// Change view mode to week
calendarCore.changeViewMode({ value: 1, unit: 'week' });

// Navigate to the next period
calendarCore.goToNextPeriod();

// Update current time
calendarCore.updateCurrentTime();

// Get event properties
const eventProps = calendarCore.getEventProps('1');
console.log(eventProps);

// Get current time marker properties
const currentTimeMarkerProps = calendarCore.getCurrentTimeMarkerProps();
console.log(currentTimeMarkerProps);

// Group days by week
const groupedDays = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'week' });
console.log(groupedDays);

useCalendar hook

Summary

The useCalendar hook provides a comprehensive set of functionalities for managing calendar events, view modes, and period navigation.

Motivation

The motivation behind this hook is to provide a reusable and efficient way to integrate calendar functionalities into React components, ensuring consistent and simplified management of calendar events, view modes, and period navigation.

Parameters

  • weekStartsOn?: number
    • This parameter is an optional number that specifies the day of the week that the calendar should start on. It defaults to 0, which is Sunday.
  • events: Event[]
    • This parameter is an array of events that the calendar should display.
  • viewMode: 'month' | 'week' | number
    • This parameter is a string that specifies the initial view mode of the calendar. It can be either 'month', 'week', or a number representing the number of days in a custom view mode.
  • locale?: string
    • This parameter is an optional string that specifies the locale to use for formatting dates and times. It defaults to the system locale.
  • onChangeViewMode?: ({ value: number; unit: "month" | "week" | "day"; }) => void
    • This parameter is an optional callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument.
  • onChangeViewMode?: (viewMode: value: number; unit: "month" | "week" | "day";) => void
    • This parameter is an optional callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument.
  • reducer?: (state: CalendarState, action: CalendarAction) => CalendarState
    • This parameter is an optional custom reducer function that can be used to manage the state of the calendar.

Returns

  • firstDayOfPeriod: Temporal.PlainDate
    • This value represents the first day of the current period displayed by the calendar.
  • currentPeriod: string
    • This value represents a string that describes the current period displayed by the calendar.
  • goToPreviousPeriod: MouseEventHandler<HTMLButtonElement>
    • This function is a click event handler that navigates to the previous period.
  • goToNextPeriod: MouseEventHandler<HTMLButtonElement>
    • This function is a click event handler that navigates to the next period.
  • goToCurrentPeriod: MouseEventHandler<HTMLButtonElement>
    • This function is a click event handler that navigates to the current period.
  • goToSpecificPeriod: (date: Temporal.PlainDate) => void
    • This function is a callback function that is called when a date is selected in the calendar. It receives the selected date as an argument.
  • days: Day[]
    • This value represents an array of days in the current period displayed by the calendar.
  • daysNames: string[]
    • This value represents an array of strings that contain the names of the days of the week.
  • viewMode: 'month' | 'week' | number
    • This value represents the current view mode of the calendar.
  • changeViewMode: (newViewMode: 'month' | 'week' | number) => void
    • This function is used to change the view mode of the calendar.
  • getEventProps: (id: string) => { style: CSSProperties } | null
    • This function is used to retrieve the style properties for a specific event based on its ID.
  • getEventProps: (id: string) => { style: CSSProperties } | null
    • This function is used to retrieve the style properties for a specific event based on its ID.
  • getEventProps: (id: string) => { style: CSSProperties } | null
    • This function is used to retrieve the style properties for a specific event based on its ID.
  • getCurrentTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }
    • This function is used to retrieve the style properties and current time for the current time marker.
  • isPending: boolean
    • This value represents whether the calendar is in a pending state.
  • groupDaysBy: ({ days: Day[], unit: 'week' | 'month', fillMissingDays?: boolean }) => Day[][]
    • This function is used to group the days in the current period by a specified unit. The fillMissingDays parameter can be used to fill in missing days with previous or next month's days.

Example Usage

const CalendarComponent = ({ events }) => {
  const {
    firstDayOfPeriod,
    currentPeriod,
    goToPreviousPeriod,
    goToNextPeriod,
    goToCurrentPeriod,
    goToSpecificPeriod,
    changeViewMode,
    days,
    daysNames,
    viewMode,
    getEventProps,
    getCurrentTimeMarkerProps,
    groupDaysBy,
  } = useCalendar({
    weekStartsOn: 1,
    viewMode: { value: 1, unit: 'month' },
    locale: 'en-US',
    onChangeViewMode: (newViewMode) => console.log('View mode changed:', newViewMode),
  });

  return (
    <div className="calendar-container">
      <div className="calendar-header">
        <button onClick={goToPreviousPeriod}>Previous</button>
        <button onClick={goToCurrentPeriod}>Today</button>
        <button onClick={goToNextPeriod}>Next</button>
      </div>
      <div className="calendar-view-mode">
        <button onClick={() => changeViewMode({ value: 1, unit: 'month' })}>Month View</button>
        <button onClick={() => changeViewMode({ value: 1, unit: 'week' })}>Week View</button>
        <button onClick={() => changeViewMode({ value: 3, unit: 'day' })}>3-Day View</button>
        <button onClick={() => changeViewMode({ value: 1, unit: 'day' })}>1-Day View</button>
      </div>
      <table className="calendar-table">
        {viewMode.unit === 'month' && (
          groupDaysBy(days, 'months').map((month, monthIndex) => (
            <tbody key={monthIndex} className="calendar-month">
              <tr>
                <th colSpan={7} className="calendar-month-name">
                  {month[0]?.date.toLocaleString('default', { month: 'long' })}{' '}
                  {month[0]?.date.year}
                </th>
              </tr>
              <tr>
                {daysNames.map((dayName, index) => (
                  <th key={index} className="calendar-day-name">
                    {dayName}
                  </th>
                ))}
              </tr>
              {groupDaysBy(month, 'weeks').map((week, weekIndex) => (
                <tr key={weekIndex} className="calendar-week">
                  {week.map((day) => (
                    <td
                      key={day.date.toString()}
                      className={`calendar-day ${day.isToday ? 'today' : ''
                        } ${day.isInCurrentPeriod ? 'current' : 'not-current'}`}
                    >
                      <div className="calendar-date">{day.date.day}</div>
                      <div className="calendar-events">
                        {day.events.map((event) => (
                          <div
                            key={event.id}
                            className="calendar-event"
                            style={getEventProps(event.id)?.style}
                          >
                            {event.title}
                          </div>
                        ))}
                      </div>
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          ))
        )}

        {viewMode.unit === 'week' && (
          <tbody className="calendar-week">
            <tr>
              {daysNames.map((dayName, index) => (
                <th key={index} className="calendar-day-name">
                  {dayName}
                </th>
              ))}
            </tr>
            {groupDaysBy(days, 'weeks').map((week, weekIndex) => (
              <tr key={weekIndex}>
                {week.map((day) => (
                  <td
                    key={day.date.toString()}
                    className={`calendar-day ${day.isToday ? 'today' : ''
                      } ${day.isInCurrentPeriod ? 'current' : 'not-current'}`}
                  >
                    <div className="calendar-date">{day.date.day}</div>
                    <div className="calendar-events">
                      {day.events.map((event) => (
                        <div
                          key={event.id}
                          className="calendar-event"
                          style={getEventProps(event.id)?.style}
                        >
                          {event.title}
                        </div>
                      ))}
                    </div>
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        )}

        {viewMode.unit === 'day' && (
          <tbody className="calendar-day">
            <tr>
              {daysNames.map((dayName, index) => (
                <th key={index} className="calendar-day-name">
                  {dayName}
                </th>
              ))}
            </tr>
            <tr>
              {days.map((day) => (
                <td
                  key={day.date.toString()}
                  className={`calendar-day ${day.isToday ? 'today' : ''
                    } ${day.isInCurrentPeriod ? 'current' : 'not-current'}`}
                >
                  <div className="calendar-date">{day.date.day}</div>
                  <div className="calendar-events">
                    {day.events.map((event) => (
                      <div
                        key={event.id}
                        className="calendar-event"
                        style={getEventProps(event.id)?.style}
                      >
                        {event.title}
                      </div>
                    ))}
                  </div>
                </td>
              ))}
            </tr>
          </tbody>
        )}
      </table>
    </div>
  );
};

packages/time/src/utils/endOf.ts Outdated Show resolved Hide resolved
packages/time/src/utils/startOf.ts Outdated Show resolved Hide resolved
packages/time/src/calendar/generateDateRange.ts Outdated Show resolved Hide resolved
Copy link

@cutterbl cutterbl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the discussion in the time-maintainers Discord channel

* @returns {Temporal.ZonedDateTime} The end of the given unit.
*/
export const endOf = (date: Temporal.ZonedDateTime, unit: 'day' | 'week' | 'month' | 'year' | 'workWeek' | 'decade'): Temporal.ZonedDateTime => {
export function endOf({
date,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the direction here. I started a discussion in the time-maintainers discord around these types of methods...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants