Skip to content

Commit

Permalink
feat(FormGroup): add as prop, allow pass through props (#5580)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lukas742 authored Mar 7, 2024
1 parent 8be9f20 commit 667c2c9
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 28 deletions.
30 changes: 24 additions & 6 deletions cypress/support/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getRGBColor } from '@ui5/webcomponents-base/dist/util/ColorConversion.js';
import { ComponentType } from 'react';
import type { ComponentType } from 'react';

export function cypressPassThroughTestsFactory(Component: ComponentType, props?: Record<string, unknown>) {
it('Pass Through HTML Standard Props', () => {
Expand Down Expand Up @@ -35,15 +35,33 @@ export function cypressPassThroughTestsFactory(Component: ComponentType, props?:
});
}

interface MountWithCustomTagNameOptions {
testTitle?: string;
defaultTagName?: string;
only?: boolean;
wrapperComponent?: ComponentType<any>;
}
export function mountWithCustomTagName<P extends { as?: keyof HTMLElementTagNameMap }>(
Component: ComponentType<P>,
props?: P
props?: P,
options: MountWithCustomTagNameOptions = {}
) {
it('mount with custom tag name', () => {
const { testTitle = 'mount with custom tag name', defaultTagName, only, wrapperComponent } = options;
const test = only ? it.only : it;
test(testTitle, () => {
const testId = 'component-to-be-tested';
const as = props?.as || 'header';
cy.mount(<Component as={as} data-testid={testId} {...props} />);
cy.get(`${as}[data-testid="${testId}"]`).should('be.visible');
const as = props?.as ?? 'header';
if (wrapperComponent) {
const Wrapper = wrapperComponent;
cy.mount(
<Wrapper>
<Component as={as} data-testid={testId} {...props} />
</Wrapper>
);
} else {
cy.mount(<Component as={as} data-testid={testId} {...props} />);
}
cy.get(`${defaultTagName ?? as}[data-testid="${testId}"]`).should('be.visible');
});
}

Expand Down
44 changes: 43 additions & 1 deletion packages/main/src/components/Form/Form.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { useReducer } from 'react';
import { createPortal } from 'react-dom';
import { InputType } from '../../enums/index.js';
import { Input, Label } from '../../webComponents/index.js';
import type { FormGroupPropTypes } from '../FormGroup';
import { FormGroup } from '../FormGroup';
import { FormItem } from '../FormItem';
import type { FormPropTypes } from './index.js';
import { Form } from './index.js';
import { cypressPassThroughTestsFactory } from '@/cypress/support/utils';
import { cypressPassThroughTestsFactory, mountWithCustomTagName } from '@/cypress/support/utils';

const component = (
<Form titleText={'Test form'}>
Expand Down Expand Up @@ -188,6 +190,46 @@ describe('Form', () => {
cy.findByTestId('2').should('not.exist');
});

[undefined, 'div'].forEach((as: FormPropTypes['as']) => {
const isDefault = !as;
mountWithCustomTagName(
Form,
{
as,
children: (
<FormItem label="FormItem">
<Input />
</FormItem>
)
},
{
testTitle: `mount with ${isDefault ? 'default' : 'custom'} tag name`,
defaultTagName: isDefault ? 'form' : as
}
);
});

[undefined, 'div'].forEach((as: FormGroupPropTypes['as']) => {
const isDefault = !as;
mountWithCustomTagName(
FormGroup,
{
as,
titleText: 'FormGroup',
children: (
<FormItem label="FormItem">
<Input />
</FormItem>
)
},
{
testTitle: `FormGroup: mount with ${isDefault ? 'default' : 'custom'} tag name`,
defaultTagName: isDefault ? 'h5' : as,
wrapperComponent: Form
}
);
});

cypressPassThroughTestsFactory(Form, {
children: (
<FormItem label="Item">
Expand Down
21 changes: 9 additions & 12 deletions packages/main/src/components/FormGroup/FormGroupTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { CssSizeVariables, ThemingParameters } from '@ui5/webcomponents-react-base';
import type { CSSProperties } from 'react';
import React from 'react';
import { clsx } from 'clsx';
import React, { type ElementType } from 'react';
import { createUseStyles } from 'react-jss';

interface FormGroupTitlePropTypes {
titleText: string;

style?: CSSProperties;
}
import type { FormGroupPropTypes } from './index.js';

const useStyles = createUseStyles(
{
Expand All @@ -27,17 +22,19 @@ const useStyles = createUseStyles(
},
{ name: 'FormGroupTitle' }
);
export function FormGroupTitle({ titleText, style }: FormGroupTitlePropTypes) {
export function FormGroupTitle({ as, className, titleText, style, ...rest }: Omit<FormGroupPropTypes, 'children'>) {
const classes = useStyles();
const CustomTag = as as ElementType;
return (
<h6
className={classes.title}
<CustomTag
{...rest}
className={clsx(classes.title, className)}
title={titleText}
aria-label={titleText}
data-component-name="FormGroupTitle"
style={style}
>
{titleText}
</h6>
</CustomTag>
);
}
18 changes: 14 additions & 4 deletions packages/main/src/components/FormGroup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import { useIsomorphicId } from '@ui5/webcomponents-react-base';
import type { ReactNode } from 'react';
import React, { useEffect, useMemo } from 'react';
import type { CommonProps } from '../../types/index.js';
import { GroupContext, useFormContext } from '../Form/FormContext.js';
import { FormGroupTitle } from './FormGroupTitle.js';

export interface FormGroupPropTypes {
export interface FormGroupPropTypes extends CommonProps<HTMLHeadingElement> {
/**
* Title of the FormGroup.
*/
Expand All @@ -17,15 +18,21 @@ export interface FormGroupPropTypes {
* __Note:__ Although this prop accepts all HTML Elements, it is strongly recommended that you only use `FormItem` in order to preserve the intended design.
*/
children: ReactNode | ReactNode[];
/**
* Sets the components outer HTML tag.
*
* @default "h5"
*/
as?: keyof HTMLElementTagNameMap;
}

/**
* The `FormGroup` encapsulates `FormItems` into groups.
* The `FormGroup` encapsulates `FormItems` into groups and allows setting a title for each group.
*
* __Note:__ `FormGroup` is only used for calculating the final layout of the `Form`, thus it doesn't accept any other props than `titleText` and `children`, especially no `className`, `style` or `ref`.
* __Note:__ Setting a React Ref is not supported by this component.
*/
const FormGroup = (props: FormGroupPropTypes) => {
const { titleText, children } = props;
const { titleText, children, as = 'h5', style, ...rest } = props;
const { formGroups: layoutInfos, registerItem, unregisterItem, labelSpan, recalcTrigger } = useFormContext();
const uniqueId = useIsomorphicId();

Expand All @@ -46,12 +53,15 @@ const FormGroup = (props: FormGroupPropTypes) => {
<GroupContext.Provider value={{ id: uniqueId }}>
<>
<FormGroupTitle
{...rest}
titleText={titleText}
style={{
...style,
display: titleText ? 'unset' : 'none',
gridColumnStart: columnIndex * 12 + 1,
gridRowStart: labelSpan === 12 ? rowIndex - 1 : rowIndex
}}
as={as}
/>
{children}
</>
Expand Down
7 changes: 3 additions & 4 deletions packages/main/src/components/Toolbar/Toolbar.cy.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { setTheme, getTheme } from '@ui5/webcomponents-base/dist/config/Theme.js';
import { setTheme } from '@ui5/webcomponents-base/dist/config/Theme.js';
import menu2Icon from '@ui5/webcomponents-icons/dist/menu2.js';
import { ThemingParameters } from '@ui5/webcomponents-react-base';
import { useRef, useState } from 'react';
Expand Down Expand Up @@ -546,17 +546,16 @@ describe('Toolbar', () => {
Text
</Toolbar>
);
console.log(getTheme());
cy.findByTestId('tb').should('have.css', 'outlineStyle', 'none');
cy.findByTestId('tb').should('have.css', 'boxShadow', 'none');

cy.findByTestId('tb').click();
cy.findByTestId('tb').realClick();
cy.findByTestId('tb').should('have.css', 'outlineStyle', 'none');
cy.findByTestId('tb').should('have.css', 'boxShadow', 'rgb(0, 50, 165) 0px 0px 0px 2px inset');

cy.wait(500).then(() => {
cy.findByTestId('tb').blur();
setTheme('sap_fiori_3');
void setTheme('sap_fiori_3');
});

cy.findByTestId('tb').should('have.css', 'outlineStyle', 'none');
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/types/CommonProps.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CSSProperties, HTMLAttributes } from 'react';

export interface CommonProps extends HTMLAttributes<HTMLElement> {
export interface CommonProps<T = HTMLElement> extends HTMLAttributes<T> {
/**
* Element style which will be appended to the most outer element of a component.
* Use this prop carefully, some css properties might break the component.
Expand Down

0 comments on commit 667c2c9

Please sign in to comment.