@gobrand/react-calendar 0.0.3

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.
package/README.md ADDED
@@ -0,0 +1,303 @@
1
+ # @temporal-calendar/react
2
+
3
+ React hook for building calendars with the Temporal API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @temporal-calendar/react
9
+ ```
10
+
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
17
+
18
+ ## Quick Start
19
+
20
+ ```tsx
21
+ import { useCalendar, createCalendarViews, createCalendarAccessor } from '@temporal-calendar/react';
22
+ import { Temporal } from '@js-temporal/polyfill';
23
+
24
+ type Event = {
25
+ id: string;
26
+ title: string;
27
+ start: Temporal.ZonedDateTime;
28
+ };
29
+
30
+ // Define how to extract dates from your data
31
+ const accessor = createCalendarAccessor<Event>({
32
+ getDate: (event) => event.start.toPlainDate(),
33
+ getStart: (event) => event.start,
34
+ getEnd: (event) => event.start,
35
+ });
36
+
37
+ function MyCalendar() {
38
+ const calendar = useCalendar({
39
+ data: events,
40
+ views: createCalendarViews({
41
+ month: { weekStartsOn: 1, accessor },
42
+ }),
43
+ });
44
+
45
+ const month = calendar.getMonth();
46
+
47
+ return (
48
+ <div>
49
+ <header>
50
+ <button onClick={calendar.previousMonth}>←</button>
51
+ <h2>{calendar.getTitle('month')}</h2>
52
+ <button onClick={calendar.nextMonth}>→</button>
53
+ <button onClick={calendar.goToToday}>Today</button>
54
+ </header>
55
+
56
+ <div className="grid grid-cols-7 gap-2">
57
+ {getWeekdays(1).map(day => (
58
+ <div key={day}>{day}</div>
59
+ ))}
60
+
61
+ {month.weeks.flat().map(day => (
62
+ <div
63
+ key={day.date.toString()}
64
+ className={`
65
+ ${!day.isCurrentMonth && 'opacity-40'}
66
+ ${day.isToday && 'bg-blue-100'}
67
+ `}
68
+ >
69
+ <div>{day.date.day}</div>
70
+ {day.items.map(event => (
71
+ <div key={event.id}>{event.title}</div>
72
+ ))}
73
+ </div>
74
+ ))}
75
+ </div>
76
+ </div>
77
+ );
78
+ }
79
+ ```
80
+
81
+ ## Multi-View Calendar
82
+
83
+ ```tsx
84
+ import { useCalendar, createCalendarViews } from '@temporal-calendar/react';
85
+
86
+ const calendar = useCalendar({
87
+ 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 },
92
+ }),
93
+ });
94
+
95
+ // Switch views
96
+ calendar.setCurrentView('week');
97
+
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
102
+ ```
103
+
104
+ ## API
105
+
106
+ ### `useCalendar<TOptions>(options)`
107
+
108
+ Main hook for calendar state management.
109
+
110
+ **Options:**
111
+ ```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
+ }
119
+ ```
120
+
121
+ **Returns:** `Calendar<T, TOptions>` with methods based on configured views
122
+
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
132
+
133
+ **View-specific methods** (conditionally available based on configured views):
134
+
135
+ **Month view:**
136
+ - `getMonth()` - Returns `{ month: PlainYearMonth, weeks: CalendarWeek<T>[] }`
137
+ - `nextMonth()` / `previousMonth()`
138
+ - `goToMonth(year, month)`
139
+
140
+ **Week view:**
141
+ - `getWeek()` - Returns `{ weekStart, weekEnd, days: CalendarDay<T>[] }`
142
+ - `nextWeek()` / `previousWeek()`
143
+
144
+ **Day view:**
145
+ - `getDay()` - Returns `{ date, timeSlots: { hour, minute, items: T[] }[] }`
146
+ - `nextDay()` / `previousDay()`
147
+
148
+ ### `createCalendarViews<T>(config)`
149
+
150
+ Type-safe builder for view configurations.
151
+
152
+ ```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
+ },
171
+ });
172
+ ```
173
+
174
+ ### `createCalendarAccessor<T>(config)`
175
+
176
+ Define how to extract date information from your data.
177
+
178
+ ```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
183
+ });
184
+ ```
185
+
186
+ ### Utility Functions
187
+
188
+ All utilities from `@temporal-calendar/core` are re-exported:
189
+
190
+ **Formatting:**
191
+ - `getWeekdays(weekStartsOn?)` - Localized weekday names
192
+ - `getMonthName(month, locale?)` - Localized month name
193
+ - `formatTime(time, locale?)` - Format PlainTime
194
+
195
+ **Navigation:**
196
+ - `nextMonth(month)` / `previousMonth(month)`
197
+ - `nextWeek(date)` / `previousWeek(date)`
198
+ - `nextDay(date)` / `previousDay(date)`
199
+ - `goToToday()` - Get current PlainDate
200
+
201
+ **Timezone:**
202
+ - `getMonthRange(timeZone?, weekStartsOn?)` - Week-aligned month range
203
+ - `getWeekRange(timeZone?, weekStartsOn?)` - Current week range
204
+ - `getDayRange(timeZone?)` - Today in timezone
205
+ - `getCurrentTimeZone()` - Get system timezone
206
+ - `convertToTimezone(zdt, timeZone)` - Convert between timezones
207
+ - `createZonedDateTime(date, time, timeZone)` - Create ZonedDateTime
208
+
209
+ **Layout:**
210
+ - `getEventPosition(start, end, startHour, endHour, slotDuration)` - Calculate event positioning for time-based grids
211
+
212
+ ## Data Structures
213
+
214
+ ### `CalendarDay<T>`
215
+
216
+ ```tsx
217
+ {
218
+ date: Temporal.PlainDate;
219
+ isCurrentMonth: boolean;
220
+ isToday: boolean;
221
+ items: T[]; // Your data filtered to this day
222
+ }
223
+ ```
224
+
225
+ ### `CalendarWeek<T>`
226
+
227
+ ```tsx
228
+ CalendarDay<T>[] // Array of 7 days
229
+ ```
230
+
231
+ ### `CalendarMonth<T>`
232
+
233
+ ```tsx
234
+ {
235
+ month: Temporal.PlainYearMonth;
236
+ weeks: CalendarWeek<T>[];
237
+ }
238
+ ```
239
+
240
+ ### `CalendarWeekView<T>`
241
+
242
+ ```tsx
243
+ {
244
+ weekStart: Temporal.PlainDate;
245
+ weekEnd: Temporal.PlainDate;
246
+ days: CalendarDay<T>[];
247
+ }
248
+ ```
249
+
250
+ ### `CalendarDayView<T>`
251
+
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
+ }
261
+ ```
262
+
263
+ ## Type Safety
264
+
265
+ The hook uses TanStack-style type inference to provide conditional types based on your configuration:
266
+
267
+ ```tsx
268
+ // Only month view configured
269
+ const calendar = useCalendar({
270
+ data: events,
271
+ views: createCalendarViews({ month: { ... } }),
272
+ });
273
+
274
+ calendar.getMonth(); // ✅ Available
275
+ calendar.nextMonth(); // ✅ Available
276
+ calendar.getWeek(); // ❌ Type error - week view not configured
277
+
278
+ // All views configured
279
+ const calendar = useCalendar({
280
+ data: events,
281
+ views: createCalendarViews({
282
+ month: { ... },
283
+ week: { ... },
284
+ day: { ... },
285
+ }),
286
+ });
287
+
288
+ calendar.getMonth(); // ✅ Available
289
+ calendar.getWeek(); // ✅ Available
290
+ calendar.getDay(); // ✅ Available
291
+ ```
292
+
293
+ ## Examples
294
+
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)
300
+
301
+ ## License
302
+
303
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var calendarCore = require('@gobrand/calendar-core');
5
+
6
+ // src/index.ts
7
+ function useCalendar(options) {
8
+ const [calendarRef] = react.useState(() => ({
9
+ current: calendarCore.createCalendar(options)
10
+ }));
11
+ const [state, setState] = react.useState(() => calendarRef.current.getState());
12
+ calendarRef.current.setOptions((prev) => ({
13
+ ...prev,
14
+ ...options,
15
+ state: {
16
+ ...state,
17
+ ...options.state
18
+ },
19
+ onStateChange: (updater) => {
20
+ setState(updater);
21
+ options.onStateChange?.(updater);
22
+ }
23
+ }));
24
+ return calendarRef.current;
25
+ }
26
+
27
+ exports.useCalendar = useCalendar;
28
+ Object.keys(calendarCore).forEach(function (k) {
29
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
30
+ enumerable: true,
31
+ get: function () { return calendarCore[k]; }
32
+ });
33
+ });
@@ -0,0 +1,4 @@
1
+ export * from '@gobrand/calendar-core';
2
+ import { type Calendar, type CalendarOptions } from '@gobrand/calendar-core';
3
+ export declare function useCalendar<TOptions extends CalendarOptions<any>>(options: TOptions): Calendar<TOptions['data'] extends (infer T)[] ? T : never, TOptions>;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,wBAAwB,CAAC;AAEvC,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAC;AAChC,wBAAgB,WAAW,CAAC,QAAQ,SAAS,eAAe,CAAC,GAAG,CAAC,EAC/D,OAAO,EAAE,QAAQ,GAChB,QAAQ,CACT,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,EAChD,QAAQ,CACT,CA2BA"}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ import { useState } from 'react';
2
+ import { createCalendar } from '@gobrand/calendar-core';
3
+ export * from '@gobrand/calendar-core';
4
+
5
+ // src/index.ts
6
+ function useCalendar(options) {
7
+ const [calendarRef] = useState(() => ({
8
+ current: createCalendar(options)
9
+ }));
10
+ const [state, setState] = useState(() => calendarRef.current.getState());
11
+ calendarRef.current.setOptions((prev) => ({
12
+ ...prev,
13
+ ...options,
14
+ state: {
15
+ ...state,
16
+ ...options.state
17
+ },
18
+ onStateChange: (updater) => {
19
+ setState(updater);
20
+ options.onStateChange?.(updater);
21
+ }
22
+ }));
23
+ return calendarRef.current;
24
+ }
25
+
26
+ export { useCalendar };
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@gobrand/react-calendar",
3
+ "version": "0.0.3",
4
+ "description": "React hooks and components for building calendars using the Temporal API",
5
+ "private": false,
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "type": "module",
10
+ "main": "./dist/index.js",
11
+ "module": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/go-brand/calendar.git",
29
+ "directory": "packages/react"
30
+ },
31
+ "bugs": {
32
+ "url": "https://github.com/go-brand/calendar/issues"
33
+ },
34
+ "homepage": "https://github.com/go-brand/calendar#readme",
35
+ "scripts": {
36
+ "build": "tsup && tsc --emitDeclarationOnly --declaration --declarationMap",
37
+ "test": "vitest",
38
+ "typecheck": "tsc --noEmit"
39
+ },
40
+ "dependencies": {
41
+ "@js-temporal/polyfill": "^0.5.1",
42
+ "@gobrand/calendar-core": "workspace:*"
43
+ },
44
+ "peerDependencies": {
45
+ "react": "^18.0.0 || ^19.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@testing-library/jest-dom": "^6.6.4",
49
+ "@testing-library/react": "^16.1.0",
50
+ "@testing-library/user-event": "^14.5.2",
51
+ "@types/react": "^19.2.7",
52
+ "@types/react-dom": "^19.2.3",
53
+ "@vitest/ui": "^2.1.8",
54
+ "jsdom": "^25.0.1",
55
+ "react": "19.2.3",
56
+ "react-dom": "19.2.3",
57
+ "tsup": "^8.3.5",
58
+ "typescript": "^5.7.3",
59
+ "vitest": "^2.1.8"
60
+ },
61
+ "keywords": [
62
+ "react",
63
+ "hooks",
64
+ "temporal",
65
+ "calendar",
66
+ "date",
67
+ "datetime",
68
+ "time",
69
+ "temporal-api",
70
+ "react-hooks"
71
+ ]
72
+ }