From bdb7748cf315365a5759f9bbaca098a6f435fe93 Mon Sep 17 00:00:00 2001 From: Marcus Notheis Date: Thu, 9 Feb 2023 07:56:02 +0100 Subject: [PATCH 01/10] refactor(Form): simplify logic --- .eslintrc.yml | 2 +- .../main/src/components/Form/FormContext.ts | 7 + packages/main/src/components/Form/index.tsx | 138 +++++++----------- .../components/FormGroup/FormGroupTitle.tsx | 43 ++++++ .../main/src/components/FormGroup/index.tsx | 20 +-- .../main/src/components/FormItem/index.tsx | 21 +-- 6 files changed, 115 insertions(+), 116 deletions(-) create mode 100644 packages/main/src/components/Form/FormContext.ts create mode 100644 packages/main/src/components/FormGroup/FormGroupTitle.tsx diff --git a/.eslintrc.yml b/.eslintrc.yml index 43be75f5ea0..413f8937411 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -10,11 +10,11 @@ env: es2022: true extends: - plugin:react/recommended + - plugin:react-hooks/recommended - plugin:import/recommended - plugin:prettier/recommended plugins: - prefer-arrow - - react-hooks settings: react: version: detect diff --git a/packages/main/src/components/Form/FormContext.ts b/packages/main/src/components/Form/FormContext.ts new file mode 100644 index 00000000000..c447321bc62 --- /dev/null +++ b/packages/main/src/components/Form/FormContext.ts @@ -0,0 +1,7 @@ +import { createContext, useContext } from 'react'; + +export const FormContext = createContext({ labelSpan: null }); + +export function useFormContext() { + return useContext(FormContext); +} diff --git a/packages/main/src/components/Form/index.tsx b/packages/main/src/components/Form/index.tsx index 3cbd341ff3a..76905337b40 100644 --- a/packages/main/src/components/Form/index.tsx +++ b/packages/main/src/components/Form/index.tsx @@ -1,6 +1,6 @@ 'use client'; -import { CssSizeVariables, Device, ThemingParameters, useSyncRef } from '@ui5/webcomponents-react-base'; +import { Device, useSyncRef } from '@ui5/webcomponents-react-base'; import { clsx } from 'clsx'; import React, { Children, @@ -19,7 +19,9 @@ import { createUseStyles } from 'react-jss'; import { FormBackgroundDesign, TitleLevel } from '../../enums'; import { CommonProps } from '../../interfaces/CommonProps'; import { Title } from '../../webComponents/Title'; +import { FormGroupTitle } from '../FormGroup/FormGroupTitle'; import { styles } from './Form.jss'; +import { FormContext } from './FormContext'; export interface FormPropTypes extends CommonProps { /** @@ -107,17 +109,6 @@ export interface FormPropTypes extends CommonProps { as?: keyof HTMLElementTagNameMap; } -const clonedChildrenForSingleColumn = (reactChildren, currentLabelSpan) => - Children.map(reactChildren, (child) => { - if (child?.type?.displayName === 'FormItem') { - return cloneElement(child, { labelSpan: currentLabelSpan }); - } - if (child?.type?.displayName === 'FormGroup') { - return cloneElement(child, { children: clonedChildrenForSingleColumn(child.props.children, currentLabelSpan) }); - } - return child; - }); - const useStyles = createUseStyles(styles, { name: 'Form' }); /** * The `Form` component arranges labels and fields into groups and rows. There are different ways to visualize forms for different screen sizes. @@ -125,18 +116,18 @@ const useStyles = createUseStyles(styles, { name: 'Form' }); */ const Form = forwardRef((props, ref) => { const { - as, - backgroundDesign, + as = 'form', + backgroundDesign = FormBackgroundDesign.Transparent, children, - columnsS, - columnsM, - columnsL, - columnsXL, + columnsS = 1, + columnsM = 1, + columnsL = 1, + columnsXL = 2, className, - labelSpanS, - labelSpanM, - labelSpanL, - labelSpanXL, + labelSpanS = 12, + labelSpanM = 2, + labelSpanL = 4, + labelSpanXL = 4, slot, titleText, style, @@ -176,33 +167,29 @@ const Form = forwardRef((props, ref) => { return () => { observer.disconnect(); }; - }, [formRef, setCurrentRange, lastRange]); + }, [formRef]); const classes = useStyles(); const currentNumberOfColumns = columnsMap.get(currentRange); const currentLabelSpan = labelSpanMap.get(currentRange); - const [formGroups, updatedTitle] = useMemo(() => { - const computedFormGroups = []; - if (Children.count(children) === 1 && !titleText) { - const singleChild = (Array.isArray(children) ? children[0] : children) as ReactElement; - if (singleChild?.props?.title?.length > 0) { - return [cloneElement(singleChild, { title: null }), singleChild.props.title]; - } + const formGroups = useMemo(() => { + if (currentNumberOfColumns === 1) { + return children; } - const currentColumnCount = currentNumberOfColumns; - if (currentColumnCount === 1) { - return [clonedChildrenForSingleColumn(children, currentLabelSpan), titleText]; - } - - const rows = []; + const computedFormGroups = []; const childrenArray = Children.toArray(children); - const estimatedNumberOfGroupRows = childrenArray.length / currentColumnCount; - for (let i = 0; i < estimatedNumberOfGroupRows; i++) { - rows[i] = childrenArray.slice(i * currentColumnCount, i * currentColumnCount + currentColumnCount); - } + const rows = childrenArray.reduce((acc, val, idx) => { + const columnIndex = Math.floor(idx / currentNumberOfColumns); + if (!acc[columnIndex]) { + acc[columnIndex] = [val]; + } else { + acc[columnIndex].push(val); + } + return acc; + }, []) as ReactElement[][]; const maxRowsPerRow: number[] = []; rows.forEach((rowGroup: ReactElement[], rowIndex) => { @@ -224,30 +211,16 @@ const Form = forwardRef((props, ref) => { const titleStyles: CSSProperties = { gridColumnEnd: 'span 12', gridColumnStart: columnIndex * 12 + 1, - gridRowStart: totalRowCount, - display: 'flex', - alignItems: 'center', - fontFamily: ThemingParameters.sapFontFamily, - height: CssSizeVariables.sapWcrFormGroupTitleHeight, - lineHeight: CssSizeVariables.sapWcrFormGroupTitleHeight, - color: ThemingParameters.sapTextColor, - fontSize: ThemingParameters.sapFontSize, - fontWeight: 'bold', - backgroundColor: ThemingParameters.sapGroup_TitleBackground, - margin: 0, - paddingTop: '1rem' + gridRowStart: totalRowCount }; if (cell?.props?.titleText) { computedFormGroups.push( -
- {cell.props.titleText} -
+ /> ); } @@ -278,8 +251,8 @@ const Form = forwardRef((props, ref) => { } }); - return [computedFormGroups, titleText]; - }, [children, currentRange, titleText, currentNumberOfColumns, currentLabelSpan]); + return computedFormGroups; + }, [children, currentNumberOfColumns, currentLabelSpan]); const formClassNames = clsx( classes.form, @@ -290,37 +263,26 @@ const Form = forwardRef((props, ref) => { const CustomTag = as as ElementType; return ( - - {updatedTitle && ( - - {updatedTitle} - - )} - {formGroups} - + + + {titleText && ( + + {titleText} + + )} + {formGroups} + + ); }); Form.displayName = 'Form'; -Form.defaultProps = { - as: 'form', - backgroundDesign: FormBackgroundDesign.Transparent, - columnsS: 1, - columnsM: 1, - columnsL: 1, - columnsXL: 2, - labelSpanS: 12, - labelSpanM: 2, - labelSpanL: 4, - labelSpanXL: 4 -}; - export { Form }; diff --git a/packages/main/src/components/FormGroup/FormGroupTitle.tsx b/packages/main/src/components/FormGroup/FormGroupTitle.tsx new file mode 100644 index 00000000000..b32a9aa7faa --- /dev/null +++ b/packages/main/src/components/FormGroup/FormGroupTitle.tsx @@ -0,0 +1,43 @@ +import { CssSizeVariables, ThemingParameters } from '@ui5/webcomponents-react-base'; +import React, { CSSProperties } from 'react'; +import { createUseStyles } from 'react-jss'; + +interface FormGroupTitlePropTypes { + titleText: string; + + style?: CSSProperties; +} + +const useStyles = createUseStyles( + { + title: { + gridColumn: 'span 12', + display: 'flex', + alignItems: 'center', + height: CssSizeVariables.sapWcrFormGroupTitleHeight, + lineHeight: CssSizeVariables.sapWcrFormGroupTitleHeight, + fontFamily: ThemingParameters.sapFontHeaderFamily, + color: ThemingParameters.sapGroup_TitleTextColor, + fontSize: ThemingParameters.sapFontHeader6Size, + fontWeight: 'bold', + backgroundColor: ThemingParameters.sapGroup_TitleBackground, + margin: 0, + marginBlockStart: '1rem' + } + }, + { name: 'FormGroupTitle' } +); +export function FormGroupTitle({ titleText, style }: FormGroupTitlePropTypes) { + const classes = useStyles(); + return ( +
+ {titleText} +
+ ); +} diff --git a/packages/main/src/components/FormGroup/index.tsx b/packages/main/src/components/FormGroup/index.tsx index 0e3fd1c7626..2e166828c32 100644 --- a/packages/main/src/components/FormGroup/index.tsx +++ b/packages/main/src/components/FormGroup/index.tsx @@ -1,6 +1,6 @@ -import { CssSizeVariables, ThemingParameters } from '@ui5/webcomponents-react-base'; import React, { FC, ReactNode } from 'react'; import { createUseStyles } from 'react-jss'; +import { FormGroupTitle } from './FormGroupTitle'; export interface FormGroupPropTypes { /** @@ -17,20 +17,6 @@ export interface FormGroupPropTypes { const useStyles = createUseStyles( { - title: { - gridColumn: 'span 12', - display: 'flex', - alignItems: 'center', - height: CssSizeVariables.sapWcrFormGroupTitleHeight, - lineHeight: CssSizeVariables.sapWcrFormGroupTitleHeight, - fontFamily: ThemingParameters.sapFontHeaderFamily, - color: ThemingParameters.sapGroup_TitleTextColor, - fontSize: ThemingParameters.sapFontHeader6Size, - fontWeight: 'bold', - backgroundColor: ThemingParameters.sapGroup_TitleBackground, - margin: 0, - marginBlockStart: '1rem' - }, spacer: { height: '1rem', gridColumn: 'span 12' } }, { name: 'FormGroup' } @@ -46,9 +32,7 @@ const FormGroup: FC = (props: FormGroupPropTypes) => { return ( <> -
- {titleText} -
+ {children} diff --git a/packages/main/src/components/FormItem/index.tsx b/packages/main/src/components/FormItem/index.tsx index 8a9581a34ee..37f60ff699b 100644 --- a/packages/main/src/components/FormItem/index.tsx +++ b/packages/main/src/components/FormItem/index.tsx @@ -5,6 +5,7 @@ import { createUseStyles } from 'react-jss'; import { FlexBoxAlignItems, FlexBoxDirection, WrappingType } from '../../enums'; import { Label, LabelPropTypes } from '../../webComponents/Label'; import { FlexBox } from '../FlexBox'; +import { useFormContext } from '../Form/FormContext'; export interface FormItemPropTypes { /** @@ -19,7 +20,6 @@ export interface FormItemPropTypes { interface InternalProps extends FormItemPropTypes { columnIndex?: number; - labelSpan?: number; rowIndex?: number; lastGroupItem?: boolean; } @@ -41,10 +41,11 @@ const useStyles = createUseStyles( { name: 'FormItem' } ); -const renderLabel = (label: ReactNode, classes: Record<'label' | 'content', string>, styles: CSSProperties) => { +function FormItemLabel({ label, style }: { label: ReactNode; style?: CSSProperties }) { + const classes = useStyles(); if (typeof label === 'string') { return ( -