@milktop/react-ui 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +57 -6
  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 +155 -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,25 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
284
300
  };
285
301
  const refocus = useRef(null);
286
302
  const suppressClick = useRef(false);
303
+ // `byMinutes` kept to the day's hours: an event moves or grows no further than
304
+ // the grid shows (one already past an edge doesn't go further past it), and
305
+ // shrinks to a slot at the shortest (one already shorter doesn't shrink).
306
+ function withinHours(event, kind, byMinutes) {
307
+ const start = parse(event.start);
308
+ const from = minutesOfDay(start);
309
+ const length = minutesBetween(start, parse(event.end));
310
+ const latest = Math.max(0, endHour * 60 - from - length);
311
+ const earliest = kind === 'move' ? Math.min(0, startHour * 60 - from) : Math.min(0, slotMinutes - length);
312
+ return Math.max(earliest, Math.min(latest, byMinutes));
313
+ }
314
+ // Where a pinned event placed at `minute` starts: on a slot, ending by the day's last hour.
315
+ function placeAt(minute) {
316
+ if (!pinned)
317
+ return minute;
318
+ const length = minutesBetween(parse(pinned.start), parse(pinned.end)) || slotMinutes;
319
+ const latest = startHour * 60 + Math.floor((span - length) / slotMinutes) * slotMinutes;
320
+ return Math.max(startHour * 60, Math.min(latest, minute));
321
+ }
287
322
  // Where an event would go if moved by `byColumns` and `byMinutes` (or resized by `byMinutes`).
288
323
  function target(event, kind, byColumns, byMinutes) {
289
324
  const start = parse(event.start);
@@ -291,6 +326,9 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
291
326
  const from = columnOf(event, event.start.slice(0, 10));
292
327
  if (from < 0)
293
328
  return null;
329
+ byMinutes = withinHours(event, kind, byMinutes);
330
+ if (!byColumns && !byMinutes)
331
+ return null;
294
332
  if (kind === 'resize') {
295
333
  const newEnd = addMinutes(end, byMinutes);
296
334
  if (minutesBetween(start, newEnd) < slotMinutes)
@@ -301,10 +339,11 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
301
339
  const days = Math.round((column.date.getTime() - columns[from].date.getTime()) / 86400000);
302
340
  return { start: addMinutes(addDays(start, days), byMinutes), end: addMinutes(addDays(end, days), byMinutes), column };
303
341
  }
304
- 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
- });
342
+ // Its new column's resource, or (in a view without their columns) its own.
343
+ const queryFor = (event, next) => {
344
+ const resourceId = next.column.resource ? next.column.resource.id : event.resourceId;
345
+ return { start: toISODateTime(next.start), end: toISODateTime(next.end), allDay: false, event, ...(resourceId != null && { resourceId }) };
346
+ };
308
347
  // Applies a change (after checking it's allowed).
309
348
  function commit(event, kind, next) {
310
349
  const query = queryFor(event, next);
@@ -316,7 +355,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
316
355
  ? t.resized(nameOf(event), timeFormat.format(next.end))
317
356
  : t.moved(nameOf(event), `${longDay.format(next.start)}, ${timeFormat.format(next.start)}${resourceName(next.column)}`));
318
357
  onEventChange?.(event, {
319
- start: query.start, end: query.end, ...(next.column.resource && { resourceId: next.column.resource.id }),
358
+ start: query.start, end: query.end, ...(query.resourceId != null && { resourceId: query.resourceId }),
320
359
  }, { kind });
321
360
  }
322
361
  function change(event, kind, byColumns, byMinutes) {
@@ -334,23 +373,30 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
334
373
  e.currentTarget.setPointerCapture(e.pointerId);
335
374
  }
336
375
  catch { /* not a live pointer */ }
376
+ // Where things are scrolled at the start: a drag near an edge scrolls the
377
+ // grid (or the page), and positions are measured again as it does.
378
+ const scroll = { left: grid.current?.scrollLeft ?? 0, x: scrollX };
379
+ const point = { x: e.clientX, y: e.clientY, lastX: e.clientX, lastY: e.clientY, moved: false, keys: keysOf(e), scroll };
337
380
  if (kind === 'all-day') {
338
381
  // The strip spans every column: which one is under the pointer.
339
382
  const box = e.currentTarget.getBoundingClientRect();
340
383
  const width = box.width / columns.length;
341
384
  const at = Math.max(0, Math.min(columns.length - 1, Math.floor((e.clientX - box.left) / width)));
342
- gesture.current = { kind, column: at, x: e.clientX, y: e.clientY, top: box.top, height: box.height, width, left: box.left, from: at, moved: false, keys: keysOf(e) };
385
+ gesture.current = { kind, column: at, ...point, target: e.currentTarget, width, from: at, exact: 0 };
343
386
  setPreview({ kind: 'all-day', columns: [at] });
344
387
  return;
345
388
  }
346
- const box = e.currentTarget.closest('[data-part=day]').getBoundingClientRect();
389
+ const day = e.currentTarget.closest('[data-part=day][role=gridcell]');
390
+ const box = day.getBoundingClientRect();
347
391
  const from = minuteAt(e.clientY, box);
348
- gesture.current = { kind, column, 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 };
392
+ gesture.current = { kind, column, ...point, target: day, width: box.width, from, exact: exactMinute(e.clientY, box), event };
349
393
  if (kind === 'select')
350
394
  setPreview({ kind: 'select', column, from, to: from + slotMinutes });
351
395
  if (kind === 'place')
352
- setPreview({ kind: 'place', column, from });
396
+ setPreview({ kind: 'place', column, from: placeAt(from) });
353
397
  }
398
+ // The minute of the day at a height in a day column (not snapped).
399
+ const exactMinute = (y, box) => startHour * 60 + Math.max(0, Math.min(span - 1, ((y - box.top) / box.height) * span));
354
400
  // The columns from one to another (with resources, only the first one's resource).
355
401
  const columnRange = (a, b) => {
356
402
  const lo = Math.min(a, b);
@@ -361,35 +407,100 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
361
407
  const g = gesture.current;
362
408
  if (!g)
363
409
  return;
364
- const dx = e.clientX - g.x;
365
- const dy = e.clientY - g.y;
366
- if (!g.moved && Math.hypot(dx, dy) < 4)
410
+ g.lastX = e.clientX;
411
+ g.lastY = e.clientY;
412
+ if (!g.moved && Math.hypot(e.clientX - g.x, e.clientY - g.y) < 4)
367
413
  return;
368
414
  g.moved = true;
369
- const perMinute = g.height / span;
415
+ updatePreview();
416
+ autoScroll();
417
+ }
418
+ // The preview from the pointer's last position, measured afresh, so it
419
+ // stays right while the grid or the page scrolls under it.
420
+ function updatePreview() {
421
+ const g = gesture.current;
422
+ if (!g)
423
+ return;
424
+ const box = g.target.getBoundingClientRect();
370
425
  if (g.kind === 'all-day') {
371
- const at = Math.max(0, Math.min(columns.length - 1, Math.floor((e.clientX - g.left) / g.width)));
372
- setPreview({ kind: 'all-day', columns: columnRange(g.from, at) });
426
+ const at = Math.max(0, Math.min(columns.length - 1, Math.floor((g.lastX - box.left) / g.width)));
427
+ return setPreview({ kind: 'all-day', columns: columnRange(g.from, at) });
373
428
  }
374
- else if (g.kind === 'select') {
375
- const at = minuteAt(e.clientY, g);
429
+ const perMinute = box.height / span;
430
+ if (g.kind === 'select') {
431
+ const at = minuteAt(g.lastY, box);
376
432
  setPreview({ kind: 'select', column: g.column, from: Math.min(g.from, at), to: Math.max(g.from, at) + slotMinutes });
377
433
  }
378
434
  else if (g.kind === 'place') {
379
- setPreview({ kind: 'place', column: g.column, from: minuteAt(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 });
435
+ setPreview({ kind: 'place', column: g.column, from: placeAt(minuteAt(g.lastY, box)) });
385
436
  }
386
437
  else {
387
- setPreview({ kind: 'resize', id: g.event.id, minutes: Math.round(dy / perMinute / slotMinutes) * slotMinutes });
438
+ let byMinutes = Math.round((exactMinute(g.lastY, box) - g.exact) / slotMinutes) * slotMinutes;
439
+ if (g.kind === 'move') {
440
+ const scrolledX = (grid.current?.scrollLeft ?? 0) - g.scroll.left + scrollX - g.scroll.x;
441
+ const byColumns = Math.round((g.lastX - g.x + scrolledX) / g.width);
442
+ byMinutes = withinHours(g.event, 'move', byMinutes);
443
+ setPreview({ kind: 'move', id: g.event.id, columns: byColumns, minutes: byMinutes, x: byColumns * g.width, y: byMinutes * perMinute });
444
+ }
445
+ else {
446
+ setPreview({ kind: 'resize', id: g.event.id, minutes: withinHours(g.event, 'resize', byMinutes) });
447
+ }
388
448
  }
389
449
  }
450
+ // Near an edge while dragging: scroll that way, faster the closer it is,
451
+ // each frame until the pointer moves away or lets go. Up and down scrolls
452
+ // the grid when it has a `height` (below its sticky headers), or else the
453
+ // page; sideways, the grid when it's wider than its box.
454
+ const scrolling = useRef(false);
455
+ function autoScroll() {
456
+ if (scrolling.current)
457
+ return;
458
+ scrolling.current = true;
459
+ // Each loop belongs to one gesture: one left over from the last drag stops.
460
+ const owner = gesture.current;
461
+ const step = () => {
462
+ const g = gesture.current;
463
+ const el = grid.current;
464
+ if (!g || g !== owner || !g.moved || !el)
465
+ return void (scrolling.current = false);
466
+ const box = el.getBoundingClientRect();
467
+ const edge = 48;
468
+ const speed = (near) => Math.ceil(Math.min(1, Math.max(0, (edge - near) / edge)) * 18);
469
+ let vy = 0;
470
+ let vx = 0;
471
+ if (g.kind !== 'all-day') {
472
+ const scroller = el.scrollHeight > el.clientHeight + 1 ? el : scrollParent(el);
473
+ // The grid below its sticky headers; the page (whose top can sit under
474
+ // a fixed top bar); or an app shell's scrolling page.
475
+ const area = scroller === el ? { top: box.top + (parseFloat(el.style.getPropertyValue('--sticky-total')) || 0), bottom: box.bottom }
476
+ : scroller === document.scrollingElement ? { top: 64, bottom: innerHeight }
477
+ : scroller.getBoundingClientRect();
478
+ vy = g.lastY < area.top + edge ? -speed(g.lastY - area.top) : g.lastY > area.bottom - edge ? speed(area.bottom - g.lastY) : 0;
479
+ const before = scroller.scrollTop;
480
+ scroller.scrollTop += vy;
481
+ // Already at the end: nothing more to scroll.
482
+ if (scroller.scrollTop === before)
483
+ vy = 0;
484
+ }
485
+ if (el.scrollWidth > el.clientWidth + 1) {
486
+ const left = box.left + (el.querySelector('[data-part=gutter]')?.offsetWidth ?? 0);
487
+ vx = g.lastX < left + edge ? -speed(g.lastX - left) : g.lastX > box.right - edge ? speed(box.right - g.lastX) : 0;
488
+ const before = el.scrollLeft;
489
+ el.scrollLeft += vx;
490
+ if (el.scrollLeft === before)
491
+ vx = 0;
492
+ }
493
+ if (!vx && !vy)
494
+ return void (scrolling.current = false);
495
+ updatePreview();
496
+ requestAnimationFrame(step);
497
+ };
498
+ requestAnimationFrame(step);
499
+ }
390
500
  function end() {
391
501
  const g = gesture.current;
392
502
  gesture.current = null;
503
+ scrolling.current = false;
393
504
  const p = latestPreview.current;
394
505
  setPreview(null);
395
506
  if (!g)
@@ -432,6 +543,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
432
543
  }
433
544
  function cancel() {
434
545
  gesture.current = null;
546
+ scrolling.current = false;
435
547
  setPreview(null);
436
548
  }
437
549
  function keyDown(e, event) {
@@ -457,10 +569,10 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
457
569
  function hover(e, column) {
458
570
  if (gesture.current)
459
571
  return setHoverSlot(null);
460
- const box = e.currentTarget.closest('[data-part=day]').getBoundingClientRect();
572
+ const box = e.currentTarget.closest('[data-part=day][role=gridcell]').getBoundingClientRect();
461
573
  const from = minuteAt(e.clientY, box);
462
574
  if (pinned)
463
- return setPreview({ kind: 'place', column, from });
575
+ return setPreview({ kind: 'place', column, from: placeAt(from) });
464
576
  // Over the empty column (or its shading), not an event.
465
577
  const over = e.target;
466
578
  setHoverSlot(onSelect && (over === e.currentTarget || over.dataset.part === 'unavailable') ? { column, from } : null);
@@ -504,7 +616,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
504
616
  if (!el || !height || !scrollToTime || scrolled.current)
505
617
  return;
506
618
  scrolled.current = true;
507
- const day = el.querySelector('[data-part=day]');
619
+ const day = el.querySelector('[data-part=day][role=gridcell]');
508
620
  if (!day)
509
621
  return;
510
622
  const [h, m] = scrollToTime === 'now' ? [now.getHours() - 1, 0] : scrollToTime.split(':').map(Number);
@@ -516,7 +628,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
516
628
  const allDay = events.filter(isAllDay).flatMap((event) => {
517
629
  const from = event.start.slice(0, 10);
518
630
  const to = event.end.slice(0, 10);
519
- const covered = columns.map((c, i) => (c.iso >= from && c.iso <= to && (!resources?.length || c.resource?.id === event.resourceId) ? i : -1))
631
+ const covered = columns.map((c, i) => (c.iso >= from && c.iso <= to && (!split || c.resource?.id === event.resourceId) ? i : -1))
520
632
  .filter((i) => i >= 0);
521
633
  // Runs of neighbouring columns (with resources, a day's column for the event's resource).
522
634
  const runs = [];
@@ -565,13 +677,15 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
565
677
  }
566
678
  const hiddenIn = (i) => hiddenCount[i];
567
679
  const barColor = (event) => event.color ?? resourceOf(event.resourceId)?.color ?? 'accent';
568
- const twoRows = !!resources?.length && count > 1;
680
+ // The events on a day, for its header.
681
+ const eventsOn = (d) => events.filter((event) => event.start.slice(0, 10) <= toISODate(d) && event.end.slice(0, 10) >= toISODate(d));
682
+ const twoRows = split && count > 1;
569
683
  const editable = (event) => !!onEventChange && event.editable !== false && !event.pending;
570
684
  const pinnedMinutes = pinned ? minutesBetween(parse(pinned.start), parse(pinned.end)) || slotMinutes : 0;
571
685
  // The header rows, numbered for sticking: days, resources, extra rows, the all-day strip.
572
686
  let rowNumber = 0;
573
- const dayRow = !resources?.length || count > 1 ? rowNumber++ : -1;
574
- const resourceRow = resources?.length ? rowNumber++ : -1;
687
+ const dayRow = !split || count > 1 ? rowNumber++ : -1;
688
+ const resourceRow = split ? rowNumber++ : -1;
575
689
  const extraRows = rows.map(() => rowNumber++);
576
690
  const allDayRow = rowNumber;
577
691
  // The preview's problem, if any, shown as it's dragged.
@@ -595,7 +709,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
595
709
  setDate(value);
596
710
  setPicking(false);
597
711
  } } }) }), _jsxs("div", { ...part('actions'), children: [!narrow && views.length > 1 && (_jsx(Segmented, { size: "sm", "aria-label": t.view, items: views.map((v) => ({ value: String(v), label: viewLabel(v) })), value: String(chosenView), onValueChange: (v) => v && setView(/^\d+$/.test(v) ? Number(v) : v) })), actions] })] })), pinned && (_jsxs("div", { ...part('pinned'), role: "status", children: [_jsx("iconify-icon", { icon: "lucide:move", "aria-hidden": "true" }), _jsx("span", { children: t.placing(nameOf(pinned)) }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => onPinnedChange?.(null), children: t.cancel })] })), _jsxs("div", { ...part('grid'), ref: grid, role: "grid", "aria-label": title, "data-two-rows": twoRows || undefined, "data-scroll": height ? '' : undefined, style: height ? { maxHeight: height } : undefined, children: [_jsx("div", { ...part('corner'), "data-sticky-row": dayRow >= 0 ? dayRow : resourceRow, style: { gridRow: twoRows ? 'span 2' : undefined, top: `var(--sticky-${dayRow >= 0 ? dayRow : resourceRow})` } }), dayRow >= 0 && dates.map((d) => (_jsx("div", { ...part('day-header'), role: "columnheader", "data-today": sameDay(d, now) || undefined, "data-sticky-row": dayRow, style: { gridColumn: `span ${lanes.length}`, top: `var(--sticky-${dayRow})` }, children: (() => {
598
- const content = renderDayHeader ? renderDayHeader(d) : _jsxs(_Fragment, { children: [_jsx("span", { children: dayFormat.format(d) }), _jsx("strong", { children: d.getDate() })] });
712
+ const content = renderDayHeader ? renderDayHeader(d, { events: eventsOn(d), resources }) : _jsxs(_Fragment, { children: [_jsx("span", { children: dayFormat.format(d) }), _jsx("strong", { children: d.getDate() })] });
599
713
  return onDayClick
600
714
  ? _jsx("button", { type: "button", ...part('header-button'), onClick: () => onDayClick(toISODate(d)), "aria-label": longDay.format(d), children: content })
601
715
  : content;
@@ -629,7 +743,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
629
743
  }, onClick: () => onEventClick?.(event), onContextMenu: (e) => { if (openMenu(e.clientX, e.clientY, eventMenu?.(event)))
630
744
  e.preventDefault(); }, children: renderEvent ? renderEvent(event, { start: parse(event.start), end: parse(event.end), resource: null, compact: true }) : event.title }, `${event.id}-${first}`))), folding && columns.map((_, i) => hiddenIn(i) > 0 && (_jsx("button", { type: "button", ...part('more'), onClick: () => setAllDayOpen(true), style: { left: `${(i / columns.length) * 100}%`, width: `${100 / columns.length}%`, top: `calc(${shownLanes} * 1.625rem + 0.25rem)` }, children: t.more(hiddenIn(i)) }, `more-${i}`)))] })] }), _jsx("div", { ...part('gutter'), "aria-hidden": "true", style: { '--hours': hours.length }, children: hours.map((hour) => _jsx("span", { ...part('hour'), style: { top: top(hour * 60) }, children: hourFormat.format(new Date(2000, 0, 1, hour)) }, hour)) }), columns.map((column, index) => {
631
745
  const own = events.filter((event) => !isAllDay(event) && event.start.startsWith(column.iso)
632
- && (!resources?.length || event.resourceId === column.resource?.id));
746
+ && (!column.resource || event.resourceId === column.resource.id));
633
747
  const selecting = preview?.kind === 'select' && preview.column === index ? preview : null;
634
748
  const placing = preview?.kind === 'place' && preview.column === index ? preview : null;
635
749
  const isToday = sameDay(column.date, now);
@@ -640,7 +754,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
640
754
  else if (onSelect)
641
755
  begin(e, 'select', index);
642
756
  };
643
- return (_jsxs("div", { ...part('day'), role: "gridcell", "aria-label": `${longDay.format(column.date)}${resourceName(column)}`, "data-today": isToday || undefined, "data-selectable": onSelect || pinned ? '' : undefined, "data-first": (!!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)
757
+ return (_jsxs("div", { ...part('day'), role: "gridcell", "aria-label": `${longDay.format(column.date)}${resourceName(column)}`, "data-today": isToday || undefined, "data-selectable": onSelect || pinned ? '' : undefined, "data-first": (split && column.resource === lanes[0]) || undefined, style: { '--hours': hours.length, ...(column.resource?.color && { '--resource-color': cssColor(column.resource.color) }) }, onPointerDown: (e) => { if (e.target === e.currentTarget)
644
758
  press(e); }, onPointerMove: (e) => { track(e); hover(e, index); }, onPointerUp: end, onPointerCancel: cancel, onPointerLeave: () => { setHoverSlot(null); if (pinned && !gesture.current)
645
759
  setPreview(null); }, onContextMenu: (e) => {
646
760
  // An empty time (or its unavailable shading), not an event.
@@ -655,7 +769,7 @@ export function EventCalendar({ events = [], resources = null, view: viewProp, d
655
769
  const resizing = preview?.kind === 'resize' && preview.id === event.id ? preview : null;
656
770
  const shownEnd = resizing ? addMinutes(finish, resizing.minutes) : finish;
657
771
  const minutes = minutesBetween(start, shownEnd);
658
- const color = event.color ?? column.resource?.color ?? 'accent';
772
+ const color = event.color ?? (column.resource ?? resourceOf(event.resourceId))?.color ?? 'accent';
659
773
  const time = `${timeFormat.format(start)} – ${timeFormat.format(shownEnd)}`;
660
774
  const box = { top: top(minutesOfDay(start)), left: `${(sub / subs) * 100}%`, width: `${100 / subs}%` };
661
775
  return (_jsxs(Fragment, { children: [(moving || resizing) && _jsx("div", { ...part('ghost'), style: { ...box, height: length(minutesBetween(start, finish)) } }), _jsxs("button", { type: "button", ...part('event'), "data-event-id": event.id, "data-dragging": moving || resizing ? '' : undefined, "data-invalid": (moving || resizing) && previewReason ? '' : undefined, "data-editable": editable(event) || undefined, "data-compact": minutes <= 30 || undefined, "data-blocked": event.blocked || undefined, "data-pending": event.pending || undefined, "data-pinned": pinned?.id === event.id || undefined, "data-color": named.includes(color) ? color : 'custom', "aria-label": `${nameOf(event)}, ${time}`, "aria-busy": event.pending || undefined, title: (moving || resizing) && previewReason ? previewReason : undefined, style: {
@@ -10,8 +10,9 @@
10
10
  &:focus-visible { outline: 2px solid var(--ui-ring-soft); } }
11
11
  [data-scope=event-calendar][data-part=actions] { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; }
12
12
 
13
- /* A time gutter, then a column per day (or per resource within each day); it scrolls sideways when narrow. */
14
- [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';