@dloizides/ui-card-roster 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/dist/index.mjs ADDED
@@ -0,0 +1,629 @@
1
+ import { useMemo, useState, useCallback } from 'react';
2
+ import { StyleSheet, useWindowDimensions, View, Pressable, Text, TextInput, ScrollView, ActivityIndicator } from 'react-native';
3
+ import { ErrorState, useUi } from '@dloizides/ui-feedback';
4
+ import { LAYOUT_COLLAPSE_BREAKPOINT, ModalShell } from '@dloizides/ui-layout';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+ import { Reorder } from '@dloizides/ui-motion';
7
+
8
+ // src/components/CardRoster.tsx
9
+
10
+ // src/constants.ts
11
+ var MIN_TOUCH_TARGET = 44;
12
+ var DEFAULT_CARD_WIDTH = 168;
13
+ var SPLIT_EDITOR_WIDTH = 400;
14
+ var SPACE_XS = 4;
15
+ var SPACE_S = 8;
16
+ var SPACE_M = 12;
17
+ var SPACE_L = 16;
18
+ var RADIUS_S = 8;
19
+ var RADIUS_M = 12;
20
+ var BORDER_WIDTH = 1;
21
+ var ACTIVE_BORDER_WIDTH = 2;
22
+ var TITLE_FONT_SIZE = 17;
23
+ var BODY_FONT_SIZE = 15;
24
+ var SMALL_FONT_SIZE = 13;
25
+ var PRESSED_OPACITY = 0.7;
26
+ var DISABLED_OPACITY = 0.4;
27
+ var BUSY_OVERLAY_OPACITY = 0.9;
28
+ var GROUP_TITLE_MIN_WIDTH = 120;
29
+ var REORDER_SPRING = { stiffness: 320, damping: 32 };
30
+ var MOVE_UP = -1;
31
+ var MOVE_DOWN = 1;
32
+ var GLYPH_UP = "\u25B2";
33
+ var GLYPH_DOWN = "\u25BC";
34
+ var DEFAULT_TEST_ID = "card-roster";
35
+ var rosterTestIds = (base) => ({
36
+ root: base,
37
+ reorderToggle: `${base}-reorder-toggle`,
38
+ addGroup: `${base}-add-group`,
39
+ error: `${base}-error`,
40
+ editor: `${base}-editor`,
41
+ editorPlaceholder: `${base}-editor-placeholder`,
42
+ group: (key) => `${base}-group-${key}`,
43
+ card: (id) => `${base}-card-${id}`
44
+ });
45
+
46
+ // src/utils/rosterOps.ts
47
+ function canMove(length, index, delta) {
48
+ const target = index + delta;
49
+ return delta !== 0 && index >= 0 && index < length && target >= 0 && target < length;
50
+ }
51
+ function moveInArray(list, index, delta) {
52
+ if (!canMove(list.length, index, delta)) return list;
53
+ const next = [...list];
54
+ const [moved] = next.splice(index, 1);
55
+ next.splice(index + delta, 0, moved);
56
+ return next;
57
+ }
58
+ function isGroupEmpty(group) {
59
+ return group.items.length === 0;
60
+ }
61
+ function mapGroup(value, key, fn) {
62
+ let changed = false;
63
+ const groups = value.groups.map((group) => {
64
+ if (group.key !== key) return group;
65
+ const next = fn(group);
66
+ if (next !== group) changed = true;
67
+ return next;
68
+ });
69
+ return changed ? { ...value, groups } : value;
70
+ }
71
+ function findItem(value, itemId) {
72
+ for (const group of value.groups) {
73
+ const index = group.items.findIndex((item2) => item2.id === itemId);
74
+ const item = group.items[index];
75
+ if (item !== void 0) return { group, item, index };
76
+ }
77
+ return void 0;
78
+ }
79
+ function addGroup(value, group) {
80
+ if (value.groups.some((g) => g.key === group.key)) return value;
81
+ return { ...value, groups: [...value.groups, group] };
82
+ }
83
+ function renameGroup(value, key, title) {
84
+ const trimmed = title.trim();
85
+ if (trimmed === "") return value;
86
+ return mapGroup(value, key, (group) => group.title === trimmed ? group : { ...group, title: trimmed });
87
+ }
88
+ function removeGroup(value, key) {
89
+ if (!value.groups.some((g) => g.key === key)) return value;
90
+ return { ...value, groups: value.groups.filter((g) => g.key !== key) };
91
+ }
92
+ function moveGroup(value, key, delta) {
93
+ const index = value.groups.findIndex((g) => g.key === key);
94
+ const groups = moveInArray(value.groups, index, delta);
95
+ return groups === value.groups ? value : { ...value, groups };
96
+ }
97
+ function addItem(value, groupKey, item) {
98
+ if (findItem(value, item.id) !== void 0) return value;
99
+ return mapGroup(value, groupKey, (group) => ({ ...group, items: [...group.items, item] }));
100
+ }
101
+ function updateItem(value, groupKey, item) {
102
+ return mapGroup(value, groupKey, (group) => {
103
+ const index = group.items.findIndex((i) => i.id === item.id);
104
+ if (index === -1) return group;
105
+ const items = [...group.items];
106
+ items[index] = item;
107
+ return { ...group, items };
108
+ });
109
+ }
110
+ function removeItem(value, groupKey, itemId) {
111
+ return mapGroup(
112
+ value,
113
+ groupKey,
114
+ (group) => group.items.some((i) => i.id === itemId) ? { ...group, items: group.items.filter((i) => i.id !== itemId) } : group
115
+ );
116
+ }
117
+ function moveItem(value, groupKey, itemId, delta) {
118
+ return mapGroup(value, groupKey, (group) => {
119
+ const items = moveInArray(group.items, group.items.findIndex((i) => i.id === itemId), delta);
120
+ return items === group.items ? group : { ...group, items };
121
+ });
122
+ }
123
+
124
+ // src/hooks/useRosterActions.ts
125
+ function useRosterActions(value, onChange) {
126
+ return useMemo(() => {
127
+ const emit = (next) => {
128
+ if (next !== value) onChange(next);
129
+ };
130
+ return {
131
+ addGroup: (group) => emit(addGroup(value, group)),
132
+ renameGroup: (key, title) => emit(renameGroup(value, key, title)),
133
+ removeGroup: (key) => emit(removeGroup(value, key)),
134
+ moveGroup: (key, delta) => emit(moveGroup(value, key, delta)),
135
+ moveItem: (groupKey, itemId, delta) => emit(moveItem(value, groupKey, itemId, delta))
136
+ };
137
+ }, [value, onChange]);
138
+ }
139
+
140
+ // src/utils/editorSession.ts
141
+ function isSplitPane(width, breakpoint) {
142
+ return width >= breakpoint;
143
+ }
144
+ function resolveEditSession(value, target, draft) {
145
+ if (target === null) return null;
146
+ if (target.itemId === null) return draft === null ? null : { item: draft, groupKey: target.groupKey, isNew: true };
147
+ const found = findItem(value, target.itemId);
148
+ return found === void 0 ? null : { item: found.item, groupKey: found.group.key, isNew: false };
149
+ }
150
+ function commitEdit(value, session, next) {
151
+ return session.isNew ? addItem(value, session.groupKey, next) : updateItem(value, session.groupKey, next);
152
+ }
153
+
154
+ // src/hooks/useRosterEditor.ts
155
+ function useRosterEditor(value, onChange, createItem) {
156
+ const [target, setTarget] = useState(null);
157
+ const [draft, setDraft] = useState(null);
158
+ const close = useCallback(() => {
159
+ setTarget(null);
160
+ setDraft(null);
161
+ }, []);
162
+ const openEdit = useCallback((groupKey, itemId) => {
163
+ setDraft(null);
164
+ setTarget({ groupKey, itemId });
165
+ }, []);
166
+ const openNew = useCallback(
167
+ (groupKey) => {
168
+ if (createItem === void 0) return;
169
+ setDraft(createItem(groupKey));
170
+ setTarget({ groupKey, itemId: null });
171
+ },
172
+ [createItem]
173
+ );
174
+ const session = resolveEditSession(value, target, draft);
175
+ const context = session === null ? null : {
176
+ ...session,
177
+ save: (next) => {
178
+ const updated = commitEdit(value, session, next);
179
+ if (updated !== value) onChange(updated);
180
+ close();
181
+ },
182
+ cancel: close,
183
+ remove: () => {
184
+ if (!session.isNew) onChange(removeItem(value, session.groupKey, session.item.id));
185
+ close();
186
+ }
187
+ };
188
+ const activeItemId = session !== null && !session.isNew ? session.item.id : null;
189
+ return { context, activeItemId, openEdit, openNew, close };
190
+ }
191
+ var styles = StyleSheet.create({
192
+ pane: { width: SPLIT_EDITOR_WIDTH, borderWidth: BORDER_WIDTH, borderRadius: RADIUS_M, padding: SPACE_L, gap: SPACE_L },
193
+ title: { fontSize: TITLE_FONT_SIZE, fontWeight: "700" },
194
+ prompt: { fontSize: BODY_FONT_SIZE }
195
+ });
196
+ function EditorPane({
197
+ context,
198
+ renderEditor,
199
+ split,
200
+ labels,
201
+ onClose,
202
+ testID,
203
+ placeholderTestID
204
+ }) {
205
+ const { theme } = useUi();
206
+ const title = context?.isNew === true ? labels.editorTitleNew : labels.editorTitleEdit;
207
+ if (!split)
208
+ return /* @__PURE__ */ jsx(ModalShell, { title, visible: context !== null, onCancel: onClose, children: context === null ? null : /* @__PURE__ */ jsx(View, { testID, children: renderEditor(context) }) });
209
+ return /* @__PURE__ */ jsx(View, { style: [styles.pane, { borderColor: theme.colors.border, backgroundColor: theme.colors.surface }], testID, children: context === null ? /* @__PURE__ */ jsx(Text, { style: [styles.prompt, { color: theme.colors.textSecondary }], testID: placeholderTestID, children: labels.selectPrompt }) : /* @__PURE__ */ jsxs(Fragment, { children: [
210
+ /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: [styles.title, { color: theme.colors.text }], children: title }),
211
+ renderEditor(context)
212
+ ] }) });
213
+ }
214
+ var styles2 = StyleSheet.create({
215
+ base: {
216
+ minHeight: MIN_TOUCH_TARGET,
217
+ minWidth: MIN_TOUCH_TARGET,
218
+ paddingHorizontal: SPACE_M,
219
+ borderRadius: RADIUS_S,
220
+ borderWidth: BORDER_WIDTH,
221
+ alignItems: "center",
222
+ justifyContent: "center"
223
+ },
224
+ label: { fontSize: BODY_FONT_SIZE, fontWeight: "600" },
225
+ pressed: { opacity: PRESSED_OPACITY },
226
+ disabled: { opacity: DISABLED_OPACITY }
227
+ });
228
+ function RosterButton({
229
+ label,
230
+ accessibilityLabel,
231
+ hint,
232
+ onPress,
233
+ testID,
234
+ emphasis = false,
235
+ danger = false,
236
+ disabled = false
237
+ }) {
238
+ const { theme } = useUi();
239
+ const accent = danger ? theme.semantic.error["500"] : theme.palette.primary["500"];
240
+ const tone = emphasis ? { backgroundColor: accent, borderColor: accent } : { borderColor: theme.colors.border };
241
+ const color = emphasis ? theme.colors.background : accent;
242
+ return /* @__PURE__ */ jsx(
243
+ Pressable,
244
+ {
245
+ accessibilityHint: hint,
246
+ accessibilityLabel: accessibilityLabel ?? label,
247
+ accessibilityRole: "button",
248
+ accessibilityState: { disabled },
249
+ disabled,
250
+ style: ({ pressed }) => [styles2.base, tone, pressed ? styles2.pressed : null, disabled ? styles2.disabled : null],
251
+ testID,
252
+ onPress,
253
+ children: /* @__PURE__ */ jsx(Text, { style: [styles2.label, { color }], children: label })
254
+ }
255
+ );
256
+ }
257
+ var styles3 = StyleSheet.create({
258
+ frame: { borderRadius: RADIUS_M, padding: SPACE_S, gap: SPACE_S, overflow: "hidden" },
259
+ fill: { alignSelf: "stretch" },
260
+ body: { position: "relative" },
261
+ busy: {
262
+ ...StyleSheet.absoluteFillObject,
263
+ alignItems: "center",
264
+ justifyContent: "center",
265
+ gap: SPACE_S,
266
+ opacity: BUSY_OVERLAY_OPACITY
267
+ },
268
+ small: { fontSize: SMALL_FONT_SIZE },
269
+ errorRow: { gap: SPACE_S },
270
+ footer: { flexDirection: "row", flexWrap: "wrap", gap: SPACE_S }
271
+ });
272
+ function ItemFrame({
273
+ width,
274
+ active,
275
+ state,
276
+ labels,
277
+ onRetry,
278
+ testID,
279
+ children,
280
+ footer
281
+ }) {
282
+ const { theme } = useUi();
283
+ const busy = state?.busy === true;
284
+ const error = state?.error ?? "";
285
+ const hasError = error !== "";
286
+ const errorColor = theme.semantic.error["500"];
287
+ const primary = theme.palette.primary["500"];
288
+ const highlighted = active || hasError;
289
+ const borderColor = hasError ? errorColor : active ? primary : theme.colors.border;
290
+ return /* @__PURE__ */ jsxs(
291
+ View,
292
+ {
293
+ accessibilityState: { busy },
294
+ style: [
295
+ styles3.frame,
296
+ width === void 0 ? styles3.fill : { width },
297
+ { borderColor, backgroundColor: theme.colors.surface, borderWidth: highlighted ? ACTIVE_BORDER_WIDTH : BORDER_WIDTH }
298
+ ],
299
+ testID,
300
+ children: [
301
+ /* @__PURE__ */ jsxs(View, { style: styles3.body, children: [
302
+ children,
303
+ busy ? /* @__PURE__ */ jsxs(View, { style: [styles3.busy, { backgroundColor: theme.colors.surfaceElevated }], testID: `${testID}-busy`, children: [
304
+ /* @__PURE__ */ jsx(ActivityIndicator, { color: primary }),
305
+ /* @__PURE__ */ jsx(Text, { accessibilityLiveRegion: "polite", style: [styles3.small, { color: theme.colors.textSecondary }], children: labels.uploading })
306
+ ] }) : null
307
+ ] }),
308
+ hasError ? /* @__PURE__ */ jsxs(View, { style: styles3.errorRow, testID: `${testID}-error`, children: [
309
+ /* @__PURE__ */ jsx(Text, { accessibilityRole: "alert", style: [styles3.small, { color: errorColor }], children: error }),
310
+ onRetry !== void 0 ? /* @__PURE__ */ jsx(RosterButton, { danger: true, hint: labels.retryHint, label: labels.retry, testID: `${testID}-retry`, onPress: onRetry }) : null
311
+ ] }) : null,
312
+ /* @__PURE__ */ jsx(View, { style: styles3.footer, children: footer })
313
+ ]
314
+ }
315
+ );
316
+ }
317
+ var styles4 = StyleSheet.create({
318
+ band: { gap: SPACE_M, paddingVertical: SPACE_XS }
319
+ });
320
+ function CardBand({ group, shared }) {
321
+ const { labels, ids, onRetryItem } = shared;
322
+ return /* @__PURE__ */ jsx(
323
+ ScrollView,
324
+ {
325
+ horizontal: true,
326
+ contentContainerStyle: styles4.band,
327
+ showsHorizontalScrollIndicator: false,
328
+ testID: `${ids.group(group.key)}-band`,
329
+ children: group.items.map((item) => {
330
+ const cardId = ids.card(item.id);
331
+ return /* @__PURE__ */ jsx(
332
+ ItemFrame,
333
+ {
334
+ active: shared.activeItemId === item.id,
335
+ footer: /* @__PURE__ */ jsx(
336
+ RosterButton,
337
+ {
338
+ accessibilityLabel: labels.editCard(shared.getItemName(item)),
339
+ hint: labels.editCardHint,
340
+ label: labels.edit,
341
+ testID: `${cardId}-edit`,
342
+ onPress: () => shared.onEdit(group.key, item.id)
343
+ }
344
+ ),
345
+ labels,
346
+ state: shared.getItemState?.(item),
347
+ testID: cardId,
348
+ width: shared.cardWidth,
349
+ onRetry: onRetryItem === void 0 ? void 0 : () => onRetryItem(item),
350
+ children: shared.renderCard(item, group)
351
+ },
352
+ item.id
353
+ );
354
+ })
355
+ }
356
+ );
357
+ }
358
+ var styles5 = StyleSheet.create({
359
+ header: { flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: SPACE_S },
360
+ title: { flexGrow: 1, flexShrink: 1, minWidth: GROUP_TITLE_MIN_WIDTH, fontSize: TITLE_FONT_SIZE, fontWeight: "700" },
361
+ input: {
362
+ flexGrow: 1,
363
+ minWidth: GROUP_TITLE_MIN_WIDTH,
364
+ minHeight: MIN_TOUCH_TARGET,
365
+ paddingHorizontal: SPACE_M,
366
+ borderWidth: BORDER_WIDTH,
367
+ borderRadius: RADIUS_S,
368
+ fontSize: BODY_FONT_SIZE
369
+ },
370
+ actions: { flexDirection: "row", flexWrap: "wrap", gap: SPACE_S }
371
+ });
372
+ function GroupHeader({ group, index, shared }) {
373
+ const { theme } = useUi();
374
+ const [draft, setDraft] = useState(null);
375
+ const { labels, actions, reordering } = shared;
376
+ const gid = shared.ids.group(group.key);
377
+ const commit = () => {
378
+ if (draft !== null) actions.renameGroup(group.key, draft);
379
+ setDraft(null);
380
+ };
381
+ const reorderActions = /* @__PURE__ */ jsxs(Fragment, { children: [
382
+ /* @__PURE__ */ jsx(
383
+ RosterButton,
384
+ {
385
+ accessibilityLabel: labels.moveGroupUp(group.title),
386
+ disabled: index === 0,
387
+ hint: labels.moveGroupHint,
388
+ label: GLYPH_UP,
389
+ testID: `${gid}-up`,
390
+ onPress: () => actions.moveGroup(group.key, MOVE_UP)
391
+ }
392
+ ),
393
+ /* @__PURE__ */ jsx(
394
+ RosterButton,
395
+ {
396
+ accessibilityLabel: labels.moveGroupDown(group.title),
397
+ disabled: index === shared.groupCount - 1,
398
+ hint: labels.moveGroupHint,
399
+ label: GLYPH_DOWN,
400
+ testID: `${gid}-down`,
401
+ onPress: () => actions.moveGroup(group.key, MOVE_DOWN)
402
+ }
403
+ )
404
+ ] });
405
+ const editActions = /* @__PURE__ */ jsxs(Fragment, { children: [
406
+ shared.onAdd !== void 0 ? /* @__PURE__ */ jsx(
407
+ RosterButton,
408
+ {
409
+ emphasis: true,
410
+ accessibilityLabel: labels.addCard(group.title),
411
+ hint: labels.addCardHint,
412
+ label: labels.add,
413
+ testID: `${gid}-add`,
414
+ onPress: () => shared.onAdd?.(group.key)
415
+ }
416
+ ) : null,
417
+ /* @__PURE__ */ jsx(
418
+ RosterButton,
419
+ {
420
+ accessibilityLabel: labels.renameGroup(group.title),
421
+ hint: labels.renameGroupHint,
422
+ label: labels.rename,
423
+ testID: `${gid}-rename`,
424
+ onPress: () => setDraft(group.title)
425
+ }
426
+ ),
427
+ shared.canRemoveGroup(group) ? /* @__PURE__ */ jsx(
428
+ RosterButton,
429
+ {
430
+ danger: true,
431
+ accessibilityLabel: labels.removeGroup(group.title),
432
+ hint: labels.removeGroupHint,
433
+ label: labels.remove,
434
+ testID: `${gid}-remove`,
435
+ onPress: () => actions.removeGroup(group.key)
436
+ }
437
+ ) : null
438
+ ] });
439
+ return /* @__PURE__ */ jsxs(View, { style: styles5.header, children: [
440
+ draft === null ? /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", numberOfLines: 1, style: [styles5.title, { color: theme.colors.text }], children: group.title }) : /* @__PURE__ */ jsx(
441
+ TextInput,
442
+ {
443
+ autoFocus: true,
444
+ accessibilityHint: labels.groupTitleInputHint,
445
+ accessibilityLabel: labels.groupTitleInput,
446
+ style: [styles5.input, { color: theme.colors.text, borderColor: theme.palette.primary["500"] }],
447
+ testID: `${gid}-title-input`,
448
+ value: draft,
449
+ onBlur: commit,
450
+ onChangeText: setDraft,
451
+ onSubmitEditing: commit
452
+ }
453
+ ),
454
+ /* @__PURE__ */ jsx(View, { style: styles5.actions, children: reordering ? reorderActions : editActions })
455
+ ] });
456
+ }
457
+ var styles6 = StyleSheet.create({
458
+ item: { paddingVertical: SPACE_S / 2 }
459
+ });
460
+ function ReorderList({ group, shared }) {
461
+ const { labels, ids, actions } = shared;
462
+ const keys = useMemo(() => group.items.map((item) => item.id), [group.items]);
463
+ const last = group.items.length - 1;
464
+ const renderItem = (key) => {
465
+ const index = group.items.findIndex((item2) => item2.id === key);
466
+ const item = group.items[index];
467
+ if (item === void 0) return null;
468
+ const name = shared.getItemName(item);
469
+ const cardId = ids.card(item.id);
470
+ return /* @__PURE__ */ jsx(View, { style: styles6.item, children: /* @__PURE__ */ jsx(
471
+ ItemFrame,
472
+ {
473
+ active: false,
474
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
475
+ /* @__PURE__ */ jsx(
476
+ RosterButton,
477
+ {
478
+ accessibilityLabel: labels.moveCardUp(name),
479
+ disabled: index === 0,
480
+ hint: labels.moveCardHint,
481
+ label: GLYPH_UP,
482
+ testID: `${cardId}-up`,
483
+ onPress: () => actions.moveItem(group.key, item.id, MOVE_UP)
484
+ }
485
+ ),
486
+ /* @__PURE__ */ jsx(
487
+ RosterButton,
488
+ {
489
+ accessibilityLabel: labels.moveCardDown(name),
490
+ disabled: index === last,
491
+ hint: labels.moveCardHint,
492
+ label: GLYPH_DOWN,
493
+ testID: `${cardId}-down`,
494
+ onPress: () => actions.moveItem(group.key, item.id, MOVE_DOWN)
495
+ }
496
+ )
497
+ ] }),
498
+ labels,
499
+ state: shared.getItemState?.(item),
500
+ testID: cardId,
501
+ children: shared.renderCard(item, group)
502
+ }
503
+ ) });
504
+ };
505
+ return /* @__PURE__ */ jsx(Reorder, { itemKeys: keys, spring: REORDER_SPRING, testID: `${ids.group(group.key)}-reorder`, children: renderItem });
506
+ }
507
+ var styles7 = StyleSheet.create({
508
+ section: { gap: SPACE_S },
509
+ empty: {
510
+ borderWidth: BORDER_WIDTH,
511
+ borderStyle: "dashed",
512
+ borderRadius: RADIUS_M,
513
+ padding: SPACE_L,
514
+ alignItems: "center"
515
+ },
516
+ emptyText: { fontSize: BODY_FONT_SIZE, textAlign: "center" }
517
+ });
518
+ function RosterGroupSection({
519
+ group,
520
+ index,
521
+ shared
522
+ }) {
523
+ const { theme } = useUi();
524
+ const gid = shared.ids.group(group.key);
525
+ let body;
526
+ if (group.items.length === 0)
527
+ body = /* @__PURE__ */ jsx(View, { style: [styles7.empty, { borderColor: theme.colors.border }], testID: `${gid}-empty`, children: /* @__PURE__ */ jsx(Text, { style: [styles7.emptyText, { color: theme.colors.textSecondary }], children: shared.labels.emptyGroup }) });
528
+ else if (shared.reordering) body = /* @__PURE__ */ jsx(ReorderList, { group, shared });
529
+ else body = /* @__PURE__ */ jsx(CardBand, { group, shared });
530
+ return /* @__PURE__ */ jsxs(View, { style: styles7.section, testID: gid, children: [
531
+ /* @__PURE__ */ jsx(GroupHeader, { group, index, shared }),
532
+ body
533
+ ] });
534
+ }
535
+ var styles8 = StyleSheet.create({
536
+ root: { gap: SPACE_L },
537
+ split: { flexDirection: "row", alignItems: "flex-start" },
538
+ list: { flex: 1, minWidth: 0, gap: SPACE_L },
539
+ toolbar: { flexDirection: "row", flexWrap: "wrap", gap: SPACE_S, justifyContent: "flex-end" }
540
+ });
541
+ function CardRoster(props) {
542
+ const {
543
+ value,
544
+ onChange,
545
+ renderCard,
546
+ renderEditor,
547
+ getItemName,
548
+ labels,
549
+ createItem,
550
+ createGroup,
551
+ canRemoveGroup = isGroupEmpty,
552
+ getItemState,
553
+ onRetryItem,
554
+ error,
555
+ wideBreakpoint = LAYOUT_COLLAPSE_BREAKPOINT,
556
+ cardWidth = DEFAULT_CARD_WIDTH,
557
+ testID = DEFAULT_TEST_ID
558
+ } = props;
559
+ const { width } = useWindowDimensions();
560
+ const split = isSplitPane(width, wideBreakpoint);
561
+ const [reordering, setReordering] = useState(false);
562
+ const editor = useRosterEditor(value, onChange, createItem);
563
+ const actions = useRosterActions(value, onChange);
564
+ const ids = useMemo(() => rosterTestIds(testID), [testID]);
565
+ const { close, openEdit, openNew } = editor;
566
+ const toggleReorder = useCallback(() => {
567
+ close();
568
+ setReordering((on) => !on);
569
+ }, [close]);
570
+ const shared = {
571
+ renderCard,
572
+ getItemName,
573
+ getItemState,
574
+ onRetryItem,
575
+ labels,
576
+ cardWidth,
577
+ ids,
578
+ reordering,
579
+ actions,
580
+ canRemoveGroup,
581
+ activeItemId: editor.activeItemId,
582
+ groupCount: value.groups.length,
583
+ onEdit: openEdit,
584
+ onAdd: createItem === void 0 ? void 0 : openNew
585
+ };
586
+ return /* @__PURE__ */ jsxs(View, { style: [styles8.root, split ? styles8.split : null], testID: ids.root, children: [
587
+ /* @__PURE__ */ jsxs(View, { style: styles8.list, children: [
588
+ /* @__PURE__ */ jsxs(View, { style: styles8.toolbar, children: [
589
+ /* @__PURE__ */ jsx(
590
+ RosterButton,
591
+ {
592
+ emphasis: reordering,
593
+ hint: reordering ? labels.doneReorderingHint : labels.reorderHint,
594
+ label: reordering ? labels.doneReordering : labels.reorder,
595
+ testID: ids.reorderToggle,
596
+ onPress: toggleReorder
597
+ }
598
+ ),
599
+ createGroup !== void 0 && !reordering ? /* @__PURE__ */ jsx(
600
+ RosterButton,
601
+ {
602
+ hint: labels.addGroupHint,
603
+ label: labels.addGroup,
604
+ testID: ids.addGroup,
605
+ onPress: () => actions.addGroup(createGroup())
606
+ }
607
+ ) : null
608
+ ] }),
609
+ error !== void 0 ? /* @__PURE__ */ jsx(ErrorState, { message: error.message, testID: ids.error, title: error.title, onRetry: error.onRetry }) : null,
610
+ value.groups.map((group, index) => /* @__PURE__ */ jsx(RosterGroupSection, { group, index, shared }, group.key))
611
+ ] }),
612
+ reordering && split ? null : /* @__PURE__ */ jsx(
613
+ EditorPane,
614
+ {
615
+ context: editor.context,
616
+ labels,
617
+ placeholderTestID: ids.editorPlaceholder,
618
+ renderEditor,
619
+ split,
620
+ testID: ids.editor,
621
+ onClose: close
622
+ }
623
+ )
624
+ ] });
625
+ }
626
+
627
+ export { CardRoster, DEFAULT_CARD_WIDTH, DEFAULT_TEST_ID, MIN_TOUCH_TARGET, MOVE_DOWN, MOVE_UP, addGroup, addItem, canMove, commitEdit, findItem, isGroupEmpty, isSplitPane, moveGroup, moveInArray, moveItem, removeGroup, removeItem, renameGroup, resolveEditSession, rosterTestIds, updateItem, useRosterActions, useRosterEditor };
628
+ //# sourceMappingURL=index.mjs.map
629
+ //# sourceMappingURL=index.mjs.map