@griddo/ax 11.14.2-rc.1 → 11.14.3-rc.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.
Files changed (69) hide show
  1. package/config/jest/setup.js +13 -7
  2. package/package.json +2 -2
  3. package/src/__tests__/components/ImageDragAndDrop/ImageDragAndDrop.test.tsx +7 -3
  4. package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +4 -3
  5. package/src/components/CategoryCell/index.tsx +3 -1
  6. package/src/components/ConfigPanel/index.tsx +6 -3
  7. package/src/components/ElementsTooltip/index.tsx +8 -4
  8. package/src/components/ErrorPage/index.tsx +3 -1
  9. package/src/components/Fields/FileField/index.tsx +0 -1
  10. package/src/components/FilterTagsBar/index.tsx +3 -3
  11. package/src/components/HeadingsPreviewModal/utils.tsx +2 -1
  12. package/src/components/KeywordsPreviewModal/atoms.tsx +2 -2
  13. package/src/components/KeywordsPreviewModal/index.tsx +6 -6
  14. package/src/components/KeywordsPreviewModal/utils.tsx +5 -3
  15. package/src/components/Modal/style.tsx +5 -5
  16. package/src/components/TableFilters/CheckGroupFilter/index.tsx +3 -2
  17. package/src/components/TableFilters/LiveFilter/index.tsx +4 -4
  18. package/src/components/TableFilters/SiteFilter/index.tsx +11 -12
  19. package/src/components/TableFilters/TranslationsFilter/index.tsx +2 -2
  20. package/src/components/TableFilters/TranslationsFilter/style.tsx +2 -2
  21. package/src/components/Tag/index.tsx +15 -7
  22. package/src/components/TruncatedTooltip/index.tsx +48 -0
  23. package/src/components/index.tsx +2 -0
  24. package/src/constants/index.ts +3 -0
  25. package/src/helpers/categoryColumns.tsx +55 -0
  26. package/src/helpers/images.tsx +3 -1
  27. package/src/helpers/index.tsx +2 -0
  28. package/src/hooks/index.tsx +2 -1
  29. package/src/hooks/window.ts +50 -2
  30. package/src/modules/ActivityLog/ItemLog/EventItem/index.tsx +17 -10
  31. package/src/modules/ActivityLog/ItemLog/EventItem/style.tsx +18 -1
  32. package/src/modules/ActivityLog/ItemLogUser/UserItem/EventItem/index.tsx +10 -7
  33. package/src/modules/ActivityLog/ItemLogUser/UserItem/index.tsx +9 -3
  34. package/src/modules/ActivityLog/ItemLogUser/UserItem/style.tsx +17 -1
  35. package/src/modules/App/Routing/NavMenu/NavItem/style.tsx +1 -0
  36. package/src/modules/App/Routing/index.tsx +2 -2
  37. package/src/modules/App/Routing/style.tsx +8 -1
  38. package/src/modules/Categories/CategoriesList/BulkHeader/TableHeader/style.tsx +1 -0
  39. package/src/modules/Categories/CategoriesList/CategoryItem/index.tsx +7 -3
  40. package/src/modules/Categories/CategoriesList/CategoryItem/style.tsx +38 -5
  41. package/src/modules/Content/BulkHeader/TableHeader/style.tsx +1 -1
  42. package/src/modules/Content/PageItem/index.tsx +80 -204
  43. package/src/modules/Content/PageItem/style.tsx +18 -10
  44. package/src/modules/Content/atoms.tsx +147 -18
  45. package/src/modules/Content/index.tsx +2 -9
  46. package/src/modules/Forms/FormCategoriesList/CategoryItem/index.tsx +7 -3
  47. package/src/modules/Forms/FormCategoriesList/CategoryItem/style.tsx +34 -0
  48. package/src/modules/Forms/FormEditor/index.tsx +28 -50
  49. package/src/modules/Forms/FormList/FormItem/index.tsx +91 -120
  50. package/src/modules/Forms/FormList/FormItem/style.tsx +19 -0
  51. package/src/modules/Forms/FormList/index.tsx +27 -48
  52. package/src/modules/Forms/atoms.tsx +44 -32
  53. package/src/modules/StructuredData/StructuredDataList/BulkHeader/TableHeader/index.tsx +8 -8
  54. package/src/modules/StructuredData/StructuredDataList/BulkHeader/TableHeader/style.tsx +2 -2
  55. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +83 -101
  56. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/style.tsx +11 -5
  57. package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/index.tsx +31 -54
  58. package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/style.tsx +13 -7
  59. package/src/modules/Users/Roles/BulkHeader/TableHeader/style.tsx +6 -6
  60. package/src/modules/Users/Roles/RoleItem/index.tsx +3 -4
  61. package/src/modules/Users/Roles/RoleItem/style.tsx +12 -15
  62. package/src/modules/Users/Roles/index.tsx +1 -2
  63. package/src/modules/Users/UserCreate/SiteItem/index.tsx +10 -4
  64. package/src/modules/Users/UserCreate/SiteItem/style.tsx +33 -1
  65. package/src/modules/Users/UserForm/index.tsx +6 -4
  66. package/src/modules/Users/UserList/BulkHeader/TableHeader/index.tsx +2 -4
  67. package/src/modules/Users/UserList/BulkHeader/TableHeader/style.tsx +1 -1
  68. package/src/modules/Users/UserList/UserItem/index.tsx +18 -10
  69. package/src/modules/Users/UserList/UserItem/style.tsx +60 -4
@@ -16,10 +16,16 @@ module.exports = () => {
16
16
 
17
17
  window.matchMedia =
18
18
  window.matchMedia ||
19
- function () {
20
- return {
21
- matches: false,
22
- addListener: function () {},
23
- removeListener: function () {},
24
- };
25
- };
19
+ (() => ({
20
+ matches: false,
21
+ addListener: function () {},
22
+ removeListener: function () {},
23
+ }));
24
+
25
+ class ResizeObserverMock {
26
+ observe() {}
27
+ unobserve() {}
28
+ disconnect() {}
29
+ }
30
+
31
+ global.ResizeObserver = ResizeObserverMock;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "11.14.2-rc.1",
4
+ "version": "11.14.3-rc.0",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Diego M. Béjar <diego.bejar@secuoyas.com>",
@@ -219,5 +219,5 @@
219
219
  "publishConfig": {
220
220
  "access": "public"
221
221
  },
222
- "gitHead": "a08bea26f0f939c8f93c9d59b5d54e80fd776700"
222
+ "gitHead": "c1d5913f76bb852a5100c7b2ee6b50e5b2128f7d"
223
223
  }
@@ -1,6 +1,7 @@
1
1
  import { Provider } from "react-redux";
2
2
 
3
3
  import ImageDragAndDrop from "@ax/components/ImageDragAndDrop";
4
+ import { VALID_IMAGE_FORMATS } from "@ax/constants";
4
5
  import { parseTheme } from "@ax/helpers";
5
6
  import globalTheme from "@ax/themes/theme.json";
6
7
 
@@ -37,7 +38,7 @@ const renderImageDragAndDrop = (props = {}, storeData = initialStore) => {
37
38
  return render(
38
39
  <Provider store={store}>
39
40
  <ThemeProvider theme={parseTheme(globalTheme)}>
40
- <ImageDragAndDrop siteID="global" validFormats={["jpg", "jpeg", "png"]} handleUpload={jest.fn()} {...props} />
41
+ <ImageDragAndDrop siteID="global" handleUpload={jest.fn()} {...props} />
41
42
  </ThemeProvider>
42
43
  </Provider>,
43
44
  );
@@ -61,7 +62,9 @@ describe("ImageDragAndDrop rendering", () => {
61
62
 
62
63
  it("should render valid formats text", () => {
63
64
  renderImageDragAndDrop();
64
- const validFormatsTexts = screen.getAllByText(/jpg, jpeg, png/);
65
+ const validFormatsTexts = screen.getAllByText((_, element) => {
66
+ return VALID_IMAGE_FORMATS.every((format) => element?.textContent?.includes(format));
67
+ });
65
68
  expect(validFormatsTexts.length).toBeGreaterThan(0);
66
69
  });
67
70
 
@@ -75,7 +78,8 @@ describe("ImageDragAndDrop rendering", () => {
75
78
  renderImageDragAndDrop();
76
79
  const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
77
80
  expect(fileInput).toBeTruthy();
78
- expect(fileInput.accept).toBe(".jpg,.jpeg,.png");
81
+ const expectedAccept = VALID_IMAGE_FORMATS.map((format) => `.${format}`).join(",");
82
+ expect(fileInput.accept).toBe(expectedAccept);
79
83
  });
80
84
 
81
85
  it("should render placeholder when isAllowedToUpload is false", () => {
@@ -71,7 +71,8 @@ describe("KeywordsPreviewModal component rendering", () => {
71
71
  renderComponent({ ...defaultProps, isOpen: true, keywordsFilter: ["seo"] });
72
72
 
73
73
  expect(screen.getByText("Show keyword:")).toBeTruthy();
74
- expect(screen.getByText("seo")).toBeTruthy();
74
+ const seoElements = screen.getAllByText("seo");
75
+ expect(seoElements.length).toBeGreaterThan(0);
75
76
  });
76
77
 
77
78
  it("should not render the filter section when keywordsFilter is empty", () => {
@@ -87,8 +88,8 @@ describe("KeywordsPreviewModal component events", () => {
87
88
 
88
89
  renderComponent({ ...defaultProps, isOpen: true, toggleModal: toggleModalMock });
89
90
 
90
- const closeButton = screen.getByTestId("icon-action-component");
91
- fireEvent.click(closeButton);
91
+ const closeButtons = screen.getAllByTestId("icon-action-component");
92
+ fireEvent.click(closeButtons[0]);
92
93
  expect(toggleModalMock).toBeCalled();
93
94
  });
94
95
 
@@ -1,5 +1,7 @@
1
- import React, { useEffect } from "react";
1
+ import { useEffect } from "react";
2
+
2
3
  import { ElementsTooltip } from "@ax/components";
4
+
3
5
  import * as S from "./style";
4
6
 
5
7
  const CategoryCell = (props: ICategoryCellProps): JSX.Element => {
@@ -1,14 +1,15 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
 
3
- import { isEmptyObj } from "@ax/helpers";
4
3
  import { Loading } from "@ax/components";
4
+ import { isEmptyObj } from "@ax/helpers";
5
+ import { useFirefoxScrollLock } from "@ax/hooks";
5
6
  import type { IBreadcrumbItem, IUserEditing } from "@ax/types";
6
7
 
7
8
  import Form from "./Form";
8
- import NavigationForm from "./NavigationForm";
9
9
  import GlobalPageForm from "./GlobalPageForm";
10
- import PreviewForm from "./PreviewForm";
11
10
  import Header from "./Header";
11
+ import NavigationForm from "./NavigationForm";
12
+ import PreviewForm from "./PreviewForm";
12
13
 
13
14
  import * as S from "./style";
14
15
 
@@ -53,6 +54,8 @@ const ConfigPanel = (props: IStateProps) => {
53
54
  }
54
55
  }, [lastElementAddedId]);
55
56
 
57
+ useFirefoxScrollLock(wrapperRef);
58
+
56
59
  if (isLoading || isEmptyObj(schema)) {
57
60
  return <Loading />;
58
61
  }
@@ -66,11 +66,15 @@ const ElementsTooltip = (props: IElementsTooltipProps): JSX.Element => {
66
66
 
67
67
  if (!elements) return <></>;
68
68
 
69
- const visibleElements = elements.slice(0, defaultElements);
70
- const remainingElements = elements.length - defaultElements;
69
+ const filteredElements = elements.filter((element) => element !== null && element !== "");
70
+
71
+ if (filteredElements.length === 0) return <></>;
72
+
73
+ const visibleElements = filteredElements.slice(0, defaultElements);
74
+ const remainingElements = filteredElements.length - defaultElements;
71
75
 
72
76
  const elementsRows: string[][] = [];
73
- elements.forEach((element, idx) => {
77
+ filteredElements.forEach((element, idx) => {
74
78
  const row = Math.floor(idx / elementsPerRow);
75
79
  const isNewRow = idx % elementsPerRow === 0;
76
80
  elementsRows[row] = isNewRow ? [element] : [...elementsRows[row], element];
@@ -91,7 +95,7 @@ const ElementsTooltip = (props: IElementsTooltipProps): JSX.Element => {
91
95
  <>
92
96
  <S.Wrapper data-testid="elements-wrapper">
93
97
  {visibleElements.map((fullElement, idx) => {
94
- const element = defaultElements === 1 && maxChar ? trimText(fullElement, maxChar) : fullElement;
98
+ const element = maxChar ? trimText(fullElement, maxChar) : fullElement;
95
99
  const color = defaultColor || colors?.[element];
96
100
 
97
101
  return (
@@ -40,7 +40,9 @@ const ErrorPage = (props: IProps): JSX.Element => {
40
40
  title: "Something went wrong",
41
41
  description: <>Try refreshing the page, or try again later</>,
42
42
  buttonText: "Refresh page",
43
- buttonAction: () => history.push("/logout"),
43
+ buttonAction: () => {
44
+ window.location.href = "/logout";
45
+ },
44
46
  },
45
47
  wrongEditor: {
46
48
  icon: "working",
@@ -21,7 +21,6 @@ const FileField = (props: IFileFieldProps): JSX.Element => {
21
21
 
22
22
  const addFile = (newFile: any) => {
23
23
  onChange(newFile);
24
- toggleModal();
25
24
  };
26
25
 
27
26
  const handleOnClickUrl = () => {
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { IQueryValue } from "@ax/types";
1
+ import type { IQueryValue } from "@ax/types";
2
+
3
3
  import Button from "../Button";
4
4
 
5
5
  import * as S from "./style";
@@ -37,7 +37,7 @@ const FilterTagsBar = (props: IFilterTagsBarProps): JSX.Element => {
37
37
  <S.TagList>
38
38
  {tags.map((tag: string, index: number) => {
39
39
  const handleDeleteTag = () => deleteTag(tag);
40
- return <S.StyledTag key={index} text={tag} color="#FFFFFF" onDeleteAction={handleDeleteTag} />;
40
+ return <S.StyledTag key={index} text={tag} color="#FFFFFF" maxChar={30} onDeleteAction={handleDeleteTag} />;
41
41
  })}
42
42
  </S.TagList>
43
43
  <S.ButtonWrapper>
@@ -1,3 +1,4 @@
1
+ import { GRIDDO_ID } from "@ax/constants";
1
2
  import type { HeadingFilter, HeadingLevel, HeadingNode } from "@ax/types";
2
3
 
3
4
  const MAX_HEADING_LENGTH = 70;
@@ -150,7 +151,7 @@ const analyzeHeadings = (headings: HeadingNode[], isFiltering = false): IHeading
150
151
 
151
152
  const parseHeadingsTree = (html: HTMLDivElement): HeadingNode[] => {
152
153
  const frameObject = html.querySelector<HTMLIFrameElement>(".frame-content");
153
- const frameContent = frameObject?.contentWindow?.document.getElementById("___griddo") as HTMLElement;
154
+ const frameContent = frameObject?.contentWindow?.document.getElementById(GRIDDO_ID) as HTMLElement;
154
155
 
155
156
  if (!frameContent) {
156
157
  return [];
@@ -1,7 +1,7 @@
1
1
  import { useState } from "react";
2
2
 
3
+ import { FieldsBehavior, Modal } from "@ax/components";
3
4
  import type { IModal } from "@ax/types";
4
- import { Modal, FieldsBehavior } from "@ax/components";
5
5
 
6
6
  import * as S from "./style";
7
7
 
@@ -39,7 +39,7 @@ const AddKeywordsModal = (props: IAddKeywordsModal) => {
39
39
  secondaryAction={secondaryModalAction}
40
40
  mainAction={mainModalAction}
41
41
  size="S"
42
- height={282}
42
+ height={288}
43
43
  >
44
44
  <S.ModalContent>
45
45
  <FieldsBehavior
@@ -69,16 +69,16 @@ const KeywordsPreviewModal = (props: IKeywordsPreviewProps) => {
69
69
  )}
70
70
  <S.KeywordsListWrapper>
71
71
  {keywords.length === 0 && <S.StyledSummaryButton />}
72
- {Object.keys(keywordCounts).map((key, index) => {
73
- const isSelected = keywordsFilter.includes(key);
72
+ {keywords.map((keyword, index) => {
73
+ const isSelected = keywordsFilter.includes(keyword);
74
74
  return (
75
75
  <KeywordItem
76
- keyword={key}
77
- count={keywordCounts[key]}
76
+ keyword={keyword}
77
+ count={keywordCounts[keyword] ?? 0}
78
78
  isSelected={isSelected}
79
- onClick={handleAddTag(key)}
79
+ onClick={handleAddTag(keyword)}
80
80
  deleteKeyword={handleDeleteKeyword}
81
- key={`${key}-${index}`}
81
+ key={`${keyword}-${index}`}
82
82
  />
83
83
  );
84
84
  })}
@@ -1,16 +1,18 @@
1
+ import { GRIDDO_ID } from "@ax/constants";
2
+
1
3
  const countKeywords = (html: HTMLDivElement, keywords: string[]) => {
2
4
  const frameObject = html.querySelector<HTMLIFrameElement>(".frame-content");
3
- const frameContent = frameObject?.contentWindow?.document.getElementById("___griddo") as HTMLElement;
5
+ const frameContent = frameObject?.contentWindow?.document.getElementById(GRIDDO_ID) as HTMLElement;
4
6
 
5
7
  if (!frameContent) {
6
8
  return {};
7
9
  }
8
10
 
9
- const htmlContent = frameContent.innerText.toLowerCase();
11
+ const htmlContent = frameContent.innerText.toLowerCase().normalize("NFC");
10
12
  const keywordCounts: Record<string, number> = {};
11
13
 
12
14
  keywords.forEach((keyword) => {
13
- const lowerKeyword = keyword.toLowerCase();
15
+ const lowerKeyword = keyword.toLowerCase().normalize("NFC");
14
16
  const regex = new RegExp(lowerKeyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
15
17
  const matches = htmlContent.match(regex);
16
18
  keywordCounts[keyword] = matches ? matches.length : 0;
@@ -26,6 +26,11 @@ const getHeight = (size: string | undefined) => {
26
26
  }
27
27
  };
28
28
 
29
+ const getModalHeight = (height?: number | string, size?: string) => {
30
+ const heightProp = typeof height === "number" ? `${height}px` : height;
31
+ return height !== undefined ? heightProp : getHeight(size);
32
+ };
33
+
29
34
  const ModalOverlay = styled.div<{ isChild?: boolean }>`
30
35
  position: fixed;
31
36
  top: 0;
@@ -51,11 +56,6 @@ const ModalWrapper = styled.div<{ isChild?: boolean }>`
51
56
  outline: 0;
52
57
  `;
53
58
 
54
- function getModalHeight(height?: number | string, size?: string) {
55
- const heightProp = typeof height === "number" ? `${height}px` : height;
56
- return height !== undefined ? heightProp : getHeight(size);
57
- }
58
-
59
59
  const Modal = styled.div<{ size?: string; height?: number | string }>`
60
60
  z-index: 100;
61
61
  display: flex;
@@ -1,7 +1,8 @@
1
- import React, { useEffect, useState } from "react";
1
+ import { useEffect, useState } from "react";
2
+
2
3
  import { CheckGroup, FloatingMenu, Icon, ListTitle, SearchField } from "@ax/components";
3
4
  import { areEquals } from "@ax/helpers";
4
- import { IFilterValue, IQueryValue } from "@ax/types";
5
+ import type { IFilterValue, IQueryValue } from "@ax/types";
5
6
 
6
7
  import * as S from "./style";
7
8
 
@@ -1,9 +1,9 @@
1
- import React, { useState, useEffect } from "react";
1
+ import { useEffect, useState } from "react";
2
2
 
3
- import { CheckGroup, Icon, FloatingMenu, ListTitle } from "@ax/components";
4
3
  import { checkgroups } from "@ax/api";
4
+ import { CheckGroup, FloatingMenu, Icon, ListTitle } from "@ax/components";
5
5
  import { areEquals, isReqOk } from "@ax/helpers";
6
- import { IFilterValue, IPageLiveStatus, IQueryValue } from "@ax/types";
6
+ import type { IFilterValue, IPageLiveStatus, IQueryValue } from "@ax/types";
7
7
 
8
8
  import * as S from "./style";
9
9
 
@@ -54,7 +54,7 @@ const LiveFilter = (props: ILiveFilterProps): JSX.Element => {
54
54
  title: item.title,
55
55
  icon: item.status,
56
56
  };
57
- if ((filterOptions && filterOptions.includes(newFilter.value)) || !filterOptions) {
57
+ if (filterOptions?.includes(newFilter.value) || !filterOptions) {
58
58
  filters.push(newFilter);
59
59
  }
60
60
  });
@@ -1,9 +1,9 @@
1
- import React, { useEffect, useState } from "react";
1
+ import { useEffect, useState } from "react";
2
2
 
3
+ import { selects } from "@ax/api";
3
4
  import { CheckGroupFilter } from "@ax/components";
4
5
  import { isReqOk } from "@ax/helpers";
5
- import { selects } from "@ax/api";
6
- import { IFilterValue, IQueryValue } from "@ax/types";
6
+ import type { IFilterValue, IQueryValue } from "@ax/types";
7
7
 
8
8
  const SiteFilter = (props: ISiteFilterProps): JSX.Element => {
9
9
  const {
@@ -47,15 +47,14 @@ const SiteFilter = (props: ISiteFilterProps): JSX.Element => {
47
47
  useEffect(() => {
48
48
  getSelectSites()
49
49
  .then((items) => {
50
- items &&
51
- items.forEach((item: { value: string; label: string }) => {
52
- const newFilter = {
53
- name: item.value,
54
- value: item.value,
55
- title: item.label,
56
- };
57
- filters.push(newFilter);
58
- });
50
+ items?.forEach((item: { value: string; label: string }) => {
51
+ const newFilter = {
52
+ name: item.value,
53
+ value: item.value,
54
+ title: item.label,
55
+ };
56
+ filters.push(newFilter);
57
+ });
59
58
 
60
59
  setOptions(filters);
61
60
  })
@@ -1,8 +1,8 @@
1
- import React, { useEffect, useState } from "react";
1
+ import { useEffect, useState } from "react";
2
2
 
3
3
  import { CheckGroup, FloatingMenu, Icon, ListTitle } from "@ax/components";
4
4
  import { areEquals } from "@ax/helpers";
5
- import { IFilterValue, IQueryValue } from "@ax/types";
5
+ import type { IFilterValue, IQueryValue } from "@ax/types";
6
6
 
7
7
  import * as S from "./style";
8
8
 
@@ -1,7 +1,7 @@
1
- import React from "react";
2
- import styled from "styled-components";
3
1
  import { Header } from "@ax/components/TableList/style";
4
2
 
3
+ import styled from "styled-components";
4
+
5
5
  const Translations = styled((props) => <Header {...props} />)<{ isActive: boolean }>`
6
6
  width: 115px;
7
7
  justify-content: center;
@@ -3,7 +3,14 @@ import { Icon } from "@ax/components";
3
3
  import * as S from "./style";
4
4
 
5
5
  const Tag = (props: ITagProps): JSX.Element => {
6
- const { type, text, color, icon, textColor, rounded = true, onDeleteAction, className, small } = props;
6
+ const { type, text, color, icon, textColor, rounded = true, onDeleteAction, className, small, maxChar } = props;
7
+
8
+ const truncateText = (str: string, max: number) => {
9
+ if (str.length <= max) return str;
10
+ return `${str.slice(0, max)}...`;
11
+ };
12
+
13
+ const displayText = maxChar && text ? truncateText(text, maxChar) : text;
7
14
 
8
15
  const handleClick = () => {
9
16
  if (onDeleteAction) {
@@ -22,27 +29,27 @@ const Tag = (props: ITagProps): JSX.Element => {
22
29
  switch (type) {
23
30
  case "status":
24
31
  return (
25
- <S.TagStatus className={className} data-testid="tag-status">
32
+ <S.TagStatus className={className} data-testid="tag-status" title={text || ""}>
26
33
  <S.Bullet color={color} />
27
- {text}
34
+ {displayText}
28
35
  </S.TagStatus>
29
36
  );
30
37
  case "square":
31
38
  return (
32
- <S.TagSquare color={color} textColor={textColor} className={className} data-testid="tag-square">
39
+ <S.TagSquare color={color} textColor={textColor} className={className} data-testid="tag-square" title={text || ""}>
33
40
  {icon && (
34
41
  <S.IconTag>
35
42
  <Icon name={icon} size="16" />
36
43
  </S.IconTag>
37
44
  )}
38
- <div>{text}</div>
45
+ <div>{displayText}</div>
39
46
  </S.TagSquare>
40
47
  );
41
48
  default:
42
49
  return (
43
- <S.TagFixed color={color} rounded={rounded} small={small} className={className} data-testid="tag-fixed">
50
+ <S.TagFixed color={color} rounded={rounded} small={small} className={className} data-testid="tag-fixed" title={text || ""}>
44
51
  <S.TagText>
45
- <S.Title data-testid="tag-fixed-title">{text}</S.Title>
52
+ <S.Title data-testid="tag-fixed-title">{displayText}</S.Title>
46
53
  {deleteIcon}
47
54
  </S.TagText>
48
55
  </S.TagFixed>
@@ -60,6 +67,7 @@ export interface ITagProps {
60
67
  onDeleteAction?: () => void;
61
68
  className?: string;
62
69
  small?: boolean;
70
+ maxChar?: number;
63
71
  }
64
72
 
65
73
  export default Tag;
@@ -0,0 +1,48 @@
1
+ import React from "react";
2
+
3
+ import { Tooltip } from "@ax/components";
4
+
5
+ const TruncatedTooltip = ({ content, children, ...props }: any) => {
6
+ const [isTruncated, setIsTruncated] = React.useState(false);
7
+ const elementRef = React.useRef<HTMLElement | null>(null);
8
+
9
+ const checkTruncation = React.useCallback(() => {
10
+ if (elementRef.current) {
11
+ const isTrunc = elementRef.current.scrollWidth > elementRef.current.clientWidth;
12
+ setIsTruncated(isTrunc);
13
+ }
14
+ }, []);
15
+
16
+ React.useEffect(() => {
17
+ const element = elementRef.current;
18
+ if (!element) return;
19
+
20
+ checkTruncation();
21
+
22
+ const observer = new ResizeObserver(() => {
23
+ checkTruncation();
24
+ });
25
+
26
+ observer.observe(element);
27
+
28
+ return () => {
29
+ observer.disconnect();
30
+ };
31
+ }, [checkTruncation]);
32
+
33
+ const ChildWithRef = React.cloneElement(children, {
34
+ ref: elementRef,
35
+ });
36
+
37
+ if (!isTruncated) {
38
+ return ChildWithRef;
39
+ }
40
+
41
+ return (
42
+ <Tooltip content={content} {...props}>
43
+ {ChildWithRef}
44
+ </Tooltip>
45
+ );
46
+ };
47
+
48
+ export default TruncatedTooltip;
@@ -125,6 +125,7 @@ import Tabs from "./Tabs";
125
125
  import Tag from "./Tag";
126
126
  import Toast from "./Toast";
127
127
  import Tooltip from "./Tooltip";
128
+ import TruncatedTooltip from "./TruncatedTooltip";
128
129
  import UserRolesAndSites from "./UserRolesAndSites";
129
130
 
130
131
  export {
@@ -246,6 +247,7 @@ export {
246
247
  Tooltip,
247
248
  TranslateButton,
248
249
  TranslationsFilter,
250
+ TruncatedTooltip,
249
251
  TypeFilter,
250
252
  UniqueCheck,
251
253
  UrlField,
@@ -29,6 +29,8 @@ const VALID_DOCUMENT_FORMATS = ["pdf", "doc", "docx", "xls", "xlsx", "zip", "csv
29
29
  const VALID_VIDEO_FORMATS = ["mov", "mp4", "wmv", "avi", "webm", "mkv"];
30
30
  const VALID_FILE_FORMATS = [...VALID_DOCUMENT_FORMATS, ...VALID_VIDEO_FORMATS];
31
31
 
32
+ const GRIDDO_ID = "___griddo" as const;
33
+
32
34
  export {
33
35
  itemLabel,
34
36
  type ItemLabel,
@@ -36,4 +38,5 @@ export {
36
38
  VALID_DOCUMENT_FORMATS,
37
39
  VALID_VIDEO_FORMATS,
38
40
  VALID_FILE_FORMATS,
41
+ GRIDDO_ID,
39
42
  };
@@ -0,0 +1,55 @@
1
+ import React from "react";
2
+
3
+ import { CategoryCell } from "@ax/components";
4
+ import { Cell } from "@ax/components/TableList/TableItem/style";
5
+ import type { ISchemaField } from "@ax/types";
6
+
7
+ import styled from "styled-components";
8
+
9
+ const ColumnCell = styled(Cell)`
10
+ flex: 0 0 215px;
11
+ position: relative;
12
+ align-items: center;
13
+ `;
14
+
15
+ const buildCategoryColumns = (
16
+ categoryColumns: ISchemaField[],
17
+ activeColumns: string[],
18
+ contentData: Record<string, any> | undefined,
19
+ categoryColors: any,
20
+ addCategoryColors: (cats: string[]) => void,
21
+ onClick: () => void,
22
+ ) => {
23
+ return categoryColumns.map((col) => {
24
+ if (!activeColumns.includes(col.key)) {
25
+ return <React.Fragment key={col.key} />;
26
+ }
27
+
28
+ const type: any = contentData?.[col.key];
29
+
30
+ if (typeof type !== "object") {
31
+ return (
32
+ <ColumnCell key={col.key} onClick={onClick}>
33
+ {type}
34
+ </ColumnCell>
35
+ );
36
+ }
37
+
38
+ const categories: string[] = !type
39
+ ? []
40
+ : Array.isArray(type)
41
+ ? type.map((cat: any) => cat.label || cat.title)
42
+ : [type.label || type.title];
43
+
44
+ return (
45
+ <CategoryCell
46
+ key={col.key}
47
+ categories={categories}
48
+ categoryColors={categoryColors}
49
+ addCategoryColors={addCategoryColors}
50
+ />
51
+ );
52
+ });
53
+ };
54
+
55
+ export { buildCategoryColumns, ColumnCell };
@@ -1,5 +1,7 @@
1
1
  import type { Area } from "react-easy-crop";
2
2
 
3
+ import { GRIDDO_ID } from "@ax/constants";
4
+
3
5
  import { toBlob } from "html-to-image";
4
6
 
5
7
  const formatBytes = (bytes: number | null, decimals = 2) => {
@@ -65,7 +67,7 @@ const getImageFromHtml = async (html: HTMLDivElement, fileName: string): Promise
65
67
 
66
68
  const getImageFromIFrame = async (html: HTMLDivElement, fileName: string): Promise<File | null> => {
67
69
  const frameOBject = html.querySelector<HTMLIFrameElement>(".frame-content");
68
- const frameContentFirst = frameOBject?.contentWindow?.document.getElementById("___griddo")?.firstChild as HTMLElement;
70
+ const frameContentFirst = frameOBject?.contentWindow?.document.getElementById(GRIDDO_ID)?.firstChild as HTMLElement;
69
71
  const frameContentId = frameOBject?.contentWindow?.document.getElementById("griddoFormThumb") as HTMLElement;
70
72
 
71
73
  const frameContent = frameContentId || frameContentFirst;
@@ -1,4 +1,5 @@
1
1
  import { arrayInsert, isEmptyArray, moveArrayElement } from "./arrays";
2
+ import { buildCategoryColumns } from "./categoryColumns";
2
3
  import {
3
4
  areAllComponentsEmpty,
4
5
  areEqual,
@@ -117,6 +118,7 @@ export {
117
118
  areEqualDates,
118
119
  areEquals,
119
120
  arrayInsert,
121
+ buildCategoryColumns,
120
122
  camelize,
121
123
  capitalize,
122
124
  compressImage,
@@ -14,7 +14,7 @@ import { useHandleClickOutside, useModal, useModals, useToast } from "./modals";
14
14
  import { useNetworkStatus } from "./network";
15
15
  import { useResizable } from "./resize";
16
16
  import { useGlobalPermission, usePermission, usePermissions } from "./users";
17
- import { useWindowSize } from "./window";
17
+ import { useFirefoxScrollLock, useWindowSize } from "./window";
18
18
 
19
19
  export {
20
20
  useAdaptiveText,
@@ -24,6 +24,7 @@ export {
24
24
  useDebouncedCallback,
25
25
  useEmptyState,
26
26
  useEqualStructured,
27
+ useFirefoxScrollLock,
27
28
  useGlobalPermission,
28
29
  useHandleClickOutside,
29
30
  useIsDirty,