@object-ui/plugin-timeline 0.5.0 → 3.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.
@@ -0,0 +1,113 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { SchemaRenderer } from '@object-ui/react';
3
+ import type { BaseSchema } from '@object-ui/types';
4
+
5
+ const meta = {
6
+ title: 'Plugins/ObjectTimeline',
7
+ component: SchemaRenderer,
8
+ parameters: {
9
+ layout: 'padded',
10
+ },
11
+ tags: ['autodocs'],
12
+ argTypes: {
13
+ schema: { table: { disable: true } },
14
+ },
15
+ } satisfies Meta<any>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ const renderStory = (args: any) => <SchemaRenderer schema={args as unknown as BaseSchema} />;
21
+
22
+ export const Default: Story = {
23
+ render: renderStory,
24
+ args: {
25
+ type: 'timeline',
26
+ variant: 'vertical',
27
+ dateFormat: 'short',
28
+ items: [
29
+ {
30
+ time: '2024-01-10',
31
+ title: 'Project Kickoff',
32
+ description: 'Initial planning and team formation',
33
+ variant: 'success',
34
+ icon: '🚀',
35
+ },
36
+ {
37
+ time: '2024-02-15',
38
+ title: 'Alpha Release',
39
+ description: 'Internal alpha build delivered',
40
+ variant: 'info',
41
+ icon: '📦',
42
+ },
43
+ {
44
+ time: '2024-03-20',
45
+ title: 'Beta Launch',
46
+ description: 'Public beta opened to early adopters',
47
+ variant: 'warning',
48
+ icon: '⚡',
49
+ },
50
+ {
51
+ time: '2024-05-01',
52
+ title: 'General Availability',
53
+ description: 'Official public release',
54
+ variant: 'success',
55
+ icon: '🎉',
56
+ },
57
+ ],
58
+ } as any,
59
+ };
60
+
61
+ export const Horizontal: Story = {
62
+ render: renderStory,
63
+ args: {
64
+ type: 'timeline',
65
+ variant: 'horizontal',
66
+ dateFormat: 'short',
67
+ items: [
68
+ { time: '2024-01-01', title: 'Q1', description: 'Foundation phase', variant: 'default' },
69
+ { time: '2024-04-01', title: 'Q2', description: 'Growth phase', variant: 'info' },
70
+ { time: '2024-07-01', title: 'Q3', description: 'Scale phase', variant: 'warning' },
71
+ { time: '2024-10-01', title: 'Q4', description: 'Optimization phase', variant: 'success' },
72
+ ],
73
+ } as any,
74
+ };
75
+
76
+ export const ActivityFeed: Story = {
77
+ render: renderStory,
78
+ args: {
79
+ type: 'timeline',
80
+ variant: 'vertical',
81
+ dateFormat: 'long',
82
+ items: [
83
+ {
84
+ time: '2024-03-15T14:30:00',
85
+ title: 'Deal Updated',
86
+ description: 'Stage changed from Proposal to Negotiation',
87
+ variant: 'info',
88
+ icon: '📝',
89
+ },
90
+ {
91
+ time: '2024-03-14T10:00:00',
92
+ title: 'Meeting Logged',
93
+ description: 'Product demo with stakeholders',
94
+ variant: 'default',
95
+ icon: '📅',
96
+ },
97
+ {
98
+ time: '2024-03-12T16:45:00',
99
+ title: 'Email Sent',
100
+ description: 'Follow-up proposal sent to client',
101
+ variant: 'default',
102
+ icon: '📧',
103
+ },
104
+ {
105
+ time: '2024-03-10T09:00:00',
106
+ title: 'Deal Created',
107
+ description: 'New opportunity worth $150,000',
108
+ variant: 'success',
109
+ icon: '✨',
110
+ },
111
+ ],
112
+ } as any,
113
+ };
@@ -4,9 +4,22 @@ import { describe, it, expect, vi } from 'vitest';
4
4
  import { ObjectTimeline } from './ObjectTimeline';
5
5
  import { DataSource } from '@object-ui/types';
6
6
 
7
- // Mock useDataScope
7
+ // Mock useDataScope and useNavigationOverlay
8
8
  vi.mock('@object-ui/react', () => ({
9
9
  useDataScope: () => undefined,
10
+ useNavigationOverlay: () => ({
11
+ isOverlay: false,
12
+ handleClick: vi.fn(),
13
+ selectedRecord: null,
14
+ isOpen: false,
15
+ close: vi.fn(),
16
+ setIsOpen: vi.fn(),
17
+ mode: 'overlay',
18
+ }),
19
+ }));
20
+
21
+ vi.mock('@object-ui/components', () => ({
22
+ NavigationOverlay: () => null,
10
23
  }));
11
24
 
12
25
  vi.mock('./renderer', () => ({
@@ -47,7 +60,7 @@ describe('ObjectTimeline', () => {
47
60
  });
48
61
 
49
62
  it('fetches data when objectName is provided', async () => {
50
- (mockDataSource.find as any).mockResolvedValue({ value: mockData });
63
+ (mockDataSource.find as any).mockResolvedValue(mockData);
51
64
 
52
65
  const schema: any = {
53
66
  type: 'timeline',
@@ -6,9 +6,11 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
 
9
- import React, { useEffect, useState } from 'react';
9
+ import React, { useEffect, useState, useCallback } from 'react';
10
10
  import type { DataSource, TimelineSchema } from '@object-ui/types';
11
- import { useDataScope } from '@object-ui/react';
11
+ import { useDataScope, useNavigationOverlay } from '@object-ui/react';
12
+ import { NavigationOverlay } from '@object-ui/components';
13
+ import { usePullToRefresh } from '@object-ui/mobile';
12
14
  import { z } from 'zod';
13
15
  import { TimelineRenderer } from './renderer';
14
16
 
@@ -43,17 +45,22 @@ export interface ObjectTimelineProps {
43
45
  };
44
46
  dataSource?: DataSource;
45
47
  className?: string;
48
+ onRowClick?: (record: any) => void;
49
+ onItemClick?: (record: any) => void;
46
50
  }
47
51
 
48
52
  export const ObjectTimeline: React.FC<ObjectTimelineProps> = ({
49
53
  schema,
50
54
  dataSource,
51
55
  className,
56
+ onRowClick,
57
+ onItemClick,
52
58
  ...props
53
59
  }) => {
54
60
  const [fetchedData, setFetchedData] = useState<any[]>([]);
55
61
  const [loading, setLoading] = useState(false);
56
62
  const [error, setError] = useState<Error | null>(null);
63
+ const [refreshKey, setRefreshKey] = useState(0);
57
64
 
58
65
  useEffect(() => {
59
66
  const result = TimelineExtensionSchema.safeParse(schema);
@@ -73,8 +80,8 @@ export const ObjectTimeline: React.FC<ObjectTimelineProps> = ({
73
80
  options: { $top: 100 }
74
81
  });
75
82
  let data = results;
76
- if ((results as any).value) {
77
- data = (results as any).value;
83
+ if ((results as any).records) {
84
+ data = (results as any).records;
78
85
  } else if ((results as any).data) {
79
86
  data = (results as any).data;
80
87
  }
@@ -93,7 +100,7 @@ export const ObjectTimeline: React.FC<ObjectTimelineProps> = ({
93
100
  if (schema.objectName && !boundData && !schema.items && !(props as any).data) {
94
101
  fetchData();
95
102
  }
96
- }, [schema.objectName, dataSource, boundData, schema.items, (props as any).data]);
103
+ }, [schema.objectName, dataSource, boundData, schema.items, (props as any).data, refreshKey]);
97
104
 
98
105
  const rawData = (props as any).data || boundData || fetchedData;
99
106
 
@@ -119,10 +126,30 @@ export const ObjectTimeline: React.FC<ObjectTimelineProps> = ({
119
126
  }));
120
127
  }
121
128
 
129
+ const handleRefresh = useCallback(async () => {
130
+ setRefreshKey(k => k + 1);
131
+ }, []);
132
+
133
+ const { ref: pullRef, isRefreshing, pullDistance } = usePullToRefresh<HTMLDivElement>({
134
+ onRefresh: handleRefresh,
135
+ enabled: !!schema.objectName && !!dataSource,
136
+ });
137
+
138
+ const navigation = useNavigationOverlay({
139
+ navigation: (schema as any).navigation,
140
+ objectName: schema.objectName,
141
+ onRowClick: onRowClick ?? onItemClick,
142
+ });
143
+
122
144
  const effectiveSchema = {
123
145
  ...schema,
124
146
  items: effectiveItems || [],
125
- className: className || schema.className
147
+ className: className || schema.className,
148
+ onItemClick: (item: any) => {
149
+ const record = item._data || item;
150
+ navigation.handleClick(record);
151
+ onItemClick?.(record);
152
+ },
126
153
  };
127
154
 
128
155
  if (error) {
@@ -133,5 +160,33 @@ export const ObjectTimeline: React.FC<ObjectTimelineProps> = ({
133
160
  );
134
161
  }
135
162
 
136
- return <TimelineRenderer schema={effectiveSchema} />;
163
+ return (
164
+ <div ref={pullRef} className="relative overflow-auto h-full">
165
+ {pullDistance > 0 && (
166
+ <div
167
+ className="flex items-center justify-center text-xs text-muted-foreground"
168
+ style={{ height: pullDistance }}
169
+ >
170
+ {isRefreshing ? 'Refreshing…' : 'Pull to refresh'}
171
+ </div>
172
+ )}
173
+ <TimelineRenderer schema={effectiveSchema} />
174
+ {navigation.isOverlay && (
175
+ <NavigationOverlay {...navigation} title="Timeline Item">
176
+ {(record) => (
177
+ <div className="space-y-3">
178
+ {Object.entries(record).map(([key, value]) => (
179
+ <div key={key} className="flex flex-col">
180
+ <span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
181
+ {key.replace(/_/g, ' ')}
182
+ </span>
183
+ <span className="text-sm">{String(value ?? '—')}</span>
184
+ </div>
185
+ ))}
186
+ </div>
187
+ )}
188
+ </NavigationOverlay>
189
+ )}
190
+ </div>
191
+ );
137
192
  }
@@ -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
+ });
package/src/index.tsx CHANGED
@@ -293,7 +293,17 @@ export const ObjectTimelineRenderer: React.FC<any> = ({ schema, ...props }) => {
293
293
  ComponentRegistry.register('object-timeline', ObjectTimelineRenderer, {
294
294
  namespace: 'plugin-timeline',
295
295
  label: 'Object Timeline',
296
- category: 'plugin',
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',
297
307
  inputs: [
298
308
  { name: 'objectName', type: 'string', label: 'Object Name', required: true },
299
309
  { name: 'variant', type: 'enum', enum: ['vertical', 'horizontal', 'gantt'], defaultValue: 'vertical' },
package/src/renderer.tsx CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  TimelineGanttBar,
28
28
  TimelineGanttBarContent,
29
29
  } from './index';
30
- import { renderChildren } from '@object-ui/components';
30
+ import { renderChildren, cn } from '@object-ui/components';
31
31
 
32
32
  // Constants
33
33
  const MILLISECONDS_PER_WEEK = 7 * 24 * 60 * 60 * 1000;
@@ -98,7 +98,7 @@ export const TimelineRenderer = ({ schema, className, ...props }: { schema: Time
98
98
  return (
99
99
  <Timeline className={className} {...props}>
100
100
  {items.map((item: any, index: number) => (
101
- <TimelineItem key={index} className={item.className}>
101
+ <TimelineItem key={index} className={cn(item.className, onItemClick && 'cursor-pointer')} onClick={() => onItemClick?.(item)}>
102
102
  <TimelineMarker variant={item.variant || 'default'}>
103
103
  {item.icon && <span className="text-xs">{item.icon}</span>}
104
104
  </TimelineMarker>
@@ -110,7 +110,7 @@ export const TimelineRenderer = ({ schema, className, ...props }: { schema: Time
110
110
  )}
111
111
  {item.title && <TimelineTitle>{item.title}</TimelineTitle>}
112
112
  {item.description && (
113
- <TimelineDescription>{item.description}</TimelineDescription>
113
+ <TimelineDescription className="line-clamp-2 sm:line-clamp-none">{item.description}</TimelineDescription>
114
114
  )}
115
115
  {item.content && renderChildren(item.content)}
116
116
  </TimelineContent>
@@ -123,9 +123,9 @@ export const TimelineRenderer = ({ schema, className, ...props }: { schema: Time
123
123
  // Horizontal Timeline
124
124
  if (variant === 'horizontal') {
125
125
  return (
126
- <TimelineHorizontal className={className} {...props}>
126
+ <TimelineHorizontal className={cn("overflow-x-auto [-webkit-overflow-scrolling:touch]", className)} {...props}>
127
127
  {items.map((item: any, index: number) => (
128
- <TimelineHorizontalItem key={index} className={item.className}>
128
+ <TimelineHorizontalItem key={index} className={cn(item.className, onItemClick && 'cursor-pointer')} onClick={() => onItemClick?.(item)}>
129
129
  <div className="flex flex-col items-center">
130
130
  <TimelineMarker variant={item.variant || 'default'}>
131
131
  {item.icon && <span className="text-xs">{item.icon}</span>}
@@ -138,7 +138,7 @@ export const TimelineRenderer = ({ schema, className, ...props }: { schema: Time
138
138
  )}
139
139
  {item.title && <TimelineTitle>{item.title}</TimelineTitle>}
140
140
  {item.description && (
141
- <TimelineDescription className="text-center">
141
+ <TimelineDescription className="text-center line-clamp-2 sm:line-clamp-none">
142
142
  {item.description}
143
143
  </TimelineDescription>
144
144
  )}
@@ -208,11 +208,11 @@ export const TimelineRenderer = ({ schema, className, ...props }: { schema: Time
208
208
  const timeHeaders = generateTimeHeaders();
209
209
 
210
210
  return (
211
- <TimelineGantt className={className} {...props}>
211
+ <TimelineGantt className={cn("overflow-x-auto [-webkit-overflow-scrolling:touch]", className)} {...props}>
212
212
  {/* Header */}
213
213
  <TimelineGanttHeader>
214
- <TimelineGanttRowLabels className="flex items-center px-4 py-3">
215
- <span className="font-semibold text-sm">
214
+ <TimelineGanttRowLabels className="flex items-center px-2 sm:px-4 py-2 sm:py-3">
215
+ <span className="font-semibold text-xs sm:text-sm">
216
216
  {schema.rowLabel || 'Items'}
217
217
  </span>
218
218
  </TimelineGanttRowLabels>
@@ -221,7 +221,7 @@ export const TimelineRenderer = ({ schema, className, ...props }: { schema: Time
221
221
  {timeHeaders.map((header, index) => (
222
222
  <div
223
223
  key={index}
224
- className="flex-1 px-2 py-3 border-r text-xs font-medium text-center"
224
+ className="flex-1 px-1 sm:px-2 py-2 sm:py-3 border-r text-xs font-medium text-center"
225
225
  >
226
226
  {header}
227
227
  </div>
@@ -236,7 +236,7 @@ export const TimelineRenderer = ({ schema, className, ...props }: { schema: Time
236
236
  <TimelineGanttRowLabels>
237
237
  {items.map((row: any, rowIndex: number) => (
238
238
  <TimelineGanttRow key={rowIndex}>
239
- <TimelineGanttLabel title={row.label}>
239
+ <TimelineGanttLabel title={row.label} className="truncate">
240
240
  {row.label}
241
241
  </TimelineGanttLabel>
242
242
  </TimelineGanttRow>
package/vite.config.ts CHANGED
@@ -29,6 +29,7 @@ export default defineConfig({
29
29
  '@object-ui/react': resolve(__dirname, '../react/src'),
30
30
  '@object-ui/components': resolve(__dirname, '../components/src'),
31
31
  '@object-ui/fields': resolve(__dirname, '../fields/src'),
32
+ '@object-ui/mobile': resolve(__dirname, '../mobile/src'),
32
33
  '@object-ui/plugin-dashboard': resolve(__dirname, '../plugin-dashboard/src'),
33
34
  '@object-ui/plugin-grid': resolve(__dirname, '../plugin-grid/src'),
34
35
  },
@@ -40,7 +41,7 @@ export default defineConfig({
40
41
  fileName: 'index',
41
42
  },
42
43
  rollupOptions: {
43
- external: ['react', 'react-dom', '@object-ui/components', '@object-ui/core', '@object-ui/react'],
44
+ external: ['react', 'react-dom', '@object-ui/components', '@object-ui/core', '@object-ui/react', '@object-ui/mobile'],
44
45
  output: {
45
46
  globals: {
46
47
  react: 'React',
@@ -48,6 +49,7 @@ export default defineConfig({
48
49
  '@object-ui/components': 'ObjectUIComponents',
49
50
  '@object-ui/core': 'ObjectUICore',
50
51
  '@object-ui/react': 'ObjectUIReact',
52
+ '@object-ui/mobile': 'ObjectUIMobile',
51
53
  },
52
54
  },
53
55
  },