cemtrik-dependencies 1.0.4

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.
Files changed (92) hide show
  1. package/README copy.md +54 -0
  2. package/README.md +29 -0
  3. package/components.js +47 -0
  4. package/hooks.js +4 -0
  5. package/package.json +58 -0
  6. package/postcss.config.js +6 -0
  7. package/src/assets/cemtrik-icons-v1.0/Read Me.txt +7 -0
  8. package/src/assets/cemtrik-icons-v1.0/demo-files/demo.css +152 -0
  9. package/src/assets/cemtrik-icons-v1.0/demo-files/demo.js +30 -0
  10. package/src/assets/cemtrik-icons-v1.0/demo.html +318 -0
  11. package/src/assets/cemtrik-icons-v1.0/fonts/cemtrik-icons.eot +0 -0
  12. package/src/assets/cemtrik-icons-v1.0/fonts/cemtrik-icons.svg +31 -0
  13. package/src/assets/cemtrik-icons-v1.0/fonts/cemtrik-icons.ttf +0 -0
  14. package/src/assets/cemtrik-icons-v1.0/fonts/cemtrik-icons.woff +0 -0
  15. package/src/assets/cemtrik-icons-v1.0/selection.json +1 -0
  16. package/src/assets/cemtrik-icons-v1.0/style.css +96 -0
  17. package/src/assets/icons/actions.svg +4 -0
  18. package/src/assets/icons/attributes.svg +5 -0
  19. package/src/assets/icons/bars.svg +3 -0
  20. package/src/assets/icons/bell.svg +4 -0
  21. package/src/assets/icons/cemtrik.svg +9 -0
  22. package/src/assets/icons/clipboardCheck.svg +4 -0
  23. package/src/assets/icons/clipboardList.svg +5 -0
  24. package/src/assets/icons/clock.svg +3 -0
  25. package/src/assets/icons/flag.svg +3 -0
  26. package/src/assets/icons/folder.svg +3 -0
  27. package/src/assets/icons/graph_indicators.svg +3 -0
  28. package/src/assets/icons/group.svg +3 -0
  29. package/src/assets/icons/letter.svg +3 -0
  30. package/src/assets/icons/measuring_points.svg +3 -0
  31. package/src/assets/icons/reports.svg +4 -0
  32. package/src/assets/icons/roboot.svg +3 -0
  33. package/src/assets/icons/user.svg +4 -0
  34. package/src/assets/icons/user_point.svg +10 -0
  35. package/src/assets/icons/vector.svg +3 -0
  36. package/src/assets/icons/vector2.svg +3 -0
  37. package/src/components/atoms/Alert/index.js +104 -0
  38. package/src/components/atoms/Alert/index.test.js +36 -0
  39. package/src/components/atoms/Avatar/index.js +63 -0
  40. package/src/components/atoms/Avatar/index.test.js +30 -0
  41. package/src/components/atoms/Bullets/Bullets.test.js +22 -0
  42. package/src/components/atoms/Bullets/index.js +51 -0
  43. package/src/components/atoms/ButtonOutline/index.js +43 -0
  44. package/src/components/atoms/ButtonOutline/index.test.js +36 -0
  45. package/src/components/atoms/ButtonPagination/index.js +37 -0
  46. package/src/components/atoms/ButtonPagination/index.test.js +36 -0
  47. package/src/components/atoms/ButtonSolid/index.js +74 -0
  48. package/src/components/atoms/ButtonSolid/index.test.js +59 -0
  49. package/src/components/atoms/Checkbox/index.js +52 -0
  50. package/src/components/atoms/Checkbox/index.test.js +15 -0
  51. package/src/components/atoms/ConfirmationAbandoningCreation/index.js +70 -0
  52. package/src/components/atoms/ConfirmationAbandoningCreation/index.test.js +70 -0
  53. package/src/components/atoms/Divider/index.js +10 -0
  54. package/src/components/atoms/Divider/index.test.js +12 -0
  55. package/src/components/atoms/GoBack/index.js +30 -0
  56. package/src/components/atoms/GoBack/index.test.js +24 -0
  57. package/src/components/atoms/Input/index.js +107 -0
  58. package/src/components/atoms/Input/index.test.js +63 -0
  59. package/src/components/atoms/InputDropdown/index.js +111 -0
  60. package/src/components/atoms/InputDropdown/index.test.js +86 -0
  61. package/src/components/atoms/Select/index.js +199 -0
  62. package/src/components/atoms/Select/index.test.js +86 -0
  63. package/src/components/atoms/Spinner/index.js +49 -0
  64. package/src/components/atoms/Spinner/index.test.js +9 -0
  65. package/src/components/atoms/Switch/index.js +46 -0
  66. package/src/components/atoms/Switch/index.test.js +18 -0
  67. package/src/components/atoms/Textarea/index.js +136 -0
  68. package/src/components/atoms/Textarea/index.spec.js +51 -0
  69. package/src/components/atoms/Tooltip/index.js +64 -0
  70. package/src/components/atoms/Tooltip/index.test.js +31 -0
  71. package/src/components/atoms/UploadImage/index.js +55 -0
  72. package/src/components/atoms/UploadImage/index.test.js +36 -0
  73. package/src/components/molecules/Dropdown/index.js +315 -0
  74. package/src/components/molecules/Dropdown/index.test.js +190 -0
  75. package/src/components/molecules/Modal/index.js +103 -0
  76. package/src/components/molecules/Modal/index.test.js +42 -0
  77. package/src/components/molecules/Pagination/index.js +126 -0
  78. package/src/components/molecules/Pagination/index.test.js +57 -0
  79. package/src/components/templates/Accordion/index.js +174 -0
  80. package/src/components/templates/Accordion/index.test.js +130 -0
  81. package/src/hooks/useCloseModal/index.js +27 -0
  82. package/src/hooks/useCloseModal/index.test.js +26 -0
  83. package/src/hooks/useForm/index.js +70 -0
  84. package/src/hooks/useForm/useForm.test.js +104 -0
  85. package/src/hooks/useMediaQuery/index.js +53 -0
  86. package/src/hooks/useMediaQuery/useMediaQuery.test.js +46 -0
  87. package/src/hooks/useWindowDimensions/index.js +34 -0
  88. package/src/hooks/useWindowDimensions/useWindowDimensions.test.js +19 -0
  89. package/src/utils/index.js +32 -0
  90. package/src/utils/index.test.js +56 -0
  91. package/tailwind.config.js +117 -0
  92. package/utils.js +8 -0
@@ -0,0 +1,315 @@
1
+ // 1.- libraries
2
+ import { Fragment, useState, useEffect } from "react";
3
+ import { Menu, Transition } from "@headlessui/react";
4
+
5
+ // 2.- components
6
+ import { SpinnerIcon } from "../../atoms/Spinner";
7
+ import Input from "../../atoms/Input";
8
+ import ButtonSolid from "../../atoms/ButtonSolid";
9
+
10
+ // 3.- icons
11
+ import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/solid";
12
+
13
+ // 4.- utils
14
+ import { classNames } from "../../../utils";
15
+
16
+ const Dropdown = ({
17
+ children,
18
+ data,
19
+ className = "",
20
+ classNameTooltip = "w-full",
21
+ classNameMenu = "",
22
+ classNameMenuItems = "",
23
+ classNameContainerSearch = "px-5",
24
+ isSelectMultiple = false,
25
+ optionsSelect,
26
+ propObject = "name",
27
+ propId = "id",
28
+ textNewItem = "",
29
+ defaultId = null,
30
+ loading = false,
31
+ search = null,
32
+ searchPlaceHolder = "Buscar...",
33
+ valueSearch = undefined,
34
+ idsOptionSelect = null,
35
+ isComponent,
36
+ hiddenMenu = false,
37
+ id,
38
+ onClick = () => {},
39
+ isRelative = true,
40
+ isMaxWidth = false,
41
+ isTag = false,
42
+ isClear = false,
43
+ clear = null,
44
+ applyButton = null,
45
+ applyButtonText = "Aplicar",
46
+ tagsParent = [],
47
+ isReadOnly,
48
+ isDisabledApplyButton = true,
49
+ isOpenMenu = undefined,
50
+ }) => {
51
+ const [ids, setIds] = useState(new Set());
52
+ const [tags, setTags] = useState(new Map());
53
+
54
+ useEffect(() => {
55
+ if (idsOptionSelect === null) return;
56
+ setIds(new Set(new Set(idsOptionSelect)));
57
+ }, [idsOptionSelect]);
58
+
59
+ useEffect(() => {
60
+ if (tagsParent.length === 0) return;
61
+ const map = new Map();
62
+ tagsParent.forEach((item) => map.set(item.id, item));
63
+ setTags(map);
64
+ }, [tagsParent]);
65
+
66
+ useEffect(() => {
67
+ if (!isClear) return;
68
+ setIds(new Set());
69
+ setTags(new Map());
70
+ clear && clear();
71
+ }, [clear, isClear]);
72
+
73
+ const selectOption = (item) => {
74
+ let tagsMap = new Map(tags);
75
+ let set = new Set(ids);
76
+ let uuid = null;
77
+
78
+ if (defaultId !== null) {
79
+ item[propId] === defaultId && (uuid = crypto.randomUUID());
80
+ uuid !== null && (item[propId] = uuid);
81
+ uuid !== null && (item[propObject] = item[propObject].replace(textNewItem, "").trim());
82
+ }
83
+
84
+ set.has(item[propId]) ? set.delete(item[propId]) : set.add(item[propId]);
85
+ tagsMap.has(item[propId]) ? tagsMap.delete(item[propId]) : tagsMap.set(item[propId], item);
86
+
87
+ if (!isSelectMultiple) {
88
+ const arrSet = Array.from(set);
89
+ const arrMap = Array.from(tagsMap);
90
+ arrSet.shift();
91
+ arrMap.shift();
92
+ set = set.size === 2 ? new Set(arrSet) : set;
93
+ tagsMap = tagsMap.size === 2 ? new Map(arrMap) : tagsMap;
94
+ }
95
+
96
+ optionsSelect(item, Array.from(set), uuid !== null);
97
+ setIds(set);
98
+ setTags(tagsMap);
99
+ };
100
+
101
+ const handleClick = (isClose) => {
102
+ onClick(isClose === true ? -1 : id);
103
+ };
104
+
105
+ const deleteTag = (item) => {
106
+ const set = new Set(ids);
107
+ const tagsMap = new Map(tags);
108
+ set.delete(item[propId]);
109
+ tagsMap.delete(item[propId]);
110
+
111
+ optionsSelect(item, Array.from(set));
112
+ setIds(set);
113
+ setTags(tagsMap);
114
+ };
115
+
116
+ function capitalizeFirstLetter(text) {
117
+ if (typeof text !== "string") return text;
118
+ const textLowerCase = text.toLowerCase();
119
+ return textLowerCase.charAt(0).toUpperCase() + textLowerCase.slice(1);
120
+ }
121
+
122
+ return (
123
+ <Menu
124
+ as="div"
125
+ className={classNames(
126
+ "inline-block text-left",
127
+ isRelative && "relative",
128
+ classNameMenu !== "" && classNameMenu,
129
+ )}
130
+ onClick={() => handleClick(true)}
131
+ data-testid={`menu-${id}`}
132
+ id={id}
133
+ >
134
+ <style>{`
135
+ .blue {
136
+ background-color: #3758F9;
137
+ }
138
+ .w-custom {
139
+ width: 320px;
140
+ }
141
+ @media(min-width: 375px) {
142
+ .w-custom {
143
+ width: 375px;
144
+ }
145
+ }
146
+ @media(min-width: 500px) {
147
+ .w-custom {
148
+ width: 414px;
149
+ }
150
+ }
151
+ `}</style>
152
+ <Menu.Button
153
+ as="div"
154
+ onClick={() => handleClick(hiddenMenu)}
155
+ data-testid={`menu-button-${id}`}
156
+ >
157
+ {children}
158
+ </Menu.Button>
159
+ <div
160
+ className={classNames(
161
+ "absolute right-0 z-10",
162
+ classNameTooltip,
163
+ hiddenMenu && "hidden",
164
+ loading && "w-full",
165
+ )}
166
+ >
167
+ <Transition
168
+ as={Fragment}
169
+ enter="transition ease-out duration-100"
170
+ enterFrom="transform opacity-0 scale-95"
171
+ enterTo="transform opacity-100 scale-100"
172
+ leave="transition ease-in duration-75"
173
+ leaveFrom="transform opacity-100 scale-100"
174
+ leaveTo="transform opacity-0 scale-95"
175
+ show={isOpenMenu}
176
+ >
177
+ <Menu.Items
178
+ className={classNames(
179
+ "mt-1 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none",
180
+ loading ? "w-full" : classNameMenuItems === "" && "w-max",
181
+ classNameMenuItems !== "" && classNameMenuItems,
182
+ )}
183
+ >
184
+ {loading ? (
185
+ <div className="flex justify-center py-4">
186
+ <SpinnerIcon />
187
+ </div>
188
+ ) : (
189
+ <div
190
+ className={classNames(
191
+ !isComponent && "pb-1",
192
+ "divide-y divide-gray-100",
193
+ className !== "" && className,
194
+ isMaxWidth && "w-custom",
195
+ )}
196
+ data-testid="dropdown-menu"
197
+ >
198
+ <div className="flex justify-between items-center sticky top-0 bg-white w-full">
199
+ {search !== null && (
200
+ <div
201
+ className={classNames(
202
+ "py-2 w-full",
203
+ classNameContainerSearch,
204
+ )}
205
+ >
206
+ <Input
207
+ htmlFor="search"
208
+ id={`dropdown-search-${id}`}
209
+ name="search"
210
+ type="text"
211
+ placeholder={searchPlaceHolder}
212
+ handleChange={(e) => search(e, id)}
213
+ icon={
214
+ searchPlaceHolder !== "" && (
215
+ <MagnifyingGlassIcon className="w-4 h-4 text-indigo-300" />
216
+ )
217
+ }
218
+ className="pl-5 pr-4 h-8"
219
+ classNameContainerInput="w-full"
220
+ value={valueSearch}
221
+ onKeyDown={(e) =>
222
+ e.key === " " && e.stopPropagation()
223
+ }
224
+ />
225
+ </div>
226
+ )}
227
+ {applyButton !== null && (
228
+ <div className="pl-1 pr-2 pt-1.5">
229
+ <ButtonSolid
230
+ classNameButton="bg-secondary-2 hover:bg-secondary-3 font-medium px-5 py-1.5 rounded-md"
231
+ outline={
232
+ ids.size === 0 && isDisabledApplyButton
233
+ ? ""
234
+ : "0px 0px 0px 2px #3758F9, 0px 0px 0px 2px #243BBD, 0px 1px 2px 0px rgba(0, 0, 0, 0.05)"
235
+ }
236
+ classNameText="text-sm text-white text-center"
237
+ id="apply"
238
+ disabled={ids.size === 0 && isDisabledApplyButton}
239
+ onClick={() =>
240
+ applyButton({
241
+ id,
242
+ items: Array.from(tags.values()),
243
+ })
244
+ }
245
+ >
246
+ {applyButtonText}
247
+ </ButtonSolid>
248
+ </div>
249
+ )}
250
+ </div>
251
+ {isTag && !isReadOnly && (
252
+ <div className="flex flex-wrap ml-7 pt-1">
253
+ {Array.from(tags).map((item, index) => (
254
+ <div
255
+ key={item}
256
+ className={classNames(index > 0 && "ml-1")}
257
+ >
258
+ <span className="inline-flex items-center gap-x-0.5 rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-600 mb-1">
259
+ {item[1][propObject]}
260
+ <button
261
+ type="button"
262
+ className="p-0.5 rounded-sm bg-gray-100 hover:bg-gray-300"
263
+ onClick={() => deleteTag(item[1])}
264
+ data-testid={`tag-${item[0]}`}
265
+ >
266
+ <XMarkIcon className="h-2.5 w-2.5 stroke-gray-700/50 group-hover:stroke-gray-700/75" />
267
+ </button>
268
+ </span>
269
+ </div>
270
+ ))}
271
+ </div>
272
+ )}
273
+ {(isReadOnly ? [] : data).map((item, index) => (
274
+ <div key={index} className={classNames(!isComponent && "py-1")}>
275
+ {isComponent ? (
276
+ <div
277
+ onClick={(e) => {
278
+ e.preventDefault();
279
+ optionsSelect();
280
+ }}
281
+ data-testid={`item-component-${index}`}
282
+ >
283
+ {item}
284
+ </div>
285
+ ) : (
286
+ <Menu.Item>
287
+ <span
288
+ className={classNames(
289
+ ids.has(item[propId]) && "blue text-white",
290
+ !ids.has(item[propId]) &&
291
+ "text-gray-700 hover:bg-gray-100 hover:text-gray-900",
292
+ "block px-4 py-2 text-13 lg:text-sm cursor-pointer font-normal",
293
+ )}
294
+ data-testid={`item-${index}`}
295
+ onClick={(e) => {
296
+ e.preventDefault();
297
+ selectOption(item);
298
+ }}
299
+ >
300
+ {capitalizeFirstLetter(item[propObject])}
301
+ </span>
302
+ </Menu.Item>
303
+ )}
304
+ </div>
305
+ ))}
306
+ </div>
307
+ )}
308
+ </Menu.Items>
309
+ </Transition>
310
+ </div>
311
+ </Menu>
312
+ );
313
+ };
314
+
315
+ export default Dropdown;
@@ -0,0 +1,190 @@
1
+ import { render, fireEvent, act } from "@testing-library/react";
2
+ import Dropdown from "./index";
3
+
4
+ describe("Dropdown", () => {
5
+ afterEach(() => {
6
+ jest.resetAllMocks();
7
+ });
8
+
9
+ it("renders Dropdown component without errors", () => {
10
+ render(
11
+ <Dropdown
12
+ isRelative={false}
13
+ data={[
14
+ { name: "data1", id: 1 },
15
+ { name: "data2", id: 2 },
16
+ ]}
17
+ loading
18
+ classNameMenuItems="text-xs"
19
+ hiddenMenu
20
+ >
21
+ <p>List1</p>
22
+ <p>List2</p>
23
+ <p>List3</p>
24
+ </Dropdown>,
25
+ );
26
+ });
27
+
28
+ it("select option", async () => {
29
+ const mockOptionsSelect = jest.fn();
30
+ const mockOnClick = jest.fn();
31
+ const component = render(
32
+ <Dropdown
33
+ data={[
34
+ { name: "data1", id: 1 },
35
+ { name: "data2", id: 2 },
36
+ { name: "data3", id: 3 },
37
+ { name: 4, id: 4 },
38
+ ]}
39
+ defaultId={1}
40
+ propObject="name"
41
+ optionsSelect={mockOptionsSelect}
42
+ className="w-full"
43
+ isMaxWidth
44
+ id="dropdown-test"
45
+ onClick={mockOnClick}
46
+ classNameMenu="text-xs"
47
+ textNewItem="data6"
48
+ >
49
+ <p>dropdown</p>
50
+ </Dropdown>,
51
+ );
52
+ const menuButton = component.getByTestId("menu-button-dropdown-test");
53
+ const menu = component.getByTestId("menu-dropdown-test");
54
+ await act(async () => fireEvent.click(menu));
55
+ await act(async () => fireEvent.click(menuButton));
56
+ expect(mockOnClick).toHaveBeenCalledWith(-1);
57
+
58
+ const item1 = component.getByTestId("item-0");
59
+ const item2 = component.getByTestId("item-1");
60
+ const item3 = component.getByTestId("item-2");
61
+ await act(async () => fireEvent.click(item1));
62
+ expect(item1).toHaveClass("blue text-white");
63
+ await act(async () => fireEvent.click(item1));
64
+ await act(async () => fireEvent.click(item2));
65
+ expect(item2).toHaveClass("blue text-white");
66
+ expect(item1).toHaveClass("text-gray-700 hover:bg-gray-100 hover:text-gray-900");
67
+ await act(async () => fireEvent.click(item3));
68
+ expect(item3).toHaveClass("blue text-white");
69
+ expect(mockOptionsSelect).toHaveBeenCalledTimes(4);
70
+ });
71
+
72
+ it("select multiple option", async () => {
73
+ const component = render(
74
+ <Dropdown
75
+ data={[
76
+ { name: "data1", id: 1 },
77
+ { name: "data2", id: 2 },
78
+ { name: "data3", id: 3 },
79
+ { name: "data4", id: 4 },
80
+ ]}
81
+ propObject="name"
82
+ isSelectMultiple
83
+ optionsSelect={() => {}}
84
+ id="dropdown-test"
85
+ isTag
86
+ >
87
+ <p>dropdown</p>
88
+ </Dropdown>,
89
+ );
90
+ const menuButton = component.getByTestId("menu-button-dropdown-test");
91
+ await act(async () => fireEvent.click(menuButton));
92
+
93
+ const item1 = component.getByTestId("item-1");
94
+ const item2 = component.getByTestId("item-2");
95
+ const item3 = component.getByTestId("item-3");
96
+ await act(async () => fireEvent.click(item1));
97
+ await act(async () => fireEvent.click(item2));
98
+ await act(async () => fireEvent.click(item3));
99
+
100
+ expect(item1).toHaveClass("blue text-white");
101
+ expect(item2).toHaveClass("blue text-white");
102
+ expect(item3).toHaveClass("blue text-white");
103
+
104
+ const tags = component.getAllByTestId(/tag-/i);
105
+ expect(tags.length).toBe(3);
106
+ await act(async () => fireEvent.click(tags[1]));
107
+
108
+ const tags2 = component.getAllByTestId(/tag-/i);
109
+ expect(tags2.length).toBe(2);
110
+
111
+ await act(async () => fireEvent.click(item3));
112
+ expect(item3).toHaveClass("text-gray-700 hover:bg-gray-100 hover:text-gray-900");
113
+ });
114
+
115
+ it("search input", async () => {
116
+ const mockSearch = jest.fn();
117
+ const component = render(
118
+ <Dropdown
119
+ data={["item1", "item2", "item3"]}
120
+ isComponent
121
+ optionsSelect={() => {}}
122
+ id="dropdown-test"
123
+ isTag
124
+ idOptionSelect={0}
125
+ search={mockSearch}
126
+ >
127
+ <p>dropdown</p>
128
+ </Dropdown>,
129
+ );
130
+ const menuButton = component.getByTestId("menu-button-dropdown-test");
131
+ await act(async () => fireEvent.click(menuButton));
132
+ const item = component.getByTestId("item-component-1");
133
+ fireEvent.click(item);
134
+
135
+ const input = component.getByTestId("input-dropdown-search-dropdown-test");
136
+ fireEvent.change(input, { target: { value: "data2" } });
137
+ fireEvent.keyDown(input, { key: " ", keyCode: 32 });
138
+ expect(mockSearch).toHaveBeenCalled();
139
+ });
140
+
141
+ it("applyButton", async () => {
142
+ const mockApplyButton = jest.fn();
143
+ const component = render(
144
+ <Dropdown
145
+ data={[
146
+ { name: "data1", id: 1 },
147
+ { name: "data2", id: 2 },
148
+ { name: "data3", id: 3 },
149
+ ]}
150
+ optionsSelect={() => {}}
151
+ id="dropdown-test"
152
+ onClick={() => {}}
153
+ applyButton={mockApplyButton}
154
+ >
155
+ <p>dropdown</p>
156
+ </Dropdown>,
157
+ );
158
+ const menuButton = component.getByTestId("menu-button-dropdown-test");
159
+ await act(async () => fireEvent.click(menuButton));
160
+ const item = component.getByTestId("item-1");
161
+ fireEvent.click(item);
162
+
163
+ const button = component.getByTestId("button-solid-apply");
164
+ fireEvent.click(button);
165
+ expect(mockApplyButton).toHaveBeenCalled();
166
+ });
167
+
168
+ it("clear ids and tags", async () => {
169
+ const mockClear = jest.fn();
170
+ render(
171
+ <Dropdown
172
+ data={[
173
+ { name: "data1", id: 1 },
174
+ { name: "data2", id: 2 },
175
+ { name: "data3", id: 3 },
176
+ ]}
177
+ id="dropdown-test"
178
+ idsOptionSelect={[1]}
179
+ isChangeOptionIdsOptionSelect
180
+ tagsParent={[{ id: 1 }]}
181
+ clear={mockClear}
182
+ isClear
183
+ isReadOnly
184
+ >
185
+ <p>dropdown</p>
186
+ </Dropdown>,
187
+ );
188
+ expect(mockClear).toHaveBeenCalled();
189
+ });
190
+ });
@@ -0,0 +1,103 @@
1
+ // 1.- libraries
2
+ import { Fragment } from "react";
3
+ import { Dialog, Transition } from "@headlessui/react";
4
+ import { CheckIcon } from "@heroicons/react/24/outline";
5
+
6
+ // 2.- components
7
+ import ButtonSolid from "../../atoms/ButtonSolid";
8
+
9
+ // 3.- utils
10
+ import { classNames } from "../../../utils";
11
+
12
+ export default function Modal({
13
+ children,
14
+ className = "sm:max-w-sm w-11/12 sm:my-8",
15
+ classNameContainer = "",
16
+ isOpen = false,
17
+ setIsOpen,
18
+ showIconSuccess = true,
19
+ showButton = true,
20
+ }) {
21
+ return (
22
+ <>
23
+ <style>{`
24
+ .bg-opacity-35 {
25
+ opacity: .35;
26
+ }
27
+ `}</style>
28
+ <Transition.Root show={isOpen} as={Fragment}>
29
+ <Dialog
30
+ as="div"
31
+ className="relative z-50 relative"
32
+ onClose={setIsOpen}
33
+ data-testid="modal"
34
+ >
35
+ <Transition.Child
36
+ as={Fragment}
37
+ enter="ease-out duration-300"
38
+ enterFrom="opacity-0"
39
+ enterTo="opacity-100"
40
+ leave="ease-in duration-200"
41
+ leaveFrom="opacity-100"
42
+ leaveTo="opacity-0"
43
+ >
44
+ <div
45
+ className="fixed inset-0 bg-opacity-35 bg-gray-900 transition-opacity"
46
+ data-testid="window-close"
47
+ onClick={() => setIsOpen(false)}
48
+ />
49
+ </Transition.Child>
50
+
51
+ <div className="fixed inset-0 z-10 overflow-y-auto">
52
+ <div className="flex min-h-full items-center justify-center xs:p-2 p-4 text-center sm:items-center sm:p-0">
53
+ <Transition.Child
54
+ as={Fragment}
55
+ enter="ease-out duration-300"
56
+ enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
57
+ enterTo="opacity-100 translate-y-0 sm:scale-100"
58
+ leave="ease-in duration-200"
59
+ leaveFrom="opacity-100 translate-y-0 sm:scale-100"
60
+ leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
61
+ >
62
+ <Dialog.Panel
63
+ className={classNames(
64
+ "relative transform rounded-lg bg-white px-4 pb-3 pt-5 text-left shadow-xl transition-all sm:w-full sm:pt-3 sm:px-3",
65
+ className !== "" && className,
66
+ )}
67
+ >
68
+ {showIconSuccess && (
69
+ <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
70
+ <CheckIcon
71
+ className="h-6 w-6 text-green-600"
72
+ aria-hidden="true"
73
+ />
74
+ </div>
75
+ )}
76
+ <div
77
+ className={classNames(
78
+ classNameContainer !== "" && classNameContainer,
79
+ )}
80
+ data-testid="content"
81
+ >
82
+ {children}
83
+ </div>
84
+ {showButton && (
85
+ <div className="mt-5 sm:mt-6">
86
+ <ButtonSolid
87
+ id="close-modal"
88
+ onClick={() => setIsOpen(false)}
89
+ >
90
+ Cerrar
91
+ </ButtonSolid>
92
+ </div>
93
+ )}
94
+ <button className="opacity-0 absolute"></button>
95
+ </Dialog.Panel>
96
+ </Transition.Child>
97
+ </div>
98
+ </div>
99
+ </Dialog>
100
+ </Transition.Root>
101
+ </>
102
+ );
103
+ }
@@ -0,0 +1,42 @@
1
+ import { render, fireEvent, act } from "@testing-library/react";
2
+ import Modal from "./index";
3
+
4
+ global.ResizeObserver = class ResizeObserver {
5
+ observe() {}
6
+ unobserve() {}
7
+ disconnect() {}
8
+ };
9
+
10
+ describe("Modal", () => {
11
+ it("renders Modal component without errors", () => {
12
+ render(
13
+ <Modal open={false}>
14
+ <p>text modal</p>
15
+ </Modal>,
16
+ );
17
+ });
18
+
19
+ it("close modal window", async () => {
20
+ const setSidebarOpenMock = jest.fn();
21
+ const component = render(
22
+ <Modal isOpen={true} setIsOpen={setSidebarOpenMock} classNameContainer="w-full">
23
+ <p>text modal</p>
24
+ </Modal>,
25
+ );
26
+ const close = component.getByTestId("window-close");
27
+ await act(async () => fireEvent.click(close));
28
+ expect(setSidebarOpenMock).toHaveBeenCalled();
29
+ });
30
+
31
+ it("close modal", async () => {
32
+ const setSidebarOpenMock = jest.fn();
33
+ const component = render(
34
+ <Modal isOpen={true} setIsOpen={setSidebarOpenMock} classNameContainer="w-full">
35
+ <p>text modal</p>
36
+ </Modal>,
37
+ );
38
+ const close = component.getByTestId("button-solid-close-modal");
39
+ await act(async () => fireEvent.click(close));
40
+ expect(setSidebarOpenMock).toHaveBeenCalled();
41
+ });
42
+ });