@milktop/react-ui 0.1.2 → 0.2.0
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 +55 -5
- 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 +154 -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,24 @@ 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).
|
|
305
|
+
function withinHours(event, kind, byMinutes) {
|
|
306
|
+
const start = parse(event.start);
|
|
307
|
+
const from = minutesOfDay(start);
|
|
308
|
+
const to = from + minutesBetween(start, parse(event.end));
|
|
309
|
+
const latest = Math.max(0, endHour * 60 - to);
|
|
310
|
+
const earliest = kind === 'move' ? Math.min(0, startHour * 60 - from) : -Infinity;
|
|
311
|
+
return Math.max(earliest, Math.min(latest, byMinutes));
|
|
312
|
+
}
|
|
313
|
+
// Where a pinned event placed at `minute` starts: on a slot, ending by the day's last hour.
|
|
314
|
+
function placeAt(minute) {
|
|
315
|
+
if (!pinned)
|
|
316
|
+
return minute;
|
|
317
|
+
const length = minutesBetween(parse(pinned.start), parse(pinned.end)) || slotMinutes;
|
|
318
|
+
const latest = startHour * 60 + Math.floor((span - length) / slotMinutes) * slotMinutes;
|
|
319
|
+
return Math.max(startHour * 60, Math.min(latest, minute));
|
|
320
|
+
}
|
|
287
321
|
// Where an event would go if moved by `byColumns` and `byMinutes` (or resized by `byMinutes`).
|
|
288
322
|
function target(event, kind, byColumns, byMinutes) {
|
|
289
323
|
const start = parse(event.start);
|
|
@@ -291,6 +325,9 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
291
325
|
const from = columnOf(event, event.start.slice(0, 10));
|
|
292
326
|
if (from < 0)
|
|
293
327
|
return null;
|
|
328
|
+
byMinutes = withinHours(event, kind, byMinutes);
|
|
329
|
+
if (!byColumns && !byMinutes)
|
|
330
|
+
return null;
|
|
294
331
|
if (kind === 'resize') {
|
|
295
332
|
const newEnd = addMinutes(end, byMinutes);
|
|
296
333
|
if (minutesBetween(start, newEnd) < slotMinutes)
|
|
@@ -301,10 +338,11 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
301
338
|
const days = Math.round((column.date.getTime() - columns[from].date.getTime()) / 86400000);
|
|
302
339
|
return { start: addMinutes(addDays(start, days), byMinutes), end: addMinutes(addDays(end, days), byMinutes), column };
|
|
303
340
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
341
|
+
// Its new column's resource, or (in a view without their columns) its own.
|
|
342
|
+
const queryFor = (event, next) => {
|
|
343
|
+
const resourceId = next.column.resource ? next.column.resource.id : event.resourceId;
|
|
344
|
+
return { start: toISODateTime(next.start), end: toISODateTime(next.end), allDay: false, event, ...(resourceId != null && { resourceId }) };
|
|
345
|
+
};
|
|
308
346
|
// Applies a change (after checking it's allowed).
|
|
309
347
|
function commit(event, kind, next) {
|
|
310
348
|
const query = queryFor(event, next);
|
|
@@ -316,7 +354,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
316
354
|
? t.resized(nameOf(event), timeFormat.format(next.end))
|
|
317
355
|
: t.moved(nameOf(event), `${longDay.format(next.start)}, ${timeFormat.format(next.start)}${resourceName(next.column)}`));
|
|
318
356
|
onEventChange?.(event, {
|
|
319
|
-
start: query.start, end: query.end, ...(
|
|
357
|
+
start: query.start, end: query.end, ...(query.resourceId != null && { resourceId: query.resourceId }),
|
|
320
358
|
}, { kind });
|
|
321
359
|
}
|
|
322
360
|
function change(event, kind, byColumns, byMinutes) {
|
|
@@ -334,23 +372,30 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
334
372
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
335
373
|
}
|
|
336
374
|
catch { /* not a live pointer */ }
|
|
375
|
+
// Where things are scrolled at the start: a drag near an edge scrolls the
|
|
376
|
+
// grid (or the page), and positions are measured again as it does.
|
|
377
|
+
const scroll = { left: grid.current?.scrollLeft ?? 0, x: scrollX };
|
|
378
|
+
const point = { x: e.clientX, y: e.clientY, lastX: e.clientX, lastY: e.clientY, moved: false, keys: keysOf(e), scroll };
|
|
337
379
|
if (kind === 'all-day') {
|
|
338
380
|
// The strip spans every column: which one is under the pointer.
|
|
339
381
|
const box = e.currentTarget.getBoundingClientRect();
|
|
340
382
|
const width = box.width / columns.length;
|
|
341
383
|
const at = Math.max(0, Math.min(columns.length - 1, Math.floor((e.clientX - box.left) / width)));
|
|
342
|
-
gesture.current = { kind, column: at,
|
|
384
|
+
gesture.current = { kind, column: at, ...point, target: e.currentTarget, width, from: at, exact: 0 };
|
|
343
385
|
setPreview({ kind: 'all-day', columns: [at] });
|
|
344
386
|
return;
|
|
345
387
|
}
|
|
346
|
-
const
|
|
388
|
+
const day = e.currentTarget.closest('[data-part=day][role=gridcell]');
|
|
389
|
+
const box = day.getBoundingClientRect();
|
|
347
390
|
const from = minuteAt(e.clientY, box);
|
|
348
|
-
gesture.current = { kind, column,
|
|
391
|
+
gesture.current = { kind, column, ...point, target: day, width: box.width, from, exact: exactMinute(e.clientY, box), event };
|
|
349
392
|
if (kind === 'select')
|
|
350
393
|
setPreview({ kind: 'select', column, from, to: from + slotMinutes });
|
|
351
394
|
if (kind === 'place')
|
|
352
|
-
setPreview({ kind: 'place', column, from });
|
|
395
|
+
setPreview({ kind: 'place', column, from: placeAt(from) });
|
|
353
396
|
}
|
|
397
|
+
// The minute of the day at a height in a day column (not snapped).
|
|
398
|
+
const exactMinute = (y, box) => startHour * 60 + Math.max(0, Math.min(span - 1, ((y - box.top) / box.height) * span));
|
|
354
399
|
// The columns from one to another (with resources, only the first one's resource).
|
|
355
400
|
const columnRange = (a, b) => {
|
|
356
401
|
const lo = Math.min(a, b);
|
|
@@ -361,35 +406,100 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
361
406
|
const g = gesture.current;
|
|
362
407
|
if (!g)
|
|
363
408
|
return;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (!g.moved && Math.hypot(
|
|
409
|
+
g.lastX = e.clientX;
|
|
410
|
+
g.lastY = e.clientY;
|
|
411
|
+
if (!g.moved && Math.hypot(e.clientX - g.x, e.clientY - g.y) < 4)
|
|
367
412
|
return;
|
|
368
413
|
g.moved = true;
|
|
369
|
-
|
|
414
|
+
updatePreview();
|
|
415
|
+
autoScroll();
|
|
416
|
+
}
|
|
417
|
+
// The preview from the pointer's last position, measured afresh, so it
|
|
418
|
+
// stays right while the grid or the page scrolls under it.
|
|
419
|
+
function updatePreview() {
|
|
420
|
+
const g = gesture.current;
|
|
421
|
+
if (!g)
|
|
422
|
+
return;
|
|
423
|
+
const box = g.target.getBoundingClientRect();
|
|
370
424
|
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) });
|
|
425
|
+
const at = Math.max(0, Math.min(columns.length - 1, Math.floor((g.lastX - box.left) / g.width)));
|
|
426
|
+
return setPreview({ kind: 'all-day', columns: columnRange(g.from, at) });
|
|
373
427
|
}
|
|
374
|
-
|
|
375
|
-
|
|
428
|
+
const perMinute = box.height / span;
|
|
429
|
+
if (g.kind === 'select') {
|
|
430
|
+
const at = minuteAt(g.lastY, box);
|
|
376
431
|
setPreview({ kind: 'select', column: g.column, from: Math.min(g.from, at), to: Math.max(g.from, at) + slotMinutes });
|
|
377
432
|
}
|
|
378
433
|
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 });
|
|
434
|
+
setPreview({ kind: 'place', column: g.column, from: placeAt(minuteAt(g.lastY, box)) });
|
|
385
435
|
}
|
|
386
436
|
else {
|
|
387
|
-
|
|
437
|
+
let byMinutes = Math.round((exactMinute(g.lastY, box) - g.exact) / slotMinutes) * slotMinutes;
|
|
438
|
+
if (g.kind === 'move') {
|
|
439
|
+
const scrolledX = (grid.current?.scrollLeft ?? 0) - g.scroll.left + scrollX - g.scroll.x;
|
|
440
|
+
const byColumns = Math.round((g.lastX - g.x + scrolledX) / g.width);
|
|
441
|
+
byMinutes = withinHours(g.event, 'move', byMinutes);
|
|
442
|
+
setPreview({ kind: 'move', id: g.event.id, columns: byColumns, minutes: byMinutes, x: byColumns * g.width, y: byMinutes * perMinute });
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
setPreview({ kind: 'resize', id: g.event.id, minutes: withinHours(g.event, 'resize', byMinutes) });
|
|
446
|
+
}
|
|
388
447
|
}
|
|
389
448
|
}
|
|
449
|
+
// Near an edge while dragging: scroll that way, faster the closer it is,
|
|
450
|
+
// each frame until the pointer moves away or lets go. Up and down scrolls
|
|
451
|
+
// the grid when it has a `height` (below its sticky headers), or else the
|
|
452
|
+
// page; sideways, the grid when it's wider than its box.
|
|
453
|
+
const scrolling = useRef(false);
|
|
454
|
+
function autoScroll() {
|
|
455
|
+
if (scrolling.current)
|
|
456
|
+
return;
|
|
457
|
+
scrolling.current = true;
|
|
458
|
+
// Each loop belongs to one gesture: one left over from the last drag stops.
|
|
459
|
+
const owner = gesture.current;
|
|
460
|
+
const step = () => {
|
|
461
|
+
const g = gesture.current;
|
|
462
|
+
const el = grid.current;
|
|
463
|
+
if (!g || g !== owner || !g.moved || !el)
|
|
464
|
+
return void (scrolling.current = false);
|
|
465
|
+
const box = el.getBoundingClientRect();
|
|
466
|
+
const edge = 48;
|
|
467
|
+
const speed = (near) => Math.ceil(Math.min(1, Math.max(0, (edge - near) / edge)) * 18);
|
|
468
|
+
let vy = 0;
|
|
469
|
+
let vx = 0;
|
|
470
|
+
if (g.kind !== 'all-day') {
|
|
471
|
+
const scroller = el.scrollHeight > el.clientHeight + 1 ? el : scrollParent(el);
|
|
472
|
+
// The grid below its sticky headers; the page (whose top can sit under
|
|
473
|
+
// a fixed top bar); or an app shell's scrolling page.
|
|
474
|
+
const area = scroller === el ? { top: box.top + (parseFloat(el.style.getPropertyValue('--sticky-total')) || 0), bottom: box.bottom }
|
|
475
|
+
: scroller === document.scrollingElement ? { top: 64, bottom: innerHeight }
|
|
476
|
+
: scroller.getBoundingClientRect();
|
|
477
|
+
vy = g.lastY < area.top + edge ? -speed(g.lastY - area.top) : g.lastY > area.bottom - edge ? speed(area.bottom - g.lastY) : 0;
|
|
478
|
+
const before = scroller.scrollTop;
|
|
479
|
+
scroller.scrollTop += vy;
|
|
480
|
+
// Already at the end: nothing more to scroll.
|
|
481
|
+
if (scroller.scrollTop === before)
|
|
482
|
+
vy = 0;
|
|
483
|
+
}
|
|
484
|
+
if (el.scrollWidth > el.clientWidth + 1) {
|
|
485
|
+
const left = box.left + (el.querySelector('[data-part=gutter]')?.offsetWidth ?? 0);
|
|
486
|
+
vx = g.lastX < left + edge ? -speed(g.lastX - left) : g.lastX > box.right - edge ? speed(box.right - g.lastX) : 0;
|
|
487
|
+
const before = el.scrollLeft;
|
|
488
|
+
el.scrollLeft += vx;
|
|
489
|
+
if (el.scrollLeft === before)
|
|
490
|
+
vx = 0;
|
|
491
|
+
}
|
|
492
|
+
if (!vx && !vy)
|
|
493
|
+
return void (scrolling.current = false);
|
|
494
|
+
updatePreview();
|
|
495
|
+
requestAnimationFrame(step);
|
|
496
|
+
};
|
|
497
|
+
requestAnimationFrame(step);
|
|
498
|
+
}
|
|
390
499
|
function end() {
|
|
391
500
|
const g = gesture.current;
|
|
392
501
|
gesture.current = null;
|
|
502
|
+
scrolling.current = false;
|
|
393
503
|
const p = latestPreview.current;
|
|
394
504
|
setPreview(null);
|
|
395
505
|
if (!g)
|
|
@@ -432,6 +542,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
432
542
|
}
|
|
433
543
|
function cancel() {
|
|
434
544
|
gesture.current = null;
|
|
545
|
+
scrolling.current = false;
|
|
435
546
|
setPreview(null);
|
|
436
547
|
}
|
|
437
548
|
function keyDown(e, event) {
|
|
@@ -457,10 +568,10 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
457
568
|
function hover(e, column) {
|
|
458
569
|
if (gesture.current)
|
|
459
570
|
return setHoverSlot(null);
|
|
460
|
-
const box = e.currentTarget.closest('[data-part=day]').getBoundingClientRect();
|
|
571
|
+
const box = e.currentTarget.closest('[data-part=day][role=gridcell]').getBoundingClientRect();
|
|
461
572
|
const from = minuteAt(e.clientY, box);
|
|
462
573
|
if (pinned)
|
|
463
|
-
return setPreview({ kind: 'place', column, from });
|
|
574
|
+
return setPreview({ kind: 'place', column, from: placeAt(from) });
|
|
464
575
|
// Over the empty column (or its shading), not an event.
|
|
465
576
|
const over = e.target;
|
|
466
577
|
setHoverSlot(onSelect && (over === e.currentTarget || over.dataset.part === 'unavailable') ? { column, from } : null);
|
|
@@ -504,7 +615,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
504
615
|
if (!el || !height || !scrollToTime || scrolled.current)
|
|
505
616
|
return;
|
|
506
617
|
scrolled.current = true;
|
|
507
|
-
const day = el.querySelector('[data-part=day]');
|
|
618
|
+
const day = el.querySelector('[data-part=day][role=gridcell]');
|
|
508
619
|
if (!day)
|
|
509
620
|
return;
|
|
510
621
|
const [h, m] = scrollToTime === 'now' ? [now.getHours() - 1, 0] : scrollToTime.split(':').map(Number);
|
|
@@ -516,7 +627,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
516
627
|
const allDay = events.filter(isAllDay).flatMap((event) => {
|
|
517
628
|
const from = event.start.slice(0, 10);
|
|
518
629
|
const to = event.end.slice(0, 10);
|
|
519
|
-
const covered = columns.map((c, i) => (c.iso >= from && c.iso <= to && (!
|
|
630
|
+
const covered = columns.map((c, i) => (c.iso >= from && c.iso <= to && (!split || c.resource?.id === event.resourceId) ? i : -1))
|
|
520
631
|
.filter((i) => i >= 0);
|
|
521
632
|
// Runs of neighbouring columns (with resources, a day's column for the event's resource).
|
|
522
633
|
const runs = [];
|
|
@@ -565,13 +676,15 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
565
676
|
}
|
|
566
677
|
const hiddenIn = (i) => hiddenCount[i];
|
|
567
678
|
const barColor = (event) => event.color ?? resourceOf(event.resourceId)?.color ?? 'accent';
|
|
568
|
-
|
|
679
|
+
// The events on a day, for its header.
|
|
680
|
+
const eventsOn = (d) => events.filter((event) => event.start.slice(0, 10) <= toISODate(d) && event.end.slice(0, 10) >= toISODate(d));
|
|
681
|
+
const twoRows = split && count > 1;
|
|
569
682
|
const editable = (event) => !!onEventChange && event.editable !== false && !event.pending;
|
|
570
683
|
const pinnedMinutes = pinned ? minutesBetween(parse(pinned.start), parse(pinned.end)) || slotMinutes : 0;
|
|
571
684
|
// The header rows, numbered for sticking: days, resources, extra rows, the all-day strip.
|
|
572
685
|
let rowNumber = 0;
|
|
573
|
-
const dayRow = !
|
|
574
|
-
const resourceRow =
|
|
686
|
+
const dayRow = !split || count > 1 ? rowNumber++ : -1;
|
|
687
|
+
const resourceRow = split ? rowNumber++ : -1;
|
|
575
688
|
const extraRows = rows.map(() => rowNumber++);
|
|
576
689
|
const allDayRow = rowNumber;
|
|
577
690
|
// The preview's problem, if any, shown as it's dragged.
|
|
@@ -595,7 +708,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
595
708
|
setDate(value);
|
|
596
709
|
setPicking(false);
|
|
597
710
|
} } }) }), _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() })] });
|
|
711
|
+
const content = renderDayHeader ? renderDayHeader(d, { events: eventsOn(d), resources }) : _jsxs(_Fragment, { children: [_jsx("span", { children: dayFormat.format(d) }), _jsx("strong", { children: d.getDate() })] });
|
|
599
712
|
return onDayClick
|
|
600
713
|
? _jsx("button", { type: "button", ...part('header-button'), onClick: () => onDayClick(toISODate(d)), "aria-label": longDay.format(d), children: content })
|
|
601
714
|
: content;
|
|
@@ -629,7 +742,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
629
742
|
}, onClick: () => onEventClick?.(event), onContextMenu: (e) => { if (openMenu(e.clientX, e.clientY, eventMenu?.(event)))
|
|
630
743
|
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
744
|
const own = events.filter((event) => !isAllDay(event) && event.start.startsWith(column.iso)
|
|
632
|
-
&& (!
|
|
745
|
+
&& (!column.resource || event.resourceId === column.resource.id));
|
|
633
746
|
const selecting = preview?.kind === 'select' && preview.column === index ? preview : null;
|
|
634
747
|
const placing = preview?.kind === 'place' && preview.column === index ? preview : null;
|
|
635
748
|
const isToday = sameDay(column.date, now);
|
|
@@ -640,7 +753,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
640
753
|
else if (onSelect)
|
|
641
754
|
begin(e, 'select', index);
|
|
642
755
|
};
|
|
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": (
|
|
756
|
+
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
757
|
press(e); }, onPointerMove: (e) => { track(e); hover(e, index); }, onPointerUp: end, onPointerCancel: cancel, onPointerLeave: () => { setHoverSlot(null); if (pinned && !gesture.current)
|
|
645
758
|
setPreview(null); }, onContextMenu: (e) => {
|
|
646
759
|
// An empty time (or its unavailable shading), not an event.
|
|
@@ -655,7 +768,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
|
|
|
655
768
|
const resizing = preview?.kind === 'resize' && preview.id === event.id ? preview : null;
|
|
656
769
|
const shownEnd = resizing ? addMinutes(finish, resizing.minutes) : finish;
|
|
657
770
|
const minutes = minutesBetween(start, shownEnd);
|
|
658
|
-
const color = event.color ?? column.resource?.color ?? 'accent';
|
|
771
|
+
const color = event.color ?? (column.resource ?? resourceOf(event.resourceId))?.color ?? 'accent';
|
|
659
772
|
const time = `${timeFormat.format(start)} – ${timeFormat.format(shownEnd)}`;
|
|
660
773
|
const box = { top: top(minutesOfDay(start)), left: `${(sub / subs) * 100}%`, width: `${100 / subs}%` };
|
|
661
774
|
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';
|