@agilant/toga-blox 1.0.32 → 1.0.34

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 (144) hide show
  1. package/dist/components/Dropdown/Dropdown.d.ts +4 -0
  2. package/dist/components/Dropdown/Dropdown.js +20 -0
  3. package/dist/components/Dropdown/Dropdown.stories.d.ts +8 -0
  4. package/dist/components/Dropdown/Dropdown.stories.js +110 -0
  5. package/dist/components/Dropdown/Dropdown.test.d.ts +1 -0
  6. package/dist/components/Dropdown/Dropdown.test.js +43 -0
  7. package/dist/components/Dropdown/Dropdown.types.d.ts +15 -0
  8. package/dist/components/Dropdown/Dropdown.types.js +1 -0
  9. package/dist/components/GenericList/GenericList.d.ts +2 -15
  10. package/dist/components/GenericList/GenericList.js +64 -51
  11. package/dist/components/GenericList/GenericList.stories.d.ts +8 -35
  12. package/dist/components/GenericList/GenericList.stories.js +46 -78
  13. package/dist/components/GenericList/GenericList.test.d.ts +1 -1
  14. package/dist/components/GenericList/GenericList.test.js +112 -22
  15. package/dist/components/GenericList/index.d.ts +2 -0
  16. package/dist/components/GenericList/index.js +2 -0
  17. package/dist/components/GenericList/types.d.ts +16 -0
  18. package/dist/components/GenericList/types.js +1 -0
  19. package/dist/components/Header/Header.stories.js +2 -4
  20. package/dist/components/Input/Input.d.ts +30 -3
  21. package/dist/components/Input/Input.js +70 -48
  22. package/dist/components/Input/Input.stories.js +3 -4
  23. package/dist/components/Input/Input.test.js +74 -42
  24. package/dist/components/InputAndCheck/InputAndCheck.d.ts +47 -0
  25. package/dist/components/InputAndCheck/InputAndCheck.js +74 -0
  26. package/dist/components/InputAndCheck/InputAndCheck.stories.d.ts +9 -0
  27. package/dist/components/InputAndCheck/InputAndCheck.stories.js +201 -0
  28. package/dist/components/InputAndCheck/InputAndCheck.test.d.ts +1 -0
  29. package/dist/components/InputAndCheck/InputAndCheck.test.js +307 -0
  30. package/dist/components/InputAndCheck/index.d.ts +0 -0
  31. package/dist/components/InputAndCheck/index.js +0 -0
  32. package/dist/components/InputAndCheck/types.d.ts +35 -0
  33. package/dist/components/InputAndCheck/types.js +1 -0
  34. package/dist/components/MagnifyingIcon/MagnifyingIcon.d.ts +4 -0
  35. package/dist/components/MagnifyingIcon/MagnifyingIcon.js +60 -0
  36. package/dist/components/MagnifyingIcon/MagnifyingIcon.stories.d.ts +9 -0
  37. package/dist/components/MagnifyingIcon/MagnifyingIcon.stories.js +72 -0
  38. package/dist/components/MagnifyingIcon/MagnifyingIcon.test.d.ts +1 -0
  39. package/dist/components/MagnifyingIcon/MagnifyingIcon.test.js +101 -0
  40. package/dist/components/MagnifyingIcon/index.d.ts +2 -0
  41. package/dist/components/MagnifyingIcon/index.js +2 -0
  42. package/dist/components/MagnifyingIcon/types.d.ts +20 -0
  43. package/dist/components/MagnifyingIcon/types.js +2 -0
  44. package/dist/components/MultiSelect/MultiSelect.d.ts +4 -0
  45. package/dist/components/MultiSelect/MultiSelect.js +30 -0
  46. package/dist/components/MultiSelect/MultiSelect.stories.d.ts +10 -0
  47. package/dist/components/MultiSelect/MultiSelect.stories.js +162 -0
  48. package/dist/components/MultiSelect/MultiSelect.test.d.ts +1 -0
  49. package/dist/components/MultiSelect/MultiSelect.test.js +107 -0
  50. package/dist/components/MultiSelect/MultiSelect.types.d.ts +28 -0
  51. package/dist/components/MultiSelect/MultiSelect.types.js +1 -0
  52. package/dist/components/Page/ViewPageTemplate.stories.js +2 -3
  53. package/dist/components/PrimaryTableHeader/PrimaryTableHeader.d.ts +3 -0
  54. package/dist/components/PrimaryTableHeader/PrimaryTableHeader.js +72 -0
  55. package/dist/components/PrimaryTableHeader/PrimaryTableHeader.stories.d.ts +4 -0
  56. package/dist/components/PrimaryTableHeader/PrimaryTableHeader.stories.js +99 -0
  57. package/dist/components/PrimaryTableHeader/PrimaryTableHeader.test.d.ts +1 -0
  58. package/dist/components/PrimaryTableHeader/PrimaryTableHeader.test.js +124 -0
  59. package/dist/components/PrimaryTableHeader/index.d.ts +0 -0
  60. package/dist/components/PrimaryTableHeader/index.js +0 -0
  61. package/dist/components/PrimaryTableHeader/types.d.ts +35 -0
  62. package/dist/components/PrimaryTableHeader/types.js +2 -0
  63. package/dist/components/SearchInput/SearchInput.d.ts +1 -2
  64. package/dist/components/SearchInput/SearchInput.js +61 -11
  65. package/dist/components/SearchInput/SearchInput.stories.d.ts +2 -4
  66. package/dist/components/SearchInput/SearchInput.stories.js +80 -93
  67. package/dist/components/SearchInput/SearchInput.types.d.ts +37 -24
  68. package/dist/components/SearchInput/SearchNumberInput.d.ts +31 -0
  69. package/dist/components/SearchInput/SearchNumberInput.js +60 -0
  70. package/dist/components/SearchInput/SearchTextInput.d.ts +24 -0
  71. package/dist/components/SearchInput/SearchTextInput.js +65 -0
  72. package/dist/components/SortArrowIcon/SortArrowIcon.d.ts +4 -0
  73. package/dist/components/SortArrowIcon/SortArrowIcon.js +12 -0
  74. package/dist/components/SortArrowIcon/SortArrowIcon.stories.d.ts +17 -0
  75. package/dist/components/SortArrowIcon/SortArrowIcon.stories.js +77 -0
  76. package/dist/components/SortArrowIcon/SortArrowIcon.test.d.ts +1 -0
  77. package/dist/components/SortArrowIcon/SortArrowIcon.test.js +44 -0
  78. package/dist/components/SortArrowIcon/index.d.ts +2 -0
  79. package/dist/components/SortArrowIcon/index.js +2 -0
  80. package/dist/components/SortArrowIcon/types.d.ts +15 -0
  81. package/dist/components/SortArrowIcon/types.js +1 -0
  82. package/dist/components/SortArrows/SortArrows.d.ts +3 -0
  83. package/dist/components/SortArrows/SortArrows.js +33 -0
  84. package/dist/components/SortArrows/SortArrows.stories.d.ts +7 -0
  85. package/dist/components/SortArrows/SortArrows.stories.js +41 -0
  86. package/dist/components/SortArrows/SortArrows.test.d.ts +1 -0
  87. package/dist/components/SortArrows/SortArrows.test.js +150 -0
  88. package/dist/components/SortArrows/index.d.ts +2 -0
  89. package/dist/components/SortArrows/index.js +2 -0
  90. package/dist/components/SortArrows/types.d.ts +21 -0
  91. package/dist/components/SortArrows/types.js +1 -0
  92. package/dist/components/SortArrows/useSortArrowsViewModel.d.ts +30 -0
  93. package/dist/components/SortArrows/useSortArrowsViewModel.js +114 -0
  94. package/dist/components/SortArrows/useSortArrowsViewModel.test.d.ts +1 -0
  95. package/dist/components/SortArrows/useSortArrowsViewModel.test.js +100 -0
  96. package/dist/components/TableCell/TableCell.d.ts +3 -0
  97. package/dist/components/TableCell/TableCell.js +13 -0
  98. package/dist/components/TableCell/TableCell.stories.d.ts +16 -0
  99. package/dist/components/TableCell/TableCell.stories.js +99 -0
  100. package/dist/components/TableCell/TableCell.test.d.ts +1 -0
  101. package/dist/components/TableCell/TableCell.test.js +84 -0
  102. package/dist/components/TableCell/index.d.ts +2 -0
  103. package/dist/components/TableCell/index.js +2 -0
  104. package/dist/components/TableCell/types.d.ts +12 -0
  105. package/dist/components/TableCell/types.js +1 -0
  106. package/dist/components/TableHeaderContent/TableHeaderContent.d.ts +3 -0
  107. package/dist/components/TableHeaderContent/TableHeaderContent.js +5 -0
  108. package/dist/components/TableHeaderContent/TableHeaderContent.stories.d.ts +6 -0
  109. package/dist/components/TableHeaderContent/TableHeaderContent.stories.js +62 -0
  110. package/dist/components/TableHeaderContent/TableHeaderContent.test.d.ts +1 -0
  111. package/dist/components/TableHeaderContent/TableHeaderContent.test.js +41 -0
  112. package/dist/components/TableHeaderContent/index.d.ts +0 -0
  113. package/dist/components/TableHeaderContent/index.js +0 -0
  114. package/dist/components/TableHeaderContent/types.d.ts +5 -0
  115. package/dist/components/TableHeaderContent/types.js +1 -0
  116. package/dist/components/TableHeaderInput/TableHeaderInput.d.ts +3 -0
  117. package/dist/components/TableHeaderInput/TableHeaderInput.js +80 -0
  118. package/dist/components/TableHeaderInput/TableHeaderInput.stories.d.ts +10 -0
  119. package/dist/components/TableHeaderInput/TableHeaderInput.stories.js +82 -0
  120. package/dist/components/TableHeaderInput/TableHeaderInput.test.d.ts +1 -0
  121. package/dist/components/TableHeaderInput/TableHeaderInput.test.js +84 -0
  122. package/dist/components/TableHeaderInput/index.d.ts +1 -0
  123. package/dist/components/TableHeaderInput/index.js +1 -0
  124. package/dist/components/TableHeaderInput/types.d.ts +30 -0
  125. package/dist/components/TableHeaderInput/types.js +1 -0
  126. package/dist/components/TableRow/TableRow.d.ts +15 -0
  127. package/dist/components/TableRow/TableRow.js +21 -0
  128. package/dist/components/TableRow/TableRow.stories.d.ts +9 -0
  129. package/dist/components/TableRow/TableRow.stories.js +195 -0
  130. package/dist/components/TableRow/TableRow.test.d.ts +1 -0
  131. package/dist/components/TableRow/TableRow.test.js +44 -0
  132. package/dist/components/TableRow/index.d.ts +2 -0
  133. package/dist/components/TableRow/index.js +2 -0
  134. package/dist/components/TableRow/types.d.ts +11 -0
  135. package/dist/components/TableRow/types.js +1 -0
  136. package/dist/components/ToggleButton/ToggleButton.d.ts +4 -0
  137. package/dist/components/ToggleButton/ToggleButton.js +41 -0
  138. package/dist/components/ToggleButton/ToggleButton.stories.d.ts +11 -0
  139. package/dist/components/ToggleButton/ToggleButton.stories.js +111 -0
  140. package/dist/components/ToggleButton/ToggleButton.test.d.ts +1 -0
  141. package/dist/components/ToggleButton/ToggleButton.test.js +106 -0
  142. package/dist/components/ToggleButton/ToggleButton.types.d.ts +22 -0
  143. package/dist/components/ToggleButton/ToggleButton.types.js +1 -0
  144. package/package.json +11 -4
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useRef } from "react";
3
+ import { motion } from "framer-motion";
4
+ import { useSortArrowsViewModel } from "./useSortArrowsViewModel";
5
+ import { getFontAwesomeIcon } from "../../utils/getFontAwesomeIcon";
6
+ import SortArrowIcon from "../SortArrowIcon";
7
+ const SortArrows = ({ column, setSortColumn, slug, isNested, parentIndex, onSortChange, renderPortalOverlay, onRequestClose, counterIcon = {
8
+ icon: "circle",
9
+ weight: "solid",
10
+ numberClassNames: "absolute text-xs top-1.5 right-0 left-0",
11
+ iconClassNames: "text-lg text-blue-400 relative hover:text-red-400",
12
+ numberSize: "text-xl",
13
+ }, }) => {
14
+ const containerRef = useRef(null);
15
+ const { showArrowContainer, handleParentClick, handleResetSort, handleClick, isActive, activeDirection, sortOrderNumber, setShowArrowContainer, } = useSortArrowsViewModel({
16
+ columnId: String(column.accessor),
17
+ slug,
18
+ isNested,
19
+ parentIndex,
20
+ onSortChange: onSortChange,
21
+ setSortColumn,
22
+ onRequestClose,
23
+ });
24
+ const arrowContainerClassNames = `${isActive ? "" : "hover:bg-white"} size-[18px] rounded-sm flex flex-col items-center justify-center cursor-pointer relative`;
25
+ const handleClose = () => {
26
+ setShowArrowContainer(false);
27
+ onRequestClose?.();
28
+ };
29
+ return (_jsxs("div", { "data-testid": `sort-arrows-${slug}`, ref: containerRef, className: arrowContainerClassNames, onClick: handleParentClick, children: [isActive && (_jsxs("div", { className: `${counterIcon.iconClassNames} ${activeDirection === "desc" ? "rotate-180" : ""}`, children: [_jsx("span", { className: `${counterIcon?.numberSize}`, children: getFontAwesomeIcon(counterIcon.icon, counterIcon.weight) }), _jsx("div", { className: `${activeDirection === "desc" ? "rotate-180" : ""} ${counterIcon.numberClassNames} `, children: sortOrderNumber })] })), !isActive && (_jsxs(_Fragment, { children: [_jsx(SortArrowIcon, { slug: slug, parentIndex: parentIndex, columnName: String(column.render("Header")), direction: "up", setColumn: () => { }, isActive: false }), _jsx(SortArrowIcon, { slug: slug, parentIndex: parentIndex, columnName: String(column.render("Header")), direction: "down", setColumn: () => { }, isActive: false })] })), showArrowContainer && (_jsxs(motion.div, { className: `z-[9989] bg-white rounded flex flex-col border-1 absolute top-6 w-32 ${column.id === "col1" ? "left-[5px]" : ""}`, initial: "initial", animate: "animate", exit: "exit", children: [activeDirection !== "asc" && (_jsxs("div", { className: "flex border-b-1 p-1 hover:bg-blue-50", onClick: () => handleClick("up"), children: [_jsx("span", { className: "pl-1 text-primary", children: getFontAwesomeIcon("arrowUp", "regular") }), _jsx("span", { className: "flex-1 text-left pl-4", children: "Ascending" })] })), activeDirection !== "desc" && (_jsxs("div", { className: "flex p-1 hover:bg-blue-50 border-b-1", onClick: () => handleClick("down"), children: [_jsx("span", { className: "pl-1 text-primary", children: getFontAwesomeIcon("arrowDown", "regular") }), _jsx("span", { className: "flex-1 text-left pl-4", children: "Descending" })] })), isActive && (_jsxs("div", { className: "flex border-b-1 p-1 hover:bg-blue-50", onClick: handleResetSort, children: [_jsx("span", { className: "pl-1 text-primary", children: getFontAwesomeIcon("rotateLeft", "solid") }), _jsx("span", { className: "flex-1 text-left pl-3", children: "Reset" })] }))] })), showArrowContainer &&
30
+ renderPortalOverlay &&
31
+ renderPortalOverlay(handleClose)] }));
32
+ };
33
+ export default SortArrows;
@@ -0,0 +1,7 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import SortArrows from "./SortArrows";
3
+ declare const meta: Meta<typeof SortArrows>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof SortArrows>;
6
+ export declare const Default: Story;
7
+ export declare const WithOverlayAndIndexCount: Story;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import SortArrows from "./SortArrows";
3
+ import { useState } from "react";
4
+ const mockColumn = (accessor, Header) => ({
5
+ accessor,
6
+ Header,
7
+ id: accessor,
8
+ render: (value) => value,
9
+ });
10
+ const meta = {
11
+ title: "Components/SortArrows",
12
+ component: SortArrows,
13
+ parameters: {
14
+ layout: "centered",
15
+ },
16
+ tags: ["autodocs"],
17
+ };
18
+ export default meta;
19
+ // Basic story with inactive state
20
+ export const Default = {
21
+ render: () => {
22
+ const [_, setSortColumn] = useState({});
23
+ const column = mockColumn("price", "Price");
24
+ return (_jsx(SortArrows, { column: column, setSortColumn: setSortColumn, slug: "products", onSortChange: (columnId, direction) => {
25
+ console.log("Sort changed:", columnId, direction);
26
+ } }));
27
+ },
28
+ };
29
+ export const WithOverlayAndIndexCount = {
30
+ render: () => {
31
+ const [_, setSortColumn] = useState({});
32
+ const column = mockColumn("category", "Category");
33
+ return (_jsx("div", { className: "size-96", children: _jsx(SortArrows, { counterIcon: {
34
+ icon: "arrowUp",
35
+ weight: "solid",
36
+ numberClassNames: "text-white text-[20px] text-red-400 rounded-full top-1.5 right-0 left-0",
37
+ iconClassNames: "text-blue-400 hover:text-red-400 p-4",
38
+ numberSize: "text-[50px]",
39
+ }, column: column, setSortColumn: setSortColumn, slug: "products", renderPortalOverlay: (onClose) => (_jsx("div", { className: "absolute top-0 left-0 w-[300px] h-[300px] bg-black bg-opacity-50 flex items-center justify-center", onClick: onClose })) }) }));
40
+ },
41
+ };
@@ -0,0 +1 @@
1
+ import "@testing-library/jest-dom";
@@ -0,0 +1,150 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect, vi, beforeEach } from "vitest";
3
+ import { render, screen, fireEvent } from "@testing-library/react";
4
+ import "@testing-library/jest-dom";
5
+ import SortArrows from "./SortArrows";
6
+ // We will mock our custom hook to control the returned values
7
+ vi.mock("./useSortArrowsViewModel", () => {
8
+ return {
9
+ useSortArrowsViewModel: vi.fn(() => ({
10
+ showArrowContainer: false,
11
+ handleParentClick: vi.fn(),
12
+ handleResetSort: vi.fn(),
13
+ handleClick: vi.fn(),
14
+ isActive: false,
15
+ activeDirection: "",
16
+ sortOrderNumber: 0,
17
+ setShowArrowContainer: vi.fn(),
18
+ })),
19
+ };
20
+ });
21
+ import { useSortArrowsViewModel } from "./useSortArrowsViewModel";
22
+ /**
23
+ * If your `SortArrowsProps<T>` requires a `CustomColumnInstance<T>`
24
+ * with a certain accessor shape, define a helper like so:
25
+ */
26
+ function createMockColumnInstance(partial) {
27
+ return {
28
+ id: "test-column",
29
+ accessor: "testColumn", // satisfies `string | number | symbol | ((row: T) => any);`
30
+ render: vi.fn(() => "Test Column"),
31
+ ...partial,
32
+ };
33
+ }
34
+ describe("SortArrows Component", () => {
35
+ let defaultProps;
36
+ beforeEach(() => {
37
+ vi.clearAllMocks();
38
+ defaultProps = {
39
+ column: createMockColumnInstance(),
40
+ setSortColumn: vi.fn(),
41
+ slug: "test-slug",
42
+ isNested: false,
43
+ parentIndex: 0,
44
+ onSortChange: undefined,
45
+ renderPortalOverlay: undefined,
46
+ onRequestClose: undefined,
47
+ };
48
+ });
49
+ it("shows active counter icon when isActive = true", () => {
50
+ // @ts-ignore
51
+ useSortArrowsViewModel.mockReturnValue({
52
+ showArrowContainer: false,
53
+ handleParentClick: vi.fn(),
54
+ handleResetSort: vi.fn(),
55
+ handleClick: vi.fn(),
56
+ isActive: true,
57
+ activeDirection: "asc",
58
+ sortOrderNumber: 2,
59
+ setShowArrowContainer: vi.fn(),
60
+ });
61
+ render(_jsx(SortArrows, { ...defaultProps }));
62
+ expect(screen.getByText("2")).toBeInTheDocument();
63
+ expect(screen.queryByRole("img", { name: /arrow up/i })).not.toBeInTheDocument();
64
+ expect(screen.queryByRole("img", { name: /arrow down/i })).not.toBeInTheDocument();
65
+ });
66
+ it("shows the menu when showArrowContainer=true", () => {
67
+ // @ts-ignore
68
+ useSortArrowsViewModel.mockReturnValue({
69
+ showArrowContainer: true,
70
+ handleParentClick: vi.fn(),
71
+ handleResetSort: vi.fn(),
72
+ handleClick: vi.fn(),
73
+ isActive: false,
74
+ activeDirection: "",
75
+ sortOrderNumber: 0,
76
+ setShowArrowContainer: vi.fn(),
77
+ });
78
+ render(_jsx(SortArrows, { ...defaultProps }));
79
+ expect(screen.getByText(/ascending/i)).toBeInTheDocument();
80
+ expect(screen.getByText(/descending/i)).toBeInTheDocument();
81
+ });
82
+ it("calls handleClick('up') when 'Ascending' is clicked", () => {
83
+ const handleClickMock = vi.fn();
84
+ // @ts-ignore
85
+ useSortArrowsViewModel.mockReturnValue({
86
+ showArrowContainer: true,
87
+ handleParentClick: vi.fn(),
88
+ handleResetSort: vi.fn(),
89
+ handleClick: handleClickMock,
90
+ isActive: false,
91
+ activeDirection: "",
92
+ sortOrderNumber: 0,
93
+ setShowArrowContainer: vi.fn(),
94
+ });
95
+ render(_jsx(SortArrows, { ...defaultProps }));
96
+ fireEvent.click(screen.getByText(/ascending/i));
97
+ expect(handleClickMock).toHaveBeenCalledWith("up");
98
+ });
99
+ it("calls handleClick('down') when 'Descending' is clicked", () => {
100
+ const handleClickMock = vi.fn();
101
+ // @ts-ignore
102
+ useSortArrowsViewModel.mockReturnValue({
103
+ showArrowContainer: true,
104
+ handleParentClick: vi.fn(),
105
+ handleResetSort: vi.fn(),
106
+ handleClick: handleClickMock,
107
+ isActive: false,
108
+ activeDirection: "",
109
+ sortOrderNumber: 0,
110
+ setShowArrowContainer: vi.fn(),
111
+ });
112
+ render(_jsx(SortArrows, { ...defaultProps }));
113
+ fireEvent.click(screen.getByText(/descending/i));
114
+ expect(handleClickMock).toHaveBeenCalledWith("down");
115
+ });
116
+ it("calls handleResetSort when 'Reset' is clicked (if isActive=true)", () => {
117
+ const handleResetSortMock = vi.fn();
118
+ // @ts-ignore
119
+ useSortArrowsViewModel.mockReturnValue({
120
+ showArrowContainer: true,
121
+ handleParentClick: vi.fn(),
122
+ handleResetSort: handleResetSortMock,
123
+ handleClick: vi.fn(),
124
+ isActive: true,
125
+ activeDirection: "asc",
126
+ sortOrderNumber: 2,
127
+ setShowArrowContainer: vi.fn(),
128
+ });
129
+ render(_jsx(SortArrows, { ...defaultProps }));
130
+ fireEvent.click(screen.getByText(/reset/i));
131
+ expect(handleResetSortMock).toHaveBeenCalled();
132
+ });
133
+ it("renders overlay when renderPortalOverlay is provided", () => {
134
+ const renderPortalOverlayMock = vi.fn((onClose) => (_jsx("div", { "data-testid": "portal-overlay", children: "Portal Content" })));
135
+ // @ts-ignore
136
+ useSortArrowsViewModel.mockReturnValue({
137
+ showArrowContainer: true,
138
+ handleParentClick: vi.fn(),
139
+ handleResetSort: vi.fn(),
140
+ handleClick: vi.fn(),
141
+ isActive: false,
142
+ activeDirection: "",
143
+ sortOrderNumber: 0,
144
+ setShowArrowContainer: vi.fn(),
145
+ });
146
+ render(_jsx(SortArrows, { ...defaultProps, renderPortalOverlay: renderPortalOverlayMock }));
147
+ expect(screen.getByTestId("portal-overlay")).toBeInTheDocument();
148
+ expect(renderPortalOverlayMock).toHaveBeenCalled();
149
+ });
150
+ });
@@ -0,0 +1,2 @@
1
+ export { default } from "./SortArrows";
2
+ export * from "./types";
@@ -0,0 +1,2 @@
1
+ export { default } from "./SortArrows";
2
+ export * from "./types";
@@ -0,0 +1,21 @@
1
+ import { ColumnInstance } from "react-table";
2
+ export interface SortArrowsProps<T extends object> {
3
+ column: CustomColumnInstance<T>;
4
+ setSortColumn: any;
5
+ slug: string;
6
+ isNested?: boolean;
7
+ parentIndex?: number;
8
+ onSortChange?: (columnId: string, direction: "asc" | "desc") => void;
9
+ renderPortalOverlay?: (onClose: () => void) => React.ReactNode;
10
+ onRequestClose?: () => void;
11
+ counterIcon?: {
12
+ icon: string;
13
+ weight: string;
14
+ numberClassNames?: string;
15
+ iconClassNames?: string;
16
+ numberSize?: string;
17
+ };
18
+ }
19
+ export interface CustomColumnInstance<T extends object> extends ColumnInstance<T> {
20
+ accessor: string | number | symbol | ((row: T) => any);
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ export interface UseSortArrowsViewModelProps {
2
+ columnId: string;
3
+ slug: string;
4
+ isNested?: boolean;
5
+ parentIndex?: number;
6
+ onSortChange?: (column: string, direction: "asc" | "desc" | "") => void;
7
+ setSortColumn: React.Dispatch<React.SetStateAction<{
8
+ [key: string]: string | string[];
9
+ }>>;
10
+ onUpdateUrlParams?: (params: Record<string, string>) => void;
11
+ onUpdateParentIndex?: (index: number) => void;
12
+ onRequestClose?: () => void;
13
+ }
14
+ type SortDirection = "asc" | "desc";
15
+ interface ActiveSortColumn {
16
+ columnId: string;
17
+ direction: SortDirection;
18
+ }
19
+ export declare const useSortArrowsViewModel: ({ columnId, slug, parentIndex, onSortChange, setSortColumn, onUpdateUrlParams, onUpdateParentIndex, }: UseSortArrowsViewModelProps) => {
20
+ activeSortColumns: ActiveSortColumn[];
21
+ activeDirection: SortDirection;
22
+ showArrowContainer: boolean;
23
+ handleParentClick: () => void;
24
+ handleResetSort: () => void;
25
+ handleClick: (direction: "up" | "down") => void;
26
+ isActive: boolean;
27
+ sortOrderNumber: number;
28
+ setShowArrowContainer: import("react").Dispatch<import("react").SetStateAction<boolean>>;
29
+ };
30
+ export {};
@@ -0,0 +1,114 @@
1
+ import { useState, useEffect } from "react";
2
+ export const useSortArrowsViewModel = ({ columnId, slug, parentIndex, onSortChange, setSortColumn, onUpdateUrlParams, onUpdateParentIndex, }) => {
3
+ const IS_TEST = process.env.NODE_ENV === "test";
4
+ // State holds objects with strict direction type
5
+ const [activeSortColumns, setActiveSortColumns] = useState([]);
6
+ const [showArrowContainer, setShowArrowContainer] = useState(false);
7
+ // Pull from localStorage or URL on mount
8
+ useEffect(() => {
9
+ if (typeof window === "undefined")
10
+ return;
11
+ const storedSortInfo = localStorage.getItem("activeSortColumns");
12
+ if (storedSortInfo) {
13
+ setActiveSortColumns(JSON.parse(storedSortInfo));
14
+ }
15
+ else {
16
+ const query = new URLSearchParams(window.location.search);
17
+ const sortParams = query.getAll(slug);
18
+ if (sortParams.length) {
19
+ const parsedSortColumns = sortParams.map((sortValue) => {
20
+ let direction = "asc";
21
+ let columnId = sortValue;
22
+ if (sortValue.startsWith("-")) {
23
+ direction = "desc";
24
+ columnId = sortValue.substring(1);
25
+ }
26
+ else if (sortValue.startsWith("+")) {
27
+ columnId = sortValue.substring(1);
28
+ }
29
+ return { columnId, direction };
30
+ });
31
+ setActiveSortColumns(parsedSortColumns.slice(0, 3));
32
+ }
33
+ }
34
+ }, [slug]);
35
+ // Update sort state and local storage
36
+ const updateSortState = (direction) => {
37
+ // Ensure newDirection is strictly typed
38
+ const newDirection = direction === "up" ? "asc" : "desc";
39
+ let updatedColumns = activeSortColumns.map((col) => col.columnId === columnId
40
+ ? { ...col, direction: newDirection }
41
+ : col);
42
+ // If not in the list, add to front
43
+ if (!updatedColumns.some((col) => col.columnId === columnId)) {
44
+ updatedColumns.unshift({ columnId, direction: newDirection });
45
+ }
46
+ // Only keep up to 3 columns
47
+ updatedColumns = updatedColumns.slice(0, 3);
48
+ // SSR-safe localStorage
49
+ if (typeof window !== "undefined") {
50
+ localStorage.setItem("activeSortColumns", JSON.stringify(updatedColumns));
51
+ }
52
+ setActiveSortColumns(updatedColumns);
53
+ // Derive new param array
54
+ const sortParams = updatedColumns.map((col) => `${col.direction === "desc" ? "-" : ""}${col.columnId}`);
55
+ // Update parent's column state
56
+ setSortColumn((prev) => ({
57
+ ...prev,
58
+ [slug]: sortParams,
59
+ }));
60
+ // Fire callback
61
+ onSortChange?.(columnId, newDirection);
62
+ };
63
+ // Reset sort for this column only
64
+ const handleResetSort = () => {
65
+ const updatedColumns = activeSortColumns.filter((col) => col.columnId !== columnId);
66
+ if (typeof window !== "undefined") {
67
+ localStorage.setItem("activeSortColumns", JSON.stringify(updatedColumns));
68
+ }
69
+ setActiveSortColumns(updatedColumns);
70
+ const sortParams = updatedColumns.map((col) => `${col.direction === "desc" ? "-" : ""}${col.columnId}`);
71
+ setSortColumn((prev) => {
72
+ const newState = { ...prev };
73
+ if (sortParams.length > 0) {
74
+ newState[slug] = sortParams;
75
+ }
76
+ else {
77
+ delete newState[slug];
78
+ }
79
+ return newState;
80
+ });
81
+ // Use callback for URL updates
82
+ if (onUpdateUrlParams) {
83
+ const params = updatedColumns.reduce((acc, col, idx) => {
84
+ acc[`${slug}_sort[${idx}]`] = `${col.direction === "desc" ? "-" : ""}${col.columnId}`;
85
+ return acc;
86
+ }, {});
87
+ onUpdateUrlParams(params);
88
+ }
89
+ // Update parent index through callback
90
+ onUpdateParentIndex?.(0);
91
+ onSortChange?.(columnId, "");
92
+ };
93
+ // Handle arrow clicks
94
+ const handleClick = (direction) => {
95
+ updateSortState(direction);
96
+ onUpdateParentIndex?.(parentIndex || 0);
97
+ };
98
+ // Check if current column is active
99
+ const columnIndex = activeSortColumns.findIndex((col) => col.columnId === columnId);
100
+ const isActive = columnIndex !== -1;
101
+ return {
102
+ activeSortColumns,
103
+ activeDirection: isActive
104
+ ? activeSortColumns[columnIndex].direction
105
+ : null,
106
+ showArrowContainer,
107
+ handleParentClick: () => setShowArrowContainer(!showArrowContainer),
108
+ handleResetSort,
109
+ handleClick,
110
+ isActive,
111
+ sortOrderNumber: isActive ? columnIndex + 1 : null,
112
+ setShowArrowContainer,
113
+ };
114
+ };
@@ -0,0 +1,100 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ // IMPORTANT: also import `waitFor` so we can wait for effect-based updates
3
+ import { renderHook, act, waitFor } from "@testing-library/react";
4
+ import { useSortArrowsViewModel, } from "./useSortArrowsViewModel";
5
+ // 1) Mock localStorage in a node test environment
6
+ const localStorageMock = (() => {
7
+ let store = {};
8
+ return {
9
+ getItem: vi.fn((key) => store[key]),
10
+ setItem: vi.fn((key, value) => {
11
+ store[key] = value;
12
+ }),
13
+ removeItem: vi.fn((key) => {
14
+ delete store[key];
15
+ }),
16
+ clear: vi.fn(() => {
17
+ store = {};
18
+ }),
19
+ };
20
+ })();
21
+ describe("useSortArrowsViewModel Hook (renderHook approach)", () => {
22
+ let defaultProps;
23
+ let setSortColumnMock;
24
+ let onSortChangeMock;
25
+ const originalLocation = window.location;
26
+ beforeEach(() => {
27
+ // Fresh mocks before each test
28
+ setSortColumnMock = vi.fn();
29
+ onSortChangeMock = vi.fn();
30
+ defaultProps = {
31
+ columnId: "myColumn",
32
+ slug: "mySlug",
33
+ parentIndex: 2,
34
+ onSortChange: onSortChangeMock,
35
+ setSortColumn: setSortColumnMock,
36
+ onUpdateUrlParams: vi.fn(),
37
+ onUpdateParentIndex: vi.fn(),
38
+ };
39
+ // Mock localStorage
40
+ global.localStorage = localStorageMock;
41
+ localStorageMock.clear();
42
+ // Mock window.location so we can manipulate window.location.search
43
+ delete global.window.location;
44
+ global.window.location = { ...originalLocation, search: "" };
45
+ });
46
+ afterEach(() => {
47
+ global.window.location = originalLocation;
48
+ vi.resetAllMocks();
49
+ });
50
+ it("initializes with empty activeSortColumns if localStorage & URL are empty", () => {
51
+ const { result } = renderHook(() => useSortArrowsViewModel({ ...defaultProps }));
52
+ // Since there's nothing to load, we can assert immediately:
53
+ expect(result.current.activeSortColumns).toEqual([]);
54
+ expect(result.current.isActive).toBe(false);
55
+ expect(result.current.activeDirection).toBeNull();
56
+ });
57
+ it("initializes from URL search params if localStorage is empty", async () => {
58
+ global.window.location.search =
59
+ "?mySlug=-myColumn&mySlug=anotherCol";
60
+ const { result } = renderHook(() => useSortArrowsViewModel({ ...defaultProps }));
61
+ // Wait for effect to parse URL params
62
+ await waitFor(() => {
63
+ expect(result.current.activeSortColumns.length).toBeGreaterThan(0);
64
+ });
65
+ // -myColumn => desc; anotherCol => asc
66
+ expect(result.current.activeSortColumns).toEqual([
67
+ { columnId: "myColumn", direction: "desc" },
68
+ { columnId: "anotherCol", direction: "asc" },
69
+ ]);
70
+ expect(result.current.isActive).toBe(true);
71
+ expect(result.current.activeDirection).toBe("desc");
72
+ });
73
+ it("handleClick('up') sets ascending, updates localStorage, calls onSortChange", () => {
74
+ const { result } = renderHook(() => useSortArrowsViewModel({ ...defaultProps }));
75
+ act(() => {
76
+ result.current.handleClick("up");
77
+ });
78
+ expect(result.current.activeDirection).toBe("asc");
79
+ expect(result.current.isActive).toBe(true);
80
+ expect(localStorageMock.setItem).toHaveBeenCalledWith("activeSortColumns", JSON.stringify([{ columnId: "myColumn", direction: "asc" }]));
81
+ expect(setSortColumnMock).toHaveBeenCalledWith(expect.any(Function));
82
+ expect(onSortChangeMock).toHaveBeenCalledWith("myColumn", "asc");
83
+ });
84
+ it("handleClick('down') sets descending", () => {
85
+ const { result } = renderHook(() => useSortArrowsViewModel({ ...defaultProps }));
86
+ act(() => {
87
+ result.current.handleClick("down");
88
+ });
89
+ expect(result.current.activeDirection).toBe("desc");
90
+ expect(localStorageMock.setItem).toHaveBeenCalledWith("activeSortColumns", JSON.stringify([{ columnId: "myColumn", direction: "desc" }]));
91
+ });
92
+ it("handleParentClick toggles showArrowContainer", () => {
93
+ const { result } = renderHook(() => useSortArrowsViewModel({ ...defaultProps }));
94
+ expect(result.current.showArrowContainer).toBe(false);
95
+ act(() => {
96
+ result.current.handleParentClick();
97
+ });
98
+ expect(result.current.showArrowContainer).toBe(true);
99
+ });
100
+ });
@@ -0,0 +1,3 @@
1
+ import { DataWithUUID, TableCellProps } from "./types";
2
+ declare const TableCell: <T extends DataWithUUID>({ globalTrimActive, rowUuid, columnInputs, cell, isLastCell, }: TableCellProps<T>) => import("react/jsx-runtime").JSX.Element;
3
+ export default TableCell;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ const TableCell = ({ globalTrimActive, rowUuid, columnInputs, cell, isLastCell, }) => {
3
+ const trimText = (text) => text.length > 30 ? `${text.substring(0, 30)}...` : text;
4
+ const shouldTrim = globalTrimActive && typeof cell.value === "string";
5
+ const cellContent = shouldTrim ? trimText(cell.value) : cell.render("Cell");
6
+ const cellProps = cell.getCellProps();
7
+ const compassTableDataClassNames = "relative overflow-hidden font-light text-sm text-left z-0 px-5";
8
+ const tableDataClassNames = compassTableDataClassNames;
9
+ ("relative overflow-hidden font-light text-sm lg:text-sm py-2 text-left z-0 pl-2 pr-2 hidden");
10
+ const cellContainerClassNames = "min-h-[40px] flex items-center";
11
+ return (_jsx("td", { "data-testid": "table-cell", ...cell.getCellProps(), className: isLastCell ? "" : tableDataClassNames, children: _jsx("div", { className: cellContainerClassNames, children: cellContent }) }, cell.value));
12
+ };
13
+ export default TableCell;
@@ -0,0 +1,16 @@
1
+ import { Meta } from "@storybook/react";
2
+ import TableCell from ".";
3
+ declare const _default: Meta<typeof TableCell>;
4
+ export default _default;
5
+ /**
6
+ * Default TableCell
7
+ */
8
+ export declare const Default: any;
9
+ /**
10
+ * TableCell with No Trimming
11
+ */
12
+ export declare const NoTrimming: any;
13
+ /**
14
+ * TableCell as Last Cell
15
+ */
16
+ export declare const LastCell: any;
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import TableCell from ".";
3
+ // Sample data for the table cell
4
+ const sampleData = {
5
+ uuid: "12345",
6
+ name: "John Doe",
7
+ age: 30,
8
+ address: "123 Main St, Springfield, IL, 62701, United States of America",
9
+ };
10
+ // Define the default export to configure the story
11
+ export default {
12
+ title: "Table/TableCell",
13
+ component: TableCell,
14
+ argTypes: {
15
+ globalTrimActive: {
16
+ control: "boolean",
17
+ description: "Toggle to enable or disable text trimming for long strings.",
18
+ },
19
+ rowUuid: {
20
+ control: "text",
21
+ description: "Unique identifier for the row.",
22
+ },
23
+ columnInputs: {
24
+ control: "object",
25
+ description: "Array of column inputs (if any).",
26
+ },
27
+ cell: {
28
+ control: "object",
29
+ description: "Object containing the cell value and rendering logic.",
30
+ },
31
+ isLastCell: {
32
+ control: "boolean",
33
+ description: "Indicates if this is the last cell in the row.",
34
+ },
35
+ },
36
+ tags: ["autodocs"],
37
+ parameters: {
38
+ layout: "centered",
39
+ docs: {
40
+ description: {
41
+ component: "A table cell component that supports text trimming and custom rendering. It is designed to work within a table row and can handle dynamic data with unique identifiers.",
42
+ },
43
+ },
44
+ },
45
+ };
46
+ // Template for the story
47
+ const Template = (args) => (_jsx("table", { children: _jsx("tbody", { children: _jsx("tr", { children: _jsx(TableCell, { ...args }) }) }) }));
48
+ /**
49
+ * Default TableCell
50
+ */
51
+ export const Default = Template.bind({});
52
+ Default.args = {
53
+ globalTrimActive: true,
54
+ rowUuid: sampleData.uuid,
55
+ columnInputs: [],
56
+ cell: {
57
+ value: sampleData.address,
58
+ render: (type) => (type === "Cell" ? sampleData.address : null),
59
+ getCellProps: () => ({}),
60
+ },
61
+ isLastCell: false,
62
+ };
63
+ Default.parameters = {
64
+ docs: {
65
+ description: {
66
+ story: "Default table cell with text trimming enabled. Long text will be truncated to 30 characters.",
67
+ },
68
+ },
69
+ };
70
+ /**
71
+ * TableCell with No Trimming
72
+ */
73
+ export const NoTrimming = Template.bind({});
74
+ NoTrimming.args = {
75
+ ...Default.args,
76
+ globalTrimActive: false,
77
+ };
78
+ NoTrimming.parameters = {
79
+ docs: {
80
+ description: {
81
+ story: "Table cell with text trimming disabled. The full text is displayed.",
82
+ },
83
+ },
84
+ };
85
+ /**
86
+ * TableCell as Last Cell
87
+ */
88
+ export const LastCell = Template.bind({});
89
+ LastCell.args = {
90
+ ...Default.args,
91
+ isLastCell: true,
92
+ };
93
+ LastCell.parameters = {
94
+ docs: {
95
+ description: {
96
+ story: "Table cell marked as the last cell in the row. May have unique styling.",
97
+ },
98
+ },
99
+ };
@@ -0,0 +1 @@
1
+ export {};