@object-ui/plugin-kanban 0.3.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.
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { ComponentRegistry } from '@object-ui/core';
3
+
4
+ describe('Plugin Kanban', () => {
5
+ // Import all renderers to register them
6
+ beforeAll(async () => {
7
+ await import('./index');
8
+ });
9
+
10
+ describe('kanban component', () => {
11
+ it('should be registered in ComponentRegistry', () => {
12
+ const kanbanRenderer = ComponentRegistry.get('kanban');
13
+ expect(kanbanRenderer).toBeDefined();
14
+ });
15
+
16
+ it('should have proper metadata', () => {
17
+ const config = ComponentRegistry.getConfig('kanban');
18
+ expect(config).toBeDefined();
19
+ expect(config?.label).toBe('Kanban Board');
20
+ expect(config?.icon).toBe('LayoutDashboard');
21
+ expect(config?.category).toBe('plugin');
22
+ expect(config?.inputs).toBeDefined();
23
+ expect(config?.defaultProps).toBeDefined();
24
+ });
25
+
26
+ it('should have expected inputs', () => {
27
+ const config = ComponentRegistry.getConfig('kanban');
28
+ const inputNames = config?.inputs?.map((input: any) => input.name) || [];
29
+
30
+ expect(inputNames).toContain('columns');
31
+ expect(inputNames).toContain('onCardMove');
32
+ expect(inputNames).toContain('className');
33
+ });
34
+
35
+ it('should have columns as required input', () => {
36
+ const config = ComponentRegistry.getConfig('kanban');
37
+ const columnsInput = config?.inputs?.find((input: any) => input.name === 'columns');
38
+
39
+ expect(columnsInput).toBeDefined();
40
+ expect(columnsInput?.required).toBe(true);
41
+ expect(columnsInput?.type).toBe('array');
42
+ expect(columnsInput?.description).toBeDefined();
43
+ });
44
+
45
+ it('should have onCardMove as code input', () => {
46
+ const config = ComponentRegistry.getConfig('kanban');
47
+ const onCardMoveInput = config?.inputs?.find((input: any) => input.name === 'onCardMove');
48
+
49
+ expect(onCardMoveInput).toBeDefined();
50
+ expect(onCardMoveInput?.type).toBe('code');
51
+ expect(onCardMoveInput?.advanced).toBe(true);
52
+ expect(onCardMoveInput?.description).toBeDefined();
53
+ });
54
+
55
+ it('should have sensible default props', () => {
56
+ const config = ComponentRegistry.getConfig('kanban');
57
+ const defaults = config?.defaultProps;
58
+
59
+ expect(defaults).toBeDefined();
60
+ expect(defaults?.columns).toBeDefined();
61
+ expect(Array.isArray(defaults?.columns)).toBe(true);
62
+ expect(defaults?.columns.length).toBeGreaterThan(0);
63
+ expect(defaults?.className).toBe('w-full');
64
+ });
65
+
66
+ it('should have default columns with proper structure', () => {
67
+ const config = ComponentRegistry.getConfig('kanban');
68
+ const defaults = config?.defaultProps;
69
+ const columns = defaults?.columns || [];
70
+
71
+ // Verify at least 3 columns exist (todo, in-progress, done)
72
+ expect(columns.length).toBeGreaterThanOrEqual(3);
73
+
74
+ // Verify each column has required properties
75
+ columns.forEach((column: any) => {
76
+ expect(column.id).toBeDefined();
77
+ expect(column.title).toBeDefined();
78
+ expect(column.cards).toBeDefined();
79
+ expect(Array.isArray(column.cards)).toBe(true);
80
+ });
81
+
82
+ // Verify at least one column has cards
83
+ const hasCards = columns.some((column: any) => column.cards.length > 0);
84
+ expect(hasCards).toBe(true);
85
+ });
86
+
87
+ it('should have cards with proper structure', () => {
88
+ const config = ComponentRegistry.getConfig('kanban');
89
+ const defaults = config?.defaultProps;
90
+ const columns = defaults?.columns || [];
91
+
92
+ // Find a column with cards
93
+ const columnWithCards = columns.find((column: any) => column.cards.length > 0);
94
+ expect(columnWithCards).toBeDefined();
95
+
96
+ const card = columnWithCards.cards[0];
97
+ expect(card.id).toBeDefined();
98
+ expect(card.title).toBeDefined();
99
+ expect(card.description).toBeDefined();
100
+ expect(card.badges).toBeDefined();
101
+ expect(Array.isArray(card.badges)).toBe(true);
102
+ });
103
+ });
104
+ });
package/src/index.tsx ADDED
@@ -0,0 +1,168 @@
1
+ import React, { Suspense } from 'react';
2
+ import { ComponentRegistry } from '@object-ui/core';
3
+ import { Skeleton } from '@object-ui/components';
4
+
5
+ // Export types for external use
6
+ export type { KanbanSchema, KanbanCard, KanbanColumn } from './types';
7
+
8
+ // 🚀 Lazy load the implementation file
9
+ // This ensures @dnd-kit is only loaded when the component is actually rendered
10
+ const LazyKanban = React.lazy(() => import('./KanbanImpl'));
11
+
12
+ export interface KanbanRendererProps {
13
+ schema: {
14
+ type: string;
15
+ id?: string;
16
+ className?: string;
17
+ columns?: Array<any>;
18
+ data?: Array<any>;
19
+ groupBy?: string;
20
+ onCardMove?: (cardId: string, fromColumnId: string, toColumnId: string, newIndex: number) => void;
21
+ };
22
+ }
23
+
24
+ /**
25
+ * KanbanRenderer - The public API for the kanban board component
26
+ * This wrapper handles lazy loading internally using React.Suspense
27
+ */
28
+ export const KanbanRenderer: React.FC<KanbanRendererProps> = ({ schema }) => {
29
+ // ⚡️ Adapter: Map flat 'data' + 'groupBy' to nested 'cards' structure
30
+ const processedColumns = React.useMemo(() => {
31
+ const { columns = [], data, groupBy } = schema;
32
+
33
+ // If we have flat data and a grouping key, distribute items into columns
34
+ if (data && groupBy && Array.isArray(data)) {
35
+ // 1. Group data by key
36
+ const groups = data.reduce((acc, item) => {
37
+ const key = item[groupBy];
38
+ if (!acc[key]) acc[key] = [];
39
+ acc[key].push(item);
40
+ return acc;
41
+ }, {} as Record<string, any[]>);
42
+
43
+ // 2. Inject into columns
44
+ return columns.map((col: any) => ({
45
+ ...col,
46
+ cards: [
47
+ ...(col.cards || []), // Preserve static cards
48
+ ...(groups[col.id] || []) // Add dynamic cards
49
+ ]
50
+ }));
51
+ }
52
+
53
+ // Default: Return columns as-is (assuming they have 'cards' inside)
54
+ return columns;
55
+ }, [schema]);
56
+
57
+ return (
58
+ <Suspense fallback={<Skeleton className="w-full h-[600px]" />}>
59
+ <LazyKanban
60
+ columns={processedColumns}
61
+ onCardMove={schema.onCardMove}
62
+ className={schema.className}
63
+ />
64
+ </Suspense>
65
+ );
66
+ };
67
+
68
+ // Register the component with the ComponentRegistry
69
+ ComponentRegistry.register(
70
+ 'kanban',
71
+ KanbanRenderer,
72
+ {
73
+ label: 'Kanban Board',
74
+ icon: 'LayoutDashboard',
75
+ category: 'plugin',
76
+ inputs: [
77
+ {
78
+ name: 'columns',
79
+ type: 'array',
80
+ label: 'Columns',
81
+ description: 'Array of { id, title, cards, limit, className }',
82
+ required: true
83
+ },
84
+ {
85
+ name: 'onCardMove',
86
+ type: 'code',
87
+ label: 'On Card Move',
88
+ description: 'Callback when a card is moved',
89
+ advanced: true
90
+ },
91
+ {
92
+ name: 'className',
93
+ type: 'string',
94
+ label: 'CSS Class'
95
+ }
96
+ ],
97
+ defaultProps: {
98
+ columns: [
99
+ {
100
+ id: 'todo',
101
+ title: 'To Do',
102
+ cards: [
103
+ {
104
+ id: 'card-1',
105
+ title: 'Task 1',
106
+ description: 'This is the first task',
107
+ badges: [
108
+ { label: 'High Priority', variant: 'destructive' },
109
+ { label: 'Feature', variant: 'default' }
110
+ ]
111
+ },
112
+ {
113
+ id: 'card-2',
114
+ title: 'Task 2',
115
+ description: 'This is the second task',
116
+ badges: [
117
+ { label: 'Bug', variant: 'destructive' }
118
+ ]
119
+ }
120
+ ]
121
+ },
122
+ {
123
+ id: 'in-progress',
124
+ title: 'In Progress',
125
+ limit: 3,
126
+ cards: [
127
+ {
128
+ id: 'card-3',
129
+ title: 'Task 3',
130
+ description: 'Currently working on this',
131
+ badges: [
132
+ { label: 'In Progress', variant: 'default' }
133
+ ]
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ id: 'done',
139
+ title: 'Done',
140
+ cards: [
141
+ {
142
+ id: 'card-4',
143
+ title: 'Task 4',
144
+ description: 'This task is completed',
145
+ badges: [
146
+ { label: 'Completed', variant: 'outline' }
147
+ ]
148
+ },
149
+ {
150
+ id: 'card-5',
151
+ title: 'Task 5',
152
+ description: 'Another completed task',
153
+ badges: [
154
+ { label: 'Completed', variant: 'outline' }
155
+ ]
156
+ }
157
+ ]
158
+ }
159
+ ],
160
+ className: 'w-full'
161
+ }
162
+ }
163
+ );
164
+
165
+ // Standard Export Protocol - for manual integration
166
+ export const kanbanComponents = {
167
+ 'kanban': KanbanRenderer,
168
+ };
package/src/types.ts ADDED
@@ -0,0 +1,47 @@
1
+ import type { BaseSchema } from '@object-ui/types';
2
+
3
+ /**
4
+ * Kanban card interface.
5
+ */
6
+ export interface KanbanCard {
7
+ id: string;
8
+ title: string;
9
+ description?: string;
10
+ badges?: Array<{ label: string; variant?: "default" | "secondary" | "destructive" | "outline" }>;
11
+ [key: string]: any;
12
+ }
13
+
14
+ /**
15
+ * Kanban column interface.
16
+ */
17
+ export interface KanbanColumn {
18
+ id: string;
19
+ title: string;
20
+ cards: KanbanCard[];
21
+ limit?: number;
22
+ className?: string;
23
+ }
24
+
25
+ /**
26
+ * Kanban Board component schema.
27
+ * Renders a drag-and-drop kanban board for task management.
28
+ */
29
+ export interface KanbanSchema extends BaseSchema {
30
+ type: 'kanban';
31
+
32
+ /**
33
+ * Array of columns to display in the kanban board.
34
+ * Each column contains an array of cards.
35
+ */
36
+ columns?: KanbanColumn[];
37
+
38
+ /**
39
+ * Callback function when a card is moved between columns or reordered.
40
+ */
41
+ onCardMove?: (cardId: string, fromColumnId: string, toColumnId: string, newIndex: number) => void;
42
+
43
+ /**
44
+ * Optional CSS class name to apply custom styling.
45
+ */
46
+ className?: string;
47
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "composite": true,
7
+ "declarationMap": true
8
+ },
9
+ "include": ["src/**/*"],
10
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
11
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import dts from 'vite-plugin-dts';
4
+ import { resolve } from 'path';
5
+
6
+ export default defineConfig({
7
+ plugins: [
8
+ react(),
9
+ dts({
10
+ insertTypesEntry: true,
11
+ include: ['src'],
12
+ }),
13
+ ],
14
+ resolve: {
15
+ alias: {
16
+ '@': resolve(__dirname, './src'),
17
+ },
18
+ },
19
+ build: {
20
+ lib: {
21
+ entry: resolve(__dirname, 'src/index.tsx'),
22
+ name: 'ObjectUIPluginKanban',
23
+ fileName: 'index',
24
+ },
25
+ rollupOptions: {
26
+ external: ['react', 'react-dom', '@object-ui/components', '@object-ui/core', '@object-ui/react'],
27
+ output: {
28
+ globals: {
29
+ react: 'React',
30
+ 'react-dom': 'ReactDOM',
31
+ '@object-ui/components': 'ObjectUIComponents',
32
+ '@object-ui/core': 'ObjectUICore',
33
+ '@object-ui/react': 'ObjectUIReact',
34
+ },
35
+ },
36
+ },
37
+ },
38
+ });