بنقرة واحدة
pimcore-studio-ui-tables-grids
Using Grid component and TanStack Table in Pimcore Studio UI for data tables and grids
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Using Grid component and TanStack Table in Pimcore Studio UI for data tables and grids
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
| name | pimcore-studio-ui-tables-grids |
| description | Using Grid component and TanStack Table in Pimcore Studio UI for data tables and grids |
| metadata | {"audience":"pimcore-developers","focus":"ui-components-tables"} |
How to use tables and grids in Pimcore Studio UI:
createColumnHelperUse this when:
BEFORE writing any import, read CRITICAL-IMPORT-PATHS.md.
All examples below use bundle imports (@pimcore/studio-ui-bundle/*). For core development (@sdk/*, @Pimcore/*), see the referenced file.
The Grid component is Pimcore's wrapper around TanStack Table with Studio UI styling and features.
import { Grid } from '@pimcore/studio-ui-bundle/components'
import { createColumnHelper } from '@tanstack/react-table'
import { useTranslation } from 'react-i18next'
interface DataRow {
id: number
name: string
type: string
modified: string
}
export const MyDataGrid = () => {
const { t } = useTranslation()
const columnHelper = createColumnHelper<DataRow>()
const columns = [
columnHelper.accessor('name', {
header: t('columns.name'),
size: 200
}),
columnHelper.accessor('type', {
header: t('columns.type'),
size: 150
}),
columnHelper.accessor('modified', {
header: t('columns.modified'),
size: 180
})
]
const data: DataRow[] = [
{ id: 1, name: 'Item 1', type: 'Type A', modified: '2024-01-15' },
{ id: 2, name: 'Item 2', type: 'Type B', modified: '2024-01-16' }
]
return (
<Grid
columns={ columns }
data={ data }
/>
)
}
interface GridProps {
columns: ColumnDef<any>[] // Column definitions (required)
data: any[] // Row data (required)
// Selection
enableRowSelection?: boolean // Enable single row selection
enableMultipleRowSelection?: boolean // Enable multiple row selection
selectedRows?: Record<string, boolean> // Controlled selection state
onRowSelectionChange?: (selection: Record<string, boolean>) => void
// Sorting
enableSorting?: boolean // Enable column sorting
manualSorting?: boolean // Control sorting externally
sorting?: SortingState // Controlled sorting state
onSortingChange?: (sorting: SortingState) => void
// Loading & Empty States
isLoading?: boolean // Show loading skeleton
// Appearance
size?: 'small' | 'normal' | 'large' // Row size
hideColumnHeaders?: boolean // Hide header row
autoWidth?: boolean // Auto-size table width
// Advanced
enableRowDrag?: boolean // Enable row drag-and-drop
enableRowVirtualizer?: boolean // Virtual scrolling for rows
enableColumnVirtualizer?: boolean // Virtual scrolling for columns
}
Use createColumnHelper to define type-safe columns.
import { createColumnHelper } from '@tanstack/react-table'
interface MyData {
id: number
name: string
status: string
count: number
}
const columnHelper = createColumnHelper<MyData>()
const columns = [
// Accessor column - directly access property
columnHelper.accessor('name', {
header: t('columns.name'),
size: 200
}),
// Accessor column with custom cell
columnHelper.accessor('status', {
header: t('columns.status'),
size: 120,
cell: (info) => {
const status = info.getValue()
return <Badge status={ status }>{status}</Badge>
}
}),
// Display column - computed value
columnHelper.display({
id: 'actions',
header: t('columns.actions'),
size: 100,
cell: (info) => (
<IconButton
icon={ { value: 'edit' } }
onClick={ () => handleEdit(info.row.original.id) }
tooltip={ { title: t('edit') } }
/>
)
})
]
{
// Identification
id?: string // Unique column ID
header: string | (() => JSX.Element) // Header content
// Sizing
size?: number // Default width in pixels
minSize?: number // Minimum width
maxSize?: number // Maximum width
// Cell Rendering
cell?: (info: CellContext) => JSX.Element // Custom cell renderer
// Sorting
enableSorting?: boolean // Enable sorting for this column
sortingFn?: string | SortingFn // Custom sort function
// Metadata
meta?: ColumnMetaType // Custom metadata (Pimcore-specific)
}
columnHelper.accessor('propertyName', {
header: t('header'),
size: 200
})
columnHelper.accessor((row) => row.firstName + ' ' + row.lastName, {
id: 'fullName',
header: t('full-name'),
size: 250
})
columnHelper.display({
id: 'actions',
header: t('actions'),
cell: (info) => (
<ButtonGroup items={ [
<IconButton
icon={ { value: 'edit' } }
key="edit"
onClick={ () => handleEdit(info.row.original.id) }
tooltip={ { title: t('edit') } }
/>,
<IconButton
icon={ { value: 'delete' } }
key="delete"
onClick={ () => handleDelete(info.row.original.id) }
tooltip={ { title: t('delete') } }
/>
] }/>
)
})
Enable row selection for interactive grids.
const [selectedRows, setSelectedRows] = useState<Record<string, boolean>>({})
return (
<Grid
columns={ columns }
data={ data }
enableRowSelection
selectedRows={ selectedRows }
onRowSelectionChange={ setSelectedRows }
/>
)
const [selectedRows, setSelectedRows] = useState<Record<string, boolean>>({})
return (
<Grid
columns={ columns }
data={ data }
enableMultipleRowSelection // Adds checkboxes
selectedRows={ selectedRows }
onRowSelectionChange={ setSelectedRows }
/>
)
const [selectedRows, setSelectedRows] = useState<Record<string, boolean>>({})
const getSelectedData = () => {
return data.filter((row, index) => selectedRows[index])
}
const handleBulkAction = () => {
const selected = getSelectedData()
console.log('Selected items:', selected)
}
Enable column sorting functionality.
const [sorting, setSorting] = useState<SortingState>([])
return (
<Grid
columns={ columns }
data={ data }
enableSorting
sorting={ sorting }
onSortingChange={ setSorting }
/>
)
const [sorting, setSorting] = useState<SortingState>([])
// Fetch data when sorting changes
useEffect(() => {
if (sorting.length > 0) {
const { id, desc } = sorting[0]
fetchData({
sortBy: id,
sortOrder: desc ? 'DESC' : 'ASC'
})
}
}, [sorting])
return (
<Grid
columns={ columns }
data={ data }
enableSorting
manualSorting // Don't sort locally
sorting={ sorting }
onSortingChange={ setSorting }
/>
)
Show skeleton loading while data is fetching.
const { data, isLoading } = useQuery()
return (
<Grid
columns={ columns }
data={ data ?? [] }
isLoading={ isLoading } // Shows skeleton rows
/>
)
import { Grid, IconButton } from '@pimcore/studio-ui-bundle/components'
import { createColumnHelper } from '@tanstack/react-table'
import { useTranslation } from 'react-i18next'
interface Metadata {
id: number
name: string
type: string
value: string
language?: string
}
export const MetadataTable = ({ metadata }: { metadata: Metadata[] }) => {
const { t } = useTranslation()
const columnHelper = createColumnHelper<Metadata>()
const columns = [
columnHelper.accessor('name', {
header: t('metadata.columns.name'),
size: 200
}),
columnHelper.accessor('type', {
header: t('metadata.columns.type'),
size: 150
}),
columnHelper.accessor('language', {
header: t('metadata.columns.language'),
size: 100,
cell: (info) => info.getValue() || '-'
}),
columnHelper.accessor('value', {
header: t('metadata.columns.value'),
size: 300
}),
columnHelper.display({
id: 'actions',
header: t('metadata.columns.actions'),
size: 100,
cell: (info) => (
<IconButton
icon={ { value: 'edit' } }
onClick={ () => handleEdit(info.row.original.id) }
tooltip={ { title: t('edit') } }
/>
)
})
]
return (
<Grid
columns={ columns }
data={ metadata }
/>
)
}
import { Grid, Button, IconButton } from '@pimcore/studio-ui-bundle/components'
import { createColumnHelper } from '@tanstack/react-table'
import { useTranslation } from 'react-i18next'
interface Item {
id: number
name: string
status: 'active' | 'inactive'
modified: string
}
export const SelectableItemList = () => {
const { t } = useTranslation()
const [selectedRows, setSelectedRows] = useState<Record<string, boolean>>({})
const columnHelper = createColumnHelper<Item>()
const { data, isLoading } = useGetItemsQuery()
const columns = [
columnHelper.accessor('name', {
header: t('columns.name'),
size: 250
}),
columnHelper.accessor('status', {
header: t('columns.status'),
size: 120,
cell: (info) => {
const status = info.getValue()
return (
<Badge color={ status === 'active' ? 'green' : 'gray' }>
{t(`status.${status}`)}
</Badge>
)
}
}),
columnHelper.accessor('modified', {
header: t('columns.modified'),
size: 180
}),
columnHelper.display({
id: 'actions',
header: t('columns.actions'),
size: 100,
cell: (info) => (
<IconButton
icon={ { value: 'edit' } }
onClick={ () => handleEdit(info.row.original.id) }
tooltip={ { title: t('edit') } }
/>
)
})
]
const selectedCount = Object.values(selectedRows).filter(Boolean).length
const handleBulkDelete = () => {
const selectedItems = data?.filter((_, index) => selectedRows[index])
// Handle bulk delete
}
return (
<div>
{selectedCount > 0 && (
<div style={ { marginBottom: 16 } }>
<span>{t('items-selected', { count: selectedCount })}</span>
<Button
color="danger"
onClick={ handleBulkDelete }
style={ { marginLeft: 16 } }
>
{t('delete-selected')}
</Button>
</div>
)}
<Grid
columns={ columns }
data={ data ?? [] }
enableMultipleRowSelection
isLoading={ isLoading }
selectedRows={ selectedRows }
onRowSelectionChange={ setSelectedRows }
/>
</div>
)
}
const columnHelper = createColumnHelper<DataType>()
const columns = [
columnHelper.accessor('field1', {
header: t('field1'),
size: 200
}),
columnHelper.accessor('field2', {
header: t('field2'),
size: 150
})
]
return <Grid columns={ columns } data={ data } />
const columns = [
// ... data columns
columnHelper.display({
id: 'actions',
header: t('actions'),
size: 80,
cell: (info) => (
<IconButton
icon={ { value: 'edit' } }
onClick={ () => handleEdit(info.row.original.id) }
tooltip={ { title: t('edit') } }
/>
)
})
]
columnHelper.accessor('status', {
header: t('status'),
size: 120,
cell: (info) => {
const value = info.getValue()
return <Badge status={ value }>{t(`status.${value}`)}</Badge>
}
})
const [sorting, setSorting] = useState<SortingState>([])
return (
<Grid
columns={ columns }
data={ data }
enableSorting
sorting={ sorting }
onSortingChange={ setSorting }
/>
)
// DON'T - Hardcoded headers
columnHelper.accessor('name', {
header: 'Name'
})
// DO - Always translate
columnHelper.accessor('name', {
header: t('columns.name')
})
// DON'T - No ID
columnHelper.display({
cell: () => <button>Action</button>
})
// DO - Always provide ID
columnHelper.display({
id: 'actions',
cell: () => <button>Action</button>
})
// DON'T - No loading state
const { data } = useQuery()
return <Grid columns={ columns } data={ data } />
// DO - Handle loading
const { data, isLoading } = useQuery()
return <Grid columns={ columns } data={ data ?? [] } isLoading={ isLoading } />
// DON'T - No tooltip
<IconButton
icon={ { value: 'edit' } }
onClick={ handleEdit }
/>
// DO - Always include tooltip
<IconButton
icon={ { value: 'edit' } }
onClick={ handleEdit }
tooltip={ { title: t('edit') } }
/>
import { Grid } from '@pimcore/studio-ui-bundle/components'
import { createColumnHelper } from '@tanstack/react-table'
const columnHelper = createColumnHelper<DataType>()
const columns = [
columnHelper.accessor('field', {
header: t('header'),
size: 200
})
]
<Grid columns={ columns } data={ data } />
// Simple accessor
columnHelper.accessor('name', { header: t('name') })
// Custom cell
columnHelper.accessor('status', {
header: t('status'),
cell: (info) => <Badge>{info.getValue()}</Badge>
})
// Actions column
columnHelper.display({
id: 'actions',
cell: (info) => <IconButton icon={ { value: 'edit' } } />
})
Use when creating a new color theme or token set for Pimcore Studio UI (a DynamicTypeThemeAbstract subclass registered in the ThemeRegistry), including choosing an extends chain, filling AntD color tokens, seeding the boot palette, and picking colors that meet WCAG AAA contrast
Building filters in Pimcore Studio UI - the declarative defineFilter framework for filter panels and sidebars (createFiltersStore, FiltersRenderer, useFilterQuery) and custom field-filter dynamic types for per-column listing filters (DynamicTypeFieldFilterAbstract, DynamicTypeFieldFilterRegistry). Use this whenever building, customizing, or extending any kind of filtering, filter panel, filter sidebar, or search/filter UI, or adding a new filter type to a listing or data view - even when the user only says "add filtering", "let users filter by X", "filter this list", or "build a filter sidebar".
Building listings in Pimcore Studio UI using the ListingBuilder decorator pattern - sorting, paging, filtering, inline editing, and custom decorators
UX and UI design conventions for Pimcore Studio - layout, spacing, action labels, writing style, and design principles for consistent extensions
Pimcore Studio migration and review checklist — 34-point verification checklist, anti-patterns to avoid, and reference base classes for validating Studio code completeness
Pimcore Studio PHP code style rules — strict types, class modifiers, final/readonly patterns, formatting (120 chars), named arguments, imports, PHPDoc, @throws documentation, and constructor promotion