@gobrand/react-calendar 0.0.8 → 0.0.10

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.
Files changed (2) hide show
  1. package/README.md +607 -154
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,24 +1,54 @@
1
1
  # @gobrand/react-calendar
2
2
 
3
- React hook for building calendars with the Temporal API.
3
+ [![npm version](https://img.shields.io/npm/v/@gobrand/react-calendar.svg)](https://www.npmjs.com/package/@gobrand/react-calendar)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ React hook for building powerful calendar views with the [Temporal API](https://tc39.es/proposal-temporal/docs/). Built on [@gobrand/calendar-core](https://www.npmjs.com/package/@gobrand/calendar-core) with optimized state management using TanStack Store.
4
7
 
5
8
  ## Installation
6
9
 
7
10
  ```bash
11
+ npm install @gobrand/react-calendar
12
+ # or
8
13
  pnpm add @gobrand/react-calendar
9
14
  ```
10
15
 
11
- ## Philosophy
12
-
13
- - **Data-agnostic**: Works with any data type (events, tasks, posts, etc.)
14
- - **Type-safe**: Full TypeScript support with TanStack-style type inference
15
- - **Zero abstractions**: Direct use of Temporal API primitives
16
- - **Minimal API surface**: Just a hook and utility functions
16
+ **Peer dependencies:**
17
+ - `react ^18.0.0 || ^19.0.0`
18
+
19
+ ## Why @gobrand/react-calendar?
20
+
21
+ Building React calendar UIs is complex: state management, timezone handling, DST transitions, multi-view navigation, and data mapping. **@gobrand/react-calendar** provides a complete React solution:
22
+
23
+ - **🪝 Simple React Hook** - Single `useCalendar()` hook with reactive state management
24
+ - **📅 Multi-view support** - Month, week, and day views with type-safe view switching
25
+ - **🌍 Timezone-aware** - Native timezone support with Temporal API primitives
26
+ - **🎯 Data-agnostic** - Works with any data type through accessor pattern
27
+ - **⚡️ Type-safe** - Full TypeScript support with conditional types based on configured views
28
+ - **⚙️ TanStack Store** - Optimized state management with TanStack Store
29
+ - **🔧 Zero config** - Sensible defaults, customize only what you need
30
+ - **📦 Minimal** - Built on [@gobrand/calendar-core](https://www.npmjs.com/package/@gobrand/calendar-core) for framework-agnostic logic
31
+
32
+ **Key features:**
33
+ - ✅ Built exclusively on Temporal API (no Date objects, no moment.js, no date-fns)
34
+ - ✅ Automatic DST handling and timezone conversions
35
+ - ✅ Type-safe view methods (only available methods for configured views)
36
+ - ✅ Calendar-aware arithmetic (leap years, month-end dates)
37
+ - ✅ Flexible accessor pattern for any data structure
38
+ - ✅ Polyfill included for browser compatibility
39
+ - ✅ All core utilities re-exported for convenience
40
+
41
+ **Perfect for:**
42
+ - React event calendars and schedulers
43
+ - Booking systems and appointment managers
44
+ - Task management with due dates
45
+ - Analytics dashboards with date ranges
46
+ - Any React app that needs calendar navigation
17
47
 
18
48
  ## Quick Start
19
49
 
20
50
  ```tsx
21
- import { useCalendar, createCalendarViews, createCalendarAccessor } from '@gobrand/react-calendar';
51
+ import { useCalendar, createCalendarViews, createCalendarAccessor, getWeekdays } from '@gobrand/react-calendar';
22
52
  import { Temporal } from '@js-temporal/polyfill';
23
53
 
24
54
  type Event = {
@@ -27,17 +57,23 @@ type Event = {
27
57
  start: Temporal.ZonedDateTime;
28
58
  };
29
59
 
30
- // Define how to extract dates from your data
60
+ const events: Event[] = [
61
+ {
62
+ id: '1',
63
+ title: 'Team Meeting',
64
+ start: Temporal.ZonedDateTime.from('2025-01-20T14:00:00-05:00[America/New_York]')
65
+ }
66
+ ];
67
+
31
68
  const accessor = createCalendarAccessor<Event>({
32
69
  getDate: (event) => event.start.toPlainDate(),
33
70
  getStart: (event) => event.start,
34
- getEnd: (event) => event.start,
35
71
  });
36
72
 
37
73
  function MyCalendar() {
38
74
  const calendar = useCalendar({
39
75
  data: events,
40
- views: createCalendarViews({
76
+ views: createCalendarViews<Event>()({
41
77
  month: { weekStartsOn: 1, accessor },
42
78
  }),
43
79
  });
@@ -78,126 +114,336 @@ function MyCalendar() {
78
114
  }
79
115
  ```
80
116
 
81
- ## Multi-View Calendar
117
+ > **Note:** For vanilla JavaScript/TypeScript usage, check out [@gobrand/calendar-core](https://www.npmjs.com/package/@gobrand/calendar-core) which provides the underlying framework-agnostic functions.
118
+
119
+ ## API
120
+
121
+ ### React Hooks
82
122
 
123
+ #### `useCalendar(options)`
124
+
125
+ Create a calendar instance with reactive state management. Returns a Calendar object with type-safe methods based on configured views.
126
+
127
+ **Parameters:**
128
+ - `options` (CalendarOptions): Configuration object
129
+ - `data` (TItem[]): Array of items to display in the calendar
130
+ - `views` (CalendarViewOptions): View configurations (month, week, day)
131
+ - `timeZone` (string, optional): IANA timezone identifier (defaults to system timezone)
132
+ - `state` (Partial<CalendarState>, optional): Initial state override
133
+ - `onStateChange` (function, optional): Callback when state changes
134
+
135
+ **Returns:** `Calendar<TItem, TOptions>` - Calendar instance with conditional methods
136
+
137
+ **Example:**
83
138
  ```tsx
84
- import { useCalendar, createCalendarViews } from '@gobrand/react-calendar';
139
+ import { useCalendar, createCalendarViews, createCalendarAccessor } from '@gobrand/react-calendar';
140
+
141
+ type Event = {
142
+ id: string;
143
+ title: string;
144
+ start: Temporal.ZonedDateTime;
145
+ end?: Temporal.ZonedDateTime;
146
+ };
147
+
148
+ const accessor = createCalendarAccessor<Event>({
149
+ getDate: (event) => event.start.toPlainDate(),
150
+ getStart: (event) => event.start,
151
+ getEnd: (event) => event.end,
152
+ });
85
153
 
86
154
  const calendar = useCalendar({
87
155
  data: events,
88
- views: createCalendarViews({
89
- month: { weekStartsOn: 1, accessor: monthAccessor },
90
- week: { weekStartsOn: 1, startHour: 8, endHour: 18, accessor: weekAccessor },
91
- day: { startHour: 8, endHour: 18, slotDuration: 30, accessor: dayAccessor },
156
+ timeZone: 'America/New_York',
157
+ views: createCalendarViews<Event>()({
158
+ month: { weekStartsOn: 1, accessor },
159
+ week: { weekStartsOn: 1, startHour: 8, endHour: 18, accessor },
160
+ day: { startHour: 8, endHour: 18, slotDuration: 30, accessor },
92
161
  }),
93
162
  });
94
163
 
95
- // Switch views
96
- calendar.setCurrentView('week');
164
+ // Type-safe methods - only available if view is configured
165
+ calendar.getMonth(); // ✓ Available (month view configured)
166
+ calendar.getWeek(); // ✓ Available (week view configured)
167
+ calendar.getDay(); // ✓ Available (day view configured)
168
+ ```
169
+
170
+ ### Core Calendar Methods
171
+
172
+ #### View Methods
173
+
174
+ ##### `getMonth()`
97
175
 
98
- // Get current view data
99
- const month = calendar.getMonth(); // Type-safe! Only available if month view configured
100
- const week = calendar.getWeek(); // Type-safe! Only available if week view configured
101
- const day = calendar.getDay(); // Type-safe! Only available if day view configured
176
+ Get the current month view. Only available if month view is configured.
177
+
178
+ **Returns:** `CalendarMonth<TItem>` - Month grid with weeks and days
179
+ - `weeks` - Array of weeks, each containing 7 days
180
+ - `month` - Temporal.PlainYearMonth for the current month
181
+
182
+ **Example:**
183
+ ```tsx
184
+ const month = calendar.getMonth();
185
+
186
+ month.weeks.forEach(week => {
187
+ week.forEach(day => {
188
+ console.log(day.date, day.items, day.isToday, day.isCurrentMonth);
189
+ });
190
+ });
102
191
  ```
103
192
 
104
- ## API
193
+ ##### `getWeek()`
105
194
 
106
- ### `useCalendar<TOptions>(options)`
195
+ Get the current week view. Only available if week view is configured.
107
196
 
108
- Main hook for calendar state management.
197
+ **Returns:** `CalendarWeekView<TItem>` - Week with days and optional time slots
198
+ - `days` - Array of 7 days in the week
199
+ - `weekStart` - Temporal.PlainDate for the first day
200
+ - `weekEnd` - Temporal.PlainDate for the last day
109
201
 
110
- **Options:**
202
+ **Example:**
111
203
  ```tsx
112
- {
113
- data: T[]; // Your data array
114
- views: CalendarViewOptions<T>; // View configurations
115
- timeZone?: string; // IANA timezone (default: system)
116
- state?: CalendarState; // External state (for controlled mode)
117
- onStateChange?: (state) => void; // State change callback
118
- }
204
+ const week = calendar.getWeek();
205
+
206
+ week.days.forEach(day => {
207
+ console.log(day.date, day.items);
208
+ day.timeSlots?.forEach(slot => {
209
+ console.log(slot.hour, slot.minute, slot.items);
210
+ });
211
+ });
119
212
  ```
120
213
 
121
- **Returns:** `Calendar<T, TOptions>` with methods based on configured views
214
+ ##### `getDay()`
122
215
 
123
- **Common methods:**
124
- - `getState()` - Get current state
125
- - `setState(updater)` - Update state
126
- - `goToToday()` - Navigate to today
127
- - `goToDate(date)` - Navigate to specific date
128
- - `getTitle(view)` - Get formatted title for view
129
- - `dateRange` - Current date range (for data fetching)
130
- - `currentView` - Current view name
131
- - `setCurrentView(view)` - Switch views
216
+ Get the current day view. Only available if day view is configured.
132
217
 
133
- **View-specific methods** (conditionally available based on configured views):
218
+ **Returns:** `CalendarDayView<TItem>` - Day with time slots
219
+ - `date` - Temporal.PlainDate for the day
220
+ - `isToday` - Boolean indicating if this is today
221
+ - `timeSlots` - Array of time slots with items
222
+ - `items` - All items for this day
134
223
 
135
- **Month view:**
136
- - `getMonth()` - Returns `{ month: PlainYearMonth, weeks: CalendarWeek<T>[] }`
137
- - `nextMonth()` / `previousMonth()`
138
- - `goToMonth(year, month)`
224
+ **Example:**
225
+ ```tsx
226
+ const day = calendar.getDay();
139
227
 
140
- **Week view:**
141
- - `getWeek()` - Returns `{ weekStart, weekEnd, days: CalendarDay<T>[] }`
142
- - `nextWeek()` / `previousWeek()`
228
+ console.log(day.date, day.isToday);
229
+ day.timeSlots.forEach(slot => {
230
+ console.log(`${slot.hour}:${slot.minute}`, slot.items);
231
+ });
232
+ ```
143
233
 
144
- **Day view:**
145
- - `getDay()` - Returns `{ date, timeSlots: { hour, minute, items: T[] }[] }`
146
- - `nextDay()` / `previousDay()`
234
+ #### Navigation Methods
147
235
 
148
- ### `createCalendarViews<T>(config)`
236
+ ##### Month Navigation
149
237
 
150
- Type-safe builder for view configurations.
238
+ ```tsx
239
+ calendar.nextMonth(); // Go to next month
240
+ calendar.previousMonth(); // Go to previous month
241
+ calendar.goToMonth(2025, 6); // Go to specific month (year, month)
242
+ ```
243
+
244
+ ##### Week Navigation
151
245
 
152
246
  ```tsx
153
- const views = createCalendarViews<Event>({
154
- month: {
155
- weekStartsOn: 1, // 0 (Sun) - 6 (Sat)
156
- accessor: monthAccessor, // CalendarAccessor<Event>
157
- },
158
- week: {
159
- weekStartsOn: 1,
160
- startHour: 8, // 0-23
161
- endHour: 18, // 0-24
162
- slotDuration: 30, // Minutes
163
- accessor: weekAccessor,
164
- },
165
- day: {
166
- startHour: 8,
167
- endHour: 18,
168
- slotDuration: 30,
169
- accessor: dayAccessor,
170
- },
247
+ calendar.nextWeek(); // Go to next week
248
+ calendar.previousWeek(); // Go to previous week
249
+ ```
250
+
251
+ ##### Day Navigation
252
+
253
+ ```tsx
254
+ calendar.nextDay(); // Go to next day
255
+ calendar.previousDay(); // Go to previous day
256
+ ```
257
+
258
+ ##### Universal Navigation
259
+
260
+ ```tsx
261
+ calendar.goToToday(); // Go to today (works for all views)
262
+ ```
263
+
264
+ #### View Management
265
+
266
+ ##### `setCurrentView(view)`
267
+
268
+ Switch between configured views.
269
+
270
+ **Parameters:**
271
+ - `view` (string): One of the configured view names ('month' | 'week' | 'day')
272
+
273
+ **Example:**
274
+ ```tsx
275
+ calendar.setCurrentView('week'); // Switch to week view
276
+ calendar.setCurrentView('month'); // Switch to month view
277
+ ```
278
+
279
+ ##### `getTitle(view?, locales?, options?)`
280
+
281
+ Get a formatted title for the current view or a specific view.
282
+
283
+ **Parameters:**
284
+ - `view` (string, optional): View name (defaults to current view)
285
+ - `locales` (string | string[], optional): Locale(s) for formatting
286
+ - `options` (Intl.DateTimeFormatOptions, optional): Formatting options
287
+
288
+ **Returns:** `string` - Formatted title
289
+
290
+ **Example:**
291
+ ```tsx
292
+ calendar.getTitle('month'); // "January 2025"
293
+ calendar.getTitle('month', 'es-ES'); // "enero de 2025"
294
+ calendar.getTitle('week'); // "Jan 20 – 26, 2025"
295
+ calendar.getTitle('day', 'en-US', {
296
+ weekday: 'long',
297
+ year: 'numeric',
298
+ month: 'long',
299
+ day: 'numeric'
300
+ }); // "Monday, January 20, 2025"
301
+ ```
302
+
303
+ #### State Management
304
+
305
+ ##### `getState()`
306
+
307
+ Get the current calendar state.
308
+
309
+ **Returns:** `CalendarState` - Current state object
310
+ - `currentView` - Active view name
311
+ - `referenceDate` - Current reference date (PlainDate)
312
+
313
+ ##### `setState(updater)`
314
+
315
+ Update the calendar state.
316
+
317
+ **Parameters:**
318
+ - `updater` (function | object): State update function or partial state object
319
+
320
+ **Example:**
321
+ ```tsx
322
+ // Function updater
323
+ calendar.setState(state => ({
324
+ ...state,
325
+ referenceDate: Temporal.PlainDate.from('2025-12-25')
326
+ }));
327
+
328
+ // Object updater
329
+ calendar.setState({ currentView: 'week' });
330
+ ```
331
+
332
+ ### Accessor Pattern
333
+
334
+ #### `createCalendarAccessor(accessor)`
335
+
336
+ Create a type-safe accessor for mapping your data to calendar dates. This is a type-identity function for TypeScript inference.
337
+
338
+ **Parameters:**
339
+ - `accessor` (CalendarAccessor<TItem>): Accessor configuration
340
+ - `getDate` (required): Extract PlainDate from item
341
+ - `getStart` (optional): Extract ZonedDateTime start time
342
+ - `getEnd` (optional): Extract ZonedDateTime end time
343
+
344
+ **Returns:** Same accessor object with proper types
345
+
346
+ **Example:**
347
+ ```tsx
348
+ // Simple date-based items (tasks, posts)
349
+ type Task = {
350
+ id: string;
351
+ name: string;
352
+ dueDate: Temporal.PlainDate;
353
+ };
354
+
355
+ const taskAccessor = createCalendarAccessor<Task>({
356
+ getDate: (task) => task.dueDate,
357
+ });
358
+
359
+ // Time-based items with start time (events, appointments)
360
+ type Event = {
361
+ id: string;
362
+ title: string;
363
+ start: Temporal.ZonedDateTime;
364
+ };
365
+
366
+ const eventAccessor = createCalendarAccessor<Event>({
367
+ getDate: (event) => event.start.toPlainDate(),
368
+ getStart: (event) => event.start,
369
+ });
370
+
371
+ // Items with start and end times (meetings, bookings)
372
+ type Meeting = {
373
+ id: string;
374
+ title: string;
375
+ start: Temporal.ZonedDateTime;
376
+ end: Temporal.ZonedDateTime;
377
+ };
378
+
379
+ const meetingAccessor = createCalendarAccessor<Meeting>({
380
+ getDate: (meeting) => meeting.start.toPlainDate(),
381
+ getStart: (meeting) => meeting.start,
382
+ getEnd: (meeting) => meeting.end,
171
383
  });
172
384
  ```
173
385
 
174
- ### `createCalendarAccessor<T>(config)`
386
+ ### View Configuration
387
+
388
+ #### `createCalendarViews()`
175
389
 
176
- Define how to extract date information from your data.
390
+ Create type-safe view configurations. This is a curried function that requires a type parameter.
177
391
 
392
+ **Usage:**
178
393
  ```tsx
179
- const accessor = createCalendarAccessor<Event>({
180
- getDate: (item) => Temporal.PlainDate, // Required
181
- getStart?: (item) => Temporal.ZonedDateTime, // For time-based views
182
- getEnd?: (item) => Temporal.ZonedDateTime, // For time-based views
394
+ const views = createCalendarViews<TItem>()({
395
+ month?: { ... },
396
+ week?: { ... },
397
+ day?: { ... }
183
398
  });
184
399
  ```
185
400
 
186
- ### Utility Functions
401
+ #### Month View Options
402
+
403
+ ```tsx
404
+ type MonthViewOptions<TItem> = {
405
+ accessor: CalendarAccessor<TItem>;
406
+ weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; // 0 = Sunday, 1 = Monday, etc.
407
+ };
408
+ ```
409
+
410
+ #### Week View Options
411
+
412
+ ```tsx
413
+ type WeekViewOptions<TItem> = {
414
+ accessor: CalendarAccessor<TItem>;
415
+ weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
416
+ startHour?: number; // Default: 0
417
+ endHour?: number; // Default: 24
418
+ slotDuration?: number; // Minutes per slot, default: 60
419
+ };
420
+ ```
421
+
422
+ #### Day View Options
187
423
 
188
- All utilities from `@gobrand/calendar-core` are re-exported:
424
+ ```tsx
425
+ type DayViewOptions<TItem> = {
426
+ accessor: CalendarAccessor<TItem>;
427
+ startHour?: number; // Default: 0
428
+ endHour?: number; // Default: 24
429
+ slotDuration?: number; // Minutes per slot, default: 60
430
+ };
431
+ ```
432
+
433
+ ### Core Utilities (Re-exported)
434
+
435
+ All utilities from [@gobrand/calendar-core](https://www.npmjs.com/package/@gobrand/calendar-core) are re-exported for convenience. For detailed documentation on these functions, see the [core package documentation](https://www.npmjs.com/package/@gobrand/calendar-core).
436
+
437
+ **Building Functions:**
438
+ - `buildMonth(year, month, options?)` - Build month grid
439
+ - `buildWeek(date, options?)` - Build week view
440
+ - `buildDay(date, options?)` - Build day view with time slots
189
441
 
190
442
  **Formatting:**
191
- - `getWeekdays(weekStartsOn?)` - Localized weekday names
443
+ - `getWeekdays(weekStartsOn?, locale?, format?)` - Localized weekday names
192
444
  - `getMonthName(month, locale?)` - Localized month name
193
445
  - `formatTime(time, locale?)` - Format PlainTime
194
446
 
195
- **Navigation:**
196
- - `nextMonth(month)` / `previousMonth(month)`
197
- - `nextWeek(date)` / `previousWeek(date)`
198
- - `nextDay(date)` / `previousDay(date)`
199
- - `goToToday()` - Get current PlainDate
200
-
201
447
  **Timezone:**
202
448
  - `getMonthRange(timeZone?, weekStartsOn?)` - Week-aligned month range
203
449
  - `getWeekRange(timeZone?, weekStartsOn?)` - Current week range
@@ -206,98 +452,305 @@ All utilities from `@gobrand/calendar-core` are re-exported:
206
452
  - `convertToTimezone(zdt, timeZone)` - Convert between timezones
207
453
  - `createZonedDateTime(date, time, timeZone)` - Create ZonedDateTime
208
454
 
209
- **Layout:**
210
- - `getEventPosition(start, end, startHour, endHour, slotDuration)` - Calculate event positioning for time-based grids
455
+ For detailed documentation and examples, see [@gobrand/calendar-core](https://www.npmjs.com/package/@gobrand/calendar-core).
211
456
 
212
- ## Data Structures
457
+ ## Real World Examples
213
458
 
214
- ### `CalendarDay<T>`
459
+ ### Example 1: Event Calendar with Multi-View Support
460
+
461
+ A complete event calendar with month, week, and day views, timezone support, and type-safe view switching.
215
462
 
216
463
  ```tsx
217
- {
218
- date: Temporal.PlainDate;
219
- isCurrentMonth: boolean;
220
- isToday: boolean;
221
- items: T[]; // Your data filtered to this day
222
- }
223
- ```
464
+ import {
465
+ useCalendar,
466
+ createCalendarViews,
467
+ createCalendarAccessor,
468
+ getWeekdays
469
+ } from '@gobrand/react-calendar';
470
+ import { Temporal } from '@js-temporal/polyfill';
471
+
472
+ type Event = {
473
+ id: string;
474
+ title: string;
475
+ description?: string;
476
+ start: Temporal.ZonedDateTime;
477
+ end: Temporal.ZonedDateTime;
478
+ };
224
479
 
225
- ### `CalendarWeek<T>`
480
+ const events: Event[] = [
481
+ {
482
+ id: '1',
483
+ title: 'Team Standup',
484
+ start: Temporal.ZonedDateTime.from('2025-01-20T09:00:00-05:00[America/New_York]'),
485
+ end: Temporal.ZonedDateTime.from('2025-01-20T09:30:00-05:00[America/New_York]')
486
+ },
487
+ {
488
+ id: '2',
489
+ title: 'Client Meeting',
490
+ start: Temporal.ZonedDateTime.from('2025-01-20T14:00:00-05:00[America/New_York]'),
491
+ end: Temporal.ZonedDateTime.from('2025-01-20T15:00:00-05:00[America/New_York]')
492
+ }
493
+ ];
226
494
 
227
- ```tsx
228
- CalendarDay<T>[] // Array of 7 days
229
- ```
495
+ const accessor = createCalendarAccessor<Event>({
496
+ getDate: (event) => event.start.toPlainDate(),
497
+ getStart: (event) => event.start,
498
+ getEnd: (event) => event.end,
499
+ });
230
500
 
231
- ### `CalendarMonth<T>`
501
+ function EventCalendar() {
502
+ const calendar = useCalendar({
503
+ data: events,
504
+ timeZone: 'America/New_York',
505
+ views: createCalendarViews<Event>()({
506
+ month: { weekStartsOn: 0, accessor },
507
+ week: { weekStartsOn: 0, startHour: 8, endHour: 18, slotDuration: 30, accessor },
508
+ day: { startHour: 8, endHour: 18, slotDuration: 30, accessor },
509
+ }),
510
+ });
232
511
 
233
- ```tsx
234
- {
235
- month: Temporal.PlainYearMonth;
236
- weeks: CalendarWeek<T>[];
512
+ const currentView = calendar.getState().currentView;
513
+
514
+ return (
515
+ <div className="calendar">
516
+ {/* Header with view switcher */}
517
+ <header>
518
+ <div className="view-buttons">
519
+ <button
520
+ onClick={() => calendar.setCurrentView('month')}
521
+ className={currentView === 'month' ? 'active' : ''}
522
+ >
523
+ Month
524
+ </button>
525
+ <button
526
+ onClick={() => calendar.setCurrentView('week')}
527
+ className={currentView === 'week' ? 'active' : ''}
528
+ >
529
+ Week
530
+ </button>
531
+ <button
532
+ onClick={() => calendar.setCurrentView('day')}
533
+ className={currentView === 'day' ? 'active' : ''}
534
+ >
535
+ Day
536
+ </button>
537
+ </div>
538
+
539
+ <h2>{calendar.getTitle()}</h2>
540
+
541
+ <div className="nav-buttons">
542
+ {currentView === 'month' && (
543
+ <>
544
+ <button onClick={calendar.previousMonth}>←</button>
545
+ <button onClick={calendar.goToToday}>Today</button>
546
+ <button onClick={calendar.nextMonth}>→</button>
547
+ </>
548
+ )}
549
+ {currentView === 'week' && (
550
+ <>
551
+ <button onClick={calendar.previousWeek}>←</button>
552
+ <button onClick={calendar.goToToday}>Today</button>
553
+ <button onClick={calendar.nextWeek}>→</button>
554
+ </>
555
+ )}
556
+ {currentView === 'day' && (
557
+ <>
558
+ <button onClick={calendar.previousDay}>←</button>
559
+ <button onClick={calendar.goToToday}>Today</button>
560
+ <button onClick={calendar.nextDay}>→</button>
561
+ </>
562
+ )}
563
+ </div>
564
+ </header>
565
+
566
+ {/* Month View */}
567
+ {currentView === 'month' && (
568
+ <div className="month-view">
569
+ <div className="weekday-headers">
570
+ {getWeekdays(0).map(day => (
571
+ <div key={day} className="weekday">{day}</div>
572
+ ))}
573
+ </div>
574
+ <div className="month-grid">
575
+ {calendar.getMonth().weeks.flat().map(day => (
576
+ <div
577
+ key={day.date.toString()}
578
+ className={`day ${!day.isCurrentMonth ? 'other-month' : ''} ${day.isToday ? 'today' : ''}`}
579
+ >
580
+ <div className="day-number">{day.date.day}</div>
581
+ <div className="events">
582
+ {day.items.map(event => (
583
+ <div key={event.id} className="event">
584
+ {event.title}
585
+ </div>
586
+ ))}
587
+ </div>
588
+ </div>
589
+ ))}
590
+ </div>
591
+ </div>
592
+ )}
593
+
594
+ {/* Week View */}
595
+ {currentView === 'week' && (
596
+ <div className="week-view">
597
+ <div className="weekday-headers">
598
+ {calendar.getWeek().days.map(day => (
599
+ <div key={day.date.toString()} className="weekday">
600
+ <div>{day.date.toLocaleString('en-US', { weekday: 'short' })}</div>
601
+ <div className={day.isToday ? 'today' : ''}>{day.date.day}</div>
602
+ </div>
603
+ ))}
604
+ </div>
605
+ <div className="week-grid">
606
+ {calendar.getWeek().days.map(day => (
607
+ <div key={day.date.toString()} className="day-column">
608
+ {day.timeSlots?.map((slot, i) => (
609
+ <div key={i} className="time-slot">
610
+ {slot.items.map(event => (
611
+ <div key={event.id} className="event">
612
+ {event.title}
613
+ </div>
614
+ ))}
615
+ </div>
616
+ ))}
617
+ </div>
618
+ ))}
619
+ </div>
620
+ </div>
621
+ )}
622
+
623
+ {/* Day View */}
624
+ {currentView === 'day' && (
625
+ <div className="day-view">
626
+ {calendar.getDay().timeSlots.map((slot, i) => (
627
+ <div key={i} className="time-slot">
628
+ <div className="time">{slot.hour}:{String(slot.minute).padStart(2, '0')}</div>
629
+ <div className="slot-events">
630
+ {slot.items.map(event => (
631
+ <div key={event.id} className="event">
632
+ <strong>{event.title}</strong>
633
+ {event.description && <p>{event.description}</p>}
634
+ </div>
635
+ ))}
636
+ </div>
637
+ </div>
638
+ ))}
639
+ </div>
640
+ )}
641
+ </div>
642
+ );
237
643
  }
238
644
  ```
239
645
 
240
- ### `CalendarWeekView<T>`
646
+ ### Example 2: Task Due Date Calendar
647
+
648
+ Simple calendar showing task due dates without time information.
241
649
 
242
650
  ```tsx
243
- {
244
- weekStart: Temporal.PlainDate;
245
- weekEnd: Temporal.PlainDate;
246
- days: CalendarDay<T>[];
651
+ import { useCalendar, createCalendarViews, createCalendarAccessor } from '@gobrand/react-calendar';
652
+ import { Temporal } from '@js-temporal/polyfill';
653
+
654
+ type Task = {
655
+ id: string;
656
+ title: string;
657
+ dueDate: Temporal.PlainDate;
658
+ completed: boolean;
659
+ };
660
+
661
+ const tasks: Task[] = [
662
+ { id: '1', title: 'Review PR #42', dueDate: Temporal.PlainDate.from('2025-01-20'), completed: false },
663
+ { id: '2', title: 'Write documentation', dueDate: Temporal.PlainDate.from('2025-01-22'), completed: false },
664
+ { id: '3', title: 'Deploy to production', dueDate: Temporal.PlainDate.from('2025-01-25'), completed: true },
665
+ ];
666
+
667
+ function TaskCalendar() {
668
+ const calendar = useCalendar({
669
+ data: tasks,
670
+ views: createCalendarViews<Task>()({
671
+ month: {
672
+ weekStartsOn: 1,
673
+ accessor: createCalendarAccessor({
674
+ getDate: (task) => task.dueDate,
675
+ })
676
+ },
677
+ }),
678
+ });
679
+
680
+ const month = calendar.getMonth();
681
+
682
+ return (
683
+ <div>
684
+ <header>
685
+ <button onClick={calendar.previousMonth}>Previous</button>
686
+ <h2>{calendar.getTitle('month')}</h2>
687
+ <button onClick={calendar.nextMonth}>Next</button>
688
+ </header>
689
+
690
+ <div className="calendar-grid">
691
+ {month.weeks.flat().map(day => (
692
+ <div
693
+ key={day.date.toString()}
694
+ className={!day.isCurrentMonth ? 'dimmed' : ''}
695
+ >
696
+ <div>{day.date.day}</div>
697
+ {day.items.map(task => (
698
+ <div
699
+ key={task.id}
700
+ className={task.completed ? 'completed' : 'pending'}
701
+ >
702
+ {task.title}
703
+ </div>
704
+ ))}
705
+ </div>
706
+ ))}
707
+ </div>
708
+ </div>
709
+ );
247
710
  }
248
711
  ```
249
712
 
250
- ### `CalendarDayView<T>`
713
+ > **For vanilla JavaScript examples:** Check out [@gobrand/calendar-core documentation](https://www.npmjs.com/package/@gobrand/calendar-core) for framework-agnostic usage examples.
251
714
 
252
- ```tsx
253
- {
254
- date: Temporal.PlainDate;
255
- timeSlots: {
256
- hour: number;
257
- minute: number;
258
- items: T[]; // Items overlapping this time slot
259
- }[];
260
- }
715
+ ## Browser Support
716
+
717
+ The Temporal API is a Stage 3 TC39 proposal. The polyfill `@js-temporal/polyfill` is included as a dependency, ensuring compatibility across all modern browsers.
718
+
719
+ ```typescript
720
+ import { Temporal } from '@js-temporal/polyfill';
261
721
  ```
262
722
 
263
- ## Type Safety
723
+ **Requirements:**
724
+ - React 18+ or React 19+
725
+ - Modern browsers with ES2015+ support
264
726
 
265
- The hook uses TanStack-style type inference to provide conditional types based on your configuration:
727
+ ## Development
266
728
 
267
- ```tsx
268
- // Only month view configured
269
- const calendar = useCalendar({
270
- data: events,
271
- views: createCalendarViews({ month: { ... } }),
272
- });
729
+ ```bash
730
+ # Install dependencies
731
+ pnpm install
273
732
 
274
- calendar.getMonth(); // Available
275
- calendar.nextMonth(); // ✅ Available
276
- calendar.getWeek(); // ❌ Type error - week view not configured
733
+ # Build all packages
734
+ pnpm build
277
735
 
278
- // All views configured
279
- const calendar = useCalendar({
280
- data: events,
281
- views: createCalendarViews({
282
- month: { ... },
283
- week: { ... },
284
- day: { ... },
285
- }),
286
- });
736
+ # Run tests
737
+ pnpm test --run
287
738
 
288
- calendar.getMonth(); // Available
289
- calendar.getWeek(); // ✅ Available
290
- calendar.getDay(); // ✅ Available
739
+ # Type check
740
+ pnpm typecheck
741
+
742
+ # Release new version
743
+ pnpm release <patch|minor|major>
291
744
  ```
292
745
 
293
- ## Examples
746
+ ## Contributing
294
747
 
295
- See the [demo app](../../apps/demo) for complete examples:
296
- - **Month view**: [PostMonthlyView.tsx](../../apps/demo/src/components/PostMonthlyView.tsx)
297
- - **Week view**: [PostWeeklyView.tsx](../../apps/demo/src/components/PostWeeklyView.tsx)
298
- - **Day view**: [PostDailyView.tsx](../../apps/demo/src/components/PostDailyView.tsx)
299
- - **Custom view**: [PostAgendaView.tsx](../../apps/demo/src/components/PostAgendaView.tsx)
748
+ Contributions are welcome! Please feel free to submit a Pull Request.
300
749
 
301
750
  ## License
302
751
 
303
752
  MIT
753
+
754
+ ## Built by Go Brand
755
+
756
+ temporal-calendar is built and maintained by [Go Brand](https://gobrand.app) - a modern social media management platform.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobrand/react-calendar",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "React hooks and components for building calendars using the Temporal API",
5
5
  "private": false,
6
6
  "publishConfig": {