@pequity/squirrel 10.0.3 → 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.
- package/README.md +20 -1
- package/dist/cjs/chunks/p-action-bar.js +17 -14
- package/dist/cjs/chunks/p-date-picker.js +3 -1
- package/dist/cjs/chunks/p-dropdown-select.js +27 -26
- package/dist/cjs/chunks/p-inline-date-picker.js +3 -1
- package/dist/cjs/chunks/p-pagination-info.js +2 -2
- package/dist/cjs/chunks/p-pagination.js +13 -11
- package/dist/cjs/chunks/p-tabs-pills.js +8 -8
- package/dist/cjs/index.js +101 -48
- package/dist/cjs/p-drawer.js +4 -4
- package/dist/cjs/p-input-search.js +5 -4
- package/dist/cjs/p-modal.js +3 -3
- package/dist/es/chunks/p-action-bar.js +18 -15
- package/dist/es/chunks/p-date-picker.js +3 -1
- package/dist/es/chunks/p-dropdown-select.js +27 -26
- package/dist/es/chunks/p-inline-date-picker.js +3 -1
- package/dist/es/chunks/p-pagination-info.js +2 -2
- package/dist/es/chunks/p-pagination.js +13 -11
- package/dist/es/chunks/p-tabs-pills.js +8 -8
- package/dist/es/index.js +102 -49
- package/dist/es/p-drawer.js +4 -4
- package/dist/es/p-input-search.js +5 -4
- package/dist/es/p-modal.js +3 -3
- package/dist/squirrel/index.d.ts +1 -0
- package/dist/squirrel/plugin/index.d.ts +11 -0
- package/dist/squirrel.css +40 -40
- package/package.json +28 -25
- package/squirrel/components/p-action-bar/p-action-bar.vue +4 -1
- package/squirrel/components/p-btn/p-btn.spec.js +0 -1
- package/squirrel/components/p-checkbox/p-checkbox.stories.js +2 -2
- package/squirrel/components/p-date-picker/p-date-picker.vue +3 -2
- package/squirrel/components/p-drawer/p-drawer.spec.js +364 -0
- package/squirrel/components/p-drawer/p-drawer.vue +8 -2
- package/squirrel/components/p-dropdown/p-dropdown.spec.js +252 -55
- package/squirrel/components/p-dropdown-select/p-dropdown-select.vue +16 -12
- package/squirrel/components/p-file-upload/p-file-upload.spec.js +0 -1
- package/squirrel/components/p-file-upload/p-file-upload.vue +26 -9
- package/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue +3 -1
- package/squirrel/components/p-input-search/p-input-search.vue +2 -2
- package/squirrel/components/p-modal/p-modal.vue +1 -1
- package/squirrel/components/p-pagination/p-pagination.vue +3 -3
- package/squirrel/components/p-pagination-info/p-pagination-info.vue +2 -2
- package/squirrel/components/p-progress-bar/{p-progess-bar.spec.js → p-progress-bar.spec.js} +7 -5
- package/squirrel/components/p-select-btn/p-select-btn.spec.js +104 -0
- package/squirrel/components/p-select-list/p-select-list.vue +7 -5
- package/squirrel/components/p-select-pill/p-select-pill.spec.js +114 -0
- package/squirrel/components/p-table/usePTableColResize.spec.js +123 -11
- package/squirrel/components/p-table/usePTableHeaderWrap.spec.js +1 -1
- package/squirrel/components/p-table/usePTableRowVirtualizer.spec.js +207 -0
- package/squirrel/components/p-table-header-cell/p-table-header-cell.stories.js +3 -0
- package/squirrel/components/p-table-sort/p-table-sort.vue +4 -4
- package/squirrel/components/p-tabs-pills/p-tabs-pills.vue +1 -1
- package/squirrel/index.spec.js +5 -0
- package/squirrel/index.ts +1 -0
- package/squirrel/locales/en-US.json +47 -0
- package/squirrel/locales/fr-CA.json +47 -0
- package/squirrel/plugin/index.spec.ts +140 -0
- package/squirrel/plugin/index.ts +54 -0
- package/squirrel/utils/listKeyboardNavigation.spec.js +58 -0
|
@@ -18,15 +18,29 @@ const createMockedKbdNavigationSvc = () => {
|
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
};
|
|
21
|
+
const createVDropdownStub = (options = {}) => {
|
|
22
|
+
const { popperMethods = {} } = options;
|
|
24
23
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const Popper = {
|
|
25
|
+
template: '<div class="popper"><slot /></div>',
|
|
26
|
+
data() {
|
|
27
|
+
return {
|
|
28
|
+
isShown: false,
|
|
29
|
+
$_referenceNode: null,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
methods: {
|
|
33
|
+
hide: vi.fn(),
|
|
34
|
+
$_detachPopperNode: vi.fn(),
|
|
35
|
+
$emit: vi.fn(),
|
|
36
|
+
...popperMethods,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const PopperContent = {
|
|
41
|
+
template: '<div class="popper-content"><slot /></div>',
|
|
42
|
+
};
|
|
28
43
|
|
|
29
|
-
const createVDropdownStub = () => {
|
|
30
44
|
return {
|
|
31
45
|
template: `
|
|
32
46
|
<div ref="vPopper">
|
|
@@ -38,10 +52,7 @@ const createVDropdownStub = () => {
|
|
|
38
52
|
</PopperContent>
|
|
39
53
|
</div>
|
|
40
54
|
`,
|
|
41
|
-
components: {
|
|
42
|
-
Popper,
|
|
43
|
-
PopperContent,
|
|
44
|
-
},
|
|
55
|
+
components: { Popper, PopperContent },
|
|
45
56
|
mounted() {
|
|
46
57
|
this.$emit('show');
|
|
47
58
|
},
|
|
@@ -51,26 +62,40 @@ const createVDropdownStub = () => {
|
|
|
51
62
|
};
|
|
52
63
|
};
|
|
53
64
|
|
|
65
|
+
const createWrapper = (options = {}) => {
|
|
66
|
+
const { props = {}, slots = {}, attrs = {}, global = {}, stubOptions = {}, ...wrapperOptions } = options;
|
|
67
|
+
|
|
68
|
+
return createWrapperFor(PDropdown, {
|
|
69
|
+
props: {
|
|
70
|
+
enableArrowNavigation: false,
|
|
71
|
+
enableCloseOnEsc: false,
|
|
72
|
+
...props,
|
|
73
|
+
},
|
|
74
|
+
slots: {
|
|
75
|
+
default: '<button>Open popper</button>',
|
|
76
|
+
popper: '<div>Dropdown content</div>',
|
|
77
|
+
...slots,
|
|
78
|
+
},
|
|
79
|
+
attrs,
|
|
80
|
+
global: {
|
|
81
|
+
stubs: {
|
|
82
|
+
VDropdown: createVDropdownStub(stubOptions),
|
|
83
|
+
},
|
|
84
|
+
...global,
|
|
85
|
+
},
|
|
86
|
+
...wrapperOptions,
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
54
90
|
describe('PDropdown.vue', () => {
|
|
55
91
|
it('renders correctly', async () => {
|
|
56
|
-
const wrapper =
|
|
92
|
+
const wrapper = createWrapper({
|
|
57
93
|
props: {
|
|
58
|
-
enableArrowNavigation: false,
|
|
59
|
-
enableCloseOnEsc: false,
|
|
60
94
|
triggerStyle: {
|
|
61
95
|
width: '200px',
|
|
62
96
|
display: 'flex',
|
|
63
97
|
},
|
|
64
98
|
},
|
|
65
|
-
slots: {
|
|
66
|
-
default: `<button>Open popper</button>`,
|
|
67
|
-
popper: `<div>Dropdown content</div>`,
|
|
68
|
-
},
|
|
69
|
-
global: {
|
|
70
|
-
stubs: {
|
|
71
|
-
VDropdown: createVDropdownStub(),
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
99
|
});
|
|
75
100
|
|
|
76
101
|
const popper = wrapper.find('.popper');
|
|
@@ -82,21 +107,7 @@ describe('PDropdown.vue', () => {
|
|
|
82
107
|
});
|
|
83
108
|
|
|
84
109
|
it(`sets the correct defaults`, async () => {
|
|
85
|
-
const wrapper =
|
|
86
|
-
props: {
|
|
87
|
-
enableArrowNavigation: false,
|
|
88
|
-
enableCloseOnEsc: false,
|
|
89
|
-
},
|
|
90
|
-
slots: {
|
|
91
|
-
default: `<button>Open popper</button>`,
|
|
92
|
-
popper: `<div>Dropdown content</div>`,
|
|
93
|
-
},
|
|
94
|
-
global: {
|
|
95
|
-
stubs: {
|
|
96
|
-
VDropdown: createVDropdownStub(),
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
});
|
|
110
|
+
const wrapper = createWrapper();
|
|
100
111
|
|
|
101
112
|
const res = {
|
|
102
113
|
triggers: 'click',
|
|
@@ -115,19 +126,9 @@ describe('PDropdown.vue', () => {
|
|
|
115
126
|
it('initializes the keyboard navigation svc', async () => {
|
|
116
127
|
setupListKeyboardNavigation.mockImplementation(() => createMockedKbdNavigationSvc());
|
|
117
128
|
|
|
118
|
-
|
|
129
|
+
createWrapper({
|
|
119
130
|
props: {
|
|
120
131
|
enableArrowNavigation: true,
|
|
121
|
-
enableCloseOnEsc: false,
|
|
122
|
-
},
|
|
123
|
-
slots: {
|
|
124
|
-
default: `<button>Open popper</button>`,
|
|
125
|
-
popper: `<div>Dropdown content</div>`,
|
|
126
|
-
},
|
|
127
|
-
global: {
|
|
128
|
-
stubs: {
|
|
129
|
-
VDropdown: createVDropdownStub(),
|
|
130
|
-
},
|
|
131
132
|
},
|
|
132
133
|
});
|
|
133
134
|
|
|
@@ -137,24 +138,220 @@ describe('PDropdown.vue', () => {
|
|
|
137
138
|
it('destroys the keyboard navigation svc', async () => {
|
|
138
139
|
setupListKeyboardNavigation.mockImplementation(() => createMockedKbdNavigationSvc());
|
|
139
140
|
|
|
140
|
-
const wrapper =
|
|
141
|
+
const wrapper = createWrapper({
|
|
141
142
|
props: {
|
|
142
143
|
enableArrowNavigation: true,
|
|
143
|
-
enableCloseOnEsc: false,
|
|
144
144
|
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
wrapper.unmount();
|
|
148
|
+
|
|
149
|
+
expect(destroyFn).toHaveBeenCalled();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it.each([
|
|
153
|
+
[true, 'enables'],
|
|
154
|
+
[false, 'disables'],
|
|
155
|
+
])('%s arrow navigation when enableArrowNavigation is %s', async (enableArrowNavigation, description) => {
|
|
156
|
+
setupListKeyboardNavigation.mockImplementation(() => createMockedKbdNavigationSvc());
|
|
157
|
+
|
|
158
|
+
createWrapper({
|
|
159
|
+
props: {
|
|
160
|
+
enableArrowNavigation,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (enableArrowNavigation) {
|
|
165
|
+
expect(setupListKeyboardNavigation).toHaveBeenCalledTimes(1);
|
|
166
|
+
} else {
|
|
167
|
+
expect(setupListKeyboardNavigation).not.toHaveBeenCalled();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('handles escape key when enableCloseOnEsc is true', async () => {
|
|
172
|
+
const mockHide = vi.fn();
|
|
173
|
+
|
|
174
|
+
const wrapper = createWrapper({
|
|
175
|
+
props: {
|
|
176
|
+
enableCloseOnEsc: true,
|
|
177
|
+
},
|
|
178
|
+
stubOptions: {
|
|
179
|
+
popperMethods: { hide: mockHide },
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Simulate escape key press
|
|
184
|
+
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' });
|
|
185
|
+
document.dispatchEvent(escapeEvent);
|
|
186
|
+
|
|
187
|
+
expect(mockHide).toHaveBeenCalled();
|
|
188
|
+
|
|
189
|
+
wrapper.unmount();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('does not handle escape key when enableCloseOnEsc is false', async () => {
|
|
193
|
+
const mockHide = vi.fn();
|
|
194
|
+
|
|
195
|
+
const wrapper = createWrapper({
|
|
196
|
+
stubOptions: {
|
|
197
|
+
popperMethods: { hide: mockHide },
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Simulate escape key press
|
|
202
|
+
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' });
|
|
203
|
+
document.dispatchEvent(escapeEvent);
|
|
204
|
+
|
|
205
|
+
expect(mockHide).not.toHaveBeenCalled();
|
|
206
|
+
|
|
207
|
+
wrapper.unmount();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('applies custom trigger styles', async () => {
|
|
211
|
+
const customStyle = {
|
|
212
|
+
width: '300px',
|
|
213
|
+
height: '50px',
|
|
214
|
+
backgroundColor: 'red',
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const wrapper = createWrapper({
|
|
218
|
+
props: {
|
|
219
|
+
triggerStyle: customStyle,
|
|
220
|
+
},
|
|
221
|
+
slots: {
|
|
222
|
+
default: '<button>Custom styled trigger</button>',
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const popper = wrapper.find('.popper');
|
|
227
|
+
|
|
228
|
+
expect(popper.attributes().style).toBe('width: 300px; height: 50px; background-color: red;');
|
|
229
|
+
|
|
230
|
+
wrapper.unmount();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('uses default trigger style when not provided', async () => {
|
|
234
|
+
const wrapper = createWrapper({
|
|
145
235
|
slots: {
|
|
146
|
-
default:
|
|
147
|
-
|
|
236
|
+
default: '<button>Default styled trigger</button>',
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const popper = wrapper.find('.popper');
|
|
241
|
+
|
|
242
|
+
expect(popper.attributes().style).toBe('display: inline-block;');
|
|
243
|
+
|
|
244
|
+
wrapper.unmount();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('reinitializes keyboard navigation on subsequent shows', async () => {
|
|
248
|
+
setupListKeyboardNavigation.mockImplementation(() => createMockedKbdNavigationSvc());
|
|
249
|
+
|
|
250
|
+
const createMultiShowStub = () => ({
|
|
251
|
+
template: `
|
|
252
|
+
<div ref="vPopper">
|
|
253
|
+
<Popper ref="popper">
|
|
254
|
+
<slot />
|
|
255
|
+
</Popper>
|
|
256
|
+
<PopperContent ref="popperContent">
|
|
257
|
+
<slot name="popper" />
|
|
258
|
+
</PopperContent>
|
|
259
|
+
</div>
|
|
260
|
+
`,
|
|
261
|
+
components: {
|
|
262
|
+
Popper: { template: '<div class="popper"><slot /></div>' },
|
|
263
|
+
PopperContent: { template: '<div class="popper-content"><slot /></div>' },
|
|
264
|
+
},
|
|
265
|
+
mounted() {
|
|
266
|
+
// Simulate multiple show events
|
|
267
|
+
this.$emit('show');
|
|
268
|
+
this.$emit('show');
|
|
269
|
+
},
|
|
270
|
+
beforeUnmount() {
|
|
271
|
+
this.$emit('hide');
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
createWrapper({
|
|
276
|
+
props: {
|
|
277
|
+
enableArrowNavigation: true,
|
|
148
278
|
},
|
|
149
279
|
global: {
|
|
150
280
|
stubs: {
|
|
151
|
-
VDropdown:
|
|
281
|
+
VDropdown: createMultiShowStub(),
|
|
152
282
|
},
|
|
153
283
|
},
|
|
154
284
|
});
|
|
155
285
|
|
|
286
|
+
// First call creates the service, second call calls init
|
|
287
|
+
expect(setupListKeyboardNavigation).toHaveBeenCalledTimes(1);
|
|
288
|
+
expect(initFn).toHaveBeenCalledTimes(1);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('updates reference element correctly', async () => {
|
|
292
|
+
const mockDetachPopperNode = vi.fn();
|
|
293
|
+
const mockEmit = vi.fn();
|
|
294
|
+
|
|
295
|
+
const wrapper = createWrapper({
|
|
296
|
+
stubOptions: {
|
|
297
|
+
popperMethods: {
|
|
298
|
+
$_detachPopperNode: mockDetachPopperNode,
|
|
299
|
+
$emit: mockEmit,
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const newReference = document.createElement('button');
|
|
305
|
+
newReference.textContent = 'New reference';
|
|
306
|
+
|
|
307
|
+
// Should not throw error when updating reference
|
|
308
|
+
await expect(wrapper.vm.updateReference(newReference)).resolves.toBeUndefined();
|
|
309
|
+
|
|
310
|
+
expect(mockDetachPopperNode).toHaveBeenCalled();
|
|
311
|
+
|
|
156
312
|
wrapper.unmount();
|
|
313
|
+
});
|
|
157
314
|
|
|
158
|
-
|
|
315
|
+
it('throws error when updateReference is called with invalid reference', async () => {
|
|
316
|
+
const wrapper = createWrapper();
|
|
317
|
+
|
|
318
|
+
// Should throw error when reference is null/undefined
|
|
319
|
+
await expect(wrapper.vm.updateReference(null)).rejects.toThrow('Reference element is required');
|
|
320
|
+
await expect(wrapper.vm.updateReference(undefined)).rejects.toThrow('Reference element is required');
|
|
321
|
+
|
|
322
|
+
wrapper.unmount();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('forwards all attributes to VDropdown component', async () => {
|
|
326
|
+
const wrapper = createWrapper({
|
|
327
|
+
attrs: {
|
|
328
|
+
'data-testid': 'custom-dropdown',
|
|
329
|
+
'aria-label': 'Custom dropdown menu',
|
|
330
|
+
'custom-prop': 'custom-value',
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Check that custom attributes are forwarded along with defaults
|
|
335
|
+
expect(wrapper.attributes()['data-testid']).toBe('custom-dropdown');
|
|
336
|
+
expect(wrapper.attributes()['aria-label']).toBe('Custom dropdown menu');
|
|
337
|
+
expect(wrapper.attributes()['custom-prop']).toBe('custom-value');
|
|
338
|
+
|
|
339
|
+
wrapper.unmount();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('properly handles slot forwarding to VDropdown', async () => {
|
|
343
|
+
const wrapper = createWrapper({
|
|
344
|
+
slots: {
|
|
345
|
+
default: '<button class="trigger-button">Trigger Content</button>',
|
|
346
|
+
popper: '<div class="popper-content-test">Popper Content</div>',
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect(wrapper.find('.trigger-button').exists()).toBe(true);
|
|
351
|
+
expect(wrapper.find('.trigger-button').text()).toBe('Trigger Content');
|
|
352
|
+
expect(wrapper.find('.popper-content-test').exists()).toBe(true);
|
|
353
|
+
expect(wrapper.find('.popper-content-test').text()).toBe('Popper Content');
|
|
354
|
+
|
|
355
|
+
wrapper.unmount();
|
|
159
356
|
});
|
|
160
357
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
:class="[{ hidden: $attrs.hidden }, $attrs.class]"
|
|
5
5
|
:data-has-error="!!errorMsg"
|
|
6
6
|
:style="style"
|
|
7
|
-
aria-label="
|
|
7
|
+
:aria-label="$t('squirrel.dropdown_select_aria_label')"
|
|
8
8
|
>
|
|
9
9
|
<label v-if="label" :class="labelClasses">{{ label }}</label>
|
|
10
10
|
<PDropdown
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
<button
|
|
49
49
|
type="button"
|
|
50
50
|
class="flex items-center justify-center text-p-gray-40 hover:text-p-gray-60"
|
|
51
|
-
aria-label="
|
|
51
|
+
:aria-label="$t('squirrel.dropdown_select_remove_item')"
|
|
52
52
|
data-test="pill-remove"
|
|
53
53
|
@click.stop="select($event, item[itemValue])"
|
|
54
54
|
>
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
{{
|
|
61
61
|
multiple
|
|
62
62
|
? selectedItems.length === computedItems.length
|
|
63
|
-
? '
|
|
64
|
-
: `${selectedItems.length}
|
|
63
|
+
? $t('squirrel.dropdown_select_all_options_selected')
|
|
64
|
+
: `${selectedItems.length} ${$t('squirrel.dropdown_select_options', selectedItems.length)} ${$t('squirrel.dropdown_select_selected')}`
|
|
65
65
|
: selectedItems[0][itemText]
|
|
66
66
|
}}
|
|
67
67
|
</div>
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
v-if="clearable && selectedItems.length"
|
|
72
72
|
class="absolute right-9 flex h-6 items-center justify-center text-base text-p-gray-40 hover:text-p-gray-60"
|
|
73
73
|
:class="[CLEAR_BUTTON_SPACING[size]]"
|
|
74
|
-
aria-label="
|
|
74
|
+
:aria-label="$t('squirrel.dropdown_select_clear_selection')"
|
|
75
75
|
@click.stop="clearAll"
|
|
76
76
|
>
|
|
77
77
|
<PIcon icon="fe:close" />
|
|
@@ -86,7 +86,9 @@
|
|
|
86
86
|
ref="actionsContainer"
|
|
87
87
|
class="flex flex-row justify-between text-xs font-semibold text-primary"
|
|
88
88
|
>
|
|
89
|
-
<p class="text-p-purple-60">
|
|
89
|
+
<p class="text-p-purple-60">
|
|
90
|
+
{{ $t('squirrel.dropdown_select_items', computedItems.length) }}
|
|
91
|
+
</p>
|
|
90
92
|
<div class="flex flex-row">
|
|
91
93
|
<a
|
|
92
94
|
v-if="computedItems.length === internalItems.length"
|
|
@@ -95,21 +97,21 @@
|
|
|
95
97
|
]"
|
|
96
98
|
@click="selectAll"
|
|
97
99
|
>
|
|
98
|
-
|
|
100
|
+
{{ $t('squirrel.dropdown_select_select_all') }}
|
|
99
101
|
</a>
|
|
100
102
|
<a
|
|
101
103
|
v-else
|
|
102
104
|
:class="[computedInsideSelected ? 'pointer-events-none opacity-50' : 'cursor-pointer']"
|
|
103
105
|
@click="selectAll"
|
|
104
106
|
>
|
|
105
|
-
|
|
107
|
+
{{ $t('squirrel.dropdown_select_select_all_filtered') }}
|
|
106
108
|
</a>
|
|
107
109
|
<span class="px-1 leading-none">.</span>
|
|
108
110
|
<a
|
|
109
111
|
:class="[selectedItems.length ? 'cursor-pointer' : 'pointer-events-none opacity-50']"
|
|
110
112
|
@click="clearAll"
|
|
111
113
|
>
|
|
112
|
-
|
|
114
|
+
{{ $t('squirrel.dropdown_select_clear_all') }}
|
|
113
115
|
</a>
|
|
114
116
|
</div>
|
|
115
117
|
</div>
|
|
@@ -184,11 +186,13 @@
|
|
|
184
186
|
@click="handleCreate"
|
|
185
187
|
>
|
|
186
188
|
<PIcon icon="fe:plus-circle" />
|
|
187
|
-
|
|
189
|
+
{{ $t('squirrel.dropdown_select_add') }} '{{ search }}'
|
|
188
190
|
</button>
|
|
189
191
|
</template>
|
|
190
|
-
<template v-else-if="creatable">
|
|
191
|
-
|
|
192
|
+
<template v-else-if="creatable">
|
|
193
|
+
{{ $t('squirrel.dropdown_select_no_items_found_type_to_add') }}
|
|
194
|
+
</template>
|
|
195
|
+
<template v-else>{{ $t('squirrel.dropdown_select_no_items_found') }}</template>
|
|
192
196
|
</div>
|
|
193
197
|
</slot>
|
|
194
198
|
</div>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import PFileUpload from '@squirrel/components/p-file-upload/p-file-upload.vue';
|
|
2
2
|
import { createWrapperFor } from '@tests/vitest.helpers';
|
|
3
3
|
import { expect } from 'storybook/test';
|
|
4
|
-
import { vi } from 'vitest';
|
|
5
4
|
|
|
6
5
|
// Mocking the toastification library
|
|
7
6
|
const toastError = vi.fn();
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
errorMsg ? 'border-on-error' : 'border-p-gray-40 hover:border-primary',
|
|
11
11
|
{ 'pointer-events-none opacity-50': disabled },
|
|
12
12
|
]"
|
|
13
|
-
aria-label="
|
|
13
|
+
:aria-label="$t('squirrel.file_upload_dropzone')"
|
|
14
14
|
@dragover.prevent.stop="isDraggingOver = true"
|
|
15
15
|
@dragleave.prevent.stop="isDraggingOver = false"
|
|
16
16
|
@drop.prevent.stop="onDrop"
|
|
@@ -20,12 +20,16 @@
|
|
|
20
20
|
<div v-if="!isDraggingOver" class="flex flex-col items-center">
|
|
21
21
|
<PIcon icon="upload" width="32" class="text-p-gray-50" />
|
|
22
22
|
<div class="mt-2 text-p-gray-50">
|
|
23
|
-
|
|
23
|
+
<I18nT keypath="squirrel.file_upload_drag_or_select" scope="global">
|
|
24
|
+
<template #select>
|
|
25
|
+
<span class="text-p-blue-60">{{ $t('squirrel.file_upload_select', { fileWord }) }}</span>
|
|
26
|
+
</template>
|
|
27
|
+
</I18nT>
|
|
24
28
|
</div>
|
|
25
29
|
</div>
|
|
26
30
|
<div v-else class="flex flex-col items-center">
|
|
27
31
|
<PIcon icon="tdesign:drag-drop" width="32" class="text-primary" />
|
|
28
|
-
<div class="mt-2 text-p-purple-60">
|
|
32
|
+
<div class="mt-2 text-p-purple-60">{{ $t('squirrel.file_upload_drop', { fileWord }) }}</div>
|
|
29
33
|
</div>
|
|
30
34
|
<input
|
|
31
35
|
ref="fileInputRef"
|
|
@@ -61,8 +65,15 @@
|
|
|
61
65
|
<div v-show="errorMsg" :class="errorMsgClasses">{{ errorMsg }}</div>
|
|
62
66
|
<!-- Info -->
|
|
63
67
|
<div class="mt-1 text-xs text-p-gray-40">
|
|
64
|
-
{{ multiple ?
|
|
65
|
-
|
|
68
|
+
{{ multiple ? $t('squirrel.file_upload_max', maxNumberOfFiles) : $t('squirrel.file_upload_one') }}
|
|
69
|
+
{{ acceptFileTypes }}
|
|
70
|
+
{{ $t('squirrel.file_upload_files', multiple ? 2 : 1) }}
|
|
71
|
+
{{
|
|
72
|
+
$t('squirrel.file_upload_with_size_less_than', {
|
|
73
|
+
count: multiple ? 2 : 1,
|
|
74
|
+
maxSize: formatBytes(maxSizeInBytes),
|
|
75
|
+
})
|
|
76
|
+
}}
|
|
66
77
|
</div>
|
|
67
78
|
</div>
|
|
68
79
|
</template>
|
|
@@ -74,6 +85,7 @@ import PIcon from '@squirrel/components/p-icon/p-icon.vue';
|
|
|
74
85
|
import { useInputClasses } from '@squirrel/composables/useInputClasses';
|
|
75
86
|
import { uniq } from 'lodash-es';
|
|
76
87
|
import { computed, onMounted, type PropType, ref, shallowRef } from 'vue';
|
|
88
|
+
import { useI18n } from 'vue-i18n';
|
|
77
89
|
import { useToast } from 'vue-toastification';
|
|
78
90
|
|
|
79
91
|
/**
|
|
@@ -185,10 +197,11 @@ const emit = defineEmits<{
|
|
|
185
197
|
const fileInputRef = shallowRef<HTMLInputElement>();
|
|
186
198
|
const isDraggingOver = ref(false);
|
|
187
199
|
const toast = useToast();
|
|
200
|
+
const { t } = useI18n({ useScope: 'global' });
|
|
188
201
|
const { labelClasses, errorMsgClasses } = useInputClasses(props);
|
|
189
202
|
|
|
190
203
|
// Computed
|
|
191
|
-
const fileWord = computed(() => (props.multiple ?
|
|
204
|
+
const fileWord = computed(() => t('squirrel.file_upload_files', props.multiple ? 2 : 1));
|
|
192
205
|
|
|
193
206
|
const files = computed<FileUploadFile[]>({
|
|
194
207
|
get() {
|
|
@@ -223,7 +236,9 @@ const validateFiles = (filesToUpload: FileUploadFile[]) => {
|
|
|
223
236
|
if (res.length + files.value.length >= props.maxNumberOfFiles) {
|
|
224
237
|
// Show a toast - except for the contradictory config (multiple: true with maxNumberOfFiles: 1)
|
|
225
238
|
if (!(props.multiple && props.maxNumberOfFiles === 1)) {
|
|
226
|
-
toast.error(
|
|
239
|
+
toast.error(
|
|
240
|
+
t('squirrel.file_upload_max_files_exceeded', { count: props.maxNumberOfFiles, fileWord: fileWord.value })
|
|
241
|
+
);
|
|
227
242
|
}
|
|
228
243
|
|
|
229
244
|
break;
|
|
@@ -248,14 +263,16 @@ const validateFiles = (filesToUpload: FileUploadFile[]) => {
|
|
|
248
263
|
const isValidExtension = extension ? props.fileTypes.includes(extension) : false;
|
|
249
264
|
|
|
250
265
|
if (!isValidExtension) {
|
|
251
|
-
toast.error(
|
|
266
|
+
toast.error(t('squirrel.file_upload_files_not_allowed', { extension }));
|
|
252
267
|
continue;
|
|
253
268
|
}
|
|
254
269
|
}
|
|
255
270
|
|
|
256
271
|
// Check filesize
|
|
257
272
|
if ((file.size || 0) > props.maxSizeInBytes) {
|
|
258
|
-
toast.error(
|
|
273
|
+
toast.error(
|
|
274
|
+
t('squirrel.file_upload_file_size_exceeded', { fileName, maxSize: formatBytes(props.maxSizeInBytes) })
|
|
275
|
+
);
|
|
259
276
|
continue;
|
|
260
277
|
}
|
|
261
278
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { useInputClasses } from '@squirrel/composables/useInputClasses';
|
|
15
15
|
import VueDatePicker, { type VueDatePickerProps } from '@vuepic/vue-datepicker';
|
|
16
16
|
import { computed, type StyleValue, useAttrs } from 'vue';
|
|
17
|
+
import { useI18n } from 'vue-i18n';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* An inline date picker component that displays a calendar for date selection.
|
|
@@ -73,6 +74,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
73
74
|
const model = defineModel<Date | string | null>({ default: '' });
|
|
74
75
|
|
|
75
76
|
// Data
|
|
77
|
+
const { locale } = useI18n();
|
|
76
78
|
const attrs = useAttrs();
|
|
77
79
|
const { labelClasses, errorMsgClasses } = useInputClasses(props);
|
|
78
80
|
|
|
@@ -81,7 +83,7 @@ const datePickerProps = computed<VueDatePickerProps>(() => {
|
|
|
81
83
|
const { modelValue: _, ...propsWithoutModelValue } = props;
|
|
82
84
|
const { class: classes, style, ...attrsWithoutClassAndStyle } = attrs;
|
|
83
85
|
|
|
84
|
-
return { ...propsWithoutModelValue, ...attrsWithoutClassAndStyle };
|
|
86
|
+
return { ...propsWithoutModelValue, ...attrsWithoutClassAndStyle, locale: locale.value };
|
|
85
87
|
});
|
|
86
88
|
|
|
87
89
|
const style = computed(() => {
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
<template #suffix>
|
|
17
17
|
<i
|
|
18
18
|
v-if="query && showEnterIcon && showEnterIconOnFocus"
|
|
19
|
-
v-tooltip.bottom="{ content: '
|
|
19
|
+
v-tooltip.bottom="{ content: $t('squirrel.input_search_press_enter_to_search'), delay: { show: 100, hide: 0 } }"
|
|
20
20
|
class="enter absolute bg-no-repeat outline-none"
|
|
21
21
|
:class="enterIconClasses[size]"
|
|
22
22
|
></i>
|
|
23
23
|
<button
|
|
24
24
|
v-if="query"
|
|
25
25
|
role="button"
|
|
26
|
-
aria-label="
|
|
26
|
+
:aria-label="$t('squirrel.input_search_clear_search_input')"
|
|
27
27
|
class="absolute cursor-pointer"
|
|
28
28
|
:class="clearIconClasses[size]"
|
|
29
29
|
@click="clearSearch"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div v-if="!loading && pages.length > 1" class="flex text-p-gray-40">
|
|
4
4
|
<div
|
|
5
5
|
:class="[BTN_CLASS, Number(modelValue) <= 1 ? ARROW_INACTIVE_CLASS : ARROW_ACTIVE_CLASS]"
|
|
6
|
-
aria-label="
|
|
6
|
+
:aria-label="$t('squirrel.pagination_go_to_previous_page')"
|
|
7
7
|
@click="setPage(Number(modelValue) - 1)"
|
|
8
8
|
>
|
|
9
9
|
<PIcon icon="chevron-left" width="24px" />
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<div
|
|
14
14
|
v-if="page !== DOTS"
|
|
15
15
|
:class="[BTN_CLASS, page === modelValue ? BTN_ACTIVE_CLASS : BTN_INACTIVE_CLASS]"
|
|
16
|
-
:aria-label="
|
|
16
|
+
:aria-label="$t('squirrel.pagination_go_to_page', { page })"
|
|
17
17
|
>
|
|
18
18
|
{{ page }}
|
|
19
19
|
</div>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</div>
|
|
23
23
|
<div
|
|
24
24
|
:class="[BTN_CLASS, modelValue === pageCount ? ARROW_INACTIVE_CLASS : ARROW_ACTIVE_CLASS]"
|
|
25
|
-
aria-label="
|
|
25
|
+
:aria-label="$t('squirrel.pagination_go_to_next_page')"
|
|
26
26
|
@click="setPage(Number(modelValue) + 1)"
|
|
27
27
|
>
|
|
28
28
|
<PIcon icon="chevron-right" width="24px" />
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<PSkeletonLoader v-if="loading" class="h-6 w-56" />
|
|
3
3
|
<div v-else class="text-sm font-medium text-p-gray-40">
|
|
4
|
-
<template v-if="count">
|
|
5
|
-
<slot v-else name="no-results">
|
|
4
|
+
<template v-if="count">{{ $t('squirrel.pagination_info_showing_results', { from, to, count }) }}</template>
|
|
5
|
+
<slot v-else name="no-results">{{ $t('squirrel.pagination_info_no_results_found') }}</slot>
|
|
6
6
|
</div>
|
|
7
7
|
</template>
|
|
8
8
|
|