@pequity/squirrel 8.3.2 → 8.3.4

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/dist/cjs/index.js CHANGED
@@ -166,7 +166,9 @@ const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
166
166
  const file = filesToUpload[i];
167
167
  const fileName = file.name || file.url;
168
168
  if (res.length + files.value.length >= props.maxNumberOfFiles) {
169
- toast.error(`You can only upload a maximum of ${props.maxNumberOfFiles} ${fileWord.value}.`);
169
+ if (!(props.multiple && props.maxNumberOfFiles === 1)) {
170
+ toast.error(`You can only upload a maximum of ${props.maxNumberOfFiles} ${fileWord.value}.`);
171
+ }
170
172
  break;
171
173
  }
172
174
  if (!fileName) {
@@ -661,7 +663,7 @@ const _sfc_main$3 = /* @__PURE__ */ vue.defineComponent({
661
663
  }
662
664
  });
663
665
  const _hoisted_1$2 = { class: "flex items-center gap-2" };
664
- const _hoisted_2$2 = ["onClick"];
666
+ const _hoisted_2$2 = ["data-state", "onClick"];
665
667
  const _hoisted_3$2 = {
666
668
  key: 0,
667
669
  class: "flex items-center"
@@ -698,6 +700,7 @@ const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
698
700
  }, [
699
701
  vue.createElementVNode("div", {
700
702
  class: vue.normalizeClass(["text-nowrap rounded-full border px-4 py-1 text-sm font-semibold", [stepClasses(step, i), { "cursor-pointer": _ctx.clickable && !step.disabled }]]),
703
+ "data-state": step.value === _ctx.activeStep ? "active" : "inactive",
701
704
  onClick: ($event) => emit("click:step", step, i)
702
705
  }, vue.toDisplayString(step.text || vue.unref(lodashEs.startCase)(String(step.value))), 11, _hoisted_2$2),
703
706
  i < _ctx.steps.length - 1 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_3$2, [
@@ -869,7 +872,7 @@ const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
869
872
  ref: (el) => updateThsRefs(el, i),
870
873
  key: col.id,
871
874
  "data-col-id": col.id
872
- }, col.thAttrs, {
875
+ }, { ref_for: true }, col.thAttrs, {
873
876
  style: col.style,
874
877
  class: "bg-surface"
875
878
  }), [
@@ -885,9 +888,8 @@ const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
885
888
  "show-filter-icon": col.filterable || col.sortable,
886
889
  "tooltip-text": col.tooltip,
887
890
  class: [{ "pl-2": i === 1 && _ctx.isFirstColFixed, "pr-2": i === _ctx.cols.length && _ctx.isLastColFixed }, "grow"],
888
- "text-color": headerCellTextColor(col),
889
- ref_for: true
890
- }, col.headerCellAttrs, {
891
+ "text-color": headerCellTextColor(col)
892
+ }, { ref_for: true }, col.headerCellAttrs, {
891
893
  onClickFilterIcon: ($event) => _ctx.$emit("click-filter-icon", $event, col)
892
894
  }), null, 16, ["text", "filter-active", "show-filter-icon", "tooltip-text", "class", "text-color", "onClickFilterIcon"])
893
895
  ]),
package/dist/es/index.js CHANGED
@@ -166,7 +166,9 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
166
166
  const file = filesToUpload[i2];
167
167
  const fileName = file.name || file.url;
168
168
  if (res.length + files.value.length >= props.maxNumberOfFiles) {
169
- toast.error(`You can only upload a maximum of ${props.maxNumberOfFiles} ${fileWord.value}.`);
169
+ if (!(props.multiple && props.maxNumberOfFiles === 1)) {
170
+ toast.error(`You can only upload a maximum of ${props.maxNumberOfFiles} ${fileWord.value}.`);
171
+ }
170
172
  break;
171
173
  }
172
174
  if (!fileName) {
@@ -661,7 +663,7 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
661
663
  }
662
664
  });
663
665
  const _hoisted_1$2 = { class: "flex items-center gap-2" };
664
- const _hoisted_2$2 = ["onClick"];
666
+ const _hoisted_2$2 = ["data-state", "onClick"];
665
667
  const _hoisted_3$2 = {
666
668
  key: 0,
667
669
  class: "flex items-center"
@@ -698,6 +700,7 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
698
700
  }, [
699
701
  createElementVNode("div", {
700
702
  class: normalizeClass(["text-nowrap rounded-full border px-4 py-1 text-sm font-semibold", [stepClasses(step, i2), { "cursor-pointer": _ctx.clickable && !step.disabled }]]),
703
+ "data-state": step.value === _ctx.activeStep ? "active" : "inactive",
701
704
  onClick: ($event) => emit("click:step", step, i2)
702
705
  }, toDisplayString(step.text || unref(startCase)(String(step.value))), 11, _hoisted_2$2),
703
706
  i2 < _ctx.steps.length - 1 ? (openBlock(), createElementBlock("div", _hoisted_3$2, [
@@ -869,7 +872,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
869
872
  ref: (el) => updateThsRefs(el, i2),
870
873
  key: col.id,
871
874
  "data-col-id": col.id
872
- }, col.thAttrs, {
875
+ }, { ref_for: true }, col.thAttrs, {
873
876
  style: col.style,
874
877
  class: "bg-surface"
875
878
  }), [
@@ -885,9 +888,8 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
885
888
  "show-filter-icon": col.filterable || col.sortable,
886
889
  "tooltip-text": col.tooltip,
887
890
  class: [{ "pl-2": i2 === 1 && _ctx.isFirstColFixed, "pr-2": i2 === _ctx.cols.length && _ctx.isLastColFixed }, "grow"],
888
- "text-color": headerCellTextColor(col),
889
- ref_for: true
890
- }, col.headerCellAttrs, {
891
+ "text-color": headerCellTextColor(col)
892
+ }, { ref_for: true }, col.headerCellAttrs, {
891
893
  onClickFilterIcon: ($event) => _ctx.$emit("click-filter-icon", $event, col)
892
894
  }), null, 16, ["text", "filter-active", "show-filter-icon", "tooltip-text", "class", "text-color", "onClickFilterIcon"])
893
895
  ]),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pequity/squirrel",
3
3
  "description": "Squirrel component library",
4
- "version": "8.3.2",
4
+ "version": "8.3.4",
5
5
  "packageManager": "pnpm@10.6.4",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -71,7 +71,7 @@
71
71
  "@types/node": "^22.15.21",
72
72
  "@vitejs/plugin-vue": "^5.2.4",
73
73
  "@vitest/coverage-v8": "^3.1.4",
74
- "@vue/compiler-sfc": "3.5.14",
74
+ "@vue/compiler-sfc": "3.5.15",
75
75
  "@vue/test-utils": "^2.4.6",
76
76
  "@vuepic/vue-datepicker": "11.0.2",
77
77
  "autoprefixer": "^10.4.21",
@@ -91,14 +91,14 @@
91
91
  "resolve-tspaths": "^0.8.23",
92
92
  "rimraf": "^6.0.1",
93
93
  "sass": "^1.89.0",
94
- "semantic-release": "^24.2.4",
94
+ "semantic-release": "^24.2.5",
95
95
  "storybook": "^8.6.14",
96
96
  "svgo": "^3.3.2",
97
97
  "tailwindcss": "^3.4.17",
98
98
  "typescript": "5.8.3",
99
99
  "vite": "^6.3.5",
100
100
  "vitest": "^3.1.4",
101
- "vue": "3.5.14",
101
+ "vue": "3.5.15",
102
102
  "vue-currency-input": "3.2.1",
103
103
  "vue-router": "4.5.1",
104
104
  "vue-toastification": "2.0.0-rc.5",
@@ -1,11 +1,26 @@
1
1
  import PFileUpload from '@squirrel/components/p-file-upload/p-file-upload.vue';
2
+ import { expect } from '@storybook/test';
2
3
  import { createWrapperFor } from '@tests/vitest.helpers';
4
+ import { vi } from 'vitest';
5
+
6
+ // Mocking the toastification library
7
+ const toastError = vi.fn();
8
+
9
+ vi.mock('vue-toastification', () => {
10
+ return {
11
+ useToast: () => {
12
+ return {
13
+ error: toastError,
14
+ };
15
+ },
16
+ };
17
+ });
3
18
 
4
- const createFile = () => {
5
- const file = new File(['filename'], 'filename.txt', {
19
+ const createFile = (fileName = 'filename') => {
20
+ const file = new File([fileName], `${fileName}.txt`, {
6
21
  type: 'text/plain',
7
- name: 'filename.txt',
8
- url: 'https://www.pequity.com/filename.txt',
22
+ name: `${fileName}.txt`,
23
+ url: `https://www.pequity.com/${fileName}.txt`,
9
24
  });
10
25
 
11
26
  return file;
@@ -131,4 +146,46 @@ describe('PFileUpload.vue', () => {
131
146
 
132
147
  expect(wrapper.findAll('div.truncate').length).toBe(3);
133
148
  });
149
+
150
+ it('shows a toast when maxNumberOfFiles is exceeded', async () => {
151
+ const file1 = createFile('file1');
152
+ const file2 = createFile('file2');
153
+
154
+ const wrapper = createWrapperFor({
155
+ components: { PFileUpload },
156
+ data() {
157
+ return {
158
+ files: [file1, file2],
159
+ };
160
+ },
161
+ template: `
162
+ <PFileUpload v-model="files" :multiple="true" :max-number-of-files="2" />
163
+ `,
164
+ });
165
+
166
+ expect(wrapper.find('div[title="file1.txt"]').exists()).toBe(true);
167
+ expect(wrapper.find('div[title="file2.txt"]').exists()).toBe(true);
168
+
169
+ expect(toastError).toHaveBeenCalledWith('You can only upload a maximum of 2 files.');
170
+ });
171
+
172
+ it('does not show a toast when multiple is true and maxNumberOfFiles is 1', async () => {
173
+ const file1 = createFile('file1');
174
+
175
+ const wrapper = createWrapperFor({
176
+ components: { PFileUpload },
177
+ data() {
178
+ return {
179
+ files: [file1],
180
+ };
181
+ },
182
+ template: `
183
+ <PFileUpload v-model="files" :multiple="true" :max-number-of-files="1" />
184
+ `,
185
+ });
186
+
187
+ expect(wrapper.find('div[title="file1.txt"]').exists()).toBe(true);
188
+
189
+ expect(toastError).not.toHaveBeenCalled();
190
+ });
134
191
  });
@@ -166,7 +166,11 @@ const validateFiles = (filesToUpload: FileUploadFile[]) => {
166
166
 
167
167
  // Bail if we reached the max number of files
168
168
  if (res.length + files.value.length >= props.maxNumberOfFiles) {
169
- toast.error(`You can only upload a maximum of ${props.maxNumberOfFiles} ${fileWord.value}.`);
169
+ // Show a toast - except for the contradictory config (multiple: true with maxNumberOfFiles: 1)
170
+ if (!(props.multiple && props.maxNumberOfFiles === 1)) {
171
+ toast.error(`You can only upload a maximum of ${props.maxNumberOfFiles} ${fileWord.value}.`);
172
+ }
173
+
170
174
  break;
171
175
  }
172
176
 
@@ -2,15 +2,15 @@
2
2
 
3
3
  exports[`PSteps.vue > render tests > renders correctly 1`] = `
4
4
  "<div class="flex items-center gap-2">
5
- <div class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold border-p-gray-30 text-p-gray-30">First</div>
5
+ <div class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold border-p-gray-30 text-p-gray-30" data-state="inactive">First</div>
6
6
  <div class="flex items-center">
7
7
  <iconify-icon icon="material-symbols:arrow-right-alt-rounded" class="text-p-gray-30"></iconify-icon>
8
8
  </div>
9
- <div class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold border-p-gray-30 text-p-gray-30">Second</div>
9
+ <div class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold border-p-gray-30 text-p-gray-30" data-state="inactive">Second</div>
10
10
  <div class="flex items-center">
11
11
  <iconify-icon icon="material-symbols:arrow-right-alt-rounded" class="text-p-gray-30"></iconify-icon>
12
12
  </div>
13
- <div class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold border-p-gray-30 text-p-gray-30">Third</div>
13
+ <div class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold border-p-gray-30 text-p-gray-30" data-state="inactive">Third</div>
14
14
  <!--v-if-->
15
15
  </div>"
16
16
  `;
@@ -37,7 +37,7 @@ describe('PSteps.vue', () => {
37
37
  expect(stepElements.length).toBe(createSteps().length);
38
38
  });
39
39
 
40
- it('applies correct classes for current step', () => {
40
+ it('applies correct classes and data-state for current step', () => {
41
41
  const wrapper = createWrapper({
42
42
  activeStep: 'second',
43
43
  });
@@ -48,17 +48,20 @@ describe('PSteps.vue', () => {
48
48
  expect(stepElements[0].classes()).toContain('text-nowrap');
49
49
  expect(stepElements[0].classes()).toContain('text-p-blue-50');
50
50
  expect(stepElements[0].classes()).toContain('border-p-blue-50');
51
+ expect(stepElements[0].attributes('data-state')).toBe('inactive');
51
52
 
52
53
  // Second step should be current (blue background)
53
54
  expect(stepElements[1].classes()).toContain('text-nowrap');
54
55
  expect(stepElements[1].classes()).toContain('bg-p-blue-50');
55
56
  expect(stepElements[1].classes()).toContain('text-surface');
56
57
  expect(stepElements[1].classes()).toContain('border-p-blue-50');
58
+ expect(stepElements[1].attributes('data-state')).toBe('active');
57
59
 
58
60
  // Third step should be upcoming (gray)
59
61
  expect(stepElements[2].classes()).toContain('text-nowrap');
60
62
  expect(stepElements[2].classes()).toContain('text-p-gray-30');
61
63
  expect(stepElements[2].classes()).toContain('border-p-gray-30');
64
+ expect(stepElements[2].attributes('data-state')).toBe('inactive');
62
65
  });
63
66
 
64
67
  it('displays step titles', () => {
@@ -4,6 +4,7 @@
4
4
  <div
5
5
  class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold"
6
6
  :class="[stepClasses(step, i), { 'cursor-pointer': clickable && !step.disabled }]"
7
+ :data-state="step.value === activeStep ? 'active' : 'inactive'"
7
8
  @click="emit('click:step', step, i)"
8
9
  >
9
10
  {{ step.text || startCase(String(step.value)) }}