From ecb96bc8f0fecbeb3921452dbec08d687cd28989 Mon Sep 17 00:00:00 2001 From: Thulasizwe Date: Mon, 19 Aug 2024 16:05:45 +0200 Subject: [PATCH 0001/1492] more properties and tabbed way of wrtting styles --- shesha-reactjs/package.json | 2 +- .../componentPropertiesPanel/index.tsx | 101 +++++++- .../_settings/settingsComponent.tsx | 39 +-- .../_settings/settingsControl.tsx | 9 +- .../_settings/styles/styles.ts | 6 +- .../components/background/background.tsx | 205 +++++++++++++++ .../components/background/interfaces.ts | 18 ++ .../components/background/sizeAndRepeat.tsx | 138 ++++++++++ .../components/background/utils.ts | 38 +++ .../components/fileProvider.tsx | 28 ++ .../components/imageUploader.tsx | 75 ++++++ .../styleBackground/index.tsx | 30 +++ .../styleBackground/settings.ts | 54 ++++ .../components/border/borderComponent.tsx | 116 +++++++++ .../components/border/interfaces.ts | 50 ++++ .../styleBorder/components/border/utils.ts | 33 +++ .../designer-components/styleBorder/index.tsx | 30 +++ .../styleBorder/settings.ts | 54 ++++ .../components/size/interfaces.ts | 19 ++ .../components/size/sizeComponent.tsx | 88 +++++++ .../styleDimensions/components/size/utils.ts | 24 ++ .../styleDimensions/index.tsx | 29 +++ .../styleDimensions/settings.ts | 54 ++++ .../components/font/fontComponent.tsx | 138 ++++++++++ .../styleFont/components/font/interfaces.ts | 16 ++ .../styleFont/components/font/utils.ts | 38 +++ .../designer-components/styleFont/index.tsx | 29 +++ .../designer-components/styleFont/settings.ts | 54 ++++ .../styleLabel/components/size/interfaces.ts | 19 ++ .../components/size/sizeComponent.tsx | 88 +++++++ .../styleLabel/components/size/utils.ts | 24 ++ .../designer-components/styleLabel/index.tsx | 30 +++ .../styleLabel/settings.ts | 54 ++++ .../styleShadow/components/size/interfaces.ts | 19 ++ .../components/size/sizeComponent.tsx | 88 +++++++ .../styleShadow/components/size/utils.ts | 24 ++ .../designer-components/styleShadow/index.tsx | 30 +++ .../styleShadow/settings.ts | 54 ++++ .../textField/interfaces.ts | 20 +- .../textField/settings.tsx | 242 ++++++++++++++++++ .../textField/settingsForm.json | 1 + .../textField/styles/styles.ts | 16 ++ .../textField/textField.tsx | 75 +++--- shesha-reactjs/src/providers/form/actions.tsx | 27 ++ 44 files changed, 2249 insertions(+), 77 deletions(-) create mode 100644 shesha-reactjs/src/designer-components/styleBackground/components/background/background.tsx create mode 100644 shesha-reactjs/src/designer-components/styleBackground/components/background/interfaces.ts create mode 100644 shesha-reactjs/src/designer-components/styleBackground/components/background/sizeAndRepeat.tsx create mode 100644 shesha-reactjs/src/designer-components/styleBackground/components/background/utils.ts create mode 100644 shesha-reactjs/src/designer-components/styleBackground/components/fileProvider.tsx create mode 100644 shesha-reactjs/src/designer-components/styleBackground/components/imageUploader.tsx create mode 100644 shesha-reactjs/src/designer-components/styleBackground/index.tsx create mode 100644 shesha-reactjs/src/designer-components/styleBackground/settings.ts create mode 100644 shesha-reactjs/src/designer-components/styleBorder/components/border/borderComponent.tsx create mode 100644 shesha-reactjs/src/designer-components/styleBorder/components/border/interfaces.ts create mode 100644 shesha-reactjs/src/designer-components/styleBorder/components/border/utils.ts create mode 100644 shesha-reactjs/src/designer-components/styleBorder/index.tsx create mode 100644 shesha-reactjs/src/designer-components/styleBorder/settings.ts create mode 100644 shesha-reactjs/src/designer-components/styleDimensions/components/size/interfaces.ts create mode 100644 shesha-reactjs/src/designer-components/styleDimensions/components/size/sizeComponent.tsx create mode 100644 shesha-reactjs/src/designer-components/styleDimensions/components/size/utils.ts create mode 100644 shesha-reactjs/src/designer-components/styleDimensions/index.tsx create mode 100644 shesha-reactjs/src/designer-components/styleDimensions/settings.ts create mode 100644 shesha-reactjs/src/designer-components/styleFont/components/font/fontComponent.tsx create mode 100644 shesha-reactjs/src/designer-components/styleFont/components/font/interfaces.ts create mode 100644 shesha-reactjs/src/designer-components/styleFont/components/font/utils.ts create mode 100644 shesha-reactjs/src/designer-components/styleFont/index.tsx create mode 100644 shesha-reactjs/src/designer-components/styleFont/settings.ts create mode 100644 shesha-reactjs/src/designer-components/styleLabel/components/size/interfaces.ts create mode 100644 shesha-reactjs/src/designer-components/styleLabel/components/size/sizeComponent.tsx create mode 100644 shesha-reactjs/src/designer-components/styleLabel/components/size/utils.ts create mode 100644 shesha-reactjs/src/designer-components/styleLabel/index.tsx create mode 100644 shesha-reactjs/src/designer-components/styleLabel/settings.ts create mode 100644 shesha-reactjs/src/designer-components/styleShadow/components/size/interfaces.ts create mode 100644 shesha-reactjs/src/designer-components/styleShadow/components/size/sizeComponent.tsx create mode 100644 shesha-reactjs/src/designer-components/styleShadow/components/size/utils.ts create mode 100644 shesha-reactjs/src/designer-components/styleShadow/index.tsx create mode 100644 shesha-reactjs/src/designer-components/styleShadow/settings.ts create mode 100644 shesha-reactjs/src/designer-components/textField/settings.tsx create mode 100644 shesha-reactjs/src/designer-components/textField/styles/styles.ts create mode 100644 shesha-reactjs/src/providers/form/actions.tsx diff --git a/shesha-reactjs/package.json b/shesha-reactjs/package.json index 1ec8e366d9..2053120102 100644 --- a/shesha-reactjs/package.json +++ b/shesha-reactjs/package.json @@ -166,7 +166,7 @@ "monaco-editor": "^0.50.0", "mustache": "^4.2.0", "nested-property": "^4.0.0", - "next": "^14.2.4", + "next": "^14.2.5", "number-to-words": "^1.2.4", "object-hash": "^3.0.0", "point-in-polygon": "^1.1.0", diff --git a/shesha-reactjs/src/components/formDesigner/componentPropertiesPanel/index.tsx b/shesha-reactjs/src/components/formDesigner/componentPropertiesPanel/index.tsx index a8c32eace8..6717ab35de 100644 --- a/shesha-reactjs/src/components/formDesigner/componentPropertiesPanel/index.tsx +++ b/shesha-reactjs/src/components/formDesigner/componentPropertiesPanel/index.tsx @@ -1,26 +1,113 @@ -import React, { FC } from 'react'; -import { Empty } from 'antd'; +import React, { FC, useState, useEffect, useCallback, useRef } from 'react'; +import { Empty, Input, Tabs } from 'antd'; import { useFormDesignerState } from '@/providers/formDesigner'; export interface IProps { } const ComponentPropertiesPanelInner: FC = () => { const { selectedComponentId, readOnly, settingsPanelRef } = useFormDesignerState(); + const [activeKey, setActiveKey] = useState('1'); + const [tabItems, setTabItems] = useState<{ label: string; key: string; children: React.ReactNode }[]>([]); + const containerRef = useRef(null); + + const hideOtherPanels = useCallback(() => { + if (!settingsPanelRef?.current) return; + + const panels = settingsPanelRef.current.querySelectorAll('.settings-group'); + + panels.forEach((panel, index) => { + const panelKey = (index + 1).toString(); + if (panelKey === activeKey) { + panel.style.display = 'block'; + panel.style.visibility = 'visible'; + panel.style.position = 'static'; + panel.style.width = 'auto'; + panel.style.height = 'auto'; + panel.style.overflow = 'auto'; + } else { + panel.style.display = 'block'; + panel.style.visibility = 'hidden'; + panel.style.position = 'absolute'; + panel.style.width = '0'; + panel.style.height = '0'; + panel.style.overflow = 'hidden'; + } + }); + + }, [activeKey, settingsPanelRef]); + + const search = useCallback((e: React.ChangeEvent) => { + const searchQuery = e.target.value; + const activePanel = settingsPanelRef.current.querySelector(`.settings-group:nth-child(${activeKey})`); + + if (activePanel) { + activePanel.querySelectorAll('.ant-form-item-label').forEach((label) => { + if (label.textContent?.toLowerCase().includes(searchQuery.toLowerCase())) { + label.parentElement.style.display = 'block'; + } else { + label.parentElement.style.display = 'none'; + } + }); + } + }, [activeKey, settingsPanelRef]); + + const getTabs = useCallback(() => { + if (!settingsPanelRef?.current || !selectedComponentId) return; + + const newTabItems: { label: string; key: string; children: React.ReactNode }[] = []; + + settingsPanelRef.current.querySelectorAll('.settings-group').forEach((panel, index) => { + const key = (index + 1).toString(); + const label = panel.classList[1].split(' ').map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); + + newTabItems.push({ + label, + key, + children: ( + <> + +
+ + ), + }); + }); + + setTabItems(newTabItems); + }, [selectedComponentId, settingsPanelRef, search]); + + useEffect(() => { + getTabs(); + hideOtherPanels(); + }, [selectedComponentId, getTabs, hideOtherPanels]); return ( <> - {!selectedComponentId && ( + {selectedComponentId ? ( + tabItems.length > 0 ? ( + { + setActiveKey(key); + hideOtherPanels(); + }} + items={tabItems} + /> + ) : null + ) : ( )} -
+
+
); }; -export const ComponentPropertiesPanel = React.memo(ComponentPropertiesPanelInner); \ No newline at end of file +export const ComponentPropertiesPanel = React.memo(ComponentPropertiesPanelInner); diff --git a/shesha-reactjs/src/designer-components/_settings/settingsComponent.tsx b/shesha-reactjs/src/designer-components/_settings/settingsComponent.tsx index de7f87e2f2..1f39f1fc8d 100644 --- a/shesha-reactjs/src/designer-components/_settings/settingsComponent.tsx +++ b/shesha-reactjs/src/designer-components/_settings/settingsComponent.tsx @@ -41,26 +41,27 @@ const SettingsComponent: IToolboxComponent = { }, [model.hidden, model?.readOnly, model?.id, actualSourceComponent]); if (model.hidden) return null; - + + console.log('SettingsComponent', model, component); return ( - - {(value, onChange) => ( - - {(_valueValue, _onChangeValue, propertyName) => { - return ( - - ); - }} - - )} - + + {(value, onChange) => ( + + {(_valueValue, _onChangeValue, propertyName) => { + return ( + + ); + }} + + )} + ); }, settingsFormMarkup: getSettings(), diff --git a/shesha-reactjs/src/designer-components/_settings/settingsControl.tsx b/shesha-reactjs/src/designer-components/_settings/settingsControl.tsx index 3e8bc1209f..dff32f3754 100644 --- a/shesha-reactjs/src/designer-components/_settings/settingsControl.tsx +++ b/shesha-reactjs/src/designer-components/_settings/settingsControl.tsx @@ -49,7 +49,7 @@ export const SettingsControl: FC = (props) => { return Promise.reject("AvailableConstantsExpression is mandatory"); const metadataBuilder = metadataBuilderFactory("baseProperties"); - + return executeScript(props.availableConstantsExpression, { data: formData, metadataBuilder }); }, [props.availableConstantsExpression, metadataBuilderFactory, formData]); @@ -81,7 +81,7 @@ export const SettingsControl: FC = (props) => { }; const propertyName = !!setting._code || setting._mode === 'code' ? `${props.propertyName}._value` : props.propertyName; - const functionName = `get${camelcase(props.propertyName, { pascalCase: true })}`; + const functionName = `get${camelcase(props.propertyName, { pascalCase: true })}`; const codeEditorProps: ICodeEditorProps = { readOnly: props.readOnly, @@ -97,14 +97,13 @@ export const SettingsControl: FC = (props) => { }; const editor = usePassedConstants - ? - : ; + ? + : ; return (
+ + ); + case 'url': + return ( + + + updateValue({ url: e.target.value })} + /> + + + ); + case 'upload': + return ( + + + + + + + ); + case 'storedFile': + return ( + <> + + + updateValue({ storedFile: { ...value?.storedFile, id: e.target.value } })} /> + + + + + updateValue({ storedFile: { ...value?.storedFile, ownerType: e } })} /> + + + + + updateValue({ storedFile: { ...value?.storedFile, ownerId: e.target.value } })} /> + + + + + updateValue({ storedFile: { ...value?.storedFile, fileCatergory: e.target.value } })} /> + + + + ); + default: + return ( + + + + updateValue({ color: color.toString() })} + /> + + + + ); + } + }; + + return ( + + + + + + + + + + + + + {renderBackgroundInput()} + + + ); +}; + +export default BackgroundConfigurator; diff --git a/shesha-reactjs/src/designer-components/styleBackground/components/background/interfaces.ts b/shesha-reactjs/src/designer-components/styleBackground/components/background/interfaces.ts new file mode 100644 index 0000000000..3cdd94bf34 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleBackground/components/background/interfaces.ts @@ -0,0 +1,18 @@ +import { IConfigurableFormComponent } from "@/providers"; + +export interface IBackgroundValue { + type: 'color' | 'url' | 'upload' | 'storedFile' | 'gradient'; + size?: 'cover' | 'contain' | 'auto' | string; + position: 'center' | 'top' | 'bottom' | 'left' | 'right' | 'top left' | 'top right' | 'bottom left' | 'bottom right' | string; + repeat?: 'no-repeat' | 'repeat' | 'repeat-x' | 'repeat-y'; + gradient?: { direction: string, colors: string[] }; + color?: string; + url?: string; + file?: string; + storedFile?: { id: string, ownerId: string, fileCatergory: string, ownerType: string }; +} + +export interface IBackgroundProps extends IConfigurableFormComponent { + onChange?: (value: IBackgroundValue) => void; + value?: IBackgroundValue; +} \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleBackground/components/background/sizeAndRepeat.tsx b/shesha-reactjs/src/designer-components/styleBackground/components/background/sizeAndRepeat.tsx new file mode 100644 index 0000000000..00edeff638 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleBackground/components/background/sizeAndRepeat.tsx @@ -0,0 +1,138 @@ +import React, { FC, useEffect, useState } from 'react'; +import { PlusOutlined } from '@ant-design/icons'; +import { Button, Col, Divider, Input, Select, Space } from 'antd'; +import SettingsFormItem from '@/designer-components/_settings/settingsFormItem'; +import { IBackgroundValue } from './interfaces'; + +type SizeAndRepeatProps = { + updateValue: (value: Partial) => void; + backgroundSize: IBackgroundValue['size']; + backgroundPosition: IBackgroundValue['position']; + backgroundRepeat: IBackgroundValue['repeat']; + readOnly?: boolean; +}; + +const units = ['px', '%', 'em', 'rem', 'vh', 'svh', 'vw', 'svw', 'auto']; +const { Option } = Select; + +const SizeAndRepeat: FC = ({ updateValue, backgroundSize, backgroundPosition, backgroundRepeat, readOnly }) => { + const defaultSizes = ['cover', 'contain', 'auto']; + const defaultPositions = ['center', 'top', 'left', 'right', 'bottom', 'top left', 'top right', 'bottom left', 'bottom right']; + + const [sizes, setSizes] = useState([...defaultSizes]); + const [positions, setPositions] = useState([...defaultPositions]); + + useEffect(() => { + if (backgroundSize && !sizes.includes(backgroundSize)) { + setSizes(() => [...defaultSizes, backgroundSize]); + } + if (backgroundPosition && !positions.includes(backgroundPosition)) { + setPositions(() => [...defaultPositions, backgroundPosition]); + } + }, [backgroundSize, backgroundPosition]); + + const [size, setSize] = useState({ width: { value: '', unit: 'px' }, height: { value: '', unit: 'px' } }); + const [position, setPosition] = useState({ width: { value: '', unit: 'px' }, height: { value: '', unit: 'px' } }); + + + const clearInputs = (type: 'size' | 'position') => { + const emptyState = { width: { value: '', unit: 'px' }, height: { value: '', unit: 'px' } }; + if (type === 'size') + setSize(emptyState); + else setPosition(emptyState); + }; + + const addItem = (type: 'size' | 'position') => { + const { width, height } = type === 'size' ? size : position; + const newValue = `${width.value}${width.unit} ${height.value}${height.unit}`; + + if (type === 'size') { + if (!sizes.includes(newValue)) { + setSizes(() => [...defaultSizes, newValue]); + } + updateValue({ size: newValue }); + } else { + if (!positions.includes(newValue)) { + setPositions(() => [...defaultPositions, newValue]); + } + updateValue({ position: newValue }); + } + + clearInputs(type); + }; + + const renderOptions = (menu, value, setValue, label: 'size' | 'position') => ( + <> + {menu} + + e.stopPropagation()}> + + {['width', 'height'].map((dim) => ( + setValue(prev => ({ ...prev, [dim]: { ...prev[dim], unit } }))} + onClick={(e) => e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + > + {units.map(unit => )} + + } + readOnly={readOnly} + value={value[dim].value} + onChange={(e) => setValue(prev => ({ ...prev, [dim]: { ...prev[dim], value: e.target.value } }))} + onClick={(e) => e.stopPropagation()} + /> + ))} + + + + + ); + + return ( + <> + {[ + { name: 'size', label: 'Size', value: backgroundSize, options: sizes, state: size, setState: setSize }, + { name: 'position', label: 'Position', value: backgroundPosition, options: positions, state: position, setState: setPosition }, + { + name: 'repeat', label: 'Repeat', value: backgroundRepeat, options: [ + { label: 'No repeat', value: 'no-repeat' }, + { label: 'Repeat', value: 'repeat' }, + { label: 'Repeat X', value: 'repeat-x' }, + { label: 'Repeat Y', value: 'repeat-y' }, + ] + } + ].map(({ name, label, value, options, state, setState }) => ( + + + e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + onChange={(unit) => onChange({ ...model, border: { ...value, border: { ...value.border, [activeBorder]: { ...value.border?.[activeBorder], unit } } } })}> + {units.map(unit => )} + ); + + return ( + + + + {renderRadioGroup(radiusOptions, activeRadius, 'Radius')} + + + + + + + + {renderRadioGroup(borderOptions, activeBorder, 'Border')} + + + + + + + + + + + + + {renderRadioGroup(styleOptions, activeBorder, 'border', 'style')} + + + + ); +}; + +export default BorderComponent; \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleBorder/components/border/interfaces.ts b/shesha-reactjs/src/designer-components/styleBorder/components/border/interfaces.ts new file mode 100644 index 0000000000..447bb89c39 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleBorder/components/border/interfaces.ts @@ -0,0 +1,50 @@ +import { IConfigurableFormComponent } from "@/providers"; + +export interface IBorderValue { + radius?: { + all?: number; + topLeft?: number; + topRight?: number; + bottomLeft?: number; + bottomRight?: number; + }; + border?: { + all?: { + width: number | string; + unit: string; + color: string; + style: string; + }; + top?: { + width: number | string; + unit: string; + color: string; + style: string; + }; + right?: { + width: number | string; + unit: string; + color: string; + style: string; + }; + bottom?: { + width: number | string; + unit: string; + color: string; + style: string; + }; + left?: { + width: number | string; + unit: string; + color: string; + style: string; + }; + }; + activeRadius: string; + activeBorder: string; +} + +export interface IBorderProps extends IConfigurableFormComponent { + onChange?: (value: IBorderValue) => void; + value?: IBorderValue; +} \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleBorder/components/border/utils.ts b/shesha-reactjs/src/designer-components/styleBorder/components/border/utils.ts new file mode 100644 index 0000000000..ce06f3363e --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleBorder/components/border/utils.ts @@ -0,0 +1,33 @@ +import { IBorderValue } from "./interfaces"; + +export const getBorderStyle = (input: IBorderValue): React.CSSProperties => { + if (!input) return {}; + + const style: React.CSSProperties = {}; + + // Handle border radius + if (input.radius) { + const { all, topLeft, topRight, bottomLeft, bottomRight } = input.radius; + style.borderRadius = `${all}px`; + style.borderRadius = `${topLeft || all || 6}px ${topRight || all || 6}px ${bottomRight || all || 6}px ${bottomLeft || all || 6}px`; + } + + // Handle border + if (input.border) { + const { all, top, right, bottom, left } = input.border; + + const handleBorderPart = (part, prefix: string) => { + if (part?.width) style[`${prefix}Width`] = `${part.width || 0}${part.unit || 'px'}`; + if (part?.style) style[`${prefix}Style`] = part.style || 'solid'; + if (part?.color) style[`${prefix}Color`] = part.color || 'black'; + }; + + handleBorderPart(all, 'border'); + handleBorderPart(top, 'borderTop'); + handleBorderPart(right, 'borderRight'); + handleBorderPart(bottom, 'borderBottom'); + handleBorderPart(left, 'borderLeft'); + } + + return style; +}; \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleBorder/index.tsx b/shesha-reactjs/src/designer-components/styleBorder/index.tsx new file mode 100644 index 0000000000..7c69bd7895 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleBorder/index.tsx @@ -0,0 +1,30 @@ + +import { IToolboxComponent } from '@/interfaces'; +import { DataTypes } from '@/interfaces/dataTypes'; +import { StrikethroughOutlined } from '@ant-design/icons'; +import React from 'react'; +import ConfigurableFormItem from '@/components/formDesigner/components/formItem'; +import { getSettings } from './settings'; +import { IBorderProps } from './components/border/interfaces'; +import BorderComponent from './components/border/borderComponent'; + +const BorderConfigurator: IToolboxComponent = { + type: 'border', + name: 'Border Configurator', + isInput: false, + icon: , + canBeJsSetting: true, + dataTypeSupported: ({ dataType }) => dataType === DataTypes.boolean, + Factory: ({ model: passedModel }) => { + const { size, ...model } = passedModel; + + return ( + + {(value, onChange) => } + + ); + }, + settingsFormMarkup: getSettings(), +}; + +export default BorderConfigurator; diff --git a/shesha-reactjs/src/designer-components/styleBorder/settings.ts b/shesha-reactjs/src/designer-components/styleBorder/settings.ts new file mode 100644 index 0000000000..c2ef37fed0 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleBorder/settings.ts @@ -0,0 +1,54 @@ +import { DesignerToolbarSettings } from '@/interfaces/toolbarSettings'; +import { nanoid } from '@/utils/uuid'; + +export const getSettings = () => + new DesignerToolbarSettings() + .addCollapsiblePanel({ + id: nanoid(), + propertyName: 'pnlDisplay', + parentId: 'root', + label: 'Display', + labelAlign: 'left', + expandIconPosition: 'start', + ghost: true, + collapsible: 'header', + content: { + id: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + components: [ + ...new DesignerToolbarSettings() + .addContextPropertyAutocomplete({ + id: nanoid(), + propertyName: 'propertyName', + label: 'Property name', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + validate: { + required: true, + }, + }) + .addTextField({ + id: nanoid(), + propertyName: 'label', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Label', + }) + .addCheckbox({ + id: nanoid(), + propertyName: 'hideLabel', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Hide Label', + labelAlign: 'right', + validate: {}, + }) + .addCheckbox({ + id: nanoid(), + propertyName: 'hidden', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Hidden', + labelAlign: 'right', + validate: {}, + }) + .toJson(), + ], + }, + }) + .toJson(); diff --git a/shesha-reactjs/src/designer-components/styleDimensions/components/size/interfaces.ts b/shesha-reactjs/src/designer-components/styleDimensions/components/size/interfaces.ts new file mode 100644 index 0000000000..d1c65408b9 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleDimensions/components/size/interfaces.ts @@ -0,0 +1,19 @@ +import { IConfigurableFormComponent } from "@/providers"; + +export interface ISizeComponentProps extends IConfigurableFormComponent { + value?: IValue; + onChange?: Function; +} + +export interface IInputUnits { + unit?: string; +} + +export interface IValue { + width?: number; + height?: number; + minWidth?: number; + minHeight?: number; + maxWidth?: number; + maxHeight?: number; +} \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleDimensions/components/size/sizeComponent.tsx b/shesha-reactjs/src/designer-components/styleDimensions/components/size/sizeComponent.tsx new file mode 100644 index 0000000000..5acfc660e3 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleDimensions/components/size/sizeComponent.tsx @@ -0,0 +1,88 @@ +import { Col, Input, Radio, Row, Select } from 'antd'; +import React, { FC } from 'react'; +import { BorderlessTableOutlined, ColumnWidthOutlined, EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons'; +import SettingsFormItem from '@/designer-components/_settings/settingsFormItem'; + +const { Option } = Select; + +const units = ['px', '%', 'em', 'rem', 'vh', 'svh', 'vw', 'svw', 'auto']; + +export interface IValueWithUnit { + value: number | string; + unit: string; +} + +export interface ISizeValue { + width?: IValueWithUnit; + height?: IValueWithUnit; + minWidth?: IValueWithUnit; + minHeight?: IValueWithUnit; + maxWidth?: IValueWithUnit; + maxHeight?: IValueWithUnit; + overflow?: string; +} + +export interface ISizeType { + onChange?: (value) => void; + value?: ISizeValue; + readOnly?: boolean; + model?: any; +} + +const SizeComponent: FC = ({ onChange, readOnly, value, model }) => { + + const updateValue = (key: keyof ISizeValue, newUnit: string) => { + const updatedValue = { + dimensions: { ...model?.dimensions, [key]: { ...value?.[key] as IValueWithUnit, unit: newUnit } } + }; + onChange(updatedValue); + }; + + const renderSizeInputWithUnits = (label: string, property: keyof ISizeValue) => { + const currentValue = value?.[property] && value?.[property] as IValueWithUnit || { value: '', unit: 'px' }; + + const selectAfter = ( + + ); + + return ( + + + + + + ); + }; + + return ( + + {renderSizeInputWithUnits('Width', 'width')} + {renderSizeInputWithUnits('Height', 'height')} + {renderSizeInputWithUnits('Min W', 'minWidth')} + {renderSizeInputWithUnits('Min H', 'minHeight')} + {renderSizeInputWithUnits('Max W', 'maxWidth')} + {renderSizeInputWithUnits('Max H', 'maxHeight')} + + + + + + + + + + + + ); +}; + +export default SizeComponent; \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleDimensions/components/size/utils.ts b/shesha-reactjs/src/designer-components/styleDimensions/components/size/utils.ts new file mode 100644 index 0000000000..669ae60fa3 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleDimensions/components/size/utils.ts @@ -0,0 +1,24 @@ +import { ISizeValue } from "./sizeComponent"; + +export const getSizeStyle = (input?: ISizeValue): React.CSSProperties => { + if (!input) return {}; + + const style: React.CSSProperties = {}; + const sizeProperties: (keyof ISizeValue)[] = ['width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight']; + + sizeProperties.forEach(prop => { + const sizeValue = input[prop]; + if (sizeValue && typeof sizeValue === 'object') { + const { value, unit } = sizeValue; + if (value) { + style[prop] = /^\d+(\.\d+)?$/.test(value as string) ? `${value}${unit ?? 'px'}` : `${value}`; + } + } + }); + + if (input.overflow) { + style.overflow = input.overflow; + }; + + return style; +}; diff --git a/shesha-reactjs/src/designer-components/styleDimensions/index.tsx b/shesha-reactjs/src/designer-components/styleDimensions/index.tsx new file mode 100644 index 0000000000..dfafe77707 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleDimensions/index.tsx @@ -0,0 +1,29 @@ + +import { IToolboxComponent } from '@/interfaces'; +import { DataTypes } from '@/interfaces/dataTypes'; +import { ColumnWidthOutlined } from '@ant-design/icons'; +import React from 'react'; +import ConfigurableFormItem from '@/components/formDesigner/components/formItem'; +import { getSettings } from './settings'; +import SizeComponent from './components/size/sizeComponent'; +import { ISizeComponentProps } from './components/size/interfaces'; + +const SizeConfigurator: IToolboxComponent = { + type: 'size', + name: 'Size', + isInput: false, + icon: , + canBeJsSetting: true, + dataTypeSupported: ({ dataType }) => dataType === DataTypes.boolean, + Factory: ({ model }) => { + + return ( + + {(value, onChange) => } + + ); + }, + settingsFormMarkup: getSettings(), +}; + +export default SizeConfigurator; diff --git a/shesha-reactjs/src/designer-components/styleDimensions/settings.ts b/shesha-reactjs/src/designer-components/styleDimensions/settings.ts new file mode 100644 index 0000000000..c2ef37fed0 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleDimensions/settings.ts @@ -0,0 +1,54 @@ +import { DesignerToolbarSettings } from '@/interfaces/toolbarSettings'; +import { nanoid } from '@/utils/uuid'; + +export const getSettings = () => + new DesignerToolbarSettings() + .addCollapsiblePanel({ + id: nanoid(), + propertyName: 'pnlDisplay', + parentId: 'root', + label: 'Display', + labelAlign: 'left', + expandIconPosition: 'start', + ghost: true, + collapsible: 'header', + content: { + id: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + components: [ + ...new DesignerToolbarSettings() + .addContextPropertyAutocomplete({ + id: nanoid(), + propertyName: 'propertyName', + label: 'Property name', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + validate: { + required: true, + }, + }) + .addTextField({ + id: nanoid(), + propertyName: 'label', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Label', + }) + .addCheckbox({ + id: nanoid(), + propertyName: 'hideLabel', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Hide Label', + labelAlign: 'right', + validate: {}, + }) + .addCheckbox({ + id: nanoid(), + propertyName: 'hidden', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Hidden', + labelAlign: 'right', + validate: {}, + }) + .toJson(), + ], + }, + }) + .toJson(); diff --git a/shesha-reactjs/src/designer-components/styleFont/components/font/fontComponent.tsx b/shesha-reactjs/src/designer-components/styleFont/components/font/fontComponent.tsx new file mode 100644 index 0000000000..93c1b89718 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleFont/components/font/fontComponent.tsx @@ -0,0 +1,138 @@ +import { Col, Input, Radio, Row, Select } from 'antd'; +import React, { FC } from 'react'; +import { AlignCenterOutlined, AlignLeftOutlined, AlignRightOutlined, PicCenterOutlined } from '@ant-design/icons'; +import SettingsFormItem from '@/designer-components/_settings/settingsFormItem'; +import { ColorPicker } from '@/components'; +import { IValueWithUnit } from '@/designer-components/styleDimensions/components/size/sizeComponent'; +import { IFontValue } from './interfaces'; + +const { Option } = Select; + +const fontTypes = [ + 'Arial', 'Helvetica', 'Times New Roman', 'Courier New', 'Verdana', + 'Georgia', 'Palatino', 'Garamond', 'Comic Sans MS', 'Trebuchet MS', + 'Arial Black', 'Impact', +]; + +const fontWeights = [ + { value: 100, title: 'thin' }, + { value: 200, title: 'extra-light' }, + { value: 300, title: 'light' }, + { value: 400, title: 'normal' }, + { value: 500, title: 'medium' }, + { value: 600, title: 'semi-bold' }, + { value: 700, title: 'bold' }, + { value: 800, title: 'extra-bold' }, + { value: 900, title: 'black' }, +]; + +const units = ['px', 'em', 'rem', '%']; + +const alignOptions = [ + { value: 'left', icon: , title: 'Left' }, + { value: 'center', icon: , title: 'Center' }, + { value: 'right', icon: , title: 'Right' }, + { value: 'justify', icon: , title: 'Justify' }, +]; + +interface IDropdownProps { + updateValue: (newValue: Partial) => void; + value: IFontValue; + options: any[]; + field: string; + labelField?: string; + valueField?: string; +} + +const Dropdown: FC = ({ updateValue, value, options, field, labelField = 'title', valueField = 'value' }) => ( + +); + + +export interface IFontType { + onChange?: (value: any) => void; + value?: IFontValue; + readOnly?: boolean; + model?: any; +} + +const FontComponent: FC = ({ onChange, readOnly, value, model }) => { + const updateValue = (newValue: Partial) => { + const updatedValue = { ...model, font: { ...value, ...newValue } }; + onChange(updatedValue); + }; + + const renderSizeInputWithUnits = (property, label) => { + const currentValue = value?.[property] || { value: '', unit: 'px' }; + + const selectAfter = ( + + ); + + return ( + + + updateValue({ [property]: { ...currentValue, value: e.target.value } })} + /> + + + ); + }; + + return ( + + + + {renderSizeInputWithUnits("size", "Font Size")} + {renderSizeInputWithUnits("lineHeight", "Line Height")} + + + + + + + + + + + ({ value: type }))} field="type" /> + + + + + + + + + + + updateValue({ align: e.target.value })}> + {alignOptions.map(({ value, icon, title }) => ( + {icon} + ))} + + + + + ); +}; + +export default FontComponent; \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleFont/components/font/interfaces.ts b/shesha-reactjs/src/designer-components/styleFont/components/font/interfaces.ts new file mode 100644 index 0000000000..31cd3d1bc2 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleFont/components/font/interfaces.ts @@ -0,0 +1,16 @@ +import { IValueWithUnit } from "@/designer-components/styleDimensions/components/size/sizeComponent"; +import { IConfigurableFormComponent } from "@/providers"; + +export interface IFontComponentProps extends IConfigurableFormComponent { + font?: IFontValue; + onChange?: Function; +} + +export interface IFontValue { + size?: IValueWithUnit; + type?: string; + weight?: string; + color?: string; + align?: AlignSetting; + lineHeight?: IValueWithUnit; +} \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleFont/components/font/utils.ts b/shesha-reactjs/src/designer-components/styleFont/components/font/utils.ts new file mode 100644 index 0000000000..5be1d2134a --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleFont/components/font/utils.ts @@ -0,0 +1,38 @@ +import { IFontValue } from "./interfaces"; + +export const getFontStyle = (input?: IFontValue): React.CSSProperties => { + if (!input) return {}; + + const style: React.CSSProperties = {}; + + if (input.size) { + const { value, unit } = input.size; + if (value) { + style.fontSize = /^\d+(\.\d+)?$/.test(value as string) ? `${value}${unit}` : value; + } + } + + if (input.lineHeight) { + style.lineHeight = `${input.lineHeight.value}${input.lineHeight.unit}`; + } + + if (input.type) { + style.fontFamily = input.type; + } + + if (input.weight) { + style.fontWeight = input.weight; + } + + if (input.color) { + style.color = input.color; + } + + if (input.align) { + console.log("Align::", input.align); + style.textAlign = input.align; + } + + console.log("Font Style::", input, style); + return style; +}; \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleFont/index.tsx b/shesha-reactjs/src/designer-components/styleFont/index.tsx new file mode 100644 index 0000000000..af83ae3186 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleFont/index.tsx @@ -0,0 +1,29 @@ + +import { IToolboxComponent } from '@/interfaces'; +import { DataTypes } from '@/interfaces/dataTypes'; +import { ColumnWidthOutlined, FileTextOutlined } from '@ant-design/icons'; +import React from 'react'; +import ConfigurableFormItem from '@/components/formDesigner/components/formItem'; +import { getSettings } from './settings'; +import { IFontComponentProps } from './components/font/interfaces'; +import FontComponent from './components/font/fontComponent'; + +const SizeConfigurator: IToolboxComponent = { + type: 'font', + name: 'Font', + isInput: false, + icon: , + canBeJsSetting: true, + dataTypeSupported: ({ dataType }) => dataType === DataTypes.boolean, + Factory: ({ model }) => { + + return ( + + {(value, onChange) => } + + ); + }, + settingsFormMarkup: getSettings(), +}; + +export default SizeConfigurator; diff --git a/shesha-reactjs/src/designer-components/styleFont/settings.ts b/shesha-reactjs/src/designer-components/styleFont/settings.ts new file mode 100644 index 0000000000..c2ef37fed0 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleFont/settings.ts @@ -0,0 +1,54 @@ +import { DesignerToolbarSettings } from '@/interfaces/toolbarSettings'; +import { nanoid } from '@/utils/uuid'; + +export const getSettings = () => + new DesignerToolbarSettings() + .addCollapsiblePanel({ + id: nanoid(), + propertyName: 'pnlDisplay', + parentId: 'root', + label: 'Display', + labelAlign: 'left', + expandIconPosition: 'start', + ghost: true, + collapsible: 'header', + content: { + id: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + components: [ + ...new DesignerToolbarSettings() + .addContextPropertyAutocomplete({ + id: nanoid(), + propertyName: 'propertyName', + label: 'Property name', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + validate: { + required: true, + }, + }) + .addTextField({ + id: nanoid(), + propertyName: 'label', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Label', + }) + .addCheckbox({ + id: nanoid(), + propertyName: 'hideLabel', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Hide Label', + labelAlign: 'right', + validate: {}, + }) + .addCheckbox({ + id: nanoid(), + propertyName: 'hidden', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Hidden', + labelAlign: 'right', + validate: {}, + }) + .toJson(), + ], + }, + }) + .toJson(); diff --git a/shesha-reactjs/src/designer-components/styleLabel/components/size/interfaces.ts b/shesha-reactjs/src/designer-components/styleLabel/components/size/interfaces.ts new file mode 100644 index 0000000000..d1c65408b9 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleLabel/components/size/interfaces.ts @@ -0,0 +1,19 @@ +import { IConfigurableFormComponent } from "@/providers"; + +export interface ISizeComponentProps extends IConfigurableFormComponent { + value?: IValue; + onChange?: Function; +} + +export interface IInputUnits { + unit?: string; +} + +export interface IValue { + width?: number; + height?: number; + minWidth?: number; + minHeight?: number; + maxWidth?: number; + maxHeight?: number; +} \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleLabel/components/size/sizeComponent.tsx b/shesha-reactjs/src/designer-components/styleLabel/components/size/sizeComponent.tsx new file mode 100644 index 0000000000..3dbb901fac --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleLabel/components/size/sizeComponent.tsx @@ -0,0 +1,88 @@ +import { Col, Input, Radio, Row, Select } from 'antd'; +import React, { FC } from 'react'; +import { BorderlessTableOutlined, ColumnWidthOutlined, EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons'; +import SettingsFormItem from '@/designer-components/_settings/settingsFormItem'; + +const { Option } = Select; + +const units = ['px', '%', 'em', 'rem', 'vh', 'svh', 'vw', 'svw', 'auto']; + +export interface ISizeValueWithUnit { + value: number | string; + unit: string; +} + +export interface ISizeValue { + width?: ISizeValueWithUnit; + height?: ISizeValueWithUnit; + minWidth?: ISizeValueWithUnit; + minHeight?: ISizeValueWithUnit; + maxWidth?: ISizeValueWithUnit; + maxHeight?: ISizeValueWithUnit; + overflow?: string; +} + +export interface ISizeType { + onChange?: (value) => void; + value?: ISizeValue; + readOnly?: boolean; + model?: any; +} + +const SizeComponent: FC = ({ onChange, readOnly, value, model }) => { + + const updateValue = (key: keyof ISizeValue, newUnit: string) => { + const updatedValue = { + dimensions: { ...model?.dimensions, [key]: { ...value?.[key] as ISizeValueWithUnit, unit: newUnit } } + }; + onChange(updatedValue); + }; + + const renderSizeInputWithUnits = (label: string, property: keyof ISizeValue) => { + const currentValue = value?.[property] && value?.[property] as ISizeValueWithUnit || { value: '', unit: 'px' }; + + const selectAfter = ( + + ); + + return ( + + + + + + ); + }; + + return ( + + {renderSizeInputWithUnits('Width', 'width')} + {renderSizeInputWithUnits('Height', 'height')} + {renderSizeInputWithUnits('Min W', 'minWidth')} + {renderSizeInputWithUnits('Min H', 'minHeight')} + {renderSizeInputWithUnits('Max W', 'maxWidth')} + {renderSizeInputWithUnits('Max H', 'maxHeight')} + + + + + + + + + + + + ); +}; + +export default SizeComponent; \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleLabel/components/size/utils.ts b/shesha-reactjs/src/designer-components/styleLabel/components/size/utils.ts new file mode 100644 index 0000000000..669ae60fa3 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleLabel/components/size/utils.ts @@ -0,0 +1,24 @@ +import { ISizeValue } from "./sizeComponent"; + +export const getSizeStyle = (input?: ISizeValue): React.CSSProperties => { + if (!input) return {}; + + const style: React.CSSProperties = {}; + const sizeProperties: (keyof ISizeValue)[] = ['width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight']; + + sizeProperties.forEach(prop => { + const sizeValue = input[prop]; + if (sizeValue && typeof sizeValue === 'object') { + const { value, unit } = sizeValue; + if (value) { + style[prop] = /^\d+(\.\d+)?$/.test(value as string) ? `${value}${unit ?? 'px'}` : `${value}`; + } + } + }); + + if (input.overflow) { + style.overflow = input.overflow; + }; + + return style; +}; diff --git a/shesha-reactjs/src/designer-components/styleLabel/index.tsx b/shesha-reactjs/src/designer-components/styleLabel/index.tsx new file mode 100644 index 0000000000..4b04787caf --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleLabel/index.tsx @@ -0,0 +1,30 @@ + +import { IToolboxComponent } from '@/interfaces'; +import { DataTypes } from '@/interfaces/dataTypes'; +import { ColumnWidthOutlined } from '@ant-design/icons'; +import React from 'react'; +import ConfigurableFormItem from '@/components/formDesigner/components/formItem'; +import { getSettings } from './settings'; +import SizeComponent from './components/size/sizeComponent'; +import { ISizeComponentProps } from './components/size/interfaces'; + +const SizeConfigurator: IToolboxComponent = { + type: 'size', + name: 'Size', + isInput: false, + icon: , + canBeJsSetting: true, + dataTypeSupported: ({ dataType }) => dataType === DataTypes.boolean, + Factory: ({ model: passedModel }) => { + const { size, ...model } = passedModel; + + return ( + + {(value, onChange) => } + + ); + }, + settingsFormMarkup: getSettings(), +}; + +export default SizeConfigurator; diff --git a/shesha-reactjs/src/designer-components/styleLabel/settings.ts b/shesha-reactjs/src/designer-components/styleLabel/settings.ts new file mode 100644 index 0000000000..c2ef37fed0 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleLabel/settings.ts @@ -0,0 +1,54 @@ +import { DesignerToolbarSettings } from '@/interfaces/toolbarSettings'; +import { nanoid } from '@/utils/uuid'; + +export const getSettings = () => + new DesignerToolbarSettings() + .addCollapsiblePanel({ + id: nanoid(), + propertyName: 'pnlDisplay', + parentId: 'root', + label: 'Display', + labelAlign: 'left', + expandIconPosition: 'start', + ghost: true, + collapsible: 'header', + content: { + id: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + components: [ + ...new DesignerToolbarSettings() + .addContextPropertyAutocomplete({ + id: nanoid(), + propertyName: 'propertyName', + label: 'Property name', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + validate: { + required: true, + }, + }) + .addTextField({ + id: nanoid(), + propertyName: 'label', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Label', + }) + .addCheckbox({ + id: nanoid(), + propertyName: 'hideLabel', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Hide Label', + labelAlign: 'right', + validate: {}, + }) + .addCheckbox({ + id: nanoid(), + propertyName: 'hidden', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Hidden', + labelAlign: 'right', + validate: {}, + }) + .toJson(), + ], + }, + }) + .toJson(); diff --git a/shesha-reactjs/src/designer-components/styleShadow/components/size/interfaces.ts b/shesha-reactjs/src/designer-components/styleShadow/components/size/interfaces.ts new file mode 100644 index 0000000000..d1c65408b9 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleShadow/components/size/interfaces.ts @@ -0,0 +1,19 @@ +import { IConfigurableFormComponent } from "@/providers"; + +export interface ISizeComponentProps extends IConfigurableFormComponent { + value?: IValue; + onChange?: Function; +} + +export interface IInputUnits { + unit?: string; +} + +export interface IValue { + width?: number; + height?: number; + minWidth?: number; + minHeight?: number; + maxWidth?: number; + maxHeight?: number; +} \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleShadow/components/size/sizeComponent.tsx b/shesha-reactjs/src/designer-components/styleShadow/components/size/sizeComponent.tsx new file mode 100644 index 0000000000..3dbb901fac --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleShadow/components/size/sizeComponent.tsx @@ -0,0 +1,88 @@ +import { Col, Input, Radio, Row, Select } from 'antd'; +import React, { FC } from 'react'; +import { BorderlessTableOutlined, ColumnWidthOutlined, EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons'; +import SettingsFormItem from '@/designer-components/_settings/settingsFormItem'; + +const { Option } = Select; + +const units = ['px', '%', 'em', 'rem', 'vh', 'svh', 'vw', 'svw', 'auto']; + +export interface ISizeValueWithUnit { + value: number | string; + unit: string; +} + +export interface ISizeValue { + width?: ISizeValueWithUnit; + height?: ISizeValueWithUnit; + minWidth?: ISizeValueWithUnit; + minHeight?: ISizeValueWithUnit; + maxWidth?: ISizeValueWithUnit; + maxHeight?: ISizeValueWithUnit; + overflow?: string; +} + +export interface ISizeType { + onChange?: (value) => void; + value?: ISizeValue; + readOnly?: boolean; + model?: any; +} + +const SizeComponent: FC = ({ onChange, readOnly, value, model }) => { + + const updateValue = (key: keyof ISizeValue, newUnit: string) => { + const updatedValue = { + dimensions: { ...model?.dimensions, [key]: { ...value?.[key] as ISizeValueWithUnit, unit: newUnit } } + }; + onChange(updatedValue); + }; + + const renderSizeInputWithUnits = (label: string, property: keyof ISizeValue) => { + const currentValue = value?.[property] && value?.[property] as ISizeValueWithUnit || { value: '', unit: 'px' }; + + const selectAfter = ( + + ); + + return ( + + + + + + ); + }; + + return ( + + {renderSizeInputWithUnits('Width', 'width')} + {renderSizeInputWithUnits('Height', 'height')} + {renderSizeInputWithUnits('Min W', 'minWidth')} + {renderSizeInputWithUnits('Min H', 'minHeight')} + {renderSizeInputWithUnits('Max W', 'maxWidth')} + {renderSizeInputWithUnits('Max H', 'maxHeight')} + + + + + + + + + + + + ); +}; + +export default SizeComponent; \ No newline at end of file diff --git a/shesha-reactjs/src/designer-components/styleShadow/components/size/utils.ts b/shesha-reactjs/src/designer-components/styleShadow/components/size/utils.ts new file mode 100644 index 0000000000..669ae60fa3 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleShadow/components/size/utils.ts @@ -0,0 +1,24 @@ +import { ISizeValue } from "./sizeComponent"; + +export const getSizeStyle = (input?: ISizeValue): React.CSSProperties => { + if (!input) return {}; + + const style: React.CSSProperties = {}; + const sizeProperties: (keyof ISizeValue)[] = ['width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight']; + + sizeProperties.forEach(prop => { + const sizeValue = input[prop]; + if (sizeValue && typeof sizeValue === 'object') { + const { value, unit } = sizeValue; + if (value) { + style[prop] = /^\d+(\.\d+)?$/.test(value as string) ? `${value}${unit ?? 'px'}` : `${value}`; + } + } + }); + + if (input.overflow) { + style.overflow = input.overflow; + }; + + return style; +}; diff --git a/shesha-reactjs/src/designer-components/styleShadow/index.tsx b/shesha-reactjs/src/designer-components/styleShadow/index.tsx new file mode 100644 index 0000000000..4b04787caf --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleShadow/index.tsx @@ -0,0 +1,30 @@ + +import { IToolboxComponent } from '@/interfaces'; +import { DataTypes } from '@/interfaces/dataTypes'; +import { ColumnWidthOutlined } from '@ant-design/icons'; +import React from 'react'; +import ConfigurableFormItem from '@/components/formDesigner/components/formItem'; +import { getSettings } from './settings'; +import SizeComponent from './components/size/sizeComponent'; +import { ISizeComponentProps } from './components/size/interfaces'; + +const SizeConfigurator: IToolboxComponent = { + type: 'size', + name: 'Size', + isInput: false, + icon: , + canBeJsSetting: true, + dataTypeSupported: ({ dataType }) => dataType === DataTypes.boolean, + Factory: ({ model: passedModel }) => { + const { size, ...model } = passedModel; + + return ( + + {(value, onChange) => } + + ); + }, + settingsFormMarkup: getSettings(), +}; + +export default SizeConfigurator; diff --git a/shesha-reactjs/src/designer-components/styleShadow/settings.ts b/shesha-reactjs/src/designer-components/styleShadow/settings.ts new file mode 100644 index 0000000000..c2ef37fed0 --- /dev/null +++ b/shesha-reactjs/src/designer-components/styleShadow/settings.ts @@ -0,0 +1,54 @@ +import { DesignerToolbarSettings } from '@/interfaces/toolbarSettings'; +import { nanoid } from '@/utils/uuid'; + +export const getSettings = () => + new DesignerToolbarSettings() + .addCollapsiblePanel({ + id: nanoid(), + propertyName: 'pnlDisplay', + parentId: 'root', + label: 'Display', + labelAlign: 'left', + expandIconPosition: 'start', + ghost: true, + collapsible: 'header', + content: { + id: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + components: [ + ...new DesignerToolbarSettings() + .addContextPropertyAutocomplete({ + id: nanoid(), + propertyName: 'propertyName', + label: 'Property name', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + validate: { + required: true, + }, + }) + .addTextField({ + id: nanoid(), + propertyName: 'label', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Label', + }) + .addCheckbox({ + id: nanoid(), + propertyName: 'hideLabel', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Hide Label', + labelAlign: 'right', + validate: {}, + }) + .addCheckbox({ + id: nanoid(), + propertyName: 'hidden', + parentId: '7d8493e3-141f-4507-a983-7f3ff7f2cf86', + label: 'Hidden', + labelAlign: 'right', + validate: {}, + }) + .toJson(), + ], + }, + }) + .toJson(); diff --git a/shesha-reactjs/src/designer-components/textField/interfaces.ts b/shesha-reactjs/src/designer-components/textField/interfaces.ts index 04d1c3f02d..8087ada6c5 100644 --- a/shesha-reactjs/src/designer-components/textField/interfaces.ts +++ b/shesha-reactjs/src/designer-components/textField/interfaces.ts @@ -1,4 +1,9 @@ import { IConfigurableFormComponent } from '@/providers/form/models'; +import { IBorderValue } from '../styleBorder/components/border/interfaces'; +import { IBackgroundValue } from '../styleBackground/components/background/interfaces'; +import { ISizeValue } from '../styleDimensions/components/size/sizeComponent'; +import { IFontComponentProps, IFontValue } from '../styleFont/components/font/interfaces'; +import { IFontType } from '../styleFont/components/font/fontComponent'; export type TextType = 'text' | 'password'; @@ -12,15 +17,8 @@ export interface ITextFieldComponentProps extends IConfigurableFormComponent { initialValue?: string; passEmptyStringByDefault?: boolean; textType?: TextType; - borderSize?: number; - borderRadius?: number; - borderType?: string; - borderColor?: string; - fontColor?: string; - fontWeight?: string | number; - fontSize?: string; - stylingBox?: string; - height?: string; - width?: string; - backgroundColor?: string; + border?: IBorderValue; + background?: IBackgroundValue; + dimensions?: ISizeValue; + font?: IFontValue; } diff --git a/shesha-reactjs/src/designer-components/textField/settings.tsx b/shesha-reactjs/src/designer-components/textField/settings.tsx new file mode 100644 index 0000000000..922e0b0048 --- /dev/null +++ b/shesha-reactjs/src/designer-components/textField/settings.tsx @@ -0,0 +1,242 @@ +import { CodeEditor } from '../codeEditor/codeEditor'; +import React, { FC, useCallback, useEffect, useRef } from 'react'; +import SettingsForm, { useSettingsForm } from '@/designer-components/_settings/settingsForm'; +import SettingsFormItem from '@/designer-components/_settings/settingsFormItem'; +import StyleBox from '../styleBox/components/box'; +import { Switch, Input, Select, Radio, Tabs } from 'antd'; +import { ISettingsFormFactoryArgs } from '@/interfaces'; +import { ITextFieldComponentProps } from './interfaces'; +import { IconPicker } from '@/components'; +import { ContextPropertyAutocomplete } from '../contextPropertyAutocomplete'; +import { useFormDesignerState } from '@/providers/formDesigner'; +import { useForm } from '@/providers'; +import TextArea from 'antd/lib/input/TextArea'; +import ReadOnlyModeSelector from '@/components/editModeSelector/index'; +import PermissionAutocomplete from '@/components/permissionAutocomplete'; +import SizeComponent from '../styleDimensions/components/size/sizeComponent'; +import BorderComponent from '../styleBorder/components/border/borderComponent'; +import BackgroundConfigurator from '../styleBackground/components/background/background'; +import FontComponent from '../styleFont/components/font/fontComponent'; +import { AlignLeftOutlined, AlignRightOutlined } from '@ant-design/icons'; + +const { TabPane } = Tabs; + +const TextFieldSettings: FC> = (props) => { + const { readOnly } = props; + const { Option } = Select; + const { model, onValuesChange } = useSettingsForm(); + + const designerModelType = useFormDesignerState(false)?.formSettings?.modelType; + const { formSettings } = useForm(); + + const settingsPanelRef = useRef(null); + + const search = useCallback((e: React.ChangeEvent, active) => { + const searchQuery = e.target.value; + const activePanel = settingsPanelRef.current.querySelector(`.${active}`); + + if (activePanel) { + activePanel.querySelectorAll('.ant-form-item-label').forEach((label) => { + if (label.textContent?.toLowerCase().includes(searchQuery.toLowerCase())) { + label.parentElement.style.display = 'block'; + } else { + label.parentElement.style.display = 'none'; + } + }); + } + }, [settingsPanelRef]); + + const renderSearchInput = (group) => { + return ( + search(e, group)} + />); + } + return ( +
+ + + {renderSearchInput('display')} + + + + + + + + onValuesChange({ labelAlign: e.target.value })}> + {[{ value: 'left', icon: }, { value: 'right', icon: }].map(({ value, icon }) => ( + {icon} + ))} + + + + + + + + + + + + + + + + +