@hyphen/hyphen-components 2.12.4 → 2.13.1
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/components/Pagination/Pagination.utilities.d.ts +3 -4
- package/dist/components/RangeInput/RangeInput.d.ts +29 -0
- package/dist/components/RangeInput/RangeInput.stories.d.ts +7 -0
- package/dist/css/index.css +1 -0
- package/dist/hyphen-components.cjs.development.js +291 -255
- package/dist/hyphen-components.cjs.development.js.map +1 -1
- package/dist/hyphen-components.cjs.production.min.js +1 -1
- package/dist/hyphen-components.cjs.production.min.js.map +1 -1
- package/dist/hyphen-components.esm.js +291 -256
- package/dist/hyphen-components.esm.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/Pagination/Pagination.test.tsx +5 -2
- package/src/components/Pagination/Pagination.tsx +43 -25
- package/src/components/Pagination/Pagination.utilities.test.ts +15 -30
- package/src/components/Pagination/Pagination.utilities.ts +21 -59
- package/src/components/RangeInput/RangeInput.mdx +25 -0
- package/src/components/RangeInput/RangeInput.module.scss +25 -0
- package/src/components/RangeInput/RangeInput.stories.tsx +43 -0
- package/src/components/RangeInput/RangeInput.test.tsx +36 -0
- package/src/components/RangeInput/RangeInput.tsx +65 -0
- package/src/index.ts +1 -0
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * from './components/Modal/Modal';
|
|
|
30
30
|
export * from './components/Pagination/Pagination';
|
|
31
31
|
export * from './components/Popover/Popover';
|
|
32
32
|
export * from './components/RadioGroup/RadioGroup';
|
|
33
|
+
export * from './components/RangeInput/RangeInput';
|
|
33
34
|
export * from './components/ResponsiveProvider/ResponsiveProvider';
|
|
34
35
|
export * from './components/SelectInput/SelectInput';
|
|
35
36
|
export * from './components/SelectInputInset/SelectInputInset';
|
package/package.json
CHANGED
|
@@ -169,8 +169,11 @@ describe('Pagination', () => {
|
|
|
169
169
|
expect(ellipsisFound.length).toBe(2);
|
|
170
170
|
|
|
171
171
|
const buttonsFound = screen.queryAllByRole('button');
|
|
172
|
-
expect(buttonsFound
|
|
173
|
-
expect(
|
|
172
|
+
expect(buttonsFound.length).toBe(8);
|
|
173
|
+
expect(ellipsisFound[0].previousElementSibling?.textContent).toBe('1');
|
|
174
|
+
expect(ellipsisFound[0].nextElementSibling?.textContent).toBe('4');
|
|
175
|
+
expect(ellipsisFound[1].previousElementSibling?.textContent).toBe('7');
|
|
176
|
+
expect(ellipsisFound[1].nextElementSibling?.textContent).toBe('12');
|
|
174
177
|
});
|
|
175
178
|
});
|
|
176
179
|
|
|
@@ -4,7 +4,6 @@ import { Box } from '../Box/Box';
|
|
|
4
4
|
import { Button } from '../Button/Button';
|
|
5
5
|
import {
|
|
6
6
|
generatePages,
|
|
7
|
-
generatePageRange,
|
|
8
7
|
generatePageTotal,
|
|
9
8
|
generateActiveListRange,
|
|
10
9
|
} from './Pagination.utilities';
|
|
@@ -72,34 +71,43 @@ export const Pagination: FC<PaginationProps> = ({
|
|
|
72
71
|
numberOfPagesDisplayed = 5,
|
|
73
72
|
prevPageText = 'Previous',
|
|
74
73
|
}) => {
|
|
75
|
-
const pageTotal = useMemo(
|
|
76
|
-
()
|
|
77
|
-
|
|
78
|
-
);
|
|
74
|
+
const pageTotal = useMemo(() => {
|
|
75
|
+
if (itemsPerPage <= 0) return 1;
|
|
76
|
+
return generatePageTotal(totalItemsCount, itemsPerPage);
|
|
77
|
+
}, [totalItemsCount, itemsPerPage]);
|
|
79
78
|
|
|
80
|
-
const
|
|
81
|
-
() => generatePageRange(numberOfPagesDisplayed, pageTotal),
|
|
82
|
-
[numberOfPagesDisplayed, pageTotal]
|
|
83
|
-
);
|
|
79
|
+
const validActivePage = Math.max(1, Math.min(activePage, pageTotal));
|
|
84
80
|
|
|
85
81
|
const activeListRange = useMemo(
|
|
86
|
-
() =>
|
|
87
|
-
|
|
82
|
+
() =>
|
|
83
|
+
generateActiveListRange(validActivePage, totalItemsCount, itemsPerPage),
|
|
84
|
+
[validActivePage, totalItemsCount, itemsPerPage]
|
|
88
85
|
);
|
|
89
86
|
|
|
90
87
|
const pages = useMemo(
|
|
91
|
-
() =>
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
() => generatePages(pageTotal, validActivePage, numberOfPagesDisplayed),
|
|
89
|
+
[pageTotal, validActivePage, numberOfPagesDisplayed]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const paginationClassNames = useMemo(
|
|
93
|
+
() => classNames(className),
|
|
94
|
+
[className]
|
|
94
95
|
);
|
|
95
96
|
|
|
97
|
+
const activeListRangeText = useMemo(() => {
|
|
98
|
+
if (totalItemsCount === 0) {
|
|
99
|
+
return 'No items to display';
|
|
100
|
+
}
|
|
101
|
+
return `Showing ${activeListRange.first}-${activeListRange.last} of ${totalItemsCount}`;
|
|
102
|
+
}, [activeListRange, totalItemsCount]);
|
|
103
|
+
|
|
96
104
|
return (
|
|
97
105
|
<Box
|
|
98
106
|
as="nav"
|
|
99
107
|
direction="row"
|
|
100
108
|
alignItems="center"
|
|
101
109
|
justifyContent="space-between"
|
|
102
|
-
className={
|
|
110
|
+
className={paginationClassNames}
|
|
103
111
|
>
|
|
104
112
|
<Box
|
|
105
113
|
direction="row"
|
|
@@ -110,16 +118,15 @@ export const Pagination: FC<PaginationProps> = ({
|
|
|
110
118
|
<Button
|
|
111
119
|
variant="secondary"
|
|
112
120
|
size={isCompact ? 'sm' : 'md'}
|
|
113
|
-
isDisabled={
|
|
114
|
-
onClick={() => onChange(
|
|
121
|
+
isDisabled={validActivePage === 1}
|
|
122
|
+
onClick={() => onChange(validActivePage - 1)}
|
|
115
123
|
>
|
|
116
124
|
{prevPageText}
|
|
117
125
|
</Button>
|
|
118
126
|
{arePagesVisible && (
|
|
119
127
|
<Box direction="row" gap="2xs">
|
|
120
|
-
{pages.map(({ pageNumber, isPage }) => {
|
|
121
|
-
|
|
122
|
-
return (
|
|
128
|
+
{pages.map(({ pageNumber, isPage }, index) => {
|
|
129
|
+
return isPage ? (
|
|
123
130
|
<Button
|
|
124
131
|
key={pageNumber}
|
|
125
132
|
onClick={() => onChange(pageNumber)}
|
|
@@ -130,8 +137,20 @@ export const Pagination: FC<PaginationProps> = ({
|
|
|
130
137
|
}}
|
|
131
138
|
className={className}
|
|
132
139
|
>
|
|
133
|
-
{
|
|
140
|
+
{pageNumber}
|
|
134
141
|
</Button>
|
|
142
|
+
) : (
|
|
143
|
+
<Box
|
|
144
|
+
key={`ellipsis-${index}`}
|
|
145
|
+
style={{
|
|
146
|
+
display: 'flexk',
|
|
147
|
+
minWidth: isCompact ? '33px' : '42px',
|
|
148
|
+
justifyContent: 'space-around',
|
|
149
|
+
alignItems: 'center',
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
...
|
|
153
|
+
</Box>
|
|
135
154
|
);
|
|
136
155
|
})}
|
|
137
156
|
</Box>
|
|
@@ -139,8 +158,8 @@ export const Pagination: FC<PaginationProps> = ({
|
|
|
139
158
|
<Button
|
|
140
159
|
variant="secondary"
|
|
141
160
|
size={isCompact ? 'sm' : 'md'}
|
|
142
|
-
isDisabled={
|
|
143
|
-
onClick={() => onChange(
|
|
161
|
+
isDisabled={validActivePage === pageTotal}
|
|
162
|
+
onClick={() => onChange(validActivePage + 1)}
|
|
144
163
|
>
|
|
145
164
|
{nextPageText}
|
|
146
165
|
</Button>
|
|
@@ -153,8 +172,7 @@ export const Pagination: FC<PaginationProps> = ({
|
|
|
153
172
|
}}
|
|
154
173
|
fontSize={isCompact ? 'sm' : 'md'}
|
|
155
174
|
>
|
|
156
|
-
{isTotalVisible &&
|
|
157
|
-
`Showing ${activeListRange.first}-${activeListRange.last} of ${totalItemsCount}`}
|
|
175
|
+
{isTotalVisible && activeListRangeText}
|
|
158
176
|
</Box>
|
|
159
177
|
</Box>
|
|
160
178
|
);
|
|
@@ -1,27 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generatePages,
|
|
3
|
-
generatePageRange,
|
|
4
3
|
generatePageTotal,
|
|
5
4
|
generateActiveListRange,
|
|
6
5
|
} from './Pagination.utilities';
|
|
7
6
|
|
|
8
|
-
describe('generatePageRange', () => {
|
|
9
|
-
it('returns the number of pages displayed if there are enough total pages', () => {
|
|
10
|
-
const pageRange = generatePageRange(3, 50);
|
|
11
|
-
expect(pageRange).toBe(3);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('returns the page total if it is smaller than the number of pages displayed', () => {
|
|
15
|
-
const pageRange = generatePageRange(3, 2);
|
|
16
|
-
expect(pageRange).toBe(2);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('returns the number of pages displayed if it is the same as total pages', () => {
|
|
20
|
-
const pageRange = generatePageRange(3, 3);
|
|
21
|
-
expect(pageRange).toBe(3);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
7
|
describe('generatePageTotal', () => {
|
|
26
8
|
it('returns correct number of pages for a variety of inputs', () => {
|
|
27
9
|
const pageTotal1 = generatePageTotal(948, 20);
|
|
@@ -51,8 +33,8 @@ describe('generateActiveListRange', () => {
|
|
|
51
33
|
|
|
52
34
|
describe('generatePages', () => {
|
|
53
35
|
it('returns correct pages -- scenario 1', () => {
|
|
54
|
-
const pages = generatePages(
|
|
55
|
-
expect(pages.length).toBe(
|
|
36
|
+
const pages = generatePages(10, 3, 3);
|
|
37
|
+
expect(pages.length).toBe(6);
|
|
56
38
|
|
|
57
39
|
expect(pages[0].isPage).toBe(true);
|
|
58
40
|
expect(pages[0].pageNumber).toBe(1);
|
|
@@ -63,22 +45,25 @@ describe('generatePages', () => {
|
|
|
63
45
|
expect(pages[2].isPage).toBe(true);
|
|
64
46
|
expect(pages[2].pageNumber).toBe(3);
|
|
65
47
|
|
|
66
|
-
expect(pages[3].isPage).toBe(
|
|
67
|
-
expect(pages[3].pageNumber).toBe(
|
|
48
|
+
expect(pages[3].isPage).toBe(true);
|
|
49
|
+
expect(pages[3].pageNumber).toBe(4);
|
|
68
50
|
|
|
69
|
-
expect(pages[4].isPage).toBe(
|
|
70
|
-
expect(pages[4].pageNumber).toBe(
|
|
51
|
+
expect(pages[4].isPage).toBe(false);
|
|
52
|
+
expect(pages[4].pageNumber).toBe(-1);
|
|
53
|
+
|
|
54
|
+
expect(pages[5].isPage).toBe(true);
|
|
55
|
+
expect(pages[5].pageNumber).toBe(10);
|
|
71
56
|
});
|
|
72
57
|
|
|
73
58
|
it('returns correct pages -- scenario 2', () => {
|
|
74
|
-
const pages = generatePages(
|
|
59
|
+
const pages = generatePages(10, 6, 3);
|
|
75
60
|
expect(pages.length).toBe(7);
|
|
76
61
|
|
|
77
62
|
expect(pages[0].isPage).toBe(true);
|
|
78
63
|
expect(pages[0].pageNumber).toBe(1);
|
|
79
64
|
|
|
80
65
|
expect(pages[1].isPage).toBe(false);
|
|
81
|
-
expect(pages[1].pageNumber).toBe(
|
|
66
|
+
expect(pages[1].pageNumber).toBe(-1);
|
|
82
67
|
|
|
83
68
|
expect(pages[2].isPage).toBe(true);
|
|
84
69
|
expect(pages[2].pageNumber).toBe(5);
|
|
@@ -90,21 +75,21 @@ describe('generatePages', () => {
|
|
|
90
75
|
expect(pages[4].pageNumber).toBe(7);
|
|
91
76
|
|
|
92
77
|
expect(pages[5].isPage).toBe(false);
|
|
93
|
-
expect(pages[5].pageNumber).toBe(
|
|
78
|
+
expect(pages[5].pageNumber).toBe(-1);
|
|
94
79
|
|
|
95
80
|
expect(pages[6].isPage).toBe(true);
|
|
96
81
|
expect(pages[6].pageNumber).toBe(10);
|
|
97
82
|
});
|
|
98
83
|
|
|
99
84
|
it('returns correct pages -- scenario 3', () => {
|
|
100
|
-
const pages = generatePages(
|
|
85
|
+
const pages = generatePages(10, 9, 3);
|
|
101
86
|
expect(pages.length).toBe(5);
|
|
102
87
|
|
|
103
88
|
expect(pages[0].isPage).toBe(true);
|
|
104
89
|
expect(pages[0].pageNumber).toBe(1);
|
|
105
90
|
|
|
106
91
|
expect(pages[1].isPage).toBe(false);
|
|
107
|
-
expect(pages[1].pageNumber).toBe(
|
|
92
|
+
expect(pages[1].pageNumber).toBe(-1);
|
|
108
93
|
|
|
109
94
|
expect(pages[2].isPage).toBe(true);
|
|
110
95
|
expect(pages[2].pageNumber).toBe(8);
|
|
@@ -117,7 +102,7 @@ describe('generatePages', () => {
|
|
|
117
102
|
});
|
|
118
103
|
|
|
119
104
|
it('returns the correct pages -- one less page range than total', () => {
|
|
120
|
-
const pages = generatePages(
|
|
105
|
+
const pages = generatePages(3, 1, 2);
|
|
121
106
|
|
|
122
107
|
expect(pages[0].isPage).toBe(true);
|
|
123
108
|
expect(pages[0].pageNumber).toBe(1);
|
|
@@ -4,70 +4,45 @@ export interface Page {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
export const generatePages = (
|
|
7
|
-
pageRange: number,
|
|
8
7
|
pageTotal: number,
|
|
9
8
|
activePage: number,
|
|
10
9
|
numberOfPagesDisplayed: number
|
|
11
10
|
): Page[] => {
|
|
11
|
+
const pageRange = Math.min(pageTotal, Math.max(1, numberOfPagesDisplayed));
|
|
12
12
|
const pages: Page[] = [];
|
|
13
13
|
let startingPage = 1;
|
|
14
14
|
let endingPage = pageRange;
|
|
15
15
|
|
|
16
|
-
if (
|
|
17
|
-
startingPage = 1;
|
|
18
|
-
endingPage =
|
|
19
|
-
} else
|
|
20
|
-
startingPage =
|
|
21
|
-
endingPage = startingPage +
|
|
22
|
-
} else if (
|
|
23
|
-
activePage > numberOfPagesDisplayed &&
|
|
24
|
-
activePage + numberOfPagesDisplayed <= pageTotal
|
|
25
|
-
) {
|
|
26
|
-
startingPage = activePage - Math.floor(numberOfPagesDisplayed / 2);
|
|
27
|
-
endingPage = startingPage + (numberOfPagesDisplayed - 1);
|
|
16
|
+
if (activePage + Math.floor(pageRange / 2) >= pageTotal) {
|
|
17
|
+
startingPage = Math.max(1, pageTotal - pageRange + 1);
|
|
18
|
+
endingPage = pageTotal;
|
|
19
|
+
} else {
|
|
20
|
+
startingPage = Math.max(1, activePage - Math.floor(pageRange / 2));
|
|
21
|
+
endingPage = Math.min(pageTotal, startingPage + pageRange - 1);
|
|
28
22
|
}
|
|
29
23
|
|
|
30
24
|
for (let i = startingPage; i <= endingPage; i += 1) {
|
|
31
25
|
pages.push({ pageNumber: i, isPage: true });
|
|
32
26
|
}
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
: pageTotal - 1;
|
|
39
|
-
|
|
40
|
-
// only add ellipsis if there are more than 0 pages between the final page and the rest of the pages
|
|
41
|
-
if (pageTotal > numberOfPagesDisplayed + 1) {
|
|
42
|
-
pages.push({ pageNumber: secondToLastPage, isPage: false });
|
|
28
|
+
// Handling ellipsis for overflow pages
|
|
29
|
+
if (endingPage < pageTotal) {
|
|
30
|
+
if (endingPage < pageTotal - 1) {
|
|
31
|
+
pages.push({ pageNumber: -1, isPage: false }); // represents ellipsis
|
|
43
32
|
}
|
|
44
|
-
|
|
45
33
|
pages.push({ pageNumber: pageTotal, isPage: true });
|
|
46
34
|
}
|
|
47
35
|
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
pages.unshift(
|
|
55
|
-
{ pageNumber: 1, isPage: true },
|
|
56
|
-
{ pageNumber: threeDotsPage, isPage: false }
|
|
57
|
-
);
|
|
36
|
+
if (startingPage > 1) {
|
|
37
|
+
pages.unshift({ pageNumber: 1, isPage: true });
|
|
38
|
+
if (startingPage > 2) {
|
|
39
|
+
pages.splice(1, 0, { pageNumber: -1, isPage: false }); // represents ellipsis
|
|
40
|
+
}
|
|
58
41
|
}
|
|
59
42
|
|
|
60
|
-
return
|
|
43
|
+
return pages;
|
|
61
44
|
};
|
|
62
45
|
|
|
63
|
-
// Return the true page range in cases
|
|
64
|
-
// where number of pages wanted for display is larger than the actual page total.
|
|
65
|
-
export const generatePageRange = (
|
|
66
|
-
numberOfPagesDisplayed: number,
|
|
67
|
-
pageTotal: number
|
|
68
|
-
): number =>
|
|
69
|
-
numberOfPagesDisplayed > pageTotal ? pageTotal : numberOfPagesDisplayed;
|
|
70
|
-
|
|
71
46
|
export const generatePageTotal = (
|
|
72
47
|
totalItemsCount: number,
|
|
73
48
|
itemsPerPage: number
|
|
@@ -80,22 +55,9 @@ export const generateActiveListRange = (
|
|
|
80
55
|
activePage: number,
|
|
81
56
|
totalItemsCount: number,
|
|
82
57
|
itemsPerPage: number
|
|
83
|
-
): { first
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
const pageTotal = generatePageTotal(totalItemsCount, itemsPerPage);
|
|
87
|
-
|
|
88
|
-
if (activePage === 1) {
|
|
89
|
-
activePageRange.first = 1;
|
|
90
|
-
activePageRange.last =
|
|
91
|
-
totalItemsCount > itemsPerPage ? itemsPerPage : totalItemsCount;
|
|
92
|
-
} else if (activePage < pageTotal) {
|
|
93
|
-
activePageRange.first = activePage * itemsPerPage - (itemsPerPage - 1);
|
|
94
|
-
activePageRange.last = activePage * itemsPerPage;
|
|
95
|
-
} else {
|
|
96
|
-
activePageRange.first = activePage * itemsPerPage - (itemsPerPage - 1);
|
|
97
|
-
activePageRange.last = totalItemsCount;
|
|
98
|
-
}
|
|
58
|
+
): { first: number; last: number } => {
|
|
59
|
+
const first = (activePage - 1) * itemsPerPage + 1;
|
|
60
|
+
const last = Math.min(activePage * itemsPerPage, totalItemsCount);
|
|
99
61
|
|
|
100
|
-
return
|
|
62
|
+
return { first, last };
|
|
101
63
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Canvas, Meta, ArgTypes } from '@storybook/blocks';
|
|
2
|
+
import * as Stories from './RangeInput.stories';
|
|
3
|
+
import {RangeInput} from "./RangeInput";
|
|
4
|
+
|
|
5
|
+
<Meta of={Stories} />
|
|
6
|
+
|
|
7
|
+
# RangeInput
|
|
8
|
+
|
|
9
|
+
Use a RangeInput when a user is required to select a value within a range. It is ideal for this scenario because the range is displayed without having to interact.
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
<ArgTypes of={RangeInput} />
|
|
14
|
+
|
|
15
|
+
## Default
|
|
16
|
+
|
|
17
|
+
All that is required to render a basic version of the RangeInput is the input's unique `id`, a `value`, a `max`, and an `onChange` event handler passed to the `onChange` prop.
|
|
18
|
+
|
|
19
|
+
<Canvas isExpanded of={Stories.Default} />
|
|
20
|
+
|
|
21
|
+
### Disabled
|
|
22
|
+
|
|
23
|
+
Use the `isDisabled` prop to disable the input.
|
|
24
|
+
|
|
25
|
+
<Canvas of={Stories.Disabled} />
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.slider {
|
|
2
|
+
appearance: none;
|
|
3
|
+
-webkit-appearance: none;
|
|
4
|
+
padding: 0 !important;
|
|
5
|
+
background-color: var(--color-base-grey-700);
|
|
6
|
+
border: none;
|
|
7
|
+
border-radius: 2.5rem;
|
|
8
|
+
height: var(--size-spacing-sm);
|
|
9
|
+
overflow: visible;
|
|
10
|
+
}
|
|
11
|
+
.slider::-webkit-slider-thumb {
|
|
12
|
+
-webkit-appearance: none;
|
|
13
|
+
appearance: none;
|
|
14
|
+
height: var(--size-spacing-2xl);
|
|
15
|
+
width: var(--size-spacing-2xl);
|
|
16
|
+
background-color: var(--color-base-white);
|
|
17
|
+
border-radius: var(--size-percentage-50);
|
|
18
|
+
border: 0.125rem solid #0f172a;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.disabled {
|
|
23
|
+
cursor: not-allowed;
|
|
24
|
+
opacity: 0.5;
|
|
25
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import type { Meta } from '@storybook/react';
|
|
3
|
+
import { RangeInput } from './RangeInput';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof RangeInput> = {
|
|
6
|
+
title: 'Components/RangeInput',
|
|
7
|
+
component: RangeInput,
|
|
8
|
+
parameters: {
|
|
9
|
+
controls: { hideNoControlsWarning: true },
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
export const Default = () => {
|
|
16
|
+
const [value, setValue] = useState(50);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<RangeInput
|
|
21
|
+
id="range"
|
|
22
|
+
value={value}
|
|
23
|
+
max={100}
|
|
24
|
+
onChange={(event) => setValue(+event.target.value)}
|
|
25
|
+
/>
|
|
26
|
+
<p>Value: {value}</p>
|
|
27
|
+
</>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Disabled = () => {
|
|
32
|
+
const [value, setValue] = useState(50);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<RangeInput
|
|
36
|
+
id="range-disabled"
|
|
37
|
+
value={value}
|
|
38
|
+
max={100}
|
|
39
|
+
onChange={(event) => setValue(+event.target.value)}
|
|
40
|
+
isDisabled={true}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
|
|
5
|
+
import { InputRangeProps, RangeInput } from './RangeInput';
|
|
6
|
+
|
|
7
|
+
describe('RangeInput', () => {
|
|
8
|
+
const defaultProps: InputRangeProps = {
|
|
9
|
+
id: 'test-range',
|
|
10
|
+
value: 50,
|
|
11
|
+
max: 100,
|
|
12
|
+
onChange: jest.fn(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
test('should render the range input with correct attributes', () => {
|
|
16
|
+
render(<RangeInput {...defaultProps} />);
|
|
17
|
+
const rangeInput = screen.getByRole('slider');
|
|
18
|
+
|
|
19
|
+
expect(rangeInput).toBeInTheDocument();
|
|
20
|
+
expect(rangeInput).toHaveAttribute('id', 'test-range');
|
|
21
|
+
expect(rangeInput).toHaveAttribute('type', 'range');
|
|
22
|
+
expect(rangeInput).toHaveAttribute('min', '0');
|
|
23
|
+
expect(rangeInput).toHaveAttribute('value', '50');
|
|
24
|
+
expect(rangeInput).toHaveAttribute('max', '100');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should update the value when changed', async () => {
|
|
28
|
+
const onChangeMock = jest.fn();
|
|
29
|
+
render(<RangeInput {...defaultProps} onChange={onChangeMock} />);
|
|
30
|
+
const rangeInput = screen.getByRole('slider');
|
|
31
|
+
|
|
32
|
+
await fireEvent.change(rangeInput, { target: { value: '75' } });
|
|
33
|
+
|
|
34
|
+
expect(onChangeMock).toHaveBeenCalledTimes(1);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FC } from 'react';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import styles from './RangeInput.module.scss';
|
|
5
|
+
|
|
6
|
+
export interface InputRangeProps {
|
|
7
|
+
/**
|
|
8
|
+
* The input's id attribute.
|
|
9
|
+
*/
|
|
10
|
+
id: string;
|
|
11
|
+
/**
|
|
12
|
+
* The value of the range.
|
|
13
|
+
*/
|
|
14
|
+
value: number;
|
|
15
|
+
/**
|
|
16
|
+
* The maximum value of the range.
|
|
17
|
+
*/
|
|
18
|
+
max: number;
|
|
19
|
+
/**
|
|
20
|
+
* Callback function to call on change event.
|
|
21
|
+
*/
|
|
22
|
+
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Custom class to be added to standard input classes.
|
|
25
|
+
*/
|
|
26
|
+
className?: string;
|
|
27
|
+
/**
|
|
28
|
+
* If the input should be disabled and not focusable.
|
|
29
|
+
*/
|
|
30
|
+
isDisabled?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const RangeInput: FC<InputRangeProps> = ({
|
|
34
|
+
value = 0,
|
|
35
|
+
max = 0,
|
|
36
|
+
id,
|
|
37
|
+
onChange,
|
|
38
|
+
className,
|
|
39
|
+
isDisabled = false,
|
|
40
|
+
...restProps
|
|
41
|
+
}) => {
|
|
42
|
+
const currentProgress = value > 0 ? (value / max) * 100 : 0;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<input
|
|
46
|
+
{...restProps}
|
|
47
|
+
id={id}
|
|
48
|
+
type="range"
|
|
49
|
+
min="0"
|
|
50
|
+
value={value}
|
|
51
|
+
max={max}
|
|
52
|
+
aria-valuemax={max}
|
|
53
|
+
aria-valuenow={value}
|
|
54
|
+
aria-label="range input"
|
|
55
|
+
className={classNames(styles.slider, className, {
|
|
56
|
+
[styles.disabled]: isDisabled,
|
|
57
|
+
})}
|
|
58
|
+
onChange={onChange}
|
|
59
|
+
disabled={isDisabled}
|
|
60
|
+
style={{
|
|
61
|
+
background: `linear-gradient(to right, var(--color-base-grey-400) ${currentProgress}%, var(--color-base-grey-700) ${currentProgress}%)`,
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * from './components/Modal/Modal';
|
|
|
30
30
|
export * from './components/Pagination/Pagination';
|
|
31
31
|
export * from './components/Popover/Popover';
|
|
32
32
|
export * from './components/RadioGroup/RadioGroup';
|
|
33
|
+
export * from './components/RangeInput/RangeInput';
|
|
33
34
|
export * from './components/ResponsiveProvider/ResponsiveProvider';
|
|
34
35
|
export * from './components/SelectInput/SelectInput';
|
|
35
36
|
export * from './components/SelectInputInset/SelectInputInset';
|