@object-ui/plugin-kanban 3.3.0 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +24 -0
- package/dist/{KanbanEnhanced-TdUe0kQH.js → KanbanEnhanced-Do9ZB1Mh.js} +35 -32
- package/dist/{KanbanImpl-BtlPa7GE.js → KanbanImpl-BdocXM5T.js} +1 -1
- package/dist/{chevron-down-B6UH8BbF.js → chevron-down-C0JUlGjk.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/index.umd.cjs +2 -2
- package/dist/{plus-BTqoaaEC.js → plus-CHsXVJSY.js} +1 -1
- package/package.json +34 -11
- package/.turbo/turbo-build.log +0 -32
- package/src/CardTemplates.tsx +0 -123
- package/src/InlineQuickAdd.tsx +0 -189
- package/src/KanbanEnhanced.tsx +0 -525
- package/src/KanbanImpl.tsx +0 -597
- package/src/ObjectKanban.EdgeCases.stories.tsx +0 -168
- package/src/ObjectKanban.msw.test.tsx +0 -95
- package/src/ObjectKanban.stories.tsx +0 -152
- package/src/ObjectKanban.tsx +0 -276
- package/src/__tests__/KanbanEnhanced.test.tsx +0 -260
- package/src/__tests__/KanbanGrouping.test.tsx +0 -164
- package/src/__tests__/KanbanSwimlanes.test.tsx +0 -194
- package/src/__tests__/ObjectKanbanTitle.test.tsx +0 -93
- package/src/__tests__/SwimlanePersistence.test.tsx +0 -159
- package/src/__tests__/accessibility.test.tsx +0 -296
- package/src/__tests__/dnd-undo-integration.test.tsx +0 -525
- package/src/__tests__/performance-benchmark.test.tsx +0 -306
- package/src/__tests__/phase13-features.test.tsx +0 -387
- package/src/__tests__/view-states.test.tsx +0 -403
- package/src/index.test.ts +0 -112
- package/src/index.tsx +0 -327
- package/src/registration.test.tsx +0 -26
- package/src/types.ts +0 -185
- package/src/useColumnWidths.ts +0 -125
- package/src/useCrossSwimlaneMove.ts +0 -116
- package/src/useQuickAddReorder.ts +0 -107
- package/tsconfig.json +0 -19
- package/vite.config.ts +0 -62
- package/vitest.config.ts +0 -12
- package/vitest.setup.ts +0 -1
package/src/index.tsx
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
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, { Suspense } from 'react';
|
|
10
|
-
import { ComponentRegistry } from '@object-ui/core';
|
|
11
|
-
import { useSchemaContext } from '@object-ui/react';
|
|
12
|
-
import { Skeleton } from '@object-ui/components';
|
|
13
|
-
import { ObjectKanban } from './ObjectKanban';
|
|
14
|
-
|
|
15
|
-
// Export types for external use
|
|
16
|
-
export type { KanbanSchema, KanbanCard, KanbanColumn, CardTemplate, ColumnWidthConfig, InlineFieldDefinition } from './types';
|
|
17
|
-
export { ObjectKanban };
|
|
18
|
-
export type { ObjectKanbanProps } from './ObjectKanban';
|
|
19
|
-
|
|
20
|
-
// Phase 13 L2/L3: New components and hooks
|
|
21
|
-
export { InlineQuickAdd } from './InlineQuickAdd';
|
|
22
|
-
export type { InlineQuickAddProps } from './InlineQuickAdd';
|
|
23
|
-
export { CardTemplates } from './CardTemplates';
|
|
24
|
-
export type { CardTemplatesProps } from './CardTemplates';
|
|
25
|
-
export { useColumnWidths } from './useColumnWidths';
|
|
26
|
-
export type { UseColumnWidthsOptions, UseColumnWidthsReturn } from './useColumnWidths';
|
|
27
|
-
export { useCrossSwimlaneMove } from './useCrossSwimlaneMove';
|
|
28
|
-
export type { Swimlane, CrossSwimlaneMoveEvent, UseCrossSwimlaneOptions, UseCrossSwimlaneMoveReturn } from './useCrossSwimlaneMove';
|
|
29
|
-
export { useQuickAddReorder } from './useQuickAddReorder';
|
|
30
|
-
export type { UseQuickAddReorderOptions, UseQuickAddReorderReturn } from './useQuickAddReorder';
|
|
31
|
-
|
|
32
|
-
// 🚀 Lazy load the implementation files
|
|
33
|
-
const LazyKanban = React.lazy(() => import('./KanbanImpl'));
|
|
34
|
-
const LazyKanbanEnhanced = React.lazy(() => import('./KanbanEnhanced'));
|
|
35
|
-
|
|
36
|
-
export interface KanbanRendererProps {
|
|
37
|
-
schema: {
|
|
38
|
-
type: string;
|
|
39
|
-
id?: string;
|
|
40
|
-
className?: string;
|
|
41
|
-
columns?: Array<any>;
|
|
42
|
-
data?: Array<any>;
|
|
43
|
-
groupBy?: string;
|
|
44
|
-
swimlaneField?: string;
|
|
45
|
-
onCardMove?: (cardId: string, fromColumnId: string, toColumnId: string, newIndex: number) => void;
|
|
46
|
-
onCardClick?: (card: any) => void;
|
|
47
|
-
quickAdd?: boolean;
|
|
48
|
-
onQuickAdd?: (columnId: string, title: string) => void;
|
|
49
|
-
coverImageField?: string;
|
|
50
|
-
conditionalFormatting?: Array<{
|
|
51
|
-
field: string;
|
|
52
|
-
operator: 'equals' | 'not_equals' | 'contains' | 'in';
|
|
53
|
-
value: string | string[];
|
|
54
|
-
backgroundColor?: string;
|
|
55
|
-
borderColor?: string;
|
|
56
|
-
}>;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* KanbanRenderer - The public API for the kanban board component
|
|
62
|
-
* This wrapper handles lazy loading internally using React.Suspense
|
|
63
|
-
*/
|
|
64
|
-
export const KanbanRenderer: React.FC<KanbanRendererProps> = ({ schema }) => {
|
|
65
|
-
// ⚡️ Adapter: Map flat 'data' + 'groupBy' to nested 'cards' structure
|
|
66
|
-
const processedColumns = React.useMemo(() => {
|
|
67
|
-
const { columns = [], data, groupBy, coverImageField } = schema;
|
|
68
|
-
|
|
69
|
-
// Helper to map cover image field onto cards
|
|
70
|
-
const mapCoverImage = (item: any) => {
|
|
71
|
-
if (!coverImageField) return item;
|
|
72
|
-
const imgValue = item[coverImageField];
|
|
73
|
-
if (!imgValue) return item;
|
|
74
|
-
const coverImage = typeof imgValue === 'string' ? imgValue : imgValue?.url;
|
|
75
|
-
return coverImage ? { ...item, coverImage } : item;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// If we have flat data and a grouping key, distribute items into columns
|
|
79
|
-
if (data && groupBy && Array.isArray(data)) {
|
|
80
|
-
// Build label→id mapping so data values (labels like "In Progress")
|
|
81
|
-
// match column IDs (option values like "in_progress")
|
|
82
|
-
const labelToColumnId: Record<string, string> = {};
|
|
83
|
-
columns.forEach((col: any) => {
|
|
84
|
-
if (col.id) labelToColumnId[String(col.id).toLowerCase()] = col.id;
|
|
85
|
-
if (col.title) labelToColumnId[String(col.title).toLowerCase()] = col.id;
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// 1. Group data by key, normalizing via label→id mapping
|
|
89
|
-
const groups = data.reduce((acc, item) => {
|
|
90
|
-
const rawKey = String(item[groupBy] ?? '');
|
|
91
|
-
const key = labelToColumnId[rawKey.toLowerCase()] ?? rawKey;
|
|
92
|
-
if (!acc[key]) acc[key] = [];
|
|
93
|
-
acc[key].push(mapCoverImage(item));
|
|
94
|
-
return acc;
|
|
95
|
-
}, {} as Record<string, any[]>);
|
|
96
|
-
|
|
97
|
-
// 2. Inject into columns
|
|
98
|
-
return columns.map((col: any) => ({
|
|
99
|
-
...col,
|
|
100
|
-
cards: [
|
|
101
|
-
...(col.cards || []).map(mapCoverImage), // Preserve static cards
|
|
102
|
-
...(groups[col.id] || []) // Add dynamic cards
|
|
103
|
-
]
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Default: Return columns as-is, mapping cover images
|
|
108
|
-
return columns.map((col: any) => ({
|
|
109
|
-
...col,
|
|
110
|
-
cards: (col.cards || []).map(mapCoverImage),
|
|
111
|
-
}));
|
|
112
|
-
}, [schema]);
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<Suspense fallback={<Skeleton className="w-full h-[600px]" />}>
|
|
116
|
-
<LazyKanban
|
|
117
|
-
columns={processedColumns}
|
|
118
|
-
onCardMove={schema.onCardMove}
|
|
119
|
-
onCardClick={schema.onCardClick}
|
|
120
|
-
className={schema.className}
|
|
121
|
-
quickAdd={schema.quickAdd}
|
|
122
|
-
onQuickAdd={schema.onQuickAdd}
|
|
123
|
-
coverImageField={schema.coverImageField}
|
|
124
|
-
conditionalFormatting={schema.conditionalFormatting}
|
|
125
|
-
swimlaneField={schema.swimlaneField}
|
|
126
|
-
/>
|
|
127
|
-
</Suspense>
|
|
128
|
-
);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// Register the component with the ComponentRegistry
|
|
132
|
-
ComponentRegistry.register(
|
|
133
|
-
'kanban-ui',
|
|
134
|
-
KanbanRenderer,
|
|
135
|
-
{
|
|
136
|
-
namespace: 'plugin-kanban',
|
|
137
|
-
label: 'Kanban Board',
|
|
138
|
-
icon: 'LayoutDashboard',
|
|
139
|
-
category: 'plugin',
|
|
140
|
-
inputs: [
|
|
141
|
-
{
|
|
142
|
-
name: 'columns',
|
|
143
|
-
type: 'array',
|
|
144
|
-
label: 'Columns',
|
|
145
|
-
description: 'Array of { id, title, cards, limit, className }',
|
|
146
|
-
required: true
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
name: 'onCardMove',
|
|
150
|
-
type: 'code',
|
|
151
|
-
label: 'On Card Move',
|
|
152
|
-
description: 'Callback when a card is moved',
|
|
153
|
-
advanced: true
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
name: 'className',
|
|
157
|
-
type: 'string',
|
|
158
|
-
label: 'CSS Class'
|
|
159
|
-
}
|
|
160
|
-
],
|
|
161
|
-
defaultProps: {
|
|
162
|
-
columns: [
|
|
163
|
-
{
|
|
164
|
-
id: 'todo',
|
|
165
|
-
title: 'To Do',
|
|
166
|
-
cards: [
|
|
167
|
-
{
|
|
168
|
-
id: 'card-1',
|
|
169
|
-
title: 'Task 1',
|
|
170
|
-
description: 'This is the first task',
|
|
171
|
-
badges: [
|
|
172
|
-
{ label: 'High Priority', variant: 'destructive' },
|
|
173
|
-
{ label: 'Feature', variant: 'default' }
|
|
174
|
-
]
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
id: 'card-2',
|
|
178
|
-
title: 'Task 2',
|
|
179
|
-
description: 'This is the second task',
|
|
180
|
-
badges: [
|
|
181
|
-
{ label: 'Bug', variant: 'destructive' }
|
|
182
|
-
]
|
|
183
|
-
}
|
|
184
|
-
]
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
id: 'in-progress',
|
|
188
|
-
title: 'In Progress',
|
|
189
|
-
limit: 3,
|
|
190
|
-
cards: [
|
|
191
|
-
{
|
|
192
|
-
id: 'card-3',
|
|
193
|
-
title: 'Task 3',
|
|
194
|
-
description: 'Currently working on this',
|
|
195
|
-
badges: [
|
|
196
|
-
{ label: 'In Progress', variant: 'default' }
|
|
197
|
-
]
|
|
198
|
-
}
|
|
199
|
-
]
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
id: 'done',
|
|
203
|
-
title: 'Done',
|
|
204
|
-
cards: [
|
|
205
|
-
{
|
|
206
|
-
id: 'card-4',
|
|
207
|
-
title: 'Task 4',
|
|
208
|
-
description: 'This task is completed',
|
|
209
|
-
badges: [
|
|
210
|
-
{ label: 'Completed', variant: 'outline' }
|
|
211
|
-
]
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
id: 'card-5',
|
|
215
|
-
title: 'Task 5',
|
|
216
|
-
description: 'Another completed task',
|
|
217
|
-
badges: [
|
|
218
|
-
{ label: 'Completed', variant: 'outline' }
|
|
219
|
-
]
|
|
220
|
-
}
|
|
221
|
-
]
|
|
222
|
-
}
|
|
223
|
-
],
|
|
224
|
-
className: 'w-full'
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
// Standard Export Protocol - for manual integration
|
|
230
|
-
export const kanbanComponents = {
|
|
231
|
-
'kanban': KanbanRenderer,
|
|
232
|
-
'kanban-enhanced': LazyKanbanEnhanced,
|
|
233
|
-
'object-kanban': ObjectKanban,
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Register enhanced Kanban
|
|
237
|
-
ComponentRegistry.register(
|
|
238
|
-
'kanban-enhanced',
|
|
239
|
-
({ schema }: { schema: any }) => {
|
|
240
|
-
const processedColumns = React.useMemo(() => {
|
|
241
|
-
const { columns = [], data, groupBy } = schema;
|
|
242
|
-
if (data && groupBy && Array.isArray(data)) {
|
|
243
|
-
const groups = data.reduce((acc, item) => {
|
|
244
|
-
const key = item[groupBy];
|
|
245
|
-
if (!acc[key]) acc[key] = [];
|
|
246
|
-
acc[key].push(item);
|
|
247
|
-
return acc;
|
|
248
|
-
}, {} as Record<string, any[]>);
|
|
249
|
-
return columns.map((col: any) => ({
|
|
250
|
-
...col,
|
|
251
|
-
cards: [...(col.cards || []), ...(groups[col.id] || [])]
|
|
252
|
-
}));
|
|
253
|
-
}
|
|
254
|
-
return columns;
|
|
255
|
-
}, [schema]);
|
|
256
|
-
|
|
257
|
-
return (
|
|
258
|
-
<Suspense fallback={<Skeleton className="w-full h-[600px]" />}>
|
|
259
|
-
<LazyKanbanEnhanced
|
|
260
|
-
columns={processedColumns}
|
|
261
|
-
onCardMove={schema.onCardMove}
|
|
262
|
-
onColumnToggle={schema.onColumnToggle}
|
|
263
|
-
enableVirtualScrolling={schema.enableVirtualScrolling}
|
|
264
|
-
virtualScrollThreshold={schema.virtualScrollThreshold}
|
|
265
|
-
className={schema.className}
|
|
266
|
-
quickAdd={schema.quickAdd}
|
|
267
|
-
onQuickAdd={schema.onQuickAdd}
|
|
268
|
-
conditionalFormatting={schema.conditionalFormatting}
|
|
269
|
-
/>
|
|
270
|
-
</Suspense>
|
|
271
|
-
);
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
namespace: 'plugin-kanban',
|
|
275
|
-
label: 'Kanban Board (Enhanced)',
|
|
276
|
-
icon: 'LayoutGrid',
|
|
277
|
-
category: 'plugin',
|
|
278
|
-
inputs: [
|
|
279
|
-
{ name: 'columns', type: 'array', label: 'Columns', required: true },
|
|
280
|
-
{ name: 'enableVirtualScrolling', type: 'boolean', label: 'Virtual Scrolling', defaultValue: false },
|
|
281
|
-
{ name: 'virtualScrollThreshold', type: 'number', label: 'Virtual Scroll Threshold', defaultValue: 50 },
|
|
282
|
-
{ name: 'onCardMove', type: 'code', label: 'On Card Move', advanced: true },
|
|
283
|
-
{ name: 'onColumnToggle', type: 'code', label: 'On Column Toggle', advanced: true },
|
|
284
|
-
{ name: 'className', type: 'string', label: 'CSS Class' }
|
|
285
|
-
],
|
|
286
|
-
defaultProps: {
|
|
287
|
-
columns: [],
|
|
288
|
-
enableVirtualScrolling: false,
|
|
289
|
-
virtualScrollThreshold: 50,
|
|
290
|
-
className: 'w-full'
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
// Register object-kanban for ListView integration
|
|
296
|
-
export const ObjectKanbanRenderer: React.FC<{ schema: any; [key: string]: any }> = ({ schema, ...props }) => {
|
|
297
|
-
const { dataSource } = useSchemaContext() || {};
|
|
298
|
-
return <ObjectKanban schema={schema} dataSource={dataSource} {...props} />;
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
ComponentRegistry.register(
|
|
302
|
-
'object-kanban',
|
|
303
|
-
ObjectKanbanRenderer,
|
|
304
|
-
{
|
|
305
|
-
namespace: 'plugin-kanban',
|
|
306
|
-
label: 'Object Kanban',
|
|
307
|
-
category: 'view',
|
|
308
|
-
inputs: [
|
|
309
|
-
{ name: 'objectName', type: 'string', label: 'Object Name', required: true },
|
|
310
|
-
{ name: 'columns', type: 'array', label: 'Columns' }
|
|
311
|
-
]
|
|
312
|
-
}
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
ComponentRegistry.register(
|
|
316
|
-
'kanban',
|
|
317
|
-
ObjectKanbanRenderer,
|
|
318
|
-
{
|
|
319
|
-
namespace: 'view',
|
|
320
|
-
label: 'Kanban Board',
|
|
321
|
-
category: 'view',
|
|
322
|
-
inputs: [
|
|
323
|
-
{ name: 'objectName', type: 'string', label: 'Object Name', required: true },
|
|
324
|
-
{ name: 'columns', type: 'array', label: 'Columns' }
|
|
325
|
-
]
|
|
326
|
-
}
|
|
327
|
-
);
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { ObjectKanbanRenderer } from './index';
|
|
5
|
-
|
|
6
|
-
// Mock dependencies
|
|
7
|
-
vi.mock('@object-ui/react', () => ({
|
|
8
|
-
useSchemaContext: vi.fn(() => ({ dataSource: { type: 'mock-datasource' } })),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
// Mock the implementation
|
|
12
|
-
vi.mock('./ObjectKanban', () => ({
|
|
13
|
-
ObjectKanban: ({ dataSource }: any) => (
|
|
14
|
-
<div data-testid="kanban-mock">
|
|
15
|
-
{dataSource ? `DataSource: ${dataSource.type}` : 'No DataSource'}
|
|
16
|
-
</div>
|
|
17
|
-
)
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
describe('Plugin Kanban Registration', () => {
|
|
21
|
-
it('renderer passes dataSource from context', () => {
|
|
22
|
-
|
|
23
|
-
render(<ObjectKanbanRenderer schema={{ type: 'object-kanban' }} />);
|
|
24
|
-
expect(screen.getByTestId('kanban-mock')).toHaveTextContent('DataSource: mock-datasource');
|
|
25
|
-
});
|
|
26
|
-
});
|
package/src/types.ts
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
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 type { BaseSchema, GroupingConfig } from '@object-ui/types';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Kanban card interface.
|
|
13
|
-
*/
|
|
14
|
-
export interface KanbanCard {
|
|
15
|
-
id: string;
|
|
16
|
-
title: string;
|
|
17
|
-
description?: string;
|
|
18
|
-
badges?: Array<{ label: string; variant?: "default" | "secondary" | "destructive" | "outline" }>;
|
|
19
|
-
[key: string]: any;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Kanban column interface.
|
|
24
|
-
*/
|
|
25
|
-
export interface KanbanColumn {
|
|
26
|
-
id: string;
|
|
27
|
-
title: string;
|
|
28
|
-
cards: KanbanCard[];
|
|
29
|
-
limit?: number;
|
|
30
|
-
className?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Kanban Board component schema.
|
|
35
|
-
* Renders a drag-and-drop kanban board for task management.
|
|
36
|
-
*/
|
|
37
|
-
export interface KanbanSchema extends BaseSchema {
|
|
38
|
-
type: 'kanban';
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Object name to fetch data from.
|
|
42
|
-
*/
|
|
43
|
-
objectName?: string;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Field to group records by (maps to column IDs).
|
|
47
|
-
*/
|
|
48
|
-
groupBy?: string;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Field for swimlane rows (2D grouping). When set, cards are grouped
|
|
52
|
-
* vertically by `groupBy` (columns) and horizontally by `swimlaneField` (rows).
|
|
53
|
-
*/
|
|
54
|
-
swimlaneField?: string;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Field to use as the card title.
|
|
58
|
-
*/
|
|
59
|
-
cardTitle?: string;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Fields to display on the card.
|
|
63
|
-
*/
|
|
64
|
-
cardFields?: string[];
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Static data or bound data.
|
|
68
|
-
*/
|
|
69
|
-
data?: any[];
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Array of columns to display in the kanban board.
|
|
73
|
-
* Each column contains an array of cards.
|
|
74
|
-
*/
|
|
75
|
-
columns?: KanbanColumn[];
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Callback function when a card is moved between columns or reordered.
|
|
79
|
-
*/
|
|
80
|
-
onCardMove?: (cardId: string, fromColumnId: string, toColumnId: string, newIndex: number) => void;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Optional CSS class name to apply custom styling.
|
|
84
|
-
*/
|
|
85
|
-
className?: string;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Enable Quick Add button at the bottom of each column.
|
|
89
|
-
* When true, a "+" button appears allowing inline card creation.
|
|
90
|
-
* @default false
|
|
91
|
-
*/
|
|
92
|
-
quickAdd?: boolean;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Callback when a new card is created via Quick Add.
|
|
96
|
-
*/
|
|
97
|
-
onQuickAdd?: (columnId: string, title: string) => void;
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Field name to use as cover image on cards.
|
|
101
|
-
* The field value should be a URL string or file object with a `url` property.
|
|
102
|
-
*/
|
|
103
|
-
coverImageField?: string;
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Allow columns to be collapsed/expanded.
|
|
107
|
-
* @default false
|
|
108
|
-
*/
|
|
109
|
-
allowCollapse?: boolean;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Conditional formatting rules for card coloring.
|
|
113
|
-
*/
|
|
114
|
-
conditionalFormatting?: Array<{
|
|
115
|
-
field: string;
|
|
116
|
-
operator: 'equals' | 'not_equals' | 'contains' | 'in';
|
|
117
|
-
value: string | string[];
|
|
118
|
-
backgroundColor?: string;
|
|
119
|
-
borderColor?: string;
|
|
120
|
-
}>;
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Predefined card templates for quick-add.
|
|
124
|
-
* Each template pre-fills the quick-add form with default values.
|
|
125
|
-
*/
|
|
126
|
-
cardTemplates?: CardTemplate[];
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Custom column width configuration.
|
|
130
|
-
* Supports per-column overrides with min/max constraints.
|
|
131
|
-
*/
|
|
132
|
-
columnWidths?: ColumnWidthConfig;
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Grouping configuration from ListView.
|
|
136
|
-
* When set, the first grouping field is used as swimlaneField fallback.
|
|
137
|
-
*/
|
|
138
|
-
grouping?: GroupingConfig;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* A predefined card template with pre-filled field values.
|
|
143
|
-
*/
|
|
144
|
-
export interface CardTemplate {
|
|
145
|
-
/** Unique template identifier */
|
|
146
|
-
id: string;
|
|
147
|
-
/** Human-readable template name */
|
|
148
|
-
name: string;
|
|
149
|
-
/** Optional Lucide icon name */
|
|
150
|
-
icon?: string;
|
|
151
|
-
/** Pre-filled field values */
|
|
152
|
-
values: Record<string, any>;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Configuration for custom column widths.
|
|
157
|
-
*/
|
|
158
|
-
export interface ColumnWidthConfig {
|
|
159
|
-
/** Default column width in pixels */
|
|
160
|
-
defaultWidth?: number;
|
|
161
|
-
/** Minimum column width in pixels */
|
|
162
|
-
minWidth?: number;
|
|
163
|
-
/** Maximum column width in pixels */
|
|
164
|
-
maxWidth?: number;
|
|
165
|
-
/** Per-column width overrides keyed by column ID */
|
|
166
|
-
overrides?: Record<string, number>;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Field definition for inline quick-add forms.
|
|
171
|
-
*/
|
|
172
|
-
export interface InlineFieldDefinition {
|
|
173
|
-
/** Field name (key in the resulting values object) */
|
|
174
|
-
name: string;
|
|
175
|
-
/** Display label */
|
|
176
|
-
label?: string;
|
|
177
|
-
/** Field type */
|
|
178
|
-
type: 'text' | 'number' | 'select';
|
|
179
|
-
/** Placeholder text */
|
|
180
|
-
placeholder?: string;
|
|
181
|
-
/** Default value */
|
|
182
|
-
defaultValue?: any;
|
|
183
|
-
/** Options for select fields */
|
|
184
|
-
options?: Array<{ label: string; value: string }>;
|
|
185
|
-
}
|
package/src/useColumnWidths.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
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 { useState, useCallback, useMemo } from "react"
|
|
10
|
-
import type { KanbanColumn, ColumnWidthConfig } from "./types"
|
|
11
|
-
|
|
12
|
-
const STORAGE_KEY = "objectui:kanban-column-widths"
|
|
13
|
-
|
|
14
|
-
export interface UseColumnWidthsOptions {
|
|
15
|
-
/** Columns on the board */
|
|
16
|
-
columns: KanbanColumn[]
|
|
17
|
-
/** Default width in pixels applied to every column */
|
|
18
|
-
defaultWidth?: number
|
|
19
|
-
/** Minimum allowed column width in pixels */
|
|
20
|
-
minWidth?: number
|
|
21
|
-
/** Maximum allowed column width in pixels */
|
|
22
|
-
maxWidth?: number
|
|
23
|
-
/** Optional persistence key suffix (to scope per-board) */
|
|
24
|
-
storageKey?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface UseColumnWidthsReturn {
|
|
28
|
-
/** Get the current width for a column (in px) */
|
|
29
|
-
getColumnWidth: (columnId: string) => number
|
|
30
|
-
/** Set an override width for a column */
|
|
31
|
-
setColumnWidth: (columnId: string, width: number) => void
|
|
32
|
-
/** Reset all overrides back to default widths */
|
|
33
|
-
resetWidths: () => void
|
|
34
|
-
/** The full config (useful for serialization) */
|
|
35
|
-
config: ColumnWidthConfig
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function loadPersistedWidths(key: string): Record<string, number> {
|
|
39
|
-
try {
|
|
40
|
-
const raw = localStorage.getItem(key)
|
|
41
|
-
if (raw) {
|
|
42
|
-
const parsed = JSON.parse(raw)
|
|
43
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
44
|
-
return parsed as Record<string, number>
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
/* ignore corrupt data */
|
|
49
|
-
}
|
|
50
|
-
return {}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function persistWidths(key: string, overrides: Record<string, number>) {
|
|
54
|
-
try {
|
|
55
|
-
localStorage.setItem(key, JSON.stringify(overrides))
|
|
56
|
-
} catch {
|
|
57
|
-
/* quota exceeded */
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Hook for managing custom per-column widths with localStorage persistence.
|
|
63
|
-
* Supports per-column overrides clamped between minWidth and maxWidth.
|
|
64
|
-
*/
|
|
65
|
-
export function useColumnWidths({
|
|
66
|
-
columns: _columns,
|
|
67
|
-
defaultWidth = 320,
|
|
68
|
-
minWidth = 200,
|
|
69
|
-
maxWidth = 600,
|
|
70
|
-
storageKey,
|
|
71
|
-
}: UseColumnWidthsOptions): UseColumnWidthsReturn {
|
|
72
|
-
const fullKey = storageKey ? `${STORAGE_KEY}:${storageKey}` : STORAGE_KEY
|
|
73
|
-
|
|
74
|
-
const [overrides, setOverrides] = useState<Record<string, number>>(() =>
|
|
75
|
-
loadPersistedWidths(fullKey),
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
const clamp = useCallback(
|
|
79
|
-
(value: number) => Math.max(minWidth, Math.min(maxWidth, value)),
|
|
80
|
-
[minWidth, maxWidth],
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
const getColumnWidth = useCallback(
|
|
84
|
-
(columnId: string): number => {
|
|
85
|
-
const override = overrides[columnId]
|
|
86
|
-
return override != null ? clamp(override) : defaultWidth
|
|
87
|
-
},
|
|
88
|
-
[overrides, defaultWidth, clamp],
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
const setColumnWidth = useCallback(
|
|
92
|
-
(columnId: string, width: number) => {
|
|
93
|
-
const clamped = clamp(width)
|
|
94
|
-
setOverrides(prev => {
|
|
95
|
-
const next = { ...prev, [columnId]: clamped }
|
|
96
|
-
persistWidths(fullKey, next)
|
|
97
|
-
return next
|
|
98
|
-
})
|
|
99
|
-
},
|
|
100
|
-
[clamp, fullKey],
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
const resetWidths = useCallback(() => {
|
|
104
|
-
setOverrides({})
|
|
105
|
-
try {
|
|
106
|
-
localStorage.removeItem(fullKey)
|
|
107
|
-
} catch {
|
|
108
|
-
/* ignore */
|
|
109
|
-
}
|
|
110
|
-
}, [fullKey])
|
|
111
|
-
|
|
112
|
-
const config: ColumnWidthConfig = useMemo(
|
|
113
|
-
() => ({
|
|
114
|
-
defaultWidth,
|
|
115
|
-
minWidth,
|
|
116
|
-
maxWidth,
|
|
117
|
-
overrides: { ...overrides },
|
|
118
|
-
}),
|
|
119
|
-
[defaultWidth, minWidth, maxWidth, overrides],
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
return { getColumnWidth, setColumnWidth, resetWidths, config }
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export default useColumnWidths
|