newspack-components 4.6.3 → 4.7.0-alpha.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 (148) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/colors/colors.module.scss +13 -7
  3. package/dist/cjs/autocomplete-tokenfield/style.scss +4 -2
  4. package/dist/cjs/breadcrumbs/format-count.js +33 -0
  5. package/dist/cjs/breadcrumbs/index.js +78 -20
  6. package/dist/cjs/breadcrumbs/index.test.js +110 -0
  7. package/dist/cjs/breadcrumbs/style.scss +6 -0
  8. package/dist/cjs/button/index.js +41 -17
  9. package/dist/cjs/button/index.test.js +273 -0
  10. package/dist/cjs/card/core-card.js +9 -4
  11. package/dist/cjs/card/core-card.test.js +29 -0
  12. package/dist/cjs/card/index.js +4 -2
  13. package/dist/cjs/card/style-core.scss +59 -0
  14. package/dist/cjs/card-feature/index.js +7 -1
  15. package/dist/cjs/card-feature/index.test.js +92 -0
  16. package/dist/cjs/color-picker/index.js +1 -1
  17. package/dist/cjs/confirm-dialog/index.js +26 -9
  18. package/dist/cjs/confirm-dialog/index.test.js +176 -0
  19. package/dist/cjs/dataviews/index.js +11 -2
  20. package/dist/cjs/divider/index.js +13 -0
  21. package/dist/cjs/drawer/action.js +74 -0
  22. package/dist/cjs/drawer/body.js +79 -0
  23. package/dist/cjs/drawer/close-icon.js +45 -0
  24. package/dist/cjs/drawer/content.js +72 -0
  25. package/dist/cjs/drawer/context.js +23 -0
  26. package/dist/cjs/drawer/context.test.js +43 -0
  27. package/dist/cjs/drawer/divider.js +24 -0
  28. package/dist/cjs/drawer/footer.js +26 -0
  29. package/dist/cjs/drawer/header.js +26 -0
  30. package/dist/cjs/drawer/index.js +42 -0
  31. package/dist/cjs/drawer/index.test.js +1287 -0
  32. package/dist/cjs/drawer/root.js +350 -0
  33. package/dist/cjs/drawer/slots.test.js +422 -0
  34. package/dist/cjs/drawer/style.scss +205 -0
  35. package/dist/cjs/drawer/title.js +54 -0
  36. package/dist/cjs/drawer/types.js +5 -0
  37. package/dist/cjs/drawer/use-exit-animation.js +59 -0
  38. package/dist/cjs/hooks/use-confirm-dialog.js +15 -3
  39. package/dist/cjs/hooks/use-unsaved-changes-dialog.js +19 -4
  40. package/dist/cjs/index.js +28 -1
  41. package/dist/cjs/page/index.js +1 -1
  42. package/dist/cjs/page-control/index.js +82 -0
  43. package/dist/cjs/plugin-settings/SettingsSection.js +7 -1
  44. package/dist/cjs/section-header/index.js +5 -2
  45. package/dist/cjs/section-header/style.scss +25 -12
  46. package/dist/cjs/table-card/index.js +67 -0
  47. package/dist/cjs/table-card/index.test.js +153 -0
  48. package/dist/cjs/with-wizard-screen/style.scss +8 -0
  49. package/dist/cjs/wizard/index.js +102 -38
  50. package/dist/esm/autocomplete-tokenfield/style.scss +4 -2
  51. package/dist/esm/breadcrumbs/format-count.js +27 -0
  52. package/dist/esm/breadcrumbs/index.js +81 -20
  53. package/dist/esm/breadcrumbs/index.test.js +110 -0
  54. package/dist/esm/breadcrumbs/style.scss +6 -0
  55. package/dist/esm/button/index.js +42 -18
  56. package/dist/esm/button/index.test.js +273 -0
  57. package/dist/esm/card/core-card.js +9 -4
  58. package/dist/esm/card/core-card.test.js +29 -0
  59. package/dist/esm/card/index.js +4 -2
  60. package/dist/esm/card/style-core.scss +59 -0
  61. package/dist/esm/card-feature/index.js +8 -2
  62. package/dist/esm/card-feature/index.test.js +96 -0
  63. package/dist/esm/color-picker/index.js +1 -1
  64. package/dist/esm/confirm-dialog/index.js +26 -9
  65. package/dist/esm/confirm-dialog/index.test.js +176 -0
  66. package/dist/esm/dataviews/index.js +11 -2
  67. package/dist/esm/divider/index.js +14 -0
  68. package/dist/esm/drawer/action.js +68 -0
  69. package/dist/esm/drawer/body.js +70 -0
  70. package/dist/esm/drawer/close-icon.js +37 -0
  71. package/dist/esm/drawer/content.js +63 -0
  72. package/dist/esm/drawer/context.js +17 -0
  73. package/dist/esm/drawer/context.test.js +40 -0
  74. package/dist/esm/drawer/divider.js +16 -0
  75. package/dist/esm/drawer/footer.js +18 -0
  76. package/dist/esm/drawer/header.js +18 -0
  77. package/dist/esm/drawer/index.js +35 -0
  78. package/dist/esm/drawer/index.test.js +1285 -0
  79. package/dist/esm/drawer/root.js +342 -0
  80. package/dist/esm/drawer/slots.test.js +422 -0
  81. package/dist/esm/drawer/style.scss +205 -0
  82. package/dist/esm/drawer/title.js +46 -0
  83. package/dist/esm/drawer/types.js +1 -0
  84. package/dist/esm/drawer/use-exit-animation.js +52 -0
  85. package/dist/esm/hooks/use-confirm-dialog.js +15 -3
  86. package/dist/esm/hooks/use-unsaved-changes-dialog.js +20 -5
  87. package/dist/esm/index.js +4 -0
  88. package/dist/esm/page/index.js +1 -1
  89. package/dist/esm/page-control/index.js +82 -0
  90. package/dist/esm/plugin-settings/SettingsSection.js +8 -2
  91. package/dist/esm/section-header/index.js +5 -2
  92. package/dist/esm/section-header/style.scss +25 -12
  93. package/dist/esm/table-card/index.js +60 -0
  94. package/dist/esm/table-card/index.test.js +150 -0
  95. package/dist/esm/with-wizard-screen/style.scss +8 -0
  96. package/dist/esm/wizard/index.js +104 -38
  97. package/package.json +2 -2
  98. package/src/autocomplete-tokenfield/style.scss +4 -2
  99. package/src/breadcrumbs/format-count.js +27 -0
  100. package/src/breadcrumbs/index.js +68 -8
  101. package/src/breadcrumbs/index.test.js +57 -0
  102. package/src/breadcrumbs/style.scss +6 -0
  103. package/src/button/index.test.js +143 -0
  104. package/src/button/index.tsx +32 -7
  105. package/src/card/core-card.js +15 -2
  106. package/src/card/core-card.test.js +19 -0
  107. package/src/card/index.js +3 -0
  108. package/src/card/style-core.scss +59 -0
  109. package/src/card-feature/README.md +4 -0
  110. package/src/card-feature/index.test.js +65 -0
  111. package/src/card-feature/index.tsx +13 -2
  112. package/src/color-picker/index.js +1 -1
  113. package/src/confirm-dialog/README.md +3 -2
  114. package/src/confirm-dialog/index.test.js +146 -0
  115. package/src/confirm-dialog/index.tsx +23 -9
  116. package/src/dataviews/index.tsx +11 -2
  117. package/src/divider/index.js +13 -0
  118. package/src/drawer/README.md +454 -0
  119. package/src/drawer/action.tsx +67 -0
  120. package/src/drawer/body.tsx +60 -0
  121. package/src/drawer/close-icon.tsx +37 -0
  122. package/src/drawer/content.tsx +54 -0
  123. package/src/drawer/context.test.js +31 -0
  124. package/src/drawer/context.ts +25 -0
  125. package/src/drawer/divider.tsx +13 -0
  126. package/src/drawer/footer.tsx +15 -0
  127. package/src/drawer/header.tsx +15 -0
  128. package/src/drawer/index.test.js +1012 -0
  129. package/src/drawer/index.tsx +33 -0
  130. package/src/drawer/root.tsx +328 -0
  131. package/src/drawer/slots.test.js +314 -0
  132. package/src/drawer/style.scss +205 -0
  133. package/src/drawer/title.tsx +40 -0
  134. package/src/drawer/types.ts +79 -0
  135. package/src/drawer/use-exit-animation.ts +46 -0
  136. package/src/hooks/use-confirm-dialog.tsx +14 -3
  137. package/src/hooks/use-unsaved-changes-dialog.tsx +17 -5
  138. package/src/index.js +4 -0
  139. package/src/page/index.js +1 -1
  140. package/src/page-control/index.js +63 -0
  141. package/src/plugin-settings/SettingsSection.js +8 -2
  142. package/src/section-header/index.js +5 -2
  143. package/src/section-header/style.scss +25 -12
  144. package/src/table-card/README.md +100 -0
  145. package/src/table-card/index.test.js +112 -0
  146. package/src/table-card/index.tsx +56 -0
  147. package/src/with-wizard-screen/style.scss +8 -0
  148. package/src/wizard/index.js +95 -34
@@ -0,0 +1,422 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ /**
5
+ * Internal dependencies.
6
+ */
7
+ import { DrawerContext } from './context';
8
+ import Header from './header';
9
+ import Title from './title';
10
+ import CloseIcon from './close-icon';
11
+ import Content from './content';
12
+ import Divider from './divider';
13
+ import Footer from './footer';
14
+ import Action from './action';
15
+
16
+ /**
17
+ * External dependencies.
18
+ */
19
+ import { fireEvent, render, screen } from '@testing-library/react';
20
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
21
+ var withContext = function withContext(ui) {
22
+ var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
23
+ return render(/*#__PURE__*/_jsx(DrawerContext.Provider, {
24
+ value: _objectSpread({
25
+ requestClose: function requestClose() {},
26
+ title: null,
27
+ setTitle: function setTitle() {}
28
+ }, value),
29
+ children: ui
30
+ }));
31
+ };
32
+ describe('Drawer.Header', function () {
33
+ it('renders a container with its children', function () {
34
+ withContext(/*#__PURE__*/_jsx(Header, {
35
+ className: "custom",
36
+ children: "content"
37
+ }));
38
+ var header = screen.getByText('content');
39
+ expect(header).toHaveClass('newspack-drawer__header', 'custom');
40
+ });
41
+ });
42
+ describe('Drawer.Title', function () {
43
+ it('renders an h2 and registers its id and text', function () {
44
+ var setTitle = jest.fn();
45
+ withContext(/*#__PURE__*/_jsx(Title, {
46
+ children: "Edit Styles"
47
+ }), {
48
+ setTitle: setTitle
49
+ });
50
+ var heading = screen.getByRole('heading', {
51
+ level: 2,
52
+ name: 'Edit Styles'
53
+ });
54
+ expect(heading.id).toBeTruthy();
55
+ expect(setTitle).toHaveBeenCalledWith({
56
+ id: heading.id,
57
+ text: 'Edit Styles'
58
+ });
59
+ });
60
+ it('registers null text for non-string children and clears on unmount', function () {
61
+ var setTitle = jest.fn();
62
+ var _withContext = withContext(/*#__PURE__*/_jsx(Title, {
63
+ children: /*#__PURE__*/_jsx("em", {
64
+ children: "Rich"
65
+ })
66
+ }), {
67
+ setTitle: setTitle
68
+ }),
69
+ unmount = _withContext.unmount;
70
+ expect(setTitle).toHaveBeenCalledWith(expect.objectContaining({
71
+ text: null
72
+ }));
73
+ unmount();
74
+ expect(setTitle).toHaveBeenLastCalledWith(null, expect.any(String));
75
+ });
76
+ });
77
+ describe('Drawer.CloseIcon', function () {
78
+ it('is named "Close" without a title and funnels clicks to requestClose', function () {
79
+ var requestClose = jest.fn();
80
+ withContext(/*#__PURE__*/_jsx(CloseIcon, {}), {
81
+ requestClose: requestClose
82
+ });
83
+ var button = screen.getByRole('button', {
84
+ name: 'Close'
85
+ });
86
+ fireEvent.click(button);
87
+ expect(requestClose).toHaveBeenCalled();
88
+ });
89
+ it('composes the label with a string title', function () {
90
+ withContext(/*#__PURE__*/_jsx(CloseIcon, {}), {
91
+ title: {
92
+ id: 't',
93
+ text: 'Edit Styles'
94
+ }
95
+ });
96
+ expect(screen.getByRole('button', {
97
+ name: 'Close Edit Styles'
98
+ })).toBeInTheDocument();
99
+ });
100
+ it('accepts a label override', function () {
101
+ withContext(/*#__PURE__*/_jsx(CloseIcon, {
102
+ label: "Dismiss panel"
103
+ }), {
104
+ title: {
105
+ id: 't',
106
+ text: 'Edit Styles'
107
+ }
108
+ });
109
+ expect(screen.getByRole('button', {
110
+ name: 'Dismiss panel'
111
+ })).toBeInTheDocument();
112
+ });
113
+ });
114
+ describe('Drawer.Content', function () {
115
+ var sectionOf = function sectionOf(container) {
116
+ return container.querySelector('.newspack-drawer__content');
117
+ };
118
+
119
+ // jest-dom's toHaveStyle is unreliable for custom properties in jsdom.
120
+ it('defaults to padding 6 on the 4px scale', function () {
121
+ var _render = render(/*#__PURE__*/_jsx(Content, {
122
+ children: "body"
123
+ })),
124
+ container = _render.container;
125
+ expect(sectionOf(container).style.getPropertyValue('--newspack-drawer-content-padding')).toBe('24px');
126
+ });
127
+ it('accepts padding 0 for a flush section', function () {
128
+ var _render2 = render(/*#__PURE__*/_jsx(Content, {
129
+ padding: 0,
130
+ children: "flush"
131
+ })),
132
+ container = _render2.container;
133
+ expect(sectionOf(container).style.getPropertyValue('--newspack-drawer-content-padding')).toBe('0px');
134
+ });
135
+ it('renders each section as a VStack', function () {
136
+ var _render3 = render(/*#__PURE__*/_jsx(Content, {
137
+ children: "stacked"
138
+ })),
139
+ container = _render3.container;
140
+ expect(sectionOf(container)).toHaveClass('newspack-drawer__content', 'components-v-stack');
141
+ });
142
+
143
+ // VStack emits its spacing as a `calc()` on the 4px base, not as pixels.
144
+ it('spaces its children by 16px, and by the gap prop', function () {
145
+ var _render4 = render(/*#__PURE__*/_jsx(Content, {
146
+ children: "stacked"
147
+ })),
148
+ container = _render4.container,
149
+ rerender = _render4.rerender;
150
+ expect(window.getComputedStyle(sectionOf(container)).gap).toBe('calc(4px * 4)');
151
+ rerender(/*#__PURE__*/_jsx(Content, {
152
+ gap: 2,
153
+ children: "stacked"
154
+ }));
155
+ expect(window.getComputedStyle(sectionOf(container)).gap).toBe('calc(4px * 2)');
156
+ });
157
+
158
+ // VStack keeps only elements, so these would otherwise vanish without warning.
159
+ it('keeps plain text sitting beside an element', function () {
160
+ render(/*#__PURE__*/_jsxs(Content, {
161
+ children: ["Lead text", /*#__PURE__*/_jsx("span", {
162
+ children: "an element"
163
+ }), 42]
164
+ }));
165
+ expect(screen.getByText('Lead text')).toBeInTheDocument();
166
+ expect(screen.getByText('an element')).toBeInTheDocument();
167
+ expect(screen.getByText('42')).toBeInTheDocument();
168
+ });
169
+ it('keeps a run of text and interpolations on one row', function () {
170
+ var _render5 = render(/*#__PURE__*/_jsxs(Content, {
171
+ children: ["Edited by ", 'Ada', " just now"]
172
+ })),
173
+ container = _render5.container;
174
+ var rows = sectionOf(container).children;
175
+ expect(rows).toHaveLength(1);
176
+ expect(rows[0]).toHaveTextContent('Edited by Ada just now');
177
+ });
178
+ it('starts a new row either side of an element', function () {
179
+ var _render6 = render(/*#__PURE__*/_jsxs(Content, {
180
+ children: ["before", /*#__PURE__*/_jsx("span", {
181
+ children: "middle"
182
+ }), "after"]
183
+ })),
184
+ container = _render6.container;
185
+ expect(Array.from(sectionOf(container).children).map(function (row) {
186
+ return row.textContent;
187
+ })).toEqual(['before', 'middle', 'after']);
188
+ });
189
+ it('emits no row for whitespace or empty text between elements', function () {
190
+ var _render7 = render(/*#__PURE__*/_jsxs(Content, {
191
+ children: [/*#__PURE__*/_jsx("span", {
192
+ children: "one"
193
+ }), " ", '', /*#__PURE__*/_jsx("span", {
194
+ children: "two"
195
+ })]
196
+ })),
197
+ container = _render7.container;
198
+ expect(Array.from(sectionOf(container).children).map(function (row) {
199
+ return row.textContent;
200
+ })).toEqual(['one', 'two']);
201
+ });
202
+ it('keeps a lone string', function () {
203
+ render(/*#__PURE__*/_jsx(Content, {
204
+ children: "on its own"
205
+ }));
206
+ expect(screen.getByText('on its own')).toBeInTheDocument();
207
+ });
208
+ });
209
+ describe('Drawer.Divider', function () {
210
+ it('renders a separator, merging className', function () {
211
+ var _render8 = render(/*#__PURE__*/_jsx(Divider, {
212
+ className: "custom"
213
+ })),
214
+ container = _render8.container;
215
+ var divider = container.querySelector('hr');
216
+ expect(divider).toHaveClass('newspack-drawer__divider', 'custom');
217
+ expect(screen.getByRole('separator')).toBe(divider);
218
+ });
219
+ });
220
+ describe('Drawer.Footer', function () {
221
+ it('renders a container with its children', function () {
222
+ render(/*#__PURE__*/_jsx(Footer, {
223
+ children: "actions"
224
+ }));
225
+ expect(screen.getByText('actions')).toHaveClass('newspack-drawer__footer');
226
+ });
227
+ it('renders its actions as direct children', function () {
228
+ var _render9 = render(/*#__PURE__*/_jsxs(Footer, {
229
+ children: [/*#__PURE__*/_jsx(Action, {
230
+ children: "One"
231
+ }), /*#__PURE__*/_jsx(Action, {
232
+ children: "Two"
233
+ }), /*#__PURE__*/_jsx(Action, {
234
+ children: "Three"
235
+ })]
236
+ })),
237
+ container = _render9.container;
238
+ var footer = container.querySelector('.newspack-drawer__footer');
239
+ expect(Array.from(footer.children)).toEqual([screen.getByRole('button', {
240
+ name: 'One'
241
+ }), screen.getByRole('button', {
242
+ name: 'Two'
243
+ }), screen.getByRole('button', {
244
+ name: 'Three'
245
+ })]);
246
+ });
247
+ });
248
+ describe('Drawer.Action', function () {
249
+ it('renders a button with aria-label from ariaLabel', function () {
250
+ render(/*#__PURE__*/_jsx(Action, {
251
+ ariaLabel: "Save styles",
252
+ children: "Save"
253
+ }));
254
+ expect(screen.getByRole('button', {
255
+ name: 'Save styles'
256
+ })).toBeInTheDocument();
257
+ });
258
+ it('warns when ariaLabel does not contain the visible label', function () {
259
+ var warn = jest.spyOn(console, 'warn').mockImplementation(function () {});
260
+ render(/*#__PURE__*/_jsx(Action, {
261
+ ariaLabel: "Apply changes",
262
+ children: "Save"
263
+ }));
264
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('Label in Name'));
265
+ warn.mockRestore();
266
+ });
267
+ it('does not warn when ariaLabel contains the visible label', function () {
268
+ var warn = jest.spyOn(console, 'warn').mockImplementation(function () {});
269
+ render(/*#__PURE__*/_jsx(Action, {
270
+ ariaLabel: "Save styles",
271
+ children: "Save"
272
+ }));
273
+ expect(warn).not.toHaveBeenCalled();
274
+ warn.mockRestore();
275
+ });
276
+ it('hands the click event to onClick', function () {
277
+ var onClick = jest.fn();
278
+ render(/*#__PURE__*/_jsx(Action, {
279
+ onClick: onClick,
280
+ children: "Save"
281
+ }));
282
+ fireEvent.click(screen.getByRole('button', {
283
+ name: 'Save'
284
+ }));
285
+ expect(onClick).toHaveBeenCalledWith(expect.objectContaining({
286
+ type: 'click'
287
+ }));
288
+ });
289
+ it('forwards the button props', function () {
290
+ render(/*#__PURE__*/_jsx(Action, {
291
+ variant: "primary",
292
+ isBusy: true,
293
+ isDestructive: true,
294
+ disabled: true,
295
+ children: "Save"
296
+ }));
297
+ var button = screen.getByRole('button', {
298
+ name: 'Save'
299
+ });
300
+ expect(button).toHaveClass('is-primary', 'is-busy', 'is-destructive');
301
+ expect(button).toBeDisabled();
302
+ });
303
+ it('forwards onClick', function () {
304
+ var onClick = jest.fn();
305
+ render(/*#__PURE__*/_jsx(Action, {
306
+ onClick: onClick,
307
+ children: "Save"
308
+ }));
309
+ fireEvent.click(screen.getByRole('button', {
310
+ name: 'Save'
311
+ }));
312
+ expect(onClick).toHaveBeenCalledTimes(1);
313
+ });
314
+
315
+ // Button drops the href as soon as an onClick is present.
316
+ it('renders an anchor for an href-only action', function () {
317
+ render(/*#__PURE__*/_jsx(Action, {
318
+ href: "/somewhere",
319
+ children: "Go"
320
+ }));
321
+ var link = screen.getByRole('link', {
322
+ name: 'Go'
323
+ });
324
+ expect(link).toHaveAttribute('href', '/somewhere');
325
+ });
326
+
327
+ // The types forbid this, but the package is consumed from plain JS.
328
+ it('stays a link and warns when href is combined with closes', function () {
329
+ var warn = jest.spyOn(console, 'warn').mockImplementation(function () {});
330
+ var requestClose = jest.fn();
331
+ var onClick = jest.fn();
332
+ withContext(/*#__PURE__*/_jsx(Action, {
333
+ href: "/somewhere",
334
+ closes: true,
335
+ onClick: onClick,
336
+ children: "Go"
337
+ }), {
338
+ requestClose: requestClose
339
+ });
340
+ var link = screen.getByRole('link', {
341
+ name: 'Go'
342
+ });
343
+ expect(link).toHaveAttribute('href', '/somewhere');
344
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('cannot also take'));
345
+ fireEvent.click(link);
346
+ expect(onClick).not.toHaveBeenCalled();
347
+ expect(requestClose).not.toHaveBeenCalled();
348
+ warn.mockRestore();
349
+ });
350
+
351
+ // An empty string still renders an anchor, so truthiness would re-arm the funnel.
352
+ it('treats an empty href as a link rather than re-arming the close funnel', function () {
353
+ var warn = jest.spyOn(console, 'warn').mockImplementation(function () {});
354
+ var requestClose = jest.fn();
355
+ var onClick = jest.fn();
356
+ withContext(/*#__PURE__*/_jsx(Action, {
357
+ href: "",
358
+ closes: true,
359
+ onClick: onClick,
360
+ children: "Go"
361
+ }), {
362
+ requestClose: requestClose
363
+ });
364
+ var anchor = screen.getByText('Go').closest('a');
365
+ expect(anchor).toHaveAttribute('href', '');
366
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('cannot also take'));
367
+ fireEvent.click(anchor);
368
+ expect(onClick).not.toHaveBeenCalled();
369
+ expect(requestClose).not.toHaveBeenCalled();
370
+ warn.mockRestore();
371
+ });
372
+
373
+ // No link, so the handlers must stay attached rather than leaving a dead anchor.
374
+ it('runs the handlers when a nullable href resolves to nothing', function () {
375
+ var requestClose = jest.fn();
376
+ var onClick = jest.fn();
377
+ withContext(/*#__PURE__*/_jsx(Action, {
378
+ href: null,
379
+ closes: true,
380
+ onClick: onClick,
381
+ children: "Go"
382
+ }), {
383
+ requestClose: requestClose
384
+ });
385
+ var button = screen.getByRole('button', {
386
+ name: 'Go'
387
+ });
388
+ fireEvent.click(button);
389
+ expect(onClick).toHaveBeenCalledTimes(1);
390
+ expect(requestClose).toHaveBeenCalledTimes(1);
391
+ });
392
+ it('requests the drawer close after onClick when `closes` is set', function () {
393
+ var requestClose = jest.fn();
394
+ var onClick = jest.fn(function () {
395
+ return expect(requestClose).not.toHaveBeenCalled();
396
+ });
397
+ withContext(/*#__PURE__*/_jsx(Action, {
398
+ closes: true,
399
+ onClick: onClick,
400
+ children: "Cancel"
401
+ }), {
402
+ requestClose: requestClose
403
+ });
404
+ fireEvent.click(screen.getByRole('button', {
405
+ name: 'Cancel'
406
+ }));
407
+ expect(onClick).toHaveBeenCalledTimes(1);
408
+ expect(requestClose).toHaveBeenCalledTimes(1);
409
+ });
410
+ it('does not request the close without `closes`', function () {
411
+ var requestClose = jest.fn();
412
+ withContext(/*#__PURE__*/_jsx(Action, {
413
+ children: "Save"
414
+ }), {
415
+ requestClose: requestClose
416
+ });
417
+ fireEvent.click(screen.getByRole('button', {
418
+ name: 'Save'
419
+ }));
420
+ expect(requestClose).not.toHaveBeenCalled();
421
+ });
422
+ });
@@ -0,0 +1,205 @@
1
+ @use "~@wordpress/base-styles/breakpoints" as wp-breakpoints;
2
+ @use "~@wordpress/base-styles/colors" as wp-colors;
3
+ @use "~@wordpress/base-styles/variables" as wp-vars;
4
+
5
+ $drawer-size-small: 280px;
6
+ $drawer-size-medium: 350px;
7
+ $drawer-size-large: 480px;
8
+ $drawer-size-x-large: 640px;
9
+
10
+ .newspack-drawer,
11
+ .newspack-drawer *,
12
+ .newspack-drawer *::before,
13
+ .newspack-drawer *::after {
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ .newspack-drawer {
18
+ display: flex;
19
+ flex-direction: column;
20
+ }
21
+
22
+ .components-modal__screen-overlay.newspack-drawer__overlay {
23
+ align-items: stretch;
24
+ justify-content: flex-end;
25
+ padding: 0;
26
+ }
27
+
28
+ .components-modal__frame.newspack-drawer {
29
+ animation: 0.2s ease-out newspack-drawer-slide-in;
30
+ border-radius: wp-vars.$radius-large 0 0 wp-vars.$radius-large;
31
+ height: 100%;
32
+ margin: 0;
33
+ max-height: none;
34
+ min-width: 0;
35
+ overflow: hidden;
36
+ width: 100%;
37
+
38
+ &.is-exiting {
39
+ animation: 0.2s ease-in forwards newspack-drawer-slide-out;
40
+ }
41
+
42
+ .components-modal__content {
43
+ display: flex;
44
+ flex-direction: column;
45
+ padding: 0;
46
+ }
47
+
48
+ .components-modal__children-container {
49
+ display: flex;
50
+ flex: 1 1 auto;
51
+ flex-direction: column;
52
+ min-height: 0;
53
+ }
54
+
55
+ &.newspack-drawer--size-small { max-width: $drawer-size-small; }
56
+ &.newspack-drawer--size-medium { max-width: $drawer-size-medium; }
57
+ &.newspack-drawer--size-large { max-width: $drawer-size-large; }
58
+ &.newspack-drawer--size-x-large { max-width: $drawer-size-x-large; }
59
+ &.newspack-drawer--size-full {
60
+ border-radius: 0;
61
+ max-width: 100vw;
62
+ }
63
+
64
+ @media (max-width: $drawer-size-x-large) {
65
+ &.newspack-drawer--size-x-large {
66
+ border-radius: 0;
67
+ }
68
+ }
69
+
70
+ @media (max-width: wp-breakpoints.$break-small) {
71
+ &.newspack-drawer--size-small,
72
+ &.newspack-drawer--size-medium,
73
+ &.newspack-drawer--size-large,
74
+ &.newspack-drawer--size-x-large,
75
+ &.newspack-drawer--size-full {
76
+ border-radius: 0;
77
+ max-width: 100vw;
78
+ }
79
+ }
80
+ }
81
+
82
+ .newspack-drawer__header {
83
+ align-items: center;
84
+ display: flex;
85
+ flex: 0 0 auto;
86
+ gap: wp-vars.$grid-unit-10;
87
+ padding: wp-vars.$grid-unit-30 wp-vars.$grid-unit-30 wp-vars.$grid-unit-20;
88
+ }
89
+
90
+ .newspack-drawer__icon {
91
+ fill: currentcolor;
92
+ flex: none;
93
+ }
94
+
95
+ .newspack-drawer__title {
96
+ flex: 1;
97
+ font-size: inherit;
98
+ font-weight: wp-vars.$font-weight-medium;
99
+ margin: 0;
100
+ min-width: 0;
101
+ }
102
+
103
+ .newspack-drawer__dismiss {
104
+ flex: none;
105
+ margin-left: auto;
106
+ }
107
+
108
+ .newspack-drawer__body {
109
+ flex: 1 1 auto;
110
+ overflow-y: auto;
111
+
112
+ > * {
113
+ flex: none;
114
+ }
115
+ }
116
+
117
+ .newspack-drawer__content {
118
+ padding: var(--newspack-drawer-content-padding, #{wp-vars.$grid-unit-30});
119
+
120
+ > * {
121
+ margin-bottom: 0;
122
+ margin-top: 0;
123
+ }
124
+ }
125
+
126
+ .newspack-drawer__divider {
127
+ border: 0;
128
+ border-top: wp-vars.$border-width solid wp-colors.$gray-300;
129
+ margin: 0;
130
+ }
131
+
132
+ .newspack-drawer__header + .newspack-drawer__body .newspack-drawer__content:first-child {
133
+ padding-top: 0;
134
+ }
135
+
136
+ .newspack-drawer__body:has(+ .newspack-drawer__footer) .newspack-drawer__content:last-child {
137
+ padding-bottom: 0;
138
+ }
139
+
140
+ .newspack-drawer__footer {
141
+ background: wp-colors.$white;
142
+ display: flex;
143
+ flex: 0 0 auto;
144
+ gap: wp-vars.$grid-unit-10;
145
+ padding: wp-vars.$grid-unit-20 wp-vars.$grid-unit-30 wp-vars.$grid-unit-30;
146
+
147
+ > .components-button {
148
+ flex: 1;
149
+ justify-content: center;
150
+ }
151
+
152
+ &:has(> :nth-child(3)) {
153
+ flex-direction: column;
154
+
155
+ > .components-button {
156
+ flex: 0 0 auto;
157
+ }
158
+ }
159
+ }
160
+
161
+ @keyframes newspack-drawer-slide-in {
162
+ from {
163
+ transform: translateX(100%);
164
+ }
165
+
166
+ to {
167
+ transform: translateX(0);
168
+ }
169
+ }
170
+
171
+ @keyframes newspack-drawer-slide-out {
172
+ from {
173
+ transform: translateX(0);
174
+ }
175
+
176
+ to {
177
+ transform: translateX(100%);
178
+ }
179
+ }
180
+
181
+ @keyframes newspack-drawer-overlay-fade-out {
182
+ from {
183
+ opacity: 1;
184
+ }
185
+
186
+ to {
187
+ opacity: 0;
188
+ }
189
+ }
190
+
191
+ .newspack-drawer__overlay.is-exiting {
192
+ animation: 0.2s ease-in forwards newspack-drawer-overlay-fade-out;
193
+ }
194
+
195
+ .components-modal__frame.newspack-drawer .components-popover__content {
196
+ opacity: 1;
197
+ }
198
+
199
+ @media (prefers-reduced-motion: reduce) {
200
+ .components-modal__frame.newspack-drawer,
201
+ .components-modal__frame.newspack-drawer.is-exiting,
202
+ .newspack-drawer__overlay.is-exiting {
203
+ animation: none;
204
+ }
205
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * WordPress dependencies.
3
+ */
4
+ import { useInstanceId } from '@wordpress/compose';
5
+ import { useLayoutEffect } from '@wordpress/element';
6
+
7
+ /**
8
+ * External dependencies.
9
+ */
10
+ import classnames from 'classnames';
11
+
12
+ /**
13
+ * Internal dependencies.
14
+ */
15
+ import { useDrawerContext } from './context';
16
+ import { jsx as _jsx } from "react/jsx-runtime";
17
+ var _Title = function Title(_ref) {
18
+ var className = _ref.className,
19
+ children = _ref.children;
20
+ var _useDrawerContext = useDrawerContext(),
21
+ setTitle = _useDrawerContext.setTitle;
22
+ var id = useInstanceId(_Title, 'newspack-drawer__title');
23
+
24
+ // Derived, not keyed on `children`: a JSX title would re-register every render.
25
+ var text = typeof children === 'string' ? children : null;
26
+ useLayoutEffect(function () {
27
+ setTitle({
28
+ id: id,
29
+ text: text
30
+ });
31
+ }, [id, text, setTitle]);
32
+
33
+ // Unmount only, scoped to this id: a text change must not re-order the stack,
34
+ // nor a second title unname the one still rendered.
35
+ useLayoutEffect(function () {
36
+ return function () {
37
+ return setTitle(null, id);
38
+ };
39
+ }, [id, setTitle]);
40
+ return /*#__PURE__*/_jsx("h2", {
41
+ className: classnames('newspack-drawer__title', className),
42
+ id: id,
43
+ children: children
44
+ });
45
+ };
46
+ export default _Title;
@@ -0,0 +1 @@
1
+ export {};