@gobrand/react-calendar 0.0.19 → 0.0.20

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 +16 -840
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -3,866 +3,42 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@gobrand/react-calendar.svg)](https://www.npmjs.com/package/@gobrand/react-calendar)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
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.
6
+ **React hooks for building calendars with the Temporal API.** Type-safe views, timezone-aware, optimized state management.
7
7
 
8
- ## Installation
8
+ 👉 **[Documentation](https://eng.gobrand.app/calendar)**
9
9
 
10
- ```bash
11
- npm install @gobrand/react-calendar
12
- # or
13
- pnpm add @gobrand/react-calendar
14
- ```
15
-
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
47
-
48
- ## Quick Start
10
+ - **Built on Temporal API** - No Date objects, no moment.js, no date-fns
11
+ - **Timezone-aware** - Native DST handling with IANA timezones
12
+ - **Type-safe** - Conditional methods based on configured views
13
+ - **Multi-view** - Month, week, and day views with time slots
14
+ - **TanStack Store** - Optimized reactive state management
49
15
 
50
- ```tsx
51
- import { useCalendar, createCalendarViews, createCalendarAccessor, getWeekdays } from '@gobrand/react-calendar';
52
- import { Temporal } from '@js-temporal/polyfill';
53
-
54
- type Event = {
55
- id: string;
56
- title: string;
57
- start: Temporal.ZonedDateTime;
58
- };
59
-
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
-
68
- const accessor = createCalendarAccessor<Event>({
69
- getDate: (event) => event.start.toPlainDate(),
70
- getStart: (event) => event.start,
71
- });
72
-
73
- function MyCalendar() {
74
- const calendar = useCalendar({
75
- data: events,
76
- views: createCalendarViews<Event>()({
77
- month: { accessor },
78
- }),
79
- });
80
-
81
- const month = calendar.getMonth();
82
-
83
- return (
84
- <div>
85
- <header>
86
- <button onClick={calendar.previousMonth}>←</button>
87
- <h2>{calendar.getTitle('month')}</h2>
88
- <button onClick={calendar.nextMonth}>→</button>
89
- <button onClick={calendar.goToToday}>Today</button>
90
- </header>
91
-
92
- <div className="grid grid-cols-7 gap-2">
93
- {getWeekdays(1).map(day => (
94
- <div key={day}>{day}</div>
95
- ))}
96
-
97
- {month.weeks.flat().map(day => (
98
- <div
99
- key={day.date.toString()}
100
- className={`
101
- ${!day.isCurrentMonth && 'opacity-40'}
102
- ${day.isToday && 'bg-blue-100'}
103
- `}
104
- >
105
- <div>{day.date.day}</div>
106
- {day.items.map(event => (
107
- <div key={event.id}>{event.title}</div>
108
- ))}
109
- </div>
110
- ))}
111
- </div>
112
- </div>
113
- );
114
- }
115
- ```
116
-
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
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:**
138
16
  ```tsx
139
17
  import { useCalendar, createCalendarViews, createCalendarAccessor } from '@gobrand/react-calendar';
140
18
 
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
- });
153
-
154
19
  const calendar = useCalendar({
155
20
  data: events,
156
- timeZone: 'America/New_York',
157
21
  views: createCalendarViews<Event>()({
158
- month: { accessor },
159
- week: { startHour: 8, endHour: 18, accessor },
160
- day: { startHour: 8, endHour: 18, slotDuration: 30, accessor },
22
+ month: { accessor: createCalendarAccessor({ getDate: (e) => e.date }) },
161
23
  }),
162
24
  });
163
25
 
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()`
175
-
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
26
  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
- });
191
- ```
192
-
193
- ##### `getWeek()`
194
-
195
- Get the current week view. Only available if week view is configured.
196
-
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
201
-
202
- **Example:**
203
- ```tsx
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
- });
212
- ```
213
-
214
- ##### `getDay()`
215
-
216
- Get the current day view. Only available if day view is configured.
217
-
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
223
-
224
- **Example:**
225
- ```tsx
226
- const day = calendar.getDay();
227
-
228
- console.log(day.date, day.isToday);
229
- day.timeSlots.forEach(slot => {
230
- console.log(`${slot.hour}:${slot.minute}`, slot.items);
231
- });
232
- ```
233
-
234
- #### Navigation Methods
235
-
236
- ##### Month Navigation
237
-
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
245
-
246
- ```tsx
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,
383
- });
384
- ```
385
-
386
- ### View Configuration
387
-
388
- #### `createCalendarViews()`
389
-
390
- Create type-safe view configurations. This is a curried function that requires a type parameter.
391
-
392
- **Usage:**
393
- ```tsx
394
- const views = createCalendarViews<TItem>()({
395
- month?: { ... },
396
- week?: { ... },
397
- day?: { ... }
398
- });
399
- ```
400
-
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
- };
27
+ // month.weeks.flat().map(day => ...)
408
28
  ```
409
29
 
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
423
-
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
441
-
442
- **Formatting:**
443
- - `getWeekdays(weekStartsOn?, locale?, format?)` - Localized weekday names
444
- - `getMonthName(month, locale?)` - Localized month name
445
- - `formatTime(time, locale?)` - Format PlainTime
446
-
447
- **Timezone:**
448
- - `getMonthRange(timeZone?, weekStartsOn?)` - Week-aligned month range
449
- - `getWeekRange(timeZone?, weekStartsOn?)` - Current week range
450
- - `getDayRange(timeZone?)` - Today in timezone
451
- - `getCurrentTimeZone()` - Get system timezone
452
- - `convertToTimezone(zdt, timeZone)` - Convert between timezones
453
- - `createZonedDateTime(date, time, timeZone)` - Create ZonedDateTime
454
-
455
- For detailed documentation and examples, see [@gobrand/calendar-core](https://www.npmjs.com/package/@gobrand/calendar-core).
456
-
457
- ## Real World Examples
458
-
459
- ### Example 1: Fetching Calendar Data with useQuery and Date-Range Pagination
460
-
461
- This is the most common real-world pattern: fetching data from an API based on the visible calendar date range. As users navigate the calendar (month/week/day), new data is automatically fetched for that time period.
462
-
463
- ```tsx
464
- import { useQuery } from '@tanstack/react-query';
465
- import { toZonedTime, now } from '@gobrand/tiempo';
466
- import {
467
- useCalendar,
468
- createCalendarViews,
469
- createCalendarAccessor,
470
- type DateRange,
471
- getMonthDateRange,
472
- } from '@gobrand/react-calendar';
473
- import { useState } from 'react';
474
-
475
- type Post = {
476
- id: string;
477
- title: string;
478
- publishedAt: string; // UTC ISO 8601 string from API
479
- status: 'draft' | 'scheduled' | 'published';
480
- };
481
-
482
- type PostWithDateTime = Post & {
483
- zonedDateTime: Temporal.ZonedDateTime;
484
- };
485
-
486
- const TIME_ZONE = 'America/New_York';
487
-
488
- function PostCalendar() {
489
- // Track the current visible date range
490
- const [dateRange, setDateRange] = useState<DateRange>(() =>
491
- getMonthDateRange(now(TIME_ZONE).toPlainDate(), TIME_ZONE)
492
- );
493
-
494
- // Fetch posts for the visible date range
495
- const { data: posts = [], isLoading } = useQuery({
496
- queryKey: ['posts', dateRange?.start.toString(), dateRange?.end.toString()],
497
- queryFn: async () => {
498
- if (!dateRange) return [];
499
-
500
- // Convert date range to UTC ISO strings for API
501
- const filters = {
502
- dateRange: {
503
- start: dateRange.start.toInstant().toString(),
504
- end: dateRange.end.toInstant().toString(),
505
- },
506
- };
507
-
508
- return getPosts(filters);
509
- },
510
- // Convert UTC strings to ZonedDateTime for calendar
511
- select: (posts) =>
512
- posts.map((post) => ({
513
- ...post,
514
- zonedDateTime: toZonedTime(post.publishedAt, TIME_ZONE),
515
- })),
516
- enabled: !!dateRange,
517
- });
518
-
519
- const calendar = useCalendar({
520
- data: posts,
521
- timeZone: TIME_ZONE,
522
- views: createCalendarViews<PostWithDateTime>()({
523
- month: {
524
- accessor: createCalendarAccessor({
525
- getDate: (post) => post.zonedDateTime.toPlainDate(),
526
- getStart: (post) => post.zonedDateTime,
527
- }),
528
- },
529
- }),
530
- // Sync date range when calendar navigation changes
531
- onStateChange: (updater) => {
532
- const newState =
533
- typeof updater === 'function' ? updater(calendar.getState()) : updater;
534
- setDateRange(newState.dateRange);
535
- },
536
- });
537
-
538
- const month = calendar.getMonth();
539
-
540
- return (
541
- <div>
542
- <header>
543
- <button onClick={calendar.previousMonth}>←</button>
544
- <h2>{calendar.getTitle('month')}</h2>
545
- <button onClick={calendar.nextMonth}>→</button>
546
- {isLoading && <span>Loading...</span>}
547
- </header>
548
-
549
- <div className="calendar-grid">
550
- {month.weeks.flat().map((day) => (
551
- <div key={day.date.toString()}>
552
- <div>{day.date.day}</div>
553
- {day.items.map((post) => (
554
- <div key={post.id}>{post.title}</div>
555
- ))}
556
- </div>
557
- ))}
558
- </div>
559
- </div>
560
- );
561
- }
562
- ```
563
-
564
- **Key points:**
565
- - `dateRange` state tracks the currently visible calendar period
566
- - `useQuery` automatically refetches when `dateRange` changes (navigation)
567
- - Date range is converted to UTC ISO strings for the API request
568
- - `select` transforms API responses (UTC ISO strings) to `ZonedDateTime` for the calendar
569
- - `onStateChange` syncs calendar navigation with the date range state
570
- - This pattern works for infinite scroll, cursor-based pagination, or any date-range filtering
571
-
572
- ### Example 2: Event Calendar with Multi-View Support
573
-
574
- A complete event calendar with month, week, and day views, timezone support, and type-safe view switching.
575
-
576
- ```tsx
577
- import {
578
- useCalendar,
579
- createCalendarViews,
580
- createCalendarAccessor,
581
- getWeekdays
582
- } from '@gobrand/react-calendar';
583
- import { Temporal } from '@js-temporal/polyfill';
584
-
585
- type Event = {
586
- id: string;
587
- title: string;
588
- description?: string;
589
- start: Temporal.ZonedDateTime;
590
- end: Temporal.ZonedDateTime;
591
- };
592
-
593
- const events: Event[] = [
594
- {
595
- id: '1',
596
- title: 'Team Standup',
597
- start: Temporal.ZonedDateTime.from('2025-01-20T09:00:00-05:00[America/New_York]'),
598
- end: Temporal.ZonedDateTime.from('2025-01-20T09:30:00-05:00[America/New_York]')
599
- },
600
- {
601
- id: '2',
602
- title: 'Client Meeting',
603
- start: Temporal.ZonedDateTime.from('2025-01-20T14:00:00-05:00[America/New_York]'),
604
- end: Temporal.ZonedDateTime.from('2025-01-20T15:00:00-05:00[America/New_York]')
605
- }
606
- ];
607
-
608
- const accessor = createCalendarAccessor<Event>({
609
- getDate: (event) => event.start.toPlainDate(),
610
- getStart: (event) => event.start,
611
- getEnd: (event) => event.end,
612
- });
613
-
614
- function EventCalendar() {
615
- const calendar = useCalendar({
616
- data: events,
617
- timeZone: 'America/New_York',
618
- views: createCalendarViews<Event>()({
619
- month: { weekStartsOn: 0, accessor },
620
- week: { weekStartsOn: 0, startHour: 8, endHour: 18, slotDuration: 30, accessor },
621
- day: { startHour: 8, endHour: 18, slotDuration: 30, accessor },
622
- }),
623
- });
624
-
625
- const currentView = calendar.getState().currentView;
626
-
627
- return (
628
- <div className="calendar">
629
- {/* Header with view switcher */}
630
- <header>
631
- <div className="view-buttons">
632
- <button
633
- onClick={() => calendar.setCurrentView('month')}
634
- className={currentView === 'month' ? 'active' : ''}
635
- >
636
- Month
637
- </button>
638
- <button
639
- onClick={() => calendar.setCurrentView('week')}
640
- className={currentView === 'week' ? 'active' : ''}
641
- >
642
- Week
643
- </button>
644
- <button
645
- onClick={() => calendar.setCurrentView('day')}
646
- className={currentView === 'day' ? 'active' : ''}
647
- >
648
- Day
649
- </button>
650
- </div>
651
-
652
- <h2>{calendar.getTitle()}</h2>
653
-
654
- <div className="nav-buttons">
655
- {currentView === 'month' && (
656
- <>
657
- <button onClick={calendar.previousMonth}>←</button>
658
- <button onClick={calendar.goToToday}>Today</button>
659
- <button onClick={calendar.nextMonth}>→</button>
660
- </>
661
- )}
662
- {currentView === 'week' && (
663
- <>
664
- <button onClick={calendar.previousWeek}>←</button>
665
- <button onClick={calendar.goToToday}>Today</button>
666
- <button onClick={calendar.nextWeek}>→</button>
667
- </>
668
- )}
669
- {currentView === 'day' && (
670
- <>
671
- <button onClick={calendar.previousDay}>←</button>
672
- <button onClick={calendar.goToToday}>Today</button>
673
- <button onClick={calendar.nextDay}>→</button>
674
- </>
675
- )}
676
- </div>
677
- </header>
678
-
679
- {/* Month View */}
680
- {currentView === 'month' && (
681
- <div className="month-view">
682
- <div className="weekday-headers">
683
- {getWeekdays(0).map(day => (
684
- <div key={day} className="weekday">{day}</div>
685
- ))}
686
- </div>
687
- <div className="month-grid">
688
- {calendar.getMonth().weeks.flat().map(day => (
689
- <div
690
- key={day.date.toString()}
691
- className={`day ${!day.isCurrentMonth ? 'other-month' : ''} ${day.isToday ? 'today' : ''}`}
692
- >
693
- <div className="day-number">{day.date.day}</div>
694
- <div className="events">
695
- {day.items.map(event => (
696
- <div key={event.id} className="event">
697
- {event.title}
698
- </div>
699
- ))}
700
- </div>
701
- </div>
702
- ))}
703
- </div>
704
- </div>
705
- )}
706
-
707
- {/* Week View */}
708
- {currentView === 'week' && (
709
- <div className="week-view">
710
- <div className="weekday-headers">
711
- {calendar.getWeek().days.map(day => (
712
- <div key={day.date.toString()} className="weekday">
713
- <div>{day.date.toLocaleString('en-US', { weekday: 'short' })}</div>
714
- <div className={day.isToday ? 'today' : ''}>{day.date.day}</div>
715
- </div>
716
- ))}
717
- </div>
718
- <div className="week-grid">
719
- {calendar.getWeek().days.map(day => (
720
- <div key={day.date.toString()} className="day-column">
721
- {day.timeSlots?.map((slot, i) => (
722
- <div key={i} className="time-slot">
723
- {slot.items.map(event => (
724
- <div key={event.id} className="event">
725
- {event.title}
726
- </div>
727
- ))}
728
- </div>
729
- ))}
730
- </div>
731
- ))}
732
- </div>
733
- </div>
734
- )}
735
-
736
- {/* Day View */}
737
- {currentView === 'day' && (
738
- <div className="day-view">
739
- {calendar.getDay().timeSlots.map((slot, i) => (
740
- <div key={i} className="time-slot">
741
- <div className="time">{slot.hour}:{String(slot.minute).padStart(2, '0')}</div>
742
- <div className="slot-events">
743
- {slot.items.map(event => (
744
- <div key={event.id} className="event">
745
- <strong>{event.title}</strong>
746
- {event.description && <p>{event.description}</p>}
747
- </div>
748
- ))}
749
- </div>
750
- </div>
751
- ))}
752
- </div>
753
- )}
754
- </div>
755
- );
756
- }
757
- ```
758
-
759
- ### Example 2: Task Due Date Calendar
760
-
761
- Simple calendar showing task due dates without time information.
762
-
763
- ```tsx
764
- import { useCalendar, createCalendarViews, createCalendarAccessor } from '@gobrand/react-calendar';
765
- import { Temporal } from '@js-temporal/polyfill';
766
-
767
- type Task = {
768
- id: string;
769
- title: string;
770
- dueDate: Temporal.PlainDate;
771
- completed: boolean;
772
- };
773
-
774
- const tasks: Task[] = [
775
- { id: '1', title: 'Review PR #42', dueDate: Temporal.PlainDate.from('2025-01-20'), completed: false },
776
- { id: '2', title: 'Write documentation', dueDate: Temporal.PlainDate.from('2025-01-22'), completed: false },
777
- { id: '3', title: 'Deploy to production', dueDate: Temporal.PlainDate.from('2025-01-25'), completed: true },
778
- ];
779
-
780
- function TaskCalendar() {
781
- const calendar = useCalendar({
782
- data: tasks,
783
- views: createCalendarViews<Task>()({
784
- month: {
785
- accessor: createCalendarAccessor({
786
- getDate: (task) => task.dueDate,
787
- })
788
- },
789
- }),
790
- });
791
-
792
- const month = calendar.getMonth();
793
-
794
- return (
795
- <div>
796
- <header>
797
- <button onClick={calendar.previousMonth}>Previous</button>
798
- <h2>{calendar.getTitle('month')}</h2>
799
- <button onClick={calendar.nextMonth}>Next</button>
800
- </header>
801
-
802
- <div className="calendar-grid">
803
- {month.weeks.flat().map(day => (
804
- <div
805
- key={day.date.toString()}
806
- className={!day.isCurrentMonth ? 'dimmed' : ''}
807
- >
808
- <div>{day.date.day}</div>
809
- {day.items.map(task => (
810
- <div
811
- key={task.id}
812
- className={task.completed ? 'completed' : 'pending'}
813
- >
814
- {task.title}
815
- </div>
816
- ))}
817
- </div>
818
- ))}
819
- </div>
820
- </div>
821
- );
822
- }
823
- ```
824
-
825
- > **For vanilla JavaScript examples:** Check out [@gobrand/calendar-core documentation](https://www.npmjs.com/package/@gobrand/calendar-core) for framework-agnostic usage examples.
826
-
827
- ## Browser Support
828
-
829
- 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.
830
-
831
- ```typescript
832
- import { Temporal } from '@js-temporal/polyfill';
833
- ```
834
-
835
- **Requirements:**
836
- - React 18+ or React 19+
837
- - Modern browsers with ES2015+ support
838
-
839
- ## Development
30
+ ## Install
840
31
 
841
32
  ```bash
842
- # Install dependencies
843
- pnpm install
844
-
845
- # Build all packages
846
- pnpm build
847
-
848
- # Run tests
849
- pnpm test --run
850
-
851
- # Type check
852
- pnpm typecheck
853
-
854
- # Release new version
855
- pnpm release <patch|minor|major>
33
+ pnpm add @gobrand/react-calendar
856
34
  ```
857
35
 
858
- ## Contributing
36
+ **Peer dependencies:** React 18+ or 19+
859
37
 
860
- Contributions are welcome! Please feel free to submit a Pull Request.
38
+ ## Docs
861
39
 
862
- ## License
863
-
864
- MIT
40
+ **[eng.gobrand.app/calendar](https://eng.gobrand.app/calendar)** — Full API reference, examples, and guides.
865
41
 
866
- ## Built by Go Brand
42
+ ## License
867
43
 
868
- temporal-calendar is built and maintained by [Go Brand](https://gobrand.app) - a modern social media management platform.
44
+ MIT © [Ruben Costa](https://x.com/PonziChad) / [Go Brand](https://gobrand.app)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobrand/react-calendar",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "React hooks and components for building calendars using the Temporal API",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -31,10 +31,10 @@
31
31
  "bugs": {
32
32
  "url": "https://github.com/go-brand/calendar/issues"
33
33
  },
34
- "homepage": "https://github.com/go-brand/calendar#readme",
34
+ "homepage": "https://eng.gobrand.app/calendar",
35
35
  "dependencies": {
36
36
  "@js-temporal/polyfill": "^0.5.1",
37
- "@gobrand/calendar-core": "^0.0.19"
37
+ "@gobrand/calendar-core": "^0.0.20"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react": "^18.0.0 || ^19.0.0"