@pequity/squirrel 10.0.3 → 11.0.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 (76) 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-dropdown-select.js +27 -26
  4. package/dist/cjs/chunks/p-inline-date-picker.js +53 -84
  5. package/dist/cjs/chunks/p-pagination-info.js +2 -2
  6. package/dist/cjs/chunks/p-pagination.js +13 -11
  7. package/dist/cjs/chunks/p-tabs-pills.js +8 -8
  8. package/dist/cjs/dateLocale.js +886 -0
  9. package/dist/cjs/index.js +103 -50
  10. package/dist/cjs/p-date-picker.js +146 -2
  11. package/dist/cjs/p-drawer.js +4 -4
  12. package/dist/cjs/p-input-search.js +5 -4
  13. package/dist/cjs/p-modal.js +3 -3
  14. package/dist/cjs/p-select-pill.js +1 -1
  15. package/dist/es/chunks/p-action-bar.js +18 -15
  16. package/dist/es/chunks/p-dropdown-select.js +27 -26
  17. package/dist/es/chunks/p-inline-date-picker.js +52 -83
  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/dateLocale.js +886 -0
  22. package/dist/es/index.js +152 -99
  23. package/dist/es/p-date-picker.js +146 -2
  24. package/dist/es/p-drawer.js +4 -4
  25. package/dist/es/p-input-search.js +5 -4
  26. package/dist/es/p-modal.js +3 -3
  27. package/dist/es/p-select-pill.js +1 -1
  28. package/dist/squirrel/components/p-date-picker/p-date-picker.vue.d.ts +19 -23
  29. package/dist/squirrel/components/p-drawer/p-drawer.vue.d.ts +2 -2
  30. package/dist/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue.d.ts +12 -13
  31. package/dist/squirrel/components/p-modal/p-modal.vue.d.ts +2 -2
  32. package/dist/squirrel/components/p-steps/p-steps.vue.d.ts +1 -1
  33. package/dist/squirrel/index.d.ts +1 -0
  34. package/dist/squirrel/plugin/index.d.ts +11 -0
  35. package/dist/squirrel/utils/dateLocale.d.ts +2 -0
  36. package/dist/squirrel.css +73 -40
  37. package/package.json +33 -29
  38. package/squirrel/components/p-action-bar/p-action-bar.vue +4 -1
  39. package/squirrel/components/p-btn/p-btn.spec.js +0 -1
  40. package/squirrel/components/p-checkbox/p-checkbox.stories.js +2 -2
  41. package/squirrel/components/p-date-picker/p-date-picker.spec.js +49 -4
  42. package/squirrel/components/p-date-picker/p-date-picker.vue +85 -12
  43. package/squirrel/components/p-drawer/p-drawer.spec.js +364 -0
  44. package/squirrel/components/p-drawer/p-drawer.vue +8 -2
  45. package/squirrel/components/p-dropdown/p-dropdown.spec.js +252 -55
  46. package/squirrel/components/p-dropdown-select/p-dropdown-select.vue +16 -12
  47. package/squirrel/components/p-file-upload/p-file-upload.spec.js +0 -1
  48. package/squirrel/components/p-file-upload/p-file-upload.vue +26 -9
  49. package/squirrel/components/p-inline-date-picker/p-inline-date-picker.spec.js +33 -18
  50. package/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue +23 -10
  51. package/squirrel/components/p-input-search/p-input-search.vue +2 -2
  52. package/squirrel/components/p-modal/p-modal.vue +1 -1
  53. package/squirrel/components/p-pagination/p-pagination.vue +3 -3
  54. package/squirrel/components/p-pagination-info/p-pagination-info.vue +2 -2
  55. package/squirrel/components/p-progress-bar/{p-progess-bar.spec.js → p-progress-bar.spec.js} +7 -5
  56. package/squirrel/components/p-select-btn/p-select-btn.spec.js +104 -0
  57. package/squirrel/components/p-select-list/p-select-list.vue +7 -5
  58. package/squirrel/components/p-select-pill/p-select-pill.spec.js +114 -0
  59. package/squirrel/components/p-select-pill/p-select-pill.vue +1 -1
  60. package/squirrel/components/p-table/usePTableColResize.spec.js +123 -11
  61. package/squirrel/components/p-table/usePTableHeaderWrap.spec.js +1 -1
  62. package/squirrel/components/p-table/usePTableRowVirtualizer.spec.js +207 -0
  63. package/squirrel/components/p-table-header-cell/p-table-header-cell.stories.js +3 -0
  64. package/squirrel/components/p-table-sort/p-table-sort.vue +4 -4
  65. package/squirrel/components/p-tabs-pills/p-tabs-pills.vue +1 -1
  66. package/squirrel/index.spec.js +5 -0
  67. package/squirrel/index.ts +1 -0
  68. package/squirrel/locales/en-US.json +47 -0
  69. package/squirrel/locales/fr-CA.json +47 -0
  70. package/squirrel/plugin/index.spec.ts +140 -0
  71. package/squirrel/plugin/index.ts +54 -0
  72. package/squirrel/utils/dateLocale.spec.ts +20 -0
  73. package/squirrel/utils/dateLocale.ts +19 -0
  74. package/squirrel/utils/listKeyboardNavigation.spec.js +58 -0
  75. package/dist/cjs/chunks/p-date-picker.js +0 -169
  76. package/dist/es/chunks/p-date-picker.js +0 -170
@@ -1,5 +1,15 @@
1
1
  <template>
2
- <VueDatePicker v-model="model" :class="[{ hidden: $attrs.hidden }, $attrs.class]" v-bind="datePickerProps">
2
+ <VueDatePicker
3
+ v-model="model"
4
+ :class="[
5
+ { hidden: $attrs.hidden },
6
+ $attrs.class,
7
+ { 'has-label': !!label },
8
+ { 'has-error': !!errorMsg },
9
+ `size-${$attrs.size || 'md'}`,
10
+ ]"
11
+ v-bind="datePickerProps"
12
+ >
3
13
  <template #dp-input="{ value, onInput, onEnter, onTab, onFocus, onBlur, onClear }">
4
14
  <PInput
5
15
  :model-value="value"
@@ -16,8 +26,10 @@
16
26
 
17
27
  <script setup lang="ts">
18
28
  import PInput from '@squirrel/components/p-input/p-input.vue';
19
- import VueDatePicker, { type VueDatePickerProps } from '@vuepic/vue-datepicker';
29
+ import { getDateFnsLocale } from '@squirrel/utils/dateLocale';
30
+ import { type RootProps, VueDatePicker } from '@vuepic/vue-datepicker';
20
31
  import { computed, useAttrs } from 'vue';
32
+ import { useI18n } from 'vue-i18n';
21
33
 
22
34
  /**
23
35
  * A date picker component that displays a dropdown calendar for date selection.
@@ -31,7 +43,14 @@ defineOptions({
31
43
  inheritAttrs: false,
32
44
  });
33
45
 
34
- type Props = {
46
+ // Extend RootProps to inherit all VueDatePicker props and add our custom ones
47
+ // Exclude modelValue since we use defineModel() for v-model handling
48
+ type Props = Omit<RootProps, 'modelValue'> & {
49
+ /**
50
+ * The name of the date picker field.
51
+ * Passed to the underlying PInput component.
52
+ */
53
+ name?: string;
35
54
  /**
36
55
  * Text label for the date picker field.
37
56
  * Passed to the underlying PInput component.
@@ -47,36 +66,45 @@ type Props = {
47
66
  * Passed to the underlying PInput component and adds visual indicator.
48
67
  */
49
68
  required?: boolean;
50
- } & VueDatePickerProps;
69
+ };
51
70
 
52
71
  const props = withDefaults(defineProps<Props>(), {
72
+ // Custom component defaults
73
+ name: '',
53
74
  label: '',
54
75
  errorMsg: '',
55
76
  required: false,
56
- inline: false,
77
+ // VueDatePicker props with intentional defaults
78
+ placeholder: '',
57
79
  autoApply: true,
58
- enableTimePicker: false,
80
+ timeConfig: () => ({ enableTimePicker: false }),
59
81
  modelType: 'yyyy-MM-dd',
60
82
  hideOffsetDates: true,
61
83
  weekStart: 0,
62
84
  textInput: true,
63
- format: 'dd-MMM-yyyy',
85
+ formats: () => ({ input: 'dd-MMM-yyyy' }),
64
86
  });
65
87
 
66
88
  /**
67
89
  * The selected date value (v-model).
68
- * Supports two-way binding for form inputs.
90
+ * Supports all VueDatePicker model types: Date, Date[], string, TimeModel, etc.
69
91
  */
70
- const model = defineModel<Date | string | null>({ default: '' });
92
+ const model = defineModel<RootProps['modelValue']>();
71
93
 
72
94
  // Data
95
+ const { locale } = useI18n();
73
96
  const attrs = useAttrs();
74
97
 
75
98
  // Computed
76
99
  const datePickerProps = computed(() => {
77
- const { modelValue: _, ...propsWithoutModelValue } = props;
100
+ const { name, label, errorMsg, required, ...vueDatePickerProps } = props;
101
+ const { class: classes, style, ...attrsWithoutClassAndStyle } = attrs;
78
102
 
79
- return propsWithoutModelValue;
103
+ return {
104
+ ...vueDatePickerProps,
105
+ ...attrsWithoutClassAndStyle,
106
+ locale: getDateFnsLocale(locale.value),
107
+ };
80
108
  });
81
109
 
82
110
  const inputPropsAndAttrs = computed(() => {
@@ -86,7 +114,11 @@ const inputPropsAndAttrs = computed(() => {
86
114
  res.errorMsg = props.errorMsg;
87
115
  res.required = props.required;
88
116
  res.disabled = props.disabled;
89
- res.placeholder = props.placeholder ? props.placeholder : props.format;
117
+ res.placeholder = props.placeholder
118
+ ? props.placeholder
119
+ : typeof props.formats?.input === 'string'
120
+ ? props.formats.input
121
+ : 'dd-MMM-yyyy';
90
122
  res.name = props.name;
91
123
  res.readonly = props.readonly;
92
124
 
@@ -103,3 +135,44 @@ const handleInput = (e: Event, onInputFn: (e: Event) => void, onClearFn: (e: Eve
103
135
  return onInputFn(e);
104
136
  };
105
137
  </script>
138
+
139
+ <style scoped>
140
+ /* Fix clear button positioning when label is present */
141
+ .has-label.size-sm :deep(.dp--clear-btn) {
142
+ margin-top: 11px;
143
+ }
144
+
145
+ .has-label.size-md :deep(.dp--clear-btn) {
146
+ margin-top: 12px;
147
+ }
148
+
149
+ .has-label.size-lg :deep(.dp--clear-btn) {
150
+ margin-top: 14px;
151
+ }
152
+
153
+ /* Fix clear button positioning when error is present */
154
+ .has-error.size-sm :deep(.dp--clear-btn) {
155
+ margin-top: -11px;
156
+ }
157
+
158
+ .has-error.size-md :deep(.dp--clear-btn) {
159
+ margin-top: -11px;
160
+ }
161
+
162
+ .has-error.size-lg :deep(.dp--clear-btn) {
163
+ margin-top: -11px;
164
+ }
165
+
166
+ /* Fix clear button positioning when label and error are present */
167
+ .has-label.has-error.size-sm :deep(.dp--clear-btn) {
168
+ margin-top: 0px;
169
+ }
170
+
171
+ .has-label.has-error.size-md :deep(.dp--clear-btn) {
172
+ margin-top: 1px;
173
+ }
174
+
175
+ .has-label.has-error.size-lg :deep(.dp--clear-btn) {
176
+ margin-top: 3px;
177
+ }
178
+ </style>
@@ -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.