@popsure/dirty-swan 0.66.25 → 0.66.27
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 +27 -10
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/components/table/utils/useTableNavigation/useTableNavigation.d.ts +3 -1
- package/dist/esm/components/table/Table.js +4 -3
- package/dist/esm/components/table/Table.js.map +1 -1
- package/dist/esm/components/table/Table.stories.js +1 -1
- package/dist/esm/components/table/Table.test.js +42 -3
- package/dist/esm/components/table/Table.test.js.map +1 -1
- package/dist/esm/components/table/components/TableCell/ButtonCell/ButtonCell.js +3 -3
- package/dist/esm/components/table/components/TableCell/ButtonCell/ButtonCell.js.map +1 -1
- package/dist/esm/components/table/components/TableCell/TableCell.js +3 -2
- package/dist/esm/components/table/components/TableCell/TableCell.js.map +1 -1
- package/dist/esm/components/table/utils/useTableNavigation/useTableNavigation.test.js +51 -1
- package/dist/esm/components/table/utils/useTableNavigation/useTableNavigation.test.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/lib/components/table/utils/useTableNavigation/useTableNavigation.d.ts +3 -1
- package/dist/esm/{useTableNavigation-CrxoXTVd.js → useTableNavigation-C-vb2BxI.js} +20 -5
- package/dist/esm/useTableNavigation-C-vb2BxI.js.map +1 -0
- package/package.json +1 -1
- package/src/lib/components/table/Table.test.tsx +64 -0
- package/src/lib/components/table/Table.tsx +5 -5
- package/src/lib/components/table/components/TableCell/ButtonCell/ButtonCell.module.scss +5 -0
- package/src/lib/components/table/components/TableCell/ButtonCell/ButtonCell.tsx +1 -0
- package/src/lib/components/table/components/TableCell/TableCell.module.scss +6 -0
- package/src/lib/components/table/components/TableCell/TableCell.tsx +1 -0
- package/src/lib/components/table/utils/useTableNavigation/useTableNavigation.test.tsx +58 -0
- package/src/lib/components/table/utils/useTableNavigation/useTableNavigation.ts +25 -3
- package/dist/esm/useTableNavigation-CrxoXTVd.js.map +0 -1
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
import { render, screen } from '../../util/testUtils';
|
|
3
|
+
import { useMediaQuery } from '../../hooks/useMediaQuery';
|
|
2
4
|
import { Table } from './Table';
|
|
3
5
|
import { TableSectionData } from './types';
|
|
4
6
|
|
|
7
|
+
jest.mock('../../hooks/useMediaQuery', () => ({
|
|
8
|
+
useMediaQuery: jest.fn(() => false),
|
|
9
|
+
}));
|
|
10
|
+
|
|
5
11
|
const tableData: TableSectionData[] = [
|
|
6
12
|
{
|
|
7
13
|
section: {
|
|
@@ -139,3 +145,61 @@ describe('Table', () => {
|
|
|
139
145
|
expect(screen.getByText('I am custom')).toBeInTheDocument();
|
|
140
146
|
});
|
|
141
147
|
});
|
|
148
|
+
|
|
149
|
+
describe('Table on mobile with a parent-controlled section', () => {
|
|
150
|
+
const planTable: TableSectionData[] = [
|
|
151
|
+
{
|
|
152
|
+
rows: [
|
|
153
|
+
[{ text: 'Choose your plan' }, { text: 'Basic' }, { text: 'Advanced' }],
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const ControlledTable = ({
|
|
159
|
+
onSelectionChanged,
|
|
160
|
+
}: {
|
|
161
|
+
onSelectionChanged: (index: number) => void;
|
|
162
|
+
}) => {
|
|
163
|
+
const [activeSection, setActiveSection] = useState(2);
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<Table
|
|
167
|
+
title="Plans"
|
|
168
|
+
tableData={planTable}
|
|
169
|
+
activeSection={activeSection}
|
|
170
|
+
onSelectionChanged={(index) => {
|
|
171
|
+
onSelectionChanged(index);
|
|
172
|
+
setActiveSection(index);
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
beforeAll(() => {
|
|
179
|
+
HTMLElement.prototype.scroll = jest.fn();
|
|
180
|
+
(useMediaQuery as jest.Mock).mockReturnValue(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
afterAll(() => {
|
|
184
|
+
(useMediaQuery as jest.Mock).mockReturnValue(false);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('settles on the parent section instead of bouncing between columns', () => {
|
|
188
|
+
const reported: number[] = [];
|
|
189
|
+
|
|
190
|
+
render(
|
|
191
|
+
<ControlledTable
|
|
192
|
+
onSelectionChanged={(index) => {
|
|
193
|
+
reported.push(index);
|
|
194
|
+
if (reported.length > 5) {
|
|
195
|
+
throw new Error(`selection bounced: ${reported.join(',')}`);
|
|
196
|
+
}
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(reported).toEqual([2]);
|
|
202
|
+
expect(screen.getByTestId('previous-section-button')).toBeEnabled();
|
|
203
|
+
expect(screen.getByTestId('next-section-button')).toBeDisabled();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -94,13 +94,13 @@ const Table = ({
|
|
|
94
94
|
|
|
95
95
|
const useTabs = isMobile && mobileNavigationMode === 'tabs';
|
|
96
96
|
|
|
97
|
-
const { activeSection, navigateTable, setActiveSection } =
|
|
98
|
-
{
|
|
97
|
+
const { activeSection, navigateTable, setActiveSection, syncActiveSection } =
|
|
98
|
+
useTableNavigation({
|
|
99
99
|
enabled: isMobile,
|
|
100
100
|
containerRef,
|
|
101
|
+
initialSection: externalActiveSection ? externalActiveSection - 1 : 0,
|
|
101
102
|
onSelectionChanged,
|
|
102
|
-
}
|
|
103
|
-
);
|
|
103
|
+
});
|
|
104
104
|
|
|
105
105
|
const headerRow = tableData?.[0]?.rows?.[0];
|
|
106
106
|
const titleCell = {
|
|
@@ -138,7 +138,7 @@ const Table = ({
|
|
|
138
138
|
|
|
139
139
|
useEffect(() => {
|
|
140
140
|
if (externalActiveSection && externalActiveSection !== activeSection) {
|
|
141
|
-
|
|
141
|
+
syncActiveSection(externalActiveSection - 1);
|
|
142
142
|
}
|
|
143
143
|
}, [externalActiveSection]);
|
|
144
144
|
|
|
@@ -42,6 +42,12 @@
|
|
|
42
42
|
padding-left: 0;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Lets the ButtonCell stretch (height: 100%) so buttons in the same row match
|
|
46
|
+
// the tallest one; the cell still grows with its content.
|
|
47
|
+
.buttonCell {
|
|
48
|
+
height: 1px;
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
.selectedColumnTop {
|
|
46
52
|
border-radius: 16px 16px 0 0;
|
|
47
53
|
}
|
|
@@ -59,6 +59,7 @@ const TableCell = React.memo(
|
|
|
59
59
|
[styles.navigationTitle]: isNavigation && isTopLeftCell,
|
|
60
60
|
[styles.fixedCell]: isFirstCellInRow && colSpan < 1 ,
|
|
61
61
|
[styles.fixedCard]: cellProps.type === 'CARD',
|
|
62
|
+
[styles.buttonCell]: cellProps.type === 'BUTTON',
|
|
62
63
|
[styles.selectedColumnTop]: selectedColumnPosition === 'top',
|
|
63
64
|
[styles.selectedColumnBottom]: selectedColumnPosition === 'bottom',
|
|
64
65
|
})}
|
|
@@ -80,4 +80,62 @@ describe('useTableNavigation', () => {
|
|
|
80
80
|
});
|
|
81
81
|
expect(onSelectionChanged).toHaveBeenCalledWith(0);
|
|
82
82
|
});
|
|
83
|
+
|
|
84
|
+
it('starts on the section passed by the parent instead of the first one', () => {
|
|
85
|
+
const { result } = renderHook(() =>
|
|
86
|
+
useTableNavigation({
|
|
87
|
+
enabled: true,
|
|
88
|
+
containerRef,
|
|
89
|
+
onSelectionChanged,
|
|
90
|
+
initialSection: 1,
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(result.current.activeSection).toBe(2);
|
|
95
|
+
expect(containerRef.current?.scroll).toHaveBeenCalledWith({
|
|
96
|
+
top: 0,
|
|
97
|
+
left: 400,
|
|
98
|
+
behavior: 'smooth',
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('does not report a section pushed in by the parent as a user selection', () => {
|
|
103
|
+
const { result } = renderHook(() =>
|
|
104
|
+
useTableNavigation({
|
|
105
|
+
enabled: true,
|
|
106
|
+
containerRef,
|
|
107
|
+
onSelectionChanged,
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
onSelectionChanged.mockClear();
|
|
111
|
+
|
|
112
|
+
act(() => {
|
|
113
|
+
result.current.syncActiveSection(1);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(result.current.activeSection).toBe(2);
|
|
117
|
+
expect(onSelectionChanged).not.toHaveBeenCalled();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('keeps reporting user navigation after a parent sync', () => {
|
|
121
|
+
const { result } = renderHook(() =>
|
|
122
|
+
useTableNavigation({
|
|
123
|
+
enabled: true,
|
|
124
|
+
containerRef,
|
|
125
|
+
onSelectionChanged,
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
act(() => {
|
|
130
|
+
result.current.syncActiveSection(1);
|
|
131
|
+
});
|
|
132
|
+
onSelectionChanged.mockClear();
|
|
133
|
+
|
|
134
|
+
act(() => {
|
|
135
|
+
result.current.navigateTable(false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(result.current.activeSection).toBe(1);
|
|
139
|
+
expect(onSelectionChanged).toHaveBeenCalledWith(1);
|
|
140
|
+
});
|
|
83
141
|
});
|
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
import debounce from "lodash.debounce";
|
|
2
|
-
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
|
|
4
4
|
interface UseTableNavigationReturn {
|
|
5
5
|
activeSection: number;
|
|
6
6
|
navigateTable: (increase?: boolean) => void;
|
|
7
7
|
setActiveSection: (section: number) => void;
|
|
8
|
+
syncActiveSection: (section: number) => void;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
interface UseTableNavigationProps {
|
|
11
12
|
containerRef: React.RefObject<HTMLElement>,
|
|
12
13
|
enabled?: boolean,
|
|
14
|
+
initialSection?: number,
|
|
13
15
|
onSelectionChanged?: (index: number) => void
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export const useTableNavigation = ({
|
|
17
19
|
enabled,
|
|
18
20
|
containerRef,
|
|
21
|
+
initialSection = 0,
|
|
19
22
|
onSelectionChanged
|
|
20
23
|
}: UseTableNavigationProps): UseTableNavigationReturn => {
|
|
21
|
-
const [activeSection, setActiveSection] = useState(
|
|
24
|
+
const [activeSection, setActiveSection] = useState(initialSection);
|
|
25
|
+
const activeSectionRef = useRef(initialSection);
|
|
26
|
+
const skipNextReportRef = useRef(false);
|
|
27
|
+
|
|
28
|
+
const syncActiveSection = useCallback((section: number) => {
|
|
29
|
+
if (section === activeSectionRef.current) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
skipNextReportRef.current = true;
|
|
34
|
+
setActiveSection(section);
|
|
35
|
+
}, []);
|
|
22
36
|
|
|
23
37
|
const handleScrollToSection = (increase?: boolean) => {
|
|
24
38
|
if (!enabled) {
|
|
@@ -55,11 +69,18 @@ export const useTableNavigation = ({
|
|
|
55
69
|
}, [enabled]);
|
|
56
70
|
|
|
57
71
|
useEffect(() => {
|
|
72
|
+
activeSectionRef.current = activeSection;
|
|
73
|
+
|
|
74
|
+
const skipReport = skipNextReportRef.current;
|
|
75
|
+
skipNextReportRef.current = false;
|
|
76
|
+
|
|
58
77
|
if (!enabled) {
|
|
59
78
|
return
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
|
|
81
|
+
if (!skipReport) {
|
|
82
|
+
onSelectionChanged?.(activeSection + 1);
|
|
83
|
+
}
|
|
63
84
|
|
|
64
85
|
if (containerRef.current) {
|
|
65
86
|
const containerWidth = containerRef.current.getBoundingClientRect().width;
|
|
@@ -77,5 +98,6 @@ export const useTableNavigation = ({
|
|
|
77
98
|
activeSection: activeSection + 1,
|
|
78
99
|
navigateTable: handleScrollToSection,
|
|
79
100
|
setActiveSection,
|
|
101
|
+
syncActiveSection,
|
|
80
102
|
}
|
|
81
103
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useTableNavigation-CrxoXTVd.js","sources":["../../../src/lib/components/table/utils/useTableNavigation/useTableNavigation.ts"],"sourcesContent":["import debounce from \"lodash.debounce\";\nimport { useCallback, useEffect, useState } from \"react\";\n\ninterface UseTableNavigationReturn {\n activeSection: number;\n navigateTable: (increase?: boolean) => void;\n setActiveSection: (section: number) => void;\n}\n\ninterface UseTableNavigationProps {\n containerRef: React.RefObject<HTMLElement>,\n enabled?: boolean,\n onSelectionChanged?: (index: number) => void\n}\n\nexport const useTableNavigation = ({\n enabled,\n containerRef,\n onSelectionChanged\n}: UseTableNavigationProps): UseTableNavigationReturn => {\n const [activeSection, setActiveSection] = useState(0);\n\n const handleScrollToSection = (increase?: boolean) => {\n if (!enabled) {\n return;\n }\n\n setActiveSection((prevSection) => \n prevSection + (increase ? 1 : -1)\n );\n };\n\n const handleTableScroll = useCallback(() => {\n if (!containerRef.current || !enabled) {\n return;\n }\n\n const containerWidth = containerRef.current.getBoundingClientRect().width;\n const scrollLeft = containerRef.current.scrollLeft * 1.1;\n const cellWidth = containerWidth / 2;\n\n setActiveSection(Math.floor(scrollLeft / cellWidth));\n }, [activeSection, containerRef, enabled]);\n\n const debouncedTableScroll = debounce(handleTableScroll, 150);\n\n useEffect(() => {\n const container = containerRef.current;\n\n container?.addEventListener('scroll', debouncedTableScroll, {\n passive: true,\n });\n\n return container?.removeEventListener('scroll', handleTableScroll);\n }, [enabled]);\n\n useEffect(() => {\n if (!enabled) {\n return\n }\n\n onSelectionChanged?.(activeSection + 1);\n\n if (containerRef.current) {\n const containerWidth = containerRef.current.getBoundingClientRect().width;\n const cellWidth = containerWidth / 2;\n\n containerRef.current.scroll({\n top: 0,\n left: cellWidth * activeSection,\n behavior: 'smooth',\n });\n }\n }, [enabled, activeSection]);\n\n return {\n activeSection: activeSection + 1,\n navigateTable: handleScrollToSection,\n setActiveSection,\n }\n}"],"names":[],"mappings":";;;AAeO,IAAM,kBAAkB,GAAG,UAAC,EAIT,EAAA;AAHxB,IAAA,IAAA,OAAO,aAAA,EACP,YAAY,GAAA,EAAA,CAAA,YAAA,EACZ,kBAAkB,GAAA,EAAA,CAAA,kBAAA;IAEZ,IAAA,EAAA,GAAoC,QAAQ,CAAC,CAAC,CAAC,EAA9C,aAAa,GAAA,EAAA,CAAA,CAAA,CAAA,EAAE,gBAAgB,GAAA,EAAA,CAAA,CAAA,CAAe;IAErD,IAAM,qBAAqB,GAAG,UAAC,QAAkB,EAAA;QAC/C,IAAI,CAAC,OAAO,EAAE;YACZ;QACF;QAEA,gBAAgB,CAAC,UAAC,WAAW,EAAA;AAC3B,YAAA,OAAA,WAAW,IAAI,QAAQ,GAAG,CAAC,GAAG,EAAE,CAAC;AAAjC,QAAA,CAAiC,CAClC;AACH,IAAA,CAAC;IAED,IAAM,iBAAiB,GAAG,WAAW,CAAC,YAAA;QACpC,IAAI,CAAC,YAAY,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE;YACrC;QACF;QAEA,IAAM,cAAc,GAAG,YAAY,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,KAAK;QACzE,IAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,UAAU,GAAG,GAAG;AACxD,QAAA,IAAM,SAAS,GAAG,cAAc,GAAG,CAAC;QAEpC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC;IACrD,CAAC,EAAE,CAAC,aAAa,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IAE3C,IAAM,oBAAoB,GAAG,QAAQ,CAAC,iBAAiB,EAAE,GAAG,CAAC;AAE7D,IAAA,SAAS,CAAC,YAAA;AACR,QAAA,IAAM,SAAS,GAAG,YAAY,CAAC,OAAO;QAEtC,SAAS,KAAA,IAAA,IAAT,SAAS,KAAA,MAAA,GAAA,MAAA,GAAT,SAAS,CAAE,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,EAAE;AAC1D,YAAA,OAAO,EAAE,IAAI;AACd,SAAA,CAAC;AAEF,QAAA,OAAO,SAAS,KAAA,IAAA,IAAT,SAAS,KAAA,MAAA,GAAA,MAAA,GAAT,SAAS,CAAE,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,CAAC;AACpE,IAAA,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;AAEb,IAAA,SAAS,CAAC,YAAA;QACR,IAAI,CAAC,OAAO,EAAE;YACZ;QACF;QAEA,kBAAkB,KAAA,IAAA,IAAlB,kBAAkB,KAAA,MAAA,GAAA,MAAA,GAAlB,kBAAkB,CAAG,aAAa,GAAG,CAAC,CAAC;AAEvC,QAAA,IAAI,YAAY,CAAC,OAAO,EAAE;YACxB,IAAM,cAAc,GAAG,YAAY,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,KAAK;AACzE,YAAA,IAAM,SAAS,GAAG,cAAc,GAAG,CAAC;AAEpC,YAAA,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC;AAC1B,gBAAA,GAAG,EAAE,CAAC;gBACN,IAAI,EAAE,SAAS,GAAG,aAAa;AAC/B,gBAAA,QAAQ,EAAE,QAAQ;AACnB,aAAA,CAAC;QACJ;AACF,IAAA,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAE5B,OAAO;QACL,aAAa,EAAE,aAAa,GAAG,CAAC;AAChC,QAAA,aAAa,EAAE,qBAAqB;AACpC,QAAA,gBAAgB,EAAA,gBAAA;KACjB;AACH;;;;"}
|