-
Notifications
You must be signed in to change notification settings - Fork 2
Add AG Grid plugin with lazy loading and peer dependency architecture #172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fb908d0
4d8c8c3
225af03
858e66d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # @object-ui/plugin-aggrid | ||
|
|
||
| ## 0.3.0 | ||
|
|
||
| ### Minor Changes | ||
|
|
||
| - Initial release of AG Grid plugin | ||
| - Support for AG Grid Community Edition | ||
| - Lazy loading with React.Suspense | ||
| - Multiple theme support (Quartz, Alpine, Balham, Material) | ||
| - Full pagination, sorting, and filtering support | ||
| - TypeScript support with type definitions | ||
| - Automatic component registration | ||
| - Comprehensive test coverage |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| # Plugin AgGrid - Lazy-Loaded AG Grid Data Grid | ||
|
|
||
| A lazy-loaded data grid component for Object UI based on AG Grid Community Edition. | ||
|
|
||
| ## Features | ||
|
|
||
| - **Internal Lazy Loading**: AG Grid libraries are loaded on-demand using `React.lazy()` and `Suspense` | ||
| - **Zero Configuration**: Just import the package and use `type: 'aggrid'` in your schema | ||
| - **Automatic Registration**: Components auto-register with the ComponentRegistry | ||
| - **Skeleton Loading**: Shows a skeleton while AG Grid loads | ||
| - **Full AG Grid Features**: Sorting, filtering, pagination, cell rendering, and more | ||
| - **Multiple Themes**: Support for Quartz, Alpine, Balham, and Material themes | ||
| - **Customizable**: Full access to AG Grid's GridOptions for advanced configuration | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| pnpm add @object-ui/plugin-aggrid ag-grid-community ag-grid-react | ||
| ``` | ||
|
|
||
| Note: `ag-grid-community` and `ag-grid-react` are peer dependencies and must be installed separately. | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Automatic Registration (Side-Effect Import) | ||
|
|
||
| ```typescript | ||
| // In your app entry point (e.g., App.tsx or main.tsx) | ||
| import '@object-ui/plugin-aggrid'; | ||
|
|
||
| // Now you can use aggrid type in your schemas | ||
| const schema = { | ||
| type: 'aggrid', | ||
| rowData: [ | ||
| { make: 'Tesla', model: 'Model Y', price: 64950 }, | ||
| { make: 'Ford', model: 'F-Series', price: 33850 }, | ||
| { make: 'Toyota', model: 'Corolla', price: 29600 } | ||
| ], | ||
| columnDefs: [ | ||
| { field: 'make', headerName: 'Make', sortable: true, filter: true }, | ||
| { field: 'model', headerName: 'Model', sortable: true, filter: true }, | ||
| { field: 'price', headerName: 'Price', sortable: true, filter: 'number' } | ||
| ], | ||
| pagination: true, | ||
| paginationPageSize: 10, | ||
| theme: 'quartz', | ||
| height: 500 | ||
| }; | ||
| ``` | ||
|
|
||
| ### Manual Integration | ||
|
|
||
| ```typescript | ||
| import { aggridComponents } from '@object-ui/plugin-aggrid'; | ||
| import { ComponentRegistry } from '@object-ui/core'; | ||
|
|
||
| // Manually register if needed | ||
| Object.entries(aggridComponents).forEach(([type, component]) => { | ||
| ComponentRegistry.register(type, component); | ||
| }); | ||
| ``` | ||
|
|
||
| ### TypeScript Support | ||
|
|
||
| The plugin exports TypeScript types for full type safety: | ||
|
|
||
| ```typescript | ||
| import type { AgGridSchema, SimpleColumnDef } from '@object-ui/plugin-aggrid'; | ||
|
|
||
| const schema: AgGridSchema = { | ||
| type: 'aggrid', | ||
| rowData: [ | ||
| { make: 'Tesla', model: 'Model Y', price: 64950 } | ||
| ], | ||
| columnDefs: [ | ||
| { field: 'make', headerName: 'Make', sortable: true, filter: true } | ||
| ], | ||
| pagination: true, | ||
| theme: 'quartz' | ||
| }; | ||
| ``` | ||
|
|
||
| ## Schema API | ||
|
|
||
| ```typescript | ||
| { | ||
| type: 'aggrid', | ||
| rowData?: any[], // Grid data (required) | ||
| columnDefs?: ColDef[], // Column definitions (required) | ||
| pagination?: boolean, // Enable pagination (default: false) | ||
| paginationPageSize?: number, // Rows per page (default: 10) | ||
| theme?: 'quartz' | 'alpine' | 'balham' | 'material', // Grid theme (default: 'quartz') | ||
| height?: number | string, // Grid height (default: 500) | ||
| rowSelection?: 'single' | 'multiple', // Row selection mode | ||
| domLayout?: 'normal' | 'autoHeight' | 'print', // Layout mode | ||
| animateRows?: boolean, // Animate row changes (default: true) | ||
| gridOptions?: GridOptions, // Advanced AG Grid options | ||
| className?: string // Tailwind classes | ||
| } | ||
| ``` | ||
|
|
||
| ## Column Definition Examples | ||
|
|
||
| ### Basic Column | ||
|
|
||
| ```typescript | ||
| { | ||
| field: 'name', | ||
| headerName: 'Name', | ||
| sortable: true, | ||
| filter: true | ||
| } | ||
| ``` | ||
|
|
||
| ### Numeric Column with Formatter | ||
|
|
||
| ```typescript | ||
| { | ||
| field: 'price', | ||
| headerName: 'Price', | ||
| sortable: true, | ||
| filter: 'number', | ||
| valueFormatter: (params) => '$' + params.value.toLocaleString() | ||
| } | ||
| ``` | ||
|
|
||
| ### Column with Custom Cell Renderer | ||
|
|
||
| ```typescript | ||
| { | ||
| field: 'status', | ||
| headerName: 'Status', | ||
| cellRenderer: (params) => { | ||
| return params.value === 'active' | ||
| ? '<span class="text-green-500">✓ Active</span>' | ||
| : '<span class="text-red-500">✗ Inactive</span>'; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Themes | ||
|
|
||
| AG Grid comes with four built-in themes: | ||
|
|
||
| - **Quartz** (default): Modern, clean design | ||
| - **Alpine**: Traditional data grid appearance | ||
| - **Balham**: Professional business look | ||
| - **Material**: Google Material Design inspired | ||
|
|
||
| ```typescript | ||
| const schema = { | ||
| type: 'aggrid', | ||
| theme: 'alpine', // or 'quartz', 'balham', 'material' | ||
| // ... other props | ||
| }; | ||
| ``` | ||
|
|
||
| ## Lazy Loading Architecture | ||
|
|
||
| The plugin uses a two-file pattern for optimal code splitting: | ||
|
|
||
| 1. **`AgGridImpl.tsx`**: Contains the actual AG Grid imports (heavy ~200-300 KB) | ||
| 2. **`index.tsx`**: Entry point with `React.lazy()` wrapper (light) | ||
|
|
||
| When bundled, Vite automatically creates separate chunks: | ||
| - `index.js` (~200 bytes) - The entry point | ||
| - `AgGridImpl-xxx.js` (~200-300 KB) - The lazy-loaded implementation | ||
|
|
||
| The AG Grid library is only downloaded when an `aggrid` component is actually rendered, not on initial page load. | ||
|
|
||
| ## Bundle Size Impact | ||
|
|
||
| By using lazy loading, the main application bundle stays lean: | ||
| - Without lazy loading: +200-300 KB on initial load | ||
| - With lazy loading: +0.19 KB on initial load, +200-300 KB only when grid is rendered | ||
|
|
||
| This results in significantly faster initial page loads for applications that don't use data grids on every page. | ||
|
|
||
| ## Advanced Usage | ||
|
|
||
| ### With GridOptions | ||
|
|
||
| ```typescript | ||
| const schema = { | ||
| type: 'aggrid', | ||
| rowData: [...], | ||
| columnDefs: [...], | ||
| gridOptions: { | ||
| suppressCellFocus: true, | ||
| enableCellTextSelection: true, | ||
| enableRangeSelection: true, | ||
| rowMultiSelectWithClick: true, | ||
| // Any AG Grid GridOptions property | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ### Auto Height Layout | ||
|
|
||
| ```typescript | ||
| const schema = { | ||
| type: 'aggrid', | ||
| rowData: [...], | ||
| columnDefs: [...], | ||
| domLayout: 'autoHeight', // Grid adjusts height to fit all rows | ||
| }; | ||
| ``` | ||
|
|
||
| ## Development | ||
|
|
||
| ```bash | ||
| # Build the plugin | ||
| pnpm build | ||
|
|
||
| # Run tests | ||
| pnpm test | ||
|
|
||
| # Type check | ||
| pnpm type-check | ||
|
|
||
| # The package will generate proper ESM and UMD builds with lazy loading preserved | ||
| ``` | ||
|
|
||
| ## License | ||
|
|
||
| MIT | ||
|
|
||
| ## Resources | ||
|
|
||
| - [AG Grid Community Documentation](https://www.ag-grid.com/documentation/) | ||
| - [AG Grid Column Definitions](https://www.ag-grid.com/documentation/javascript/column-definitions/) | ||
| - [AG Grid Grid Options](https://www.ag-grid.com/documentation/javascript/grid-options/) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| { | ||
| "name": "@object-ui/plugin-aggrid", | ||
| "version": "0.3.0", | ||
| "type": "module", | ||
| "license": "MIT", | ||
| "description": "AG Grid data grid plugin for Object UI, powered by AG Grid Community", | ||
| "homepage": "https://www.objectui.org", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/objectstack-ai/objectui.git", | ||
| "directory": "packages/plugin-aggrid" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/objectstack-ai/objectui/issues" | ||
| }, | ||
| "main": "dist/index.umd.cjs", | ||
| "module": "dist/index.js", | ||
| "types": "dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "import": "./dist/index.js", | ||
| "require": "./dist/index.umd.cjs" | ||
| } | ||
| }, | ||
| "scripts": { | ||
| "build": "vite build", | ||
| "test": "vitest run", | ||
| "test:watch": "vitest", | ||
| "type-check": "tsc --noEmit", | ||
| "lint": "eslint ." | ||
| }, | ||
| "dependencies": { | ||
| "@object-ui/components": "workspace:*", | ||
| "@object-ui/core": "workspace:*", | ||
| "@object-ui/react": "workspace:*", | ||
| "@object-ui/types": "workspace:*" | ||
| }, | ||
|
Comment on lines
+33
to
+38
|
||
| "peerDependencies": { | ||
| "react": "^18.0.0 || ^19.0.0", | ||
| "react-dom": "^18.0.0 || ^19.0.0", | ||
| "ag-grid-community": "^32.0.0", | ||
| "ag-grid-react": "^32.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/react": "^19.0.6", | ||
|
||
| "@types/react-dom": "^19.0.3", | ||
|
||
| "@vitejs/plugin-react": "^4.2.1", | ||
| "ag-grid-community": "^32.3.4", | ||
| "ag-grid-react": "^32.3.4", | ||
| "typescript": "^5.9.3", | ||
| "vite": "^7.3.1", | ||
| "vite-plugin-dts": "^4.5.4" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| /** | ||
| * ObjectUI | ||
| * Copyright (c) 2024-present ObjectStack Inc. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
|
|
||
| import React, { useMemo } from 'react'; | ||
| import { AgGridReact } from 'ag-grid-react'; | ||
| import type { ColDef, GridOptions } from 'ag-grid-community'; | ||
|
|
||
| // Import AG Grid CSS - These are required for AG Grid to render properly | ||
| import 'ag-grid-community/styles/ag-grid.css'; | ||
| import 'ag-grid-community/styles/ag-theme-quartz.css'; | ||
| import 'ag-grid-community/styles/ag-theme-alpine.css'; | ||
| import 'ag-grid-community/styles/ag-theme-balham.css'; | ||
| import 'ag-grid-community/styles/ag-theme-material.css'; | ||
|
|
||
| export interface AgGridImplProps { | ||
| rowData?: any[]; | ||
| columnDefs?: ColDef[]; | ||
| gridOptions?: GridOptions; | ||
| pagination?: boolean; | ||
| paginationPageSize?: number; | ||
| domLayout?: 'normal' | 'autoHeight' | 'print'; | ||
| animateRows?: boolean; | ||
| rowSelection?: 'single' | 'multiple'; | ||
| theme?: 'alpine' | 'balham' | 'material' | 'quartz'; | ||
| height?: number | string; | ||
| className?: string; | ||
| } | ||
|
|
||
| /** | ||
| * AgGridImpl - The heavy implementation that imports AG Grid | ||
| * This component is lazy-loaded to avoid including AG Grid in the initial bundle | ||
| */ | ||
| export default function AgGridImpl({ | ||
| rowData = [], | ||
| columnDefs = [], | ||
| gridOptions = {}, | ||
| pagination = false, | ||
| paginationPageSize = 10, | ||
| domLayout = 'normal', | ||
| animateRows = true, | ||
| rowSelection, | ||
| theme = 'quartz', | ||
| height = 500, | ||
| className = '', | ||
| }: AgGridImplProps) { | ||
| // Merge grid options with props | ||
| const mergedGridOptions: GridOptions = useMemo(() => ({ | ||
| ...gridOptions, | ||
| pagination, | ||
| paginationPageSize, | ||
| domLayout, | ||
| animateRows, | ||
| rowSelection, | ||
| // Default options for better UX | ||
| suppressCellFocus: true, | ||
| enableCellTextSelection: true, | ||
| ensureDomOrder: true, | ||
| }), [gridOptions, pagination, paginationPageSize, domLayout, animateRows, rowSelection]); | ||
|
|
||
| // Compute container style | ||
| const containerStyle = useMemo(() => ({ | ||
| height: typeof height === 'number' ? `${height}px` : height, | ||
| width: '100%', | ||
| }), [height]); | ||
|
|
||
| // Determine theme class and build complete class list | ||
| const themeClass = `ag-theme-${theme}`; | ||
| const classList = [ | ||
| themeClass, | ||
| 'rounded-xl', | ||
| 'border', | ||
| 'border-border', | ||
| 'overflow-hidden', | ||
| 'shadow-lg', | ||
| className | ||
| ].filter(Boolean).join(' '); | ||
|
|
||
| return ( | ||
| <div | ||
| className={classList} | ||
| style={containerStyle} | ||
| > | ||
| <AgGridReact | ||
| rowData={rowData} | ||
| columnDefs={columnDefs} | ||
| gridOptions={mergedGridOptions} | ||
| /> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The valueFormatter example uses an arrow function, which cannot be serialized to JSON. This contradicts ObjectUI's JSON-first Schema-Driven UI principle where all component configurations should be representable as pure JSON. While AG Grid supports functions in columnDefs, in a truly schema-driven architecture, formatters should be referenced by string identifiers or use expression syntax instead of inline functions. Consider documenting how valueFormatter should be handled in a JSON schema context, or note this as a limitation that requires programmatic configuration.