@loadsmart/loadsmart-ui 5.1.1 → 5.3.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 (48) hide show
  1. package/dist/components/Dropdown/Dropdown.types.d.ts +2 -2
  2. package/dist/components/Dropdown/useDropdown.d.ts +1 -5
  3. package/dist/components/Icon/Icon.d.ts +1 -0
  4. package/dist/components/Select/Select.types.d.ts +4 -4
  5. package/dist/components/TablePagination/RowsPerPage.d.ts +4 -0
  6. package/dist/components/TablePagination/TablePagination.d.ts +4 -0
  7. package/dist/components/TablePagination/TablePagination.stories.d.ts +5 -0
  8. package/dist/components/TablePagination/TablePagination.styles.d.ts +5 -0
  9. package/dist/components/TablePagination/TablePagination.test.d.ts +1 -0
  10. package/dist/components/TablePagination/TablePagination.types.d.ts +50 -0
  11. package/dist/components/TablePagination/TablePaginationActions.d.ts +11 -0
  12. package/dist/components/TablePagination/index.d.ts +2 -0
  13. package/dist/hooks/useClickOutside/useClickOutside.d.ts +1 -1
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +445 -437
  16. package/dist/index.js.map +1 -1
  17. package/dist/loadsmart.theme-63c13988.js +2 -0
  18. package/dist/{loadsmart.theme-37a60d56.js.map → loadsmart.theme-63c13988.js.map} +1 -1
  19. package/dist/{prop-06c02f6d.js → prop-0c635ee9.js} +2 -2
  20. package/dist/{prop-06c02f6d.js.map → prop-0c635ee9.js.map} +1 -1
  21. package/dist/testing/index.js +1 -1
  22. package/dist/testing/index.js.map +1 -1
  23. package/dist/theming/index.js +1 -1
  24. package/dist/tools/index.js +1 -1
  25. package/dist/utils/toolset/keyboard.d.ts +4 -0
  26. package/package.json +1 -1
  27. package/src/components/Dropdown/Dropdown.tsx +11 -8
  28. package/src/components/Dropdown/Dropdown.types.ts +2 -2
  29. package/src/components/Dropdown/useDropdown.ts +1 -6
  30. package/src/components/Icon/Icon.tsx +2 -0
  31. package/src/components/Icon/assets/caret-right-last.svg +4 -0
  32. package/src/components/Select/Select.test.tsx +23 -1
  33. package/src/components/Select/Select.types.ts +4 -4
  34. package/src/components/Select/useSelect.ts +11 -9
  35. package/src/components/TablePagination/RowsPerPage.tsx +76 -0
  36. package/src/components/TablePagination/TablePagination.stories.tsx +37 -0
  37. package/src/components/TablePagination/TablePagination.styles.ts +13 -0
  38. package/src/components/TablePagination/TablePagination.test.tsx +112 -0
  39. package/src/components/TablePagination/TablePagination.tsx +51 -0
  40. package/src/components/TablePagination/TablePagination.types.ts +59 -0
  41. package/src/components/TablePagination/TablePaginationActions.tsx +144 -0
  42. package/src/components/TablePagination/index.ts +2 -0
  43. package/src/components/Tag/Tag.tsx +1 -1
  44. package/src/hooks/useClickOutside/useClickOutside.ts +6 -6
  45. package/src/index.ts +3 -0
  46. package/src/testing/SelectEvent/SelectEvent.ts +8 -7
  47. package/src/utils/toolset/keyboard.ts +4 -0
  48. package/dist/loadsmart.theme-37a60d56.js +0 -2
@@ -1,11 +1,6 @@
1
1
  import { useCallback, useEffect, useState } from 'react'
2
2
 
3
- import type { HTMLAttributes } from 'react'
4
-
5
- export interface DropdownProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'onBlur'> {
6
- disabled?: boolean
7
- onBlur?: () => void
8
- }
3
+ import type { DropdownProps } from './Dropdown.types'
9
4
 
10
5
  export interface useDropdownProps {
11
6
  expanded: boolean
@@ -20,6 +20,7 @@ import SortIcon from './assets/sort.svg'
20
20
  import UploadIcon from './assets/upload.svg'
21
21
  import WarningIcon from './assets/warning.svg'
22
22
  import DotsHorizontalIcon from './assets/dots-horizontal.svg'
23
+ import CaretRightLastIcon from './assets/caret-right-last.svg'
23
24
 
24
25
  import type { IconProps as GenericIconProps } from '../IconFactory'
25
26
 
@@ -44,6 +45,7 @@ const icons = {
44
45
  upload: UploadIcon as JSX.Element,
45
46
  warning: WarningIcon as JSX.Element,
46
47
  'dots-horizontal': DotsHorizontalIcon as JSX.Element,
48
+ 'caret-right-last': CaretRightLastIcon as JSX.Element,
47
49
  }
48
50
 
49
51
  const Icon = IconFactory(icons)
@@ -0,0 +1,4 @@
1
+ <svg viewBox="0 0 48 48" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M18.2501 6.38126L14.1842 10.4471L27.7371 24L14.1842 37.5529L18.2501 41.6187L31.803 28.0659L35.8688 24L31.803 19.9341L18.2501 6.38126Z"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M40.2922 38.375L44.1256 38.375L44.1256 9.625L40.2922 9.625L40.2922 38.375Z"/>
4
+ </svg>
@@ -11,6 +11,7 @@ import selectEvent from '../../testing/SelectEvent'
11
11
 
12
12
  import type { SelectProps, Option, GenericOption } from './Select.types'
13
13
  import Select from './Select'
14
+ import userEvent from '@testing-library/user-event'
14
15
 
15
16
  const {
16
17
  Playground,
@@ -159,6 +160,27 @@ describe('Select', () => {
159
160
  expect(items.length).toBeGreaterThanOrEqual(1)
160
161
  expect(items[0]).toHaveTextContent(FRUITS[indexToSearch].label)
161
162
  })
163
+
164
+ it('calls blur when focus is lost', async () => {
165
+ const onBlur = jest.fn((event?: MouseEvent | TouchEvent | KeyboardEvent) => event)
166
+
167
+ setup({ onBlur })
168
+
169
+ const selectInput = screen.getByLabelText('Select your favorite fruit')
170
+
171
+ await selectEvent.expand(selectInput)
172
+
173
+ userEvent.click(document.body)
174
+
175
+ await selectEvent.expand(selectInput)
176
+
177
+ userEvent.keyboard('{Escape}{/Escape}')
178
+
179
+ expect(onBlur).toHaveBeenCalledTimes(2)
180
+
181
+ expect(onBlur).toHaveBeenNthCalledWith(1, new MouseEvent('mousedown'))
182
+ expect(onBlur).toHaveBeenNthCalledWith(2, new KeyboardEvent('keyup'))
183
+ })
162
184
  })
163
185
 
164
186
  describe('Single selection', () => {
@@ -839,7 +861,7 @@ describe('Select', () => {
839
861
  const setup = (overrides: Partial<SelectProps>) =>
840
862
  renderer(<Playground {...overrides} options={SelectableKeyTypeOptions} />).render()
841
863
 
842
- const expectedValue = (value: any) => ({
864
+ const expectedValue = (value: unknown) => ({
843
865
  target: {
844
866
  id: 'select-playground',
845
867
  name: 'select-playground',
@@ -1,16 +1,16 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import EventLike from 'utils/types/EventLike'
3
3
 
4
- import type { ChangeEvent, FocusEvent, ComponentType, HTMLAttributes } from 'react'
5
- import type { DropdownProps, DropdownMenuItemProps } from 'components/Dropdown'
4
+ import type { DropdownMenuItemProps, DropdownProps } from 'components/Dropdown'
6
5
  import type { TextFieldProps } from 'components/TextField'
7
6
  import type {
8
- SelectableAdapter,
9
7
  Selectable,
8
+ SelectableAdapter,
10
9
  SelectableKeyType,
11
10
  SelectableState,
12
11
  } from 'hooks/useSelectable'
13
12
  import { useSelectableReturn } from 'hooks/useSelectable/useSelectable.types'
13
+ import type { ChangeEvent, ComponentType, FocusEvent, HTMLAttributes } from 'react'
14
14
 
15
15
  export type Option = Selectable
16
16
  export interface GenericOption {
@@ -119,7 +119,7 @@ export type useSelectReturn = {
119
119
  getDropdownProps: () => {
120
120
  toggle: () => void
121
121
  expanded: boolean
122
- onBlur: () => void
122
+ onBlur: (event?: MouseEvent | TouchEvent | KeyboardEvent) => void
123
123
  }
124
124
  getCreatebleProps: () => CreatableProps
125
125
  isCreatable: () => boolean
@@ -1,18 +1,18 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
3
2
  import { isFunction } from '@loadsmart/utils-function'
4
3
  import { isNil } from '@loadsmart/utils-object'
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
5
5
 
6
- import { GenericAdapter } from './Select.constants'
7
- import { getValue, getDisplayValue, escapeRegExp, getAdapter } from './useSelect.helpers'
8
6
  import { useDropdown } from 'components/Dropdown'
7
+ import { useDidMount } from 'hooks/useDidMount'
9
8
  import { useFocusTrap } from 'hooks/useFocusTrap'
10
9
  import { SelectableKeyType } from 'hooks/useSelectable'
11
- import { useSelectable } from './Select.context'
12
10
  import to from 'utils/toolset/awaitTo'
13
- import toArray from 'utils/toolset/toArray'
14
11
  import { isThenable } from 'utils/toolset/isThenable'
15
- import { useDidMount } from 'hooks/useDidMount'
12
+ import toArray from 'utils/toolset/toArray'
13
+ import { GenericAdapter } from './Select.constants'
14
+ import { useSelectable } from './Select.context'
15
+ import { escapeRegExp, getAdapter, getDisplayValue, getValue } from './useSelect.helpers'
16
16
 
17
17
  import type { ChangeEvent, FocusEvent } from 'react'
18
18
  import type {
@@ -191,7 +191,7 @@ function useOptions<T = any>(props: { datasources: SelectDatasource<T>[]; adapte
191
191
  */
192
192
  function useSelect(props: SelectProps): useSelectReturn {
193
193
  const didMount = useDidMount()
194
- const { multiple, onQueryChange, onChange, onCreate, id, name, disabled = false } = props
194
+ const { multiple, onQueryChange, onChange, onCreate, id, name, disabled = false, onBlur } = props
195
195
  const dropdown = useDropdown(props)
196
196
 
197
197
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -264,17 +264,19 @@ function useSelect(props: SelectProps): useSelectReturn {
264
264
  return {
265
265
  toggle: dropdown.toggle,
266
266
  expanded: dropdown.expanded,
267
- onBlur() {
267
+ onBlur(event?: MouseEvent | TouchEvent | KeyboardEvent) {
268
268
  if (!multiple) {
269
269
  setQuery(getDisplayValue(adapters, selectable.selected, multiple))
270
270
  } else {
271
271
  setQuery('')
272
272
  }
273
273
  options.reset()
274
+
275
+ onBlur?.(event)
274
276
  },
275
277
  }
276
278
  },
277
- [adapters, dropdown.expanded, dropdown.toggle, multiple, options, selectable.selected]
279
+ [adapters, dropdown.expanded, dropdown.toggle, multiple, options, selectable.selected, onBlur]
278
280
  )
279
281
 
280
282
  const getTriggerProps = useCallback(
@@ -0,0 +1,76 @@
1
+ import React from 'react'
2
+ import { Text } from 'components/Text'
3
+ import { Dropdown, DropdownContext } from 'components/Dropdown'
4
+ import { Layout } from 'components/Layout'
5
+ import { Icon } from 'components/Icon'
6
+ import { RowsPerPageProps } from './TablePagination.types'
7
+ import { ButtonProps } from 'components/Button'
8
+ import { NoPaddingButton } from './TablePagination.styles'
9
+
10
+ const TriggerButton = (props: Omit<ButtonProps, 'scale' | 'variant'>) => {
11
+ const { toggle } = React.useContext(DropdownContext)
12
+
13
+ return (
14
+ <NoPaddingButton data-testid="rows-per-page-button" onClick={toggle} {...props}>
15
+ <Icon name="caret-down" size={16} color="neutral-darker" />
16
+ </NoPaddingButton>
17
+ )
18
+ }
19
+
20
+ function RowsPerPage({
21
+ page,
22
+ rowsPerPage,
23
+ onRowsPerPageChange,
24
+ labelRowsPerPage,
25
+ count,
26
+ rowsPerPageOptions,
27
+ disabled = false,
28
+ }: RowsPerPageProps): JSX.Element {
29
+ const getItemsRange = () => {
30
+ if (!count) {
31
+ return 0
32
+ }
33
+
34
+ const from = page * rowsPerPage + 1
35
+ let to = (page + 1) * rowsPerPage
36
+
37
+ if (to > count) {
38
+ to = count
39
+ }
40
+
41
+ return `${from}-${to}`
42
+ }
43
+
44
+ return (
45
+ <Layout.Group space="s" align="center">
46
+ <Text variant="caption" color={disabled ? 'color-neutral' : 'color-neutral-dark'}>
47
+ {labelRowsPerPage}
48
+ </Text>
49
+ <Text variant="body" color={disabled ? 'color-neutral' : 'color-neutral-dark'}>
50
+ <Text variant="body-bold" color={disabled ? 'color-neutral' : 'color-neutral-dark'}>
51
+ {getItemsRange()}
52
+ </Text>{' '}
53
+ of{' '}
54
+ <Text variant="body-bold" color={disabled ? 'color-neutral' : 'color-neutral-dark'}>
55
+ {count}
56
+ </Text>
57
+ </Text>
58
+ <Dropdown>
59
+ <TriggerButton disabled={disabled} />
60
+ <Dropdown.Menu>
61
+ {rowsPerPageOptions.map((option) => (
62
+ <Dropdown.Item
63
+ key={option}
64
+ onClick={() => onRowsPerPageChange(option)}
65
+ trailing={option === rowsPerPage && <Icon name="check" size={20} color="accent" />}
66
+ >
67
+ {option} per page
68
+ </Dropdown.Item>
69
+ ))}
70
+ </Dropdown.Menu>
71
+ </Dropdown>
72
+ </Layout.Group>
73
+ )
74
+ }
75
+
76
+ export default RowsPerPage
@@ -0,0 +1,37 @@
1
+ import React, { useState } from 'react'
2
+ import TablePagination from './TablePagination'
3
+ import { Meta, Story } from '@storybook/react/types-6-0'
4
+
5
+ import type { TablePaginationProps } from './TablePagination.types'
6
+ import { Layout } from 'components/Layout'
7
+
8
+ export default {
9
+ title: 'Components/TablePagination',
10
+ component: TablePagination,
11
+ argTypes: {},
12
+ } as Meta
13
+
14
+ export const Playground: Story<TablePaginationProps> = (args: TablePaginationProps) => {
15
+ const [page, setPage] = useState(args.page)
16
+ const [rowsPerPage, setRowsPerPage] = useState(args.rowsPerPage)
17
+
18
+ return (
19
+ <Layout.Group>
20
+ <TablePagination
21
+ {...args}
22
+ onPageChange={(page) => setPage(page)}
23
+ page={page}
24
+ rowsPerPage={rowsPerPage}
25
+ onRowsPerPageChange={setRowsPerPage}
26
+ />
27
+ </Layout.Group>
28
+ )
29
+ }
30
+
31
+ Playground.args = {
32
+ page: 0,
33
+ count: 1004,
34
+ rowsPerPage: 100,
35
+ disabled: false,
36
+ variant: 'default',
37
+ }
@@ -0,0 +1,13 @@
1
+ import styled from 'styled-components'
2
+ import { Button } from 'components/Button'
3
+
4
+ export const NoPaddingButton = styled(Button).attrs({
5
+ variant: 'tertiary',
6
+ scale: 'small',
7
+ })`
8
+ padding: 0;
9
+
10
+ > span {
11
+ margin: 0 !important;
12
+ }
13
+ `
@@ -0,0 +1,112 @@
1
+ import userEvent from '@testing-library/user-event'
2
+ import React from 'react'
3
+
4
+ import renderer, { screen } from '../../../tests/renderer'
5
+
6
+ import TablePagination from './TablePagination'
7
+ import { TablePaginationProps } from './TablePagination.types'
8
+
9
+ describe('TablePagination', () => {
10
+ const setup = ({ ...overrides }: Partial<TablePaginationProps> = {}) => {
11
+ const defaultProps: TablePaginationProps = {
12
+ variant: 'default',
13
+ count: 1000,
14
+ onPageChange: jest.fn(),
15
+ onRowsPerPageChange: jest.fn(),
16
+ rowsPerPage: 100,
17
+ page: 0,
18
+ }
19
+
20
+ renderer(<TablePagination {...defaultProps} {...overrides} />).render()
21
+ }
22
+
23
+ it('disables preview page buttons if page = 0', () => {
24
+ setup()
25
+
26
+ expect(screen.getByTitle(/previous page/i)).toBeDisabled()
27
+ expect(screen.getByTitle(/first page/i)).toBeDisabled()
28
+ })
29
+
30
+ it('disables next and last page buttons if the page is the last one', () => {
31
+ setup({
32
+ page: 9,
33
+ })
34
+
35
+ expect(screen.getByTitle(/next page/i)).toBeDisabled()
36
+ expect(screen.getByTitle(/last page/i)).toBeDisabled()
37
+ })
38
+
39
+ it('calls the onChangePage action after the pagination actions are clicked', () => {
40
+ const onPageChange = jest.fn()
41
+ setup({
42
+ page: 2,
43
+ onPageChange,
44
+ })
45
+
46
+ userEvent.click(screen.getByTitle(/previous page/i))
47
+ expect(onPageChange).toHaveBeenLastCalledWith(1)
48
+ userEvent.click(screen.getByTitle(/first page/i))
49
+ expect(onPageChange).toHaveBeenLastCalledWith(0)
50
+ userEvent.click(screen.getByTitle(/next page/i))
51
+ expect(onPageChange).toHaveBeenLastCalledWith(3)
52
+ userEvent.click(screen.getByTitle(/last page/i))
53
+ expect(onPageChange).toHaveBeenLastCalledWith(9)
54
+
55
+ userEvent.clear(screen.getByTitle('Page'))
56
+ userEvent.type(screen.getByTitle('Page'), '5')
57
+ userEvent.tab()
58
+ expect(onPageChange).toHaveBeenLastCalledWith(4)
59
+ })
60
+
61
+ it('calls the onRowsPerPageChange with the selected option', () => {
62
+ const onPageChange = jest.fn()
63
+ const onRowsPerPageChange = jest.fn()
64
+ setup({
65
+ page: 2,
66
+ onPageChange,
67
+ onRowsPerPageChange,
68
+ })
69
+ userEvent.click(screen.getByTestId('rows-per-page-button'))
70
+ userEvent.click(screen.getByText(/50 per page/i))
71
+
72
+ expect(onRowsPerPageChange).toHaveBeenLastCalledWith(50)
73
+ expect(onPageChange).toHaveBeenLastCalledWith(0)
74
+ })
75
+
76
+ it('hides some actions for the compact variant', () => {
77
+ setup({
78
+ variant: 'compact',
79
+ })
80
+
81
+ expect(screen.queryByTitle(/first page/i)).not.toBeInTheDocument()
82
+ expect(screen.queryByTitle(/last page/i)).not.toBeInTheDocument()
83
+ expect(screen.queryByTitle('Page')).not.toBeInTheDocument()
84
+ })
85
+
86
+ it('disables all the buttons when the component is disabled', () => {
87
+ setup({
88
+ disabled: true,
89
+ })
90
+
91
+ expect(screen.getByTitle('Page')).toBeDisabled()
92
+ expect(screen.getByTitle(/previous page/i)).toBeDisabled()
93
+ expect(screen.getByTitle(/first page/i)).toBeDisabled()
94
+ expect(screen.getByTitle(/next page/i)).toBeDisabled()
95
+ expect(screen.getByTitle(/last page/i)).toBeDisabled()
96
+ expect(screen.getByTestId('rows-per-page-button')).toBeDisabled()
97
+ })
98
+
99
+ it('displays no pages state when no items available', () => {
100
+ setup({
101
+ count: 0,
102
+ })
103
+
104
+ expect(screen.getByTitle('Page')).toHaveValue(0)
105
+ expect(screen.getByTitle('Page')).toBeDisabled()
106
+ expect(screen.getByTitle(/previous page/i)).toBeDisabled()
107
+ expect(screen.getByTitle(/first page/i)).toBeDisabled()
108
+ expect(screen.getByTitle(/next page/i)).toBeDisabled()
109
+ expect(screen.getByTitle(/last page/i)).toBeDisabled()
110
+ expect(screen.getByTestId('rows-per-page-button')).toBeDisabled()
111
+ })
112
+ })
@@ -0,0 +1,51 @@
1
+ import React from 'react'
2
+
3
+ import { Layout } from 'components/Layout'
4
+
5
+ import type { TablePaginationProps } from './TablePagination.types'
6
+ import TablePaginationActions from 'components/TablePagination/TablePaginationActions'
7
+ import RowsPerPage from 'components/TablePagination/RowsPerPage'
8
+
9
+ function TablePagination(props: TablePaginationProps): JSX.Element {
10
+ const {
11
+ variant = 'default',
12
+ count,
13
+ labelRowsPerPage = 'Rows per page:',
14
+ onPageChange,
15
+ onRowsPerPageChange,
16
+ page,
17
+ rowsPerPage = 50,
18
+ rowsPerPageOptions = [10, 25, 50, 100],
19
+ disabled = false,
20
+ ...rest
21
+ } = props
22
+
23
+ const handleRowsPerPageChange = (selectedOption: number) => {
24
+ onRowsPerPageChange(selectedOption)
25
+ onPageChange(0)
26
+ }
27
+
28
+ return (
29
+ <Layout.Group space="xl" align="center" justify="space-between" {...rest}>
30
+ <RowsPerPage
31
+ page={page}
32
+ count={count}
33
+ onRowsPerPageChange={handleRowsPerPageChange}
34
+ rowsPerPage={rowsPerPage}
35
+ rowsPerPageOptions={rowsPerPageOptions}
36
+ labelRowsPerPage={labelRowsPerPage}
37
+ disabled={disabled || !count}
38
+ />
39
+ <TablePaginationActions
40
+ variant={variant}
41
+ page={page}
42
+ onPageChange={onPageChange}
43
+ rowsPerPage={rowsPerPage}
44
+ count={count}
45
+ disabled={disabled || !count}
46
+ />
47
+ </Layout.Group>
48
+ )
49
+ }
50
+
51
+ export default TablePagination
@@ -0,0 +1,59 @@
1
+ import { GroupProps } from 'components/Layout/Group'
2
+
3
+ export interface TablePaginationProps extends GroupProps {
4
+ /**
5
+ * The pagination variant
6
+ * @default 'default'
7
+ */
8
+ variant?: 'compact' | 'default'
9
+ /**
10
+ * The total number of paginated items
11
+ */
12
+ count: number
13
+ /**
14
+ * Customize the rows per page label.
15
+ * @default 'Rows per page:'
16
+ */
17
+ labelRowsPerPage?: string
18
+ /**
19
+ * Callback fired when the page is changed.
20
+ */
21
+ onPageChange: (page: number) => void
22
+ /**
23
+ * Callback fired when the number of rows per page is changed.
24
+ */
25
+ onRowsPerPageChange: (rowsPerPage: number) => void
26
+ /**
27
+ * The number of rows per page.
28
+ * @default 50
29
+ */
30
+ rowsPerPage?: number
31
+ /**
32
+ * The zero-based index of the current page.
33
+ */
34
+ page: number
35
+ /**
36
+ * Customizes the options of the rows per page select field.
37
+ * @default [10, 25, 50, 100]
38
+ */
39
+ rowsPerPageOptions?: number[]
40
+ /**
41
+ * Disable all the pagination actions
42
+ */
43
+ disabled?: boolean
44
+ }
45
+
46
+ export type TablePaginationActionsProps = Omit<
47
+ TablePaginationProps,
48
+ 'labelRowsPerPage' | 'onRowsPerPageChange' | 'rowsPerPageOptions' | 'rowsPerPage'
49
+ > & {
50
+ rowsPerPage: number
51
+ }
52
+
53
+ export type RowsPerPageProps = Omit<
54
+ TablePaginationProps,
55
+ 'rowsPerPageOptions' | 'onPageChange' | 'rowsPerPage'
56
+ > & {
57
+ rowsPerPageOptions: number[]
58
+ rowsPerPage: number
59
+ }
@@ -0,0 +1,144 @@
1
+ import React, { ChangeEvent, KeyboardEvent, useEffect, useState } from 'react'
2
+
3
+ import { Layout } from 'components/Layout'
4
+ import { Text } from 'components/Text'
5
+ import { TextField } from 'components/TextField'
6
+ import { TablePaginationActionsProps } from 'components/TablePagination/TablePagination.types'
7
+ import styled from 'styled-components'
8
+ import { Icon, IconProps } from 'components/Icon'
9
+ import Keyboard from 'utils/toolset/keyboard'
10
+ import { NoPaddingButton } from './TablePagination.styles'
11
+ import { prop } from 'tools/index'
12
+
13
+ export const ActionIcon = styled(Icon).attrs({
14
+ color: 'neutral-darker',
15
+ size: '16',
16
+ })<IconProps & { rotate?: number }>`
17
+ transform: rotate(${prop('rotate', 0)}deg);
18
+ `
19
+
20
+ function TablePaginationActions({
21
+ variant = 'default',
22
+ disabled = false,
23
+ onPageChange,
24
+ page,
25
+ count,
26
+ rowsPerPage,
27
+ }: TablePaginationActionsProps): JSX.Element {
28
+ const totalPages = Math.ceil(count / rowsPerPage)
29
+ const [pageValue, setPageValue] = useState<number | ''>(page + 1)
30
+ const isCompact = variant === 'compact'
31
+
32
+ useEffect(() => {
33
+ setPageValue(page + 1)
34
+ }, [page])
35
+
36
+ const handleFirstPageClick = () => {
37
+ onPageChange(0)
38
+ }
39
+
40
+ const handlePreviousPageClick = () => {
41
+ onPageChange(page - 1)
42
+ }
43
+
44
+ const handleNextPageClick = () => {
45
+ onPageChange(page + 1)
46
+ }
47
+
48
+ const handleLastPageClick = () => {
49
+ onPageChange(totalPages - 1)
50
+ }
51
+
52
+ const publishPageChange = () => {
53
+ if (pageValue && pageValue - 1 !== page) {
54
+ onPageChange(pageValue - 1)
55
+ }
56
+ }
57
+
58
+ const handleKeyUp = (e: KeyboardEvent) => {
59
+ if (Keyboard(e).is('ENTER')) {
60
+ publishPageChange()
61
+ }
62
+ }
63
+
64
+ const handleKeyDown = (e: KeyboardEvent) => {
65
+ if (Keyboard(e).is(['E_LOWERCASE', 'DOT', 'MINUS', 'PLUS'])) {
66
+ e.preventDefault()
67
+ }
68
+ }
69
+
70
+ const handlePageChange = (e: ChangeEvent<HTMLInputElement>) => {
71
+ if (e.target.value === '') {
72
+ setPageValue('')
73
+ return
74
+ }
75
+
76
+ const numberValue = Number(e.target.value)
77
+
78
+ if (!numberValue || numberValue < 1 || numberValue > totalPages) return
79
+
80
+ setPageValue(numberValue)
81
+ }
82
+
83
+ return (
84
+ <Layout.Group space="s" align="center">
85
+ {!isCompact && (
86
+ <NoPaddingButton
87
+ onClick={handleFirstPageClick}
88
+ disabled={page === 0 || disabled}
89
+ title="First page"
90
+ >
91
+ <ActionIcon name="caret-right-last" rotate={180} />
92
+ </NoPaddingButton>
93
+ )}
94
+ <NoPaddingButton
95
+ onClick={handlePreviousPageClick}
96
+ disabled={page === 0 || disabled}
97
+ title="Previous page"
98
+ >
99
+ <ActionIcon name="caret-left" />
100
+ </NoPaddingButton>
101
+ {!isCompact && (
102
+ <>
103
+ <TextField
104
+ type="number"
105
+ min={1}
106
+ max={totalPages}
107
+ disabled={disabled || totalPages === 1}
108
+ onChange={handlePageChange}
109
+ onBlur={publishPageChange}
110
+ onKeyUp={handleKeyUp}
111
+ onKeyDown={handleKeyDown}
112
+ scale="small"
113
+ value={count ? pageValue : 0}
114
+ title="Page"
115
+ />
116
+ <Text variant="body" color={disabled ? 'color-neutral' : 'color-neutral-dark'}>
117
+ of{' '}
118
+ <Text variant="body-bold" color={disabled ? 'color-neutral' : 'color-neutral-dark'}>
119
+ {totalPages}
120
+ </Text>
121
+ </Text>
122
+ </>
123
+ )}
124
+ <NoPaddingButton
125
+ onClick={handleNextPageClick}
126
+ disabled={page >= totalPages - 1 || disabled}
127
+ title="Next page"
128
+ >
129
+ <ActionIcon name="caret-right" />
130
+ </NoPaddingButton>
131
+ {!isCompact && (
132
+ <NoPaddingButton
133
+ onClick={handleLastPageClick}
134
+ disabled={page >= totalPages - 1 || disabled}
135
+ title="Last page"
136
+ >
137
+ <ActionIcon name="caret-right-last" />
138
+ </NoPaddingButton>
139
+ )}
140
+ </Layout.Group>
141
+ )
142
+ }
143
+
144
+ export default TablePaginationActions
@@ -0,0 +1,2 @@
1
+ export { default as TablePagination } from './TablePagination'
2
+ export type { TablePaginationProps } from './TablePagination.types'
@@ -338,7 +338,7 @@ function Tag(props: TagProps): JSX.Element {
338
338
  const {
339
339
  children,
340
340
  leading,
341
- size,
341
+ size = 'default',
342
342
  onRemove,
343
343
  variant,
344
344
  removable,