@object-ui/plugin-grid 3.0.2 → 3.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/.turbo/turbo-build.log +10 -49
- package/CHANGELOG.md +11 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2169 -922
- package/dist/index.umd.cjs +9 -3
- package/dist/plugin-grid/src/FormulaBar.d.ts +29 -0
- package/dist/plugin-grid/src/GroupRow.d.ts +23 -0
- package/dist/plugin-grid/src/ImportWizard.d.ts +29 -0
- package/dist/{packages/plugin-grid → plugin-grid}/src/ObjectGrid.d.ts +1 -0
- package/dist/plugin-grid/src/SplitPaneGrid.d.ts +22 -0
- package/dist/plugin-grid/src/components/BulkActionBar.d.ts +12 -0
- package/dist/plugin-grid/src/components/RowActionMenu.d.ts +23 -0
- package/dist/plugin-grid/src/index.d.ts +35 -0
- package/dist/plugin-grid/src/useCellClipboard.d.ts +47 -0
- package/dist/plugin-grid/src/useColumnSummary.d.ts +25 -0
- package/dist/plugin-grid/src/useGradientColor.d.ts +37 -0
- package/dist/plugin-grid/src/useGroupReorder.d.ts +34 -0
- package/dist/{packages/plugin-grid → plugin-grid}/src/useGroupedData.d.ts +24 -3
- package/package.json +10 -10
- package/src/FormulaBar.tsx +151 -0
- package/src/GroupRow.tsx +69 -0
- package/src/ImportWizard.tsx +412 -0
- package/src/ListColumnExtensions.test.tsx +4 -5
- package/src/ObjectGrid.tsx +994 -139
- package/src/SplitPaneGrid.tsx +120 -0
- package/src/VirtualGrid.tsx +2 -2
- package/src/__tests__/GroupRow.test.tsx +206 -0
- package/src/__tests__/ImportPreview.test.tsx +171 -0
- package/src/__tests__/accessorKey-inference.test.tsx +132 -0
- package/src/__tests__/airtable-style.test.tsx +508 -0
- package/src/__tests__/column-features.test.tsx +490 -0
- package/src/__tests__/grid-export.test.tsx +121 -0
- package/src/__tests__/mobile-card-view.test.tsx +355 -0
- package/src/__tests__/objectdef-enrichment.test.tsx +566 -0
- package/src/__tests__/phase11-features.test.tsx +418 -0
- package/src/__tests__/row-bulk-actions.test.tsx +413 -0
- package/src/__tests__/row-height.test.tsx +160 -0
- package/src/__tests__/useGroupedData.test.ts +165 -0
- package/src/components/BulkActionBar.tsx +66 -0
- package/src/components/RowActionMenu.tsx +91 -0
- package/src/index.tsx +46 -2
- package/src/useCellClipboard.ts +136 -0
- package/src/useColumnSummary.ts +128 -0
- package/src/useGradientColor.ts +103 -0
- package/src/useGroupReorder.ts +123 -0
- package/src/useGroupedData.ts +69 -4
- package/tsconfig.json +2 -1
- package/dist/packages/plugin-grid/src/ListColumnExtensions.test.d.ts +0 -0
- package/dist/packages/plugin-grid/src/ListColumnSchema.test.d.ts +0 -1
- package/dist/packages/plugin-grid/src/ObjectGrid.EdgeCases.stories.d.ts +0 -25
- package/dist/packages/plugin-grid/src/ObjectGrid.msw.test.d.ts +0 -0
- package/dist/packages/plugin-grid/src/ObjectGrid.stories.d.ts +0 -33
- package/dist/packages/plugin-grid/src/VirtualGrid.test.d.ts +0 -8
- package/dist/packages/plugin-grid/src/__tests__/InlineEditing.test.d.ts +0 -0
- package/dist/packages/plugin-grid/src/__tests__/VirtualGrid.test.d.ts +0 -0
- package/dist/packages/plugin-grid/src/__tests__/accessibility.test.d.ts +0 -0
- package/dist/packages/plugin-grid/src/__tests__/performance-benchmark.test.d.ts +0 -0
- package/dist/packages/plugin-grid/src/__tests__/view-states.test.d.ts +0 -0
- package/dist/packages/plugin-grid/src/index.d.ts +0 -15
- package/dist/packages/plugin-grid/src/index.test.d.ts +0 -1
- package/src/VirtualGrid.test.tsx +0 -23
- /package/dist/{packages/plugin-grid → plugin-grid}/src/InlineEditing.d.ts +0 -0
- /package/dist/{packages/plugin-grid → plugin-grid}/src/VirtualGrid.d.ts +0 -0
- /package/dist/{packages/plugin-grid → plugin-grid}/src/useRowColor.d.ts +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/** A single stop in a colour gradient. */
|
|
9
|
+
export interface GradientStop {
|
|
10
|
+
/** Position between 0 and 1. */
|
|
11
|
+
position: number;
|
|
12
|
+
/** Tailwind background class applied at this stop (e.g. "bg-green-100"). */
|
|
13
|
+
className: string;
|
|
14
|
+
}
|
|
15
|
+
export interface UseGradientColorOptions {
|
|
16
|
+
/** The numeric field to evaluate on each row. */
|
|
17
|
+
field: string;
|
|
18
|
+
/** Flat data array used to derive min/max when not provided. */
|
|
19
|
+
data: Record<string, any>[];
|
|
20
|
+
/** Optional explicit minimum value. */
|
|
21
|
+
min?: number;
|
|
22
|
+
/** Optional explicit maximum value. */
|
|
23
|
+
max?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Ordered gradient stops (position 0 → 1).
|
|
26
|
+
* When omitted a default green→yellow→red palette is used.
|
|
27
|
+
*/
|
|
28
|
+
stops?: GradientStop[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Hook that returns a row → Tailwind class resolver based on a numeric
|
|
32
|
+
* field's value mapped onto a configurable colour gradient.
|
|
33
|
+
*
|
|
34
|
+
* @param options - gradient configuration
|
|
35
|
+
* @returns `(row) => className | undefined`
|
|
36
|
+
*/
|
|
37
|
+
export declare function useGradientColor(options: UseGradientColorOptions): (row: Record<string, any>) => string | undefined;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
export interface UseGroupReorderOptions {
|
|
9
|
+
/** Initial ordered list of group keys. */
|
|
10
|
+
groupKeys: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface UseGroupReorderResult {
|
|
13
|
+
/** Current ordered list of group keys. */
|
|
14
|
+
groupOrder: string[];
|
|
15
|
+
/** Move a group from one index to another. */
|
|
16
|
+
moveGroup: (fromIndex: number, toIndex: number) => void;
|
|
17
|
+
/** Drag-start handler – stores the dragged group key. */
|
|
18
|
+
onDragStart: (e: React.DragEvent, key: string) => void;
|
|
19
|
+
/** Drag-over handler – must be attached to allow drop. */
|
|
20
|
+
onDragOver: (e: React.DragEvent) => void;
|
|
21
|
+
/** Drop handler – reorders the group under the cursor. */
|
|
22
|
+
onDrop: (e: React.DragEvent, targetKey: string) => void;
|
|
23
|
+
/** Drag-end handler – cleans up drag state. */
|
|
24
|
+
onDragEnd: () => void;
|
|
25
|
+
/** The key currently being dragged (null when idle). */
|
|
26
|
+
draggingKey: string | null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Hook for drag-and-drop reordering of grouped sections.
|
|
30
|
+
*
|
|
31
|
+
* Attach the returned handlers to the group header elements to enable
|
|
32
|
+
* reordering via native HTML drag-and-drop.
|
|
33
|
+
*/
|
|
34
|
+
export declare function useGroupReorder({ groupKeys }: UseGroupReorderOptions): UseGroupReorderResult;
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { GroupingConfig } from '../../types/src';
|
|
2
|
+
/** Supported aggregation function types. */
|
|
3
|
+
export type AggregationType = 'sum' | 'count' | 'avg' | 'min' | 'max';
|
|
4
|
+
/** Describes a single aggregation to compute per group. */
|
|
5
|
+
export interface AggregationConfig {
|
|
6
|
+
/** The field to aggregate. */
|
|
7
|
+
field: string;
|
|
8
|
+
/** The aggregation function. */
|
|
9
|
+
type: AggregationType;
|
|
10
|
+
}
|
|
11
|
+
/** Result of a computed aggregation for a group. */
|
|
12
|
+
export interface AggregationResult {
|
|
13
|
+
/** The field that was aggregated. */
|
|
14
|
+
field: string;
|
|
15
|
+
/** The aggregation function used. */
|
|
16
|
+
type: AggregationType;
|
|
17
|
+
/** The computed value. */
|
|
18
|
+
value: number;
|
|
19
|
+
}
|
|
2
20
|
export interface GroupEntry {
|
|
3
21
|
/** Composite key identifying this group (field values joined by ' / ') */
|
|
4
22
|
key: string;
|
|
@@ -8,6 +26,8 @@ export interface GroupEntry {
|
|
|
8
26
|
rows: any[];
|
|
9
27
|
/** Whether the group section is collapsed */
|
|
10
28
|
collapsed: boolean;
|
|
29
|
+
/** Computed aggregations for this group (empty when no aggregations configured). */
|
|
30
|
+
aggregations: AggregationResult[];
|
|
11
31
|
}
|
|
12
32
|
export interface UseGroupedDataResult {
|
|
13
33
|
/** Grouped entries (empty when grouping is not configured) */
|
|
@@ -24,7 +44,8 @@ export interface UseGroupedDataResult {
|
|
|
24
44
|
* collapsed state. Collapse state is managed internally so the consumer only
|
|
25
45
|
* needs to wire `toggleGroup` to the UI.
|
|
26
46
|
*
|
|
27
|
-
* @param config
|
|
28
|
-
* @param data
|
|
47
|
+
* @param config - GroupingConfig from the grid schema (optional)
|
|
48
|
+
* @param data - flat data rows
|
|
49
|
+
* @param aggregations - optional aggregation definitions to compute per group
|
|
29
50
|
*/
|
|
30
|
-
export declare function useGroupedData(config: GroupingConfig | undefined, data: any[]): UseGroupedDataResult;
|
|
51
|
+
export declare function useGroupedData(config: GroupingConfig | undefined, data: any[], aggregations?: AggregationConfig[]): UseGroupedDataResult;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/plugin-grid",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Grid plugin for Object UI",
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@tanstack/react-virtual": "^3.13.
|
|
19
|
-
"lucide-react": "^0.
|
|
20
|
-
"@object-ui/components": "3.0
|
|
21
|
-
"@object-ui/core": "3.0
|
|
22
|
-
"@object-ui/fields": "3.0
|
|
23
|
-
"@object-ui/mobile": "3.0
|
|
24
|
-
"@object-ui/react": "3.0
|
|
25
|
-
"@object-ui/types": "3.0
|
|
18
|
+
"@tanstack/react-virtual": "^3.13.19",
|
|
19
|
+
"lucide-react": "^0.576.0",
|
|
20
|
+
"@object-ui/components": "3.1.0",
|
|
21
|
+
"@object-ui/core": "3.1.0",
|
|
22
|
+
"@object-ui/fields": "3.1.0",
|
|
23
|
+
"@object-ui/mobile": "3.1.0",
|
|
24
|
+
"@object-ui/react": "3.1.0",
|
|
25
|
+
"@object-ui/types": "3.1.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"typescript": "^5.9.3",
|
|
35
35
|
"vite": "^7.3.1",
|
|
36
36
|
"vite-plugin-dts": "^4.5.4",
|
|
37
|
-
"@object-ui/data-objectstack": "3.0
|
|
37
|
+
"@object-ui/data-objectstack": "3.1.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "vite build",
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
10
|
+
import { cn } from '@object-ui/components';
|
|
11
|
+
import { FunctionSquare, Check, X } from 'lucide-react';
|
|
12
|
+
|
|
13
|
+
export interface FormulaBarProps {
|
|
14
|
+
/** Current cell value displayed in the bar. */
|
|
15
|
+
value: string;
|
|
16
|
+
/** Called when the user edits the value (controlled input). */
|
|
17
|
+
onChange?: (value: string) => void;
|
|
18
|
+
/** Called when the user confirms the edit (Enter or check button). */
|
|
19
|
+
onConfirm?: (value: string) => void;
|
|
20
|
+
/** Called when the user cancels the edit (Escape or X button). */
|
|
21
|
+
onCancel?: () => void;
|
|
22
|
+
/** Label describing the active cell, e.g. "A1" or "name". */
|
|
23
|
+
activeCell?: string;
|
|
24
|
+
/** Whether editing is disabled. */
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
/** Additional class names. */
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Excel-like formula bar that displays and allows editing of the active cell's
|
|
32
|
+
* value. Press Enter (or the ✓ button) to confirm, Escape (or the ✗ button)
|
|
33
|
+
* to cancel.
|
|
34
|
+
*/
|
|
35
|
+
export function FormulaBar({
|
|
36
|
+
value,
|
|
37
|
+
onChange,
|
|
38
|
+
onConfirm,
|
|
39
|
+
onCancel,
|
|
40
|
+
activeCell,
|
|
41
|
+
disabled = false,
|
|
42
|
+
className,
|
|
43
|
+
}: FormulaBarProps) {
|
|
44
|
+
const [editing, setEditing] = useState(false);
|
|
45
|
+
const [editValue, setEditValue] = useState(value);
|
|
46
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
47
|
+
|
|
48
|
+
// Sync external value when not editing.
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!editing) {
|
|
51
|
+
setEditValue(value);
|
|
52
|
+
}
|
|
53
|
+
}, [value, editing]);
|
|
54
|
+
|
|
55
|
+
// Auto-focus on edit start.
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (editing && inputRef.current) {
|
|
58
|
+
inputRef.current.focus();
|
|
59
|
+
inputRef.current.select();
|
|
60
|
+
}
|
|
61
|
+
}, [editing]);
|
|
62
|
+
|
|
63
|
+
const startEditing = useCallback(() => {
|
|
64
|
+
if (disabled) return;
|
|
65
|
+
setEditing(true);
|
|
66
|
+
setEditValue(value);
|
|
67
|
+
}, [disabled, value]);
|
|
68
|
+
|
|
69
|
+
const confirm = useCallback(() => {
|
|
70
|
+
setEditing(false);
|
|
71
|
+
onChange?.(editValue);
|
|
72
|
+
onConfirm?.(editValue);
|
|
73
|
+
}, [editValue, onChange, onConfirm]);
|
|
74
|
+
|
|
75
|
+
const cancel = useCallback(() => {
|
|
76
|
+
setEditing(false);
|
|
77
|
+
setEditValue(value);
|
|
78
|
+
onCancel?.();
|
|
79
|
+
}, [value, onCancel]);
|
|
80
|
+
|
|
81
|
+
const handleKeyDown = useCallback(
|
|
82
|
+
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
83
|
+
if (e.key === 'Enter') {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
confirm();
|
|
86
|
+
} else if (e.key === 'Escape') {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
cancel();
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
[confirm, cancel],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
className={cn(
|
|
97
|
+
'flex items-center gap-2 border-b border-border bg-muted/30 px-3 py-1.5',
|
|
98
|
+
className,
|
|
99
|
+
)}
|
|
100
|
+
>
|
|
101
|
+
{/* f(x) indicator */}
|
|
102
|
+
<FunctionSquare className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
103
|
+
|
|
104
|
+
{/* Active cell label */}
|
|
105
|
+
{activeCell && (
|
|
106
|
+
<span className="min-w-[4rem] shrink-0 rounded bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">
|
|
107
|
+
{activeCell}
|
|
108
|
+
</span>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
{/* Value input */}
|
|
112
|
+
<input
|
|
113
|
+
ref={inputRef}
|
|
114
|
+
type="text"
|
|
115
|
+
value={editing ? editValue : value}
|
|
116
|
+
readOnly={!editing}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
onClick={startEditing}
|
|
119
|
+
onChange={(e) => setEditValue(e.target.value)}
|
|
120
|
+
onKeyDown={handleKeyDown}
|
|
121
|
+
className={cn(
|
|
122
|
+
'flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground',
|
|
123
|
+
editing && 'rounded ring-1 ring-ring px-1',
|
|
124
|
+
disabled && 'cursor-not-allowed opacity-50',
|
|
125
|
+
)}
|
|
126
|
+
/>
|
|
127
|
+
|
|
128
|
+
{/* Confirm / Cancel buttons (visible only while editing) */}
|
|
129
|
+
{editing && (
|
|
130
|
+
<div className="flex items-center gap-1">
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
onClick={confirm}
|
|
134
|
+
className="rounded p-0.5 text-green-600 hover:bg-green-100"
|
|
135
|
+
aria-label="Confirm"
|
|
136
|
+
>
|
|
137
|
+
<Check className="h-4 w-4" />
|
|
138
|
+
</button>
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
onClick={cancel}
|
|
142
|
+
className="rounded p-0.5 text-red-600 hover:bg-red-100"
|
|
143
|
+
aria-label="Cancel"
|
|
144
|
+
>
|
|
145
|
+
<X className="h-4 w-4" />
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
package/src/GroupRow.tsx
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { ChevronRight, ChevronDown } from 'lucide-react';
|
|
11
|
+
import type { AggregationResult } from './useGroupedData';
|
|
12
|
+
|
|
13
|
+
export interface GroupRowProps {
|
|
14
|
+
/** Unique key identifying this group */
|
|
15
|
+
groupKey: string;
|
|
16
|
+
/** Display label for the group (field value or "(empty)") */
|
|
17
|
+
label: string;
|
|
18
|
+
/** Number of rows in this group */
|
|
19
|
+
count: number;
|
|
20
|
+
/** Whether the group is collapsed */
|
|
21
|
+
collapsed: boolean;
|
|
22
|
+
/** Computed aggregation results for this group */
|
|
23
|
+
aggregations?: AggregationResult[];
|
|
24
|
+
/** Callback when the group header is clicked to toggle collapse */
|
|
25
|
+
onToggle: (key: string) => void;
|
|
26
|
+
/** Children to render when not collapsed (the group content) */
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* GroupRow renders a collapsible group header with field value, record count,
|
|
32
|
+
* and optional aggregation summary. Used by ObjectGrid for grouped rendering.
|
|
33
|
+
*/
|
|
34
|
+
export const GroupRow: React.FC<GroupRowProps> = ({
|
|
35
|
+
groupKey,
|
|
36
|
+
label,
|
|
37
|
+
count,
|
|
38
|
+
collapsed,
|
|
39
|
+
aggregations,
|
|
40
|
+
onToggle,
|
|
41
|
+
children,
|
|
42
|
+
}) => {
|
|
43
|
+
return (
|
|
44
|
+
<div className="border rounded-md" data-testid={`group-row-${groupKey}`}>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
className="flex w-full items-center gap-2 px-3 py-2 text-sm font-medium text-left bg-muted/50 hover:bg-muted transition-colors"
|
|
48
|
+
onClick={() => onToggle(groupKey)}
|
|
49
|
+
aria-expanded={!collapsed}
|
|
50
|
+
>
|
|
51
|
+
{collapsed
|
|
52
|
+
? <ChevronRight className="h-4 w-4 shrink-0" />
|
|
53
|
+
: <ChevronDown className="h-4 w-4 shrink-0" />}
|
|
54
|
+
<span className="group-label">{label}</span>
|
|
55
|
+
{aggregations && aggregations.length > 0 && (
|
|
56
|
+
<span className="ml-2 text-xs text-muted-foreground group-aggregations">
|
|
57
|
+
{aggregations.map((agg) => (
|
|
58
|
+
<span key={`${agg.field}-${agg.type}`} className="mr-2">
|
|
59
|
+
{agg.type}: {Number.isInteger(agg.value) ? agg.value : agg.value.toFixed(2)}
|
|
60
|
+
</span>
|
|
61
|
+
))}
|
|
62
|
+
</span>
|
|
63
|
+
)}
|
|
64
|
+
<span className="ml-auto text-xs text-muted-foreground group-count">({count})</span>
|
|
65
|
+
</button>
|
|
66
|
+
{!collapsed && children}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
};
|