Skip to content

[Fix]: #1626 Width Column Layout #1675

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

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
116 changes: 83 additions & 33 deletions client/packages/lowcoder/src/comps/comps/columnLayout/columnLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,34 @@ import { DisabledContext } from "comps/generators/uiCompBuilder";
import { SliderControl } from "@lowcoder-ee/comps/controls/sliderControl";
import { getBackgroundStyle } from "@lowcoder-ee/util/styleUtils";

// Extended ContainerStyleType for our specific needs
interface ExtendedContainerStyleType extends ContainerStyleType {
display?: string;
gridTemplateColumns?: string;
gridTemplateRows?: string;
columnGap?: string;
rowGap?: string;
[key: string]: string | undefined;
}

const ContainWrapper = styled.div<{
$style: ContainerStyleType & {
display: string,
gridTemplateColumns: string,
columnGap: string,
gridTemplateRows: string,
rowGap: string,
} | undefined;
$style: ExtendedContainerStyleType | undefined;
$useFlexLayout: boolean;
}>`
display: ${(props) => props.$style?.display};
grid-template-columns: ${(props) => props.$style?.gridTemplateColumns};
grid-template-rows: ${(props) => props.$style?.gridTemplateRows};
column-gap: ${(props) => props.$style?.columnGap};
row-gap: ${(props) => props.$style?.rowGap};
display: ${(props) => props.$useFlexLayout ? 'flex' : props.$style?.display};
flex-wrap: ${(props) => props.$useFlexLayout ? 'wrap' : 'nowrap'};

${(props) => !props.$useFlexLayout && `
grid-template-columns: ${props.$style?.gridTemplateColumns};
grid-template-rows: ${props.$style?.gridTemplateRows};
column-gap: ${props.$style?.columnGap};
row-gap: ${props.$style?.rowGap};
`}

${(props) => props.$useFlexLayout && `
column-gap: ${props.$style?.columnGap || '0'};
row-gap: ${props.$style?.rowGap || '0'};
`}

border-radius: ${(props) => props.$style?.radius};
border-width: ${(props) => props.$style?.borderWidth};
Expand All @@ -67,11 +81,29 @@ const ContainWrapper = styled.div<{
${props => props.$style && getBackgroundStyle(props.$style)}
`;

const ColWrapper = styled(Col)<{
const getColumnWidth = (column: any): string => {
// Use explicit width if available
if (column.width) {
return column.width;
}

// No explicit width - return auto to let flex handle it
return 'auto';
};

const ColWrapper = styled.div<{
$style: ResponsiveLayoutColStyleType | undefined,
$minWidth?: string,
$width: string,
$matchColumnsHeight: boolean,
$useFlexLayout: boolean,
$hasExplicitWidth: boolean,
}>`
${props => props.$useFlexLayout ? `
${props.$hasExplicitWidth
? `flex: 0 0 ${props.$width}; max-width: ${props.$width};`
: 'flex: 1 1 0%; min-width: 0;'}
` : ''}

> div {
height: ${(props) => props.$matchColumnsHeight ? `calc(100% - ${props.$style?.padding || 0} - ${props.$style?.padding || 0})` : 'auto'};
border-radius: ${(props) => props.$style?.radius};
Expand All @@ -95,12 +127,13 @@ const childrenMap = {
autoHeight: AutoHeightControl,
matchColumnsHeight: withDefault(BoolControl, true),
templateRows: withDefault(StringControl, "1fr"),
rowGap: withDefault(StringControl, "20px"),
rowGap: withDefault(StringControl, "0"),
templateColumns: withDefault(StringControl, "1fr 1fr"),
mainScrollbar: withDefault(BoolControl, false),
columnGap: withDefault(StringControl, "20px"),
columnGap: withDefault(StringControl, "0"),
style: styleControl(ContainerStyle, 'style'),
columnStyle: styleControl(ResponsiveLayoutColStyle , 'columnStyle')
columnStyle: styleControl(ResponsiveLayoutColStyle , 'columnStyle'),
useFlexLayout: withDefault(BoolControl, false),
};

type ViewProps = RecordConstructorToView<typeof childrenMap>;
Expand All @@ -122,7 +155,6 @@ const ColumnContainer = (props: ColumnContainerProps) => {
);
};


const ColumnLayout = (props: ColumnLayoutProps) => {
let {
columns,
Expand All @@ -135,35 +167,42 @@ const ColumnLayout = (props: ColumnLayoutProps) => {
columnGap,
columnStyle,
horizontalGridCells,
mainScrollbar
mainScrollbar,
useFlexLayout,
} = props;

return (
<BackgroundColorContext.Provider value={props.style.background}>
<DisabledContext.Provider value={props.disabled}>
<div style={{ height: "inherit", overflow: "auto"}}>
<ScrollBar style={{ margin: "0px", padding: "0px" }} overflow="scroll" hideScrollbar={!mainScrollbar}>
<ContainWrapper $style={{
...props.style,
display: "grid",
gridTemplateColumns: templateColumns,
columnGap,
gridTemplateRows: templateRows,
rowGap,
}}>
<ContainWrapper
$style={{
...props.style,
display: "grid",
gridTemplateColumns: templateColumns,
columnGap,
gridTemplateRows: templateRows,
rowGap,
}}
$useFlexLayout={useFlexLayout}
>
{columns.map(column => {
const id = String(column.id);
const childDispatch = wrapDispatch(wrapDispatch(dispatch, "containers"), id);
if(!containers[id]) return null
const containerProps = containers[id].children;
const noOfColumns = columns.length;
const columnWidth = getColumnWidth(column);
const hasExplicitWidth = !!column.width;

return (
<BackgroundColorContext.Provider value={props.columnStyle.background}>
<BackgroundColorContext.Provider key={id} value={props.columnStyle.background}>
<ColWrapper
key={id}
$style={props.columnStyle}
$minWidth={column.minWidth}
$width={columnWidth}
$matchColumnsHeight={matchColumnsHeight}
$useFlexLayout={useFlexLayout}
$hasExplicitWidth={hasExplicitWidth}
>
<ColumnContainer
layout={containerProps.layout.getView()}
Expand Down Expand Up @@ -200,6 +239,7 @@ export const ResponsiveLayoutBaseComp = (function () {
{children.columns.propertyView({
title: trans("responsiveLayout.column"),
newOptionLabel: trans("responsiveLayout.addColumn"),
useFlexLayout: children.useFlexLayout.getView(),
})}
</Section>

Expand All @@ -220,15 +260,25 @@ export const ResponsiveLayoutBaseComp = (function () {
{children.horizontalGridCells.propertyView({
label: trans('prop.horizontalGridCells'),
})}
{children.useFlexLayout.propertyView({
label: trans("responsiveLayout.useFlexLayout"),
tooltip: trans("responsiveLayout.useFlexLayoutTooltip")
})}
</Section>
<Section name={trans("responsiveLayout.columnsLayout")}>
{children.matchColumnsHeight.propertyView({ label: trans("responsiveLayout.matchColumnsHeight")
})}
{controlItem({}, (
<div style={{ marginTop: '8px' }}>{trans("responsiveLayout.columnsSpacing")}</div>
))}
{children.templateColumns.propertyView({label: trans("responsiveLayout.columnDefinition"), tooltip: trans("responsiveLayout.columnsDefinitionTooltip")})}
{children.templateRows.propertyView({label: trans("responsiveLayout.rowDefinition"), tooltip: trans("responsiveLayout.rowsDefinitionTooltip")})}
{!children.useFlexLayout.getView() && children.templateColumns.propertyView({
label: trans("responsiveLayout.columnDefinition"),
tooltip: trans("responsiveLayout.columnsDefinitionTooltip")
})}
{!children.useFlexLayout.getView() && children.templateRows.propertyView({
label: trans("responsiveLayout.rowDefinition"),
tooltip: trans("responsiveLayout.rowsDefinitionTooltip")
})}
{children.columnGap.propertyView({label: trans("responsiveLayout.columnGap")})}
{children.rowGap.propertyView({label: trans("responsiveLayout.rowGap")})}
</Section>
Expand Down
94 changes: 53 additions & 41 deletions client/packages/lowcoder/src/comps/controls/optionsControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { ControlItemCompBuilder } from "comps/generators/controlCompBuilder";
import { ColorControl } from "./colorControl";
import { StringStateControl } from "./codeStateControl";
import { reduceInContext } from "../utils/reduceContext";
import React from "react";

const OptionTypes = [
{
Expand All @@ -65,10 +66,13 @@ type OptionControlParam = {
title?: string;
// The new option's label name
newOptionLabel?: string;
// Whether to use flex layout (for column options)
useFlexLayout?: boolean;
};

type OptionPropertyParam = {
autoMap?: boolean;
useFlexLayout?: boolean;
};

interface OptionCompProperty {
Expand Down Expand Up @@ -176,7 +180,7 @@ export function manualOptionsControl<T extends OptionsControlType>(
itemTitle={(comp) => comp.children.label.getView()}
popoverTitle={() => trans("edit")}
content={(comp) => {
return hasPropertyView(comp) ? comp.propertyView({}) : comp.getPropertyView();
return hasPropertyView(comp) ? comp.propertyView({ useFlexLayout: param.useFlexLayout }) : comp.getPropertyView();
}}
items={manualComp.getView()}
onAdd={() => {
Expand Down Expand Up @@ -576,12 +580,13 @@ const StyledContent = styled.div`
}
`;

const ColumnOption = new MultiCompBuilder(
let ColumnOption = new MultiCompBuilder(
{
id: valueComp<number>(-1),
label: StringControl,
key: StringControl,
minWidth: withDefault(RadiusControl, ""),
width: withDefault(RadiusControl, ""),
background: withDefault(ColorControl, ""),
backgroundImage: withDefault(StringControl, ""),
border: withDefault(ColorControl, ""),
Expand All @@ -590,48 +595,55 @@ const ColumnOption = new MultiCompBuilder(
padding: withDefault(StringControl, ""),
},
(props) => props
)
.setPropertyViewFn((children) => (
<StyledContent>
{children.minWidth.propertyView({
label: trans('responsiveLayout.minWidth'),
preInputNode: <StyledIcon as={WidthIcon} title="" />,
placeholder: '3px',
})}
{children.background.propertyView({
label: trans('style.background'),
})}
{children.backgroundImage.propertyView({
label: `Background Image`,
// preInputNode: <StyledIcon as={ImageCompIcon} title="" />,
placeholder: 'https://temp.im/350x400',
})}
{children.border.propertyView({
label: trans('style.border')
})}
{children.radius.propertyView({
label: trans('style.borderRadius'),
preInputNode: <StyledIcon as={IconRadius} title="" />,
placeholder: '3px',
})}
{children.margin.propertyView({
label: trans('style.margin'),
preInputNode: <StyledIcon as={ExpandIcon} title="" />,
placeholder: '3px',
})}
{children.padding.propertyView({
label: trans('style.padding'),
preInputNode: <StyledIcon as={CompressIcon} title="" />,
placeholder: '3px',
})}
</StyledContent>
))
.build();
).build();

// Add propertyView method through class extension
ColumnOption = class extends ColumnOption implements OptionCompProperty {
propertyView(param: OptionPropertyParam) {
const useFlexLayout = param?.useFlexLayout !== undefined ? param.useFlexLayout : true;

return (
<StyledContent>
{useFlexLayout && this.children.width.propertyView({
label: trans('responsiveLayout.width'),
preInputNode: <StyledIcon as={WidthIcon} title="" />,
placeholder: '50%',
})}
{this.children.background.propertyView({
label: trans('style.background'),
})}
{this.children.backgroundImage.propertyView({
label: `Background Image`,
// preInputNode: <StyledIcon as={ImageCompIcon} title="" />,
placeholder: 'https://temp.im/350x400',
})}
{this.children.border.propertyView({
label: trans('style.border')
})}
{this.children.radius.propertyView({
label: trans('style.borderRadius'),
preInputNode: <StyledIcon as={IconRadius} title="" />,
placeholder: '3px',
})}
{this.children.margin.propertyView({
label: trans('style.margin'),
preInputNode: <StyledIcon as={ExpandIcon} title="" />,
placeholder: '3px',
})}
{this.children.padding.propertyView({
label: trans('style.padding'),
preInputNode: <StyledIcon as={CompressIcon} title="" />,
placeholder: '3px',
})}
</StyledContent>
);
}
};

export const ColumnOptionControl = manualOptionsControl(ColumnOption, {
initOptions: [
{ id: 0, key: "Column1", label: "Column1" },
{ id: 1, key: "Column2", label: "Column2" },
{ id: 0, key: "Column1", label: "Column1", width: "" },
{ id: 1, key: "Column2", label: "Column2", width: "" },
],
uniqField: "key",
autoIncField: "id",
Expand Down
4 changes: 4 additions & 0 deletions client/packages/lowcoder/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3910,6 +3910,8 @@ export const en = {
"rowStyle": "Row Style",
"columnStyle": "Column Style",
"minWidth": "Min. Width",
"width": "Width",
"widthTooltip": "Set the column width (e.g., '300px', '50%', '100%'). When set to 100%, columns will stack vertically.",
"rowBreak": "Row Break",
"useComponentWidth" : "Use Self Size",
"useComponentWidthDesc" : "Use the container width instead the App width",
Expand All @@ -3918,6 +3920,8 @@ export const en = {
"columnsLayout": "Columns Layout",
"columnsDefinitionTooltip": "Columns can be defined freely based on the CSS columns properties. For example, 'auto auto' will create two columns with equal width. Read more here: https://css-tricks.com/almanac/properties/g/grid-template-columns",
"rowsDefinitionTooltip": "Rows can be defined freely based on the CSS rows properties. For example, 'auto auto' will create two rows with equal height. Read more here: https://css-tricks.com/almanac/properties/g/grid-template-rows",
"useFlexLayout": "Use Flexible Layout",
"useFlexLayoutTooltip": "Enable responsive behavior where columns can wrap when there's not enough space"
},
"splitLayout" : {
"column": "View Areas",
Expand Down
Loading