Skip to content
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

feat(react-data-grid): support autoFitColumns: false #260

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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": "minor",
"comment": "Keep header and body in sync when the DataGrid columns are overflowing #176",
"packageName": "@fluentui-contrib/react-data-grid-react-window",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as React from 'react';
import { useDataGrid_unstable } from './useDataGrid';
import { renderDataGrid_unstable } from './renderDataGrid';

import {
renderDataGrid_unstable,
useDataGridStyles_unstable,
useDataGridContextValues_unstable,
DataGridProps,
} from '@fluentui/react-components';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { useDataGridStyles_unstable } from './useDataGridStyles.styles';

export const DataGrid: ForwardRefComponent<DataGridProps> = React.forwardRef(
(props, ref) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from 'react';
import {
DataGridContextValues,
DataGridState,
renderDataGrid_unstable as baseRender,
} from '@fluentui/react-components';
import { HeaderRefContextProvider } from '../../contexts/headerRefContext';
import { BodyRefContextProvider } from '../../contexts/bodyRefContext';

/**
* Render the final JSX of DataGrid
*/
export const renderDataGrid_unstable = (
state: DataGridState,
contextValues: DataGridContextValues
) => {
const headerRef = React.useRef<HTMLDivElement | null>(null);
const bodyRef = React.useRef<HTMLDivElement | null>(null);

return (
<HeaderRefContextProvider value={headerRef}>
<BodyRefContextProvider value={bodyRef}>
{baseRender(state, contextValues)}
</BodyRefContextProvider>
</HeaderRefContextProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
DataGridState,
makeStyles,
mergeClasses,
useDataGridStyles_unstable as useDataGridStylesBase_unstable,
} from '@fluentui/react-components';

const useStyles = makeStyles({
root: {
// DataGrid gets min-width: fit-content applied directly to the element, thus the need for !important
// without auto width, the DataGrid will not scroll
minWidth: 'auto !important',
},
});

/**
* Apply styling to the DataGrid slots based on the state
*/
export const useDataGridStyles_unstable = (
state: DataGridState
): DataGridState => {
const classes = useStyles();
state.root.className = mergeClasses(classes.root, state.root.className);

useDataGridStylesBase_unstable(state);
return state;
};
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,6 @@ export type DataGridBodyState = Omit<DataGridBodyStateBase, 'renderRow'> &
virtualizedRow: (props: ListChildComponentProps) => React.ReactElement;
} & {
listProps?: Partial<FixedSizeListProps>;

outerRef?: React.Ref<unknown>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const renderDataGridBody_unstable = (state: DataGridBodyState) => {
height={state.height}
itemCount={state.rows.length}
direction={dir}
outerRef={state.outerRef}
{...state.listProps}
>
{state.virtualizedRow}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
} from '@fluentui/react-components';
import type { RowRenderFunction } from '@fluentui/react-table';
import { TableRowIndexContextProvider } from '../../contexts/rowIndexContext';
import { useBodyRefContext } from '../../contexts/bodyRefContext';
import { useHeaderRefContext } from '../../contexts/headerRefContext';

/**
* Create the state required to render DataGridBody.
Expand All @@ -33,6 +35,9 @@ export const useDataGridBody_unstable = (
listProps,
} = props;

const bodyRef = useBodyRefContext();
const headerRef = useHeaderRefContext();

// cast the row render function to work with unknown args
const renderRowWithUnknown = children as RowRenderFunction;
const baseState = useDataGridBodyBase_unstable(
Expand All @@ -54,6 +59,28 @@ export const useDataGridBody_unstable = (
[ariaRowIndexStart, children]
);

const onScroll = React.useCallback(() => {
if (bodyRef.current && headerRef.current) {
headerRef.current.scroll({
left: bodyRef.current.scrollLeft,
behavior: 'instant',
});
}
}, []);

// Use a onScroll callback on the outerElement since react-window's onScroll will only work for horizontal scrolls
const setupOuterRef = React.useCallback(
(outerElement: HTMLElement | null) => {
bodyRef.current?.removeEventListener('scroll', onScroll);

bodyRef.current = outerElement;
if (outerElement) {
outerElement.addEventListener('scroll', onScroll);
}
},
[]
);

return {
...baseState,
itemSize,
Expand All @@ -62,5 +89,6 @@ export const useDataGridBody_unstable = (
width,
ariaRowIndexStart,
listProps,
outerRef: setupOuterRef,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react';
import {
renderDataGridHeader_unstable,
type DataGridHeaderProps,
type ForwardRefComponent,
} from '@fluentui/react-components';
import { useDataGridHeader_unstable } from './useDataGridHeader';
import { useDataGridHeaderStyles_unstable } from './useDataGridHeaderStyles.styles';

/**
* DataGridHeader component
*/
export const DataGridHeader: ForwardRefComponent<DataGridHeaderProps> &
((props: DataGridHeaderProps) => JSX.Element) = React.forwardRef(
(props, ref) => {
const state = useDataGridHeader_unstable(props, ref);

useDataGridHeaderStyles_unstable(state);
return renderDataGridHeader_unstable(state);
}
) as ForwardRefComponent<DataGridHeaderProps> &
((props: DataGridHeaderProps) => JSX.Element);

DataGridHeader.displayName = 'DataGridHeader';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './DataGridHeader';
export * from './useDataGridHeader';
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as React from 'react';
import {
useDataGridHeader_unstable as useBaseState,
DataGridHeaderProps,
DataGridHeaderState,
} from '@fluentui/react-components';
import { useBodyRefContext } from '../../contexts/bodyRefContext';
import { useHeaderRefContext } from '../../contexts/headerRefContext';

const setRef = (ref: React.Ref<HTMLElement>, value: HTMLElement | null) => {
if (typeof ref === 'function') {
ref(value);
} else if (ref) {
(ref as React.MutableRefObject<HTMLElement | null>).current = value;
}
};

/**
* Create the state required to render DataGridHeader.
*
* The returned state can be modified with hooks such as useDataGridHeaderStyles_unstable,
* before being passed to renderDataGridHeader_unstable.
*
* @param props - props from this instance of DataGridHeader
* @param ref - reference to root HTMLElement of DataGridHeader
*/
export const useDataGridHeader_unstable = (
props: DataGridHeaderProps,
ref: React.Ref<HTMLElement>
): DataGridHeaderState => {
const bodyRef = useBodyRefContext();
const headerRef = useHeaderRefContext();

const onScroll = React.useCallback(() => {
if (bodyRef.current && headerRef.current) {
bodyRef.current.scroll({
left: headerRef.current.scrollLeft,
behavior: 'instant',
});
}
}, []);

const setupRef = React.useCallback((element: HTMLElement | null) => {
setRef(ref, element);
headerRef.current?.removeEventListener('scroll', onScroll);

headerRef.current = element;
if (element) {
element.addEventListener('scroll', onScroll);
}
}, []);

const baseState = useBaseState(props, setupRef);

return {
...baseState,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
makeStyles,
mergeClasses,
useDataGridHeaderStyles_unstable as useDataGridHeaderStylesBase_unstable,
DataGridHeaderState,
} from '@fluentui/react-components';

const useStyles = makeStyles({
root: {
overflowX: 'auto',
// Hide the scrollbar in the header, it is synced to the scrollbar of the body so shouldn't be shown
scrollbarWidth: 'none',
'::-webkit-scrollbar': {
width: 0,
height: 0,
},
},
});

/**
* Apply styling to the DataGridHeader slots based on the state
*/
export const useDataGridHeaderStyles_unstable = (
state: DataGridHeaderState
): DataGridHeaderState => {
const classes = useStyles();
state.root.className = mergeClasses(classes.root, state.root.className);

useDataGridHeaderStylesBase_unstable(state);
return state;
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as React from 'react';
import {
useDataGridRowStyles_unstable,
renderDataGridRow_unstable,
DataGridRowProps,
} from '@fluentui/react-components';
import type { ForwardRefComponent } from '@fluentui/react-components';
import { useDataGridRow_unstable } from './useDataGridRow.styles';
import { useDataGridRow_unstable } from './useDataGridRow';
import { useDataGridRowStyles_unstable } from './useDataGridRow.styles';

/**
* DataGridRow component
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import * as React from 'react';
import type {
DataGridRowProps,
import {
makeStyles,
mergeClasses,
useDataGridRowStyles_unstable as useDataGridRowStylesBase_unstable,
DataGridRowState,
} from '@fluentui/react-components';
import { useDataGridRow_unstable as useBaseState } from '@fluentui/react-components';
import { useTableRowIndexContext } from '../../contexts/rowIndexContext';

const useStyles = makeStyles({
root: {
minWidth: 'fit-content',
},
});

/**
* Create the state required to render DataGridRow.
*
* The returned state can be modified with hooks such as useDataGridRowStyles_unstable,
* before being passed to renderDataGridRow_unstable.
*
* @param props - props from this instance of DataGridRow
* @param ref - reference to root HTMLElement of DataGridRow
* Apply styling to the DataGridRow slots based on the state
*/
export const useDataGridRow_unstable = (
props: DataGridRowProps,
ref: React.Ref<HTMLElement>
export const useDataGridRowStyles_unstable = (
state: DataGridRowState
): DataGridRowState => {
const rowIndex = useTableRowIndexContext();
return useBaseState({ ...props, 'aria-rowindex': rowIndex }, ref);
const classes = useStyles();
state.root.className = mergeClasses(classes.root, state.root.className);

useDataGridRowStylesBase_unstable(state);
return state;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react';
import type {
DataGridRowProps,
DataGridRowState,
} from '@fluentui/react-components';
import { useDataGridRow_unstable as useBaseState } from '@fluentui/react-components';
import { useTableRowIndexContext } from '../../contexts/rowIndexContext';

/**
* Create the state required to render DataGridRow.
*
* The returned state can be modified with hooks such as useDataGridRowStyles_unstable,
* before being passed to renderDataGridRow_unstable.
*
* @param props - props from this instance of DataGridRow
* @param ref - reference to root HTMLElement of DataGridRow
*/
export const useDataGridRow_unstable = (
props: DataGridRowProps,
ref: React.Ref<HTMLElement>
): DataGridRowState => {
const rowIndex = useTableRowIndexContext();
return useBaseState({ ...props, 'aria-rowindex': rowIndex }, ref);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react';

const bodyRefContext: React.Context<
React.MutableRefObject<HTMLElement | null>
> = React.createContext<React.MutableRefObject<HTMLElement | null>>({
current: null,
});

export const bodyRefContextDefaultValue: React.MutableRefObject<HTMLElement | null> =
{ current: null };

export const useBodyRefContext = () =>
React.useContext(bodyRefContext) ?? bodyRefContextDefaultValue;

export const BodyRefContextProvider = bodyRefContext.Provider;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react';

const headerRefContext: React.Context<
React.MutableRefObject<HTMLElement | null>
> = React.createContext<React.MutableRefObject<HTMLElement | null>>({
current: null,
});

export const headerRefContextDefaultValue: React.MutableRefObject<HTMLElement | null> =
{ current: null };

export const useHeaderRefContext = () =>
React.useContext(headerRefContext) ?? headerRefContextDefaultValue;

export const HeaderRefContextProvider = headerRefContext.Provider;
2 changes: 1 addition & 1 deletion packages/react-data-grid-react-window/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export { DataGridBody } from './components/DataGridBody';
export { DataGrid } from './components/DataGrid';
export { DataGridRow } from './components/DataGridRow';
export { DataGridHeader } from './components/DataGridHeader';

export {
DataGridCell,
DataGridHeader,
DataGridHeaderCell,
DataGridSelectionCell,
} from '@fluentui/react-components';
Expand Down
Loading