@milktop/react-ui 0.1.2 → 0.2.1
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 +57 -6
- package/dist/accordion/Accordion.d.ts +1 -1
- package/dist/accordion/Accordion.js +1 -1
- package/dist/action-bar/ActionBar.d.ts +1 -1
- package/dist/action-bar/ActionBar.js +1 -1
- package/dist/alert/Alert.d.ts +1 -1
- package/dist/alert/Alert.js +1 -1
- package/dist/app-shell/AppShell.d.ts +9 -1
- package/dist/app-shell/AppShell.js +46 -5
- package/dist/app-shell/Topbar.d.ts +1 -1
- package/dist/app-shell/Topbar.js +1 -1
- package/dist/app-shell/app-shell.css +5 -0
- package/dist/app-shell/shell.d.ts +2 -0
- package/dist/appearance-panel/Appearance.d.ts +39 -0
- package/dist/appearance-panel/Appearance.js +74 -0
- package/dist/appearance-panel/appearance.css +17 -0
- package/dist/appearance.d.ts +42 -0
- package/dist/appearance.js +157 -0
- package/dist/attachments/Attachments.d.ts +1 -1
- package/dist/attachments/Attachments.js +1 -1
- package/dist/avatar/Avatar.d.ts +1 -1
- package/dist/avatar/Avatar.js +1 -1
- package/dist/badge/Badge.d.ts +1 -1
- package/dist/badge/Badge.js +1 -1
- package/dist/banner/Banner.d.ts +1 -1
- package/dist/banner/Banner.js +1 -1
- package/dist/breadcrumbs/Breadcrumbs.d.ts +1 -1
- package/dist/breadcrumbs/Breadcrumbs.js +1 -1
- package/dist/button/Button.d.ts +3 -2
- package/dist/button/Button.js +1 -1
- package/dist/card/Card.d.ts +2 -2
- package/dist/card/Card.js +1 -1
- package/dist/checkbox/Checkbox.d.ts +1 -1
- package/dist/checkbox/Checkbox.js +1 -1
- package/dist/checkbox/CheckboxGroup.d.ts +1 -1
- package/dist/checkbox/CheckboxGroup.js +1 -1
- package/dist/checkbox/checkbox.css +1 -1
- package/dist/collapsible/Collapsible.d.ts +1 -1
- package/dist/collapsible/Collapsible.js +1 -1
- package/dist/command/Command.d.ts +1 -1
- package/dist/command/Command.js +1 -1
- package/dist/copy-button/CopyButton.d.ts +1 -1
- package/dist/copy-button/CopyButton.js +1 -1
- package/dist/data-list/DataList.d.ts +1 -1
- package/dist/data-list/DataList.js +1 -1
- package/dist/date-picker/DatePicker.d.ts +1 -1
- package/dist/date-picker/DatePicker.js +1 -1
- package/dist/date-picker/date-picker.css +6 -3
- package/dist/dialog/Dialog.d.ts +1 -1
- package/dist/dialog/Dialog.js +1 -1
- package/dist/editable/Editable.d.ts +1 -1
- package/dist/editable/Editable.js +1 -1
- package/dist/editor/Editor.d.ts +1 -1
- package/dist/editor/Editor.js +1 -1
- package/dist/editor/editor.css +2 -1
- package/dist/empty-state/EmptyState.d.ts +1 -1
- package/dist/empty-state/EmptyState.js +1 -1
- package/dist/event-calendar/EventCalendar.d.ts +12 -5
- package/dist/event-calendar/EventCalendar.js +155 -41
- package/dist/event-calendar/event-calendar.css +10 -5
- package/dist/file-upload/FileUpload.d.ts +1 -1
- package/dist/file-upload/FileUpload.js +1 -1
- package/dist/format/messages.d.ts +12 -0
- package/dist/format/messages.js +12 -0
- package/dist/hover-card/hover-card.css +3 -1
- package/dist/icons.d.ts +1 -0
- package/dist/icons.js +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/input/Input.d.ts +1 -1
- package/dist/input/Input.js +1 -1
- package/dist/input/input.css +7 -1
- package/dist/menu/Menu.d.ts +1 -1
- package/dist/menu/Menu.js +1 -1
- package/dist/menu/menu.css +3 -1
- package/dist/native-select/NativeSelect.d.ts +1 -1
- package/dist/native-select/NativeSelect.js +1 -1
- package/dist/native-select/native-select.css +2 -1
- package/dist/number-input/NumberInput.d.ts +1 -1
- package/dist/number-input/NumberInput.js +1 -1
- package/dist/number-input/number-input.css +6 -3
- package/dist/page/Page.d.ts +4 -0
- package/dist/page/Page.js +4 -2
- package/dist/pagination/Pagination.d.ts +1 -1
- package/dist/pagination/Pagination.js +1 -1
- package/dist/password-input/PasswordInput.d.ts +1 -1
- package/dist/password-input/PasswordInput.js +1 -1
- package/dist/password-input/password-input.css +6 -3
- package/dist/pin-input/pin-input.css +2 -1
- package/dist/popover/Popover.d.ts +1 -1
- package/dist/popover/Popover.js +1 -1
- package/dist/popover/popover.css +3 -1
- package/dist/radio-group/RadioGroup.d.ts +1 -1
- package/dist/radio-group/RadioGroup.js +1 -1
- package/dist/radio-group/radio-group.css +1 -1
- package/dist/segment-group/Segmented.d.ts +1 -1
- package/dist/segment-group/Segmented.js +1 -1
- package/dist/select/ListSelect.d.ts +1 -1
- package/dist/select/ListSelect.js +1 -1
- package/dist/select/SearchSelect.d.ts +1 -1
- package/dist/select/SearchSelect.js +1 -1
- package/dist/select/search-select.css +6 -3
- package/dist/select/select.css +5 -2
- package/dist/sheet/Sheet.d.ts +1 -1
- package/dist/sheet/Sheet.js +1 -1
- package/dist/sidebar/NavGroup.d.ts +3 -2
- package/dist/sidebar/NavGroup.js +14 -4
- package/dist/sidebar/NavItem.d.ts +1 -1
- package/dist/sidebar/NavItem.js +1 -1
- package/dist/sidebar/NavSection.d.ts +1 -1
- package/dist/sidebar/NavSection.js +1 -1
- package/dist/sidebar/Sidebar.d.ts +1 -1
- package/dist/sidebar/Sidebar.js +1 -1
- package/dist/sidebar/SidebarUser.d.ts +1 -1
- package/dist/sidebar/SidebarUser.js +1 -1
- package/dist/sidebar/sidebar.css +75 -14
- package/dist/sortable-list/SortableList.d.ts +1 -1
- package/dist/sortable-list/SortableList.js +1 -1
- package/dist/stat/Stat.d.ts +1 -1
- package/dist/stat/Stat.js +1 -1
- package/dist/swatch-picker/SwatchPicker.d.ts +1 -1
- package/dist/swatch-picker/SwatchPicker.js +1 -1
- package/dist/swatch-picker/swatch-picker.css +3 -2
- package/dist/table/Table.d.ts +1 -1
- package/dist/table/Table.js +1 -1
- package/dist/tabs/Tabs.d.ts +1 -1
- package/dist/tabs/Tabs.js +1 -1
- package/dist/tags-input/TagsInput.d.ts +1 -1
- package/dist/tags-input/TagsInput.js +1 -1
- package/dist/tags-input/tags-input.css +6 -3
- package/dist/textarea/textarea.css +2 -1
- package/dist/theme-toggle/ThemeToggle.d.ts +1 -1
- package/dist/theme-toggle/ThemeToggle.js +1 -1
- package/dist/theme.css +51 -10
- package/dist/timeline/Timeline.d.ts +1 -1
- package/dist/timeline/Timeline.js +1 -1
- package/dist/toast/Toaster.d.ts +1 -1
- package/dist/toast/Toaster.js +1 -1
- package/dist/tooltip/tooltip.css +3 -1
- package/dist/tree-view/TreeView.d.ts +1 -1
- package/dist/tree-view/TreeView.js +1 -1
- package/package.json +7 -6
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
* - `onEventChange(event, { start, end, resourceId }, { kind })`: makes
|
|
57
57
|
* events movable and resizable: drag one to another time, day or resource,
|
|
58
58
|
* or its bottom edge to change its length; or focus it and use ↑↓ (a slot),
|
|
59
|
-
* ←→ (a column), Shift+↑↓ (its length).
|
|
59
|
+
* ←→ (a column), Shift+↑↓ (its length). Dragging (or selecting) near an
|
|
60
|
+
* edge scrolls the grid, or the page. The change spreads straight into
|
|
60
61
|
* the event (`{ ...event, ...change }`); `kind` is 'move' or 'resize', and
|
|
61
62
|
* comparing `resourceId` with the event's shows a change of resource.
|
|
62
63
|
* Update your events in response (or don't, and it stays where it was)
|
|
@@ -80,7 +81,9 @@
|
|
|
80
81
|
*
|
|
81
82
|
* Drawing and words
|
|
82
83
|
* - `renderEvent(event, { start, end, resource, compact })`, `renderResource(resource)`,
|
|
83
|
-
* `renderDayHeader(date)`: draw those yourself
|
|
84
|
+
* `renderDayHeader(date, { events, resources })`: draw those yourself. A day
|
|
85
|
+
* header gets that day's events and the resources (e.g. who's in, in a view
|
|
86
|
+
* without their columns)
|
|
84
87
|
* - `eventLabel(event)`: what screen readers call an event (its title, if text)
|
|
85
88
|
* - `translations`: its words (Today, All day, the views, the announcements…),
|
|
86
89
|
* over the LocaleProvider's (English and French are built in);
|
|
@@ -88,7 +91,7 @@
|
|
|
88
91
|
* - `actions`: in the toolbar; `toolbar`: false hides it
|
|
89
92
|
*/
|
|
90
93
|
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
91
|
-
import '
|
|
94
|
+
import '../icons.js';
|
|
92
95
|
import type { CalendarMessages } from '../format/messages.js';
|
|
93
96
|
import { type Classes } from '../utils.js';
|
|
94
97
|
import '../theme.css';
|
|
@@ -160,6 +163,7 @@ export type CalendarTranslations = CalendarMessages;
|
|
|
160
163
|
export type EventCalendarProps<E extends CalendarEvent = CalendarEvent, R extends CalendarResource = CalendarResource> = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onSelect'> & {
|
|
161
164
|
events?: E[];
|
|
162
165
|
resources?: R[] | null;
|
|
166
|
+
resourceViews?: CalendarView[] | null;
|
|
163
167
|
view?: CalendarView;
|
|
164
168
|
defaultView?: CalendarView;
|
|
165
169
|
onViewChange?: (view: CalendarView) => void;
|
|
@@ -201,7 +205,10 @@ export type EventCalendarProps<E extends CalendarEvent = CalendarEvent, R extend
|
|
|
201
205
|
compact: boolean;
|
|
202
206
|
}) => ReactNode;
|
|
203
207
|
renderResource?: (resource: R) => ReactNode;
|
|
204
|
-
renderDayHeader?: (date: Date
|
|
208
|
+
renderDayHeader?: (date: Date, day: {
|
|
209
|
+
events: E[];
|
|
210
|
+
resources: R[] | null;
|
|
211
|
+
}) => ReactNode;
|
|
205
212
|
eventLabel?: (event: E) => string;
|
|
206
213
|
translations?: Partial<CalendarTranslations>;
|
|
207
214
|
actions?: ReactNode;
|
|
@@ -209,5 +216,5 @@ export type EventCalendarProps<E extends CalendarEvent = CalendarEvent, R extend
|
|
|
209
216
|
locale?: string;
|
|
210
217
|
classes?: Classes<'root' | 'toolbar' | 'nav' | 'title' | 'actions' | 'pinned' | 'grid' | 'corner' | 'dayHeader' | 'resourceHeader' | 'rowLabel' | 'rowCell' | 'allDayLabel' | 'allDay' | 'allDayEvent' | 'more' | 'gutter' | 'hour' | 'day' | 'unavailable' | 'event' | 'eventTitle' | 'headerButton' | 'hover' | 'eventTime' | 'resize' | 'ghost' | 'selection' | 'now' | 'status'>;
|
|
211
218
|
};
|
|
212
|
-
export declare function EventCalendar<E extends CalendarEvent = CalendarEvent, R extends CalendarResource = CalendarResource>({ events, resources, view: viewProp, defaultView, onViewChange, views, date: dateProp, defaultDate, onDateChange, weekStartsOn, startHour, endHour, slotMinutes, hourHeight, height, scrollToTime, rows, allDayMax, onSelect, onEventClick, onDayClick, onResourceClick, onRangeChange, onEventChange, isAvailable, onUnavailable, pinned, onPinnedChange, eventMenu, slotMenu, renderEvent, renderResource, renderDayHeader, eventLabel, translations, actions, toolbar, locale: localeProp, className, classes, style, ...rest }: EventCalendarProps<E, R>): import("react").JSX.Element;
|
|
219
|
+
export declare function EventCalendar<E extends CalendarEvent = CalendarEvent, R extends CalendarResource = CalendarResource>({ events, resources, resourceViews, view: viewProp, defaultView, onViewChange, views, date: dateProp, defaultDate, onDateChange, weekStartsOn, startHour, endHour, slotMinutes, hourHeight, height, scrollToTime, rows, allDayMax, onSelect, onEventClick, onDayClick, onResourceClick, onRangeChange, onEventChange, isAvailable, onUnavailable, pinned, onPinnedChange, eventMenu, slotMenu, renderEvent, renderResource, renderDayHeader, eventLabel, translations, actions, toolbar, locale: localeProp, className, classes, style, ...rest }: EventCalendarProps<E, R>): import("react").JSX.Element;
|
|
213
220
|
export {};
|
|
@@ -57,7 +57,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
57
57
|
* - `onEventChange(event, { start, end, resourceId }, { kind })`: makes
|
|
58
58
|
* events movable and resizable: drag one to another time, day or resource,
|
|
59
59
|
* or its bottom edge to change its length; or focus it and use ↑↓ (a slot),
|
|
60
|
-
* ←→ (a column), Shift+↑↓ (its length).
|
|
60
|
+
* ←→ (a column), Shift+↑↓ (its length). Dragging (or selecting) near an
|
|
61
|
+
* edge scrolls the grid, or the page. The change spreads straight into
|
|
61
62
|
* the event (`{ ...event, ...change }`); `kind` is 'move' or 'resize', and
|
|
62
63
|
* comparing `resourceId` with the event's shows a change of resource.
|
|
63
64
|
* Update your events in response (or don't, and it stays where it was)
|
|
@@ -81,7 +82,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
81
82
|
*
|
|
82
83
|
* Drawing and words
|
|
83
84
|
* - `renderEvent(event, { start, end, resource, compact })`, `renderResource(resource)`,
|
|
84
|
-
* `renderDayHeader(date)`: draw those yourself
|
|
85
|
+
* `renderDayHeader(date, { events, resources })`: draw those yourself. A day
|
|
86
|
+
* header gets that day's events and the resources (e.g. who's in, in a view
|
|
87
|
+
* without their columns)
|
|
85
88
|
* - `eventLabel(event)`: what screen readers call an event (its title, if text)
|
|
86
89
|
* - `translations`: its words (Today, All day, the views, the announcements…),
|
|
87
90
|
* over the LocaleProvider's (English and French are built in);
|
|
@@ -89,7 +92,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
89
92
|
* - `actions`: in the toolbar; `toolbar`: false hides it
|
|
90
93
|
*/
|
|
91
94
|
import { Fragment, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
92
|
-
import '
|
|
95
|
+
import '../icons.js';
|
|
93
96
|
import { Button } from '../button/Button.js';
|
|
94
97
|
import { Popover } from '../popover/Popover.js';
|
|
95
98
|
import { DatePicker } from '../date-picker/DatePicker.js';
|
|
@@ -137,7 +140,18 @@ function place(events) {
|
|
|
137
140
|
close();
|
|
138
141
|
return placed;
|
|
139
142
|
}
|
|
140
|
-
|
|
143
|
+
// The nearest ancestor that scrolls up and down, or the document.
|
|
144
|
+
function scrollParent(from) {
|
|
145
|
+
let el = from.parentElement;
|
|
146
|
+
while (el && el !== document.body) {
|
|
147
|
+
const overflow = getComputedStyle(el).overflowY;
|
|
148
|
+
if ((overflow === 'auto' || overflow === 'scroll') && el.scrollHeight > el.clientHeight + 1)
|
|
149
|
+
return el;
|
|
150
|
+
el = el.parentElement;
|
|
151
|
+
}
|
|
152
|
+
return document.scrollingElement;
|
|
153
|
+
}
|
|
154
|
+
export function EventCalendar({ events = [], resources = null, resourceViews = null, view: viewProp, defaultView = 'week', onViewChange, views = ['day', 'week'], date: dateProp, defaultDate, onDateChange, weekStartsOn = 1, startHour = 7, endHour = 21, slotMinutes = 30, hourHeight = '3rem', height = null, scrollToTime = null, rows = [], allDayMax = 3, onSelect, onEventClick, onDayClick, onResourceClick, onRangeChange, onEventChange, isAvailable, onUnavailable, pinned = null, onPinnedChange, eventMenu, slotMenu, renderEvent, renderResource, renderDayHeader, eventLabel, translations, actions = null, toolbar = true, locale: localeProp, className, classes, style, ...rest }) {
|
|
141
155
|
const app = useLocale();
|
|
142
156
|
const locale = localeProp ?? app;
|
|
143
157
|
const part = partsOf('event-calendar', classes);
|
|
@@ -165,7 +179,9 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
165
179
|
const count = view === 'day' ? 1 : view === 'week' ? 7 : view === 'workweek' ? 5 : Math.max(1, view);
|
|
166
180
|
const step = view === 'week' || view === 'workweek' ? 7 : count;
|
|
167
181
|
const dates = Array.from({ length: count }, (_, i) => addDays(first, i));
|
|
168
|
-
|
|
182
|
+
// A column per resource in this view, or everyone's events together.
|
|
183
|
+
const split = !!resources?.length && (!resourceViews || resourceViews.includes(view));
|
|
184
|
+
const lanes = split ? resources : [null];
|
|
169
185
|
const columns = dates.flatMap((d) => lanes.map((resource) => ({ date: d, iso: toISODate(d), resource })));
|
|
170
186
|
const hours = Array.from({ length: endHour - startHour }, (_, i) => startHour + i);
|
|
171
187
|
const span = (endHour - startHour) * 60;
|
|
@@ -193,7 +209,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
193
209
|
const snap = (minutes) => Math.floor(minutes / slotMinutes) * slotMinutes;
|
|
194
210
|
const nameOf = (event) => eventLabel ? eventLabel(event) : typeof event.title === 'string' ? event.title : 'Event';
|
|
195
211
|
const resourceName = (column) => column?.resource && typeof column.resource.title === 'string' ? `, ${column.resource.title}` : '';
|
|
196
|
-
const columnOf = (event, iso) => columns.findIndex((c) => c.iso === iso && (!
|
|
212
|
+
const columnOf = (event, iso) => columns.findIndex((c) => c.iso === iso && (!split || c.resource?.id === event.resourceId));
|
|
197
213
|
const [picking, setPicking] = useState(false);
|
|
198
214
|
const [status, setStatus] = useState('');
|
|
199
215
|
const [allDayOpen, setAllDayOpen] = useState(false);
|
|
@@ -284,6 +300,25 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
284
300
|
};
|
|
285
301
|
const refocus = useRef(null);
|
|
286
302
|
const suppressClick = useRef(false);
|
|
303
|
+
// `byMinutes` kept to the day's hours: an event moves or grows no further than
|
|
304
|
+
// the grid shows (one already past an edge doesn't go further past it), and
|
|
305
|
+
// shrinks to a slot at the shortest (one already shorter doesn't shrink).
|
|
306
|
+
function withinHours(event, kind, byMinutes) {
|
|
307
|
+
const start = parse(event.start);
|
|
308
|
+
const from = minutesOfDay(start);
|
|
309
|
+
const length = minutesBetween(start, parse(event.end));
|
|
310
|
+
const latest = Math.max(0, endHour * 60 - from - length);
|
|
311
|
+
const earliest = kind === 'move' ? Math.min(0, startHour * 60 - from) : Math.min(0, slotMinutes - length);
|
|
312
|
+
return Math.max(earliest, Math.min(latest, byMinutes));
|
|
313
|
+
}
|
|
314
|
+
// Where a pinned event placed at `minute` starts: on a slot, ending by the day's last hour.
|
|
315
|
+
function placeAt(minute) {
|
|
316
|
+
if (!pinned)
|
|
317
|
+
return minute;
|
|
318
|
+
const length = minutesBetween(parse(pinned.start), parse(pinned.end)) || slotMinutes;
|
|
319
|
+
const latest = startHour * 60 + Math.floor((span - length) / slotMinutes) * slotMinutes;
|
|
320
|
+
return Math.max(startHour * 60, Math.min(latest, minute));
|
|
321
|
+
}
|
|
287
322
|
// Where an event would go if moved by `byColumns` and `byMinutes` (or resized by `byMinutes`).
|
|
288
323
|
function target(event, kind, byColumns, byMinutes) {
|
|
289
324
|
const start = parse(event.start);
|
|
@@ -291,6 +326,9 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
291
326
|
const from = columnOf(event, event.start.slice(0, 10));
|
|
292
327
|
if (from < 0)
|
|
293
328
|
return null;
|
|
329
|
+
byMinutes = withinHours(event, kind, byMinutes);
|
|
330
|
+
if (!byColumns && !byMinutes)
|
|
331
|
+
return null;
|
|
294
332
|
if (kind === 'resize') {
|
|
295
333
|
const newEnd = addMinutes(end, byMinutes);
|
|
296
334
|
if (minutesBetween(start, newEnd) < slotMinutes)
|
|
@@ -301,10 +339,11 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
301
339
|
const days = Math.round((column.date.getTime() - columns[from].date.getTime()) / 86400000);
|
|
302
340
|
return { start: addMinutes(addDays(start, days), byMinutes), end: addMinutes(addDays(end, days), byMinutes), column };
|
|
303
341
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
342
|
+
// Its new column's resource, or (in a view without their columns) its own.
|
|
343
|
+
const queryFor = (event, next) => {
|
|
344
|
+
const resourceId = next.column.resource ? next.column.resource.id : event.resourceId;
|
|
345
|
+
return { start: toISODateTime(next.start), end: toISODateTime(next.end), allDay: false, event, ...(resourceId != null && { resourceId }) };
|
|
346
|
+
};
|
|
308
347
|
// Applies a change (after checking it's allowed).
|
|
309
348
|
function commit(event, kind, next) {
|
|
310
349
|
const query = queryFor(event, next);
|
|
@@ -316,7 +355,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
316
355
|
? t.resized(nameOf(event), timeFormat.format(next.end))
|
|
317
356
|
: t.moved(nameOf(event), `${longDay.format(next.start)}, ${timeFormat.format(next.start)}${resourceName(next.column)}`));
|
|
318
357
|
onEventChange?.(event, {
|
|
319
|
-
start: query.start, end: query.end, ...(
|
|
358
|
+
start: query.start, end: query.end, ...(query.resourceId != null && { resourceId: query.resourceId }),
|
|
320
359
|
}, { kind });
|
|
321
360
|
}
|
|
322
361
|
function change(event, kind, byColumns, byMinutes) {
|
|
@@ -334,23 +373,30 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
334
373
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
335
374
|
}
|
|
336
375
|
catch { /* not a live pointer */ }
|
|
376
|
+
// Where things are scrolled at the start: a drag near an edge scrolls the
|
|
377
|
+
// grid (or the page), and positions are measured again as it does.
|
|
378
|
+
const scroll = { left: grid.current?.scrollLeft ?? 0, x: scrollX };
|
|
379
|
+
const point = { x: e.clientX, y: e.clientY, lastX: e.clientX, lastY: e.clientY, moved: false, keys: keysOf(e), scroll };
|
|
337
380
|
if (kind === 'all-day') {
|
|
338
381
|
// The strip spans every column: which one is under the pointer.
|
|
339
382
|
const box = e.currentTarget.getBoundingClientRect();
|
|
340
383
|
const width = box.width / columns.length;
|
|
341
384
|
const at = Math.max(0, Math.min(columns.length - 1, Math.floor((e.clientX - box.left) / width)));
|
|
342
|
-
gesture.current = { kind, column: at,
|
|
385
|
+
gesture.current = { kind, column: at, ...point, target: e.currentTarget, width, from: at, exact: 0 };
|
|
343
386
|
setPreview({ kind: 'all-day', columns: [at] });
|
|
344
387
|
return;
|
|
345
388
|
}
|
|
346
|
-
const
|
|
389
|
+
const day = e.currentTarget.closest('[data-part=day][role=gridcell]');
|
|
390
|
+
const box = day.getBoundingClientRect();
|
|
347
391
|
const from = minuteAt(e.clientY, box);
|
|
348
|
-
gesture.current = { kind, column,
|
|
392
|
+
gesture.current = { kind, column, ...point, target: day, width: box.width, from, exact: exactMinute(e.clientY, box), event };
|
|
349
393
|
if (kind === 'select')
|
|
350
394
|
setPreview({ kind: 'select', column, from, to: from + slotMinutes });
|
|
351
395
|
if (kind === 'place')
|
|
352
|
-
setPreview({ kind: 'place', column, from });
|
|
396
|
+
setPreview({ kind: 'place', column, from: placeAt(from) });
|
|
353
397
|
}
|
|
398
|
+
// The minute of the day at a height in a day column (not snapped).
|
|
399
|
+
const exactMinute = (y, box) => startHour * 60 + Math.max(0, Math.min(span - 1, ((y - box.top) / box.height) * span));
|
|
354
400
|
// The columns from one to another (with resources, only the first one's resource).
|
|
355
401
|
const columnRange = (a, b) => {
|
|
356
402
|
const lo = Math.min(a, b);
|
|
@@ -361,35 +407,100 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
361
407
|
const g = gesture.current;
|
|
362
408
|
if (!g)
|
|
363
409
|
return;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (!g.moved && Math.hypot(
|
|
410
|
+
g.lastX = e.clientX;
|
|
411
|
+
g.lastY = e.clientY;
|
|
412
|
+
if (!g.moved && Math.hypot(e.clientX - g.x, e.clientY - g.y) < 4)
|
|
367
413
|
return;
|
|
368
414
|
g.moved = true;
|
|
369
|
-
|
|
415
|
+
updatePreview();
|
|
416
|
+
autoScroll();
|
|
417
|
+
}
|
|
418
|
+
// The preview from the pointer's last position, measured afresh, so it
|
|
419
|
+
// stays right while the grid or the page scrolls under it.
|
|
420
|
+
function updatePreview() {
|
|
421
|
+
const g = gesture.current;
|
|
422
|
+
if (!g)
|
|
423
|
+
return;
|
|
424
|
+
const box = g.target.getBoundingClientRect();
|
|
370
425
|
if (g.kind === 'all-day') {
|
|
371
|
-
const at = Math.max(0, Math.min(columns.length - 1, Math.floor((
|
|
372
|
-
setPreview({ kind: 'all-day', columns: columnRange(g.from, at) });
|
|
426
|
+
const at = Math.max(0, Math.min(columns.length - 1, Math.floor((g.lastX - box.left) / g.width)));
|
|
427
|
+
return setPreview({ kind: 'all-day', columns: columnRange(g.from, at) });
|
|
373
428
|
}
|
|
374
|
-
|
|
375
|
-
|
|
429
|
+
const perMinute = box.height / span;
|
|
430
|
+
if (g.kind === 'select') {
|
|
431
|
+
const at = minuteAt(g.lastY, box);
|
|
376
432
|
setPreview({ kind: 'select', column: g.column, from: Math.min(g.from, at), to: Math.max(g.from, at) + slotMinutes });
|
|
377
433
|
}
|
|
378
434
|
else if (g.kind === 'place') {
|
|
379
|
-
setPreview({ kind: 'place', column: g.column, from: minuteAt(
|
|
380
|
-
}
|
|
381
|
-
else if (g.kind === 'move') {
|
|
382
|
-
const byColumns = Math.round(dx / g.width);
|
|
383
|
-
const byMinutes = Math.round(dy / perMinute / slotMinutes) * slotMinutes;
|
|
384
|
-
setPreview({ kind: 'move', id: g.event.id, columns: byColumns, minutes: byMinutes, x: byColumns * g.width, y: byMinutes * perMinute });
|
|
435
|
+
setPreview({ kind: 'place', column: g.column, from: placeAt(minuteAt(g.lastY, box)) });
|
|
385
436
|
}
|
|
386
437
|
else {
|
|
387
|
-
|
|
438
|
+
let byMinutes = Math.round((exactMinute(g.lastY, box) - g.exact) / slotMinutes) * slotMinutes;
|
|
439
|
+
if (g.kind === 'move') {
|
|
440
|
+
const scrolledX = (grid.current?.scrollLeft ?? 0) - g.scroll.left + scrollX - g.scroll.x;
|
|
441
|
+
const byColumns = Math.round((g.lastX - g.x + scrolledX) / g.width);
|
|
442
|
+
byMinutes = withinHours(g.event, 'move', byMinutes);
|
|
443
|
+
setPreview({ kind: 'move', id: g.event.id, columns: byColumns, minutes: byMinutes, x: byColumns * g.width, y: byMinutes * perMinute });
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
setPreview({ kind: 'resize', id: g.event.id, minutes: withinHours(g.event, 'resize', byMinutes) });
|
|
447
|
+
}
|
|
388
448
|
}
|
|
389
449
|
}
|
|
450
|
+
// Near an edge while dragging: scroll that way, faster the closer it is,
|
|
451
|
+
// each frame until the pointer moves away or lets go. Up and down scrolls
|
|
452
|
+
// the grid when it has a `height` (below its sticky headers), or else the
|
|
453
|
+
// page; sideways, the grid when it's wider than its box.
|
|
454
|
+
const scrolling = useRef(false);
|
|
455
|
+
function autoScroll() {
|
|
456
|
+
if (scrolling.current)
|
|
457
|
+
return;
|
|
458
|
+
scrolling.current = true;
|
|
459
|
+
// Each loop belongs to one gesture: one left over from the last drag stops.
|
|
460
|
+
const owner = gesture.current;
|
|
461
|
+
const step = () => {
|
|
462
|
+
const g = gesture.current;
|
|
463
|
+
const el = grid.current;
|
|
464
|
+
if (!g || g !== owner || !g.moved || !el)
|
|
465
|
+
return void (scrolling.current = false);
|
|
466
|
+
const box = el.getBoundingClientRect();
|
|
467
|
+
const edge = 48;
|
|
468
|
+
const speed = (near) => Math.ceil(Math.min(1, Math.max(0, (edge - near) / edge)) * 18);
|
|
469
|
+
let vy = 0;
|
|
470
|
+
let vx = 0;
|
|
471
|
+
if (g.kind !== 'all-day') {
|
|
472
|
+
const scroller = el.scrollHeight > el.clientHeight + 1 ? el : scrollParent(el);
|
|
473
|
+
// The grid below its sticky headers; the page (whose top can sit under
|
|
474
|
+
// a fixed top bar); or an app shell's scrolling page.
|
|
475
|
+
const area = scroller === el ? { top: box.top + (parseFloat(el.style.getPropertyValue('--sticky-total')) || 0), bottom: box.bottom }
|
|
476
|
+
: scroller === document.scrollingElement ? { top: 64, bottom: innerHeight }
|
|
477
|
+
: scroller.getBoundingClientRect();
|
|
478
|
+
vy = g.lastY < area.top + edge ? -speed(g.lastY - area.top) : g.lastY > area.bottom - edge ? speed(area.bottom - g.lastY) : 0;
|
|
479
|
+
const before = scroller.scrollTop;
|
|
480
|
+
scroller.scrollTop += vy;
|
|
481
|
+
// Already at the end: nothing more to scroll.
|
|
482
|
+
if (scroller.scrollTop === before)
|
|
483
|
+
vy = 0;
|
|
484
|
+
}
|
|
485
|
+
if (el.scrollWidth > el.clientWidth + 1) {
|
|
486
|
+
const left = box.left + (el.querySelector('[data-part=gutter]')?.offsetWidth ?? 0);
|
|
487
|
+
vx = g.lastX < left + edge ? -speed(g.lastX - left) : g.lastX > box.right - edge ? speed(box.right - g.lastX) : 0;
|
|
488
|
+
const before = el.scrollLeft;
|
|
489
|
+
el.scrollLeft += vx;
|
|
490
|
+
if (el.scrollLeft === before)
|
|
491
|
+
vx = 0;
|
|
492
|
+
}
|
|
493
|
+
if (!vx && !vy)
|
|
494
|
+
return void (scrolling.current = false);
|
|
495
|
+
updatePreview();
|
|
496
|
+
requestAnimationFrame(step);
|
|
497
|
+
};
|
|
498
|
+
requestAnimationFrame(step);
|
|
499
|
+
}
|
|
390
500
|
function end() {
|
|
391
501
|
const g = gesture.current;
|
|
392
502
|
gesture.current = null;
|
|
503
|
+
scrolling.current = false;
|
|
393
504
|
const p = latestPreview.current;
|
|
394
505
|
setPreview(null);
|
|
395
506
|
if (!g)
|
|
@@ -432,6 +543,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
432
543
|
}
|
|
433
544
|
function cancel() {
|
|
434
545
|
gesture.current = null;
|
|
546
|
+
scrolling.current = false;
|
|
435
547
|
setPreview(null);
|
|
436
548
|
}
|
|
437
549
|
function keyDown(e, event) {
|
|
@@ -457,10 +569,10 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
457
569
|
function hover(e, column) {
|
|
458
570
|
if (gesture.current)
|
|
459
571
|
return setHoverSlot(null);
|
|
460
|
-
const box = e.currentTarget.closest('[data-part=day]').getBoundingClientRect();
|
|
572
|
+
const box = e.currentTarget.closest('[data-part=day][role=gridcell]').getBoundingClientRect();
|
|
461
573
|
const from = minuteAt(e.clientY, box);
|
|
462
574
|
if (pinned)
|
|
463
|
-
return setPreview({ kind: 'place', column, from });
|
|
575
|
+
return setPreview({ kind: 'place', column, from: placeAt(from) });
|
|
464
576
|
// Over the empty column (or its shading), not an event.
|
|
465
577
|
const over = e.target;
|
|
466
578
|
setHoverSlot(onSelect && (over === e.currentTarget || over.dataset.part === 'unavailable') ? { column, from } : null);
|
|
@@ -504,7 +616,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
504
616
|
if (!el || !height || !scrollToTime || scrolled.current)
|
|
505
617
|
return;
|
|
506
618
|
scrolled.current = true;
|
|
507
|
-
const day = el.querySelector('[data-part=day]');
|
|
619
|
+
const day = el.querySelector('[data-part=day][role=gridcell]');
|
|
508
620
|
if (!day)
|
|
509
621
|
return;
|
|
510
622
|
const [h, m] = scrollToTime === 'now' ? [now.getHours() - 1, 0] : scrollToTime.split(':').map(Number);
|
|
@@ -516,7 +628,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
516
628
|
const allDay = events.filter(isAllDay).flatMap((event) => {
|
|
517
629
|
const from = event.start.slice(0, 10);
|
|
518
630
|
const to = event.end.slice(0, 10);
|
|
519
|
-
const covered = columns.map((c, i) => (c.iso >= from && c.iso <= to && (!
|
|
631
|
+
const covered = columns.map((c, i) => (c.iso >= from && c.iso <= to && (!split || c.resource?.id === event.resourceId) ? i : -1))
|
|
520
632
|
.filter((i) => i >= 0);
|
|
521
633
|
// Runs of neighbouring columns (with resources, a day's column for the event's resource).
|
|
522
634
|
const runs = [];
|
|
@@ -565,13 +677,15 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
565
677
|
}
|
|
566
678
|
const hiddenIn = (i) => hiddenCount[i];
|
|
567
679
|
const barColor = (event) => event.color ?? resourceOf(event.resourceId)?.color ?? 'accent';
|
|
568
|
-
|
|
680
|
+
// The events on a day, for its header.
|
|
681
|
+
const eventsOn = (d) => events.filter((event) => event.start.slice(0, 10) <= toISODate(d) && event.end.slice(0, 10) >= toISODate(d));
|
|
682
|
+
const twoRows = split && count > 1;
|
|
569
683
|
const editable = (event) => !!onEventChange && event.editable !== false && !event.pending;
|
|
570
684
|
const pinnedMinutes = pinned ? minutesBetween(parse(pinned.start), parse(pinned.end)) || slotMinutes : 0;
|
|
571
685
|
// The header rows, numbered for sticking: days, resources, extra rows, the all-day strip.
|
|
572
686
|
let rowNumber = 0;
|
|
573
|
-
const dayRow = !
|
|
574
|
-
const resourceRow =
|
|
687
|
+
const dayRow = !split || count > 1 ? rowNumber++ : -1;
|
|
688
|
+
const resourceRow = split ? rowNumber++ : -1;
|
|
575
689
|
const extraRows = rows.map(() => rowNumber++);
|
|
576
690
|
const allDayRow = rowNumber;
|
|
577
691
|
// The preview's problem, if any, shown as it's dragged.
|
|
@@ -595,7 +709,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
595
709
|
setDate(value);
|
|
596
710
|
setPicking(false);
|
|
597
711
|
} } }) }), _jsxs("div", { ...part('actions'), children: [!narrow && views.length > 1 && (_jsx(Segmented, { size: "sm", "aria-label": t.view, items: views.map((v) => ({ value: String(v), label: viewLabel(v) })), value: String(chosenView), onValueChange: (v) => v && setView(/^\d+$/.test(v) ? Number(v) : v) })), actions] })] })), pinned && (_jsxs("div", { ...part('pinned'), role: "status", children: [_jsx("iconify-icon", { icon: "lucide:move", "aria-hidden": "true" }), _jsx("span", { children: t.placing(nameOf(pinned)) }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => onPinnedChange?.(null), children: t.cancel })] })), _jsxs("div", { ...part('grid'), ref: grid, role: "grid", "aria-label": title, "data-two-rows": twoRows || undefined, "data-scroll": height ? '' : undefined, style: height ? { maxHeight: height } : undefined, children: [_jsx("div", { ...part('corner'), "data-sticky-row": dayRow >= 0 ? dayRow : resourceRow, style: { gridRow: twoRows ? 'span 2' : undefined, top: `var(--sticky-${dayRow >= 0 ? dayRow : resourceRow})` } }), dayRow >= 0 && dates.map((d) => (_jsx("div", { ...part('day-header'), role: "columnheader", "data-today": sameDay(d, now) || undefined, "data-sticky-row": dayRow, style: { gridColumn: `span ${lanes.length}`, top: `var(--sticky-${dayRow})` }, children: (() => {
|
|
598
|
-
const content = renderDayHeader ? renderDayHeader(d) : _jsxs(_Fragment, { children: [_jsx("span", { children: dayFormat.format(d) }), _jsx("strong", { children: d.getDate() })] });
|
|
712
|
+
const content = renderDayHeader ? renderDayHeader(d, { events: eventsOn(d), resources }) : _jsxs(_Fragment, { children: [_jsx("span", { children: dayFormat.format(d) }), _jsx("strong", { children: d.getDate() })] });
|
|
599
713
|
return onDayClick
|
|
600
714
|
? _jsx("button", { type: "button", ...part('header-button'), onClick: () => onDayClick(toISODate(d)), "aria-label": longDay.format(d), children: content })
|
|
601
715
|
: content;
|
|
@@ -629,7 +743,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
629
743
|
}, onClick: () => onEventClick?.(event), onContextMenu: (e) => { if (openMenu(e.clientX, e.clientY, eventMenu?.(event)))
|
|
630
744
|
e.preventDefault(); }, children: renderEvent ? renderEvent(event, { start: parse(event.start), end: parse(event.end), resource: null, compact: true }) : event.title }, `${event.id}-${first}`))), folding && columns.map((_, i) => hiddenIn(i) > 0 && (_jsx("button", { type: "button", ...part('more'), onClick: () => setAllDayOpen(true), style: { left: `${(i / columns.length) * 100}%`, width: `${100 / columns.length}%`, top: `calc(${shownLanes} * 1.625rem + 0.25rem)` }, children: t.more(hiddenIn(i)) }, `more-${i}`)))] })] }), _jsx("div", { ...part('gutter'), "aria-hidden": "true", style: { '--hours': hours.length }, children: hours.map((hour) => _jsx("span", { ...part('hour'), style: { top: top(hour * 60) }, children: hourFormat.format(new Date(2000, 0, 1, hour)) }, hour)) }), columns.map((column, index) => {
|
|
631
745
|
const own = events.filter((event) => !isAllDay(event) && event.start.startsWith(column.iso)
|
|
632
|
-
&& (!
|
|
746
|
+
&& (!column.resource || event.resourceId === column.resource.id));
|
|
633
747
|
const selecting = preview?.kind === 'select' && preview.column === index ? preview : null;
|
|
634
748
|
const placing = preview?.kind === 'place' && preview.column === index ? preview : null;
|
|
635
749
|
const isToday = sameDay(column.date, now);
|
|
@@ -640,7 +754,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
640
754
|
else if (onSelect)
|
|
641
755
|
begin(e, 'select', index);
|
|
642
756
|
};
|
|
643
|
-
return (_jsxs("div", { ...part('day'), role: "gridcell", "aria-label": `${longDay.format(column.date)}${resourceName(column)}`, "data-today": isToday || undefined, "data-selectable": onSelect || pinned ? '' : undefined, "data-first": (
|
|
757
|
+
return (_jsxs("div", { ...part('day'), role: "gridcell", "aria-label": `${longDay.format(column.date)}${resourceName(column)}`, "data-today": isToday || undefined, "data-selectable": onSelect || pinned ? '' : undefined, "data-first": (split && column.resource === lanes[0]) || undefined, style: { '--hours': hours.length, ...(column.resource?.color && { '--resource-color': cssColor(column.resource.color) }) }, onPointerDown: (e) => { if (e.target === e.currentTarget)
|
|
644
758
|
press(e); }, onPointerMove: (e) => { track(e); hover(e, index); }, onPointerUp: end, onPointerCancel: cancel, onPointerLeave: () => { setHoverSlot(null); if (pinned && !gesture.current)
|
|
645
759
|
setPreview(null); }, onContextMenu: (e) => {
|
|
646
760
|
// An empty time (or its unavailable shading), not an event.
|
|
@@ -655,7 +769,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
655
769
|
const resizing = preview?.kind === 'resize' && preview.id === event.id ? preview : null;
|
|
656
770
|
const shownEnd = resizing ? addMinutes(finish, resizing.minutes) : finish;
|
|
657
771
|
const minutes = minutesBetween(start, shownEnd);
|
|
658
|
-
const color = event.color ?? column.resource?.color ?? 'accent';
|
|
772
|
+
const color = event.color ?? (column.resource ?? resourceOf(event.resourceId))?.color ?? 'accent';
|
|
659
773
|
const time = `${timeFormat.format(start)} – ${timeFormat.format(shownEnd)}`;
|
|
660
774
|
const box = { top: top(minutesOfDay(start)), left: `${(sub / subs) * 100}%`, width: `${100 / subs}%` };
|
|
661
775
|
return (_jsxs(Fragment, { children: [(moving || resizing) && _jsx("div", { ...part('ghost'), style: { ...box, height: length(minutesBetween(start, finish)) } }), _jsxs("button", { type: "button", ...part('event'), "data-event-id": event.id, "data-dragging": moving || resizing ? '' : undefined, "data-invalid": (moving || resizing) && previewReason ? '' : undefined, "data-editable": editable(event) || undefined, "data-compact": minutes <= 30 || undefined, "data-blocked": event.blocked || undefined, "data-pending": event.pending || undefined, "data-pinned": pinned?.id === event.id || undefined, "data-color": named.includes(color) ? color : 'custom', "aria-label": `${nameOf(event)}, ${time}`, "aria-busy": event.pending || undefined, title: (moving || resizing) && previewReason ? previewReason : undefined, style: {
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
&:focus-visible { outline: 2px solid var(--ui-ring-soft); } }
|
|
11
11
|
[data-scope=event-calendar][data-part=actions] { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; }
|
|
12
12
|
|
|
13
|
-
/* A time gutter, then a column per day (or per resource within each day); it scrolls sideways when narrow.
|
|
14
|
-
|
|
13
|
+
/* A time gutter, then a column per day (or per resource within each day); it scrolls sideways when narrow.
|
|
14
|
+
The gutter is as wide as its widest label, however many columns there are. */
|
|
15
|
+
[data-scope=event-calendar][data-part=grid] { display: grid; grid-template-columns: max-content repeat(var(--columns), minmax(6.5rem, 1fr));
|
|
15
16
|
overflow-x: auto; background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 2px);
|
|
16
17
|
box-shadow: var(--ui-card-shadow); }
|
|
17
18
|
[data-scope=event-calendar]:is([data-part=corner], [data-part=day-header], [data-part=resource-header]) {
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
&:focus-visible { outline: 2px solid var(--ui-ring); outline-offset: 1px; }
|
|
58
59
|
&[data-continued] { border-top-left-radius: 0; border-bottom-left-radius: 0; border-left: none; } }
|
|
59
60
|
|
|
60
|
-
[data-scope=event-calendar][data-part=gutter] { position: relative; height: calc(var(--hour-height) * var(--hours)); }
|
|
61
|
+
[data-scope=event-calendar][data-part=gutter] { position: relative; min-width: 3.5rem; height: calc(var(--hour-height) * var(--hours)); }
|
|
61
62
|
[data-scope=event-calendar][data-part=hour] { position: absolute; right: 0.5rem; translate: 0 -50%; color: var(--ui-muted); font-size: 11px;
|
|
62
63
|
&:first-child { translate: 0 2px; } }
|
|
63
64
|
/* Hour lines, with a fainter one at the half hour. */
|
|
@@ -70,6 +71,8 @@
|
|
|
70
71
|
/* With resources over several days, a stronger line where each day starts. */
|
|
71
72
|
[data-two-rows] > &[data-first] { border-left-color: var(--ui-muted); } }
|
|
72
73
|
[data-two-rows] > [data-scope=event-calendar][data-part=day-header] { border-left-color: var(--ui-muted); }
|
|
74
|
+
/* Pressing and dragging selects times, not the text on them. */
|
|
75
|
+
[data-scope=event-calendar]:is([data-part=day], [data-part=all-day], [data-part=event]) { user-select: none; -webkit-user-select: none; }
|
|
73
76
|
|
|
74
77
|
[data-scope=event-calendar][data-part=event], [data-scope=event-calendar][data-part=all-day-event] {
|
|
75
78
|
&[data-color=success] { --event-color: var(--ui-success); }
|
|
@@ -78,7 +81,7 @@
|
|
|
78
81
|
&[data-color=neutral] { --event-color: var(--ui-muted); } }
|
|
79
82
|
[data-scope=event-calendar][data-part=event] { --event-color: var(--ui-accent);
|
|
80
83
|
position: absolute; z-index: 1; display: flex; flex-direction: column; gap: 0.125rem; box-sizing: border-box; overflow: hidden;
|
|
81
|
-
padding: 0.25rem 0.375rem; border: 2px solid var(--ui-surface); border-radius: calc(var(--ui-radius) + 2px);
|
|
84
|
+
padding: 0.25rem 0.375rem; border: 2px solid var(--ui-surface); border-radius: 0 calc(var(--ui-radius) + 2px) calc(var(--ui-radius) + 2px) 0;
|
|
82
85
|
border-left: 3px solid var(--event-color); background: color-mix(in srgb, var(--event-color) 16%, var(--ui-surface));
|
|
83
86
|
color: var(--ui-text); font: inherit; font-size: 12px; text-align: left; cursor: pointer;
|
|
84
87
|
&:hover { background: color-mix(in srgb, var(--event-color) 24%, var(--ui-surface)); }
|
|
@@ -111,7 +114,9 @@
|
|
|
111
114
|
[data-scope=event-calendar][data-part=grid][data-scroll] { position: relative; overflow: auto;
|
|
112
115
|
& > [data-sticky-row] { position: sticky; z-index: 5; background-color: var(--ui-surface); }
|
|
113
116
|
& > :is([data-part=corner], [data-part=all-day-label], [data-part=row-label]) { left: 0; z-index: 6; }
|
|
114
|
-
& > [data-part=gutter] { position: sticky; left: 0; z-index: 4; background: var(--ui-surface); }
|
|
117
|
+
& > [data-part=gutter] { position: sticky; left: 0; z-index: 4; background: var(--ui-surface); }
|
|
118
|
+
/* A header with its menu open (focus is in it) sits above the rows below. */
|
|
119
|
+
& > [data-sticky-row]:focus-within { z-index: 7; } }
|
|
115
120
|
|
|
116
121
|
/* Time that can't be used (isAvailable): hatched, with its reason on hover. */
|
|
117
122
|
[data-scope=event-calendar][data-part=unavailable] { position: absolute; left: 0; right: 0; cursor: not-allowed;
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { type ReactNode } from 'react';
|
|
30
30
|
import { FileUpload as Ark } from '@ark-ui/react/file-upload';
|
|
31
|
-
import '
|
|
31
|
+
import '../icons.js';
|
|
32
32
|
import { type Classes } from '../utils.js';
|
|
33
33
|
import '../theme.css';
|
|
34
34
|
import './file-upload.css';
|
|
@@ -31,7 +31,7 @@ import { useEffect, useMemo, useRef } from 'react';
|
|
|
31
31
|
import { FileUpload as Ark } from '@ark-ui/react/file-upload';
|
|
32
32
|
import { createFileTypeValidator, getAcceptAttrString, getFileKey, isValidFileSize } from '@zag-js/file-utils';
|
|
33
33
|
import { useFieldContext } from '@ark-ui/react/field';
|
|
34
|
-
import '
|
|
34
|
+
import '../icons.js';
|
|
35
35
|
import { cx, partsOf, useControllable, withParts } from '../utils.js';
|
|
36
36
|
import { useLocale, useMessages } from '../format/LocaleProvider.js';
|
|
37
37
|
import { formatBytes } from '../format/format.js';
|
|
@@ -146,6 +146,18 @@ export type Messages = {
|
|
|
146
146
|
dark: string;
|
|
147
147
|
system: string;
|
|
148
148
|
};
|
|
149
|
+
appearance: {
|
|
150
|
+
label: string;
|
|
151
|
+
mode: string;
|
|
152
|
+
accent: string;
|
|
153
|
+
sidebar: string;
|
|
154
|
+
lightOnly: string;
|
|
155
|
+
custom: string;
|
|
156
|
+
dev: string;
|
|
157
|
+
reset: string;
|
|
158
|
+
accents: Record<string, string>;
|
|
159
|
+
sidebars: Record<string, string>;
|
|
160
|
+
};
|
|
149
161
|
sortableList: {
|
|
150
162
|
handle: string;
|
|
151
163
|
instructions: string;
|
package/dist/format/messages.js
CHANGED
|
@@ -36,6 +36,12 @@ export const en = {
|
|
|
36
36
|
chart: { label: 'Chart', total: 'Total' },
|
|
37
37
|
stat: { up: 'Up', down: 'Down', same: 'No change' },
|
|
38
38
|
themeToggle: { label: 'Colour scheme', light: 'Light', dark: 'Dark', system: 'System' },
|
|
39
|
+
appearance: {
|
|
40
|
+
label: 'Appearance', mode: 'Mode', accent: 'Colour', sidebar: 'Sidebar', lightOnly: 'in light mode',
|
|
41
|
+
custom: 'Any colour', dev: 'Dev mode', reset: 'Reset',
|
|
42
|
+
accents: { slate: 'Slate', blue: 'Blue', indigo: 'Indigo', violet: 'Violet', teal: 'Teal', emerald: 'Emerald', rose: 'Rose' },
|
|
43
|
+
sidebars: { default: 'Default', neutral: 'Neutral', soft: 'Soft', tint: 'Tint', accent: 'Accent', dark: 'Dark', deep: 'Deep' },
|
|
44
|
+
},
|
|
39
45
|
sortableList: {
|
|
40
46
|
handle: 'Drag to reorder',
|
|
41
47
|
instructions: 'Press Space to pick this up. While dragging, use the arrow keys to move it, Space to drop it, or Escape to cancel.',
|
|
@@ -115,6 +121,12 @@ export const fr = {
|
|
|
115
121
|
chart: { label: 'Graphique', total: 'Total' },
|
|
116
122
|
stat: { up: 'Hausse', down: 'Baisse', same: 'Stable' },
|
|
117
123
|
themeToggle: { label: 'Thème', light: 'Clair', dark: 'Sombre', system: 'Système' },
|
|
124
|
+
appearance: {
|
|
125
|
+
label: 'Apparence', mode: 'Mode', accent: 'Couleur', sidebar: 'Barre latérale', lightOnly: 'en mode clair',
|
|
126
|
+
custom: 'Couleur libre', dev: 'Mode dev', reset: 'Réinitialiser',
|
|
127
|
+
accents: { slate: 'Ardoise', blue: 'Bleu', indigo: 'Indigo', violet: 'Violet', teal: 'Teal', emerald: 'Émeraude', rose: 'Rose' },
|
|
128
|
+
sidebars: { default: 'Par défaut', neutral: 'Neutre', soft: 'Doux', tint: 'Teinte', accent: 'Accent', dark: 'Foncé', deep: 'Très foncé' },
|
|
129
|
+
},
|
|
118
130
|
sortableList: {
|
|
119
131
|
handle: 'Glisser pour réordonner',
|
|
120
132
|
instructions: 'Appuyez sur Espace pour saisir cet élément. Déplacez-le ensuite avec les flèches, déposez-le avec Espace, ou appuyez sur Échap pour annuler.',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
@layer ui {
|
|
2
|
-
/*
|
|
2
|
+
/* The positioner carries the z-index itself. Zag sets it inline as
|
|
3
|
+
var(--z-index) but doesn't always fill in the variable, so set both. */
|
|
4
|
+
[data-scope=hover-card][data-part=positioner] { z-index: 50; --z-index: 50; }
|
|
3
5
|
[data-scope=hover-card][data-part=content] {
|
|
4
6
|
z-index: 50; font-weight: 400; width: 18rem; max-width: calc(100vw - 32px); padding: 1rem; box-sizing: border-box; outline: none;
|
|
5
7
|
background: var(--ui-surface); color: var(--ui-text); font-family: var(--ui-font); font-size: 14px;
|
package/dist/icons.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/icons.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Loads iconify-icon (the <iconify-icon> element) for the components that
|
|
2
|
+
// draw icons. Icons draw inside its shadow root, out of reach of page CSS,
|
|
3
|
+
// but custom properties inherit into it: this rule there gives Lucide-style
|
|
4
|
+
// icons (a stroke width of 2 in their markup) the --ui-icon-stroke weight.
|
|
5
|
+
// Icons with any other stroke, or none, are left alone.
|
|
6
|
+
import { appendCustomStyle } from 'iconify-icon';
|
|
7
|
+
appendCustomStyle('[stroke-width="2"] { stroke-width: var(--ui-icon-stroke, 2); }');
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export { RadioGroup } from './radio-group/RadioGroup.js';
|
|
|
26
26
|
export { Segmented } from './segment-group/Segmented.js';
|
|
27
27
|
export { ThemeToggle } from './theme-toggle/ThemeToggle.js';
|
|
28
28
|
export { colorScheme, useColorScheme, type Scheme } from './color-scheme.js';
|
|
29
|
+
export { Appearance } from './appearance-panel/Appearance.js';
|
|
30
|
+
export { appearance, useAppearance, accents, accentScale, accentSteps, sidebarStyles, type AccentName, type AppearanceValue, type SidebarStyle, } from './appearance.js';
|
|
29
31
|
export { Tooltip } from './tooltip/Tooltip.js';
|
|
30
32
|
export { Menu, ContextMenu, MenuItem, MenuSeparator, MenuGroup, MenuCheckbox, MenuRadioGroup, MenuRadio, Submenu, type MenuEntry, type MenuOptionChange, } from './menu/Menu.js';
|
|
31
33
|
export { Tabs, Tab } from './tabs/Tabs.js';
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,8 @@ export { RadioGroup } from './radio-group/RadioGroup.js';
|
|
|
27
27
|
export { Segmented } from './segment-group/Segmented.js';
|
|
28
28
|
export { ThemeToggle } from './theme-toggle/ThemeToggle.js';
|
|
29
29
|
export { colorScheme, useColorScheme } from './color-scheme.js';
|
|
30
|
+
export { Appearance } from './appearance-panel/Appearance.js';
|
|
31
|
+
export { appearance, useAppearance, accents, accentScale, accentSteps, sidebarStyles, } from './appearance.js';
|
|
30
32
|
export { Tooltip } from './tooltip/Tooltip.js';
|
|
31
33
|
export { Menu, ContextMenu, MenuItem, MenuSeparator, MenuGroup, MenuCheckbox, MenuRadioGroup, MenuRadio, Submenu, } from './menu/Menu.js';
|
|
32
34
|
export { Tabs, Tab } from './tabs/Tabs.js';
|
package/dist/input/Input.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ComponentProps, HTMLInputTypeAttribute, ReactNode } from 'react';
|
|
2
2
|
import { Field } from '@ark-ui/react/field';
|
|
3
|
-
import '
|
|
3
|
+
import '../icons.js';
|
|
4
4
|
import { type Classes } from '../utils.js';
|
|
5
5
|
import '../theme.css';
|
|
6
6
|
import './input.css';
|
package/dist/input/Input.js
CHANGED
|
@@ -24,7 +24,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
24
24
|
*/
|
|
25
25
|
import { useId } from 'react';
|
|
26
26
|
import { Field, useFieldContext } from '@ark-ui/react/field';
|
|
27
|
-
import '
|
|
27
|
+
import '../icons.js';
|
|
28
28
|
import { cx, partOf, partsOf, withParts } from '../utils.js';
|
|
29
29
|
import '../theme.css';
|
|
30
30
|
import './input.css';
|