@centreon/ui 25.7.3 → 25.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/Form/CollapsibleGroup.tsx +10 -10
- package/src/Form/Form.cypress.spec.tsx +137 -2
- package/src/Form/Form.stories.tsx +11 -31
- package/src/Form/Form.tsx +2 -0
- package/src/Form/Inputs/Grid.tsx +10 -28
- package/src/Form/Inputs/SubGroupDivider.tsx +7 -0
- package/src/Form/Inputs/Text.tsx +1 -0
- package/src/Form/Inputs/index.tsx +6 -0
- package/src/Form/Inputs/models.ts +2 -1
- package/src/Form/Section/FormSection.tsx +34 -0
- package/src/Form/Section/PanelTabs.tsx +13 -0
- package/src/Form/Section/navigateToSection.ts +9 -0
- package/src/Form/storiesData.tsx +12 -2
- package/src/components/Modal/Modal.stories.tsx +20 -0
- package/src/components/Modal/Modal.styles.ts +2 -23
- package/src/components/Modal/Modal.tsx +1 -1
- package/src/components/Modal/ModalBody.tsx +6 -4
- package/src/components/Modal/ModalHeader.tsx +5 -5
- package/src/components/Modal/modal.module.css +16 -0
- package/src/components/Tabs/Tab.styles.ts +0 -6
- package/src/components/Tabs/Tabs.tsx +31 -10
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -35,12 +35,6 @@ const useStyles = makeStyles()((theme) => ({
|
|
|
35
35
|
display: 'flex',
|
|
36
36
|
flexDirection: 'row'
|
|
37
37
|
},
|
|
38
|
-
groupTitleIcon: {
|
|
39
|
-
alignItems: 'center',
|
|
40
|
-
columnGap: theme.spacing(1),
|
|
41
|
-
display: 'flex',
|
|
42
|
-
flexDirection: 'row'
|
|
43
|
-
},
|
|
44
38
|
tooltip: {
|
|
45
39
|
maxWidth: theme.spacing(60)
|
|
46
40
|
},
|
|
@@ -77,7 +71,7 @@ const CollapsibleGroup = ({
|
|
|
77
71
|
disableGutters
|
|
78
72
|
disableRipple
|
|
79
73
|
aria-label={group?.name}
|
|
80
|
-
className={cx(classes.groupTitleContainer, containerClassName)}
|
|
74
|
+
className={`${cx(classes.groupTitleContainer, containerClassName)} bg-background-listing-header`}
|
|
81
75
|
onClick={toggle}
|
|
82
76
|
>
|
|
83
77
|
{containerComponentChildren}
|
|
@@ -94,12 +88,17 @@ const CollapsibleGroup = ({
|
|
|
94
88
|
<>
|
|
95
89
|
{hasGroupTitle && (
|
|
96
90
|
<ContainerComponent>
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
<div
|
|
92
|
+
data-testid={`${group?.name}-header`}
|
|
93
|
+
className={
|
|
94
|
+
'snap-y flex flex-row justify-between w-full pl-3 pr-1 text-white items-center'
|
|
95
|
+
}
|
|
96
|
+
>
|
|
99
97
|
<Typography
|
|
100
|
-
className="groupText"
|
|
98
|
+
className="groupText scroll-m-12 snap-start"
|
|
101
99
|
variant="h6"
|
|
102
100
|
{...group?.titleAttributes}
|
|
101
|
+
data-section-group-form-id={group?.name}
|
|
103
102
|
>
|
|
104
103
|
{t(group?.name as string)}
|
|
105
104
|
</Typography>
|
|
@@ -116,6 +115,7 @@ const CollapsibleGroup = ({
|
|
|
116
115
|
</MuiIconButton>
|
|
117
116
|
</Tooltip>
|
|
118
117
|
)}
|
|
118
|
+
{isCollapsible && <CollapseIcon />}
|
|
119
119
|
</div>
|
|
120
120
|
</ContainerComponent>
|
|
121
121
|
)}
|
|
@@ -132,16 +132,129 @@ describe('Form list', () => {
|
|
|
132
132
|
});
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
const
|
|
135
|
+
const initializeFormWithSections = (): void => {
|
|
136
136
|
cy.mount({
|
|
137
137
|
Component: (
|
|
138
138
|
<Form
|
|
139
|
+
isCollapsible
|
|
139
140
|
initialValues={{
|
|
140
141
|
list: []
|
|
141
142
|
}}
|
|
143
|
+
groups={[
|
|
144
|
+
{
|
|
145
|
+
name: 'First group',
|
|
146
|
+
order: 1
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'Third group',
|
|
150
|
+
order: 3
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'Second group',
|
|
154
|
+
order: 2
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'Fourth group',
|
|
158
|
+
order: 4
|
|
159
|
+
}
|
|
160
|
+
]}
|
|
142
161
|
inputs={[
|
|
143
162
|
{
|
|
144
|
-
fieldName: '
|
|
163
|
+
fieldName: 'First name',
|
|
164
|
+
group: 'First group',
|
|
165
|
+
label: 'Name',
|
|
166
|
+
type: InputType.Text
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
fieldName: 'Divider',
|
|
170
|
+
group: 'First group',
|
|
171
|
+
label: 'Divider',
|
|
172
|
+
type: InputType.Divider
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
fieldName: 'Second name',
|
|
176
|
+
group: 'First group',
|
|
177
|
+
label: 'Name',
|
|
178
|
+
type: InputType.Text
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
fieldName: 'Third name',
|
|
182
|
+
group: 'First group',
|
|
183
|
+
label: 'Name',
|
|
184
|
+
type: InputType.Text
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
fieldName: 'Fourth name',
|
|
188
|
+
group: 'First group',
|
|
189
|
+
label: 'Name',
|
|
190
|
+
type: InputType.Text
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
fieldName: 'Fifth name',
|
|
194
|
+
group: 'First group',
|
|
195
|
+
label: 'Name',
|
|
196
|
+
type: InputType.Text
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
fieldName: 'Sixth name',
|
|
200
|
+
group: 'First group',
|
|
201
|
+
label: 'Name',
|
|
202
|
+
type: InputType.Text
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
fieldName: 'Seventh name',
|
|
206
|
+
group: 'First group',
|
|
207
|
+
label: 'Name',
|
|
208
|
+
type: InputType.Text
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
fieldName: 'Eighth name',
|
|
212
|
+
group: 'First group',
|
|
213
|
+
label: 'Name',
|
|
214
|
+
type: InputType.Text
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
fieldName: 'Ninth name',
|
|
218
|
+
group: 'First group',
|
|
219
|
+
label: 'Name',
|
|
220
|
+
type: InputType.Text
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
fieldName: 'First second group name',
|
|
224
|
+
group: 'Second group',
|
|
225
|
+
label: 'Name',
|
|
226
|
+
type: InputType.Text
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
fieldName: 'First third group name',
|
|
230
|
+
group: 'Third group',
|
|
231
|
+
label: 'Name',
|
|
232
|
+
type: InputType.Text
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
fieldName: 'First fourth group name',
|
|
236
|
+
group: 'Fourth group',
|
|
237
|
+
label: 'Name',
|
|
238
|
+
type: InputType.Text
|
|
239
|
+
}
|
|
240
|
+
]}
|
|
241
|
+
submit={cy.stub()}
|
|
242
|
+
validationSchema={object()}
|
|
243
|
+
/>
|
|
244
|
+
)
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const initializeFile = () => {
|
|
249
|
+
cy.mount({
|
|
250
|
+
Component: (
|
|
251
|
+
<Form
|
|
252
|
+
initialValues={{
|
|
253
|
+
list: []
|
|
254
|
+
}}
|
|
255
|
+
inputs={[
|
|
256
|
+
{
|
|
257
|
+
fieldName: 'list',
|
|
145
258
|
group: '',
|
|
146
259
|
label: 'json',
|
|
147
260
|
type: InputType.File,
|
|
@@ -170,3 +283,25 @@ describe('File', () => {
|
|
|
170
283
|
cy.makeSnapshot();
|
|
171
284
|
});
|
|
172
285
|
});
|
|
286
|
+
|
|
287
|
+
describe('Form with sections', () => {
|
|
288
|
+
beforeEach(initializeFormWithSections);
|
|
289
|
+
it('displays sections when correct amount of sections', () => {
|
|
290
|
+
cy.contains('First group').should('be.visible');
|
|
291
|
+
cy.contains('Second group').should('be.visible');
|
|
292
|
+
cy.contains('Third group').should('be.visible');
|
|
293
|
+
cy.contains('Fourth group').should('be.visible');
|
|
294
|
+
cy.makeSnapshot();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('scrolls correctly to section', () => {
|
|
298
|
+
cy.window().then((win) => {
|
|
299
|
+
const initialScrollY = win.scrollY;
|
|
300
|
+
cy.contains('Third group').click();
|
|
301
|
+
cy.window().its('scrollY').should('be.greaterThan', initialScrollY);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
cy.wait(500); // Wait for the scroll animation to complete
|
|
305
|
+
cy.makeSnapshot();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Paper } from '@mui/material';
|
|
2
|
-
|
|
3
1
|
import { Form, GroupDirection } from './Form';
|
|
4
2
|
import {
|
|
5
3
|
BasicForm,
|
|
@@ -27,51 +25,33 @@ const mandatoryProps = {
|
|
|
27
25
|
};
|
|
28
26
|
|
|
29
27
|
export const basicForm = (): JSX.Element => (
|
|
30
|
-
<
|
|
31
|
-
<Form<BasicForm> {...mandatoryProps} />
|
|
32
|
-
</Paper>
|
|
28
|
+
<Form<BasicForm> {...mandatoryProps} />
|
|
33
29
|
);
|
|
34
30
|
|
|
35
31
|
export const basicFormWithGroups = (): JSX.Element => (
|
|
36
|
-
<
|
|
37
|
-
<Form<BasicForm> {...mandatoryProps} groups={basicFormGroups} />
|
|
38
|
-
</Paper>
|
|
32
|
+
<Form<BasicForm> {...mandatoryProps} groups={basicFormGroups} />
|
|
39
33
|
);
|
|
40
34
|
|
|
41
35
|
export const basicFormWithCollapsibleGroups = (): JSX.Element => (
|
|
42
|
-
<
|
|
43
|
-
<Form<BasicForm>
|
|
44
|
-
{...mandatoryProps}
|
|
45
|
-
isCollapsible
|
|
46
|
-
groups={basicFormGroups}
|
|
47
|
-
/>
|
|
48
|
-
</Paper>
|
|
36
|
+
<Form<BasicForm> {...mandatoryProps} isCollapsible groups={basicFormGroups} />
|
|
49
37
|
);
|
|
50
38
|
|
|
51
39
|
export const basicFormWithCustomButton = (): JSX.Element => (
|
|
52
|
-
<
|
|
53
|
-
<Form<BasicForm> {...mandatoryProps} Buttons={CustomButton} />
|
|
54
|
-
</Paper>
|
|
40
|
+
<Form<BasicForm> {...mandatoryProps} Buttons={CustomButton} />
|
|
55
41
|
);
|
|
56
42
|
|
|
57
43
|
export const loadingForm = (): JSX.Element => (
|
|
58
|
-
<
|
|
59
|
-
<Form<BasicForm> {...mandatoryProps} isLoading />
|
|
60
|
-
</Paper>
|
|
44
|
+
<Form<BasicForm> {...mandatoryProps} isLoading />
|
|
61
45
|
);
|
|
62
46
|
|
|
63
47
|
export const loadingFormWithGroups = (): JSX.Element => (
|
|
64
|
-
<
|
|
65
|
-
<Form<BasicForm> {...mandatoryProps} isLoading groups={basicFormGroups} />
|
|
66
|
-
</Paper>
|
|
48
|
+
<Form<BasicForm> {...mandatoryProps} isLoading groups={basicFormGroups} />
|
|
67
49
|
);
|
|
68
50
|
|
|
69
51
|
export const basicFormWithHorizontalGroups = (): JSX.Element => (
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
/>
|
|
76
|
-
</Paper>
|
|
52
|
+
<Form<BasicForm>
|
|
53
|
+
{...mandatoryProps}
|
|
54
|
+
groupDirection={GroupDirection.Horizontal}
|
|
55
|
+
groups={basicFormGroups.filter((group) => group.order !== 3)}
|
|
56
|
+
/>
|
|
77
57
|
);
|
package/src/Form/Form.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import { useStyles } from './Form.styles';
|
|
|
11
11
|
import FormButtons from './FormButtons';
|
|
12
12
|
import Inputs from './Inputs';
|
|
13
13
|
import { Group, InputProps } from './Inputs/models';
|
|
14
|
+
import { FormSection } from './Section/FormSection';
|
|
14
15
|
|
|
15
16
|
export enum GroupDirection {
|
|
16
17
|
Horizontal = 'horizontal',
|
|
@@ -76,6 +77,7 @@ const Form = <T extends object>({
|
|
|
76
77
|
{...formikSharedConfig}
|
|
77
78
|
>
|
|
78
79
|
<div>
|
|
80
|
+
<FormSection groups={groups} />
|
|
79
81
|
{children}
|
|
80
82
|
<div className={cx(className, classes.form)}>
|
|
81
83
|
<Inputs
|
package/src/Form/Inputs/Grid.tsx
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { makeStyles } from 'tss-react/mui';
|
|
2
|
-
|
|
3
1
|
import { InputPropsWithoutGroup } from './models';
|
|
4
2
|
|
|
5
3
|
import { Box, Typography } from '@mui/material';
|
|
@@ -7,34 +5,10 @@ import { FormikValues, useFormikContext } from 'formik';
|
|
|
7
5
|
import { isNotEmpty, isNotNil } from 'ramda';
|
|
8
6
|
import { getInput } from '.';
|
|
9
7
|
|
|
10
|
-
interface StylesProps {
|
|
11
|
-
alignItems?: string;
|
|
12
|
-
columns?: number;
|
|
13
|
-
gridTemplateColumns?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const useStyles = makeStyles<StylesProps>()(
|
|
17
|
-
(theme, { columns, gridTemplateColumns, alignItems }) => ({
|
|
18
|
-
gridFields: {
|
|
19
|
-
alignItems: alignItems || 'flex-start',
|
|
20
|
-
columnGap: theme.spacing(4),
|
|
21
|
-
display: 'grid',
|
|
22
|
-
gridTemplateColumns: gridTemplateColumns || `repeat(${columns}, 1fr)`,
|
|
23
|
-
rowGap: theme.spacing(2)
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
);
|
|
27
|
-
|
|
28
8
|
const Grid = ({
|
|
29
9
|
grid,
|
|
30
10
|
hideInput
|
|
31
11
|
}: InputPropsWithoutGroup): JSX.Element | null => {
|
|
32
|
-
const { classes, cx } = useStyles({
|
|
33
|
-
alignItems: grid?.alignItems,
|
|
34
|
-
columns: grid?.columns.length,
|
|
35
|
-
gridTemplateColumns: grid?.gridTemplateColumns
|
|
36
|
-
});
|
|
37
|
-
|
|
38
12
|
const { values } = useFormikContext<FormikValues>();
|
|
39
13
|
|
|
40
14
|
if (hideInput?.(values) ?? false) {
|
|
@@ -44,7 +18,15 @@ const Grid = ({
|
|
|
44
18
|
const className = grid?.className || '';
|
|
45
19
|
|
|
46
20
|
return (
|
|
47
|
-
<div
|
|
21
|
+
<div
|
|
22
|
+
className={`${className} grid gap-3`}
|
|
23
|
+
style={{
|
|
24
|
+
gridTemplateColumns:
|
|
25
|
+
className ? grid?.gridTemplateColumns || undefined : grid?.gridTemplateColumns ||
|
|
26
|
+
`repeat(${grid?.columns.length || 1}, 1fr)`,
|
|
27
|
+
alignItems: grid?.alignItems || 'flex-start'
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
48
30
|
{grid?.columns.map((field) => {
|
|
49
31
|
const Input = getInput(field.type);
|
|
50
32
|
|
|
@@ -62,7 +44,7 @@ const Grid = ({
|
|
|
62
44
|
{field.additionalLabel && (
|
|
63
45
|
<Typography
|
|
64
46
|
sx={{ marginBottom: 0.5, color: 'primary.main' }}
|
|
65
|
-
className={
|
|
47
|
+
className={field?.additionalLabelClassName}
|
|
66
48
|
variant="h6"
|
|
67
49
|
>
|
|
68
50
|
{field.additionalLabel}
|
package/src/Form/Inputs/Text.tsx
CHANGED
|
@@ -43,6 +43,7 @@ import Grid from './Grid';
|
|
|
43
43
|
import List from './List/List';
|
|
44
44
|
import LoadingSkeleton from './LoadingSkeleton';
|
|
45
45
|
import RadioInput from './Radio';
|
|
46
|
+
import { SubgroupDivider } from './SubGroupDivider';
|
|
46
47
|
import SwitchInput from './Switch';
|
|
47
48
|
import TextInput from './Text';
|
|
48
49
|
import { Group, InputProps, InputPropsWithoutGroup, InputType } from './models';
|
|
@@ -82,6 +83,11 @@ export const getInput = cond<
|
|
|
82
83
|
],
|
|
83
84
|
[equals(InputType.List) as (b: InputType) => boolean, always(List)],
|
|
84
85
|
[equals(InputType.File) as (b: InputType) => boolean, always(File)],
|
|
86
|
+
[equals(InputType.Text) as (b: InputType) => boolean, always(TextInput)],
|
|
87
|
+
[
|
|
88
|
+
equals(InputType.Divider) as (b: InputType) => boolean,
|
|
89
|
+
always(SubgroupDivider)
|
|
90
|
+
],
|
|
85
91
|
[T, always(TextInput)]
|
|
86
92
|
]);
|
|
87
93
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Box, TabsProps } from '@mui/material';
|
|
2
|
+
import { isNil } from 'ramda';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { Tabs } from '../../components/Tabs';
|
|
5
|
+
import { Group } from '../Inputs/models';
|
|
6
|
+
import { groupToTab } from './PanelTabs';
|
|
7
|
+
import { useNavigateToSection } from './navigateToSection';
|
|
8
|
+
|
|
9
|
+
export interface FormSectionProps extends TabsProps {
|
|
10
|
+
groups?: Group[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const FormSection = ({ groups }: FormSectionProps) => {
|
|
14
|
+
if (isNil(groups) || groups.length < 4) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const navigateToSection = useNavigateToSection();
|
|
19
|
+
const tabMemo = useMemo(() => groupToTab(groups), [groups]);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Box className="sticky top-0 bg-background-paper z-100">
|
|
23
|
+
<Tabs
|
|
24
|
+
variant="scrollable"
|
|
25
|
+
scrollButtons={false}
|
|
26
|
+
tabs={tabMemo}
|
|
27
|
+
defaultTab={tabMemo[0].value}
|
|
28
|
+
onChange={navigateToSection}
|
|
29
|
+
/>
|
|
30
|
+
</Box>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { FormSection };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { prop, sortBy } from 'ramda';
|
|
2
|
+
import { TabI } from 'src/components/Tabs/Tabs';
|
|
3
|
+
import { Group } from '../Inputs/models';
|
|
4
|
+
|
|
5
|
+
const groupToTab = (groups: Array<Group>): Array<TabI> => {
|
|
6
|
+
const sortedGroups = sortBy(prop('order'), groups);
|
|
7
|
+
|
|
8
|
+
return sortedGroups.map((group) => {
|
|
9
|
+
return { value: group.name, label: group.name };
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { groupToTab };
|
package/src/Form/storiesData.tsx
CHANGED
|
@@ -157,12 +157,16 @@ export const basicFormGroups: Array<Group> = [
|
|
|
157
157
|
{
|
|
158
158
|
EndIcon: () => <HelpOutlineIcon />,
|
|
159
159
|
TooltipContent: (): JSX.Element => <Typography>Tooltip content</Typography>,
|
|
160
|
+
name: 'Third group',
|
|
161
|
+
order: 3
|
|
162
|
+
},
|
|
163
|
+
{
|
|
160
164
|
name: 'Second group',
|
|
161
165
|
order: 2
|
|
162
166
|
},
|
|
163
167
|
{
|
|
164
|
-
name: '
|
|
165
|
-
order:
|
|
168
|
+
name: 'Fourth group',
|
|
169
|
+
order: 4
|
|
166
170
|
}
|
|
167
171
|
];
|
|
168
172
|
|
|
@@ -215,6 +219,12 @@ export const basicFormInputs: Array<InputProps> = [
|
|
|
215
219
|
},
|
|
216
220
|
type: InputType.Radio
|
|
217
221
|
},
|
|
222
|
+
{
|
|
223
|
+
fieldName: 'div',
|
|
224
|
+
group: 'First group',
|
|
225
|
+
label: 'divider',
|
|
226
|
+
type: InputType.Divider
|
|
227
|
+
},
|
|
218
228
|
{
|
|
219
229
|
additionalLabel: 'Notifications',
|
|
220
230
|
fieldName: '',
|
|
@@ -4,6 +4,7 @@ import { Button } from '../Button';
|
|
|
4
4
|
|
|
5
5
|
import { Modal } from '.';
|
|
6
6
|
import '../../ThemeProvider/tailwindcss.css';
|
|
7
|
+
import { basicFormWithCollapsibleGroups } from '../../Form/Form.stories';
|
|
7
8
|
|
|
8
9
|
const meta: Meta<typeof Modal> = {
|
|
9
10
|
argTypes: {
|
|
@@ -45,6 +46,25 @@ export const Default: Story = {
|
|
|
45
46
|
)
|
|
46
47
|
};
|
|
47
48
|
|
|
49
|
+
export const WithForm: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
...Default.args
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
render: (args) => (
|
|
55
|
+
<Modal {...args}>
|
|
56
|
+
<Modal.Header>Modal title</Modal.Header>
|
|
57
|
+
<Modal.Body>{basicFormWithCollapsibleGroups()}</Modal.Body>
|
|
58
|
+
<Modal.Actions
|
|
59
|
+
labels={{
|
|
60
|
+
cancel: 'Cancel',
|
|
61
|
+
confirm: 'Confirm'
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
</Modal>
|
|
65
|
+
)
|
|
66
|
+
};
|
|
67
|
+
|
|
48
68
|
export const AsDangerAction: Story = {
|
|
49
69
|
args: {
|
|
50
70
|
...Default.args
|
|
@@ -8,7 +8,7 @@ const useStyles = makeStyles<{
|
|
|
8
8
|
}>()((theme, props) => ({
|
|
9
9
|
modal: {
|
|
10
10
|
'& .MuiDialog-paper': {
|
|
11
|
-
gap: theme.spacing(
|
|
11
|
+
gap: theme.spacing(3),
|
|
12
12
|
padding: theme.spacing(2.5)
|
|
13
13
|
},
|
|
14
14
|
'&[data-size="fullscreen"]': {
|
|
@@ -65,15 +65,6 @@ const useStyles = makeStyles<{
|
|
|
65
65
|
right: 0,
|
|
66
66
|
zIndex: theme.zIndex.modal
|
|
67
67
|
},
|
|
68
|
-
modalBody: {
|
|
69
|
-
'& > p': {
|
|
70
|
-
'&:first-of-type': {
|
|
71
|
-
margin: theme.spacing(0, 0, 1, 0)
|
|
72
|
-
},
|
|
73
|
-
margin: theme.spacing(1, 0, 1, 0),
|
|
74
|
-
width: '90%'
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
68
|
modalCloseButton: {
|
|
78
69
|
position: 'absolute',
|
|
79
70
|
right: theme.spacing(1),
|
|
@@ -82,19 +73,7 @@ const useStyles = makeStyles<{
|
|
|
82
73
|
},
|
|
83
74
|
top: theme.spacing(1)
|
|
84
75
|
},
|
|
85
|
-
|
|
86
|
-
'& .MuiDialogTitle-root': {
|
|
87
|
-
padding: theme.spacing(0)
|
|
88
|
-
},
|
|
89
|
-
display: 'flex',
|
|
90
|
-
gap: theme.spacing(2),
|
|
91
|
-
|
|
92
|
-
justifyContent: 'space-between'
|
|
93
|
-
},
|
|
94
|
-
modalTitle: {
|
|
95
|
-
fontSize: theme.typography.h5.fontSize,
|
|
96
|
-
fontWeight: theme.typography.fontWeightBold
|
|
97
|
-
}
|
|
76
|
+
|
|
98
77
|
}));
|
|
99
78
|
|
|
100
79
|
export { useStyles };
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { ReactElement, ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { modalBody } from './modal.module.css';
|
|
4
4
|
|
|
5
5
|
export type ModalHeaderProps = {
|
|
6
6
|
children?: ReactNode;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
const ModalBody = ({ children }: ModalHeaderProps): ReactElement => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
return (
|
|
11
|
+
<div className={modalBody} data-testid="modal-body">
|
|
12
|
+
{children}
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export { ModalBody };
|
|
@@ -2,7 +2,9 @@ import { ReactElement, ReactNode } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { DialogTitleProps, DialogTitle as MuiDialogTitle } from '@mui/material';
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import '../../../src/ThemeProvider/tailwindcss.css';
|
|
6
|
+
|
|
7
|
+
import { modalHeader } from './modal.module.css';
|
|
6
8
|
|
|
7
9
|
export type ModalHeaderProps = {
|
|
8
10
|
children?: ReactNode;
|
|
@@ -12,11 +14,9 @@ const ModalHeader = ({
|
|
|
12
14
|
children,
|
|
13
15
|
...rest
|
|
14
16
|
}: ModalHeaderProps & DialogTitleProps): ReactElement => {
|
|
15
|
-
const { classes } = useStyles();
|
|
16
|
-
|
|
17
17
|
return (
|
|
18
|
-
<div className={
|
|
19
|
-
<MuiDialogTitle color="primary"
|
|
18
|
+
<div className={modalHeader}>
|
|
19
|
+
<MuiDialogTitle className="p-0 font-bold text-2xl" color="primary" {...rest}>
|
|
20
20
|
{children}
|
|
21
21
|
</MuiDialogTitle>
|
|
22
22
|
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.modalHeader {
|
|
2
|
+
& .MuiDialogTitle-root {
|
|
3
|
+
padding: 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
display: flex;
|
|
7
|
+
gap: var(--spacing-4);
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.modalBody {
|
|
12
|
+
overflow-y: auto;
|
|
13
|
+
overflow-x: hidden;
|
|
14
|
+
height: 100%;
|
|
15
|
+
padding-right: var(--spacing-4); /* To prevent scrollbar from overlapping content */
|
|
16
|
+
}
|
|
@@ -5,12 +5,6 @@ export const useTabsStyles = makeStyles()((theme) => ({
|
|
|
5
5
|
bottom: 'unset'
|
|
6
6
|
},
|
|
7
7
|
tab: {
|
|
8
|
-
'&[aria-selected="true"]': {
|
|
9
|
-
color: theme.palette.text.primary,
|
|
10
|
-
fontWeight: theme.typography.fontWeightBold
|
|
11
|
-
},
|
|
12
|
-
color: theme.palette.text.primary,
|
|
13
|
-
fontWeight: theme.typography.fontWeightRegular,
|
|
14
8
|
marginRight: theme.spacing(2),
|
|
15
9
|
minHeight: 0,
|
|
16
10
|
minWidth: 0,
|
|
@@ -5,33 +5,53 @@ import { Tabs as MuiTabs, Tab, TabsProps } from '@mui/material';
|
|
|
5
5
|
|
|
6
6
|
import { useTabsStyles } from './Tab.styles';
|
|
7
7
|
|
|
8
|
+
import '../../ThemeProvider/tailwindcss.css';
|
|
9
|
+
|
|
10
|
+
export interface TabI {
|
|
11
|
+
label: string;
|
|
12
|
+
value: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
type Props = {
|
|
9
|
-
children
|
|
16
|
+
children?: Array<JSX.Element>;
|
|
10
17
|
defaultTab: string;
|
|
11
18
|
tabList?: TabsProps;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
variant?: 'standard' | 'scrollable' | 'fullWidth';
|
|
20
|
+
scrollButtons?: boolean | 'auto';
|
|
21
|
+
tabs: TabI[];
|
|
22
|
+
onChange?: (newValue: string) => void;
|
|
16
23
|
};
|
|
17
24
|
|
|
18
25
|
export const Tabs = ({
|
|
19
26
|
children,
|
|
20
27
|
defaultTab,
|
|
21
28
|
tabs,
|
|
22
|
-
tabList
|
|
29
|
+
tabList,
|
|
30
|
+
variant,
|
|
31
|
+
scrollButtons = 'auto',
|
|
32
|
+
onChange
|
|
23
33
|
}: Props): JSX.Element => {
|
|
24
34
|
const { classes } = useTabsStyles();
|
|
25
35
|
|
|
26
36
|
const [selectedTab, setSelectedTab] = useState(defaultTab);
|
|
27
37
|
|
|
28
|
-
const changeTab = useCallback(
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
const changeTab = useCallback(
|
|
39
|
+
(_, newValue: string): void => {
|
|
40
|
+
if (onChange) onChange(newValue);
|
|
41
|
+
|
|
42
|
+
setSelectedTab(newValue);
|
|
43
|
+
},
|
|
44
|
+
[onChange]
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const selectedTabStyle = ' font-bold text-primary-main';
|
|
48
|
+
const defaultTabStyle = ' font-normal';
|
|
31
49
|
|
|
32
50
|
return (
|
|
33
51
|
<TabContext value={selectedTab}>
|
|
34
52
|
<MuiTabs
|
|
53
|
+
scrollButtons={scrollButtons}
|
|
54
|
+
variant={variant}
|
|
35
55
|
classes={{
|
|
36
56
|
indicator: classes.indicator,
|
|
37
57
|
root: classes.tabs
|
|
@@ -43,10 +63,11 @@ export const Tabs = ({
|
|
|
43
63
|
{tabs.map(({ value, label }) => (
|
|
44
64
|
<Tab
|
|
45
65
|
aria-label={label}
|
|
46
|
-
className={classes.tab}
|
|
66
|
+
className={`${classes.tab} ${selectedTab === value ? selectedTabStyle : defaultTabStyle}`}
|
|
47
67
|
key={value}
|
|
48
68
|
label={label}
|
|
49
69
|
value={value}
|
|
70
|
+
data-testid={`tab-${value}`}
|
|
50
71
|
/>
|
|
51
72
|
))}
|
|
52
73
|
</MuiTabs>
|
package/src/index.ts
CHANGED
|
@@ -89,6 +89,7 @@ export type {
|
|
|
89
89
|
SearchMatch
|
|
90
90
|
} from './api/buildListingEndpoint/models';
|
|
91
91
|
export { default as buildListingDecoder } from './api/buildListingDecoder';
|
|
92
|
+
export { customFetch } from './api/customFetch';
|
|
92
93
|
|
|
93
94
|
export { default as ContentWithCircularLoading } from './ContentWithCircularProgress';
|
|
94
95
|
export {
|