diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx index f8319e2b0..1c6891208 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx @@ -63,91 +63,64 @@ const getStyle = ( const alternateBackground = genLinerGradient(rowStyle.alternateBackground); return css` - border-color: ${style.border}; - border-radius: ${style.radius}; - - & > div > div > div > .ant-table > .ant-table-container > .ant-table-content > table { - > thead > tr > th, - > tbody > tr > td { - border-color: ${style.border}; - } - - > .ant-table-thead > tr > th::before { - background-color: ${style.border}; + .ant-table-body { + background: white; + } + .ant-table-tbody { + > tr:nth-of-type(2n + 1) { + &, + > td { + background: ${genLinerGradient(rowStyle.background)}; + } } - > .ant-table-thead { - > tr > th { - background-color: ${style.headerBackground}; - border-color: ${style.border}; - color: ${style.headerText}; - - &.ant-table-column-has-sorters:hover { - background-color: ${darkenColor(style.headerBackground, 0.05)}; - } - - > .ant-table-column-sorters > .ant-table-column-sorter { - color: ${style.headerText === defaultTheme.textDark ? "#bfbfbf" : style.headerText}; - } + > tr:nth-of-type(2n) { + &, + > td { + background: ${alternateBackground}; } } - > .ant-table-tbody { - > tr:nth-of-type(2n + 1) { - &, - > td { - background: ${genLinerGradient(rowStyle.background)}; - } + // selected row + > tr:nth-of-type(2n + 1).ant-table-row-selected { + > td { + background: ${selectedRowBackground}, ${rowStyle.background} !important; } - > tr:nth-of-type(2n) { - &, - > td { - background: ${alternateBackground}; - } + > td.ant-table-cell-row-hover, + &:hover > td { + background: ${hoverRowBackground}, ${selectedRowBackground}, ${rowStyle.background} !important; } + } - // selected row - > tr:nth-of-type(2n + 1).ant-table-row-selected { - > td { - background: ${selectedRowBackground}, ${rowStyle.background} !important; - } - - > td.ant-table-cell-row-hover, - &:hover > td { - background: ${hoverRowBackground}, ${selectedRowBackground}, ${rowStyle.background} !important; - } + > tr:nth-of-type(2n).ant-table-row-selected { + > td { + background: ${selectedRowBackground}, ${alternateBackground} !important; } - > tr:nth-of-type(2n).ant-table-row-selected { - > td { - background: ${selectedRowBackground}, ${alternateBackground} !important; - } - - > td.ant-table-cell-row-hover, - &:hover > td { - background: ${hoverRowBackground}, ${selectedRowBackground}, ${alternateBackground} !important; - } + > td.ant-table-cell-row-hover, + &:hover > td { + background: ${hoverRowBackground}, ${selectedRowBackground}, ${alternateBackground} !important; } + } - // hover row - > tr:nth-of-type(2n + 1) > td.ant-table-cell-row-hover { - &, - > div:nth-of-type(2) { - background: ${hoverRowBackground}, ${rowStyle.background} !important; - } + // hover row + > tr:nth-of-type(2n + 1) > td.ant-table-cell-row-hover { + &, + > div:nth-of-type(2) { + background: ${hoverRowBackground}, ${rowStyle.background} !important; } + } - > tr:nth-of-type(2n) > td.ant-table-cell-row-hover { - &, - > div:nth-of-type(2) { - background: ${hoverRowBackground}, ${alternateBackground} !important; - } + > tr:nth-of-type(2n) > td.ant-table-cell-row-hover { + &, + > div:nth-of-type(2) { + background: ${hoverRowBackground}, ${alternateBackground} !important; } + } - > tr.ant-table-expanded-row > td { - background: ${background}; - } + > tr.ant-table-expanded-row > td { + background: ${background}; } } `; @@ -157,11 +130,14 @@ const TableWrapper = styled.div<{ $style: TableStyleType; $rowStyle: TableRowStyleType; toolbarPosition: "above" | "below" | "close"; + fixedHeader: boolean; + fixedToolbar: boolean; }>` max-height: 100%; overflow-y: auto; background: white; - border: 1px solid #d7d9e0; + border: ${(props) => `1px solid ${props.$style.border}`}; + border-radius: ${(props) => props.$style.radius}; .ant-table-wrapper { border-top: ${(props) => (props.toolbarPosition === "above" ? "1px solid" : "unset")}; @@ -194,63 +170,102 @@ const TableWrapper = styled.div<{ border-inline-start: none !important; .ant-table-content { - // A table expand row contains table - .ant-table-tbody .ant-table-wrapper:only-child .ant-table { - margin: 0; - } + overflow: unset !important; + } - table { - border-top: unset; + // A table expand row contains table + .ant-table-tbody .ant-table-wrapper:only-child .ant-table { + margin: 0; + } - td { - padding: 0px 0px; - } + table { + border-top: unset; + + > .ant-table-thead { + > tr > th { + background-color: ${(props) => props.$style.headerBackground}; + border-color: ${(props) => props.$style.border}; + color: ${(props) => props.$style.headerText}; + border-inline-end: ${(props) => `1px solid ${props.$style.border}`} !important; + ${(props) => + props.fixedHeader && ` + position: sticky; + position: -webkit-sticky; + top: ${props.fixedToolbar ? '47px' : '0'}; + z-index: 99; + ` + } + + &:last-child { + border-inline-end: none !important; + } + &.ant-table-column-has-sorters:hover { + background-color: ${(props) => darkenColor(props.$style.headerBackground, 0.05)}; + } + + > .ant-table-column-sorters > .ant-table-column-sorter { + color: ${(props) => props.$style.headerText === defaultTheme.textDark ? "#bfbfbf" : props.$style.headerText}; + } - thead > tr:first-child { - th:last-child { - border-right: unset; + &::before { + background-color: ${(props) => props.$style.border}; } } + } - tbody > tr > td:last-child { + > thead > tr > th, + > tbody > tr > td { + border-color: ${(props) => props.$style.border}; + } + + td { + padding: 0px 0px; + } + + thead > tr:first-child { + th:last-child { border-right: unset; } + } - .ant-empty-img-simple-g { - fill: #fff; - } + tbody > tr > td:last-child { + border-right: unset; + } - > thead > tr:first-child { - th:first-child { - border-top-left-radius: 0px; - } + .ant-empty-img-simple-g { + fill: #fff; + } - th:last-child { - border-top-right-radius: 0px; - } + > thead > tr:first-child { + th:first-child { + border-top-left-radius: 0px; } - // hide the bottom border of the last row - ${(props) => - props.toolbarPosition !== "below" && - ` - tbody > tr:last-child > td { - border-bottom: unset; - } - `} + th:last-child { + border-top-right-radius: 0px; + } } - .ant-table-expanded-row-fixed:after { - border-right: unset !important; - } + // hide the bottom border of the last row + ${(props) => + props.toolbarPosition !== "below" && + ` + tbody > tr:last-child > td { + border-bottom: unset; + } + `} + } + + .ant-table-expanded-row-fixed:after { + border-right: unset !important; } } } - + ${(props) => props.$style && getStyle(props.$style, props.$rowStyle)} `; - + const TableTh = styled.th<{ width?: number }>` overflow: hidden; @@ -272,6 +287,11 @@ const TableTd = styled.td<{ .ant-table-row-indent { display: ${(props) => (props.$isEditing ? "none" : "initial")}; } + &.ant-table-row-expand-icon-cell { + background: ${(props) => props.background}; + border-color: ${(props) => props.$style.border}; + } + background: ${(props) => props.background} !important; border-color: ${(props) => props.$style.border} !important; border-width: ${(props) => props.$style.borderWidth} !important; @@ -364,9 +384,6 @@ type CustomTableProps = Omit, "components" | viewModeResizable: boolean; rowColorFn: RowColorViewType; columnsStyle: TableColumnStyleType; - fixedHeader: boolean; - height?: number; - autoHeight?: boolean; }; function TableCellView(props: { @@ -540,9 +557,7 @@ function ResizeableTable(props: CustomTableProps ); @@ -556,10 +571,10 @@ export function TableCompView(props: { onDownload: (fileName: string) => void; }) { const editorState = useContext(EditorContext); - const { width, height, ref } = useResizeDetector({ + const { width, ref } = useResizeDetector({ refreshMode: "debounce", refreshRate: 600, - handleHeight: true, + handleHeight: false, }); const viewMode = useUserViewMode(); const compName = useContext(CompNameContext); @@ -584,7 +599,6 @@ export function TableCompView(props: { () => compChildren.dynamicColumnConfig.getView(), [compChildren.dynamicColumnConfig] ); - const autoHeight = compChildren.autoHeight.getView(); const columnsAggrData = comp.columnAggrData; const expansion = useMemo(() => compChildren.expansion.getView(), [compChildren.expansion]); const antdColumns = useMemo( @@ -681,6 +695,8 @@ export function TableCompView(props: { $style={style} $rowStyle={rowStyle} toolbarPosition={toolbar.position} + fixedHeader={compChildren.fixedHeader.getView()} + fixedToolbar={toolbar.fixedToolbar && toolbar.position === 'above'} > {toolbar.position === "above" && toolbarView} @@ -701,15 +717,12 @@ export function TableCompView(props: { onTableChange(pagination, filters, sorter, extra, comp.dispatch, onEvent); }} showHeader={!compChildren.hideHeader.getView()} - fixedHeader={compChildren.fixedHeader.getView()} columns={antdColumns} columnsStyle={columnsStyle} viewModeResizable={compChildren.viewModeResizable.getView()} dataSource={pageDataInfo.data} size={compChildren.size.getView()} tableLayout="fixed" - height={height} - autoHeight={autoHeight} loading={ loading || // fixme isLoading type diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableToolbarComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableToolbarComp.tsx index cff63a784..ca60434e3 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableToolbarComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableToolbarComp.tsx @@ -48,13 +48,22 @@ const getStyle = ( style: TableStyleType, filtered: boolean, theme: ThemeDetail, - position: ToolbarRowType["position"] + position: ToolbarRowType["position"], + fixedToolbar: boolean, ) => { return css` background-color: ${style.toolbarBackground}; // Implement horizontal scrollbar and vertical page number selection is not blocked - padding: ${position === "above" ? "13px 16px 313px 16px" : "313px 16px 13px 16px"}; - margin: ${position === "above" ? "0 0 -300px 0" : "-300px 0 0 0"}; + // padding: ${position === "above" ? "13px 16px 313px 16px" : "313px 16px 13px 16px"}; + // margin: ${position === "above" ? "0 0 -300px 0" : "-300px 0 0 0"}; + padding: 13px 12px; + ${fixedToolbar && ` + position: sticky; + postion: -webkit-sticky; + z-index: 99; + `}; + ${fixedToolbar && position === 'below' && `bottom: 0;`}; + ${fixedToolbar && position === 'above' && `top: 0;` }; .toolbar-icons { .refresh, @@ -147,9 +156,16 @@ const ToolbarWrapper = styled.div<{ $filtered: boolean; theme: ThemeDetail; position: ToolbarRowType["position"]; + fixedToolbar: boolean; }>` - overflow: auto; - ${(props) => props.$style && getStyle(props.$style, props.$filtered, props.theme, props.position)} + // overflow: auto; + ${(props) => props.$style && getStyle( + props.$style, + props.$filtered, + props.theme, + props.position, + props.fixedToolbar, + )} `; const ToolbarWrapper2 = styled.div` @@ -539,6 +555,7 @@ export const TableToolbarComp = (function () { showDownload: BoolControl, showFilter: BoolControl, columnSetting: BoolControl, + fixedToolbar: BoolControl, // searchText: StringControl, filter: stateComp({ stackType: "and", filters: [] }), position: dropdownControl(positionOptions, "below"), @@ -563,6 +580,10 @@ export const TableToolbarComp = (function () { }) .setPropertyViewFn((children) => [ children.position.propertyView({ label: trans("table.position"), radioButton: true }), + children.fixedToolbar.propertyView({ + label: trans("table.fixedToolbar"), + tooltip: trans("table.fixedToolbarTooltip") + }), children.showFilter.propertyView({ label: trans("table.showFilter") }), children.showRefresh.propertyView({ label: trans("table.showRefresh") }), children.showDownload.propertyView({ label: trans("table.showDownload") }), @@ -728,6 +749,7 @@ export function TableToolbar(props: { theme={theme} $filtered={toolbar.filter.filters.length > 0} position={toolbar.position} + fixedToolbar={toolbar.fixedToolbar} > diff --git a/client/packages/lowcoder/src/comps/hooks/screenInfoComp.tsx b/client/packages/lowcoder/src/comps/hooks/screenInfoComp.tsx index 00d0fceec..a41ae99a3 100644 --- a/client/packages/lowcoder/src/comps/hooks/screenInfoComp.tsx +++ b/client/packages/lowcoder/src/comps/hooks/screenInfoComp.tsx @@ -20,8 +20,8 @@ type ScreenInfo = { function useScreenInfo() { const getDeviceType = () => { - if (window.screen.width < 768) return ScreenTypes.Mobile; - if (window.screen.width < 889) return ScreenTypes.Tablet; + if (window.innerWidth < 768) return ScreenTypes.Mobile; + if (window.innerWidth < 889) return ScreenTypes.Tablet; return ScreenTypes.Desktop; } const getFlagsByDeviceType = (deviceType: ScreenType) => { diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index a06f7cdf0..e27a915fb 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -1253,6 +1253,8 @@ export const en = { hideHeader: "Hide table header", fixedHeader: "Fixed table header", fixedHeaderTooltip: "Header will be fixed for vertically scrollable table", + fixedToolbar: "Fixed toolbar", + fixedToolbarTooltip: "Toolbaar will be fixed for vertically scrollable table based on position", hideBordered: "Hide column border", deleteColumn: "Delete column", confirmDeleteColumn: "Confirm delete column: ", diff --git a/client/packages/lowcoder/src/i18n/locales/zh.ts b/client/packages/lowcoder/src/i18n/locales/zh.ts index 60f66ccec..4cfa6474a 100644 --- a/client/packages/lowcoder/src/i18n/locales/zh.ts +++ b/client/packages/lowcoder/src/i18n/locales/zh.ts @@ -1176,6 +1176,8 @@ table: { hideHeader: "隐藏表头", fixedHeader: "固定表头", fixedHeaderTooltip: "垂直滚动表格的标题将被固定", + fixedToolbar: "固定工具栏", + fixedToolbarTooltip: "工具栏将根据所选位置固定为垂直滚动表格", hideBordered: "隐藏列边框", deleteColumn: "删除列", confirmDeleteColumn: "确认删除列:",