diff --git a/client/packages/lowcoder-design/src/icons/index.ts b/client/packages/lowcoder-design/src/icons/index.ts index 27d9428fc..3e30692ea 100644 --- a/client/packages/lowcoder-design/src/icons/index.ts +++ b/client/packages/lowcoder-design/src/icons/index.ts @@ -216,6 +216,7 @@ export { ReactComponent as BorderRadiusIcon } from "./remix/rounded-corner.svg"; export { ReactComponent as ShadowIcon } from "./remix/shadow-line.svg"; export { ReactComponent as OpacityIcon } from "./remix/contrast-drop-2-line.svg"; export { ReactComponent as AnimationIcon } from "./remix/loader-line.svg"; +export { ReactComponent as LineHeightIcon } from "./remix/line-height.svg"; export { ReactComponent as LeftInfoLine } from "./remix/information-line.svg"; diff --git a/client/packages/lowcoder/src/api/commonSettingApi.ts b/client/packages/lowcoder/src/api/commonSettingApi.ts index d199cd9c5..1e7d23227 100644 --- a/client/packages/lowcoder/src/api/commonSettingApi.ts +++ b/client/packages/lowcoder/src/api/commonSettingApi.ts @@ -62,6 +62,7 @@ export interface ThemeDetail { animationDuration?: string; opacity?: string; boxShadow?: string; + lineHeight?: string; boxShadowColor?: string; animationIterationCount?: string; components?: Record; @@ -83,6 +84,7 @@ export function getThemeDetailName(key: keyof ThemeDetail) { case "padding": return trans("style.padding"); case "gridColumns": return trans("themeDetail.gridColumns"); case "textSize": return trans("style.textSize"); + case "lineHeight": return trans("themeDetail.lineHeight"); } return ""; } @@ -105,6 +107,7 @@ export function isThemeColorKey(key: string) { case "padding": case "gridColumns": case "textSize": + case "lineHeight": return true; } return false; diff --git a/client/packages/lowcoder/src/components/ThemeSettingsCompStyles.tsx b/client/packages/lowcoder/src/components/ThemeSettingsCompStyles.tsx index 9f2b0abe5..c34f5e5c8 100644 --- a/client/packages/lowcoder/src/components/ThemeSettingsCompStyles.tsx +++ b/client/packages/lowcoder/src/components/ThemeSettingsCompStyles.tsx @@ -25,6 +25,7 @@ import { TextStyleIcon, ImageCompIconSmall, RotationIcon, + LineHeightIcon } from "lowcoder-design/src/icons"; import { trans } from "i18n"; import { debounce } from "lodash"; @@ -375,6 +376,10 @@ export default function ThemeSettingsCompStyles(props: CompStyleProps) { icon = ; break; } + case 'lineHeight': { + icon = ; + break; + } } return icon; } diff --git a/client/packages/lowcoder/src/comps/comps/buttonComp/buttonCompConstants.tsx b/client/packages/lowcoder/src/comps/comps/buttonComp/buttonCompConstants.tsx index 377d662a9..e2517d306 100644 --- a/client/packages/lowcoder/src/comps/comps/buttonComp/buttonCompConstants.tsx +++ b/client/packages/lowcoder/src/comps/comps/buttonComp/buttonCompConstants.tsx @@ -65,6 +65,7 @@ export const Button100 = styled(Button)<{ $buttonStyle?: ButtonStyleType }>` overflow: hidden; text-overflow: ellipsis; } + line-height:${(props) => props.$buttonStyle?.lineHeight}; `; export const ButtonCompWrapper = styled.div<{ disabled: boolean }>` diff --git a/client/packages/lowcoder/src/comps/comps/buttonComp/dropdownComp.tsx b/client/packages/lowcoder/src/comps/comps/buttonComp/dropdownComp.tsx index 629314582..1788b1143 100644 --- a/client/packages/lowcoder/src/comps/comps/buttonComp/dropdownComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/buttonComp/dropdownComp.tsx @@ -60,6 +60,7 @@ const LeftButtonWrapper = styled.div<{ $buttonStyle: DropdownStyleType }>` ${(props) => `font-style: ${props.$buttonStyle.fontStyle};`} width: 100%; + line-height:${(props) => props.$buttonStyle.lineHeight}; } `; diff --git a/client/packages/lowcoder/src/comps/comps/numberInputComp/numberInputComp.tsx b/client/packages/lowcoder/src/comps/comps/numberInputComp/numberInputComp.tsx index ee736cfd2..655aa8e9a 100644 --- a/client/packages/lowcoder/src/comps/comps/numberInputComp/numberInputComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/numberInputComp/numberInputComp.tsx @@ -60,6 +60,7 @@ const getStyle = (style: InputLikeStyleType) => { return css` border-radius: ${style.radius}; border-width:${style.borderWidth} !important; + line-height: ${style.lineHeight} !important; // still use antd style when disabled &:not(.ant-input-number-disabled) { color: ${style.text}; diff --git a/client/packages/lowcoder/src/comps/comps/selectInputComp/checkboxComp.tsx b/client/packages/lowcoder/src/comps/comps/selectInputComp/checkboxComp.tsx index 0a29c71b3..90f1fbf68 100644 --- a/client/packages/lowcoder/src/comps/comps/selectInputComp/checkboxComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/selectInputComp/checkboxComp.tsx @@ -150,7 +150,7 @@ let CheckboxBasicComp = (function () { options: SelectInputOptionControl, style: styleControl(InputFieldStyle , 'style'), labelStyle: styleControl( - LabelStyle.filter((style) => ['accent', 'validate'].includes(style.name) === false), + LabelStyle.filter((style) => ['accent', 'validate', 'lineheight'].includes(style.name) === false), 'labelStyle', ), layout: dropdownControl(RadioLayoutOptions, "horizontal"), diff --git a/client/packages/lowcoder/src/comps/comps/selectInputComp/selectCompConstants.tsx b/client/packages/lowcoder/src/comps/comps/selectInputComp/selectCompConstants.tsx index db1f9f050..40ca4b668 100644 --- a/client/packages/lowcoder/src/comps/comps/selectInputComp/selectCompConstants.tsx +++ b/client/packages/lowcoder/src/comps/comps/selectInputComp/selectCompConstants.tsx @@ -204,6 +204,7 @@ const DropdownStyled = styled.div<{ $style: ChildrenMultiSelectStyleType }>` font-weight: ${props => props.$style?.textWeight}; text-transform: ${props => props.$style?.textTransform}; color: ${props => props.$style?.text}; + line-height: ${props => props.$style?.lineHeight}; } .option-label{ text-decoration: ${props => props.$style?.textDecoration} !important; diff --git a/client/packages/lowcoder/src/comps/comps/textComp.tsx b/client/packages/lowcoder/src/comps/comps/textComp.tsx index 0dd9c11c8..49e24b741 100644 --- a/client/packages/lowcoder/src/comps/comps/textComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/textComp.tsx @@ -64,6 +64,7 @@ const getStyle = (style: TextStyleType) => { h6 { color: ${style.text}; font-weight: ${style.textWeight} !important; + line-height:${style.lineHeight}; } img, pre { diff --git a/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx b/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx index f2212358e..2532912bc 100644 --- a/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx +++ b/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx @@ -252,6 +252,7 @@ export function getStyle(style: InputLikeStyleType, labelStyle?: LabelStyleType) text-decoration:${style.textDecoration}; background-color: ${style.background}; border-color: ${style.border}; + line-height: ${style.lineHeight}; &:focus, &.ant-input-affix-wrapper-focused { diff --git a/client/packages/lowcoder/src/comps/controls/labelControl.tsx b/client/packages/lowcoder/src/comps/controls/labelControl.tsx index 07a5c9ef5..ef93db58e 100644 --- a/client/packages/lowcoder/src/comps/controls/labelControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/labelControl.tsx @@ -108,6 +108,7 @@ const Label = styled.span<{ $border: boolean, $labelStyle: LabelStyleType, $vali border-radius:${(props) => props.$labelStyle.radius}; padding:${(props) => props.$labelStyle.padding}; margin:${(props) => props.$labelStyle.margin}; + line-height:${(props) => props.$labelStyle.lineHeight}; width: fit-content; user-select: text; white-space: nowrap; diff --git a/client/packages/lowcoder/src/comps/controls/styleControl.tsx b/client/packages/lowcoder/src/comps/controls/styleControl.tsx index 17201277e..a1fa7f993 100644 --- a/client/packages/lowcoder/src/comps/controls/styleControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/styleControl.tsx @@ -29,6 +29,7 @@ import { RefreshLineIcon, ShadowIcon, OpacityIcon, + LineHeightIcon } from 'lowcoder-design'; import { useContext } from "react"; import styled from "styled-components"; @@ -74,6 +75,7 @@ import { AnimationConfig, AnimationDelayConfig, AnimationDurationConfig, + LineHeightConfig, } from "./styleControlConstants"; @@ -89,6 +91,12 @@ import { inputFieldComps } from "@lowcoder-ee/constants/compConstants"; function isSimpleColorConfig(config: SingleColorConfig): config is SimpleColorConfig { return config.hasOwnProperty("color"); } +function isLineHeightConfig(config: SingleColorConfig): config is LineHeightConfig { + return config.hasOwnProperty("lineHeight"); +} +function isTextSizeConfig(config: SingleColorConfig): config is TextSizeConfig { + return config.hasOwnProperty("textSize"); +} function isDepColorConfig(config: SingleColorConfig): config is DepColorConfig { return config.hasOwnProperty("depName") || config.hasOwnProperty("depTheme"); @@ -157,9 +165,6 @@ function isFooterBackgroundImageOriginConfig(config: SingleColorConfig): config return config.hasOwnProperty("footerBackgroundImageOrigin"); } -function isTextSizeConfig(config: SingleColorConfig): config is TextSizeConfig { - return config.hasOwnProperty("textSize"); -} function isTextWeightConfig(config: SingleColorConfig): config is TextWeightConfig { return config.hasOwnProperty("textWeight"); @@ -237,7 +242,12 @@ export type StyleConfigType = { [K in Na function isEmptyColor(color: string) { return _.isEmpty(color); } - +function isEmptyLineHeight(lineHeight: string) { + return _.isEmpty(lineHeight); +} +function isEmptyTextSize(textSize: string) { + return _.isEmpty(textSize); +} function isEmptyRadius(radius: string) { return _.isEmpty(radius); } @@ -293,9 +303,7 @@ function isEmptyFooterBackgroundImageOriginConfig(footerBackgroundImageOrigin: s return _.isEmpty(footerBackgroundImageOrigin); } -function isEmptyTextSize(textSize: string) { - return _.isEmpty(textSize); -} + function isEmptyTextWeight(textWeight: string) { return _.isEmpty(textWeight); } @@ -375,6 +383,14 @@ function calcColors>( let res: Record = {}; colorConfigs.forEach((config) => { const name = config.name; + if (!isEmptyLineHeight(props[name]) && isLineHeightConfig(config)) { + res[name] = props[name]; + return; + } + if (!isEmptyTextSize(props[name]) && isTextSizeConfig(config)) { + res[name] = props[name]; + return; + } if (!isEmptyRadius(props[name]) && isRadiusConfig(config)) { res[name] = props[name]; return; @@ -448,10 +464,7 @@ function calcColors>( res[name] = props[name]; return; } - if (!isEmptyTextSize(props[name]) && isTextSizeConfig(config)) { - res[name] = props[name]; - return; - } + if (!isEmptyTextWeight(props[name]) && isTextWeightConfig(config)) { res[name] = props[name]; return; @@ -633,6 +646,10 @@ function calcColors>( if (isAnimationDurationConfig(config)) { res[name] = themeWithDefault[config.animationDuration] || '0s'; } + if (isLineHeightConfig(config)) { + + res[name] = themeWithDefault[config.lineHeight] || '20px'; + } }); // The second pass calculates dep colorConfigs.forEach((config) => { @@ -743,6 +760,11 @@ const StyleContent = styled.div` border-radius: 0 0 6px 6px; } `; +const LineHeightPropIcon = styled(LineHeightIcon)` + margin: 0 8px 0 -3px; + padding: 3px; + color: #888; +`; const MarginIcon = styled(ExpandIcon)` margin: 0 8px 0 2px; color: #888`; const PaddingIcon = styled(CompressIcon)` margin: 0 8px 0 2px; color: #888`; @@ -853,7 +875,8 @@ export function styleControl( name === 'containerHeaderPadding' || name === 'containerSiderPadding' || name === 'containerFooterPadding' || - name === 'containerBodyPadding' + name === 'containerBodyPadding' || + name === 'lineHeight' ) { childrenMap[name] = StringControl; } else { @@ -966,7 +989,8 @@ export function styleControl( name === 'footerBackgroundImageRepeat' || name === 'footerBackgroundImageSize' || name === 'footerBackgroundImagePosition' || - name === 'footerBackgroundImageOrigin' + name === 'footerBackgroundImageOrigin' || + name === 'lineHeight' ) { children[name]?.dispatchChangeValueAction(''); } else { @@ -1299,6 +1323,14 @@ export function styleControl( placeholder: props[name], }) + : name === 'lineHeight' // Added lineHeight here + ? ( + children[name] as InstanceType + ).propertyView({ + label: config.label, + preInputNode: , + placeholder: props[name], + }) : children[ name ].propertyView({ @@ -1330,4 +1362,5 @@ export function useStyle(colorConfigs: T props[config.name as Names] = ""; }); return calcColors(props, colorConfigs, theme?.theme, bgColor); + } diff --git a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx index 4cadf8483..47717d578 100644 --- a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx +++ b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx @@ -16,6 +16,11 @@ export type SimpleColorConfig = CommonColorConfig & { readonly color: string; }; +export type LineHeightConfig = CommonColorConfig & { + readonly lineHeight: string; // Define the lineHeight property +}; + + export type RadiusConfig = CommonColorConfig & { readonly radius: string; }; @@ -224,7 +229,10 @@ export type SingleColorConfig = | OpacityConfig | BoxShadowConfig | BoxShadowColorConfig - | AnimationIterationCountConfig; + | AnimationIterationCountConfig + | LineHeightConfig + + export const SURFACE_COLOR = "#FFFFFF"; const SECOND_SURFACE_COLOR = "#D7D9E0"; @@ -368,6 +376,18 @@ export function handleToCalendarToday(color: string) { return "#0000000c"; } } +export function getLineHeightValue(theme: ThemeDetail, value: string | number) { + if (typeof value === 'number') { + return `${value}px`; + } else { + const lineHeightValue = theme.lineHeight; + if (lineHeightValue) { + return lineHeightValue; + } else { + return value; // default line height value + } + } +} // return calendar text function handleCalendarText( @@ -516,6 +536,12 @@ const BACKGROUND_IMAGE_ORIGIN = { backgroundImageOrigin: "backgroundImageOrigin", } as const; +const LINE_HEIGHT = { + name: "lineHeight", + label: trans("style.lineHeight"), + lineHeight: "lineHeight", +} as const; + const MARGIN = { name: "margin", label: trans("style.margin"), @@ -645,6 +671,7 @@ const STYLING_FIELDS_SEQUENCE = [ RADIUS, BORDER_WIDTH, ROTATION, + LINE_HEIGHT ]; const STYLING_FIELDS_CONTAINER_SEQUENCE = [ @@ -658,6 +685,7 @@ const STYLING_FIELDS_CONTAINER_SEQUENCE = [ BOXSHADOW, BOXSHADOWCOLOR, ROTATION, + LINE_HEIGHT ]; export const AnimationStyle = [ @@ -751,7 +779,7 @@ function replaceAndMergeMultipleStyles( export const ButtonStyle = [ getBackground('primary'), - ...STYLING_FIELDS_SEQUENCE + ...STYLING_FIELDS_SEQUENCE.filter(style=>style.name!=='lineHeight'), ] as const; export const DropdownStyle = [ @@ -1116,10 +1144,9 @@ export const LabelStyle = [ export const InputFieldStyle = [ getBackground(), getStaticBorder(), - ...STYLING_FIELDS_CONTAINER_SEQUENCE.filter( - (style) => ["border"].includes(style.name) === false + ...STYLING_FIELDS_CONTAINER_SEQUENCE.filter( + (style) =>!["border", "lineHeight"].includes(style.name) ), - // ...STYLING_FIELDS_CONTAINER_SEQUENCE, ] as const; export const SignatureContainerStyle = [ @@ -1170,7 +1197,7 @@ export const SwitchStyle = [ ] as const; export const SelectStyle = [ - ...replaceAndMergeMultipleStyles(STYLING_FIELDS_SEQUENCE.filter(style=>style.name!=='rotation'), "border", [ + ...replaceAndMergeMultipleStyles(STYLING_FIELDS_SEQUENCE.filter(style=>style.name!=='rotation' && style.name !== 'lineHeight'), "border", [ ...getStaticBgBorderRadiusByBg(SURFACE_COLOR, "pc"), ]), BOXSHADOW, @@ -1179,7 +1206,7 @@ export const SelectStyle = [ ] as const; const multiSelectCommon = [ - ...replaceAndMergeMultipleStyles(STYLING_FIELDS_SEQUENCE.filter(style=>style.name!=='rotation'), "border", [ + ...replaceAndMergeMultipleStyles(STYLING_FIELDS_SEQUENCE.filter(style=>style.name!=='rotation' && style.name !== 'lineHeight'), "border", [ ...getStaticBgBorderRadiusByBg(SURFACE_COLOR, "pc"), ]), { @@ -1291,7 +1318,7 @@ function checkAndUncheck() { } export const CheckboxStyle = [ - ...replaceAndMergeMultipleStyles(STYLING_FIELDS_SEQUENCE.filter(styles=>styles.name!=='rotation'), "text", [ + ...replaceAndMergeMultipleStyles(STYLING_FIELDS_SEQUENCE.filter(styles=>styles.name!=='rotation' && styles.name !== 'lineHeight'),"text", [ STATIC_TEXT, VALIDATE, ]).filter((style) => style.name !== "border"), @@ -1309,7 +1336,7 @@ export const CheckboxStyle = [ ] as const; export const RadioStyle = [ - ...replaceAndMergeMultipleStyles(STYLING_FIELDS_SEQUENCE.filter(style=>style.name!=='rotation'), "text", [ + ...replaceAndMergeMultipleStyles(STYLING_FIELDS_SEQUENCE.filter(style=>style.name!=='rotation'&& style.name !== 'lineHeight'), "text", [ STATIC_TEXT, VALIDATE, ]).filter((style) => style.name !== "border" && style.name !== "radius"), @@ -1554,7 +1581,7 @@ export const IframeStyle = [ BORDER_WIDTH, MARGIN, PADDING, - ROTATION + ROTATION, ] as const; export const CustomStyle = [ diff --git a/client/packages/lowcoder/src/constants/themeConstants.ts b/client/packages/lowcoder/src/constants/themeConstants.ts index 977db98fd..6f1e4e7a5 100644 --- a/client/packages/lowcoder/src/constants/themeConstants.ts +++ b/client/packages/lowcoder/src/constants/themeConstants.ts @@ -12,6 +12,7 @@ const theme = { borderStyle: "solid", margin: "3px", padding: "3px", + lineHeight: "18px", gridColumns: "24", textSize: "14px", // text: "#222222", @@ -33,7 +34,7 @@ const text = { const input = { style: { borderWidth: '0px', - background: 'transparent', + background: 'transparent', }, labelStyle: { borderWidth: '0px', @@ -41,6 +42,7 @@ const input = { inputFieldStyle: { // borderWidth: '1px', border: theme.border, + lineHeight: theme.lineHeight, } }; diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 8e6c83f0b..2d26d0a17 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -545,6 +545,7 @@ export const en = { "headerText": "Header Text Color", "labelColor": "Label Color", "label": "Label Color", + "lineHeight":"Line Height", "subTitleColor": "SubTitle Color", "titleText": "Title Color", "success": "Success Color",