@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.
Files changed (142) hide show
  1. package/README.md +55 -5
  2. package/dist/accordion/Accordion.d.ts +1 -1
  3. package/dist/accordion/Accordion.js +1 -1
  4. package/dist/action-bar/ActionBar.d.ts +1 -1
  5. package/dist/action-bar/ActionBar.js +1 -1
  6. package/dist/alert/Alert.d.ts +1 -1
  7. package/dist/alert/Alert.js +1 -1
  8. package/dist/app-shell/AppShell.d.ts +9 -1
  9. package/dist/app-shell/AppShell.js +46 -5
  10. package/dist/app-shell/Topbar.d.ts +1 -1
  11. package/dist/app-shell/Topbar.js +1 -1
  12. package/dist/app-shell/app-shell.css +5 -0
  13. package/dist/app-shell/shell.d.ts +2 -0
  14. package/dist/appearance-panel/Appearance.d.ts +39 -0
  15. package/dist/appearance-panel/Appearance.js +74 -0
  16. package/dist/appearance-panel/appearance.css +17 -0
  17. package/dist/appearance.d.ts +42 -0
  18. package/dist/appearance.js +157 -0
  19. package/dist/attachments/Attachments.d.ts +1 -1
  20. package/dist/attachments/Attachments.js +1 -1
  21. package/dist/avatar/Avatar.d.ts +1 -1
  22. package/dist/avatar/Avatar.js +1 -1
  23. package/dist/badge/Badge.d.ts +1 -1
  24. package/dist/badge/Badge.js +1 -1
  25. package/dist/banner/Banner.d.ts +1 -1
  26. package/dist/banner/Banner.js +1 -1
  27. package/dist/breadcrumbs/Breadcrumbs.d.ts +1 -1
  28. package/dist/breadcrumbs/Breadcrumbs.js +1 -1
  29. package/dist/button/Button.d.ts +3 -2
  30. package/dist/button/Button.js +1 -1
  31. package/dist/card/Card.d.ts +2 -2
  32. package/dist/card/Card.js +1 -1
  33. package/dist/checkbox/Checkbox.d.ts +1 -1
  34. package/dist/checkbox/Checkbox.js +1 -1
  35. package/dist/checkbox/CheckboxGroup.d.ts +1 -1
  36. package/dist/checkbox/CheckboxGroup.js +1 -1
  37. package/dist/checkbox/checkbox.css +1 -1
  38. package/dist/collapsible/Collapsible.d.ts +1 -1
  39. package/dist/collapsible/Collapsible.js +1 -1
  40. package/dist/command/Command.d.ts +1 -1
  41. package/dist/command/Command.js +1 -1
  42. package/dist/copy-button/CopyButton.d.ts +1 -1
  43. package/dist/copy-button/CopyButton.js +1 -1
  44. package/dist/data-list/DataList.d.ts +1 -1
  45. package/dist/data-list/DataList.js +1 -1
  46. package/dist/date-picker/DatePicker.d.ts +1 -1
  47. package/dist/date-picker/DatePicker.js +1 -1
  48. package/dist/date-picker/date-picker.css +6 -3
  49. package/dist/dialog/Dialog.d.ts +1 -1
  50. package/dist/dialog/Dialog.js +1 -1
  51. package/dist/editable/Editable.d.ts +1 -1
  52. package/dist/editable/Editable.js +1 -1
  53. package/dist/editor/Editor.d.ts +1 -1
  54. package/dist/editor/Editor.js +1 -1
  55. package/dist/editor/editor.css +2 -1
  56. package/dist/empty-state/EmptyState.d.ts +1 -1
  57. package/dist/empty-state/EmptyState.js +1 -1
  58. package/dist/event-calendar/EventCalendar.d.ts +12 -5
  59. package/dist/event-calendar/EventCalendar.js +154 -41
  60. package/dist/event-calendar/event-calendar.css +10 -5
  61. package/dist/file-upload/FileUpload.d.ts +1 -1
  62. package/dist/file-upload/FileUpload.js +1 -1
  63. package/dist/format/messages.d.ts +12 -0
  64. package/dist/format/messages.js +12 -0
  65. package/dist/hover-card/hover-card.css +3 -1
  66. package/dist/icons.d.ts +1 -0
  67. package/dist/icons.js +7 -0
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.js +2 -0
  70. package/dist/input/Input.d.ts +1 -1
  71. package/dist/input/Input.js +1 -1
  72. package/dist/input/input.css +7 -1
  73. package/dist/menu/Menu.d.ts +1 -1
  74. package/dist/menu/Menu.js +1 -1
  75. package/dist/menu/menu.css +3 -1
  76. package/dist/native-select/NativeSelect.d.ts +1 -1
  77. package/dist/native-select/NativeSelect.js +1 -1
  78. package/dist/native-select/native-select.css +2 -1
  79. package/dist/number-input/NumberInput.d.ts +1 -1
  80. package/dist/number-input/NumberInput.js +1 -1
  81. package/dist/number-input/number-input.css +6 -3
  82. package/dist/page/Page.d.ts +4 -0
  83. package/dist/page/Page.js +4 -2
  84. package/dist/pagination/Pagination.d.ts +1 -1
  85. package/dist/pagination/Pagination.js +1 -1
  86. package/dist/password-input/PasswordInput.d.ts +1 -1
  87. package/dist/password-input/PasswordInput.js +1 -1
  88. package/dist/password-input/password-input.css +6 -3
  89. package/dist/pin-input/pin-input.css +2 -1
  90. package/dist/popover/Popover.d.ts +1 -1
  91. package/dist/popover/Popover.js +1 -1
  92. package/dist/popover/popover.css +3 -1
  93. package/dist/radio-group/RadioGroup.d.ts +1 -1
  94. package/dist/radio-group/RadioGroup.js +1 -1
  95. package/dist/radio-group/radio-group.css +1 -1
  96. package/dist/segment-group/Segmented.d.ts +1 -1
  97. package/dist/segment-group/Segmented.js +1 -1
  98. package/dist/select/ListSelect.d.ts +1 -1
  99. package/dist/select/ListSelect.js +1 -1
  100. package/dist/select/SearchSelect.d.ts +1 -1
  101. package/dist/select/SearchSelect.js +1 -1
  102. package/dist/select/search-select.css +6 -3
  103. package/dist/select/select.css +5 -2
  104. package/dist/sheet/Sheet.d.ts +1 -1
  105. package/dist/sheet/Sheet.js +1 -1
  106. package/dist/sidebar/NavGroup.d.ts +3 -2
  107. package/dist/sidebar/NavGroup.js +14 -4
  108. package/dist/sidebar/NavItem.d.ts +1 -1
  109. package/dist/sidebar/NavItem.js +1 -1
  110. package/dist/sidebar/NavSection.d.ts +1 -1
  111. package/dist/sidebar/NavSection.js +1 -1
  112. package/dist/sidebar/Sidebar.d.ts +1 -1
  113. package/dist/sidebar/Sidebar.js +1 -1
  114. package/dist/sidebar/SidebarUser.d.ts +1 -1
  115. package/dist/sidebar/SidebarUser.js +1 -1
  116. package/dist/sidebar/sidebar.css +75 -14
  117. package/dist/sortable-list/SortableList.d.ts +1 -1
  118. package/dist/sortable-list/SortableList.js +1 -1
  119. package/dist/stat/Stat.d.ts +1 -1
  120. package/dist/stat/Stat.js +1 -1
  121. package/dist/swatch-picker/SwatchPicker.d.ts +1 -1
  122. package/dist/swatch-picker/SwatchPicker.js +1 -1
  123. package/dist/swatch-picker/swatch-picker.css +3 -2
  124. package/dist/table/Table.d.ts +1 -1
  125. package/dist/table/Table.js +1 -1
  126. package/dist/tabs/Tabs.d.ts +1 -1
  127. package/dist/tabs/Tabs.js +1 -1
  128. package/dist/tags-input/TagsInput.d.ts +1 -1
  129. package/dist/tags-input/TagsInput.js +1 -1
  130. package/dist/tags-input/tags-input.css +6 -3
  131. package/dist/textarea/textarea.css +2 -1
  132. package/dist/theme-toggle/ThemeToggle.d.ts +1 -1
  133. package/dist/theme-toggle/ThemeToggle.js +1 -1
  134. package/dist/theme.css +51 -10
  135. package/dist/timeline/Timeline.d.ts +1 -1
  136. package/dist/timeline/Timeline.js +1 -1
  137. package/dist/toast/Toaster.d.ts +1 -1
  138. package/dist/toast/Toaster.js +1 -1
  139. package/dist/tooltip/tooltip.css +3 -1
  140. package/dist/tree-view/TreeView.d.ts +1 -1
  141. package/dist/tree-view/TreeView.js +1 -1
  142. 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). The change spreads straight into
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 'iconify-icon';
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) => ReactNode;
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). The change spreads straight into
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 'iconify-icon';
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
- export function EventCalendar({ events = [], resources = 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 }) {
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
- const lanes = resources?.length ? resources : [null];
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 && (!resources?.length || c.resource?.id === event.resourceId));
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
- const queryFor = (event, next) => ({
305
- start: toISODateTime(next.start), end: toISODateTime(next.end), allDay: false, event,
306
- ...(next.column.resource && { resourceId: next.column.resource.id }),
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, ...(next.column.resource && { resourceId: next.column.resource.id }),
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, x: e.clientX, y: e.clientY, top: box.top, height: box.height, width, left: box.left, from: at, moved: false, keys: keysOf(e) };
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 box = e.currentTarget.closest('[data-part=day]').getBoundingClientRect();
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, x: e.clientX, y: e.clientY, top: box.top, height: box.height, width: box.width, left: box.left, from, moved: false, keys: keysOf(e), event };
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
- const dx = e.clientX - g.x;
365
- const dy = e.clientY - g.y;
366
- if (!g.moved && Math.hypot(dx, dy) < 4)
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
- const perMinute = g.height / span;
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((e.clientX - g.left) / g.width)));
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
- else if (g.kind === 'select') {
375
- const at = minuteAt(e.clientY, g);
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(e.clientY, g) });
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
- setPreview({ kind: 'resize', id: g.event.id, minutes: Math.round(dy / perMinute / slotMinutes) * slotMinutes });
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 && (!resources?.length || c.resource?.id === event.resourceId) ? i : -1))
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
- const twoRows = !!resources?.length && count > 1;
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 = !resources?.length || count > 1 ? rowNumber++ : -1;
574
- const resourceRow = resources?.length ? rowNumber++ : -1;
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
- && (!resources?.length || event.resourceId === column.resource?.id));
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": (!!resources?.length && 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)
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
- [data-scope=event-calendar][data-part=grid] { display: grid; grid-template-columns: minmax(3.5rem, max-content) repeat(var(--columns), minmax(6.5rem, 1fr));
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 'iconify-icon';
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 'iconify-icon';
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;
@@ -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
- /* Zag copies the content's z-index onto its positioner. */
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;
@@ -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';
@@ -1,6 +1,6 @@
1
1
  import type { ComponentProps, HTMLInputTypeAttribute, ReactNode } from 'react';
2
2
  import { Field } from '@ark-ui/react/field';
3
- import 'iconify-icon';
3
+ import '../icons.js';
4
4
  import { type Classes } from '../utils.js';
5
5
  import '../theme.css';
6
6
  import './input.css';
@@ -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 'iconify-icon';
27
+ import '../icons.js';
28
28
  import { cx, partOf, partsOf, withParts } from '../utils.js';
29
29
  import '../theme.css';
30
30
  import './input.css';