@dxos/react-ui-calendar 0.8.4-main.c85a9c8dae → 0.8.4-main.e00bdcdb52
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/dist/lib/browser/index.mjs +39 -52
- 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 +39 -52
- 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 +25 -19
- package/src/components/Calendar/Calendar.stories.tsx +2 -2
- package/src/components/Calendar/Calendar.tsx +137 -148
- 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.e00bdcdb52",
|
|
4
4
|
"description": "A calendar component.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -11,53 +11,59 @@
|
|
|
11
11
|
"license": "MIT",
|
|
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/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
42
|
+
"@dxos/async": "0.8.4-main.e00bdcdb52",
|
|
43
|
+
"@dxos/log": "0.8.4-main.e00bdcdb52",
|
|
44
|
+
"@dxos/debug": "0.8.4-main.e00bdcdb52",
|
|
45
|
+
"@dxos/invariant": "0.8.4-main.e00bdcdb52",
|
|
46
|
+
"@dxos/util": "0.8.4-main.e00bdcdb52"
|
|
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/react-ui": "0.8.4-main.
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/
|
|
55
|
+
"vite": "^8.0.10",
|
|
56
|
+
"@dxos/random": "0.8.4-main.e00bdcdb52",
|
|
57
|
+
"@dxos/react-ui": "0.8.4-main.e00bdcdb52",
|
|
58
|
+
"@dxos/storybook-utils": "0.8.4-main.e00bdcdb52",
|
|
59
|
+
"@dxos/ui-theme": "0.8.4-main.e00bdcdb52"
|
|
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/ui
|
|
60
|
-
"@dxos/
|
|
65
|
+
"@dxos/react-ui": "0.8.4-main.e00bdcdb52",
|
|
66
|
+
"@dxos/ui-theme": "0.8.4-main.e00bdcdb52"
|
|
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
|
|
|
@@ -35,7 +35,7 @@ export const Default: Story = {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
export const Column: Story = {
|
|
38
|
-
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
38
|
+
decorators: [withTheme(), withLayout({ layout: 'column', classNames: 'w-auto' })],
|
|
39
39
|
render: () => (
|
|
40
40
|
<Calendar.Root>
|
|
41
41
|
<Panel.Root>
|
|
@@ -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,157 +150,145 @@ 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
|
-
);
|
|
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
|
+
);
|
|
204
208
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
)}
|
|
246
|
-
{border && (
|
|
247
|
-
<div
|
|
248
|
-
role='none'
|
|
249
|
-
className={mx('absolute top-0 left-0 w-full h-full border-2 rounded-full', border)}
|
|
250
|
-
/>
|
|
251
|
-
)}
|
|
252
|
-
{num > 0 && (
|
|
253
|
-
<Icon
|
|
254
|
-
classNames='absolute bottom-0'
|
|
255
|
-
icon={num > 3 ? 'ph--dots-three--regular' : 'ph--dot--regular'}
|
|
256
|
-
size={5}
|
|
257
|
-
/>
|
|
258
|
-
)}
|
|
259
|
-
</div>
|
|
260
|
-
);
|
|
261
|
-
})}
|
|
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} role='none' style={style} className='grid'>
|
|
218
|
+
<div
|
|
219
|
+
role='none'
|
|
220
|
+
className='grid grid-cols-7 bg-input-surface'
|
|
221
|
+
style={{ gridTemplateColumns: `repeat(7, ${size}px)` }}
|
|
222
|
+
>
|
|
223
|
+
{Array.from({ length: 7 }).map((_, i) => {
|
|
224
|
+
const date = getDate(start, index, i, weekStartsOn);
|
|
225
|
+
const border = isSameDay(date, selected)
|
|
226
|
+
? 'border-primary-500'
|
|
227
|
+
: isSameDay(date, today)
|
|
228
|
+
? 'border-amber-500'
|
|
229
|
+
: hasDate(date)
|
|
230
|
+
? 'border-neutral-700 border-dashed'
|
|
231
|
+
: undefined;
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<div
|
|
235
|
+
key={i}
|
|
236
|
+
role='none'
|
|
237
|
+
className={mx('relative flex justify-center items-center cursor-pointer', getBgColor(date))}
|
|
238
|
+
onClick={() => handleDaySelect(date)}
|
|
239
|
+
>
|
|
240
|
+
<span className='text-description'>{date.getDate()}</span>
|
|
241
|
+
{!border && date.getDate() === 1 && (
|
|
242
|
+
<span className='absolute top-0 text-xs text-description'>{format(date, 'MMM')}</span>
|
|
243
|
+
)}
|
|
244
|
+
{border && <div role='none' className={mx('absolute inset-1 border-2 rounded-full', border)} />}
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
})}
|
|
248
|
+
</div>
|
|
262
249
|
</div>
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
[handleDaySelect, getNumAppointments, selected, weekStartsOn],
|
|
268
|
-
);
|
|
250
|
+
);
|
|
251
|
+
},
|
|
252
|
+
[handleDaySelect, hasDate, selected, weekStartsOn],
|
|
253
|
+
);
|
|
269
254
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
255
|
+
return (
|
|
256
|
+
<div
|
|
257
|
+
{...composableProps(props, {
|
|
258
|
+
role: 'none',
|
|
259
|
+
classNames: ['flex flex-col h-full w-full justify-center overflow-hidden', classNames],
|
|
260
|
+
})}
|
|
261
|
+
ref={forwardedRef}
|
|
262
|
+
>
|
|
263
|
+
{/* Day of week labels */}
|
|
264
|
+
<div role='none' className='grid w-full grid-cols-7' style={{ width: defaultWidth }}>
|
|
275
265
|
{days.map((date, i) => (
|
|
276
266
|
<div key={i} role='none' className='flex justify-center p-2 text-sm font-thin'>
|
|
277
267
|
{date}
|
|
278
268
|
</div>
|
|
279
269
|
))}
|
|
280
270
|
</div>
|
|
281
|
-
</div>
|
|
282
271
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
272
|
+
{/* Grid */}
|
|
273
|
+
<div role='none' className='flex flex-col h-full w-full justify-center overflow-hidden' ref={containerRef}>
|
|
274
|
+
<List
|
|
275
|
+
ref={listRef}
|
|
276
|
+
role='none'
|
|
277
|
+
className='scrollbar-none outline-hidden'
|
|
278
|
+
width={width}
|
|
279
|
+
height={maxHeight ?? height}
|
|
280
|
+
rowCount={maxRows}
|
|
281
|
+
rowHeight={size}
|
|
282
|
+
rowRenderer={rowRenderer}
|
|
283
|
+
scrollToAlignment='start'
|
|
284
|
+
onScroll={handleScroll}
|
|
285
|
+
onRowsRendered={() => setInitialized(true)}
|
|
286
|
+
/>
|
|
287
|
+
</div>
|
|
299
288
|
</div>
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
289
|
+
);
|
|
290
|
+
},
|
|
291
|
+
);
|
|
303
292
|
|
|
304
293
|
CalendarGrid.displayName = CALENDAR_GRID_NAME;
|
|
305
294
|
|