@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 +303 -0
- package/dist/index.cjs +33 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/package.json +72 -0
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
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|