@loadsmart/loadsmart-ui 5.2.1 → 5.3.2

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/Icon/Icon.d.ts +1 -0
  2. package/dist/components/TablePagination/RowsPerPage.d.ts +4 -0
  3. package/dist/components/TablePagination/TablePagination.d.ts +4 -0
  4. package/dist/components/TablePagination/TablePagination.stories.d.ts +5 -0
  5. package/dist/components/TablePagination/TablePagination.styles.d.ts +5 -0
  6. package/dist/components/TablePagination/TablePagination.test.d.ts +1 -0
  7. package/dist/components/TablePagination/TablePagination.types.d.ts +53 -0
  8. package/dist/components/TablePagination/TablePaginationActions.d.ts +11 -0
  9. package/dist/components/TablePagination/index.d.ts +2 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +444 -438
  12. package/dist/index.js.map +1 -1
  13. package/dist/utils/toolset/keyboard.d.ts +4 -0
  14. package/package.json +1 -1
  15. package/src/components/Card/Card.tsx +0 -5
  16. package/src/components/Icon/Icon.tsx +2 -0
  17. package/src/components/Icon/assets/caret-right-last.svg +4 -0
  18. package/src/components/TablePagination/RowsPerPage.tsx +76 -0
  19. package/src/components/TablePagination/TablePagination.stories.tsx +42 -0
  20. package/src/components/TablePagination/TablePagination.styles.ts +13 -0
  21. package/src/components/TablePagination/TablePagination.test.tsx +111 -0
  22. package/src/components/TablePagination/TablePagination.tsx +46 -0
  23. package/src/components/TablePagination/TablePagination.types.ts +62 -0
  24. package/src/components/TablePagination/TablePaginationActions.tsx +144 -0
  25. package/src/components/TablePagination/index.ts +2 -0
  26. package/src/index.ts +3 -0
  27. package/src/utils/toolset/keyboard.ts +4 -0
@@ -11,6 +11,10 @@ declare const SUPPORTED_KEYS: {
11
11
  SHIFT: string;
12
12
  SPACE: string;
13
13
  TAB: string;
14
+ E_LOWERCASE: string;
15
+ DOT: string;
16
+ PLUS: string;
17
+ MINUS: string;
14
18
  };
15
19
  export declare type SupportedKey = keyof typeof SUPPORTED_KEYS;
16
20
  export declare function getKeyboardKey(e: KeyboardEvent): KeyboardEvent['key'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loadsmart/loadsmart-ui",
3
- "version": "5.2.1",
3
+ "version": "5.3.2",
4
4
  "description": "Miranda UI, a React UI library",
5
5
  "main": "dist",
6
6
  "files": [
@@ -60,11 +60,6 @@ const Container = styled.div<{ flagged: boolean }>`
60
60
  flex: 1;
61
61
  flex-direction: column;
62
62
 
63
- margin-left: ${conditional({
64
- 'space-s': whenProps({ flagged: true }),
65
- 'space-none': whenProps({ flagged: false }),
66
- })};
67
-
68
63
  line-height: ${token('card-font-height')};
69
64
 
70
65
  ${({ flagged }) =>
@@ -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>
@@ -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,42 @@
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
+ const handleRowsPerPageChange = (rowsPerPage: number) => {
19
+ setRowsPerPage(rowsPerPage)
20
+ setPage(0)
21
+ }
22
+
23
+ return (
24
+ <Layout.Group>
25
+ <TablePagination
26
+ {...args}
27
+ onPageChange={(page) => setPage(page)}
28
+ page={page}
29
+ rowsPerPage={rowsPerPage}
30
+ onRowsPerPageChange={handleRowsPerPageChange}
31
+ />
32
+ </Layout.Group>
33
+ )
34
+ }
35
+
36
+ Playground.args = {
37
+ page: 0,
38
+ count: 1004,
39
+ rowsPerPage: 100,
40
+ disabled: false,
41
+ variant: 'default',
42
+ }
@@ -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,111 @@
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
+ })
74
+
75
+ it('hides some actions for the compact variant', () => {
76
+ setup({
77
+ variant: 'compact',
78
+ })
79
+
80
+ expect(screen.queryByTitle(/first page/i)).not.toBeInTheDocument()
81
+ expect(screen.queryByTitle(/last page/i)).not.toBeInTheDocument()
82
+ expect(screen.queryByTitle('Page')).not.toBeInTheDocument()
83
+ })
84
+
85
+ it('disables all the buttons when the component is disabled', () => {
86
+ setup({
87
+ disabled: true,
88
+ })
89
+
90
+ expect(screen.getByTitle('Page')).toBeDisabled()
91
+ expect(screen.getByTitle(/previous page/i)).toBeDisabled()
92
+ expect(screen.getByTitle(/first page/i)).toBeDisabled()
93
+ expect(screen.getByTitle(/next page/i)).toBeDisabled()
94
+ expect(screen.getByTitle(/last page/i)).toBeDisabled()
95
+ expect(screen.getByTestId('rows-per-page-button')).toBeDisabled()
96
+ })
97
+
98
+ it('displays no pages state when no items available', () => {
99
+ setup({
100
+ count: 0,
101
+ })
102
+
103
+ expect(screen.getByTitle('Page')).toHaveValue(0)
104
+ expect(screen.getByTitle('Page')).toBeDisabled()
105
+ expect(screen.getByTitle(/previous page/i)).toBeDisabled()
106
+ expect(screen.getByTitle(/first page/i)).toBeDisabled()
107
+ expect(screen.getByTitle(/next page/i)).toBeDisabled()
108
+ expect(screen.getByTitle(/last page/i)).toBeDisabled()
109
+ expect(screen.getByTestId('rows-per-page-button')).toBeDisabled()
110
+ })
111
+ })
@@ -0,0 +1,46 @@
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
+ return (
24
+ <Layout.Group space="xl" align="center" justify="space-between" {...rest}>
25
+ <RowsPerPage
26
+ page={page}
27
+ count={count}
28
+ onRowsPerPageChange={onRowsPerPageChange}
29
+ rowsPerPage={rowsPerPage}
30
+ rowsPerPageOptions={rowsPerPageOptions}
31
+ labelRowsPerPage={labelRowsPerPage}
32
+ disabled={disabled || !count}
33
+ />
34
+ <TablePaginationActions
35
+ variant={variant}
36
+ page={page}
37
+ onPageChange={onPageChange}
38
+ rowsPerPage={rowsPerPage}
39
+ count={count}
40
+ disabled={disabled || !count}
41
+ />
42
+ </Layout.Group>
43
+ )
44
+ }
45
+
46
+ export default TablePagination
@@ -0,0 +1,62 @@
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
+ * Note: Resetting the page to zero after the number of rows per page is changed isn't part of
26
+ * the component and has to be implemented where it's necessary
27
+ */
28
+ onRowsPerPageChange: (rowsPerPage: number) => void
29
+ /**
30
+ * The number of rows per page.
31
+ * @default 50
32
+ */
33
+ rowsPerPage?: number
34
+ /**
35
+ * The zero-based index of the current page.
36
+ */
37
+ page: number
38
+ /**
39
+ * Customizes the options of the rows per page select field.
40
+ * @default [10, 25, 50, 100]
41
+ */
42
+ rowsPerPageOptions?: number[]
43
+ /**
44
+ * Disable all the pagination actions
45
+ */
46
+ disabled?: boolean
47
+ }
48
+
49
+ export type TablePaginationActionsProps = Omit<
50
+ TablePaginationProps,
51
+ 'labelRowsPerPage' | 'onRowsPerPageChange' | 'rowsPerPageOptions' | 'rowsPerPage'
52
+ > & {
53
+ rowsPerPage: number
54
+ }
55
+
56
+ export type RowsPerPageProps = Omit<
57
+ TablePaginationProps,
58
+ 'rowsPerPageOptions' | 'onPageChange' | 'rowsPerPage'
59
+ > & {
60
+ rowsPerPageOptions: number[]
61
+ rowsPerPage: number
62
+ }
@@ -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'
package/src/index.ts CHANGED
@@ -173,3 +173,6 @@ export type {
173
173
  DragDropFileProviderProps,
174
174
  DragDropFileContextValue,
175
175
  } from './components/DragDropFile/types'
176
+
177
+ export { TablePagination } from './components/TablePagination'
178
+ export type { TablePaginationProps } from './components/TablePagination'
@@ -15,6 +15,10 @@ const SUPPORTED_KEYS = {
15
15
  SHIFT: 'Shift',
16
16
  SPACE: ' ',
17
17
  TAB: 'Tab',
18
+ E_LOWERCASE: 'e',
19
+ DOT: '.',
20
+ PLUS: '+',
21
+ MINUS: '-',
18
22
  }
19
23
 
20
24
  export type SupportedKey = keyof typeof SUPPORTED_KEYS