@scality/core-ui 0.134.0 → 0.136.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 (53) hide show
  1. package/dist/components/constrainedtext/Constrainedtext.component.d.ts.map +1 -1
  2. package/dist/components/constrainedtext/Constrainedtext.component.js +4 -1
  3. package/dist/components/icon/Icon.component.d.ts.map +1 -1
  4. package/dist/components/icon/Icon.component.js +9 -3
  5. package/dist/components/searchinput/SearchInput.component.d.ts +0 -1
  6. package/dist/components/searchinput/SearchInput.component.d.ts.map +1 -1
  7. package/dist/components/searchinput/SearchInput.component.js +1 -1
  8. package/dist/components/selectv2/Selectv2.component.d.ts +1 -1
  9. package/dist/components/selectv2/Selectv2.component.d.ts.map +1 -1
  10. package/dist/components/selectv2/Selectv2.component.js +5 -9
  11. package/dist/components/tablev2/Search.d.ts +1 -1
  12. package/dist/components/tablev2/Search.d.ts.map +1 -1
  13. package/dist/components/tablev2/Search.js +1 -1
  14. package/dist/components/tablev2/TableCommon.d.ts +1 -1
  15. package/dist/components/tablev2/TableCommon.js +3 -3
  16. package/dist/components/tablev2/Tablestyle.d.ts +1 -1
  17. package/dist/components/tablev2/Tablestyle.d.ts.map +1 -1
  18. package/dist/components/tablev2/Tablev2.component.d.ts +5 -1
  19. package/dist/components/tablev2/Tablev2.component.d.ts.map +1 -1
  20. package/dist/components/tablev2/Tablev2.component.js +6 -0
  21. package/dist/components/tablev2/useSyncedScroll.d.ts +2 -1
  22. package/dist/components/tablev2/useSyncedScroll.d.ts.map +1 -1
  23. package/dist/components/tablev2/useSyncedScroll.js +17 -19
  24. package/dist/components/tabsv2/StyledTabs.d.ts +1 -1
  25. package/dist/components/tabsv2/StyledTabs.d.ts.map +1 -1
  26. package/dist/components/tabsv2/Tabsv2.component.js +5 -1
  27. package/dist/components/toggle/Toggle.component.d.ts +1 -1
  28. package/dist/components/toggle/Toggle.component.d.ts.map +1 -1
  29. package/dist/components/toggle/Toggle.component.js +8 -11
  30. package/dist/organisms/attachments/AttachmentTable.d.ts.map +1 -1
  31. package/dist/organisms/attachments/AttachmentTable.js +2 -2
  32. package/package.json +2 -2
  33. package/src/lib/components/constrainedtext/Constrainedtext.component.tsx +4 -1
  34. package/src/lib/components/icon/Icon.component.tsx +12 -5
  35. package/src/lib/components/searchinput/SearchInput.component.tsx +0 -2
  36. package/src/lib/components/searchinput/SearchInput.test.tsx +88 -0
  37. package/src/lib/components/selectv2/Selectv2.component.tsx +7 -11
  38. package/src/lib/components/selectv2/selectv2.test.tsx +190 -200
  39. package/src/lib/components/tablev2/Search.tsx +1 -2
  40. package/src/lib/components/tablev2/TableCommon.tsx +5 -5
  41. package/src/lib/components/tablev2/Tablestyle.tsx +1 -1
  42. package/src/lib/components/tablev2/Tablev2.component.tsx +14 -0
  43. package/src/lib/components/tablev2/useSyncedScroll.ts +22 -24
  44. package/src/lib/components/tabsv2/StyledTabs.ts +1 -1
  45. package/src/lib/components/tabsv2/Tabsv2.component.tsx +1 -1
  46. package/src/lib/components/toggle/Toggle.component.tsx +9 -12
  47. package/src/lib/components/toggle/Toggle.test.tsx +56 -0
  48. package/src/lib/organisms/attachments/AttachmentTable.tsx +0 -2
  49. package/stories/SearchInput/searchinput.guideline.mdx +20 -0
  50. package/stories/{searchinput.stories.tsx → SearchInput/searchinput.stories.tsx} +13 -20
  51. package/stories/Select/selectv2.stories.tsx +23 -5
  52. package/stories/Toggle/toggle.guideline.mdx +20 -0
  53. package/stories/{toggle.stories.tsx → Toggle/toggle.stories.tsx} +17 -10
@@ -1,13 +1,7 @@
1
- import {
2
- screen,
3
- render as testingRender,
4
- within,
5
- } from '@testing-library/react';
1
+ import { screen, render as testingRender } from '@testing-library/react';
6
2
  import userEvent from '@testing-library/user-event';
7
3
  import React, { useState } from 'react';
8
4
  import { QueryClient, QueryClientProvider } from 'react-query';
9
- import { debug } from 'jest-preview';
10
- import { Icon } from '../icon/Icon.component';
11
5
  import { Option, Select } from '../selectv2/Selectv2.component';
12
6
 
13
7
  const render = (args) => {
@@ -27,26 +21,24 @@ const generateOptionsData = (n: number) =>
27
21
 
28
22
  const generateOptions = (n: number) => {
29
23
  return generateOptionsData(n).map((o, i) => (
30
- <Option key={i} value={o.value} {...o}>
24
+ <Option key={i} {...o} value={o.value}>
31
25
  {o.label}
32
26
  </Option>
33
27
  ));
34
28
  };
29
+ const optionsWithScrollSearchBar = generateOptions(10); // more than 8 options should display searchbar + scrollbar
30
+
31
+ const simpleOptions = generateOptions(4); // less than 5 options should not displays any scroll/search bar
35
32
 
36
33
  const SelectWrapper = (props) => {
37
- const [value, setValue] = useState(null);
34
+ const [value, setValue] = useState<string | null>(null);
38
35
  return (
39
36
  <Select value={value} onChange={(value) => setValue(value)} {...props}>
40
- {props.children}
37
+ {props.children || simpleOptions}
41
38
  </Select>
42
39
  );
43
40
  };
44
41
 
45
- const variants = ['default', 'rounded'];
46
- const optionsWithScrollSearchBar = generateOptions(10); // more than 8 options should display searchbar + scrollbar
47
-
48
- const simpleOptions = generateOptions(4); // less than 5 options should not displays any scroll/search bar
49
-
50
42
  const SelectReset = (props) => {
51
43
  const [value, setValue] = useState('default');
52
44
 
@@ -64,220 +56,218 @@ const SelectReset = (props) => {
64
56
  };
65
57
 
66
58
  describe('SelectV2', () => {
67
- const toBeClose = (container) => {
68
- expect(container.getElementsByClassName('sc-select__option').length).toBe(
69
- 0,
70
- );
71
- };
72
-
73
- const toBeOpenWith = (container, optionsLength: number) => {
74
- expect(container.getElementsByClassName('sc-select__option').length).toBe(
75
- optionsLength,
76
- );
77
- };
78
-
79
- const toggleSelect = (container) => {
80
- userEvent.click(container.querySelector('.sc-select__control'));
59
+ const selectors = {
60
+ option: (name: string | RegExp) => screen.getByRole('option', { name }),
61
+ options: () => screen.queryAllByRole('option'),
62
+ select: (withSearch?: boolean, name?: string) => {
63
+ if (withSearch) {
64
+ return screen.getByRole('combobox', { name });
65
+ }
66
+ return screen.getByRole('listbox', { name });
67
+ },
68
+ input: () => screen.getByRole('textbox'),
69
+ noOptions: () => screen.getByText(/No options/i),
70
+ highlightedText: () => screen.getByRole('mark'),
81
71
  };
82
72
 
83
- test.each(variants)(
84
- 'should open select on click/Enter/ArrowDown',
85
- (variant) => {
86
- const { container } = render(
87
- <SelectWrapper variant={variant}>{simpleOptions}</SelectWrapper>,
88
- );
89
- // should open on click
90
- toBeClose(container);
91
- toggleSelect(container); // open
92
-
93
- toBeOpenWith(container, simpleOptions.length);
94
- toggleSelect(container); // close
95
-
96
- userEvent.tab(); // remove focus
73
+ it('should throw error if <Option/> is outside <Select/>', () => {
74
+ expect(() => render(<Option value="Option 1" />)).toThrowError();
75
+ });
97
76
 
98
- // should open with Enter/ArrowDown
99
- ['Enter', 'ArrowDown'].forEach((key) => {
100
- // should open on arrow down
101
- toBeClose(container);
102
- userEvent.tab(); // focus
77
+ it('should open/close on click', () => {
78
+ render(<SelectWrapper />);
79
+ const select = selectors.select();
80
+ expect(select).toBeInTheDocument();
81
+ let options = selectors.options();
82
+ expect(options).toHaveLength(0);
83
+
84
+ // should open on click
85
+ userEvent.click(select);
86
+ simpleOptions.forEach((opt) => {
87
+ const option = selectors.option(opt.props.label);
88
+ expect(option).toBeInTheDocument();
89
+ });
90
+
91
+ userEvent.click(select);
92
+ options = selectors.options();
93
+ expect(options).toHaveLength(0);
94
+ });
103
95
 
104
- userEvent.keyboard(`{${key}}`);
105
- toBeOpenWith(container, simpleOptions.length);
106
- toggleSelect(container); // close select
96
+ it('should open/close with keyboard', () => {
97
+ render(<SelectWrapper />);
98
+ const select = selectors.select();
99
+ expect(select).toBeInTheDocument();
100
+ const options = selectors.options();
101
+ expect(options).toHaveLength(0);
102
+
103
+ // should open on Enter
104
+ userEvent.tab();
105
+ userEvent.keyboard('{Enter}');
106
+ simpleOptions.forEach((opt) => {
107
+ const option = selectors.option(opt.props.label);
108
+ expect(option).toBeInTheDocument();
109
+ });
110
+
111
+ // should close on Enter
112
+ userEvent.keyboard('{Enter}');
113
+ expect(options).toHaveLength(0);
114
+
115
+ // should open on ArrowDown
116
+ userEvent.tab();
117
+ userEvent.keyboard('{ArrowDown}');
118
+ simpleOptions.forEach((opt) => {
119
+ const option = selectors.option(opt.props.label);
120
+ expect(option).toBeInTheDocument();
121
+ });
122
+ });
107
123
 
108
- toBeClose(container);
109
- userEvent.tab(); // remove focus
110
- });
111
- },
112
- );
113
- test.each(variants)('should display custom placeholder', (variant) => {
124
+ it('should display custom placeholder', () => {
114
125
  const placeholder = 'My placeholder...';
115
- const { container } = render(
116
- <SelectWrapper variant={variant} placeholder={placeholder}>
126
+ render(<SelectWrapper placeholder={placeholder} />);
127
+ expect(screen.getByText(placeholder)).toBeInTheDocument();
128
+ });
129
+
130
+ it('should be disabled', () => {
131
+ render(
132
+ <SelectWrapper value="1" disabled={true}>
117
133
  {simpleOptions}
118
134
  </SelectWrapper>,
119
135
  );
120
- expect(
121
- container.querySelector('.sc-select__placeholder'),
122
- ).toHaveTextContent(placeholder);
123
- });
124
- test.each(variants)('should display default value', (variant) => {
125
- const options = [
126
- {
127
- value: 0,
128
- label: 'Label 0',
129
- },
130
- ];
131
- const { container } = render(
132
- <Select variant={variant} defaultValue={options[0]} options={options} />,
133
- );
134
- expect(container.querySelector('.sc-select__placeholder')).toBeNull();
135
- expect(
136
- container.querySelector('.sc-select__single-value'),
137
- ).toHaveTextContent('Label 0');
136
+ userEvent.tab();
137
+ const select = selectors.select();
138
+ expect(select).not.toHaveFocus();
139
+ // use input instead of select because select will still trigger the open/close action
140
+ // despite select container not being clickable and input being disabled
141
+ const input = selectors.input();
142
+ userEvent.click(input);
143
+ const options = selectors.options();
144
+ expect(options).toHaveLength(0);
138
145
  });
139
- test.each(variants)('select should be disabled', (variant) => {
140
- const { container } = render(
141
- <Select variant={variant} disabled defaultValue="0">
142
- <Option value="0">{'Label 0'}</Option>
143
- </Select>,
146
+
147
+ it('should display no option', () => {
148
+ render(
149
+ <SelectWrapper>
150
+ <></>
151
+ </SelectWrapper>,
144
152
  );
145
- expect(container.querySelector('input')).toBeDisabled();
153
+ const select = selectors.select();
154
+ userEvent.click(select);
155
+ const noOptions = selectors.noOptions();
156
+ expect(noOptions).toBeInTheDocument();
146
157
  });
147
- test.each(variants)('should display no options', (variant) => {
148
- const { container } = render(<Select variant={variant} />);
149
- toggleSelect(container, variant);
150
- expect(
151
- container.querySelector('.sc-select__menu-notice--no-options'),
152
- ).toHaveTextContent('No options');
158
+
159
+ it('should filter and highlight on search', () => {
160
+ render(<SelectWrapper>{optionsWithScrollSearchBar} </SelectWrapper>);
161
+ const select = selectors.select(true);
162
+ userEvent.click(select);
163
+ const input = selectors.input();
164
+
165
+ userEvent.type(input, '2');
166
+ const options = selectors.options();
167
+ expect(options).toHaveLength(1);
168
+ const searchedText = selectors.highlightedText();
169
+ expect(searchedText).toHaveTextContent('2');
153
170
  });
154
- test.each(variants)(
155
- 'should display a search bar if more than 8 options',
156
- (variant) => {
157
- const { container } = render(
158
- <SelectWrapper variant={variant}>
159
- {optionsWithScrollSearchBar}
160
- </SelectWrapper>,
161
- );
162
- expect(container.querySelector('.sc-select__input')).not.toBeNull();
163
- },
164
- );
165
- test.each(variants)('should display option', (variant) => {
166
- const { container, getByTestId } = render(
167
- <Select variant={variant}>
168
- <Option data-testid="disabledOption" value="0" disabled>
169
- Label 1
170
- </Option>
171
- <Option
172
- data-testid="option2"
173
- value="1"
174
- icon={<Icon name="Deletion-marker" />}
175
- >
176
- Label 2
177
- </Option>
178
- </Select>,
179
- );
180
- toggleSelect(container);
181
- expect(getByTestId('disabledOption')).toHaveAttribute(
182
- 'aria-disabled',
183
- 'true',
184
- );
185
- const icon = getByTestId('option2').querySelector('i');
186
- expect(icon).not.toBeNull();
187
- expect(icon).toHaveAttribute('aria-label', 'Deletion-marker ');
171
+
172
+ it('should unfocus the search input when the select is closed', () => {
173
+ render(<SelectWrapper>{optionsWithScrollSearchBar} </SelectWrapper>);
174
+ const select = selectors.select(true);
175
+ userEvent.click(select);
176
+ let input = selectors.input();
177
+ expect(input).toHaveFocus();
178
+ const option = selectors.option(/Item 1/);
179
+ userEvent.click(option);
180
+ input = selectors.input();
181
+ expect(input).not.toHaveFocus();
188
182
  });
189
- test.each(variants)(
190
- '<Option/> component should throw if outside <Select/>',
191
- () => {
192
- // mock console.error to not display error message on throw
193
- jest.spyOn(console, 'error');
194
- console.error.mockImplementation(() => {});
195
- expect(() => render(<Option />)).toThrowError();
196
- console.error.mockRestore(); // restore console.error
197
- },
198
- );
199
- test.each(variants)('should highlight text on search', (variant) => {
200
- const { container, getByTestId } = render(
201
- <SelectWrapper variant={variant}>
202
- {optionsWithScrollSearchBar}
203
- </SelectWrapper>,
183
+
184
+ it('should be possible to use searchbar when option is selected', () => {
185
+ render(
186
+ <SelectWrapper value="1">{optionsWithScrollSearchBar}</SelectWrapper>,
204
187
  );
205
- toggleSelect(container);
206
- userEvent.type(container.querySelector('input'), 'Ite');
207
- const firstOption = getByTestId('option0');
208
- expect(firstOption).toHaveClass('sc-select__option--is-focused');
209
- expect(
210
- container.querySelector('.sc-highlighted-matching-text'),
211
- ).toHaveTextContent('Ite');
188
+ expect(screen.getByText(/Item 1/)).toBeVisible();
189
+ const select = selectors.select(true);
190
+ userEvent.click(select);
191
+ const input = selectors.input();
192
+ userEvent.type(input, '2');
193
+ expect(screen.queryByText(/Item 1/)).not.toBeInTheDocument();
194
+ const options = selectors.options();
195
+ expect(options).toHaveLength(1);
212
196
  });
213
- test.each(variants)('should select/unselect option', (variant) => {
214
- const { container, getByTestId } = render(
215
- <SelectWrapper variant={variant}>{simpleOptions}</SelectWrapper>,
216
- );
217
- // select option
218
- toggleSelect(container); // open select
219
197
 
220
- toBeOpenWith(container, simpleOptions.length);
221
- userEvent.click(getByTestId('option0'));
222
- toBeClose(container); // selecting an option should close select
223
-
224
- // unselect option
225
- toggleSelect(container); // reopen select
198
+ it('should select/unselect option with keyboard', () => {
199
+ render(<SelectWrapper />);
200
+ const select = selectors.select();
201
+ userEvent.tab();
202
+ userEvent.keyboard('{ArrowDown}');
226
203
 
227
- expect(getByTestId('option0')).toHaveClass(
228
- 'sc-select__option--is-selected',
229
- );
230
- // should be focused on the selected option
231
- expect(getByTestId('option0')).toHaveAttribute('aria-selected', 'true');
232
- userEvent.click(getByTestId('option1')); // click on another option
204
+ // should select first option
205
+ userEvent.keyboard('{Enter}');
206
+ expect(select).toHaveTextContent('Item 0');
233
207
 
234
- toBeClose(container); // selecting an option should close select
208
+ // should select second option
209
+ userEvent.tab();
210
+ userEvent.keyboard('{ArrowDown}');
211
+ userEvent.keyboard('{ArrowDown}');
235
212
 
236
- toggleSelect(container); // reopen select
213
+ userEvent.keyboard('{Enter}');
214
+ expect(select).toHaveTextContent('Item 1');
215
+ });
237
216
 
238
- expect(getByTestId('option1')).toHaveClass(
239
- 'sc-select__option--is-selected',
240
- );
241
- expect(getByTestId('option1')).toHaveAttribute('aria-selected', 'true');
242
- expect(getByTestId('option0')).not.toHaveClass(
243
- 'sc-select__option--is-selected',
217
+ it('should scroll to selected value when opening select', () => {
218
+ render(
219
+ <SelectWrapper value={optionsWithScrollSearchBar[9].props.value}>
220
+ {optionsWithScrollSearchBar}
221
+ </SelectWrapper>,
244
222
  );
245
- expect(getByTestId('option0')).not.toHaveAttribute('aria-selected', 'true');
223
+ const select = selectors.select(true);
224
+ userEvent.click(select);
225
+ const option = selectors.option(/Item 9/);
226
+ expect(screen.queryByRole('option', { name: /Item 1/i })).toBeNull();
227
+ expect(option).toBeVisible();
246
228
  });
247
- test.each(variants)('should focus on keyDown/keyUp', (variant) => {
248
- const { container, getByTestId } = render(
249
- <SelectWrapper variant={variant}>{simpleOptions}</SelectWrapper>,
250
- );
251
- toggleSelect(container);
252
- userEvent.keyboard('{ArrowDown}');
253
- expect(getByTestId('option0')).not.toHaveClass(
254
- 'sc-select__option--is-focused',
255
- );
256
- expect(getByTestId('option1')).toHaveClass('sc-select__option--is-focused');
257
- userEvent.keyboard('{ArrowUp}');
258
- expect(getByTestId('option0')).toHaveClass('sc-select__option--is-focused');
259
- expect(getByTestId('option1')).not.toHaveClass(
260
- 'sc-select__option--is-focused',
261
- );
229
+
230
+ it('should be able to reset the value', () => {
231
+ render(<SelectReset>{simpleOptions}</SelectReset>);
232
+ const button = screen.getByText(/reset/);
233
+ userEvent.click(button);
234
+ const select = selectors.select();
235
+ expect(select).toHaveTextContent('Select...');
262
236
  });
263
- test.each(variants)('should be able to reset the value', (variant) => {
264
- const { container } = render(
265
- <SelectReset variant={variant}>{simpleOptions}</SelectReset>,
237
+
238
+ it('should not be possible to select an option if it is disabled', () => {
239
+ render(
240
+ <SelectWrapper>
241
+ <Option value="1" disabled>
242
+ Item 1
243
+ </Option>
244
+ <Option value="2">Item 2</Option>
245
+ </SelectWrapper>,
266
246
  );
267
- userEvent.click(container.querySelector('button'));
268
- expect(
269
- container.querySelector('.sc-select__placeholder'),
270
- ).toHaveTextContent('Select...');
247
+ const select = selectors.select();
248
+ userEvent.click(select);
249
+ const option = selectors.option(/Item 1/);
250
+
251
+ userEvent.click(option);
252
+ const option2 = selectors.option(/Item 2/);
253
+ expect(option2).toBeVisible();
271
254
  });
272
255
 
273
- it('should not trigger onChange when defaultValue is empty string', () => {
274
- const onChange = jest.fn();
256
+ it('should display a tooltip if the option is disabled with a reason', () => {
275
257
  render(
276
- <Select value={''} onChange={onChange}>
277
- <Option value="test">test</Option>
278
- </Select>,
258
+ <SelectWrapper>
259
+ <Option value="1" disabled disabledReason="This option is disabled">
260
+ Item 1
261
+ </Option>
262
+ </SelectWrapper>,
279
263
  );
280
- expect(onChange).toBeCalledTimes(0);
264
+ const select = selectors.select();
265
+ userEvent.click(select);
266
+ const option = selectors.option(/Item 1/);
267
+ expect(option).toHaveAttribute('aria-disabled', 'true');
268
+ userEvent.hover(option);
269
+ const tooltip = screen.getByText(/This option is disabled/);
270
+ expect(tooltip).toBeInTheDocument();
281
271
  });
282
272
 
283
273
  it('should select with the right selector', async () => {
@@ -17,7 +17,7 @@ export type SearchProps = {
17
17
  value?: string;
18
18
  locale?: TableLocalType;
19
19
  totalCount?: number;
20
- } & Omit<Props, 'disableToggle' | 'onChange'>;
20
+ } & Omit<Props, 'onChange'>;
21
21
 
22
22
  const SearchContainer = styled.div`
23
23
  display: flex;
@@ -88,7 +88,6 @@ export function TableSearch(props: SearchProps) {
88
88
  <SearchInput
89
89
  value={value}
90
90
  placeholder={translations[locale].search}
91
- disableToggle
92
91
  size="1"
93
92
  onChange={(evt) => {
94
93
  if (typeof onChange === 'function') {
@@ -44,14 +44,14 @@ export const VirtualizedRows = <
44
44
  listRef,
45
45
  itemKey,
46
46
  }: VirtualizedRowsType<DATA_ROW>) => (
47
- <AutoSizer>
48
- {({ height, width }) => {
47
+ <AutoSizer disableWidth>
48
+ {({ height }) => {
49
49
  return (
50
50
  <List
51
- height={height}
51
+ height={height - 1}
52
52
  itemCount={rows.length} // how many items we are going to render
53
53
  itemSize={convertRemToPixels(tableRowHeight[rowHeight])} // height of each row in pixel
54
- width={width}
54
+ width={'100%'}
55
55
  itemKey={itemKey}
56
56
  itemData={rows}
57
57
  ref={listRef}
@@ -81,7 +81,7 @@ export const VirtualizedRows = <
81
81
  );
82
82
 
83
83
  export const useTableScrollbar = () => {
84
- const [hasScrollbar, setHasScrollbar] = useState(false);
84
+ const { hasScrollbar, setHasScrollbar } = useTableContext();
85
85
  const [scrollBarWidth, setScrollBarWidth] = useState(0);
86
86
 
87
87
  const handleScrollbarWidth = useCallback((node) => {
@@ -42,7 +42,7 @@ export const TableHeader = styled.div<{
42
42
  `;
43
43
 
44
44
  type HeadRowType = {
45
- hasScrollBar: boolean;
45
+ hasScrollBar?: boolean;
46
46
  scrollBarWidth: number;
47
47
  rowHeight: TableHeightKeyType;
48
48
  separationLineVariant: TableVariantType;
@@ -105,6 +105,10 @@ type TableContextType<
105
105
  en: { singular: string; plural: string };
106
106
  fr?: { singular: string; plural: string };
107
107
  };
108
+ syncScrollListener: ((event: Event) => void) | null;
109
+ setSyncScrollListener: (listener: (event: Event) => void) => void;
110
+ setHasScrollbar: React.Dispatch<React.SetStateAction<boolean>>;
111
+ hasScrollbar?: boolean;
108
112
  };
109
113
  const TableContext = React.createContext<TableContextType | null>(null);
110
114
 
@@ -215,6 +219,12 @@ function Table<
215
219
 
216
220
  const [rowHeight, setRowHeight] = React.useState<TableHeightKeyType>('h40');
217
221
 
222
+ const [syncScrollListener, setSyncScrollListener] = React.useState<
223
+ ((event: Event) => void) | null
224
+ >(null);
225
+
226
+ const [hasScrollbar, setHasScrollbar] = React.useState<boolean>(false);
227
+
218
228
  const {
219
229
  headerGroups,
220
230
  rows,
@@ -310,6 +320,10 @@ function Table<
310
320
  toggleAllRowsSelected,
311
321
  status,
312
322
  entityName,
323
+ syncScrollListener,
324
+ setSyncScrollListener,
325
+ setHasScrollbar,
326
+ hasScrollbar,
313
327
  };
314
328
  return (
315
329
  <TableContext.Provider
@@ -1,17 +1,15 @@
1
- import { useEffect, useState, useCallback } from 'react';
1
+ import { useEffect, useState, useCallback, useRef } from 'react';
2
2
  import { Row } from 'react-table';
3
3
  import { FixedSizeList } from 'react-window';
4
+ import { useTableContext } from './Tablev2.component';
4
5
 
5
6
  export default function useSyncedScroll<
6
7
  DATA_ROW extends Record<string, unknown> = Record<string, unknown>,
7
8
  >(): {
8
9
  headerRef: (element: HTMLDivElement) => void;
9
- bodyRef: (tableBody: FixedSizeList<Row<DATA_ROW>[]>) => void;
10
+ bodyRef: React.RefObject<FixedSizeList<Row<DATA_ROW>[]>>;
10
11
  } {
11
- const [listener, setListener] =
12
- useState<((event: Event) => void) | null>(null);
13
- const [tableBody, setTableBody] =
14
- useState<FixedSizeList<Row<DATA_ROW>[]> | null>(null);
12
+ const { syncScrollListener, setSyncScrollListener } = useTableContext();
15
13
 
16
14
  const headerRef = useCallback(
17
15
  (element: HTMLDivElement) => {
@@ -24,41 +22,41 @@ export default function useSyncedScroll<
24
22
  });
25
23
  }
26
24
  };
27
- if (!listener) {
28
- setListener(() => {
25
+ if (!syncScrollListener) {
26
+ setSyncScrollListener(() => {
29
27
  return callback;
30
28
  });
31
29
  }
32
30
  }
33
31
  },
34
- [listener],
32
+ [syncScrollListener],
35
33
  );
36
34
 
37
- const bodyRef = useCallback((tableBody: FixedSizeList<Row<DATA_ROW>[]>) => {
38
- setTableBody(tableBody);
39
- }, []);
35
+ const bodyRef = useRef<FixedSizeList<Row<DATA_ROW>[]> | null>(null);
40
36
 
41
37
  useEffect(() => {
42
- if (tableBody && listener) {
38
+ if (bodyRef.current && syncScrollListener) {
43
39
  /*
44
40
  We intentionally use _outerRef prop here despite the fact that it is
45
41
  internal use only and not typed, as it is the only way for us to access to the scrollable element
46
42
  */
47
43
  //@ts-expect-error
48
- (tableBody._outerRef as HTMLDivElement).addEventListener(
44
+ (bodyRef.current._outerRef as HTMLDivElement).addEventListener(
49
45
  'scroll',
50
- listener,
46
+ syncScrollListener,
51
47
  );
52
-
53
- return () => {
54
- //@ts-expect-error
55
- if (tableBody && tableBody._outerRef) {
56
- //@ts-expect-error
57
- tableBody._outerRef.removeEventListener('scroll', listener);
58
- }
59
- };
60
48
  }
61
- }, [tableBody, listener]);
49
+ return () => {
50
+ //@ts-expect-error
51
+ if (bodyRef.current && bodyRef.current._outerRef) {
52
+ //@ts-expect-error
53
+ bodyRef.current._outerRef.removeEventListener(
54
+ 'scroll',
55
+ syncScrollListener,
56
+ );
57
+ }
58
+ };
59
+ }, [bodyRef.current, syncScrollListener]);
62
60
 
63
61
  return { headerRef, bodyRef };
64
62
  }
@@ -58,7 +58,7 @@ export const TabItem = styled.div<{
58
58
  `;
59
59
  export const TabsContainer = styled.div<{
60
60
  tabLineColor?: string;
61
- separatorColor: string;
61
+ separatorColor?: string;
62
62
  }>`
63
63
  height: 100%;
64
64
  width: 100%;
@@ -185,8 +185,8 @@ function Tabs({
185
185
  });
186
186
  return (
187
187
  <TabsContext.Provider value={true}>
188
- {/*@ts-expect-error containerType is not yet a valid prop for react */}
189
188
  <TabsContainer
189
+ // @ts-expect-error containerType is not yet a valid prop for react
190
190
  style={{ containerType: 'size' }}
191
191
  className={['sc-tabs', className].join(' ')}
192
192
  tabLineColor={tabLineColor}