@sanity/workflow-components 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/index.cjs +215 -123
- package/dist/index.js +218 -124
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @sanity/workflow-components
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6351232: Add reusable member, assignee, and date controls for Studio and App SDK integrations.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 775fb3c: Move focus into an opened date picker so calendar arrow-key navigation works immediately, and shrink picker result lists to their measured content height.
|
|
12
|
+
- Updated dependencies [239d9f6]
|
|
13
|
+
- Updated dependencies [3af2ca0]
|
|
14
|
+
- Updated dependencies [7276702]
|
|
15
|
+
- Updated dependencies [58f8211]
|
|
16
|
+
- Updated dependencies [00c089d]
|
|
17
|
+
- Updated dependencies [71cd58e]
|
|
18
|
+
- Updated dependencies [cd03973]
|
|
19
|
+
- Updated dependencies [d26cc1a]
|
|
20
|
+
- Updated dependencies [9126299]
|
|
21
|
+
- Updated dependencies [72018af]
|
|
22
|
+
- Updated dependencies [5cd8ffd]
|
|
23
|
+
- Updated dependencies [b82bda4]
|
|
24
|
+
- Updated dependencies [55a54b3]
|
|
25
|
+
- Updated dependencies [b82bda4]
|
|
26
|
+
- Updated dependencies [4490442]
|
|
27
|
+
- Updated dependencies [1540dce]
|
|
28
|
+
- @sanity/workflow-engine@0.18.0
|
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: !0
|
|
5
5
|
});
|
|
6
6
|
|
|
7
|
-
var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), Close = require("@sanity/icons/Close"), ChevronLeft = require("@sanity/icons/ChevronLeft"), ChevronRight = require("@sanity/icons/ChevronRight"), addDays = require("date-fns/addDays"), addMonths = require("date-fns/addMonths"), setDate = require("date-fns/setDate"), setHours = require("date-fns/setHours"), setMinutes = require("date-fns/setMinutes"), setMonth = require("date-fns/setMonth"), setYear = require("date-fns/setYear"), react = require("react"), eachWeekOfInterval = require("date-fns/eachWeekOfInterval"), isSameDay = require("date-fns/isSameDay"), isSameMonth = require("date-fns/isSameMonth"), lastDayOfMonth = require("date-fns/lastDayOfMonth"), startOfMonth = require("date-fns/startOfMonth"), Checkmark = require("@sanity/icons/Checkmark"), Empty = require("@sanity/icons/Empty"), Users = require("@sanity/icons/Users");
|
|
7
|
+
var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), Close = require("@sanity/icons/Close"), ChevronLeft = require("@sanity/icons/ChevronLeft"), ChevronRight = require("@sanity/icons/ChevronRight"), addDays = require("date-fns/addDays"), addMonths = require("date-fns/addMonths"), setDate = require("date-fns/setDate"), setHours = require("date-fns/setHours"), setMinutes = require("date-fns/setMinutes"), setMonth = require("date-fns/setMonth"), setYear = require("date-fns/setYear"), react = require("react"), eachWeekOfInterval = require("date-fns/eachWeekOfInterval"), isSameDay = require("date-fns/isSameDay"), isSameMonth = require("date-fns/isSameMonth"), lastDayOfMonth = require("date-fns/lastDayOfMonth"), startOfMonth = require("date-fns/startOfMonth"), Checkmark = require("@sanity/icons/Checkmark"), Empty = require("@sanity/icons/Empty"), Users = require("@sanity/icons/Users"), reactVirtual = require("@tanstack/react-virtual");
|
|
8
8
|
|
|
9
9
|
function roleListLabel(roles) {
|
|
10
10
|
return `${roles.length === 1 ? "Role" : "Roles"}: ${roles.join(", ")}`;
|
|
@@ -152,7 +152,11 @@ function DatePicker({onSelect: onSelect, selectTime: selectTime = !1, value: val
|
|
|
152
152
|
setFocusedDate(current => setDate.setDate(setMonth.setMonth(current, month), 1));
|
|
153
153
|
}, []), moveFocusedDate = react.useCallback(byMonths => setFocusedDate(current => addMonths.addMonths(current, byMonths)), []), setFocusedYear = react.useCallback(year => setFocusedDate(current => setYear.setYear(current, year)), []), focusCurrentWeekDay = react.useCallback(() => {
|
|
154
154
|
ref.current?.querySelector('[data-focused="true"]')?.focus();
|
|
155
|
-
}, [])
|
|
155
|
+
}, []);
|
|
156
|
+
react.useEffect(() => {
|
|
157
|
+
ref.current?.querySelector("[data-calendar-grid]")?.focus();
|
|
158
|
+
}, []);
|
|
159
|
+
const handleKeyDown = react.useCallback(event => {
|
|
156
160
|
if (!ARROW_KEYS.includes(event.key)) return;
|
|
157
161
|
if (event.preventDefault(), event.target instanceof HTMLElement && event.target.hasAttribute("data-calendar-grid")) {
|
|
158
162
|
focusCurrentWeekDay();
|
|
@@ -346,57 +350,81 @@ function MemberAvatarGroup({members: members, maxLength: maxLength = 3}) {
|
|
|
346
350
|
});
|
|
347
351
|
}
|
|
348
352
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
size: 1,
|
|
362
|
-
children: "Loading members…"
|
|
363
|
-
}) ]
|
|
364
|
-
}) : error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
|
|
365
|
-
padding: 3,
|
|
366
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
|
|
367
|
-
muted: !0,
|
|
368
|
-
size: 1,
|
|
369
|
-
children: [ "Could not list members: ", error ]
|
|
370
|
-
})
|
|
371
|
-
}) : null;
|
|
353
|
+
const ESTIMATED_ROW_HEIGHT = 44, MAX_VISIBLE_ROWS = 8, MAX_VIEWPORT_HEIGHT = ESTIMATED_ROW_HEIGHT * MAX_VISIBLE_ROWS;
|
|
354
|
+
|
|
355
|
+
function hasTouchInput() {
|
|
356
|
+
return typeof navigator > "u" ? !1 : navigator.maxTouchPoints > 0 || globalThis.matchMedia?.("(pointer: coarse)").matches === !0;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function nextEnabledIndex({current: current, direction: direction, disabled: disabled, rows: rows}) {
|
|
360
|
+
for (let offset = 1; offset <= rows.length; offset += 1) {
|
|
361
|
+
const index = (current + direction * offset + rows.length) % rows.length, row = rows[index];
|
|
362
|
+
if (row !== void 0 && !disabled(row)) return index;
|
|
363
|
+
}
|
|
364
|
+
return -1;
|
|
372
365
|
}
|
|
373
366
|
|
|
374
|
-
|
|
367
|
+
function arrowDirection(key) {
|
|
368
|
+
if (key === "ArrowDown") return 1;
|
|
369
|
+
if (key === "ArrowUp") return -1;
|
|
370
|
+
}
|
|
375
371
|
|
|
376
|
-
function
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
372
|
+
function navigationStart(activeIndex, direction) {
|
|
373
|
+
return activeIndex >= 0 ? activeIndex : direction === 1 ? -1 : 0;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function CommandList({ariaLabel: ariaLabel, disabled: disabled = () => !1, emptyText: emptyText, getRowKey: getRowKey, onQueryChange: onQueryChange, onSelect: onSelect, placeholder: placeholder, query: query, renderRow: renderRow, rows: rows}) {
|
|
377
|
+
const inputRef = react.useRef(null), scrollRef = react.useRef(null), listId = react.useId(), [activeIndex, setActiveIndex] = react.useState(-1), estimatedViewportHeight = Math.min(rows.length, MAX_VISIBLE_ROWS) * ESTIMATED_ROW_HEIGHT, virtualizer = reactVirtual.useVirtualizer({
|
|
378
|
+
count: rows.length,
|
|
379
|
+
estimateSize: () => ESTIMATED_ROW_HEIGHT,
|
|
380
|
+
getItemKey: index => {
|
|
381
|
+
const row = rows[index];
|
|
382
|
+
return row === void 0 ? index : getRowKey(row);
|
|
383
|
+
},
|
|
384
|
+
getScrollElement: () => scrollRef.current,
|
|
385
|
+
initialRect: {
|
|
386
|
+
height: estimatedViewportHeight,
|
|
387
|
+
width: 300
|
|
388
|
+
},
|
|
389
|
+
initialMeasurementsCache: rows.map((row, index) => ({
|
|
390
|
+
end: (index + 1) * ESTIMATED_ROW_HEIGHT,
|
|
391
|
+
index: index,
|
|
392
|
+
key: getRowKey(row),
|
|
393
|
+
lane: 0,
|
|
394
|
+
size: ESTIMATED_ROW_HEIGHT,
|
|
395
|
+
start: index * ESTIMATED_ROW_HEIGHT
|
|
396
|
+
})),
|
|
397
|
+
overscan: 5,
|
|
398
|
+
observeElementRect: (_instance, callback) => (callback({
|
|
399
|
+
height: estimatedViewportHeight,
|
|
400
|
+
width: 300
|
|
401
|
+
}), () => {})
|
|
402
|
+
}), viewportHeight = Math.min(virtualizer.getTotalSize(), MAX_VIEWPORT_HEIGHT);
|
|
403
|
+
react.useEffect(() => {
|
|
404
|
+
hasTouchInput() || inputRef.current?.focus();
|
|
405
|
+
}, []), react.useEffect(() => setActiveIndex(-1), [ rows ]);
|
|
406
|
+
const moveActive = react.useCallback(direction => {
|
|
407
|
+
const start = navigationStart(activeIndex, direction), next = nextEnabledIndex({
|
|
408
|
+
current: start,
|
|
409
|
+
direction: direction,
|
|
410
|
+
disabled: disabled,
|
|
411
|
+
rows: rows
|
|
412
|
+
});
|
|
413
|
+
setActiveIndex(next), next >= 0 && virtualizer.scrollToIndex(next, {
|
|
414
|
+
align: "auto"
|
|
415
|
+
});
|
|
416
|
+
}, [ activeIndex, disabled, rows, virtualizer ]), handleKeyDown = react.useCallback(event => {
|
|
417
|
+
const direction = arrowDirection(event.key);
|
|
418
|
+
if (direction !== void 0) {
|
|
419
|
+
event.preventDefault(), moveActive(direction);
|
|
382
420
|
return;
|
|
383
421
|
}
|
|
384
|
-
if (event.key !== "
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const nextIndex = (startingIndex + direction + rows.length) % rows.length, nextRow = rows[nextIndex];
|
|
391
|
-
return setActiveRowId(nextRow?.id), nextRow?.scrollIntoView?.({
|
|
392
|
-
block: "nearest"
|
|
393
|
-
}), nextIndex;
|
|
394
|
-
});
|
|
395
|
-
}, [ activeIndex ]);
|
|
396
|
-
return loading || error ? /* @__PURE__ */ jsxRuntime.jsx(PickerStatus, {
|
|
397
|
-
error: error,
|
|
398
|
-
loading: loading
|
|
399
|
-
}) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
|
|
422
|
+
if (event.key !== "Enter") return;
|
|
423
|
+
const row = rows[activeIndex];
|
|
424
|
+
row === void 0 || disabled(row) || (event.preventDefault(), onSelect(row));
|
|
425
|
+
}, [ activeIndex, disabled, moveActive, onSelect, rows ]), activeRow = activeIndex < 0 ? void 0 : rows[activeIndex], activeId = activeRow === void 0 ? void 0 : `${listId}-${getRowKey(activeRow)}`;
|
|
426
|
+
/* @__PURE__ */
|
|
427
|
+
return jsxRuntime.jsxs(ui.Flex, {
|
|
400
428
|
direction: "column",
|
|
401
429
|
style: {
|
|
402
430
|
width: 300
|
|
@@ -406,65 +434,106 @@ function PickerShell({children: children, empty: empty, error: error, loading: l
|
|
|
406
434
|
padding: 1,
|
|
407
435
|
paddingBottom: 0,
|
|
408
436
|
children: /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, {
|
|
409
|
-
"aria-activedescendant":
|
|
437
|
+
"aria-activedescendant": activeId,
|
|
410
438
|
"aria-controls": listId,
|
|
411
439
|
"aria-expanded": !0,
|
|
412
440
|
"aria-label": placeholder,
|
|
413
441
|
fontSize: 1,
|
|
414
|
-
onChange: event =>
|
|
415
|
-
setActiveIndex(-1), setActiveRowId(void 0), onQueryChange(event.currentTarget.value);
|
|
416
|
-
},
|
|
442
|
+
onChange: event => onQueryChange(event.currentTarget.value),
|
|
417
443
|
onKeyDown: handleKeyDown,
|
|
418
444
|
placeholder: placeholder,
|
|
419
445
|
radius: 2,
|
|
446
|
+
ref: inputRef,
|
|
420
447
|
role: "combobox",
|
|
421
448
|
value: query
|
|
422
449
|
})
|
|
423
|
-
}),
|
|
450
|
+
}), rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
|
|
424
451
|
padding: 3,
|
|
425
452
|
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
|
|
426
453
|
muted: !0,
|
|
427
454
|
size: 1,
|
|
428
|
-
children:
|
|
455
|
+
children: emptyText
|
|
429
456
|
})
|
|
430
|
-
}) : /* @__PURE__ */ jsxRuntime.jsx(ui.
|
|
431
|
-
|
|
457
|
+
}) : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
|
|
458
|
+
"aria-label": ariaLabel,
|
|
432
459
|
id: listId,
|
|
433
|
-
|
|
460
|
+
overflow: "auto",
|
|
461
|
+
ref: scrollRef,
|
|
434
462
|
role: "listbox",
|
|
435
|
-
padding: 1,
|
|
436
|
-
space: 1,
|
|
437
463
|
style: {
|
|
438
|
-
|
|
439
|
-
maxHeight: 360,
|
|
440
|
-
overflowY: "auto"
|
|
464
|
+
height: viewportHeight
|
|
441
465
|
},
|
|
442
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
443
|
-
|
|
444
|
-
|
|
466
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
|
|
467
|
+
style: {
|
|
468
|
+
height: virtualizer.getTotalSize(),
|
|
469
|
+
position: "relative",
|
|
470
|
+
width: "100%"
|
|
471
|
+
},
|
|
472
|
+
children: virtualizer.getVirtualItems().map(item => {
|
|
473
|
+
const row = rows[item.index];
|
|
474
|
+
if (row === void 0) return null;
|
|
475
|
+
const rowId = `${listId}-${getRowKey(row)}`;
|
|
476
|
+
/* @__PURE__ */
|
|
477
|
+
return jsxRuntime.jsx(ui.Box, {
|
|
478
|
+
"data-index": item.index,
|
|
479
|
+
ref: virtualizer.measureElement,
|
|
480
|
+
style: {
|
|
481
|
+
position: "absolute",
|
|
482
|
+
transform: `translateY(${item.start}px)`,
|
|
483
|
+
width: "100%"
|
|
484
|
+
},
|
|
485
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
|
|
486
|
+
id: rowId,
|
|
487
|
+
children: renderRow(row, item.index === activeIndex)
|
|
488
|
+
})
|
|
489
|
+
}, item.key);
|
|
490
|
+
})
|
|
445
491
|
})
|
|
446
492
|
}) ]
|
|
447
493
|
});
|
|
448
494
|
}
|
|
449
495
|
|
|
450
|
-
function
|
|
451
|
-
|
|
496
|
+
function PickerStatus({loading: loading, error: error}) {
|
|
497
|
+
return loading ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
|
|
498
|
+
align: "center",
|
|
499
|
+
gap: 2,
|
|
500
|
+
padding: 3,
|
|
501
|
+
children: [
|
|
502
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
|
|
503
|
+
muted: !0,
|
|
504
|
+
size: 1
|
|
505
|
+
}),
|
|
506
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
|
|
507
|
+
muted: !0,
|
|
508
|
+
size: 1,
|
|
509
|
+
children: "Loading members…"
|
|
510
|
+
}) ]
|
|
511
|
+
}) : error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
|
|
512
|
+
padding: 3,
|
|
513
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
|
|
514
|
+
muted: !0,
|
|
515
|
+
size: 1,
|
|
516
|
+
children: [ "Could not list members: ", error ]
|
|
517
|
+
})
|
|
518
|
+
}) : null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function PickerRow({active: active, icon: icon, label: label, onSelect: onSelect, selected: selected}) {
|
|
452
522
|
/* @__PURE__ */
|
|
453
523
|
return jsxRuntime.jsx(ui.Box, {
|
|
454
|
-
|
|
524
|
+
paddingX: 1,
|
|
525
|
+
paddingY: .5,
|
|
455
526
|
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
|
|
456
527
|
"aria-pressed": selected,
|
|
457
528
|
"aria-selected": active,
|
|
458
|
-
"data-picker-row": !0,
|
|
459
|
-
id: rowId,
|
|
460
529
|
justify: "flex-start",
|
|
461
530
|
mode: active ? "ghost" : "bleed",
|
|
462
531
|
onClick: onSelect,
|
|
463
532
|
padding: 2,
|
|
533
|
+
role: "option",
|
|
464
534
|
style: {
|
|
465
535
|
width: "100%"
|
|
466
536
|
},
|
|
467
|
-
role: "option",
|
|
468
537
|
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
|
|
469
538
|
align: "center",
|
|
470
539
|
gap: 2,
|
|
@@ -496,41 +565,45 @@ function Glyph({children: children}) {
|
|
|
496
565
|
}
|
|
497
566
|
|
|
498
567
|
function MemberPicker({error: error, loading: loading, members: members, onSelect: onSelect, selectedIds: selectedIds, unassignRow: unassignRow = !1}) {
|
|
499
|
-
const [query, setQuery] = react.useState(""),
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
568
|
+
const [query, setQuery] = react.useState(""), rows = react.useMemo(() => {
|
|
569
|
+
const matches = members.filter(member => matchesMemberSearch(member, query)).map(member => ({
|
|
570
|
+
kind: "member",
|
|
571
|
+
member: member
|
|
572
|
+
}));
|
|
573
|
+
return unassignRow && query.trim() === "" ? [ {
|
|
574
|
+
kind: "unassigned"
|
|
575
|
+
}, ...matches ] : matches;
|
|
576
|
+
}, [ members, query, unassignRow ]), selectRow = react.useCallback(row => onSelect(row.kind === "member" ? row.member : null), [ onSelect ]);
|
|
577
|
+
return loading || error ? /* @__PURE__ */ jsxRuntime.jsx(PickerStatus, {
|
|
503
578
|
error: error,
|
|
504
|
-
loading: loading
|
|
505
|
-
|
|
579
|
+
loading: loading
|
|
580
|
+
}) : /* @__PURE__ */ jsxRuntime.jsx(CommandList, {
|
|
581
|
+
ariaLabel: "Project members",
|
|
582
|
+
emptyText: "Nothing matches",
|
|
583
|
+
getRowKey: row => row.kind === "member" ? `member-${row.member.id}` : "unassigned",
|
|
506
584
|
onQueryChange: setQuery,
|
|
585
|
+
onSelect: selectRow,
|
|
507
586
|
placeholder: "Search members…",
|
|
508
587
|
query: query,
|
|
509
|
-
|
|
510
|
-
|
|
588
|
+
renderRow: (row, active) => /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
|
|
589
|
+
active: active,
|
|
590
|
+
icon: row.kind === "member" ? /* @__PURE__ */ jsxRuntime.jsx(MemberAvatar, {
|
|
591
|
+
imageUrl: row.member.imageUrl,
|
|
592
|
+
name: row.member.displayName
|
|
593
|
+
}) : /* @__PURE__ */ jsxRuntime.jsx(Glyph, {
|
|
511
594
|
children: /* @__PURE__ */ jsxRuntime.jsx(Empty.EmptyIcon, {})
|
|
512
595
|
}),
|
|
513
|
-
label: "No assignee",
|
|
514
|
-
onSelect: () =>
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(MemberAvatar, {
|
|
519
|
-
imageUrl: member.imageUrl,
|
|
520
|
-
name: member.displayName
|
|
521
|
-
}),
|
|
522
|
-
label: member.displayName,
|
|
523
|
-
onSelect: () => onSelect(member),
|
|
524
|
-
rowId: `workflow-member-${encodeURIComponent(member.id)}`,
|
|
525
|
-
selected: selectedIds.has(member.id)
|
|
526
|
-
}, member.id)) ]
|
|
596
|
+
label: row.kind === "member" ? row.member.displayName : "No assignee",
|
|
597
|
+
onSelect: () => selectRow(row),
|
|
598
|
+
selected: row.kind === "member" ? selectedIds.has(row.member.id) : selectedIds.size === 0
|
|
599
|
+
}),
|
|
600
|
+
rows: rows
|
|
527
601
|
});
|
|
528
602
|
}
|
|
529
603
|
|
|
530
604
|
function GroupHeading({children: children}) {
|
|
531
605
|
/* @__PURE__ */
|
|
532
606
|
return jsxRuntime.jsx(ui.Card, {
|
|
533
|
-
as: "li",
|
|
534
607
|
borderTop: !0,
|
|
535
608
|
padding: 2,
|
|
536
609
|
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
|
|
@@ -543,44 +616,63 @@ function GroupHeading({children: children}) {
|
|
|
543
616
|
}
|
|
544
617
|
|
|
545
618
|
function AssigneePicker({error: error, loading: loading, members: members, onToggle: onToggle, value: value}) {
|
|
546
|
-
const [query, setQuery] = react.useState(""),
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
619
|
+
const [query, setQuery] = react.useState(""), roles = react.useMemo(() => [ ...new Set(members.flatMap(member => member.roles)) ].toSorted(), [ members ]), rows = react.useMemo(() => {
|
|
620
|
+
const normalized = query.trim().toLowerCase(), roleRows = roles.filter(role => role.toLowerCase().includes(normalized)).map(role => ({
|
|
621
|
+
kind: "role",
|
|
622
|
+
role: role
|
|
623
|
+
})), memberRows = members.filter(member => matchesMemberSearch(member, query)).map(member => ({
|
|
624
|
+
kind: "member",
|
|
625
|
+
member: member
|
|
626
|
+
}));
|
|
627
|
+
return [ ...roleRows.length > 0 ? [ {
|
|
628
|
+
kind: "heading",
|
|
629
|
+
label: "Roles"
|
|
630
|
+
} ] : [], ...roleRows, ...memberRows.length > 0 ? [ {
|
|
631
|
+
kind: "heading",
|
|
632
|
+
label: "Members"
|
|
633
|
+
} ] : [], ...memberRows ];
|
|
634
|
+
}, [ members, query, roles ]), selectedRoles = new Set(value.flatMap(item => item.type === "role" ? [ item.role ] : [])), selectedUsers = new Set(value.flatMap(item => item.type === "user" ? [ item.id ] : [])), selectRow = react.useCallback(row => {
|
|
635
|
+
row.kind === "role" && onToggle({
|
|
636
|
+
type: "role",
|
|
637
|
+
role: row.role
|
|
638
|
+
}), row.kind === "member" && onToggle({
|
|
639
|
+
type: "user",
|
|
640
|
+
id: row.member.id
|
|
641
|
+
});
|
|
642
|
+
}, [ onToggle ]);
|
|
643
|
+
return loading || error ? /* @__PURE__ */ jsxRuntime.jsx(PickerStatus, {
|
|
550
644
|
error: error,
|
|
551
|
-
loading: loading
|
|
552
|
-
|
|
645
|
+
loading: loading
|
|
646
|
+
}) : /* @__PURE__ */ jsxRuntime.jsx(CommandList, {
|
|
647
|
+
ariaLabel: "Roles and members",
|
|
648
|
+
disabled: row => row.kind === "heading",
|
|
649
|
+
emptyText: "Nothing matches",
|
|
650
|
+
getRowKey: row => row.kind === "heading" ? `heading-${row.label}` : row.kind === "role" ? `role-${row.role}` : `member-${row.member.id}`,
|
|
553
651
|
onQueryChange: setQuery,
|
|
652
|
+
onSelect: selectRow,
|
|
554
653
|
placeholder: "Search roles and members…",
|
|
555
654
|
query: query,
|
|
556
|
-
|
|
557
|
-
children:
|
|
558
|
-
}) :
|
|
655
|
+
renderRow: (row, active) => row.kind === "heading" ? /* @__PURE__ */ jsxRuntime.jsx(GroupHeading, {
|
|
656
|
+
children: row.label
|
|
657
|
+
}) : row.kind === "role" ? /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
|
|
658
|
+
active: active,
|
|
559
659
|
icon: /* @__PURE__ */ jsxRuntime.jsx(Glyph, {
|
|
560
660
|
children: /* @__PURE__ */ jsxRuntime.jsx(Users.UsersIcon, {})
|
|
561
661
|
}),
|
|
562
|
-
label: role,
|
|
563
|
-
onSelect: () =>
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
rowId: `workflow-role-${encodeURIComponent(role)}`,
|
|
568
|
-
selected: selectedRoles.has(role)
|
|
569
|
-
}, `role:${role}`)), matchingMembers.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(GroupHeading, {
|
|
570
|
-
children: "Members"
|
|
571
|
-
}) : null, matchingMembers.map(member => /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
|
|
662
|
+
label: row.role,
|
|
663
|
+
onSelect: () => selectRow(row),
|
|
664
|
+
selected: selectedRoles.has(row.role)
|
|
665
|
+
}) : /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
|
|
666
|
+
active: active,
|
|
572
667
|
icon: /* @__PURE__ */ jsxRuntime.jsx(MemberAvatar, {
|
|
573
|
-
imageUrl: member.imageUrl,
|
|
574
|
-
name: member.displayName
|
|
575
|
-
}),
|
|
576
|
-
label: member.displayName,
|
|
577
|
-
onSelect: () => onToggle({
|
|
578
|
-
type: "user",
|
|
579
|
-
id: member.id
|
|
668
|
+
imageUrl: row.member.imageUrl,
|
|
669
|
+
name: row.member.displayName
|
|
580
670
|
}),
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
671
|
+
label: row.member.displayName,
|
|
672
|
+
onSelect: () => selectRow(row),
|
|
673
|
+
selected: selectedUsers.has(row.member.id)
|
|
674
|
+
}),
|
|
675
|
+
rows: rows
|
|
584
676
|
});
|
|
585
677
|
}
|
|
586
678
|
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
2
|
|
|
3
|
-
import { Badge, Flex, Grid, Box, Text, Card, TextInput, Select, Button, Avatar, AvatarStack,
|
|
3
|
+
import { Badge, Flex, Grid, Box, Text, Card, TextInput, Select, Button, Avatar, AvatarStack, Spinner } from "@sanity/ui";
|
|
4
4
|
|
|
5
5
|
import { CloseIcon } from "@sanity/icons/Close";
|
|
6
6
|
|
|
@@ -22,7 +22,7 @@ import { setMonth } from "date-fns/setMonth";
|
|
|
22
22
|
|
|
23
23
|
import { setYear } from "date-fns/setYear";
|
|
24
24
|
|
|
25
|
-
import { forwardRef, useState, useCallback, useMemo, useRef, useEffect,
|
|
25
|
+
import { forwardRef, useState, useCallback, useMemo, useRef, useEffect, useId } from "react";
|
|
26
26
|
|
|
27
27
|
import { eachWeekOfInterval } from "date-fns/eachWeekOfInterval";
|
|
28
28
|
|
|
@@ -40,6 +40,8 @@ import { EmptyIcon } from "@sanity/icons/Empty";
|
|
|
40
40
|
|
|
41
41
|
import { UsersIcon } from "@sanity/icons/Users";
|
|
42
42
|
|
|
43
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
44
|
+
|
|
43
45
|
function roleListLabel(roles) {
|
|
44
46
|
return `${roles.length === 1 ? "Role" : "Roles"}: ${roles.join(", ")}`;
|
|
45
47
|
}
|
|
@@ -186,7 +188,11 @@ function DatePicker({onSelect: onSelect, selectTime: selectTime = !1, value: val
|
|
|
186
188
|
setFocusedDate(current => setDate(setMonth(current, month), 1));
|
|
187
189
|
}, []), moveFocusedDate = useCallback(byMonths => setFocusedDate(current => addMonths(current, byMonths)), []), setFocusedYear = useCallback(year => setFocusedDate(current => setYear(current, year)), []), focusCurrentWeekDay = useCallback(() => {
|
|
188
190
|
ref.current?.querySelector('[data-focused="true"]')?.focus();
|
|
189
|
-
}, [])
|
|
191
|
+
}, []);
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
ref.current?.querySelector("[data-calendar-grid]")?.focus();
|
|
194
|
+
}, []);
|
|
195
|
+
const handleKeyDown = useCallback(event => {
|
|
190
196
|
if (!ARROW_KEYS.includes(event.key)) return;
|
|
191
197
|
if (event.preventDefault(), event.target instanceof HTMLElement && event.target.hasAttribute("data-calendar-grid")) {
|
|
192
198
|
focusCurrentWeekDay();
|
|
@@ -380,57 +386,81 @@ function MemberAvatarGroup({members: members, maxLength: maxLength = 3}) {
|
|
|
380
386
|
});
|
|
381
387
|
}
|
|
382
388
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
padding: 3,
|
|
388
|
-
children: [
|
|
389
|
-
/* @__PURE__ */ jsx(Spinner, {
|
|
390
|
-
muted: !0,
|
|
391
|
-
size: 1
|
|
392
|
-
}),
|
|
393
|
-
/* @__PURE__ */ jsx(Text, {
|
|
394
|
-
muted: !0,
|
|
395
|
-
size: 1,
|
|
396
|
-
children: "Loading members…"
|
|
397
|
-
}) ]
|
|
398
|
-
}) : error ? /* @__PURE__ */ jsx(Box, {
|
|
399
|
-
padding: 3,
|
|
400
|
-
children: /* @__PURE__ */ jsxs(Text, {
|
|
401
|
-
muted: !0,
|
|
402
|
-
size: 1,
|
|
403
|
-
children: [ "Could not list members: ", error ]
|
|
404
|
-
})
|
|
405
|
-
}) : null;
|
|
389
|
+
const ESTIMATED_ROW_HEIGHT = 44, MAX_VISIBLE_ROWS = 8, MAX_VIEWPORT_HEIGHT = ESTIMATED_ROW_HEIGHT * MAX_VISIBLE_ROWS;
|
|
390
|
+
|
|
391
|
+
function hasTouchInput() {
|
|
392
|
+
return typeof navigator > "u" ? !1 : navigator.maxTouchPoints > 0 || globalThis.matchMedia?.("(pointer: coarse)").matches === !0;
|
|
406
393
|
}
|
|
407
394
|
|
|
408
|
-
|
|
395
|
+
function nextEnabledIndex({current: current, direction: direction, disabled: disabled, rows: rows}) {
|
|
396
|
+
for (let offset = 1; offset <= rows.length; offset += 1) {
|
|
397
|
+
const index = (current + direction * offset + rows.length) % rows.length, row = rows[index];
|
|
398
|
+
if (row !== void 0 && !disabled(row)) return index;
|
|
399
|
+
}
|
|
400
|
+
return -1;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function arrowDirection(key) {
|
|
404
|
+
if (key === "ArrowDown") return 1;
|
|
405
|
+
if (key === "ArrowUp") return -1;
|
|
406
|
+
}
|
|
409
407
|
|
|
410
|
-
function
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
408
|
+
function navigationStart(activeIndex, direction) {
|
|
409
|
+
return activeIndex >= 0 ? activeIndex : direction === 1 ? -1 : 0;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function CommandList({ariaLabel: ariaLabel, disabled: disabled = () => !1, emptyText: emptyText, getRowKey: getRowKey, onQueryChange: onQueryChange, onSelect: onSelect, placeholder: placeholder, query: query, renderRow: renderRow, rows: rows}) {
|
|
413
|
+
const inputRef = useRef(null), scrollRef = useRef(null), listId = useId(), [activeIndex, setActiveIndex] = useState(-1), estimatedViewportHeight = Math.min(rows.length, MAX_VISIBLE_ROWS) * ESTIMATED_ROW_HEIGHT, virtualizer = useVirtualizer({
|
|
414
|
+
count: rows.length,
|
|
415
|
+
estimateSize: () => ESTIMATED_ROW_HEIGHT,
|
|
416
|
+
getItemKey: index => {
|
|
417
|
+
const row = rows[index];
|
|
418
|
+
return row === void 0 ? index : getRowKey(row);
|
|
419
|
+
},
|
|
420
|
+
getScrollElement: () => scrollRef.current,
|
|
421
|
+
initialRect: {
|
|
422
|
+
height: estimatedViewportHeight,
|
|
423
|
+
width: 300
|
|
424
|
+
},
|
|
425
|
+
initialMeasurementsCache: rows.map((row, index) => ({
|
|
426
|
+
end: (index + 1) * ESTIMATED_ROW_HEIGHT,
|
|
427
|
+
index: index,
|
|
428
|
+
key: getRowKey(row),
|
|
429
|
+
lane: 0,
|
|
430
|
+
size: ESTIMATED_ROW_HEIGHT,
|
|
431
|
+
start: index * ESTIMATED_ROW_HEIGHT
|
|
432
|
+
})),
|
|
433
|
+
overscan: 5,
|
|
434
|
+
observeElementRect: (_instance, callback) => (callback({
|
|
435
|
+
height: estimatedViewportHeight,
|
|
436
|
+
width: 300
|
|
437
|
+
}), () => {})
|
|
438
|
+
}), viewportHeight = Math.min(virtualizer.getTotalSize(), MAX_VIEWPORT_HEIGHT);
|
|
439
|
+
useEffect(() => {
|
|
440
|
+
hasTouchInput() || inputRef.current?.focus();
|
|
441
|
+
}, []), useEffect(() => setActiveIndex(-1), [ rows ]);
|
|
442
|
+
const moveActive = useCallback(direction => {
|
|
443
|
+
const start = navigationStart(activeIndex, direction), next = nextEnabledIndex({
|
|
444
|
+
current: start,
|
|
445
|
+
direction: direction,
|
|
446
|
+
disabled: disabled,
|
|
447
|
+
rows: rows
|
|
448
|
+
});
|
|
449
|
+
setActiveIndex(next), next >= 0 && virtualizer.scrollToIndex(next, {
|
|
450
|
+
align: "auto"
|
|
451
|
+
});
|
|
452
|
+
}, [ activeIndex, disabled, rows, virtualizer ]), handleKeyDown = useCallback(event => {
|
|
453
|
+
const direction = arrowDirection(event.key);
|
|
454
|
+
if (direction !== void 0) {
|
|
455
|
+
event.preventDefault(), moveActive(direction);
|
|
416
456
|
return;
|
|
417
457
|
}
|
|
418
|
-
if (event.key !== "
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const nextIndex = (startingIndex + direction + rows.length) % rows.length, nextRow = rows[nextIndex];
|
|
425
|
-
return setActiveRowId(nextRow?.id), nextRow?.scrollIntoView?.({
|
|
426
|
-
block: "nearest"
|
|
427
|
-
}), nextIndex;
|
|
428
|
-
});
|
|
429
|
-
}, [ activeIndex ]);
|
|
430
|
-
return loading || error ? /* @__PURE__ */ jsx(PickerStatus, {
|
|
431
|
-
error: error,
|
|
432
|
-
loading: loading
|
|
433
|
-
}) : /* @__PURE__ */ jsxs(Flex, {
|
|
458
|
+
if (event.key !== "Enter") return;
|
|
459
|
+
const row = rows[activeIndex];
|
|
460
|
+
row === void 0 || disabled(row) || (event.preventDefault(), onSelect(row));
|
|
461
|
+
}, [ activeIndex, disabled, moveActive, onSelect, rows ]), activeRow = activeIndex < 0 ? void 0 : rows[activeIndex], activeId = activeRow === void 0 ? void 0 : `${listId}-${getRowKey(activeRow)}`;
|
|
462
|
+
/* @__PURE__ */
|
|
463
|
+
return jsxs(Flex, {
|
|
434
464
|
direction: "column",
|
|
435
465
|
style: {
|
|
436
466
|
width: 300
|
|
@@ -440,65 +470,106 @@ function PickerShell({children: children, empty: empty, error: error, loading: l
|
|
|
440
470
|
padding: 1,
|
|
441
471
|
paddingBottom: 0,
|
|
442
472
|
children: /* @__PURE__ */ jsx(TextInput, {
|
|
443
|
-
"aria-activedescendant":
|
|
473
|
+
"aria-activedescendant": activeId,
|
|
444
474
|
"aria-controls": listId,
|
|
445
475
|
"aria-expanded": !0,
|
|
446
476
|
"aria-label": placeholder,
|
|
447
477
|
fontSize: 1,
|
|
448
|
-
onChange: event =>
|
|
449
|
-
setActiveIndex(-1), setActiveRowId(void 0), onQueryChange(event.currentTarget.value);
|
|
450
|
-
},
|
|
478
|
+
onChange: event => onQueryChange(event.currentTarget.value),
|
|
451
479
|
onKeyDown: handleKeyDown,
|
|
452
480
|
placeholder: placeholder,
|
|
453
481
|
radius: 2,
|
|
482
|
+
ref: inputRef,
|
|
454
483
|
role: "combobox",
|
|
455
484
|
value: query
|
|
456
485
|
})
|
|
457
|
-
}),
|
|
486
|
+
}), rows.length === 0 ? /* @__PURE__ */ jsx(Box, {
|
|
458
487
|
padding: 3,
|
|
459
488
|
children: /* @__PURE__ */ jsx(Text, {
|
|
460
489
|
muted: !0,
|
|
461
490
|
size: 1,
|
|
462
|
-
children:
|
|
491
|
+
children: emptyText
|
|
463
492
|
})
|
|
464
|
-
}) : /* @__PURE__ */ jsx(
|
|
465
|
-
|
|
493
|
+
}) : /* @__PURE__ */ jsx(Box, {
|
|
494
|
+
"aria-label": ariaLabel,
|
|
466
495
|
id: listId,
|
|
467
|
-
|
|
496
|
+
overflow: "auto",
|
|
497
|
+
ref: scrollRef,
|
|
468
498
|
role: "listbox",
|
|
469
|
-
padding: 1,
|
|
470
|
-
space: 1,
|
|
471
499
|
style: {
|
|
472
|
-
|
|
473
|
-
maxHeight: 360,
|
|
474
|
-
overflowY: "auto"
|
|
500
|
+
height: viewportHeight
|
|
475
501
|
},
|
|
476
|
-
children: /* @__PURE__ */ jsx(
|
|
477
|
-
|
|
478
|
-
|
|
502
|
+
children: /* @__PURE__ */ jsx(Box, {
|
|
503
|
+
style: {
|
|
504
|
+
height: virtualizer.getTotalSize(),
|
|
505
|
+
position: "relative",
|
|
506
|
+
width: "100%"
|
|
507
|
+
},
|
|
508
|
+
children: virtualizer.getVirtualItems().map(item => {
|
|
509
|
+
const row = rows[item.index];
|
|
510
|
+
if (row === void 0) return null;
|
|
511
|
+
const rowId = `${listId}-${getRowKey(row)}`;
|
|
512
|
+
/* @__PURE__ */
|
|
513
|
+
return jsx(Box, {
|
|
514
|
+
"data-index": item.index,
|
|
515
|
+
ref: virtualizer.measureElement,
|
|
516
|
+
style: {
|
|
517
|
+
position: "absolute",
|
|
518
|
+
transform: `translateY(${item.start}px)`,
|
|
519
|
+
width: "100%"
|
|
520
|
+
},
|
|
521
|
+
children: /* @__PURE__ */ jsx(Box, {
|
|
522
|
+
id: rowId,
|
|
523
|
+
children: renderRow(row, item.index === activeIndex)
|
|
524
|
+
})
|
|
525
|
+
}, item.key);
|
|
526
|
+
})
|
|
479
527
|
})
|
|
480
528
|
}) ]
|
|
481
529
|
});
|
|
482
530
|
}
|
|
483
531
|
|
|
484
|
-
function
|
|
485
|
-
|
|
532
|
+
function PickerStatus({loading: loading, error: error}) {
|
|
533
|
+
return loading ? /* @__PURE__ */ jsxs(Flex, {
|
|
534
|
+
align: "center",
|
|
535
|
+
gap: 2,
|
|
536
|
+
padding: 3,
|
|
537
|
+
children: [
|
|
538
|
+
/* @__PURE__ */ jsx(Spinner, {
|
|
539
|
+
muted: !0,
|
|
540
|
+
size: 1
|
|
541
|
+
}),
|
|
542
|
+
/* @__PURE__ */ jsx(Text, {
|
|
543
|
+
muted: !0,
|
|
544
|
+
size: 1,
|
|
545
|
+
children: "Loading members…"
|
|
546
|
+
}) ]
|
|
547
|
+
}) : error ? /* @__PURE__ */ jsx(Box, {
|
|
548
|
+
padding: 3,
|
|
549
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
550
|
+
muted: !0,
|
|
551
|
+
size: 1,
|
|
552
|
+
children: [ "Could not list members: ", error ]
|
|
553
|
+
})
|
|
554
|
+
}) : null;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function PickerRow({active: active, icon: icon, label: label, onSelect: onSelect, selected: selected}) {
|
|
486
558
|
/* @__PURE__ */
|
|
487
559
|
return jsx(Box, {
|
|
488
|
-
|
|
560
|
+
paddingX: 1,
|
|
561
|
+
paddingY: .5,
|
|
489
562
|
children: /* @__PURE__ */ jsx(Button, {
|
|
490
563
|
"aria-pressed": selected,
|
|
491
564
|
"aria-selected": active,
|
|
492
|
-
"data-picker-row": !0,
|
|
493
|
-
id: rowId,
|
|
494
565
|
justify: "flex-start",
|
|
495
566
|
mode: active ? "ghost" : "bleed",
|
|
496
567
|
onClick: onSelect,
|
|
497
568
|
padding: 2,
|
|
569
|
+
role: "option",
|
|
498
570
|
style: {
|
|
499
571
|
width: "100%"
|
|
500
572
|
},
|
|
501
|
-
role: "option",
|
|
502
573
|
children: /* @__PURE__ */ jsxs(Flex, {
|
|
503
574
|
align: "center",
|
|
504
575
|
gap: 2,
|
|
@@ -530,41 +601,45 @@ function Glyph({children: children}) {
|
|
|
530
601
|
}
|
|
531
602
|
|
|
532
603
|
function MemberPicker({error: error, loading: loading, members: members, onSelect: onSelect, selectedIds: selectedIds, unassignRow: unassignRow = !1}) {
|
|
533
|
-
const [query, setQuery] = useState(""),
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
604
|
+
const [query, setQuery] = useState(""), rows = useMemo(() => {
|
|
605
|
+
const matches = members.filter(member => matchesMemberSearch(member, query)).map(member => ({
|
|
606
|
+
kind: "member",
|
|
607
|
+
member: member
|
|
608
|
+
}));
|
|
609
|
+
return unassignRow && query.trim() === "" ? [ {
|
|
610
|
+
kind: "unassigned"
|
|
611
|
+
}, ...matches ] : matches;
|
|
612
|
+
}, [ members, query, unassignRow ]), selectRow = useCallback(row => onSelect(row.kind === "member" ? row.member : null), [ onSelect ]);
|
|
613
|
+
return loading || error ? /* @__PURE__ */ jsx(PickerStatus, {
|
|
537
614
|
error: error,
|
|
538
|
-
loading: loading
|
|
539
|
-
|
|
615
|
+
loading: loading
|
|
616
|
+
}) : /* @__PURE__ */ jsx(CommandList, {
|
|
617
|
+
ariaLabel: "Project members",
|
|
618
|
+
emptyText: "Nothing matches",
|
|
619
|
+
getRowKey: row => row.kind === "member" ? `member-${row.member.id}` : "unassigned",
|
|
540
620
|
onQueryChange: setQuery,
|
|
621
|
+
onSelect: selectRow,
|
|
541
622
|
placeholder: "Search members…",
|
|
542
623
|
query: query,
|
|
543
|
-
|
|
544
|
-
|
|
624
|
+
renderRow: (row, active) => /* @__PURE__ */ jsx(PickerRow, {
|
|
625
|
+
active: active,
|
|
626
|
+
icon: row.kind === "member" ? /* @__PURE__ */ jsx(MemberAvatar, {
|
|
627
|
+
imageUrl: row.member.imageUrl,
|
|
628
|
+
name: row.member.displayName
|
|
629
|
+
}) : /* @__PURE__ */ jsx(Glyph, {
|
|
545
630
|
children: /* @__PURE__ */ jsx(EmptyIcon, {})
|
|
546
631
|
}),
|
|
547
|
-
label: "No assignee",
|
|
548
|
-
onSelect: () =>
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
icon: /* @__PURE__ */ jsx(MemberAvatar, {
|
|
553
|
-
imageUrl: member.imageUrl,
|
|
554
|
-
name: member.displayName
|
|
555
|
-
}),
|
|
556
|
-
label: member.displayName,
|
|
557
|
-
onSelect: () => onSelect(member),
|
|
558
|
-
rowId: `workflow-member-${encodeURIComponent(member.id)}`,
|
|
559
|
-
selected: selectedIds.has(member.id)
|
|
560
|
-
}, member.id)) ]
|
|
632
|
+
label: row.kind === "member" ? row.member.displayName : "No assignee",
|
|
633
|
+
onSelect: () => selectRow(row),
|
|
634
|
+
selected: row.kind === "member" ? selectedIds.has(row.member.id) : selectedIds.size === 0
|
|
635
|
+
}),
|
|
636
|
+
rows: rows
|
|
561
637
|
});
|
|
562
638
|
}
|
|
563
639
|
|
|
564
640
|
function GroupHeading({children: children}) {
|
|
565
641
|
/* @__PURE__ */
|
|
566
642
|
return jsx(Card, {
|
|
567
|
-
as: "li",
|
|
568
643
|
borderTop: !0,
|
|
569
644
|
padding: 2,
|
|
570
645
|
children: /* @__PURE__ */ jsx(Text, {
|
|
@@ -577,44 +652,63 @@ function GroupHeading({children: children}) {
|
|
|
577
652
|
}
|
|
578
653
|
|
|
579
654
|
function AssigneePicker({error: error, loading: loading, members: members, onToggle: onToggle, value: value}) {
|
|
580
|
-
const [query, setQuery] = useState(""),
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
655
|
+
const [query, setQuery] = useState(""), roles = useMemo(() => [ ...new Set(members.flatMap(member => member.roles)) ].toSorted(), [ members ]), rows = useMemo(() => {
|
|
656
|
+
const normalized = query.trim().toLowerCase(), roleRows = roles.filter(role => role.toLowerCase().includes(normalized)).map(role => ({
|
|
657
|
+
kind: "role",
|
|
658
|
+
role: role
|
|
659
|
+
})), memberRows = members.filter(member => matchesMemberSearch(member, query)).map(member => ({
|
|
660
|
+
kind: "member",
|
|
661
|
+
member: member
|
|
662
|
+
}));
|
|
663
|
+
return [ ...roleRows.length > 0 ? [ {
|
|
664
|
+
kind: "heading",
|
|
665
|
+
label: "Roles"
|
|
666
|
+
} ] : [], ...roleRows, ...memberRows.length > 0 ? [ {
|
|
667
|
+
kind: "heading",
|
|
668
|
+
label: "Members"
|
|
669
|
+
} ] : [], ...memberRows ];
|
|
670
|
+
}, [ members, query, roles ]), selectedRoles = new Set(value.flatMap(item => item.type === "role" ? [ item.role ] : [])), selectedUsers = new Set(value.flatMap(item => item.type === "user" ? [ item.id ] : [])), selectRow = useCallback(row => {
|
|
671
|
+
row.kind === "role" && onToggle({
|
|
672
|
+
type: "role",
|
|
673
|
+
role: row.role
|
|
674
|
+
}), row.kind === "member" && onToggle({
|
|
675
|
+
type: "user",
|
|
676
|
+
id: row.member.id
|
|
677
|
+
});
|
|
678
|
+
}, [ onToggle ]);
|
|
679
|
+
return loading || error ? /* @__PURE__ */ jsx(PickerStatus, {
|
|
584
680
|
error: error,
|
|
585
|
-
loading: loading
|
|
586
|
-
|
|
681
|
+
loading: loading
|
|
682
|
+
}) : /* @__PURE__ */ jsx(CommandList, {
|
|
683
|
+
ariaLabel: "Roles and members",
|
|
684
|
+
disabled: row => row.kind === "heading",
|
|
685
|
+
emptyText: "Nothing matches",
|
|
686
|
+
getRowKey: row => row.kind === "heading" ? `heading-${row.label}` : row.kind === "role" ? `role-${row.role}` : `member-${row.member.id}`,
|
|
587
687
|
onQueryChange: setQuery,
|
|
688
|
+
onSelect: selectRow,
|
|
588
689
|
placeholder: "Search roles and members…",
|
|
589
690
|
query: query,
|
|
590
|
-
|
|
591
|
-
children:
|
|
592
|
-
}) :
|
|
691
|
+
renderRow: (row, active) => row.kind === "heading" ? /* @__PURE__ */ jsx(GroupHeading, {
|
|
692
|
+
children: row.label
|
|
693
|
+
}) : row.kind === "role" ? /* @__PURE__ */ jsx(PickerRow, {
|
|
694
|
+
active: active,
|
|
593
695
|
icon: /* @__PURE__ */ jsx(Glyph, {
|
|
594
696
|
children: /* @__PURE__ */ jsx(UsersIcon, {})
|
|
595
697
|
}),
|
|
596
|
-
label: role,
|
|
597
|
-
onSelect: () =>
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
rowId: `workflow-role-${encodeURIComponent(role)}`,
|
|
602
|
-
selected: selectedRoles.has(role)
|
|
603
|
-
}, `role:${role}`)), matchingMembers.length > 0 ? /* @__PURE__ */ jsx(GroupHeading, {
|
|
604
|
-
children: "Members"
|
|
605
|
-
}) : null, matchingMembers.map(member => /* @__PURE__ */ jsx(PickerRow, {
|
|
698
|
+
label: row.role,
|
|
699
|
+
onSelect: () => selectRow(row),
|
|
700
|
+
selected: selectedRoles.has(row.role)
|
|
701
|
+
}) : /* @__PURE__ */ jsx(PickerRow, {
|
|
702
|
+
active: active,
|
|
606
703
|
icon: /* @__PURE__ */ jsx(MemberAvatar, {
|
|
607
|
-
imageUrl: member.imageUrl,
|
|
608
|
-
name: member.displayName
|
|
609
|
-
}),
|
|
610
|
-
label: member.displayName,
|
|
611
|
-
onSelect: () => onToggle({
|
|
612
|
-
type: "user",
|
|
613
|
-
id: member.id
|
|
704
|
+
imageUrl: row.member.imageUrl,
|
|
705
|
+
name: row.member.displayName
|
|
614
706
|
}),
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
707
|
+
label: row.member.displayName,
|
|
708
|
+
onSelect: () => selectRow(row),
|
|
709
|
+
selected: selectedUsers.has(row.member.id)
|
|
710
|
+
}),
|
|
711
|
+
rows: rows
|
|
618
712
|
});
|
|
619
713
|
}
|
|
620
714
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/workflow-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Reusable React components for Editorial Workflows member, assignee, and date fields.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -43,8 +43,9 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@sanity/icons": "^4.1.0",
|
|
46
|
+
"@tanstack/react-virtual": "^3.14.6",
|
|
46
47
|
"date-fns": "^4.4.0",
|
|
47
|
-
"@sanity/workflow-engine": "0.
|
|
48
|
+
"@sanity/workflow-engine": "0.18.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@sanity/pkg-utils": "^10.5.3",
|