@ssa-ui-kit/core 1.0.17 → 1.0.19

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 (27) hide show
  1. package/dist/components/Pagination/Pagination.d.ts +1 -1
  2. package/dist/components/Pagination/PaginationContext.d.ts +1 -1
  3. package/dist/components/Pagination/components/RowsPerPageDropdown/RowsPerPageDropdown.d.ts +2 -0
  4. package/dist/components/Pagination/components/RowsPerPageDropdown/index.d.ts +2 -0
  5. package/dist/components/Pagination/components/RowsPerPageDropdown/types.d.ts +9 -0
  6. package/dist/components/Pagination/components/index.d.ts +1 -0
  7. package/dist/components/Pagination/constants.d.ts +6 -0
  8. package/dist/components/Pagination/styles.d.ts +7 -0
  9. package/dist/components/Pagination/types.d.ts +10 -0
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/package.json +1 -1
  13. package/src/components/Dropdown/Dropdown.spec.tsx +62 -0
  14. package/src/components/Dropdown/Dropdown.stories.tsx +27 -2
  15. package/src/components/Dropdown/Dropdown.tsx +4 -0
  16. package/src/components/Pagination/Pagination.spec.tsx +2 -1
  17. package/src/components/Pagination/Pagination.stories.tsx +16 -2
  18. package/src/components/Pagination/Pagination.tsx +85 -43
  19. package/src/components/Pagination/PaginationContext.tsx +4 -1
  20. package/src/components/Pagination/components/RowsPerPageDropdown/RowsPerPageDropdown.tsx +70 -0
  21. package/src/components/Pagination/components/RowsPerPageDropdown/index.ts +2 -0
  22. package/src/components/Pagination/components/RowsPerPageDropdown/types.ts +7 -0
  23. package/src/components/Pagination/components/index.ts +1 -0
  24. package/src/components/Pagination/constants.ts +18 -0
  25. package/src/components/Pagination/styles.tsx +25 -0
  26. package/src/components/Pagination/types.ts +10 -0
  27. package/tsbuildcache +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssa-ui-kit/core",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "private": false,
@@ -141,6 +141,68 @@ describe('Dropdown', () => {
141
141
  }
142
142
  });
143
143
 
144
+ it('Selected item changed successfully', async () => {
145
+ const selectedItem = items[2];
146
+ const {
147
+ user,
148
+ mockOnChange,
149
+ getByRole,
150
+ queryByRole,
151
+ getByTestId,
152
+ rerender,
153
+ } = setup({
154
+ selectedItem,
155
+ });
156
+
157
+ expect(mockOnChange).not.toBeCalled();
158
+
159
+ let dropdownEl = getByTestId('dropdown');
160
+
161
+ let dropdownToggleEl = within(dropdownEl).getByRole('combobox');
162
+
163
+ let listboxEl = queryByRole('listbox');
164
+
165
+ await user.click(dropdownToggleEl);
166
+
167
+ listboxEl = getByRole('listbox');
168
+ const listItemEls = within(listboxEl).getAllByRole('listitem');
169
+
170
+ dropdownToggleEl = within(dropdownEl).getByRole('combobox');
171
+
172
+ await within(dropdownToggleEl).findByTitle('Carrot up');
173
+
174
+ for (let i = 0; i < items.length; ++i) {
175
+ const listItem = items[i];
176
+ const listItemEl = listItemEls[i];
177
+
178
+ const itemListValue = getListItemValue(listItem);
179
+
180
+ await within(listItemEl).findByText(itemListValue);
181
+ expect(within(listItemEl).getByRole('button')).toHaveTextContent(
182
+ itemListValue,
183
+ );
184
+
185
+ if (listItem.id === selectedItem.id) {
186
+ expect(listItemEl).toHaveAttribute('aria-selected', 'true');
187
+ expect(listItemEl).toHaveStyle('background: #DEE1EC');
188
+ } else {
189
+ expect(listItemEl).toHaveAttribute('aria-selected', 'false');
190
+ }
191
+ }
192
+
193
+ rerender(
194
+ <Dropdown onChange={mockOnChange} selectedItem={items[0]}>
195
+ {items.map((item, index) => (
196
+ <DropdownOption key={index} value={item.value} />
197
+ ))}
198
+ </Dropdown>,
199
+ );
200
+
201
+ dropdownEl = getByTestId('dropdown');
202
+ dropdownToggleEl = within(dropdownEl).getByRole('combobox');
203
+ expect(dropdownToggleEl).toHaveTextContent(getListItemValue(items[0]));
204
+ });
205
+
144
206
  it('Renders with an empty items array', async () => {
145
207
  const mockOnChange = jest.fn();
146
208
  const { getByTestId, queryByRole, getByRole } = render(
@@ -1,9 +1,9 @@
1
+ import { useState } from 'react';
1
2
  import type { Meta, StoryObj } from '@storybook/react';
2
3
  import styled from '@emotion/styled';
3
4
  import { css } from '@emotion/react';
4
-
5
5
  import DropdownOption from '@components/DropdownOption';
6
-
6
+ import Button from '@components/Button';
7
7
  import Dropdown from './Dropdown';
8
8
  import { DropdownProps } from './types';
9
9
  import { DropdownOptionProps } from '../..';
@@ -153,3 +153,28 @@ export const Custom: StoryObj = (args: Args) => {
153
153
  Custom.args = {
154
154
  isDisabled: false,
155
155
  };
156
+
157
+ export const DynamicallyChangedSelectedItem: StoryObj = (args: Args) => {
158
+ const [selectedIndex, setSelectedIndex] = useState(1);
159
+
160
+ const handleUpdate = () => {
161
+ const newIndex = selectedIndex < items.length - 1 ? selectedIndex + 1 : 0;
162
+ setSelectedIndex(newIndex);
163
+ };
164
+ return (
165
+ <div>
166
+ <Dropdown
167
+ isDisabled={args.isDisabled}
168
+ selectedItem={items[selectedIndex]}>
169
+ {items.map((item) => (
170
+ <DropdownOption key={item.id} value={item.value} />
171
+ ))}
172
+ </Dropdown>
173
+ <Button variant="info" css={{ marginLeft: 10 }} onClick={handleUpdate}>
174
+ Update selected item
175
+ </Button>
176
+ </div>
177
+ );
178
+ };
179
+
180
+ DynamicallyChangedSelectedItem.args = { isDisabled: false };
@@ -77,6 +77,10 @@ const Dropdown = <T extends DropdownOptionProps>({
77
77
  }
78
78
  }, [isOpen, isDisabled, isFocused]);
79
79
 
80
+ useEffect(() => {
81
+ setActiveItem(selectedItem);
82
+ }, [selectedItem]);
83
+
80
84
  useEffect(() => {
81
85
  if (isDisabled && isOpen) {
82
86
  setIsOpen(false);
@@ -43,6 +43,7 @@ const testCases = {
43
43
 
44
44
  const checkPages = (range: number[], selected?: number) => {
45
45
  const navigation = screen.getByRole('navigation');
46
+ const buttonsWrapper = navigation.querySelector('div') as HTMLDivElement;
46
47
  const withinNavigation = within(navigation);
47
48
 
48
49
  const prevPageBtn = within(
@@ -54,7 +55,7 @@ const checkPages = (range: number[], selected?: number) => {
54
55
  );
55
56
  nextPageBtn.getByTitle('Carrot right');
56
57
 
57
- const buttonsAndBreaks = Array.from(navigation.children).slice(1, -1);
58
+ const buttonsAndBreaks = Array.from(buttonsWrapper.children).slice(1, -1);
58
59
 
59
60
  for (let i = 0; i < range.length; ++i) {
60
61
  const page = range[i];
@@ -1,4 +1,5 @@
1
1
  import type { Meta } from '@storybook/react';
2
+ import { StoryAnnotations } from '@storybook/types';
2
3
  import { Pagination, PaginationContextProvider } from './index';
3
4
 
4
5
  export default {
@@ -6,9 +7,8 @@ export default {
6
7
  component: Pagination,
7
8
  decorators: [
8
9
  (Story, { parameters, args }) => {
9
- const { selectedPage } = parameters;
10
10
  return (
11
- <PaginationContextProvider selectedPage={selectedPage}>
11
+ <PaginationContextProvider {...parameters}>
12
12
  {Story(args)}
13
13
  </PaginationContextProvider>
14
14
  );
@@ -58,3 +58,17 @@ export const Disabled = {
58
58
  selectedPage: 5,
59
59
  },
60
60
  };
61
+
62
+ export const WithManualPageSettingAndPerPage: StoryAnnotations = {
63
+ args: {
64
+ pagesCount: 10,
65
+ isPageSettingVisible: true,
66
+ isRowPerPageVisible: true,
67
+ },
68
+ parameters: {
69
+ selectedPage: 1,
70
+ },
71
+ };
72
+
73
+ WithManualPageSettingAndPerPage.storyName =
74
+ 'With records per page and page number setting';
@@ -1,13 +1,13 @@
1
- import styled from '@emotion/styled';
1
+ import { KeyboardEvent, useState } from 'react';
2
2
  import { usePaginationRange } from '@ssa-ui-kit/hooks';
3
-
3
+ import { InputProps } from '@components/Input/types';
4
+ import Wrapper from '@components/Wrapper';
4
5
  import { ArrowButton } from './ArrowButton';
5
6
  import { PaginationButtons } from './PaginationButtons';
6
-
7
7
  import { PaginationProps } from './types';
8
8
  import { usePaginationContext } from './PaginationContext';
9
-
10
- const Nav = styled.nav``;
9
+ import { RowsPerPageDropdown } from './components';
10
+ import * as S from './styles';
11
11
 
12
12
  const Pagination = ({
13
13
  pagesCount,
@@ -15,51 +15,93 @@ const Pagination = ({
15
15
  as,
16
16
  ariaLabel,
17
17
  isDisabled,
18
+ pageNumberPlaceholder = 'Page №',
19
+ isPageSettingVisible = false,
20
+ isRowPerPageVisible = false,
21
+ rowPerPageProps,
22
+ manualPageNumberProps,
18
23
  }: PaginationProps) => {
19
24
  const { page, setPage } = usePaginationContext();
20
25
  const range = usePaginationRange({ pagesCount, selectedPage: page });
26
+ const [inputStatus, setInputStatus] = useState<InputProps['status']>('basic');
27
+ const handlePageNumberChange = (event: KeyboardEvent<HTMLInputElement>) => {
28
+ if (event.code === 'Enter') {
29
+ const { value: inputValue } = event.currentTarget;
30
+ const newPageNumber = Number(inputValue);
31
+ if (newPageNumber > 0 && newPageNumber <= pagesCount) {
32
+ setInputStatus('basic');
33
+ setPage(Number(inputValue));
34
+ } else {
35
+ setInputStatus('error');
36
+ }
37
+ }
38
+ };
21
39
 
22
40
  return (
23
- <Nav className={className} as={as} aria-label={ariaLabel || 'Pagination'}>
24
- <ArrowButton
25
- direction="left"
26
- onClick={() => {
27
- if (page) {
28
- setPage(page - 1);
41
+ <S.PaginationNav
42
+ className={className}
43
+ as={as}
44
+ aria-label={ariaLabel || 'Pagination'}>
45
+ {isRowPerPageVisible && <RowsPerPageDropdown {...rowPerPageProps} />}
46
+ {isPageSettingVisible && (
47
+ <Wrapper css={{ width: 'auto', marginRight: 32 }}>
48
+ <S.PageNumberInput
49
+ name="page-number"
50
+ placeholder={pageNumberPlaceholder}
51
+ onKeyUp={handlePageNumberChange}
52
+ status={inputStatus}
53
+ type="number"
54
+ inputProps={{
55
+ autoComplete: 'off',
56
+ }}
57
+ {...manualPageNumberProps}
58
+ />
59
+ <span css={{ textWrap: 'nowrap', fontSize: 14 }}>
60
+ {page || 0} / {pagesCount}
61
+ </span>
62
+ </Wrapper>
63
+ )}
64
+ <Wrapper>
65
+ <ArrowButton
66
+ direction="left"
67
+ onClick={() => {
68
+ if (page) {
69
+ setPage(page - 1);
70
+ }
71
+ }}
72
+ isDisabled={
73
+ isDisabled ||
74
+ pagesCount == null ||
75
+ pagesCount <= 1 ||
76
+ page == null ||
77
+ page === 1
29
78
  }
30
- }}
31
- isDisabled={
32
- isDisabled ||
33
- pagesCount == null ||
34
- pagesCount <= 1 ||
35
- page == null ||
36
- page === 1
37
- }
38
- css={{ marginRight: '12px' }}
39
- />
40
- <PaginationButtons
41
- range={range}
42
- selectedPage={page}
43
- onClick={setPage}
44
- isDisabled={isDisabled}
45
- />
46
- <ArrowButton
47
- direction="right"
48
- onClick={() => {
49
- if (page) {
50
- setPage(page + 1);
79
+ css={{ marginRight: '12px' }}
80
+ />
81
+ <PaginationButtons
82
+ range={range}
83
+ selectedPage={page}
84
+ onClick={setPage}
85
+ isDisabled={isDisabled}
86
+ />
87
+ <ArrowButton
88
+ direction="right"
89
+ onClick={() => {
90
+ if (page) {
91
+ setPage(page + 1);
92
+ }
93
+ }}
94
+ isDisabled={
95
+ isDisabled ||
96
+ pagesCount == null ||
97
+ pagesCount <= 1 ||
98
+ page == null ||
99
+ page === pagesCount
51
100
  }
52
- }}
53
- isDisabled={
54
- isDisabled ||
55
- pagesCount == null ||
56
- pagesCount <= 1 ||
57
- page == null ||
58
- page === pagesCount
59
- }
60
- css={{ marginLeft: '7px' }}
61
- />
62
- </Nav>
101
+ css={{ marginLeft: '7px' }}
102
+ />
103
+ </Wrapper>
104
+ </S.PaginationNav>
63
105
  );
64
106
  };
65
107
 
@@ -3,6 +3,7 @@ import {
3
3
  PaginationContextProps,
4
4
  PaginationContextProviderProps,
5
5
  } from './types';
6
+ import { DEFAULT_PER_PAGE_VALUE } from './constants';
6
7
 
7
8
  export const PaginationContext = createContext<PaginationContextProps>(
8
9
  {} as PaginationContextProps,
@@ -12,11 +13,13 @@ export const usePaginationContext = () => useContext(PaginationContext);
12
13
 
13
14
  export const PaginationContextProvider = ({
14
15
  selectedPage,
16
+ defaultPerPage = DEFAULT_PER_PAGE_VALUE,
15
17
  children,
16
18
  }: PaginationContextProviderProps) => {
19
+ const [perPage, setPerPage] = useState<number>(defaultPerPage);
17
20
  const [page, setPage] = useState(selectedPage);
18
21
  return (
19
- <PaginationContext.Provider value={{ page, setPage }}>
22
+ <PaginationContext.Provider value={{ page, perPage, setPage, setPerPage }}>
20
23
  {children}
21
24
  </PaginationContext.Provider>
22
25
  );
@@ -0,0 +1,70 @@
1
+ import { useTheme } from '@emotion/react';
2
+ import {
3
+ DropdownOption,
4
+ Typography,
5
+ Wrapper,
6
+ Dropdown,
7
+ usePaginationContext,
8
+ } from '@components';
9
+ import { RowsPerPageDropdownProps } from './types';
10
+ import { DEFAULT_PER_PAGE_VALUE, ROWS_PER_PAGE_LIST } from '../../constants';
11
+
12
+ export const RowsPerPageDropdown = ({
13
+ selectedItem = DEFAULT_PER_PAGE_VALUE,
14
+ rowsPerPageList = ROWS_PER_PAGE_LIST,
15
+ rowsPerPageText = 'Rows per page',
16
+ ...rest
17
+ }: RowsPerPageDropdownProps) => {
18
+ const theme = useTheme();
19
+ const { setPerPage } = usePaginationContext();
20
+
21
+ const selectedItemForDropdown =
22
+ rowsPerPageList.find(({ value }) => value === selectedItem) ||
23
+ rowsPerPageList[0];
24
+
25
+ const onChange: Parameters<typeof Dropdown>[0]['onChange'] = ({ value }) => {
26
+ setPerPage(value as number);
27
+ };
28
+
29
+ return (
30
+ <Wrapper css={{ width: 'auto', ul: { width: 'auto' } }}>
31
+ <Typography
32
+ variant="subtitle"
33
+ css={{
34
+ fontSize: 14,
35
+ lineHeight: 1,
36
+ textWrap: 'nowrap',
37
+ }}>
38
+ {rowsPerPageText}:
39
+ </Typography>
40
+ <Dropdown
41
+ selectedItem={selectedItemForDropdown}
42
+ onChange={onChange}
43
+ css={{
44
+ marginLeft: 10,
45
+ marginRight: 37,
46
+ background: 'none',
47
+ color: '#070821',
48
+ gap: 5,
49
+ padding: 0,
50
+ '&:focus': {
51
+ color: '#070821',
52
+ background: 'none',
53
+ '&::before': {
54
+ display: 'none',
55
+ },
56
+ },
57
+ '& svg path': {
58
+ stroke: theme.colors.greyDarker,
59
+ },
60
+ }}
61
+ {...rest}>
62
+ {rowsPerPageList.map((item) => (
63
+ <DropdownOption key={item.id} value={item.value}>
64
+ {item.value}
65
+ </DropdownOption>
66
+ ))}
67
+ </Dropdown>
68
+ </Wrapper>
69
+ );
70
+ };
@@ -0,0 +1,2 @@
1
+ export * from './RowsPerPageDropdown';
2
+ export * from '../../constants';
@@ -0,0 +1,7 @@
1
+ import { CommonProps } from '@global-types/emotion';
2
+
3
+ export interface RowsPerPageDropdownProps extends CommonProps {
4
+ selectedItem?: number;
5
+ rowsPerPageText?: string;
6
+ rowsPerPageList?: Array<{ id: number; value: number }>;
7
+ }
@@ -0,0 +1 @@
1
+ export * from './RowsPerPageDropdown';
@@ -0,0 +1,18 @@
1
+ export const ROWS_PER_PAGE_LIST = [
2
+ {
3
+ id: 1,
4
+ value: 10,
5
+ },
6
+ {
7
+ id: 2,
8
+ value: 25,
9
+ },
10
+ {
11
+ id: 3,
12
+ value: 50,
13
+ },
14
+ ];
15
+
16
+ export const DEFAULT_SELECTED_INDEX = 1;
17
+ export const DEFAULT_PER_PAGE_VALUE =
18
+ ROWS_PER_PAGE_LIST[DEFAULT_SELECTED_INDEX].value;
@@ -1,4 +1,6 @@
1
+ import Input from '@components/Input';
1
2
  import { css, Theme } from '@emotion/react';
3
+ import styled from '@emotion/styled';
2
4
 
3
5
  const baseBtnStyles = (theme: Theme) => css`
4
6
  height: 30px;
@@ -96,3 +98,26 @@ export const arrowBtnStyles = (theme: Theme) => css`
96
98
  cursor: pointer;
97
99
  }
98
100
  `;
101
+
102
+ export const PaginationNav = styled.nav`
103
+ display: flex;
104
+ `;
105
+
106
+ export const PageNumberInput = styled(Input)`
107
+ width: 80px;
108
+ margin-right: 16px;
109
+ -moz-appearance: textfield;
110
+ appearance: textfield;
111
+ &::-webkit-outer-spin-button,
112
+ &::-webkit-inner-spin-button {
113
+ -webkit-appearance: none;
114
+ margin: 0;
115
+ }
116
+ &:focus,
117
+ &:hover {
118
+ border-width: 1px !important;
119
+ }
120
+ & + div {
121
+ right: 24px;
122
+ }
123
+ `;
@@ -1,9 +1,16 @@
1
1
  import { CommonProps } from '@global-types/emotion';
2
+ import { InputProps } from '@components/Input/types';
3
+ import { RowsPerPageDropdownProps } from './components/RowsPerPageDropdown/types';
2
4
 
3
5
  export interface PaginationProps extends CommonProps {
4
6
  pagesCount: number;
5
7
  ariaLabel?: string;
6
8
  isDisabled?: boolean;
9
+ pageNumberPlaceholder?: string;
10
+ isPageSettingVisible?: boolean;
11
+ isRowPerPageVisible?: boolean;
12
+ rowPerPageProps?: RowsPerPageDropdownProps;
13
+ manualPageNumberProps?: InputProps;
7
14
  }
8
15
 
9
16
  export interface PaginationButtonsProps {
@@ -29,10 +36,13 @@ export interface PageButtonProps {
29
36
 
30
37
  export interface PaginationContextProps {
31
38
  page?: number;
39
+ perPage: number;
32
40
  setPage: React.Dispatch<React.SetStateAction<number | undefined>>;
41
+ setPerPage: React.Dispatch<React.SetStateAction<number>>;
33
42
  }
34
43
 
35
44
  export interface PaginationContextProviderProps {
36
45
  selectedPage?: number;
46
+ defaultPerPage?: number;
37
47
  children: React.ReactNode;
38
48
  }