@object-ui/plugin-timeline 0.3.0 → 0.5.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,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,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,27 @@ 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: 'plugin',
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
+ });
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
- ComponentRegistry.register(
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
  });
@@ -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
+ });
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';