@dxos/react-ui-calendar 0.8.4-main.8360d9e660 → 0.8.4-main.8baae0fced
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/LICENSE +102 -5
- package/dist/lib/browser/index.mjs +34 -45
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +16 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +34 -45
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/translations.mjs +18 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/components/Calendar/Calendar.d.ts +11 -12
- package/dist/types/src/components/Calendar/Calendar.d.ts.map +1 -1
- package/dist/types/src/components/Calendar/Calendar.stories.d.ts +4 -5
- package/dist/types/src/components/Calendar/Calendar.stories.d.ts.map +1 -1
- package/dist/types/src/components/Calendar/util.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +26 -20
- package/src/components/Calendar/Calendar.stories.tsx +1 -1
- package/src/components/Calendar/Calendar.tsx +138 -143
- package/src/translations.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-calendar",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.8baae0fced",
|
|
4
4
|
"description": "A calendar component.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -8,56 +8,62 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/dxos/dxos"
|
|
10
10
|
},
|
|
11
|
-
"license": "
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
12
12
|
"author": "DXOS.org",
|
|
13
13
|
"type": "module",
|
|
14
|
+
"imports": {
|
|
15
|
+
"#translations": "./src/translations.ts"
|
|
16
|
+
},
|
|
14
17
|
"exports": {
|
|
15
18
|
".": {
|
|
16
19
|
"source": "./src/index.ts",
|
|
17
20
|
"types": "./dist/types/src/index.d.ts",
|
|
18
21
|
"browser": "./dist/lib/browser/index.mjs",
|
|
19
22
|
"node": "./dist/lib/node-esm/index.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./translations": {
|
|
25
|
+
"source": "./src/translations.ts",
|
|
26
|
+
"types": "./dist/types/src/translations.d.ts",
|
|
27
|
+
"browser": "./dist/lib/browser/translations.mjs",
|
|
28
|
+
"node": "./dist/lib/node-esm/translations.mjs"
|
|
20
29
|
}
|
|
21
30
|
},
|
|
22
31
|
"types": "dist/types/src/index.d.ts",
|
|
23
|
-
"typesVersions": {
|
|
24
|
-
"*": {}
|
|
25
|
-
},
|
|
26
32
|
"files": [
|
|
27
33
|
"dist",
|
|
28
34
|
"src"
|
|
29
35
|
],
|
|
30
36
|
"dependencies": {
|
|
31
37
|
"@radix-ui/react-context": "1.1.1",
|
|
32
|
-
"date-fns": "^3.
|
|
38
|
+
"date-fns": "^3.6.0",
|
|
33
39
|
"react-resize-detector": "^11.0.1",
|
|
34
40
|
"react-virtualized": "^9.22.6",
|
|
35
41
|
"react-window": "^2.2.3",
|
|
36
|
-
"@dxos/async": "0.8.4-main.
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/log": "0.8.4-main.
|
|
39
|
-
"@dxos/util": "0.8.4-main.
|
|
40
|
-
"@dxos/
|
|
42
|
+
"@dxos/async": "0.8.4-main.8baae0fced",
|
|
43
|
+
"@dxos/invariant": "0.8.4-main.8baae0fced",
|
|
44
|
+
"@dxos/log": "0.8.4-main.8baae0fced",
|
|
45
|
+
"@dxos/util": "0.8.4-main.8baae0fced",
|
|
46
|
+
"@dxos/debug": "0.8.4-main.8baae0fced"
|
|
41
47
|
},
|
|
42
48
|
"devDependencies": {
|
|
43
49
|
"@types/react": "~19.2.7",
|
|
44
50
|
"@types/react-dom": "~19.2.3",
|
|
45
51
|
"@types/react-virtualized": "^9.22.3",
|
|
46
|
-
"effect": "3.
|
|
52
|
+
"effect": "3.20.0",
|
|
47
53
|
"react": "~19.2.3",
|
|
48
54
|
"react-dom": "~19.2.3",
|
|
49
|
-
"vite": "^
|
|
50
|
-
"@dxos/random": "0.8.4-main.
|
|
51
|
-
"@dxos/ui
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/
|
|
55
|
+
"vite": "^8.0.13",
|
|
56
|
+
"@dxos/random": "0.8.4-main.8baae0fced",
|
|
57
|
+
"@dxos/react-ui": "0.8.4-main.8baae0fced",
|
|
58
|
+
"@dxos/ui-theme": "0.8.4-main.8baae0fced",
|
|
59
|
+
"@dxos/storybook-utils": "0.8.4-main.8baae0fced"
|
|
54
60
|
},
|
|
55
61
|
"peerDependencies": {
|
|
56
|
-
"effect": "3.
|
|
62
|
+
"effect": "3.20.0",
|
|
57
63
|
"react": "~19.2.3",
|
|
58
64
|
"react-dom": "~19.2.3",
|
|
59
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
60
|
-
"@dxos/ui-theme": "0.8.4-main.
|
|
65
|
+
"@dxos/react-ui": "0.8.4-main.8baae0fced",
|
|
66
|
+
"@dxos/ui-theme": "0.8.4-main.8baae0fced"
|
|
61
67
|
},
|
|
62
68
|
"publishConfig": {
|
|
63
69
|
"access": "public"
|
|
@@ -8,7 +8,7 @@ import React from 'react';
|
|
|
8
8
|
import { Panel } from '@dxos/react-ui';
|
|
9
9
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
10
|
|
|
11
|
-
import { translations } from '
|
|
11
|
+
import { translations } from '#translations';
|
|
12
12
|
|
|
13
13
|
import { Calendar } from './Calendar';
|
|
14
14
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { createContext } from '@radix-ui/react-context';
|
|
6
|
-
import { type Day, addDays, differenceInWeeks, format, startOfWeek } from 'date-fns';
|
|
6
|
+
import { type Day, addDays, differenceInWeeks, format, startOfDay, startOfWeek } from 'date-fns';
|
|
7
7
|
import React, {
|
|
8
8
|
type Dispatch,
|
|
9
9
|
type PropsWithChildren,
|
|
@@ -20,10 +20,10 @@ import { useResizeDetector } from 'react-resize-detector';
|
|
|
20
20
|
import { List, type ListProps, type ListRowRenderer } from 'react-virtualized';
|
|
21
21
|
|
|
22
22
|
import { Event } from '@dxos/async';
|
|
23
|
-
import {
|
|
24
|
-
import { mx } from '@dxos/ui-theme';
|
|
23
|
+
import { IconButton, useTranslation } from '@dxos/react-ui';
|
|
24
|
+
import { composable, composableProps, mx } from '@dxos/ui-theme';
|
|
25
25
|
|
|
26
|
-
import { translationKey } from '
|
|
26
|
+
import { translationKey } from '#translations';
|
|
27
27
|
|
|
28
28
|
import { getDate, isSameDay } from './util';
|
|
29
29
|
|
|
@@ -103,9 +103,9 @@ const CalendarRoot = forwardRef<CalendarController, CalendarRootProps>(
|
|
|
103
103
|
|
|
104
104
|
const CALENDAR_TOOLBAR_NAME = 'CalendarHeader';
|
|
105
105
|
|
|
106
|
-
type CalendarToolbarProps =
|
|
106
|
+
type CalendarToolbarProps = {};
|
|
107
107
|
|
|
108
|
-
const CalendarToolbar = ({ classNames, ...props }
|
|
108
|
+
const CalendarToolbar = composable<HTMLDivElement, CalendarToolbarProps>(({ classNames, ...props }, forwardedRef) => {
|
|
109
109
|
const { t } = useTranslation(translationKey);
|
|
110
110
|
const { weekStartsOn, event, index, selected } = useCalendarContext(CALENDAR_TOOLBAR_NAME);
|
|
111
111
|
const top = useMemo(() => getDate(start, index ?? 0, 6, weekStartsOn), [index, weekStartsOn]);
|
|
@@ -117,19 +117,20 @@ const CalendarToolbar = ({ classNames, ...props }: CalendarToolbarProps) => {
|
|
|
117
117
|
|
|
118
118
|
return (
|
|
119
119
|
<div
|
|
120
|
-
{...props
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
{...composableProps(props, {
|
|
121
|
+
role: 'none',
|
|
122
|
+
classNames: ['shrink-0 grid! grid-cols-3 items-center bg-toolbar-surface', classNames],
|
|
123
|
+
})}
|
|
124
|
+
ref={forwardedRef}
|
|
123
125
|
style={{ width: defaultWidth }}
|
|
124
126
|
>
|
|
125
127
|
<div className='flex justify-start'>
|
|
126
128
|
<IconButton
|
|
127
129
|
variant='ghost'
|
|
128
|
-
size={5}
|
|
129
130
|
icon='ph--calendar--regular'
|
|
130
131
|
iconOnly
|
|
131
132
|
classNames='aspect-square'
|
|
132
|
-
label={t('today
|
|
133
|
+
label={t('today.button')}
|
|
133
134
|
onClick={handleToday}
|
|
134
135
|
/>
|
|
135
136
|
</div>
|
|
@@ -137,7 +138,7 @@ const CalendarToolbar = ({ classNames, ...props }: CalendarToolbarProps) => {
|
|
|
137
138
|
<div className='flex justify-end p-2 text-description'>{(selected ?? top).getFullYear()}</div>
|
|
138
139
|
</div>
|
|
139
140
|
);
|
|
140
|
-
};
|
|
141
|
+
});
|
|
141
142
|
|
|
142
143
|
CalendarToolbar.displayName = CALENDAR_TOOLBAR_NAME;
|
|
143
144
|
|
|
@@ -149,146 +150,140 @@ CalendarToolbar.displayName = CALENDAR_TOOLBAR_NAME;
|
|
|
149
150
|
|
|
150
151
|
const CALENDAR_GRID_NAME = 'CalendarGrid';
|
|
151
152
|
|
|
152
|
-
type CalendarGridProps =
|
|
153
|
+
type CalendarGridProps = {
|
|
153
154
|
rows?: number;
|
|
155
|
+
/** Dates to highlight on the grid. Each date that appears in this array receives a border indicator. */
|
|
156
|
+
dates?: Date[];
|
|
154
157
|
onSelect?: (event: { date: Date }) => void;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const CalendarGrid = ({ classNames, rows, onSelect, ...props }: CalendarGridProps) => {
|
|
158
|
-
const { weekStartsOn, event, setIndex, selected, setSelected } = useCalendarContext(CALENDAR_GRID_NAME);
|
|
159
|
-
const { ref: containerRef, width = 0, height = 0 } = useResizeDetector();
|
|
160
|
-
const maxHeight = rows ? rows * size : undefined;
|
|
161
|
-
const listRef = useRef<List>(null);
|
|
162
|
-
const today = useMemo(() => new Date(), []);
|
|
158
|
+
};
|
|
163
159
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
160
|
+
const CalendarGrid = composable<HTMLDivElement, CalendarGridProps>(
|
|
161
|
+
({ classNames, rows, dates = [], onSelect, ...props }, forwardedRef) => {
|
|
162
|
+
const { weekStartsOn, event, setIndex, selected, setSelected } = useCalendarContext(CALENDAR_GRID_NAME);
|
|
163
|
+
const { ref: containerRef, width = 0, height = 0 } = useResizeDetector();
|
|
164
|
+
const maxHeight = rows ? rows * size : undefined;
|
|
165
|
+
const listRef = useRef<List>(null);
|
|
166
|
+
const today = useMemo(() => new Date(), []);
|
|
167
|
+
|
|
168
|
+
// Build a set of ISO date strings (YYYY-MM-DD) for O(1) per-cell lookup.
|
|
169
|
+
const dateSet = useMemo(() => new Set(dates.map((date) => startOfDay(date).toISOString())), [dates]);
|
|
170
|
+
|
|
171
|
+
const hasDate = useCallback((date: Date) => dateSet.has(startOfDay(date).toISOString()), [dateSet]);
|
|
172
|
+
|
|
173
|
+
const [initialized, setInitialized] = useState(false);
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
const index = differenceInWeeks(today, start);
|
|
176
|
+
listRef.current?.scrollToRow(index);
|
|
177
|
+
}, [initialized, start, today]);
|
|
178
|
+
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
return event.on((event) => {
|
|
181
|
+
switch (event.type) {
|
|
182
|
+
case 'scroll': {
|
|
183
|
+
const index = differenceInWeeks(event.date, start);
|
|
184
|
+
listRef.current?.scrollToRow(index);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
177
187
|
}
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
(date: Date) => {
|
|
199
|
-
setSelected((current) => (isSameDay(date, current) ? undefined : date));
|
|
200
|
-
onSelect?.({ date });
|
|
201
|
-
},
|
|
202
|
-
[onSelect],
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
const handleScroll = useCallback<NonNullable<ListProps['onScroll']>>((info) => {
|
|
206
|
-
setIndex(Math.round(info.scrollTop / size));
|
|
207
|
-
}, []);
|
|
188
|
+
});
|
|
189
|
+
}, [event]);
|
|
190
|
+
|
|
191
|
+
const days = useMemo(() => {
|
|
192
|
+
const weekStart = startOfWeek(new Date(), { weekStartsOn });
|
|
193
|
+
return Array.from({ length: 7 }, (_, i) => {
|
|
194
|
+
const day = addDays(weekStart, i);
|
|
195
|
+
return format(day, 'EEE'); // Short day name (Mon, Tue, etc.)
|
|
196
|
+
});
|
|
197
|
+
}, []);
|
|
198
|
+
|
|
199
|
+
// TODO(burdon): Get info by range.
|
|
200
|
+
|
|
201
|
+
const handleDaySelect = useCallback(
|
|
202
|
+
(date: Date) => {
|
|
203
|
+
setSelected((current) => (isSameDay(date, current) ? undefined : date));
|
|
204
|
+
onSelect?.({ date });
|
|
205
|
+
},
|
|
206
|
+
[onSelect],
|
|
207
|
+
);
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
)}
|
|
245
|
-
{num > 0 && (
|
|
246
|
-
<Icon
|
|
247
|
-
classNames='absolute bottom-0'
|
|
248
|
-
icon={num > 3 ? 'ph--dots-three--regular' : 'ph--dot--regular'}
|
|
249
|
-
size={5}
|
|
250
|
-
/>
|
|
251
|
-
)}
|
|
252
|
-
</div>
|
|
253
|
-
);
|
|
254
|
-
})}
|
|
209
|
+
const handleScroll = useCallback<NonNullable<ListProps['onScroll']>>((info) => {
|
|
210
|
+
setIndex(Math.round(info.scrollTop / size));
|
|
211
|
+
}, []);
|
|
212
|
+
|
|
213
|
+
const rowRenderer = useCallback<ListRowRenderer>(
|
|
214
|
+
({ key, index, style }) => {
|
|
215
|
+
const getBgColor = (date: Date) => date.getMonth() % 2 === 0 && 'bg-modal-surface';
|
|
216
|
+
return (
|
|
217
|
+
<div key={key} style={style} className='grid'>
|
|
218
|
+
<div className='grid grid-cols-7 bg-input-surface' style={{ gridTemplateColumns: `repeat(7, ${size}px)` }}>
|
|
219
|
+
{Array.from({ length: 7 }).map((_, i) => {
|
|
220
|
+
const date = getDate(start, index, i, weekStartsOn);
|
|
221
|
+
const border = isSameDay(date, selected)
|
|
222
|
+
? 'border-primary-500'
|
|
223
|
+
: isSameDay(date, today)
|
|
224
|
+
? 'border-amber-500'
|
|
225
|
+
: hasDate(date)
|
|
226
|
+
? 'border-neutral-700 border-dashed'
|
|
227
|
+
: undefined;
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<div
|
|
231
|
+
key={i}
|
|
232
|
+
className={mx('relative flex justify-center items-center cursor-pointer', getBgColor(date))}
|
|
233
|
+
onClick={() => handleDaySelect(date)}
|
|
234
|
+
>
|
|
235
|
+
<span className='text-description'>{date.getDate()}</span>
|
|
236
|
+
{!border && date.getDate() === 1 && (
|
|
237
|
+
<span className='absolute top-0 text-xs text-description'>{format(date, 'MMM')}</span>
|
|
238
|
+
)}
|
|
239
|
+
{border && <div className={mx('absolute inset-1 border-2 rounded-full', border)} />}
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
})}
|
|
243
|
+
</div>
|
|
255
244
|
</div>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
);
|
|
245
|
+
);
|
|
246
|
+
},
|
|
247
|
+
[handleDaySelect, hasDate, selected, weekStartsOn],
|
|
248
|
+
);
|
|
261
249
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
250
|
+
return (
|
|
251
|
+
<div
|
|
252
|
+
{...composableProps(props, {
|
|
253
|
+
role: 'none',
|
|
254
|
+
classNames: ['flex flex-col h-full w-full justify-center overflow-hidden', classNames],
|
|
255
|
+
})}
|
|
256
|
+
ref={forwardedRef}
|
|
257
|
+
>
|
|
258
|
+
{/* Day of week labels */}
|
|
259
|
+
<div className='grid w-full grid-cols-7' style={{ width: defaultWidth }}>
|
|
260
|
+
{days.map((date, i) => (
|
|
261
|
+
<div key={i} className='flex justify-center p-2 text-sm font-thin'>
|
|
262
|
+
{date}
|
|
263
|
+
</div>
|
|
264
|
+
))}
|
|
265
|
+
</div>
|
|
272
266
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
267
|
+
{/* Grid */}
|
|
268
|
+
<div className='flex flex-col h-full w-full justify-center overflow-hidden' ref={containerRef}>
|
|
269
|
+
<List
|
|
270
|
+
ref={listRef}
|
|
271
|
+
role='none'
|
|
272
|
+
className='scrollbar-none outline-hidden'
|
|
273
|
+
width={width}
|
|
274
|
+
height={maxHeight ?? height}
|
|
275
|
+
rowCount={maxRows}
|
|
276
|
+
rowHeight={size}
|
|
277
|
+
rowRenderer={rowRenderer}
|
|
278
|
+
scrollToAlignment='start'
|
|
279
|
+
onScroll={handleScroll}
|
|
280
|
+
onRowsRendered={() => setInitialized(true)}
|
|
281
|
+
/>
|
|
282
|
+
</div>
|
|
288
283
|
</div>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
284
|
+
);
|
|
285
|
+
},
|
|
286
|
+
);
|
|
292
287
|
|
|
293
288
|
CalendarGrid.displayName = CALENDAR_GRID_NAME;
|
|
294
289
|
|