Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat: Implement child render function for DataGrid rows",
"packageName": "@fluentui/react-table",
"email": "[email protected]",
"dependentChangeType": "patch"
}
20 changes: 16 additions & 4 deletions packages/react-components/react-table/etc/react-table.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export const DataGridBody: ForwardRefComponent<DataGridBodyProps>;
export const dataGridBodyClassNames: SlotClassNames<DataGridBodySlots>;

// @public
export type DataGridBodyProps = TableBodyProps;
export type DataGridBodyProps = Omit<TableBodyProps, 'children'> & {
children: RowRenderFunction;
};

// @public (undocumented)
export type DataGridBodySlots = TableBodySlots;
Expand All @@ -66,7 +68,12 @@ export type DataGridCellState = TableCellState;
export const dataGridClassNames: SlotClassNames<DataGridSlots>;

// @public (undocumented)
export type DataGridContextValues = TableContextValues;
export type DataGridContextValue = HeadlessTableState<any>;

// @public (undocumented)
export type DataGridContextValues = TableContextValues & {
dataGrid: DataGridContextValue;
};

// @public
export const DataGridHeader: ForwardRefComponent<DataGridHeaderProps>;
Expand Down Expand Up @@ -99,7 +106,7 @@ export type DataGridHeaderSlots = TableHeaderSlots;
export type DataGridHeaderState = TableHeaderState;

// @public
export type DataGridProps = TableProps;
export type DataGridProps = TableProps & Pick<DataGridContextValue, 'items' | 'columns'>;

// @public
export const DataGridRow: ForwardRefComponent<DataGridRowProps>;
Expand Down Expand Up @@ -135,7 +142,9 @@ export type DataGridSelectionCellState = TableSelectionCellState;
export type DataGridSlots = TableSlots;

// @public
export type DataGridState = TableState;
export type DataGridState = TableState & {
tableState: HeadlessTableState<unknown>;
};

// @public (undocumented)
export interface HeadlessTableState<TItem> extends Pick<UseTableOptions<TItem>, 'items' | 'getRowId'> {
Expand Down Expand Up @@ -196,6 +205,9 @@ export const renderTableSelectionCell_unstable: (state: TableSelectionCellState)
// @public (undocumented)
export type RowId = string | number;

// @public (undocumented)
export type RowRenderFunction<TItem = any> = (row: RowState<TItem>) => React_2.ReactNode;

// @public (undocumented)
export interface RowState<TItem> {
item: TItem;
Expand Down
1 change: 1 addition & 0 deletions packages/react-components/react-table/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@fluentui/react-aria": "^9.2.3",
"@fluentui/react-avatar": "^9.2.4",
"@fluentui/react-checkbox": "^9.0.10",
"@fluentui/react-context-selector": "^9.0.5",
"@fluentui/react-icons": "^2.0.175",
"@fluentui/react-radio": "^9.0.9",
"@fluentui/react-tabster": "^9.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,44 @@ import { render } from '@testing-library/react';
import { DataGrid } from './DataGrid';
import { isConformant } from '../../testing/isConformant';
import { DataGridProps } from './DataGrid.types';
import { ColumnDefinition, RowState } from '../../hooks';
import { DataGridBody } from '../DataGridBody/DataGridBody';
import { DataGridRow } from '../DataGridRow/DataGridRow';
import { DataGridCell } from '../DataGridCell/DataGridCell';

describe('DataGrid', () => {
isConformant<DataGridProps>({
Component: DataGrid,
displayName: 'DataGrid',
});

// TODO add more tests here, and create visual regression tests in /apps/vr-tests
interface Item {
first: string;
second: string;
third: string;
}

const testColumns: ColumnDefinition<Item>[] = [{ columnId: 'first' }, { columnId: 'second' }, { columnId: 'third' }];
const testItems: Item[] = [
{ first: 'first', second: 'second', third: 'third' },
{ first: 'first', second: 'second', third: 'third' },
{ first: 'first', second: 'second', third: 'third' },
];

it('renders a default state', () => {
const result = render(<DataGrid>Default DataGrid</DataGrid>);
const result = render(
<DataGrid items={testItems} columns={testColumns}>
<DataGridBody>
{({ item, rowId }: RowState<Item>) => (
<DataGridRow key={rowId}>
<DataGridCell>{item.first}</DataGridCell>
<DataGridCell>{item.second}</DataGridCell>
<DataGridCell>{item.third}</DataGridCell>
</DataGridRow>
)}
</DataGridBody>
</DataGrid>,
);
expect(result.container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { TableContextValues, TableProps, TableSlots, TableState } from '../Table/Table.types';
import { TableState as HeadlessTableState } from '../../hooks';

export type DataGridSlots = TableSlots;

export type DataGridContextValues = TableContextValues;
export type DataGridContextValues = TableContextValues & {
dataGrid: DataGridContextValue;
};

// Use any here since we can't know the user types
// The user is responsible for narrowing the type downstream
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type DataGridContextValue = HeadlessTableState<any>;

/**
* DataGrid Props
*/
export type DataGridProps = TableProps;
export type DataGridProps = TableProps & Pick<DataGridContextValue, 'items' | 'columns'>;

/**
* State used in rendering DataGrid
*/
export type DataGridState = TableState;
export type DataGridState = TableState & { tableState: HeadlessTableState<unknown> };
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,80 @@ exports[`DataGrid renders a default state 1`] = `
class="fui-DataGrid fui-Table"
role="table"
>
Default DataGrid
<div
class="fui-DataGridBody fui-TableBody"
role="rowgroup"
>
<div
class="fui-DataGridRow fui-TableRow"
role="row"
>
<div
class="fui-DataGridCell fui-TableCell"
role="cell"
>
first
</div>
<div
class="fui-DataGridCell fui-TableCell"
role="cell"
>
second
</div>
<div
class="fui-DataGridCell fui-TableCell"
role="cell"
>
third
</div>
</div>
<div
class="fui-DataGridRow fui-TableRow"
role="row"
>
<div
class="fui-DataGridCell fui-TableCell"
role="cell"
>
first
</div>
<div
class="fui-DataGridCell fui-TableCell"
role="cell"
>
second
</div>
<div
class="fui-DataGridCell fui-TableCell"
role="cell"
>
third
</div>
</div>
<div
class="fui-DataGridRow fui-TableRow"
role="row"
>
<div
class="fui-DataGridCell fui-TableCell"
role="cell"
>
first
</div>
<div
class="fui-DataGridCell fui-TableCell"
role="cell"
>
second
</div>
<div
class="fui-DataGridCell fui-TableCell"
role="cell"
>
third
</div>
</div>
</div>
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import * as React from 'react';
import type { DataGridContextValues, DataGridState } from './DataGrid.types';
import { renderTable_unstable } from '../Table/renderTable';
import { DataGridContextProvider } from '../../contexts/dataGridContext';

/**
* Render the final JSX of DataGrid
*/
export const renderDataGrid_unstable = (state: DataGridState, contextValues: DataGridContextValues) => {
return renderTable_unstable(state, contextValues);
return (
<DataGridContextProvider value={contextValues.dataGrid}>
{renderTable_unstable(state, contextValues)}
</DataGridContextProvider>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import type { DataGridProps, DataGridState } from './DataGrid.types';
import { useTable_unstable } from '../Table/useTable';
import { useTable } from '../../hooks/useTable';

/**
* Create the state required to render DataGrid.
Expand All @@ -12,5 +13,12 @@ import { useTable_unstable } from '../Table/useTable';
* @param ref - reference to root HTMLElement of DataGrid
*/
export const useDataGrid_unstable = (props: DataGridProps, ref: React.Ref<HTMLElement>): DataGridState => {
return useTable_unstable({ ...props, as: 'div' }, ref);
const { items, columns } = props;
const tableState = useTable({ items, columns }, []);
const baseTableState = useTable_unstable({ ...props, as: 'div' }, ref);
Comment on lines +17 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the naming is very confusing.... perhaps useHeadlessTable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I agree, but I'd rather do this later, there are more important thigns that need to be implemented right now before we do a naming pass over everything


return {
...baseTableState,
tableState,
};
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { useTableContextValues_unstable } from '../Table/useTableContextValues';
import { DataGridState } from './DataGrid.types';
import { DataGridContextValues, DataGridState } from './DataGrid.types';

export function useDataGridContextValues_unstable(state: DataGridState) {
return useTableContextValues_unstable(state);
export function useDataGridContextValues_unstable(state: DataGridState): DataGridContextValues {
const tableContextValues = useTableContextValues_unstable(state);
return {
...tableContextValues,
dataGrid: {
...state.tableState,
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ describe('DataGridBody', () => {
displayName: 'DataGridBody',
});

// TODO add more tests here, and create visual regression tests in /apps/vr-tests

it('renders a default state', () => {
const result = render(<DataGridBody>Default DataGridBody</DataGridBody>);
it('renders items from render function', () => {
const result = render(<DataGridBody>{() => 'foo'}</DataGridBody>);
expect(result.container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { TableBodySlots, TableBodyProps, TableBodyState } from '../TableBody/TableBody.types';
import * as React from 'react';
import type { RowState } from '../../hooks';
import type { TableBodySlots, TableBodyProps, TableBodyState } from '../TableBody/TableBody.types';

export type DataGridBodySlots = TableBodySlots;

// Use any here since we can't know the user types
// The user is responsible for narrowing the type downstream
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type RowRenderFunction<TItem = any> = (row: RowState<TItem>) => React.ReactNode;

/**
* DataGridBody Props
*/
export type DataGridBodyProps = TableBodyProps;
export type DataGridBodyProps = Omit<TableBodyProps, 'children'> & {
/**
* Render function for rows
*/
children: RowRenderFunction;
};

/**
* State used in rendering DataGridBody
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`DataGridBody renders a default state 1`] = `
exports[`DataGridBody renders items from render function 1`] = `
<div>
<div
class="fui-DataGridBody fui-TableBody"
role="rowgroup"
>
Default DataGridBody
</div>
/>
</div>
`;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import type { DataGridBodyProps, DataGridBodyState } from './DataGridBody.types';
import { useTableBody_unstable } from '../TableBody/useTableBody';
import { useDataGridContext_unstable } from '../../contexts/dataGridContext';

/**
* Create the state required to render DataGridBody.
Expand All @@ -12,5 +13,10 @@ import { useTableBody_unstable } from '../TableBody/useTableBody';
* @param ref - reference to root HTMLElement of DataGridBody
*/
export const useDataGridBody_unstable = (props: DataGridBodyProps, ref: React.Ref<HTMLElement>): DataGridBodyState => {
return useTableBody_unstable({ ...props, as: 'div' }, ref);
const getRows = useDataGridContext_unstable(ctx => ctx.getRows);
const rows = getRows();

const { children: renderRow } = props;
const children = rows.map(row => renderRow(row));
return useTableBody_unstable({ ...props, children, as: 'div' }, ref);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createContext, useContextSelector } from '@fluentui/react-context-selector';
import type { ContextSelector } from '@fluentui/react-context-selector';
import { DataGridContextValue } from '../components/DataGrid/DataGrid.types';
import { defaultTableState } from '../hooks';

const dataGridContext = createContext<DataGridContextValue | undefined>(undefined);

const dataGridContextDefaultValue: DataGridContextValue = {
...defaultTableState,
};

export const DataGridContextProvider = dataGridContext.Provider;

export const useDataGridContext_unstable = <T>(selector: ContextSelector<DataGridContextValue, T>) =>
useContextSelector(dataGridContext, (ctx = dataGridContextDefaultValue) => selector(ctx));
9 changes: 9 additions & 0 deletions packages/react-components/react-table/src/hooks/useTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import { defaultTableSortState } from './useSort';

const defaultRowEnhancer: RowEnhancer<unknown, RowState<unknown>> = row => row;

export const defaultTableState: TableState<unknown> = {
selection: defaultTableSelectionState,
sort: defaultTableSortState,
getRows: () => [],
getRowId: () => '',
items: [],
columns: [],
};

export function useTable<TItem>(options: UseTableOptions<TItem>, plugins: TableStatePlugin[] = []): TableState<TItem> {
const { items, getRowId, columns } = options;

Expand Down
Loading