@automattic/vip-design-system 2.16.1 → 2.17.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.
@@ -0,0 +1,425 @@
1
+ function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == typeof h && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(typeof e + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
2
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
3
+ function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
4
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
5
+ /** @jsxImportSource theme-ui */
6
+
7
+ import { render, screen } from '@testing-library/react';
8
+ import userEvent from '@testing-library/user-event';
9
+ import { axe } from 'jest-axe';
10
+ import '@testing-library/jest-dom';
11
+ import { Pagination, getPageNumbers } from './Pagination';
12
+ import { jsx as _jsx } from "theme-ui/jsx-runtime";
13
+ var defaultProps = {
14
+ currentPage: 1,
15
+ totalItems: 200,
16
+ totalPages: 10,
17
+ itemsPerPage: 20,
18
+ onPageChange: jest.fn(),
19
+ onItemsPerPageChange: jest.fn()
20
+ };
21
+ describe('<Pagination />', function () {
22
+ beforeEach(function () {
23
+ jest.clearAllMocks();
24
+ });
25
+ it('renders a nav landmark with aria-label', function () {
26
+ render(_jsx(Pagination, _extends({}, defaultProps)));
27
+ expect(screen.getByRole('navigation', {
28
+ name: 'Pagination'
29
+ })).toBeInTheDocument();
30
+ });
31
+ it('renders prev and next arrow buttons', function () {
32
+ render(_jsx(Pagination, _extends({}, defaultProps)));
33
+ expect(screen.getByRole('button', {
34
+ name: 'Previous page'
35
+ })).toBeInTheDocument();
36
+ expect(screen.getByRole('button', {
37
+ name: 'Next page'
38
+ })).toBeInTheDocument();
39
+ });
40
+ it('disables prev button on first page', function () {
41
+ render(_jsx(Pagination, _extends({}, defaultProps, {
42
+ currentPage: 1
43
+ })));
44
+ expect(screen.getByRole('button', {
45
+ name: 'Previous page'
46
+ })).toBeDisabled();
47
+ });
48
+ it('disables next button on last page', function () {
49
+ render(_jsx(Pagination, _extends({}, defaultProps, {
50
+ currentPage: 10
51
+ })));
52
+ expect(screen.getByRole('button', {
53
+ name: 'Next page'
54
+ })).toBeDisabled();
55
+ });
56
+ it('marks current page with aria-current', function () {
57
+ render(_jsx(Pagination, _extends({}, defaultProps, {
58
+ currentPage: 3
59
+ })));
60
+ expect(screen.getByRole('button', {
61
+ name: 'Go to page 3'
62
+ })).toHaveAttribute('aria-current', 'page');
63
+ });
64
+ it('does not mark non-current pages with aria-current', function () {
65
+ render(_jsx(Pagination, _extends({}, defaultProps, {
66
+ currentPage: 3
67
+ })));
68
+ expect(screen.getByRole('button', {
69
+ name: 'Go to page 1'
70
+ })).not.toHaveAttribute('aria-current');
71
+ });
72
+ it('calls onPageChange when clicking a page number', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
73
+ var user;
74
+ return _regeneratorRuntime().wrap(function _callee$(_context) {
75
+ while (1) switch (_context.prev = _context.next) {
76
+ case 0:
77
+ user = userEvent.setup();
78
+ render(_jsx(Pagination, _extends({}, defaultProps, {
79
+ currentPage: 1
80
+ })));
81
+ _context.next = 4;
82
+ return user.click(screen.getByRole('button', {
83
+ name: 'Go to page 2'
84
+ }));
85
+ case 4:
86
+ expect(defaultProps.onPageChange).toHaveBeenCalledWith(2);
87
+ case 5:
88
+ case "end":
89
+ return _context.stop();
90
+ }
91
+ }, _callee);
92
+ })));
93
+ it('calls onPageChange when clicking next', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2() {
94
+ var user;
95
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
96
+ while (1) switch (_context2.prev = _context2.next) {
97
+ case 0:
98
+ user = userEvent.setup();
99
+ render(_jsx(Pagination, _extends({}, defaultProps, {
100
+ currentPage: 3
101
+ })));
102
+ _context2.next = 4;
103
+ return user.click(screen.getByRole('button', {
104
+ name: 'Next page'
105
+ }));
106
+ case 4:
107
+ expect(defaultProps.onPageChange).toHaveBeenCalledWith(4);
108
+ case 5:
109
+ case "end":
110
+ return _context2.stop();
111
+ }
112
+ }, _callee2);
113
+ })));
114
+ it('calls onPageChange when clicking prev', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3() {
115
+ var user;
116
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
117
+ while (1) switch (_context3.prev = _context3.next) {
118
+ case 0:
119
+ user = userEvent.setup();
120
+ render(_jsx(Pagination, _extends({}, defaultProps, {
121
+ currentPage: 3
122
+ })));
123
+ _context3.next = 4;
124
+ return user.click(screen.getByRole('button', {
125
+ name: 'Previous page'
126
+ }));
127
+ case 4:
128
+ expect(defaultProps.onPageChange).toHaveBeenCalledWith(2);
129
+ case 5:
130
+ case "end":
131
+ return _context3.stop();
132
+ }
133
+ }, _callee3);
134
+ })));
135
+ it('shows ellipsis for large page counts', function () {
136
+ render(_jsx(Pagination, _extends({}, defaultProps, {
137
+ currentPage: 10,
138
+ totalPages: 20
139
+ })));
140
+
141
+ // Intermediate pages are replaced by ellipsis icon
142
+ expect(screen.queryByRole('button', {
143
+ name: 'Go to page 2'
144
+ })).not.toBeInTheDocument();
145
+ expect(screen.queryByRole('button', {
146
+ name: 'Go to page 3'
147
+ })).not.toBeInTheDocument();
148
+ });
149
+ it('does not show ellipsis for small page counts', function () {
150
+ render(_jsx(Pagination, _extends({}, defaultProps, {
151
+ currentPage: 1,
152
+ totalPages: 5
153
+ })));
154
+
155
+ // All pages are rendered when total is small
156
+ expect(screen.getByRole('button', {
157
+ name: 'Go to page 2'
158
+ })).toBeInTheDocument();
159
+ expect(screen.getByRole('button', {
160
+ name: 'Go to page 3'
161
+ })).toBeInTheDocument();
162
+ expect(screen.getByRole('button', {
163
+ name: 'Go to page 4'
164
+ })).toBeInTheDocument();
165
+ expect(screen.getByRole('button', {
166
+ name: 'Go to page 5'
167
+ })).toBeInTheDocument();
168
+ });
169
+ it('renders compact variant with Page text', function () {
170
+ render(_jsx(Pagination, _extends({}, defaultProps, {
171
+ variant: "compact",
172
+ currentPage: 3
173
+ })));
174
+ expect(screen.getByText('Page')).toBeInTheDocument();
175
+ expect(screen.getByText('of 10')).toBeInTheDocument();
176
+ });
177
+ it('renders custom pageSizeOptions in the trigger', function () {
178
+ render(_jsx(Pagination, _extends({}, defaultProps, {
179
+ displayItemsPerPageSelector: true,
180
+ pageSizeOptions: [5, 25, 75],
181
+ itemsPerPage: 5
182
+ })));
183
+ expect(screen.getByRole('combobox')).toBeInTheDocument();
184
+ expect(screen.getAllByRole('option')).toHaveLength(3);
185
+ });
186
+ it('has no accessibility violations (full variant)', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4() {
187
+ var _render, container;
188
+ return _regeneratorRuntime().wrap(function _callee4$(_context4) {
189
+ while (1) switch (_context4.prev = _context4.next) {
190
+ case 0:
191
+ _render = render(_jsx(Pagination, _extends({}, defaultProps, {
192
+ currentPage: 5
193
+ }))), container = _render.container;
194
+ _context4.t0 = expect;
195
+ _context4.next = 4;
196
+ return axe(container);
197
+ case 4:
198
+ _context4.t1 = _context4.sent;
199
+ (0, _context4.t0)(_context4.t1).toHaveNoViolations();
200
+ case 6:
201
+ case "end":
202
+ return _context4.stop();
203
+ }
204
+ }, _callee4);
205
+ })));
206
+ it('has no accessibility violations (compact variant)', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5() {
207
+ var _render2, container;
208
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
209
+ while (1) switch (_context5.prev = _context5.next) {
210
+ case 0:
211
+ _render2 = render(_jsx(Pagination, _extends({}, defaultProps, {
212
+ variant: "compact",
213
+ currentPage: 5
214
+ }))), container = _render2.container;
215
+ _context5.t0 = expect;
216
+ _context5.next = 4;
217
+ return axe(container);
218
+ case 4:
219
+ _context5.t1 = _context5.sent;
220
+ (0, _context5.t0)(_context5.t1).toHaveNoViolations();
221
+ case 6:
222
+ case "end":
223
+ return _context5.stop();
224
+ }
225
+ }, _callee5);
226
+ })));
227
+ });
228
+ describe('getPageNumbers', function () {
229
+ it('returns all pages when totalPages <= 8', function () {
230
+ expect(getPageNumbers(1, 5)).toEqual([1, 2, 3, 4, 5]);
231
+ expect(getPageNumbers(3, 7)).toEqual([1, 2, 3, 4, 5, 6, 7]);
232
+ expect(getPageNumbers(4, 8)).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
233
+ });
234
+ it('returns single page', function () {
235
+ expect(getPageNumbers(1, 1)).toEqual([1]);
236
+ });
237
+ it('always returns 8 items when totalPages > 8', function () {
238
+ for (var cp = 1; cp <= 20; cp++) {
239
+ expect(getPageNumbers(cp, 20)).toHaveLength(8);
240
+ }
241
+ });
242
+ it('shows end ellipsis when current page is near start', function () {
243
+ expect(getPageNumbers(1, 10)).toEqual([1, 2, 3, 4, 5, 6, 'ellipsis', 10]);
244
+ expect(getPageNumbers(3, 10)).toEqual([1, 2, 3, 4, 5, 6, 'ellipsis', 10]);
245
+ expect(getPageNumbers(5, 10)).toEqual([1, 2, 3, 4, 5, 6, 'ellipsis', 10]);
246
+ });
247
+ it('shows start ellipsis when current page is near end', function () {
248
+ expect(getPageNumbers(9, 10)).toEqual([1, 'ellipsis', 5, 6, 7, 8, 9, 10]);
249
+ expect(getPageNumbers(8, 10)).toEqual([1, 'ellipsis', 5, 6, 7, 8, 9, 10]);
250
+ });
251
+ it('shows both ellipsis when current page is in the middle', function () {
252
+ expect(getPageNumbers(10, 20)).toEqual([1, 'ellipsis', 9, 10, 11, 12, 'ellipsis', 20]);
253
+ });
254
+ });
255
+ describe('getPageNumbers (open-ended with maxReachablePage)', function () {
256
+ it('caps pages to maxReachablePage when near start', function () {
257
+ expect(getPageNumbers(1, undefined, true, 2)).toEqual([1, 2]);
258
+ });
259
+ it('shows all reachable pages when they fit', function () {
260
+ expect(getPageNumbers(3, undefined, true, 4)).toEqual([1, 2, 3, 4]);
261
+ });
262
+ it('shows ellipsis for large reachable ranges', function () {
263
+ expect(getPageNumbers(8, undefined, true, 9)).toEqual([1, 'ellipsis', 7, 8, 9]);
264
+ });
265
+ it('shows both ellipsis when end is far from current page', function () {
266
+ expect(getPageNumbers(8, undefined, true, 15)).toEqual([1, 'ellipsis', 7, 8, 9, 10, 'ellipsis', 15]);
267
+ });
268
+ it('returns all pages when maxReachablePage <= 8', function () {
269
+ expect(getPageNumbers(1, undefined, true, 8)).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
270
+ });
271
+ it('does not affect behavior when maxReachablePage is undefined', function () {
272
+ expect(getPageNumbers(1, undefined, true)).toEqual([1, 2, 3, 4, 5, 6, 7, 'ellipsis']);
273
+ });
274
+ });
275
+ describe('getPageNumbers (open-ended)', function () {
276
+ it('always returns 8 items when page >= 6', function () {
277
+ for (var cp = 6; cp <= 20; cp++) {
278
+ expect(getPageNumbers(cp)).toHaveLength(8);
279
+ }
280
+ });
281
+ it('returns near-start pattern for pages 1-5', function () {
282
+ expect(getPageNumbers(1)).toEqual([1, 2, 3, 4, 5, 6, 7, 'ellipsis']);
283
+ expect(getPageNumbers(5)).toEqual([1, 2, 3, 4, 5, 6, 7, 'ellipsis']);
284
+ });
285
+ it('returns middle pattern with trailing ellipsis for higher pages', function () {
286
+ expect(getPageNumbers(10)).toEqual([1, 'ellipsis', 9, 10, 11, 12, 13, 'ellipsis']);
287
+ });
288
+ it('excludes forward pages and trailing ellipsis when hasNextPage is false', function () {
289
+ expect(getPageNumbers(1, undefined, false)).toEqual([1]);
290
+ expect(getPageNumbers(5, undefined, false)).toEqual([1, 2, 3, 4, 5]);
291
+ expect(getPageNumbers(10, undefined, false)).toEqual([1, 'ellipsis', 5, 6, 7, 8, 9, 10]);
292
+ });
293
+ });
294
+ describe('<Pagination /> with maxReachablePage', function () {
295
+ var maxReachableProps = {
296
+ currentPage: 1,
297
+ itemsPerPage: 20,
298
+ hasNextPage: true,
299
+ maxReachablePage: 2,
300
+ onPageChange: jest.fn(),
301
+ onItemsPerPageChange: jest.fn()
302
+ };
303
+ it('only renders reachable page buttons', function () {
304
+ render(_jsx(Pagination, _extends({}, maxReachableProps)));
305
+ expect(screen.getByRole('button', {
306
+ name: 'Go to page 1'
307
+ })).toBeInTheDocument();
308
+ expect(screen.getByRole('button', {
309
+ name: 'Go to page 2'
310
+ })).toBeInTheDocument();
311
+ expect(screen.queryByRole('button', {
312
+ name: 'Go to page 3'
313
+ })).not.toBeInTheDocument();
314
+ expect(screen.queryByRole('button', {
315
+ name: 'Go to page 7'
316
+ })).not.toBeInTheDocument();
317
+ });
318
+ it('has no accessibility violations', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6() {
319
+ var _render3, container;
320
+ return _regeneratorRuntime().wrap(function _callee6$(_context6) {
321
+ while (1) switch (_context6.prev = _context6.next) {
322
+ case 0:
323
+ _render3 = render(_jsx(Pagination, _extends({}, maxReachableProps))), container = _render3.container;
324
+ _context6.t0 = expect;
325
+ _context6.next = 4;
326
+ return axe(container);
327
+ case 4:
328
+ _context6.t1 = _context6.sent;
329
+ (0, _context6.t0)(_context6.t1).toHaveNoViolations();
330
+ case 6:
331
+ case "end":
332
+ return _context6.stop();
333
+ }
334
+ }, _callee6);
335
+ })));
336
+ });
337
+ describe('<Pagination /> open-ended mode', function () {
338
+ var openEndedProps = {
339
+ currentPage: 5,
340
+ itemsPerPage: 20,
341
+ onPageChange: jest.fn(),
342
+ onItemsPerPageChange: jest.fn()
343
+ };
344
+ beforeEach(function () {
345
+ jest.clearAllMocks();
346
+ });
347
+ it('enables "Next" button when totalPages is omitted', function () {
348
+ render(_jsx(Pagination, _extends({}, openEndedProps)));
349
+ expect(screen.getByRole('button', {
350
+ name: 'Next page'
351
+ })).not.toBeDisabled();
352
+ });
353
+ it('disables "Next" button when hasNextPage is false', function () {
354
+ render(_jsx(Pagination, _extends({}, openEndedProps, {
355
+ hasNextPage: false
356
+ })));
357
+ expect(screen.getByRole('button', {
358
+ name: 'Next page'
359
+ })).toBeDisabled();
360
+ });
361
+ it('calls onPageChange when clicking next in open-ended mode', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee7() {
362
+ var user;
363
+ return _regeneratorRuntime().wrap(function _callee7$(_context7) {
364
+ while (1) switch (_context7.prev = _context7.next) {
365
+ case 0:
366
+ user = userEvent.setup();
367
+ render(_jsx(Pagination, _extends({}, openEndedProps)));
368
+ _context7.next = 4;
369
+ return user.click(screen.getByRole('button', {
370
+ name: 'Next page'
371
+ }));
372
+ case 4:
373
+ expect(openEndedProps.onPageChange).toHaveBeenCalledWith(6);
374
+ case 5:
375
+ case "end":
376
+ return _context7.stop();
377
+ }
378
+ }, _callee7);
379
+ })));
380
+ it('renders compact variant with "Page" but without "of Y"', function () {
381
+ render(_jsx(Pagination, _extends({}, openEndedProps, {
382
+ variant: "compact"
383
+ })));
384
+ expect(screen.getByText('Page')).toBeInTheDocument();
385
+ expect(screen.queryByText(/of \d+/)).not.toBeInTheDocument();
386
+ });
387
+ it('has no accessibility violations (open-ended full)', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee8() {
388
+ var _render4, container;
389
+ return _regeneratorRuntime().wrap(function _callee8$(_context8) {
390
+ while (1) switch (_context8.prev = _context8.next) {
391
+ case 0:
392
+ _render4 = render(_jsx(Pagination, _extends({}, openEndedProps))), container = _render4.container;
393
+ _context8.t0 = expect;
394
+ _context8.next = 4;
395
+ return axe(container);
396
+ case 4:
397
+ _context8.t1 = _context8.sent;
398
+ (0, _context8.t0)(_context8.t1).toHaveNoViolations();
399
+ case 6:
400
+ case "end":
401
+ return _context8.stop();
402
+ }
403
+ }, _callee8);
404
+ })));
405
+ it('has no accessibility violations (open-ended compact)', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee9() {
406
+ var _render5, container;
407
+ return _regeneratorRuntime().wrap(function _callee9$(_context9) {
408
+ while (1) switch (_context9.prev = _context9.next) {
409
+ case 0:
410
+ _render5 = render(_jsx(Pagination, _extends({}, openEndedProps, {
411
+ variant: "compact"
412
+ }))), container = _render5.container;
413
+ _context9.t0 = expect;
414
+ _context9.next = 4;
415
+ return axe(container);
416
+ case 4:
417
+ _context9.t1 = _context9.sent;
418
+ (0, _context9.t0)(_context9.t1).toHaveNoViolations();
419
+ case 6:
420
+ case "end":
421
+ return _context9.stop();
422
+ }
423
+ }, _callee9);
424
+ })));
425
+ });
@@ -0,0 +1,2 @@
1
+ export { Pagination, getPageNumbers } from './Pagination';
2
+ export type { PaginationProps, PaginationVariant, PageNumberItem } from './Pagination';
@@ -0,0 +1 @@
1
+ export { Pagination, getPageNumbers } from './Pagination';
@@ -0,0 +1,9 @@
1
+ import { ThemeUIStyleObject } from 'theme-ui';
2
+ export declare const containerStyles: ThemeUIStyleObject;
3
+ export declare const navigationStyles: ThemeUIStyleObject;
4
+ export declare const pageButtonStyles: ThemeUIStyleObject;
5
+ export declare const activePageButtonStyles: ThemeUIStyleObject;
6
+ export declare const arrowButtonStyles: ThemeUIStyleObject;
7
+ export declare const ellipsisStyles: ThemeUIStyleObject;
8
+ export declare const compactTextStyles: ThemeUIStyleObject;
9
+ export declare const compactTriggerStyles: ThemeUIStyleObject;
@@ -0,0 +1,96 @@
1
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+ export var containerStyles = {
3
+ display: 'flex',
4
+ alignItems: 'center',
5
+ justifyContent: 'space-between',
6
+ flexFlow: 'row wrap',
7
+ gap: 3
8
+ };
9
+ export var navigationStyles = {
10
+ display: 'flex',
11
+ alignItems: 'center',
12
+ gap: 1,
13
+ justifySelf: 'flex-end'
14
+ };
15
+ export var pageButtonStyles = {
16
+ minWidth: '32px',
17
+ height: '32px',
18
+ display: 'inline-flex',
19
+ alignItems: 'center',
20
+ justifyContent: 'center',
21
+ borderRadius: 1,
22
+ border: 'none',
23
+ bg: 'transparent',
24
+ color: 'heading',
25
+ cursor: 'pointer',
26
+ fontSize: 2,
27
+ fontFamily: 'body',
28
+ px: 2,
29
+ borderBottom: '3px solid',
30
+ borderColor: 'transparent',
31
+ '&:hover': {
32
+ bg: 'button.tertiary.background.hover'
33
+ },
34
+ '&:focus-visible': {
35
+ outline: '2px solid',
36
+ outlineColor: 'primary',
37
+ outlineOffset: '-2px'
38
+ }
39
+ };
40
+ export var activePageButtonStyles = _extends({}, pageButtonStyles, {
41
+ borderColor: 'button.display.background.default',
42
+ borderRadius: 0,
43
+ fontWeight: 'bold',
44
+ '&:hover': {
45
+ bg: 'button.tertiary.background.hover'
46
+ }
47
+ });
48
+ export var arrowButtonStyles = _extends({}, pageButtonStyles, {
49
+ '&:disabled, &[aria-disabled="true"]': {
50
+ bg: 'button.tertiary.background.disabled',
51
+ cursor: 'not-allowed',
52
+ opacity: 0.4,
53
+ pointerEvents: 'none'
54
+ }
55
+ });
56
+ export var ellipsisStyles = {
57
+ minWidth: '32px',
58
+ height: '32px',
59
+ display: 'inline-flex',
60
+ alignItems: 'center',
61
+ justifyContent: 'center',
62
+ color: 'muted',
63
+ fontSize: 2
64
+ };
65
+ export var compactTextStyles = {
66
+ display: 'inline-flex',
67
+ alignItems: 'center',
68
+ gap: 1,
69
+ fontSize: 2,
70
+ color: 'heading',
71
+ whiteSpace: 'nowrap'
72
+ };
73
+ export var compactTriggerStyles = {
74
+ display: 'inline-flex',
75
+ alignItems: 'center',
76
+ gap: 1,
77
+ border: '1px solid',
78
+ borderColor: 'button.tertiary.border.default',
79
+ bg: 'transparent',
80
+ color: 'heading',
81
+ cursor: 'pointer',
82
+ fontSize: 2,
83
+ fontFamily: 'body',
84
+ fontWeight: 'bold',
85
+ px: 2,
86
+ borderRadius: 1,
87
+ '&:hover': {
88
+ bg: 'button.tertiary.background.hover',
89
+ borderColor: 'button.tertiary.background.hover'
90
+ },
91
+ '&:focus-visible': {
92
+ outline: '2px solid',
93
+ outlineColor: 'primary',
94
+ outlineOffset: '-2px'
95
+ }
96
+ };
@@ -32,6 +32,7 @@ import { FilterDropdown } from './FilterDropdown/FilterDropdown';
32
32
  import { Flex } from './Flex';
33
33
  import { Notice } from './Notice';
34
34
  import { OptionRow } from './OptionRow';
35
+ import { Pagination } from './Pagination';
35
36
  import { Heading } from './Heading';
36
37
  import { Hr } from './Hr/Hr';
37
38
  import { Input } from './Form';
@@ -65,4 +66,4 @@ import { Validation } from './Form';
65
66
  import { Wizard } from './Wizard';
66
67
  import { WizardStep } from './Wizard';
67
68
  import theme from './theme';
68
- export { Accordion, Avatar, Badge, Box, Breadcrumbs, Button, ButtonSubmit, ButtonVariant, Card, Checkbox, Code, Dialog, NewDialog, Form, Drawer, Dropdown, DialogButton, DialogMenu, DialogMenuItem, DialogDivider, DialogContent, DialogTrigger, DescriptionList, ConfirmationDialog, MobileMenu, MobileMenuTrigger, MobileMenuWrapper, NewConfirmationDialog, NewTooltip, Grid, FilterDropdown, Flex, Notice, OptionRow, Heading, Hr, Input, Label, ScreenReaderText, Spinner, Table, TableRow, TableCell, Tooltip, Link, LinkExternal, Radio, RadioBoxGroup, RadioGroupChip, Textarea, Progress, Text, Tabs, Nav, NavItem, Skeleton, TabsTrigger, TabsContent, TabsList, Toggle, ToggleRow, Toolbar, Snackbar, Validation, Wizard, WizardStep, theme };
69
+ export { Accordion, Avatar, Badge, Box, Breadcrumbs, Button, ButtonSubmit, ButtonVariant, Card, Checkbox, Code, Dialog, NewDialog, Form, Drawer, Dropdown, DialogButton, DialogMenu, DialogMenuItem, DialogDivider, DialogContent, DialogTrigger, DescriptionList, ConfirmationDialog, MobileMenu, MobileMenuTrigger, MobileMenuWrapper, NewConfirmationDialog, NewTooltip, Grid, FilterDropdown, Flex, Notice, OptionRow, Pagination, Heading, Hr, Input, Label, ScreenReaderText, Spinner, Table, TableRow, TableCell, Tooltip, Link, LinkExternal, Radio, RadioBoxGroup, RadioGroupChip, Textarea, Progress, Text, Tabs, Nav, NavItem, Skeleton, TabsTrigger, TabsContent, TabsList, Toggle, ToggleRow, Toolbar, Snackbar, Validation, Wizard, WizardStep, theme };
@@ -50,6 +50,7 @@ import * as Form from './NewForm';
50
50
  import { NewTooltip } from './NewTooltip';
51
51
  import { Notice } from './Notice';
52
52
  import { OptionRow } from './OptionRow';
53
+ import { Pagination } from './Pagination';
53
54
  import { Progress } from './Progress';
54
55
  import { ScreenReaderText } from './ScreenReaderText';
55
56
  import { Skeleton } from './Skeleton';
@@ -98,6 +99,7 @@ export {
98
99
  Flex,
99
100
  Notice,
100
101
  OptionRow,
102
+ Pagination,
101
103
  Heading,
102
104
  Hr,
103
105
  Input,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip-design-system",
3
- "version": "2.16.1",
3
+ "version": "2.17.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/Automattic/vip-design-system"
@@ -46,6 +46,7 @@ interface FormSelectProps {
46
46
  options: Option[];
47
47
  required?: boolean;
48
48
  label?: string;
49
+ separator?: boolean;
49
50
  getOptionLabel?: ( option: Option ) => string;
50
51
  getOptionValue?: ( option: Option ) => string | number;
51
52
  onChange?: ( option: Option | undefined, event: React.ChangeEvent< HTMLSelectElement > ) => void;
@@ -91,6 +92,7 @@ const FormSelect = React.forwardRef< HTMLSelectElement, FormSelectProps >(
91
92
  hasError,
92
93
  errorMessage,
93
94
  wrapperSx,
95
+ separator = true,
94
96
  ...props
95
97
  },
96
98
  forwardRef
@@ -150,7 +152,11 @@ const FormSelect = React.forwardRef< HTMLSelectElement, FormSelectProps >(
150
152
  <select
151
153
  onChange={ onValueChange }
152
154
  ref={ forwardRef }
153
- sx={ { cursor: disabled ? 'not-allowed' : 'pointer', ...defaultStyles } }
155
+ sx={ {
156
+ cursor: disabled ? 'not-allowed' : 'pointer',
157
+ ...defaultStyles,
158
+ ...( ! separator && { paddingRight: 6 } ),
159
+ } }
154
160
  required={ required }
155
161
  disabled={ disabled }
156
162
  aria-required={ required }
@@ -165,7 +171,7 @@ const FormSelect = React.forwardRef< HTMLSelectElement, FormSelectProps >(
165
171
  : renderOption( optionLabel( option ), optionValue( option ) )
166
172
  ) }
167
173
  </select>
168
- <FormSelectArrow iconSize={ ICON_SIZE } />
174
+ <FormSelectArrow iconSize={ ICON_SIZE } separator={ separator } />
169
175
  </FormSelectContent>
170
176
 
171
177
  { hasError && errorMessage && (