@dxos/react-ui-calendar 0.8.4-main.1068cf700f
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 +8 -0
- package/README.md +1 -0
- package/dist/lib/browser/index.mjs +261 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/node-esm/index.mjs +263 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/types/src/components/Calendar/Calendar.d.ts +47 -0
- package/dist/types/src/components/Calendar/Calendar.d.ts.map +1 -0
- package/dist/types/src/components/Calendar/Calendar.stories.d.ts +27 -0
- package/dist/types/src/components/Calendar/Calendar.stories.d.ts.map +1 -0
- package/dist/types/src/components/Calendar/index.d.ts +2 -0
- package/dist/types/src/components/Calendar/index.d.ts.map +1 -0
- package/dist/types/src/components/Calendar/util.d.ts +4 -0
- package/dist/types/src/components/Calendar/util.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +9 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +175 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +66 -0
- package/src/components/Calendar/Calendar.stories.tsx +76 -0
- package/src/components/Calendar/Calendar.tsx +324 -0
- package/src/components/Calendar/index.ts +5 -0
- package/src/components/Calendar/util.ts +22 -0
- package/src/components/index.ts +5 -0
- package/src/index.ts +5 -0
- package/src/translations.ts +17 -0
- package/src/types.ts +193 -0
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dxos/react-ui-calendar",
|
|
3
|
+
"version": "0.8.4-main.1068cf700f",
|
|
4
|
+
"description": "A calendar component.",
|
|
5
|
+
"homepage": "https://dxos.org",
|
|
6
|
+
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "DXOS.org",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"source": "./src/index.ts",
|
|
17
|
+
"types": "./dist/types/src/index.d.ts",
|
|
18
|
+
"browser": "./dist/lib/browser/index.mjs",
|
|
19
|
+
"node": "./dist/lib/node-esm/index.mjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"types": "dist/types/src/index.d.ts",
|
|
23
|
+
"typesVersions": {
|
|
24
|
+
"*": {}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@radix-ui/react-context": "1.1.1",
|
|
32
|
+
"date-fns": "^3.3.1",
|
|
33
|
+
"react-resize-detector": "^11.0.1",
|
|
34
|
+
"react-virtualized": "^9.22.6",
|
|
35
|
+
"react-window": "^2.2.3",
|
|
36
|
+
"@dxos/async": "0.8.4-main.1068cf700f",
|
|
37
|
+
"@dxos/debug": "0.8.4-main.1068cf700f",
|
|
38
|
+
"@dxos/invariant": "0.8.4-main.1068cf700f",
|
|
39
|
+
"@dxos/log": "0.8.4-main.1068cf700f",
|
|
40
|
+
"@dxos/util": "0.8.4-main.1068cf700f"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/react": "~19.2.7",
|
|
44
|
+
"@types/react-dom": "~19.2.3",
|
|
45
|
+
"@types/react-virtualized": "^9.22.3",
|
|
46
|
+
"@types/react-window": "^2.0.0",
|
|
47
|
+
"effect": "3.19.16",
|
|
48
|
+
"react": "~19.2.3",
|
|
49
|
+
"react-dom": "~19.2.3",
|
|
50
|
+
"vite": "7.1.9",
|
|
51
|
+
"@dxos/react-ui": "0.8.4-main.1068cf700f",
|
|
52
|
+
"@dxos/random": "0.8.4-main.1068cf700f",
|
|
53
|
+
"@dxos/ui-theme": "0.8.4-main.1068cf700f",
|
|
54
|
+
"@dxos/storybook-utils": "0.8.4-main.1068cf700f"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"effect": "3.19.16",
|
|
58
|
+
"react": "~19.2.3",
|
|
59
|
+
"react-dom": "~19.2.3",
|
|
60
|
+
"@dxos/react-ui": "0.8.4-main.1068cf700f",
|
|
61
|
+
"@dxos/ui-theme": "0.8.4-main.1068cf700f"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
9
|
+
|
|
10
|
+
import { translations } from '../../translations';
|
|
11
|
+
|
|
12
|
+
import { Calendar } from './Calendar';
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
title: 'ui/react-ui-calendar/Calendar',
|
|
16
|
+
component: Calendar.Grid,
|
|
17
|
+
decorators: [withTheme()],
|
|
18
|
+
parameters: {
|
|
19
|
+
layout: 'centered',
|
|
20
|
+
translations,
|
|
21
|
+
},
|
|
22
|
+
} satisfies Meta<typeof Calendar.Grid>;
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof meta>;
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
render: () => (
|
|
30
|
+
<Calendar.Root>
|
|
31
|
+
<Calendar.Viewport>
|
|
32
|
+
<Calendar.Toolbar />
|
|
33
|
+
<Calendar.Grid rows={6} />
|
|
34
|
+
</Calendar.Viewport>
|
|
35
|
+
</Calendar.Root>
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Border: Story = {
|
|
40
|
+
render: () => (
|
|
41
|
+
<Calendar.Root>
|
|
42
|
+
<Calendar.Viewport classNames='bg-modalSurface border border-separator rounded'>
|
|
43
|
+
<Calendar.Toolbar />
|
|
44
|
+
<Calendar.Grid rows={6} />
|
|
45
|
+
</Calendar.Viewport>
|
|
46
|
+
</Calendar.Root>
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const Column: Story = {
|
|
51
|
+
render: () => (
|
|
52
|
+
<div className='absolute inset-0 flex bs-full justify-center'>
|
|
53
|
+
<Calendar.Root>
|
|
54
|
+
<Calendar.Viewport>
|
|
55
|
+
<Calendar.Toolbar />
|
|
56
|
+
<Calendar.Grid />
|
|
57
|
+
</Calendar.Viewport>
|
|
58
|
+
</Calendar.Root>
|
|
59
|
+
</div>
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Mobile: Story = {
|
|
64
|
+
render: () => (
|
|
65
|
+
<div className='absolute inset-0 flex bs-full justify-center'>
|
|
66
|
+
<div className='flex bs-full is-[400px] justify-center'>
|
|
67
|
+
<Calendar.Root>
|
|
68
|
+
<Calendar.Viewport classNames='is-full'>
|
|
69
|
+
<Calendar.Toolbar />
|
|
70
|
+
<Calendar.Grid />
|
|
71
|
+
</Calendar.Viewport>
|
|
72
|
+
</Calendar.Root>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
),
|
|
76
|
+
};
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
|
6
|
+
import { type Day, addDays, differenceInWeeks, format, startOfWeek } from 'date-fns';
|
|
7
|
+
import React, {
|
|
8
|
+
type Dispatch,
|
|
9
|
+
type PropsWithChildren,
|
|
10
|
+
type SetStateAction,
|
|
11
|
+
forwardRef,
|
|
12
|
+
useCallback,
|
|
13
|
+
useEffect,
|
|
14
|
+
useImperativeHandle,
|
|
15
|
+
useMemo,
|
|
16
|
+
useRef,
|
|
17
|
+
useState,
|
|
18
|
+
} from 'react';
|
|
19
|
+
import { useResizeDetector } from 'react-resize-detector';
|
|
20
|
+
import { List, type ListProps, type ListRowRenderer } from 'react-virtualized';
|
|
21
|
+
|
|
22
|
+
import { Event } from '@dxos/async';
|
|
23
|
+
import { Icon, IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
24
|
+
import { mx } from '@dxos/ui-theme';
|
|
25
|
+
|
|
26
|
+
import { translationKey } from '../../translations';
|
|
27
|
+
|
|
28
|
+
import { getDate, isSameDay } from './util';
|
|
29
|
+
|
|
30
|
+
const maxRows = 50 * 100;
|
|
31
|
+
const start = new Date('1970-01-01');
|
|
32
|
+
const size = 48;
|
|
33
|
+
const defaultWidth = 7 * size;
|
|
34
|
+
|
|
35
|
+
//
|
|
36
|
+
// Context
|
|
37
|
+
//
|
|
38
|
+
|
|
39
|
+
type CalendarEvent = {
|
|
40
|
+
type: 'scroll';
|
|
41
|
+
date: Date;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type CalendarContextValue = {
|
|
45
|
+
weekStartsOn: Day;
|
|
46
|
+
event: Event<CalendarEvent>;
|
|
47
|
+
index: number | undefined;
|
|
48
|
+
setIndex: Dispatch<SetStateAction<number | undefined>>;
|
|
49
|
+
selected: Date | undefined;
|
|
50
|
+
setSelected: Dispatch<SetStateAction<Date | undefined>>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const [CalendarContextProvider, useCalendarContext] = createContext<CalendarContextValue>('Calendar');
|
|
54
|
+
|
|
55
|
+
//
|
|
56
|
+
// Controller
|
|
57
|
+
//
|
|
58
|
+
|
|
59
|
+
type CalendarController = {
|
|
60
|
+
scrollTo: (date: Date) => void;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
//
|
|
64
|
+
// Root
|
|
65
|
+
//
|
|
66
|
+
|
|
67
|
+
type CalendarRootProps = PropsWithChildren<Partial<Pick<CalendarContextValue, 'weekStartsOn'>>>;
|
|
68
|
+
|
|
69
|
+
const CalendarRoot = forwardRef<CalendarController, CalendarRootProps>(
|
|
70
|
+
({ children, weekStartsOn = 1 }, forwardedRef) => {
|
|
71
|
+
const event = useMemo(() => new Event<CalendarEvent>(), []);
|
|
72
|
+
const [selected, setSelected] = useState<Date | undefined>();
|
|
73
|
+
const [index, setIndex] = useState<number | undefined>();
|
|
74
|
+
|
|
75
|
+
useImperativeHandle(
|
|
76
|
+
forwardedRef,
|
|
77
|
+
() => ({
|
|
78
|
+
scrollTo: (date: Date) => {
|
|
79
|
+
event.emit({ type: 'scroll', date });
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
[event],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<CalendarContextProvider
|
|
87
|
+
weekStartsOn={weekStartsOn}
|
|
88
|
+
event={event}
|
|
89
|
+
index={index}
|
|
90
|
+
setIndex={setIndex}
|
|
91
|
+
selected={selected}
|
|
92
|
+
setSelected={setSelected}
|
|
93
|
+
>
|
|
94
|
+
{children}
|
|
95
|
+
</CalendarContextProvider>
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
//
|
|
101
|
+
// Viewport
|
|
102
|
+
//
|
|
103
|
+
|
|
104
|
+
const CALENDAR_VIEWPORT_NAME = 'CalendarContent';
|
|
105
|
+
|
|
106
|
+
type CalendarViewportProps = PropsWithChildren<ThemedClassName>;
|
|
107
|
+
|
|
108
|
+
const CalendarViewport = ({ children, classNames }: CalendarViewportProps) => {
|
|
109
|
+
return (
|
|
110
|
+
<div role='none' className={mx('flex flex-col items-center overflow-hidden bg-inputSurface', classNames)}>
|
|
111
|
+
{children}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
CalendarViewport.displayName = CALENDAR_VIEWPORT_NAME;
|
|
117
|
+
|
|
118
|
+
//
|
|
119
|
+
// Header
|
|
120
|
+
//
|
|
121
|
+
|
|
122
|
+
const CALENDAR_TOOLBAR_NAME = 'CalendarHeader';
|
|
123
|
+
|
|
124
|
+
type CalendarToolbarProps = ThemedClassName;
|
|
125
|
+
|
|
126
|
+
const CalendarToolbar = ({ classNames }: CalendarToolbarProps) => {
|
|
127
|
+
const { t } = useTranslation(translationKey);
|
|
128
|
+
const { weekStartsOn, event, index, selected } = useCalendarContext(CALENDAR_TOOLBAR_NAME);
|
|
129
|
+
const top = useMemo(() => getDate(start, index ?? 0, 6, weekStartsOn), [index, weekStartsOn]);
|
|
130
|
+
const today = useMemo(() => new Date(), []);
|
|
131
|
+
|
|
132
|
+
const handleToday = useCallback(() => {
|
|
133
|
+
event.emit({ type: 'scroll', date: today });
|
|
134
|
+
}, [event, start, today]);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div
|
|
138
|
+
role='none'
|
|
139
|
+
className={mx('shink-0 is-full grid grid-cols-3 items-center bg-barSurface', classNames)}
|
|
140
|
+
style={{ width: defaultWidth }}
|
|
141
|
+
>
|
|
142
|
+
<div className='flex justify-start'>
|
|
143
|
+
<IconButton
|
|
144
|
+
variant='ghost'
|
|
145
|
+
size={5}
|
|
146
|
+
icon='ph--calendar--regular'
|
|
147
|
+
iconOnly
|
|
148
|
+
classNames='aspect-square'
|
|
149
|
+
label={t('today button')}
|
|
150
|
+
onClick={handleToday}
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
<div className='flex justify-center p-2 text-description'>{format(selected ?? top, 'MMMM')}</div>
|
|
154
|
+
<div className='flex justify-end p-2 text-description'>{(selected ?? top).getFullYear()}</div>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
CalendarToolbar.displayName = CALENDAR_TOOLBAR_NAME;
|
|
160
|
+
|
|
161
|
+
//
|
|
162
|
+
// Grid
|
|
163
|
+
// TODO(burdon): Key nav.
|
|
164
|
+
// TODO(burdon): Drag range.
|
|
165
|
+
//
|
|
166
|
+
|
|
167
|
+
const CALENDAR_GRID_NAME = 'CalendarGrid';
|
|
168
|
+
|
|
169
|
+
type CalendarGridProps = ThemedClassName<{
|
|
170
|
+
rows?: number;
|
|
171
|
+
onSelect?: (event: { date: Date }) => void;
|
|
172
|
+
}>;
|
|
173
|
+
|
|
174
|
+
const CalendarGrid = ({ classNames, rows, onSelect }: CalendarGridProps) => {
|
|
175
|
+
const { weekStartsOn, event, setIndex, selected, setSelected } = useCalendarContext(CALENDAR_GRID_NAME);
|
|
176
|
+
const { ref: containerRef, width = 0, height = 0 } = useResizeDetector();
|
|
177
|
+
const maxHeight = rows ? rows * size : undefined;
|
|
178
|
+
const listRef = useRef<List>(null);
|
|
179
|
+
const today = useMemo(() => new Date(), []);
|
|
180
|
+
|
|
181
|
+
const [initialized, setInitialized] = useState(false);
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
const index = differenceInWeeks(today, start);
|
|
184
|
+
listRef.current?.scrollToRow(index);
|
|
185
|
+
}, [initialized, start, today]);
|
|
186
|
+
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
return event.on((event) => {
|
|
189
|
+
switch (event.type) {
|
|
190
|
+
case 'scroll': {
|
|
191
|
+
const index = differenceInWeeks(event.date, start);
|
|
192
|
+
listRef.current?.scrollToRow(index);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}, [event]);
|
|
198
|
+
|
|
199
|
+
const days = useMemo(() => {
|
|
200
|
+
const weekStart = startOfWeek(new Date(), { weekStartsOn });
|
|
201
|
+
return Array.from({ length: 7 }, (_, i) => {
|
|
202
|
+
const day = addDays(weekStart, i);
|
|
203
|
+
return format(day, 'EEE'); // Short day name (Mon, Tue, etc.)
|
|
204
|
+
});
|
|
205
|
+
}, []);
|
|
206
|
+
|
|
207
|
+
// TODO(burdon): Get info by range.
|
|
208
|
+
// TODO(burdon): Border marker for "all day events?"
|
|
209
|
+
const getNumAppointments = useCallback((_date: Date) => {
|
|
210
|
+
// return Math.floor(Math.random() * 10);
|
|
211
|
+
return 0;
|
|
212
|
+
}, []);
|
|
213
|
+
|
|
214
|
+
const handleDaySelect = useCallback(
|
|
215
|
+
(date: Date) => {
|
|
216
|
+
setSelected((current) => (isSameDay(date, current) ? undefined : date));
|
|
217
|
+
onSelect?.({ date });
|
|
218
|
+
},
|
|
219
|
+
[onSelect],
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const handleScroll = useCallback<NonNullable<ListProps['onScroll']>>((info) => {
|
|
223
|
+
setIndex(Math.round(info.scrollTop / size));
|
|
224
|
+
}, []);
|
|
225
|
+
|
|
226
|
+
const rowRenderer = useCallback<ListRowRenderer>(
|
|
227
|
+
({ key, index, style }) => {
|
|
228
|
+
const getBgColor = (date: Date) => date.getMonth() % 2 === 0 && 'bg-modalSurface';
|
|
229
|
+
return (
|
|
230
|
+
<div key={key} role='none' style={style} className='is-full grid grid-cols-[1fr_max-content_1fr] snap-center'>
|
|
231
|
+
<div role='none' className={mx(getBgColor(getDate(start, index, 0, weekStartsOn)))} />
|
|
232
|
+
<div role='none' className='grid grid-cols-7' style={{ gridTemplateColumns: `repeat(7, ${size}px)` }}>
|
|
233
|
+
{Array.from({ length: 7 }).map((_, i) => {
|
|
234
|
+
const date = getDate(start, index, i, weekStartsOn);
|
|
235
|
+
const num = getNumAppointments(date);
|
|
236
|
+
const border = isSameDay(date, selected)
|
|
237
|
+
? 'border-primary-500'
|
|
238
|
+
: isSameDay(date, today)
|
|
239
|
+
? 'border-amber-500'
|
|
240
|
+
: undefined;
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<div
|
|
244
|
+
key={i}
|
|
245
|
+
role='none'
|
|
246
|
+
className={mx('relative flex justify-center items-center cursor-pointer', getBgColor(date))}
|
|
247
|
+
onClick={() => handleDaySelect(date)}
|
|
248
|
+
>
|
|
249
|
+
<span className='text-description'>{date.getDate()}</span>
|
|
250
|
+
{!border && date.getDate() === 1 && (
|
|
251
|
+
<span className='absolute top-0 text-xs text-description'>{format(date, 'MMM')}</span>
|
|
252
|
+
)}
|
|
253
|
+
{border && (
|
|
254
|
+
<div
|
|
255
|
+
role='none'
|
|
256
|
+
className={mx('absolute top-0 left-0 is-full bs-full border-2 rounded-full', border)}
|
|
257
|
+
/>
|
|
258
|
+
)}
|
|
259
|
+
{num > 0 && (
|
|
260
|
+
<Icon
|
|
261
|
+
classNames='absolute bottom-0'
|
|
262
|
+
icon={num > 3 ? 'ph--dots-three--regular' : 'ph--dot--regular'}
|
|
263
|
+
size={5}
|
|
264
|
+
/>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
})}
|
|
269
|
+
</div>
|
|
270
|
+
<div className={mx(getBgColor(getDate(start, index, 6, weekStartsOn)))} />
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
},
|
|
274
|
+
[handleDaySelect, getNumAppointments, selected, weekStartsOn],
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<div role='none' className={mx('flex flex-col bs-full is-full justify-center overflow-hidden', classNames)}>
|
|
279
|
+
{/* Day labels */}
|
|
280
|
+
<div role='none' className='flex justify-center bg-groupSurface'>
|
|
281
|
+
<div role='none' className='flex is-full grid grid-cols-7' style={{ width: defaultWidth }}>
|
|
282
|
+
{days.map((date, i) => (
|
|
283
|
+
<div key={i} role='none' className='flex justify-center p-2 text-sm font-thin'>
|
|
284
|
+
{date}
|
|
285
|
+
</div>
|
|
286
|
+
))}
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
{/* Grid */}
|
|
291
|
+
<div role='none' className='flex flex-col bs-full is-full justify-center overflow-hidden' ref={containerRef}>
|
|
292
|
+
<List
|
|
293
|
+
ref={listRef}
|
|
294
|
+
role='none'
|
|
295
|
+
// TODO(burdon): Snap isn't working.
|
|
296
|
+
className='[&>div]:snap-y scrollbar-none outline-none'
|
|
297
|
+
width={width}
|
|
298
|
+
height={maxHeight ?? height}
|
|
299
|
+
rowCount={maxRows}
|
|
300
|
+
rowHeight={size}
|
|
301
|
+
rowRenderer={rowRenderer}
|
|
302
|
+
scrollToAlignment='start'
|
|
303
|
+
onScroll={handleScroll}
|
|
304
|
+
onRowsRendered={() => setInitialized(true)}
|
|
305
|
+
/>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
CalendarGrid.displayName = CALENDAR_GRID_NAME;
|
|
312
|
+
|
|
313
|
+
//
|
|
314
|
+
// Calendar
|
|
315
|
+
//
|
|
316
|
+
|
|
317
|
+
export const Calendar = {
|
|
318
|
+
Root: CalendarRoot,
|
|
319
|
+
Viewport: CalendarViewport,
|
|
320
|
+
Toolbar: CalendarToolbar,
|
|
321
|
+
Grid: CalendarGrid,
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
export type { CalendarController, CalendarRootProps, CalendarViewportProps, CalendarToolbarProps, CalendarGridProps };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Day } from 'date-fns';
|
|
6
|
+
|
|
7
|
+
export const getDate = (start: Date, weekNumber: number, dayOfWeek: number, weekStartsOn: Day): Date => {
|
|
8
|
+
const result = new Date(start);
|
|
9
|
+
const startDayOfWeek = start.getDay(); // 0 = Sunday, 1 = Monday, etc.
|
|
10
|
+
const adjustedStartDay = (startDayOfWeek === 0 ? 7 : startDayOfWeek) - weekStartsOn; // Adjust for weekStartsOn.
|
|
11
|
+
result.setDate(start.getDate() - adjustedStartDay + weekNumber * 7 + dayOfWeek);
|
|
12
|
+
return result;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const isSameDay = (date1: Date, date2: Date | undefined): boolean => {
|
|
16
|
+
return (
|
|
17
|
+
!!date2 &&
|
|
18
|
+
date1.getFullYear() === date2.getFullYear() &&
|
|
19
|
+
date1.getMonth() === date2.getMonth() &&
|
|
20
|
+
date1.getDate() === date2.getDate()
|
|
21
|
+
);
|
|
22
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Resource } from '@dxos/react-ui';
|
|
6
|
+
|
|
7
|
+
export const translationKey = '@dxos/react-ui-calendar';
|
|
8
|
+
|
|
9
|
+
export const translations = [
|
|
10
|
+
{
|
|
11
|
+
'en-US': {
|
|
12
|
+
[translationKey]: {
|
|
13
|
+
'today button': 'Today',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
] as const satisfies Resource[];
|