Skip to content

Commit c4c2848

Browse files
andybessmvbersch
authored andcommitted
feat(Form): initial Implementation (#242)
1 parent 0ed5dd6 commit c4c2848

File tree

18 files changed

+1339
-23
lines changed

18 files changed

+1339
-23
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useCallback, useEffect, useState } from 'react';
2+
import { Device } from '@ui5/webcomponents-react-base/lib/Device';
3+
4+
export const useViewportRange = (rangeSet) => {
5+
const [currentRange, setCurrentRange] = useState(Device.media.getCurrentRange(rangeSet, window.innerWidth).name);
6+
7+
const onWindowResize = useCallback(
8+
({ name: range }) => {
9+
setCurrentRange(range);
10+
},
11+
[currentRange, setCurrentRange]
12+
);
13+
14+
useEffect(() => {
15+
Device.media.attachHandler(onWindowResize, null, 'StdExt');
16+
return () => Device.resize.detachHandler(onWindowResize, null);
17+
}, [onWindowResize]);
18+
19+
return currentRange;
20+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { useViewportRange } from '../hooks/useViewportRange';
2+
3+
export { useViewportRange };
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { TextAlign } from '@ui5/webcomponents-react/lib/TextAlign';
2+
import { VerticalAlign } from '@ui5/webcomponents-react/lib/VerticalAlign';
3+
import { CSSProperties } from 'react';
4+
5+
export const useCellStyling = ({ rowHeight }, classes) => ({ column }) => {
6+
const style: CSSProperties = {};
7+
8+
if (rowHeight) {
9+
style.height = `${rowHeight}px`;
10+
}
11+
switch (column.hAlign) {
12+
case TextAlign.Begin:
13+
style.textAlign = 'start';
14+
break;
15+
case TextAlign.Center:
16+
style.textAlign = 'center';
17+
break;
18+
case TextAlign.End:
19+
style.textAlign = 'end';
20+
break;
21+
case TextAlign.Left:
22+
style.textAlign = 'left';
23+
break;
24+
case TextAlign.Right:
25+
style.textAlign = 'right';
26+
break;
27+
}
28+
switch (column.vAlign) {
29+
case VerticalAlign.Bottom:
30+
style.verticalAlign = 'bottom';
31+
break;
32+
case VerticalAlign.Middle:
33+
style.verticalAlign = 'middle';
34+
break;
35+
case VerticalAlign.Top:
36+
style.verticalAlign = 'top';
37+
break;
38+
}
39+
40+
let className = classes.tableCell;
41+
if (column.className) {
42+
className += ` ${column.className}`;
43+
}
44+
return {
45+
className,
46+
style
47+
};
48+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react';
2+
3+
const CurrentRange = React.createContext(null);
4+
5+
export { CurrentRange };
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { JSSTheme } from '../../interfaces/JSSTheme';
2+
3+
const styles = ({ parameters }: JSSTheme) => ({
4+
formTitle: {
5+
borderBottom: `1px solid ${parameters.sapUiGroupTitleBorderColor}`
6+
},
7+
formTitlePaddingBottom: {
8+
paddingBottom: '2em'
9+
},
10+
formPaddingBottom: {
11+
paddingBottom: '1em'
12+
},
13+
formGroupStyle: {
14+
width: '100%',
15+
paddingTop: '0.25em'
16+
},
17+
formItemTopDiv: {
18+
alignItems: 'center'
19+
},
20+
formLabel: {
21+
paddingRight: '0.5em'
22+
},
23+
formElement: {
24+
display: 'block'
25+
}
26+
});
27+
28+
export { styles };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```jsx
2+
import { Form } from '@ui5/webcomponents-react/lib/Form';
3+
```
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Form } from './index';
2+
import React from 'react';
3+
import notes from './Form.md';
4+
import { FormItem } from './FormItem';
5+
import { FormGroup } from './FormGroup';
6+
import { CheckBox } from '@ui5/webcomponents-react/lib/CheckBox';
7+
import { Input } from '@ui5/webcomponents-react/lib/Input';
8+
import { InputType } from '@ui5/webcomponents-react/lib/InputType';
9+
import { Option } from '@ui5/webcomponents-react/lib/Option';
10+
import { Select } from '@ui5/webcomponents-react/lib/Select';
11+
12+
export const defaultStory = () => {
13+
return (
14+
<Form title={'Test Form'}>
15+
<FormGroup title={'Personal Data'}>
16+
<FormItem labelText={'Name'}>
17+
<Input type={InputType.Text} />
18+
</FormItem>
19+
<FormItem labelText={'Address'}>
20+
<Input type={InputType.Text} />
21+
</FormItem>
22+
<FormItem labelText={'Country'}>
23+
<Select>
24+
<Option>Germany</Option>
25+
<Option>France</Option>
26+
<Option>Italy</Option>
27+
</Select>
28+
</FormItem>
29+
<FormItem labelText={'Home address'}>
30+
<CheckBox checked />
31+
</FormItem>
32+
</FormGroup>
33+
<FormGroup title={'Business Data'}>
34+
<FormItem labelText={'Organization'}>
35+
<Input type={InputType.Text} />
36+
</FormItem>
37+
<FormItem labelText={'Position'}>
38+
<Input type={InputType.Text} />
39+
</FormItem>
40+
<FormItem labelText={'Wage'}>
41+
<Input type={InputType.Number} value={'5000'} disabled />
42+
</FormItem>
43+
<FormItem labelText={'Pilot license'}>
44+
<CheckBox checked />
45+
</FormItem>
46+
</FormGroup>
47+
</Form>
48+
);
49+
};
50+
51+
export default {
52+
title: 'Components | Form',
53+
component: Form,
54+
parameters: { notes }
55+
};
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { mountThemedComponent } from '@shared/tests/utils';
2+
import * as React from 'react';
3+
import { Form } from '@ui5/webcomponents-react/lib/Form';
4+
import { FormGroup } from '@ui5/webcomponents-react/lib/FormGroup';
5+
import { FormItem } from '@ui5/webcomponents-react/lib/FormItem';
6+
import { Input } from '../../webComponents/Input';
7+
import { InputType } from '../..';
8+
9+
const SIZE_S = 200;
10+
const SIZE_M = 800;
11+
const SIZE_L = 1200;
12+
const SIZE_XL = 1600;
13+
const component = (
14+
<Form title={'Test form'}>
15+
<FormGroup title={'Group 1'}>
16+
<FormItem labelText={'item 1'}>
17+
<Input type={InputType.Text}></Input>
18+
</FormItem>
19+
<FormItem labelText={'item 2'}>
20+
<Input type={InputType.Number}></Input>
21+
</FormItem>
22+
</FormGroup>
23+
<FormGroup title={'Group 2'}>
24+
<FormItem labelText={'item 1'}>
25+
<Input type={InputType.Text}></Input>
26+
</FormItem>
27+
<FormItem labelText={'item 2'}>
28+
<Input type={InputType.Number}></Input>
29+
</FormItem>
30+
</FormGroup>
31+
</Form>
32+
);
33+
34+
describe('Create a Form', () => {
35+
test('size rate S; should create Label and Element with 100% width and display: block for top FormItem div', () => {
36+
window = Object.assign(window, { innerWidth: SIZE_S });
37+
const wrapper = mountThemedComponent(component);
38+
expect(wrapper.render()).toMatchSnapshot();
39+
});
40+
41+
test('size rate M; should create Label and Element with 16% and 83% width respectively and display: flex for top FormItem div', () => {
42+
window = Object.assign(window, { innerWidth: SIZE_M });
43+
const wrapper = mountThemedComponent(component);
44+
expect(wrapper.render()).toMatchSnapshot();
45+
});
46+
47+
test('size rate L; should create Label and Element with 33% and 66% width respectively and display: flex for top FormItem div', () => {
48+
window = Object.assign(window, { innerWidth: SIZE_L });
49+
const wrapper = mountThemedComponent(component);
50+
expect(wrapper.render()).toMatchSnapshot();
51+
});
52+
53+
test('size rate XL; should create Label and Element with 33% and 66% width respectively and display: flex for top FormItem div', () => {
54+
window = Object.assign(window, { innerWidth: SIZE_XL });
55+
const wrapper = mountThemedComponent(component);
56+
expect(wrapper.render()).toMatchSnapshot();
57+
});
58+
59+
test('should create a FormGroup and put ungrouped FormItems into it', () => {
60+
const ungroupedChildren = (
61+
<Form title={'Test form'}>
62+
<FormItem labelText={'item 1'}>
63+
<Input type={InputType.Text}></Input>
64+
</FormItem>
65+
<FormItem labelText={'item 2'}>
66+
<Input type={InputType.Number}></Input>
67+
</FormItem>
68+
</Form>
69+
);
70+
const wrapper = mountThemedComponent(ungroupedChildren);
71+
expect(wrapper.render()).toMatchSnapshot();
72+
});
73+
74+
test("should use a single FormGroup's title as a Form title if one is not set", () => {
75+
const ungroupedChildren = (
76+
<Form>
77+
<FormGroup title={'To be Form title'}>
78+
<FormItem labelText={'item 1'}>
79+
<Input type={InputType.Text}></Input>
80+
</FormItem>
81+
<FormItem labelText={'item 2'}>
82+
<Input type={InputType.Number}></Input>
83+
</FormItem>
84+
</FormGroup>
85+
</Form>
86+
);
87+
const wrapper = mountThemedComponent(ungroupedChildren);
88+
expect(wrapper.render()).toMatchSnapshot();
89+
});
90+
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { Children, FC, forwardRef, ReactNode, ReactNodeArray, Ref } from 'react';
2+
import { FlexBox } from '@ui5/webcomponents-react/lib/FlexBox';
3+
import { styles } from '../Form.jss';
4+
import { FlexBoxAlignItems } from '@ui5/webcomponents-react/lib/FlexBoxAlignItems';
5+
import { FlexBoxDirection } from '@ui5/webcomponents-react/lib/FlexBoxDirection';
6+
import { FlexBoxJustifyContent } from '@ui5/webcomponents-react/lib/FlexBoxJustifyContent';
7+
import { Title } from '@ui5/webcomponents-react/lib/Title';
8+
import { TitleLevel } from '@ui5/webcomponents-react/lib/TitleLevel';
9+
import { createUseStyles } from 'react-jss';
10+
import { JSSTheme } from '../../../interfaces/JSSTheme';
11+
12+
export interface FormGroupProps {
13+
title?: string;
14+
children: ReactNode | ReactNodeArray;
15+
type?: string;
16+
}
17+
18+
const useStyles = createUseStyles<JSSTheme, keyof ReturnType<typeof styles>>(styles, { name: 'FormGroup' });
19+
20+
const FormGroup: FC<FormGroupProps> = forwardRef((props: FormGroupProps, ref: Ref<HTMLDivElement>) => {
21+
const { title, children } = props;
22+
23+
const classes = useStyles();
24+
25+
return (
26+
<div ref={ref}>
27+
{title && (
28+
<Title level={TitleLevel.H5} className={classes.formPaddingBottom}>
29+
{title}
30+
</Title>
31+
)}
32+
<FlexBox
33+
justifyContent={FlexBoxJustifyContent.Start}
34+
alignItems={FlexBoxAlignItems.End}
35+
fitContainer
36+
direction={FlexBoxDirection.Column}
37+
>
38+
{Children.map(children, (child, index) => {
39+
return (
40+
<div key={index.toString()} className={classes.formGroupStyle}>
41+
{child}
42+
</div>
43+
);
44+
})}
45+
</FlexBox>
46+
</div>
47+
);
48+
});
49+
50+
FormGroup.defaultProps = {
51+
type: 'formGroup'
52+
};
53+
54+
export { FormGroup };

0 commit comments

Comments
 (0)