diff --git a/client/packages/lowcoder/src/components/table/EditableCell.tsx b/client/packages/lowcoder/src/components/table/EditableCell.tsx index 9955438b2..799c21a26 100644 --- a/client/packages/lowcoder/src/components/table/EditableCell.tsx +++ b/client/packages/lowcoder/src/components/table/EditableCell.tsx @@ -34,6 +34,7 @@ export interface CellProps { size?: string; candidateTags?: string[]; candidateStatus?: { text: string; status: StatusType }[]; + textOverflow?: boolean; } export type CellViewReturn = (props: CellProps) => ReactNode; @@ -43,17 +44,6 @@ export type EditViewFn = (props: { onChangeEnd: () => void; }) => ReactNode; -export const SizeWrapper = styled.div<{ $size?: string }>` - ${(props) => - props.$size && - `padding: ${ - props.$size === "small" ? "8.5px 8px" : props.$size === "large" ? "16.5px 16px" : "12.5px 8px" - }; - line-height: 21px; - min-height: ${props.$size === "small" ? "39px" : props.$size === "large" ? "55px" : "47px"}; - `} -`; - const BorderDiv = styled.div` position: absolute; border: 1.5px solid #315efb; @@ -127,11 +117,15 @@ export function EditableCell(props: EditableCellProps) { } return ( - + {status === "toSave" && !isEditing && } - +
{normalView} - +
); } diff --git a/client/packages/lowcoder/src/components/table/columnTypeView.tsx b/client/packages/lowcoder/src/components/table/columnTypeView.tsx index d3a54cdf1..bf50fac8c 100644 --- a/client/packages/lowcoder/src/components/table/columnTypeView.tsx +++ b/client/packages/lowcoder/src/components/table/columnTypeView.tsx @@ -1,13 +1,17 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import styled from "styled-components"; -const ColumnTypeViewWrapper = styled.div` - div { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - word-break: keep-all; - } +const ColumnTypeViewWrapper = styled.div<{ + textOverflow?: boolean +}>` + ${props => !props.textOverflow && ` + div { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + word-break: keep-all; + } + `} `; const ColumnTypeHoverView = styled.div<{ @@ -62,7 +66,10 @@ function childIsOverflow(nodes: HTMLCollection): boolean { return false; } -export default function ColumnTypeView(props: { children: React.ReactNode }) { +export default function ColumnTypeView(props: { + children: React.ReactNode, + textOverflow?: boolean, +}) { const wrapperRef = useRef(null); const hoverViewRef = useRef(null); const [isHover, setIsHover] = useState(false); @@ -161,6 +168,7 @@ export default function ColumnTypeView(props: { children: React.ReactNode }) { <> { delayMouseEnter(); }} @@ -171,7 +179,7 @@ export default function ColumnTypeView(props: { children: React.ReactNode }) { > {props.children} - {isHover && hasOverflow && wrapperRef.current && ( + {isHover && hasOverflow && wrapperRef.current && !props.textOverflow && ( ["getOriginalComp"]>; export const RenderComp = withSelectedMultiContext(ColumnTypeComp); @@ -103,7 +104,8 @@ export const columnChildrenMap = { borderWidth: withDefault(RadiusControl, ""), radius: withDefault(RadiusControl, ""), textSize: withDefault(RadiusControl, ""), - cellColor: CellColorComp, + cellColor: CellColorComp, + textOverflow: withDefault(TextOverflowControl, "ellipsis"), }; const StyledIcon = styled.span` @@ -228,6 +230,7 @@ export class ColumnComp extends ColumnInitComp { preInputNode: , placeholder: '14px', })} + {this.children.textOverflow.getPropertyView()} {this.children.cellColor.getPropertyView()} ); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx index 6db2445f2..65555c29f 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx @@ -51,7 +51,7 @@ import { lastValueIfEqual, shallowEqual } from "util/objectUtils"; import { IContainer } from "../containerBase"; import { getSelectedRowKeys } from "./selectionControl"; import { compTablePropertyView } from "./tablePropertyView"; -import { RowColorComp, TableChildrenView, TableInitComp } from "./tableTypes"; +import { RowColorComp, RowHeightComp, TableChildrenView, TableInitComp } from "./tableTypes"; import { useContext } from "react"; import { EditorContext } from "comps/editorState"; @@ -196,6 +196,17 @@ export class TableImplComp extends TableInitComp implements IContainer { }) ) ); + comp = comp.setChild( + "rowHeight", + comp.children.rowHeight.reduce( + RowHeightComp.changeContextDataAction({ + currentRow: nextRowExample, + currentIndex: 0, + currentOriginalIndex: 0, + columnTitle: nextRowExample ? Object.keys(nextRowExample)[0] : undefined, + }) + ) + ); } if (dataChanged) { diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx index 1c6891208..811d4998a 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx @@ -2,7 +2,7 @@ import { Table } from "antd"; import { TableProps } from "antd/es/table"; import { TableCellContext, TableRowContext } from "comps/comps/tableComp/tableContext"; import { TableToolbar } from "comps/comps/tableComp/tableToolbarComp"; -import { RowColorViewType } from "comps/comps/tableComp/tableTypes"; +import { RowColorViewType, RowHeightViewType } from "comps/comps/tableComp/tableTypes"; import { COL_MIN_WIDTH, COLUMN_CHILDREN_KEY, @@ -169,6 +169,10 @@ const TableWrapper = styled.div<{ border-top: none !important; border-inline-start: none !important; + &::after { + box-shadow: none !important; + } + .ant-table-content { overflow: unset !important; } @@ -280,8 +284,10 @@ const TableTh = styled.th<{ width?: number }>` const TableTd = styled.td<{ background: string; - $style: TableColumnStyleType; + $style: TableColumnStyleType & {rowHeight?: string}; $isEditing: boolean; + $tableSize?: string; + $autoHeight?: boolean; }>` .ant-table-row-expand-icon, .ant-table-row-indent { @@ -291,16 +297,42 @@ const TableTd = styled.td<{ 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; border-radius: ${(props) => props.$style.radius}; padding: 0 !important; - > div > div { + > div { color: ${(props) => props.$style.text}; font-size: ${(props) => props.$style.textSize}; + line-height: 21px; + + ${(props) => props.$tableSize === 'small' && ` + padding: 8.5px 8px; + min-height: ${props.$style.rowHeight || '39px'}; + ${!props.$autoHeight && ` + overflow-y: auto; + max-height: ${props.$style.rowHeight || '39px'}; + `}; + `}; + ${(props) => props.$tableSize === 'middle' && ` + padding: 12.5px 8px; + min-height: ${props.$style.rowHeight || '47px'}; + ${!props.$autoHeight && ` + overflow-y: auto; + max-height: ${props.$style.rowHeight || '47px'}; + `}; + `}; + ${(props) => props.$tableSize === 'large' && ` + padding: 16.5px 16px; + min-height: ${props.$style.rowHeight || '55px'}; + ${!props.$autoHeight && ` + overflow-y: auto; + max-height: ${props.$style.rowHeight || '55px'}; + `}; + `}; + &, > .ant-badge > .ant-badge-status-text, > div > .markdown-body { @@ -383,30 +415,40 @@ type CustomTableProps = Omit, "components" | columns: CustomColumnType[]; viewModeResizable: boolean; rowColorFn: RowColorViewType; + rowHeightFn: RowHeightViewType; columnsStyle: TableColumnStyleType; + size?: string; + rowAutoHeight?: boolean; }; function TableCellView(props: { record: RecordType; title: string; rowColorFn: RowColorViewType; + rowHeightFn: RowHeightViewType; cellColorFn: CellColorViewType; rowIndex: number; children: any; columnsStyle: TableColumnStyleType; columnStyle: TableColumnStyleType; + tableSize?: string; + autoHeight?: boolean; }) { const { record, title, rowIndex, rowColorFn, + rowHeightFn, cellColorFn, children, columnsStyle, columnStyle, + tableSize, + autoHeight, ...restProps } = props; + const [editing, setEditing] = useState(false); const rowContext = useContext(TableRowContext); let tdView; @@ -419,17 +461,24 @@ function TableCellView(props: { currentOriginalIndex: record[OB_ROW_ORI_INDEX], columnTitle: title, }); + const rowHeight = rowHeightFn({ + currentRow: record, + currentIndex: rowIndex, + currentOriginalIndex: record[OB_ROW_ORI_INDEX], + columnTitle: title, + }); const cellColor = cellColorFn({ currentCell: record[title.toLowerCase()], }); - const style: TableColumnStyleType = { + const style = { background: cellColor || rowColor || columnStyle.background || columnsStyle.background, text: columnStyle.text || columnsStyle.text, border: columnStyle.border || columnsStyle.border, radius: columnStyle.radius || columnsStyle.radius, borderWidth: columnStyle.borderWidth || columnsStyle.borderWidth, textSize: columnStyle.textSize || columnsStyle.textSize, + rowHeight: rowHeight, } let { background } = style; if (rowContext.selected) { @@ -444,6 +493,8 @@ function TableCellView(props: { background={background} $style={style} $isEditing={editing} + $tableSize={tableSize} + $autoHeight={autoHeight} > {children} @@ -511,10 +562,13 @@ function ResizeableTable(props: CustomTableProps ({ width: resizeWidth, @@ -583,6 +637,7 @@ export function TableCompView(props: { const compChildren = comp.children; const style = compChildren.style.getView(); const rowStyle = compChildren.rowStyle.getView(); + const rowAutoHeight = compChildren.rowAutoHeight.getView(); const columnsStyle = compChildren.columnsStyle.getView(); const changeSet = useMemo(() => compChildren.columns.getChangeSet(), [compChildren.columns]); const hasChange = useMemo(() => !_.isEmpty(changeSet), [changeSet]); @@ -610,7 +665,7 @@ export function TableCompView(props: { size, dynamicColumn, dynamicColumnConfig, - columnsAggrData + columnsAggrData, ), [ columnViews, @@ -711,6 +766,7 @@ export function TableCompView(props: { } }} rowColorFn={compChildren.rowColor.getView() as any} + rowHeightFn={compChildren.rowHeight.getView() as any} {...compChildren.selection.getView()(onEvent)} bordered={!compChildren.hideBordered.getView()} onChange={(pagination, filters, sorter, extra) => { @@ -722,6 +778,7 @@ export function TableCompView(props: { viewModeResizable={compChildren.viewModeResizable.getView()} dataSource={pageDataInfo.data} size={compChildren.size.getView()} + rowAutoHeight={rowAutoHeight} tableLayout="fixed" loading={ loading || diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx index 8ed0d09b0..14a18e140 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx @@ -504,11 +504,12 @@ export function compTablePropertyView {["layout", "both"].includes(editorModeStatus) && ( <>
- {comp.children.style.getPropertyView()} - + {comp.children.style.getPropertyView()}
{comp.children.rowStyle.getPropertyView()} + {comp.children.rowAutoHeight.getPropertyView()} + {comp.children.rowHeight.getPropertyView()} {comp.children.rowColor.getPropertyView()}
diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableToolbarComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableToolbarComp.tsx index ca60434e3..0fadfffc2 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableToolbarComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableToolbarComp.tsx @@ -54,14 +54,12 @@ const getStyle = ( 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: 13px 12px; - ${fixedToolbar && ` - position: sticky; - postion: -webkit-sticky; - z-index: 99; - `}; + position: sticky; + postion: -webkit-sticky; + left: 0; + + ${fixedToolbar && `z-index: 99;`}; ${fixedToolbar && position === 'below' && `bottom: 0;`}; ${fixedToolbar && position === 'above' && `top: 0;` }; diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableTypes.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableTypes.tsx index 144e61fb6..db7ddab68 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableTypes.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableTypes.tsx @@ -5,6 +5,7 @@ import { ArrayStringControl, BoolCodeControl, ColorOrBoolCodeControl, + HeightOrBoolCodeControl, JSONObjectArrayControl, RadiusControl, StringControl, @@ -108,7 +109,9 @@ const TableEventControl = eventHandlerControl(TableEventOptions); const rowColorLabel = trans("table.rowColor"); const RowColorTempComp = withContext( - new MultiCompBuilder({ color: ColorOrBoolCodeControl }, (props) => props.color) + new MultiCompBuilder({ + color: ColorOrBoolCodeControl, + }, (props) => props.color) .setPropertyViewFn((children) => children.color.propertyView({ label: rowColorLabel, @@ -134,6 +137,36 @@ export type RowColorViewType = (param: { columnTitle: string; }) => string; +const rowHeightLabel = trans("table.rowHeight"); +const RowHeightTempComp = withContext( + new MultiCompBuilder({ + height: HeightOrBoolCodeControl, + }, (props) => props.height) + .setPropertyViewFn((children) => + children.height.propertyView({ + label: rowHeightLabel, + tooltip: trans("table.rowColorDesc"), + }) + ) + .build(), + ["currentRow", "currentIndex", "currentOriginalIndex", "columnTitle"] as const +); + +// @ts-ignore +export class RowHeightComp extends RowHeightTempComp { + override getPropertyView() { + return controlItem({ filterText: rowHeightLabel }, super.getPropertyView()); + } +} + +// fixme, should be infer from RowHeightComp, but withContext type incorrect +export type RowHeightViewType = (param: { + currentRow: any; + currentIndex: number; + currentOriginalIndex: number | string; + columnTitle: string; +}) => string; + const tableChildrenMap = { hideBordered: BoolControl, hideHeader: BoolControl, @@ -157,6 +190,8 @@ const tableChildrenMap = { onEvent: TableEventControl, loading: BoolCodeControl, rowColor: RowColorComp, + rowAutoHeight: withDefault(AutoHeightControl, "auto"), + rowHeight: RowHeightComp, dynamicColumn: BoolPureControl, // todo: support object config dynamicColumnConfig: ArrayStringControl, diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx index e56f05f25..c2f73f76f 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx @@ -267,7 +267,7 @@ export function columnsToAntdFormat( size: string, dynamicColumn: boolean, dynamicColumnConfig: Array, - columnsAggrData: ColumnsAggrData + columnsAggrData: ColumnsAggrData, ): Array> { const sortMap: Map = new Map( sort.map((s) => [s.column, s.desc ? "descend" : "ascend"]) @@ -338,7 +338,12 @@ export function columnsToAntdFormat( String(record[OB_ROW_ORI_INDEX]) ) .getView() - .view({ editable: column.editable, size, candidateTags: tags, candidateStatus: status }); + .view({ + editable: column.editable, + size, candidateTags: tags, + candidateStatus: status, + textOverflow: column.textOverflow, + }); }, ...(column.sortable ? { diff --git a/client/packages/lowcoder/src/comps/controls/codeControl.tsx b/client/packages/lowcoder/src/comps/controls/codeControl.tsx index 27b9197c5..3b50b3318 100644 --- a/client/packages/lowcoder/src/comps/controls/codeControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/codeControl.tsx @@ -472,6 +472,26 @@ export const RadiusControl = codeControl( } ); +export const HeightOrBoolCodeControl = codeControl( + (value: unknown) => { + const valueString = toString(value); + if (valueString === "true") { + // true default 40px + return "40px"; + } + if (valueString === "" || valueString === "false") { + return ""; + } + if (/^[0-9]+(px|%)?$/.test(valueString)) { + return valueString; + } + throw new Error(`the argument must be a number(4), a number of pixels (4px), or a percent (50%).`); + }, + { + expectedType: "CSS", + } +); + export const FunctionControl = codeControl( (value) => { if (typeof value === "function") { diff --git a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx index 311a1fa56..c77f487a5 100644 --- a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx +++ b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx @@ -279,7 +279,7 @@ const TEXT_SIZE = { name: "textSize", label: trans("style.textSize"), textSize: "textSize", -} as const; +} as const; const CONTAINERHEADERPADDING = { name: "containerheaderpadding", diff --git a/client/packages/lowcoder/src/comps/controls/textOverflowControl.tsx b/client/packages/lowcoder/src/comps/controls/textOverflowControl.tsx new file mode 100644 index 000000000..b8a60e9da --- /dev/null +++ b/client/packages/lowcoder/src/comps/controls/textOverflowControl.tsx @@ -0,0 +1,29 @@ +import { trans } from "i18n"; +import { ControlParams } from "./controlParams"; +import { dropdownAbstractControl } from "./dropdownControl"; + +const overflowOptions = [ + { + label: trans("textOverflowProp.ellipsis"), + value: "ellipsis", + }, + { + label: trans("textOverflowProp.wrap"), + value: "wrap", + }, +] as const; + +const TextOverflowTmpControl = dropdownAbstractControl(overflowOptions, "ellipsis"); +export class TextOverflowControl extends TextOverflowTmpControl { + override getView() { + return this.value !== "ellipsis"; + } + + override getPropertyView() { + return this.propertyView({ label: trans("prop.textOverflow") }); + } + + override propertyView(params: ControlParams) { + return super.propertyView({ radioButton: true, ...params }); + } +} diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index f77fe975c..fa8b1aa3a 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -186,12 +186,17 @@ export const en = { "showBody": "Show Body", "showFooter": "Show Footer", "maskClosable": "Click Outside to Close", - "showMask": "Show Mask" + "showMask": "Show Mask", + "textOverflow": "Text Overflow", }, "autoHeightProp": { "auto": "Auto", "fixed": "Fixed" }, + "textOverflowProp": { + "ellipsis": "Ellipsis", + "wrap": "Wrap" + }, "labelProp": { "text": "Label", "tooltip": "Tooltip", @@ -1290,9 +1295,11 @@ export const en = { "sortChange": "Sort Change", "pageChange": "Page Change", "refresh": "Refresh", - "rowColor": "Conditional Row Color", + "rowColor": "Conditional row color", "rowColorDesc": "Conditionally Set the Row Color Based on the Optional Variables: CurrentRow, CurrentOriginalIndex, CurrentIndex, ColumnTitle. For Example: '{{ currentRow.id > 3 ? \"green\" : \"red\" }}'", - "cellColor": "Conditional Cell Color", + "rowHeight": "Conditional row height", + "rowHeightDesc": "Conditionally Set the Row Height Based on the Optional Variables: CurrentRow, CurrentOriginalIndex, CurrentIndex, ColumnTitle. For Example: '{{ currentRow.id > 3 ? \"60px\" : \"40px\" }}'", + "cellColor": "Conditional cell color", "cellColorDesc": "Conditionally Set the Cell Color Based on the Cell Value Using CurrentCell. For Example: '{{ currentCell == 3 ? \"green\" : \"red\" }}'", "saveChangesNotBind": "No Event Handler Configured for Saving Changes. Please Bind at Least One Event Handler Before Click.", "dynamicColumn": "Use Dynamic Column Setting",