@pautena/react-design-system 0.4.7 → 0.5.0
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/dist/cjs/index.js +4 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/alerts/expandable-alert/expandable-alert.d.ts +11 -0
- package/dist/cjs/types/components/alerts/expandable-alert/index.d.ts +1 -0
- package/dist/cjs/types/components/alerts/index.d.ts +1 -0
- package/dist/cjs/types/components/data-display/board/board.d.ts +10 -0
- package/dist/cjs/types/components/data-display/board/index.d.ts +1 -0
- package/dist/cjs/types/components/data-display/index.d.ts +2 -0
- package/dist/cjs/types/components/data-display/markdown/index.d.ts +1 -0
- package/dist/cjs/types/components/data-display/markdown/markdown.d.ts +7 -0
- package/dist/cjs/types/components/dialogs/bootstrap-dialog/bootstrap-dialog.d.ts +2 -0
- package/dist/cjs/types/components/dialogs/bootstrap-dialog/index.d.ts +1 -0
- package/dist/cjs/types/components/dialogs/confirm-dialog/confirm-dialog.d.ts +10 -0
- package/dist/cjs/types/components/dialogs/confirm-dialog/index.d.ts +1 -0
- package/dist/cjs/types/components/dialogs/dialog-hooks/index.d.ts +1 -0
- package/dist/cjs/types/components/dialogs/dialog-hooks/use-dialog.d.ts +7 -0
- package/dist/cjs/types/components/dialogs/dialog.types.d.ts +26 -0
- package/dist/cjs/types/components/dialogs/form-dialog/form-dialog.d.ts +10 -0
- package/dist/cjs/types/components/dialogs/form-dialog/index.d.ts +1 -0
- package/dist/cjs/types/components/dialogs/index.d.ts +5 -0
- package/dist/cjs/types/components/index.d.ts +2 -0
- package/dist/cjs/types/components/inputs/enhanced-autocomplete/enhanced-autocomplete.d.ts +10 -0
- package/dist/cjs/types/components/inputs/enhanced-autocomplete/index.d.ts +1 -0
- package/dist/cjs/types/components/inputs/enhanced-text-field/enhanced-text-field.d.ts +7 -0
- package/dist/cjs/types/components/inputs/enhanced-text-field/index.d.ts +1 -0
- package/dist/cjs/types/components/inputs/index.d.ts +1 -0
- package/dist/cjs/types/components/value-displays/index.d.ts +2 -1
- package/dist/cjs/types/components/value-displays/value-base/index.d.ts +2 -0
- package/dist/cjs/types/components/value-displays/{value-displays.types.d.ts → value-base/value-displays.types.d.ts} +10 -0
- package/dist/cjs/types/components/value-displays/value-base/value-edit.d.ts +19 -0
- package/dist/cjs/types/components/value-displays/value-boolean/value-boolean.d.ts +3 -3
- package/dist/cjs/types/components/value-displays/value-datetime/value-datetime.d.ts +8 -3
- package/dist/cjs/types/components/value-displays/value-image/value-image.d.ts +1 -1
- package/dist/cjs/types/components/value-displays/value-rating/value-rating.d.ts +3 -3
- package/dist/cjs/types/components/value-displays/value-text/value-text.d.ts +3 -17
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/alerts/expandable-alert/expandable-alert.d.ts +11 -0
- package/dist/esm/types/components/alerts/expandable-alert/index.d.ts +1 -0
- package/dist/esm/types/components/alerts/index.d.ts +1 -0
- package/dist/esm/types/components/data-display/board/board.d.ts +10 -0
- package/dist/esm/types/components/data-display/board/index.d.ts +1 -0
- package/dist/esm/types/components/data-display/index.d.ts +2 -0
- package/dist/esm/types/components/data-display/markdown/index.d.ts +1 -0
- package/dist/esm/types/components/data-display/markdown/markdown.d.ts +7 -0
- package/dist/esm/types/components/dialogs/bootstrap-dialog/bootstrap-dialog.d.ts +2 -0
- package/dist/esm/types/components/dialogs/bootstrap-dialog/index.d.ts +1 -0
- package/dist/esm/types/components/dialogs/confirm-dialog/confirm-dialog.d.ts +10 -0
- package/dist/esm/types/components/dialogs/confirm-dialog/index.d.ts +1 -0
- package/dist/esm/types/components/dialogs/dialog-hooks/index.d.ts +1 -0
- package/dist/esm/types/components/dialogs/dialog-hooks/use-dialog.d.ts +7 -0
- package/dist/esm/types/components/dialogs/dialog.types.d.ts +26 -0
- package/dist/esm/types/components/dialogs/form-dialog/form-dialog.d.ts +10 -0
- package/dist/esm/types/components/dialogs/form-dialog/index.d.ts +1 -0
- package/dist/esm/types/components/dialogs/index.d.ts +5 -0
- package/dist/esm/types/components/index.d.ts +2 -0
- package/dist/esm/types/components/inputs/enhanced-autocomplete/enhanced-autocomplete.d.ts +10 -0
- package/dist/esm/types/components/inputs/enhanced-autocomplete/index.d.ts +1 -0
- package/dist/esm/types/components/inputs/enhanced-text-field/enhanced-text-field.d.ts +7 -0
- package/dist/esm/types/components/inputs/enhanced-text-field/index.d.ts +1 -0
- package/dist/esm/types/components/inputs/index.d.ts +1 -0
- package/dist/esm/types/components/value-displays/index.d.ts +2 -1
- package/dist/esm/types/components/value-displays/value-base/index.d.ts +2 -0
- package/dist/esm/types/components/value-displays/{value-displays.types.d.ts → value-base/value-displays.types.d.ts} +10 -0
- package/dist/esm/types/components/value-displays/value-base/value-edit.d.ts +19 -0
- package/dist/esm/types/components/value-displays/value-boolean/value-boolean.d.ts +3 -3
- package/dist/esm/types/components/value-displays/value-datetime/value-datetime.d.ts +8 -3
- package/dist/esm/types/components/value-displays/value-image/value-image.d.ts +1 -1
- package/dist/esm/types/components/value-displays/value-rating/value-rating.d.ts +3 -3
- package/dist/esm/types/components/value-displays/value-text/value-text.d.ts +3 -17
- package/dist/index.d.ts +130 -22
- package/package.json +34 -32
- package/src/components/alerts/alerts.stories.mdx +10 -0
- package/src/components/alerts/expandable-alert/expandable-alert.stories.tsx +48 -0
- package/src/components/alerts/expandable-alert/expandable-alert.test.tsx +114 -0
- package/src/components/alerts/expandable-alert/expandable-alert.tsx +71 -0
- package/src/components/alerts/expandable-alert/index.ts +1 -0
- package/src/components/alerts/index.ts +1 -0
- package/src/components/components.stories.mdx +2 -0
- package/src/components/data-display/board/board.stories.tsx +54 -0
- package/src/components/data-display/board/board.test.tsx +100 -0
- package/src/components/data-display/board/board.tsx +63 -0
- package/src/components/data-display/board/index.ts +1 -0
- package/src/components/data-display/data-display.stories.mdx +2 -0
- package/src/components/data-display/index.ts +2 -0
- package/src/components/data-display/markdown/index.ts +1 -0
- package/src/components/data-display/markdown/markdown.stories.tsx +25 -0
- package/src/components/data-display/markdown/markdown.test.tsx +64 -0
- package/src/components/data-display/markdown/markdown.tsx +38 -0
- package/src/components/dialogs/bootstrap-dialog/bootstrap-dialog.stories.tsx +81 -0
- package/src/components/dialogs/bootstrap-dialog/bootstrap-dialog.test.tsx +233 -0
- package/src/components/dialogs/bootstrap-dialog/bootstrap-dialog.tsx +95 -0
- package/src/components/dialogs/bootstrap-dialog/index.ts +1 -0
- package/src/components/dialogs/confirm-dialog/confirm-dialog.stories.tsx +43 -0
- package/src/components/dialogs/confirm-dialog/confirm-dialog.test.tsx +150 -0
- package/src/components/dialogs/confirm-dialog/confirm-dialog.tsx +51 -0
- package/src/components/dialogs/confirm-dialog/index.ts +1 -0
- package/src/components/dialogs/dialog-hooks/index.ts +1 -0
- package/src/components/dialogs/dialog-hooks/use-dialog.ts +10 -0
- package/src/components/dialogs/dialog.types.ts +27 -0
- package/src/components/dialogs/dialogs.stories.mdx +12 -0
- package/src/components/dialogs/form-dialog/form-dialog.stories.tsx +52 -0
- package/src/components/dialogs/form-dialog/form-dialog.test.tsx +164 -0
- package/src/components/dialogs/form-dialog/form-dialog.tsx +69 -0
- package/src/components/dialogs/form-dialog/index.ts +1 -0
- package/src/components/dialogs/index.ts +5 -0
- package/src/components/drawers/drawer-collapsable-item/drawer-collapsable-item.tsx +1 -3
- package/src/components/index.ts +2 -0
- package/src/components/inputs/enhanced-autocomplete/enhanced-autocomplete.stories.tsx +109 -0
- package/src/components/inputs/enhanced-autocomplete/enhanced-autocomplete.test.tsx +74 -0
- package/src/components/inputs/enhanced-autocomplete/enhanced-autocomplete.tsx +64 -0
- package/src/components/inputs/enhanced-autocomplete/index.ts +1 -0
- package/src/components/inputs/enhanced-text-field/enhanced-text-field.stories.tsx +120 -0
- package/src/components/inputs/enhanced-text-field/enhanced-text-field.test.tsx +63 -0
- package/src/components/inputs/enhanced-text-field/enhanced-text-field.tsx +101 -0
- package/src/components/inputs/enhanced-text-field/index.ts +1 -0
- package/src/components/inputs/index.ts +1 -0
- package/src/components/inputs/inputs.stories.mdx +2 -0
- package/src/components/value-displays/index.ts +2 -1
- package/src/components/value-displays/value-base/index.ts +2 -0
- package/src/components/value-displays/{value-displays.types.ts → value-base/value-displays.types.ts} +12 -2
- package/src/components/value-displays/value-base/value-edit.tsx +59 -0
- package/src/components/value-displays/value-boolean/value-boolean.stories.tsx +7 -0
- package/src/components/value-displays/value-boolean/value-boolean.test.tsx +81 -2
- package/src/components/value-displays/value-boolean/value-boolean.tsx +33 -8
- package/src/components/value-displays/value-datetime/value-datetime.stories.tsx +29 -2
- package/src/components/value-displays/value-datetime/value-datetime.test.tsx +103 -5
- package/src/components/value-displays/value-datetime/value-datetime.tsx +67 -6
- package/src/components/value-displays/value-image/value-image.tsx +1 -1
- package/src/components/value-displays/value-rating/value-rating.stories.tsx +8 -0
- package/src/components/value-displays/value-rating/value-rating.test.tsx +71 -2
- package/src/components/value-displays/value-rating/value-rating.tsx +29 -5
- package/src/components/value-displays/value-text/value-text.test.tsx +5 -8
- package/src/components/value-displays/value-text/value-text.tsx +16 -60
- package/src/generators/generators.mock.ts +3 -14
- package/src/generators/model-form/model-form.tsx +3 -6
- package/src/storybook.tsx +34 -2
- package/src/tests/actions.ts +3 -2
- package/src/tests/assertions.ts +17 -5
- package/src/tests/datatable-placeholder/datatable-placeholder.tsx +2 -4
- package/src/tests/mocks/markdown.mock.ts +25 -0
- package/src/types/index.d.ts +6 -0
- package/dist/cjs/types/generators/generators.model.test.d.ts +0 -1
- package/dist/cjs/types/utils/arrays.test.d.ts +0 -1
- package/dist/esm/types/generators/generators.model.test.d.ts +0 -1
- package/dist/esm/types/utils/arrays.test.d.ts +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { render, screen } from "~/tests/testing-library";
|
|
2
|
+
import { EnhancedAutocomplete } from "./enhanced-autocomplete";
|
|
3
|
+
import { faker } from "@faker-js/faker";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { selectOption } from "~/tests/actions";
|
|
6
|
+
import userEvents from "@testing-library/user-event";
|
|
7
|
+
import { expectProgressIndicator } from "~/tests/assertions";
|
|
8
|
+
|
|
9
|
+
const options: string[] = faker.definitions.vehicle?.model || [];
|
|
10
|
+
|
|
11
|
+
describe("EnhancedAutocomplete", () => {
|
|
12
|
+
const renderComponent = ({
|
|
13
|
+
value,
|
|
14
|
+
loading,
|
|
15
|
+
fetching,
|
|
16
|
+
}: { value?: string; loading?: boolean; fetching?: boolean } = {}) => {
|
|
17
|
+
const onChangeValue = jest.fn();
|
|
18
|
+
render(
|
|
19
|
+
<EnhancedAutocomplete
|
|
20
|
+
label="Car model"
|
|
21
|
+
loading={loading}
|
|
22
|
+
fetching={fetching}
|
|
23
|
+
value={value}
|
|
24
|
+
options={options}
|
|
25
|
+
onChangeValue={onChangeValue}
|
|
26
|
+
/>,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return { onChangeValue };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
it("should render a label", () => {
|
|
33
|
+
renderComponent();
|
|
34
|
+
|
|
35
|
+
expect(screen.getByRole("combobox", { name: /car model/i })).toBeVisible();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should render the value if is defined", () => {
|
|
39
|
+
const value = options[3];
|
|
40
|
+
renderComponent({ value });
|
|
41
|
+
|
|
42
|
+
expect(screen.getByRole("combobox")).toHaveValue(value);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should call onChange when the selected value changes", async () => {
|
|
46
|
+
const { onChangeValue } = renderComponent();
|
|
47
|
+
|
|
48
|
+
await selectOption(screen.getByRole("combobox"), "Model T");
|
|
49
|
+
|
|
50
|
+
expect(onChangeValue).toHaveBeenCalledTimes(1);
|
|
51
|
+
expect(onChangeValue).toHaveBeenCalledWith("Model T");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should filter the options if the user enters a search", async () => {
|
|
55
|
+
renderComponent();
|
|
56
|
+
|
|
57
|
+
await userEvents.type(screen.getByRole("combobox"), "Rang");
|
|
58
|
+
|
|
59
|
+
expect(screen.getByRole("option", { name: "Durango" })).toBeVisible();
|
|
60
|
+
expect(screen.getByRole("option", { name: "Wrangler" })).toBeVisible();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should render a loading indicator if is fetching", () => {
|
|
64
|
+
renderComponent({ loading: true });
|
|
65
|
+
|
|
66
|
+
expectProgressIndicator();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should render a loading indicator if is loading", () => {
|
|
70
|
+
renderComponent({ fetching: true });
|
|
71
|
+
|
|
72
|
+
expectProgressIndicator();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Autocomplete, AutocompleteProps, ChipTypeMap } from "@mui/material";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { EnhancedTextField } from "../enhanced-text-field";
|
|
4
|
+
|
|
5
|
+
export interface EnhancedAutocompleteProps<
|
|
6
|
+
T,
|
|
7
|
+
Multiple extends boolean | undefined,
|
|
8
|
+
DisableClearable extends boolean | undefined,
|
|
9
|
+
FreeSolo extends boolean | undefined,
|
|
10
|
+
ChipComponent extends React.ElementType = ChipTypeMap["defaultComponent"],
|
|
11
|
+
> extends Omit<
|
|
12
|
+
AutocompleteProps<T, Multiple, DisableClearable, FreeSolo, ChipComponent>,
|
|
13
|
+
"renderInput" | "onChange" | "color"
|
|
14
|
+
> {
|
|
15
|
+
label: string;
|
|
16
|
+
helperText?: string;
|
|
17
|
+
color?: string;
|
|
18
|
+
fetching?: boolean;
|
|
19
|
+
onChangeValue?: (value: T) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const EnhancedAutocomplete = <
|
|
23
|
+
T,
|
|
24
|
+
Multiple extends boolean | undefined,
|
|
25
|
+
DisableClearable extends boolean | undefined,
|
|
26
|
+
FreeSolo extends boolean | undefined,
|
|
27
|
+
ChipComponent extends React.ElementType = ChipTypeMap["defaultComponent"],
|
|
28
|
+
>(
|
|
29
|
+
props: EnhancedAutocompleteProps<T, Multiple, DisableClearable, FreeSolo, ChipComponent>,
|
|
30
|
+
) => {
|
|
31
|
+
const {
|
|
32
|
+
label,
|
|
33
|
+
loading,
|
|
34
|
+
fetching,
|
|
35
|
+
options,
|
|
36
|
+
helperText,
|
|
37
|
+
color,
|
|
38
|
+
onChangeValue = () => null,
|
|
39
|
+
sx,
|
|
40
|
+
...rest
|
|
41
|
+
} = props;
|
|
42
|
+
const loadingOrFetching = loading || fetching;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Autocomplete
|
|
46
|
+
loading={loadingOrFetching}
|
|
47
|
+
options={fetching ? [] : options}
|
|
48
|
+
onChange={(_, value) => onChangeValue(value as T)}
|
|
49
|
+
{...(rest as any)}
|
|
50
|
+
renderInput={(params) => (
|
|
51
|
+
<EnhancedTextField
|
|
52
|
+
{...params}
|
|
53
|
+
label={label}
|
|
54
|
+
fullWidth
|
|
55
|
+
fetching={fetching}
|
|
56
|
+
loading={loading}
|
|
57
|
+
hexColor={color}
|
|
58
|
+
helperText={helperText}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
sx={sx}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./enhanced-autocomplete";
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { ComponentMeta } from "@storybook/react";
|
|
2
|
+
import { createTemplate, withContainer } from "../../../storybook";
|
|
3
|
+
import { EnhancedTextField } from "./enhanced-text-field";
|
|
4
|
+
import { faker } from "@faker-js/faker";
|
|
5
|
+
import { Box, IconButton, useTheme } from "@mui/material";
|
|
6
|
+
import ClearIcon from "@mui/icons-material/Clear";
|
|
7
|
+
import React from "react";
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
title: "Components/Inputs/EnhancedTextField",
|
|
11
|
+
component: EnhancedTextField,
|
|
12
|
+
decorators: [withContainer({ width: 200 })],
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: "centered",
|
|
15
|
+
},
|
|
16
|
+
} as ComponentMeta<typeof EnhancedTextField>;
|
|
17
|
+
|
|
18
|
+
const options: string[] = faker.definitions.vehicle?.model || [];
|
|
19
|
+
|
|
20
|
+
const Template = createTemplate(EnhancedTextField);
|
|
21
|
+
|
|
22
|
+
export const Default = Template.bind({});
|
|
23
|
+
Default.args = {
|
|
24
|
+
label: "Car model",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const HelperText = Template.bind({});
|
|
28
|
+
HelperText.args = {
|
|
29
|
+
...Default.args,
|
|
30
|
+
helperText: "This is a helper text",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const Loading = Template.bind({});
|
|
34
|
+
Loading.args = {
|
|
35
|
+
...Default.args,
|
|
36
|
+
loading: true,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const LoadingWithValue = Template.bind({});
|
|
40
|
+
LoadingWithValue.args = {
|
|
41
|
+
...Default.args,
|
|
42
|
+
loading: true,
|
|
43
|
+
value: options[0],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Fetching = Template.bind({});
|
|
47
|
+
Fetching.args = {
|
|
48
|
+
...Default.args,
|
|
49
|
+
fetching: true,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const FetchingWithEndAdornment = Template.bind({});
|
|
53
|
+
FetchingWithEndAdornment.args = {
|
|
54
|
+
...Default.args,
|
|
55
|
+
fetching: true,
|
|
56
|
+
InputProps: {
|
|
57
|
+
endAdornment: (
|
|
58
|
+
<IconButton>
|
|
59
|
+
<ClearIcon />
|
|
60
|
+
</IconButton>
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const SizeSmall = Template.bind({});
|
|
66
|
+
SizeSmall.args = {
|
|
67
|
+
...Default.args,
|
|
68
|
+
size: "small",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const SizeSmallFetching = Template.bind({});
|
|
72
|
+
SizeSmallFetching.args = {
|
|
73
|
+
...Default.args,
|
|
74
|
+
size: "small",
|
|
75
|
+
fetching: true,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const Disabled = Template.bind({});
|
|
79
|
+
Disabled.args = {
|
|
80
|
+
...Default.args,
|
|
81
|
+
disabled: true,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const SizeSmallLoading = Template.bind({});
|
|
85
|
+
SizeSmallLoading.args = {
|
|
86
|
+
...Default.args,
|
|
87
|
+
size: "small",
|
|
88
|
+
loading: true,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
type WithBackgroundProps = {
|
|
92
|
+
bgcolor: "primary" | "secondary";
|
|
93
|
+
fetching?: boolean;
|
|
94
|
+
loading?: boolean;
|
|
95
|
+
};
|
|
96
|
+
export const WithBackground = ({
|
|
97
|
+
bgcolor: bgcolorProp,
|
|
98
|
+
fetching,
|
|
99
|
+
loading,
|
|
100
|
+
}: WithBackgroundProps) => {
|
|
101
|
+
const { palette } = useTheme();
|
|
102
|
+
const BackgroundColors: Record<"primary" | "secondary", string> = {
|
|
103
|
+
primary: palette.primary.main,
|
|
104
|
+
secondary: palette.secondary.main,
|
|
105
|
+
};
|
|
106
|
+
const bgcolor = BackgroundColors[bgcolorProp];
|
|
107
|
+
const color = palette.getContrastText(bgcolor);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Box bgcolor={bgcolor} padding={3}>
|
|
111
|
+
<EnhancedTextField label="Car model" hexColor={color} fetching={fetching} loading={loading} />
|
|
112
|
+
</Box>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
WithBackground.args = {
|
|
117
|
+
bgcolor: "secondary",
|
|
118
|
+
loading: true,
|
|
119
|
+
fetching: true,
|
|
120
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { render, screen } from "~/tests/testing-library";
|
|
2
|
+
import { EnhancedTextField } from "./enhanced-text-field";
|
|
3
|
+
import { faker } from "@faker-js/faker";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import userEvent from "@testing-library/user-event";
|
|
6
|
+
import { expectProgressIndicator } from "~/tests/assertions";
|
|
7
|
+
|
|
8
|
+
const options: string[] = faker.definitions.vehicle?.model || [];
|
|
9
|
+
|
|
10
|
+
describe("EnhancedAutocomplete", () => {
|
|
11
|
+
const renderComponent = ({
|
|
12
|
+
value,
|
|
13
|
+
loading,
|
|
14
|
+
fetching,
|
|
15
|
+
}: { value?: string; loading?: boolean; fetching?: boolean } = {}) => {
|
|
16
|
+
const onChange = jest.fn();
|
|
17
|
+
render(
|
|
18
|
+
<EnhancedTextField
|
|
19
|
+
label="Car model"
|
|
20
|
+
loading={loading}
|
|
21
|
+
fetching={fetching}
|
|
22
|
+
value={value}
|
|
23
|
+
onChange={onChange}
|
|
24
|
+
/>,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return { onChange };
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
it("should render a label", () => {
|
|
31
|
+
renderComponent();
|
|
32
|
+
|
|
33
|
+
expect(screen.getByRole("textbox", { name: /car model/i })).toBeVisible();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should render the value if is defined", () => {
|
|
37
|
+
const value = options[3];
|
|
38
|
+
renderComponent({ value });
|
|
39
|
+
|
|
40
|
+
expect(screen.getByRole("textbox")).toHaveValue(value);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should call onChange when we type something", async () => {
|
|
44
|
+
const { onChange } = renderComponent();
|
|
45
|
+
|
|
46
|
+
await userEvent.type(screen.getByRole("textbox"), "Model T");
|
|
47
|
+
|
|
48
|
+
expect(onChange).toHaveBeenCalledTimes(7);
|
|
49
|
+
expect(onChange.mock.calls[6][0].target.value).toBe("Model T");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should render a loading indicator if is fetching", () => {
|
|
53
|
+
renderComponent({ loading: true });
|
|
54
|
+
|
|
55
|
+
expectProgressIndicator();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should render a loading indicator if is loading", () => {
|
|
59
|
+
renderComponent({ fetching: true });
|
|
60
|
+
|
|
61
|
+
expectProgressIndicator();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
autocompleteClasses,
|
|
3
|
+
CircularProgress,
|
|
4
|
+
circularProgressClasses,
|
|
5
|
+
FormControl,
|
|
6
|
+
FormHelperText,
|
|
7
|
+
iconButtonClasses,
|
|
8
|
+
InputAdornment,
|
|
9
|
+
InputLabel,
|
|
10
|
+
inputLabelClasses,
|
|
11
|
+
LinearProgress,
|
|
12
|
+
linearProgressClasses,
|
|
13
|
+
OutlinedInput,
|
|
14
|
+
outlinedInputClasses,
|
|
15
|
+
TextFieldProps,
|
|
16
|
+
} from "@mui/material";
|
|
17
|
+
import { unstable_useId as useId } from "@mui/utils";
|
|
18
|
+
import React from "react";
|
|
19
|
+
|
|
20
|
+
export type EnhancedTextFieldProps = TextFieldProps & {
|
|
21
|
+
fetching?: boolean;
|
|
22
|
+
loading?: boolean;
|
|
23
|
+
hexColor?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const EnhancedTextField = ({
|
|
27
|
+
id: overrideId,
|
|
28
|
+
label,
|
|
29
|
+
InputLabelProps,
|
|
30
|
+
InputProps,
|
|
31
|
+
fetching,
|
|
32
|
+
loading,
|
|
33
|
+
helperText,
|
|
34
|
+
hexColor,
|
|
35
|
+
size,
|
|
36
|
+
fullWidth,
|
|
37
|
+
sx,
|
|
38
|
+
...rest
|
|
39
|
+
}: EnhancedTextFieldProps) => {
|
|
40
|
+
const id = useId(overrideId);
|
|
41
|
+
const helperTextId = helperText && id ? `${id}-helper-text` : undefined;
|
|
42
|
+
const inputLabelId = label && id ? `${id}-label` : undefined;
|
|
43
|
+
|
|
44
|
+
const hexColorSx = hexColor
|
|
45
|
+
? {
|
|
46
|
+
[`& .${outlinedInputClasses.notchedOutline}`]: {
|
|
47
|
+
borderColor: `${hexColor} !important`,
|
|
48
|
+
},
|
|
49
|
+
[`& .${inputLabelClasses.root}`]: {
|
|
50
|
+
color: hexColor,
|
|
51
|
+
},
|
|
52
|
+
[`& .${outlinedInputClasses.input}`]: {
|
|
53
|
+
color: hexColor,
|
|
54
|
+
},
|
|
55
|
+
[`& .${circularProgressClasses.root}`]: {
|
|
56
|
+
color: hexColor,
|
|
57
|
+
},
|
|
58
|
+
[`& .${linearProgressClasses.bar}`]: {
|
|
59
|
+
backgroundColor: hexColor,
|
|
60
|
+
},
|
|
61
|
+
[`& .${autocompleteClasses.endAdornment} .${iconButtonClasses.root}`]: {
|
|
62
|
+
color: hexColor,
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
: {};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<FormControl sx={hexColorSx} fullWidth={fullWidth}>
|
|
69
|
+
<InputLabel
|
|
70
|
+
size={size === "small" ? "small" : "normal"}
|
|
71
|
+
id={inputLabelId}
|
|
72
|
+
htmlFor={id}
|
|
73
|
+
{...InputLabelProps}
|
|
74
|
+
>
|
|
75
|
+
{label}
|
|
76
|
+
</InputLabel>
|
|
77
|
+
<OutlinedInput
|
|
78
|
+
{...InputProps}
|
|
79
|
+
id={id}
|
|
80
|
+
label={label}
|
|
81
|
+
size={size}
|
|
82
|
+
fullWidth={fullWidth}
|
|
83
|
+
endAdornment={
|
|
84
|
+
<InputAdornment position="end">
|
|
85
|
+
{InputProps?.endAdornment}
|
|
86
|
+
{fetching ? <CircularProgress color="inherit" size={20} sx={{ ml: 1 }} /> : null}
|
|
87
|
+
</InputAdornment>
|
|
88
|
+
}
|
|
89
|
+
sx={sx}
|
|
90
|
+
{...(rest as any)}
|
|
91
|
+
/>
|
|
92
|
+
{loading && (
|
|
93
|
+
<LinearProgress
|
|
94
|
+
color="inherit"
|
|
95
|
+
sx={{ position: "absolute", left: 0, right: 0, bottom: 0 }}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
{helperText && <FormHelperText id={helperTextId}>{helperText}</FormHelperText>}
|
|
99
|
+
</FormControl>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./enhanced-text-field";
|
|
@@ -8,4 +8,6 @@ import LinkTo from '@storybook/addon-links/react';
|
|
|
8
8
|
<ul>
|
|
9
9
|
<li><LinkTo kind="Components/Inputs/EnhancedSelect">EnhancedSelect</LinkTo></li>
|
|
10
10
|
<li><LinkTo kind="Components/Inputs/Sign In">Sign In</LinkTo></li>
|
|
11
|
+
<li><LinkTo kind="Components/Inputs/EnhancedAutocomplete">EnhancedAutocomplete</LinkTo></li>
|
|
12
|
+
<li><LinkTo kind="Components/Inputs/EnhancedTextField">EnhancedTextField</LinkTo></li>
|
|
11
13
|
</ul>
|
|
@@ -3,5 +3,6 @@ export * from "./value-card";
|
|
|
3
3
|
export * from "./value-boolean";
|
|
4
4
|
export * from "./value-datetime";
|
|
5
5
|
export * from "./group-value-card";
|
|
6
|
-
export * from "./value-displays.types";
|
|
6
|
+
export * from "./value-base/value-displays.types";
|
|
7
7
|
export * from "./value-rating";
|
|
8
|
+
export * from "./value-base";
|
package/src/components/value-displays/{value-displays.types.ts → value-base/value-displays.types.ts}
RENAMED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { ReactElement } from "react";
|
|
2
|
-
|
|
3
1
|
export interface BaseValueProps<T> {
|
|
4
2
|
/**
|
|
5
3
|
* Name of the displayed value
|
|
@@ -17,4 +15,16 @@ export interface BaseValueProps<T> {
|
|
|
17
15
|
placeholder?: string;
|
|
18
16
|
}
|
|
19
17
|
|
|
18
|
+
export interface EditableValueProps<T> {
|
|
19
|
+
/**
|
|
20
|
+
* This field can be edited or not
|
|
21
|
+
*/
|
|
22
|
+
editable?: boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Callback executed when the value is edited
|
|
26
|
+
*/
|
|
27
|
+
onEdit?: (value?: T) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
export const DefaultPlaceholder = "-";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import CheckIcon from "@mui/icons-material/Check";
|
|
3
|
+
import ClearIcon from "@mui/icons-material/Clear";
|
|
4
|
+
import { Button, InputAdornment, SxProps, Theme } from "@mui/material";
|
|
5
|
+
|
|
6
|
+
export interface ValueEditButtonsProps {
|
|
7
|
+
onClickCancel: () => void;
|
|
8
|
+
onSubmitEdit: () => void;
|
|
9
|
+
sx?: SxProps<Theme>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ValueEditButtons = ({ onClickCancel, onSubmitEdit, sx }: ValueEditButtonsProps) => {
|
|
13
|
+
return (
|
|
14
|
+
<InputAdornment position="end" sx={sx}>
|
|
15
|
+
<Button
|
|
16
|
+
variant="contained"
|
|
17
|
+
size="small"
|
|
18
|
+
color="error"
|
|
19
|
+
startIcon={<ClearIcon />}
|
|
20
|
+
onClick={onClickCancel}
|
|
21
|
+
sx={{ paddingRight: 0, minWidth: 0, marginRight: 1 }}
|
|
22
|
+
/>
|
|
23
|
+
<Button
|
|
24
|
+
variant="contained"
|
|
25
|
+
size="small"
|
|
26
|
+
color="primary"
|
|
27
|
+
startIcon={<CheckIcon />}
|
|
28
|
+
onClick={onSubmitEdit}
|
|
29
|
+
sx={{ paddingRight: 0, minWidth: 0 }}
|
|
30
|
+
/>
|
|
31
|
+
</InputAdornment>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Hook to manage the editing behaviour
|
|
37
|
+
*/
|
|
38
|
+
export const useEditableValueDisplay = <T,>(
|
|
39
|
+
initialValue: T | undefined,
|
|
40
|
+
onEdit: (value?: T) => void,
|
|
41
|
+
) => {
|
|
42
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
43
|
+
const [editValue, setEditValue] = useState<T | undefined>(initialValue);
|
|
44
|
+
|
|
45
|
+
const cancelEdit = () => {
|
|
46
|
+
setIsEditing(false);
|
|
47
|
+
setEditValue(initialValue);
|
|
48
|
+
};
|
|
49
|
+
const startEdit = () => {
|
|
50
|
+
setIsEditing(true);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const submitEdit = () => {
|
|
54
|
+
onEdit(editValue);
|
|
55
|
+
cancelEdit();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return { isEditing, cancelEdit, editValue, setEditValue, startEdit, submitEdit };
|
|
59
|
+
};
|
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { ValueBoolean } from "./value-boolean";
|
|
3
3
|
import { render, screen } from "~/tests/testing-library";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
4
5
|
|
|
5
6
|
describe("ValueBoolean", () => {
|
|
6
|
-
const renderComponent = ({
|
|
7
|
-
|
|
7
|
+
const renderComponent = ({
|
|
8
|
+
value,
|
|
9
|
+
placeholder,
|
|
10
|
+
editable,
|
|
11
|
+
}: {
|
|
12
|
+
value?: boolean;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
editable?: boolean;
|
|
15
|
+
}) => {
|
|
16
|
+
const onEdit = jest.fn();
|
|
17
|
+
render(
|
|
18
|
+
<ValueBoolean
|
|
19
|
+
label="Hello world"
|
|
20
|
+
value={value}
|
|
21
|
+
placeholder={placeholder}
|
|
22
|
+
editable={editable}
|
|
23
|
+
onEdit={onEdit}
|
|
24
|
+
/>,
|
|
25
|
+
);
|
|
26
|
+
return { onEdit };
|
|
8
27
|
};
|
|
9
28
|
|
|
10
29
|
it("would render the label", () => {
|
|
@@ -36,4 +55,64 @@ describe("ValueBoolean", () => {
|
|
|
36
55
|
|
|
37
56
|
expect(screen.getByText(/_/i)).toBeInTheDocument();
|
|
38
57
|
});
|
|
58
|
+
|
|
59
|
+
describe("editable", () => {
|
|
60
|
+
it("shouldn't render an option to edit if editable is false", () => {
|
|
61
|
+
renderComponent({ value: true, editable: false });
|
|
62
|
+
|
|
63
|
+
expect(screen.queryByTestId("EditIcon")).not.toBeInTheDocument();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should render an option to edit if editable is true", () => {
|
|
67
|
+
renderComponent({ value: true, editable: true });
|
|
68
|
+
|
|
69
|
+
expect(screen.getByTestId("EditIcon")).toBeVisible();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should render an input with the value if the edit button is clicked", async () => {
|
|
73
|
+
renderComponent({ value: true, editable: true });
|
|
74
|
+
|
|
75
|
+
await userEvent.click(screen.getByTestId("EditIcon"));
|
|
76
|
+
|
|
77
|
+
expect(screen.getByRole("checkbox")).toBeChecked();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it.each([
|
|
81
|
+
[true, false],
|
|
82
|
+
[false, true],
|
|
83
|
+
])(
|
|
84
|
+
"should submit %s if is edited and the initial value is %s",
|
|
85
|
+
async (expectedValue: boolean, initialValue: boolean) => {
|
|
86
|
+
const { onEdit } = renderComponent({ value: initialValue, editable: true });
|
|
87
|
+
|
|
88
|
+
await userEvent.click(screen.getByTestId("EditIcon"));
|
|
89
|
+
await userEvent.click(screen.getByRole("checkbox"));
|
|
90
|
+
await userEvent.click(screen.getByTestId("CheckIcon"));
|
|
91
|
+
|
|
92
|
+
expect(onEdit).toHaveBeenCalledTimes(1);
|
|
93
|
+
expect(onEdit).toHaveBeenCalledWith(expectedValue);
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
it("should not call onEdit if the edition is cancelled", async () => {
|
|
98
|
+
const { onEdit } = renderComponent({ value: true, editable: true });
|
|
99
|
+
|
|
100
|
+
await userEvent.click(screen.getByTestId("EditIcon"));
|
|
101
|
+
await userEvent.click(screen.getByRole("checkbox"));
|
|
102
|
+
await userEvent.click(screen.getByTestId("ClearIcon"));
|
|
103
|
+
|
|
104
|
+
expect(onEdit).not.toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should have the original value if is edited again after clear a change", async () => {
|
|
108
|
+
renderComponent({ value: true, editable: true });
|
|
109
|
+
|
|
110
|
+
await userEvent.click(screen.getByTestId("EditIcon"));
|
|
111
|
+
await userEvent.click(screen.getByRole("checkbox"));
|
|
112
|
+
await userEvent.click(screen.getByTestId("ClearIcon"));
|
|
113
|
+
await userEvent.click(screen.getByTestId("EditIcon"));
|
|
114
|
+
|
|
115
|
+
expect(screen.getByRole("checkbox")).toBeChecked();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
39
118
|
});
|