@pequity/squirrel 10.0.2 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +20 -1
  2. package/dist/cjs/chunks/p-action-bar.js +17 -14
  3. package/dist/cjs/chunks/p-date-picker.js +3 -1
  4. package/dist/cjs/chunks/p-dropdown-select.js +27 -26
  5. package/dist/cjs/chunks/p-inline-date-picker.js +3 -1
  6. package/dist/cjs/chunks/p-pagination-info.js +2 -2
  7. package/dist/cjs/chunks/p-pagination.js +13 -11
  8. package/dist/cjs/chunks/p-tabs-pills.js +8 -8
  9. package/dist/cjs/index.js +101 -48
  10. package/dist/cjs/p-drawer.js +4 -4
  11. package/dist/cjs/p-icon.js +1 -0
  12. package/dist/cjs/p-input-search.js +5 -4
  13. package/dist/cjs/p-modal.js +3 -3
  14. package/dist/es/chunks/p-action-bar.js +18 -15
  15. package/dist/es/chunks/p-date-picker.js +3 -1
  16. package/dist/es/chunks/p-dropdown-select.js +27 -26
  17. package/dist/es/chunks/p-inline-date-picker.js +3 -1
  18. package/dist/es/chunks/p-pagination-info.js +2 -2
  19. package/dist/es/chunks/p-pagination.js +13 -11
  20. package/dist/es/chunks/p-tabs-pills.js +8 -8
  21. package/dist/es/index.js +102 -49
  22. package/dist/es/p-drawer.js +4 -4
  23. package/dist/es/p-icon.js +1 -0
  24. package/dist/es/p-input-search.js +5 -4
  25. package/dist/es/p-modal.js +3 -3
  26. package/dist/squirrel/components/p-btn/p-btn.vue.d.ts +2 -2
  27. package/dist/squirrel/components/p-icon/p-icon.types.d.ts +1 -0
  28. package/dist/squirrel/index.d.ts +1 -0
  29. package/dist/squirrel/plugin/index.d.ts +11 -0
  30. package/dist/squirrel.css +40 -40
  31. package/package.json +28 -25
  32. package/squirrel/components/p-action-bar/p-action-bar.vue +4 -1
  33. package/squirrel/components/p-btn/p-btn.spec.js +0 -1
  34. package/squirrel/components/p-checkbox/p-checkbox.stories.js +2 -2
  35. package/squirrel/components/p-date-picker/p-date-picker.vue +3 -2
  36. package/squirrel/components/p-drawer/p-drawer.spec.js +364 -0
  37. package/squirrel/components/p-drawer/p-drawer.vue +8 -2
  38. package/squirrel/components/p-dropdown/p-dropdown.spec.js +252 -55
  39. package/squirrel/components/p-dropdown-select/p-dropdown-select.vue +16 -12
  40. package/squirrel/components/p-file-upload/p-file-upload.spec.js +0 -1
  41. package/squirrel/components/p-file-upload/p-file-upload.vue +26 -9
  42. package/squirrel/components/p-icon/p-icon.types.ts +1 -0
  43. package/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue +3 -1
  44. package/squirrel/components/p-input-search/p-input-search.vue +2 -2
  45. package/squirrel/components/p-modal/p-modal-features.spec.js +10 -10
  46. package/squirrel/components/p-modal/p-modal.vue +1 -1
  47. package/squirrel/components/p-pagination/p-pagination.vue +3 -3
  48. package/squirrel/components/p-pagination-info/p-pagination-info.vue +2 -2
  49. package/squirrel/components/p-progress-bar/{p-progess-bar.spec.js → p-progress-bar.spec.js} +7 -5
  50. package/squirrel/components/p-select-btn/p-select-btn.spec.js +104 -0
  51. package/squirrel/components/p-select-list/p-select-list.vue +7 -5
  52. package/squirrel/components/p-select-pill/p-select-pill.spec.js +114 -0
  53. package/squirrel/components/p-table/usePTableColResize.spec.js +123 -11
  54. package/squirrel/components/p-table/usePTableHeaderWrap.spec.js +1 -1
  55. package/squirrel/components/p-table/usePTableRowVirtualizer.spec.js +207 -0
  56. package/squirrel/components/p-table-header-cell/p-table-header-cell.stories.js +3 -0
  57. package/squirrel/components/p-table-sort/p-table-sort.vue +4 -4
  58. package/squirrel/components/p-tabs-pills/p-tabs-pills.vue +1 -1
  59. package/squirrel/index.spec.js +5 -0
  60. package/squirrel/index.ts +1 -0
  61. package/squirrel/locales/en-US.json +47 -0
  62. package/squirrel/locales/fr-CA.json +47 -0
  63. package/squirrel/plugin/index.spec.ts +140 -0
  64. package/squirrel/plugin/index.ts +54 -0
  65. package/squirrel/utils/listKeyboardNavigation.spec.js +58 -0
@@ -0,0 +1,364 @@
1
+ import PDrawer from '@squirrel/components/p-drawer/p-drawer.vue';
2
+ import { I18nPlugin } from '@tests/i18n.plugin';
3
+ import { sleep, waitRAF } from '@tests/vitest.helpers';
4
+ import { mount } from '@vue/test-utils';
5
+ import FloatingVue from 'floating-vue';
6
+
7
+ const createWrapperContainer = (componentArgs) => {
8
+ const args = componentArgs || {};
9
+
10
+ args.appendTo = '#drawer-host';
11
+ const wrapperContainer = {
12
+ components: {
13
+ PDrawer,
14
+ },
15
+ data() {
16
+ return {
17
+ showDrawer: false,
18
+ args,
19
+ };
20
+ },
21
+ template: `
22
+ <div id="drawer-host"></div>
23
+ <PDrawer v-model="showDrawer" v-bind="args"><p>Drawer content goes here...</p></PDrawer>
24
+ `,
25
+ };
26
+
27
+ return mount(wrapperContainer, {
28
+ attachTo: document.body,
29
+ global: {
30
+ plugins: [FloatingVue, I18nPlugin],
31
+ stubs: {
32
+ transition: false,
33
+ },
34
+ },
35
+ });
36
+ };
37
+
38
+ describe('PDrawer basic functionality', () => {
39
+ beforeEach(() => {
40
+ document.body.innerHTML = '';
41
+ });
42
+
43
+ it('shows the drawer', async () => {
44
+ const wrapper = createWrapperContainer();
45
+
46
+ expect(wrapper.find('[data-drawer-id]').exists()).toBe(false);
47
+
48
+ await wrapper.setData({ showDrawer: true });
49
+
50
+ expect(wrapper.find('[data-drawer-id]').exists()).toBe(true);
51
+
52
+ wrapper.unmount();
53
+ });
54
+
55
+ it('hides the drawer', async () => {
56
+ const wrapper = createWrapperContainer();
57
+
58
+ await wrapper.setData({ showDrawer: true });
59
+ await waitRAF();
60
+ await wrapper.setData({ showDrawer: false });
61
+ await waitRAF();
62
+ // Wait a bit longer for the transition to complete and element to unmount
63
+ await new Promise((resolve) => setTimeout(resolve, 50));
64
+
65
+ expect(wrapper.find('[data-drawer-id]').exists()).toBe(false);
66
+
67
+ wrapper.unmount();
68
+ });
69
+
70
+ it('renders with correct title', async () => {
71
+ const wrapper = createWrapperContainer({
72
+ title: 'Test Drawer Title',
73
+ });
74
+
75
+ await wrapper.setData({ showDrawer: true });
76
+
77
+ const drawer = wrapper.find('[data-drawer-id]');
78
+ const titleElement = wrapper.find('h3');
79
+
80
+ expect(drawer.attributes('aria-label')).toBe('Test Drawer Title');
81
+ expect(titleElement.text()).toBe('Test Drawer Title');
82
+
83
+ wrapper.unmount();
84
+ });
85
+
86
+ it.each([
87
+ ['right', 'drawer-right', 'drawer-left'],
88
+ ['left', 'drawer-left', 'drawer-right'],
89
+ ])('renders on the %s when position is %s', async (position, expectedClass, unexpectedClass) => {
90
+ const wrapper = createWrapperContainer({
91
+ position: position === 'right' ? undefined : position,
92
+ });
93
+
94
+ await wrapper.setData({ showDrawer: true });
95
+
96
+ const drawer = wrapper.find('[data-drawer-id]');
97
+
98
+ expect(drawer.classes()).toContain(expectedClass);
99
+ expect(drawer.classes()).not.toContain(unexpectedClass);
100
+
101
+ wrapper.unmount();
102
+ });
103
+
104
+ it('applies correct z-index', async () => {
105
+ const wrapper = createWrapperContainer({
106
+ zIndex: 950,
107
+ });
108
+
109
+ await wrapper.setData({ showDrawer: true });
110
+
111
+ const drawer = wrapper.find('[data-drawer-id]');
112
+
113
+ expect(drawer.element.style.zIndex).toBe('950');
114
+
115
+ wrapper.unmount();
116
+ });
117
+
118
+ it.each([
119
+ [true, 'renders', true],
120
+ [false, 'does not render', false],
121
+ ])('%s close button when enableClose is %s', async (enableClose, description, shouldExist) => {
122
+ const wrapper = createWrapperContainer({ enableClose });
123
+
124
+ await wrapper.setData({ showDrawer: true });
125
+
126
+ const closeBtn = wrapper.findComponent({ name: 'PCloseBtn' });
127
+
128
+ expect(closeBtn.exists()).toBe(shouldExist);
129
+
130
+ wrapper.unmount();
131
+ });
132
+
133
+ it('applies custom drawer class', async () => {
134
+ const wrapper = createWrapperContainer({
135
+ drawerClass: 'custom-drawer-class',
136
+ });
137
+
138
+ await wrapper.setData({ showDrawer: true });
139
+
140
+ const drawer = wrapper.find('[data-drawer-id]');
141
+
142
+ expect(drawer.classes()).toContain('custom-drawer-class');
143
+
144
+ wrapper.unmount();
145
+ });
146
+
147
+ it('shows error message when provided', async () => {
148
+ const wrapper = createWrapperContainer({
149
+ errorMsg: 'This is an error message',
150
+ });
151
+
152
+ await wrapper.setData({ showDrawer: true });
153
+
154
+ const errorAlert = wrapper.findComponent({ name: 'PAlert' });
155
+
156
+ expect(errorAlert.exists()).toBe(true);
157
+ expect(errorAlert.text()).toContain('This is an error message');
158
+
159
+ wrapper.unmount();
160
+ });
161
+
162
+ it('applies disabled state correctly', async () => {
163
+ const wrapper = createWrapperContainer({
164
+ disabled: true,
165
+ });
166
+
167
+ await wrapper.setData({ showDrawer: true });
168
+
169
+ const contentWrapper = wrapper.find('.relative.grow.overflow-y-auto');
170
+
171
+ expect(contentWrapper.classes()).toContain('pointer-events-none');
172
+ expect(contentWrapper.classes()).toContain('opacity-50');
173
+
174
+ wrapper.unmount();
175
+ });
176
+
177
+ it.each([
178
+ [true, 'renders', true, 'bg-black/20'],
179
+ [false, 'does not render', false, null],
180
+ ])('%s backdrop when showBackdrop is %s', async (showBackdrop, description, shouldExist, expectedClass) => {
181
+ const wrapper = createWrapperContainer({ showBackdrop });
182
+
183
+ await wrapper.setData({ showDrawer: true });
184
+
185
+ const backdrop = wrapper.find('.fixed.bottom-0.left-0.right-0.top-0');
186
+
187
+ if (shouldExist) {
188
+ expect(backdrop.exists()).toBe(true);
189
+ expect(backdrop.classes()).toContain(expectedClass);
190
+ } else {
191
+ expect(backdrop.isVisible()).toBe(false);
192
+ }
193
+
194
+ wrapper.unmount();
195
+ });
196
+
197
+ it('sets correct ARIA attributes', async () => {
198
+ const wrapper = createWrapperContainer({
199
+ title: 'Test Drawer',
200
+ });
201
+
202
+ await wrapper.setData({ showDrawer: true });
203
+
204
+ const drawer = wrapper.find('[data-drawer-id]');
205
+ const drawerComponent = wrapper.findComponent(PDrawer);
206
+ const drawerId = drawerComponent.vm.id;
207
+
208
+ expect(drawer.attributes('role')).toBe('dialog');
209
+ expect(drawer.attributes('aria-modal')).toBe('false');
210
+ expect(drawer.attributes('aria-label')).toBe('Test Drawer');
211
+ expect(drawer.attributes('aria-describedby')).toBe(`${drawerId}-content`);
212
+ expect(drawer.attributes('aria-labelledby')).toBe(`${drawerId}-title`);
213
+
214
+ wrapper.unmount();
215
+ });
216
+
217
+ it.each([
218
+ [true, 'closes', false],
219
+ [false, 'does not close', true],
220
+ ])('%s when backdrop is clicked and enableClose is %s', async (enableClose, description, shouldStayOpen) => {
221
+ const wrapper = createWrapperContainer({
222
+ showBackdrop: true,
223
+ enableClose,
224
+ });
225
+
226
+ await wrapper.setData({ showDrawer: true });
227
+ await waitRAF();
228
+
229
+ const backdrop = wrapper.find('.fixed.bottom-0.left-0.right-0.top-0');
230
+ await backdrop.trigger('click');
231
+ await waitRAF();
232
+
233
+ if (!shouldStayOpen) {
234
+ await new Promise((resolve) => setTimeout(resolve, 50));
235
+ }
236
+
237
+ expect(wrapper.find('[data-drawer-id]').exists()).toBe(shouldStayOpen);
238
+
239
+ wrapper.unmount();
240
+ });
241
+
242
+ it('stays mounted in DOM when live prop is true', async () => {
243
+ const wrapper = createWrapperContainer({
244
+ live: true,
245
+ });
246
+
247
+ // Should be in DOM even before showing
248
+ expect(wrapper.find('#drawer-host').exists()).toBe(true);
249
+
250
+ // Should stay in DOM after closing
251
+ await wrapper.setData({ showDrawer: true });
252
+ await wrapper.setData({ showDrawer: false });
253
+
254
+ expect(wrapper.find('#drawer-host').exists()).toBe(true);
255
+
256
+ wrapper.unmount();
257
+ });
258
+
259
+ it('calls close method when close button is clicked', async () => {
260
+ const wrapper = createWrapperContainer({
261
+ enableClose: true,
262
+ });
263
+
264
+ await wrapper.setData({ showDrawer: true });
265
+
266
+ const drawerComponent = wrapper.findComponent(PDrawer);
267
+
268
+ const closeBtn = wrapper.findComponent({ name: 'PCloseBtn' });
269
+ await closeBtn.trigger('click');
270
+ await new Promise((resolve) => setTimeout(resolve, 50));
271
+
272
+ // Check that the drawer was closed (emits update:modelValue)
273
+ expect(drawerComponent.emitted()['update:modelValue']).toBeTruthy();
274
+ expect(drawerComponent.emitted()['update:modelValue'][0]).toEqual([false]);
275
+
276
+ wrapper.unmount();
277
+ });
278
+
279
+ it('uses custom transition classes when provided', async () => {
280
+ const wrapper = createWrapperContainer({
281
+ inClass: 'custom-slide-in',
282
+ outClass: 'custom-slide-out',
283
+ });
284
+
285
+ await wrapper.setData({ showDrawer: true });
286
+
287
+ const drawerComponent = wrapper.findComponent(PDrawer);
288
+
289
+ // Test that the custom classes are passed as props
290
+ expect(drawerComponent.props('inClass')).toBe('custom-slide-in');
291
+ expect(drawerComponent.props('outClass')).toBe('custom-slide-out');
292
+
293
+ wrapper.unmount();
294
+ });
295
+
296
+ it('uses position-based transition classes by default', async () => {
297
+ const wrapperRight = createWrapperContainer({ position: 'right' });
298
+ await wrapperRight.setData({ showDrawer: true });
299
+
300
+ expect(wrapperRight.findComponent(PDrawer).vm.transitionInClass).toBe('slideInRight');
301
+ expect(wrapperRight.findComponent(PDrawer).vm.transitionOutClass).toBe('slideOutRight');
302
+
303
+ wrapperRight.unmount();
304
+
305
+ const wrapperLeft = createWrapperContainer({ position: 'left' });
306
+ await wrapperLeft.setData({ showDrawer: true });
307
+
308
+ expect(wrapperLeft.findComponent(PDrawer).vm.transitionInClass).toBe('slideInLeft');
309
+ expect(wrapperLeft.findComponent(PDrawer).vm.transitionOutClass).toBe('slideOutLeft');
310
+
311
+ wrapperLeft.unmount();
312
+ });
313
+
314
+ it('emits all lifecycle events', async () => {
315
+ const wrapper = createWrapperContainer();
316
+ const drawerComponent = wrapper.findComponent(PDrawer);
317
+
318
+ // Test opening events
319
+ await wrapper.setData({ showDrawer: true });
320
+ await sleep(100);
321
+
322
+ expect(drawerComponent.emitted()['before-open']).toBeTruthy();
323
+ expect(drawerComponent.emitted().opening).toBeTruthy();
324
+ expect(drawerComponent.emitted().opened).toBeTruthy();
325
+
326
+ // Test closing events
327
+ await wrapper.setData({ showDrawer: false });
328
+ await sleep(100);
329
+
330
+ expect(drawerComponent.emitted()['before-close']).toBeTruthy();
331
+ expect(drawerComponent.emitted().closing).toBeTruthy();
332
+ expect(drawerComponent.emitted().closed).toBeTruthy();
333
+
334
+ wrapper.unmount();
335
+ });
336
+
337
+ it('applies backdrop cursor style based on enableClose', async () => {
338
+ const wrapper = createWrapperContainer({
339
+ showBackdrop: true,
340
+ enableClose: true,
341
+ });
342
+
343
+ await wrapper.setData({ showDrawer: true });
344
+
345
+ const backdrop = wrapper.find('.fixed.bottom-0.left-0.right-0.top-0');
346
+
347
+ expect(backdrop.classes()).toContain('cursor-pointer');
348
+
349
+ wrapper.unmount();
350
+ });
351
+
352
+ it('validates width prop format', async () => {
353
+ const wrapper = createWrapperContainer({
354
+ width: '600px',
355
+ });
356
+
357
+ await wrapper.setData({ showDrawer: true });
358
+
359
+ // Component should render successfully with valid width
360
+ expect(wrapper.find('[data-drawer-id]').exists()).toBe(true);
361
+
362
+ wrapper.unmount();
363
+ });
364
+ });
@@ -39,7 +39,13 @@
39
39
  { 'flex-row-reverse': position === 'right' },
40
40
  ]"
41
41
  >
42
- <PCloseBtn v-if="enableClose" :aria-label="closeLabel" class="flex-0" :disabled="disabled" @click="close" />
42
+ <PCloseBtn
43
+ v-if="enableClose"
44
+ :aria-label="closeLabel || $t('squirrel.close')"
45
+ class="flex-0"
46
+ :disabled="disabled"
47
+ @click="close"
48
+ />
43
49
  <slot name="title">
44
50
  <h3
45
51
  v-if="title"
@@ -201,7 +207,7 @@ export default defineComponent({
201
207
  */
202
208
  closeLabel: {
203
209
  type: String,
204
- default: 'Close',
210
+ default: '',
205
211
  },
206
212
  /**
207
213
  * Whether to show the backdrop behind the drawer.