@object-ui/plugin-timeline 0.3.1 → 2.0.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 +16 -0
- package/CHANGELOG.md +14 -0
- package/dist/index.js +2991 -408
- package/dist/index.umd.cjs +41 -3
- package/dist/src/ObjectTimeline.d.ts +20 -0
- package/dist/src/ObjectTimeline.d.ts.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/renderer.d.ts +6 -8
- package/dist/src/renderer.d.ts.map +1 -1
- package/package.json +11 -8
- package/src/ObjectTimeline.msw.test.tsx +80 -0
- package/src/ObjectTimeline.test.tsx +69 -0
- package/src/ObjectTimeline.tsx +137 -0
- package/src/__tests__/renderer.test.tsx +295 -0
- package/src/index.test.tsx +24 -0
- package/src/index.tsx +34 -0
- package/src/renderer.tsx +7 -4
- package/vite.config.ts +13 -0
- package/vitest.config.ts +13 -0
- package/vitest.setup.ts +1 -0
|
@@ -0,0 +1,137 @@
|
|
|
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, { useEffect, useState } from 'react';
|
|
10
|
+
import type { DataSource, TimelineSchema } from '@object-ui/types';
|
|
11
|
+
import { useDataScope } from '@object-ui/react';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { TimelineRenderer } from './renderer';
|
|
14
|
+
|
|
15
|
+
const TimelineMappingSchema = z.object({
|
|
16
|
+
title: z.string().optional(),
|
|
17
|
+
date: z.string().optional(),
|
|
18
|
+
description: z.string().optional(),
|
|
19
|
+
variant: z.string().optional(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const TimelineExtensionSchema = z.object({
|
|
23
|
+
mapping: TimelineMappingSchema.optional(),
|
|
24
|
+
objectName: z.string().optional(),
|
|
25
|
+
titleField: z.string().optional(),
|
|
26
|
+
dateField: z.string().optional(),
|
|
27
|
+
descriptionField: z.string().optional(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export interface ObjectTimelineProps {
|
|
31
|
+
schema: TimelineSchema & {
|
|
32
|
+
objectName?: string;
|
|
33
|
+
titleField?: string;
|
|
34
|
+
dateField?: string;
|
|
35
|
+
descriptionField?: string;
|
|
36
|
+
// Map data fields to timeline item properties
|
|
37
|
+
mapping?: {
|
|
38
|
+
title?: string;
|
|
39
|
+
date?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
variant?: string;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
dataSource?: DataSource;
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const ObjectTimeline: React.FC<ObjectTimelineProps> = ({
|
|
49
|
+
schema,
|
|
50
|
+
dataSource,
|
|
51
|
+
className,
|
|
52
|
+
...props
|
|
53
|
+
}) => {
|
|
54
|
+
const [fetchedData, setFetchedData] = useState<any[]>([]);
|
|
55
|
+
const [loading, setLoading] = useState(false);
|
|
56
|
+
const [error, setError] = useState<Error | null>(null);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const result = TimelineExtensionSchema.safeParse(schema);
|
|
60
|
+
if (!result.success) {
|
|
61
|
+
console.warn(`[ObjectTimeline] Invalid timeline configuration:`, result.error.format());
|
|
62
|
+
}
|
|
63
|
+
}, [schema]);
|
|
64
|
+
|
|
65
|
+
const boundData = useDataScope(schema.bind);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const fetchData = async () => {
|
|
69
|
+
if (!dataSource || !schema.objectName) return;
|
|
70
|
+
setLoading(true);
|
|
71
|
+
try {
|
|
72
|
+
const results = await dataSource.find(schema.objectName, {
|
|
73
|
+
options: { $top: 100 }
|
|
74
|
+
});
|
|
75
|
+
let data = results;
|
|
76
|
+
if ((results as any).value) {
|
|
77
|
+
data = (results as any).value;
|
|
78
|
+
} else if ((results as any).data) {
|
|
79
|
+
data = (results as any).data;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (Array.isArray(data)) {
|
|
83
|
+
setFetchedData(data);
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error(e);
|
|
87
|
+
setError(e as Error);
|
|
88
|
+
} finally {
|
|
89
|
+
setLoading(false);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (schema.objectName && !boundData && !schema.items && !(props as any).data) {
|
|
94
|
+
fetchData();
|
|
95
|
+
}
|
|
96
|
+
}, [schema.objectName, dataSource, boundData, schema.items, (props as any).data]);
|
|
97
|
+
|
|
98
|
+
const rawData = (props as any).data || boundData || fetchedData;
|
|
99
|
+
|
|
100
|
+
// Transform data to items if we have raw data and no explicit items
|
|
101
|
+
let effectiveItems = schema.items;
|
|
102
|
+
|
|
103
|
+
if (!effectiveItems && rawData && Array.isArray(rawData)) {
|
|
104
|
+
const titleField = schema.mapping?.title || schema.titleField || 'name';
|
|
105
|
+
const dateField = schema.mapping?.date || schema.dateField || 'date';
|
|
106
|
+
const descField = schema.mapping?.description || schema.descriptionField || 'description';
|
|
107
|
+
const variantField = schema.mapping?.variant || 'variant';
|
|
108
|
+
|
|
109
|
+
effectiveItems = rawData.map(item => ({
|
|
110
|
+
title: item[titleField],
|
|
111
|
+
// Support both 'time' (vertical) and 'startDate' (gantt)
|
|
112
|
+
time: item[dateField],
|
|
113
|
+
startDate: item[dateField],
|
|
114
|
+
endDate: item[dateField], // Default single point
|
|
115
|
+
description: item[descField],
|
|
116
|
+
variant: item[variantField] || 'default',
|
|
117
|
+
// Pass original item for click handlers
|
|
118
|
+
_data: item
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const effectiveSchema = {
|
|
123
|
+
...schema,
|
|
124
|
+
items: effectiveItems || [],
|
|
125
|
+
className: className || schema.className
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (error) {
|
|
129
|
+
return (
|
|
130
|
+
<div className="p-4 text-red-500">
|
|
131
|
+
Error loading timeline: {error.message}
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return <TimelineRenderer schema={effectiveSchema} />;
|
|
137
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
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 { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
11
|
+
import { render, screen } from '@testing-library/react';
|
|
12
|
+
import '@testing-library/jest-dom';
|
|
13
|
+
import { ComponentRegistry } from '@object-ui/core';
|
|
14
|
+
|
|
15
|
+
// Import renderer to trigger registration
|
|
16
|
+
import '../renderer';
|
|
17
|
+
|
|
18
|
+
// Mock renderChildren used by the renderer, preserving cn and other exports
|
|
19
|
+
vi.mock('@object-ui/components', async (importOriginal) => {
|
|
20
|
+
const actual = await importOriginal<typeof import('@object-ui/components')>();
|
|
21
|
+
return {
|
|
22
|
+
...actual,
|
|
23
|
+
renderChildren: vi.fn(() => null),
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('Timeline Renderer (ComponentRegistry)', () => {
|
|
28
|
+
let TimelineComponent: any;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
TimelineComponent = ComponentRegistry.get('timeline');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('is registered in ComponentRegistry as "timeline"', () => {
|
|
35
|
+
expect(TimelineComponent).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// --- Vertical variant ---
|
|
39
|
+
|
|
40
|
+
it('renders in vertical variant by default', () => {
|
|
41
|
+
const schema: any = {
|
|
42
|
+
type: 'timeline',
|
|
43
|
+
items: [
|
|
44
|
+
{ time: '2024-01-15', title: 'First Event', description: 'Description one' },
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const { container } = render(<TimelineComponent schema={schema} />);
|
|
49
|
+
// Vertical uses an <ol> element
|
|
50
|
+
expect(container.querySelector('ol')).toBeInTheDocument();
|
|
51
|
+
expect(screen.getByText('First Event')).toBeInTheDocument();
|
|
52
|
+
expect(screen.getByText('Description one')).toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders items with titles and descriptions in vertical variant', () => {
|
|
56
|
+
const schema: any = {
|
|
57
|
+
type: 'timeline',
|
|
58
|
+
variant: 'vertical',
|
|
59
|
+
items: [
|
|
60
|
+
{ time: '2024-01-15', title: 'Alpha Release', description: 'Initial alpha' },
|
|
61
|
+
{ time: '2024-02-01', title: 'Beta Release', description: 'Public beta' },
|
|
62
|
+
{ time: '2024-03-10', title: 'GA Launch', description: 'General availability' },
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
render(<TimelineComponent schema={schema} />);
|
|
67
|
+
expect(screen.getByText('Alpha Release')).toBeInTheDocument();
|
|
68
|
+
expect(screen.getByText('Initial alpha')).toBeInTheDocument();
|
|
69
|
+
expect(screen.getByText('Beta Release')).toBeInTheDocument();
|
|
70
|
+
expect(screen.getByText('Public beta')).toBeInTheDocument();
|
|
71
|
+
expect(screen.getByText('GA Launch')).toBeInTheDocument();
|
|
72
|
+
expect(screen.getByText('General availability')).toBeInTheDocument();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('handles empty items array in vertical variant', () => {
|
|
76
|
+
const schema: any = {
|
|
77
|
+
type: 'timeline',
|
|
78
|
+
variant: 'vertical',
|
|
79
|
+
items: [],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const { container } = render(<TimelineComponent schema={schema} />);
|
|
83
|
+
// Should render the <ol> wrapper but no <li> items
|
|
84
|
+
expect(container.querySelector('ol')).toBeInTheDocument();
|
|
85
|
+
expect(container.querySelectorAll('li')).toHaveLength(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('formats dates correctly with short format', () => {
|
|
89
|
+
const schema: any = {
|
|
90
|
+
type: 'timeline',
|
|
91
|
+
variant: 'vertical',
|
|
92
|
+
dateFormat: 'short',
|
|
93
|
+
items: [
|
|
94
|
+
{ time: '2024-06-15', title: 'Event' },
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
render(<TimelineComponent schema={schema} />);
|
|
99
|
+
// short format uses toLocaleDateString()
|
|
100
|
+
const expected = new Date('2024-06-15').toLocaleDateString();
|
|
101
|
+
expect(screen.getByText(expected)).toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('formats dates correctly with long format', () => {
|
|
105
|
+
const schema: any = {
|
|
106
|
+
type: 'timeline',
|
|
107
|
+
variant: 'vertical',
|
|
108
|
+
dateFormat: 'long',
|
|
109
|
+
items: [
|
|
110
|
+
{ time: '2024-06-15', title: 'Event' },
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
render(<TimelineComponent schema={schema} />);
|
|
115
|
+
const expected = new Date('2024-06-15').toLocaleDateString('en-US', {
|
|
116
|
+
year: 'numeric',
|
|
117
|
+
month: 'long',
|
|
118
|
+
day: 'numeric',
|
|
119
|
+
});
|
|
120
|
+
expect(screen.getByText(expected)).toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('formats dates correctly with iso (default) format', () => {
|
|
124
|
+
const schema: any = {
|
|
125
|
+
type: 'timeline',
|
|
126
|
+
variant: 'vertical',
|
|
127
|
+
dateFormat: 'iso',
|
|
128
|
+
items: [
|
|
129
|
+
{ time: '2024-06-15', title: 'Event' },
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
render(<TimelineComponent schema={schema} />);
|
|
134
|
+
expect(screen.getByText('2024-06-15')).toBeInTheDocument();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// --- Horizontal variant ---
|
|
138
|
+
|
|
139
|
+
it('renders in horizontal variant', () => {
|
|
140
|
+
const schema: any = {
|
|
141
|
+
type: 'timeline',
|
|
142
|
+
variant: 'horizontal',
|
|
143
|
+
items: [
|
|
144
|
+
{ time: '2024-01-01', title: 'Q1', description: 'First quarter' },
|
|
145
|
+
{ time: '2024-04-01', title: 'Q2', description: 'Second quarter' },
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
render(<TimelineComponent schema={schema} />);
|
|
150
|
+
expect(screen.getByText('Q1')).toBeInTheDocument();
|
|
151
|
+
expect(screen.getByText('First quarter')).toBeInTheDocument();
|
|
152
|
+
expect(screen.getByText('Q2')).toBeInTheDocument();
|
|
153
|
+
expect(screen.getByText('Second quarter')).toBeInTheDocument();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('handles empty items array in horizontal variant', () => {
|
|
157
|
+
const schema: any = {
|
|
158
|
+
type: 'timeline',
|
|
159
|
+
variant: 'horizontal',
|
|
160
|
+
items: [],
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const { container } = render(<TimelineComponent schema={schema} />);
|
|
164
|
+
// Horizontal wraps in a div, should have no child items
|
|
165
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
166
|
+
expect(screen.queryByRole('heading')).not.toBeInTheDocument();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// --- Gantt variant ---
|
|
170
|
+
|
|
171
|
+
it('renders in gantt variant', () => {
|
|
172
|
+
const schema: any = {
|
|
173
|
+
type: 'timeline',
|
|
174
|
+
variant: 'gantt',
|
|
175
|
+
timeScale: 'month',
|
|
176
|
+
items: [
|
|
177
|
+
{
|
|
178
|
+
label: 'Backend',
|
|
179
|
+
items: [
|
|
180
|
+
{ title: 'API Design', startDate: '2024-01-01', endDate: '2024-02-01' },
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
label: 'Frontend',
|
|
185
|
+
items: [
|
|
186
|
+
{ title: 'UI Build', startDate: '2024-02-01', endDate: '2024-03-01' },
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
render(<TimelineComponent schema={schema} />);
|
|
193
|
+
expect(screen.getByText('Backend')).toBeInTheDocument();
|
|
194
|
+
expect(screen.getByText('Frontend')).toBeInTheDocument();
|
|
195
|
+
expect(screen.getByText('API Design')).toBeInTheDocument();
|
|
196
|
+
expect(screen.getByText('UI Build')).toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('handles rows with no sub-items in gantt variant', () => {
|
|
200
|
+
const schema: any = {
|
|
201
|
+
type: 'timeline',
|
|
202
|
+
variant: 'gantt',
|
|
203
|
+
minDate: '2024-01-01',
|
|
204
|
+
maxDate: '2024-03-01',
|
|
205
|
+
items: [
|
|
206
|
+
{ label: 'Empty Row', items: [] },
|
|
207
|
+
{
|
|
208
|
+
label: 'Has Items',
|
|
209
|
+
items: [
|
|
210
|
+
{ title: 'Task', startDate: '2024-01-15', endDate: '2024-02-15' },
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
render(<TimelineComponent schema={schema} />);
|
|
217
|
+
expect(screen.getByText('Empty Row')).toBeInTheDocument();
|
|
218
|
+
expect(screen.getByText('Has Items')).toBeInTheDocument();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('gantt variant calculates date ranges from items', () => {
|
|
222
|
+
const schema: any = {
|
|
223
|
+
type: 'timeline',
|
|
224
|
+
variant: 'gantt',
|
|
225
|
+
timeScale: 'month',
|
|
226
|
+
items: [
|
|
227
|
+
{
|
|
228
|
+
label: 'Project A',
|
|
229
|
+
items: [
|
|
230
|
+
{ title: 'Task 1', startDate: '2024-01-01', endDate: '2024-01-31' },
|
|
231
|
+
{ title: 'Task 2', startDate: '2024-02-01', endDate: '2024-03-31' },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
render(<TimelineComponent schema={schema} />);
|
|
238
|
+
// Gantt header should contain month labels spanning the range
|
|
239
|
+
expect(screen.getByText('Project A')).toBeInTheDocument();
|
|
240
|
+
expect(screen.getByText('Task 1')).toBeInTheDocument();
|
|
241
|
+
expect(screen.getByText('Task 2')).toBeInTheDocument();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('gantt variant uses custom rowLabel', () => {
|
|
245
|
+
const schema: any = {
|
|
246
|
+
type: 'timeline',
|
|
247
|
+
variant: 'gantt',
|
|
248
|
+
rowLabel: 'Projects',
|
|
249
|
+
minDate: '2024-01-01',
|
|
250
|
+
maxDate: '2024-03-01',
|
|
251
|
+
items: [
|
|
252
|
+
{
|
|
253
|
+
label: 'My Project',
|
|
254
|
+
items: [
|
|
255
|
+
{ title: 'Work', startDate: '2024-01-15', endDate: '2024-02-15' },
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
render(<TimelineComponent schema={schema} />);
|
|
262
|
+
expect(screen.getByText('Projects')).toBeInTheDocument();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('gantt variant defaults rowLabel to "Items"', () => {
|
|
266
|
+
const schema: any = {
|
|
267
|
+
type: 'timeline',
|
|
268
|
+
variant: 'gantt',
|
|
269
|
+
minDate: '2024-01-01',
|
|
270
|
+
maxDate: '2024-03-01',
|
|
271
|
+
items: [
|
|
272
|
+
{
|
|
273
|
+
label: 'Row',
|
|
274
|
+
items: [
|
|
275
|
+
{ title: 'Bar', startDate: '2024-01-10', endDate: '2024-02-10' },
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
render(<TimelineComponent schema={schema} />);
|
|
282
|
+
expect(screen.getByText('Items')).toBeInTheDocument();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('returns null for unknown variant', () => {
|
|
286
|
+
const schema: any = {
|
|
287
|
+
type: 'timeline',
|
|
288
|
+
variant: 'unknown',
|
|
289
|
+
items: [],
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const { container } = render(<TimelineComponent schema={schema} />);
|
|
293
|
+
expect(container.innerHTML).toBe('');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { ObjectTimelineRenderer } from './index';
|
|
5
|
+
|
|
6
|
+
// Mock dependencies
|
|
7
|
+
vi.mock('@object-ui/react', () => ({
|
|
8
|
+
useSchemaContext: vi.fn(() => ({ dataSource: { type: 'mock-datasource' } })),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock('./ObjectTimeline', () => ({
|
|
12
|
+
ObjectTimeline: ({ dataSource }: any) => (
|
|
13
|
+
<div data-testid="timeline-mock">
|
|
14
|
+
{dataSource ? `DataSource: ${dataSource.type}` : 'No DataSource'}
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
describe('Plugin Timeline Registration', () => {
|
|
20
|
+
it('renderer passes dataSource from context', () => {
|
|
21
|
+
render(<ObjectTimelineRenderer schema={{ type: 'object-timeline' }} />);
|
|
22
|
+
expect(screen.getByTestId('timeline-mock')).toHaveTextContent('DataSource: mock-datasource');
|
|
23
|
+
});
|
|
24
|
+
});
|
package/src/index.tsx
CHANGED
|
@@ -275,3 +275,37 @@ export {
|
|
|
275
275
|
|
|
276
276
|
// Export renderer to register the component with ObjectUI
|
|
277
277
|
export * from './renderer';
|
|
278
|
+
|
|
279
|
+
// Export ObjectTimeline
|
|
280
|
+
export { ObjectTimeline } from './ObjectTimeline';
|
|
281
|
+
export type { ObjectTimelineProps } from './ObjectTimeline';
|
|
282
|
+
|
|
283
|
+
import { ComponentRegistry } from '@object-ui/core';
|
|
284
|
+
import { ObjectTimeline } from './ObjectTimeline';
|
|
285
|
+
import { useSchemaContext } from '@object-ui/react';
|
|
286
|
+
|
|
287
|
+
// Register object-timeline component
|
|
288
|
+
export const ObjectTimelineRenderer: React.FC<any> = ({ schema, ...props }) => {
|
|
289
|
+
const { dataSource } = useSchemaContext();
|
|
290
|
+
return <ObjectTimeline schema={schema} dataSource={dataSource} {...props} />;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
ComponentRegistry.register('object-timeline', ObjectTimelineRenderer, {
|
|
294
|
+
namespace: 'plugin-timeline',
|
|
295
|
+
label: 'Object Timeline',
|
|
296
|
+
category: 'view',
|
|
297
|
+
inputs: [
|
|
298
|
+
{ name: 'objectName', type: 'string', label: 'Object Name', required: true },
|
|
299
|
+
{ name: 'variant', type: 'enum', enum: ['vertical', 'horizontal', 'gantt'], defaultValue: 'vertical' },
|
|
300
|
+
]
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
ComponentRegistry.register('timeline', ObjectTimelineRenderer, {
|
|
304
|
+
namespace: 'view',
|
|
305
|
+
label: 'Timeline View',
|
|
306
|
+
category: 'view',
|
|
307
|
+
inputs: [
|
|
308
|
+
{ name: 'objectName', type: 'string', label: 'Object Name', required: true },
|
|
309
|
+
{ name: 'variant', type: 'enum', enum: ['vertical', 'horizontal', 'gantt'], defaultValue: 'vertical' },
|
|
310
|
+
]
|
|
311
|
+
});
|
package/src/renderer.tsx
CHANGED
|
@@ -85,9 +85,7 @@ function formatDate(dateString: string, format?: string): string {
|
|
|
85
85
|
return date.toISOString().split('T')[0];
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
'timeline',
|
|
90
|
-
({ schema, className, ...props }: { schema: TimelineSchema; className?: string; [key: string]: any }) => {
|
|
88
|
+
export const TimelineRenderer = ({ schema, className, ...props }: { schema: TimelineSchema; className?: string; [key: string]: any }) => {
|
|
91
89
|
const {
|
|
92
90
|
variant = 'vertical',
|
|
93
91
|
items = [],
|
|
@@ -280,8 +278,13 @@ ComponentRegistry.register(
|
|
|
280
278
|
}
|
|
281
279
|
|
|
282
280
|
return null;
|
|
283
|
-
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
ComponentRegistry.register(
|
|
284
|
+
'timeline',
|
|
285
|
+
TimelineRenderer,
|
|
284
286
|
{
|
|
287
|
+
namespace: 'plugin-timeline',
|
|
285
288
|
label: 'Timeline',
|
|
286
289
|
category: 'data-display',
|
|
287
290
|
inputs: [
|
package/vite.config.ts
CHANGED
|
@@ -24,6 +24,13 @@ export default defineConfig({
|
|
|
24
24
|
resolve: {
|
|
25
25
|
alias: {
|
|
26
26
|
'@': resolve(__dirname, './src'),
|
|
27
|
+
'@object-ui/core': resolve(__dirname, '../core/src'),
|
|
28
|
+
'@object-ui/types': resolve(__dirname, '../types/src'),
|
|
29
|
+
'@object-ui/react': resolve(__dirname, '../react/src'),
|
|
30
|
+
'@object-ui/components': resolve(__dirname, '../components/src'),
|
|
31
|
+
'@object-ui/fields': resolve(__dirname, '../fields/src'),
|
|
32
|
+
'@object-ui/plugin-dashboard': resolve(__dirname, '../plugin-dashboard/src'),
|
|
33
|
+
'@object-ui/plugin-grid': resolve(__dirname, '../plugin-grid/src'),
|
|
27
34
|
},
|
|
28
35
|
},
|
|
29
36
|
build: {
|
|
@@ -45,4 +52,10 @@ export default defineConfig({
|
|
|
45
52
|
},
|
|
46
53
|
},
|
|
47
54
|
},
|
|
55
|
+
test: {
|
|
56
|
+
setupFiles: ['../../vitest.setup.tsx'],
|
|
57
|
+
passWithNoTests: true,
|
|
58
|
+
globals: true,
|
|
59
|
+
environment: 'jsdom',
|
|
60
|
+
},
|
|
48
61
|
});
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="vitest" />
|
|
2
|
+
import { defineConfig } from 'vite';
|
|
3
|
+
import react from '@vitejs/plugin-react';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [react()],
|
|
8
|
+
test: {
|
|
9
|
+
environment: 'happy-dom',
|
|
10
|
+
globals: true,
|
|
11
|
+
setupFiles: ['./vitest.setup.ts'],
|
|
12
|
+
},
|
|
13
|
+
});
|
package/vitest.setup.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|