目标
希望table组件能支持多列的左右固定,像这样
材料
现在的table有固定的头和一列固定的checkbox,如下
table 元素的样式
因为
table
元素的table-layout
并没有设置为fixed
,所以列目前是自适应的宽度。
开始
固定列的宽度
在table里,使用<colgroup>
,<col>
来设置列的宽度。
入参需要所有table列的配置的数组,如果有父子列则只需要子列的配置。
格式大概是
{
Header: t('Table.Header.BatchId'),
id: 'invoiceId',
fixed: 'left',
width: 115,
accessor: NoEmptyAccessor('invoiceId'),
}
相关入参
-
width
来固定列的宽度 -
fixed:'left'|'right'
来确定列是靠左还是靠右
生成 <colgroup>
export function ColGroup<D extends object = {}>({
allFlatColumns,
hasSelect,
}: {
allFlatColumns: readonly ColumnInstance<D>[];
hasSelect: boolean;
}) {
const children = [];
if (hasSelect) children.push(<S.InnerCol width={CHECKBOX_WIDTH} />); // when there has checkbox
allFlatColumns.forEach((item) => {
if (item.isVisible && typeof item.width === 'number') {
children.push(<S.InnerCol width={item.width} key={item.id} />);
}
});
return <colgroup>{children}</colgroup>;
}
export const InnerCol = styled.col<{ width?: number }>(({ width }) => css`
&:first-child{
min-width: ${width || 60}px;
}
${width && css`
width: ${width}px;
min-width: ${width}px;
max-width: ${width}px;
`}
`);
使用
<ColGroup allFlatColumns={allColumns} hasSelect={!!onSelected} />
距离
规定了列的宽度后,还需要确定距离左右边界的offset,然后得到需要固定的列的Map以便render时候读取,代码如下:
export enum Fixed {
left = 'left',
right = 'right',
}
interface Sticky {
fixed: Fixed;
offset: number;
shadow?: boolean;
}
type FixedColumn<D extends object> = ColumnInstance<D> & {
sticky: Sticky;
};
const CHECKBOX_WIDTH = 60;
function genSticky(
fixed: Sticky['fixed'],
offset: Sticky['offset'],
shadow?: Sticky['shadow'],
) {
const val: Sticky = { fixed, offset };
if (shadow) val.shadow = shadow;
return val;
}
export const FIXED_LEFT_0 = genSticky(Fixed.left, 0);
export function getTableFixedInfo<D extends object = {}>(
allFlatColumns: readonly ColumnInstance<D>[],
hasSelect: boolean,
) {
const fixedLeftColumns: FixedColumn<D>[] = [];
const fixedRightColumns: FixedColumn<D>[] = [];
const fixedConfig: Map<string, Sticky> = new Map();
let offsetLeft = hasSelect ? CHECKBOX_WIDTH : 0;
let offsetRight = 0;
// initial sticky parameter
allFlatColumns.forEach((item) => {
if (item?.fixed === Fixed.left) {
const sticky: Sticky = genSticky(
Fixed.left,
offsetLeft,
);
fixedLeftColumns.push({ ...item, sticky });
fixedConfig.set(item.id, sticky);
if (typeof item.width === 'number') {
offsetLeft += item.width;
}
} else if (item.fixed === Fixed.right) {
const sticky: Sticky = genSticky(
Fixed.right,
offsetRight,
);
fixedRightColumns.push({ ...item, sticky });
fixedConfig.set(item.id, sticky);
}
});
if (fixedLeftColumns.length) {
fixedLeftColumns[fixedLeftColumns.length - 1].sticky.shadow = true;
}
// handle fixedRightColumns sticky parameter
// eslint-disable-next-line no-plusplus
for (let idx = fixedRightColumns.length - 1; idx >= 0; idx--) {
const item = fixedRightColumns[idx];
item.sticky.offset = offsetRight;
if (idx === 0) item.sticky.shadow = true;
if (typeof item?.width === 'number') {
offsetRight += item.width;
}
}
return {
fixedConfig,
parentHeaderSticky: genSticky(Fixed.left, hasSelect ? CHECKBOX_WIDTH : 0),
};
}
阴影
阴影就是在列上加一个伪元素,阴影样式如下:
type Sticky = { offset: number; fixed: 'left' | 'right'; shadow?: boolean };
const VerticalShadow = (position: Sticky['fixed']) => css`
position: absolute;
top: 0;
bottom: -1px;
width: 30px;
transition: box-shadow .3s;
content: "";
pointer-events: none;
direction: ltr;
${position === 'right' && css`
box-shadow: inset -10px 0 8px -8px rgb(5 5 5 / 6%);
border-inline-end: 1px solid ${getColorByName('borderDivider')};
transform: translateX(-100%);
left: 0;
`}
${position === 'left' && css`
box-shadow: inset 10px 0 8px -8px rgb(5 5 5 / 6%);
transform: translateX(100%);
right: 0;
`}
`;
使用
${sticky.shadow && css`
&::after {
${VerticalShadow(sticky.fixed)}
}
`}