@casualoffice/sheets 0.17.0 → 0.19.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/chrome.cjs +3339 -244
- package/dist/chrome.cjs.map +1 -1
- package/dist/chrome.js +3298 -203
- package/dist/chrome.js.map +1 -1
- package/dist/embed/embed-runtime.js +203 -157
- package/dist/index.cjs +5971 -1572
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5961 -1548
- package/dist/index.js.map +1 -1
- package/dist/sheets.cjs +4937 -538
- package/dist/sheets.cjs.map +1 -1
- package/dist/sheets.js +4945 -532
- package/dist/sheets.js.map +1 -1
- package/package.json +8 -7
- package/src/charts/ChartContextMenu.tsx +264 -0
- package/src/charts/ChartLayer.tsx +333 -0
- package/src/charts/ChartOverlay.tsx +293 -0
- package/src/charts/ChartsPanel.tsx +211 -0
- package/src/charts/FormatChartDialog.tsx +419 -0
- package/src/charts/InsertChartDialog.tsx +478 -0
- package/src/charts/build-option.ts +476 -0
- package/src/charts/charts-context.tsx +205 -0
- package/src/charts/echarts-init.ts +58 -0
- package/src/charts/hit-test.ts +130 -0
- package/src/charts/insert-chart.ts +106 -0
- package/src/charts/naming.ts +38 -0
- package/src/charts/render-to-png.ts +117 -0
- package/src/charts/resources.ts +108 -0
- package/src/charts/types.ts +239 -0
- package/src/charts/univer-dom.ts +102 -0
- package/src/chrome/CommentsPanel.tsx +427 -0
- package/src/chrome/ConditionalFormattingDialog.tsx +534 -0
- package/src/chrome/CustomSortDialog.tsx +357 -0
- package/src/chrome/DataValidationDialog.tsx +536 -0
- package/src/chrome/DeleteCellsDialog.tsx +183 -0
- package/src/chrome/GoalSeekDialog.tsx +370 -0
- package/src/chrome/HistoryPanel.tsx +319 -0
- package/src/chrome/InsertCellsDialog.tsx +185 -0
- package/src/chrome/InsertChartDialog.tsx +490 -0
- package/src/chrome/InsertFunctionDialog.tsx +493 -0
- package/src/chrome/InsertPivotDialog.tsx +488 -0
- package/src/chrome/InsertSparklineDialog.tsx +344 -0
- package/src/chrome/NameManagerDialog.tsx +378 -0
- package/src/chrome/PanelHost.tsx +55 -0
- package/src/chrome/PanelRail.tsx +90 -0
- package/src/chrome/PasteSpecialDialog.tsx +286 -0
- package/src/chrome/PivotFieldsPanel.tsx +1052 -0
- package/src/chrome/TablesPanel.tsx +301 -0
- package/src/chrome/dialog-context.tsx +24 -0
- package/src/chrome/panel-context.tsx +55 -0
- package/src/chrome/panel-registry.ts +48 -0
- package/src/sheets/CasualSheets.tsx +60 -34
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Casual Office
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License").
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Comments side panel: lists the thread comments on the active sheet (active +
|
|
9
|
+
* resolved), navigates to a comment's cell on click, and resolves / reopens a
|
|
10
|
+
* thread inline. "Add comment" opens Univer's comment-on-cell modal (the same
|
|
11
|
+
* one as Review → New comment); the in-cell popup owns full reply threading.
|
|
12
|
+
*
|
|
13
|
+
* Ported from the standalone app's `shell/CommentsPanel`. The app-only collab
|
|
14
|
+
* stores it depended on — `collab/comment-authors` (avatar authorship),
|
|
15
|
+
* `collab/presence` (display name / initials) and `collab/comment-mentions`
|
|
16
|
+
* (@-mention "mentions you" badge) — DO NOT exist in the SDK and are dropped:
|
|
17
|
+
* the byline degrades to the in-band `personId` string carried on the comment
|
|
18
|
+
* model (no avatar, no color), and the mentions-me highlight is omitted. The
|
|
19
|
+
* `useUniverAPI()` / `useUI()` couplings are replaced by the `api` / `onClose`
|
|
20
|
+
* panel-host props, and the Univer facade is reached through `api.univer`.
|
|
21
|
+
*/
|
|
22
|
+
import { useEffect, useMemo, useState, type CSSProperties } from 'react';
|
|
23
|
+
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
|
|
24
|
+
|
|
25
|
+
import { ensurePluginByName } from '../univer';
|
|
26
|
+
import type { PanelComponentProps } from './extensions';
|
|
27
|
+
import { Icon } from './Icon';
|
|
28
|
+
|
|
29
|
+
interface CommentRow {
|
|
30
|
+
id: string;
|
|
31
|
+
text: string;
|
|
32
|
+
ref: string;
|
|
33
|
+
author: string;
|
|
34
|
+
replies: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function cleanText(stream: string | undefined): string {
|
|
38
|
+
return (stream ?? '').replace(/[\r\n\t]+/g, ' ').trim() || '(empty comment)';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Active comments come straight off the facade (`getComments()` returns the
|
|
43
|
+
* cell-location index; resolved threads leave it, so they're read separately).
|
|
44
|
+
* Only root comments are listed — replies render under their root in the
|
|
45
|
+
* in-cell popup.
|
|
46
|
+
*/
|
|
47
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
48
|
+
function readActive(univer: any): CommentRow[] {
|
|
49
|
+
const ws = univer?.getActiveWorkbook?.()?.getActiveSheet?.();
|
|
50
|
+
const all: any[] = ws?.getComments?.() ?? [];
|
|
51
|
+
const rows: CommentRow[] = [];
|
|
52
|
+
for (const c of all) {
|
|
53
|
+
try {
|
|
54
|
+
if (c.getIsRoot && !c.getIsRoot()) continue;
|
|
55
|
+
const data = c.getCommentData?.() ?? {};
|
|
56
|
+
let ref = '';
|
|
57
|
+
try {
|
|
58
|
+
ref = c.getRange?.()?.getA1Notation?.() ?? '';
|
|
59
|
+
} catch {
|
|
60
|
+
/* range no longer resolvable */
|
|
61
|
+
}
|
|
62
|
+
rows.push({
|
|
63
|
+
id: c.id ?? data.id ?? ref,
|
|
64
|
+
text: cleanText(data.text?.dataStream),
|
|
65
|
+
ref,
|
|
66
|
+
author: data.personId ?? '',
|
|
67
|
+
replies: c.getReplies?.()?.length ?? 0,
|
|
68
|
+
});
|
|
69
|
+
} catch {
|
|
70
|
+
/* skip malformed thread */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return rows;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolved comments have left the cell-location index, so `getComments()` no
|
|
78
|
+
* longer returns them. Read them straight from the model — the only path to a
|
|
79
|
+
* "resolved" view + reopen.
|
|
80
|
+
*/
|
|
81
|
+
function readResolved(univer: any): CommentRow[] {
|
|
82
|
+
try {
|
|
83
|
+
const injector = (univer as { _injector?: { get(t: unknown): unknown } })?._injector;
|
|
84
|
+
const model = injector?.get?.(SheetsThreadCommentModel) as any;
|
|
85
|
+
const wb = univer?.getActiveWorkbook?.();
|
|
86
|
+
const ws = wb?.getActiveSheet?.();
|
|
87
|
+
if (!model?.getSubUnitAll || !wb || !ws) return [];
|
|
88
|
+
const unitId = wb.getId();
|
|
89
|
+
const subUnitId = ws.getSheetId?.() ?? ws.getId?.();
|
|
90
|
+
const all: any[] = model.getSubUnitAll(unitId, subUnitId) ?? [];
|
|
91
|
+
const rows: CommentRow[] = [];
|
|
92
|
+
for (const c of all) {
|
|
93
|
+
if (!c?.resolved) continue;
|
|
94
|
+
rows.push({
|
|
95
|
+
id: c.id,
|
|
96
|
+
text: cleanText(c.text?.dataStream),
|
|
97
|
+
ref: c.ref ?? '',
|
|
98
|
+
author: c.personId ?? '',
|
|
99
|
+
replies: (c.children ?? []).length,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return rows;
|
|
103
|
+
} catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
108
|
+
|
|
109
|
+
const header: CSSProperties = {
|
|
110
|
+
display: 'flex',
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
gap: 8,
|
|
113
|
+
padding: '10px 12px',
|
|
114
|
+
borderBottom: '1px solid var(--cs-chrome-border, #edeff3)',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const rowCard: CSSProperties = {
|
|
118
|
+
border: '1px solid var(--cs-chrome-border, #edeff3)',
|
|
119
|
+
borderRadius: 8,
|
|
120
|
+
padding: 10,
|
|
121
|
+
display: 'flex',
|
|
122
|
+
alignItems: 'flex-start',
|
|
123
|
+
gap: 6,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const iconBtn: CSSProperties = {
|
|
127
|
+
border: 'none',
|
|
128
|
+
background: 'transparent',
|
|
129
|
+
cursor: 'pointer',
|
|
130
|
+
color: 'inherit',
|
|
131
|
+
padding: 0,
|
|
132
|
+
display: 'inline-flex',
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
function CommentCard({
|
|
137
|
+
row,
|
|
138
|
+
resolved,
|
|
139
|
+
onOpen,
|
|
140
|
+
onToggle,
|
|
141
|
+
}: {
|
|
142
|
+
row: CommentRow;
|
|
143
|
+
resolved: boolean;
|
|
144
|
+
onOpen: (r: CommentRow) => void;
|
|
145
|
+
onToggle: (id: string, value: boolean) => void;
|
|
146
|
+
}) {
|
|
147
|
+
return (
|
|
148
|
+
<li
|
|
149
|
+
data-testid={`cs-comments-panel-${resolved ? 'resolved-row' : 'row'}-${row.id}`}
|
|
150
|
+
style={{ ...rowCard, opacity: resolved ? 0.72 : 1 }}
|
|
151
|
+
>
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
onClick={() => onOpen(row)}
|
|
155
|
+
title={`Go to ${row.ref || 'comment'}`}
|
|
156
|
+
style={{
|
|
157
|
+
flex: 1,
|
|
158
|
+
minWidth: 0,
|
|
159
|
+
textAlign: 'left',
|
|
160
|
+
border: 'none',
|
|
161
|
+
background: 'transparent',
|
|
162
|
+
cursor: 'pointer',
|
|
163
|
+
color: 'inherit',
|
|
164
|
+
font: 'inherit',
|
|
165
|
+
display: 'flex',
|
|
166
|
+
flexDirection: 'column',
|
|
167
|
+
gap: 4,
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
171
|
+
<span style={{ fontWeight: 600 }}>{row.ref || 'Comment'}</span>
|
|
172
|
+
{row.author && <span style={{ opacity: 0.6, fontSize: 12 }}>{row.author}</span>}
|
|
173
|
+
{row.replies > 0 && (
|
|
174
|
+
<span
|
|
175
|
+
title={`${row.replies} replies`}
|
|
176
|
+
style={{
|
|
177
|
+
opacity: 0.6,
|
|
178
|
+
fontSize: 12,
|
|
179
|
+
display: 'inline-flex',
|
|
180
|
+
alignItems: 'center',
|
|
181
|
+
gap: 2,
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
<Icon name="reply" size={13} />
|
|
185
|
+
{row.replies}
|
|
186
|
+
</span>
|
|
187
|
+
)}
|
|
188
|
+
</span>
|
|
189
|
+
<span
|
|
190
|
+
style={{
|
|
191
|
+
fontSize: 13,
|
|
192
|
+
overflow: 'hidden',
|
|
193
|
+
textOverflow: 'ellipsis',
|
|
194
|
+
display: '-webkit-box',
|
|
195
|
+
WebkitLineClamp: 2,
|
|
196
|
+
WebkitBoxOrient: 'vertical',
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
{row.text}
|
|
200
|
+
</span>
|
|
201
|
+
</button>
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
data-testid={`cs-comments-panel-${resolved ? 'reopen' : 'resolve'}-${row.id}`}
|
|
205
|
+
aria-label={resolved ? 'Reopen comment' : 'Resolve comment'}
|
|
206
|
+
title={resolved ? 'Reopen comment' : 'Resolve comment'}
|
|
207
|
+
onClick={() => onToggle(row.id, !resolved)}
|
|
208
|
+
style={iconBtn}
|
|
209
|
+
>
|
|
210
|
+
<Icon name={resolved ? 'undo' : 'check_circle'} size={16} />
|
|
211
|
+
</button>
|
|
212
|
+
</li>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const REFRESH_CMDS = (id: string) =>
|
|
217
|
+
id.includes('comment') ||
|
|
218
|
+
id === 'sheet.operation.set-worksheet-activate' ||
|
|
219
|
+
id === 'doc.command-replace-snapshot';
|
|
220
|
+
|
|
221
|
+
export function CommentsPanel({ api, onClose }: PanelComponentProps) {
|
|
222
|
+
const univer = (api as { univer?: any }).univer; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
223
|
+
const [rows, setRows] = useState<CommentRow[]>([]);
|
|
224
|
+
const [resolved, setResolved] = useState<CommentRow[]>([]);
|
|
225
|
+
const [showResolved, setShowResolved] = useState(false);
|
|
226
|
+
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (!univer) return;
|
|
229
|
+
let cancelled = false;
|
|
230
|
+
let disp: { dispose?: () => void } | undefined;
|
|
231
|
+
const read = () => {
|
|
232
|
+
if (cancelled) return;
|
|
233
|
+
setRows(readActive(univer));
|
|
234
|
+
setResolved(readResolved(univer));
|
|
235
|
+
};
|
|
236
|
+
// The thread-comment plugin is lazy — `getComments()` and
|
|
237
|
+
// SheetsThreadCommentModel only resolve once its group is registered.
|
|
238
|
+
void ensurePluginByName('threadComment').then(() => {
|
|
239
|
+
if (cancelled) return;
|
|
240
|
+
read();
|
|
241
|
+
disp = univer.addEvent(univer.Event.CommandExecuted, (e: { id?: string }) => {
|
|
242
|
+
if (e.id && REFRESH_CMDS(e.id)) read();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
return () => {
|
|
246
|
+
cancelled = true;
|
|
247
|
+
disp?.dispose?.();
|
|
248
|
+
};
|
|
249
|
+
}, [univer]);
|
|
250
|
+
|
|
251
|
+
const empty = rows.length === 0 && resolved.length === 0;
|
|
252
|
+
|
|
253
|
+
const openThread = (r: CommentRow) => {
|
|
254
|
+
if (!univer || !r.ref) return;
|
|
255
|
+
try {
|
|
256
|
+
univer.getActiveWorkbook?.()?.getActiveSheet?.()?.getRange?.(r.ref)?.activate?.();
|
|
257
|
+
} catch {
|
|
258
|
+
/* range gone */
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const setResolvedState = async (commentId: string, value: boolean) => {
|
|
263
|
+
if (!univer) return;
|
|
264
|
+
try {
|
|
265
|
+
const wb = univer.getActiveWorkbook?.();
|
|
266
|
+
const ws = wb?.getActiveSheet?.();
|
|
267
|
+
if (!wb || !ws) return;
|
|
268
|
+
const subUnitId = ws.getSheetId?.() ?? ws.getId?.();
|
|
269
|
+
await ensurePluginByName('threadComment');
|
|
270
|
+
univer.executeCommand('thread-comment.command.resolve-comment', {
|
|
271
|
+
unitId: wb.getId(),
|
|
272
|
+
subUnitId,
|
|
273
|
+
commentId,
|
|
274
|
+
resolved: value,
|
|
275
|
+
});
|
|
276
|
+
} catch {
|
|
277
|
+
/* command unavailable — plugin not loaded */
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const addComment = async () => {
|
|
282
|
+
if (!univer) return;
|
|
283
|
+
await ensurePluginByName('threadComment');
|
|
284
|
+
univer.executeCommand('sheet.operation.show-comment-modal');
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const handlers = useMemo(
|
|
288
|
+
() => ({ open: openThread, toggle: setResolvedState }),
|
|
289
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
290
|
+
[univer],
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<div
|
|
295
|
+
data-testid="cs-comments-panel"
|
|
296
|
+
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
|
|
297
|
+
>
|
|
298
|
+
<header style={header}>
|
|
299
|
+
<Icon name="forum" size={18} />
|
|
300
|
+
<span style={{ fontWeight: 600, flex: 1 }}>Comments</span>
|
|
301
|
+
{rows.length > 0 && <span style={{ opacity: 0.6, fontSize: 12 }}>{rows.length}</span>}
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
aria-label="Add comment"
|
|
305
|
+
title="Add comment"
|
|
306
|
+
onClick={addComment}
|
|
307
|
+
style={iconBtn}
|
|
308
|
+
>
|
|
309
|
+
<Icon name="add_comment" size={18} />
|
|
310
|
+
</button>
|
|
311
|
+
<button type="button" aria-label="Close comments panel" onClick={onClose} style={iconBtn}>
|
|
312
|
+
<Icon name="close" size={18} />
|
|
313
|
+
</button>
|
|
314
|
+
</header>
|
|
315
|
+
|
|
316
|
+
<div style={{ flex: 1, overflow: 'auto', padding: 12 }}>
|
|
317
|
+
{empty ? (
|
|
318
|
+
<div
|
|
319
|
+
data-testid="cs-comments-panel-empty"
|
|
320
|
+
style={{ textAlign: 'center', opacity: 0.75, padding: '24px 8px' }}
|
|
321
|
+
>
|
|
322
|
+
<Icon name="forum" size={40} style={{ opacity: 0.4 }} />
|
|
323
|
+
<div style={{ fontWeight: 600, marginTop: 8 }}>No comments yet</div>
|
|
324
|
+
<div style={{ fontSize: 13, marginTop: 4 }}>
|
|
325
|
+
Select a cell and add a comment to start a discussion — or use{' '}
|
|
326
|
+
<strong>Review → New comment</strong>.
|
|
327
|
+
</div>
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
data-testid="cs-comments-panel-empty-cta"
|
|
331
|
+
disabled={!univer}
|
|
332
|
+
onClick={addComment}
|
|
333
|
+
style={{
|
|
334
|
+
marginTop: 12,
|
|
335
|
+
padding: '6px 14px',
|
|
336
|
+
borderRadius: 6,
|
|
337
|
+
border: 'none',
|
|
338
|
+
cursor: univer ? 'pointer' : 'default',
|
|
339
|
+
background: 'var(--cs-chrome-active-fg, #0e7490)',
|
|
340
|
+
color: '#fff',
|
|
341
|
+
font: 'inherit',
|
|
342
|
+
fontWeight: 600,
|
|
343
|
+
}}
|
|
344
|
+
>
|
|
345
|
+
Add comment
|
|
346
|
+
</button>
|
|
347
|
+
</div>
|
|
348
|
+
) : (
|
|
349
|
+
<>
|
|
350
|
+
{rows.length > 0 && (
|
|
351
|
+
<ul
|
|
352
|
+
style={{
|
|
353
|
+
listStyle: 'none',
|
|
354
|
+
margin: 0,
|
|
355
|
+
padding: 0,
|
|
356
|
+
display: 'flex',
|
|
357
|
+
flexDirection: 'column',
|
|
358
|
+
gap: 8,
|
|
359
|
+
}}
|
|
360
|
+
>
|
|
361
|
+
{rows.map((r) => (
|
|
362
|
+
<CommentCard
|
|
363
|
+
key={r.id}
|
|
364
|
+
row={r}
|
|
365
|
+
resolved={false}
|
|
366
|
+
onOpen={handlers.open}
|
|
367
|
+
onToggle={handlers.toggle}
|
|
368
|
+
/>
|
|
369
|
+
))}
|
|
370
|
+
</ul>
|
|
371
|
+
)}
|
|
372
|
+
|
|
373
|
+
{resolved.length > 0 && (
|
|
374
|
+
<div style={{ marginTop: rows.length > 0 ? 12 : 0 }}>
|
|
375
|
+
<button
|
|
376
|
+
type="button"
|
|
377
|
+
data-testid="cs-comments-panel-resolved-toggle"
|
|
378
|
+
aria-expanded={showResolved}
|
|
379
|
+
onClick={() => setShowResolved((v) => !v)}
|
|
380
|
+
style={{
|
|
381
|
+
display: 'flex',
|
|
382
|
+
alignItems: 'center',
|
|
383
|
+
gap: 4,
|
|
384
|
+
width: '100%',
|
|
385
|
+
border: 'none',
|
|
386
|
+
background: 'transparent',
|
|
387
|
+
cursor: 'pointer',
|
|
388
|
+
color: 'inherit',
|
|
389
|
+
font: 'inherit',
|
|
390
|
+
fontWeight: 600,
|
|
391
|
+
padding: '4px 0',
|
|
392
|
+
}}
|
|
393
|
+
>
|
|
394
|
+
<Icon name={showResolved ? 'expand_more' : 'chevron_right'} size={16} />
|
|
395
|
+
Resolved
|
|
396
|
+
<span style={{ opacity: 0.6, fontSize: 12 }}>{resolved.length}</span>
|
|
397
|
+
</button>
|
|
398
|
+
{showResolved && (
|
|
399
|
+
<ul
|
|
400
|
+
style={{
|
|
401
|
+
listStyle: 'none',
|
|
402
|
+
margin: '8px 0 0',
|
|
403
|
+
padding: 0,
|
|
404
|
+
display: 'flex',
|
|
405
|
+
flexDirection: 'column',
|
|
406
|
+
gap: 8,
|
|
407
|
+
}}
|
|
408
|
+
>
|
|
409
|
+
{resolved.map((r) => (
|
|
410
|
+
<CommentCard
|
|
411
|
+
key={r.id}
|
|
412
|
+
row={r}
|
|
413
|
+
resolved
|
|
414
|
+
onOpen={handlers.open}
|
|
415
|
+
onToggle={handlers.toggle}
|
|
416
|
+
/>
|
|
417
|
+
))}
|
|
418
|
+
</ul>
|
|
419
|
+
)}
|
|
420
|
+
</div>
|
|
421
|
+
)}
|
|
422
|
+
</>
|
|
423
|
+
)}
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
);
|
|
427
|
+
}
|